From f2466baed654769f96f1899d7c886314d1a253b5 Mon Sep 17 00:00:00 2001 From: wukko Date: Mon, 30 Mar 2026 16:13:32 +0600 Subject: [PATCH] api: remove support for xiaohongshu posts are no longer viewable without logging in --- api/README.md | 1 - api/src/processing/match-action.js | 2 - api/src/processing/match.js | 10 -- api/src/processing/service-config.js | 8 -- api/src/processing/service-patterns.js | 4 - api/src/processing/services/xiaohongshu.js | 109 --------------------- api/src/processing/url.js | 11 --- api/src/util/tests/xiaohongshu.json | 60 ------------ docs/api.md | 2 +- 9 files changed, 1 insertion(+), 206 deletions(-) delete mode 100644 api/src/processing/services/xiaohongshu.js delete mode 100644 api/src/util/tests/xiaohongshu.json diff --git a/api/README.md b/api/README.md index 3bdfb519..968e3a18 100644 --- a/api/README.md +++ b/api/README.md @@ -37,7 +37,6 @@ if the desired service isn't supported yet, feel free to create an appropriate i | twitter/x | ✅ | ✅ | ✅ | ➖ | ➖ | | vimeo | ✅ | ✅ | ✅ | ✅ | ✅ | | vk videos & clips | ✅ | ❌ | ✅ | ✅ | ✅ | -| xiaohongshu | ✅ | ✅ | ✅ | ➖ | ➖ | | youtube | ✅ | ✅ | ✅ | ✅ | ✅ | | emoji | meaning | diff --git a/api/src/processing/match-action.js b/api/src/processing/match-action.js index 5d7abea2..ff7175c9 100644 --- a/api/src/processing/match-action.js +++ b/api/src/processing/match-action.js @@ -103,7 +103,6 @@ export default function({ case "twitter": case "snapchat": case "bsky": - case "xiaohongshu": params = { picker: r.picker }; break; @@ -180,7 +179,6 @@ export default function({ break; case "ok": - case "xiaohongshu": case "newgrounds": params = { type: "proxy" }; break; diff --git a/api/src/processing/match.js b/api/src/processing/match.js index 1265297c..e97f7600 100644 --- a/api/src/processing/match.js +++ b/api/src/processing/match.js @@ -28,7 +28,6 @@ import snapchat from "./services/snapchat.js"; import loom from "./services/loom.js"; import facebook from "./services/facebook.js"; import bluesky from "./services/bluesky.js"; -import xiaohongshu from "./services/xiaohongshu.js"; import newgrounds from "./services/newgrounds.js"; let freebind; @@ -260,15 +259,6 @@ export default async function({ host, patternMatch, params, authType }) { }); break; - case "xiaohongshu": - r = await xiaohongshu({ - ...patternMatch, - h265: params.allowH265, - isAudioOnly, - dispatcher, - }); - break; - case "newgrounds": r = await newgrounds({ ...patternMatch, diff --git a/api/src/processing/service-config.js b/api/src/processing/service-config.js index 4ca3c833..fbe25805 100644 --- a/api/src/processing/service-config.js +++ b/api/src/processing/service-config.js @@ -205,14 +205,6 @@ export const services = { subdomains: ["m"], altDomains: ["vkvideo.ru", "vk.ru"], }, - xiaohongshu: { - patterns: [ - "explore/:id?xsec_token=:token", - "discovery/item/:id?xsec_token=:token", - ":shareType/:shareId", - ], - altDomains: ["xhslink.com"], - }, youtube: { patterns: [ "watch?v=:id", diff --git a/api/src/processing/service-patterns.js b/api/src/processing/service-patterns.js index 6dc3ccbd..07bef989 100644 --- a/api/src/processing/service-patterns.js +++ b/api/src/processing/service-patterns.js @@ -81,10 +81,6 @@ export const testers = { (pattern.ownerId?.length <= 10 && pattern.videoId?.length <= 10) || (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 => pattern.id?.length <= 11, } diff --git a/api/src/processing/services/xiaohongshu.js b/api/src/processing/services/xiaohongshu.js deleted file mode 100644 index f9cbf680..00000000 --- a/api/src/processing/services/xiaohongshu.js +++ /dev/null @@ -1,109 +0,0 @@ -import { resolveRedirectingURL } from "../url.js"; -import { genericUserAgent } from "../../config.js"; -import { createStream } from "../../stream/manage.js"; - -const https = (url) => { - return url.replace(/^http:/i, 'https:'); -} - -export default async function ({ id, token, shareType, shareId, h265, isAudioOnly, dispatcher }) { - let noteId = id; - let xsecToken = token; - - if (!noteId) { - const patternMatch = await resolveRedirectingURL( - `https://xhslink.com/${shareType}/${shareId}`, - dispatcher - ); - - noteId = patternMatch?.id; - xsecToken = patternMatch?.token; - } - - if (!noteId || !xsecToken) return { error: "fetch.short_link" }; - - const res = await fetch(`https://www.xiaohongshu.com/explore/${noteId}?xsec_token=${xsecToken}`, { - headers: { - "user-agent": genericUserAgent, - }, - dispatcher, - }); - - const html = await res.text(); - - let note; - try { - const initialState = html - .split('')[0] - .replace(/:\s*undefined/g, ":null"); - - const data = JSON.parse(initialState); - - const noteInfo = data?.note?.noteDetailMap; - if (!noteInfo) throw "no note detail map"; - - const currentNote = noteInfo[noteId]; - if (!currentNote) throw "no current note in detail map"; - - note = currentNote.note; - } catch {} - - if (!note) return { error: "fetch.empty" }; - - const video = note.video; - const images = note.imageList; - - const filenameBase = `xiaohongshu_${noteId}`; - - if (video) { - const videoFilename = `${filenameBase}.mp4`; - const audioFilename = `${filenameBase}_audio`; - - let videoURL; - - if (h265 && !isAudioOnly && video.consumer?.originVideoKey) { - videoURL = `https://sns-video-bd.xhscdn.com/${video.consumer.originVideoKey}`; - } else { - const h264Streams = video.media?.stream?.h264; - - if (h264Streams?.length) { - videoURL = h264Streams.reduce((a, b) => Number(a?.videoBitrate) > Number(b?.videoBitrate) ? a : b).masterUrl; - } - } - - if (!videoURL) return { error: "fetch.empty" }; - - return { - urls: https(videoURL), - filename: videoFilename, - audioFilename: audioFilename, - } - } - - if (!images || images.length === 0) { - return { error: "fetch.empty" }; - } - - if (images.length === 1) { - return { - isPhoto: true, - urls: https(images[0].urlDefault), - filename: `${filenameBase}.jpg`, - } - } - - const picker = images.map((image, i) => { - return { - type: "photo", - url: createStream({ - service: "xiaohongshu", - type: "proxy", - url: https(image.urlDefault), - filename: `${filenameBase}_${i + 1}.jpg`, - }) - } - }); - - return { picker }; -} diff --git a/api/src/processing/url.js b/api/src/processing/url.js index 2c4be1e3..26df685b 100644 --- a/api/src/processing/url.js +++ b/api/src/processing/url.js @@ -97,12 +97,6 @@ function aliasURL(url) { } break; - case "xhslink": - if (url.hostname === 'xhslink.com' && parts.length === 3) { - url = new URL(`https://www.xiaohongshu.com/${parts[1]}/${parts[2]}`); - } - break; - case "loom": const idPart = parts[parts.length - 1]; if (idPart.length > 32) { @@ -158,11 +152,6 @@ function cleanURL(url) { limitQuery('post_id'); } break; - case "xiaohongshu": - if (url.searchParams.get('xsec_token')) { - limitQuery('xsec_token'); - } - break; } if (stripQuery) { diff --git a/api/src/util/tests/xiaohongshu.json b/api/src/util/tests/xiaohongshu.json deleted file mode 100644 index ed523856..00000000 --- a/api/src/util/tests/xiaohongshu.json +++ /dev/null @@ -1,60 +0,0 @@ -[ - { - "name": "video (might have expired)", - "url": "https://www.xiaohongshu.com/explore/685e63e1000000000b02ee3b?xsec_token=ABN8EQJCDMPcFX9RRggeIPSHLIJ8zkGceFDyBewLGUz30=", - "canFail": true, - "params": {}, - "expected": { - "code": 200, - "status": "tunnel" - } - }, - { - "name": "picker with multiple live photos (might have expired)", - "url": "https://www.xiaohongshu.com/explore/687128a2000000001203d94c?xsec_token=CBlDi5QDXDWZu2uUmbUrpKwg8lEL3uC10mc59lGf43r9w=", - "canFail": true, - "params": {}, - "expected": { - "code": 200, - "status": "picker" - } - }, - { - "name": "one photo (might have expired)", - "url": "https://www.xiaohongshu.com/explore/64726b99000000000800e115?xsec_token=ABoD3qPHqVZolCfS-J8UP9QQaPXZ6Z6PVyODrhaiUg27U=", - "canFail": true, - "params": {}, - "expected": { - "code": 200, - "status": "tunnel" - } - }, - { - "name": "short link (might have expired)", - "url": "https://xhslink.com/m/2wAnaTkLRc1", - "canFail": true, - "params": {}, - "expected": { - "code": 200, - "status": "tunnel" - } - }, - { - "name": "wrong note id", - "url": "https://www.xiaohongshu.com/discovery/item/6789065911100000210035fc?source=webshare&xhsshare=pc_web&xsec_token=CBustnz_Twf1BSybpe5-D-BzUb-Bx28DPLb418TN9S9Kk&xsec_source=pc_share", - "params": {}, - "expected": { - "code": 400, - "status": "error" - } - }, - { - "name": "short link, wrong id", - "url": "https://xhslink.com/a/aaaaaa", - "params": {}, - "expected": { - "code": 400, - "status": "error" - } - } -] diff --git a/docs/api.md b/docs/api.md index 47fc7115..4ad2839b 100644 --- a/docs/api.md +++ b/docs/api.md @@ -88,7 +88,7 @@ all keys except for `url` are optional. value options are separated by `/`. | `youtubeVideoContainer` | `string` | `auto / mp4 / webm / mkv` | `auto` | | `youtubeDubLang` | `string` | any valid ISO 639-1 language code | *none* | | `convertGif` | `boolean` | convert twitter gifs to the actual GIF format | `true` | -| `allowH265` | `boolean` | allow H265/HEVC videos from tiktok/xiaohongshu | `false` | +| `allowH265` | `boolean` | allow H265/HEVC videos from tiktok | `false` | | `tiktokFullAudio` | `boolean` | download the original sound used in a video | `false` | | `youtubeBetterAudio` | `boolean` | prefer higher quality youtube audio if possible | `false` | | `youtubeHLS` | `boolean` | use HLS formats when downloading from youtube | `false` |