Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save HStep20/d6c5350bbcc12e40b1c9cdf7d9178c16 to your computer and use it in GitHub Desktop.
Save HStep20/d6c5350bbcc12e40b1c9cdf7d9178c16 to your computer and use it in GitHub Desktop.
This script will read your Overseer data and create/apply user tags to all of your sonarr/radarr instances
"""
This script will read your Overseer data and create/apply user tags to all of your sonarr/radarr instances, then create a filter in each connected -arr application for the users you specify.
It is forward compatible with the future User Tagging feature of overseer, and formats the tag in the same 'id - lowercase username' pattern Overseer will
It only uses built in python libraries, so you should be able to download and run without much hassle.
NOTE: YOU ARE REQUIRED TO USE IP:PORT CONNECTIONS FOR YOUR SONARR/RADARR INSTANCES INSIDE OF OVERSEERR
This will NOT utilize docker-compose style hostnames at the moment, and I don't use them personally, so I don't see myself adding them
Steps to use:
1. Add your Overseer API key
2. Add your Overseer Internal URL (might work with external domain url, but I didn't test)
2.5 Edit the Default values for number of users/requests
3. Press Play and wait
"""
import requests
from requests import HTTPError
from typing import Any
import re
import logging
from requests.models import Response
from urllib3.util import Retry
OVERSEER_API_KEY = "YOURAPIKEY"
OVERSEER_URL = "http://YOURIP:PORT/api/v1"
# I didn't want to figure out Pagination, so I set defaults to what I felt would be the maximum someone could have.
# If you have more than 100 users, or a user has more than 1000 requests, you'll need to update these values to reflect that
NUM_USERS_TO_PULL = 100
NUM_MAX_USER_REQUESTS = 1000
def handle_response(response: Response, *args: Any, **kwargs: Any) -> None:
"""Handles the Response and throws an error if there is an error with the request
Args:
response (Response): The response of the call being made
Raises:
requests.exceptions.HTTPError: Error raised by API
"""
try:
response.raise_for_status()
except requests.exceptions.HTTPError as e:
logger.error(
f"{response.status_code} - {response.request.url} - {response.text}"
)
raise requests.exceptions.HTTPError(f"{str(e)}: {response.text}") from e
def make_session(api_key: str) -> requests.Session:
"""Creates a Requests Session with headers and logging set up
Args:
api_key (str): API key of service being accessed with the session
Returns:
requests.Session: Requests session with overhead added
"""
session = requests.Session()
session.hooks["response"] = [handle_response]
adapter = requests.adapters.HTTPAdapter(
max_retries=Retry(
total=10,
backoff_factor=5,
status_forcelist=[429, 500],
allowed_methods=["GET", "POST", "PUT"],
)
)
session.mount("https://", adapter)
session.headers.update({"X-Api-Key": api_key})
return session
def map_arr_api_keys() -> dict[str, str]:
"""Gets all sonarr/radarr servers + api keys from Overseer and returns a map of them
Returns:
dict[str,str]: A Map of -arr server_urls : api_keys
"""
requests_session = make_session(api_key=OVERSEER_API_KEY)
sonarr_servers = requests_session.get(url=OVERSEER_URL + "/settings/sonarr").json()
radarr_servers = requests_session.get(url=OVERSEER_URL + "/settings/radarr").json()
all_servers = sonarr_servers + radarr_servers
api_key_map = {}
for server in all_servers:
api_key_map[server["hostname"] + ":" + str(server["port"])] = server["apiKey"]
return api_key_map
def tag_requests_from_user(
arr_api_key_map: dict[str, str],
user_requests: dict[str, Any],
user_tag_string: str,
) -> None:
"""Tags all the requests for each user
Args:
arr_api_key_map (dict[str, str]): Map of the server URL and API key for each service connected to overseer
user_requests (dict[str, Any]): list of user requests
user_tag_string (str): Formatted user tag name. Follows "id - lowercase username" format
"""
for media_request in user_requests:
try:
tag_request_from_user(
media_request=media_request,
arr_api_key_map=arr_api_key_map,
user_tag_string=user_tag_string,
)
except ValueError as e:
logger.error(e)
def tag_request_from_user(
media_request: dict[str, Any], arr_api_key_map: dict[str, str], user_tag_string: str
):
"""Reads request data from Overseer, and finds the media within Sonarr/Radarr, and applies a user tag to that item in its respective server
Args:
media_request (dict[str, Any]): The Media Request metadata provided by Overseerr API
arr_api_key_map (dict[str, str]): Map of all servers connected to Overseerr, and their API keys
user_tag_string (str): Formatted user tag name. Follows "id - lowercase username" format
"""
if media_request["status"] == 4:
raise ValueError(
f"{arr_object_data['title']} has ERROR request status - Skipping"
)
if "serviceUrl" not in media_request["media"]:
raise ValueError(
f"{arr_object_data['title']} has no ServiceURL associated with it - Skipping"
)
# Unfortunately the provided service URL doesn't include the /v3/api slug, so we have to build our own
non_api_url = media_request["media"]["serviceUrl"]
ip_port = re.findall(r"[0-9]+(?:\.[0-9]+){3}:[0-9]+", non_api_url)[0]
base_url = "http://" + ip_port
service_url = base_url + "/api/v3"
if media_request["type"] == "tv":
request_path = "/series"
request_params = {"tvdbId": media_request["media"]["tvdbId"]}
else:
request_path = "/movie"
request_params = {"tmdbId": media_request["media"]["tmdbId"]}
requests_session = make_session(api_key=arr_api_key_map[ip_port])
arr_object_data = requests_session.get(
url=service_url + request_path,
params=request_params,
).json()
if len(arr_object_data) == 0:
raise ValueError(
f"{base_url} - {media_request['media']['externalServiceSlug']} is in the user's request list, but not found on server - Skipping"
)
arr_object_data = arr_object_data[0]
tag_data = requests_session.get(
url=service_url + "/tag",
).json()
# Because each request has its own server associated with it, we should check for the tag each time.
# The alternate way would be to group by server, then do one check per server, but we don't need to worry about api calls here
tag_id = get_tag_id(tag_data, user_tag_string)
if tag_id == -1:
logger.warning(f'{base_url} - Tag "{user_tag_string}" not found in server.')
tag_creation_response = create_user_tag(
requests_session=requests_session,
service_url=service_url,
user_tag_string=user_tag_string,
)
if tag_creation_response.ok:
tag_id = tag_creation_response.json()["id"]
logger.info(f"{base_url} - Created tag {user_tag_string} with id: {tag_id}")
else:
raise HTTPError(f'{base_url} - Failed to create tag "{user_tag_string}"')
if tag_id in arr_object_data["tags"]:
logger.info(
f"{base_url} - {user_tag_string} - {arr_object_data['title']} already has user tag"
)
else:
tag_addition_response = tag_media_with_user_data(
requests_session=requests_session,
service_url=service_url,
request_path=request_path,
request_params=request_params,
arr_object_data=arr_object_data,
tag_id=tag_id,
)
if tag_addition_response.ok:
logger.info(
f"{base_url} - {user_tag_string} - Tagged {arr_object_data['title']}"
)
else:
raise HTTPError(tag_addition_response.text)
def get_tag_id(tag_data: dict[str, Any], user_tag_string: str) -> int:
"""Gets the tagId of the user's tag from the respective server.
Args:
tag_data (dict[str, Any]): The Tag Data from the -arr api
user_tag_string (str): The tag name for the current overseer user
Returns:
int: The tagId of the respective -arr instance. Returns -1 if it doesn't exist
"""
for tag in tag_data:
if tag["label"] == user_tag_string:
return tag["id"]
return -1
def create_user_tag(
requests_session: requests.Session,
service_url: str,
user_tag_string: str,
) -> dict[str, Any]:
"""Create a user tag in Sonarr/Radarr
Args:
requests_session (requests.Session): Requests session for app you are creating tag in
service_url (str): the URL of the app you are creating the tag in
user_tag_string (str): tag string, which will be the tag name
Returns:
dict[str, Any]: Tag creation return data, including new ID
"""
return requests_session.post(
url=service_url + "/tag",
json={"label": user_tag_string},
)
def tag_media_with_user_data(
requests_session: requests.Session,
service_url: str,
request_path: str,
request_params: dict[str, Any],
arr_object_data: dict[str, Any],
tag_id: int,
) -> requests.Response:
"""Applies tag to selected media object
Args:
requests_session (requests.Session): Requests session for app you are apply tag in
service_url (str): URL of app
request_path (str): Slug to interact with media object
request_params (dict[str, Any]): Extra request params to dictate the media object
arr_object_data (dict[str, Any]): Media Object metadata from Sonarr/Radarr
tag_id (int): Tag ID to apply to arr_object_data
Returns:
requests.Response: Response from tag call
"""
if tag_id not in arr_object_data["tags"]:
arr_object_data["tags"].append(tag_id)
return requests_session.put(
url=service_url + request_path,
params=request_params,
json=arr_object_data,
)
def create_tag_filter_in_application(
arr_api_key_map: dict[str, str], user_tag_string: str
):
"""Create a custom filter in each server for the user tag
Args:
arr_api_key_map (dict[str, str]): Map of -arr URLs:API Keys
user_tag_string (str): Tag Name for the current user
"""
for server in arr_api_key_map:
base_url = "http://" + server + "/api/v3"
requests_session = make_session(api_key=arr_api_key_map[server])
current_filters = requests_session.get(url=base_url + "/customfilter").json()
current_filter_labels = [x["label"] for x in current_filters]
if user_tag_string not in current_filter_labels:
tag_info = requests_session.get(url=base_url + "/tag").json()
tag_id = get_tag_id(tag_data=tag_info, user_tag_string=user_tag_string)
server_info = requests_session.get(url=base_url + "/system/status").json()
if server_info["appName"].lower() == "sonarr":
filter_type = "series"
else:
filter_type = "movieIndex"
sonarr_filter = {
"type": filter_type,
"label": user_tag_string,
"filters": [{"key": "tags", "value": [tag_id], "type": "contains"}],
}
requests_session.post(url=base_url + "/customfilter", json=sonarr_filter)
logger.info(f"http://{server} - {user_tag_string} - Created Filter")
else:
logger.warning(
f"http://{server} - {user_tag_string} - Filter Already Exists - Skipping"
)
def main():
arr_api_key_map = map_arr_api_keys()
overseer_requests_session = make_session(api_key=OVERSEER_API_KEY)
all_users = overseer_requests_session.get(
url=OVERSEER_URL + "/user", params={"take": NUM_USERS_TO_PULL}
).json()["results"]
for user in all_users:
user_data = overseer_requests_session.get(
url=OVERSEER_URL + f"/user/{user['id']}"
).json()
# My users don't have a ton of requests, so I didn't want to bother figuring out pagination.
# This should just pull all requests (unless you have users who request A TON)
user_requests = overseer_requests_session.get(
url=OVERSEER_URL + f"/user/{user['id']}/requests",
params={"take": NUM_MAX_USER_REQUESTS},
).json()["results"]
user_tag_string = (
str(user_data["id"]) + " - " + user_data["displayName"].lower()
)
separator = "\n==============================================\n"
print(
separator
+ f" Tagging {user_data['displayName']}'s Media"
+ separator
)
if len(user_requests) > 0:
tag_requests_from_user(arr_api_key_map, user_requests, user_tag_string)
create_tag_filter_in_application(arr_api_key_map, user_tag_string)
else:
logger.warning(f"{user['displayName']} has no requests - Skipping")
if __name__ == "__main__":
# create logger with 'spam_application'
logger = logging.getLogger("overseer_tagger")
logger.setLevel(logging.INFO)
fh = logging.StreamHandler()
fh.setLevel(logging.DEBUG)
ch = logging.StreamHandler()
ch.setLevel(logging.ERROR)
# create formatter and add it to the handlers
formatter = logging.Formatter(
"%(asctime)s - %(name)s - %(levelname)s - %(message)s"
)
fh.setFormatter(formatter)
ch.setFormatter(formatter)
# add the handlers to the logger
logger.addHandler(fh)
logger.addHandler(ch)
main()
@nemchik
Copy link

