feat(gateway): persistent voice auto-reconnect — rejoin same channel after restart/reboot or unexpected drop
This commit is contained in:
@@ -277,6 +277,22 @@ export const pgVoiceRecordingsTable = pgTable(
|
||||
|
||||
export const voiceRecordingsTable = pgVoiceRecordingsTable;
|
||||
|
||||
/**
|
||||
* Voice Auto-Reconnect Table (PostgreSQL)
|
||||
* Persists the desired voice state per guild so the gateway can rejoin the
|
||||
* same channel after a restart/reboot, or after an unexpected drop (kick /
|
||||
* server move / voice server restart).
|
||||
*/
|
||||
export const pgVoiceAutoReconnectTable = pgTable("voice_auto_reconnect", {
|
||||
guild_id: pgText("guild_id").primaryKey(),
|
||||
channel_id: pgText("channel_id").notNull(),
|
||||
channel_name: pgText("channel_name"),
|
||||
connected_at: pgBigint("connected_at", { mode: "number" }).notNull(),
|
||||
updated_at: pgBigint("updated_at", { mode: "number" }).notNull(),
|
||||
});
|
||||
|
||||
export const voiceAutoReconnectTable = pgVoiceAutoReconnectTable;
|
||||
|
||||
// =============================================================================
|
||||
// AI Analysis / Analytics
|
||||
// =============================================================================
|
||||
@@ -558,6 +574,11 @@ export type CorrectedModerationInsert =
|
||||
export type VoiceRecording = typeof voiceRecordingsTable.$inferSelect;
|
||||
export type VoiceRecordingInsert = typeof voiceRecordingsTable.$inferInsert;
|
||||
|
||||
// Voice Auto-Reconnect
|
||||
export type VoiceAutoReconnect = typeof voiceAutoReconnectTable.$inferSelect;
|
||||
export type VoiceAutoReconnectInsert =
|
||||
typeof voiceAutoReconnectTable.$inferInsert;
|
||||
|
||||
// AI Analysis Runs
|
||||
export type AIAnalysisRun = typeof aiAnalysisRunsTable.$inferSelect;
|
||||
export type AIAnalysisRunInsert = typeof aiAnalysisRunsTable.$inferInsert;
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
import { eq } from "drizzle-orm";
|
||||
import { getDatabase } from "./drizzle.js";
|
||||
import {
|
||||
type VoiceAutoReconnect,
|
||||
type VoiceAutoReconnectInsert,
|
||||
voiceAutoReconnectTable,
|
||||
} from "./schema.js";
|
||||
|
||||
/**
|
||||
* Upsert a guild's desired voice state.
|
||||
* Called after a successful connect so the gateway can rejoin after a
|
||||
* restart/reboot or unexpected drop.
|
||||
*/
|
||||
export async function upsertVoiceAutoReconnect(
|
||||
record: VoiceAutoReconnectInsert,
|
||||
): Promise<VoiceAutoReconnect | undefined> {
|
||||
const db = getDatabase();
|
||||
const [row] = await db
|
||||
.insert(voiceAutoReconnectTable)
|
||||
.values(record)
|
||||
.onConflictDoUpdate({
|
||||
target: voiceAutoReconnectTable.guild_id,
|
||||
set: {
|
||||
channel_id: record.channel_id,
|
||||
channel_name: record.channel_name ?? null,
|
||||
connected_at: record.connected_at,
|
||||
updated_at: record.updated_at,
|
||||
},
|
||||
})
|
||||
.returning();
|
||||
return row;
|
||||
}
|
||||
|
||||
/** List all persisted desired voice states (for startup auto-reconnect). */
|
||||
export async function listVoiceAutoReconnects(): Promise<VoiceAutoReconnect[]> {
|
||||
const db = getDatabase();
|
||||
return db.select().from(voiceAutoReconnectTable);
|
||||
}
|
||||
|
||||
/** Clear a guild's persisted voice state (explicit manual leave). */
|
||||
export async function deleteVoiceAutoReconnect(guildId: string): Promise<void> {
|
||||
const db = getDatabase();
|
||||
await db
|
||||
.delete(voiceAutoReconnectTable)
|
||||
.where(eq(voiceAutoReconnectTable.guild_id, guildId));
|
||||
}
|
||||
|
||||
/** Fetch a single guild's persisted state, if any. */
|
||||
export async function getVoiceAutoReconnect(
|
||||
guildId: string,
|
||||
): Promise<VoiceAutoReconnect | undefined> {
|
||||
const db = getDatabase();
|
||||
const rows = await db
|
||||
.select()
|
||||
.from(voiceAutoReconnectTable)
|
||||
.where(eq(voiceAutoReconnectTable.guild_id, guildId))
|
||||
.limit(1);
|
||||
return rows[0];
|
||||
}
|
||||
Reference in New Issue
Block a user