kiranshila / Doplarr

An *arr request bot for Discord
MIT License
425 stars 30 forks source link

Overseerr Support #5

Closed kiranshila closed 2 years ago

kiranshila commented 3 years ago

Integrate Ombi/Overseer somehow

ghost commented 2 years ago

Ah shit mate, I think I figured it out 👍

I took my friends ID and put it into the admin account of Overseerr (marked as Owner in Overseerr) - and then it worked.

So I think there's something going on there 😄

kiranshila commented 2 years ago

...uhhh haha. I'll take a look. Something is up with https://github.com/kiranshila/Doplarr/blob/5c68b56c35be43e597b6600211ea28c4202920a2/src/doplarr/overseerr.clj#L34-L46 then

kiranshila commented 2 years ago

This seemed a little hacky anyway, but there wasn't an obvious way to connect the overseerr user account to the discord ID

ghost commented 2 years ago

Clojure code looks strange to me lol. But to clarify, the bot only seems to allow the ID/Discord-user associated with the owner account of the Overseerr instance if that makes sense.

kiranshila commented 2 years ago

@VP-EN I just tried a Local User and Plex User in overseerr and have not been able to reproduce the issue.

ghost commented 2 years ago

In my case, both of the users are imported from Plex and not local Overseerr users. Not sure if that makes any difference. I'll test it a bit later and see if I'm able to reproduce the error over and over and then report back

Would it matter if the OVERSEERR_URL is set to a remote host? The bot and the Overseerr are not hosted on the same server yet, in my case..

Is there any settings in Overseerr that I've missed? This seems weird.

Edit: I'll try a few different things, see if anything works lol.

This is what I've tried: 0) Launch Doplarr bot using:

docker run \
-e OVERSEERR_URL='https://overseerr.urlFromRemoteHost.com' \
-e OVERSEERR_API='OverseerrApiKey=' \
-e BOT_TOKEN='BotTokenFromDiscord' \
-e ROLE_ID='88XX504XXXX89407016' \
--name doplarr ghcr.io/kiranshila/doplarr:overseerr

The ROLE_ID is the role ID of CDR Bot in Discord. The CDR Bot role has no global permissions set, but only enabled a few permissions in one specific channel on Discord.

edited re-producing steps to actually reproduce the error: 1) Create 12 (or more) local profiles in Overseerr, give them these permissions: https://i.imgur.com/5xtbHVT.png

2) Create a new Discord account / Discord user, named dpzTEST

3) Invite dpzTEST user to server

4) Assign the same role as the bot (CDR Bot) to dpzTEST

5) Copy the ID of the user dpzTEST to the 12th (or above) local profile created

6) Press on save changes

7) Try and request a movie using /request movie {term}

8) Get the error message: You do not have an associated account on Overseerr

9) Remove the ID from the 12th (or above) local user, that you put in (from step 5), and press Save Changes

10) Add the ID from user dpzTEST to one of the 9 first users in Overseerr, and press Save Changes

11) You should now be able to request from the dpzTEST user in the Discord channel.

Is there anyone else able to test this? @onedr0p @cpt-kuesel

Bonus Info, cause why not: The Owner user in Overseerr has this URL: https://overseerr.urlFromRemoteHost.com/profile/settings/notifications/discord

while the local user has a quite different URL, and also a unique number https://overseerr.urlFromRemoteHost.com/users/66/settings/notifications/discord

Could that be related to the code bit from https://github.com/kiranshila/Doplarr/issues/5#issuecomment-922069915 ? It seems to look for the /user/ part of the URL, and somehow, in my case, it only works with the Owner user, which has no /user/ in the URL

Bonus Info pt2 Visiting: https://overseerr.urlFromRemoteHost.com/api/v1/user/1 shows the correct discordId of my personal Discord ID

Visiting: https://overseerr.urlFromRemoteHost.com/api/v1/user/66 shows the correct discordId of the dpzTEST user

Bonus Info pt3 Might be a stupid assumption, but could it be that these lines https://github.com/kiranshila/Doplarr/blob/5c68b56c35be43e597b6600211ea28c4202920a2/src/doplarr/overseerr.clj#L28-L32

