-
Notifications
You must be signed in to change notification settings - Fork 14
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* added AMiABLE and PiGNUS to scene groups (#25) * Major Code Refactor (#27) Description Reimplemented Profilarr from the ground up to be more reusable, in addition to implemented a few improvements. Works almost identically to before, but will be much easier to develop for going forward. Improvements Implements feature mentioned in Issue #11. - Custom Formats are now automatically imported before quality profiles are imported. * fixed 2160 remux bug (#28) Fixed bug that was incorrectly prioritising WEBs in 2160p optimal profiles.
- Loading branch information
1 parent
2e5cabe
commit ff79de7
Showing
33 changed files
with
1,151 additions
and
862 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,164 +1,101 @@ | ||
import requests | ||
import os | ||
import yaml | ||
import json | ||
|
||
# ANSI escape sequences for colors | ||
class Colors: | ||
HEADER = '\033[95m' # Purple for questions and headers | ||
OKBLUE = '\033[94m' # Blue for actions | ||
OKGREEN = '\033[92m' # Green for success messages | ||
FAIL = '\033[91m' # Red for error messages | ||
ENDC = '\033[0m' # Reset to default | ||
BOLD = '\033[1m' | ||
UNDERLINE = '\033[4m' | ||
|
||
# Load configuration for main app | ||
with open('config.yml', 'r') as config_file: | ||
config = yaml.safe_load(config_file) | ||
master_config = config['instances']['master'] | ||
|
||
def print_success(message): | ||
print(Colors.OKGREEN + message + Colors.ENDC) | ||
|
||
def print_error(message): | ||
print(Colors.FAIL + message + Colors.ENDC) | ||
|
||
def print_connection_error(): | ||
print(Colors.FAIL + "Failed to connect to the service! Please check if it's running and accessible." + Colors.ENDC) | ||
|
||
def get_user_choice(): | ||
print(Colors.HEADER + "\nAvailable instances to delete from:" + Colors.ENDC) | ||
sources = [] | ||
|
||
# Add master installations | ||
for app in master_config: | ||
sources.append((app, f"{app.capitalize()} [Master]")) | ||
|
||
# Add extra installations | ||
if "extras" in config['instances']: | ||
for app, instances in config['instances']['extras'].items(): | ||
for install in instances: | ||
sources.append((app, f"{app.capitalize()} [{install['name']}]")) | ||
|
||
# Display sources with numbers | ||
for idx, (app, name) in enumerate(sources, start=1): | ||
print(f"{idx}. {name}") | ||
|
||
# User selection | ||
choice = input(Colors.HEADER + "Enter the number of the instance to delete from: " + Colors.ENDC).strip() | ||
while not choice.isdigit() or int(choice) < 1 or int(choice) > len(sources): | ||
print_error("Invalid input. Please enter a valid number.") | ||
choice = input(Colors.HEADER + "Enter the number of the instance to delete from: " + Colors.ENDC).strip() | ||
|
||
selected_app, selected_name = sources[int(choice) - 1] | ||
print() | ||
return selected_app, selected_name | ||
from helpers import * | ||
|
||
def user_select_items_to_delete(items): | ||
print(Colors.HEADER + "\nAvailable items:" + Colors.ENDC) | ||
for idx, item in enumerate(items, start=1): | ||
print(f"{idx}. {item['name']}") | ||
print(Colors.HEADER + "Type the number(s) of the items you wish to delete separated by commas, or type 'all' to delete everything." + Colors.ENDC) | ||
|
||
selection = input(Colors.HEADER + "Your choice: " + Colors.ENDC).strip().lower() | ||
if selection == 'all': | ||
return [item['id'] for item in items] # Return all IDs if "all" is selected | ||
""" | ||
Prompts the user to select items to delete from a given list of items. | ||
Each item in the list is expected to be a dictionary with at least an 'id' and 'name' key. | ||
""" | ||
print_message("Available items to delete:", "purple") | ||
for index, item in enumerate(items, start=1): | ||
print_message(f"{index}. {item['name']}", "green") | ||
|
||
print_message("Enter the number(s) of the items you wish to delete, separated by commas, or type 'all' for all:", "yellow") | ||
user_input = input("Your choice: ").strip().lower() | ||
selected_items = [] | ||
|
||
if user_input == 'all': | ||
return items | ||
else: | ||
selected_ids = [] | ||
try: | ||
selected_indices = [int(i) - 1 for i in selection.split(',') if i.isdigit()] | ||
for idx in selected_indices: | ||
if idx < len(items): | ||
selected_ids.append(items[idx]['id']) | ||
return selected_ids | ||
except ValueError: | ||
print_error("Invalid input. Please enter a valid number or 'all'.") | ||
return [] | ||
|
||
def delete_custom_formats(source_config): | ||
print(Colors.OKBLUE + "\nDeleting selected custom formats..." + Colors.ENDC) | ||
headers = {"X-Api-Key": source_config['api_key']} | ||
get_url = f"{source_config['base_url']}/api/v3/customformat" | ||
|
||
try: | ||
response = requests.get(get_url, headers=headers) | ||
if response.status_code == 200: | ||
formats_to_delete = response.json() | ||
selected_ids = user_select_items_to_delete(formats_to_delete) | ||
|
||
for format_id in selected_ids: | ||
delete_url = f"{get_url}/{format_id}" | ||
del_response = requests.delete(delete_url, headers=headers) | ||
format_name = next((item['name'] for item in formats_to_delete if item['id'] == format_id), "Unknown") | ||
if del_response.status_code in [200, 202, 204]: | ||
print(Colors.OKBLUE + f"Deleting custom format '{format_name}': " + Colors.ENDC + Colors.OKGREEN + "SUCCESS" + Colors.ENDC) | ||
indices = user_input.split(',') | ||
for index in indices: | ||
try: | ||
index = int(index.strip()) - 1 | ||
if 0 <= index < len(items): | ||
selected_items.append(items[index]) | ||
else: | ||
print(Colors.OKBLUE + f"Deleting custom format '{format_name}': " + Colors.ENDC + Colors.FAIL + "FAIL" + Colors.ENDC) | ||
else: | ||
print_error("Failed to retrieve custom formats for deletion!") | ||
except requests.exceptions.ConnectionError: | ||
print_connection_error() | ||
|
||
def delete_quality_profiles(source_config): | ||
print(Colors.OKBLUE + "\nDeleting selected quality profiles..." + Colors.ENDC) | ||
headers = {"X-Api-Key": source_config['api_key']} | ||
get_url = f"{source_config['base_url']}/api/v3/qualityprofile" | ||
|
||
try: | ||
response = requests.get(get_url, headers=headers) | ||
if response.status_code == 200: | ||
profiles_to_delete = response.json() | ||
selected_ids = user_select_items_to_delete(profiles_to_delete) | ||
|
||
for profile_id in selected_ids: | ||
delete_url = f"{get_url}/{profile_id}" | ||
del_response = requests.delete(delete_url, headers=headers) | ||
profile_name = next((item['name'] for item in profiles_to_delete if item['id'] == profile_id), "Unknown") | ||
if del_response.status_code in [200, 202, 204]: | ||
print(Colors.OKBLUE + f"Deleting quality profile '{profile_name}': " + Colors.ENDC + Colors.OKGREEN + "SUCCESS" + Colors.ENDC) | ||
else: | ||
# Handle failure due to the profile being in use or other errors | ||
error_message = "Failed to delete due to an unknown error." | ||
try: | ||
# Attempt to parse JSON error message from response | ||
error_details = del_response.json() | ||
if 'message' in error_details: | ||
error_message = error_details['message'] | ||
elif 'error' in error_details: | ||
error_message = error_details['error'] | ||
except json.JSONDecodeError: | ||
# If response is not JSON or doesn't have expected fields | ||
error_message = del_response.text or "Failed to delete with no detailed error message." | ||
|
||
print(Colors.OKBLUE + f"Deleting quality profile '{profile_name}': " + Colors.ENDC + Colors.FAIL + f"FAIL - {error_message}" + Colors.ENDC) | ||
print_message("Invalid selection, ignoring.", "red") | ||
except ValueError: | ||
print_message("Invalid input, please enter numbers only.", "red") | ||
|
||
return selected_items | ||
|
||
|
||
def prompt_export_choice(): | ||
""" | ||
Prompt user to choose between exporting Custom Formats, Quality Profiles, or both. | ||
Returns a list of choices. | ||
""" | ||
print_message("Please select what you want to delete:", "blue") | ||
options = {"1": "Custom Formats", "2": "Quality Profiles", "3": "Both"} | ||
for key, value in options.items(): | ||
print_message(f"{key}. {value}", "green") | ||
choice = input("Enter your choice: ").strip() | ||
|
||
# Validate choice | ||
while choice not in options: | ||
print_message("Invalid choice, please select a valid option:", "red") | ||
choice = input("Enter your choice: ").strip() | ||
|
||
if choice == "3": | ||
return ["Custom Formats", "Quality Profiles"] | ||
else: | ||
return [options[choice]] | ||
|
||
def delete_custom_formats_or_profiles(app, instance, item_type, config): | ||
""" | ||
Deletes either custom formats or quality profiles based on the item_type. | ||
""" | ||
api_key = instance['api_key'] | ||
base_url = instance['base_url'] | ||
resource_type = item_type # 'customformat' or 'qualityprofile' | ||
|
||
if item_type == 'customformat': | ||
type = 'Custom Format' | ||
elif item_type == 'qualityprofile': | ||
type = 'Quality Profile' | ||
|
||
# Fetch items to delete | ||
items = make_request('get', base_url, api_key, resource_type) | ||
if items is None or not isinstance(items, list): | ||
return | ||
|
||
# Assuming a function to select items to delete. It could list items and ask the user which to delete. | ||
items_to_delete = user_select_items_to_delete(items) # This needs to be implemented or adapted | ||
|
||
# Proceed to delete selected items | ||
for item in items_to_delete: | ||
item_id = item['id'] | ||
item_name = item['name'] | ||
print_message(f"Deleting {type} ({item_name})", "blue", newline=False) | ||
response = make_request('delete', base_url, api_key, f"{resource_type}/{item_id}") | ||
if response in [200, 202, 204]: | ||
print_message(" : SUCCESS", "green") | ||
else: | ||
print_error("Failed to retrieve quality profiles for deletion!") | ||
except requests.exceptions.ConnectionError: | ||
print_connection_error() | ||
print_message(" : FAIL", "red") | ||
|
||
def main(): | ||
app = get_app_choice() | ||
instances = get_instance_choice(app) | ||
config = load_config() | ||
|
||
choices = prompt_export_choice() | ||
for instance in instances: | ||
for choice in choices: | ||
if choice == "Custom Formats": | ||
delete_custom_formats_or_profiles(app, instance, 'customformat', config) | ||
elif choice == "Quality Profiles": | ||
delete_custom_formats_or_profiles(app, instance, 'qualityprofile', config) | ||
|
||
def get_app_config(app_name, instance_name): | ||
if instance_name.endswith("[Master]"): | ||
return master_config[app_name] | ||
else: | ||
instance_name = instance_name.replace(f"{app_name.capitalize()} [", "").replace("]", "") | ||
extras = config['instances']['extras'].get(app_name, []) | ||
for instance in extras: | ||
if instance['name'] == instance_name: | ||
return instance | ||
raise ValueError(f"Configuration for {app_name} - {instance_name} not found.") | ||
|
||
if __name__ == "__main__": | ||
selected_app, selected_instance = get_user_choice() | ||
source_config = get_app_config(selected_app, selected_instance) | ||
source_config['app_name'] = selected_app | ||
|
||
print(Colors.HEADER + "\nChoose what to delete:" + Colors.ENDC) | ||
print("1. Custom Formats") | ||
print("2. Quality Profiles") | ||
choice = input(Colors.HEADER + "Enter your choice (1/2): " + Colors.ENDC).strip() | ||
|
||
if choice == "1": | ||
delete_custom_formats(source_config) | ||
elif choice == "2": | ||
delete_quality_profiles(source_config) | ||
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,23 +1,37 @@ | ||
version: "3.3" | ||
|
||
x-common-settings: &common-settings | ||
environment: | ||
PUID: 1000 # user id, change as necessary | ||
PGID: 1000 # group id, change as necessary | ||
TZ: Europe/London # timezone, change as necessary | ||
restart: unless-stopped | ||
|
||
services: | ||
radarr: | ||
<<: *common-settings | ||
image: linuxserver/radarr | ||
container_name: radarr | ||
environment: | ||
- PUID=1000 # user id, change as necessary | ||
- PGID=1000 # group id, change as necessary | ||
- TZ=Europe/London # timezone, change as necessary | ||
ports: | ||
- "7887:7878" # change the left value to the desired host port for Radarr | ||
restart: unless-stopped | ||
|
||
radarr2: | ||
<<: *common-settings | ||
image: linuxserver/radarr | ||
container_name: radarr2 | ||
ports: | ||
- "7888:7878" # change the left value to the desired host port for Radarr | ||
|
||
sonarr: | ||
<<: *common-settings | ||
image: linuxserver/sonarr | ||
container_name: sonarr | ||
environment: | ||
- PUID=1000 # user id, change as necessary | ||
- PGID=1000 # group id, change as necessary | ||
- TZ=Europe/London # timezone, change as necessary | ||
ports: | ||
- "8998:8989" # change the left value to the desired host port for Sonarr | ||
restart: unless-stopped | ||
|
||
sonarr2: | ||
<<: *common-settings | ||
image: linuxserver/sonarr | ||
container_name: sonarr2 | ||
ports: | ||
- "8999:8989" # change the left value to the desired host port for Sonarr |
Oops, something went wrong.