Compare commits

...

3 Commits

Author SHA1 Message Date
wukko
7a6977ec35
api/processing/service-patterns: refactor
sorted all patterns alphabetically and moved the "or" operator to the end of the line
2025-08-11 18:31:52 +06:00
wukko
64a7b1dd62
api/bilibili: add support for video parts/episodes
different videos share the same id, kind of weird
2025-08-11 18:15:38 +06:00
TfourJ
1d5db46a79
api/service-config: add support for m subdomain for twitch (#1407)
* api/twitch: add support for m. subdomain

* tests/twitch: add test for m. subdomain
2025-08-10 13:14:23 +02:00
6 changed files with 84 additions and 47 deletions

View File

@ -7,6 +7,7 @@ export const services = {
bilibili: { bilibili: {
patterns: [ patterns: [
"video/:comId", "video/:comId",
"video/:comId?p=:partId",
"_shortLink/:comShortLink", "_shortLink/:comShortLink",
"_tv/:lang/video/:tvId", "_tv/:lang/video/:tvId",
"_tv/video/:tvId" "_tv/video/:tvId"
@ -165,6 +166,7 @@ export const services = {
twitch: { twitch: {
patterns: [":channel/clip/:clip"], patterns: [":channel/clip/:clip"],
tld: "tv", tld: "tv",
subdomains: ["clips", "www", "m"],
}, },
twitter: { twitter: {
patterns: [ patterns: [

View File

@ -1,53 +1,72 @@
export const testers = { export const testers = {
"bilibili": pattern => "bilibili": pattern =>
pattern.comId?.length <= 12 || pattern.comShortLink?.length <= 16 (pattern.comId?.length <= 12 && pattern.partId?.length <= 3) ||
|| pattern.tvId?.length <= 24, (pattern.comId?.length <= 12 && !pattern.partId) ||
pattern.comShortLink?.length <= 16 ||
pattern.tvId?.length <= 24,
"bsky": pattern =>
pattern.user?.length <= 128 && pattern.post?.length <= 128,
"dailymotion": pattern => pattern.id?.length <= 32, "dailymotion": pattern => pattern.id?.length <= 32,
"facebook": pattern =>
pattern.shortLink?.length <= 11 ||
pattern.username?.length <= 30 ||
pattern.caption?.length <= 255 ||
pattern.id?.length <= 20 && !pattern.shareType ||
pattern.id?.length <= 20 && pattern.shareType?.length === 1,
"instagram": pattern => "instagram": pattern =>
pattern.postId?.length <= 48 pattern.postId?.length <= 48 ||
|| pattern.shareId?.length <= 16 pattern.shareId?.length <= 16 ||
|| (pattern.username?.length <= 30 && pattern.storyId?.length <= 24), (pattern.username?.length <= 30 && pattern.storyId?.length <= 24),
"loom": pattern => "loom": pattern =>
pattern.id?.length <= 32, pattern.id?.length <= 32,
"newgrounds": pattern =>
pattern.id?.length <= 12 ||
pattern.audioId?.length <= 12,
"ok": pattern => "ok": pattern =>
pattern.id?.length <= 16, pattern.id?.length <= 16,
"pinterest": pattern => "pinterest": pattern =>
pattern.id?.length <= 128 || pattern.shortLink?.length <= 32, pattern.id?.length <= 128 ||
pattern.shortLink?.length <= 32,
"reddit": pattern => "reddit": pattern =>
pattern.id?.length <= 16 && !pattern.sub && !pattern.user pattern.id?.length <= 16 && !pattern.sub && !pattern.user ||
|| (pattern.sub?.length <= 22 && pattern.id?.length <= 16) (pattern.sub?.length <= 22 && pattern.id?.length <= 16) ||
|| (pattern.user?.length <= 22 && pattern.id?.length <= 16) (pattern.user?.length <= 22 && pattern.id?.length <= 16) ||
|| (pattern.sub?.length <= 22 && pattern.shareId?.length <= 16) (pattern.sub?.length <= 22 && pattern.shareId?.length <= 16) ||
|| (pattern.shortId?.length <= 16), (pattern.shortId?.length <= 16),
"rutube": pattern => "rutube": pattern =>
(pattern.id?.length === 32 && pattern.key?.length <= 32) || (pattern.id?.length === 32 && pattern.key?.length <= 32) ||
pattern.id?.length === 32 || pattern.yappyId?.length === 32, pattern.id?.length === 32 ||
pattern.yappyId?.length === 32,
"soundcloud": pattern =>
(pattern.author?.length <= 255 && pattern.song?.length <= 255)
|| pattern.shortLink?.length <= 32,
"snapchat": pattern => "snapchat": pattern =>
(pattern.username?.length <= 32 && (!pattern.storyId || pattern.storyId?.length <= 255)) (pattern.username?.length <= 32 && (!pattern.storyId || pattern.storyId?.length <= 255)) ||
|| pattern.spotlightId?.length <= 255 pattern.spotlightId?.length <= 255 ||
|| pattern.shortLink?.length <= 16, pattern.shortLink?.length <= 16,
"soundcloud": pattern =>
(pattern.author?.length <= 255 && pattern.song?.length <= 255) ||
pattern.shortLink?.length <= 32,
"streamable": pattern => "streamable": pattern =>
pattern.id?.length <= 6, pattern.id?.length <= 6,
"tiktok": pattern => "tiktok": pattern =>
pattern.postId?.length <= 21 || pattern.shortLink?.length <= 21, pattern.postId?.length <= 21 ||
pattern.shortLink?.length <= 21,
"tumblr": pattern => "tumblr": pattern =>
pattern.id?.length < 21 pattern.id?.length < 21 ||
|| (pattern.id?.length < 21 && pattern.user?.length <= 32), (pattern.id?.length < 21 && pattern.user?.length <= 32),
"twitch": pattern => "twitch": pattern =>
pattern.channel && pattern.clip?.length <= 100, pattern.channel && pattern.clip?.length <= 100,
@ -56,30 +75,16 @@ export const testers = {
pattern.id?.length < 20, pattern.id?.length < 20,
"vimeo": pattern => "vimeo": pattern =>
pattern.id?.length <= 11 pattern.id?.length <= 11 && (!pattern.password || pattern.password.length < 16),
&& (!pattern.password || pattern.password.length < 16),
"vk": pattern => "vk": pattern =>
(pattern.ownerId?.length <= 10 && pattern.videoId?.length <= 10) || (pattern.ownerId?.length <= 10 && pattern.videoId?.length <= 10) ||
(pattern.ownerId?.length <= 10 && pattern.videoId?.length <= 10 && pattern.videoId?.accessKey <= 18), (pattern.ownerId?.length <= 10 && pattern.videoId?.length <= 10 && pattern.videoId?.accessKey <= 18),
"xiaohongshu": pattern =>
pattern.id?.length <= 24 && pattern.token?.length <= 64 ||
pattern.shareId?.length <= 24 && pattern.shareType?.length === 1,
"youtube": pattern => "youtube": pattern =>
pattern.id?.length <= 11, pattern.id?.length <= 11,
"facebook": pattern =>
pattern.shortLink?.length <= 11
|| pattern.username?.length <= 30
|| pattern.caption?.length <= 255
|| pattern.id?.length <= 20 && !pattern.shareType
|| pattern.id?.length <= 20 && pattern.shareType?.length === 1,
"bsky": pattern =>
pattern.user?.length <= 128 && pattern.post?.length <= 128,
"xiaohongshu": pattern =>
pattern.id?.length <= 24 && pattern.token?.length <= 64
|| pattern.shareId?.length <= 24 && pattern.shareType?.length === 1,
"newgrounds": pattern =>
pattern.id?.length <= 12 || pattern.audioId?.length <= 12,
} }

View File

@ -17,8 +17,14 @@ function extractBestQuality(dashData) {
return [ bestVideo, bestAudio ]; return [ bestVideo, bestAudio ];
} }
async function com_download(id) { async function com_download(id, partId) {
const html = await fetch(`https://bilibili.com/video/${id}`, { const url = new URL(`https://bilibili.com/video/${id}`);
if (partId) {
url.searchParams.set('p', partId);
}
const html = await fetch(url, {
headers: { headers: {
"user-agent": genericUserAgent "user-agent": genericUserAgent
} }
@ -47,10 +53,15 @@ async function com_download(id) {
return { error: "fetch.empty" }; return { error: "fetch.empty" };
} }
let filenameBase = `bilibili_${id}`;
if (partId) {
filenameBase += `_${partId}`;
}
return { return {
urls: [video.baseUrl, audio.baseUrl], urls: [video.baseUrl, audio.baseUrl],
audioFilename: `bilibili_${id}_audio`, audioFilename: `${filenameBase}_audio`,
filename: `bilibili_${id}_${video.width}x${video.height}.mp4`, filename: `${filenameBase}_${video.width}x${video.height}.mp4`,
}; };
} }
@ -89,14 +100,14 @@ async function tv_download(id) {
}; };
} }
export default async function({ comId, tvId, comShortLink }) { export default async function({ comId, tvId, comShortLink, partId }) {
if (comShortLink) { if (comShortLink) {
const patternMatch = await resolveRedirectingURL(`https://b23.tv/${comShortLink}`); const patternMatch = await resolveRedirectingURL(`https://b23.tv/${comShortLink}`);
comId = patternMatch?.comId; comId = patternMatch?.comId;
} }
if (comId) { if (comId) {
return com_download(comId); return com_download(comId, partId);
} else if (tvId) { } else if (tvId) {
return tv_download(tvId); return tv_download(tvId);
} }

View File

@ -147,6 +147,7 @@ function cleanURL(url) {
limitQuery('v'); limitQuery('v');
} }
break; break;
case "bilibili":
case "rutube": case "rutube":
if (url.searchParams.get('p')) { if (url.searchParams.get('p')) {
limitQuery('p'); limitQuery('p');

View File

@ -56,5 +56,14 @@
"code": 200, "code": 200,
"status": "tunnel" "status": "tunnel"
} }
},
{
"name": "bilibili.com link with part id",
"url": "https://www.bilibili.com/video/BV1uo4y1K72s?spm_id_from=333.788.videopod.episodes&p=6",
"params": {},
"expected": {
"code": 200,
"status": "tunnel"
}
} }
] ]

View File

@ -29,5 +29,14 @@
"code": 200, "code": 200,
"status": "tunnel" "status": "tunnel"
} }
},
{
"name": "clip (mobile subdomain)",
"url": "https://m.twitch.tv/rtgame/clip/TubularInventiveSardineCorgiDerp-PM47mJQQ2vsL5B5G",
"params": {},
"expected": {
"code": 200,
"status": "redirect"
}
} }
] ]