diff --git a/src/core/scraper/vidlox.js b/src/core/scraper/vidlox.js
new file mode 100644
index 00000000..c9ab9726
--- /dev/null
+++ b/src/core/scraper/vidlox.js
@@ -0,0 +1,37 @@
+/**
+ * @module
+ */
+/* eslint-disable require-await */
+
+import { matchPattern } from "../../tools/matchpattern.js";
+
+/**
+ * L'expression rationnelle pour extraire l'URL de la vidéo.
+ *
+ * @constant {RegExp}
+ */
+const URL_REGEXP = /sources: \["([^"]+)",/u;
+
+/**
+ * Extrait les informations nécessaire pour lire une vidéo sur Kodi.
+ *
+ * @param {URL} _url L'URL d'une vidéo de Bigo Live.
+ * @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();
+ for (const script of doc.querySelectorAll("script:not([src])")) {
+ const result = URL_REGEXP.exec(script.text);
+ if (null === result) {
+ continue;
+ }
+
+ return result[1];
+ }
+ return null;
+};
+export const extract = matchPattern(action, "*://vidlox.me/*");
diff --git a/src/core/scrapers.js b/src/core/scrapers.js
index f2a2a53b..2f6b6396 100644
--- a/src/core/scrapers.js
+++ b/src/core/scrapers.js
@@ -46,6 +46,7 @@ import * as ultimedia from "./scraper/ultimedia.js";
import * as veoh from "./scraper/veoh.js";
import * as video from "./scraper/video.js";
import * as videopress from "./scraper/videopress.js";
+import * as vidlox from "./scraper/vidlox.js";
import * as vimeo from "./scraper/vimeo.js";
import * as vrtnu from "./scraper/vrtnu.js";
import * as youtube from "./scraper/youtube.js";
@@ -92,6 +93,7 @@ const SCRAPERS = [
ultimedia,
veoh,
videopress,
+ vidlox,
vimeo,
vrtnu,
youtube,
diff --git a/test/integration/scraper/vidlox.js b/test/integration/scraper/vidlox.js
new file mode 100644
index 00000000..20a3e92c
--- /dev/null
+++ b/test/integration/scraper/vidlox.js
@@ -0,0 +1,20 @@
+import assert from "assert";
+import { extract } from "../../../src/core/scrapers.js";
+
+describe("Scraper: Vidlox", function () {
+ it("should return URL when it's not a video", async function () {
+ const url = "https://vidlox.me/faq";
+ const options = { depth: 0, incognito: false };
+
+ const file = await extract(new URL(url), options);
+ assert.strictEqual(file, url);
+ });
+
+ it("should return video URL", async function () {
+ const url = "https://vidlox.me/30fxi9o50b3v";
+ const options = { depth: 0, incognito: false };
+
+ const file = await extract(new URL(url), options);
+ assert.ok(file.endsWith("/master.m3u8"), `"${file}".endsWith(...)`);
+ });
+});
diff --git a/test/unit/core/scraper/vidlox.js b/test/unit/core/scraper/vidlox.js
new file mode 100644
index 00000000..6441ce04
--- /dev/null
+++ b/test/unit/core/scraper/vidlox.js
@@ -0,0 +1,45 @@
+import assert from "assert";
+import { extract } from "../../../../src/core/scraper/vidlox.js";
+
+describe("core/scraper/vidlox.js", function () {
+ describe("extract()", function () {
+ it("should return null when it's a unsupported URL", async function () {
+ const url = "https://twitter.com/vidloxtv";
+
+ const file = await extract(new URL(url));
+ assert.strictEqual(file, null);
+ });
+
+ it("should return null when it's not a video", async function () {
+ const url = "https://vidlox.me/foo";
+ const content = {
+ html: () => Promise.resolve(new DOMParser().parseFromString(`
+
+