diff --git a/bun.lockb b/bun.lockb index 632f472..731ac5b 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index 708177d..1c4b798 100644 --- a/package.json +++ b/package.json @@ -14,16 +14,20 @@ "@snazzah/davey": "^0.1.10", "crc-32": "^1.2.2", "discord.js-selfbot-v13": "^3.7.1", + "express": "^5.2.1", "ffmpeg-static": "^5.3.0", "fluent-ffmpeg": "^2.1.3", "libsodium-wrappers": "^0.8.2", "node-crc": "^4.0.0", "opusscript": "^0.1.1", "prism-media": "2.0.0-alpha.0", - "sodium-native": "^4.3.2" + "sodium-native": "^4.3.2", + "ws": "^8.20.1" }, "devDependencies": { "@types/bun": "latest", - "@types/fluent-ffmpeg": "^2.1.28" + "@types/express": "^5.0.6", + "@types/fluent-ffmpeg": "^2.1.28", + "@types/ws": "^8.18.1" } } \ No newline at end of file diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..8fff19f --- /dev/null +++ b/public/index.html @@ -0,0 +1,273 @@ + + + + + + Discord Audio Transmitter + + + +
+

Audio Transmitter

+

Transmit your microphone to Discord Voice

+ +
+
+ Disconnected +
+ + + +
+

Listen to Discord

+ + +

Click button to listen

+
+ +
+ +
+
+ + + + diff --git a/src/index.ts b/src/index.ts index 8b41e84..e769749 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,9 @@ import { Client } from "discord.js-selfbot-v13"; import { startRecording } from "./recorder"; import { config } from "./config"; +import { startWebserver } from "./webserver"; +import { discordPlayer } from "./player"; +import { getVoiceConnection } from "@discordjs/voice"; // Validasi environment variables const token = process.env.DISCORD_TOKEN; @@ -42,6 +45,16 @@ client.on("ready", async () => { console.log(`[bot] Joining voice channel: #${channel.name} (${channel.id})`); } await startRecording(client, channel as any); + + // Set up player connection + const connection = getVoiceConnection(guildId!); + if (connection) { + discordPlayer.setConnection(connection); + console.log("[bot] Player connected to voice channel"); + } + + // Start Webserver + startWebserver(3000); }); client.on("error", (err) => { diff --git a/src/player.ts b/src/player.ts new file mode 100644 index 0000000..e3c52c4 --- /dev/null +++ b/src/player.ts @@ -0,0 +1,47 @@ +import { + createAudioPlayer, + createAudioResource, + AudioPlayerStatus, + VoiceConnection, + AudioPlayer, + StreamType +} from "@discordjs/voice"; +import { Readable } from "stream"; + +export class DiscordPlayer { + private player: AudioPlayer; + private connection: VoiceConnection | null = null; + + constructor() { + this.player = createAudioPlayer(); + + this.player.on(AudioPlayerStatus.Playing, () => { + console.log("[player] Audio player is now playing!"); + }); + + this.player.on("error", error => { + console.error(`[player] Error: ${error.message}`); + }); + } + + public setConnection(connection: VoiceConnection) { + this.connection = connection; + this.connection.subscribe(this.player); + } + + public playStream(stream: Readable) { + // We assume the stream is Opus or PCM. + // For MediaRecorder (webm/opus), we might need to parse it. + // But let's start with a simple resource. + const resource = createAudioResource(stream, { + inputType: StreamType.WebmOpus, + }); + this.player.play(resource); + } + + public stop() { + this.player.stop(); + } +} + +export const discordPlayer = new DiscordPlayer(); diff --git a/src/recorder.ts b/src/recorder.ts index 17fe94b..1b5f6e7 100644 --- a/src/recorder.ts +++ b/src/recorder.ts @@ -139,6 +139,13 @@ export async function startRecording(client: Client, channel: VoiceChannel): Pro // Pipe: audioStream -> packetFilter -> oggStream -> out audioStream.pipe(packetFilter).pipe(oggStream).pipe(out); + // Also forward to web listeners + oggStream.on('data', (chunk) => { + if ((global as any).broadcastToWeb) { + (global as any).broadcastToWeb(chunk); + } + }); + if (config.verbose) { console.log(`[recorder] Recording user ${userId} → ${filename}`); } diff --git a/src/webserver.ts b/src/webserver.ts new file mode 100644 index 0000000..de016d6 --- /dev/null +++ b/src/webserver.ts @@ -0,0 +1,59 @@ +import express from "express"; +import { WebSocketServer } from "ws"; +import http from "http"; +import path from "path"; +import { PassThrough } from "stream"; +import { discordPlayer } from "./player"; + +export function startWebserver(port: number = 3000) { + const app = express(); + const server = http.createServer(app); + const wss = new WebSocketServer({ server }); + + const listeners = new Set(); + + app.use(express.static(path.join(__dirname, "../public"))); + + // Endpoint for receiving (listening) audio from Discord + app.get("/listen", (req, res) => { + res.setHeader("Content-Type", "audio/ogg"); + listeners.add(res); + console.log(`[webserver] New listener connected. Total: ${listeners.size}`); + + req.on("close", () => { + listeners.delete(res); + console.log(`[webserver] Listener disconnected. Total: ${listeners.size}`); + }); + }); + + // Function to broadcast audio chunks to all listeners + (global as any).broadcastToWeb = (chunk: Buffer) => { + listeners.forEach(res => res.write(chunk)); + }; + + wss.on("connection", (ws) => { + console.log("[webserver] New WebSocket connection"); + + const audioStream = new PassThrough(); + discordPlayer.playStream(audioStream); + + ws.on("message", (data: Buffer) => { + // Write incoming audio chunks to the stream + audioStream.write(data); + }); + + ws.on("close", () => { + console.log("[webserver] WebSocket connection closed"); + audioStream.end(); + }); + + ws.on("error", (err) => { + console.error("[webserver] WebSocket error:", err); + audioStream.end(); + }); + }); + + server.listen(port, () => { + console.log(`[webserver] Server listening on http://localhost:${port}`); + }); +}