tries to get all users, using GET "/user" - which only gets the first 10 hits/results? The user I'm testing is at the very bottom and only visible if you run https://overseerr.urlFromRemoteHost.com/api/v1/user?take=100

Bonus Info pt40243938 I think I fixed it, my stupid assumption seems to be somewhat correct I changed the line https://github.com/kiranshila/Doplarr/blob/5c68b56c35be43e597b6600211ea28c4202920a2/src/doplarr/overseerr.clj#L30 to (->> (a/<! (GET "/user?take=1000")) compiled it, and now the local user now works just fine

Not sure if anyone has over 1000 users in their Overseerr, or if there's a better solution than putting a specific number

cpt-kuesel commented 2 years ago

I will try and follow your steps. But I will probably only have time this evening. Will report back with my findings!

ghost commented 2 years ago

I'm not sure you need to - the issue lies in this part of the code https://github.com/kiranshila/Doplarr/blob/5c68b56c35be43e597b6600211ea28c4202920a2/src/doplarr/overseerr.clj#L28-L32

It uses the API call GET from /user, but by default that only gets the first 10 users.. So the issue will only appear if you have more than 10 users, and if the user in question will be the 11th (or later) user. By forcing it to get more users, like /user?take=1000, it'll actually work as intended 👍 So it works if you edit that line, and (re)compile it. So I think it's up to @kiranshila to fix it 😄

But thanks anyways 👍

kiranshila commented 2 years ago

Awesome detective work @VP-EN! Nowhere in the docs could I find that the users endpoint is paged as well. The docs clearly say get "all" users.

Great find. I'll get it fixed.

ghost commented 2 years ago

Awesome detective work @VP-EN! Nowhere in the docs could I find that the users endpoint is paged as well. The docs clearly say get "all" users.

Great find. I'll get it fixed.

Yeah, thanks. Found it by doing the https://overseerr.urlremote.com/api/v1/user and only saw 10 results. Then it clicked lol. And thanks to you too :D

Anyways, I'm not sure if I asked this - is there any way for you to implement an option, where IF a user requests some movie/series in 4K, that's not already present in non-4K, it'll request both of them? e.g. if a user requests "Die Hard" in 4K, and it's not already present on Plex, in either 4K or not, it'll send a request to both "profiles" (4K + Non-4K)?

Edit Ah wait think I requested it here https://github.com/kiranshila/Doplarr/issues/5#issuecomment-908545271 . Should I create a separate GitHub issue?

kiranshila commented 2 years ago

Yeah I'd say make a separate issue.

@VP-EN https://overseerr.urlFromRemoteHost.com/api/v1/user/66 shows the correct discordId of the dpzTEST user

This is genius. Absolutely nowhere in the API docs does it say that the user endpoint returns the discord id. That makes everything so much simpler, I don't have to do this whole get all users thing. https://api-docs.overseerr.dev/#/users/get_user__userId_

It doesn't even mention the settings block.

kiranshila commented 2 years ago

Yeah all of that code is now

