mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-07 19:22:14 +09:00
chore(voice): remove voice reconciliation leftovers (#2810)
This commit is contained in:
@@ -413,7 +413,6 @@ services:
|
|||||||
NODE_OPTIONS: --enable-source-maps${FLUXER_WORKER_NODE_HEAP_MB:+ --max-old-space-size=$FLUXER_WORKER_NODE_HEAP_MB}
|
NODE_OPTIONS: --enable-source-maps${FLUXER_WORKER_NODE_HEAP_MB:+ --max-old-space-size=$FLUXER_WORKER_NODE_HEAP_MB}
|
||||||
FLUXER_API_WORKER_MODE: all_lanes
|
FLUXER_API_WORKER_MODE: all_lanes
|
||||||
FLUXER_API_WORKER_ENABLE_CRON_SCHEDULER: "true"
|
FLUXER_API_WORKER_ENABLE_CRON_SCHEDULER: "true"
|
||||||
FLUXER_API_WORKER_ENABLE_VOICE_RECONCILIATION: "true"
|
|
||||||
FLUXER_POSTGRES_MAX_CONNECTIONS: "25"
|
FLUXER_POSTGRES_MAX_CONNECTIONS: "25"
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "node", "-e", "const age=Date.now()-require('node:fs').statSync('/tmp/fluxer-worker-heartbeat').mtimeMs;if(age>30000){console.error('worker heartbeat is '+Math.round(age)+'ms old');process.exit(1)}"]
|
test: ["CMD", "node", "-e", "const age=Date.now()-require('node:fs').statSync('/tmp/fluxer-worker-heartbeat').mtimeMs;if(age>30000){console.error('worker heartbeat is '+Math.round(age)+'ms old');process.exit(1)}"]
|
||||||
|
|||||||
@@ -1,11 +1,7 @@
|
|||||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
import type {ChannelID, GuildID, UserID} from '@app/api/BrandedTypes';
|
import type {ChannelID, GuildID, UserID} from '@app/api/BrandedTypes';
|
||||||
import type {
|
import type {ILiveKitService, ListParticipantsResult} from '@app/api/infrastructure/ILiveKitService';
|
||||||
ILiveKitService,
|
|
||||||
ListActiveRoomsResult,
|
|
||||||
ListParticipantsResult,
|
|
||||||
} from '@app/api/infrastructure/ILiveKitService';
|
|
||||||
import type {VoiceRegionMetadata, VoiceServerRecord} from '@app/api/voice/VoiceModel';
|
import type {VoiceRegionMetadata, VoiceServerRecord} from '@app/api/voice/VoiceModel';
|
||||||
|
|
||||||
interface CreateTokenParams {
|
interface CreateTokenParams {
|
||||||
@@ -78,10 +74,6 @@ export class DisabledLiveKitService implements ILiveKitService {
|
|||||||
return {status: 'ok', participants: []};
|
return {status: 'ok', participants: []};
|
||||||
}
|
}
|
||||||
|
|
||||||
async listActiveRooms(): Promise<ListActiveRoomsResult> {
|
|
||||||
return {rooms: [], errors: [], searchedServers: 0, completed: true};
|
|
||||||
}
|
|
||||||
|
|
||||||
getDefaultRegionId(): string | null {
|
getDefaultRegionId(): string | null {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
import type {ChannelID, GuildID, MessageID, RoleID, UserID} from '@app/api/BrandedTypes';
|
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 {SYSTEM_USER_ID} from '@app/api/constants/Core';
|
||||||
import type {GatewayDispatchEvent} from '@app/api/constants/Gateway';
|
import type {GatewayDispatchEvent} from '@app/api/constants/Gateway';
|
||||||
import {GatewayRpcClient} from '@app/api/infrastructure/GatewayRpcClient';
|
import {GatewayRpcClient} from '@app/api/infrastructure/GatewayRpcClient';
|
||||||
import {GatewayRpcMethodError, GatewayRpcMethodErrorCodes} from '@app/api/infrastructure/GatewayRpcError';
|
import {GatewayRpcMethodError, GatewayRpcMethodErrorCodes} from '@app/api/infrastructure/GatewayRpcError';
|
||||||
import type {
|
import type {
|
||||||
CallData,
|
CallData,
|
||||||
GatewayActiveVoiceRooms,
|
|
||||||
GatewayChannelMention,
|
GatewayChannelMention,
|
||||||
GatewayGuildMemoryStats,
|
GatewayGuildMemoryStats,
|
||||||
GatewayMentionSources,
|
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> {
|
async getUserPermissions({guildId, userId, channelId}: UserPermissionsParams): Promise<bigint> {
|
||||||
const result = await this.call<{
|
const result = await this.call<{
|
||||||
permissions: string;
|
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<{
|
async getVoiceStatesForChannel({guildId, channelId}: {guildId?: GuildID; channelId: ChannelID}): Promise<{
|
||||||
voiceStates: Array<GatewayVoiceStateEntry>;
|
voiceStates: Array<GatewayVoiceStateEntry>;
|
||||||
}> {
|
}> {
|
||||||
|
|||||||
@@ -88,17 +88,6 @@ export interface GatewayVoiceStateCounts {
|
|||||||
servers: Array<GatewayVoiceStateServerCount>;
|
servers: Array<GatewayVoiceStateServerCount>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GatewayActiveVoiceRoom {
|
|
||||||
guildId?: GuildID;
|
|
||||||
channelId: ChannelID;
|
|
||||||
voiceStateCount: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface GatewayActiveVoiceRooms {
|
|
||||||
rooms: Array<GatewayActiveVoiceRoom>;
|
|
||||||
nodeCount: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface GatewayVoiceStateEntry {
|
export interface GatewayVoiceStateEntry {
|
||||||
connectionId: string;
|
connectionId: string;
|
||||||
userId: string;
|
userId: string;
|
||||||
@@ -162,8 +151,6 @@ export abstract class IGatewayService {
|
|||||||
|
|
||||||
abstract getVoiceStateCounts(): Promise<GatewayVoiceStateCounts>;
|
abstract getVoiceStateCounts(): Promise<GatewayVoiceStateCounts>;
|
||||||
|
|
||||||
abstract getActiveVoiceRooms(): Promise<GatewayActiveVoiceRooms>;
|
|
||||||
|
|
||||||
abstract getUsersToMentionByRoles(params: {
|
abstract getUsersToMentionByRoles(params: {
|
||||||
guildId: GuildID;
|
guildId: GuildID;
|
||||||
channelId: ChannelID;
|
channelId: ChannelID;
|
||||||
@@ -345,17 +332,6 @@ export abstract class IGatewayService {
|
|||||||
error?: string;
|
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<{
|
abstract getVoiceStatesForChannel(params: {guildId?: GuildID; channelId: ChannelID}): Promise<{
|
||||||
voiceStates: Array<GatewayVoiceStateEntry>;
|
voiceStates: Array<GatewayVoiceStateEntry>;
|
||||||
}>;
|
}>;
|
||||||
|
|||||||
@@ -61,13 +61,6 @@ interface ListParticipantsParams {
|
|||||||
serverId: string;
|
serverId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LiveKitServerError {
|
|
||||||
regionId: string;
|
|
||||||
serverId: string;
|
|
||||||
errorCode: string;
|
|
||||||
retryable: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ListParticipantsSuccess {
|
export interface ListParticipantsSuccess {
|
||||||
status: 'ok';
|
status: 'ok';
|
||||||
participants: Array<{
|
participants: Array<{
|
||||||
@@ -84,19 +77,6 @@ export interface ListParticipantsError {
|
|||||||
|
|
||||||
export type ListParticipantsResult = ListParticipantsSuccess | 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 {
|
export abstract class ILiveKitService {
|
||||||
abstract createToken(params: CreateTokenParams): Promise<{
|
abstract createToken(params: CreateTokenParams): Promise<{
|
||||||
token: string;
|
token: string;
|
||||||
@@ -111,8 +91,6 @@ export abstract class ILiveKitService {
|
|||||||
|
|
||||||
abstract listParticipants(params: ListParticipantsParams): Promise<ListParticipantsResult>;
|
abstract listParticipants(params: ListParticipantsParams): Promise<ListParticipantsResult>;
|
||||||
|
|
||||||
abstract listActiveRooms(): Promise<ListActiveRoomsResult>;
|
|
||||||
|
|
||||||
abstract getDefaultRegionId(): string | null;
|
abstract getDefaultRegionId(): string | null;
|
||||||
|
|
||||||
abstract getRegionMetadata(): Array<VoiceRegionMetadata>;
|
abstract getRegionMetadata(): Array<VoiceRegionMetadata>;
|
||||||
|
|||||||
@@ -2,11 +2,7 @@
|
|||||||
|
|
||||||
import type {ChannelID, GuildID, UserID} from '@app/api/BrandedTypes';
|
import type {ChannelID, GuildID, UserID} from '@app/api/BrandedTypes';
|
||||||
import {Config} from '@app/api/Config';
|
import {Config} from '@app/api/Config';
|
||||||
import type {
|
import type {ListParticipantsResult} from '@app/api/infrastructure/ILiveKitService';
|
||||||
ListActiveRoomsResult,
|
|
||||||
ListParticipantsResult,
|
|
||||||
LiveKitServerError,
|
|
||||||
} from '@app/api/infrastructure/ILiveKitService';
|
|
||||||
import {ILiveKitService} from '@app/api/infrastructure/ILiveKitService';
|
import {ILiveKitService} from '@app/api/infrastructure/ILiveKitService';
|
||||||
import {Logger} from '@app/api/Logger';
|
import {Logger} from '@app/api/Logger';
|
||||||
import type {VoiceRegionMetadata, VoiceServerRecord} from '@app/api/voice/VoiceModel';
|
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 {
|
private static isHttp404(error: unknown): boolean {
|
||||||
return LiveKitService.getHttpStatus(error) === 404;
|
return LiveKitService.getHttpStatus(error) === 404;
|
||||||
}
|
}
|
||||||
@@ -396,34 +359,6 @@ export class LiveKitService extends ILiveKitService {
|
|||||||
return region.get(serverId) ?? null;
|
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 {
|
getDefaultRegionId(): string | null {
|
||||||
return this.topology.getDefaultRegionId();
|
return this.topology.getDefaultRegionId();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ export class LiveKitWebhookService {
|
|||||||
roomName: data.event.room?.name ?? null,
|
roomName: data.event.room?.name ?? null,
|
||||||
eventType: data.event.event,
|
eventType: data.event.event,
|
||||||
},
|
},
|
||||||
'LiveKit webhook reports dropped events - reconciliation may be needed',
|
'LiveKit webhook reports dropped events',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
await this.processEvent(data);
|
await this.processEvent(data);
|
||||||
@@ -256,7 +256,7 @@ export class LiveKitWebhookService {
|
|||||||
error: result.error,
|
error: result.error,
|
||||||
participantIdentity: participant.identity,
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ import {GuildRepository} from '@app/api/guild/repositories/GuildRepository';
|
|||||||
import {GuildRoleRepository} from '@app/api/guild/repositories/GuildRoleRepository';
|
import {GuildRoleRepository} from '@app/api/guild/repositories/GuildRoleRepository';
|
||||||
import {
|
import {
|
||||||
type CallData,
|
type CallData,
|
||||||
type GatewayActiveVoiceRooms,
|
|
||||||
type GatewayChannelMention,
|
type GatewayChannelMention,
|
||||||
type GatewayGuildMemoryStats,
|
type GatewayGuildMemoryStats,
|
||||||
type GatewayMentionSources,
|
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: {
|
async getUsersToMentionByRoles(_params: {
|
||||||
guildId: GuildID;
|
guildId: GuildID;
|
||||||
channelId: ChannelID;
|
channelId: ChannelID;
|
||||||
@@ -882,19 +862,6 @@ export class NoopGatewayService extends IGatewayService {
|
|||||||
return {success: false};
|
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<{
|
async getVoiceStatesForChannel(params: {guildId?: GuildID; channelId: ChannelID}): Promise<{
|
||||||
voiceStates: Array<GatewayVoiceStateEntry>;
|
voiceStates: Array<GatewayVoiceStateEntry>;
|
||||||
}> {
|
}> {
|
||||||
|
|||||||
@@ -75,7 +75,6 @@ route(M, P) ->
|
|||||||
<<"guild.disconnect_voice_user_if_in_channel">> => fun gateway_rpc_guild_voice:handle/2,
|
<<"guild.disconnect_voice_user_if_in_channel">> => fun gateway_rpc_guild_voice:handle/2,
|
||||||
<<"guild.disconnect_all_voice_users_in_channel">> => fun gateway_rpc_guild_voice:handle/2,
|
<<"guild.disconnect_all_voice_users_in_channel">> => fun gateway_rpc_guild_voice:handle/2,
|
||||||
<<"guild.confirm_voice_connection_from_livekit">> => fun gateway_rpc_guild_voice:handle/2,
|
<<"guild.confirm_voice_connection_from_livekit">> => fun gateway_rpc_guild_voice:handle/2,
|
||||||
<<"guild.repair_voice_state_from_cache">> => fun gateway_rpc_guild_voice:handle/2,
|
|
||||||
<<"guild.get_voice_states_for_channel">> => fun gateway_rpc_guild_voice:handle/2,
|
<<"guild.get_voice_states_for_channel">> => fun gateway_rpc_guild_voice:handle/2,
|
||||||
<<"guild.get_pending_joins_for_channel">> => fun gateway_rpc_guild_voice:handle/2,
|
<<"guild.get_pending_joins_for_channel">> => fun gateway_rpc_guild_voice:handle/2,
|
||||||
<<"guild.move_member">> => fun gateway_rpc_guild_voice:handle/2,
|
<<"guild.move_member">> => fun gateway_rpc_guild_voice:handle/2,
|
||||||
|
|||||||
@@ -24,8 +24,6 @@ handle(<<"guild.disconnect_all_voice_users_in_channel">>, P) ->
|
|||||||
handle_disconnect_all_in_channel(P);
|
handle_disconnect_all_in_channel(P);
|
||||||
handle(<<"guild.confirm_voice_connection_from_livekit">>, P) ->
|
handle(<<"guild.confirm_voice_connection_from_livekit">>, P) ->
|
||||||
handle_confirm_connection(P);
|
handle_confirm_connection(P);
|
||||||
handle(<<"guild.repair_voice_state_from_cache">>, P) ->
|
|
||||||
handle_repair_state(P);
|
|
||||||
handle(<<"guild.get_voice_states_for_channel">>, P) ->
|
handle(<<"guild.get_voice_states_for_channel">>, P) ->
|
||||||
handle_get_voice_states(P);
|
handle_get_voice_states(P);
|
||||||
handle(<<"guild.get_pending_joins_for_channel">>, P) ->
|
handle(<<"guild.get_pending_joins_for_channel">>, P) ->
|
||||||
@@ -167,27 +165,6 @@ format_confirm_result({error, _, EA}) ->
|
|||||||
format_confirm_result(#{error := E}) ->
|
format_confirm_result(#{error := E}) ->
|
||||||
raise_voice_error(E).
|
raise_voice_error(E).
|
||||||
|
|
||||||
-spec handle_repair_state(map()) -> term().
|
|
||||||
handle_repair_state(P) ->
|
|
||||||
GuildId = validation:snowflake_or_throw(<<"guild_id">>, maps:get(<<"guild_id">>, P)),
|
|
||||||
ChannelId = validation:snowflake_or_throw(<<"channel_id">>, maps:get(<<"channel_id">>, P)),
|
|
||||||
UserId = validation:snowflake_or_throw(<<"user_id">>, maps:get(<<"user_id">>, P)),
|
|
||||||
gateway_rpc_guild_infra:with_voice_server(GuildId, fun(VPid, _) ->
|
|
||||||
repair_voice_state(VPid, P, ChannelId, UserId)
|
|
||||||
end).
|
|
||||||
|
|
||||||
-spec repair_voice_state(pid(), map(), integer(), integer()) -> map().
|
|
||||||
repair_voice_state(VPid, P, ChannelId, UserId) ->
|
|
||||||
Req = #{
|
|
||||||
connection_id => maps:get(<<"connection_id">>, P),
|
|
||||||
channel_id => ChannelId,
|
|
||||||
user_id => UserId
|
|
||||||
},
|
|
||||||
Result = gen_server:call(
|
|
||||||
VPid, {repair_voice_state_from_guild_cache, Req}, ?GUILD_CALL_TIMEOUT
|
|
||||||
),
|
|
||||||
gateway_rpc_guild_voice_util:handle_repair_result(Result).
|
|
||||||
|
|
||||||
-spec handle_get_voice_states(map()) -> term().
|
-spec handle_get_voice_states(map()) -> term().
|
||||||
handle_get_voice_states(P) ->
|
handle_get_voice_states(P) ->
|
||||||
case validate_gc_params(P) of
|
case validate_gc_params(P) of
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
|
|
||||||
-export([
|
-export([
|
||||||
handle_move_member_result/4,
|
handle_move_member_result/4,
|
||||||
handle_repair_result/1,
|
|
||||||
normalize_voice_rpc_error/1,
|
normalize_voice_rpc_error/1,
|
||||||
parse_voice_update/1,
|
parse_voice_update/1,
|
||||||
process_voice_update/1
|
process_voice_update/1
|
||||||
@@ -58,18 +57,6 @@ cleanup_virtual_access_on_disconnect(DisconnectedUserId, Pid) ->
|
|||||||
spawn(fun() -> guild_voice:cleanup_virtual_access_on_disconnect(DisconnectedUserId, Pid) end),
|
spawn(fun() -> guild_voice:cleanup_virtual_access_on_disconnect(DisconnectedUserId, Pid) end),
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
-spec handle_repair_result(term()) -> map().
|
|
||||||
handle_repair_result(#{success := true, repaired := Repaired}) ->
|
|
||||||
#{<<"success">> => true, <<"repaired">> => Repaired};
|
|
||||||
handle_repair_result(#{success := true}) ->
|
|
||||||
#{<<"success">> => true};
|
|
||||||
handle_repair_result(#{success := false, error := Error}) ->
|
|
||||||
#{<<"success">> => false, <<"error">> => normalize_voice_rpc_error(Error)};
|
|
||||||
handle_repair_result(#{error := Error}) ->
|
|
||||||
gateway_rpc_error:raise(normalize_voice_rpc_error(Error));
|
|
||||||
handle_repair_result(_) ->
|
|
||||||
#{<<"success">> => false, <<"error">> => <<"repair_voice_state_error">>}.
|
|
||||||
|
|
||||||
-spec voice_session_data([term()]) -> [map()].
|
-spec voice_session_data([term()]) -> [map()].
|
||||||
voice_session_data(SessionData) ->
|
voice_session_data(SessionData) ->
|
||||||
[Entry || Entry <- SessionData, is_map(Entry)].
|
[Entry || Entry <- SessionData, is_map(Entry)].
|
||||||
|
|||||||
@@ -8,8 +8,7 @@
|
|||||||
get_local_node_id/0,
|
get_local_node_id/0,
|
||||||
get_local_node_stats/0,
|
get_local_node_stats/0,
|
||||||
get_local_memory_stats/1,
|
get_local_memory_stats/1,
|
||||||
get_local_voice_state_counts/0,
|
get_local_voice_state_counts/0
|
||||||
get_local_active_voice_rooms/0
|
|
||||||
]).
|
]).
|
||||||
|
|
||||||
-define(DEFAULT_MEMORY_STATS_LIMIT, 100).
|
-define(DEFAULT_MEMORY_STATS_LIMIT, 100).
|
||||||
@@ -31,9 +30,6 @@ execute_method(<<"process.node_stats">>, _Params) ->
|
|||||||
execute_method(<<"process.voice_state_counts">>, _Params) ->
|
execute_method(<<"process.voice_state_counts">>, _Params) ->
|
||||||
ActiveNodes = voice_state_count_nodes(),
|
ActiveNodes = voice_state_count_nodes(),
|
||||||
gateway_rpc_misc_presence:collect_and_aggregate_voice_state_counts(ActiveNodes);
|
gateway_rpc_misc_presence:collect_and_aggregate_voice_state_counts(ActiveNodes);
|
||||||
execute_method(<<"process.active_voice_rooms">>, _Params) ->
|
|
||||||
ActiveNodes = gateway_node_router:active_nodes(),
|
|
||||||
gateway_rpc_misc_push:collect_and_aggregate_active_voice_rooms(ActiveNodes);
|
|
||||||
execute_method(Method, _Params) ->
|
execute_method(Method, _Params) ->
|
||||||
gateway_rpc_error:raise(<<"Unknown method: ", Method/binary>>).
|
gateway_rpc_error:raise(<<"Unknown method: ", Method/binary>>).
|
||||||
|
|
||||||
@@ -53,10 +49,6 @@ get_local_memory_stats(Limit) ->
|
|||||||
get_local_voice_state_counts() ->
|
get_local_voice_state_counts() ->
|
||||||
gateway_rpc_misc_presence:get_local_voice_state_counts().
|
gateway_rpc_misc_presence:get_local_voice_state_counts().
|
||||||
|
|
||||||
-spec get_local_active_voice_rooms() -> map().
|
|
||||||
get_local_active_voice_rooms() ->
|
|
||||||
gateway_rpc_misc_push:get_local_active_voice_rooms().
|
|
||||||
|
|
||||||
-spec parse_memory_stats_limit(map()) -> pos_integer().
|
-spec parse_memory_stats_limit(map()) -> pos_integer().
|
||||||
parse_memory_stats_limit(Params) ->
|
parse_memory_stats_limit(Params) ->
|
||||||
Limit =
|
Limit =
|
||||||
|
|||||||
@@ -1,356 +0,0 @@
|
|||||||
%% SPDX-License-Identifier: AGPL-3.0-or-later
|
|
||||||
|
|
||||||
-module(gateway_rpc_misc_push).
|
|
||||||
-typing([eqwalizer]).
|
|
||||||
|
|
||||||
-export([
|
|
||||||
get_local_active_voice_rooms/0,
|
|
||||||
collect_and_aggregate_active_voice_rooms/1
|
|
||||||
]).
|
|
||||||
|
|
||||||
-define(NODE_RPC_TIMEOUT, 10000).
|
|
||||||
|
|
||||||
-spec get_local_active_voice_rooms() -> map().
|
|
||||||
get_local_active_voice_rooms() ->
|
|
||||||
#{
|
|
||||||
<<"node_id">> => gateway_rpc_misc_session:node_id(node()),
|
|
||||||
<<"rooms">> => get_local_guild_active_voice_rooms() ++ get_local_dm_active_voice_rooms()
|
|
||||||
}.
|
|
||||||
|
|
||||||
-spec collect_and_aggregate_active_voice_rooms([node()]) -> map().
|
|
||||||
collect_and_aggregate_active_voice_rooms(Nodes) ->
|
|
||||||
NodeRooms = [
|
|
||||||
fetch_node_active_voice_rooms(N)
|
|
||||||
|| N <- gateway_rpc_misc_session:normalize_nodes(Nodes)
|
|
||||||
],
|
|
||||||
aggregate_active_voice_rooms(NodeRooms).
|
|
||||||
|
|
||||||
-spec get_local_guild_active_voice_rooms() -> [map()].
|
|
||||||
get_local_guild_active_voice_rooms() ->
|
|
||||||
try
|
|
||||||
ets:foldl(
|
|
||||||
fun
|
|
||||||
({GuildId, Pid}, Acc) when is_integer(GuildId), is_pid(Pid) ->
|
|
||||||
get_guild_active_voice_rooms(GuildId, Pid) ++ Acc;
|
|
||||||
(_Row, Acc) ->
|
|
||||||
Acc
|
|
||||||
end,
|
|
||||||
[],
|
|
||||||
guild_voice_registry
|
|
||||||
)
|
|
||||||
catch
|
|
||||||
error:badarg ->
|
|
||||||
[]
|
|
||||||
end.
|
|
||||||
|
|
||||||
-spec get_guild_active_voice_rooms(integer(), pid()) -> [map()].
|
|
||||||
get_guild_active_voice_rooms(GuildId, Pid) ->
|
|
||||||
case process_liveness:is_alive(Pid) of
|
|
||||||
true -> fetch_guild_voice_states(GuildId, Pid);
|
|
||||||
false -> []
|
|
||||||
end.
|
|
||||||
|
|
||||||
-spec fetch_guild_voice_states(integer(), pid()) -> [map()].
|
|
||||||
fetch_guild_voice_states(GuildId, Pid) ->
|
|
||||||
try gen_server:call(Pid, {get_voice_states_map}, 500) of
|
|
||||||
VoiceStates when is_map(VoiceStates) ->
|
|
||||||
build_guild_voice_room_entries(GuildId, VoiceStates);
|
|
||||||
_ ->
|
|
||||||
[]
|
|
||||||
catch
|
|
||||||
_:_ -> []
|
|
||||||
end.
|
|
||||||
|
|
||||||
-spec build_guild_voice_room_entries(integer(), map()) -> [map()].
|
|
||||||
build_guild_voice_room_entries(GuildId, VoiceStates) ->
|
|
||||||
Counts = maps:fold(
|
|
||||||
fun(_ConnId, VoiceState, Acc) ->
|
|
||||||
fold_voice_state(VoiceState, Acc)
|
|
||||||
end,
|
|
||||||
#{},
|
|
||||||
VoiceStates
|
|
||||||
),
|
|
||||||
[
|
|
||||||
#{
|
|
||||||
<<"guild_id">> => integer_to_binary(GuildId),
|
|
||||||
<<"channel_id">> => ChannelId,
|
|
||||||
<<"voice_state_count">> => Count
|
|
||||||
}
|
|
||||||
|| {ChannelId, Count} <- lists:sort(maps:to_list(Counts)),
|
|
||||||
Count > 0
|
|
||||||
].
|
|
||||||
|
|
||||||
-spec fold_voice_state(term(), map()) -> map().
|
|
||||||
fold_voice_state(State, Acc) when is_map(State) ->
|
|
||||||
ChannelId = normalize_id(maps:get(<<"channel_id">>, State, undefined)),
|
|
||||||
increment_channel_count(ChannelId, Acc);
|
|
||||||
fold_voice_state(_, Acc) ->
|
|
||||||
Acc.
|
|
||||||
|
|
||||||
-spec increment_channel_count(binary() | undefined, map()) -> map().
|
|
||||||
increment_channel_count(undefined, Acc) ->
|
|
||||||
Acc;
|
|
||||||
increment_channel_count(ChannelId, Acc) ->
|
|
||||||
maps:update_with(ChannelId, fun(C) -> C + 1 end, 1, Acc).
|
|
||||||
|
|
||||||
-spec get_local_dm_active_voice_rooms() -> [map()].
|
|
||||||
get_local_dm_active_voice_rooms() ->
|
|
||||||
RawIds = shard_utils:safe_apply(fun call_manager:local_call_ids/0, []),
|
|
||||||
CallIds = integer_entries(RawIds),
|
|
||||||
lists:flatmap(fun get_dm_active_voice_room/1, CallIds).
|
|
||||||
|
|
||||||
-spec integer_entries(term()) -> [integer()].
|
|
||||||
integer_entries(Values) when is_list(Values) ->
|
|
||||||
[Value || Value <- Values, is_integer(Value)];
|
|
||||||
integer_entries(_) ->
|
|
||||||
[].
|
|
||||||
|
|
||||||
-spec get_dm_active_voice_room(integer()) -> [map()].
|
|
||||||
get_dm_active_voice_room(ChannelId) ->
|
|
||||||
case lookup_call(ChannelId) of
|
|
||||||
{ok, Pid} -> get_dm_room_from_pid(ChannelId, Pid);
|
|
||||||
_ -> []
|
|
||||||
end.
|
|
||||||
|
|
||||||
-spec lookup_call(integer()) -> {ok, pid()} | error.
|
|
||||||
lookup_call(ChannelId) ->
|
|
||||||
try call_manager:lookup(ChannelId) of
|
|
||||||
{ok, Pid} when is_pid(Pid) -> {ok, Pid};
|
|
||||||
_ -> error
|
|
||||||
catch
|
|
||||||
_:_ -> error
|
|
||||||
end.
|
|
||||||
|
|
||||||
-spec get_dm_room_from_pid(integer(), pid()) -> [map()].
|
|
||||||
get_dm_room_from_pid(ChannelId, Pid) ->
|
|
||||||
try gen_server:call(Pid, {get_state}, 500) of
|
|
||||||
{ok, CallData} when is_map(CallData) ->
|
|
||||||
build_dm_room_entry(ChannelId, CallData);
|
|
||||||
_ ->
|
|
||||||
[]
|
|
||||||
catch
|
|
||||||
_:_ -> []
|
|
||||||
end.
|
|
||||||
|
|
||||||
-spec build_dm_room_entry(integer(), map()) -> [map()].
|
|
||||||
build_dm_room_entry(ChannelId, CallData) ->
|
|
||||||
VoiceStates = maps:get(
|
|
||||||
voice_states,
|
|
||||||
CallData,
|
|
||||||
maps:get(<<"voice_states">>, CallData, [])
|
|
||||||
),
|
|
||||||
Count =
|
|
||||||
case VoiceStates of
|
|
||||||
Values when is_list(Values) -> length(Values);
|
|
||||||
_ -> 0
|
|
||||||
end,
|
|
||||||
case Count > 0 of
|
|
||||||
true ->
|
|
||||||
[
|
|
||||||
#{
|
|
||||||
<<"guild_id">> => null,
|
|
||||||
<<"channel_id">> => integer_to_binary(ChannelId),
|
|
||||||
<<"voice_state_count">> => Count
|
|
||||||
}
|
|
||||||
];
|
|
||||||
false ->
|
|
||||||
[]
|
|
||||||
end.
|
|
||||||
|
|
||||||
-spec fetch_node_active_voice_rooms(node()) -> map().
|
|
||||||
fetch_node_active_voice_rooms(TargetNode) ->
|
|
||||||
case
|
|
||||||
gateway_rpc_misc_session:safe_node_call(
|
|
||||||
TargetNode, get_local_active_voice_rooms, [], ?NODE_RPC_TIMEOUT
|
|
||||||
)
|
|
||||||
of
|
|
||||||
#{<<"rooms">> := Rooms} = Result when is_list(Rooms) ->
|
|
||||||
Result#{<<"node_id">> => gateway_rpc_misc_session:node_id(TargetNode)};
|
|
||||||
_ ->
|
|
||||||
#{
|
|
||||||
<<"node_id">> => gateway_rpc_misc_session:node_id(TargetNode),
|
|
||||||
<<"rooms">> => []
|
|
||||||
}
|
|
||||||
end.
|
|
||||||
|
|
||||||
-spec aggregate_active_voice_rooms([term()]) -> map().
|
|
||||||
aggregate_active_voice_rooms(NodeRooms) ->
|
|
||||||
{RoomCounts, NodeCount} = lists:foldl(
|
|
||||||
fun merge_node_rooms/2,
|
|
||||||
{#{}, 0},
|
|
||||||
NodeRooms
|
|
||||||
),
|
|
||||||
Rooms = [
|
|
||||||
build_room_entry(RoomKey, Count)
|
|
||||||
|| {RoomKey, Count} <- lists:sort(maps:to_list(RoomCounts)),
|
|
||||||
Count > 0
|
|
||||||
],
|
|
||||||
#{<<"node_count">> => NodeCount, <<"rooms">> => Rooms}.
|
|
||||||
|
|
||||||
-spec merge_node_rooms(term(), {map(), non_neg_integer()}) -> {map(), non_neg_integer()}.
|
|
||||||
merge_node_rooms(NodeRooms, {RoomAcc, NodeCount}) when is_map(NodeRooms) ->
|
|
||||||
Rooms = maps:get(<<"rooms">>, NodeRooms, []),
|
|
||||||
MergedRooms =
|
|
||||||
case Rooms of
|
|
||||||
RoomList when is_list(RoomList) ->
|
|
||||||
lists:foldl(fun merge_single_room/2, RoomAcc, RoomList);
|
|
||||||
_ ->
|
|
||||||
RoomAcc
|
|
||||||
end,
|
|
||||||
{MergedRooms, NodeCount + 1};
|
|
||||||
merge_node_rooms(_, Acc) ->
|
|
||||||
Acc.
|
|
||||||
|
|
||||||
-spec merge_single_room(term(), map()) -> map().
|
|
||||||
merge_single_room(Room, Acc) when is_map(Room) ->
|
|
||||||
ChannelId = normalize_id(maps:get(<<"channel_id">>, Room, undefined)),
|
|
||||||
GuildIdResult = normalize_room_guild_id(maps:get(<<"guild_id">>, Room, null)),
|
|
||||||
Count = gateway_rpc_misc_session:decode_integer(
|
|
||||||
maps:get(<<"voice_state_count">>, Room, 0)
|
|
||||||
),
|
|
||||||
apply_room_merge(ChannelId, GuildIdResult, Count, Acc);
|
|
||||||
merge_single_room(_, Acc) ->
|
|
||||||
Acc.
|
|
||||||
|
|
||||||
-spec apply_room_merge(
|
|
||||||
binary() | undefined, {ok, binary() | null} | error, non_neg_integer(), map()
|
|
||||||
) -> map().
|
|
||||||
apply_room_merge(undefined, _, _, Acc) ->
|
|
||||||
Acc;
|
|
||||||
apply_room_merge(_, error, _, Acc) ->
|
|
||||||
Acc;
|
|
||||||
apply_room_merge(_, _, Count, Acc) when Count =< 0 -> Acc;
|
|
||||||
apply_room_merge(ChannelId, {ok, GuildId}, Count, Acc) ->
|
|
||||||
Key =
|
|
||||||
case GuildId of
|
|
||||||
null -> {dm, ChannelId};
|
|
||||||
_ -> {guild, GuildId, ChannelId}
|
|
||||||
end,
|
|
||||||
maps:update_with(Key, fun(Current) -> Current + Count end, Count, Acc).
|
|
||||||
|
|
||||||
-spec build_room_entry(tuple(), non_neg_integer()) -> map().
|
|
||||||
build_room_entry({dm, ChannelId}, Count) ->
|
|
||||||
#{
|
|
||||||
<<"guild_id">> => null,
|
|
||||||
<<"channel_id">> => ChannelId,
|
|
||||||
<<"voice_state_count">> => Count
|
|
||||||
};
|
|
||||||
build_room_entry({guild, GuildId, ChannelId}, Count) ->
|
|
||||||
#{
|
|
||||||
<<"guild_id">> => GuildId,
|
|
||||||
<<"channel_id">> => ChannelId,
|
|
||||||
<<"voice_state_count">> => Count
|
|
||||||
}.
|
|
||||||
|
|
||||||
-spec normalize_id(term()) -> binary() | undefined.
|
|
||||||
normalize_id(Value) when is_binary(Value), byte_size(Value) > 0 ->
|
|
||||||
case validation:validate_snowflake(Value) of
|
|
||||||
{ok, Id} when Id > 0 -> integer_to_binary(Id);
|
|
||||||
_ -> undefined
|
|
||||||
end;
|
|
||||||
normalize_id(Value) when is_integer(Value), Value > 0 ->
|
|
||||||
integer_to_binary(Value);
|
|
||||||
normalize_id(Value) when is_list(Value), Value =/= [] ->
|
|
||||||
normalize_integer_id(type_conv:to_integer(Value));
|
|
||||||
normalize_id(_) ->
|
|
||||||
undefined.
|
|
||||||
|
|
||||||
-spec normalize_integer_id(integer() | undefined) -> binary() | undefined.
|
|
||||||
normalize_integer_id(Id) when is_integer(Id), Id > 0 ->
|
|
||||||
integer_to_binary(Id);
|
|
||||||
normalize_integer_id(_) ->
|
|
||||||
undefined.
|
|
||||||
|
|
||||||
-spec normalize_room_guild_id(term()) -> {ok, binary() | null} | error.
|
|
||||||
normalize_room_guild_id(null) ->
|
|
||||||
{ok, null};
|
|
||||||
normalize_room_guild_id(undefined) ->
|
|
||||||
{ok, null};
|
|
||||||
normalize_room_guild_id(Value) ->
|
|
||||||
case normalize_id(Value) of
|
|
||||||
undefined -> error;
|
|
||||||
Normalized -> {ok, Normalized}
|
|
||||||
end.
|
|
||||||
|
|
||||||
-ifdef(TEST).
|
|
||||||
-include_lib("eunit/include/eunit.hrl").
|
|
||||||
|
|
||||||
malformed_test_node_rooms() ->
|
|
||||||
[
|
|
||||||
#{
|
|
||||||
<<"rooms">> => [
|
|
||||||
#{
|
|
||||||
<<"guild_id">> => <<"10">>,
|
|
||||||
<<"channel_id">> => <<"20">>,
|
|
||||||
<<"voice_state_count">> => <<"2">>
|
|
||||||
},
|
|
||||||
#{
|
|
||||||
<<"guild_id">> => 10,
|
|
||||||
<<"channel_id">> => 20,
|
|
||||||
<<"voice_state_count">> => 3
|
|
||||||
},
|
|
||||||
#{
|
|
||||||
<<"guild_id">> => null,
|
|
||||||
<<"channel_id">> => "30",
|
|
||||||
<<"voice_state_count">> => "4"
|
|
||||||
},
|
|
||||||
#{
|
|
||||||
<<"guild_id">> => <<"bad">>,
|
|
||||||
<<"channel_id">> => <<"40">>,
|
|
||||||
<<"voice_state_count">> => 5
|
|
||||||
},
|
|
||||||
#{
|
|
||||||
<<"guild_id">> => <<"10">>,
|
|
||||||
<<"channel_id">> => <<"bad">>,
|
|
||||||
<<"voice_state_count">> => 5
|
|
||||||
},
|
|
||||||
#{
|
|
||||||
<<"guild_id">> => <<"10">>,
|
|
||||||
<<"channel_id">> => <<"50">>,
|
|
||||||
<<"voice_state_count">> => 0
|
|
||||||
},
|
|
||||||
not_a_room
|
|
||||||
]
|
|
||||||
},
|
|
||||||
#{<<"rooms">> => not_a_list},
|
|
||||||
not_a_node
|
|
||||||
].
|
|
||||||
|
|
||||||
aggregate_active_voice_rooms_ignores_malformed_nodes_and_rooms_test() ->
|
|
||||||
Aggregate = aggregate_active_voice_rooms(malformed_test_node_rooms()),
|
|
||||||
?assertEqual(2, maps:get(<<"node_count">>, Aggregate)),
|
|
||||||
?assertEqual(
|
|
||||||
[
|
|
||||||
#{
|
|
||||||
<<"guild_id">> => null,
|
|
||||||
<<"channel_id">> => <<"30">>,
|
|
||||||
<<"voice_state_count">> => 4
|
|
||||||
},
|
|
||||||
#{
|
|
||||||
<<"guild_id">> => <<"10">>,
|
|
||||||
<<"channel_id">> => <<"20">>,
|
|
||||||
<<"voice_state_count">> => 5
|
|
||||||
}
|
|
||||||
],
|
|
||||||
maps:get(<<"rooms">>, Aggregate)
|
|
||||||
).
|
|
||||||
|
|
||||||
build_guild_voice_room_entries_ignores_malformed_voice_states_test() ->
|
|
||||||
VoiceStates = #{
|
|
||||||
<<"good">> => #{<<"channel_id">> => <<"20">>},
|
|
||||||
<<"same">> => #{<<"channel_id">> => 20},
|
|
||||||
<<"bad_channel">> => #{<<"channel_id">> => <<"bad">>},
|
|
||||||
<<"not_a_map">> => not_a_map
|
|
||||||
},
|
|
||||||
?assertEqual(
|
|
||||||
[
|
|
||||||
#{
|
|
||||||
<<"guild_id">> => <<"10">>,
|
|
||||||
<<"channel_id">> => <<"20">>,
|
|
||||||
<<"voice_state_count">> => 2
|
|
||||||
}
|
|
||||||
],
|
|
||||||
build_guild_voice_room_entries(10, VoiceStates)
|
|
||||||
).
|
|
||||||
|
|
||||||
-endif.
|
|
||||||
@@ -13,8 +13,6 @@
|
|||||||
-spec execute_method(binary(), map()) -> term().
|
-spec execute_method(binary(), map()) -> term().
|
||||||
execute_method(<<"voice.confirm_connection">>, P) ->
|
execute_method(<<"voice.confirm_connection">>, P) ->
|
||||||
handle_confirm_connection(P);
|
handle_confirm_connection(P);
|
||||||
execute_method(<<"voice.repair_state_from_cache">>, P) ->
|
|
||||||
handle_repair_state(P);
|
|
||||||
execute_method(<<"voice.disconnect_user_if_in_channel">>, P) ->
|
execute_method(<<"voice.disconnect_user_if_in_channel">>, P) ->
|
||||||
handle_disconnect_if_in_channel(P);
|
handle_disconnect_if_in_channel(P);
|
||||||
execute_method(<<"voice.get_voice_states_for_channel">>, P) ->
|
execute_method(<<"voice.get_voice_states_for_channel">>, P) ->
|
||||||
@@ -46,18 +44,6 @@ handle_confirm_connection(Params) ->
|
|||||||
)
|
)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-spec handle_repair_state(map()) -> map() | term().
|
|
||||||
handle_repair_state(Params) ->
|
|
||||||
case parse_optional_guild_id(Params) of
|
|
||||||
undefined ->
|
|
||||||
#{<<"success">> => false, <<"error">> => <<"voice_not_supported">>};
|
|
||||||
GuildId ->
|
|
||||||
gateway_rpc_guild:execute_method(
|
|
||||||
<<"guild.repair_voice_state_from_cache">>,
|
|
||||||
Params#{<<"guild_id">> => integer_to_binary(GuildId)}
|
|
||||||
)
|
|
||||||
end.
|
|
||||||
|
|
||||||
-spec handle_disconnect_if_in_channel(map()) -> term().
|
-spec handle_disconnect_if_in_channel(map()) -> term().
|
||||||
handle_disconnect_if_in_channel(Params) ->
|
handle_disconnect_if_in_channel(Params) ->
|
||||||
ChannelIdBin = maps:get(<<"channel_id">>, Params),
|
ChannelIdBin = maps:get(<<"channel_id">>, Params),
|
||||||
|
|||||||
@@ -45,8 +45,6 @@ handle_call({session_connect, Request}, {CallerPid, _}, State) ->
|
|||||||
handle_session_connect_call(Request, CallerPid, State);
|
handle_session_connect_call(Request, CallerPid, State);
|
||||||
handle_call(export_handoff_state, _From, State) ->
|
handle_call(export_handoff_state, _From, State) ->
|
||||||
{reply, {ok, guild_handoff:export_handoff_state(State)}, State};
|
{reply, {ok, guild_handoff:export_handoff_state(State)}, State};
|
||||||
handle_call({get_cached_voice_state_by_connection, ConnectionId}, _From, State) ->
|
|
||||||
handle_cached_voice_state_call(ConnectionId, State);
|
|
||||||
handle_call({get_guild_id}, _From, State) ->
|
handle_call({get_guild_id}, _From, State) ->
|
||||||
{reply, maps:get(id, State, undefined), State};
|
{reply, maps:get(id, State, undefined), State};
|
||||||
handle_call({get_voice_guild_state}, _From, State) ->
|
handle_call({get_voice_guild_state}, _From, State) ->
|
||||||
@@ -239,10 +237,6 @@ session_connect_pid(#{session_pid := Pid}, _CallerPid) ->
|
|||||||
session_connect_pid(_Request, CallerPid) ->
|
session_connect_pid(_Request, CallerPid) ->
|
||||||
CallerPid.
|
CallerPid.
|
||||||
|
|
||||||
-spec handle_cached_voice_state_call(term(), guild_state()) -> call_reply().
|
|
||||||
handle_cached_voice_state_call(ConnectionId, State) when is_binary(ConnectionId) ->
|
|
||||||
guild_voice_lifecycle:reply_cached_voice_state(ConnectionId, State).
|
|
||||||
|
|
||||||
-spec handle_reload_call(term(), guild_state()) -> call_reply().
|
-spec handle_reload_call(term(), guild_state()) -> call_reply().
|
||||||
handle_reload_call(NewData, State) when is_map(NewData) ->
|
handle_reload_call(NewData, State) when is_map(NewData) ->
|
||||||
guild_init:handle_reload(NewData, State).
|
guild_init:handle_reload(NewData, State).
|
||||||
|
|||||||
@@ -7,7 +7,6 @@
|
|||||||
ensure_voice_server/1,
|
ensure_voice_server/1,
|
||||||
handle_voice_server_exit/3,
|
handle_voice_server_exit/3,
|
||||||
reply_voice_server_pid/1,
|
reply_voice_server_pid/1,
|
||||||
reply_cached_voice_state/2,
|
|
||||||
clear_stale_cached_voice_states/2
|
clear_stale_cached_voice_states/2
|
||||||
]).
|
]).
|
||||||
|
|
||||||
@@ -61,15 +60,6 @@ reply_voice_server_pid(State) ->
|
|||||||
{{error, Reason}, NewState} -> {reply, {error, Reason}, NewState}
|
{{error, Reason}, NewState} -> {reply, {error, Reason}, NewState}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-spec reply_cached_voice_state(binary(), guild_state()) ->
|
|
||||||
{reply, {ok, map()} | {error, not_found}, guild_state()}.
|
|
||||||
reply_cached_voice_state(ConnectionId, State) ->
|
|
||||||
VoiceStates = maps:get(voice_states, State, #{}),
|
|
||||||
case maps:find(ConnectionId, VoiceStates) of
|
|
||||||
{ok, VoiceState} -> {reply, {ok, VoiceState}, State};
|
|
||||||
error -> {reply, {error, not_found}, State}
|
|
||||||
end.
|
|
||||||
|
|
||||||
-spec clear_stale_cached_voice_states([binary()], guild_state()) -> guild_state().
|
-spec clear_stale_cached_voice_states([binary()], guild_state()) -> guild_state().
|
||||||
clear_stale_cached_voice_states(ConnectionIds, State) ->
|
clear_stale_cached_voice_states(ConnectionIds, State) ->
|
||||||
case read_authoritative_voice_states(State) of
|
case read_authoritative_voice_states(State) of
|
||||||
|
|||||||
@@ -189,13 +189,6 @@ voice_request_handler(_) ->
|
|||||||
error.
|
error.
|
||||||
|
|
||||||
-spec handle_call_local(term(), server_state()) -> {reply, term(), server_state()}.
|
-spec handle_call_local(term(), server_state()) -> {reply, term(), server_state()}.
|
||||||
handle_call_local({repair_voice_state_from_guild_cache, Request}, State) when is_map(Request) ->
|
|
||||||
{Reply, NewState} = guild_voice_server_state:repair_voice_state_from_guild_cache(
|
|
||||||
Request, State
|
|
||||||
),
|
|
||||||
{reply, Reply, NewState};
|
|
||||||
handle_call_local({repair_voice_state_from_guild_cache, _Request}, State) ->
|
|
||||||
{reply, #{success => false, error => voice_invalid_state}, State};
|
|
||||||
handle_call_local({store_pending_connection, ConnectionId, Metadata}, State) ->
|
handle_call_local({store_pending_connection, ConnectionId, Metadata}, State) ->
|
||||||
Pending = maps:get(pending_voice_connections, State, #{}),
|
Pending = maps:get(pending_voice_connections, State, #{}),
|
||||||
NewPending = bounded_put(ConnectionId, Metadata, Pending, ?MAX_PENDING_CONNECTIONS),
|
NewPending = bounded_put(ConnectionId, Metadata, Pending, ?MAX_PENDING_CONNECTIONS),
|
||||||
|
|||||||
@@ -10,7 +10,6 @@
|
|||||||
local_voice_states_for_channel/2,
|
local_voice_states_for_channel/2,
|
||||||
local_pending_joins_for_channel/2,
|
local_pending_joins_for_channel/2,
|
||||||
parse_voice_channel_id/1,
|
parse_voice_channel_id/1,
|
||||||
repair_voice_state_from_guild_cache/2,
|
|
||||||
voice_state_rpc_entries/1,
|
voice_state_rpc_entries/1,
|
||||||
pending_join_rpc_entries/1,
|
pending_join_rpc_entries/1,
|
||||||
fetch_guild_data/1,
|
fetch_guild_data/1,
|
||||||
@@ -117,114 +116,6 @@ parse_voice_channel_id(ChannelId) ->
|
|||||||
error
|
error
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-spec repair_voice_state_from_guild_cache(map(), server_state()) -> {map(), server_state()}.
|
|
||||||
repair_voice_state_from_guild_cache(Request, State) ->
|
|
||||||
ConnectionId = maps:get(connection_id, Request, undefined),
|
|
||||||
UserId = maps:get(user_id, Request, undefined),
|
|
||||||
ChannelId = maps:get(channel_id, Request, undefined),
|
|
||||||
case {ConnectionId, UserId, ChannelId} of
|
|
||||||
{Conn, UId, CId} when is_binary(Conn), is_integer(UId), is_integer(CId) ->
|
|
||||||
do_repair(Conn, UId, CId, State);
|
|
||||||
_ ->
|
|
||||||
{#{success => false, error => voice_invalid_state}, State}
|
|
||||||
end.
|
|
||||||
|
|
||||||
-spec do_repair(binary(), integer(), integer(), server_state()) -> {map(), server_state()}.
|
|
||||||
do_repair(ConnectionId, UserId, ChannelId, State) ->
|
|
||||||
VoiceStates = maps:get(voice_states, State, #{}),
|
|
||||||
case maps:get(ConnectionId, VoiceStates, undefined) of
|
|
||||||
ExistingVoiceState when is_map(ExistingVoiceState) ->
|
|
||||||
repair_existing_voice_state(
|
|
||||||
ExistingVoiceState, ConnectionId, UserId, ChannelId, State
|
|
||||||
);
|
|
||||||
_ ->
|
|
||||||
repair_from_cached(ConnectionId, UserId, ChannelId, VoiceStates, State)
|
|
||||||
end.
|
|
||||||
|
|
||||||
-spec repair_existing_voice_state(
|
|
||||||
voice_state(), binary(), integer(), integer(), server_state()
|
|
||||||
) ->
|
|
||||||
{map(), server_state()}.
|
|
||||||
repair_existing_voice_state(ExistingVoiceState, ConnectionId, UserId, ChannelId, State) ->
|
|
||||||
case voice_state_matches(ExistingVoiceState, ConnectionId, UserId, ChannelId) of
|
|
||||||
true -> {#{success => true, repaired => false}, State};
|
|
||||||
false -> {#{success => false, error => voice_state_mismatch}, State}
|
|
||||||
end.
|
|
||||||
|
|
||||||
-spec repair_from_cached(binary(), integer(), integer(), voice_state_map(), server_state()) ->
|
|
||||||
{map(), server_state()}.
|
|
||||||
repair_from_cached(ConnectionId, UserId, ChannelId, VoiceStates, State) ->
|
|
||||||
case fetch_cached_voice_state(ConnectionId, State) of
|
|
||||||
{ok, CachedVoiceState} ->
|
|
||||||
repair_with_cached(
|
|
||||||
ConnectionId, UserId, ChannelId, CachedVoiceState, VoiceStates, State
|
|
||||||
);
|
|
||||||
{error, _Reason} ->
|
|
||||||
{#{success => false, error => voice_connection_not_found}, State}
|
|
||||||
end.
|
|
||||||
|
|
||||||
-spec repair_with_cached(
|
|
||||||
binary(), integer(), integer(), voice_state(), voice_state_map(), server_state()
|
|
||||||
) -> {map(), server_state()}.
|
|
||||||
repair_with_cached(ConnectionId, UserId, ChannelId, CachedVoiceState, VoiceStates, State) ->
|
|
||||||
case voice_state_matches(CachedVoiceState, ConnectionId, UserId, ChannelId) of
|
|
||||||
false ->
|
|
||||||
{#{success => false, error => voice_state_mismatch}, State};
|
|
||||||
true ->
|
|
||||||
OldVoiceStates = maps:get(voice_states, State, #{}),
|
|
||||||
NewVoiceStates = VoiceStates#{ConnectionId => CachedVoiceState},
|
|
||||||
_ = guild_voice_server_sync:sync_replaced_voice_states(
|
|
||||||
OldVoiceStates, NewVoiceStates
|
|
||||||
),
|
|
||||||
PendingConns = maps:remove(
|
|
||||||
ConnectionId, maps:get(pending_voice_connections, State, #{})
|
|
||||||
),
|
|
||||||
RecentDisc = maps:remove(
|
|
||||||
ConnectionId,
|
|
||||||
maps:get(recently_disconnected_voice_states, State, #{})
|
|
||||||
),
|
|
||||||
NewState0 = State#{
|
|
||||||
voice_states => NewVoiceStates,
|
|
||||||
pending_voice_connections => PendingConns,
|
|
||||||
recently_disconnected_voice_states => RecentDisc
|
|
||||||
},
|
|
||||||
GuildState = build_guild_state(NewState0),
|
|
||||||
ChannelIdBin = maps:get(<<"channel_id">>, CachedVoiceState, null),
|
|
||||||
guild_voice_broadcast:broadcast_voice_state_update(
|
|
||||||
CachedVoiceState, GuildState, ChannelIdBin
|
|
||||||
),
|
|
||||||
logger:warning(
|
|
||||||
voice_state_repaired_log_message(),
|
|
||||||
[maps:get(guild_id, State), ChannelId, UserId, ConnectionId]
|
|
||||||
),
|
|
||||||
{#{success => true, repaired => true}, NewState0}
|
|
||||||
end.
|
|
||||||
|
|
||||||
-spec voice_state_repaired_log_message() -> string().
|
|
||||||
voice_state_repaired_log_message() ->
|
|
||||||
"guild_voice_state_repaired_from_guild_cache: guild_id=~p "
|
|
||||||
"channel_id=~p user_id=~p connection_id=~p".
|
|
||||||
|
|
||||||
-spec fetch_cached_voice_state(binary(), server_state()) ->
|
|
||||||
{ok, voice_state()} | {error, not_found}.
|
|
||||||
fetch_cached_voice_state(ConnectionId, #{guild_pid := GuildPid}) when is_pid(GuildPid) ->
|
|
||||||
try gen_server:call(GuildPid, {get_cached_voice_state_by_connection, ConnectionId}, 1000) of
|
|
||||||
{ok, VoiceState} when is_map(VoiceState) -> {ok, VoiceState};
|
|
||||||
_ -> {error, not_found}
|
|
||||||
catch
|
|
||||||
throw:_ -> {error, not_found};
|
|
||||||
error:_ -> {error, not_found};
|
|
||||||
exit:_ -> {error, not_found}
|
|
||||||
end;
|
|
||||||
fetch_cached_voice_state(_ConnectionId, _State) ->
|
|
||||||
{error, not_found}.
|
|
||||||
|
|
||||||
-spec voice_state_matches(voice_state(), binary(), integer(), integer()) -> boolean().
|
|
||||||
voice_state_matches(VoiceState, ConnectionId, UserId, ChannelId) ->
|
|
||||||
maps:get(<<"connection_id">>, VoiceState, ConnectionId) =:= ConnectionId andalso
|
|
||||||
voice_state_utils:voice_state_user_id(VoiceState) =:= UserId andalso
|
|
||||||
voice_state_utils:voice_state_channel_id(VoiceState) =:= ChannelId.
|
|
||||||
|
|
||||||
-spec voice_state_rpc_entries(term()) -> [map()].
|
-spec voice_state_rpc_entries(term()) -> [map()].
|
||||||
voice_state_rpc_entries(VoiceStates) when is_list(VoiceStates) ->
|
voice_state_rpc_entries(VoiceStates) when is_list(VoiceStates) ->
|
||||||
lists:filtermap(fun voice_state_rpc_entry/1, VoiceStates);
|
lists:filtermap(fun voice_state_rpc_entry/1, VoiceStates);
|
||||||
|
|||||||
Reference in New Issue
Block a user