;;;; seanut.lisp (in-package #:seanut) (defun prompt-and-download (domain auth item opts &optional assume-yes) "prompt the user with y-or-n-p and download ITEM" ;; debug print statements lol (format t "~A~%" auth) (y-or-n-p) (labels ((generate-root-name (&optional add-trailing) (format nil "~A (~A)~@[ Season ~A~]~@[/~]" (gethash "Name" item) (gethash "ProductionYear" item) (getf opts :season-number) add-trailing)) (ensure-download-dir (&optional new-path) (ensure-directories-exist (merge-pathnames (or new-path "") (merge-pathnames (generate-root-name 'add-slash) (getf opts :output #P"./"))))) (download-item-or-children (item &optional parents) ;; PARENTS is a list of all parent names with FIRST being ;; the oldest grandparent (for building complete download path) (let ((children (if (string= (gethash "Type" item) "Season") (json-request (format-url domain "Shows/~A/Episodes?fields=Path~@[&season=~A~]" (gethash "Id" item) (getf opts :season-number)) auth) (json-request (format-url domain "Items?fields=Path&parentId=~A" (gethash "Id" item)) auth)))) (if (zerop (length children)) ;; download single file ;; to get the extension type i think we may need to include ;; "Path" field in the api request, then use that to get the extension (download-media (format nil "~A~A~{~A~^/~}~A.~A" (getf opts :output #P"./") (generate-root-name 'trailing) parents (gethash "Name" item) (pathname-type (gethash "Path" item))) (format-url domain "Items/~A/Download" (gethash "Id" item)) auth) ;; 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 (progn (ensure-download-dir (format nil "~A~A~{~A/~}~A" (getf opts :output #P"./") (generate-root-name 'trailing) parents (gethash "Name" item))) (loop :for child :in children :do (apply #'download-item-or-children `(,child (,@parents ,(gethash "Name" item)))))))))) (when (or assume-yes (y-or-n-p "Download ~A" (generate-root-name))) (ensure-download-dir) (download-item-or-children item)))) (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 (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 (loop :for item :across results :do (prompt-and-download domain authorization item (getf opts :assume-yes))) (quit-with-message 0 "No results found for ~A" search-term))))) (error (e) (quit-with-message 1 "encountered error: ~A" e))))