fix(video): detect pre-existing voice members after gateway restart
Root cause: scanExistingStreamers only read channel.members, which is
EMPTY after a gateway restart because the selfbot's guild member cache
hasn't been populated yet. So any user who was ALREADY on camera /
screen-sharing when the bot (re)joined was never detected → no video.
Fix: when the member cache is empty, fall back to guild.members.fetch()
(REST GET /guilds/{id}/members — user-token compatible; the bot-only
GET /channels/{id}/voice-states returns 404 for selfbots, verified live)
which populates member.voice states, then re-scan channel.members for
streaming/selfVideo.
- scanExistingStreamers is now async; trackChannel fire-and-forgets it
- logs source=cache vs source=rest-members for observability
- +1 test: cold-start channel.members empty → REST fetch → watch camera user
- 191/191 tests pass, typecheck + build + biome clean
This commit is contained in:
@@ -14,7 +14,10 @@ import {
|
||||
function makeChannel(guildId = "g1", channelId = "c1") {
|
||||
return {
|
||||
id: channelId,
|
||||
guild: { id: guildId },
|
||||
guild: {
|
||||
id: guildId,
|
||||
members: { fetch: vi.fn().mockResolvedValue(new Map()) },
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -152,4 +155,39 @@ describe("videoRecorder (stream-watch orchestration)", () => {
|
||||
trackChannel("g1", makeChannel()); // no members
|
||||
expect(spy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("trackChannel falls back to REST guild.members.fetch() when cache is empty (cold start bug fix)", async () => {
|
||||
const client = makeClient() as any;
|
||||
setVideoRecorderClient(client);
|
||||
const spy = vi
|
||||
.spyOn(streamWatch, "startStreamWatch")
|
||||
.mockImplementation(() => {});
|
||||
// Warmable guild: members.fetch() populates channel.members with a
|
||||
// camera-on member (selfVideo) — the cold-start scenario where the bot
|
||||
// joins a channel that already has someone on camera but the member cache
|
||||
// hasn't been populated yet.
|
||||
const cameraMember = {
|
||||
id: "u-cam",
|
||||
voice: { streaming: false, selfVideo: true },
|
||||
};
|
||||
const ch = {
|
||||
id: "c1",
|
||||
guild: {
|
||||
id: "g1",
|
||||
members: {
|
||||
fetch: vi.fn().mockImplementation(async () => {
|
||||
// Simulate REST fetch populating the channel member cache.
|
||||
(ch as any).members = new Map([["u-cam", cameraMember]]);
|
||||
return new Map([["u-cam", cameraMember]]);
|
||||
}),
|
||||
},
|
||||
},
|
||||
members: new Map(), // empty at join time — cold cache
|
||||
};
|
||||
trackChannel("g1", ch as any);
|
||||
// Allow the async scanExistingStreamers (fire-and-forget) to complete.
|
||||
await vi.waitFor(() => {
|
||||
expect(spy).toHaveBeenCalledWith(ch as any, "u-cam");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user