chore(voice): remove voice reconciliation leftovers (#2810)

This commit is contained in:
Hampus
2026-09-16 17:09:57 +02:00
committed by GitHub
parent 964845d7a7
commit 03d1354562
18 changed files with 6 additions and 762 deletions
@@ -1,11 +1,7 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import type {ChannelID, GuildID, UserID} from '@app/api/BrandedTypes';
import type {
ILiveKitService,
ListActiveRoomsResult,
ListParticipantsResult,
} from '@app/api/infrastructure/ILiveKitService';
import type {ILiveKitService, ListParticipantsResult} from '@app/api/infrastructure/ILiveKitService';
import type {VoiceRegionMetadata, VoiceServerRecord} from '@app/api/voice/VoiceModel';
interface CreateTokenParams {
@@ -78,10 +74,6 @@ export class DisabledLiveKitService implements ILiveKitService {
return {status: 'ok', participants: []};
}
async listActiveRooms(): Promise<ListActiveRoomsResult> {
return {rooms: [], errors: [], searchedServers: 0, completed: true};
}
getDefaultRegionId(): string | null {
return null;
}
@@ -1,14 +1,13 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import type {ChannelID, GuildID, MessageID, RoleID, UserID} from '@app/api/BrandedTypes';
import {createChannelID, createGuildID, createRoleID, createUserID} from '@app/api/BrandedTypes';
import {createChannelID, createRoleID, createUserID} from '@app/api/BrandedTypes';
import {SYSTEM_USER_ID} from '@app/api/constants/Core';
import type {GatewayDispatchEvent} from '@app/api/constants/Gateway';
import {GatewayRpcClient} from '@app/api/infrastructure/GatewayRpcClient';
import {GatewayRpcMethodError, GatewayRpcMethodErrorCodes} from '@app/api/infrastructure/GatewayRpcError';
import type {
CallData,
GatewayActiveVoiceRooms,
GatewayChannelMention,
GatewayGuildMemoryStats,
GatewayMentionSources,
@@ -1066,26 +1065,6 @@ export class GatewayService {
};
}
async getActiveVoiceRooms(): Promise<GatewayActiveVoiceRooms> {
const result = await this.call<{
rooms?: Array<{
guild_id?: string | null;
channel_id: string;
voice_state_count?: number;
}>;
node_count?: number;
}>('process.active_voice_rooms', {});
return {
nodeCount: result.node_count ?? 0,
rooms: (result.rooms ?? []).map((room) => ({
guildId:
room.guild_id === undefined || room.guild_id === null ? undefined : createGuildID(BigInt(room.guild_id)),
channelId: createChannelID(BigInt(room.channel_id)),
voiceStateCount: room.voice_state_count ?? 0,
})),
};
}
async getUserPermissions({guildId, userId, channelId}: UserPermissionsParams): Promise<bigint> {
const result = await this.call<{
permissions: string;
@@ -1638,41 +1617,6 @@ export class GatewayService {
};
}
async repairVoiceStateFromCache({
guildId,
channelId,
userId,
connectionId,
}: {
guildId?: GuildID;
channelId: ChannelID;
userId: UserID;
connectionId: string;
}): Promise<{
success: boolean;
repaired?: boolean;
error?: string;
}> {
const params: Record<string, string> = {
channel_id: channelId.toString(),
user_id: userId.toString(),
connection_id: connectionId,
};
if (guildId !== undefined) {
params['guild_id'] = guildId.toString();
}
const result = await this.call<{
success: boolean;
repaired?: boolean;
error?: string;
}>('voice.repair_state_from_cache', params);
return {
success: result.success,
repaired: result.repaired,
error: result.error,
};
}
async getVoiceStatesForChannel({guildId, channelId}: {guildId?: GuildID; channelId: ChannelID}): Promise<{
voiceStates: Array<GatewayVoiceStateEntry>;
}> {
@@ -88,17 +88,6 @@ export interface GatewayVoiceStateCounts {
servers: Array<GatewayVoiceStateServerCount>;
}
export interface GatewayActiveVoiceRoom {
guildId?: GuildID;
channelId: ChannelID;
voiceStateCount: number;
}
export interface GatewayActiveVoiceRooms {
rooms: Array<GatewayActiveVoiceRoom>;
nodeCount: number;
}
export interface GatewayVoiceStateEntry {
connectionId: string;
userId: string;
@@ -162,8 +151,6 @@ export abstract class IGatewayService {
abstract getVoiceStateCounts(): Promise<GatewayVoiceStateCounts>;
abstract getActiveVoiceRooms(): Promise<GatewayActiveVoiceRooms>;
abstract getUsersToMentionByRoles(params: {
guildId: GuildID;
channelId: ChannelID;
@@ -345,17 +332,6 @@ export abstract class IGatewayService {
error?: string;
}>;
abstract repairVoiceStateFromCache(params: {
guildId?: GuildID;
channelId: ChannelID;
userId: UserID;
connectionId: string;
}): Promise<{
success: boolean;
repaired?: boolean;
error?: string;
}>;
abstract getVoiceStatesForChannel(params: {guildId?: GuildID; channelId: ChannelID}): Promise<{
voiceStates: Array<GatewayVoiceStateEntry>;
}>;
@@ -61,13 +61,6 @@ interface ListParticipantsParams {
serverId: string;
}
export interface LiveKitServerError {
regionId: string;
serverId: string;
errorCode: string;
retryable: boolean;
}
export interface ListParticipantsSuccess {
status: 'ok';
participants: Array<{
@@ -84,19 +77,6 @@ export interface ListParticipantsError {
export type ListParticipantsResult = ListParticipantsSuccess | ListParticipantsError;
export interface LiveKitRoomLocation {
roomName: string;
regionId: string;
serverId: string;
}
export interface ListActiveRoomsResult {
rooms: Array<LiveKitRoomLocation>;
errors: Array<LiveKitServerError>;
searchedServers: number;
completed: boolean;
}
export abstract class ILiveKitService {
abstract createToken(params: CreateTokenParams): Promise<{
token: string;
@@ -111,8 +91,6 @@ export abstract class ILiveKitService {
abstract listParticipants(params: ListParticipantsParams): Promise<ListParticipantsResult>;
abstract listActiveRooms(): Promise<ListActiveRoomsResult>;
abstract getDefaultRegionId(): string | null;
abstract getRegionMetadata(): Array<VoiceRegionMetadata>;
@@ -2,11 +2,7 @@
import type {ChannelID, GuildID, UserID} from '@app/api/BrandedTypes';
import {Config} from '@app/api/Config';
import type {
ListActiveRoomsResult,
ListParticipantsResult,
LiveKitServerError,
} from '@app/api/infrastructure/ILiveKitService';
import type {ListParticipantsResult} from '@app/api/infrastructure/ILiveKitService';
import {ILiveKitService} from '@app/api/infrastructure/ILiveKitService';
import {Logger} from '@app/api/Logger';
import type {VoiceRegionMetadata, VoiceServerRecord} from '@app/api/voice/VoiceModel';
@@ -339,39 +335,6 @@ export class LiveKitService extends ILiveKitService {
}
}
async listActiveRooms(): Promise<ListActiveRoomsResult> {
const rooms: ListActiveRoomsResult['rooms'] = [];
const errors: Array<LiveKitServerError> = [];
const servers = this.getActiveServerClients();
for (const server of servers) {
try {
const liveRooms = await server.roomServiceClient.listRooms();
for (const room of liveRooms) {
if (typeof room.name !== 'string' || room.name.length === 0) {
continue;
}
rooms.push({
roomName: room.name,
regionId: server.regionId,
serverId: server.serverId,
});
}
} catch (error) {
Logger.warn(
{error, regionId: server.regionId, serverId: server.serverId},
'LiveKit listRooms failed during voice reconciliation',
);
errors.push(this.toServerError(server.regionId, server.serverId, error));
}
}
return {
rooms,
errors,
searchedServers: servers.length,
completed: errors.length === 0,
};
}
private static isHttp404(error: unknown): boolean {
return LiveKitService.getHttpStatus(error) === 404;
}
@@ -396,34 +359,6 @@ export class LiveKitService extends ILiveKitService {
return region.get(serverId) ?? null;
}
private getActiveServerClients(): Array<ServerClientConfig & {regionId: string; serverId: string}> {
const servers: Array<ServerClientConfig & {regionId: string; serverId: string}> = [];
for (const [regionId, region] of this.serverClients.entries()) {
for (const [serverId, server] of region.entries()) {
if (server.isActive) {
servers.push({...server, regionId, serverId});
}
}
}
return servers.sort((left, right) => {
const regionComparison = left.regionId.localeCompare(right.regionId);
if (regionComparison !== 0) {
return regionComparison;
}
return left.serverId.localeCompare(right.serverId);
});
}
private toServerError(regionId: string, serverId: string, error: unknown): LiveKitServerError {
const status = LiveKitService.getHttpStatus(error);
return {
regionId,
serverId,
errorCode: error instanceof Error ? error.message : 'unknown',
retryable: status != null && status >= 500,
};
}
getDefaultRegionId(): string | null {
return this.topology.getDefaultRegionId();
}
@@ -92,7 +92,7 @@ export class LiveKitWebhookService {
roomName: data.event.room?.name ?? null,
eventType: data.event.event,
},
'LiveKit webhook reports dropped events - reconciliation may be needed',
'LiveKit webhook reports dropped events',
);
}
await this.processEvent(data);
@@ -256,7 +256,7 @@ export class LiveKitWebhookService {
error: result.error,
participantIdentity: participant.identity,
},
'LiveKit participant_joined did not match gateway state; leaving participant connected for reconciliation',
'LiveKit participant_joined did not match gateway state; leaving participant connected',
);
return;
}
@@ -20,7 +20,6 @@ import {GuildRepository} from '@app/api/guild/repositories/GuildRepository';
import {GuildRoleRepository} from '@app/api/guild/repositories/GuildRoleRepository';
import {
type CallData,
type GatewayActiveVoiceRooms,
type GatewayChannelMention,
type GatewayGuildMemoryStats,
type GatewayMentionSources,
@@ -168,25 +167,6 @@ export class NoopGatewayService extends IGatewayService {
};
}
async getActiveVoiceRooms(): Promise<GatewayActiveVoiceRooms> {
return {
nodeCount: 1,
rooms: Array.from(this.voiceStatesByChannel.entries()).flatMap(([key, voiceStates]) => {
if (voiceStates.length === 0) {
return [];
}
const [guildIdText, channelIdText] = key.split(':');
return [
{
guildId: guildIdText === 'dm' ? undefined : (BigInt(guildIdText) as GuildID),
channelId: BigInt(channelIdText) as ChannelID,
voiceStateCount: voiceStates.length,
},
];
}),
};
}
async getUsersToMentionByRoles(_params: {
guildId: GuildID;
channelId: ChannelID;
@@ -882,19 +862,6 @@ export class NoopGatewayService extends IGatewayService {
return {success: false};
}
async repairVoiceStateFromCache(_params: {
guildId?: GuildID;
channelId: ChannelID;
userId: UserID;
connectionId: string;
}): Promise<{
success: boolean;
repaired?: boolean;
error?: string;
}> {
return {success: false};
}
async getVoiceStatesForChannel(params: {guildId?: GuildID; channelId: ChannelID}): Promise<{
voiceStates: Array<GatewayVoiceStateEntry>;
}> {