diff --git a/backend/classes/webhook-manager.js b/backend/classes/webhook-manager.js
index 233f2cc..01ee8f0 100644
--- a/backend/classes/webhook-manager.js
+++ b/backend/classes/webhook-manager.js
@@ -19,8 +19,12 @@ class WebhookManager {
await this.triggerEventWebhooks('playback_started', data);
});
- this.eventEmitter.on('user_login', async (data) => {
- await this.triggerEventWebhooks('user_login', data);
+ this.eventEmitter.on('playback_ended', async (data) => {
+ await this.triggerEventWebhooks('playback_ended', data);
+ });
+
+ this.eventEmitter.on('media_recently_added', async (data) => {
+ await this.triggerEventWebhooks('media_recently_added', data);
});
// If needed, add more event listeners here
@@ -40,11 +44,33 @@ class WebhookManager {
).then(res => res.rows);
}
- async triggerEventWebhooks(eventType, data) {
- const webhooks = await this.getWebhooksByEventType(eventType);
-
- for (const webhook of webhooks) {
- await this.executeWebhook(webhook, data);
+ async triggerEventWebhooks(eventType, data = {}) {
+ try {
+ const webhooks = await this.getWebhooksByEventType(eventType);
+
+ if (webhooks.length === 0) {
+ console.log(`[WEBHOOK] No webhooks registered for event: ${eventType}`);
+ return;
+ }
+
+ console.log(`[WEBHOOK] Triggering ${webhooks.length} webhooks for event: ${eventType}`);
+
+ const enrichedData = {
+ ...data,
+ event: eventType,
+ triggeredAt: new Date().toISOString()
+ };
+
+ const promises = webhooks.map(webhook => {
+ return this.executeWebhook(webhook, enrichedData);
+ });
+
+ await Promise.all(promises);
+
+ return true;
+ } catch (error) {
+ console.error(`[WEBHOOK] Error triggering webhooks for event ${eventType}:`, error);
+ return false;
}
}
@@ -135,6 +161,31 @@ class WebhookManager {
return template;
}
+ async triggerEvent(eventType, eventData = {}) {
+ try {
+ const webhooks = this.eventWebhooks?.[eventType] || [];
+
+ if (webhooks.length === 0) {
+ console.log(`[WEBHOOK] No webhooks registered for event: ${eventType}`);
+ return;
+ }
+
+ console.log(`[WEBHOOK] Triggering ${webhooks.length} webhooks for event: ${eventType}`);
+
+ const promises = webhooks.map(webhook => {
+ return this.webhookManager.executeWebhook(webhook, {
+ ...eventData,
+ event: eventType,
+ triggeredAt: new Date().toISOString()
+ });
+ });
+
+ await Promise.all(promises);
+ } catch (error) {
+ console.error(`[WEBHOOK] Error triggering webhooks for event ${eventType}:`, error);
+ }
+ }
+
emitEvent(eventType, data) {
this.eventEmitter.emit(eventType, data);
}
diff --git a/backend/classes/webhook-scheduler.js b/backend/classes/webhook-scheduler.js
index d1fddc3..4340217 100644
--- a/backend/classes/webhook-scheduler.js
+++ b/backend/classes/webhook-scheduler.js
@@ -35,6 +35,54 @@ class WebhookScheduler {
}
}
+ async loadEventWebhooks() {
+ try {
+ const eventWebhooks = await this.webhookManager.getEventWebhooks();
+ if (eventWebhooks && eventWebhooks.length > 0) {
+ this.eventWebhooks = {};
+
+ eventWebhooks.forEach(webhook => {
+ if (!this.eventWebhooks[webhook.eventType]) {
+ this.eventWebhooks[webhook.eventType] = [];
+ }
+ this.eventWebhooks[webhook.eventType].push(webhook);
+ });
+
+ console.log(`[WEBHOOK] Loaded ${eventWebhooks.length} event-based webhooks`);
+ } else {
+ console.log('[WEBHOOK] No event-based webhooks found');
+ this.eventWebhooks = {};
+ }
+ } catch (error) {
+ console.error('[WEBHOOK] Failed to load event-based webhooks:', error);
+ }
+ }
+
+ async triggerEvent(eventType, eventData = {}) {
+ try {
+ const webhooks = this.eventWebhooks[eventType] || [];
+
+ if (webhooks.length === 0) {
+ console.log(`[WEBHOOK] No webhooks registered for event: ${eventType}`);
+ return;
+ }
+
+ console.log(`[WEBHOOK] Triggering ${webhooks.length} webhooks for event: ${eventType}`);
+
+ const promises = webhooks.map(webhook => {
+ return this.webhookManager.executeWebhook(webhook, {
+ event: eventType,
+ data: eventData,
+ triggeredAt: new Date().toISOString()
+ });
+ });
+
+ await Promise.all(promises);
+ } catch (error) {
+ console.error(`[WEBHOOK] Error triggering webhooks for event ${eventType}:`, error);
+ }
+ }
+
scheduleWebhook(webhook) {
try {
this.cronJobs[webhook.id] = cron.schedule(webhook.schedule, async () => {
@@ -50,6 +98,7 @@ class WebhookScheduler {
async refreshSchedule() {
await this.loadScheduledWebhooks();
+ await this.loadEventWebhooks();
}
}
diff --git a/backend/routes/sync.js b/backend/routes/sync.js
index 4f811ce..4783ebf 100644
--- a/backend/routes/sync.js
+++ b/backend/routes/sync.js
@@ -824,6 +824,8 @@ async function partialSync(triggertype) {
const config = await new configClass().getConfig();
const uuid = randomUUID();
+
+ const newItems = [];
syncTask = { loggedData: [], uuid: uuid, wsKey: "PartialSyncTask", taskName: taskName.partialsync };
try {
@@ -833,7 +835,7 @@ async function partialSync(triggertype) {
if (config.error) {
syncTask.loggedData.push({ Message: config.error });
await logging.updateLog(syncTask.uuid, syncTask.loggedData, taskstate.FAILED);
- return;
+ return { success: false, error: config.error };
}
const libraries = await API.getLibraries();
@@ -842,7 +844,7 @@ async function partialSync(triggertype) {
syncTask.loggedData.push({ Message: "Error: No Libararies found to sync." });
await logging.updateLog(syncTask.uuid, syncTask.loggedData, taskstate.FAILED);
sendUpdate(syncTask.wsKey, { type: "Success", message: triggertype + " " + taskName.fullsync + " Completed" });
- return;
+ return { success: false, error: "No libraries found" };
}
const excluded_libraries = config.settings.ExcludedLibraries || [];
@@ -850,10 +852,10 @@ async function partialSync(triggertype) {
const filtered_libraries = libraries.filter((library) => !excluded_libraries.includes(library.Id));
const existing_excluded_libraries = libraries.filter((library) => excluded_libraries.includes(library.Id));
- // //syncUserData
+ // syncUserData
await syncUserData();
- // //syncLibraryFolders
+ // syncLibraryFolders
await syncLibraryFolders(filtered_libraries, existing_excluded_libraries);
//item sync counters
@@ -956,7 +958,7 @@ async function partialSync(triggertype) {
insertEpisodeInfoCount += Number(infoCount.insertEpisodeInfoCount);
updateEpisodeInfoCount += Number(infoCount.updateEpisodeInfoCount);
- //clear data from memory as its no longer needed
+ //clear data from memory as it's no longer needed
library_items = null;
seasons = null;
episodes = null;
@@ -1023,10 +1025,22 @@ async function partialSync(triggertype) {
await logging.updateLog(syncTask.uuid, syncTask.loggedData, taskstate.SUCCESS);
sendUpdate(syncTask.wsKey, { type: "Success", message: triggertype + " Sync Completed" });
+
+ return {
+ success: true,
+ newItems: newItems,
+ stats: {
+ itemsAdded: insertedItemsCount,
+ episodesAdded: insertedEpisodeCount,
+ seasonsAdded: insertedSeasonsCount
+ }
+ };
} catch (error) {
syncTask.loggedData.push({ color: "red", Message: getErrorLineNumber(error) + ": Error: " + error });
await logging.updateLog(syncTask.uuid, syncTask.loggedData, taskstate.FAILED);
sendUpdate(syncTask.wsKey, { type: "Error", message: triggertype + " Sync Halted with Errors" });
+
+ return { success: false, error: error.message };
}
}
diff --git a/backend/routes/webhooks.js b/backend/routes/webhooks.js
index 67a6370..6260d79 100644
--- a/backend/routes/webhooks.js
+++ b/backend/routes/webhooks.js
@@ -211,4 +211,104 @@ router.post('/:id/trigger-monthly', async (req, res) => {
}
});
+// Get status of event webhooks
+router.get('/event-status', async (req, res) => {
+ try {
+ const eventTypes = ['playback_started', 'playback_ended', 'media_recently_added'];
+ const result = {};
+
+ for (const eventType of eventTypes) {
+ const webhooks = await dbInstance.query(
+ 'SELECT id, name, enabled FROM webhooks WHERE trigger_type = $1 AND event_type = $2',
+ ['event', eventType]
+ );
+
+ result[eventType] = {
+ exists: webhooks.rows.length > 0,
+ enabled: webhooks.rows.some(webhook => webhook.enabled),
+ webhooks: webhooks.rows
+ };
+ }
+
+ res.json(result);
+ } catch (error) {
+ console.error('Error fetching webhook status:', error);
+ res.status(500).json({ error: 'Failed to fetch webhook status' });
+ }
+});
+
+// Toggle all webhooks of a specific event type
+router.post('/toggle-event/:eventType', async (req, res) => {
+ try {
+ const { eventType } = req.params;
+ const { enabled } = req.body;
+
+ if (!['playback_started', 'playback_ended', 'media_recently_added'].includes(eventType)) {
+ return res.status(400).json({ error: 'Invalid event type' });
+ }
+
+ if (typeof enabled !== 'boolean') {
+ return res.status(400).json({ error: 'Enabled parameter must be a boolean' });
+ }
+
+ // Mettre à jour tous les webhooks de ce type d'événement
+ const result = await dbInstance.query(
+ 'UPDATE webhooks SET enabled = $1 WHERE trigger_type = $2 AND event_type = $3 RETURNING id',
+ [enabled, 'event', eventType]
+ );
+
+ // Si aucun webhook n'existe pour ce type, en créer un de base
+ if (result.rows.length === 0 && enabled) {
+ const defaultWebhook = {
+ name: `Webhook pour ${eventType}`,
+ url: req.body.url || '',
+ method: 'POST',
+ trigger_type: 'event',
+ event_type: eventType,
+ enabled: true,
+ headers: '{}',
+ payload: JSON.stringify({
+ event: `{{event}}`,
+ data: `{{data}}`,
+ timestamp: `{{triggeredAt}}`
+ })
+ };
+
+ if (!defaultWebhook.url) {
+ return res.status(400).json({
+ error: 'URL parameter is required when creating a new webhook',
+ needsUrl: true
+ });
+ }
+
+ await dbInstance.query(
+ `INSERT INTO webhooks (name, url, method, trigger_type, event_type, enabled, headers, payload)
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8)`,
+ [
+ defaultWebhook.name,
+ defaultWebhook.url,
+ defaultWebhook.method,
+ defaultWebhook.trigger_type,
+ defaultWebhook.event_type,
+ defaultWebhook.enabled,
+ defaultWebhook.headers,
+ defaultWebhook.payload
+ ]
+ );
+ }
+
+ // Rafraîchir le planificateur de webhooks
+ await webhookScheduler.refreshSchedule();
+
+ res.json({
+ success: true,
+ message: `Webhooks for ${eventType} ${enabled ? 'enabled' : 'disabled'}`,
+ affectedCount: result.rows.length
+ });
+ } catch (error) {
+ console.error('Error toggling webhooks:', error);
+ res.status(500).json({ error: 'Failed to toggle webhooks' });
+ }
+});
+
module.exports = router;
\ No newline at end of file
diff --git a/backend/tasks/ActivityMonitor.js b/backend/tasks/ActivityMonitor.js
index 43f1969..16d5e11 100644
--- a/backend/tasks/ActivityMonitor.js
+++ b/backend/tasks/ActivityMonitor.js
@@ -7,10 +7,14 @@ const configClass = require("../classes/config");
const API = require("../classes/api-loader");
const { sendUpdate } = require("../ws");
const { isNumber } = require("@mui/x-data-grid/internals");
+const WebhookManager = require("../classes/webhook-manager");
+
const MINIMUM_SECONDS_TO_INCLUDE_PLAYBACK = process.env.MINIMUM_SECONDS_TO_INCLUDE_PLAYBACK
? Number(process.env.MINIMUM_SECONDS_TO_INCLUDE_PLAYBACK)
: 1;
+const webhookManager = new WebhookManager();
+
async function getSessionsInWatchDog(SessionData, WatchdogData) {
let existingData = await WatchdogData.filter((wdData) => {
return SessionData.some((sessionData) => {
@@ -146,6 +150,42 @@ async function ActivityMonitor(interval) {
//filter fix if table is empty
if (WatchdogDataToInsert.length > 0) {
+ for (const session of WatchdogDataToInsert) {
+ let userData = {};
+ try {
+ const userInfo = await API.getUserById(session.UserId);
+ if (userInfo) {
+ userData = {
+ username: userInfo.Name,
+ userImageTag: userInfo.PrimaryImageTag
+ };
+ }
+ } catch (error) {
+ console.error(`[WEBHOOK] Error fetching user data: ${error.message}`);
+ }
+
+ await webhookManager.triggerEventWebhooks('playback_started', {
+ sessionInfo: {
+ userId: session.UserId,
+ deviceId: session.DeviceId,
+ deviceName: session.DeviceName,
+ clientName: session.ClientName,
+ isPaused: session.IsPaused,
+ mediaType: session.MediaType,
+ mediaName: session.NowPlayingItemName,
+ startTime: session.ActivityDateInserted
+ },
+ userData,
+ mediaInfo: {
+ itemId: session.NowPlayingItemId,
+ episodeId: session.EpisodeId,
+ mediaName: session.NowPlayingItemName,
+ seasonName: session.SeasonName,
+ seriesName: session.SeriesName
+ }
+ });
+ }
+
//insert new rows where not existing items
// console.log("Inserted " + WatchdogDataToInsert.length + " wd playback records");
db.insertBulk("jf_activity_watchdog", WatchdogDataToInsert, jf_activity_watchdog_columns);
@@ -158,6 +198,43 @@ async function ActivityMonitor(interval) {
console.log("Existing Data Updated: ", WatchdogDataToUpdate.length);
}
+ if (dataToRemove.length > 0) {
+ for (const session of dataToRemove) {
+ let userData = {};
+ try {
+ const userInfo = await API.getUserById(session.UserId);
+ if (userInfo) {
+ userData = {
+ username: userInfo.Name,
+ userImageTag: userInfo.PrimaryImageTag
+ };
+ }
+ } catch (error) {
+ console.error(`[WEBHOOK] Error fetching user data: ${error.message}`);
+ }
+
+ await webhookManager.triggerEventWebhooks('playback_ended', {
+ sessionInfo: {
+ userId: session.UserId,
+ deviceId: session.DeviceId,
+ deviceName: session.DeviceName,
+ clientName: session.ClientName,
+ playbackDuration: session.PlaybackDuration,
+ endTime: session.ActivityDateInserted
+ },
+ userData,
+ mediaInfo: {
+ itemId: session.NowPlayingItemId,
+ episodeId: session.EpisodeId,
+ mediaName: session.NowPlayingItemName,
+ seasonName: session.SeasonName,
+ seriesName: session.SeriesName
+ }
+ });
+ }
+
+ const toDeleteIds = dataToRemove.map((row) => row.ActivityId);
+
//delete from db no longer in session data and insert into stats db
//Bulk delete from db thats no longer on api
diff --git a/backend/tasks/RecentlyAddedItemsSyncTask.js b/backend/tasks/RecentlyAddedItemsSyncTask.js
index 85f0676..c688c1e 100644
--- a/backend/tasks/RecentlyAddedItemsSyncTask.js
+++ b/backend/tasks/RecentlyAddedItemsSyncTask.js
@@ -1,6 +1,7 @@
const { parentPort } = require("worker_threads");
const triggertype = require("../logging/triggertype");
const sync = require("../routes/sync");
+const WebhookManager = require("../classes/webhook-manager");
async function runPartialSyncTask(triggerType = triggertype.Automatic) {
try {
@@ -17,12 +18,25 @@ async function runPartialSyncTask(triggerType = triggertype.Automatic) {
});
parentPort.postMessage({ type: "log", message: formattedArgs.join(" ") });
};
- await sync.partialSync(triggerType);
+
+ const syncResults = await sync.partialSync(triggerType);
+
+ const webhookManager = new WebhookManager();
+
+ const newMediaCount = syncResults?.newItems?.length || 0;
+
+ if (newMediaCount > 0) {
+ await webhookManager.triggerEventWebhooks('media_recently_added', {
+ count: newMediaCount,
+ items: syncResults.newItems,
+ syncDate: new Date().toISOString(),
+ triggerType: triggerType
+ });
+ }
parentPort.postMessage({ status: "complete" });
} catch (error) {
parentPort.postMessage({ status: "error", message: error.message });
-
console.log(error);
return [];
}
diff --git a/package-lock.json b/package-lock.json
index e3f19fc..7eba186 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -6,7 +6,7 @@
"packages": {
"": {
"name": "jfstat",
- "version": "1.1.5",
+ "version": "1.1.6",
"dependencies": {
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.0",
diff --git a/public/locales/en-UK/translation.json b/public/locales/en-UK/translation.json
index 58ce159..7f6c974 100644
--- a/public/locales/en-UK/translation.json
+++ b/public/locales/en-UK/translation.json
@@ -226,7 +226,9 @@
"URL": "URL",
"TYPE": "Type",
"TRIGGER": "Trigger",
- "STATUS": "Status"
+ "STATUS": "Status",
+ "EVENT_WEBHOOKS": "Event notifications",
+ "EVENT_WEBHOOKS_TOOLTIP": "Enable or disable event notifications"
},
"TASK_TYPE": {
"JOB": "Job",
diff --git a/public/locales/fr-FR/translation.json b/public/locales/fr-FR/translation.json
index 78929ef..d4b799e 100644
--- a/public/locales/fr-FR/translation.json
+++ b/public/locales/fr-FR/translation.json
@@ -213,7 +213,22 @@
},
"SELECT_LIBRARIES_TO_IMPORT": "Sélectionner les médiathèques à importer",
"SELECT_LIBRARIES_TO_IMPORT_TOOLTIP": "L'activité du contenu de ces médiathèques est toujours suivie, même s'ils ne sont pas importés.",
- "DATE_ADDED": "Date d'ajout"
+ "DATE_ADDED": "Date d'ajout",
+ "WEBHOOKS": "Webhooks",
+ "WEBHOOK_TYPE": "Type de webhook",
+ "TEST_NOW": "Tester maintenant",
+ "WEBHOOKS_CONFIGURATION": "Configuration des webhooks",
+ "WEBHOOKS_TOOLTIP": "L'URL des webhooks utiliser pour envoyer des notifications à Discord ou à d'autres services",
+ "WEBHOOK_SAVED": "Webhook sauvegardé",
+ "WEBHOOK_NAME": "Nom du webhook",
+ "DISCORD_WEBHOOK_URL": "URL du webhook Discord",
+ "ENABLE_WEBHOOK": "Activer le webhook",
+ "URL": "URL",
+ "TYPE": "Type",
+ "TRIGGER": "Déclencheur",
+ "STATUS": "Status",
+ "EVENT_WEBHOOKS": "Notifications d'événements",
+ "EVENT_WEBHOOKS_TOOLTIP": "Activez ou désactivez les notifications pour différents événements du système"
},
"TASK_TYPE": {
"JOB": "Job",
diff --git a/src/pages/components/settings/webhooks.jsx b/src/pages/components/settings/webhooks.jsx
index 4b08657..307eabd 100644
--- a/src/pages/components/settings/webhooks.jsx
+++ b/src/pages/components/settings/webhooks.jsx
@@ -63,6 +63,13 @@ function WebhooksSettings() {
webhook_type: 'discord'
});
+ // État pour suivre les webhooks événementiels
+ const [eventWebhooks, setEventWebhooks] = useState({
+ playback_started: { exists: false, enabled: false },
+ playback_ended: { exists: false, enabled: false },
+ media_recently_added: { exists: false, enabled: false }
+ });
+
useEffect(() => {
const fetchWebhooks = async () => {
try {
@@ -73,18 +80,20 @@ function WebhooksSettings() {
},
});
- if (response.data != webhooks) {
+ if (response.data !== webhooks) {
setWebhooks(response.data);
- }
-
- if (loading) {
+ // Charger l'état des webhooks événementiels une fois les webhooks chargés
+ await loadEventWebhooks();
+ }
+
+ if (loading) {
setLoading(false);
- }
+ }
} catch (err) {
console.error("Error loading webhooks:", err);
if (loading) {
setLoading(false);
- }
+ }
}
};
@@ -92,7 +101,7 @@ function WebhooksSettings() {
const intervalId = setInterval(fetchWebhooks, 1000 * 10);
return () => clearInterval(intervalId);
- }, []);
+ }, [webhooks.length]);
const handleInputChange = (e) => {
const { name, value } = e.target;
@@ -111,7 +120,14 @@ function WebhooksSettings() {
setSuccess(false);
if (!currentWebhook.url) {
- setError("Discord webhook URL is required");
+ setError("L'URL du webhook est requise");
+ setSaving(false);
+ return;
+ }
+
+ // Si c'est un webhook de type événement, s'assurer que les propriétés nécessaires sont présentes
+ if (currentWebhook.trigger_type === 'event' && !currentWebhook.event_type) {
+ setError("Le type d'événement est requis pour un webhook événementiel");
setSaving(false);
return;
}
@@ -134,6 +150,20 @@ function WebhooksSettings() {
});
}
+ // Rafraîchir la liste des webhooks
+ const webhooksResponse = await axios.get('/webhooks', {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ "Content-Type": "application/json",
+ },
+ });
+
+ setWebhooks(webhooksResponse.data);
+
+ // Mettre à jour l'état des webhooks événementiels
+ await loadEventWebhooks();
+
+ // Réinitialiser le formulaire
setCurrentWebhook({
name: 'New Webhook',
url: '',
@@ -143,10 +173,11 @@ function WebhooksSettings() {
method: 'POST',
webhook_type: 'discord'
});
- setSuccess("Webhook saved successfully!");
+
+ setSuccess("Webhook enregistré avec succès!");
setSaving(false);
} catch (err) {
- setError("Error during webhook saving: " + (err.response?.data?.error || err.message));
+ setError("Erreur lors de l'enregistrement du webhook: " + (err.response?.data?.error || err.message));
setSaving(false);
}
};
@@ -180,6 +211,118 @@ function WebhooksSettings() {
}
};
+ // Fonction pour obtenir le statut d'un webhook événementiel
+ const getEventWebhookStatus = (eventType) => {
+ return eventWebhooks[eventType]?.enabled || false;
+ };
+
+ // Fonction pour charger le statut des webhooks événementiels
+ const loadEventWebhooks = async () => {
+ try {
+ const eventTypes = ['playback_started', 'playback_ended', 'media_recently_added'];
+ const status = {};
+
+ // Vérifier chaque type d'événement dans les webhooks actuels
+ eventTypes.forEach(eventType => {
+ const matchingWebhooks = webhooks.filter(
+ webhook => webhook.trigger_type === 'event' && webhook.event_type === eventType
+ );
+
+ status[eventType] = {
+ exists: matchingWebhooks.length > 0,
+ enabled: matchingWebhooks.some(webhook => webhook.enabled)
+ };
+ });
+
+ setEventWebhooks(status);
+ } catch (error) {
+ console.error('Error loading event webhook status:', error);
+ }
+ };
+
+ // Fonction pour basculer un webhook événementiel
+ const toggleEventWebhook = async (eventType) => {
+ try {
+ setLoading(true);
+ setError(null);
+
+ const isCurrentlyEnabled = getEventWebhookStatus(eventType);
+ const matchingWebhooks = webhooks.filter(
+ webhook => webhook.trigger_type === 'event' && webhook.event_type === eventType
+ );
+
+ // Si aucun webhook n'existe pour cet événement et qu'on veut l'activer
+ if (matchingWebhooks.length === 0 && !isCurrentlyEnabled) {
+ // Créer un nouveau webhook pour cet événement
+ const newWebhook = {
+ name: `Notification - ${getEventDisplayName(eventType)}`,
+ url: '', // Demander à l'utilisateur de saisir l'URL
+ enabled: true,
+ trigger_type: 'event',
+ event_type: eventType,
+ method: 'POST',
+ webhook_type: 'discord'
+ };
+
+ // Mettre à jour le webhook actuel pour que l'utilisateur puisse le configurer
+ setCurrentWebhook(newWebhook);
+ setLoading(false);
+ return;
+ }
+
+ // Sinon, activer/désactiver tous les webhooks existants pour cet événement
+ for (const webhook of matchingWebhooks) {
+ await axios.put(`/webhooks/${webhook.id}`,
+ { ...webhook, enabled: !isCurrentlyEnabled },
+ {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ "Content-Type": "application/json",
+ }
+ }
+ );
+ }
+
+ // Mettre à jour l'état local
+ setEventWebhooks(prev => ({
+ ...prev,
+ [eventType]: {
+ ...prev[eventType],
+ enabled: !isCurrentlyEnabled
+ }
+ }));
+
+ // Actualiser la liste des webhooks
+ const response = await axios.get('/webhooks', {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ "Content-Type": "application/json",
+ },
+ });
+
+ setWebhooks(response.data);
+ setLoading(false);
+ setSuccess(`Webhook pour ${getEventDisplayName(eventType)} ${!isCurrentlyEnabled ? 'activé' : 'désactivé'} avec succès!`);
+ } catch (error) {
+ setError("Erreur lors de la modification du webhook: " + (error.response?.data?.error || error.message));
+ setLoading(false);
+ }
+ };
+
+ // Fonction utilitaire pour obtenir le nom d'affichage d'un type d'événement
+ const getEventDisplayName = (eventType) => {
+ switch(eventType) {
+ case 'playback_started':
+ return 'Lecture démarrée';
+ case 'playback_ended':
+ return 'Lecture terminée';
+ case 'media_recently_added':
+ return 'Nouveaux médias ajoutés';
+ default:
+ return eventType;
+ }
+ };
+
if (loading && !webhooks.length) {
return
+ Notification lorsqu'un utilisateur commence à regarder un média +
++ Notification lorsqu'un utilisateur termine de regarder un média +
++ Notification lorsque de nouveaux médias sont ajoutés à la bibliothèque +
+