1
0

rym.lisp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  1. (in-package :cl-user)
  2. (defpackage chad-music.rym
  3. (:use :cl #:alexandria #:chad-music.utils)
  4. (:export #:custom-chart
  5. #:load-all))
  6. (in-package :chad-music.rym)
  7. (defvar *cookies* (cl-cookie:make-cookie-jar) "rateyourmusic.com valid cookies")
  8. (defparameter +rym-charts-url+ "https://rateyourmusic.com/customchart")
  9. (defparameter +ua+ "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:58.0) Gecko/20100101 Firefox/58.0")
  10. (defun custom-chart (&key
  11. (chart-type :top)
  12. (countries)
  13. (genre-include t)
  14. (genres)
  15. (include :both)
  16. (include-child-genres t)
  17. (origin-countries nil)
  18. (page 1)
  19. (type :album)
  20. (year :alltime))
  21. (let* ((parameters
  22. `(("chart_type" . ,(dekeyify chart-type))
  23. ("countries" . ,(or countries ""))
  24. ("genre_include" . ,(if genre-include "1" "0"))
  25. ("genres" . ,(format nil "~{~A~^,~}" genres))
  26. ("include" . ,(dekeyify include))
  27. ("include_child_genres" . ,(if include-child-genres "1" "0"))
  28. ("include_child_genres_chk" . ,(if include-child-genres "1" ""))
  29. ("origin_countries" . ,(format nil "~{~A~^,~}" origin-countries))
  30. ("page" . ,(write-to-string page))
  31. ("type" . ,(dekeyify type))
  32. ("year" . ,(etypecase year
  33. (integer (write-to-string year))
  34. (symbol (dekeyify year))
  35. (string year)))))
  36. (dom (xml-request +rym-charts-url+
  37. :parameters parameters
  38. :cookie-jar *cookies*
  39. :user-agent +ua+)))
  40. (loop for tr across (clss:select "#content table tr" dom)
  41. when (select-text tr ".artist")
  42. collect
  43. (list :chart-year year
  44. :chart-pos (parse-integer (select-text tr ".ooookiig"))
  45. :artist (select-text tr ".artist")
  46. :album (select-text tr ".album")
  47. :year (let ((text (select-text tr ".chart_year")))
  48. (when text
  49. (parse-integer (subseq text 1 (1- (length text))) :junk-allowed t)))
  50. :genres (map 'list #'select-text (clss:select ".genre" tr))
  51. :stats (map 'list #'select-text (clss:select ".chart_stats b" tr))))))
  52. (defvar *dull-genres* '("television music" "video game music" "film score" "film soundtrack"))
  53. (defun scrape-charts (filename &key (max-year 2017) (min-year 1950) (max-page 3) (min-page 1))
  54. (with-output-to-file (s filename :if-exists :append)
  55. (loop for page from min-page upto max-page
  56. do (loop for year from max-year downto min-year
  57. do (progn
  58. (loop for item in (custom-chart :genre-include nil
  59. :genres *dull-genres*
  60. :year year
  61. :page page)
  62. do (print item s))
  63. (print (cons year page))
  64. (sleep 5))))))
  65. (defun load-all (filename &optional if-does-not-exist)
  66. (with-input-from-file (s filename :if-does-not-exist if-does-not-exist)
  67. (loop for value = (read s nil)
  68. while value
  69. collect value)))
  70. (defun range (field min &optional (max 3000))
  71. (lambda (c)
  72. (let ((value (getf c field)))
  73. (<= min value max))))
  74. (defun getter (field)
  75. (lambda (c)
  76. (getf c field)))
  77. (defstruct ordered artist albums genres score)
  78. (defun ordered-print (instance &optional stream)
  79. (with-slots (artist albums genres score) instance
  80. (format stream "Artist: ~A~%Genres: ~{~A~^, ~}~%Albums: ~{~A~^, ~}~%Score: ~A~%~%"
  81. artist genres albums score)))
  82. (defun order-artists (charts)
  83. (let ((ht (make-hash-table :test 'equal)))
  84. (loop for c in charts
  85. for artist = (getf c :artist)
  86. do (multiple-value-bind (item foundp)
  87. (gethash artist ht)
  88. (unless foundp
  89. (setf item (make-ordered :artist artist :score 0)
  90. (gethash artist ht) item))
  91. (pushnew (getf c :album) (ordered-albums item) :test 'equal)
  92. (dolist (genre (getf c :genres))
  93. (pushnew genre (ordered-genres item) :test 'equal))
  94. (incf (ordered-score item)
  95. (ceiling (- 121 (getf c :chart-pos)) 12))))
  96. (sort (hash-table-values ht) #'> :key 'ordered-score)))
  97. (defun set-cookie (name value path domain)
  98. (cl-cookie:merge-cookies
  99. *cookies*
  100. (list
  101. (cl-cookie:make-cookie :name name
  102. :value value
  103. :path path
  104. :domain domain))))
  105. (defun infohash-magnet (info-hash)
  106. (concatenate 'string "magnet:?xt=urn:btih:" info-hash))
  107. (defvar *deluge-api* "http://10.8.77.1:8112/json")
  108. (defvar *deluge-password* "deluge")
  109. (defvar *deluge-request-id* 1)
  110. (defun deluge-request (method &optional params is-auth)
  111. (let* ((content (trivial-utf-8:string-to-utf-8-bytes
  112. (jojo:to-json `(:|id| ,(incf *deluge-request-id*)
  113. :|method| ,method
  114. :|params| ,params))))
  115. (response (json-request *deluge-api* :method :post
  116. :content content
  117. :cookie-jar *cookies*))
  118. (error-message (getf (getf response :|error|) :|message|)))
  119. (if error-message
  120. (if (and (null is-auth)
  121. (equal error-message "Not authenticated"))
  122. (progn
  123. (deluge-auth)
  124. (deluge-request method params t)) ;; Retry original request
  125. (error error-message))
  126. (getf response :|result|))))
  127. (defun deluge-auth ()
  128. (deluge-request "auth.login" (list *deluge-password*) t))
  129. (defun deluge-add-info-hash (info-hashes)
  130. (unless (listp info-hashes)
  131. (setf info-hashes (list info-hashes)))
  132. (let ((torrents (loop for ih in info-hashes
  133. collect (list :|path| (infohash-magnet ih)
  134. :|options| '(:|add_paused| :true)))))
  135. (deluge-request "web.add_torrents" (list torrents))))
  136. (defun deluge-get-torrents ()
  137. (deluge-request "core.get_session_state"))
  138. (defun deluge-get-torrents-status (filter fields)
  139. (deluge-request "core.get_torrents_status" (list filter fields)))
  140. (defparameter +deluge-default-status-fields+ '("files" "file_priorities" "save_path"))
  141. (defun deluge-get-torrent-files (info-hash)
  142. (let* ((status (deluge-request "core.get_torrent_status" (list info-hash +deluge-default-status-fields+)))
  143. (save-path (getf status :|save_path|)))
  144. (loop for file in (getf status :|files|)
  145. for prio in (getf status :|file_priorities|)
  146. do (setf (getf file :|prio|) prio
  147. (getf file :|path|) (format nil "~A/~A" save-path (getf file :|path|)))
  148. collect file)))
  149. (defun deluge-get-seeding-torrents ()
  150. (loop for (torrent state) on (deluge-get-torrents-status '(:|state| "Seeding") '("name")) by #'cddr
  151. collect (cons (string torrent) (getf state :|name|))))
  152. (defun deluge-pause-torrents (&rest torrents)
  153. (deluge-request "core.pause_torrent" (list torrents)))
  154. (defun raw-pathname (pathspec)
  155. (values (cl-ppcre:regex-replace-all "\\[" pathspec "\\\\[")))
  156. (defun deluge-delete-skipped (torrent &optional dry)
  157. (let* ((status (deluge-request "core.get_torrent_status" (list torrent +deluge-default-status-fields+)))
  158. (save-path (getf status :|save_path|)))
  159. (loop for file in (getf status :|files|)
  160. for prio in (getf status :|file_priorities|)
  161. for path = (pathname (raw-pathname (format nil "~A/~A" save-path (getf file :|path|))))
  162. when (zerop prio)
  163. when (probe-file path)
  164. do (if dry (format t "Deleting '~A'~%" path)
  165. (uiop:delete-file-if-exists path)))))
  166. (defun deluge-process-downloaded (&optional dry)
  167. (loop for (ih . name) in (deluge-get-seeding-torrents)
  168. do (progn
  169. (format t "Processing '~a'~%" name)
  170. (deluge-pause-torrents ih)
  171. (deluge-delete-skipped ih dry))))
  172. (defun deluge-save-rm-list (filename)
  173. (let ((torrents (deluge-get-torrents)))
  174. (with-output-to-file (s filename)
  175. (dolist (torrent torrents)
  176. (let* ((status (deluge-request "core.get_torrent_status" (list torrent +deluge-default-status-fields+)))
  177. (save-path (getf status :|save_path|)))
  178. (loop
  179. for file in (getf status :|files|)
  180. for prio in (getf status :|file_priorities|)
  181. when (zerop prio)
  182. do (format s "~A/~A~%" save-path (getf file :|path|))))))))
  183. (defparameter +rutracker-search+ "https://rutracker.org/forum/tracker.php")
  184. (defun search-artist (artist &optional (suffix "дискография"))
  185. (http-request +rutracker-search+ :parameters `(("nm" . ,(format nil "~A~@[ ~A~]" artist suffix)))))
  186. ;;; rutracker sqlite dump
  187. (defvar *rt-db* nil)
  188. (defun rt-connect (path)
  189. (setf *rt-db* (sqlite:connect path)))
  190. (defun rt-select (sql &rest parameters)
  191. (apply #'sqlite:execute-to-list *rt-db* sql parameters))
  192. (defun rt-artist (artist)
  193. (rt-select "select title, hash_info from torrent where title like ? and title like '%искография%' and not title like '%lossless%' order by size_b desc limit 30" (format nil "%~A%" artist)))
  194. (defun save-rt (filename artists)
  195. (with-output-to-file (s filename :if-exists :append :if-does-not-exist :create)
  196. (loop for artist in artists
  197. do (print artist)
  198. do (loop for result in (rt-artist artist)
  199. do (print (cons artist result) s)))))
  200. (defun select-torrent (torrents)
  201. (format t " Select torrent:~%~{~A) ~A~%~}0) Skip~%s) Stop~%~%Enter: "
  202. (loop for i from 1 for tor in torrents append (list i (car tor))))
  203. (let ((answer (read)))
  204. (typecase answer
  205. (integer (if (zerop answer) nil
  206. (or (nth (1- answer) torrents)
  207. (select-torrent torrents))))
  208. (symbol :stop))))
  209. (defun rt-find-artists (charts-filename found-filename)
  210. (let ((found (load-all found-filename :create))
  211. (ordered (order-artists (load-all charts-filename))))
  212. (with-output-to-file (s found-filename :if-exists :append :if-does-not-exist :create)
  213. (dolist (item ordered)
  214. (let ((artist (ordered-artist item)))
  215. (unless (member artist found :test 'equal :key 'car)
  216. (ordered-print item t)
  217. (let ((torrents (rt-artist artist)))
  218. (if torrents
  219. (let ((torrent (select-torrent torrents)))
  220. (when (eq torrent :stop)
  221. (return))
  222. (when torrent (deluge-add-info-hash (cdr torrent)))
  223. (print (cons artist torrent) s))
  224. (print (cons artist nil) s)))))))))