nemchik commented May 30, 2023

@nemchik You'll need the IPs in overseerr for this, I don't think it'll resolve container names in the current state, but at least that should catch you up with the rest of us now at the serviceUrl issue

That's not a viable solution. The IP addresses of my docker containers are dynamic. All of the rest of my applications play nice by talking to each other by hostname. All of my docker containers have a static hostname matching the name of the application, ex: overseerr, sonarr, and radarr. so in any given application, if I want an application to talk to another application and it's asking for an IP or hostname I use the application name rather than localhost or my server IP.

I could set them to static, but that requires additional config and there's many other reasons NOT to want to have to set static IP addresses on containers, but most of that is outside the scope of this discussion. I will indulge in one reason because it's one that I have first hand experience with over the past 2 weeks: Moving all of my applications from one system to another. I have multiple servers, all of them already running docker with various applications, but no duplicate applications across servers. I needed to decommission one of the servers, so I moved the docker compose stack and the data mounts to one of the other existing servers and the apps started right up as if nothing had changed. no changing configs, no reassigning IP addresses, no reconfiguring settings, it all just worked as if i simply restarted the service and nothing more. If I had to assign IP addresses to dozens of containers every time I needed to move a stack from one system to another (and then also reconfigure all the apps to talk to each other using new IP addresses) it would be a pretty big waste of time.

