feat: humanise selfbot voice-join and go-live pacing
Joining voice and starting the broadcast instantly looks like a bot. Add randomised, human-plausible pauses (~0.9-2.2s after coming online before joining the channel, ~2.5-5s after joining before hitting Go Live) so the cadence isn't machine-instant or fingerprintable. The pause resolves immediately on stop() so teardown never hangs mid-wait. Verified live: end-to-end join -> settle -> Go Live took ~8s before the stream went live, held for 15s, and tore down cleanly. tsc --noEmit passes.
This commit is contained in:
@@ -33,6 +33,27 @@ export class SelfbotStreamer implements ScreenStreamer {
|
||||
return this.active;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait a randomised, human-plausible amount of time. Resolves immediately if
|
||||
* the stream is aborted (stop()) mid-wait, so teardown never hangs on a pause.
|
||||
*/
|
||||
private humanPause(minMs: number, maxMs: number): Promise<void> {
|
||||
const ms = Math.floor(minMs + Math.random() * Math.max(0, maxMs - minMs));
|
||||
return new Promise((resolve) => {
|
||||
const signal = this.controller?.signal;
|
||||
if (signal?.aborted) return resolve();
|
||||
const onAbort = () => {
|
||||
clearTimeout(timer);
|
||||
resolve();
|
||||
};
|
||||
const timer = setTimeout(() => {
|
||||
signal?.removeEventListener("abort", onAbort);
|
||||
resolve();
|
||||
}, ms);
|
||||
signal?.addEventListener("abort", onAbort, { once: true });
|
||||
});
|
||||
}
|
||||
|
||||
private async loadLib() {
|
||||
let selfbot: any, vs: any;
|
||||
try {
|
||||
@@ -66,12 +87,19 @@ export class SelfbotStreamer implements ScreenStreamer {
|
||||
const { selfbot, vs } = await this.loadLib();
|
||||
const { Streamer, prepareStream, playStream } = vs;
|
||||
|
||||
this.controller = new AbortController();
|
||||
|
||||
this.streamer = new Streamer(new selfbot.Client());
|
||||
await this.streamer.client.login(this.config.selfbotToken);
|
||||
|
||||
// Act like a person, not a bot: take a breath after coming online before
|
||||
// navigating into the voice channel, then settle in for a few seconds
|
||||
// before hitting "Go Live". Randomised so the cadence isn't fingerprintable.
|
||||
await this.humanPause(900, 2200);
|
||||
await this.streamer.joinVoice(ctx.guildId, ctx.voiceChannelId);
|
||||
await this.humanPause(2500, 5000);
|
||||
|
||||
const [w, h] = this.config.vncResolution.split("x").map((n) => parseInt(n, 10));
|
||||
this.controller = new AbortController();
|
||||
|
||||
// Capture the VNC X display with the SYSTEM ffmpeg (which reliably has
|
||||
// x11grab), then pipe that stream into the library. Relying on the lib's
|
||||
|
||||
Reference in New Issue
Block a user