Skip to content

Commit

Permalink
Add Biomejs for linting (and maybe formatting?) (#136)
Browse files Browse the repository at this point in the history
* Add Biome

* fix lint: utils.ts

* Don't lint examples

Don't think this needs to be linted yet, although it can be beneficial

* fix lint: AudioPacketizer.ts

* fix lint: BaseMediaPacketizer.ts

* fix lint: VideoPacketizerAnnexB.ts

* fix lint: VideoPacketizerVP8.ts

* fix lint: AnnexBHelper.ts

* fix lint: BaseMediaConnection.ts

* Consolidates all WebRTC parameters into one object

* fix lint: VoiceConnection.ts

* Use event emitter to remove all ugly non-null assertions

* fix lint: AudioStream.ts

* fix lint: BaseMediaStream.ts

* fix lint: LibavCodecId.ts

* fix lint: LibavDemuxer.ts

* fix lint: newApi.ts

* fix lint: streamLivestreamVideo.ts

* fix lint: VideoStream.ts

* Wrong library

* ts-expect-error bit me

* why

* Set up automatic linting

* Disable import organizing
  • Loading branch information
longnguyen2004 authored Jan 2, 2025
1 parent a2a2376 commit e7090a5
Show file tree
Hide file tree
Showing 22 changed files with 358 additions and 222 deletions.
18 changes: 18 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
name: Linting

on:
push:
pull_request:

jobs:
quality:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Biome
uses: biomejs/setup-biome@v2
with:
version: latest
- name: Run Biome
run: biome ci .
30 changes: 30 additions & 0 deletions biome.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
"vcs": {
"enabled": true,
"clientKind": "git",
"useIgnoreFile": true
},
"files": {
"ignoreUnknown": false,
"ignore": ["./examples/**/*"]
},
"formatter": {
"enabled": false,
"indentStyle": "tab"
},
"organizeImports": {
"enabled": false
},
"linter": {
"enabled": true,
"rules": {
"recommended": true
}
},
"javascript": {
"formatter": {
"quoteStyle": "double"
}
}
}
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,11 @@
"ws": "^8.18.0"
},
"devDependencies": {
"@biomejs/biome": "^1.9.4",
"@types/fluent-ffmpeg": "^2.1.27",
"@types/node": "^20.17.1",
"@types/ws": "^8.5.12",
"typed-emitter": "^2.1.0",
"typescript": "^5.6.3"
},
"peerDependencies": {
Expand Down
40 changes: 40 additions & 0 deletions src/client/GatewayEvents.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
type GatewayEventGeneric<Type extends string = string, Data = unknown> = {
t: Type,
d: Data
}

export namespace GatewayEvent {
export type VoiceStateUpdate = GatewayEventGeneric<
"VOICE_STATE_UPDATE", {
user_id: string,
session_id: string
}
>
export type VoiceServerUpdate = GatewayEventGeneric<
"VOICE_SERVER_UPDATE", {
guild_id: string,
endpoint: string,
token: string
}
>
export type StreamCreate = GatewayEventGeneric<
"STREAM_CREATE", {
stream_key: string,
rtc_server_id: string
}
>
export type StreamServerUpdate = GatewayEventGeneric<
"STREAM_SERVER_UPDATE", {
stream_key: string,
endpoint: string,
token: string
}
>
}


export type GatewayEvent =
GatewayEvent.VoiceStateUpdate |
GatewayEvent.VoiceServerUpdate |
GatewayEvent.StreamCreate |
GatewayEvent.StreamServerUpdate
174 changes: 93 additions & 81 deletions src/client/Streamer.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,29 @@
import { EventEmitter } from "node:events";
import { VoiceConnection } from "./voice/VoiceConnection.js";
import { Client } from 'discord.js-selfbot-v13';
import { MediaUdp } from "./voice/MediaUdp.js";
import { StreamConnection } from "./voice/StreamConnection.js";
import { GatewayOpCodes } from "./GatewayOpCodes.js";
import { StreamOptions } from "./voice/index.js";
import type TypedEmitter from "typed-emitter";
import type { Client } from 'discord.js-selfbot-v13';
import type { MediaUdp } from "./voice/MediaUdp.js";
import type { StreamOptions } from "./voice/index.js";
import type { GatewayEvent } from "./GatewayEvents.js";

type EmitterEvents = {
[K in GatewayEvent["t"]]: (data: Extract<GatewayEvent, { t: K }>["d"]) => void
}

export class Streamer {
private _voiceConnection?: VoiceConnection;
private _client: Client;
private _gatewayEmitter = new EventEmitter() as TypedEmitter.default<EmitterEvents>

constructor(client: Client) {
this._client = client;

//listen for messages
this.client.on('raw', (packet: any) => {
this.handleGatewayEvent(packet.t, packet.d);
this.client.on('raw', (packet: GatewayEvent) => {
// @ts-expect-error I don't know how to make this work with TypeScript, so whatever
this._gatewayEmitter.emit(packet.t, packet.d);
});
}

Expand All @@ -26,8 +35,8 @@ export class Streamer {
return this._voiceConnection;
}

public sendOpcode(code: number, data: any): void {
// @ts-ignore
public sendOpcode(code: number, data: unknown): void {
// @ts-expect-error Please make this public
this.client.ws.broadcast({
op: code,
d: data,
Expand All @@ -36,77 +45,125 @@ export class Streamer {

public joinVoice(guild_id: string, channel_id: string, options?: Partial<StreamOptions>): Promise<MediaUdp> {
return new Promise<MediaUdp>((resolve, reject) => {
if (!this.client.user)
{
if (!this.client.user) {
reject("Client not logged in");
return;
}
this._voiceConnection = new VoiceConnection(
const user_id = this.client.user.id;
const voiceConn = new VoiceConnection(
guild_id,
this.client.user.id,
user_id,
channel_id,
options ?? {},
(udp) => {
udp.mediaConnection.setProtocols().then(() => resolve(udp))
}
);
this._voiceConnection = voiceConn;
this._gatewayEmitter.on("VOICE_STATE_UPDATE", (d) => {
if (user_id !== d.user_id) return;
voiceConn.setSession(d.session_id);
});
this._gatewayEmitter.on("VOICE_SERVER_UPDATE", (d) => {
if (guild_id !== d.guild_id) return;
voiceConn.setTokens(d.endpoint, d.token);
})
this.signalVideo(false);
});
}

public createStream(options?: Partial<StreamOptions>): Promise<MediaUdp> {
return new Promise<MediaUdp>((resolve, reject) => {
if (!this.client.user)
{
if (!this.client.user) {
reject("Client not logged in");
return;
}
if (!this.voiceConnection)
{
if (!this.voiceConnection) {
reject("cannot start stream without first joining voice channel");
return;
}

this.signalStream();

this.voiceConnection.streamConnection = new StreamConnection(
this.voiceConnection.guildId,
this.client.user.id,
this.voiceConnection.channelId,
const {
guildId: clientGuildId,
channelId: clientChannelId,
session_id
} = this.voiceConnection;
const {
id: clientUserId
} = this.client.user;

if (!session_id)
throw new Error("Session doesn't exist yet");
const streamConn = new StreamConnection(
clientGuildId,
clientUserId,
clientChannelId,
options ?? {},
(udp) => {
udp.mediaConnection.setProtocols().then(() => resolve(udp))
}
);
this.voiceConnection.streamConnection = streamConn;
this._gatewayEmitter.on("STREAM_CREATE", (d) => {
const [type, guildId, channelId, userId] = d.stream_key.split(":");

if (
clientGuildId !== guildId ||
clientChannelId !== channelId ||
clientUserId !== userId
)
return;

streamConn.serverId = d.rtc_server_id;
streamConn.streamKey = d.stream_key;
streamConn.setSession(session_id);
});
this._gatewayEmitter.on("STREAM_SERVER_UPDATE", (d) => {
const [type, guildId, channelId, userId] = d.stream_key.split(":");

if (
clientGuildId !== guildId ||
clientChannelId !== channelId ||
clientUserId !== userId
)
return;

streamConn.setTokens(d.endpoint, d.token);
})
});
}

public stopStream(): void {
const stream = this.voiceConnection?.streamConnection;
if(!stream) return;

if (!stream) return;

stream.stop();

this.signalStopStream();

this.voiceConnection.streamConnection = undefined;
this._gatewayEmitter.removeAllListeners("STREAM_CREATE");
this._gatewayEmitter.removeAllListeners("STREAM_SERVER_UPDATE");
}

public leaveVoice(): void {
this.voiceConnection?.stop();

this.signalLeaveVoice();

this._voiceConnection = undefined;
this._gatewayEmitter.removeAllListeners("VOICE_STATE_UPDATE");
this._gatewayEmitter.removeAllListeners("VOICE_SERVER_UPDATE");
}

public signalVideo(video_enabled: boolean): void {
if (!this.voiceConnection)
return;
const {
guildId: guild_id,
channelId: channel_id
channelId: channel_id,
} = this.voiceConnection;
this.sendOpcode(GatewayOpCodes.VOICE_STATE_UPDATE, {
guild_id,
Expand All @@ -122,17 +179,18 @@ export class Streamer {
return;
const {
guildId: guild_id,
channelId: channel_id
channelId: channel_id,
botId: user_id
} = this.voiceConnection;
this.sendOpcode(GatewayOpCodes.STREAM_CREATE, {
type: "guild",
guild_id,
channel_id,
preferred_region: null,
});

this.sendOpcode(GatewayOpCodes.STREAM_SET_PAUSED, {
stream_key: `guild:${guild_id}:${channel_id}:${this.client.user!.id}`,
stream_key: `guild:${guild_id}:${channel_id}:${user_id}`,
paused: false,
});
}
Expand All @@ -142,10 +200,11 @@ export class Streamer {
return;
const {
guildId: guild_id,
channelId: channel_id
channelId: channel_id,
botId: user_id
} = this.voiceConnection;
this.sendOpcode(GatewayOpCodes.STREAM_DELETE, {
stream_key: `guild:${guild_id}:${channel_id}:${this.client.user!.id}`
stream_key: `guild:${guild_id}:${channel_id}:${user_id}`
});
}

Expand All @@ -158,51 +217,4 @@ export class Streamer {
self_video: false,
});
}

private handleGatewayEvent(event: string, data: any): void {
switch(event) {
case "VOICE_STATE_UPDATE": {
if (data.user_id === this.client.user!.id) {
// transfer session data to voice connection
this.voiceConnection?.setSession(data.session_id);
}
break;
}
case "VOICE_SERVER_UPDATE": {
// transfer voice server update to voice connection
if (data.guild_id != this.voiceConnection?.guildId) return;

this.voiceConnection?.setTokens(data.endpoint, data.token);
break;
}
case "STREAM_CREATE": {
const [type, guildId, channelId, userId] = data.stream_key.split(":");

if (this.voiceConnection?.guildId != guildId) return;

if (userId === this.client.user!.id) {
this.voiceConnection!.streamConnection!.serverId = data.rtc_server_id;

this.voiceConnection!.streamConnection!.streamKey = data.stream_key;
this.voiceConnection!.streamConnection!.setSession(
this.voiceConnection!.session_id!
);
}
break;
}
case "STREAM_SERVER_UPDATE": {
const [type, guildId, channelId, userId] = data.stream_key.split(":");

if (this.voiceConnection?.guildId != guildId) return;

if (userId === this.client.user!.id) {
this.voiceConnection!.streamConnection!.setTokens(
data.endpoint,
data.token
);
}
break;
}
}
}
}
}
2 changes: 1 addition & 1 deletion src/client/packet/AudioPacketizer.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { MediaUdp } from "../voice/MediaUdp.js";
import type { MediaUdp } from "../voice/MediaUdp.js";
import { BaseMediaPacketizer } from "./BaseMediaPacketizer.js";
import { CodecPayloadType } from "../voice/BaseMediaConnection.js";

Expand Down
Loading

0 comments on commit e7090a5

Please sign in to comment.