So I am at the mercy of those who wrote this script or know enough python to post a modification that would support hostnames instead of (or alongside) IP addresses.

@DanWillman
Copy link

That’s fair, but just to clarify I was suggesting to change it just to run this script, then change it back. I’m not sure if what you’re wanting is in scope for what HStep is up to here. Since it’s a one-time run script to get the databases up to date with the latest overseerr feature that does it going forward, a temporary change is probably easier since the short period of time you’d run this it’s unlikely any of your relevant container IPs would change.

But maybe it’ll get update, or an alternative that fits your use-case will surface. This has given me an idea that it might be nice to host something like this as a web page in a container alongside the others, which would make implementing host names easier than (I assume) adding discovery to this script. If I get around to it soon, I’ll share an update here

@HStep20
Copy link
Author

HStep20 commented May 30, 2023

Man, I wish Gists had a patchnotes section:

@Rustymage, I've added a check for those items that may show up without a ServiceURL

@DanWillman I updated the typing error (should have been a , not a | )

@nemchik - I unfortunately will not be supporting the use of Hostnames for this, as I don't use them myself. Since the ServiceURL doesn't include the /api/v3 slug for Sonarr/Radarr respectively, I am using the pattern of the IP:PORT in order to insert the slug myself so it can properly communicate with Sonarr/Radarr. Because Hostnames have no strict pattern, I don't want to attempt pattern matching with the URL, but I do believe that someone could pattern match with the existing slug to insert the API Path before the slug in the url - i.e. replace /movie in movie requests with /api/v3/movie, and /series in tv requests with /api/v3/series.

