Skip to content

Commit

Permalink
feat: Gérer les clips YouTube.
Browse files Browse the repository at this point in the history
  • Loading branch information
regseb committed Mar 29, 2024
1 parent dd5bbf9 commit d8277d6
Show file tree
Hide file tree
Showing 9 changed files with 268 additions and 4 deletions.
20 changes: 20 additions & 0 deletions src/core/labeller/plugin/youtube.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,23 @@ export const extract = matchPattern(
action,
"plugin://plugin.video.youtube/play/*",
);

/**
* Extrait le titre à partir d'une URL de YouTube.
*
* @param {URL} url L'URL utilisant le plugin YouTube.
* @param {Object} context Le contexte du labellisateur.
* @param {Function} context.metaExtract La fonction parente pour extraire un
* label.
* @returns {Promise<string|undefined>} Une promesse contenant le titre ou
* <code>undefined</code>.
*/
const actionUri = function ({ searchParams }, { metaExtract }) {
return searchParams.has("uri")
? metaExtract(new URL(searchParams.get("uri")))
: Promise.resolve(undefined);
};
export const extractUri = matchPattern(
actionUri,
"plugin://plugin.video.youtube/uri2addon/*",
);
17 changes: 17 additions & 0 deletions src/core/labeller/youtube.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,20 @@ export const extractPlaylist = matchPattern(
actionPlaylist,
"*://www.youtube.com/playlist*",
);

/**
* Extrait le titre d'un clip YouTube.
*
* @param {URL} url L'URL du clip YouTube.
* @returns {Promise<string|undefined>} Une promesse contenant le titre.
*/
const actionClip = async function (url) {
const response = await fetch(url);
const text = await response.text();
const doc = new DOMParser().parseFromString(text, "text/html");
return doc.querySelector('meta[property="og:title"]').content;
};
export const extractClip = matchPattern(
actionClip,
"*://www.youtube.com/clip/*",
);
22 changes: 19 additions & 3 deletions src/core/plugin/youtube.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
*
* @type {string}
*/
const PLUGIN_URL = "plugin://plugin.video.youtube/play/";
const PLUGIN_URL = "plugin://plugin.video.youtube";

