✨ New Features: - Auto-sync service with configurable intervals (15min, 30min, 1hour, 6hours, 12hours, 24hours, custom cron) - Automatic JSON file synchronization from GitHub repositories - Auto-download new scripts when JSON files are updated - Auto-update existing scripts when newer versions are available - Apprise notification service integration for sync status updates - Comprehensive error handling and logging 🔧 Technical Implementation: - AutoSyncService: Core scheduling and execution logic - GitHubJsonService: Handles JSON file synchronization from GitHub - AppriseService: Sends notifications via multiple channels (Discord, Telegram, Email, Slack, etc.) - ScriptDownloaderService: Manages automatic script downloads and updates - Settings API: RESTful endpoints for auto-sync configuration - UI Integration: Settings modal with auto-sync configuration options 📋 Configuration Options: - Enable/disable auto-sync functionality - Flexible scheduling (predefined intervals or custom cron expressions) - Selective script processing (new downloads, updates, or both) - Notification settings with multiple Apprise URL support - Environment-based configuration with .env file persistence 🎯 Benefits: - Keeps script repository automatically synchronized - Reduces manual maintenance overhead - Provides real-time notifications of sync status - Supports multiple notification channels - Configurable to match different deployment needs This feature significantly enhances the automation capabilities of PVE Scripts Local, making it a truly hands-off solution for script management.
124 lines
3.3 KiB
JavaScript
124 lines
3.3 KiB
JavaScript
import axios from 'axios';
|
|
|
|
export class AppriseService {
|
|
constructor() {
|
|
this.baseUrl = 'http://localhost:8080'; // Default Apprise API URL
|
|
}
|
|
|
|
/**
|
|
* Send notification via Apprise
|
|
* @param {string} title - Notification title
|
|
* @param {string} body - Notification body
|
|
* @param {string[]} urls - Array of Apprise URLs
|
|
*/
|
|
async sendNotification(title, body, urls) {
|
|
if (!urls || urls.length === 0) {
|
|
throw new Error('No Apprise URLs provided');
|
|
}
|
|
|
|
try {
|
|
// Format the notification as form data (Apprise API expects form data)
|
|
const formData = new URLSearchParams();
|
|
formData.append('body', body || '');
|
|
formData.append('title', title || 'PVE Scripts Local');
|
|
formData.append('tags', 'all');
|
|
|
|
// Send to each URL
|
|
const results = [];
|
|
for (const url of urls) {
|
|
try {
|
|
const response = await axios.post(url, formData, {
|
|
headers: {
|
|
'Content-Type': 'application/x-www-form-urlencoded'
|
|
},
|
|
timeout: 10000 // 10 second timeout
|
|
});
|
|
|
|
results.push({
|
|
url,
|
|
success: true,
|
|
status: response.status
|
|
});
|
|
} catch (error) {
|
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
console.error(`Failed to send notification to ${url}:`, errorMessage);
|
|
results.push({
|
|
url,
|
|
success: false,
|
|
error: errorMessage
|
|
});
|
|
}
|
|
}
|
|
|
|
// Check if any notifications succeeded
|
|
const successCount = results.filter(r => r.success).length;
|
|
if (successCount === 0) {
|
|
throw new Error('All notification attempts failed');
|
|
}
|
|
|
|
return {
|
|
success: true,
|
|
message: `Notification sent to ${successCount}/${urls.length} services`,
|
|
results
|
|
};
|
|
|
|
} catch (error) {
|
|
console.error('Apprise notification failed:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Test notification to a single URL
|
|
* @param {string} url - Apprise URL to test
|
|
*/
|
|
async testUrl(url) {
|
|
try {
|
|
await this.sendNotification('Test', 'This is a test notification', [url]);
|
|
return { success: true, message: 'Test notification sent successfully' };
|
|
} catch (error) {
|
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
return { success: false, message: errorMessage };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Validate Apprise URL format
|
|
* @param {string} url - URL to validate
|
|
*/
|
|
validateUrl(url) {
|
|
if (!url || typeof url !== 'string') {
|
|
return { valid: false, error: 'URL is required' };
|
|
}
|
|
|
|
// Basic URL validation
|
|
try {
|
|
new URL(url);
|
|
} catch {
|
|
return { valid: false, error: 'Invalid URL format' };
|
|
}
|
|
|
|
// Check for common Apprise URL patterns
|
|
const apprisePatterns = [
|
|
/^discord:\/\//,
|
|
/^tgram:\/\//,
|
|
/^mailto:\/\//,
|
|
/^slack:\/\//,
|
|
/^https?:\/\//
|
|
];
|
|
|
|
const isValidAppriseUrl = apprisePatterns.some(pattern => pattern.test(url));
|
|
|
|
if (!isValidAppriseUrl) {
|
|
return {
|
|
valid: false,
|
|
error: 'URL does not match known Apprise service patterns'
|
|
};
|
|
}
|
|
|
|
return { valid: true };
|
|
}
|
|
}
|
|
|
|
export const appriseService = new AppriseService();
|