mirror of
https://github.com/KuekHaoYang/KVideo.git
synced 2026-08-13 07:43:43 +08:00
Reject unsafe dynamic filter execution, reduce heuristic false positives, add regression coverage, bump the app to 4.9.17, and update Next.js to the latest stable security patch.
101 lines
3.2 KiB
TypeScript
101 lines
3.2 KiB
TypeScript
import {
|
|
hasDominantFilm24LikeGrid,
|
|
hasDominantNtscLikeGrid,
|
|
} from './m3u8-duration-grid';
|
|
import type { Block, MainPattern, SegmentLocation } from './m3u8-ad-types';
|
|
|
|
function unwrapProxyUrl(url: string): string {
|
|
if (!url.includes('/api/proxy?url=')) return url;
|
|
|
|
try {
|
|
const match = url.match(/[?&]url=([^&]+)/);
|
|
return match?.[1] ? decodeURIComponent(match[1]) : url;
|
|
} catch {
|
|
return url;
|
|
}
|
|
}
|
|
|
|
export function extractFilename(url: string): string {
|
|
try {
|
|
const unwrappedUrl = unwrapProxyUrl(url);
|
|
const path = unwrappedUrl.includes('://')
|
|
? new URL(unwrappedUrl).pathname
|
|
: unwrappedUrl;
|
|
return path.split('/').pop() || '';
|
|
} catch {
|
|
return url.split('/').pop() || '';
|
|
}
|
|
}
|
|
|
|
function findCommonPrefix(strings: string[]): string {
|
|
if (strings.length < 2) return '';
|
|
|
|
let prefix = '';
|
|
for (let index = 0; index < strings[0].length; index += 1) {
|
|
const character = strings[0][index];
|
|
if (!strings.every(value => value[index] === character)) break;
|
|
prefix += character;
|
|
}
|
|
return prefix;
|
|
}
|
|
|
|
export function extractSegmentLocation(url: string): SegmentLocation {
|
|
try {
|
|
const unwrappedUrl = unwrapProxyUrl(url);
|
|
const parsedUrl = unwrappedUrl.includes('://') ? new URL(unwrappedUrl) : null;
|
|
const path = parsedUrl?.pathname || unwrappedUrl;
|
|
const lastSlash = path.lastIndexOf('/');
|
|
return {
|
|
origin: parsedUrl?.origin || '',
|
|
pathPrefix: lastSlash >= 0 ? path.substring(0, lastSlash + 1) : '',
|
|
};
|
|
} catch {
|
|
const lastSlash = url.lastIndexOf('/');
|
|
return {
|
|
origin: '',
|
|
pathPrefix: lastSlash >= 0 ? url.substring(0, lastSlash + 1) : '',
|
|
};
|
|
}
|
|
}
|
|
|
|
export function learnMainPattern(blocks: Block[]): MainPattern {
|
|
const mainBlock = blocks.length > 0
|
|
? blocks.reduce((largest, block) => (
|
|
block.segments.length > largest.segments.length ? block : largest
|
|
))
|
|
: null;
|
|
|
|
if (!mainBlock || mainBlock.segments.length === 0) {
|
|
return {
|
|
filenameRegex: null,
|
|
avgDuration: 0,
|
|
commonPrefix: '',
|
|
pathPrefix: '',
|
|
origin: '',
|
|
usesNtscLikeGrid: false,
|
|
usesFilm24LikeGrid: false,
|
|
};
|
|
}
|
|
|
|
const filenames = mainBlock.segments.map(segment => extractFilename(segment.url));
|
|
const commonPrefix = findCommonPrefix(filenames);
|
|
const totalDuration = mainBlock.segments.reduce((sum, segment) => (
|
|
sum + segment.duration
|
|
), 0);
|
|
const avgDuration = totalDuration / mainBlock.segments.length;
|
|
const escapedPrefix = commonPrefix.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
const filenameRegex = commonPrefix.length >= 2
|
|
? new RegExp(`^${escapedPrefix}`)
|
|
: null;
|
|
const location = extractSegmentLocation(mainBlock.segments[0].url);
|
|
|
|
return {
|
|
filenameRegex,
|
|
avgDuration,
|
|
commonPrefix,
|
|
...location,
|
|
usesNtscLikeGrid: hasDominantNtscLikeGrid(mainBlock.segments),
|
|
usesFilm24LikeGrid: hasDominantFilm24LikeGrid(mainBlock.segments),
|
|
};
|
|
}
|