Skip to content

Commit 4e9b8de

Browse files
committedDec 18, 2021
feat: Ajouter un scraper pour Play SRF.
1 parent 0d76fe7 commit 4e9b8de

File tree

8 files changed

+139
-2
lines changed

8 files changed

+139
-2
lines changed
 

‎README.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ de diffuser des vidéos et des musiques sur **Kodi** :
3131
- Islande : Útvarp Saga ;
3232
- Pays-Bas : Dumpert ;
3333
- Royaume-Uni : Daily Mail, The Guardian ;
34-
- Russie : Первый канал.
34+
- Russie : Первый канал ;
35+
- Suisse : Play SRF.
3536

3637
Cast Kodi analyse aussi les pages pour y trouver des vidéos, de la musique ou
3738
des intégrations de plateformes externes. Par exemple, si une page affiche une

‎locales/en/description.tpl

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ Cast Kodi, as the name suggests, adds the ability to cast videos and music
1818
<li>Iran: آپارات;</li>
1919
<li>Netherlands: Dumpert;</li>
2020
<li>Russia: Первый&nbsp;канал;</li>
21+
<li>Switzerland: Play SRF;</li>
2122
<li>United Kingdom: Daily&nbsp;Mail, The&nbsp;Guardian;</li>
2223
<li>USA: KCAA&nbsp;Radio.</li>
2324
</ul>

‎locales/fr/description.tpl

+2-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ Cast&nbsp;Kodi permet de diffuser des vidéos et des musiques sur <strong>Kodi</
1919
<li>Islande&nbsp;: Útvarp&nbsp;Saga&nbsp;;</li>
2020
<li>Pays-Bas&nbsp;: Dumpert&nbsp;;</li>
2121
<li>Royaume-Uni&nbsp;: Daily&nbsp;Mail, The&nbsp;Guardian&nbsp;;</li>
22-
<li>Russie&nbsp;: Первый&nbsp;канал.</li>
22+
<li>Russie&nbsp;: Первый&nbsp;канал&nbsp;;</li>
23+
<li>Suisse&nbsp;: Play&nbsp;SRF.</li>
2324
</ul>
2425
</li>
2526
</ul>

‎locales/sk/description.tpl

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ Cast&nbsp;Kodi, pridá možnosť posielania videí a hudby do <strong>Kodi</stro
1919
<li>Nemecko: ARD&nbsp;Mediathek, Arte, ZDF;</li>
2020
<li>Rusko: Первый&nbsp;канал;</li>
2121
<li>Spojené kráľovstvo: Daily&nbsp;Mail, The&nbsp;Guardian;</li>
22+
<li>Švajčiarsko: Play&nbsp;SRF;</li>
2223
<li>USA: KCAA&nbsp;Radio.</li>
2324
</ul>
2425
</li>

‎src/core/scraper/srf.js

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/**
2+
* @module
3+
*/
4+
/* eslint-disable require-await */
5+
6+
import { matchPattern } from "../tools/matchpattern.js";
7+
8+
/**
9+
* L'URL de l'API de Play SRF pour obtenir des informations sur la vidéo.
10+
*
11+
* @type {string}
12+
*/
13+
const API_URL = "https://il.srgssr.ch/integrationlayer/2.0/mediaComposition" +
14+
"/byUrn/";
15+
16+
/**
17+
* Extrait les informations nécessaire pour lire une vidéo sur Kodi.
18+
*
19+
* @param {URL} url L'URL d'une vidéo de Play SRF.
20+
* @returns {Promise<?string>} Une promesse contenant le lien du
21+
* <em>fichier</em> ou <code>null</code>.
22+
*/
23+
const action = async function ({ searchParams }) {
24+
if (!searchParams.has("urn")) {
25+
return null;
26+
}
27+
28+
const response = await fetch(API_URL + searchParams.get("urn"));
29+
const json = await response.json();
30+
return json.chapterList?.[0].resourceList[0].analyticsMetadata.media_url ??
31+
null;
32+
};
33+
export const extract = matchPattern(action, "*://www.srf.ch/play/tv/*");

‎src/core/scrapers.js

