You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

132 lines
6.7 KiB
Common Lisp

;;;; seanut.lisp
(in-package #:seanut)
(defun prompt-and-download (domain auth root opts)
"prompt the user with y-or-n-p and download ITEM"
(labels ((fetch-user-id ()
(gethash "Id" (json-request (format-url domain "Users/Me")
:auth auth)))
(generate-filename (item)
(make-pathname :name
(str:replace-all "/" "-"
(format nil "~A.~A"
(gethash "Name" item)
(if (string= (gethash "Type" item) "Season")
(subseq (gethash "Container" item) 0
(search "," (gethash "Container" item)))
(pathname-type (gethash "Path" item)))))))
(generate-root-name (&optional add-trailing)
(format nil "~A (~A)~@[ Season ~A~]~@[/~]"
(gethash "Name" root)
(gethash "ProductionYear" root)
(getf opts :season)
add-trailing))
(create-directory (dir)
(ensure-directories-exist (uiop:ensure-directory-pathname (make-pathname :name dir))))
(download-item-or-children (item)
;; PARENTS is a list of all parent names with FIRST being
;; the oldest grandparent (for building complete download path)
(let ((children (gethash "Items"
(if (or (string= (gethash "Type" item) "Season")
(and (string= (gethash "Type" item) "Series")
(getf opts :season)))
(json-request (format-url domain "Shows/~A/Episodes?fields=Path~@[&seasonId=~A~]~@[&season=~A~]"
(or (gethash "SeriesId" item)
(gethash "Id" item))
(unless (getf opts :season)
(gethash "Id" item))
(getf opts :season))
:auth auth)
(json-request (format-url domain "Items?userId=~A&fields=Path,ChildCount&parentId=~A"
(fetch-user-id)
(gethash "Id" item))
:auth auth)))))
(loop :for child :across children
:if (zerop (gethash "ChildCount" child 0))
:do
;; download single file
(when (getf opts :verbose)
(format t "Downloading ~A~%" (generate-filename child)))
(download-media (generate-filename child)
(format-url domain "Items/~A/Download"
(gethash "Id" child))
auth)
:else
:do
;; if the item has children we need to download them.
;; to accomplish this we get the list of children
;; and loop over them, recursing for each one
;; ensure that a directory exists for Parent
;; then recurse with children
(uiop:with-current-directory ((create-directory (gethash "Name" child)))
(download-item-or-children child))))))
(when (or (getf opts :assume-yes)
(y-or-n-p "Download \"~A\"" (generate-root-name)))
;; CD into our output directory
(uiop:with-current-directory ((getf opts :output #P"./"))
;; create our folder for our current download
;; then CD into it, and start downloading
(uiop:with-current-directory ((create-directory (generate-root-name 'trailing-slash)))
(download-item-or-children root))))))
(defun main ()
"binary entry point"
(handle-user-abort
(multiple-value-bind (opts args) (get-opts)
(when (or (getf opts :help)
(and (every #'null args)
(every #'null opts)))
(opts:describe :usage-of "seanut"
:args "DOMAIN MEDIA-NAME")
(uiop:quit 0))
(when (getf opts :version)
(quit-with-message 0 "seanut v~A" (seanut-version)))
(unless (or (and (getf opts :username)
(getf opts :password))
(getf opts :quick-connect-p)
(getf opts :token))
(quit-with-message 1 "please provide an access token, username & password, or use quick connect"))
(unless (getf opts :media-type)
(quit-with-message 1 "Please specify media type to download.~%~A ~{~A~^, ~}"
"Supported media types are:"
*valid-media-types*))
(when (some #'null args)
(quit-with-message 1 "domain and/or media name not provided"))
(destructuring-bind (domain search-term) args
(let* ((authorization (or (and (getf opts :token) (generate-authorization (getf opts :token)))
(generate-authorization (get-access-token domain opts))))
(results (run-search-query domain authorization
(getf opts :media-type)
(url-encode search-term))))
(if (< 0 (length results))
;; FIXME: for some reason this access token is not "valid" enough to get
;; certain info? when we run the parentID search it craps out on us?
;; maybe its not something wrong with the token, but the auth string as a
;; whole? look into this more tomorrow
;;
;; after reading more
(loop :for item :across results
:do (prompt-and-download domain authorization
item opts))
(quit-with-message 0 "No results found for ~A" search-term)))))
(error (e)
(quit-with-message 1 "encountered error: ~A" e))))