From c0e4b2427474251918195359ae10700a71a889c4 Mon Sep 17 00:00:00 2001 From: siritami <102145692+FiorenMas@users.noreply.github.com> Date: Fri, 12 Jun 2026 01:18:05 +0700 Subject: [PATCH 1/2] Add m3u --- plugins/index.ts | 20 +-- plugins/vietnamese/Playm3u/index.ts | 208 ++++++++++++++++++++++++++ public/static/src/vi/playm3u/icon.png | Bin 0 -> 13113 bytes 3 files changed, 219 insertions(+), 9 deletions(-) create mode 100644 plugins/vietnamese/Playm3u/index.ts create mode 100644 public/static/src/vi/playm3u/icon.png diff --git a/plugins/index.ts b/plugins/index.ts index 2b1651e1..0e18c1cf 100644 --- a/plugins/index.ts +++ b/plugins/index.ts @@ -18,15 +18,16 @@ import p_15 from '@plugins/vietnamese/MotTruyen'; import p_16 from '@plugins/vietnamese/NguonC'; import p_17 from '@plugins/vietnamese/OPhim'; import p_18 from '@plugins/vietnamese/PhimFun'; -import p_19 from '@plugins/vietnamese/SangTacViet'; -import p_20 from '@plugins/vietnamese/TieuThuyetMang'; -import p_21 from '@plugins/vietnamese/TomatoMTL'; -import p_22 from '@plugins/vietnamese/TruyenCV'; -import p_23 from '@plugins/vietnamese/TruyenFull'; -import p_24 from '@plugins/vietnamese/Valvrareteam'; -import p_25 from '@plugins/vietnamese/WanwanSekai'; -import p_26 from '@plugins/vietnamese/YeuAnime'; -import p_27 from '@plugins/vietnamese/ZumiNovel'; +import p_19 from '@plugins/vietnamese/Playm3u'; +import p_20 from '@plugins/vietnamese/SangTacViet'; +import p_21 from '@plugins/vietnamese/TieuThuyetMang'; +import p_22 from '@plugins/vietnamese/TomatoMTL'; +import p_23 from '@plugins/vietnamese/TruyenCV'; +import p_24 from '@plugins/vietnamese/TruyenFull'; +import p_25 from '@plugins/vietnamese/Valvrareteam'; +import p_26 from '@plugins/vietnamese/WanwanSekai'; +import p_27 from '@plugins/vietnamese/YeuAnime'; +import p_28 from '@plugins/vietnamese/ZumiNovel'; const PLUGINS: Plugin.PluginBase[] = [ p_0, @@ -57,5 +58,6 @@ const PLUGINS: Plugin.PluginBase[] = [ p_25, p_26, p_27, + p_28, ]; export default PLUGINS; diff --git a/plugins/vietnamese/Playm3u/index.ts b/plugins/vietnamese/Playm3u/index.ts new file mode 100644 index 00000000..e6d50028 --- /dev/null +++ b/plugins/vietnamese/Playm3u/index.ts @@ -0,0 +1,208 @@ +import { fetchText } from '@libs/fetch'; +import { Plugin } from '@/types/plugin'; +import { defaultCover } from '@libs/defaultCover'; +import { NovelStatus } from '@libs/novelStatus'; +import { storage } from '@libs/storage'; + +interface PlaylistItem { + name: string; + tvg: { + id: string; + name: string; + logo: string; + url: string; + rec: string; + shift: string; + }; + group: { + title: string; + }; + http: { + referrer: string; + 'user-agent': string; + }; + url: string; + catchup: { + type: string; + days: string; + source: string; + }; + timeshift: string; + lang: string; +} + +const iptvPlaylistParser = { + parse: (content: string) => { + const getAttribute = (str: string, name: string) => { + const regex = new RegExp(`${name}="(.*?)"`, 'gi'); + const match = regex.exec(str); + return match?.[1] ?? ''; + }; + const getName = (str: string) => { + const info = str.replace(/\="(.*?)"/g, ''); + const parts = info.split(/,(.*)/); + return parts[1] || ''; + }; + const getOption = (str: string, name: string) => { + const regex = new RegExp(`:${name}=(.*)`, 'gi'); + const match = regex.exec(str); + return typeof match?.[1] === 'string' ? match[1].replace(/"/g, '') : ''; + }; + const getValue = (str: string) => { + const regex = new RegExp(':(.*)', 'gi'); + const match = regex.exec(str); + return typeof match?.[1] === 'string' ? match[1].replace(/"/g, '') : ''; + }; + const getParameter = (str: string, name: string) => { + const params = str.replace(/^(.*)\|/, ''); + const regex = new RegExp(`${name}=(\\w[^&]*)`, 'gi'); + const match = regex.exec(params); + return match?.[1] ?? ''; + }; + + const lines = content.split(/\r?\n/); + if (!lines[0] || !/^#EXTM3U/.test(lines[0])) throw new Error('Playlist is not valid'); + + const items: PlaylistItem[] = []; + let currentItem: Partial | null = null; + + for (let i = 1; i < lines.length; i++) { + const line = lines[i].trim(); + if (!line) continue; + + if (line.startsWith('#EXTINF:')) { + currentItem = { + name: getName(line), + tvg: { + id: getAttribute(line, 'tvg-id'), + name: getAttribute(line, 'tvg-name'), + logo: getAttribute(line, 'tvg-logo'), + url: getAttribute(line, 'tvg-url'), + rec: getAttribute(line, 'tvg-rec'), + shift: getAttribute(line, 'tvg-shift') + }, + group: { + title: getAttribute(line, 'group-title') + }, + http: { + referrer: getAttribute(line, 'referrer'), + 'user-agent': getAttribute(line, 'user-agent') + }, + url: '', + catchup: { + type: getAttribute(line, 'catchup'), + days: getAttribute(line, 'catchup-days'), + source: getAttribute(line, 'catchup-source') + }, + timeshift: getAttribute(line, 'timeshift'), + lang: getAttribute(line, 'lang') + }; + } else if (line.startsWith('#EXTVLCOPT:')) { + if (!currentItem?.http) continue; + currentItem.http.referrer = getOption(line, 'http-referrer') || currentItem.http.referrer; + currentItem.http['user-agent'] = getOption(line, 'http-user-agent') || currentItem.http['user-agent']; + } else if (line.startsWith('#EXTGRP:')) { + if (!currentItem?.group) continue; + currentItem.group.title = getValue(line) || currentItem.group.title; + } else if (line.startsWith('#')) { + continue; + } else { + if (!currentItem?.http) continue; + currentItem.url = line; + currentItem.http['user-agent'] = getParameter(line, 'user-agent') || currentItem.http['user-agent']; + currentItem.http.referrer = getParameter(line, 'referer') || currentItem.http.referrer; + items.push(currentItem as PlaylistItem); + currentItem = null; + } + } + + return { items }; + } +}; + +class Playm3uPlugin implements Plugin.PluginBase { + id = 'playm3u'; + name = '🎞 Play m3u'; + icon = 'src/vi/playm3u/icon.png'; + site = 'https://vnepg.site'; + version = '1.0.0'; + + pluginSettings: Plugin.PluginSettings = { + m3uUrl: { + value: '', + label: 'M3U URL', + type: 'Text', + }, + }; + + get m3uUrl(): string { + return (storage.get('m3uUrl') as string) || this.pluginSettings.m3uUrl.value; + } + + async popularNovels(pageNo: number): Promise { + if (pageNo > 1 || !this.m3uUrl) return []; + const text = await fetchText(this.m3uUrl); + + const playlist = iptvPlaylistParser.parse(text); + + return playlist.items.map((item) => { + const params = new URLSearchParams(); + params.set('url', item.url); + params.set('name', item.name); + if (item.tvg.logo) params.set('logo', item.tvg.logo); + if (item.http['user-agent']) params.set('ua', item.http['user-agent']); + + return { + name: item.name, + path: `/playm3u?${params.toString()}`, + cover: item.tvg.logo || defaultCover, + }; + }); + } + + async searchNovels(searchTerm: string, pageNo: number): Promise { + if (pageNo > 1) return []; + const novels = await this.popularNovels(1); + return novels.filter(n => n.name.toLowerCase().includes(searchTerm.toLowerCase())); + } + + async parseNovel(novelPath: string): Promise { + const params = new URLSearchParams(novelPath.split('?')[1]); + const name = params.get('name') || 'Unknown'; + const cover = params.get('logo') || defaultCover; + + return { + path: novelPath, + name, + cover, + status: NovelStatus.Ongoing, + chapters: [{ + name, + path: novelPath, + chapterNumber: 1, + }] + }; + } + + async parseChapter(chapterPath: string): Promise { + const params = new URLSearchParams(chapterPath.split('?')[1]); + const url = params.get('url') || ''; + const ua = params.get('ua') || ''; + + return [ + '', + '', + '', + ``, + ua ? `` : '', + '', + '', + ].join('\n'); + } + + resolveUrl(path: string): string { + return this.site; + } +} + +export default new Playm3uPlugin(); diff --git a/public/static/src/vi/playm3u/icon.png b/public/static/src/vi/playm3u/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..22ecaa596a7352134ea598d439843b6ad538d407 GIT binary patch literal 13113 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 literal 0 HcmV?d00001 From 5d31816c2f7bc010474650af973aba1e365446a1 Mon Sep 17 00:00:00 2001 From: Fioren <102145692+FiorenMas@users.noreply.github.com> Date: Fri, 12 Jun 2026 01:23:02 +0700 Subject: [PATCH 2/2] Change interface declaration to type declaration --- plugins/vietnamese/Playm3u/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/vietnamese/Playm3u/index.ts b/plugins/vietnamese/Playm3u/index.ts index e6d50028..e18bd3a8 100644 --- a/plugins/vietnamese/Playm3u/index.ts +++ b/plugins/vietnamese/Playm3u/index.ts @@ -4,7 +4,7 @@ import { defaultCover } from '@libs/defaultCover'; import { NovelStatus } from '@libs/novelStatus'; import { storage } from '@libs/storage'; -interface PlaylistItem { +type PlaylistItem = { name: string; tvg: { id: string; @@ -39,7 +39,7 @@ const iptvPlaylistParser = { return match?.[1] ?? ''; }; const getName = (str: string) => { - const info = str.replace(/\="(.*?)"/g, ''); + const info = str.replace(/="(.*?)"/g, ''); const parts = info.split(/,(.*)/); return parts[1] || ''; };