Compare commits
10 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
c810083b74 | ||
|
be192c8585 | ||
|
c066a5a8c4 | ||
|
7a5475b93c | ||
|
f39c3b628f | ||
|
e70174f4a4 | ||
|
aa6e39ffe4 | ||
|
dcb617f43d | ||
|
7cf982baec | ||
|
186324bfb8 |
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,5 +1,7 @@
|
|||||||
config/*
|
config/*
|
||||||
log/*
|
log/*
|
||||||
|
plugins/*
|
||||||
|
!plugins/.gitkeep
|
||||||
!config/.gitkeep
|
!config/.gitkeep
|
||||||
!log/.gitkeep
|
!log/.gitkeep
|
||||||
dist
|
dist
|
||||||
|
0
plugins/.gitkeep
Normal file
0
plugins/.gitkeep
Normal file
@ -1,15 +1,15 @@
|
|||||||
import { EventEmitter, WebSocketClient, WebSocketServer } from "./deps.ts";
|
import { EventEmitter, WebSocketClient, WebSocketServer } from "./Deps.ts"
|
||||||
import { sendDanmaku } from './SendDanmaku.ts'
|
import { sendDanmaku } from './SendDanmaku.ts'
|
||||||
import config from "./config.ts";
|
import config from "./Config.ts"
|
||||||
|
|
||||||
interface Message {
|
interface Message {
|
||||||
cmd: string;
|
cmd: string
|
||||||
// deno-lint-ignore no-explicit-any
|
// deno-lint-ignore no-explicit-any
|
||||||
data: any;
|
data: any
|
||||||
}
|
}
|
||||||
|
|
||||||
const server = new WebSocketServer(config.api.port);
|
const server: WebSocketServer = new WebSocketServer(config.api.port)
|
||||||
const authedClientSet: Set<WebSocketClient> = new Set();
|
const authedClientSet: Set<WebSocketClient> = new Set()
|
||||||
|
|
||||||
class APIMsgHandler extends EventEmitter {
|
class APIMsgHandler extends EventEmitter {
|
||||||
emit(
|
emit(
|
||||||
@ -19,42 +19,38 @@ class APIMsgHandler extends EventEmitter {
|
|||||||
): boolean {
|
): boolean {
|
||||||
if (eventName === "AUTH") {
|
if (eventName === "AUTH") {
|
||||||
if (args[0] === config.api.token) {
|
if (args[0] === config.api.token) {
|
||||||
authedClientSet.add(socket);
|
authedClientSet.add(socket)
|
||||||
socket.send(JSON.stringify({ cmd: "AUTH", data: "AUTHED" }));
|
socket.send(JSON.stringify({ cmd: "AUTH", data: "AUTHED" }))
|
||||||
return true;
|
return true
|
||||||
} else {
|
} else {
|
||||||
socket.send(JSON.stringify({ cmd: "AUTH", data: "FAILED" }));
|
socket.send(JSON.stringify({ cmd: "AUTH", data: "FAILED" }))
|
||||||
return true;
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (authedClientSet.has(socket)) {
|
if (authedClientSet.has(socket)) {
|
||||||
super.emit.apply(this, [eventName, socket, args]);
|
return super.emit.apply(this, [eventName, socket, args])
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
return false;
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const serverEventEmitter = new APIMsgHandler();
|
const serverEventEmitter = new APIMsgHandler()
|
||||||
|
|
||||||
serverEventEmitter.on("SEND", (_socket: WebSocketClient, data: string) => {
|
serverEventEmitter.on("SEND", (_socket: WebSocketClient, dataJson: string) => {
|
||||||
sendDanmaku({
|
const data: {
|
||||||
msg: data,
|
msg: string
|
||||||
});
|
roomId: number
|
||||||
});
|
} = JSON.parse(dataJson)
|
||||||
|
sendDanmaku(data.roomId, {
|
||||||
serverEventEmitter.on("ROOMID", (socket: WebSocket) => {
|
msg: data.msg
|
||||||
socket.send(JSON.stringify({
|
})
|
||||||
cmd: "ROOMID",
|
})
|
||||||
data: config.room_id,
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
|
|
||||||
server.on("connection", (client: WebSocketClient) => {
|
server.on("connection", (client: WebSocketClient) => {
|
||||||
client.on("message", (data: string) => {
|
client.on("message", (data: string) => {
|
||||||
const msg: Message = JSON.parse(data);
|
const msg: Message = JSON.parse(data)
|
||||||
serverEventEmitter.emit(msg.cmd, client, msg.data);
|
serverEventEmitter.emit(msg.cmd, client, msg.data)
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
|
||||||
export { server };
|
export { server }
|
||||||
|
@ -21,15 +21,19 @@ interface api_config {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface ConfigStruct {
|
interface ConfigStruct {
|
||||||
room_id: number
|
room_id: Array<number>
|
||||||
verify: Credential
|
verify: Credential
|
||||||
danmakus: DanmakuTemplate
|
danmakus: DanmakuTemplate
|
||||||
cold_down_time: number
|
cold_down_time: number
|
||||||
advertiseing_cold_down: number
|
advertiseing_cold_down: number
|
||||||
api: api_config
|
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 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(Deno.args[0])))
|
||||||
|
|
||||||
export default config;
|
export default config
|
@ -1,6 +1,6 @@
|
|||||||
// deno-lint-ignore-file
|
// deno-lint-ignore-file
|
||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import config from './config.ts'
|
import config from './Config.ts'
|
||||||
import { sendDanmaku } from './SendDanmaku.ts'
|
import { sendDanmaku } from './SendDanmaku.ts'
|
||||||
import { Encoding } from './Text.ts'
|
import { Encoding } from './Text.ts'
|
||||||
import { getTimeString, FormatString } from './utils/mod.ts'
|
import { getTimeString, FormatString } from './utils/mod.ts'
|
||||||
@ -12,58 +12,60 @@ let logFile = Deno.openSync(`./log/${getTimeString()}-${config.room_id}.log`, {
|
|||||||
const thanksColdDownSet = new Set<string>()
|
const thanksColdDownSet = new Set<string>()
|
||||||
|
|
||||||
|
|
||||||
export function receiveGift(data: any) {
|
export function receiveGift(roomId: number, data: any) {
|
||||||
if(thanksColdDownSet.has(data.uname)){
|
if(thanksColdDownSet.has(data.uname)){
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
logFile.writeSync(Encoding.UTF8.getBytes(`${getTimeString()} ${data.uname} 投喂了${data.super_gift_num}个 ${data.giftName} 价值${data.price / 1000 * data.super_gift_num}元\n`))
|
logFile.writeSync(Encoding.UTF8.getBytes(`${getTimeString()} ${data.uname} 投喂了${data.super_gift_num}个 ${data.giftName} 价值${data.price / 1000 * data.super_gift_num}元\n`))
|
||||||
sendDanmaku({
|
if (config.free_gift_action || data.super_gift_num > 0) {
|
||||||
msg: FormatString(config.danmakus.gift, { name: data.uname, gift: data.giftName })
|
sendDanmaku(roomId, {
|
||||||
})
|
msg: FormatString(config.danmakus.gift, { name: data.uname, gift: data.giftName })
|
||||||
thanksColdDownSet.add(data.uname)
|
})
|
||||||
setTimeout(() => {thanksColdDownSet.delete(data.uname)}, config.cold_down_time)
|
thanksColdDownSet.add(data.uname)
|
||||||
|
setTimeout(() => { thanksColdDownSet.delete(data.uname) }, config.cold_down_time)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function onTotalGift(data: any) {
|
export function onTotalGift(roomId: number, data: any) {
|
||||||
logFile.writeSync(Encoding.UTF8.getBytes(`${getTimeString()} ${data.uname}投喂了${data.total_num}个${data.gift_name}\n`))
|
logFile.writeSync(Encoding.UTF8.getBytes(`${getTimeString()} ${data.uname}投喂了${data.total_num}个${data.gift_name}\n`))
|
||||||
sendDanmaku({
|
sendDanmaku(roomId, {
|
||||||
msg: FormatString(config.danmakus.gift_total, { name: data.uname, gift: data.gift_name, count: data.total_num })
|
msg: FormatString(config.danmakus.gift_total, { name: data.uname, gift: data.gift_name, count: data.total_num })
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function receiveDanmaku(data: any) {
|
export function receiveDanmaku(roomId:number, data: any) {
|
||||||
logFile.writeSync(Encoding.UTF8.getBytes(`${getTimeString()} ${data[2][1]}:${data[2][0]} ${data[1]}\n`))
|
logFile.writeSync(Encoding.UTF8.getBytes(`${getTimeString()} ${roomId} ${data[2][1]}:${data[2][0]} ${data[1]}\n`))
|
||||||
}
|
}
|
||||||
|
|
||||||
export function onLiveStart() {
|
export function onLiveStart(roomId: number) {
|
||||||
if(skipCount != 1){
|
if(skipCount != 1){
|
||||||
skipCount ++
|
skipCount ++
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
skipCount = 0
|
skipCount = 0
|
||||||
logFile.close()
|
logFile.close()
|
||||||
sendDanmaku({ msg: config.danmakus.live_start })
|
sendDanmaku(roomId, { msg: config.danmakus.live_start })
|
||||||
logFile = Deno.openSync(`./log/${getTimeString()}-${config.room_id}.log`, {
|
logFile = Deno.openSync(`./log/${getTimeString()}-${config.room_id}.log`, {
|
||||||
create: true
|
create: true
|
||||||
})
|
})
|
||||||
logFile.writeSync(Encoding.UTF8.getBytes(`${getTimeString()} 直播开始\n`))
|
logFile.writeSync(Encoding.UTF8.getBytes(`${getTimeString()} 直播开始\n`))
|
||||||
}
|
}
|
||||||
|
|
||||||
export function onLiveEnd() {
|
export function onLiveEnd(roomId: number) {
|
||||||
logFile.writeSync(Encoding.UTF8.getBytes(`${getTimeString()} 直播结束\n`))
|
logFile.writeSync(Encoding.UTF8.getBytes(`${getTimeString()} 直播结束\n`))
|
||||||
sendDanmaku({ msg: config.danmakus.live_end })
|
sendDanmaku(roomId, { msg: config.danmakus.live_end })
|
||||||
}
|
}
|
||||||
|
|
||||||
export function onGraud(data: any) {
|
export function onGraud(roomId: number, data: any) {
|
||||||
logFile.writeSync(Encoding.UTF8.getBytes(`${getTimeString()} ${data.username}:${data.uid} 购买了 ${data.gift_name}\n`))
|
logFile.writeSync(Encoding.UTF8.getBytes(`${getTimeString()} ${data.username}:${data.uid} 购买了 ${data.gift_name}\n`))
|
||||||
sendDanmaku({
|
sendDanmaku(roomId, {
|
||||||
msg: FormatString(config.danmakus.guard, { type: data.gift_name, name: data.username })
|
msg: FormatString(config.danmakus.guard, { type: data.gift_name, name: data.username })
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function onSuperChat(data: any) {
|
export function onSuperChat(roomId: number, data: any) {
|
||||||
logFile.writeSync(Encoding.UTF8.getBytes(`${getTimeString()} ${data.user_info.uname}发送了SC 价格${data.price}\n`))
|
logFile.writeSync(Encoding.UTF8.getBytes(`${getTimeString()} ${data.user_info.uname}发送了SC 价格${data.price}\n`))
|
||||||
sendDanmaku({
|
sendDanmaku(roomId, {
|
||||||
msg: FormatString(config.danmakus.sc, { name: data.user_info.uname })
|
msg: FormatString(config.danmakus.sc, { name: data.user_info.uname })
|
||||||
})
|
})
|
||||||
}
|
}
|
@ -1,6 +1,6 @@
|
|||||||
import { brotli, EventEmitter } from "./deps.ts";
|
import { brotli, EventEmitter, WebSocketClient } from "./Deps.ts"
|
||||||
import config from "./config.ts";
|
import config from "./Config.ts"
|
||||||
import { printLog } from "./utils/printLog.ts";
|
import { printLog } from "./utils/mod.ts"
|
||||||
import { server as apiServer } from './APIServer.ts'
|
import { server as apiServer } from './APIServer.ts'
|
||||||
|
|
||||||
enum DANMAKU_PROTOCOL {
|
enum DANMAKU_PROTOCOL {
|
||||||
@ -19,34 +19,36 @@ enum DANMAKU_TYPE {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const cookie =
|
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}`
|
||||||
const encoder = new TextEncoder();
|
const encoder = new TextEncoder()
|
||||||
const decoder = new TextDecoder("utf-8");
|
const decoder = new TextDecoder("utf-8")
|
||||||
|
|
||||||
export class DanmakuReceiver extends EventEmitter {
|
export class DanmakuReceiver extends EventEmitter {
|
||||||
private roomId: number;
|
private roomId: number
|
||||||
private ws: WebSocket | null = null;
|
private ws: WebSocket | null = null
|
||||||
constructor(roomId: number) {
|
constructor(roomId: number) {
|
||||||
super();
|
super()
|
||||||
this.roomId = roomId;
|
this.roomId = roomId
|
||||||
}
|
}
|
||||||
public async connect() {
|
public async connect() {
|
||||||
const roomConfig = await (await fetch(
|
const roomConfig = await (await fetch(
|
||||||
`https://api.live.bilibili.com/room/v1/Danmu/getConf?room_id=${this.roomId}&platform=pc&player=web`,
|
`https://api.live.bilibili.com/xlive/web-room/v1/index/getDanmuInfo?id=${this.roomId}&type=0`,
|
||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
cookie,
|
Cookie: cookie,
|
||||||
"user-agent":
|
"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",
|
Host: "api.live.bilibili.com",
|
||||||
|
Origin: 'https://live.bilibili.com',
|
||||||
|
Referer: `https://live.bilibili.com/${this.roomId}?broadcast_type=0`
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
)).json();
|
)).json()
|
||||||
this.ws = new WebSocket(
|
this.ws = new WebSocket(
|
||||||
`wss://${roomConfig.data.host_server_list[0].host}:${
|
`wss://${roomConfig.data.host_list[0].host}:${
|
||||||
roomConfig.data.host_server_list[0].wss_port
|
roomConfig.data.host_list[0].wss_port
|
||||||
}/sub`,
|
}/sub`,
|
||||||
);
|
)
|
||||||
this.ws.onopen = () => {
|
this.ws.onopen = () => {
|
||||||
const payload = JSON.stringify({
|
const payload = JSON.stringify({
|
||||||
roomid: this.roomId,
|
roomid: this.roomId,
|
||||||
@ -54,87 +56,88 @@ export class DanmakuReceiver extends EventEmitter {
|
|||||||
platform: "web",
|
platform: "web",
|
||||||
uid: config.verify.uid,
|
uid: config.verify.uid,
|
||||||
key: roomConfig.data.token,
|
key: roomConfig.data.token,
|
||||||
});
|
type: 2
|
||||||
|
})
|
||||||
this.ws!.send(this.generatePacket(
|
this.ws!.send(this.generatePacket(
|
||||||
1,
|
1,
|
||||||
7,
|
7,
|
||||||
payload,
|
payload,
|
||||||
));
|
))
|
||||||
this.ws!.onmessage = this.danmakuProcesser.bind(this);
|
this.ws!.onmessage = this.danmakuProcesser.bind(this)
|
||||||
};
|
}
|
||||||
this.ws.onclose = () => {
|
this.ws.onclose = () => {
|
||||||
this.emit("closed");
|
this.emit("closed")
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
private generatePacket(
|
private generatePacket(
|
||||||
protocol: number,
|
protocol: number,
|
||||||
type: number,
|
type: number,
|
||||||
payload: string,
|
payload: string,
|
||||||
): ArrayBuffer {
|
): ArrayBuffer {
|
||||||
const payloadEncoded = encoder.encode(payload);
|
const payloadEncoded = encoder.encode(payload)
|
||||||
const packetLength = 16 + payloadEncoded.length;
|
const packetLength = 16 + payloadEncoded.length
|
||||||
const packet = new ArrayBuffer(packetLength);
|
const packet = new ArrayBuffer(packetLength)
|
||||||
const packetArray = new Uint8Array(packet);
|
const packetArray = new Uint8Array(packet)
|
||||||
const packetView = new DataView(packet);
|
const packetView = new DataView(packet)
|
||||||
packetView.setInt32(0, packetLength); // 总长度
|
packetView.setInt32(0, packetLength) // 总长度
|
||||||
packetView.setInt16(4, 16); // 头长度
|
packetView.setInt16(4, 16) // 头长度
|
||||||
packetView.setUint16(6, protocol); // 协议类型
|
packetView.setUint16(6, protocol) // 协议类型
|
||||||
packetView.setUint32(8, type); // 包类型
|
packetView.setUint32(8, type) // 包类型
|
||||||
packetView.setUint32(12, 1); // 一个常数
|
packetView.setUint32(12, 1) // 一个常数
|
||||||
packetArray.set(payloadEncoded, 16); //写入负载
|
packetArray.set(payloadEncoded, 16) //写入负载
|
||||||
return packet;
|
return packet
|
||||||
}
|
}
|
||||||
private async danmakuProcesser(ev: MessageEvent<Blob>) {
|
private async danmakuProcesser(ev: MessageEvent<Blob>) {
|
||||||
// 弹幕事件处理
|
// 弹幕事件处理
|
||||||
const msgPacket = await ev.data.arrayBuffer()
|
const msgPacket = await ev.data.arrayBuffer()
|
||||||
const msgArray = new Uint8Array(msgPacket);
|
const msgArray = new Uint8Array(msgPacket)
|
||||||
const msg = new DataView(msgPacket);
|
const msg = new DataView(msgPacket)
|
||||||
const packetProtocol = msg.getInt16(6);
|
const packetProtocol = msg.getInt16(6)
|
||||||
const packetType = msg.getInt32(8);
|
const packetType = msg.getInt32(8)
|
||||||
const packetPayload: Uint8Array = msgArray.slice(16);
|
const packetPayload: Uint8Array = msgArray.slice(16)
|
||||||
let jsonData;
|
let jsonData
|
||||||
switch (packetType) {
|
switch (packetType) {
|
||||||
case DANMAKU_TYPE.HEARTBEAT_REPLY:
|
case DANMAKU_TYPE.HEARTBEAT_REPLY:
|
||||||
// 心跳包,不做处理
|
// 心跳包,不做处理
|
||||||
break;
|
break
|
||||||
case DANMAKU_TYPE.AUTH_REPLY:
|
case DANMAKU_TYPE.AUTH_REPLY:
|
||||||
printLog("通过认证");
|
printLog('弹幕接收器' ,"通过认证")
|
||||||
// 认证通过,每30秒发一次心跳包
|
// 认证通过,每30秒发一次心跳包
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
const heartbeatPayload = "陈睿你妈死了";
|
const heartbeatPayload = "陈睿你妈死了"
|
||||||
if (this.ws) {
|
if (this.ws) {
|
||||||
this.ws.send(this.generatePacket(1, 2, heartbeatPayload));
|
this.ws.send(this.generatePacket(1, 2, heartbeatPayload))
|
||||||
}
|
}
|
||||||
}, 30000);
|
}, 30000)
|
||||||
this.emit("connected");
|
this.emit("connected")
|
||||||
break;
|
break
|
||||||
case DANMAKU_TYPE.DATA:
|
case DANMAKU_TYPE.DATA:
|
||||||
switch (packetProtocol) {
|
switch (packetProtocol) {
|
||||||
case DANMAKU_PROTOCOL.JSON:
|
case DANMAKU_PROTOCOL.JSON:
|
||||||
// 这些数据大都没用,但还是留着吧
|
// 这些数据大都没用,但还是留着吧
|
||||||
jsonData = JSON.parse(decoder.decode(packetPayload));
|
jsonData = JSON.parse(decoder.decode(packetPayload))
|
||||||
this.emit(jsonData.cmd, jsonData.data);
|
this.emit(jsonData.cmd, jsonData.data)
|
||||||
break;
|
break
|
||||||
case DANMAKU_PROTOCOL.BROTLI: {
|
case DANMAKU_PROTOCOL.BROTLI: {
|
||||||
const resultRaw = brotli.decompress(packetPayload);
|
const resultRaw = brotli.decompress(packetPayload)
|
||||||
const result = new DataView(resultRaw.buffer);
|
const result = new DataView(resultRaw.buffer)
|
||||||
let offset = 0;
|
let offset = 0
|
||||||
while (offset < resultRaw.length) {
|
while (offset < resultRaw.length) {
|
||||||
const length = result.getUint32(offset);
|
const length = result.getUint32(offset)
|
||||||
const packetData = resultRaw.slice(offset + 16, offset + length);
|
const packetData = resultRaw.slice(offset + 16, offset + length)
|
||||||
const data = JSON.parse(decoder.decode(packetData));
|
const data = JSON.parse(decoder.decode(packetData))
|
||||||
const cmd = data.cmd.split(":")[0];
|
const cmd = data.cmd.split(":")[0]
|
||||||
this.emit(cmd, data.info || data.data);
|
this.emit(cmd, this.roomId, data.info || data.data)
|
||||||
apiServer.clients.forEach((client) => {
|
apiServer.clients.forEach((client: WebSocketClient) => {
|
||||||
client.send(JSON.stringify({ cmd, data: data.info || data.data }))
|
client.send(JSON.stringify({ cmd, room: this.roomId, data: data.info || data.data }))
|
||||||
})
|
})
|
||||||
offset += length;
|
offset += length
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break
|
||||||
default:
|
default:
|
||||||
printLog("什么鬼,没见过这种包");
|
printLog('弹幕接收器', `未知的弹幕数据包种类 ${packetType}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
6
src/Deps.ts
Normal file
6
src/Deps.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
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@0.1.7/mod.ts'
|
||||||
|
export { WebSocketServer, type WebSocketClient } from 'https://deno.land/x/websocket@v0.1.4/mod.ts'
|
38
src/Main.ts
Normal file
38
src/Main.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
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 { launchAllPlugins } from './Plugins.ts'
|
||||||
|
|
||||||
|
const roomReceiverMap: Map<number, DanmakuReceiver> = new Map()
|
||||||
|
for(const room of config.room_id) {
|
||||||
|
const danmakuReceiver = new DanmakuReceiver(room)
|
||||||
|
danmakuReceiver.on('connected', () => {
|
||||||
|
printLog('主程序', '连接成功')
|
||||||
|
})
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
globalThis.onunload = () => {
|
||||||
|
printLog('主程序', '退出')
|
||||||
|
}
|
||||||
|
danmakuReceiver.on('closed', () => {
|
||||||
|
printLog('主程序', '掉线了')
|
||||||
|
danmakuReceiver.connect()
|
||||||
|
})
|
||||||
|
danmakuReceiver.on('LIVE', onLiveStart)
|
||||||
|
danmakuReceiver.on('PREPARING', onLiveEnd)
|
||||||
|
danmakuReceiver.on('DANMU_MSG', receiveDanmaku)
|
||||||
|
danmakuReceiver.connect()
|
||||||
|
roomReceiverMap.set(room, danmakuReceiver)
|
||||||
|
}
|
||||||
|
|
||||||
|
launchAllPlugins()
|
37
src/Plugins.ts
Normal file
37
src/Plugins.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import { printErr } from "./utils/PrintLog.ts"
|
||||||
|
|
||||||
|
const pluginSet: Set<Deno.Process> = new Set()
|
||||||
|
|
||||||
|
export async function launchAllPlugins() {
|
||||||
|
const pluginsList = await Deno.readDir('./plugins')
|
||||||
|
for await (const plugin of pluginsList) {
|
||||||
|
if (plugin.name === '.gitkeep') {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const pluginProcess = Deno.run({
|
||||||
|
cmd: [`./plugins/${plugin.name}/main`, `./plugins/${plugin.name}/config.json`]
|
||||||
|
})
|
||||||
|
pluginSet.add(pluginProcess)
|
||||||
|
} catch {
|
||||||
|
printErr('主程序', `启动插件${plugin.name}失败`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopAllPlugins() {
|
||||||
|
for (const pluginProcess of pluginSet) {
|
||||||
|
pluginProcess.kill()
|
||||||
|
}
|
||||||
|
Deno.exit()
|
||||||
|
}
|
||||||
|
|
||||||
|
Deno.addSignalListener('SIGTERM', () => {
|
||||||
|
stopAllPlugins()
|
||||||
|
Deno.exit()
|
||||||
|
})
|
||||||
|
|
||||||
|
Deno.addSignalListener('SIGINT', () => {
|
||||||
|
stopAllPlugins()
|
||||||
|
Deno.exit()
|
||||||
|
})
|
@ -1,50 +1,50 @@
|
|||||||
import config from "./config.ts";
|
import config from "./Config.ts"
|
||||||
|
|
||||||
const cookie =
|
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 {
|
export interface DanmakuStruct {
|
||||||
color?: number;
|
color?: number
|
||||||
bubble?: number;
|
bubble?: number
|
||||||
msg: string;
|
msg: string
|
||||||
mode?: number;
|
mode?: number
|
||||||
fontsize?: number;
|
fontsize?: number
|
||||||
rnd?: number;
|
rnd?: number
|
||||||
roomid?: number;
|
roomid?: number
|
||||||
csrf?: string;
|
csrf?: string
|
||||||
csrf_token?: string;
|
csrf_token?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export function sendDanmaku(danmaku: DanmakuStruct) {
|
export function sendDanmaku(roomId: number, danmaku: DanmakuStruct) {
|
||||||
if (danmaku.msg.length > 19) {
|
if (danmaku.msg.length > 19) {
|
||||||
sendDanmaku({
|
sendDanmaku(roomId, {
|
||||||
msg: danmaku.msg.slice(0, 15),
|
msg: danmaku.msg.slice(0, 15),
|
||||||
});
|
})
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
sendDanmaku({
|
sendDanmaku(roomId, {
|
||||||
msg: danmaku.msg.slice(15, danmaku.msg.length),
|
msg: danmaku.msg.slice(15, danmaku.msg.length),
|
||||||
});
|
})
|
||||||
}, 2000);
|
}, 2000)
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
danmaku.rnd = new Date().getTime();
|
danmaku.rnd = new Date().getTime()
|
||||||
if (!danmaku.color) {
|
if (!danmaku.color) {
|
||||||
danmaku.color = 5816798;
|
danmaku.color = 5816798
|
||||||
}
|
}
|
||||||
if (!danmaku.bubble) {
|
if (!danmaku.bubble) {
|
||||||
danmaku.bubble = 0;
|
danmaku.bubble = 0
|
||||||
}
|
}
|
||||||
if (!danmaku.mode) {
|
if (!danmaku.mode) {
|
||||||
danmaku.mode = 1;
|
danmaku.mode = 1
|
||||||
}
|
}
|
||||||
if (!danmaku.fontsize) {
|
if (!danmaku.fontsize) {
|
||||||
danmaku.fontsize = 24;
|
danmaku.fontsize = 24
|
||||||
}
|
}
|
||||||
danmaku.roomid = config.room_id as number;
|
danmaku.roomid = roomId
|
||||||
danmaku.csrf = danmaku.csrf_token = config.verify.csrf;
|
danmaku.csrf = danmaku.csrf_token = config.verify.csrf
|
||||||
const data = new FormData();
|
const data = new FormData()
|
||||||
for (const k in danmaku) {
|
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", {
|
fetch("https://api.live.bilibili.com/msg/send", {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@ -52,9 +52,9 @@ export function sendDanmaku(danmaku: DanmakuStruct) {
|
|||||||
headers: {
|
headers: {
|
||||||
cookie: cookie,
|
cookie: cookie,
|
||||||
"user-agent":
|
"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",
|
host: "api.live.bilibili.com",
|
||||||
"Referer": "https://live.bilibili.com",
|
"Referer": "https://live.bilibili.com",
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
@ -1,7 +0,0 @@
|
|||||||
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 } from "https://deno.land/x/websocket@v0.1.4/mod.ts";
|
|
||||||
export type { WebSocketClient } from "https://deno.land/x/websocket@v0.1.4/mod.ts";
|
|
20
src/main.ts
20
src/main.ts
@ -1,20 +0,0 @@
|
|||||||
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';
|
|
||||||
|
|
||||||
const danmakuReceiver = new DanmakuReceiver(config.room_id);
|
|
||||||
danmakuReceiver.on('connected', () => {
|
|
||||||
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);
|
|
||||||
|
|
||||||
danmakuReceiver.connect();
|
|
@ -1,8 +1,8 @@
|
|||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// deno-lint-ignore no-explicit-any
|
||||||
export function FormatString(str: string, args: any): string {
|
export function FormatString(str: string, args: any): string {
|
||||||
let result: string = str;
|
let result: string = str
|
||||||
for(const arg in args) {
|
for(const arg in args) {
|
||||||
result = result.replace(new RegExp(`{${arg}}`, 'g'), args[arg])
|
result = result.replace(new RegExp(`{${arg}}`, 'g'), args[arg])
|
||||||
}
|
}
|
||||||
return result;
|
return result
|
||||||
}
|
}
|
7
src/utils/PrintLog.ts
Normal file
7
src/utils/PrintLog.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { getTimeString } from './GetTimeString.ts'
|
||||||
|
export function printLog(source: string, msg: unknown) {
|
||||||
|
console.log(`[log][${source}][${getTimeString()}] ${msg}`)
|
||||||
|
}
|
||||||
|
export function printErr(source: string, msg: unknown) {
|
||||||
|
console.error(`[err][${source}][${getTimeString()}] ${msg}`)
|
||||||
|
}
|
@ -1,3 +1,3 @@
|
|||||||
export { getTimeString } from './getTimeString.ts'
|
export { getTimeString } from './GetTimeString.ts'
|
||||||
export { printLog } from './printLog.ts'
|
export { printLog } from './PrintLog.ts'
|
||||||
export { FormatString } from './FormatString.ts'
|
export { FormatString } from './FormatString.ts'
|
@ -1,4 +0,0 @@
|
|||||||
import { getTimeString } from './getTimeString.ts'
|
|
||||||
export function printLog(msg: unknown) {
|
|
||||||
console.log(`[${getTimeString()}] ${msg}`)
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user