/**
* Génère l'URL d'une vidéo dans l'extension YouTube.
Expand All @@ -22,7 +22,7 @@ const PLUGIN_URL = "plugin://plugin.video.youtube/play/";
*/
export const generateVideoUrl = function (videoId, incognito) {
return (
`${PLUGIN_URL}?video_id=${videoId}` +
`${PLUGIN_URL}/play/?video_id=${videoId}` +
`&incognito=${incognito.toString()}`
);
};
Expand All @@ -39,8 +39,24 @@ export const generateVideoUrl = function (videoId, incognito) {
export const generatePlaylistUrl = async function (playlistId, incognito) {
const config = await browser.storage.local.get(["youtube-order"]);
return (
`${PLUGIN_URL}?playlist_id=${playlistId}` +
`${PLUGIN_URL}/play/?playlist_id=${playlistId}` +
`&order=${config["youtube-order"]}&play=1` +
`&incognito=${incognito.toString()}`
);
};

/**
* Génère l'URL d'un clip dans l'extension YouTube.
*
* @param {string} clipId L'identifiant du clip YouTube.
* @param {boolean} incognito La marque indiquant s'il faut lire le clip en
* navigation privée.
* @returns {string} Le lien du <em>fichier</em>.
*/
export const generateClipUrl = function (clipId, incognito) {
return (
`${PLUGIN_URL}/uri2addon/?uri=` +
encodeURIComponent(`https://www.youtube.com/clip/${clipId}`) +
`&incognito=${incognito.toString()}`
);
};
47 changes: 46 additions & 1 deletion src/core/scraper/youtube.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,30 @@ const dispatchPlaylist = async function (playlistId, { incognito }) {
return await youtubePlugin.generatePlaylistUrl(playlistId, incognito);
};

/**
* Répartit un clip YouTube à un plugin de Kodi.
*
* @param {string} clipId L'identifiant du clip YouTube.
* @param {Object} context Le contexte du clip.
* @param {boolean} context.incognito La marque indiquant si l'utilisateur est
* en navigation privée.
* @returns {Promise<string>} Une promesse contenant le lien du
* <em>fichier</em>.
*/
const dispatchClip = async function (clipId, { incognito }) {
const addons = new Set(await kodi.addons.getAddons("video"));
if (addons.has("plugin.video.youtube")) {
return youtubePlugin.generateClipUrl(clipId, incognito);
}
if (addons.has("plugin.video.sendtokodi")) {
return sendtokodiPlugin.generateUrl(
new URL(`https://www.youtube.com/clip/${clipId}`),
);
}
// Envoyer par défaut au plugin YouTube.
return youtubePlugin.generateClipUrl(clipId, incognito);
};

/**
* Extrait les informations nécessaires pour lire une vidéo / playlist sur Kodi.
*
Expand Down Expand Up @@ -158,7 +182,28 @@ export const extractEmbed = matchPattern(
);

/**
* Extrait les informations nécessaire pour lire une vidéo sur Kodi.
* Extrait les informations nécessaires pour lire un clip sur Kodi.
*
* @param {URL} url L'URL d'un clip YouTube.
* @param {Object} _metadata Les métadonnées de l'URL.
* @param {Function} _metadata.html La fonction retournant la promesse
* contenant le document HTML.
* @param {Object} context Le contexte de l'extraction.
* @param {boolean} context.incognito La marque indiquant si l'utilisateur est
* en navigation privée.
* @returns {Promise<string>} Une promesse contenant le lien du
* <em>fichier</em>.
*/
const actionClip = function ({ pathname }, _metadata, { incognito }) {
return dispatchClip(pathname.slice(6), { incognito });
};
export const extractClip = matchPattern(
actionClip,
"*://www.youtube.com/clip/*",
);

/**
* Extrait les informations nécessaires pour lire une vidéo sur Kodi.
*
* @param {URL} url L'URL minifiée d'une vidéo YouTube.
* @param {Object} _metadata Les métadonnées de l'URL.
Expand Down
23 changes: 23 additions & 0 deletions test/integration/labeller/youtube.js
Original file line number Diff line number Diff line change
Expand Up @@ -186,4 +186,27 @@ describe("Labeller: YouTube", function () {
type: "unknown",
});
});

it("should return clip label", async function () {
const url = new URL(
"https://www.youtube.com/clip/UgkxI0KsdOZA-Pq3tUjucaib8b1tbixLMXhz",
);
const context = { depth: false, incognito: false };

const file = await extract(url, context);
const item = await complete({
file,
label: "",
position: 0,
title: "",
type: "unknown",
});
assert.deepEqual(item, {
file,
label: "✂️ zyw000",
position: 0,
title: "",
type: "unknown",
});
});
});
32 changes: 32 additions & 0 deletions test/unit/core/labeller/plugin/youtube.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,36 @@ describe("core/labeller/plugin/youtube.js", function () {
assert.equal(fake.callCount, 0);
});
});

describe("extractUri()", function () {
it("should return label", async function () {
const fake = sinon.fake.resolves("foo");

const url = new URL(
"plugin://plugin.video.youtube/uri2addon/" +
"?uri=https%3A%2F%2Fwww.youtube.com%2Fclip%2Fbar",
);

const label = await labeller.extractUri(url, { metaExtract: fake });
assert.equal(label, "foo");

assert.equal(fake.callCount, 1);
assert.deepEqual(fake.firstCall.args, [
new URL("https://www.youtube.com/clip/bar"),
]);
});

it("should return undefined when there isn't 'uri' parameter", async function () {
const fake = sinon.fake.resolves("foo");

const url = new URL(
"plugin://plugin.video.youtube/uri2addon/?bar=baz",
);

const label = await labeller.extractUri(url, { metaExtract: fake });
assert.equal(label, undefined);

assert.equal(fake.callCount, 0);
});
});
});
22 changes: 22 additions & 0 deletions test/unit/core/labeller/youtube.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,4 +109,26 @@ describe("core/labeller/youtube.js", function () {
assert.equal(label, undefined);
});
});

describe("actionClip()", function () {
it("should return label", async function () {
const stub = sinon.stub(globalThis, "fetch").resolves(
new Response(
`<html><head>
<meta property="og:title" content="foo" />
</head></html>`,
),
);

const url = new URL("https://www.youtube.com/clip/bar");

const label = await labeller.extractClip(url);
assert.equal(label, "foo");

assert.equal(stub.callCount, 1);
assert.deepEqual(stub.firstCall.args, [
new URL("https://www.youtube.com/clip/bar"),
]);
});
});
});
22 changes: 22 additions & 0 deletions test/unit/core/plugin/youtube.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,26 @@ describe("core/plugin/youtube.js", function () {
);
});
});

describe("generateClipUrl()", function () {
it("should return YouTube URL", function () {
const label = plugin.generateClipUrl("foo", false);
assert.equal(
label,
"plugin://plugin.video.youtube/uri2addon/" +
"?uri=https%3A%2F%2Fwww.youtube.com%2Fclip%2Ffoo" +
"&incognito=false",
);
});

it("should return YouTube URL with incognito", function () {
const label = plugin.generateClipUrl("foo", true);
assert.equal(
label,
"plugin://plugin.video.youtube/uri2addon/" +
"?uri=https%3A%2F%2Fwww.youtube.com%2Fclip%2Ffoo" +
"&incognito=true",
);
});
});
});
67 changes: 67 additions & 0 deletions test/unit/core/scraper/youtube.js
Original file line number Diff line number Diff line change
Expand Up @@ -479,6 +479,73 @@ describe("core/scraper/youtube.js", function () {
});
});

describe("extractClip()", function () {
it("should return clip id", async function () {
const stub = sinon.stub(kodi.addons, "getAddons").resolves([]);

const url = new URL("https://www.youtube.com/clip/foo");
const metadata = undefined;
const context = { incognito: true };

const file = await scraper.extractClip(url, metadata, context);
assert.equal(
file,
"plugin://plugin.video.youtube/uri2addon/" +
"?uri=https%3A%2F%2Fwww.youtube.com%2Fclip%2Ffoo" +
"&incognito=true",
);

assert.equal(stub.callCount, 1);
assert.deepEqual(stub.firstCall.args, ["video"]);
});

it("should return clip id to youtube", async function () {
const stub = sinon
.stub(kodi.addons, "getAddons")
.resolves([
"plugin.video.sendtokodi",
"plugin.video.tubed",
"plugin.video.youtube",
]);

const url = new URL("https://www.youtube.com/clip/foo");
const metadata = undefined;
const context = { incognito: false };

const file = await scraper.extractClip(url, metadata, context);
assert.equal(
file,
"plugin://plugin.video.youtube/uri2addon/" +
"?uri=https%3A%2F%2Fwww.youtube.com%2Fclip%2Ffoo" +
"&incognito=false",
);

assert.equal(stub.callCount, 1);
assert.deepEqual(stub.firstCall.args, ["video"]);
});

it("should return clip id to sendtokodi", async function () {
await browser.storage.local.set({ "youtube-order": "" });
const stub = sinon
.stub(kodi.addons, "getAddons")
.resolves(["plugin.video.sendtokodi"]);

const url = new URL("https://www.youtube.com/clip/foo");
const metadata = undefined;
const context = { incognito: false };

const file = await scraper.extractClip(url, metadata, context);
assert.equal(
file,
"plugin://plugin.video.sendtokodi/?" +
"https://www.youtube.com/clip/foo",
);

assert.equal(stub.callCount, 1);
assert.deepEqual(stub.firstCall.args, ["video"]);
});
});

describe("extractMinify()", function () {
it("should return video id", async function () {
const stub = sinon.stub(kodi.addons, "getAddons").resolves([]);
Expand Down

0 comments on commit d8277d6

Please sign in to comment.