+2
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ import * as pokemontv from "./scraper/pokemontv.js";
4747
import * as radio from "./scraper/radio.js";
4848
import * as radioline from "./scraper/radioline.js";
4949
import * as soundcloud from "./scraper/soundcloud.js";
50+
import * as srf from "./scraper/srf.js";
5051
// eslint-disable-next-line import/no-cycle
5152
import * as stargr from "./scraper/stargr.js";
5253
import * as steam from "./scraper/steam.js";
@@ -108,6 +109,7 @@ const SCRAPERS = [
108109
radio,
109110
radioline,
110111
soundcloud,
112+
srf,
111113
stargr,
112114
steam,
113115
tiktok,

‎test/integration/scraper/srf.js

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import assert from "node:assert";
2+
import { extract } from "../../../src/core/scrapers.js";
3+
4+
describe("Scraper: Play SRF", function () {
5+
it("should return URL when it's not a video", async function () {
6+
const url = new URL("https://www.srf.ch/hilfe/kontakt" +
7+
"?srg_shorturl_source=kontakt");
8+
const options = { depth: false, incognito: false };
9+
10+
const file = await extract(url, options);
11+
assert.strictEqual(file, url.href);
12+
});
13+
14+
it("should return URL when urn is invalid", async function () {
15+
const url = new URL("https://www.srf.ch/play/tv/foo/video/bar" +
16+
"?urn=urn:srf:video:d5cb6b79-cc9f-4e29-82fb-64e8283f02e2");
17+
const options = { depth: false, incognito: false };
18+
19+
const file = await extract(url, options);
20+
assert.strictEqual(file, url.href);
21+
});
22+
23+
it("should return video URL", async function () {
24+
const url = new URL("https://www.srf.ch/play/tv/mona-mittendrin/video" +
25+
"/bei-spitzenkoechin-tanja-grandits" +
26+
"?urn=urn:srf:video:05fe9231-6f59-46e1-bcd1-82c3d30f9ccd");
27+
const options = { depth: false, incognito: false };
28+
29+
const file = await extract(url, options);
30+
assert.ok(null !== file &&
31+
new URL(file).pathname.endsWith("/master.m3u8"),
32+
`new URL("${file}").pathname.endsWith(...) from ${url}`);
33+
});
34+
});

‎test/unit/core/scraper/srf.js

+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import assert from "node:assert";
2+
import sinon from "sinon";
3+
import * as scraper from "../../../../src/core/scraper/srf.js";
4+
5+
describe("core/scraper/srf.js", function () {
6+
describe("extract()", function () {
7+
it("should return null when it's a unsupported URL", async function () {
8+
const url = new URL("https://www.srf.ch/foo");
9+
10+
const file = await scraper.extract(url);
11+
assert.strictEqual(file, null);
12+
});
13+
14+
it("should return null when there isn't urn", async function () {
15+
const url = new URL("https://www.srf.ch/play/tv/foo");
16+
17+
const file = await scraper.extract(url);
18+
assert.strictEqual(file, null);
19+
});
20+
21+
it("should return null when urn is invalid", async function () {
22+
const stub = sinon.stub(globalThis, "fetch").resolves(new Response(
23+
JSON.stringify({ status: "foo" }),
24+
));
25+
26+
const url = new URL("https://www.srf.ch/play/tv/bar?urn=baz");
27+
28+
const file = await scraper.extract(url);
29+
assert.strictEqual(file, null);
30+
31+
assert.strictEqual(stub.callCount, 1);
32+
assert.deepStrictEqual(stub.firstCall.args, [
33+
"https://il.srgssr.ch/integrationlayer/2.0/mediaComposition" +
34+
"/byUrn/baz",
35+
]);
36+
});
37+
38+
it("should return video URL", async function () {
39+
const stub = sinon.stub(globalThis, "fetch").resolves(new Response(
40+
JSON.stringify({
41+
chapterList: [{
42+
resourceList: [{
43+
analyticsMetadata: {
44+
// eslint-disable-next-line camelcase
45+
media_url: "http://foo.ch/bar.m3u8",
46+
},
47+
}],
48+
}],
49+
}),
50+
));
51+
52+
const url = new URL("https://www.srf.ch/play/tv/bar?urn=baz");
53+
54+
const file = await scraper.extract(url);
55+
assert.strictEqual(file, "http://foo.ch/bar.m3u8");
56+
57+
assert.strictEqual(stub.callCount, 1);
58+
assert.deepStrictEqual(stub.firstCall.args, [
59+
"https://il.srgssr.ch/integrationlayer/2.0/mediaComposition" +
60+
"/byUrn/baz",
61+
]);
62+
});
63+
});
64+
});

0 commit comments

Comments
 (0)
Please sign in to comment.