UNPKG

@bililive-tools/douyin-recorder

Version:
115 lines (114 loc) 4.12 kB
import axios from "axios"; import { assert } from "./utils.js"; const requester = axios.create({ timeout: 10e3, // axios 会自动读取环境变量中的 http_proxy 和 https_proxy 并应用,这会让请求发往代理的 host。 // 所以这里需要主动禁用代理功能。 proxy: false, }); let cookieCache; export const getCookie = async () => { const now = new Date().getTime(); // 缓存24小时 if (cookieCache?.startTimestamp && now - cookieCache.startTimestamp < 24 * 60 * 60 * 1000) { return cookieCache.cookies; } const res = await requester.get("https://live.douyin.com/"); if (!res.headers["set-cookie"]) { throw new Error("No cookie in response"); } const cookies = (res.headers["set-cookie"] ?? []) .map((cookie) => { return cookie.split(";")[0]; }) .join("; "); cookieCache = { startTimestamp: now, cookies, }; return cookies; }; export async function getRoomInfo(webRoomId, retryOnSpecialCode = true) { // 抖音的 'webcast/room/web/enter' api 会需要 ttwid 的 cookie,这个 cookie 是由这个请求的响应头设置的, // 所以在这里请求一次自动设置。 const cookies = await getCookie(); const res = await requester.get("https://live.douyin.com/webcast/room/web/enter/", { params: { aid: 6383, live_id: 1, device_platform: "web", language: "zh-CN", enter_from: "web_live", cookie_enabled: "true", screen_width: 1920, screen_height: 1080, browser_language: "zh-CN", browser_platform: "MacIntel", browser_name: "Chrome", browser_version: "108.0.0.0", web_rid: webRoomId, // enter_source:, "Room-Enter-User-Login-Ab": 0, is_need_double_stream: "false", }, headers: { cookie: cookies, }, }); // console.log(JSON.stringify(res.data, null, 2)); // 无 cookie 时 code 为 10037 if (res.data.status_code === 10037 && retryOnSpecialCode) { // resp 自动设置 cookie // const cookieRes = await requester.get("https://live.douyin.com/favicon.ico"); // const cookies = cookieRes.headers["set-cookie"] // .map((cookie) => { // return cookie.split(";")[0]; // }) // .join("; "); // console.log("cookies", cookies); return getRoomInfo(webRoomId, false); } assert(res.data.status_code === 0, `Unexpected resp, code ${res.data.status_code}, msg ${res.data.data}, id ${webRoomId}`); const data = res.data.data; const room = data.data[0]; assert(room, `No room data, id ${webRoomId}`); if (room?.stream_url == null) { return { living: false, roomId: webRoomId, owner: data.user.nickname, title: room?.title ?? data.user.nickname, streams: [], sources: [], avatar: data.user?.avatar_thumb?.url_list?.[0], cover: room.cover?.url_list?.[0], liveId: room.id_str, }; } const { options: { qualities }, stream_data, } = room.stream_url.live_core_sdk_data.pull_data; const streamData = JSON.parse(stream_data).data; const streams = qualities.map((info) => ({ desc: info.name, key: info.sdk_key, bitRate: info.v_bit_rate, })); // 看起来抖音是自动切换 cdn 的,所以这里固定返回一个默认的 source。 const sources = [ { name: "自动", streamMap: streamData, }, ]; return { living: data.room_status === 0, // 接口里不会再返回 web room id,只能直接用入参原路返回了。 roomId: webRoomId, owner: data.user.nickname, title: room.title, streams, sources, avatar: data.user.avatar_thumb.url_list[0], cover: room.cover?.url_list?.[0], liveId: room.id_str, }; }