Phase 4: Bots & Extensibility
Backend: - internal/bot/auth.go: bot token generation and verification - internal/bot/handlers.go: bot CRUD (create, list, get, update, delete, server management, token regen) - internal/bot/commands.go: slash command registration and management - internal/webhook/handlers.go: webhook CRUD and execution endpoint - internal/webhook/token.go: webhook token generation - internal/db/db.go: bots, bot_servers, slash_commands, webhooks tables - internal/gateway/events.go: BOT_JOIN, BOT_LEAVE event constants - cmd/server/main.go: wired bot, webhook, invite routes Frontend: - stores/bot.ts: Zustand store for bot management - BotManager.tsx: bot list, create, edit, delete, add to server, token display - CommandManager.tsx: slash command CRUD per bot - SlashCommandPopup.tsx: / command autocomplete popup - App.tsx: /bots and /bots/:id/commands routes Examples: - examples/modbot/: auto-delete banned words, /kick, /ban, /purge commands - examples/welcome/: welcome message on member join
This commit is contained in:
@@ -0,0 +1,200 @@
|
||||
import { create } from 'zustand';
|
||||
import { api } from '../lib/api.ts';
|
||||
|
||||
export interface Bot {
|
||||
id: string;
|
||||
name: string;
|
||||
avatar: string | null;
|
||||
description: string;
|
||||
owner_id: string;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
export interface SlashCommand {
|
||||
id: string;
|
||||
bot_id: string;
|
||||
server_id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
interface BotState {
|
||||
bots: Bot[];
|
||||
loading: boolean;
|
||||
error: string | null;
|
||||
|
||||
fetchBots: () => Promise<void>;
|
||||
createBot: (name: string, description: string) => Promise<Bot & { token: string }>;
|
||||
updateBot: (id: string, data: { name?: string; description?: string; avatar?: string }) => Promise<Bot>;
|
||||
deleteBot: (id: string) => Promise<void>;
|
||||
addToServer: (botId: string, serverId: string) => Promise<void>;
|
||||
removeFromServer: (botId: string, serverId: string) => Promise<void>;
|
||||
regenerateToken: (id: string) => Promise<{ token: string }>;
|
||||
|
||||
fetchCommands: (botId: string) => Promise<SlashCommand[]>;
|
||||
createCommand: (botId: string, serverId: string, name: string, description: string) => Promise<SlashCommand>;
|
||||
deleteCommand: (botId: string, commandId: string) => Promise<void>;
|
||||
fetchServerCommands: (serverId: string) => Promise<SlashCommand[]>;
|
||||
}
|
||||
|
||||
export const useBotStore = create<BotState>((set) => ({
|
||||
bots: [],
|
||||
loading: false,
|
||||
error: null,
|
||||
|
||||
fetchBots: async () => {
|
||||
set({ loading: true, error: null });
|
||||
try {
|
||||
const bots = await api.get<Bot[]>('/bots');
|
||||
set({ bots, loading: false });
|
||||
} catch (error) {
|
||||
set({
|
||||
loading: false,
|
||||
error: error instanceof Error ? error.message : 'Failed to fetch bots',
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
createBot: async (name, description) => {
|
||||
set({ loading: true, error: null });
|
||||
try {
|
||||
const result = await api.post<Bot & { token: string }>('/bots', { name, description });
|
||||
set((state) => ({
|
||||
bots: [...state.bots, result],
|
||||
loading: false,
|
||||
}));
|
||||
return result;
|
||||
} catch (error) {
|
||||
set({
|
||||
loading: false,
|
||||
error: error instanceof Error ? error.message : 'Failed to create bot',
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
updateBot: async (id, data) => {
|
||||
set({ loading: true, error: null });
|
||||
try {
|
||||
const bot = await api.patch<Bot>(`/bots/${id}`, data);
|
||||
set((state) => ({
|
||||
bots: state.bots.map((b) => (b.id === id ? bot : b)),
|
||||
loading: false,
|
||||
}));
|
||||
return bot;
|
||||
} catch (error) {
|
||||
set({
|
||||
loading: false,
|
||||
error: error instanceof Error ? error.message : 'Failed to update bot',
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
deleteBot: async (id) => {
|
||||
set({ loading: true, error: null });
|
||||
try {
|
||||
await api.delete(`/bots/${id}`);
|
||||
set((state) => ({
|
||||
bots: state.bots.filter((b) => b.id !== id),
|
||||
loading: false,
|
||||
}));
|
||||
} catch (error) {
|
||||
set({
|
||||
loading: false,
|
||||
error: error instanceof Error ? error.message : 'Failed to delete bot',
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
addToServer: async (botId, serverId) => {
|
||||
set({ error: null });
|
||||
try {
|
||||
await api.post(`/bots/${botId}/servers`, { server_id: serverId });
|
||||
} catch (error) {
|
||||
set({
|
||||
error: error instanceof Error ? error.message : 'Failed to add bot to server',
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
removeFromServer: async (botId, serverId) => {
|
||||
set({ error: null });
|
||||
try {
|
||||
await api.delete(`/bots/${botId}/servers/${serverId}`);
|
||||
} catch (error) {
|
||||
set({
|
||||
error: error instanceof Error ? error.message : 'Failed to remove bot from server',
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
regenerateToken: async (id) => {
|
||||
set({ loading: true, error: null });
|
||||
try {
|
||||
const result = await api.post<{ token: string }>(`/bots/${id}/regenerate-token`);
|
||||
set({ loading: false });
|
||||
return result;
|
||||
} catch (error) {
|
||||
set({
|
||||
loading: false,
|
||||
error: error instanceof Error ? error.message : 'Failed to regenerate token',
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
fetchCommands: async (botId) => {
|
||||
try {
|
||||
return await api.get<SlashCommand[]>(`/bots/${botId}/commands`);
|
||||
} catch (error) {
|
||||
set({
|
||||
error: error instanceof Error ? error.message : 'Failed to fetch commands',
|
||||
});
|
||||
return [];
|
||||
}
|
||||
},
|
||||
|
||||
createCommand: async (botId, serverId, name, description) => {
|
||||
set({ error: null });
|
||||
try {
|
||||
const cmd = await api.post<SlashCommand>(`/bots/${botId}/commands`, {
|
||||
server_id: serverId,
|
||||
name,
|
||||
description,
|
||||
});
|
||||
return cmd;
|
||||
} catch (error) {
|
||||
set({
|
||||
error: error instanceof Error ? error.message : 'Failed to create command',
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
deleteCommand: async (botId, commandId) => {
|
||||
set({ error: null });
|
||||
try {
|
||||
await api.delete(`/bots/${botId}/commands/${commandId}`);
|
||||
} catch (error) {
|
||||
set({
|
||||
error: error instanceof Error ? error.message : 'Failed to delete command',
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
fetchServerCommands: async (serverId) => {
|
||||
try {
|
||||
return await api.get<SlashCommand[]>(`/servers/${serverId}/commands`);
|
||||
} catch (error) {
|
||||
set({
|
||||
error: error instanceof Error ? error.message : 'Failed to fetch server commands',
|
||||
});
|
||||
return [];
|
||||
}
|
||||
},
|
||||
}));
|
||||
Reference in New Issue
Block a user