(defn discord-id [ovsr-id]
  (a/go
    (->> (a/<! (GET (str "/user/" ovsr-id)))
         (then #(s/select-one [:body :settings :discordId] %))
         (else utils/fatal-error))))

So much better.

EDIT: Wait I lied. I forgot that at this point we only have the discord id, we don't have an immediate way of mapping from discord id to overseerr id, only the other way

kiranshila commented 2 years ago

Lol requestrr does exactly what you proposed @VP-EN

https://github.com/darkalfx/requestrr/blob/7c42356bcd1855e8d4fab2f18c3d0bd854f5a720/Requestrr.WebApi/RequestrrBot/DownloadClients/Overseerr/OverseerrClient.cs#L351

kiranshila commented 2 years ago

Alright here is my solution, ping the endpoint once to find number of ids, then get all of them. That's the only way without having a magic maximum users number

(defn num-users []
  (a/go
    (->> (a/<! (GET "/user" {:query-params {:take 1}}))
         (then #(s/select-one [:body :pageInfo :results] %))
         (else utils/fatal-error))))

(defn all-users []
  (a/go
    (->> (a/<! (GET "/user" {:query-params {:take (a/<! (num-users))}}))
         (then #(->> (s/select-one [:body :results] %)
                     (map :id)
                     (into [])))
         (else utils/fatal-error))))
ghost commented 2 years ago

I think that's good 😄

kiranshila commented 2 years ago

Alright, give the most recent docker image on this branch a try - should have a message on the thing being requested, and fix the number of users error.

ghost commented 2 years ago

I'll test in a short while 👍 I'll edit this post with the "results"

Edit: Sorry don't have time for much more than this What works:

Issues:

kiranshila commented 2 years ago

Very helpful! Thank you!

Yeah the requested deal has a few more states that I missed. I also noticed that things currently downloading also show up as un-requested.

kiranshila commented 2 years ago

@VP-EN I found the bug that was causing the those issues, I forgot to pass in the bool that checks if the request was 4K. I also overhauled the response messages for the different media states. I'm finishing up these patches, I basically rewrote all the interaction flow control so now there is a single state machine that controls it, making the whole thing easier to modify instead of a ton of nested ifs.

ghost commented 2 years ago

Sounds awesome 😁

kiranshila commented 2 years ago

Ok whenever y'all get a chance, please give the latest build a try

ghost commented 2 years ago

Finally had a few minutes to test it @kiranshila.

Tried the latest build as of the time of this post: What works:

Issues:

Logs from the docker (nothing interesting I think): https://0bin.net/paste/amJW0Tvh#1jJLbae8rfAHkdwWldo1xQqtuTR14ZFeNXDtGodjFjd

kiranshila commented 2 years ago

@VP-EN Found the bugs, should be squashed now

ghost commented 2 years ago

Seems to work perfectly now 😄 I can atleast request movies now quite seamlessly.

One issue though.. In Overseerr, there's a setting called Allow Partial Series Requests. This bot doesn't really "respect" that choice, since using /request series {term} will ask what season you want to request, and is able to circumvent this limitation set by the user in Overseerr settings.

It would be nice if the bot would check Overseerr if Allow Partial Series Requests is enabled or not: https://i.imgur.com/7O2j0nf.png

If it's enabled, then it should be possible to request individual seasons, and if's it's disabled (not allowed) the only option should be All Seasons when requesting a series.

kiranshila commented 2 years ago

Oh interesting - didn't even know that option existed. Should be easy enough to implement.

kiranshila commented 2 years ago

I'm surprised Overseerr doesn't throw an error when requesting partial seasons

ghost commented 2 years ago

I'm surprised Overseerr doesn't throw an error when requesting partial seasons

Yeah kinda thought that would happen as well. Tested it, just in case lol 👍

Also, not sure if this is relevant and/or avoidable, but the docker instance crashes when a user inputs a imdb url.. I'm guessing it crashes when other urls are put in.. Haven't tested it that much, just did it by accident

kiranshila commented 2 years ago

As like a query term?

ghost commented 2 years ago

Yeah. Ah wait nvm, it doesn't happen anymore lol

kiranshila commented 2 years ago

Buh? Was there an error?

ghost commented 2 years ago

Nah, not even. I must've tried on a previous version, where it happened. Now when I put in a url, nothing seems to happen, other than a "no results" message. My mistake sorry 😄

ghost commented 2 years ago

also just tested your latest commit, it does seem to register that I've disabled the Allow Partial Series Requests and now only gives me the choice of All Seasons 👍 I don't think I have anything more to add for now lol, other than my other feature request 😉

Woops, had this happen, requesting a series in 4K (new series requested first on regular profile - went fine - then tried Request 4K, this came up in the logs)

Exception in thread "async-dispatch-1" java.lang.NullPointerException
        at clojure.lang.Numbers.ops(Numbers.java:1068)
        at clojure.lang.Numbers.dec(Numbers.java:145)
        at doplarr.overseerr$season_status.invokeStatic(overseerr.clj:99)
        at doplarr.overseerr$season_status.doInvoke(overseerr.clj:96)
        at clojure.lang.RestFn.invoke(RestFn.java:486)
        at doplarr.interaction_state_machine$fn__19758$fn__19826$state_machine__                                                                             8301__auto____19833$fn__19835.invoke(interaction_state_machine.clj:108)
        at doplarr.interaction_state_machine$fn__19758$fn__19826$state_machine__                                                                             8301__auto____19833.invoke(interaction_state_machine.clj:102)
        at clojure.core.async.impl.ioc_macros$run_state_machine.invokeStatic(ioc                                                                             _macros.clj:978)
        at clojure.core.async.impl.ioc_macros$run_state_machine.invoke(ioc_macro                                                                             s.clj:977)
        at clojure.core.async.impl.ioc_macros$run_state_machine_wrapped.invokeSt                                                                             atic(ioc_macros.clj:982)
        at clojure.core.async.impl.ioc_macros$run_state_machine_wrapped.invoke(i                                                                             oc_macros.clj:980)
        at clojure.core.async.impl.ioc_macros$take_BANG_$fn__8319.invoke(ioc_mac                                                                             ros.clj:991)
        at clojure.core.async.impl.channels.ManyToManyChannel$fn__3152$fn__3153.                                                                             invoke(channels.clj:95)
        at clojure.lang.AFn.run(AFn.java:22)
        at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown S                                                                             ource)
        at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown                                                                              Source)
        at clojure.core.async.impl.concurrent$counted_thread_factory$reify__3055                                                                             $fn__3056.invoke(concurrent.clj:29)
        at clojure.lang.AFn.run(AFn.java:22)
        at java.base/java.lang.Thread.run(Unknown Source)
