clojure
notes
- https://aphyr.com/posts/353-rewriting-the-technical-interview
lein trampolineruns a separate jvm
webdriver form submit with cli tools example
- Uses jvm shutdown hooks instead of implementing
AutoClosablefor headless browser
(ns scape.core
(:gen-class)
(:require [etaoin.api :as api]
[clojure.tools.cli :refer [parse-opts]]
[clojure.tools.logging :as log])
(:import [java.util.concurrent TimeUnit]))
(defonce cli-options
[["-t" "--type TYPE" "type of router"
:default "arris"]
["-l" "--login LOGIN" "login username"
:default "admin"]
["-p" "--pass PASS" "login password"
:default "password"]
["-s" "--sleep SLEEP" "Interval in seconds for refreshing"
:default 180
:parse-fn #(Integer/parseInt %)]
["-u" "--url URL" "URL of website to use"
:default "http://192.168.0.1/"
:parse-fn #(.toString (java.net.URL. %))]])
(defn create-driver []
(log/info "Creating driver")
(let [driver (api/firefox-headless)]
(log/info "adding shutdown hook")
(.addShutdownHook (Runtime/getRuntime)
(Thread. (fn []
(println "Shutting down driver")
(api/quit driver)
(println "Quit driver"))))
(log/info "added shutdown hook")
driver))
(defn reset-linksys [driver url login pass]
(log/info "Going to url: " url)
(api/go driver url)
(log/info "Getting username box")
(api/wait-visible driver [{:tag :input :id :userName}])
(api/fill driver {:tag :input :id :userName} login)
(log/info "Entering password")
(api/wait-visible driver [{:tag :input :id :pcPassword}])
(api/fill driver {:tag :input :id :pcPassword} pass)
(log/info "Clicking login")
(api/wait-visible driver [{:tag :label :id :loginBtn}])
(api/click driver {:tag :label :id :loginBtn})
(log/info "Switching to left frame")
(api/wait-visible driver [{:tag :frame :name :bottomLeftFrame}])
(api/switch-frame driver {:tag :frame :name :bottomLeftFrame})
(log/info "Clicking system button")
(api/wait-visible driver [{:tag :ol :id :ol62}])
(api/click driver {:tag :ol :id :ol62})
(log/info "Clicking reboot menu button")
(api/wait-visible driver [{:tag :a :id :a68}])
(api/click driver {:tag :a :id :a68})
(log/info "Switching to top frame")
(api/switch-frame-top driver)
(log/info "Switching to main frame")
(api/wait-visible driver [{:tag :frame :name :mainFrame}])
(api/switch-frame driver {:tag :frame :name :mainFrame})
(log/info "Clicking reboot button")
(api/wait-visible driver [{:tag :input :id :reboot}])
(api/click driver {:tag :input :id :reboot})
(log/info "Clicked reboot button")
(log/info "waiting on alert")
(api/wait-has-alert driver)
(log/info "accepting alert")
(api/accept-alert driver)
(log/info "accepted alert"))
(defn reset-arris [driver url login pass]
(log/info "Going to url: " url)
(api/go driver url)
(log/info "Entering username")
(api/wait-visible driver [{:tag :input :id :UserName}])
(api/fill driver {:tag :input :id :UserName} login)
(log/info "Entering password")
(api/wait-visible driver [{:tag :input :id :Password}])
(api/fill driver {:tag :input :id :Password} pass)
(log/info "Clicking login")
(api/wait-visible driver [{:tag :input :class :submitBtn}])
(api/click driver {:tag :input :class :submitBtn})
(log/info "clicked button")
(log/info "waiting on utilites menu")
(api/wait-visible driver [{:tag :a :href :?util_status}])
(api/go driver (.concat url "/?util_restart"));"/?util_status"))
(log/info "redirected menu")
(log/info "waiting on reboot button")
(api/wait-visible driver [{:tag :input :class :submitBtn}])
(log/info "Clicking the reboot button")
(api/click driver {:tag :input :class :submitBtn})
(log/info "waiting on alert")
(api/wait-has-alert driver)
(log/info "accepting alert")
(api/accept-alert driver)
(log/info "accepted alert"))
(defn -main
[& args]
(log/info "Ensure Geckodriver and Firefox with headless support are available.") ;; ten attempts 2 hours
(let [{:keys [options arguments summary errors]} (parse-opts args cli-options)
driver (create-driver)]
(log/info "arguments: " arguments)
(log/info "using config values: " options)
(if (.equals "arris" (:type options))
(reset-arris driver (:url options) (:login options) (:pass options))
(reset-linksys driver (:url options) (:login options) (:pass options)))
(log/info "Done sleeping for: " (:sleep options) " seconds")
(.sleep TimeUnit/SECONDS (:sleep options))
(log/info "Done")))
(defn goto-site [driver url]
(log/info "Going to url: " url)
(api/go driver url) ;; get iframe of menu from url
(api/wait-visible driver [{:tag :iframe :id :jane-menu}])
(log/info "Getting site iframe")
(let [site (str (api/get-element-property driver {:tag :iframe :id :jane-menu} :src) "refinementList%5Broot_types%5D%5B0%5D=flower")]
(log/info "Going to site: " site)
(api/go driver site)))
(defn get-max [driver]
(log/info "Getting max %")
(api/wait-visible driver [{:fn/has-text "Max:"}])
(let [max (atom (Integer/parseInt (re-find #"\d+" (api/get-element-text driver [{:fn/has-text "Max:"}]))))]
(log/info "Max is: " @max)
max))
(defn scrape-site
[& args]
(log/info "Ensure Geckodriver and Firefox with headless support are available.")
(let [{:keys [options arguments summary errors]} (parse-opts args cli-options)
driver (create-driver)
max (do
(goto-site driver (:url options))
(get-max driver))]
(while (< @max (:threshold options))
(try
(log/info "Sleeping for: " (:sleep options) " mins")
(.sleep TimeUnit/MINUTES (:sleep options))
(log/info "Refreshing...")
(api/refresh)
(reset! max (get-max driver))
(catch Throwable e (str " error : " (.getMessage e))))))
(log/info "Done"))
javafx example with graalvm
(ns jam.start (:import (javafx.scene Scene) (javafx.application Application) (javafx.application Platform) (javafx.scene.control Button) (javafx.stage Stage)) (:gen-class :extends javafx.application.Application :name jam.start :main true)) (defn -start [this ^Stage stage] (println "START") (.setScene stage (Scene. (Button. "My Button") 200 100)) (.show stage) (println "started")) (defn -stop [this] (println "Exiting")) (defn -main [& args] (println "Starting") (System/setProperty "prism.order" "sw") (System/setProperty "prism.text" "t2k") (System/setProperty "prism.nativepisces" "false") (System/setProperty "prism.allowhidpi" "false") (System/setProperty "prism.vsync" "false") (Application/launch jam.start args) (println "Done"))
- Run with
mvn gluonfx:build gluonfx:nativerun- needs at least java 17 GraalVM CE 22.1.0
- WIP: web profile breaks on clojure ex: mvn gluonfx:build gluonfx:nativerun -Pweb android profile breaks on clojure ex: mvn gluonfx:build gluonfx:nativerun -Pandroid
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<name>mvnclj</name>
<groupId>com.mycompany</groupId>
<artifactId>my-app</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>clojure</packaging>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<javafx.version>18.0.1</javafx.version>
<client.plugin.version>1.0.14</client.plugin.version>
<clojure.mainClass>jam.start</clojure.mainClass>
<mainClassName>jam.start</mainClassName>
</properties>
<build>
<plugins>
<plugin>
<groupId>com.theoryinpractise</groupId>
<artifactId>clojure-maven-plugin</artifactId>
<version>1.8.4</version>
<extensions>true</extensions>
<configuration>
<mainClass>${mainClassName}</mainClass>
</configuration>
</plugin>
<plugin>
<groupId>org.openjfx</groupId>
<artifactId>javafx-maven-plugin</artifactId>
<version>0.0.8</version>
<configuration>
<mainClass>${mainClassName}</mainClass>
</configuration>
</plugin>
<plugin>
<groupId>com.gluonhq</groupId>
<artifactId>gluonfx-maven-plugin</artifactId>
<version>${client.plugin.version}</version>
<configuration>
<mainClass>${mainClassName}</mainClass>
<nativeImageArgs>
<list>--no-fallback</list>
<list>-J-Dclojure.compiler.direct-linking=true</list>
<list>-J-Dclojure.spec.skip-macros=true</list>
</nativeImageArgs>
<verbose>true</verbose>
<enableSWRendering>true</enableSWRendering>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.clojure</groupId>
<artifactId>clojure</artifactId>
<version>1.11.1</version>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-controls</artifactId>
<version>${javafx.version}</version>
</dependency>
<dependency>
<groupId>com.github.clj-easy</groupId>
<artifactId>graal-build-time</artifactId>
<version>0.1.4</version>
</dependency>
</dependencies>
<repositories>
<repository>
<id>gluon</id>
<name>gluon</name>
<url>https://nexus.gluonhq.com/nexus/content/repositories/releases</url>
</repository>
<repository>
<id>sona</id>
<name>sona</name>
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
</repository>
<repository>
<id>clojars.org</id>
<name>clojars</name>
<url>https://repo.clojars.org</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>gluon-releases</id>
<url>https://nexus.gluonhq.com/nexus/content/repositories/releases/</url>
</pluginRepository>
</pluginRepositories>
<profiles>
<profile>
<id>desktop</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<gluonfx.target>host</gluonfx.target>
</properties>
</profile>
<profile>
<id>android</id>
<properties>
<gluonfx.target>android</gluonfx.target>
</properties>
</profile>
<profile>
<id>ios</id>
<properties>
<gluonfx.target>ios</gluonfx.target>
</properties>
</profile>
<profile>
<id>web</id>
<properties>
<gluonfx.target>web</gluonfx.target>
</properties>
</profile>
</profiles>
</project>