Skip to content

Commit

Permalink
Refactors
Browse files Browse the repository at this point in the history
  • Loading branch information
Dobby233Liu committed Sep 16, 2024
1 parent 5c9dae8 commit da80d24
Show file tree
Hide file tree
Showing 8 changed files with 221 additions and 151 deletions.
13 changes: 0 additions & 13 deletions .eslintrc.cjs

This file was deleted.

30 changes: 30 additions & 0 deletions .markdownlint.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"blanks-around-fences": false,
"blanks-around-headings": false,
"blanks-around-lists": false,
"code-fence-style": false,
"emphasis-style": false,
"heading-start-left": false,
"heading-style": false,
"hr-style": false,
"line-length": false,
"list-indent": false,
"no-blanks-blockquote": false,
"no-hard-tabs": false,
"no-missing-space-atx": false,
"no-missing-space-closed-atx": false,
"no-multiple-blanks": false,
"no-multiple-space-atx": false,
"no-multiple-space-blockquote": false,
"no-multiple-space-closed-atx": false,
"no-trailing-spaces": false,
"ol-prefix": false,
"strong-style": false,
"list-marker-space": {
"ul_multi": 3,
"ul_single": 3
},
"ul-indent": {
"indent": 4
}
}
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ bembedfix 目前提供三种元数据:
- [Twitter Cards](https://developer.x.com/en/docs/twitter-for-websites/cards/overview/abouts-cards)

> [!WARNING]
> 如果用户代理为 Discordbot,Twitter Cards 元数据中提供的 player MIME 类型会被谎报为 video/mp4,表现为客户端里视频封面上有播放按钮但是不能直接播放。详情见 [#26][issue-26]
> 如果用户代理为 Discordbot,Twitter Cards 元数据中提供的 player MIME 类型会被谎报为 video/mp4。
> 表现为客户端里视频封面上有播放按钮但是不能直接播放。
> 详情见 [#26][issue-26]
值得注意的是,本服务目前只在 Discord 和 Twitter 上正式受测试过。

Expand Down
16 changes: 16 additions & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import globals from "globals";
import js from "@eslint/js";

export default [
js.configs.recommended,
{
languageOptions: {
globals: {
...globals.node,
},

ecmaVersion: "latest",
sourceType: "module",
},
},
];
16 changes: 9 additions & 7 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
shouldNotAddRedirectMetaprop,
oembedAddExtraMetadata,
isUserAStupidKidAndTryingToAccessAWordpressApi,
applySearchParams,
} from "./utils.js";
import {
getRequestedInfo,
Expand Down Expand Up @@ -108,13 +109,14 @@ export default async function handler(req, res) {

data.provider = PROVIDER_NAME;
// FIXME: preferredly do this in some other way or somewhere else
let oembedJson = new URL("oembed.json", getMyBaseURL(req));
let oembedXml = new URL("oembed.xml", getMyBaseURL(req));
for (let [k, v] of Object.entries(data.oembedAPIQueries)) {
if (v == null) continue;
oembedJson.searchParams.set(k, v);
oembedXml.searchParams.set(k, v);
}
let oembedJson = applySearchParams(
new URL("oembed.json", getMyBaseURL(req)),
data.oembedAPIQueries,
);
let oembedXml = applySearchParams(
new URL("oembed.xml", getMyBaseURL(req)),
data.oembedAPIQueries,
);
data.oembed_json = oembedJson;
data.oembed_xml = oembedXml;
data.lie_about_embed_player = !html5EmbedWorks;
Expand Down
19 changes: 19 additions & 0 deletions src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -256,3 +256,22 @@ export async function obtainVideoStreamFromCobalt(videoPageURL, page = 1) {
return item.url;
}
}

// This lets nullish operators work
export function parseIntSafe(value, radix) {
let ret = parseInt(value, radix);
if (isNaN(ret)) return null;
return ret;
}

export function applySearchParams(url, newParams) {
if (!newParams) return url;
// I hate my job
for (const [k, v] of newParams instanceof URLSearchParams
? newParams.entries()
: Object.entries(newParams)) {
if (v === null || v === undefined) continue;
url.searchParams.set(k, v);
}
return url;
}
150 changes: 20 additions & 130 deletions src/utils_bilibili.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@ import {
DEFAULT_WIDTH,
DEFAULT_HEIGHT,
obtainVideoStreamFromCobalt,
parseIntSafe,
applySearchParams,
} from "./utils.js";
import { FAKE_CLIENT_UA_HEADERS, COBALT_API_INSTANCE } from "./constants.js";
import makeFetchCookie from "fetch-cookie";
import * as crypto from "node:crypto";
import { wbiGetKeys, wbiSignURLSearchParams } from "./utils_bilibili_crypto.js";

// Group 4 is the ID of the video
const MAIN_SITE_VIDEO_PAGE_PATHNAME_REGEX =
Expand All @@ -27,23 +29,16 @@ export const isURLBilibiliVideo = (u) =>
export const getVideoIdByPath = (p) =>
MAIN_SITE_VIDEO_PAGE_PATHNAME_REGEX.exec(stripTrailingSlashes(p))[1];

// I hate my job
const searchParamEntries = (searchParams) =>
searchParams instanceof URLSearchParams
? searchParams.entries()
: Object.entries(searchParams);

export function makeVideoPage(vid, page = 1, searchParams) {
assert(vid);
const ret = new URL(vid, "https://www.bilibili.com/video/");
if (page != 1) ret.searchParams.set("p", page);
if (searchParams) {
for (const [k, v] of searchParamEntries(searchParams))
ret.searchParams.set(k, v);
}
applySearchParams(ret, searchParams);
return ret.href;
}

export function makeEmbedPlayerURL(vid, cid, page = 1, searchParams) {
assert(vid && cid);
// //player.bilibili.com/player.html?aid=429619610&bvid=BV1GG411b7sc&cid=805522554&page=1
const ret = new URL("https://player.bilibili.com/player.html");
if (vid.startsWith("BV")) {
Expand All @@ -53,10 +48,7 @@ export function makeEmbedPlayerURL(vid, cid, page = 1, searchParams) {
}
ret.searchParams.set("cid", cid);
ret.searchParams.set("page", page);
if (searchParams) {
for (const [k, v] of searchParamEntries(searchParams))
ret.searchParams.set(k, v);
}
applySearchParams(ret, searchParams);
return ret.href;
}

Expand Down Expand Up @@ -142,113 +134,6 @@ function genSpoofHeaders(referer = null, destOrigin) {
};
}

// XREF: https://socialsisteryi.github.io/bilibili-API-collect/docs/misc/sign/wbi.html#javascript

/**
* @param {import("fetch-cookie").FetchCookieImpl} fetchCookie
*/
// FIXME: We'd want to cache this somehow
async function getWbiKeys(fetchCookie, referer) {
const response = await fetchCookie(
"https://api.bilibili.com/x/web-interface/nav",
{
headers: genSpoofHeaders(referer, "api.bilibili.com"),
referrerPolicy: "strict-origin-when-cross-origin",
},
);

let responseDataRaw;
try {
responseDataRaw = await response.text();
} catch (e) {
e.message =
`请求 WBI 签名口令失败。(HTTP 状态码为 ${response.status})` +
"\n" +
e.message;
throw e;
}

let responseData = {};
try {
responseData = JSON.parse(responseDataRaw);
} catch (_) {
// pass
}
if (
!response.ok ||
(responseData.code && ![0, -101].includes(responseData.code))
) {
throw errorFromBilibili(
new Error(
`请求 WBI 签名口令失败。(HTTP 状态码为 ${response.status})` +
"\n" +
responseDataRaw,
),
responseData,
);
}

function getKeyFromFakeBfsPath(path) {
// Future-proof
let paramLoc = path.indexOf("@");
path = path.slice(
path.lastIndexOf("/") + 1,
paramLoc >= 0 ? paramLoc : path.length,
);
path = path.slice(0, path.lastIndexOf("."));
assert(path.length >= 32);
return path;
}

return {
img: getKeyFromFakeBfsPath(
new URL(responseData.data.wbi_img.img_url).pathname,
),
sub: getKeyFromFakeBfsPath(
new URL(responseData.data.wbi_img.sub_url).pathname,
),
};
}

const WBI_MIXIN_KEY_SHUFFLE_ORDER = [
46, 47, 18, 2, 53, 8, 23, 32, 15, 50, 10, 31, 58, 3, 45, 35, 27, 43, 5, 49,
33, 9, 42, 19, 29, 28, 14, 39, 12, 38, 41, 13, 37, 48, 7, 16, 24, 55, 40,
61, 26, 17, 0, 1, 60, 51, 30, 4, 22, 25, 54, 21, 56, 59, 6, 63, 57, 62, 11,
36, 20, 34, 44, 52,
];

const WBI_SIGN_CHAR_FILTER_REGEX = /[!'()*]/g;

/**
* @param {URL} url
*/
function wbiSignURLSearchParams(url, img, sub) {
const fullKey = img + sub;
const mixinKey = WBI_MIXIN_KEY_SHUFFLE_ORDER.map((i) => fullKey.charAt(i))
.join("")
.slice(0, 32);
const timestamp = Math.round(Date.now() / 1000);

url.searchParams.set("wts", timestamp);
url.searchParams.sort();
url.searchParams.forEach((value, key) => {
url.searchParams.set(
key,
value.replace(WBI_SIGN_CHAR_FILTER_REGEX, ""),
);
});
let query = "?" + url.searchParams.toString();

const signature = crypto
.createHash("md5")
.update(query + mixinKey)
.digest("hex");
query += `&w_rid=${signature}`;

url.search = query;
return url;
}

/**
* @param {import("fetch-cookie").FetchCookieImpl} fetchCookie
*/
Expand Down Expand Up @@ -309,8 +194,8 @@ export async function getRequestedInfo(path, search) {
id: null,
page: null,
searchParams: {
videoPage: {},
embedPlayer: {},
videoPage: new URLSearchParams(),
embedPlayer: new URLSearchParams(),
},
};

Expand Down Expand Up @@ -346,7 +231,7 @@ export async function getRequestedInfo(path, search) {

ret.id = getVideoIdByPath(url.pathname);
assert(ret.id, "无法从 URL 中提取视频 ID");
ret.page = parseInt(search.get("p") ?? "1") ?? 1;
ret.page = parseIntSafe(search.get("p")) ?? 1;

const fakeReferer = makeVideoPage(
ret.id,
Expand All @@ -370,14 +255,15 @@ export async function getRequestedInfo(path, search) {

// web / b23.tv
let startProgress =
parseInt(search.get("t")) || parseInt(search.get("start_progress"));
parseIntSafe(search.get("t")) ??
parseIntSafe(search.get("start_progress"));
if (startProgress) {
ret.searchParams.videoPage["t"] = startProgress;
ret.searchParams.videoPage.set("t", startProgress);
// this has to be t for the embed player, start_progress will not work
ret.searchParams.embedPlayer["t"] = startProgress;
ret.searchParams.embedPlayer.set("t", startProgress);
}

ret.wbiKeys = await getWbiKeys(fetchCookie, fakeReferer);
ret.wbiKeys = await wbiGetKeys(fetchCookie, fakeReferer);
// TODO: buvid4; bili_ticket (for space). Doesn't seem so important right now

return ret;
Expand Down Expand Up @@ -420,7 +306,11 @@ export async function getVideoData(info, getVideoURL, dropCobaltErrs) {
const resInfo = res.data;

page = page <= resInfo.pages.length ? page : 1;
videoPageURL = makeVideoPage(resInfo.id, page, info.searchParams.videoPage);
videoPageURL = makeVideoPage(
resInfo.bvid,
page,
info.searchParams.videoPage,
);
let cid = resInfo.pages[page - 1].cid ?? resInfo.cid;
let title = resInfo.title;
// TODO
Expand Down
Loading

0 comments on commit da80d24

Please sign in to comment.