Exception in thread "async-dispatch-5" java.lang.NullPointerException
        at clojure.lang.Numbers.ops(Numbers.java:1068)
        at clojure.lang.Numbers.dec(Numbers.java:145)
        at doplarr.overseerr$season_status.invokeStatic(overseerr.clj:99)
        at doplarr.overseerr$season_status.doInvoke(overseerr.clj:96)
        at clojure.lang.RestFn.invoke(RestFn.java:486)
        at doplarr.interaction_state_machine$fn__19758$fn__19826$state_machine__                                                                             8301__auto____19833$fn__19835.invoke(interaction_state_machine.clj:108)
        at doplarr.interaction_state_machine$fn__19758$fn__19826$state_machine__                                                                             8301__auto____19833.invoke(interaction_state_machine.clj:102)
        at clojure.core.async.impl.ioc_macros$run_state_machine.invokeStatic(ioc                                                                             _macros.clj:978)
        at clojure.core.async.impl.ioc_macros$run_state_machine.invoke(ioc_macro                                                                             s.clj:977)
        at clojure.core.async.impl.ioc_macros$run_state_machine_wrapped.invokeSt                                                                             atic(ioc_macros.clj:982)
        at clojure.core.async.impl.ioc_macros$run_state_machine_wrapped.invoke(i                                                                             oc_macros.clj:980)
        at clojure.core.async.impl.ioc_macros$take_BANG_$fn__8319.invoke(ioc_mac                                                                             ros.clj:991)
        at clojure.core.async.impl.channels.ManyToManyChannel$fn__3152$fn__3153.                                                                             invoke(channels.clj:95)
        at clojure.lang.AFn.run(AFn.java:22)
        at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown S                                                                             ource)
        at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown                                                                              Source)
        at clojure.core.async.impl.concurrent$counted_thread_factory$reify__3055                                                                             $fn__3056.invoke(concurrent.clj:29)
        at clojure.lang.AFn.run(AFn.java:22)
        at java.base/java.lang.Thread.run(Unknown Source)
