Skip to content

Commit

Permalink
Implemented a specific Jellyseer user selection to initiate requests …
Browse files Browse the repository at this point in the history
…via SuggestArr
  • Loading branch information
giuseppe99barchetta committed Oct 16, 2024
1 parent 3281097 commit 9a90a55
Show file tree
Hide file tree
Showing 8 changed files with 136 additions and 8 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
# Changelog
## [1.0.3] - 2024-10-16
### Added
- **User Selection**: Added the ability to select a specific Jellyseer user to initiate requests, enabling management and approval of content.
- **Web Interface Redesign**: Completely revamped the web interface for improved user experience and functionality.

## [1.0.2] - 2024-10-16
### Added
- **Utility Class**: Introduced `AppUtils` utility class to handle environment loading, worker identification, and welcome message logging. This improves code modularity and reusability.
Expand Down
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ This project is designed to fully automate the process of managing media content
- Searches for similar movies and TV shows on TMDb.
- Sends download requests for similar content to Jellyseer.
- Web Interface: A user-friendly web interface for configuration management.
- User Selection: Choose a specific Jellyseer user to initiate requests, enabling you to manage and approve auto-requested content.
- Cron Job Management: Easily update the cron job schedule directly from the web interface.

## Prerequisites
Expand All @@ -33,6 +34,7 @@ The project uses the following environment variables that can be passed via the
- `MAX_SIMILAR_MOVIE`: (Optional) The maximum number of similar movies to download. Default is 3, with a max limit of 20.
- `MAX_SIMILAR_TV`: (Optional) The maximum number of similar TV shows to download. Default is 2, with a max limit of 20.
- `CRON_TIMES`: (Optional) Your preferred cron schedule otherwise it runs at midnight.
- `JELLYSEER_USER`: (Optional) Your preferred user to make Movie or TV Show request to Jellyseer. Otherwise it use the admin profile.

## Docker Usage

