From 105e484231f1df4d9bb4f8ab59fdda729a51b63d Mon Sep 17 00:00:00 2001 From: whale9820 <87256750+whale9820@users.noreply.github.com> Date: Mon, 5 Oct 2026 17:27:00 +0800 Subject: [PATCH] api/services: add alipay --- api/README.md | 1 + api/src/processing/match.js | 5 ++ api/src/processing/service-config.js | 7 +++ api/src/processing/service-patterns.js | 4 ++ api/src/processing/services/alipay.js | 72 ++++++++++++++++++++++++++ api/src/processing/url.js | 17 ++++++ api/src/util/tests/alipay.json | 31 +++++++++++ 7 files changed, 137 insertions(+) create mode 100644 api/src/processing/services/alipay.js create mode 100644 api/src/util/tests/alipay.json diff --git a/api/README.md b/api/README.md index 968e3a18..9a84e746 100644 --- a/api/README.md +++ b/api/README.md @@ -17,6 +17,7 @@ if the desired service isn't supported yet, feel free to create an appropriate i | service | video + audio | only audio | only video | metadata | rich file names | | :-------- | :-----------: | :--------: | :--------: | :------: | :-------------: | +| alipay | ✅ | ❌ | ✅ | ✅ | ➖ | | bilibili | ✅ | ✅ | ✅ | ➖ | ➖ | | bluesky | ✅ | ✅ | ✅ | ➖ | ➖ | | dailymotion | ✅ | ✅ | ✅ | ✅ | ✅ | diff --git a/api/src/processing/match.js b/api/src/processing/match.js index e97f7600..90e3301b 100644 --- a/api/src/processing/match.js +++ b/api/src/processing/match.js @@ -25,6 +25,7 @@ import twitch from "./services/twitch.js"; import rutube from "./services/rutube.js"; import dailymotion from "./services/dailymotion.js"; import snapchat from "./services/snapchat.js"; +import alipay from "./services/alipay.js"; import loom from "./services/loom.js"; import facebook from "./services/facebook.js"; import bluesky from "./services/bluesky.js"; @@ -237,6 +238,10 @@ export default async function({ host, patternMatch, params, authType }) { }); break; + case "alipay": + r = await alipay(patternMatch); + break; + case "loom": r = await loom({ id: patternMatch.id, diff --git a/api/src/processing/service-config.js b/api/src/processing/service-config.js index fbe25805..e4b7022c 100644 --- a/api/src/processing/service-config.js +++ b/api/src/processing/service-config.js @@ -4,6 +4,13 @@ export const audioIgnore = new Set(["vk", "ok", "loom"]); export const hlsExceptions = new Set(["dailymotion", "vimeo", "rutube", "bsky", "youtube"]); export const services = { + alipay: { + patterns: [ + "video/:contentId", + "_shortLink/:shortLink" + ], + subdomains: ["render", "ur"], + }, bilibili: { patterns: [ "video/:comId", diff --git a/api/src/processing/service-patterns.js b/api/src/processing/service-patterns.js index 07bef989..eb15b175 100644 --- a/api/src/processing/service-patterns.js +++ b/api/src/processing/service-patterns.js @@ -22,6 +22,10 @@ export const testers = { pattern.shareId?.length <= 16 || (pattern.username?.length <= 30 && pattern.storyId?.length <= 24), + "alipay": pattern => + pattern.contentId?.length <= 40 || + pattern.shortLink?.length <= 40, + "loom": pattern => pattern.id?.length <= 32, diff --git a/api/src/processing/services/alipay.js b/api/src/processing/services/alipay.js new file mode 100644 index 00000000..b8798ed8 --- /dev/null +++ b/api/src/processing/services/alipay.js @@ -0,0 +1,72 @@ +import { genericUserAgent } from "../../config.js"; +import { getRedirectingURL } from "../../misc/utils.js"; + +const gateway = "https://webgw-internet.alipay.com/contentservice/com.alipay.sofa.function.SOFAFunction/apply/myjf.yuyan.contentservice.default.ContentWebGWController.list"; +const contentIdRegex = /contentId(?:%253D|%3D|=)([A-Za-z0-9]+)/; +const clarities = ["1080P_h265_EH", "1080P_h265", "720P_h265", "720P_h264", "540P_h264"]; + +const headers = { + "user-agent": genericUserAgent, + "content-type": "application/json", + "x-webgw-appid": "180020010001266490", + "x-webgw-version": "2.0" +}; + +async function getContent(contentId) { + const json = await fetch(gateway, { + method: "POST", + headers, + body: JSON.stringify({ contentId }) + }).then(r => r.json()).catch(() => {}); + + if (!json?.success) return; + return json.resultObj?.data?.[0]; +} + +async function pickStream(vid) { + const [base, token] = vid.split("?t="); + if (!token) return vid; + + const prefix = base.slice(0, base.lastIndexOf("/")); + + for (const clarity of clarities) { + const url = `${prefix}/${clarity}?t=${token}`; + const status = await fetch(url, { + headers: { "user-agent": genericUserAgent, range: "bytes=0-1" } + }).then(r => r.status).catch(() => 0); + + if (status === 200 || status === 206) return url; + } + + return vid; +} + +export default async function({ contentId, shortLink }) { + let id = contentId; + + if (!id && shortLink) { + const location = await getRedirectingURL(`https://ur.lepudding.com/${shortLink}`); + id = location?.match(contentIdRegex)?.[1]; + } + + if (!id) return { error: "fetch.short_link" }; + + const content = await getContent(id); + if (!content) return { error: "fetch.fail" }; + + if (!content.video?.vid) return { error: "fetch.empty" }; + + const url = await pickStream(content.video.vid); + + const fileMetadata = { + title: content.title, + artist: content.author?.nickName + }; + + return { + urls: url, + filename: `alipay_${id}.mp4`, + audioFilename: `alipay_${id}_audio`, + fileMetadata + }; +} diff --git a/api/src/processing/url.js b/api/src/processing/url.js index 26df685b..fb154a4c 100644 --- a/api/src/processing/url.js +++ b/api/src/processing/url.js @@ -97,6 +97,23 @@ function aliasURL(url) { } break; + case "lepudding": + if (url.hostname === 'ur.lepudding.com' && parts.length === 2) { + url = new URL(`https://alipay.com/_shortLink/${ + encodeURIComponent(parts[1]) + }`); + } + break; + + case "alipay": + if (url.pathname.endsWith('/video-share.html')) { + const contentId = url.search.match(/contentId(?:%253D|%3D|=)([A-Za-z0-9]+)/)?.[1]; + if (contentId) { + url = new URL(`https://alipay.com/video/${contentId}`); + } + } + break; + case "loom": const idPart = parts[parts.length - 1]; if (idPart.length > 32) { diff --git a/api/src/util/tests/alipay.json b/api/src/util/tests/alipay.json new file mode 100644 index 00000000..e006c744 --- /dev/null +++ b/api/src/util/tests/alipay.json @@ -0,0 +1,31 @@ +[ + { + "name": "short link video", + "url": "https://ur.lepudding.com/_3Je3KhPExzQMlHZRvuBgcd", + "params": {}, + "expected": { + "code": 200, + "status": "tunnel" + } + }, + { + "name": "short link video (muted)", + "url": "https://ur.lepudding.com/_3Je3KhPExzQMlHZRvuBgcd", + "params": { + "downloadMode": "mute" + }, + "expected": { + "code": 200, + "status": "tunnel" + } + }, + { + "name": "content id link", + "url": "https://alipay.com/video/20260913OB020010038324417829", + "params": {}, + "expected": { + "code": 200, + "status": "tunnel" + } + } +]