mirror of
https://github.com/kiranshila/Doplarr.git
synced 2026-03-31 06:23:48 -04:00
Overseerr works!
This commit is contained in:
51
README.md
51
README.md
@@ -10,13 +10,13 @@
|
||||
<img alt="Discord" src="https://img.shields.io/discord/890634173751119882?color=ff69b4&label=discord&style=for-the-badge">
|
||||
</p>
|
||||
|
||||
> A Sonarr/Radarr Request Bot for Discord
|
||||
> An \*arr Request Bot for Discord
|
||||
|
||||
## Why does this exist
|
||||
|
||||
- Uses modern Discord slash commands and components, which provides a clean, performant UI on desktop and mobile
|
||||
- This has the added benifit of not requiring privileged intents, so this bot will _never_ look at message content
|
||||
- Simple codebase, <1k lines of code which makes it easier to maintain. [Code is not an asset](https://robinbb.com/blog/code-is-not-an-asset/)
|
||||
- Uses modern Discord slash commands and components, which provides a clean, performant UI on desktop and mobile.
|
||||
This has the added benefit of not requiring privileged intents, so this bot will _never_ look at message content
|
||||
- Small codebase as [code is not an asset](https://robinbb.com/blog/code-is-not-an-asset/)
|
||||
- Simple configuration, no need to have a whole web frontend just for configuration
|
||||
- Powered by Clojure and [Discljord](https://github.com/IGJoshua/discljord), a markedly good combination 😛
|
||||
|
||||
@@ -36,10 +36,7 @@ There is only a boolean permission (role gated) for who has access to the bot, n
|
||||
|
||||
#### Will you support Lidarr/Readarr/\*arr
|
||||
|
||||
Not yet. The idea is that one can work directly with the collection managers or
|
||||
work through a request manager (Overseerr). As Overseerr doesn't support
|
||||
collections managers other than radarr/sonarr and I want feature-parity, those
|
||||
other managers will be left out until Overseerr supports them.
|
||||
Soon™
|
||||
|
||||
#### Why are the commands greyed out?
|
||||
|
||||
@@ -96,8 +93,8 @@ This bot isn't meant to wrap the entirety of what Overseerr can do, just the
|
||||
necessary bits for requesting with optional 4K and quota support. Just use the
|
||||
web interface to Overseerr if you need more features.
|
||||
|
||||
In the config, you replace `SONARR_URL`, `SONARR_API`, `RADARR_URL`,
|
||||
`RADARR_API` with `OVERSEERR_URL` and `OVERSEERR_API`.
|
||||
In the config, you replace `SONARR__URL`, `SONARR__API`, `RADARR__URL`,
|
||||
`RADARR__API` with `OVERSEERR__URL` and `OVERSEERR__API`.
|
||||
|
||||
## Running with Docker
|
||||
|
||||
@@ -105,11 +102,11 @@ Simply run with
|
||||
|
||||
```bash
|
||||
docker run \
|
||||
-e SONARR_URL='http://localhost:8989' \
|
||||
-e RADARR_URL='http://localhost:7878' \
|
||||
-e SONARR_API='sonarr_api' \
|
||||
-e RADARR_API='radarr_api' \
|
||||
-e BOT_TOKEN='bot_token' \
|
||||
-e SONARR__URL='http://localhost:8989' \
|
||||
-e RADARR__URL='http://localhost:7878' \
|
||||
-e SONARR__API='sonarr_api' \
|
||||
-e RADARR__API='radarr_api' \
|
||||
-e DISCORD__TOKEN='bot_token' \
|
||||
--name doplarr ghcr.io/kiranshila/doplarr:latest
|
||||
```
|
||||
|
||||
@@ -118,11 +115,11 @@ Alternatively, use docker-compose:
|
||||
```yaml
|
||||
doplarr:
|
||||
environment:
|
||||
- ‘SONARR_URL=http://localhost:8989’
|
||||
- ‘RADARR_URL=http://localhost:7878’
|
||||
- SONARR_API=sonarr_api
|
||||
- RADARR_API=radarr_api
|
||||
- BOT_TOKEN=bot_token
|
||||
- SONARR__URL='http://localhost:8989’
|
||||
- RADARR__URL='http://localhost:7878’
|
||||
- SONARR__API=sonarr_api
|
||||
- RADARR__API=radarr_api
|
||||
- DISCORD__TOKEN=bot_token
|
||||
container_name: doplarr
|
||||
image: ‘ghcr.io/kiranshila/doplarr:latest’
|
||||
```
|
||||
@@ -142,11 +139,15 @@ To skip the build, just download `Doplarr.jar` and `config.edn` from the release
|
||||
|
||||
### Optional Settings
|
||||
|
||||
| Environment Variable (Docker) | Config File Keyword | Type | Description |
|
||||
| ----------------------------- | ------------------- | ------- | ------------------------------------------------------------------------------------------------ |
|
||||
| `MAX_RESULTS` | `:max-results` | Integer | Sets the maximum size of the search results selection |
|
||||
| `ROLE_ID` | `:role-id` | String | The discord role id for users of the bot (omitting this lets everyone on the server use the bot) |
|
||||
| `PARTIAL_SEASONS` | `:partial-seasons` | Boolean | Sets whether users can request partial seasons. Defaults to true or setting in Overseer |
|
||||
| Environment Variable (Docker) | Config File Keyword | Type | Default Value | Description |
|
||||
| ----------------------------- | -------------------------- | ------- | ------------- | --------------------------------------------------------------------------------------------------- |
|
||||
| `DISCORD__MAX_RESULTS` | `:discord/max-results` | Integer | 25 | Sets the maximum size of the search results selection |
|
||||
| `DISCORD__ROLE_ID` | `:discord/role-id` | String | N/A | The discord role id for users of the bot (omitting this lets everyone on the server use the bot) |
|
||||
| `PARTIAL_SEASONS` | `:partial-seasons` | Boolean | True | Sets whether users can request partial seasons. |
|
||||
| `SONARR__QUALITY_PROFILE` | `:sonarr/quality-profile` | String | N/A | The name of the quality profile to use by default for Sonarr |
|
||||
| `RADARR__QUALITY_PROFILE` | `:radarr/quality-profile` | String | N/A | The name of the quality profile to use by default for Radarr |
|
||||
| `SONARR__LANGUAGE_PROFILE` | `:sonarr/language-profile` | String | N/A | The name of the language profile to use by default for Radarr |
|
||||
| `OVERSEERR__DEFAULT_ID` | `:overseerr/default-id` | Integer | N/A | The Overseerr user id to use by default if there is no associated discord account for the requester |
|
||||
|
||||
### Setting up on Windows
|
||||
|
||||
|
||||
22
config.edn
22
config.edn
@@ -1,6 +1,16 @@
|
||||
{:sonarr-url "http://localhost:8989"
|
||||
:sonarr-api "sonarr_api_kei"
|
||||
:radarr-url "http://localhost:7878"
|
||||
:radarr-api "radarr_api_kei"
|
||||
:bot-token "bot_token"
|
||||
:role-id "role_id"}
|
||||
{:sonarr/url ""
|
||||
:sonarr/api ""
|
||||
:radarr/url ""
|
||||
:radarr/api ""
|
||||
; :overseerr/url ""
|
||||
; :overseerr/api ""
|
||||
:discord/token ""
|
||||
; -- Optional Settings
|
||||
; :partial-seasons false
|
||||
; :sonarr/quality-profile ""
|
||||
; :radarr/quality-profile ""
|
||||
; :sonarrr/language-profile ""
|
||||
; :discord/role-id ""
|
||||
; :discord/max-results 10
|
||||
; :overseerr/default-id 1
|
||||
}
|
||||
|
||||
58
doplarr.xml
58
doplarr.xml
@@ -1,58 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
<Container version="2">
|
||||
<Name>doplarr</Name>
|
||||
<Repository>ghcr.io/kiranshila/doplarr:latest</Repository>
|
||||
<Registry>
|
||||
https://github.com/kiranshila/doplarr/pkgs/container/doplarr</Registry>
|
||||
<Network>bridge</Network>
|
||||
<Privileged>false</Privileged>
|
||||
<Support>https://discord.gg/YSga2wxr</Support>
|
||||
<Project>https://github.com/kiranshila/Doplarr/</Project>
|
||||
<Overview>A Better Sonarr/Radarr Request Bot for
|
||||
Discord</Overview>
|
||||
<Shell>bash</Shell>
|
||||
<WebUI />
|
||||
<TemplateURL>
|
||||
https://github.com/kiranshila/Doplarr/raw/main/doplarr.xml</TemplateURL>
|
||||
<Icon>
|
||||
https://github.com/kiranshila/Doplarr/raw/main/logos/logo_cropped.png</Icon>
|
||||
<ExtraParams />
|
||||
<PostArgs />
|
||||
<DonateText />
|
||||
<DonateLink />
|
||||
<Config Name="Discord Bot Token" Target="BOT_TOKEN" Default=""
|
||||
Mode="" Description="Discord bot API token" Type="Variable"
|
||||
Display="always" Required="true" Mask="false" />
|
||||
<Config Name="Radarr URL" Target="RADARR_URL" Default=""
|
||||
Mode="" Description="URL of Radarr instance" Type="Variable"
|
||||
Display="always" Required="false" Mask="false" />
|
||||
<Config Name="Radarr API Key" Target="RADARR_API" Default=""
|
||||
Mode="" Description="API key of Radarr instance" Type="Variable"
|
||||
Display="always" Required="false" Mask="false" />
|
||||
<Config Name="Sonarr URL" Target="SONARR_URL" Default="" Mode=""
|
||||
Description="URL of Sonarr Instance" Type="Variable"
|
||||
Display="always" Required="false" Mask="false" />
|
||||
<Config Name="Sonarr API Key" Target="SONARR_API" Default=""
|
||||
Mode="" Description="API key of Sonarr Instance" Type="Variable"
|
||||
Display="always" Required="false" Mask="false" />
|
||||
<Config Name="Overseerr URL" Target="OVERSEERR_URL" Default=""
|
||||
Mode="" Description="URL of Overseerr instance" Type="Variable"
|
||||
Display="always" Required="false" Mask="false" />
|
||||
<Config Name="Overseerr API Key" Target="OVERSEERR_API"
|
||||
Default="" Mode="" Description="API key of overseerr instance"
|
||||
Type="Variable" Display="always" Required="false" Mask="false" />
|
||||
<Config Name="Discord Role ID" Target="ROLE_ID" Default=""
|
||||
Mode="" Description="Optional role id for restricting bot access"
|
||||
Type="Variable" Display="always-hide" Required="false"
|
||||
Mask="false" />
|
||||
<Config Name="Maximum search results" Target="MAX_RESULTS"
|
||||
Default="10" Mode=""
|
||||
Description="Maximum number of results to show in the search results dropdown"
|
||||
Type="Variable" Display="always-hide" Required="false"
|
||||
Mask="false" />
|
||||
<Config Name="Allow partial seasons?" Target="PARTIAL_SEASONS"
|
||||
Default="true" Mode=""
|
||||
Description="If set to false, force requests of entire series"
|
||||
Type="Variable" Display="always-hide" Required="false"
|
||||
Mask="false" />
|
||||
</Container>
|
||||
@@ -16,8 +16,7 @@
|
||||
; if the request type is a series
|
||||
(defn additional-options [result media-type]
|
||||
(a/go
|
||||
(let [type (impl/media-type media-type)
|
||||
details (a/<! (impl/details (:id result) type))
|
||||
(let [details (a/<! (impl/details (:id result) media-type))
|
||||
{:keys [partial-seasons]} env]
|
||||
(when (= media-type :series)
|
||||
(let [seasons (impl/seasons-list details)
|
||||
@@ -26,17 +25,40 @@
|
||||
(= 1 (count seasons)) (:id (first seasons))
|
||||
(false? partial-seasons) -1
|
||||
(false? backend-partial-seasons?) -1
|
||||
:else (impl/seasons-list details))})))))
|
||||
:else (impl/seasons-list details))
|
||||
:season-count (count seasons)})))))
|
||||
|
||||
(defn request-embed [{:keys [title id season]} media-type]
|
||||
(a/go
|
||||
(let [fourk (a/<! (impl/backend-4k? media-type))
|
||||
details (a/<! (impl/details id (impl/media-type media-type)))]
|
||||
details (a/<! (impl/details id media-type))]
|
||||
{:title title
|
||||
:overview (:overview details)
|
||||
:poster (str impl/poster-path (:poster-path details))
|
||||
:media-type media-type
|
||||
:request-formats [""]
|
||||
:request-formats (cond-> [""] fourk (conj "4K"))
|
||||
:season season})))
|
||||
|
||||
(defn request [payload])
|
||||
(defn request [payload media-type]
|
||||
(a/go
|
||||
(let [{:keys [format id season season-count discord-id]} payload
|
||||
{:keys [default-id]} env
|
||||
details (a/<! (impl/details id media-type))
|
||||
ovsr-id ((a/<! (impl/discord-users)) discord-id)
|
||||
status (impl/media-status details media-type
|
||||
:is-4k? (= format :4K)
|
||||
:season season)
|
||||
body (cond-> {:mediaType (impl/media-type media-type)
|
||||
:mediaId id
|
||||
:is4k (= format :4K)}
|
||||
(= :series media-type)
|
||||
(assoc :seasons
|
||||
(if (= -1 season)
|
||||
(into [] (range 1 (inc season-count)))
|
||||
[season])))]
|
||||
(cond
|
||||
(contains? #{:unauthorized :pending :processing :available} status) status
|
||||
(and (nil? ovsr-id) (nil? default-id)) :unauthorized
|
||||
:else (a/<! (impl/POST "/request" {:form-params body
|
||||
:content-type :json
|
||||
:headers {"X-API-User" (str (or ovsr-id default-id))}}))))))
|
||||
|
||||
@@ -43,9 +43,9 @@
|
||||
"tv"
|
||||
(name kw)))
|
||||
|
||||
(defn details [id media-type-str]
|
||||
(defn details [id media]
|
||||
(a/go
|
||||
(->> (a/<! (GET (str "/" media-type-str "/" id)))
|
||||
(->> (a/<! (GET (str "/" (media-type media) "/" id)))
|
||||
(then (comp :body utils/from-camel))
|
||||
(else #(fatal % "Error requesting details on selection from Overseerr")))))
|
||||
|
||||
@@ -61,8 +61,8 @@
|
||||
(defn backend-4k? [media]
|
||||
(a/go
|
||||
(->> (a/<! (GET (str "/settings/" (if (= (media-type media) "tv") "sonarr" "radarr"))))
|
||||
(then #(->> (:body %)
|
||||
(map :is4k)
|
||||
(then #(->> (utils/from-camel (:body %))
|
||||
(map :is-4k)
|
||||
(some identity)))
|
||||
(else #(fatal % "Exception on checking Overseeerr 4K backend support")))))
|
||||
|
||||
@@ -73,17 +73,29 @@
|
||||
:partial-requests-enabled))
|
||||
(else #(fatal % "Exception testing for partial seasons")))))
|
||||
|
||||
;;; NOT MODIFED YET
|
||||
(defn media-status [details media-type & {:keys [is-4k? season]}]
|
||||
(when-let [info (:media-info details)]
|
||||
(let [primary-status (status (dec ((if is-4k? :status-4k :status) info)))]
|
||||
(case media-type
|
||||
:movie primary-status
|
||||
:series (if (or (= -1 season)
|
||||
(not= primary-status :partially-available))
|
||||
primary-status
|
||||
(when-let [seasons (seq (:seasons info))]
|
||||
(status (dec ((if is-4k? :status-4k :status) (nth seasons (dec season)))))))))))
|
||||
|
||||
(defn num-users []
|
||||
(a/go
|
||||
(->> (a/<! (GET "/user" {:query-params {:take 1}}))
|
||||
(then #(s/select-one [:body :pageInfo :results] %))
|
||||
(then #(->> (utils/from-camel %)
|
||||
(s/select-one [:body :page-info :results])))
|
||||
(else #(fatal % "Exception on querying Overseerr users")))))
|
||||
|
||||
(defn all-users []
|
||||
(a/go
|
||||
(->> (a/<! (GET "/user" {:query-params {:take (a/<! (num-users))}}))
|
||||
(then #(->> (s/select-one [:body :results] %)
|
||||
(then #(->> (utils/from-camel %)
|
||||
(s/select-one [:body :results])
|
||||
(map :id)
|
||||
(into [])))
|
||||
(else #(fatal % "Exception on querying Overseerr users")))))
|
||||
@@ -91,7 +103,8 @@
|
||||
(defn discord-id [ovsr-id]
|
||||
(a/go
|
||||
(->> (a/<! (GET (str "/user/" ovsr-id)))
|
||||
(then #(s/select-one [:body :settings :discordId] %))
|
||||
(then #(->> (utils/from-camel %)
|
||||
(s/select-one [:body :settings :discord-id])))
|
||||
(else #(fatal % "Exception on querying Overseerr discord id")))))
|
||||
|
||||
(defn discord-users []
|
||||
@@ -101,45 +114,3 @@
|
||||
users
|
||||
(let [id (first ids)]
|
||||
(recur (rest ids) (assoc users (a/<! (discord-id id)) id))))))
|
||||
|
||||
(defn series-status [selection & {:keys [is4k]}]
|
||||
(when-let [info (:mediaInfo selection)]
|
||||
(status (dec ((if is4k :status4k :status) info)))))
|
||||
|
||||
(defn season-status [selection & {:keys [season is4k]}]
|
||||
(when-let [ss (series-status selection :is4k is4k)]
|
||||
(if (= ss :partially-available)
|
||||
(when-let [seasons (seq (:seasons (:mediaInfo selection)))]
|
||||
(status (dec ((if is4k :status4k :status) (nth seasons (dec season))))))
|
||||
ss)))
|
||||
|
||||
(defn movie-status [selection & {:keys [is4k]}]
|
||||
(when-let [info (:mediaInfo selection)]
|
||||
(status (dec ((if is4k :status4k :status) info)))))
|
||||
|
||||
(defn selection-to-request [selection & {:keys [season is4k]}]
|
||||
(cond-> {:mediaType (:mediaType selection)
|
||||
:mediaId (:id selection)
|
||||
:is4k is4k}
|
||||
(= "tv" (:mediaType selection)) (assoc :seasons (if (= -1 season)
|
||||
(into [] (range 1 (inc (:seasonCount selection))))
|
||||
[season]))))
|
||||
|
||||
(defn selection-to-embedable [selection]
|
||||
(as-> selection s
|
||||
(assoc s :seasonCount (:numberOfSeasons s))
|
||||
(assoc s :description (:overview s))
|
||||
(assoc s :remotePoster (str poster-path (:posterPath s)))))
|
||||
|
||||
(defn post-process-selection [selection]
|
||||
(a/go
|
||||
(let [details (a/<! (details selection))
|
||||
fourK-backend? (a/<! (backend-4k? (:mediaType selection)))]
|
||||
(selection-to-embedable (merge details selection {:backend-4k fourK-backend?})))))
|
||||
|
||||
(defn request [body & {:keys [ovsr-id]}]
|
||||
(a/go
|
||||
(->> (a/<! (POST "/request" {:form-params body
|
||||
:content-type :json
|
||||
:headers {"X-API-User" (str ovsr-id)}}))
|
||||
(then (constantly nil)))))
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
:request-formats [""]
|
||||
:quality-profile (utils/profile-id-name quality-profiles quality-profile-id)})))
|
||||
|
||||
(defn request [payload]
|
||||
(defn request [payload _]
|
||||
(a/go
|
||||
(let [status (impl/status (a/<! (impl/get-from-tmdb (:tmdb-id payload))))]
|
||||
(if status
|
||||
|
||||
@@ -59,7 +59,7 @@
|
||||
:quality-profile (:name (first (filter #(= quality-profile-id (:id %)) quality-profiles)))
|
||||
:language-profile (:name (first (filter #(= language-profile-id (:id %)) language-profiles)))})))
|
||||
|
||||
(defn request [payload]
|
||||
(defn request [payload _]
|
||||
(a/go (let [details (a/<! (if-let [id (:id payload)]
|
||||
(impl/get-from-id id)
|
||||
(impl/get-from-tvdb (:tvdb-id payload))))
|
||||
|
||||
@@ -8,23 +8,31 @@
|
||||
; Backend endpoints
|
||||
(spec/def :sonarr/url ::valid-url)
|
||||
(spec/def :radarr/url ::valid-url)
|
||||
(spec/def :overseerr/url ::valid-url)
|
||||
|
||||
; Backend API keys
|
||||
(spec/def :sonarr/api string?)
|
||||
(spec/def :radarr/api string?)
|
||||
(spec/def :overseerr/api string?)
|
||||
|
||||
; Discord bot token
|
||||
(spec/def :discord/token string?)
|
||||
|
||||
; Optional settings
|
||||
; --- Optional settings
|
||||
(spec/def :discord/role-id string?)
|
||||
(spec/def :discord/max-results #(and (pos-int? %)
|
||||
(<= % 25)))
|
||||
|
||||
; Radarr optionals
|
||||
(spec/def :radarr/quality-profile string?)
|
||||
(spec/def :sonarr/quality-profile string?)
|
||||
|
||||
; Sonarr optionals
|
||||
(spec/def :sonarr/language-profile string?)
|
||||
|
||||
; Overseerr optionals
|
||||
(spec/def :overseerr/default-id pos-int?)
|
||||
|
||||
(spec/def ::partial-seasons boolean?)
|
||||
|
||||
(defn when-req [pred spec]
|
||||
@@ -44,8 +52,11 @@
|
||||
:radarr/quality-profile
|
||||
:sonarr/quality-profile
|
||||
:sonarr/language-profile
|
||||
:overseerr/default-id
|
||||
::partial-seasons])
|
||||
#(some (partial contains? %) [:sonarr/url
|
||||
:radarr/url])
|
||||
:radarr/url
|
||||
:overseerr/url])
|
||||
(matched-keys :sonarr/url :sonarr/api)
|
||||
(matched-keys :radarr/url :radarr/api)))
|
||||
(matched-keys :radarr/url :radarr/api)
|
||||
(matched-keys :overseerr/url :overseerr/api)))
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
(let [results (->> (log-on-error
|
||||
(a/<! ((utils/media-fn media-type "search") query media-type))
|
||||
"Exception from search")
|
||||
(then #(->> (take (:max-results env 10) %)
|
||||
(then #(->> (take (:max-results env discord/MAX-OPTIONS) %)
|
||||
(into []))))]
|
||||
; Setup ttl cache entry
|
||||
(swap! state/cache assoc uuid {:results results
|
||||
@@ -91,18 +91,20 @@
|
||||
(swap! state/cache assoc-in [uuid :payload (keyword option)] selection)
|
||||
(query-for-option-or-request (get-in cache-val [uuid :pending-opts]) uuid)))
|
||||
|
||||
(defmethod process-event! "request" [_ _ uuid format]
|
||||
(defmethod process-event! "request" [_ interaction uuid format]
|
||||
(let [{:keys [messaging bot-id]} @state/discord
|
||||
{:keys [payload media-type token]} (get @state/cache uuid)]
|
||||
{:keys [payload media-type token]} (get @state/cache uuid)
|
||||
{:keys [user-id]} interaction]
|
||||
(letfn [(msg-resp [msg] (->> @(m/edit-original-interaction-response! messaging bot-id token (discord/content-response msg))
|
||||
(else #(fatal % "Error in message response"))))]
|
||||
(->> (log-on-error
|
||||
(a/<!! ((utils/media-fn media-type "request")
|
||||
(assoc payload :format (keyword format))))
|
||||
(assoc payload :format (keyword format) :discord-id user-id)
|
||||
media-type))
|
||||
"Exception from request")
|
||||
(then (fn [status]
|
||||
(case status
|
||||
:unauthorized (msg-resp "You do not have an associated account in the request backend")
|
||||
:unauthorized (msg-resp "You are unauthorized to perform this request in the configured backend")
|
||||
:pending (msg-resp "This has already been requested and the request is pending")
|
||||
:processing (msg-resp "This is currently processing and should be available soon!")
|
||||
:available (msg-resp "This selection is already available!")
|
||||
@@ -125,7 +127,7 @@
|
||||
(->> @(m/create-interaction-response! messaging id token 6)
|
||||
(else #(fatal % "Error sending response ack")))
|
||||
; Check last modified
|
||||
(let [{:keys [token last-modified]} (get @state/cache uuid)]
|
||||
(if-let [{:keys [token last-modified]} (get @state/cache uuid)]
|
||||
(if (> (- now last-modified) channel-timeout)
|
||||
; Update interaction with timeout message
|
||||
(->> @(m/edit-original-interaction-response! messaging bot-id token (discord/content-response "Request timed out, please try again"))
|
||||
@@ -133,4 +135,6 @@
|
||||
; Move through the state machine to update cache side effecting new components
|
||||
(do
|
||||
(swap! state/cache assoc-in [uuid :last-modified] now)
|
||||
(process-event! event interaction uuid option))))))
|
||||
(process-event! event interaction uuid option)))
|
||||
(->> @(m/edit-original-interaction-response! messaging bot-id token (discord/content-response "Request timed out, please try again"))
|
||||
(else #(fatal % "Error in sending timeout response"))))))
|
||||
|
||||
Reference in New Issue
Block a user