mirror of
https://github.com/kiranshila/Doplarr.git
synced 2026-07-22 05:21:40 -04:00
More generalizations
This commit is contained in:
1
deps.edn
1
deps.edn
@@ -10,6 +10,7 @@
|
||||
cheshire/cheshire {:mvn/version "5.10.1"}
|
||||
fmnoise/flow {:mvn/version "4.1.0"}
|
||||
hato/hato {:mvn/version "0.8.2"}
|
||||
integrant/integrant {:mvn/version "0.8.0"}
|
||||
camel-snake-kebab/camel-snake-kebab {:mvn/version "0.4.2"}
|
||||
org.clojure/alpha.spec {:git/url "https://github.com/clojure/spec-alpha2.git"
|
||||
:sha "c087ded910b3532a938b37e853df79fc3b9c48c1"}}
|
||||
|
||||
5
src/doplarr/backends/overseerr.clj
Normal file
5
src/doplarr/backends/overseerr.clj
Normal file
@@ -0,0 +1,5 @@
|
||||
(ns doplarr.backends.overseerr)
|
||||
|
||||
(defn search [])
|
||||
|
||||
(defn request [])
|
||||
1
src/doplarr/backends/overseerr/specs.clj
Normal file
1
src/doplarr/backends/overseerr/specs.clj
Normal file
@@ -0,0 +1 @@
|
||||
(ns doplarr.backends.overseerr.specs)
|
||||
45
src/doplarr/backends/radarr.clj
Normal file
45
src/doplarr/backends/radarr.clj
Normal file
@@ -0,0 +1,45 @@
|
||||
(ns doplarr.backends.radarr
|
||||
(:require
|
||||
[config.core :refer [env]]
|
||||
[clojure.spec.alpha :as spec]
|
||||
[doplarr.utils :as utils]
|
||||
[doplarr.backends.radarr.impl :as impl]
|
||||
[doplarr.backends.radarr.specs :as specs]
|
||||
[doplarr.backends.specs :as bs]
|
||||
[clojure.core.async :as a]))
|
||||
|
||||
(defn search [term]
|
||||
(utils/request-and-process-body
|
||||
impl/GET
|
||||
#(into [] (map utils/process-search-result %))
|
||||
"/movie/lookup"
|
||||
{:query-params {:term term}}))
|
||||
(spec/fdef search
|
||||
:args (spec/cat :term string?))
|
||||
|
||||
(defn request [payload]
|
||||
(utils/request-and-process-body
|
||||
impl/POST
|
||||
(constantly nil)
|
||||
"/movie"
|
||||
{:form-params (utils/to-camel payload)
|
||||
:content-type :json}))
|
||||
(spec/fdef request
|
||||
:args (spec/cat :payload ::specs/payload))
|
||||
|
||||
(defn additional-options [result]
|
||||
(a/go
|
||||
(let [quality-profiles (a/<! (impl/quality-profiles))
|
||||
{:keys [default-quality-profile
|
||||
partial-seasons]} env]
|
||||
{:quality-profile-id (cond
|
||||
(= 1 (count quality-profiles)) (->> quality-profiles
|
||||
first
|
||||
:id)
|
||||
default-quality-profile (->> quality-profiles
|
||||
(filter #(= default-quality-profile (:name %)))
|
||||
first
|
||||
:id)
|
||||
:else quality-profiles)})))
|
||||
(spec/fdef additional-options
|
||||
:args (spec/cat :result ::bs/result))
|
||||
@@ -11,64 +11,41 @@
|
||||
(def api-key (delay (:radarr-api env)))
|
||||
|
||||
(defn GET [endpoint & [params]]
|
||||
(utils/http-request
|
||||
:get
|
||||
(str @base-url endpoint)
|
||||
@api-key
|
||||
params))
|
||||
(utils/http-request :get (str @base-url endpoint) @api-key params))
|
||||
|
||||
(defn POST [endpoint & [params]]
|
||||
(utils/http-request
|
||||
:post
|
||||
(str @base-url endpoint)
|
||||
@api-key
|
||||
params))
|
||||
(utils/http-request :post (str @base-url endpoint) @api-key params))
|
||||
|
||||
(def rootfolder (delay (a/<!! (utils/request-and-process-body GET #(get (first %) "path") "/rootfolder"))))
|
||||
|
||||
(defn search [term]
|
||||
(utils/request-and-process-body
|
||||
GET
|
||||
#(into [] (map utils/process-search-result %))
|
||||
"/movie/lookup"
|
||||
{:query-params {:term term}}))
|
||||
|
||||
(defn quality-profiles []
|
||||
(utils/request-and-process-body
|
||||
GET
|
||||
#(into [] (map utils/process-quality-profile %))
|
||||
#(into [] (map utils/process-profile %))
|
||||
"/qualityProfile"))
|
||||
|
||||
(defn status [movie & _]
|
||||
(defn status [result]
|
||||
(cond
|
||||
(and (:has-file movie)
|
||||
(:is-available movie)
|
||||
(:monitored movie)) :available
|
||||
(and (not (:has-file movie))
|
||||
(:is-available movie)
|
||||
(:monitored movie)) :processing
|
||||
(and (:has-file result)
|
||||
(:is-available result)
|
||||
(:monitored result)) :available
|
||||
(and (not (:has-file result))
|
||||
(:is-available result)
|
||||
(:monitored result)) :processing
|
||||
:else nil))
|
||||
(spec/fdef status
|
||||
:args (spec/cat :result ::bs/result)
|
||||
:ret ::bs/status)
|
||||
|
||||
(defn request-payload [result quality-profile-id]
|
||||
(merge result
|
||||
{:quality-profile-id quality-profile-id
|
||||
:monitored true
|
||||
:rootFolderPath @rootfolder
|
||||
:add-options {:search-for-movie true}}))
|
||||
(-> result
|
||||
(select-keys [:title :tmdb-id])
|
||||
(assoc :quality-profile-id quality-profile-id
|
||||
:monitored true
|
||||
:root-folder-path @rootfolder
|
||||
:add-options {:search-for-movie true})))
|
||||
(spec/fdef request-payload
|
||||
:args (spec/cat :result ::bs/result
|
||||
:quality-profile-id ::bs/quality-profile-id)
|
||||
:ret ::specs/payload)
|
||||
|
||||
(spec/fdef request-payload :args (spec/cat :arg ::bs/result :arg ::bs/quality-profile-id))
|
||||
|
||||
(defn request [payload]
|
||||
(utils/request-and-process-body
|
||||
POST
|
||||
(constantly nil)
|
||||
"/movie"
|
||||
{:form-params (utils/to-camel payload)
|
||||
:content-type :json}))
|
||||
|
||||
(spec/fdef request :args (spec/cat :arg ::specs/payload))
|
||||
|
||||
(def payload {:title "Making 'The Matrix'"
|
||||
:tmdb-id 684431
|
||||
:quality-profile-id 1
|
||||
:root-folder-path (a/<!! (rootfolder))})
|
||||
|
||||
67
src/doplarr/backends/sonarr.clj
Normal file
67
src/doplarr/backends/sonarr.clj
Normal file
@@ -0,0 +1,67 @@
|
||||
(ns doplarr.backends.sonarr
|
||||
(:require
|
||||
[config.core :refer [env]]
|
||||
[doplarr.utils :as utils]
|
||||
[doplarr.backends.sonarr.impl :as impl]
|
||||
[clojure.spec.alpha :as spec]
|
||||
[doplarr.backends.sonarr.specs :as specs]
|
||||
[doplarr.backends.specs :as bs]
|
||||
[clojure.core.async :as a]))
|
||||
|
||||
(defn search [term]
|
||||
(utils/request-and-process-body
|
||||
impl/GET
|
||||
#(mapv utils/process-search-result %)
|
||||
"/series/lookup"
|
||||
{:query-params {:term term}}))
|
||||
(spec/fdef search
|
||||
:args (spec/cat :term string?))
|
||||
|
||||
(defn request [payload]
|
||||
(utils/request-and-process-body
|
||||
(if (:id payload) impl/PUT impl/POST)
|
||||
(constantly nil)
|
||||
"/series"
|
||||
{:form-params (utils/to-camel payload)
|
||||
:content-type :json}))
|
||||
(spec/fdef request
|
||||
:args (spec/cat :payload ::specs/payload))
|
||||
|
||||
(defn additional-options [result]
|
||||
(a/go
|
||||
(let [quality-profiles (a/<! (impl/quality-profiles))
|
||||
language-profiles (a/<! (impl/language-profiles))
|
||||
details (a/<! (impl/get-from-tvdb (:tvdb-id result)))
|
||||
seasons (->> (:seasons details)
|
||||
(filterv #(pos? (:season-number %)))
|
||||
(mapv #(let [ssn (:season-number %)]
|
||||
(hash-map :id ssn :name (str ssn)))))
|
||||
{:keys [default-language-profile
|
||||
default-quality-profile
|
||||
partial-seasons]} env]
|
||||
{:season (cond
|
||||
(= 1 (count seasons)) (->> seasons
|
||||
first
|
||||
:id)
|
||||
(false? partial-seasons) -1
|
||||
:else seasons)
|
||||
:quality-profile-id (cond
|
||||
(= 1 (count quality-profiles)) (->> quality-profiles
|
||||
first
|
||||
:id)
|
||||
default-quality-profile (->> quality-profiles
|
||||
(filter #(= default-quality-profile (:name %)))
|
||||
first
|
||||
:id)
|
||||
:else quality-profiles)
|
||||
:language-profile-id (cond
|
||||
(= 1 (count language-profiles)) (->> language-profiles
|
||||
first
|
||||
:id)
|
||||
default-language-profile (->> language-profiles
|
||||
(filter #(= default-language-profile (:name %)))
|
||||
first
|
||||
:id)
|
||||
:else language-profiles)})))
|
||||
(spec/fdef additional-options
|
||||
:args (spec/cat :result ::bs/result))
|
||||
@@ -1,6 +1,5 @@
|
||||
(ns doplarr.backends.sonarr.impl
|
||||
(:require
|
||||
[com.rpl.specter :as s]
|
||||
[clojure.core.async :as a]
|
||||
[config.core :refer [env]]
|
||||
[fmnoise.flow :as flow :refer [then else]]
|
||||
@@ -9,50 +8,41 @@
|
||||
(def base-url (delay (str (:sonarr-url env) "/api/v3")))
|
||||
(def api-key (delay (:sonarr-api env)))
|
||||
|
||||
(defn rootfolder [] (utils/rootfolder @base-url @api-key))
|
||||
|
||||
(defn GET [endpoint & [params]]
|
||||
(utils/http-request
|
||||
:get
|
||||
(str @base-url endpoint)
|
||||
@api-key
|
||||
params))
|
||||
(utils/http-request :get (str @base-url endpoint) @api-key params))
|
||||
|
||||
(defn POST [endpoint & [params]]
|
||||
(utils/http-request
|
||||
:post
|
||||
(str @base-url endpoint)
|
||||
@api-key
|
||||
params))
|
||||
(utils/http-request :post (str @base-url endpoint) @api-key params))
|
||||
|
||||
(defn PUT [endpoint & [params]]
|
||||
(utils/http-request
|
||||
:put
|
||||
(str @base-url endpoint)
|
||||
@api-key
|
||||
params))
|
||||
(utils/http-request :put (str @base-url endpoint) @api-key params))
|
||||
|
||||
(defn search [term]
|
||||
(a/go
|
||||
(->> (a/<! (GET "/series/lookup" {:query-params {:term term}}))
|
||||
(then :body)
|
||||
(else utils/fatal-error))))
|
||||
(def rootfolder (delay (a/<!! (utils/request-and-process-body GET #(get (first %) "path") "/rootfolder"))))
|
||||
|
||||
(defn quality-profiles []
|
||||
(a/go
|
||||
(->> (a/<! (GET "/qualityprofile"))
|
||||
(then #(->> (:body %)
|
||||
(map utils/quality-profile-data)))
|
||||
(else utils/fatal-error))))
|
||||
(utils/request-and-process-body
|
||||
GET
|
||||
#(into [] (map utils/process-profile %))
|
||||
"/qualityProfile"))
|
||||
|
||||
(defn request-options [profile-id]
|
||||
(a/go
|
||||
{:profileId profile-id
|
||||
:monitored true
|
||||
:seasonFolder true
|
||||
:rootFolderPath (a/<! (rootfolder))
|
||||
:addOptions {:ignoreEpisodesWithFiles true
|
||||
:searchForMissingEpisodes true}}))
|
||||
(defn language-profiles []
|
||||
(utils/request-and-process-body
|
||||
GET
|
||||
#(into [] (map utils/process-profile %))
|
||||
"/languageProfile"))
|
||||
|
||||
(defn get-from-tvdb [tvdb-id]
|
||||
(utils/request-and-process-body
|
||||
GET
|
||||
(comp utils/from-camel first)
|
||||
"/series/lookup"
|
||||
{:query-params {:term (str "tvdbId:" tvdb-id)}}))
|
||||
|
||||
(defn get-from-id [id]
|
||||
(utils/request-and-process-body
|
||||
GET
|
||||
utils/from-camel
|
||||
(str "/series/" id)))
|
||||
|
||||
(defn execute-command [command & {:as opts}]
|
||||
(a/go
|
||||
@@ -71,75 +61,7 @@
|
||||
(->> (a/<! (execute-command "SeriesSearch" {:seriesId series-id})))
|
||||
(then (constantly nil))))
|
||||
|
||||
(defn request-all [series profile-id]
|
||||
(a/go
|
||||
(let [id (:id series)
|
||||
series (if id
|
||||
(s/multi-transform
|
||||
(s/multi-path
|
||||
[:seasons
|
||||
s/ALL
|
||||
(comp pos? :seasonNumber)
|
||||
:monitored
|
||||
(s/terminal-val true)]
|
||||
[:profileId
|
||||
(s/terminal-val profile-id)])
|
||||
series)
|
||||
(merge series (a/<! (request-options profile-id))))]
|
||||
(->> (a/<! ((if id PUT POST)
|
||||
"/series"
|
||||
{:form-params series
|
||||
:content-type :json}))
|
||||
(then (fn [_]
|
||||
(when id
|
||||
(search-series id))))))))
|
||||
|
||||
(defn request-season [series season profile-id]
|
||||
(a/go
|
||||
(let [id (:id series)
|
||||
series (if id
|
||||
(s/multi-transform
|
||||
(s/multi-path
|
||||
[:seasons
|
||||
s/ALL
|
||||
(comp (partial = season) :seasonNumber)
|
||||
(s/multi-path
|
||||
[:monitored
|
||||
(s/terminal-val true)]
|
||||
[:statistics
|
||||
(s/terminal #(assoc % :episodeCount (:totalEpisodeCount %)))])]
|
||||
[:profileId
|
||||
(s/terminal-val profile-id)])
|
||||
series)
|
||||
(merge (s/setval [:seasons
|
||||
s/ALL
|
||||
(comp (partial not= season) :seasonNumber)
|
||||
:monitored]
|
||||
false series)
|
||||
(a/<! (request-options profile-id))))]
|
||||
(->> (a/<! ((if id PUT POST)
|
||||
"/series"
|
||||
{:form-params series
|
||||
:content-type :json}))
|
||||
(then (fn [_]
|
||||
(when id
|
||||
(search-season id season))))))))
|
||||
|
||||
(defn request [series & {:keys [season profile-id]}]
|
||||
(if (= -1 season)
|
||||
(request-all series profile-id)
|
||||
(request-season series season profile-id)))
|
||||
|
||||
(defn post-process-series [series]
|
||||
(a/go
|
||||
(if-let [id (:id series)]
|
||||
(->> (a/<! (GET (str "/series/" id)))
|
||||
(then #(->> (:body %)
|
||||
(merge series)))
|
||||
(else utils/fatal-error))
|
||||
series)))
|
||||
|
||||
(defn season-status [series & {:keys [season]}]
|
||||
(defn status [series season]
|
||||
(let [ssn (->> (:seasons series)
|
||||
(filter (comp (partial = season) :seasonNumber))
|
||||
first)]
|
||||
|
||||
@@ -1,2 +1,13 @@
|
||||
(ns doplarr.backends.sonarr.specs
|
||||
(:require [clojure.spec.alpha :as spec]))
|
||||
(:require [clojure.spec.alpha :as spec]
|
||||
[doplarr.backends.specs :as bs]))
|
||||
|
||||
(spec/def ::payload (spec/keys :req-un [::bs/title
|
||||
::bs/tvdb-id
|
||||
::bs/root-folder-path
|
||||
::bs/seasons
|
||||
::bs/monitored
|
||||
::bs/add-options
|
||||
::bs/quality-profile-id
|
||||
::bs/language-profile-id]
|
||||
:opt-un [::bs/id]))
|
||||
|
||||
@@ -1,14 +1,34 @@
|
||||
(ns doplarr.backends.specs
|
||||
(:require [clojure.spec.alpha :as spec]))
|
||||
|
||||
; Generic
|
||||
(spec/def ::title string?)
|
||||
(spec/def ::year int?)
|
||||
(spec/def ::id int?)
|
||||
(spec/def ::tmdb-id int?)
|
||||
(spec/def ::tvdb-id int?)
|
||||
(spec/def ::id pos-int?)
|
||||
(spec/def ::root-folder-path string?)
|
||||
(spec/def ::quality-profile-id (spec/and int? pos?))
|
||||
(spec/def ::quality-profile-id pos-int?)
|
||||
(spec/def ::language-profile-id pos-int?)
|
||||
(spec/def ::monitored boolean?)
|
||||
|
||||
(spec/def ::result (spec/keys
|
||||
:req-un [::title ::year]
|
||||
:opt-un [::id ::tvdb-id ::tmdb-id]))
|
||||
; Movie
|
||||
(spec/def ::tmdb-id pos-int?)
|
||||
|
||||
; Series
|
||||
(spec/def ::season-number int?)
|
||||
(spec/def ::season (spec/keys :req-un [::season-number ::monitored]))
|
||||
(spec/def ::seasons (spec/coll-of ::season))
|
||||
(spec/def ::tvdb-id pos-int?)
|
||||
(spec/def ::ignore-episodes-with-files boolean?)
|
||||
(spec/def ::search-for-missing-episodes boolean?)
|
||||
(spec/def ::add-options (spec/keys :req-un [::ignore-episodes-with-files ::search-for-missing-episodes]))
|
||||
|
||||
; Searching
|
||||
(spec/def ::result (spec/keys :req-un [::title ::year
|
||||
(or ::id ::tvdb-id ::tmdb-id)]))
|
||||
|
||||
; Doplarr Internals
|
||||
(spec/def ::status #{:unknown :pending :processing :partially-available :available})
|
||||
(spec/def ::name string?)
|
||||
(spec/def ::option (spec/keys :req-un [::name ::id]))
|
||||
(spec/def ::options (spec/coll-of ::option))
|
||||
(spec/def ::additional-options (spec/keys :req-un [::name ::options]))
|
||||
|
||||
@@ -1,24 +1,15 @@
|
||||
(ns doplarr.config
|
||||
(:require
|
||||
[config.core :refer [env]]))
|
||||
[config.core :refer [env]]
|
||||
[doplarr.config.specs :as specs]
|
||||
[clojure.spec.alpha :as spec]))
|
||||
|
||||
(def bot-requirements #{:bot-token})
|
||||
(defn validate-config []
|
||||
(spec/explain-data ::specs/config env))
|
||||
|
||||
(def direct-requirements #{:sonarr-url
|
||||
:sonarr-api
|
||||
:radarr-url
|
||||
:radarr-api})
|
||||
|
||||
(def overseerr-requirements #{:overseerr-url
|
||||
:overseerr-api})
|
||||
|
||||
;; Default to overseerr if both are configured
|
||||
(defn backend []
|
||||
(cond
|
||||
(every? env overseerr-requirements) :overseerr
|
||||
(every? env direct-requirements) :direct
|
||||
:else nil))
|
||||
|
||||
(defn validate-env []
|
||||
(and (every? env bot-requirements)
|
||||
(keyword? (backend))))
|
||||
(defn available-backends []
|
||||
(let [{:keys [sonarr-url radarr-url overseerr-url]} env]
|
||||
(cond-> #{}
|
||||
sonarr-url (conj :sonarr)
|
||||
radarr-url (conj :radarr)
|
||||
overseerr-url (conj :overseerr))))
|
||||
|
||||
31
src/doplarr/config/specs.clj
Normal file
31
src/doplarr/config/specs.clj
Normal file
@@ -0,0 +1,31 @@
|
||||
(ns doplarr.config.specs
|
||||
(:require [clojure.spec.alpha :as spec]))
|
||||
|
||||
; Backend endpoints
|
||||
(spec/def ::sonarr-url string?)
|
||||
(spec/def ::radarr-url string?)
|
||||
(spec/def ::overseerr-url string?)
|
||||
|
||||
; Backend API keys
|
||||
(spec/def ::sonarr-api string?)
|
||||
(spec/def ::radarr-api string?)
|
||||
(spec/def ::overseerr-api string?)
|
||||
|
||||
; Discord bot token
|
||||
(spec/def ::bot-token string?)
|
||||
|
||||
; Optional settings
|
||||
(spec/def ::partial-seasons boolean?)
|
||||
(spec/def ::default-quality-profile string?)
|
||||
(spec/def ::default-language-profile string?)
|
||||
(spec/def ::role-id string?)
|
||||
|
||||
; Complete Config
|
||||
(spec/def ::config (spec/keys :req-un [(or (and ::sonarr-url ::sonarr-api)
|
||||
(and ::radarr-url ::radarr-api)
|
||||
(and ::overseerr-url ::overseerr-api))
|
||||
::bot-token]
|
||||
:opt-un [::partial-seasons
|
||||
::default-quality-profile
|
||||
::default-language-profile
|
||||
::role-id]))
|
||||
@@ -52,8 +52,8 @@
|
||||
|
||||
(defn -main
|
||||
[& _]
|
||||
(when-not (config/validate-env)
|
||||
(println "Error in configuration")
|
||||
(when-let [config-error (config/validate-config)]
|
||||
(ex-info "Error in configuration" {:spec-error config-error})
|
||||
(System/exit -1))
|
||||
(run)
|
||||
(shutdown-agents))
|
||||
|
||||
@@ -10,9 +10,9 @@
|
||||
|
||||
(def channel-timeout 600000)
|
||||
|
||||
(def request-command
|
||||
(defn request-command []
|
||||
{:name "request"
|
||||
:description "Request a series or movie"
|
||||
:description "Request media"
|
||||
:default_permission (boolean (not (:role-id env)))
|
||||
:options
|
||||
[{:type 1
|
||||
@@ -150,10 +150,13 @@
|
||||
(str "result-select:" uuid)
|
||||
(map-indexed select-menu-option results))))
|
||||
|
||||
(defn select-profile [profiles uuid]
|
||||
(dropdown "Which quality profile?"
|
||||
(str "profile-select:" uuid)
|
||||
(map #(hash-map :label (:name %) :value (:id %)) profiles)))
|
||||
(defn option-dropdown [name options uuid]
|
||||
(dropdown (str "Which " name "?")
|
||||
(str "option-select:" uuid)
|
||||
(map #(hash-map :label (:name %) :value (:id %)) options)))
|
||||
|
||||
(defn dropdown-result [interaction]
|
||||
(Integer/parseInt (s/select-one [:payload :values 0] interaction)))
|
||||
|
||||
(defn selection-embed [selection & {:keys [season profile]}]
|
||||
{:title (:title selection)
|
||||
@@ -182,12 +185,3 @@
|
||||
{:content "This has been requested!"
|
||||
:embeds [(selection-embed selection :season season :profile profile)]})
|
||||
|
||||
(defn select-season [series uuid]
|
||||
(dropdown "Which season?"
|
||||
(str "season-select:" uuid)
|
||||
(conj (map #(hash-map :label (str "Season: " %) :value %)
|
||||
(range 1 (inc (:seasonCount series))))
|
||||
{:label "All Seasons" :value "-1"})))
|
||||
|
||||
(defn dropdown-index [interaction]
|
||||
(Integer/parseInt (s/select-one [:payload :values 0] interaction)))
|
||||
|
||||
@@ -1,48 +1,10 @@
|
||||
(ns doplarr.interaction-state-machine
|
||||
(:require
|
||||
[doplarr.overseerr :as ovsr]
|
||||
[config.core :refer [env]]
|
||||
[doplarr.discord :as discord]
|
||||
[clojure.core.async :as a]
|
||||
[com.rpl.specter :as s]
|
||||
[fmnoise.flow :as flow :refer [then else]]
|
||||
[clojure.string :as str]
|
||||
[doplarr.config :as config]
|
||||
[doplarr.sonarr :as sonarr]
|
||||
[doplarr.radarr :as radarr]))
|
||||
|
||||
(def backend (delay (config/backend)))
|
||||
|
||||
(def search-fn {:overseerr {:series #'ovsr/search-series
|
||||
:movie #'ovsr/search-movie}
|
||||
:direct {:series #'sonarr/search
|
||||
:movie #'radarr/search}})
|
||||
|
||||
(def profiles-fn {:overseerr {:series #(a/go nil)
|
||||
:movie #(a/go nil)}
|
||||
:direct {:series #'sonarr/quality-profiles
|
||||
:movie #'radarr/quality-profiles}})
|
||||
|
||||
(def process-selection-fn {:overseerr {:series #'ovsr/post-process-selection
|
||||
:movie #'ovsr/post-process-selection}
|
||||
:direct {:series #'sonarr/post-process-series
|
||||
:movie (fn [movie] (a/go movie))}})
|
||||
|
||||
(def request-selection-fn {:overseerr #'ovsr/selection-to-request
|
||||
:direct (fn [selection & _] selection)})
|
||||
|
||||
(def account-id-fn {:overseerr #(a/go ((a/<! (ovsr/discord-users)) %))
|
||||
:direct (fn [_] (a/go 1))}) ; Dummy id to get around account check
|
||||
|
||||
(def request-fn {:overseerr {:series #'ovsr/request
|
||||
:movie #'ovsr/request}
|
||||
:direct {:series #'sonarr/request
|
||||
:movie #'radarr/request}})
|
||||
|
||||
(def content-status-fn {:overseerr {:series #'ovsr/season-status
|
||||
:movie #'ovsr/movie-status}
|
||||
:direct {:series #'sonarr/season-status
|
||||
:movie #'radarr/movie-status}})
|
||||
[clojure.string :as str]))
|
||||
|
||||
(defn start-interaction [interaction]
|
||||
(let [uuid (str (java.util.UUID/randomUUID))
|
||||
@@ -61,78 +23,6 @@
|
||||
:last-modified (System/currentTimeMillis)})
|
||||
(discord/update-interaction-response token (discord/search-response results uuid))))))
|
||||
|
||||
(defmulti process-event (fn [event _ _] event))
|
||||
|
||||
(defmethod process-event "result-select" [_ interaction uuid]
|
||||
(a/go
|
||||
(let [{:keys [results request-type token]} (get @discord/cache uuid)
|
||||
selection-id (discord/dropdown-index interaction)
|
||||
profiles (->> (a/<! (((profiles-fn @backend) request-type)))
|
||||
(into []))
|
||||
selection (a/<! (((process-selection-fn @backend) request-type) (nth results selection-id)))]
|
||||
(case request-type
|
||||
:series (let [one-season? (> (case @backend :overseerr 1 :direct 2) (count (:seasons selection)))
|
||||
partial-seasons? (if (nil? (:partial-seasons env))
|
||||
(case @backend :overseerr (a/<! (ovsr/partial-seasons?)) :direct true)
|
||||
(:partial-seasons env))]
|
||||
(if (and partial-seasons? (not one-season?))
|
||||
(discord/update-interaction-response token (discord/select-season selection uuid))
|
||||
(let [season (if one-season? 1 -1)]
|
||||
(swap! discord/cache assoc-in [uuid :season] season)
|
||||
(case @backend
|
||||
:overseerr (discord/update-interaction-response token (discord/request selection uuid :season season))
|
||||
:direct (discord/update-interaction-response token (discord/select-profile profiles uuid))))))
|
||||
:movie (case @backend
|
||||
:overseerr (discord/update-interaction-response token (discord/request selection uuid))
|
||||
:direct (discord/update-interaction-response token (discord/select-profile profiles uuid))))
|
||||
(swap! discord/cache assoc-in [uuid :profiles] profiles)
|
||||
(swap! discord/cache assoc-in [uuid :selection] selection))))
|
||||
|
||||
(defmethod process-event "season-select" [_ interaction uuid]
|
||||
(let [{:keys [token selection profiles]} (get @discord/cache uuid)
|
||||
season (discord/dropdown-index interaction)]
|
||||
(case @backend
|
||||
:overseerr (discord/update-interaction-response token (discord/request selection uuid :season season))
|
||||
:direct (discord/update-interaction-response token (discord/select-profile profiles uuid)))
|
||||
(swap! discord/cache assoc-in [uuid :season] season)))
|
||||
|
||||
(defmethod process-event "profile-select" [_ interaction uuid]
|
||||
(let [{:keys [token profiles season selection]} (get @discord/cache uuid)
|
||||
profile-id (discord/dropdown-index interaction)
|
||||
profile (s/select-one [s/ALL (comp (partial = profile-id) :id) :name] profiles)]
|
||||
(discord/update-interaction-response token (discord/request selection uuid :season season :profile profile))
|
||||
(swap! discord/cache assoc-in [uuid :profile-id] profile-id)))
|
||||
|
||||
(defmethod process-event "request" [_ interaction uuid]
|
||||
(a/go
|
||||
(let [{:keys [token selection season profile request-type profile-id is4k]} (get @discord/cache uuid)
|
||||
user-id (:user-id interaction)
|
||||
backend-id (a/<! ((account-id-fn @backend) user-id))]
|
||||
(if (nil? backend-id)
|
||||
(discord/update-interaction-response token (discord/content-response "You do not have an associated account on Overseerr"))
|
||||
(case (((content-status-fn @backend) request-type) selection :season season :is4k is4k)
|
||||
:pending (discord/update-interaction-response token (discord/content-response "This has been requested, and the request is pending."))
|
||||
:processing (discord/update-interaction-response token (discord/content-response "This is currently processing and should be available soon."))
|
||||
:available (discord/update-interaction-response token (discord/content-response "This is already available!"))
|
||||
(->> (a/<! (((request-fn @backend) request-type)
|
||||
((request-selection-fn @backend) selection :season season :is4k (boolean is4k))
|
||||
:season season
|
||||
:ovsr-id backend-id
|
||||
:profile-id profile-id))
|
||||
(then (fn [_]
|
||||
(discord/update-interaction-response token (discord/content-response "Requested!"))
|
||||
(discord/followup-repsonse token (discord/request-alert selection :season season :profile profile))))
|
||||
(else (fn [e]
|
||||
(let [{:keys [status body] :as data} (ex-data e)
|
||||
msg (second (re-matches #"\{\"message\":\"(.+)\"\}" body))] ; Not sure why this JSON didn't get parsed
|
||||
(cond
|
||||
(= status 403) (discord/update-interaction-response token (discord/content-response msg))
|
||||
:else (throw (ex-info "Non 403 error on request" data))))))))))))
|
||||
|
||||
(defmethod process-event "request-4k" [_ interaction uuid]
|
||||
(swap! discord/cache assoc-in [uuid :is4k] true)
|
||||
(process-event "request" interaction uuid))
|
||||
|
||||
(defn continue-interaction [interaction]
|
||||
(let [[event uuid] (str/split (s/select-one [:payload :component-id] interaction) #":")
|
||||
now (System/currentTimeMillis)]
|
||||
|
||||
28
src/doplarr/system.clj
Normal file
28
src/doplarr/system.clj
Normal file
@@ -0,0 +1,28 @@
|
||||
(ns doplarr.system
|
||||
(:require [integrant.core :as ig]
|
||||
[doplarr.config :as config]))
|
||||
|
||||
(def backends [:radarr :sonarr :overseerr])
|
||||
(def backend-fns [:search :request :additional-options])
|
||||
|
||||
(def media-backends {:movie [:overseerr :radarr]
|
||||
:series [:overseerr :sonarr]
|
||||
; :book [:readarr]
|
||||
; :music [:lidarr]
|
||||
})
|
||||
|
||||
(defn derive-backend! [backend]
|
||||
(derive (keyword "backend" (name backend)) :doplarr/backend))
|
||||
|
||||
; Generate Parent-Child Relationships
|
||||
(run! derive-backend! backends)
|
||||
|
||||
(def config
|
||||
(into {} (for [b backends]
|
||||
[(keyword "backend" (name b)) {:ns b}])))
|
||||
|
||||
(defmethod ig/init-key :doplarr/backend [_ {:keys [ns]}]
|
||||
(zipmap backend-fns (for [f backend-fns
|
||||
:let [ns (str "doplarr.backends." (name ns))
|
||||
sym (symbol ns (name f))]]
|
||||
(requiring-resolve sym))))
|
||||
@@ -45,7 +45,7 @@
|
||||
(select-keys ["title" "year" "id" "tvdbId" "tmdbId"])
|
||||
from-camel))
|
||||
|
||||
(defn process-quality-profile [profile]
|
||||
(defn process-profile [profile]
|
||||
(->> (select-keys profile ["id" "name"])
|
||||
from-camel))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user