@hyrious/blivec
Advanced tools
Comparing version 0.2.0 to 0.2.1
@@ -101,4 +101,4 @@ "use strict"; | ||
} | ||
_on_error(err2) { | ||
(this.events.error || noop)(err2); | ||
_on_error(err) { | ||
(this.events.error || noop)(err); | ||
} | ||
@@ -206,30 +206,66 @@ _on_data(buffer) { | ||
} | ||
export const FAIL = -1; | ||
export const ENOENT = -60004; | ||
async function getRealRoomId(id) { | ||
const v1 = "https://api.live.bilibili.com/room/v1/room/room_init"; | ||
const ret = await get(`${v1}?id=${id}`); | ||
const { code, data } = JSON.parse(ret); | ||
if (code == 60004) | ||
return ENOENT; | ||
if (code == 0 && data.live_status == 1) | ||
return data.room_id; | ||
return FAIL; | ||
} | ||
function ok(data) { | ||
return { ok: true, data }; | ||
} | ||
function err(reason) { | ||
return { ok: false, reason }; | ||
} | ||
const api_index_v2 = "https://api.live.bilibili.com/xlive/web-room/v2/index"; | ||
export async function getRoomPlayInfo(id) { | ||
const roomId = await getRealRoomId(id); | ||
if (roomId < 0) | ||
return err(roomId === ENOENT ? "not found such room" : "failed"); | ||
const url = `${api_index_v2}/getRoomPlayInfo?room_id=${roomId}&platform=web&protocol=0,1&format=0,1,2&codec=0,1&ptype=8&dolby=5`; | ||
const { code, message, data } = JSON.parse(await get(url)); | ||
let code, message, data; | ||
const room_v1 = "https://api.live.bilibili.com/room/v1/room"; | ||
const room_init = `${room_v1}/room_init`; | ||
({ code, message, data } = JSON.parse(await get(`${room_init}?id=${id}`))); | ||
if (code != 0) | ||
return err(message); | ||
return ok(data); | ||
throw new Error(message); | ||
const { uid, room_id, live_status, is_locked, encrypted } = data; | ||
if (is_locked) | ||
throw new Error("room is locked"); | ||
if (encrypted) | ||
throw new Error("room is encrypted"); | ||
if (live_status !== 1) | ||
throw new Error("room is offline"); | ||
const status = `${room_v1}/get_status_info_by_uids`; | ||
({ code, message, data } = JSON.parse(await get(`${status}?uids[]=${uid}`))); | ||
if (code != 0) | ||
throw new Error(message); | ||
const title = data[uid].title + " - " + data[uid].uname; | ||
const api_index_v2 = "https://api.live.bilibili.com/xlive/web-room/v2/index"; | ||
const streams = {}; | ||
const queue_of_qn = [1]; | ||
const visited = /* @__PURE__ */ new Set(); | ||
while (queue_of_qn.length > 0) { | ||
const qn = queue_of_qn.shift(); | ||
if (visited.has(qn)) | ||
continue; | ||
visited.add(qn); | ||
const url = `${api_index_v2}/getRoomPlayInfo?room_id=${room_id}&qn=${qn}&platform=web&protocol=0,1&format=0,1,2&codec=0,1&ptype=8&dolby=5`; | ||
({ code, message, data } = JSON.parse(await get(url))); | ||
if (code != 0) | ||
throw new Error(message); | ||
const { g_qn_desc, stream } = data.playurl_info.playurl; | ||
const qn_desc = Object.fromEntries(g_qn_desc.map((e) => [e.qn, e.desc])); | ||
let desc, container; | ||
for (const { protocol_name, format } of stream) { | ||
for (const { format_name, codec } of format) { | ||
for (const e of codec) { | ||
queue_of_qn.push(...e.accept_qn); | ||
desc = qn_desc[e.current_qn]; | ||
if (protocol_name.includes("http_hls")) { | ||
container = "m3u8"; | ||
desc += "-hls"; | ||
} else { | ||
container = format_name; | ||
} | ||
if (e.codec_name === "hevc") { | ||
desc += "-h265"; | ||
} | ||
const { host, extra } = sample(e.url_info); | ||
streams[desc] = { | ||
container, | ||
url: host + e.base_url + extra, | ||
qn, | ||
desc: qn_desc[e.current_qn] | ||
}; | ||
} | ||
} | ||
} | ||
} | ||
function sample(arr) { | ||
return arr[Math.floor(Math.random() * arr.length)]; | ||
} | ||
return { title, streams }; | ||
} |
@@ -6,7 +6,7 @@ #!/usr/bin/env node | ||
import { Connection, getRoomPlayInfo, sendDanmaku } from "./index.js"; | ||
const [arg1, arg2] = process.argv.slice(2); | ||
const [arg1, arg2, ...rest] = process.argv.slice(2); | ||
if (arg1 === "get") { | ||
const id = Number.parseInt(arg2); | ||
if (Number.isSafeInteger(id) && id > 0) { | ||
get(id); | ||
get(id, ...rest); | ||
} else { | ||
@@ -24,5 +24,5 @@ help(); | ||
function help() { | ||
console.log("Usage: bl <room_id> # listen danmaku"); | ||
console.log(" bl <room_id> <message> # send danmaku"); | ||
console.log(" bl get <room_id> # get stream url"); | ||
console.log("Usage: bl <room_id> # listen danmaku"); | ||
console.log(" bl <room_id> <message> # send danmaku"); | ||
console.log(" bl get <room_id> [--play] # get stream url"); | ||
process.exit(0); | ||
@@ -88,15 +88,22 @@ } | ||
} | ||
async function get(id) { | ||
const info = await getRoomPlayInfo(id); | ||
if (!info.ok) { | ||
console.log("Error:", info.reason); | ||
async function get(id, ...args) { | ||
let title; | ||
let urls = []; | ||
try { | ||
const info = await getRoomPlayInfo(id); | ||
title = info.title; | ||
console.log(title); | ||
console.log(); | ||
for (const name in info.streams) { | ||
const stream = info.streams[name]; | ||
urls.push([stream.qn, stream.url]); | ||
console.log(` ${name}: ${stream.url}`); | ||
console.log(); | ||
} | ||
} catch (err) { | ||
console.log("Error:", err.message); | ||
process.exit(1); | ||
} | ||
const platurl = info.data.playurl_info.playurl; | ||
const codec = platurl.stream[0].format[0].codec[0]; | ||
const { base_url, url_info } = codec; | ||
const { host, extra } = url_info[Math.random() * url_info.length | 0]; | ||
const url = host + base_url + extra; | ||
console.log(url); | ||
if (process.env.BLIVC_FFPLAY) { | ||
if (args.includes("--play")) { | ||
const url = args.includes("--max") ? urls.reduce((a, b) => a[0] > b[0] ? a : b)[1] : urls[0][1]; | ||
const headers = [ | ||
@@ -106,5 +113,14 @@ "User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:60.1) Gecko/20100101 Firefox/60.1\r\n", | ||
]; | ||
const args = [url, "-headers", headers.join(""), "-window_title", "a.flv"]; | ||
cp.spawnSync("ffplay", args, { stdio: "inherit" }); | ||
const cmds = [ | ||
"-hide_banner", | ||
"-loglevel", | ||
"error", | ||
url, | ||
"-headers", | ||
headers.join(""), | ||
"-window_title", | ||
title | ||
]; | ||
cp.spawnSync("ffplay", cmds, { stdio: "inherit" }); | ||
} | ||
} |
@@ -101,4 +101,4 @@ "use strict"; | ||
} | ||
_on_error(err2) { | ||
(this.events.error || noop)(err2); | ||
_on_error(err) { | ||
(this.events.error || noop)(err); | ||
} | ||
@@ -206,30 +206,66 @@ _on_data(buffer) { | ||
} | ||
export const FAIL = -1; | ||
export const ENOENT = -60004; | ||
async function getRealRoomId(id) { | ||
const v1 = "https://api.live.bilibili.com/room/v1/room/room_init"; | ||
const ret = await get(`${v1}?id=${id}`); | ||
const { code, data } = JSON.parse(ret); | ||
if (code == 60004) | ||
return ENOENT; | ||
if (code == 0 && data.live_status == 1) | ||
return data.room_id; | ||
return FAIL; | ||
} | ||
function ok(data) { | ||
return { ok: true, data }; | ||
} | ||
function err(reason) { | ||
return { ok: false, reason }; | ||
} | ||
const api_index_v2 = "https://api.live.bilibili.com/xlive/web-room/v2/index"; | ||
export async function getRoomPlayInfo(id) { | ||
const roomId = await getRealRoomId(id); | ||
if (roomId < 0) | ||
return err(roomId === ENOENT ? "not found such room" : "failed"); | ||
const url = `${api_index_v2}/getRoomPlayInfo?room_id=${roomId}&platform=web&protocol=0,1&format=0,1,2&codec=0,1&ptype=8&dolby=5`; | ||
const { code, message, data } = JSON.parse(await get(url)); | ||
let code, message, data; | ||
const room_v1 = "https://api.live.bilibili.com/room/v1/room"; | ||
const room_init = `${room_v1}/room_init`; | ||
({ code, message, data } = JSON.parse(await get(`${room_init}?id=${id}`))); | ||
if (code != 0) | ||
return err(message); | ||
return ok(data); | ||
throw new Error(message); | ||
const { uid, room_id, live_status, is_locked, encrypted } = data; | ||
if (is_locked) | ||
throw new Error("room is locked"); | ||
if (encrypted) | ||
throw new Error("room is encrypted"); | ||
if (live_status !== 1) | ||
throw new Error("room is offline"); | ||
const status = `${room_v1}/get_status_info_by_uids`; | ||
({ code, message, data } = JSON.parse(await get(`${status}?uids[]=${uid}`))); | ||
if (code != 0) | ||
throw new Error(message); | ||
const title = data[uid].title + " - " + data[uid].uname; | ||
const api_index_v2 = "https://api.live.bilibili.com/xlive/web-room/v2/index"; | ||
const streams = {}; | ||
const queue_of_qn = [1]; | ||
const visited = /* @__PURE__ */ new Set(); | ||
while (queue_of_qn.length > 0) { | ||
const qn = queue_of_qn.shift(); | ||
if (visited.has(qn)) | ||
continue; | ||
visited.add(qn); | ||
const url = `${api_index_v2}/getRoomPlayInfo?room_id=${room_id}&qn=${qn}&platform=web&protocol=0,1&format=0,1,2&codec=0,1&ptype=8&dolby=5`; | ||
({ code, message, data } = JSON.parse(await get(url))); | ||
if (code != 0) | ||
throw new Error(message); | ||
const { g_qn_desc, stream } = data.playurl_info.playurl; | ||
const qn_desc = Object.fromEntries(g_qn_desc.map((e) => [e.qn, e.desc])); | ||
let desc, container; | ||
for (const { protocol_name, format } of stream) { | ||
for (const { format_name, codec } of format) { | ||
for (const e of codec) { | ||
queue_of_qn.push(...e.accept_qn); | ||
desc = qn_desc[e.current_qn]; | ||
if (protocol_name.includes("http_hls")) { | ||
container = "m3u8"; | ||
desc += "-hls"; | ||
} else { | ||
container = format_name; | ||
} | ||
if (e.codec_name === "hevc") { | ||
desc += "-h265"; | ||
} | ||
const { host, extra } = sample(e.url_info); | ||
streams[desc] = { | ||
container, | ||
url: host + e.base_url + extra, | ||
qn, | ||
desc: qn_desc[e.current_qn] | ||
}; | ||
} | ||
} | ||
} | ||
} | ||
function sample(arr) { | ||
return arr[Math.floor(Math.random() * arr.length)]; | ||
} | ||
return { title, streams }; | ||
} |
{ | ||
"name": "@hyrious/blivec", | ||
"version": "0.2.0", | ||
"version": "0.2.1", | ||
"description": "bilibili live cli", | ||
@@ -5,0 +5,0 @@ "type": "module", |
@@ -6,3 +6,3 @@ #!/usr/bin/env node | ||
const [arg1, arg2] = process.argv.slice(2); | ||
const [arg1, arg2, ...rest] = process.argv.slice(2); | ||
@@ -12,3 +12,3 @@ if (arg1 === "get") { | ||
if (Number.isSafeInteger(id) && id > 0) { | ||
get(id); | ||
get(id, ...rest); | ||
} else { | ||
@@ -27,5 +27,5 @@ help(); | ||
function help() { | ||
console.log("Usage: bl <room_id> # listen danmaku"); | ||
console.log(" bl <room_id> <message> # send danmaku"); | ||
console.log(" bl get <room_id> # get stream url"); | ||
console.log("Usage: bl <room_id> # listen danmaku"); | ||
console.log(" bl <room_id> <message> # send danmaku"); | ||
console.log(" bl get <room_id> [--play] # get stream url"); | ||
process.exit(0); | ||
@@ -101,18 +101,26 @@ } | ||
async function get(id: number) { | ||
const info = await getRoomPlayInfo(id); | ||
if (!info.ok) { | ||
console.log("Error:", info.reason); | ||
async function get(id: number, ...args: string[]) { | ||
let title: string; | ||
let urls: [qn: number, url: string][] = []; | ||
try { | ||
const info = await getRoomPlayInfo(id); | ||
title = info.title; | ||
console.log(title); | ||
console.log(); | ||
for (const name in info.streams) { | ||
const stream = info.streams[name]; | ||
urls.push([stream.qn, stream.url]); | ||
console.log(` ${name}: ${stream.url}`); | ||
console.log(); | ||
} | ||
} catch (err: any) { | ||
console.log("Error:", err.message); | ||
process.exit(1); | ||
} | ||
const platurl = info.data.playurl_info.playurl; | ||
const codec = platurl.stream[0].format[0].codec[0]; | ||
const { base_url, url_info } = codec; | ||
const { host, extra } = url_info[(Math.random() * url_info.length) | 0]; | ||
const url = host + base_url + extra; | ||
console.log(url); | ||
if (process.env.BLIVC_FFPLAY) { | ||
if (args.includes("--play")) { | ||
const url = args.includes("--max") | ||
? urls.reduce((a, b) => (a[0] > b[0] ? a : b))[1] | ||
: urls[0][1]; | ||
const headers = [ | ||
@@ -122,5 +130,14 @@ "User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:60.1) Gecko/20100101 Firefox/60.1\r\n", | ||
]; | ||
const args = [url, "-headers", headers.join(""), "-window_title", "a.flv"]; | ||
cp.spawnSync("ffplay", args, { stdio: "inherit" }); | ||
const cmds = [ | ||
"-hide_banner", | ||
"-loglevel", | ||
"error", | ||
url, | ||
"-headers", | ||
headers.join(""), | ||
"-window_title", | ||
title, | ||
]; | ||
cp.spawnSync("ffplay", cmds, { stdio: "inherit" }); | ||
} | ||
} |
123
src/index.ts
@@ -277,38 +277,3 @@ import https from "https"; | ||
export const FAIL = -1; | ||
export const ENOENT = -60004; | ||
async function getRealRoomId(id: number) { | ||
const v1 = "https://api.live.bilibili.com/room/v1/room/room_init"; | ||
const ret = await get(`${v1}?id=${id}`); | ||
const { code, data } = JSON.parse(ret) as { | ||
code: number | string; | ||
data: Pick<RoomInfo, "room_id" | "live_status">; | ||
}; | ||
if (code == 60004) return ENOENT; | ||
if (code == 0 && data.live_status == 1) return data.room_id; | ||
return FAIL; | ||
} | ||
interface Ok<T> { | ||
ok: true; | ||
data: T; | ||
} | ||
function ok<T>(data: T): Ok<T> { | ||
return { ok: true, data }; | ||
} | ||
interface Err { | ||
ok: false; | ||
reason: string; | ||
} | ||
function err(reason: string): Err { | ||
return { ok: false, reason }; | ||
} | ||
const api_index_v2 = "https://api.live.bilibili.com/xlive/web-room/v2/index"; | ||
export interface RoomPlayInfo { | ||
interface PlayUrlInfo { | ||
playurl_info: { | ||
@@ -319,2 +284,3 @@ playurl: { | ||
stream: Array<{ | ||
protocol_name: string; | ||
format: Array<{ | ||
@@ -339,17 +305,78 @@ format_name: string; | ||
export async function getRoomPlayInfo( | ||
id: number | ||
): Promise<Ok<RoomPlayInfo> | Err> { | ||
const roomId = await getRealRoomId(id); | ||
if (roomId < 0) | ||
return err(roomId === ENOENT ? "not found such room" : "failed"); | ||
interface PlayInfo { | ||
container: string; | ||
url: string; | ||
qn: number; | ||
desc: string; | ||
} | ||
const url = | ||
`${api_index_v2}/getRoomPlayInfo?room_id=${roomId}&` + | ||
`platform=web&protocol=0,1&format=0,1,2&codec=0,1&ptype=8&dolby=5`; | ||
export async function getRoomPlayInfo(id: number) { | ||
let code: string | number, message: string, data: any; | ||
const { code, message, data } = JSON.parse(await get(url)); | ||
if (code != 0) return err(message); | ||
const room_v1 = "https://api.live.bilibili.com/room/v1/room"; | ||
const room_init = `${room_v1}/room_init`; | ||
({ code, message, data } = JSON.parse(await get(`${room_init}?id=${id}`))); | ||
if (code != 0) throw new Error(message); | ||
return ok(data); | ||
const { uid, room_id, live_status, is_locked, encrypted } = data; | ||
if (is_locked) throw new Error("room is locked"); | ||
if (encrypted) throw new Error("room is encrypted"); | ||
if (live_status !== 1) throw new Error("room is offline"); | ||
const status = `${room_v1}/get_status_info_by_uids`; | ||
({ code, message, data } = JSON.parse(await get(`${status}?uids[]=${uid}`))); | ||
if (code != 0) throw new Error(message); | ||
const title = data[uid].title + " - " + data[uid].uname; | ||
const api_index_v2 = "https://api.live.bilibili.com/xlive/web-room/v2/index"; | ||
const streams: Record<string, PlayInfo> = {}; | ||
const queue_of_qn = [1]; | ||
const visited = new Set<number>(); | ||
while (queue_of_qn.length > 0) { | ||
const qn = queue_of_qn.shift()!; | ||
if (visited.has(qn)) continue; | ||
visited.add(qn); | ||
const url = | ||
`${api_index_v2}/getRoomPlayInfo?room_id=${room_id}&qn=${qn}` + | ||
`&platform=web&protocol=0,1&format=0,1,2&codec=0,1&ptype=8&dolby=5`; | ||
({ code, message, data } = JSON.parse(await get(url))); | ||
if (code != 0) throw new Error(message); | ||
const { g_qn_desc, stream } = (data as PlayUrlInfo).playurl_info.playurl; | ||
const qn_desc = Object.fromEntries(g_qn_desc.map((e) => [e.qn, e.desc])); | ||
let desc: string, container: string; | ||
for (const { protocol_name, format } of stream) { | ||
for (const { format_name, codec } of format) { | ||
for (const e of codec) { | ||
queue_of_qn.push(...e.accept_qn); | ||
desc = qn_desc[e.current_qn]; | ||
if (protocol_name.includes("http_hls")) { | ||
container = "m3u8"; | ||
desc += "-hls"; | ||
} else { | ||
container = format_name; | ||
} | ||
if (e.codec_name === "hevc") { | ||
desc += "-h265"; | ||
} | ||
const { host, extra } = sample(e.url_info); | ||
streams[desc] = { | ||
container, | ||
url: host + e.base_url + extra, | ||
qn, | ||
desc: qn_desc[e.current_qn], | ||
}; | ||
} | ||
} | ||
} | ||
} | ||
function sample<T>(arr: T[]): T { | ||
return arr[Math.floor(Math.random() * arr.length)]; | ||
} | ||
return { title, streams }; | ||
} |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
38430
1105
2