init
This commit is contained in:
1
routes/__init__.py
Normal file
1
routes/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
# Routes package initialization
|
||||
64
routes/auth.py
Normal file
64
routes/auth.py
Normal file
@@ -0,0 +1,64 @@
|
||||
from flask import Blueprint, render_template, request, redirect, url_for, session, jsonify
|
||||
import sqlite3
|
||||
import hashlib
|
||||
import os
|
||||
|
||||
auth_bp = Blueprint('auth', __name__)
|
||||
|
||||
def get_db_connection():
|
||||
conn = sqlite3.connect('pt_manager.db')
|
||||
conn.row_factory = sqlite3.Row
|
||||
return conn
|
||||
|
||||
@auth_bp.route('/login', methods=['GET', 'POST'])
|
||||
def login():
|
||||
if request.method == 'POST':
|
||||
username = request.form['username']
|
||||
password = request.form['password']
|
||||
|
||||
conn = get_db_connection()
|
||||
user = conn.execute('SELECT * FROM users WHERE username = ?', (username,)).fetchone()
|
||||
conn.close()
|
||||
|
||||
if user and user['password_hash'] == password: # In production, use proper password hashing
|
||||
session['user_id'] = user['id']
|
||||
session['username'] = user['username']
|
||||
session['role'] = user['role']
|
||||
return redirect(url_for('main.index'))
|
||||
else:
|
||||
return render_template('auth/login.html', error='Invalid credentials')
|
||||
|
||||
return render_template('auth/login.html')
|
||||
|
||||
@auth_bp.route('/logout')
|
||||
def logout():
|
||||
session.clear()
|
||||
return redirect(url_for('auth.login'))
|
||||
|
||||
@auth_bp.route('/change_password', methods=['GET', 'POST'])
|
||||
def change_password():
|
||||
if 'user_id' not in session:
|
||||
return redirect(url_for('auth.login'))
|
||||
|
||||
if request.method == 'POST':
|
||||
current_password = request.form['current_password']
|
||||
new_password = request.form['new_password']
|
||||
confirm_password = request.form['confirm_password']
|
||||
|
||||
if new_password != confirm_password:
|
||||
return render_template('auth/change_password.html', error='New passwords do not match')
|
||||
|
||||
conn = get_db_connection()
|
||||
user = conn.execute('SELECT * FROM users WHERE id = ?', (session['user_id'],)).fetchone()
|
||||
|
||||
if user and user['password_hash'] == current_password: # In production, use proper password hashing
|
||||
conn.execute('UPDATE users SET password_hash = ? WHERE id = ?',
|
||||
(new_password, session['user_id']))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return redirect(url_for('main.index'))
|
||||
else:
|
||||
conn.close()
|
||||
return render_template('auth/change_password.html', error='Current password is incorrect')
|
||||
|
||||
return render_template('auth/change_password.html')
|
||||
15
routes/main.py
Normal file
15
routes/main.py
Normal file
@@ -0,0 +1,15 @@
|
||||
from flask import Blueprint, render_template, session, redirect, url_for
|
||||
|
||||
main_bp = Blueprint('main', __name__)
|
||||
|
||||
@main_bp.route('/')
|
||||
def index():
|
||||
if 'user_id' not in session:
|
||||
return redirect(url_for('auth.login'))
|
||||
return render_template('main/index.html')
|
||||
|
||||
@main_bp.route('/dashboard')
|
||||
def dashboard():
|
||||
if 'user_id' not in session:
|
||||
return redirect(url_for('auth.login'))
|
||||
return render_template('main/dashboard.html')
|
||||
121
routes/qbittorrent.py
Normal file
121
routes/qbittorrent.py
Normal file
@@ -0,0 +1,121 @@
|
||||
from flask import Blueprint, render_template, session, redirect, url_for, jsonify, request
|
||||
import sqlite3
|
||||
import qbittorrentapi
|
||||
import sys
|
||||
import os
|
||||
|
||||
# Import format functions
|
||||
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
|
||||
from utils.format import format_file_size, format_status
|
||||
|
||||
qbittorrent_bp = Blueprint('qbittorrent', __name__)
|
||||
|
||||
def get_db_connection():
|
||||
conn = sqlite3.connect('pt_manager.db')
|
||||
conn.row_factory = sqlite3.Row
|
||||
return conn
|
||||
|
||||
def get_qbittorrent_client():
|
||||
conn = get_db_connection()
|
||||
client_config = conn.execute(
|
||||
"SELECT * FROM clients WHERE name = 'qbittorrent' AND enabled = 1"
|
||||
).fetchone()
|
||||
conn.close()
|
||||
|
||||
if not client_config:
|
||||
return None
|
||||
|
||||
try:
|
||||
qb = qbittorrentapi.Client(
|
||||
host=client_config['host'],
|
||||
port=client_config['port'],
|
||||
username=client_config['username'],
|
||||
password=client_config['password']
|
||||
)
|
||||
qb.auth_log_in()
|
||||
return qb
|
||||
except Exception as e:
|
||||
print(f"Failed to connect to qBittorrent: {e}")
|
||||
return None
|
||||
|
||||
@qbittorrent_bp.route('/qbittorrent')
|
||||
def qbittorrent_index():
|
||||
if 'user_id' not in session:
|
||||
return redirect(url_for('auth.login'))
|
||||
return render_template('qbittorrent/index.html')
|
||||
|
||||
@qbittorrent_bp.route('/qbittorrent/torrents')
|
||||
def torrents():
|
||||
if 'user_id' not in session:
|
||||
return redirect(url_for('auth.login'))
|
||||
|
||||
qb = get_qbittorrent_client()
|
||||
if not qb:
|
||||
return render_template('qbittorrent/torrents.html', error='qBittorrent client not configured or unavailable')
|
||||
|
||||
try:
|
||||
torrents = qb.torrents_info()
|
||||
# Process torrents to format file sizes and statuses
|
||||
processed_torrents = []
|
||||
for torrent in torrents:
|
||||
processed_torrent = {
|
||||
'hash': torrent.hash,
|
||||
'name': torrent.name,
|
||||
'size': format_file_size(torrent.size),
|
||||
'progress': torrent.progress,
|
||||
'state': torrent.state,
|
||||
'status': format_status(torrent.state),
|
||||
'num_seeds': getattr(torrent, 'num_seeds', 0),
|
||||
'num_leechs': getattr(torrent, 'num_leechs', 0)
|
||||
}
|
||||
processed_torrents.append(processed_torrent)
|
||||
|
||||
return render_template('qbittorrent/torrents.html', torrents=processed_torrents)
|
||||
except Exception as e:
|
||||
return render_template('qbittorrent/torrents.html', error=f'Failed to fetch torrents: {str(e)}')
|
||||
|
||||
@qbittorrent_bp.route('/qbittorrent/torrent/<info_hash>/pause', methods=['POST'])
|
||||
def pause_torrent(info_hash):
|
||||
if 'user_id' not in session:
|
||||
return jsonify({'error': 'Authentication required'}), 401
|
||||
|
||||
qb = get_qbittorrent_client()
|
||||
if not qb:
|
||||
return jsonify({'error': 'qBittorrent client not configured or unavailable'}), 500
|
||||
|
||||
try:
|
||||
qb.torrents_pause(hashes=info_hash)
|
||||
return jsonify({'success': True})
|
||||
except Exception as e:
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
@qbittorrent_bp.route('/qbittorrent/torrent/<info_hash>/resume', methods=['POST'])
|
||||
def resume_torrent(info_hash):
|
||||
if 'user_id' not in session:
|
||||
return jsonify({'error': 'Authentication required'}), 401
|
||||
|
||||
qb = get_qbittorrent_client()
|
||||
if not qb:
|
||||
return jsonify({'error': 'qBittorrent client not configured or unavailable'}), 500
|
||||
|
||||
try:
|
||||
qb.torrents_resume(hashes=info_hash)
|
||||
return jsonify({'success': True})
|
||||
except Exception as e:
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
@qbittorrent_bp.route('/qbittorrent/torrent/<info_hash>/delete', methods=['POST'])
|
||||
def delete_torrent(info_hash):
|
||||
if 'user_id' not in session:
|
||||
return jsonify({'error': 'Authentication required'}), 401
|
||||
|
||||
qb = get_qbittorrent_client()
|
||||
if not qb:
|
||||
return jsonify({'error': 'qBittorrent client not configured or unavailable'}), 500
|
||||
|
||||
try:
|
||||
# Delete torrent and data
|
||||
qb.torrents_delete(hashes=info_hash, delete_files=True)
|
||||
return jsonify({'success': True})
|
||||
except Exception as e:
|
||||
return jsonify({'error': str(e)}), 500
|
||||
162
routes/settings.py
Normal file
162
routes/settings.py
Normal file
@@ -0,0 +1,162 @@
|
||||
from flask import Blueprint, render_template, session, redirect, url_for, jsonify, request
|
||||
import sqlite3
|
||||
|
||||
settings_bp = Blueprint('settings', __name__)
|
||||
|
||||
def get_db_connection():
|
||||
conn = sqlite3.connect('pt_manager.db')
|
||||
conn.row_factory = sqlite3.Row
|
||||
return conn
|
||||
|
||||
@settings_bp.route('/settings')
|
||||
def settings_index():
|
||||
if 'user_id' not in session:
|
||||
return redirect(url_for('auth.login'))
|
||||
|
||||
if session['role'] != 'admin':
|
||||
return redirect(url_for('main.index'))
|
||||
|
||||
conn = get_db_connection()
|
||||
# Get NexusPHP settings
|
||||
nexusphp_site_url = conn.execute(
|
||||
"SELECT value FROM settings WHERE key = 'nexusphp_site_url'"
|
||||
).fetchone()
|
||||
|
||||
nexusphp_api_token = conn.execute(
|
||||
"SELECT value FROM settings WHERE key = 'nexusphp_api_token'"
|
||||
).fetchone()
|
||||
|
||||
# Get client settings
|
||||
qbittorrent_config = conn.execute(
|
||||
"SELECT * FROM clients WHERE name = 'qbittorrent'"
|
||||
).fetchone()
|
||||
|
||||
transmission_config = conn.execute(
|
||||
"SELECT * FROM clients WHERE name = 'transmission'"
|
||||
).fetchone()
|
||||
|
||||
conn.close()
|
||||
|
||||
return render_template('settings/index.html',
|
||||
nexusphp_site_url=nexusphp_site_url['value'] if nexusphp_site_url else '',
|
||||
nexusphp_api_token=nexusphp_api_token['value'] if nexusphp_api_token else '',
|
||||
qbittorrent_config=qbittorrent_config,
|
||||
transmission_config=transmission_config)
|
||||
|
||||
@settings_bp.route('/settings/nexusphp', methods=['POST'])
|
||||
def save_nexusphp_settings():
|
||||
if 'user_id' not in session:
|
||||
return jsonify({'error': 'Authentication required'}), 401
|
||||
|
||||
if session['role'] != 'admin':
|
||||
return jsonify({'error': 'Admin access required'}), 403
|
||||
|
||||
site_url = request.form.get('site_url')
|
||||
api_token = request.form.get('api_token')
|
||||
|
||||
conn = get_db_connection()
|
||||
try:
|
||||
# Save or update site URL
|
||||
conn.execute(
|
||||
"INSERT OR REPLACE INTO settings (key, value) VALUES (?, ?)",
|
||||
('nexusphp_site_url', site_url)
|
||||
)
|
||||
|
||||
# Save or update API token
|
||||
conn.execute(
|
||||
"INSERT OR REPLACE INTO settings (key, value) VALUES (?, ?)",
|
||||
('nexusphp_api_token', api_token)
|
||||
)
|
||||
print(api_token)#262|wEGbxaqybJ6ZfLZAAtxX0oiQTymFcelHpc6YHims27a70898
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return jsonify({'success': True})
|
||||
except Exception as e:
|
||||
conn.close()
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
@settings_bp.route('/settings/qbittorrent', methods=['POST'])
|
||||
def save_qbittorrent_settings():
|
||||
if 'user_id' not in session:
|
||||
return jsonify({'error': 'Authentication required'}), 401
|
||||
|
||||
if session['role'] != 'admin':
|
||||
return jsonify({'error': 'Admin access required'}), 403
|
||||
|
||||
host = request.form.get('host')
|
||||
port = request.form.get('port')
|
||||
username = request.form.get('username')
|
||||
password = request.form.get('password')
|
||||
enabled = bool(request.form.get('enabled'))
|
||||
|
||||
conn = get_db_connection()
|
||||
try:
|
||||
# Check if config exists
|
||||
existing = conn.execute(
|
||||
"SELECT id FROM clients WHERE name = 'qbittorrent'"
|
||||
).fetchone()
|
||||
|
||||
if existing:
|
||||
# Update existing config
|
||||
conn.execute(
|
||||
"""UPDATE clients SET host = ?, port = ?, username = ?, password = ?, enabled = ?
|
||||
WHERE name = 'qbittorrent'""",
|
||||
(host, port, username, password, enabled)
|
||||
)
|
||||
else:
|
||||
# Insert new config
|
||||
conn.execute(
|
||||
"""INSERT INTO clients (name, host, port, username, password, enabled)
|
||||
VALUES (?, ?, ?, ?, ?, ?)""",
|
||||
('qbittorrent', host, port, username, password, enabled)
|
||||
)
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return jsonify({'success': True})
|
||||
except Exception as e:
|
||||
conn.close()
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
@settings_bp.route('/settings/transmission', methods=['POST'])
|
||||
def save_transmission_settings():
|
||||
if 'user_id' not in session:
|
||||
return jsonify({'error': 'Authentication required'}), 401
|
||||
|
||||
if session['role'] != 'admin':
|
||||
return jsonify({'error': 'Admin access required'}), 403
|
||||
|
||||
host = request.form.get('host')
|
||||
port = request.form.get('port')
|
||||
username = request.form.get('username')
|
||||
password = request.form.get('password')
|
||||
enabled = bool(request.form.get('enabled'))
|
||||
|
||||
conn = get_db_connection()
|
||||
try:
|
||||
# Check if config exists
|
||||
existing = conn.execute(
|
||||
"SELECT id FROM clients WHERE name = 'transmission'"
|
||||
).fetchone()
|
||||
|
||||
if existing:
|
||||
# Update existing config
|
||||
conn.execute(
|
||||
"""UPDATE clients SET host = ?, port = ?, username = ?, password = ?, enabled = ?
|
||||
WHERE name = 'transmission'""",
|
||||
(host, port, username, password, enabled)
|
||||
)
|
||||
else:
|
||||
# Insert new config
|
||||
conn.execute(
|
||||
"""INSERT INTO clients (name, host, port, username, password, enabled)
|
||||
VALUES (?, ?, ?, ?, ?, ?)""",
|
||||
('transmission', host, port, username, password, enabled)
|
||||
)
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return jsonify({'success': True})
|
||||
except Exception as e:
|
||||
conn.close()
|
||||
return jsonify({'error': str(e)}), 500
|
||||
217
routes/site.py
Normal file
217
routes/site.py
Normal file
@@ -0,0 +1,217 @@
|
||||
from flask import Blueprint, render_template, session, redirect, url_for, jsonify, request
|
||||
import requests
|
||||
import sqlite3
|
||||
|
||||
site_bp = Blueprint('site', __name__)
|
||||
|
||||
def get_db_connection():
|
||||
conn = sqlite3.connect('pt_manager.db')
|
||||
conn.row_factory = sqlite3.Row
|
||||
return conn
|
||||
|
||||
def get_nexusphp_config(site_id=None):
|
||||
conn = get_db_connection()
|
||||
|
||||
if site_id:
|
||||
site = conn.execute("SELECT * FROM sites WHERE id = ?", (site_id,)).fetchone()
|
||||
conn.close()
|
||||
if site:
|
||||
return {
|
||||
'site_url': site['url'],
|
||||
'api_token': site['api_token'] if site['api_token'] else ''
|
||||
}
|
||||
else:
|
||||
# Get the first enabled site
|
||||
site = conn.execute("SELECT * FROM sites WHERE enabled = 1 LIMIT 1").fetchone()
|
||||
conn.close()
|
||||
if site:
|
||||
return {
|
||||
'site_url': site['url'],
|
||||
'api_token': site['api_token'] if site['api_token'] else ''
|
||||
}
|
||||
|
||||
# Fallback to default settings
|
||||
site_url = conn.execute("SELECT value FROM settings WHERE key = 'nexusphp_site_url'").fetchone()
|
||||
api_token = conn.execute("SELECT value FROM settings WHERE key = 'nexusphp_api_token'").fetchone()
|
||||
conn.close()
|
||||
|
||||
return {
|
||||
'site_url': site_url['value'] if site_url else 'https://www.ptskit.org',
|
||||
'api_token': api_token['value'] if api_token else ''
|
||||
}
|
||||
|
||||
def get_all_sites():
|
||||
conn = get_db_connection()
|
||||
sites = conn.execute("SELECT * FROM sites WHERE enabled = 1").fetchall()
|
||||
conn.close()
|
||||
return sites
|
||||
|
||||
@site_bp.route('/site')
|
||||
def site_index():
|
||||
if 'user_id' not in session:
|
||||
return redirect(url_for('auth.login'))
|
||||
|
||||
sites = get_all_sites()
|
||||
return render_template('site/index.html', sites=sites)
|
||||
|
||||
@site_bp.route('/site/<int:site_id>/profile')
|
||||
def profile(site_id):
|
||||
if 'user_id' not in session:
|
||||
return redirect(url_for('auth.login'))
|
||||
|
||||
sites = get_all_sites()
|
||||
config = get_nexusphp_config(site_id)
|
||||
headers = {
|
||||
'Accept': 'application/json',
|
||||
# 'Authorization': f'Bearer {config["api_token"]}'
|
||||
'Authorization': f'Bearer 262|wEGbxaqybJ6ZfLZAAtxX0oiQTymFcelHpc6YHims27a70898'
|
||||
|
||||
}
|
||||
|
||||
try:
|
||||
response = requests.get(f'{config["site_url"]}/api/v1/profile', headers=headers)
|
||||
if response.status_code == 200:
|
||||
profile_data = response.json()
|
||||
return render_template('site/profile.html', profile=profile_data, sites=sites, current_site_id=site_id)
|
||||
else:
|
||||
return render_template('site/profile.html', error='Failed to fetch profile data', sites=sites, current_site_id=site_id)
|
||||
except Exception as e:
|
||||
return render_template('site/profile.html', error=str(e), sites=sites, current_site_id=site_id)
|
||||
|
||||
@site_bp.route('/site/<int:site_id>/torrents')
|
||||
def torrents(site_id):
|
||||
if 'user_id' not in session:
|
||||
return redirect(url_for('auth.login'))
|
||||
|
||||
sites = get_all_sites()
|
||||
config = get_nexusphp_config(site_id)
|
||||
headers = {
|
||||
'Accept': 'application/json',
|
||||
# 'Authorization': f'Bearer {config["api_token"]}'
|
||||
'Authorization': f'Bearer 262|wEGbxaqybJ6ZfLZAAtxX0oiQTymFcelHpc6YHims27a70898'
|
||||
}
|
||||
|
||||
# Get query parameters
|
||||
page = request.args.get('page', 1, type=int)
|
||||
per_page = request.args.get('per_page', 20, type=int)
|
||||
sort = request.args.get('sort', '-seeders')
|
||||
title_filter = request.args.get('title', '')
|
||||
|
||||
params = {
|
||||
'page': page,
|
||||
'per_page': per_page,
|
||||
'sorts': sort
|
||||
}
|
||||
|
||||
if title_filter:
|
||||
params['filter[title]'] = title_filter
|
||||
|
||||
try:
|
||||
response = requests.get(f'{config["site_url"]}/api/v1/torrents',
|
||||
headers=headers, params=params)
|
||||
if response.status_code == 200:
|
||||
torrents_data = response.json()
|
||||
return render_template('site/torrents.html', torrents=torrents_data,
|
||||
page=page, per_page=per_page, sort=sort, title_filter=title_filter,
|
||||
sites=sites, current_site_id=site_id)
|
||||
else:
|
||||
return render_template('site/torrents.html', error='Failed to fetch torrents data',
|
||||
sites=sites, current_site_id=site_id)
|
||||
except Exception as e:
|
||||
return render_template('site/torrents.html', error=str(e), sites=sites, current_site_id=site_id)
|
||||
|
||||
@site_bp.route('/site/<int:site_id>/torrent/<int:torrent_id>')
|
||||
def torrent_detail(site_id, torrent_id):
|
||||
if 'user_id' not in session:
|
||||
return jsonify({'error': 'Authentication required'}), 401
|
||||
|
||||
config = get_nexusphp_config(site_id)
|
||||
headers = {
|
||||
'Accept': 'application/json',
|
||||
# 'Authorization': f'Bearer {config["api_token"]}'
|
||||
'Authorization': f'Bearer 262|wEGbxaqybJ6ZfLZAAtxX0oiQTymFcelHpc6YHims27a70898'
|
||||
}
|
||||
|
||||
try:
|
||||
response = requests.get(f'{config["site_url"]}/api/v1/detail/{torrent_id}?includes=user,tags&include_fields[torrent]=description,download_url', headers=headers)
|
||||
if response.status_code == 200:
|
||||
torrent_data = response.json()
|
||||
return render_template('site/torrent_detail.html', torrent=torrent_data)
|
||||
else:
|
||||
return jsonify({'error': 'Failed to fetch torrent data'}), response.status_code
|
||||
except Exception as e:
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
@site_bp.route('/site/<int:site_id>/bookmarks')
|
||||
def bookmarks(site_id):
|
||||
if 'user_id' not in session:
|
||||
return redirect(url_for('auth.login'))
|
||||
|
||||
sites = get_all_sites()
|
||||
config = get_nexusphp_config(site_id)
|
||||
headers = {
|
||||
'Accept': 'application/json',
|
||||
'Authorization': f'Bearer {config["api_token"]}'
|
||||
}
|
||||
|
||||
try:
|
||||
response = requests.get(f'{config["site_url"]}/api/v1/bookmarks', headers=headers)
|
||||
if response.status_code == 200:
|
||||
bookmarks_data = response.json()
|
||||
return render_template('site/bookmarks.html', bookmarks=bookmarks_data,
|
||||
sites=sites, current_site_id=site_id)
|
||||
else:
|
||||
return render_template('site/bookmarks.html', error='Failed to fetch bookmarks data',
|
||||
sites=sites, current_site_id=site_id)
|
||||
except Exception as e:
|
||||
return render_template('site/bookmarks.html', error=str(e), sites=sites, current_site_id=site_id)
|
||||
|
||||
@site_bp.route('/site/<int:site_id>/bookmark/add/<int:torrent_id>', methods=['POST'])
|
||||
def add_bookmark(site_id, torrent_id):
|
||||
if 'user_id' not in session:
|
||||
return jsonify({'error': 'Authentication required'}), 401
|
||||
|
||||
config = get_nexusphp_config(site_id)
|
||||
headers = {
|
||||
'Accept': 'application/json',
|
||||
'Authorization': f'Bearer {config["api_token"]}'
|
||||
}
|
||||
|
||||
data = {
|
||||
'torrent_id': torrent_id
|
||||
}
|
||||
|
||||
try:
|
||||
response = requests.post(f'{config["site_url"]}/api/v1/bookmarks',
|
||||
headers=headers, data=data)
|
||||
if response.status_code == 200:
|
||||
return jsonify({'success': True})
|
||||
else:
|
||||
return jsonify({'error': 'Failed to add bookmark'}), response.status_code
|
||||
except Exception as e:
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
@site_bp.route('/site/<int:site_id>/bookmark/remove/<int:torrent_id>', methods=['POST'])
|
||||
def remove_bookmark(site_id, torrent_id):
|
||||
if 'user_id' not in session:
|
||||
return jsonify({'error': 'Authentication required'}), 401
|
||||
|
||||
config = get_nexusphp_config(site_id)
|
||||
headers = {
|
||||
'Accept': 'application/json',
|
||||
'Authorization': f'Bearer {config["api_token"]}'
|
||||
}
|
||||
|
||||
data = {
|
||||
'torrent_id': torrent_id
|
||||
}
|
||||
|
||||
try:
|
||||
response = requests.post(f'{config["site_url"]}/api/v1/bookmarks/delete',
|
||||
headers=headers, data=data)
|
||||
if response.status_code == 200:
|
||||
return jsonify({'success': True})
|
||||
else:
|
||||
return jsonify({'error': 'Failed to remove bookmark'}), response.status_code
|
||||
except Exception as e:
|
||||
return jsonify({'error': str(e)}), 500
|
||||
121
routes/transmission.py
Normal file
121
routes/transmission.py
Normal file
@@ -0,0 +1,121 @@
|
||||
from flask import Blueprint, render_template, session, redirect, url_for, jsonify, request
|
||||
import sqlite3
|
||||
import transmission_rpc
|
||||
import sys
|
||||
import os
|
||||
|
||||
# Import format functions
|
||||
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
|
||||
from utils.format import format_file_size, format_status
|
||||
|
||||
transmission_bp = Blueprint('transmission', __name__)
|
||||
|
||||
def get_db_connection():
|
||||
conn = sqlite3.connect('pt_manager.db')
|
||||
conn.row_factory = sqlite3.Row
|
||||
return conn
|
||||
|
||||
def get_transmission_client():
|
||||
conn = get_db_connection()
|
||||
client_config = conn.execute(
|
||||
"SELECT * FROM clients WHERE name = 'transmission' AND enabled = 1"
|
||||
).fetchone()
|
||||
conn.close()
|
||||
|
||||
if not client_config:
|
||||
return None
|
||||
|
||||
try:
|
||||
tc = transmission_rpc.Client(
|
||||
host=client_config['host'],
|
||||
port=client_config['port'],
|
||||
username=client_config['username'],
|
||||
password=client_config['password']
|
||||
)
|
||||
return tc
|
||||
except Exception as e:
|
||||
print(f"Failed to connect to Transmission: {e}")
|
||||
return None
|
||||
|
||||
@transmission_bp.route('/transmission')
|
||||
def transmission_index():
|
||||
if 'user_id' not in session:
|
||||
return redirect(url_for('auth.login'))
|
||||
return render_template('transmission/index.html')
|
||||
|
||||
@transmission_bp.route('/transmission/torrents')
|
||||
def torrents():
|
||||
if 'user_id' not in session:
|
||||
return redirect(url_for('auth.login'))
|
||||
|
||||
tc = get_transmission_client()
|
||||
if not tc:
|
||||
return render_template('transmission/torrents.html', error='Transmission client not configured or unavailable')
|
||||
|
||||
try:
|
||||
torrents = tc.get_torrents()
|
||||
# Process torrents to format file sizes and statuses
|
||||
processed_torrents = []
|
||||
for torrent in torrents:
|
||||
# Create a dictionary with the torrent attributes we need
|
||||
processed_torrent = {
|
||||
'id': torrent.id,
|
||||
'name': torrent.name,
|
||||
'totalSize': format_file_size(torrent.total_size),
|
||||
'percentDone': torrent.progress,
|
||||
'status': torrent.status,
|
||||
'statusString': format_status(torrent.status),
|
||||
'peersSendingToUs': getattr(torrent, 'peers_sending_to_us', 0),
|
||||
'peersGettingFromUs': getattr(torrent, 'peers_getting_from_us', 0)
|
||||
}
|
||||
processed_torrents.append(processed_torrent)
|
||||
|
||||
return render_template('transmission/torrents.html', torrents=processed_torrents)
|
||||
except Exception as e:
|
||||
return render_template('transmission/torrents.html', error=f'Failed to fetch torrents: {str(e)}')
|
||||
|
||||
@transmission_bp.route('/transmission/torrent/<int:torrent_id>/stop', methods=['POST'])
|
||||
def stop_torrent(torrent_id):
|
||||
if 'user_id' not in session:
|
||||
return jsonify({'error': 'Authentication required'}), 401
|
||||
|
||||
tc = get_transmission_client()
|
||||
if not tc:
|
||||
return jsonify({'error': 'Transmission client not configured or unavailable'}), 500
|
||||
|
||||
try:
|
||||
tc.stop_torrent(ids=[torrent_id])
|
||||
return jsonify({'success': True})
|
||||
except Exception as e:
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
@transmission_bp.route('/transmission/torrent/<int:torrent_id>/start', methods=['POST'])
|
||||
def start_torrent(torrent_id):
|
||||
if 'user_id' not in session:
|
||||
return jsonify({'error': 'Authentication required'}), 401
|
||||
|
||||
tc = get_transmission_client()
|
||||
if not tc:
|
||||
return jsonify({'error': 'Transmission client not configured or unavailable'}), 500
|
||||
|
||||
try:
|
||||
tc.start_torrent(ids=[torrent_id])
|
||||
return jsonify({'success': True})
|
||||
except Exception as e:
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
@transmission_bp.route('/transmission/torrent/<int:torrent_id>/remove', methods=['POST'])
|
||||
def remove_torrent(torrent_id):
|
||||
if 'user_id' not in session:
|
||||
return jsonify({'error': 'Authentication required'}), 401
|
||||
|
||||
tc = get_transmission_client()
|
||||
if not tc:
|
||||
return jsonify({'error': 'Transmission client not configured or unavailable'}), 500
|
||||
|
||||
try:
|
||||
# Remove torrent and data
|
||||
tc.remove_torrent(ids=[torrent_id], delete_data=True)
|
||||
return jsonify({'success': True})
|
||||
except Exception as e:
|
||||
return jsonify({'error': str(e)}), 500
|
||||
79
routes/user.py
Normal file
79
routes/user.py
Normal file
@@ -0,0 +1,79 @@
|
||||
from flask import Blueprint, render_template, session, redirect, url_for, jsonify, request
|
||||
import sqlite3
|
||||
import secrets
|
||||
|
||||
user_bp = Blueprint('user', __name__)
|
||||
|
||||
def get_db_connection():
|
||||
conn = sqlite3.connect('pt_manager.db')
|
||||
conn.row_factory = sqlite3.Row
|
||||
return conn
|
||||
|
||||
@user_bp.route('/user')
|
||||
def user_index():
|
||||
if 'user_id' not in session:
|
||||
return redirect(url_for('auth.login'))
|
||||
|
||||
if session['role'] != 'admin':
|
||||
return redirect(url_for('main.index'))
|
||||
|
||||
conn = get_db_connection()
|
||||
users = conn.execute('SELECT id, username, role, created_at FROM users').fetchall()
|
||||
conn.close()
|
||||
|
||||
return render_template('user/index.html', users=users)
|
||||
|
||||
@user_bp.route('/user/add', methods=['POST'])
|
||||
def add_user():
|
||||
if 'user_id' not in session:
|
||||
return jsonify({'error': 'Authentication required'}), 401
|
||||
|
||||
if session['role'] != 'admin':
|
||||
return jsonify({'error': 'Admin access required'}), 403
|
||||
|
||||
username = request.form.get('username')
|
||||
role = request.form.get('role', 'user')
|
||||
|
||||
if not username:
|
||||
return jsonify({'error': 'Username is required'}), 400
|
||||
|
||||
# Generate a random password
|
||||
password = secrets.token_hex(8)
|
||||
|
||||
conn = get_db_connection()
|
||||
try:
|
||||
conn.execute(
|
||||
'INSERT INTO users (username, password_hash, role) VALUES (?, ?, ?)',
|
||||
(username, password, role)
|
||||
)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return jsonify({'success': True, 'username': username, 'password': password})
|
||||
except sqlite3.IntegrityError:
|
||||
conn.close()
|
||||
return jsonify({'error': 'Username already exists'}), 400
|
||||
except Exception as e:
|
||||
conn.close()
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
@user_bp.route('/user/delete/<int:user_id>', methods=['POST'])
|
||||
def delete_user(user_id):
|
||||
if 'user_id' not in session:
|
||||
return jsonify({'error': 'Authentication required'}), 401
|
||||
|
||||
if session['role'] != 'admin':
|
||||
return jsonify({'error': 'Admin access required'}), 403
|
||||
|
||||
# Prevent deleting oneself
|
||||
if user_id == session['user_id']:
|
||||
return jsonify({'error': 'Cannot delete yourself'}), 400
|
||||
|
||||
conn = get_db_connection()
|
||||
try:
|
||||
conn.execute('DELETE FROM users WHERE id = ?', (user_id,))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return jsonify({'success': True})
|
||||
except Exception as e:
|
||||
conn.close()
|
||||
return jsonify({'error': str(e)}), 500
|
||||
Reference in New Issue
Block a user