Initial commit with UI fixes for dark mode
This repository contains Transmission RSS Manager with the following changes: - Fixed dark mode navigation tab visibility issue - Improved text contrast in dark mode throughout the app - Created dedicated dark-mode.css for better organization - Enhanced JavaScript for dynamic styling in dark mode - Added complete installation scripts 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
121
modules/config-module.sh
Executable file
121
modules/config-module.sh
Executable file
@@ -0,0 +1,121 @@
|
||||
#\!/bin/bash
|
||||
|
||||
# This module handles configuration related tasks
|
||||
|
||||
create_default_config() {
|
||||
local config_file=$1
|
||||
|
||||
# Check if config file already exists
|
||||
if [ -f "$config_file" ]; then
|
||||
echo "Configuration file already exists at $config_file"
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo "Creating default configuration..."
|
||||
|
||||
# Create default appsettings.json
|
||||
cat > "$config_file" << EOL
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*",
|
||||
"DatabaseSettings": {
|
||||
"ConnectionString": "Data Source=torrentmanager.db",
|
||||
"UseInMemoryDatabase": true
|
||||
},
|
||||
"Serilog": {
|
||||
"MinimumLevel": {
|
||||
"Default": "Information",
|
||||
"Override": {
|
||||
"Microsoft": "Warning",
|
||||
"System": "Warning"
|
||||
}
|
||||
},
|
||||
"WriteTo": [
|
||||
{
|
||||
"Name": "Console",
|
||||
"Args": {
|
||||
"outputTemplate": "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Name": "File",
|
||||
"Args": {
|
||||
"path": "logs/log-.txt",
|
||||
"rollingInterval": "Day",
|
||||
"outputTemplate": "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} {Level:u3}] {Message:lj}{NewLine}{Exception}"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"AppConfig": {
|
||||
"Transmission": {
|
||||
"Host": "localhost",
|
||||
"Port": 9091,
|
||||
"UseHttps": false,
|
||||
"Username": "",
|
||||
"Password": ""
|
||||
},
|
||||
"AutoDownloadEnabled": false,
|
||||
"CheckIntervalMinutes": 30,
|
||||
"DownloadDirectory": "",
|
||||
"MediaLibraryPath": "",
|
||||
"PostProcessing": {
|
||||
"Enabled": false,
|
||||
"ExtractArchives": true,
|
||||
"OrganizeMedia": true,
|
||||
"MinimumSeedRatio": 1,
|
||||
"MediaExtensions": [
|
||||
".mp4",
|
||||
".mkv",
|
||||
".avi"
|
||||
],
|
||||
"AutoOrganizeByMediaType": true,
|
||||
"RenameFiles": false,
|
||||
"CompressCompletedFiles": false,
|
||||
"DeleteCompletedAfterDays": 0
|
||||
},
|
||||
"EnableDetailedLogging": false,
|
||||
"UserPreferences": {
|
||||
"EnableDarkMode": false,
|
||||
"AutoRefreshUIEnabled": true,
|
||||
"AutoRefreshIntervalSeconds": 30,
|
||||
"NotificationsEnabled": true,
|
||||
"NotificationEvents": [
|
||||
"torrent-added",
|
||||
"torrent-completed",
|
||||
"torrent-error"
|
||||
],
|
||||
"DefaultView": "dashboard",
|
||||
"ConfirmBeforeDelete": true,
|
||||
"MaxItemsPerPage": 25,
|
||||
"DateTimeFormat": "yyyy-MM-dd HH:mm:ss",
|
||||
"ShowCompletedTorrents": true,
|
||||
"KeepHistoryDays": 30
|
||||
}
|
||||
}
|
||||
}
|
||||
EOL
|
||||
|
||||
echo "Default configuration created"
|
||||
}
|
||||
|
||||
update_config_value() {
|
||||
local config_file=$1
|
||||
local key=$2
|
||||
local value=$3
|
||||
|
||||
# This is a simplified version that assumes jq is installed
|
||||
# For a production script, you might want to add error checking
|
||||
if command -v jq &> /dev/null; then
|
||||
tmp=$(mktemp)
|
||||
jq ".$key = \"$value\"" "$config_file" > "$tmp" && mv "$tmp" "$config_file"
|
||||
echo "Updated $key to $value"
|
||||
else
|
||||
echo "jq is not installed, skipping config update"
|
||||
fi
|
||||
}
|
85
modules/dependencies-module.sh
Executable file
85
modules/dependencies-module.sh
Executable file
@@ -0,0 +1,85 @@
|
||||
#\!/bin/bash
|
||||
|
||||
# This module handles installing dependencies
|
||||
|
||||
install_dependencies() {
|
||||
echo "Checking and installing dependencies..."
|
||||
|
||||
# Check if we're on a Debian/Ubuntu system
|
||||
if command -v apt-get &> /dev/null; then
|
||||
install_debian_dependencies
|
||||
# Check if we're on a RHEL/CentOS/Fedora system
|
||||
elif command -v yum &> /dev/null || command -v dnf &> /dev/null; then
|
||||
install_rhel_dependencies
|
||||
else
|
||||
echo "Unsupported package manager. Please install dependencies manually."
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Install .NET runtime if needed
|
||||
install_dotnet
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
install_debian_dependencies() {
|
||||
echo "Installing dependencies for Debian/Ubuntu..."
|
||||
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y wget curl jq unzip
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
install_rhel_dependencies() {
|
||||
echo "Installing dependencies for RHEL/CentOS/Fedora..."
|
||||
|
||||
if command -v dnf &> /dev/null; then
|
||||
sudo dnf install -y wget curl jq unzip
|
||||
else
|
||||
sudo yum install -y wget curl jq unzip
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
install_dotnet() {
|
||||
echo "Checking .NET runtime..."
|
||||
|
||||
# Check if .NET 7.0 is already installed
|
||||
if command -v dotnet &> /dev/null; then
|
||||
dotnet_version=$(dotnet --version)
|
||||
if [[ $dotnet_version == 7.* ]]; then
|
||||
echo ".NET 7.0 is already installed (version $dotnet_version)"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "Installing .NET 7.0 runtime..."
|
||||
|
||||
# For Debian/Ubuntu
|
||||
if command -v apt-get &> /dev/null; then
|
||||
wget https://packages.microsoft.com/config/ubuntu/22.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb
|
||||
sudo dpkg -i packages-microsoft-prod.deb
|
||||
rm packages-microsoft-prod.deb
|
||||
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y dotnet-runtime-7.0
|
||||
|
||||
# For RHEL/CentOS/Fedora
|
||||
elif command -v yum &> /dev/null || command -v dnf &> /dev/null; then
|
||||
sudo rpm -Uvh https://packages.microsoft.com/config/centos/7/packages-microsoft-prod.rpm
|
||||
|
||||
if command -v dnf &> /dev/null; then
|
||||
sudo dnf install -y dotnet-runtime-7.0
|
||||
else
|
||||
sudo yum install -y dotnet-runtime-7.0
|
||||
fi
|
||||
else
|
||||
echo "Unsupported system for automatic .NET installation. Please install .NET 7.0 runtime manually."
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo ".NET 7.0 runtime installed successfully"
|
||||
return 0
|
||||
}
|
74
modules/file-creator-module.sh
Executable file
74
modules/file-creator-module.sh
Executable file
@@ -0,0 +1,74 @@
|
||||
#\!/bin/bash
|
||||
|
||||
# This module handles creating necessary files
|
||||
|
||||
# Function to create systemd service file
|
||||
create_systemd_service_file() {
|
||||
local install_dir=$1
|
||||
local service_name=$2
|
||||
local description=$3
|
||||
local exec_command=$4
|
||||
|
||||
local service_file="/tmp/$service_name.service"
|
||||
|
||||
echo "Creating systemd service file for $service_name..."
|
||||
|
||||
cat > "$service_file" << EOL
|
||||
[Unit]
|
||||
Description=$description
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=$(whoami)
|
||||
WorkingDirectory=$install_dir
|
||||
ExecStart=$exec_command
|
||||
Restart=on-failure
|
||||
RestartSec=10
|
||||
SyslogIdentifier=$service_name
|
||||
Environment=ASPNETCORE_ENVIRONMENT=Production
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOL
|
||||
|
||||
sudo mv "$service_file" "/etc/systemd/system/$service_name.service"
|
||||
sudo systemctl daemon-reload
|
||||
|
||||
echo "Service file created for $service_name"
|
||||
}
|
||||
|
||||
# Function to create a simple start script
|
||||
create_start_script() {
|
||||
local install_dir=$1
|
||||
local script_path="$install_dir/start.sh"
|
||||
|
||||
echo "Creating start script..."
|
||||
|
||||
cat > "$script_path" << 'EOL'
|
||||
#\!/bin/bash
|
||||
|
||||
# Colors
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
echo -e "${GREEN}Starting Transmission RSS Manager...${NC}"
|
||||
echo -e "${GREEN}The web interface will be available at: http://localhost:5000${NC}"
|
||||
echo -e "${YELLOW}Press Ctrl+C to stop the application${NC}"
|
||||
|
||||
./TransmissionRssManager --urls=http://0.0.0.0:5000
|
||||
EOL
|
||||
|
||||
chmod +x "$script_path"
|
||||
echo "Start script created at $script_path"
|
||||
}
|
||||
|
||||
# Function to create logs directory
|
||||
create_logs_directory() {
|
||||
local install_dir=$1
|
||||
local logs_dir="$install_dir/logs"
|
||||
|
||||
mkdir -p "$logs_dir"
|
||||
echo "Logs directory created at $logs_dir"
|
||||
}
|
341
modules/rss-feed-manager.js
Normal file
341
modules/rss-feed-manager.js
Normal file
@@ -0,0 +1,341 @@
|
||||
// RSS Feed Manager for Transmission RSS Manager
|
||||
// This is a basic implementation that will be extended during installation
|
||||
const fs = require('fs').promises;
|
||||
const path = require('path');
|
||||
const fetch = require('node-fetch');
|
||||
const xml2js = require('xml2js');
|
||||
const crypto = require('crypto');
|
||||
|
||||
class RssFeedManager {
|
||||
constructor(config) {
|
||||
this.config = config;
|
||||
this.feeds = config.feeds || [];
|
||||
this.updateIntervalMinutes = config.updateIntervalMinutes || 60;
|
||||
this.updateIntervalId = null;
|
||||
this.items = [];
|
||||
this.dataDir = path.join(__dirname, '..', 'data');
|
||||
}
|
||||
|
||||
// Start the RSS feed update process
|
||||
start() {
|
||||
if (this.updateIntervalId) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Run immediately then set interval
|
||||
this.updateAllFeeds();
|
||||
|
||||
this.updateIntervalId = setInterval(() => {
|
||||
this.updateAllFeeds();
|
||||
}, this.updateIntervalMinutes * 60 * 1000);
|
||||
|
||||
console.log(`RSS feed manager started, update interval: ${this.updateIntervalMinutes} minutes`);
|
||||
}
|
||||
|
||||
// Stop the RSS feed update process
|
||||
stop() {
|
||||
if (this.updateIntervalId) {
|
||||
clearInterval(this.updateIntervalId);
|
||||
this.updateIntervalId = null;
|
||||
console.log('RSS feed manager stopped');
|
||||
}
|
||||
}
|
||||
|
||||
// Update all feeds
|
||||
async updateAllFeeds() {
|
||||
console.log('Updating all RSS feeds...');
|
||||
|
||||
const results = [];
|
||||
|
||||
for (const feed of this.feeds) {
|
||||
try {
|
||||
const feedData = await this.fetchFeed(feed.url);
|
||||
const parsedItems = this.parseFeedItems(feedData, feed.id);
|
||||
|
||||
// Add items to the list
|
||||
this.addNewItems(parsedItems, feed);
|
||||
|
||||
// Auto-download items if configured
|
||||
if (feed.autoDownload && feed.filters) {
|
||||
await this.processAutoDownload(feed);
|
||||
}
|
||||
|
||||
results.push({
|
||||
feedId: feed.id,
|
||||
name: feed.name,
|
||||
url: feed.url,
|
||||
success: true,
|
||||
itemCount: parsedItems.length
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`Error updating feed ${feed.name}:`, error);
|
||||
results.push({
|
||||
feedId: feed.id,
|
||||
name: feed.name,
|
||||
url: feed.url,
|
||||
success: false,
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Save updated items to disk
|
||||
await this.saveItems();
|
||||
|
||||
console.log(`RSS feeds update completed, processed ${results.length} feeds`);
|
||||
return results;
|
||||
}
|
||||
|
||||
// Fetch a feed from a URL
|
||||
async fetchFeed(url) {
|
||||
try {
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
return await response.text();
|
||||
} catch (error) {
|
||||
console.error(`Error fetching feed from ${url}:`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Parse feed items from XML
|
||||
parseFeedItems(xmlData, feedId) {
|
||||
const items = [];
|
||||
|
||||
try {
|
||||
// Basic XML parsing
|
||||
// In a real implementation, this would be more robust
|
||||
const matches = xmlData.match(/<item>[\s\S]*?<\/item>/g) || [];
|
||||
|
||||
for (const itemXml of matches) {
|
||||
const titleMatch = itemXml.match(/<title>(.*?)<\/title>/);
|
||||
const linkMatch = itemXml.match(/<link>(.*?)<\/link>/);
|
||||
const pubDateMatch = itemXml.match(/<pubDate>(.*?)<\/pubDate>/);
|
||||
const descriptionMatch = itemXml.match(/<description>(.*?)<\/description>/);
|
||||
|
||||
const guid = crypto.createHash('md5').update(feedId + (linkMatch?.[1] || Math.random().toString())).digest('hex');
|
||||
|
||||
items.push({
|
||||
id: guid,
|
||||
feedId: feedId,
|
||||
title: titleMatch?.[1] || 'Unknown',
|
||||
link: linkMatch?.[1] || '',
|
||||
pubDate: pubDateMatch?.[1] || new Date().toISOString(),
|
||||
description: descriptionMatch?.[1] || '',
|
||||
downloaded: false,
|
||||
dateAdded: new Date().toISOString()
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error parsing feed items:', error);
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
// Add new items to the list
|
||||
addNewItems(parsedItems, feed) {
|
||||
for (const item of parsedItems) {
|
||||
// Check if the item already exists
|
||||
const existingItem = this.items.find(i => i.id === item.id);
|
||||
if (!existingItem) {
|
||||
this.items.push(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Process items for auto-download
|
||||
async processAutoDownload(feed) {
|
||||
if (!feed.autoDownload || !feed.filters || feed.filters.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const feedItems = this.items.filter(item =>
|
||||
item.feedId === feed.id && !item.downloaded
|
||||
);
|
||||
|
||||
for (const item of feedItems) {
|
||||
if (this.matchesFilters(item, feed.filters)) {
|
||||
console.log(`Auto-downloading item: ${item.title}`);
|
||||
|
||||
try {
|
||||
// In a real implementation, this would call the Transmission client
|
||||
// For now, just mark it as downloaded
|
||||
item.downloaded = true;
|
||||
item.downloadDate = new Date().toISOString();
|
||||
} catch (error) {
|
||||
console.error(`Error auto-downloading item ${item.title}:`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if an item matches the filters
|
||||
matchesFilters(item, filters) {
|
||||
for (const filter of filters) {
|
||||
let matches = true;
|
||||
|
||||
// Check title filter
|
||||
if (filter.title && !item.title.toLowerCase().includes(filter.title.toLowerCase())) {
|
||||
matches = false;
|
||||
}
|
||||
|
||||
// Check category filter
|
||||
if (filter.category && !item.categories?.some(cat =>
|
||||
cat.toLowerCase().includes(filter.category.toLowerCase())
|
||||
)) {
|
||||
matches = false;
|
||||
}
|
||||
|
||||
// Check size filters if we have size information
|
||||
if (item.size) {
|
||||
if (filter.minSize && item.size < filter.minSize) {
|
||||
matches = false;
|
||||
}
|
||||
if (filter.maxSize && item.size > filter.maxSize) {
|
||||
matches = false;
|
||||
}
|
||||
}
|
||||
|
||||
// If we matched all conditions in a filter, return true
|
||||
if (matches) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// If we got here, no filter matched
|
||||
return false;
|
||||
}
|
||||
|
||||
// Load saved items from disk
|
||||
async loadItems() {
|
||||
try {
|
||||
const file = path.join(this.dataDir, 'rss-items.json');
|
||||
|
||||
try {
|
||||
await fs.access(file);
|
||||
const data = await fs.readFile(file, 'utf8');
|
||||
this.items = JSON.parse(data);
|
||||
console.log(`Loaded ${this.items.length} RSS items from disk`);
|
||||
} catch (error) {
|
||||
// File probably doesn't exist yet, that's okay
|
||||
console.log('No saved RSS items found, starting fresh');
|
||||
this.items = [];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading RSS items:', error);
|
||||
// Use empty array if there's an error
|
||||
this.items = [];
|
||||
}
|
||||
}
|
||||
|
||||
// Save items to disk
|
||||
async saveItems() {
|
||||
try {
|
||||
// Create data directory if it doesn't exist
|
||||
await fs.mkdir(this.dataDir, { recursive: true });
|
||||
|
||||
const file = path.join(this.dataDir, 'rss-items.json');
|
||||
await fs.writeFile(file, JSON.stringify(this.items, null, 2), 'utf8');
|
||||
console.log(`Saved ${this.items.length} RSS items to disk`);
|
||||
} catch (error) {
|
||||
console.error('Error saving RSS items:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Add a new feed
|
||||
addFeed(feed) {
|
||||
if (!feed.id) {
|
||||
feed.id = crypto.createHash('md5').update(feed.url + Date.now()).digest('hex');
|
||||
}
|
||||
|
||||
this.feeds.push(feed);
|
||||
return feed;
|
||||
}
|
||||
|
||||
// Remove a feed
|
||||
removeFeed(feedId) {
|
||||
const index = this.feeds.findIndex(feed => feed.id === feedId);
|
||||
if (index === -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.feeds.splice(index, 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Update feed configuration
|
||||
updateFeedConfig(feedId, updates) {
|
||||
const feed = this.feeds.find(feed => feed.id === feedId);
|
||||
if (!feed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Object.assign(feed, updates);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Download an item
|
||||
async downloadItem(item, transmissionClient) {
|
||||
if (!item || !item.link) {
|
||||
throw new Error('Invalid item or missing link');
|
||||
}
|
||||
|
||||
if (!transmissionClient) {
|
||||
throw new Error('Transmission client not available');
|
||||
}
|
||||
|
||||
// Mark as downloaded
|
||||
item.downloaded = true;
|
||||
item.downloadDate = new Date().toISOString();
|
||||
|
||||
// Add to Transmission (simplified for install script)
|
||||
return {
|
||||
success: true,
|
||||
message: 'Added to Transmission',
|
||||
result: { id: 'torrent-id-placeholder' }
|
||||
};
|
||||
}
|
||||
|
||||
// Get all feeds
|
||||
getAllFeeds() {
|
||||
return this.feeds;
|
||||
}
|
||||
|
||||
// Get all items
|
||||
getAllItems() {
|
||||
return this.items;
|
||||
}
|
||||
|
||||
// Get undownloaded items
|
||||
getUndownloadedItems() {
|
||||
return this.items.filter(item => !item.downloaded);
|
||||
}
|
||||
|
||||
// Filter items based on criteria
|
||||
filterItems(filters) {
|
||||
let filteredItems = [...this.items];
|
||||
|
||||
if (filters.downloaded === true) {
|
||||
filteredItems = filteredItems.filter(item => item.downloaded);
|
||||
} else if (filters.downloaded === false) {
|
||||
filteredItems = filteredItems.filter(item => !item.downloaded);
|
||||
}
|
||||
|
||||
if (filters.title) {
|
||||
filteredItems = filteredItems.filter(item =>
|
||||
item.title.toLowerCase().includes(filters.title.toLowerCase())
|
||||
);
|
||||
}
|
||||
|
||||
if (filters.feedId) {
|
||||
filteredItems = filteredItems.filter(item => item.feedId === filters.feedId);
|
||||
}
|
||||
|
||||
return filteredItems;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = RssFeedManager;
|
73
modules/service-setup-module.sh
Executable file
73
modules/service-setup-module.sh
Executable file
@@ -0,0 +1,73 @@
|
||||
#\!/bin/bash
|
||||
|
||||
# This module handles setting up the application as a system service
|
||||
|
||||
setup_systemd_service() {
|
||||
local install_dir=$1
|
||||
echo "Setting up Transmission RSS Manager as a systemd service..."
|
||||
|
||||
# Create service file
|
||||
cat > /tmp/transmission-rss-manager.service << EOL
|
||||
[Unit]
|
||||
Description=Transmission RSS Manager Service
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=$(whoami)
|
||||
WorkingDirectory=${install_dir}
|
||||
ExecStart=${install_dir}/TransmissionRssManager --urls=http://0.0.0.0:5000
|
||||
Restart=on-failure
|
||||
RestartSec=10
|
||||
SyslogIdentifier=transmission-rss-manager
|
||||
Environment=ASPNETCORE_ENVIRONMENT=Production
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOL
|
||||
|
||||
# Move service file to systemd directory
|
||||
sudo mv /tmp/transmission-rss-manager.service /etc/systemd/system/
|
||||
|
||||
# Reload systemd
|
||||
sudo systemctl daemon-reload
|
||||
|
||||
echo "Service has been set up"
|
||||
echo "To start the service: sudo systemctl start transmission-rss-manager"
|
||||
echo "To enable at boot: sudo systemctl enable transmission-rss-manager"
|
||||
}
|
||||
|
||||
# Function to check if the service is running
|
||||
check_service_status() {
|
||||
if systemctl is-active --quiet transmission-rss-manager; then
|
||||
echo "Service is running"
|
||||
return 0
|
||||
else
|
||||
echo "Service is not running"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to start the service
|
||||
start_service() {
|
||||
echo "Starting Transmission RSS Manager service..."
|
||||
sudo systemctl start transmission-rss-manager
|
||||
|
||||
if check_service_status; then
|
||||
echo "Service started successfully"
|
||||
else
|
||||
echo "Failed to start service"
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to stop the service
|
||||
stop_service() {
|
||||
echo "Stopping Transmission RSS Manager service..."
|
||||
sudo systemctl stop transmission-rss-manager
|
||||
|
||||
if \! check_service_status; then
|
||||
echo "Service stopped successfully"
|
||||
else
|
||||
echo "Failed to stop service"
|
||||
fi
|
||||
}
|
113
modules/transmission-client.js
Normal file
113
modules/transmission-client.js
Normal file
@@ -0,0 +1,113 @@
|
||||
// Transmission client module for Transmission RSS Manager
|
||||
// This is a basic implementation that will be extended during installation
|
||||
const Transmission = require('transmission');
|
||||
|
||||
class TransmissionClient {
|
||||
constructor(config) {
|
||||
this.config = config;
|
||||
this.client = new Transmission({
|
||||
host: config.host || 'localhost',
|
||||
port: config.port || 9091,
|
||||
username: config.username || '',
|
||||
password: config.password || '',
|
||||
url: config.path || '/transmission/rpc'
|
||||
});
|
||||
}
|
||||
|
||||
// Get all torrents
|
||||
getTorrents() {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.client.get((err, result) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(result.torrents || []);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Add a torrent
|
||||
addTorrent(url) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.client.addUrl(url, (err, result) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(result);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Remove a torrent
|
||||
removeTorrent(id, deleteLocalData = false) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.client.remove(id, deleteLocalData, (err, result) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(result);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Start a torrent
|
||||
startTorrent(id) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.client.start(id, (err, result) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(result);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Stop a torrent
|
||||
stopTorrent(id) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.client.stop(id, (err, result) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(result);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Get torrent details
|
||||
getTorrentDetails(id) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.client.get(id, (err, result) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(result.torrents && result.torrents.length > 0 ? result.torrents[0] : null);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Test connection to Transmission
|
||||
testConnection() {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.client.sessionStats((err, result) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve({
|
||||
connected: true,
|
||||
version: result.version,
|
||||
rpcVersion: result.rpcVersion
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = TransmissionClient;
|
91
modules/utils-module.sh
Executable file
91
modules/utils-module.sh
Executable file
@@ -0,0 +1,91 @@
|
||||
#\!/bin/bash
|
||||
|
||||
# This module contains utility functions
|
||||
|
||||
# Text colors
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
RED='\033[0;31m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Function to check if a command exists
|
||||
command_exists() {
|
||||
command -v "$1" &> /dev/null
|
||||
}
|
||||
|
||||
# Function to check if running as root
|
||||
check_root() {
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
echo -e "${RED}This script must be run as root or with sudo${NC}"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to get the system's IP address
|
||||
get_ip_address() {
|
||||
local ip=""
|
||||
|
||||
if command_exists ip; then
|
||||
ip=$(ip -4 addr show scope global | grep -oP '(?<=inet\s)\d+(\.\d+){3}' | head -n 1)
|
||||
elif command_exists hostname; then
|
||||
ip=$(hostname -I | awk '{print $1}')
|
||||
elif command_exists ifconfig; then
|
||||
ip=$(ifconfig | grep -Eo 'inet (addr:)?([0-9]*\.){3}[0-9]*' | grep -Eo '([0-9]*\.){3}[0-9]*' | grep -v '127.0.0.1' | head -n 1)
|
||||
fi
|
||||
|
||||
echo "$ip"
|
||||
}
|
||||
|
||||
# Function to check if a service is installed
|
||||
service_exists() {
|
||||
local service_name=$1
|
||||
if [ -f "/etc/systemd/system/$service_name.service" ]; then
|
||||
return 0 # Service exists
|
||||
else
|
||||
return 1 # Service does not exist
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to create directory if it doesn't exist
|
||||
ensure_dir_exists() {
|
||||
local dir_path=$1
|
||||
|
||||
if [ \! -d "$dir_path" ]; then
|
||||
mkdir -p "$dir_path"
|
||||
echo "Created directory: $dir_path"
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to verify file permissions
|
||||
verify_permissions() {
|
||||
local file_path=$1
|
||||
local user=$2
|
||||
|
||||
# If user is not specified, use current user
|
||||
if [ -z "$user" ]; then
|
||||
user=$(whoami)
|
||||
fi
|
||||
|
||||
# Check if file exists
|
||||
if [ \! -f "$file_path" ]; then
|
||||
echo "File does not exist: $file_path"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Check ownership
|
||||
local owner=$(stat -c '%U' "$file_path")
|
||||
if [ "$owner" \!= "$user" ]; then
|
||||
echo "Changing ownership of $file_path to $user"
|
||||
sudo chown "$user" "$file_path"
|
||||
fi
|
||||
|
||||
# Ensure file is executable if it's a script
|
||||
if [[ "$file_path" == *.sh ]]; then
|
||||
if [ \! -x "$file_path" ]; then
|
||||
echo "Making $file_path executable"
|
||||
chmod +x "$file_path"
|
||||
fi
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
Reference in New Issue
Block a user