Advertisement
Guest User

Untitled

a guest
May 14th, 2019
87
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. (ns goodreads.core
  2.   (:gen-class)
  3.   (:require [clojure.tools.cli :as cli]
  4.             [clj-http.client :as client]
  5.             [clojure.xml :as xml]
  6.             [clojure.data.zip.xml :as zx]
  7.             [clojure.zip :as zip]
  8.             [clojure.string :as str]
  9.             [manifold.deferred :as d]))
  10.  
  11. ; key: U72x1vCYKt4nFpQLhJ3g
  12. ; secret: tBF5p5i2aIkvNo3GHOwj8DoVty0ZvtO1iv8R7V94PM
  13.  
  14. (defn goodreads-get-books [token user-id]
  15.   (client/get (str "https://www.goodreads.com/review/list?key=" token "&v=2&id=" user-id)))
  16.  
  17. (defn goodreads-book-info [token book-id]
  18.   (client/get (str "https://www.goodreads.com/book/show/" book-id ".XML?key=" token)))
  19.  
  20. (defn zip-str [s]
  21.   (zip/xml-zip
  22.    (xml/parse (java.io.ByteArrayInputStream. (.getBytes s "UTF-8")))))
  23.  
  24. (defn book-status [review]
  25.   (let [id-tag (zx/xml1-> review :book :id)
  26.         shelf-tag (zx/xml1-> review :shelves :shelf)]
  27.     {:id (zx/text id-tag)
  28.      :status (keyword (zx/attr shelf-tag :name))}))
  29.  
  30. (defn read-book [book]
  31.   (let [book-xml1-> (partial zx/xml1-> book)
  32.         id-tag (book-xml1-> :id)
  33.         title-tag (zx/xml1-> book :title)
  34.         link-tag (zx/xml1-> book :link)
  35.         authors-name-tags (zx/xml-> book :authors :author :name)
  36.         average_rating-tag (zx/xml1-> book :average_rating)]
  37.     {:id (zx/text id-tag)
  38.      :title (zx/text title-tag)
  39.      :link (zx/text link-tag)
  40.      :author-names (map zx/text authors-name-tags)
  41.      :average-rating (bigdec (zx/text average_rating-tag))}))
  42.  
  43. (defn goodread-similar-book [token id]
  44.   (let [book-zip (zip-str (:body (goodreads-book-info token id)))]
  45.     (map read-book (zx/xml-> book-zip :book :similar_books :book))))
  46.  
  47. (defn user-books-provider [token id]
  48.   (let [books-zip (zip-str (:body (goodreads-get-books token id)))]
  49.     (map book-status (zx/xml-> books-zip :reviews :review))))
  50.  
  51. (defn similar-provider [token book-ids]
  52.   (flatten (map #(goodread-similar-book token %) book-ids)))
  53.  
  54. (defn recomendations
  55.   [{:keys [token user-id]}]
  56.   (let [user-books (user-books-provider token user-id)
  57.         user-readed-book-ids (->> user-books
  58.                                   (filter #(= (:status %) :read))
  59.                                   (map :id))
  60.         not-readed (fn [{id :id}] (not-any? #(= % id) user-readed-book-ids))]
  61.     (d/success-deferred (->> user-books
  62.                              (map :id)
  63.                              (similar-provider token)
  64.                              (filter not-readed)
  65.                              (sort-by :average-rating >)
  66.                              (take 100)))))
  67.  
  68. ;; TODO: this implementation is pretty useless :(
  69. (defn build-recommentations [config]
  70.   (d/success-deferred
  71.    [{:title "My Side of the Mountain (Mountain, #1)"
  72.      :authors [{:name "Jean Craighead George"}]
  73.      :link "https://www.goodreads.com/book/show/41667.My_Side_of_the_Mountain"}
  74.     {:title "Incident at Hawk's Hill"
  75.      :authors [{:name "Allan W. Eckert"}]
  76.      :link "https://www.goodreads.com/book/show/438131.Incident_at_Hawk_s_Hill"}
  77.     {:title "The Black Pearl"
  78.      :authors [{:name "Scott O'Dell"}]
  79.      :link "https://www.goodreads.com/book/show/124245.The_Black_Pearl"}]))
  80.  
  81. (def cli-options [["-t"
  82.                    "--timeout-ms"
  83.                    "Wait before finished"
  84.                    :default 5000
  85.                    :parse-fn #(Integer/parseInt %)]
  86.                   ["-n"
  87.                    "--number-books"
  88.                    "How many books do you want to recommend"
  89.                    :default 10
  90.                    :parse-fn #(Integer/parseInt %)]
  91.                   ["-h" "--help"]])
  92.  
  93. (defn book->str [{:keys [title link author-names]}]
  94.   (format "\"%s\" by %s\nMore: %s"
  95.           title
  96.           (str/join ", " author-names)
  97.           link))
  98.  
  99. (defn -main [& args]
  100.   (let [{:keys [options errors summary]} (cli/parse-opts args cli-options)]
  101.     (cond
  102.       (contains? options :help) (do (println summary) (System/exit 0))
  103.       (some? errors) (do (println errors) (System/exit 1))
  104.       (empty? args) (do (println "Please, specify user's token") (System/exit 1))
  105.       :else (let [[token user-id] args
  106.                   config {:token token :user-id user-id}
  107.                   books (-> (recomendations config)
  108.                             (d/timeout! (:timeout-ms options) ::timeout)
  109.                             deref)]
  110.               (cond
  111.                 (= ::timeout books) (println "Not enough time :(")
  112.                 (empty? books) (println "Nothing found, leave me alone :(")
  113.                 :else (doseq [[i book] (map-indexed vector books)]
  114.                         (println (str "#" (inc i)))
  115.                         (println (book->str book))
  116.                         (println)))))))
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement