feat: add web interface and WebSocket server for real-time audio transmission to Discord
This commit is contained in:
@@ -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) => {
|
||||
|
||||
@@ -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();
|
||||
@@ -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}`);
|
||||
}
|
||||
|
||||
@@ -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<express.Response>();
|
||||
|
||||
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}`);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user