diff --git a/README.md b/README.md index 9316e5a8..3ae31147 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ des vidéos et des musiques sur **Kodi** : My Cloud Player, Odysee, Overcast, PeerTube, Pippa, podCloud, Pokémon TV, Radio, Radioline, Steam, Streamable, TikTok, Ultimedia, Veoh, VideoPress, Viously ; - - Allemagne : Arte ; + - Allemagne : Arte, ZDF ; - Belgique : VRT NU, VTM GO ; - États-Unis : KCAA Radio ; - France : 20 Minutes, AlloCiné, Arte, Arte Radio, France Inter, Futura diff --git a/locales/en/amo_description.html b/locales/en/amo_description.html index 99176c52..c41efc47 100644 --- a/locales/en/amo_description.html +++ b/locales/en/amo_description.html @@ -6,16 +6,16 @@
  • YouTube, Twitch, Vimeo, SoundCloud as well as Ace Stream, Apple Podcasts, Bigo Live, BitChute, Blog Talk Radio, Castbox, Dailymotion, DevTube, Facebook, Flickr, Instagram, ItemFix, Jamendo, LBRY, Megaphone, Metacafe, Mixcloud, My Cloud Player, Odysee, Overcast, PeerTube, Pippa, podCloud, Pokémon TV, Radio, Radioline, Steam, Streamable, TikTok, Ultimedia, Veoh, VideoPress, Viously;
  • diff --git a/locales/fr/amo_description.html b/locales/fr/amo_description.html index fed29635..e7317fa0 100644 --- a/locales/fr/amo_description.html +++ b/locales/fr/amo_description.html @@ -6,7 +6,7 @@
  • YouTube, Twitch, Vimeo, SoundCloud ainsi que Ace Stream, Apple Podcasts, Bigo Live, BitChute, Blog Talk Radio, Castbox, Dailymotion, DevTube, Facebook, Flickr, Instagram, ItemFix, Jamendo, LBRY, Megaphone, Metacafe, Mixcloud, My Cloud Player, Odysee, Overcast, PeerTube, Pippa, podCloud, Pokémon TV, Radio, Radioline, Steam, Streamable, TikTok, Ultimedia, Veoh, VideoPress, Viously ; diff --git a/src/core/scraper/zdf.js b/src/core/scraper/zdf.js new file mode 100644 index 00000000..d89e5208 --- /dev/null +++ b/src/core/scraper/zdf.js @@ -0,0 +1,33 @@ +/** + * @module + */ +/* eslint-disable require-await */ + +import { matchPattern } from "../../tools/matchpattern.js"; + +/** + * Extrait les informations nécessaire pour lire une vidéo sur Kodi. + * + * @param {URL} _url L'URL d'une vidéo de ZDF. + * @param {Object} content Le contenu de l'URL. + * @param {Function} content.html La fonction retournant la promesse contenant + * le document HTML. + * @returns {Promise} Une promesse contenant le lien du + * fichier ou null. + */ +const action = async function (_url, content) { + const doc = await content.html(); + const button = doc.querySelector("button.download-btn[data-dialog]"); + if (null === button) { + return null; + } + + const { contentUrl, apiToken } = JSON.parse(button.dataset.dialog); + const url = contentUrl.replace("{playerId}", "ngplayer_2_4"); + const response = await fetch(url, { + headers: new Headers({ "Api-Auth": `Bearer ${apiToken}` }), + }); + const json = await response.json(); + return json.priorityList[0].formitaeten[0].qualities[0].audio.tracks[0].uri; +}; +export const extract = matchPattern(action, "*://www.zdf.de/*"); diff --git a/src/core/scrapers.js b/src/core/scrapers.js index a9f3c13f..3ef4a752 100644 --- a/src/core/scrapers.js +++ b/src/core/scrapers.js @@ -67,6 +67,7 @@ import * as viously from "./scraper/viously.js"; import * as vrtnu from "./scraper/vrtnu.js"; import * as vtmgo from "./scraper/vtmgo.js"; import * as youtube from "./scraper/youtube.js"; +import * as zdf from "./scraper/zdf.js"; /** * La liste des extracteurs (retournant le fichier extrait ou @@ -124,6 +125,7 @@ const SCRAPERS = [ vrtnu, vtmgo, youtube, + zdf, // Utiliser les scrapers génériques en dernier recours. video, audio, diff --git a/test/integration/scraper/zdf.js b/test/integration/scraper/zdf.js new file mode 100644 index 00000000..3b967117 --- /dev/null +++ b/test/integration/scraper/zdf.js @@ -0,0 +1,23 @@ +import assert from "node:assert"; +import { extract } from "../../../src/core/scrapers.js"; + +describe("Scraper: ZDF", function () { + it("should return URL when it's not a video", async function () { + const url = new URL("https://www.zdf.de/filme"); + const options = { depth: false, incognito: false }; + + const file = await extract(url, options); + assert.strictEqual(file, url.href); + }); + + it("should return video URL", async function () { + const url = new URL("https://www.zdf.de/dokumentation/37-grad" + + "/37-im-schuldenstrudel-100.html"); + const options = { depth: false, incognito: false }; + + const file = await extract(url, options); + assert.strictEqual(file, + "https://nrodlzdf-a.akamaihd.net/none/zdf/21/04" + + "/210427_sendung_37g/4/210427_sendung_37g_a1a2_2128k_p18v15.webm"); + }); +}); diff --git a/test/unit/core/scraper/zdf.js b/test/unit/core/scraper/zdf.js new file mode 100644 index 00000000..3a34622a --- /dev/null +++ b/test/unit/core/scraper/zdf.js @@ -0,0 +1,70 @@ +import assert from "node:assert"; +import sinon from "sinon"; +import { extract } from "../../../../src/core/scraper/zdf.js"; + +describe("core/scraper/zdf.js", function () { + describe("extract()", function () { + it("should return null when it's a unsupported URL", async function () { + const url = new URL("https://www.zdftext.de/"); + + const file = await extract(url); + assert.strictEqual(file, null); + }); + + it("should return null when it's not a video", async function () { + const url = new URL("https://www.zdf.de/foo"); + const content = { + html: () => Promise.resolve(new DOMParser().parseFromString(` + + + `, "text/html")), + }; + + const file = await extract(url, content); + assert.strictEqual(file, null); + }); + + it("should return video URL", async function () { + const stub = sinon.stub(globalThis, "fetch").resolves(new Response( + JSON.stringify({ + priorityList: [{ + formitaeten: [{ + qualities: [{ + audio: { + tracks: [{ + uri: "https://quux.de/corge.webm", + }], + }, + }], + }], + }], + }), + )); + + const url = new URL("https://www.zdf.de/foo"); + const content = { + html: () => Promise.resolve(new DOMParser().parseFromString(` + + + + + `, "text/html")), + }; + + const file = await extract(url, content); + assert.strictEqual(file, "https://quux.de/corge.webm"); + + assert.strictEqual(stub.callCount, 1); + assert.deepStrictEqual(stub.firstCall.args, [ + "http://bar.de/ngplayer_2_4/baz.json", + { headers: new Headers({ "Api-Auth": "Bearer qux" }) }, + ]); + + stub.restore(); + }); + }); +});