Exception in thread "async-dispatch-3" java.lang.NullPointerException
        at clojure.lang.Numbers.ops(Numbers.java:1068)
        at clojure.lang.Numbers.dec(Numbers.java:145)
        at doplarr.overseerr$season_status.invokeStatic(overseerr.clj:99)
        at doplarr.overseerr$season_status.doInvoke(overseerr.clj:96)
        at clojure.lang.RestFn.invoke(RestFn.java:486)
        at doplarr.interaction_state_machine$fn__19758$fn__19826$state_machine__8301__auto____19833$fn__19835.invoke(interaction_state_machine.clj:108)
        at doplarr.interaction_state_machine$fn__19758$fn__19826$state_machine__8301__auto____19833.invoke(interaction_state_machine.clj:102)
        at clojure.core.async.impl.ioc_macros$run_state_machine.invokeStatic(ioc_macros.clj:978)
        at clojure.core.async.impl.ioc_macros$run_state_machine.invoke(ioc_macros.clj:977)
        at clojure.core.async.impl.ioc_macros$run_state_machine_wrapped.invokeStatic(ioc_macros.clj:982)
        at clojure.core.async.impl.ioc_macros$run_state_machine_wrapped.invoke(ioc_macros.clj:980)
        at clojure.core.async.impl.ioc_macros$take_BANG_$fn__8319.invoke(ioc_macros.clj:991)
        at clojure.core.async.impl.channels.ManyToManyChannel$fn__3152$fn__3153.invoke(channels.clj:95)
        at clojure.lang.AFn.run(AFn.java:22)
        at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
        at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
        at clojure.core.async.impl.concurrent$counted_thread_factory$reify__3055$fn__3056.invoke(concurrent.clj:29)
        at clojure.lang.AFn.run(AFn.java:22)
        at java.base/java.lang.Thread.run(Unknown Source)
kiranshila commented 2 years ago

Yeah I just have it skip the season selection altogether if that is set.

I broke something in the direct sonarr backend though, I'm going to fix that, go through the tests again to make sure it's all still working, then publish.

kiranshila commented 2 years ago

Alright @VP-EN I fixed that NPE

ghost commented 2 years ago

Hey @kiranshila

Tried the latest commit as of this post

Tried requesting series again, and it says it has been requested. But looking in Overseerr, there's nothing there.

E.g.

A bit different output from the logs this time: https://0bin.net/paste/HbQjWEST#YvGXqe9O5a3Q9Ihch1KNh+dP0+k-ZitrN/j1nw7ZiCE

Tried removing the docker instance + image, and re-tried:

kiranshila commented 2 years ago

Yeah sorry, I was messing with the build and went to bed. There is a bug in the newer 1.11.0-alpha2 clojure - going back to alpha1 fixes parts of it. Also found a bug in the finding the status of a new series. Working it now.

kiranshila commented 2 years ago

Alright @VP-EN I think I got that sorted. How many bugs could there possibly be haha

ghost commented 2 years ago

Tested latest commit as of this post

Requesting seems to work, except this weird error:

kiranshila commented 2 years ago

@VP-EN That is on purpose! The Requested! message is the end of the request interaction while the This has been requested message is non-ephemeral (everyone on the channel can see it) to let everyone know something has been requested

ghost commented 2 years ago

Ah alright :D Then I think it works. I'll do a movie test in a few minutes, and update this post

Movie requests also work. So I guess it's all good, atleast from my short amount of testing 👍

kiranshila commented 2 years ago

I wouldn't say short - we've been at this a while haha!

ghost commented 2 years ago

Haha yeah true 😆

kiranshila commented 2 years ago

I'm going to do a few last tests with the direct api then publish the release!

AstralDestiny commented 2 years ago

So not sure what I did wrong when trying it out but I don't see any slash commands not even greyed out stuff either. Bot is online but not seeing commands nor is it responding to any commands, I'm using it in container with docker.

kiranshila commented 2 years ago

@AstralDestiny Sounds like a bot permissions issue

AstralDestiny commented 2 years ago

It has the Admin one that grants it all permissions so I only assume that's not an issue in the end image

kiranshila commented 2 years ago

@AstralDestiny I wouldn't give the bot admin - as it mentions there, that is generally not a good idea. What matters here is the OAuth2 permissions (how you invited the bot to your server). The important ones being applications.commands and bot

You shouldn't need to give the bot any permissions from the Bot tab - those don't impact slash commands.

kiranshila commented 2 years ago

So when you invite the bot to your server it will say something like

image

AstralDestiny commented 2 years ago

Yeah I missed the part about OAuth my bad. Ahaha

Sorry about that. I went through the steps of adding the bot via permissions and such and like didn't even notice I was supposed to do it via OAuth flow part.

AstralDestiny commented 2 years ago

I'm just laughing at myself I was blind.