fixed error fetching results after authentication

updated authorization format string
ensured we url encode our authorization values
removed ".local" from hostnames if it's present
added str package as dependency
fixed using deprecated authorization header
main
a. fox 2 years ago
parent 51b659725f
commit 96f6b24227

@ -21,9 +21,9 @@
(in-package :seanut) (in-package :seanut)
(defvar *authorization-format* (defparameter *authorization-format*
"MediaBrowser Client=\"Seanut v~A\", Device=\"~A\", DeviceId=\"~A\", Version=\"~A\", Token=\"~A\"") "MediaBrowser Client=\"~A\", Device=\"~A\", DeviceId=\"~A\", Version=\"~A\", Token=\"~A\"")
(defvar *valid-media-types* (defparameter *valid-media-types*
'("Book" "BoxSet" "Movie" "MusicAlbum" "MusicArtist" '("Book" "BoxSet" "Movie" "MusicAlbum" "MusicArtist"
"MusicGenre" "Playlist" "Season" "Series")) "MusicGenre" "Playlist" "Season" "Series"))

@ -8,7 +8,7 @@
:serial t :serial t
:depends-on (#:dexador #:with-user-abort #:unix-opts :depends-on (#:dexador #:with-user-abort #:unix-opts
#:com.inuoe.jzon #:babel #:ironclad #:quri #:com.inuoe.jzon #:babel #:ironclad #:quri
#:alexandria) #:alexandria #:str)
:components ((:file "package") :components ((:file "package")
(:file "util") (:file "util")
(:file "web") (:file "web")

@ -2,67 +2,93 @@
(in-package #:seanut) (in-package #:seanut)
(defun prompt-and-download (domain auth item opts &optional assume-yes) (defun prompt-and-download (domain auth root opts &optional assume-yes)
"prompt the user with y-or-n-p and download ITEM" "prompt the user with y-or-n-p and download ITEM"
;; debug print statements lol (labels ((fetch-user-id ()
(format t "~A~%" auth) (gethash "Id" (json-request (format-url domain "Users/Me")
(y-or-n-p) :auth auth)))
(generate-filename (item)
(labels ((generate-root-name (&optional add-trailing) (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~]~@[/~]" (format nil "~A (~A)~@[ Season ~A~]~@[/~]"
(gethash "Name" item) (gethash "Name" root)
(gethash "ProductionYear" item) (gethash "ProductionYear" root)
(getf opts :season-number) (getf opts :season-number)
add-trailing)) add-trailing))
(ensure-download-dir (&optional new-path) (create-directory (dir)
(ensure-directories-exist (ensure-directories-exist (uiop:ensure-directory-pathname (make-pathname :name dir))))
(merge-pathnames (or new-path "")
(merge-pathnames (generate-root-name 'add-slash)
(getf opts :output #P"./")))))
(download-item-or-children (item &optional parents) (download-item-or-children (item)
;; PARENTS is a list of all parent names with FIRST being ;; PARENTS is a list of all parent names with FIRST being
;; the oldest grandparent (for building complete download path) ;; the oldest grandparent (for building complete download path)
(let ((children (if (string= (gethash "Type" item) "Season") (let ((children (gethash "Items"
(json-request (format-url domain "Shows/~A/Episodes?fields=Path~@[&season=~A~]" (if (string= (gethash "Type" item) "Season")
(gethash "Id" item) (json-request (format-url domain "Shows/~A/Episodes?fields=Path&seasonId=~A~@[&season=~A~]"
(getf opts :season-number)) (gethash "SeriesId" item)
:auth auth) (gethash "Id" item)
(json-request (format-url domain "Items?fields=Path&parentId=~A" (getf opts :season-number))
(gethash "Id" item)) :auth auth)
:auth auth)))) (json-request (format-url domain "Items?userId=~A&fields=Path,ChildCount&parentId=~A"
(if (zerop (length children)) (fetch-user-id)
;; download single file (gethash "Id" item))
;; to get the extension type i think we may need to include :auth auth)))))
;; "Path" field in the api request, then use that to get the extension ;; DELETE: debug prints lmao
(download-media (format nil "~A~A~{~A~^/~}~A.~A" (loop :for child :across children
(getf opts :output #P"./") :do (format t "Name: ~A, Id: ~A~%"
(generate-root-name 'trailing) (gethash "Name" child)
parents (gethash "Name" item) (gethash "Id" child)))
(pathname-type (gethash "Path" item)))
(format-url domain "Items/~A/Download"
(gethash "Id" item)) (loop :for child :across children
auth) :if (zerop (gethash "ChildCount" child 0))
:do
;; download single file
(download-media (generate-filename child)
(format-url domain "Items/~A/Download"
(gethash "Id" child))
auth)
;; if the item has children we need to download them. :else
;; to accomplish this we get the list of children :do
;; and loop over them, recursing for each one ;; if the item has children we need to download them.
;; ensure that a directory exists for Parent ;; to accomplish this we get the list of children
;; then recurse with children ;; and loop over them, recursing for each one
(progn ;; ensure that a directory exists for Parent
(ensure-download-dir (format nil "~A~A~{~A/~}~A" ;; then recurse with children
(getf opts :output #P"./") (format t "Creating child directory in ~A and recursing: ~A~%"
(generate-root-name 'trailing) (uiop:getcwd) (gethash "Name" child))
parents (gethash "Name" item))) (uiop:with-current-directory ((create-directory (gethash "Name" child)))
(loop :for child :in children (download-item-or-children child))))))
:do (apply #'download-item-or-children
`(,child (,@parents ,(gethash "Name" item)))))))))) (format t "Name: ~A, Id: ~A~%"
(gethash "Name" root)
(gethash "Id" root))
(when (or assume-yes (when (or assume-yes
(y-or-n-p "Download ~A" (generate-root-name))) (y-or-n-p "Download ~A" (generate-root-name)))
(ensure-download-dir)
(download-item-or-children item)))) ;; CD into our output directory
(uiop:with-current-directory ((getf opts :output #P"./"))
(let ((grandest-parent (format nil "~A (~A)~@[ Season ~A~]/"
(gethash "Name" root)
(gethash "ProductionYear" root)
(getf opts :season-number))))
;; create our folder for our current download
(ensure-directories-exist grandest-parent)
;; CD into it, then download the files
(uiop:with-current-directory (grandest-parent)
(download-item-or-children root)))))))
(defun main () (defun main ()
@ -94,7 +120,7 @@
(quit-with-message 1 "domain and/or media name not provided")) (quit-with-message 1 "domain and/or media name not provided"))
(destructuring-bind (domain search-term) args (destructuring-bind (domain search-term) args
(let* ((authorization (or (getf opts :token) (let* ((authorization (or (and (getf opts :token) (generate-authorization (getf opts :token)))
(generate-authorization (get-access-token domain opts)))) (generate-authorization (get-access-token domain opts))))
(results (run-search-query domain authorization (results (run-search-query domain authorization
(getf opts :media-type) (getf opts :media-type)

@ -3,13 +3,21 @@
(in-package :seanut) (in-package :seanut)
(eval-when (:compile-toplevel) (eval-when (:compile-toplevel)
(declaim (inline json-request format-url generate-authorization download-media))) (declaim (inline json-request format-url generate-authorization download-media
format-hostname)))
(defun format-hostname ()
(if (uiop:string-suffix-p (uiop:hostname) ".local")
(subseq (uiop:hostname) 0 (- (length (uiop:hostname)) 6))
(uiop:hostname)))
(defun generate-authorization (&optional token) (defun generate-authorization (&optional token)
"generates a properly formatted authorization header" "generates a properly formatted authorization header"
(format nil *authorization-format* (apply #'format `(nil ,*authorization-format*
(seanut-version) (uiop:hostname) (md5-string (uiop:hostname)) ,@(mapcar #'url-encode
(seanut-version) (or token ""))) (list (concatenate 'string "Seanut " (seanut-version))
(format-hostname) (string-downcase (md5-string (format-hostname)))
(seanut-version) (or token ""))))))
(defun format-url (domain slug &rest args) (defun format-url (domain slug &rest args)
"formats DOMAIN into a url, ensures we include the url scheme "formats DOMAIN into a url, ensures we include the url scheme
@ -46,4 +54,4 @@ can probably be removed and the request can be made in-line"
if DESTINATION is non-nil, dumps media into that directory, otherwise it uses CWD" if DESTINATION is non-nil, dumps media into that directory, otherwise it uses CWD"
(dex:fetch url path (dex:fetch url path
:if-exists nil :if-exists nil
:headers `(("X-Emby-Authorization" . ,auth)))) :headers `(("Authorization" . ,auth))))

Loading…
Cancel
Save