diff --git a/mobile/src/changelog.ts b/mobile/src/changelog.ts index a3cadb8..6bcc472 100644 --- a/mobile/src/changelog.ts +++ b/mobile/src/changelog.ts @@ -35,6 +35,13 @@ export type ChangelogEntry = { }; export const CHANGELOG: ChangelogEntry[] = [ + { + id: '2026-10-15e', + date: 'October 2026', + items: [ + 'New movie source: xtapes, with 3 to 5 fresh full movies a day, often on the day they come out. They play in the app player.', + ], + }, { id: '2026-10-15d', date: 'October 2026', diff --git a/mobile/src/lib/packerHoster.ts b/mobile/src/lib/packerHoster.ts index 4f50b27..4ca90bc 100644 --- a/mobile/src/lib/packerHoster.ts +++ b/mobile/src/lib/packerHoster.ts @@ -31,6 +31,9 @@ const PACKER_HOSTS = new Set([ 'streamwish.com', 'streamwish.to', 'embedwish.com', 'wishembed.pro', 'awish.pro', 'dwish.pro', 'mwish.pro', 'swishsrv.com', 'streamwish.site', 'hlswish.com', 'kswplayer.info', + // sextapes.to (filmy z xtapes.la, 2026-10-15): ten sam P.A.C.K.E.R.-JWPlayer, HLS na + // cdn-centaurus zwiazany z IP rozwiazujacego, wiec rozwiazac musi telefon. + 'sextapes.to', ]); const UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36'; @@ -93,6 +96,30 @@ export function unpackPacker(js: string): string | null { const FILE_RE = /(?:["']?file["']?|sources?)\s*[:=]\s*["'](https?:\/\/[^"']+\.(?:m3u8|mp4|mpd)[^"']*)["']/i; const AD_RE = /\/(?:preroll|midroll|postroll|preplay|ads?|advert|promo)\d*\.(?:mp4|m3u8|webm)/i; +// Nowszy zapis StreamWish (sextapes.to 2026-10): `var links={"hls4":"/stream/.../master.m3u8", +// "hls2":"https://cdn/.../master.m3u8?t=..."}` i `sources:[{file:links.hls4||links.hls2}]`, +// wiec FILE_RE nie widzi literalu. Bierzemy pelny adres (hls2), a gdy go nie ma, wzgledny +// (hls4) doklejony do domeny embedu. +const LINKS_ENTRY_RE = /["']?(hls\d+)["']?\s*:\s*["']([^"']+\.m3u8[^"']*)["']/gi; + +function linksObjectUrl(js: string, embedUrl: string): string | null { + let relative: string | null = null; + let m: RegExpExecArray | null; + LINKS_ENTRY_RE.lastIndex = 0; + while ((m = LINKS_ENTRY_RE.exec(js)) !== null) { + const u = m[2]; + if (AD_RE.test(u)) continue; + if (/^https?:\/\//i.test(u)) return u; + if (!relative && u.startsWith('/')) relative = u; + } + if (!relative) return null; + try { + return new URL(relative, embedUrl).toString(); + } catch { + return null; + } +} + export async function resolvePackerHoster( embedUrl: string, sourceUrl?: string, @@ -137,6 +164,7 @@ export async function resolvePackerHoster( } const um = unpacked.match(FILE_RE); if (um && !AD_RE.test(um[1])) videoUrl = um[1]; + if (!videoUrl) videoUrl = linksObjectUrl(unpacked, url); } if (!videoUrl) return { error: 'no_video_url' };