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;
|
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() {
|
private async loadLib() {
|
||||||
let selfbot: any, vs: any;
|
let selfbot: any, vs: any;
|
||||||
try {
|
try {
|
||||||
@@ -66,12 +87,19 @@ export class SelfbotStreamer implements ScreenStreamer {
|
|||||||
const { selfbot, vs } = await this.loadLib();
|
const { selfbot, vs } = await this.loadLib();
|
||||||
const { Streamer, prepareStream, playStream } = vs;
|
const { Streamer, prepareStream, playStream } = vs;
|
||||||
|
|
||||||
|
this.controller = new AbortController();
|
||||||
|
|
||||||
this.streamer = new Streamer(new selfbot.Client());
|
this.streamer = new Streamer(new selfbot.Client());
|
||||||
await this.streamer.client.login(this.config.selfbotToken);
|
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.streamer.joinVoice(ctx.guildId, ctx.voiceChannelId);
|
||||||
|
await this.humanPause(2500, 5000);
|
||||||
|
|
||||||
const [w, h] = this.config.vncResolution.split("x").map((n) => parseInt(n, 10));
|
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
|
// 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
|
// x11grab), then pipe that stream into the library. Relying on the lib's
|
||||||
|
|||||||
Reference in New Issue
Block a user