Expand All @@ -56,9 +58,10 @@ services:
MAX_SIMILAR_MOVIE: 5 # (Optional: You can configure it in the dashboard. Default: 2, Max: 20)
MAX_SIMILAR_TV: 2 # (Optional: You can configure it in the dashboard. Default: 2, Max: 20)
CRON_TIMES: ${CRON_TIMES:-0 0 * * *} # (Optional: You can configure it in the dashboard. Default run at midnight.)
JELLYSEER_USER: 1 # (Optional: You can configure it in the dashboard. Otherwise it use the admin profile.)
volumes:
- .:/app
container_name: jellyser_automation_job
container_name: SuggestArr
restart: always
ports:
- "5000:5000"
Expand Down Expand Up @@ -94,6 +97,7 @@ export JELLYFIN_TOKEN=your_jellyfin_token
export JELLYSEER_API_URL=http://your_jellyseer_url
export JELLYSEER_TOKEN=your_jellyseer_token
export CRON_TIMES="0 0 * * *" # Optional, your preferred cron schedule
export JELLYSEER_USER=1 # Optional, your preferred user to make request to Jellyseer
```

Or create an .env file inside the project.
Expand Down
29 changes: 29 additions & 0 deletions app.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"""
from flask import Flask, render_template, request, jsonify
from flask_cors import CORS
from jellyseer.jellyseer_client import JellyseerClient
from utils.utils import AppUtils
from automate_process import ContentAutomation
from config.config import load_env_vars, save_env_vars
Expand Down Expand Up @@ -85,6 +86,34 @@ def run_now():
except Exception as e: # pylint: disable=broad-except
return jsonify({'status': 'error', 'message': 'Unexpected error: ' + str(e)}), 500

@app.route('/api/get_users', methods=['POST'])
def get_users():
"""
Fetch Jellyseer users using the provided API key.
"""
try:
config_data = request.json
api_url = config_data.get('JELLYSEER_API_URL')
api_key = config_data.get('JELLYSEER_TOKEN')

if not api_key:
return jsonify({'message': 'API key is required', 'type': 'error'}), 400

# Initialize JellyseerClient with the provided API key
jellyseer_client = JellyseerClient(api_url=api_url, api_key=api_key)

# Fetch users from Jellyseer
users = jellyseer_client.get_all_users()

if users is None or len(users) == 0:
return jsonify({'message': 'Failed to fetch users', 'type': 'error'}), 500

return jsonify(
{'message': 'Users fetched successfully', 'users': users, 'type': 'success'}), 200

except Exception as e: # pylint: disable=broad-except
return jsonify({'message': f'Error fetching users: {str(e)}', 'type': 'error'}), 500

if __name__ == '__main__':
app = create_app()
app.run(host='0.0.0.0', port=5000)
4 changes: 3 additions & 1 deletion automate_process.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,16 @@ def __init__(self):
jellyseer_api_url = env_vars['JELLYSEER_API_URL']
jellyseer_token = env_vars['JELLYSEER_TOKEN']
tmdb_api_key = env_vars['TMDB_API_KEY']
jellyseer_user = env_vars['JELLYSEER_USER']

self.jellyfin_client = JellyfinClient(
jellyfin_api_url,
jellyfin_token
)
self.jellyseer_client = JellyseerClient(
jellyseer_api_url,
jellyseer_token
jellyseer_token,
jellyseer_user
)
self.tmdb_client = TMDbClient(
tmdb_api_key
Expand Down
9 changes: 8 additions & 1 deletion config/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"""
import os
import subprocess
import platform
from dotenv import load_dotenv

from croniter import croniter
Expand All @@ -17,6 +18,7 @@
MAX_SIMILAR_MOVIE = 'MAX_SIMILAR_MOVIE'
MAX_SIMILAR_TV = 'MAX_SIMILAR_TV'
CRON_TIMES = 'CRON_TIMES'
JELLYSEER_USER = 'JELLYSEER_USER'

def load_env_vars():
"""
Expand All @@ -32,6 +34,7 @@ def load_env_vars():
MAX_SIMILAR_MOVIE: os.getenv(MAX_SIMILAR_MOVIE, '5'),
MAX_SIMILAR_TV: os.getenv(MAX_SIMILAR_TV, '2'),
CRON_TIMES: os.getenv(CRON_TIMES, '0 0 * * *'),
JELLYSEER_USER: os.getenv(JELLYSEER_USER, 'default')
}

def save_env_vars(config_data):
Expand All @@ -52,14 +55,18 @@ def save_env_vars(config_data):
'MAX_SIMILAR_MOVIE': config_data.get('MAX_SIMILAR_MOVIE', '5'),
'MAX_SIMILAR_TV': config_data.get('MAX_SIMILAR_TV', '2'),
'CRON_TIMES': cron_times,
'JELLYSEER_USER': config_data.get('JELLYSEER_USER', 'default')
}

with open('.env', 'w', encoding='utf-8') as f:
for key, value in env_vars.items():
f.write(f'{key}="{value}"\n')

load_env_vars()
update_cron_job(cron_times)

# Update cron only in linux system
if platform.system() != 'Windows':
update_cron_job(cron_times)


def update_cron_job(cron_time):
Expand Down
39 changes: 37 additions & 2 deletions jellyseer/jellyseer_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class JellyseerClient:
A client to interact with the Jellyseer API for handling media requests and authentication.
"""

def __init__(self, api_url, api_key):
def __init__(self, api_url, api_key, jellyseer_user = None):
"""
Initializes the JellyseerClient with the API URL and logs in the user.
:param api_url: The URL of the Jellyseer API.
Expand All @@ -29,6 +29,7 @@ def __init__(self, api_url, api_key):
self.logger = LoggerManager.get_logger(self.__class__.__name__)
self.api_url = api_url
self.api_key = api_key
self.user_id = jellyseer_user if jellyseer_user else "default"
self.session = requests.Session()
self.session.headers.update({'X-Api-Key': self.api_key})

Expand Down Expand Up @@ -68,7 +69,14 @@ def request_media(self, media_type, media_id, tvdb_id=None):
:param tvdb_id: The TVDb ID of the media item (optional, only for TV shows).
:return: The response JSON from Jellyseer or None if an error occurs.
"""
data = {"mediaType": media_type, "mediaId": media_id}
data = {
"mediaType": media_type,
"mediaId": media_id,
}

# Only add userId if it's not "default"
if self.user_id != "default":
data["userId"] = int(self.user_id)

if media_type == 'tv':
data["tvdbId"] = tvdb_id if tvdb_id else media_id
Expand All @@ -95,3 +103,30 @@ def request_media(self, media_type, media_id, tvdb_id=None):
)

return None

def get_all_users(self):
"""Fetch all users from Jellyseer API, returning a list of user IDs and names."""
try:
response = self.session.get(
f"{self.api_url}/api/v1/user",
timeout=REQUEST_TIMEOUT
)

if response.status_code in HTTP_OK:
users_data = response.json().get('results', [])
# Extracting only id and displayName or jellyfinUsername as fallback
return [
{
'id': user['id'],
'name': (user.get('displayName')
or user.get('jellyfinUsername', 'Unknown User'))
}
for user in users_data
]

except requests.Timeout:
self.logger.error("Request to get all Jellyseer users timed out.")
except requests.RequestException as e:
self.logger.error("An error occurred while requesting users from Jellyseer: %s", str(e))

return []
32 changes: 31 additions & 1 deletion static/js/script.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ new Vue({
JELLYSEER_TOKEN: '',
MAX_SIMILAR_MOVIE: '5',
MAX_SIMILAR_TV: '2',
CRON_TIMES: '0 0 * * *'
CRON_TIMES: '0 0 * * *',
JELLYSEER_USER: ''
},
users: [],
isConfigSaved: false,
isLoading: false
},
Expand Down Expand Up @@ -40,13 +42,41 @@ new Vue({
axios.get('/api/config')
.then(response => {
this.config = response.data;
this.fetchUsers();
this.isConfigSaved = true;
})
.catch(error => {
console.error('Error fetching configuration:', error);
this.showToast('Error fetching configuration:' + error, 'error');
});
},
fetchUsers() {
if (this.config.JELLYSEER_TOKEN && this.config.JELLYSEER_API_URL) {
axios.post('/api/get_users', this.config, {
headers: {
'Content-Type': 'application/json'
}
})
.then(response => {
if (response.data.type === 'success') {
this.users = response.data.users;
} else {
this.users = []
this.config.JELLYSEER_USER = 'default'
this.showToast(response.data.message, 'error');
}
})
.catch(error => {
this.users = []
this.config.JELLYSEER_USER = 'default'
this.showToast('Failed to fetch users. Please check the API key.', 'error');
});
} else {
// If API key or URL is not provided, clear the users list
this.config.JELLYSEER_USER = 'default'
this.users = [];
}
},
saveConfig() {
axios.post('/api/save', this.config, {
headers: {
Expand Down
20 changes: 18 additions & 2 deletions templates/config.html
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,24 @@ <h1 class="text-center text-3xl font-bold mb-6 text-indigo-400">SuggestArr Confi
</div>

<div>
<label for="JELLYSEER_TOKEN" class="block text-sm font-semibold text-gray-300">Jellyseer Token:</label>
<input type="text" class="mt-2 block w-full bg-gray-700 border border-gray-600 rounded-lg shadow-md focus:ring-indigo-500 focus:border-indigo-500 transition duration-300 px-4 py-2" id="JELLYSEER_TOKEN" v-model="config.JELLYSEER_TOKEN" placeholder="Your Jellyseer Token">
<label for="JELLYSEER_TOKEN" class="block text-sm font-semibold text-gray-300">Jellyseer API Key:</label>
<input type="text" class="mt-2 block w-full bg-gray-700 border border-gray-600 rounded-lg shadow-md focus:ring-indigo-500 focus:border-indigo-500 transition duration-300 px-4 py-2"
id="JELLYSEER_TOKEN" v-model="config.JELLYSEER_TOKEN" @blur="fetchUsers" placeholder="Enter your Jellyseer API Key">
</div>

<div id="app" v-cloak>
{% raw %}
<label for="USER_SELECTION" class="block text-sm font-semibold text-gray-300">Select a Jellyseer user:</label>
<select id="USER_SELECTION" v-model="config.JELLYSEER_USER"
:disabled="!users || users.length === 0"
class="mt-2 block w-full bg-gray-700 border border-gray-600 rounded-lg shadow-md px-4 py-2 focus:ring-indigo-500 focus:border-indigo-500 transition duration-300">
<option disabled value="">Select a Jellyseer user:</option>
<option v-for="user in users" :key="user.id" :value="user.id">{{ user.name }}</option>
</select>
<div v-if="!users || users.length === 0">
<small class="text-gray-400">Insert correct Jellyseer URL and API to retrieve the users.</small>
</div>
{% endraw %}
</div>

<div class="grid grid-cols-2 gap-4">
Expand Down

0 comments on commit 9a90a55

Please sign in to comment.