Refactor Requestor module to Requestarr and update related functionality

- Removed the Requestor module and replaced it with the Requestarr module, including all associated files and functionality.
- Updated frontend templates and JavaScript to reflect the new Requestarr naming convention.
- Adjusted database schema to create new tables for Requestarr settings and requests.
- Enhanced the web server and blueprints to register the new Requestarr routes and API endpoints.
- Improved navigation and UI elements to support the transition from Requestor to Requestarr.
This commit is contained in:
Admin9705
2025-06-20 00:35:45 -04:00
parent 654b3b1e29
commit 0e05988318
11 changed files with 68 additions and 57 deletions

View File

@@ -602,6 +602,17 @@ let huntarrUI = {
this.currentSection = 'scheduling';
console.debug('Scheduling section activated');
} else if (section === 'requestarr' && document.getElementById('requestarr-section')) {
document.getElementById('requestarr-section').classList.add('active');
document.getElementById('requestarr-section').style.display = 'block';
if (document.getElementById('requestarrNav')) document.getElementById('requestarrNav').classList.add('active');
newTitle = 'Requestarr';
this.currentSection = 'requestarr';
// Initialize requestarr module if it exists
if (typeof window.requestarrModule !== 'undefined') {
window.requestarrModule.loadInstances();
}
} else {
// Default to home if section is unknown or element missing
if (this.elements.homeSection) {

View File

@@ -1,8 +1,8 @@
/**
* Requestor functionality - Media search and request system
* Requestarr functionality - Media search and request system
*/
class RequestorModule {
class RequestarrModule {
constructor() {
this.searchTimeout = null;
this.instances = { sonarr: [], radarr: [] };
@@ -17,13 +17,13 @@ class RequestorModule {
setupEventListeners() {
// Instance selection
const instanceSelect = document.getElementById('requestor-instance-select');
const instanceSelect = document.getElementById('requestarr-instance-select');
if (instanceSelect) {
instanceSelect.addEventListener('change', (e) => this.handleInstanceChange(e));
}
// Search input with debouncing
const searchInput = document.getElementById('requestor-search');
const searchInput = document.getElementById('requestarr-search');
if (searchInput) {
searchInput.disabled = true;
searchInput.placeholder = 'Select an instance first...';
@@ -56,7 +56,7 @@ class RequestorModule {
// Clear previous results and enable search
this.clearResults();
const searchInput = document.getElementById('requestor-search');
const searchInput = document.getElementById('requestarr-search');
if (searchInput) {
searchInput.disabled = false;
searchInput.placeholder = `Search for ${appType === 'radarr' ? 'movies' : 'TV shows'}...`;
@@ -64,7 +64,7 @@ class RequestorModule {
}
} else {
this.selectedInstance = null;
const searchInput = document.getElementById('requestor-search');
const searchInput = document.getElementById('requestarr-search');
if (searchInput) {
searchInput.disabled = true;
searchInput.placeholder = 'Select an instance first...';
@@ -76,7 +76,7 @@ class RequestorModule {
async loadInstances() {
try {
const response = await fetch('./api/requestor/instances');
const response = await fetch('./api/requestarr/instances');
this.instances = await response.json();
this.updateInstanceSelect();
} catch (error) {
@@ -86,7 +86,7 @@ class RequestorModule {
}
updateInstanceSelect() {
const instanceSelect = document.getElementById('requestor-instance-select');
const instanceSelect = document.getElementById('requestarr-instance-select');
if (!instanceSelect) return;
instanceSelect.innerHTML = '<option value="">Select an instance to search...</option>';
@@ -114,7 +114,7 @@ class RequestorModule {
return;
}
const resultsContainer = document.getElementById('requestor-results');
const resultsContainer = document.getElementById('requestarr-results');
if (!resultsContainer) return;
// Show loading
@@ -127,7 +127,7 @@ class RequestorModule {
instance_name: this.selectedInstance.instanceName
});
const response = await fetch(`./api/requestor/search?${params}`);
const response = await fetch(`./api/requestarr/search?${params}`);
const data = await response.json();
if (data.error) {
@@ -143,7 +143,7 @@ class RequestorModule {
}
displayResults(results) {
const resultsContainer = document.getElementById('requestor-results');
const resultsContainer = document.getElementById('requestarr-results');
if (!resultsContainer) return;
if (results.length === 0) {
@@ -283,7 +283,7 @@ class RequestorModule {
instance_name: this.selectedInstance.instanceName
};
const response = await fetch('./api/requestor/request', {
const response = await fetch('./api/requestarr/request', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
@@ -320,7 +320,7 @@ class RequestorModule {
}
clearResults() {
const resultsContainer = document.getElementById('requestor-results');
const resultsContainer = document.getElementById('requestarr-results');
if (resultsContainer) {
resultsContainer.innerHTML = '';
}
@@ -346,7 +346,7 @@ class RequestorModule {
// Initialize when DOM is loaded
document.addEventListener('DOMContentLoaded', () => {
if (document.getElementById('requestor-section')) {
window.requestorModule = new RequestorModule();
if (document.getElementById('requestarr-section')) {
window.requestarrModule = new RequestarrModule();
}
});

View File

@@ -60,11 +60,11 @@
</div>
<span>Hunt Manager</span>
</a>
<a href="./#requestor" class="nav-item" id="requestorNav">
<a href="./#requestarr" class="nav-item" id="requestarrNav">
<div class="nav-icon-wrapper">
<i class="fas fa-plus-circle"></i>
</div>
<span>Requestor</span>
<span>Requestarr</span>
</a>
<a href="./user" class="nav-item" id="userNav">
<div class="nav-icon-wrapper">

View File

@@ -26,7 +26,7 @@
{% include 'components/hunt_manager_section.html' %}
<!-- Requestor Section -->
{% include 'components/requestor_section.html' %}
{% include 'components/requestarr_section.html' %}
<!-- Apps Section -->
{% include 'components/apps_section.html' %}

View File

@@ -12,7 +12,7 @@ from src.primary.apps.readarr_routes import readarr_bp
from src.primary.apps.whisparr_routes import whisparr_bp
from src.primary.apps.eros_routes import eros_bp
from src.primary.apps.swaparr_routes import swaparr_bp
from src.primary.apps.requestor_routes import requestor_bp
from src.primary.apps.requestarr_routes import requestarr_bp
__all__ = [
"sonarr_bp",
@@ -22,5 +22,5 @@ __all__ = [
"whisparr_bp",
"eros_bp",
"swaparr_bp",
"requestor_bp"
"requestarr_bp"
]

View File

@@ -1,5 +1,5 @@
"""
Requestor module for searching and requesting media through TMDB and *arr apps
Requestarr module for searching and requesting media through TMDB and *arr apps
"""
import requests
@@ -9,8 +9,8 @@ from src.primary.utils.database import get_database
logger = logging.getLogger(__name__)
class RequestorAPI:
"""API handler for Requestor functionality"""
class RequestarrAPI:
"""API handler for Requestarr functionality"""
def __init__(self):
self.db = get_database()
@@ -687,4 +687,4 @@ class RequestorAPI:
}
# Global instance
requestor_api = RequestorAPI()
requestarr_api = RequestarrAPI()

View File

@@ -1,17 +1,17 @@
"""
Requestor routes for media search and request functionality
Requestarr routes for media search and request functionality
"""
from flask import Blueprint, request, jsonify
import logging
from src.primary.apps.requestor import requestor_api
from src.primary.apps.requestarr import requestarr_api
logger = logging.getLogger(__name__)
# Create blueprint
requestor_bp = Blueprint('requestor', __name__, url_prefix='/api/requestor')
requestarr_bp = Blueprint('requestarr', __name__, url_prefix='/api/requestarr')
@requestor_bp.route('/search', methods=['GET'])
@requestarr_bp.route('/search', methods=['GET'])
def search_media():
"""Search for media using TMDB with availability checking"""
try:
@@ -25,24 +25,24 @@ def search_media():
if not app_type or not instance_name:
return jsonify({'error': 'App type and instance name are required'}), 400
results = requestor_api.search_media_with_availability(query, app_type, instance_name)
results = requestarr_api.search_media_with_availability(query, app_type, instance_name)
return jsonify({'results': results})
except Exception as e:
logger.error(f"Error searching media: {e}")
return jsonify({'error': 'Search failed'}), 500
@requestor_bp.route('/instances', methods=['GET'])
@requestarr_bp.route('/instances', methods=['GET'])
def get_enabled_instances():
"""Get enabled Sonarr and Radarr instances"""
try:
instances = requestor_api.get_enabled_instances()
instances = requestarr_api.get_enabled_instances()
return jsonify(instances)
except Exception as e:
logger.error(f"Error getting instances: {e}")
return jsonify({'error': 'Failed to get instances'}), 500
@requestor_bp.route('/request', methods=['POST'])
@requestarr_bp.route('/request', methods=['POST'])
def request_media():
"""Request media through app instance"""
try:
@@ -54,7 +54,7 @@ def request_media():
if field not in data:
return jsonify({'success': False, 'error': f'Missing required field: {field}'}), 400
result = requestor_api.request_media(
result = requestarr_api.request_media(
tmdb_id=data['tmdb_id'],
media_type=data['media_type'],
title=data['title'],
@@ -75,19 +75,19 @@ def request_media():
logger.error(f"Error requesting media: {e}")
return jsonify({'success': False, 'error': 'Request failed'}), 500
@requestor_bp.route('/requests', methods=['GET'])
@requestarr_bp.route('/requests', methods=['GET'])
def get_requests():
"""Get paginated list of requests"""
try:
page = int(request.args.get('page', 1))
page_size = int(request.args.get('page_size', 20))
requests_data = requestor_api.db.get_requests(page, page_size)
requests_data = requestarr_api.db.get_requests(page, page_size)
return jsonify(requests_data)
except Exception as e:
logger.error(f"Error getting requests: {e}")
return jsonify({'error': 'Failed to get requests'}), 500
# Requestor is always enabled with hardcoded TMDB API key
logger.info("Requestor initialized with hardcoded TMDB API key")
# Requestarr is always enabled with hardcoded TMDB API key
logger.info("Requestarr initialized with hardcoded TMDB API key")

View File

@@ -742,9 +742,9 @@ class HuntarrDatabase:
# Logs table moved to separate logs.db - remove if it exists
conn.execute('DROP TABLE IF EXISTS logs')
# Create requestor_settings table for Requestor configuration
# Create requestarr_settings table for Requestarr configuration
conn.execute('''
CREATE TABLE IF NOT EXISTS requestor_settings (
CREATE TABLE IF NOT EXISTS requestarr_settings (
id INTEGER PRIMARY KEY CHECK (id = 1),
tmdb_api_key TEXT,
enabled BOOLEAN DEFAULT FALSE,
@@ -753,9 +753,9 @@ class HuntarrDatabase:
)
''')
# Create requestor_requests table for tracking media requests
# Create requestarr_requests table for tracking media requests
conn.execute('''
CREATE TABLE IF NOT EXISTS requestor_requests (
CREATE TABLE IF NOT EXISTS requestarr_requests (
id INTEGER PRIMARY KEY AUTOINCREMENT,
tmdb_id INTEGER NOT NULL,
media_type TEXT NOT NULL CHECK (media_type IN ('movie', 'tv')),
@@ -2313,13 +2313,13 @@ class HuntarrDatabase:
logger.error(f"Failed to check setup progress: {e}")
return False
# Requestor functionality methods
def get_requestor_settings(self) -> Dict[str, Any]:
"""Get Requestor configuration settings"""
# Requestarr functionality methods
def get_requestarr_settings(self) -> Dict[str, Any]:
"""Get Requestarr configuration settings"""
try:
with self.get_connection() as conn:
cursor = conn.execute("""
SELECT tmdb_api_key, enabled FROM requestor_settings WHERE id = 1
SELECT tmdb_api_key, enabled FROM requestarr_settings WHERE id = 1
""")
row = cursor.fetchone()
if row:
@@ -2329,20 +2329,20 @@ class HuntarrDatabase:
}
return {'tmdb_api_key': '', 'enabled': False}
except Exception as e:
logger.error(f"Error getting requestor settings: {e}")
logger.error(f"Error getting requestarr settings: {e}")
return {'tmdb_api_key': '', 'enabled': False}
def save_requestor_settings(self, tmdb_api_key: str, enabled: bool) -> bool:
"""Save Requestor configuration settings"""
def save_requestarr_settings(self, tmdb_api_key: str, enabled: bool) -> bool:
"""Save Requestarr configuration settings"""
try:
with self.get_connection() as conn:
conn.execute("""
INSERT OR REPLACE INTO requestor_settings (id, tmdb_api_key, enabled, updated_at)
INSERT OR REPLACE INTO requestarr_settings (id, tmdb_api_key, enabled, updated_at)
VALUES (1, ?, ?, CURRENT_TIMESTAMP)
""", (tmdb_api_key, enabled))
return True
except Exception as e:
logger.error(f"Error saving requestor settings: {e}")
logger.error(f"Error saving requestarr settings: {e}")
return False
def add_request(self, tmdb_id: int, media_type: str, title: str, year: int,
@@ -2352,7 +2352,7 @@ class HuntarrDatabase:
try:
with self.get_connection() as conn:
conn.execute("""
INSERT OR REPLACE INTO requestor_requests
INSERT OR REPLACE INTO requestarr_requests
(tmdb_id, media_type, title, year, overview, poster_path, backdrop_path,
app_type, instance_name, status, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 'pending', CURRENT_TIMESTAMP)
@@ -2369,14 +2369,14 @@ class HuntarrDatabase:
offset = (page - 1) * page_size
with self.get_connection() as conn:
# Get total count
count_cursor = conn.execute("SELECT COUNT(*) FROM requestor_requests")
count_cursor = conn.execute("SELECT COUNT(*) FROM requestarr_requests")
total_requests = count_cursor.fetchone()[0]
# Get paginated requests
cursor = conn.execute("""
SELECT tmdb_id, media_type, title, year, overview, poster_path, backdrop_path,
app_type, instance_name, status, request_date, updated_at
FROM requestor_requests
FROM requestarr_requests
ORDER BY request_date DESC
LIMIT ? OFFSET ?
""", (page_size, offset))
@@ -2415,7 +2415,7 @@ class HuntarrDatabase:
try:
with self.get_connection() as conn:
conn.execute("""
UPDATE requestor_requests
UPDATE requestarr_requests
SET status = ?, updated_at = CURRENT_TIMESTAMP
WHERE tmdb_id = ? AND media_type = ? AND app_type = ? AND instance_name = ?
""", (status, tmdb_id, media_type, app_type, instance_name))
@@ -2429,7 +2429,7 @@ class HuntarrDatabase:
try:
with self.get_connection() as conn:
cursor = conn.execute("""
SELECT COUNT(*) FROM requestor_requests
SELECT COUNT(*) FROM requestarr_requests
WHERE tmdb_id = ? AND media_type = ? AND app_type = ? AND instance_name = ?
""", (tmdb_id, media_type, app_type, instance_name))
return cursor.fetchone()[0] > 0

View File

@@ -39,7 +39,7 @@ from src.primary.auth import (
from src.primary.routes.common import common_bp
from src.primary.routes.plex_auth_routes import plex_auth_bp
# Import blueprints for each app from the centralized blueprints module
from src.primary.apps.blueprints import sonarr_bp, radarr_bp, lidarr_bp, readarr_bp, whisparr_bp, eros_bp, swaparr_bp, requestor_bp
from src.primary.apps.blueprints import sonarr_bp, radarr_bp, lidarr_bp, readarr_bp, whisparr_bp, eros_bp, swaparr_bp, requestarr_bp
# Import stateful blueprint
from src.primary.stateful_routes import stateful_api
@@ -271,7 +271,7 @@ app.register_blueprint(readarr_bp, url_prefix='/api/readarr')
app.register_blueprint(whisparr_bp, url_prefix='/api/whisparr')
app.register_blueprint(eros_bp, url_prefix='/api/eros')
app.register_blueprint(swaparr_bp, url_prefix='/api/swaparr')
app.register_blueprint(requestor_bp)
app.register_blueprint(requestarr_bp)
app.register_blueprint(stateful_api, url_prefix='/api/stateful')
app.register_blueprint(history_blueprint, url_prefix='/api/hunt-manager')
app.register_blueprint(scheduler_api)