diff --git a/src/APIServer.ts b/src/APIServer.ts index e39f0e7..e2fa380 100644 --- a/src/APIServer.ts +++ b/src/APIServer.ts @@ -1,15 +1,15 @@ -import { EventEmitter, WebSocketClient, WebSocketServer } from "./Deps.ts"; +import { EventEmitter, WebSocketClient, WebSocketServer } from "./Deps.ts" import { sendDanmaku } from './SendDanmaku.ts' -import config from "./Config.ts"; +import config from "./Config.ts" interface Message { - cmd: string; + cmd: string // deno-lint-ignore no-explicit-any - data: any; + data: any } -const server: WebSocketServer = new WebSocketServer(config.api.port); -const authedClientSet: Set = new Set(); +const server: WebSocketServer = new WebSocketServer(config.api.port) +const authedClientSet: Set = new Set() class APIMsgHandler extends EventEmitter { emit( @@ -19,41 +19,41 @@ class APIMsgHandler extends EventEmitter { ): boolean { if (eventName === "AUTH") { if (args[0] === config.api.token) { - authedClientSet.add(socket); - socket.send(JSON.stringify({ cmd: "AUTH", data: "AUTHED" })); - return true; + authedClientSet.add(socket) + socket.send(JSON.stringify({ cmd: "AUTH", data: "AUTHED" })) + return true } else { - socket.send(JSON.stringify({ cmd: "AUTH", data: "FAILED" })); - return true; + socket.send(JSON.stringify({ cmd: "AUTH", data: "FAILED" })) + return true } } if (authedClientSet.has(socket)) { - return super.emit.apply(this, [eventName, socket, args]); + return super.emit.apply(this, [eventName, socket, args]) } - return false; + return false } } -const serverEventEmitter = new APIMsgHandler(); +const serverEventEmitter = new APIMsgHandler() serverEventEmitter.on("SEND", (_socket: WebSocketClient, data: string) => { sendDanmaku({ msg: data, - }); -}); + }) +}) serverEventEmitter.on("ROOMID", (socket: WebSocket) => { socket.send(JSON.stringify({ cmd: "ROOMID", data: config.room_id, - })); -}); + })) +}) server.on("connection", (client: WebSocketClient) => { client.on("message", (data: string) => { - const msg: Message = JSON.parse(data); - serverEventEmitter.emit(msg.cmd, client, msg.data); - }); -}); + const msg: Message = JSON.parse(data) + serverEventEmitter.emit(msg.cmd, client, msg.data) + }) +}) -export { server }; +export { server } diff --git a/src/Config.ts b/src/Config.ts index 1b48baa..1763acd 100644 --- a/src/Config.ts +++ b/src/Config.ts @@ -28,9 +28,12 @@ interface ConfigStruct { advertiseing_cold_down: number api: api_config free_gift_action: boolean + disable_gift_action: boolean + disable_super_chat_action: boolean + disable_graud_action: boolean } const decoder = new TextDecoder('utf-8') -const config: ConfigStruct = JSON.parse(decoder.decode(Deno.readFileSync(`config/${Deno.args[0]}`))); +const config: ConfigStruct = JSON.parse(decoder.decode(Deno.readFileSync(`config/${Deno.args[0]}`))) -export default config; +export default config diff --git a/src/DanmakuReceiver.ts b/src/DanmakuReceiver.ts index d4c5a9a..c1aab38 100644 --- a/src/DanmakuReceiver.ts +++ b/src/DanmakuReceiver.ts @@ -1,6 +1,6 @@ -import { brotli, EventEmitter, WebSocketClient } from "./Deps.ts"; -import config from "./Config.ts"; -import { printLog } from "./utils/mod.ts"; +import { brotli, EventEmitter, WebSocketClient } from "./Deps.ts" +import config from "./Config.ts" +import { printLog } from "./utils/mod.ts" import { server as apiServer } from './APIServer.ts' enum DANMAKU_PROTOCOL { @@ -19,16 +19,16 @@ enum DANMAKU_TYPE { } const cookie = - `buvid3=${config.verify.buvid3};SESSDATA=${config.verify.sessdata};bili_jct=${config.verify.csrf}`; -const encoder = new TextEncoder(); -const decoder = new TextDecoder("utf-8"); + `buvid3=${config.verify.buvid3}SESSDATA=${config.verify.sessdata}bili_jct=${config.verify.csrf}` +const encoder = new TextEncoder() +const decoder = new TextDecoder("utf-8") export class DanmakuReceiver extends EventEmitter { - private roomId: number; - private ws: WebSocket | null = null; + private roomId: number + private ws: WebSocket | null = null constructor(roomId: number) { - super(); - this.roomId = roomId; + super() + this.roomId = roomId } public async connect() { const roomConfig = await (await fetch( @@ -41,12 +41,12 @@ export class DanmakuReceiver extends EventEmitter { host: "api.live.bilibili.com", }, }, - )).json(); + )).json() this.ws = new WebSocket( `wss://${roomConfig.data.host_server_list[0].host}:${ roomConfig.data.host_server_list[0].wss_port }/sub`, - ); + ) this.ws.onopen = () => { const payload = JSON.stringify({ roomid: this.roomId, @@ -54,87 +54,87 @@ export class DanmakuReceiver extends EventEmitter { platform: "web", uid: config.verify.uid, key: roomConfig.data.token, - }); + }) this.ws!.send(this.generatePacket( 1, 7, payload, - )); - this.ws!.onmessage = this.danmakuProcesser.bind(this); - }; + )) + this.ws!.onmessage = this.danmakuProcesser.bind(this) + } this.ws.onclose = () => { - this.emit("closed"); - }; + this.emit("closed") + } } private generatePacket( protocol: number, type: number, payload: string, ): ArrayBuffer { - const payloadEncoded = encoder.encode(payload); - const packetLength = 16 + payloadEncoded.length; - const packet = new ArrayBuffer(packetLength); - const packetArray = new Uint8Array(packet); - const packetView = new DataView(packet); - packetView.setInt32(0, packetLength); // 总长度 - packetView.setInt16(4, 16); // 头长度 - packetView.setUint16(6, protocol); // 协议类型 - packetView.setUint32(8, type); // 包类型 - packetView.setUint32(12, 1); // 一个常数 - packetArray.set(payloadEncoded, 16); //写入负载 - return packet; + const payloadEncoded = encoder.encode(payload) + const packetLength = 16 + payloadEncoded.length + const packet = new ArrayBuffer(packetLength) + const packetArray = new Uint8Array(packet) + const packetView = new DataView(packet) + packetView.setInt32(0, packetLength) // 总长度 + packetView.setInt16(4, 16) // 头长度 + packetView.setUint16(6, protocol) // 协议类型 + packetView.setUint32(8, type) // 包类型 + packetView.setUint32(12, 1) // 一个常数 + packetArray.set(payloadEncoded, 16) //写入负载 + return packet } private async danmakuProcesser(ev: MessageEvent) { // 弹幕事件处理 const msgPacket = await ev.data.arrayBuffer() - const msgArray = new Uint8Array(msgPacket); - const msg = new DataView(msgPacket); - const packetProtocol = msg.getInt16(6); - const packetType = msg.getInt32(8); - const packetPayload: Uint8Array = msgArray.slice(16); - let jsonData; + const msgArray = new Uint8Array(msgPacket) + const msg = new DataView(msgPacket) + const packetProtocol = msg.getInt16(6) + const packetType = msg.getInt32(8) + const packetPayload: Uint8Array = msgArray.slice(16) + let jsonData switch (packetType) { case DANMAKU_TYPE.HEARTBEAT_REPLY: // 心跳包,不做处理 - break; + break case DANMAKU_TYPE.AUTH_REPLY: - printLog('弹幕接收器' ,"通过认证"); + printLog('弹幕接收器' ,"通过认证") // 认证通过,每30秒发一次心跳包 setInterval(() => { - const heartbeatPayload = "陈睿你妈死了"; + const heartbeatPayload = "陈睿你妈死了" if (this.ws) { - this.ws.send(this.generatePacket(1, 2, heartbeatPayload)); + this.ws.send(this.generatePacket(1, 2, heartbeatPayload)) } - }, 30000); - this.emit("connected"); - break; + }, 30000) + this.emit("connected") + break case DANMAKU_TYPE.DATA: switch (packetProtocol) { case DANMAKU_PROTOCOL.JSON: // 这些数据大都没用,但还是留着吧 - jsonData = JSON.parse(decoder.decode(packetPayload)); - this.emit(jsonData.cmd, jsonData.data); - break; + jsonData = JSON.parse(decoder.decode(packetPayload)) + this.emit(jsonData.cmd, jsonData.data) + break case DANMAKU_PROTOCOL.BROTLI: { - const resultRaw = brotli.decompress(packetPayload); - const result = new DataView(resultRaw.buffer); - let offset = 0; + const resultRaw = brotli.decompress(packetPayload) + const result = new DataView(resultRaw.buffer) + let offset = 0 while (offset < resultRaw.length) { - const length = result.getUint32(offset); - const packetData = resultRaw.slice(offset + 16, offset + length); - const data = JSON.parse(decoder.decode(packetData)); - const cmd = data.cmd.split(":")[0]; - this.emit(cmd, data.info || data.data); + const length = result.getUint32(offset) + const packetData = resultRaw.slice(offset + 16, offset + length) + const data = JSON.parse(decoder.decode(packetData)) + const cmd = data.cmd.split(":")[0] + this.emit(cmd, data.info || data.data) apiServer.clients.forEach((client: WebSocketClient) => { client.send(JSON.stringify({ cmd, data: data.info || data.data })) }) - offset += length; + offset += length } } } - break; + break default: - printLog('弹幕接收器', `未知的弹幕数据包种类 ${packetType}`); + printLog('弹幕接收器', `未知的弹幕数据包种类 ${packetType}`) } } } diff --git a/src/Deps.ts b/src/Deps.ts index 4802188..d6456d2 100644 --- a/src/Deps.ts +++ b/src/Deps.ts @@ -3,4 +3,4 @@ import * as events from 'https://deno.land/x/events@v1.0.0/mod.ts' const EventEmitter = events.default export { EventEmitter } export * as brotli from 'https://deno.land/x/brotli@v0.1.4/mod.ts' -export { WebSocketServer, type WebSocketClient } from 'https://deno.land/x/websocket@v0.1.4/mod.ts'; +export { WebSocketServer, type WebSocketClient } from 'https://deno.land/x/websocket@v0.1.4/mod.ts' diff --git a/src/Main.ts b/src/Main.ts index a6e6f21..266d157 100644 --- a/src/Main.ts +++ b/src/Main.ts @@ -1,7 +1,7 @@ -import config from './Config.ts'; -import { DanmakuReceiver } from './DanmakuReceiver.ts'; -import { onGraud, onLiveEnd, onLiveStart, onSuperChat, onTotalGift, receiveDanmaku, receiveGift } from './DanmakuCallbacks.ts'; -import { printLog } from './utils/mod.ts'; +import config from './Config.ts' +import { DanmakuReceiver } from './DanmakuReceiver.ts' +import { onGraud, onLiveEnd, onLiveStart, onSuperChat, onTotalGift, receiveDanmaku, receiveGift } from './DanmakuCallbacks.ts' +import { printLog } from './utils/mod.ts' async function launchAllPlugins() { const pluginsList = await Deno.readDir('./plugins') @@ -15,19 +15,26 @@ async function launchAllPlugins() { } } -const danmakuReceiver = new DanmakuReceiver(config.room_id); +const danmakuReceiver = new DanmakuReceiver(config.room_id) danmakuReceiver.on('connected', () => { - printLog('主程序', '连接成功'); -}); + printLog('主程序', '连接成功') +}) -danmakuReceiver.on('closed', () => danmakuReceiver.connect()); -danmakuReceiver.on('SEND_GIFT', receiveGift); -danmakuReceiver.on('LIVE', onLiveStart); -danmakuReceiver.on('PREPARING', onLiveEnd); -danmakuReceiver.on('DANMU_MSG', receiveDanmaku); -danmakuReceiver.on('COMBO_SEND', onTotalGift); -danmakuReceiver.on('GUARD_BUY', onGraud); -danmakuReceiver.on('SUPER_CHAT_MESSAGE', onSuperChat); +if (!config.disable_gift_action) { + danmakuReceiver.on('COMBO_SEND', onTotalGift) + danmakuReceiver.on('SEND_GIFT', receiveGift) +} +if (!config.disable_super_chat_action) { + danmakuReceiver.on('GUARD_BUY', onGraud) +} +if (!config.disable_super_chat_action) { + danmakuReceiver.on('SUPER_CHAT_MESSAGE', onSuperChat) +} + +danmakuReceiver.on('closed', () => danmakuReceiver.connect()) +danmakuReceiver.on('LIVE', onLiveStart) +danmakuReceiver.on('PREPARING', onLiveEnd) +danmakuReceiver.on('DANMU_MSG', receiveDanmaku) launchAllPlugins() -danmakuReceiver.connect(); +danmakuReceiver.connect() diff --git a/src/SendDanmaku.ts b/src/SendDanmaku.ts index 86aa8a5..ed279dd 100644 --- a/src/SendDanmaku.ts +++ b/src/SendDanmaku.ts @@ -1,50 +1,50 @@ -import config from "./Config.ts"; +import config from "./Config.ts" const cookie = - `buvid3=${config.verify.buvid3}; SESSDATA=${config.verify.sessdata}; bili_jct=${config.verify.csrf};`; + `buvid3=${config.verify.buvid3} SESSDATA=${config.verify.sessdata} bili_jct=${config.verify.csrf}` export interface DanmakuStruct { - color?: number; - bubble?: number; - msg: string; - mode?: number; - fontsize?: number; - rnd?: number; - roomid?: number; - csrf?: string; - csrf_token?: string; + color?: number + bubble?: number + msg: string + mode?: number + fontsize?: number + rnd?: number + roomid?: number + csrf?: string + csrf_token?: string } export function sendDanmaku(danmaku: DanmakuStruct) { if (danmaku.msg.length > 19) { sendDanmaku({ msg: danmaku.msg.slice(0, 15), - }); + }) setTimeout(() => { sendDanmaku({ msg: danmaku.msg.slice(15, danmaku.msg.length), - }); - }, 2000); - return; + }) + }, 2000) + return } - danmaku.rnd = new Date().getTime(); + danmaku.rnd = new Date().getTime() if (!danmaku.color) { - danmaku.color = 5816798; + danmaku.color = 5816798 } if (!danmaku.bubble) { - danmaku.bubble = 0; + danmaku.bubble = 0 } if (!danmaku.mode) { - danmaku.mode = 1; + danmaku.mode = 1 } if (!danmaku.fontsize) { - danmaku.fontsize = 24; + danmaku.fontsize = 24 } - danmaku.roomid = config.room_id as number; - danmaku.csrf = danmaku.csrf_token = config.verify.csrf; - const data = new FormData(); + danmaku.roomid = config.room_id as number + danmaku.csrf = danmaku.csrf_token = config.verify.csrf + const data = new FormData() for (const k in danmaku) { - data.append(k, danmaku[k as keyof DanmakuStruct]!.toString()); + data.append(k, danmaku[k as keyof DanmakuStruct]!.toString()) } fetch("https://api.live.bilibili.com/msg/send", { method: 'POST', @@ -52,9 +52,9 @@ export function sendDanmaku(danmaku: DanmakuStruct) { headers: { cookie: cookie, "user-agent": - "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.64 Safari/537.36", + "Mozilla/5.0 (X11 Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.64 Safari/537.36", host: "api.live.bilibili.com", "Referer": "https://live.bilibili.com", } - }); + }) } diff --git a/src/utils/FormatString.ts b/src/utils/FormatString.ts index e7c5431..b5fc92c 100644 --- a/src/utils/FormatString.ts +++ b/src/utils/FormatString.ts @@ -1,8 +1,8 @@ // deno-lint-ignore no-explicit-any export function FormatString(str: string, args: any): string { - let result: string = str; + let result: string = str for(const arg in args) { result = result.replace(new RegExp(`{${arg}}`, 'g'), args[arg]) } - return result; + return result } \ No newline at end of file