@nemchik
Copy link

nemchik commented May 30, 2023

No worries. Thanks for the script. I swapped temporarily to container IP addresses and it worked great!

@Rustymage
Copy link

Man, I wish Gists had a patchnotes section:

@Rustymage, I've added a check for those items that may show up without a ServiceURL

@DanWillman I updated the typing error (should have been a , not a | )

@nemchik - I unfortunately will not be supporting the use of Hostnames for this, as I don't use them myself. Since the ServiceURL doesn't include the /api/v3 slug for Sonarr/Radarr respectively, I am using the pattern of the IP:PORT in order to insert the slug myself so it can properly communicate with Sonarr/Radarr. Because Hostnames have no strict pattern, I don't want to attempt pattern matching with the URL, but I do believe that someone could pattern match with the existing slug to insert the API Path before the slug in the url - i.e. replace /movie in movie requests with /api/v3/movie, and /series in tv requests with /api/v3/series.

Worked like a dream once I manually checked for those missing shows/movies. As has been said, it's a once-and-done so no big deal and certainly a very useful script. I did notice once it was all tagged, I was the most prevalent requester non-watcher 🤔

Super pleased with the new tagging system and retrospective script you've crafted 🤝

@jakereed1
Copy link

Hi, I get this error when trying to run from my Ubuntu box. I have Python 2.7.18 installed. Any ideas please?

