// ==UserScript== // @name ydl_api_ng // @match http*://*/* // @grant GM_registerMenuCommand // @grant GM_xmlhttpRequest // @grant GM_notification // ==/UserScript== (function () { 'use strict'; const key_mapping = "0123456789abcdefghijklmnopqrstuvwxyz"; // CUSTOMIZE HERE const default_host = 'http://localhost:5011'; const notificationTimeout = 5000; const userToken = null; // STOP CUSTOMIZE HERE const format_date = function (date) { const year = date.getFullYear(); const month = `${date.getMonth() + 1}`.padStart(2, "0"); const day = `${date.getDate()}`.padStart(2, "0"); const hours = `${date.getHours()}`.padStart(2, "0"); const minutes = `${date.getMinutes()}`.padStart(2, "0"); return `${year}-${month}-${day} ${hours}:${minutes}`; } const find_url_in_mapping = function (site_mapping) { const current_url = window.location.href; for (let site of site_mapping) { for (let url of site.url) { if (current_url.includes(url)) { return site.presets; } } } return null; } const build_url = function (preset) { const url = new URL(preset.route.route); url.searchParams.append('url', window.location.href); if (userToken !== null) { url.searchParams.append('token', userToken); } if (preset.query_params !== undefined) { Object.entries(preset.query_params).forEach(([key, value]) => { url.searchParams.append(key, value); }); } return url.href; }; const launch_request = function (preset) { const notificationOptions = {}; const effective_method = preset.method !== undefined ? preset.method : preset.route.method; let data = null; let headers = {}; if (preset.body !== undefined) { data = JSON.stringify(preset.body()); headers = { "Content-Type": "application/json", }; } GM_xmlhttpRequest({ method: effective_method, headers: headers, data: data, url: build_url(preset), onerror: function () { notificationOptions.title = `Download failed`; notificationOptions.text = `Host seams unreachable, is the server up ?`; GM_notification(notificationOptions); }, onload: function (response) { notificationOptions.title = `Unknown error`; notificationOptions.text = `An unknown response code has been found`; notificationOptions.timeout = null; const status_code = preset.route.return_code[response.status] if (status_code !== undefined) { notificationOptions.title = status_code.title; notificationOptions.text = status_code.text; notificationOptions.timeout = status_code.timeout; } GM_notification(notificationOptions); } }); }; // CUSTOMIZE HERE const routes = { download: { route: default_host + '/download', method: 'GET', return_code: { 200: { title: 'Download launched', text: 'Downloading', timeout: notificationTimeout }, 202: { title: 'Download launched', text: 'Download not checked. Some files may not be downloaded', timeout: notificationTimeout }, 206: { title: 'Download launched', text: 'Some presets failed download check', timeout: notificationTimeout }, 400: { title: 'Bad request', text: 'An error append during parameters validation', timeout: null }, 401: { title: 'Authentication failed', text: 'The server requires an user token or the provided token is wrong', timeout: null }, 403: { title: 'Unauthorized', text: 'You are not allowed to use this api', timeout: null } } }, programmation: { route: default_host + '/programmation', method: 'POST', return_code: { 200: { title: 'Programmation added', text: '', timeout: notificationTimeout }, 400: { title: 'Invalid programmation', text: 'Some parameters are wrong', timeout: null }, 401: { title: 'Authentication failed', text: 'The server requires an user token or the provided token is wrong', timeout: null }, 403: { title: 'Unauthorized', text: 'You are not allowed to use this api', timeout: null }, 409: { title: 'Non supported', text: 'This feature require redis enabled on the server', timeout: null } } } } const programmation_id = new URL(document.URL).pathname.replaceAll('/', '') const presets_mapping = { 'default': {name: 'Default', route: routes.download}, 'best': {name: 'Best', route: routes.download, query_params: {presets: 'BEST'}}, '720p': {name: '720p', route: routes.download, query_params: {presets: 'HD'}}, 'audio': {name: 'Audio', route: routes.download, query_params: {presets: 'AUDIO'}}, 'best+audio': {name: 'Best + Audio', route: routes.download, query_params: {presets: 'BEST,AUDIO'}}, 'samples': (days = 15, presets = null) => { let preset_name = 'Samples' let recurrence_end_date = null if (days !== null) { const end_date = new Date(); end_date.setDate(end_date.getDate() + days) recurrence_end_date = format_date(end_date) preset_name = `Samples (${days} days)` } return { name: preset_name, route: routes.programmation, body: () => { const end_date = new Date(); end_date.setDate(end_date.getDate() + days) return { id: new URL(document.URL).pathname.replaceAll('/', ''), planning: { recurrence_cron: `${Math.floor(Math.random() * 15)}/15 * * * *`, recording_duration: 2, recording_stops_at_end: true, recording_restarts_during_duration: false, recurrence_end_date: recurrence_end_date }, presets: presets } } } }, 'spy': (days = null, presets = null) => { let planning = {} let preset_name = 'Spy' if (days !== null) { const end_date = new Date(); end_date.setDate(end_date.getDate() + days) planning = { recurrence_end_date: format_date(end_date) } preset_name = `Spy (${days} days)` } return { name: preset_name, route: routes.programmation, body: () => { return { id: programmation_id, presets: presets, planning: planning } } } }, 'prog_id': (name = 'DEFAULT', preset = null) => { return { name: name, route: routes.download, method: 'POST', body: () => { return { programmation: { id: programmation_id }, presets: [{ "_preset": preset }] } } } }, 'time_limit': (minutes = 60, preset = 'DEFAULT') => { return { name: `${minutes} minutes`, route: routes.download, method: 'POST', body: () => { return { programmation: { id: programmation_id, planning: { recording_duration: minutes, }, }, presets: [{ "_preset": preset }] } } } }, 'delete_prog': (id) => { return { name: 'Delete programmation', route: routes.programmation, method: 'DELETE', route_path: `/${id}` } }, }; const site_mapping = [ { url: ['youtube.com', 'youtu.be'], presets: ['best', '720p', 'audio', 'best+audio'], }, { url: ['twitch.tv'], presets: [ presets_mapping['prog_id']('Best', 'BEST'), presets_mapping['prog_id']('720p', 'HD'), presets_mapping['time_limit'](60), presets_mapping['time_limit'](30), presets_mapping['time_limit'](15), 'spy', 'samples'], }, ]; // STOP CUSTOMIZE HERE let effective_presets = find_url_in_mapping(site_mapping); if (effective_presets == null) { effective_presets = Object.keys(presets_mapping); } effective_presets.forEach((preset, index) => { let preset_object = null; if (typeof preset == 'object') { preset_object = preset; } else if (typeof preset == 'string') { if (typeof presets_mapping[preset] == 'function') { preset_object = presets_mapping[preset](); } else { preset_object = presets_mapping[preset]; } } else if (typeof preset == 'function') { preset_object = preset(); } if (preset_object !== undefined && preset_object !== null) { let key = null; if (index < key_mapping.length) { key = key_mapping[index]; } GM_registerMenuCommand(preset_object.name, () => { launch_request(preset_object); }, key); } }); })();