diff --git a/backend/appdata/default.json b/backend/appdata/default.json index 92f56fe..0fae31f 100644 --- a/backend/appdata/default.json +++ b/backend/appdata/default.json @@ -49,7 +49,8 @@ "use_telegram_API": false, "telegram_bot_token": "", "telegram_chat_id": "", - "webhook_URL": "" + "webhook_URL": "", + "discord_webhook_URL": "" }, "Themes": { "default_theme": "default", diff --git a/backend/config.js b/backend/config.js index 28b58c2..2bfdec9 100644 --- a/backend/config.js +++ b/backend/config.js @@ -219,7 +219,8 @@ const DEFAULT_CONFIG = { "use_telegram_API": false, "telegram_bot_token": "", "telegram_chat_id": "", - "webhook_URL": "" + "webhook_URL": "", + "discord_webhook_URL": "" }, "Themes": { "default_theme": "default", diff --git a/backend/consts.js b/backend/consts.js index 506d545..49f7634 100644 --- a/backend/consts.js +++ b/backend/consts.js @@ -158,6 +158,10 @@ exports.CONFIG_ITEMS = { 'key': 'ytdl_webhook_url', 'path': 'YoutubeDLMaterial.API.webhook_URL' }, + 'ytdl_discord_webhook_url': { + 'key': 'ytdl_discord_webhook_url', + 'path': 'YoutubeDLMaterial.API.discord_webhook_URL' + }, // Themes @@ -342,4 +346,6 @@ const YTDL_ARGS_WITH_VALUES = [ // we're using a Set here for performance exports.YTDL_ARGS_WITH_VALUES = new Set(YTDL_ARGS_WITH_VALUES); +exports.ICON_URL = 'https://i.imgur.com/IKOlr0N.png'; + exports.CURRENT_VERSION = 'v4.3.1'; diff --git a/backend/downloader.js b/backend/downloader.js index 7a035c4..9850ccc 100644 --- a/backend/downloader.js +++ b/backend/downloader.js @@ -128,7 +128,7 @@ exports.clearDownload = async (download_uid) => { async function handleDownloadError(download, error_message, error_type = null) { if (!download || !download['uid']) return; - notifications_api.sendDownloadErrorNotification(download, download['user_uid'], error_type); + notifications_api.sendDownloadErrorNotification(download, download['user_uid'], error_message, error_type); await db_api.updateRecord('download_queue', {uid: download['uid']}, {error: error_message, finished: true, running: false, error_type: error_type}); } @@ -314,7 +314,7 @@ async function downloadQueuedFile(download_uid) { clearInterval(download_checker); if (err) { logger.error(err.stderr); - await handleDownloadError(download, err.stderr); + await handleDownloadError(download, err.stderr, 'unknown_error'); resolve(false); return; } else if (output) { @@ -596,7 +596,7 @@ exports.getVideoInfoByURL = async (url, args = [], download_uid = null) => { logger.error(error_message); if (download_uid) { const download = await db_api.getRecord('download_queue', {uid: download_uid}); - await handleDownloadError(download, error_message); + await handleDownloadError(download, error_message, 'info_retrieve_failed'); } resolve(null); } diff --git a/backend/notifications.js b/backend/notifications.js index dac8700..5e64283 100644 --- a/backend/notifications.js +++ b/backend/notifications.js @@ -2,12 +2,16 @@ const db_api = require('./db'); const config_api = require('./config'); const logger = require('./logger'); const utils = require('./utils'); +const consts = require('./consts'); const { uuid } = require('uuidv4'); const fetch = require('node-fetch'); const { gotify } = require("gotify"); const TelegramBot = require('node-telegram-bot-api'); +const REST = require('@discordjs/rest').REST; +const API = require('@discordjs/core').API; +const EmbedBuilder = require('@discordjs/builders').EmbedBuilder; const NOTIFICATION_TYPE_TO_TITLE = { task_finished: 'Task finished', @@ -18,7 +22,7 @@ const NOTIFICATION_TYPE_TO_TITLE = { const NOTIFICATION_TYPE_TO_BODY = { task_finished: (notification) => notification['data']['task_title'], download_complete: (notification) => {return `${notification['data']['file_title']}\nOriginal URL: ${notification['data']['original_url']}`}, - download_error: (notification) => {return `Error: ${notification['data']['download_error_type']}\nURL: ${notification['data']['download_url']}`} + download_error: (notification) => {return `Error: ${notification['data']['download_error_message']}\nError code: ${notification['data']['download_error_type']}\n\nOriginal URL: ${notification['data']['download_url']}`} } const NOTIFICATION_TYPE_TO_URL = { @@ -57,6 +61,9 @@ exports.sendNotification = async (notification) => { if (config_api.getConfigItem('ytdl_webhook_url')) { sendGenericNotification(data); } + if (config_api.getConfigItem('ytdl_discord_webhook_url')) { + sendDiscordNotification(data); + } await db_api.insertRecordIntoTable('notifications', notification); return notification; @@ -79,9 +86,9 @@ exports.sendDownloadNotification = async (file, user_uid) => { return await exports.sendNotification(notification); } -exports.sendDownloadErrorNotification = async (download, user_uid, error_type = null) => { +exports.sendDownloadErrorNotification = async (download, user_uid, error_message, error_type = null) => { if (!notificationEnabled('download_error')) return; - const data = {download_uid: download.uid, download_url: download.url, download_error_type: error_type}; + const data = {download_uid: download.uid, download_url: download.url, download_error_message: error_message, download_error_type: error_type}; const notification = exports.createNotification('download_error', ['view_download_error', 'retry_download'], data, user_uid); return await exports.sendNotification(notification); } @@ -144,6 +151,29 @@ async function sendTelegramNotification({body, title, type, url, thumbnail}) { bot.sendMessage(chat_id, `${title}\n\n${body}\n${url}`, {parse_mode: 'HTML'}); } +async function sendDiscordNotification({body, title, type, url, thumbnail}) { + const discord_webhook_url = config_api.getConfigItem('ytdl_discord_webhook_url'); + const url_split = discord_webhook_url.split('webhooks/'); + const [webhook_id, webhook_token] = url_split[1].split('/'); + const rest = new REST({ version: '10' }); + const api = new API(rest); + const embed = new EmbedBuilder() + .setTitle(title) + .setColor(0x00FFFF) + .setURL(url) + .setDescription(`ID: ${type}`); + if (thumbnail) embed.setThumbnail(thumbnail); + if (type === 'download_error') embed.setColor(0xFC2003); + + const result = await api.webhooks.execute(webhook_id, webhook_token, { + content: body, + username: 'YoutubeDL-Material', + avatar_url: consts.ICON_URL, + embeds: [embed], + }); + return result; +} + function sendGenericNotification(data) { const webhook_url = config_api.getConfigItem('ytdl_webhook_url'); logger.verbose(`Sending generic notification to ${webhook_url}`); diff --git a/backend/package-lock.json b/backend/package-lock.json index 6078695..8731114 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -19,6 +19,101 @@ "kuler": "^2.0.0" } }, + "@discordjs/builders": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-1.6.1.tgz", + "integrity": "sha512-CCcLwn/8ANhlAbhlE18fcaN0hfXTen53/JiwZs1t9oE/Cqa9maA8ZRarkCIsXF4J7J/MYnd0J6IsxeKsq+f6mw==", + "requires": { + "@discordjs/formatters": "^0.3.0", + "@discordjs/util": "^0.2.0", + "@sapphire/shapeshift": "^3.8.1", + "discord-api-types": "^0.37.37", + "fast-deep-equal": "^3.1.3", + "ts-mixer": "^6.0.3", + "tslib": "^2.5.0" + }, + "dependencies": { + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + } + } + }, + "@discordjs/collection": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-1.5.0.tgz", + "integrity": "sha512-suyVndkEAAWrGxyw/CPGdtXoRRU6AUNkibtnbJevQzpelkJh3Q1gQqWDpqf5i39CnAn5+LrN0YS+cULeEjq2Yw==" + }, + "@discordjs/core": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@discordjs/core/-/core-0.5.2.tgz", + "integrity": "sha512-OEgK8GYNB1IJK3nPQ3QBvNUmuvlPTitc0j9oXe801Z7xWOFwL/lePAGhd6cAFH7yYaslwhCoSh85KI9glrmjNQ==", + "requires": { + "@discordjs/rest": "^1.7.0", + "@discordjs/util": "^0.2.0", + "@discordjs/ws": "^0.8.1", + "@sapphire/snowflake": "^3.4.2", + "@vladfrangu/async_event_emitter": "^2.2.1", + "discord-api-types": "^0.37.38" + } + }, + "@discordjs/formatters": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@discordjs/formatters/-/formatters-0.3.0.tgz", + "integrity": "sha512-Fc4MomalbP8HMKEMor3qUiboAKDtR7PSBoPjwm7WYghVRwgJlj5WYvUsriLsxeKk8+Qq2oy+HJlGTUkGvX0YnA==", + "requires": { + "discord-api-types": "^0.37.37" + } + }, + "@discordjs/rest": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-1.7.0.tgz", + "integrity": "sha512-r2HzmznRIo8IDGYBWqQfkEaGN1LrFfWQd3dSyC4tOpMU8nuVvFUEw6V/lwnG44jyOq+vgyDny2fxeUDMt9I4aQ==", + "requires": { + "@discordjs/collection": "^1.5.0", + "@discordjs/util": "^0.2.0", + "@sapphire/async-queue": "^1.5.0", + "@sapphire/snowflake": "^3.4.0", + "discord-api-types": "^0.37.37", + "file-type": "^18.2.1", + "tslib": "^2.5.0", + "undici": "^5.21.0" + }, + "dependencies": { + "file-type": { + "version": "18.3.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-18.3.0.tgz", + "integrity": "sha512-pkPZ5OGIq0TYb37b8bHDLNeQSe1H2KlaQ2ySGpJkkr2KZdaWsO4QhPzHA0mQcsUW2cSqJk+4gM/UyLz/UFbXdQ==", + "requires": { + "readable-web-to-node-stream": "^3.0.2", + "strtok3": "^7.0.0", + "token-types": "^5.0.1" + } + } + } + }, + "@discordjs/util": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@discordjs/util/-/util-0.2.0.tgz", + "integrity": "sha512-/8qNbebFzLWKOOg+UV+RB8itp4SmU5jw0tBUD3ifElW6rYNOj1Ku5JaSW7lLl/WgjjxF01l/1uQPCzkwr110vg==" + }, + "@discordjs/ws": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@discordjs/ws/-/ws-0.8.1.tgz", + "integrity": "sha512-RZwlluBGmrgAgvTHP8w9IW7Kp/idWEQgSHBs5h0ecqiWGVCueoIr6jMmvbxqZ7vVirric3zRhNdmG/TNRxhWLg==", + "requires": { + "@discordjs/collection": "^1.5.0", + "@discordjs/rest": "^1.7.0", + "@discordjs/util": "^0.2.0", + "@sapphire/async-queue": "^1.5.0", + "@types/ws": "^8.5.4", + "@vladfrangu/async_event_emitter": "^2.2.1", + "discord-api-types": "^0.37.38", + "tslib": "^2.5.0", + "ws": "^8.13.0" + } + }, "@oozcitak/dom": { "version": "1.15.10", "resolved": "https://registry.npmjs.org/@oozcitak/dom/-/dom-1.15.10.tgz", @@ -51,6 +146,32 @@ "resolved": "https://registry.npmjs.org/@oozcitak/util/-/util-8.3.8.tgz", "integrity": "sha512-T8TbSnGsxo6TDBJx/Sgv/BlVJL3tshxZP7Aq5R1mSnM5OcHY2dQaxLMu2+E8u3gN0MLOzdjurqN4ZRVuzQycOQ==" }, + "@sapphire/async-queue": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@sapphire/async-queue/-/async-queue-1.5.0.tgz", + "integrity": "sha512-JkLdIsP8fPAdh9ZZjrbHWR/+mZj0wvKS5ICibcLrRI1j84UmLMshx5n9QmL8b95d4onJ2xxiyugTgSAX7AalmA==" + }, + "@sapphire/shapeshift": { + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/@sapphire/shapeshift/-/shapeshift-3.8.2.tgz", + "integrity": "sha512-NXpnJAsxN3/h9TqQPntOeVWZrpIuucqXI3IWF6tj2fWCoRLCuVK5wx7Dtg7pRrtkYfsMUbDqgKoX26vrC5iYfA==", + "requires": { + "fast-deep-equal": "^3.1.3", + "lodash": "^4.17.21" + }, + "dependencies": { + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + } + } + }, + "@sapphire/snowflake": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/@sapphire/snowflake/-/snowflake-3.4.2.tgz", + "integrity": "sha512-KJwlv5gkGjs1uFV7/xx81n3tqgBwBJvH94n1xDyH3q+JSmtsMeSleJffarEBfG2yAFeJiFA4BnGOK6FFPHc19g==" + }, "@sindresorhus/is": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-2.1.1.tgz", @@ -64,6 +185,11 @@ "defer-to-connect": "^2.0.0" } }, + "@tokenizer/token": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", + "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==" + }, "@types/cacheable-request": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", @@ -114,11 +240,24 @@ "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz", "integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==" }, + "@types/ws": { + "version": "8.5.4", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.4.tgz", + "integrity": "sha512-zdQDHKUgcX/zBc4GrwsE/7dVdAD8JR4EuiAXiiUhhfyIJXXb2+PrGshFyeXWQPMmmZ2XxgaqclgpIC7eTXc1mg==", + "requires": { + "@types/node": "*" + } + }, "@ungap/promise-all-settled": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==" }, + "@vladfrangu/async_event_emitter": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@vladfrangu/async_event_emitter/-/async_event_emitter-2.2.1.tgz", + "integrity": "sha512-XtUEAS0m6uVddXW+EImGunLiJZzWNWAZQBoQCUneowrYXPQ6y7c0iWEm/wVYyGpTixTIhUfLRSoYCwojL64htA==" + }, "abstract-logging": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/abstract-logging/-/abstract-logging-2.0.1.tgz", @@ -849,6 +988,11 @@ "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==" }, + "discord-api-types": { + "version": "0.37.40", + "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.40.tgz", + "integrity": "sha512-LMALvtO+p6ERK8rwWoaI490NfIE/egbqjR4/rfLL1z9gQE1gqLiTpIUUDIunfAtKYzeH6ucyXhaXXWpfZh/Q6g==" + }, "duplexer2": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", @@ -2816,6 +2960,11 @@ "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", "integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==" }, + "peek-readable": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-5.0.0.tgz", + "integrity": "sha512-YtCKvLUOvwtMGmrniQPdO7MwPjgkFBtFIrmfSbYmYuq3tKDV/mcfAhBth1+C3ru7uXIZasc/pHnb+YDYNkkj4A==" + }, "performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", @@ -2944,6 +3093,14 @@ "util-deprecate": "^1.0.1" } }, + "readable-web-to-node-stream": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.2.tgz", + "integrity": "sha512-ePeK6cc1EcKLEhJFt/AebMCLL+GgSKhuygrZ/GLaKZYEecIgIECf4UaUuaByiGtzckwR4ain9VzUh95T1exYGw==", + "requires": { + "readable-stream": "^3.6.0" + } + }, "readdir-glob": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.1.tgz", @@ -3384,6 +3541,15 @@ "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==" }, + "strtok3": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-7.0.0.tgz", + "integrity": "sha512-pQ+V+nYQdC5H3Q7qBZAz/MO6lwGhoC2gOAjuouGf/VO0m7vQRh8QNMl2Uf6SwAtzZ9bOw3UIeBukEGNJl5dtXQ==", + "requires": { + "@tokenizer/token": "^0.3.0", + "peek-readable": "^5.0.0" + } + }, "table-parser": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/table-parser/-/table-parser-0.1.3.tgz", @@ -3443,6 +3609,15 @@ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" }, + "token-types": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/token-types/-/token-types-5.0.1.tgz", + "integrity": "sha512-Y2fmSnZjQdDb9W4w4r1tswlMHylzWIeOKpx0aZH9BgGtACHhrk3OkT52AzwcuqTRBZtvvnTjDBh8eynMulu8Vg==", + "requires": { + "@tokenizer/token": "^0.3.0", + "ieee754": "^1.2.1" + } + }, "tough-cookie": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", @@ -3467,6 +3642,11 @@ "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.3.0.tgz", "integrity": "sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==" }, + "ts-mixer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.3.tgz", + "integrity": "sha512-k43M7uCG1AkTyxgnmI5MPwKoUvS/bRvLvUb7+Pgpdlmok8AoqmUaZxUUw8zKM5B1lqZrt41GjYgnvAi0fppqgQ==" + }, "tslib": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", @@ -3533,6 +3713,14 @@ "which-boxed-primitive": "^1.0.2" } }, + "undici": { + "version": "5.22.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.22.0.tgz", + "integrity": "sha512-fR9RXCc+6Dxav4P9VV/sp5w3eFiSdOjJYsbtWfd4s5L5C4ogyuVpdKIVHeW0vV1MloM65/f7W45nR9ZxwVdyiA==", + "requires": { + "busboy": "^1.6.0" + } + }, "universalify": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz", @@ -3735,6 +3923,11 @@ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, + "ws": { + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", + "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==" + }, "xml-js": { "version": "1.6.11", "resolved": "https://registry.npmjs.org/xml-js/-/xml-js-1.6.11.tgz", diff --git a/backend/package.json b/backend/package.json index 042c3cc..81249bf 100644 --- a/backend/package.json +++ b/backend/package.json @@ -19,6 +19,8 @@ }, "homepage": "", "dependencies": { + "@discordjs/builders": "^1.6.1", + "@discordjs/core": "^0.5.2", "archiver": "^5.3.1", "async": "^3.2.3", "async-mutex": "^0.4.0", diff --git a/src/app/settings/settings.component.html b/src/app/settings/settings.component.html index f95bbc6..78ba4cd 100644 --- a/src/app/settings/settings.component.html +++ b/src/app/settings/settings.component.html @@ -385,6 +385,13 @@ Place endpoint URL here to integrate with services like Zapier and Automatisch. +
+ + Discord Webhook URL + + See docs here. + +
Use ntfy API