jake@vm-ubuntu:/var/docker/overseerr/scripts$ python script.py
File "script.py", line 38
def handle_response(response: Response, *args: Any, **kwargs: Any) -> None:
^
SyntaxError: invalid syntax

I have copied your entire script and only modified the API key / URL.
Cheers.

@HStep20
Copy link
Author

HStep20 commented Jun 3, 2023

Python 2 is way beyond deprecated at this point. I wrote it with python 3.10, so you should likely upgrade your version of python

@jakereed1
Copy link

jakereed1 commented Jun 3, 2023

Thanks. I've uninstalled 2.x and installed Python3.
I now get a different error. I am using IP addresses in the script and also in Overseerr. I can reach the API if I do a curl request.
I've checked the API key is correct.

jake@vm-ubuntu:/var/docker/overseerr/scripts$ curl http://10.20.10.41:5055/api/v1
{"api":"Overseerr API","version":"1.0"}
jake@vm-ubuntu:/var/docker/overseerr/scripts$ python3.10 script.py
Traceback (most recent call last):
File "/var/docker/overseerr/scripts/script.py", line 362, in
main()
File "/var/docker/overseerr/scripts/script.py", line 311, in main
arr_api_key_map = map_arr_api_keys()
File "/var/docker/overseerr/scripts/script.py", line 87, in map_arr_api_keys
requests_session = make_session(api_key=OVERSEER_API_KEY)
File "/var/docker/overseerr/scripts/script.py", line 68, in make_session
max_retries=Retry(
TypeError: Retry.init() got an unexpected keyword argument 'allowed_methods'

If I run it using Python3 instead of Python3.10 then I get the below. Don't worry if this error is specific to my box / python install etc, I'm happy to live without this script but it would be useful.

jake@vm-ubuntu:/var/docker/overseerr/scripts$ python3 script.py
Traceback (most recent call last):
File "script.py", line 81, in
def map_arr_api_keys() -> dict[str, str]:
TypeError: 'type' object is not subscriptable

@HStep20
Copy link
Author

HStep20 commented Jun 4, 2023

Is your urllib/requests libraries current and up to date? Thats the only thing I can think it may be, since your library is whats throwing the error, and not any of the code I wrote.

@jakereed1
Copy link

As far as I know everything is up to date, Plex is on the latest version, Overseerr on the latest version. I have run all the scheduled tasks in Plex and Overseerr. No worries, I will wait for Overseerr to release tagging and just have tags going forwards :). Thanks anyways.

@dlabrie
Copy link

dlabrie commented Jun 6, 2023

I'm using a private reverse proxy with https for my sonarr/radarr services.

I updated line 142-3 to this as well as replacing all http: instances to https: and it appears to work.

ip_port = re.findall(r"https?\:\/\/([a-zA-Z0-9\.]+:[0-9]+)?", non_api_url)[0] base_url = "https://" + ip_port

@HStep20
Copy link
Author

HStep20 commented Jun 6, 2023

Thanks for the work on that! Thats pretty easy to do, so I can add it in there and use it as a fallback check if no IP match is found.

The unsupported aspect will definitely be using something like docker-compose labels, as the script wouldn't be running in the docker network to talk to the hostnames directly.

@danshilm
Copy link

Just came across this and wanted to say that this is awesome! Thanks a lot for the work to make this @HStep20, I know it's something that users will find useful.
If you need any help/clarifications/insight with anything on Overseerr's side, feel free to tag me on Discord in the Overseerr server 😃

@NeeWii
Copy link

NeeWii commented Jul 17, 2023

Getting the same "out of range" error. Python version is 3.10.6. Overseerr is v1.33.0, and is configured to use the LAN IP (192.168...) and port combo for each service. I have overseerr set up with two movies instances (one normal, one 4k) and similar for TV. Would appreciate any help on how to troubleshoot, I'm very much a novice!

Traceback (most recent call last):
File "/home/username/Downloads/tag_sonarr_radarr_media_with_overseer_users.py", line 362, in
main()
File "/home/username/Downloads/tag_sonarr_radarr_media_with_overseer_users.py", line 338, in main
tag_requests_from_user(arr_api_key_map, user_requests, user_tag_string)
File "/home/username/Downloads/tag_sonarr_radarr_media_with_overseer_users.py", line 112, in tag_requests_from_user
tag_request_from_user(
File "/home/username/Downloads/tag_sonarr_radarr_media_with_overseer_users.py", line 142, in tag_request_from_user
ip_port = re.findall(r"[0-9]+(?:.[0-9]+){3}:[0-9]+", non_api_url)[0]
IndexError: list index out of range

@goodyear77
Copy link

goodyear77 commented Aug 13, 2023

EDIT: Nevermind, figured it out, leaving for others having the same issue. Just install the module request with the following command and the script then works:

# sudo pip3 install requests


Great tool! I've installed python 3.10 on my macOS Ventura, but when I try to run the script I get the following error:

# cd '' && '/usr/local/bin/python3'  '/Users/dwight/Downloads/overseerr_tags.py'  && echo Exit status: $? && exit 1
Traceback (most recent call last):
  File "/Users/dwight/Downloads/overseerr_tags.py", line 2, in <module>
    import requests
ModuleNotFoundError: No module named 'requests'

Same thing if I just run it from the directory where I've placed the script:

# python3 overseerr_tags.py 
Traceback (most recent call last):
  File "/Users/dwight/Downloads/overseerr_tags.py", line 2, in <module>
    import requests
ModuleNotFoundError: No module named 'requests'

Probably a super noob issue, any help would be appreciated!

@TheChrisK
Copy link

TheChrisK commented Feb 7, 2024

Getting the same "out of range" error. Python version is 3.10.6. Overseerr is v1.33.0, and is configured to use the LAN IP (192.168...) and port combo for each service. I have overseerr set up with two movies instances (one normal, one 4k) and similar for TV. Would appreciate any help on how to troubleshoot, I'm very much a novice!

Traceback (most recent call last):
File "/home/username/Downloads/tag_sonarr_radarr_media_with_overseer_users.py", line 362, in
main()
File "/home/username/Downloads/tag_sonarr_radarr_media_with_overseer_users.py", line 338, in main
tag_requests_from_user(arr_api_key_map, user_requests, user_tag_string)
File "/home/username/Downloads/tag_sonarr_radarr_media_with_overseer_users.py", line 112, in tag_requests_from_user
tag_request_from_user(
File "/home/username/Downloads/tag_sonarr_radarr_media_with_overseer_users.py", line 142, in tag_request_from_user
ip_port = re.findall(r"[0-9]+(?:.[0-9]+){3}:[0-9]+", non_api_url)[0]
IndexError: list index out of range

I am seeing the same error. I know this is old now and maybe out of date with Overseerr.

Edit: I see the issue is the "External URL" must be the IP and port. I thought it was the service URL. That fixed it and ran successfully. Thanks!

@mattague
Copy link

mattague commented Feb 9, 2024

I am having slightly different issue that I can't quite figure out

Traceback (most recent call last):
  File "/home/mattague/docker/tag.py", line 344, in <module>
    main()
  File "/home/mattague/docker/tag.py", line 320, in main
    tag_requests_from_user(arr_api_key_map, user_requests, user_tag_string)
  File "/home/mattague/docker/tag.py", line 102, in tag_requests_from_user
    tag_request_from_user(
  File "/home/mattague/docker/tag.py", line 167, in tag_request_from_user
    tag_id = tag_creation_response.json()["id"]
TypeError: list indices must be integers or slices, not str

@johnistheman
Copy link

I added some logging but I can't figure out why it's not working...

After changing the URLs in Overseerr to use IPs, it's able to connect to Overseerr/Sonarr/Radarr, but it fails to create tags in Radarr/Sonarr for some reason.

If I manually create the tags in Sonarr/Radarr, it doesn't fail, it carrys on as if it worked, but no tags are actually added to media in Sonarr/Radarr. It will return tags that are on the media after "adding" the new tag, but it still shows only the old tags (or no tags).

@PierreDurrr
Copy link

PierreDurrr commented Apr 24, 2024

And a different one here :

Traceback (most recent call last):
  File "/config/scripts/tag.py", line 362, in <module>
    main()
  File "/config/scripts/tag.py", line 338, in main
    tag_requests_from_user(arr_api_key_map, user_requests, user_tag_string)
  File "/config/scripts/tag.py", line 112, in tag_requests_from_user
    tag_request_from_user(
  File "/config/scripts/tag.py", line 133, in tag_request_from_user
    f"{arr_object_data['title']} has ERROR request status - Skipping"
UnboundLocalError: local variable 'arr_object_data' referenced before assignment```

@Shomesomesho
Copy link

Shomesomesho commented May 4, 2024

Trying to run this on Jellyseer. It successfully tags items for a couple users but then throws this error.

  File "/mnt/user/appdata/scripts/jellyseer.py", line 344, in <module>
    main()
  File "/mnt/user/appdata/scripts/jellyseer.py", line 320, in main
    tag_requests_from_user(arr_api_key_map, user_requests, user_tag_string)
  File "/mnt/user/appdata/scripts/jellyseer.py", line 102, in tag_requests_from_user
    tag_request_from_user(
  File "/mnt/user/appdata/scripts/jellyseer.py", line 126, in tag_request_from_user
    f"{arr_object_data['title']} has no ServiceURL associated with it - Skipping"
UnboundLocalError: local variable 'arr_object_data' referenced before assignment`

@jgramling17
Copy link

And a different one here :

Traceback (most recent call last):
  File "/config/scripts/tag.py", line 362, in <module>
    main()
  File "/config/scripts/tag.py", line 338, in main
    tag_requests_from_user(arr_api_key_map, user_requests, user_tag_string)
  File "/config/scripts/tag.py", line 112, in tag_requests_from_user
    tag_request_from_user(
  File "/config/scripts/tag.py", line 133, in tag_request_from_user
    f"{arr_object_data['title']} has ERROR request status - Skipping"
UnboundLocalError: local variable 'arr_object_data' referenced before assignment```

I found that this happens with the following types of requests.

  • Failed
  • Declined
  • Pending

This is probably because it's expecting every 'request' to be in sonarr/radarr and it's not when it's in within one of these statuses ^
Clean up your requests and re-run and this should fix.

@Shomesomesho
Copy link

Shomesomesho commented May 10, 2024

Appreciate the tip, but all I have for statuses on all requests are either "Requested" or "Available" and I still encounter this error.

Edit: I figured out my specific issue. I use Jellyseer and 2 instances of Sonarr. One for 1080p & one for 4K. Same situation for Radarr. The 4K requests were causing my errors. Removing those requests from Jellyseer allows the script to run to completion.

@crodgers89
Copy link

TL;DR version:
Is there a way to modify the script to only run for a specific user? (single user run) That or run the script for all except for a specific user? (multi-user with exclusions) I've narrowed it down to a specific user and cleaning up all his requests didn't fix it, but if I could skip him I think I'd get the majority of the rest to run and just manually fix his later.

Long Version:
I was getting the UnboundLocalError: local variable 'arr_object_data' referenced before assignment error too and the script would stop dead following the first request that triggered the error. I managed to get farther by adding global arr_object_data just before line 131, this didn't stop from getting an error for the first request still in "pending" status but it did make it so the script would at least skip to process the next user instead of stopping dead.

global arr_object_data #added
if media_request["status"] == 4:
    raise ValueError(
        f"{arr_object_data['title']} has ERROR request status - Skipping"
    )
if "serviceUrl" not in media_request["media"]:
    raise ValueError(
        f"{arr_object_data['title']} has no ServiceURL associated with it - Skipping"
    )

However now I'm getting a new error with the specific user where I cleaned up all the remaining requests by either accepting the pending requests or deleting the declined/failed requests. All his requests are now either Pending or Available. I started getting TypeError: argument of type 'NoneType' is not iterable and once again the script stops dead in it's tracks. If I shorten the max requests in line 35 to 11 requests it won't encounter the error and will continue to subsequent users but only processes the first 11 of theirs. If I increase it to 12 that 12th request is what errors out and kills the script which sucks 'cause he's only the 3rd user of 8.

Traceback (most recent call last):
  File "/docker_comp/tag_sonarr_radarr_media_with_overseer_users.py", line 363, in <module>
    main()
  File "/docker_comp/tag_sonarr_radarr_media_with_overseer_users.py", line 339, in main
    tag_requests_from_user(arr_api_key_map, user_requests, user_tag_string)
  File "/docker_comp/tag_sonarr_radarr_media_with_overseer_users.py", line 112, in tag_requests_from_user
    tag_request_from_user(
  File "/docker_comp/tag_sonarr_radarr_media_with_overseer_users.py", line 135, in tag_request_from_user
    if "serviceUrl" not in media_request["media"]:
TypeError: argument of type 'NoneType' is not iterable

@northirid
Copy link

northirid commented Jul 13, 2024

I'm late to the party, I know, but I'm getting an index out of range error similar to others.

==============================================
         Tagging northirid's Media
==============================================

Traceback (most recent call last):
  File "c:\Users\matt\OneDrive\Documents\Scripts\Python\OverseerTagger.py", line 362, in <module>
    main()
  File "c:\Users\matt\OneDrive\Documents\Scripts\Python\OverseerTagger.py", line 338, in main
    tag_requests_from_user(arr_api_key_map, user_requests, user_tag_string)
  File "c:\Users\matt\OneDrive\Documents\Scripts\Python\OverseerTagger.py", line 112, in tag_requests_from_user
    tag_request_from_user(
  File "c:\Users\matt\OneDrive\Documents\Scripts\Python\OverseerTagger.py", line 142, in tag_request_from_user
    ip_port = re.findall(r"[0-9]+(?:\.[0-9]+){3}:[0-9]+", non_api_url)[0]
              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^
IndexError: list index out of range

I currently have sonarr & radarr set up as IP:PORT in overseerr services, and set up overseerr with IP:PORT as the application URL. Still get that error though.

If I manually configure the ip_port (i.e. hardcode 192.168.x.x:8989) in there, it runs, but then errors out when it hits movies requests vs tv shows, as it's connecting to sonarr only.

Is there something that I can modify to have that list index work as intended?

@northirid
Copy link

I'm late to the party, I know, but I'm getting an index out of range error similar to others.

Is there something that I can modify to have that list index work as intended?

For anyone who comes after me:
The key thing to modify is in Overseerr -> Services -> Radarr/Sonarr -> external URL in that specific popup. That was the bit I was hung up on.

NGL, chatgpt helped by adding in this error handling.

    non_api_url = media_request["media"]["serviceUrl"]
    ip_port_matches = re.findall(r"[0-9]+(?:\.[0-9]+){3}:[0-9]+", non_api_url)
    if not ip_port_matches:
        raise ValueError(f"Service URL {non_api_url} does not contain a valid IP:PORT")
    ip_port = ip_port_matches[0]
    base_url = "http://" + ip_port
    service_url = base_url + "/api/v3"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment