synabun 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +665 -0
- package/LICENSE +191 -0
- package/README.md +853 -0
- package/hooks/claude-code/post-plan.mjs +391 -0
- package/hooks/claude-code/post-remember.mjs +239 -0
- package/hooks/claude-code/pre-compact.mjs +241 -0
- package/hooks/claude-code/pre-websearch.mjs +90 -0
- package/hooks/claude-code/prompt-submit.mjs +861 -0
- package/hooks/claude-code/session-start.mjs +494 -0
- package/hooks/claude-code/shared.mjs +368 -0
- package/hooks/claude-code/stop.mjs +646 -0
- package/hooks/claude-code/test-user-learning.mjs +531 -0
- package/mcp-server/package.json +38 -0
- package/mcp-server/run.mjs +10 -0
- package/mcp-server/src/config.ts +70 -0
- package/mcp-server/src/http.ts +93 -0
- package/mcp-server/src/index.ts +203 -0
- package/mcp-server/src/preload-http.ts +25 -0
- package/mcp-server/src/preload.ts +29 -0
- package/mcp-server/src/scripts/migrate-qdrant-to-sqlite.ts +506 -0
- package/mcp-server/src/services/categories.ts +429 -0
- package/mcp-server/src/services/discord.ts +702 -0
- package/mcp-server/src/services/file-checksums.ts +33 -0
- package/mcp-server/src/services/local-embeddings.ts +77 -0
- package/mcp-server/src/services/neural-interface.ts +344 -0
- package/mcp-server/src/services/sqlite.ts +823 -0
- package/mcp-server/src/tools/browser-advanced.ts +123 -0
- package/mcp-server/src/tools/browser-interact.ts +255 -0
- package/mcp-server/src/tools/browser-navigate.ts +77 -0
- package/mcp-server/src/tools/browser-observe.ts +1330 -0
- package/mcp-server/src/tools/browser.ts +107 -0
- package/mcp-server/src/tools/card-tools.ts +193 -0
- package/mcp-server/src/tools/card.ts +22 -0
- package/mcp-server/src/tools/category.ts +379 -0
- package/mcp-server/src/tools/discord-channel.ts +274 -0
- package/mcp-server/src/tools/discord-guild.ts +188 -0
- package/mcp-server/src/tools/discord-member.ts +158 -0
- package/mcp-server/src/tools/discord-message.ts +231 -0
- package/mcp-server/src/tools/discord-onboarding.ts +228 -0
- package/mcp-server/src/tools/discord-role.ts +209 -0
- package/mcp-server/src/tools/discord-thread.ts +110 -0
- package/mcp-server/src/tools/discord-webhook.ts +176 -0
- package/mcp-server/src/tools/discord.ts +40 -0
- package/mcp-server/src/tools/forget.ts +38 -0
- package/mcp-server/src/tools/git-tools.ts +141 -0
- package/mcp-server/src/tools/git.ts +13 -0
- package/mcp-server/src/tools/image-tools.ts +98 -0
- package/mcp-server/src/tools/image.ts +17 -0
- package/mcp-server/src/tools/leonardo-browser-tools.ts +281 -0
- package/mcp-server/src/tools/leonardo.ts +28 -0
- package/mcp-server/src/tools/loop.ts +348 -0
- package/mcp-server/src/tools/memories.ts +118 -0
- package/mcp-server/src/tools/recall.ts +323 -0
- package/mcp-server/src/tools/reflect.ts +145 -0
- package/mcp-server/src/tools/remember.ts +115 -0
- package/mcp-server/src/tools/response.ts +26 -0
- package/mcp-server/src/tools/restore.ts +34 -0
- package/mcp-server/src/tools/sync.ts +114 -0
- package/mcp-server/src/tools/tictactoe-tools.ts +98 -0
- package/mcp-server/src/tools/tictactoe.ts +13 -0
- package/mcp-server/src/tools/utils.ts +25 -0
- package/mcp-server/src/tools/whiteboard-tools.ts +260 -0
- package/mcp-server/src/tools/whiteboard.ts +22 -0
- package/mcp-server/src/tui.ts +940 -0
- package/mcp-server/src/types.ts +85 -0
- package/mcp-server/tsconfig.json +19 -0
- package/neural-interface/package.json +27 -0
- package/neural-interface/public/apple-touch-icon.png +0 -0
- package/neural-interface/public/category-logos/claude.svg +1 -0
- package/neural-interface/public/category-logos/gemini-color.svg +1 -0
- package/neural-interface/public/category-logos/openai.svg +1 -0
- package/neural-interface/public/category-logos/synabun.png +0 -0
- package/neural-interface/public/claude-chat.css +878 -0
- package/neural-interface/public/claude-chat.html +92 -0
- package/neural-interface/public/claude-chat.js +1168 -0
- package/neural-interface/public/favicon-16x16.png +0 -0
- package/neural-interface/public/favicon-32x32.png +0 -0
- package/neural-interface/public/gallery.svg +6 -0
- package/neural-interface/public/icon-192.png +0 -0
- package/neural-interface/public/icon-512.png +0 -0
- package/neural-interface/public/index-legacy.html +14677 -0
- package/neural-interface/public/index.html +212 -0
- package/neural-interface/public/index2d-legacy.html +8193 -0
- package/neural-interface/public/index2d.html +127 -0
- package/neural-interface/public/invite.html +351 -0
- package/neural-interface/public/leonardoai.svg +1 -0
- package/neural-interface/public/logo-synabun.png +0 -0
- package/neural-interface/public/logoHD.png +0 -0
- package/neural-interface/public/manifest.json +14 -0
- package/neural-interface/public/offline.html +299 -0
- package/neural-interface/public/onboarding.html +2335 -0
- package/neural-interface/public/openclaw-logo-text.png +0 -0
- package/neural-interface/public/pin-svgrepo-com.svg +25 -0
- package/neural-interface/public/sessionicon.svg +8 -0
- package/neural-interface/public/shared/ansi-parser.js +305 -0
- package/neural-interface/public/shared/api.js +1043 -0
- package/neural-interface/public/shared/colors.js +153 -0
- package/neural-interface/public/shared/constants.js +141 -0
- package/neural-interface/public/shared/html-shell.js +777 -0
- package/neural-interface/public/shared/html-term-renderer.js +1022 -0
- package/neural-interface/public/shared/i18n.js +147 -0
- package/neural-interface/public/shared/registry.js +82 -0
- package/neural-interface/public/shared/state.js +86 -0
- package/neural-interface/public/shared/storage.js +150 -0
- package/neural-interface/public/shared/styles.css +21212 -0
- package/neural-interface/public/shared/ui-automation-studio.js +3573 -0
- package/neural-interface/public/shared/ui-bookmarks.js +231 -0
- package/neural-interface/public/shared/ui-claude-panel.js +6333 -0
- package/neural-interface/public/shared/ui-command-runner.js +534 -0
- package/neural-interface/public/shared/ui-cost-widget.js +602 -0
- package/neural-interface/public/shared/ui-detail.js +1454 -0
- package/neural-interface/public/shared/ui-explorer.js +1356 -0
- package/neural-interface/public/shared/ui-file-explorer.js +2750 -0
- package/neural-interface/public/shared/ui-games.js +411 -0
- package/neural-interface/public/shared/ui-help.js +107 -0
- package/neural-interface/public/shared/ui-image-gallery.js +461 -0
- package/neural-interface/public/shared/ui-invite.js +519 -0
- package/neural-interface/public/shared/ui-keybinds.js +452 -0
- package/neural-interface/public/shared/ui-layouts.js +297 -0
- package/neural-interface/public/shared/ui-link.js +731 -0
- package/neural-interface/public/shared/ui-loading.js +255 -0
- package/neural-interface/public/shared/ui-menubar.js +499 -0
- package/neural-interface/public/shared/ui-multiselect.js +93 -0
- package/neural-interface/public/shared/ui-navbar.js +222 -0
- package/neural-interface/public/shared/ui-notifications.js +287 -0
- package/neural-interface/public/shared/ui-panels.js +309 -0
- package/neural-interface/public/shared/ui-resume.js +653 -0
- package/neural-interface/public/shared/ui-search.js +145 -0
- package/neural-interface/public/shared/ui-sessions.js +531 -0
- package/neural-interface/public/shared/ui-settings.js +5987 -0
- package/neural-interface/public/shared/ui-sidebar.js +1905 -0
- package/neural-interface/public/shared/ui-skills.js +2805 -0
- package/neural-interface/public/shared/ui-stats.js +38 -0
- package/neural-interface/public/shared/ui-sync.js +201 -0
- package/neural-interface/public/shared/ui-terminal.js +6385 -0
- package/neural-interface/public/shared/ui-tooltip.js +123 -0
- package/neural-interface/public/shared/ui-trash.js +439 -0
- package/neural-interface/public/shared/ui-tutorial-draw.js +875 -0
- package/neural-interface/public/shared/ui-tutorial-steps.js +2276 -0
- package/neural-interface/public/shared/ui-tutorial.js +1117 -0
- package/neural-interface/public/shared/ui-update.js +244 -0
- package/neural-interface/public/shared/ui-whiteboard.js +3262 -0
- package/neural-interface/public/shared/ui-workspaces.js +722 -0
- package/neural-interface/public/shared/utils.js +360 -0
- package/neural-interface/public/shared/vterm-buffer.js +1102 -0
- package/neural-interface/public/sw.js +52 -0
- package/neural-interface/public/synabun.png +0 -0
- package/neural-interface/public/toolbar-album.svg +8 -0
- package/neural-interface/public/toolbar-question.svg +6 -0
- package/neural-interface/public/toolbar-sessions.svg +6 -0
- package/neural-interface/public/variant/2d/background.js +113 -0
- package/neural-interface/public/variant/2d/context-menu.js +133 -0
- package/neural-interface/public/variant/2d/gfx.js +45 -0
- package/neural-interface/public/variant/2d/graph.js +1635 -0
- package/neural-interface/public/variant/2d/hulls.js +207 -0
- package/neural-interface/public/variant/2d/lasso.js +135 -0
- package/neural-interface/public/variant/2d/layout.js +594 -0
- package/neural-interface/public/variant/2d/main.js +521 -0
- package/neural-interface/public/variant/2d/minimap.js +312 -0
- package/neural-interface/public/variant/2d/settings-gfx.js +98 -0
- package/neural-interface/public/variant/3d/background.js +884 -0
- package/neural-interface/public/variant/3d/camera.js +661 -0
- package/neural-interface/public/variant/3d/gfx.js +116 -0
- package/neural-interface/public/variant/3d/graph.js +2399 -0
- package/neural-interface/public/variant/3d/main.js +500 -0
- package/neural-interface/public/variant/3d/settings-gfx.js +228 -0
- package/neural-interface/scripts/rebuild-pty.js +100 -0
- package/neural-interface/server.js +15000 -0
- package/neural-interface/templates/CLAUDE-template.md +211 -0
- package/package.json +53 -0
- package/postinstall.js +135 -0
- package/setup.js +260 -0
- package/skills/leonardo/SKILL.md +159 -0
- package/skills/leonardo/icon.svg +1 -0
- package/skills/leonardo/modules/browser-guide.md +378 -0
- package/skills/leonardo/modules/image-prompter.md +277 -0
- package/skills/leonardo/modules/model-advisor.md +100 -0
- package/skills/leonardo/modules/prompt-library.md +110 -0
- package/skills/leonardo/modules/style-guide.md +186 -0
- package/skills/leonardo/modules/video-prompter.md +311 -0
- package/skills/synabun/SKILL.md +185 -0
- package/skills/synabun/icon.png +0 -0
- package/skills/synabun/modules/audit.md +374 -0
- package/skills/synabun/modules/changelog.md +194 -0
- package/skills/synabun/modules/idea.md +144 -0
- package/skills/synabun/modules/memorize.md +188 -0
- package/skills/synabun/synabunicon.png +0 -0
|
@@ -0,0 +1,702 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Discord REST API v10 client for MCP tools.
|
|
3
|
+
* Follows the neural-interface.ts pattern: typed HTTP client with
|
|
4
|
+
* timeout handling, rate limit retry, and name resolution helpers.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const API_BASE = 'https://discord.com/api/v10';
|
|
8
|
+
const DEFAULT_TIMEOUT = 10_000;
|
|
9
|
+
const LONG_TIMEOUT = 30_000;
|
|
10
|
+
const MAX_RETRIES = 3;
|
|
11
|
+
|
|
12
|
+
// ── Types ──────────────────────────────────────────────────────
|
|
13
|
+
|
|
14
|
+
export interface DiscordResponse {
|
|
15
|
+
ok?: boolean;
|
|
16
|
+
error?: string;
|
|
17
|
+
data?: unknown;
|
|
18
|
+
[key: string]: unknown;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface CacheEntry<T> {
|
|
22
|
+
data: T;
|
|
23
|
+
expiresAt: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// ── Permission flags ───────────────────────────────────────────
|
|
27
|
+
|
|
28
|
+
export const PERMISSION_FLAGS: Record<string, bigint> = {
|
|
29
|
+
CREATE_INSTANT_INVITE: 1n << 0n,
|
|
30
|
+
KICK_MEMBERS: 1n << 1n,
|
|
31
|
+
BAN_MEMBERS: 1n << 2n,
|
|
32
|
+
ADMINISTRATOR: 1n << 3n,
|
|
33
|
+
MANAGE_CHANNELS: 1n << 4n,
|
|
34
|
+
MANAGE_GUILD: 1n << 5n,
|
|
35
|
+
ADD_REACTIONS: 1n << 6n,
|
|
36
|
+
VIEW_AUDIT_LOG: 1n << 7n,
|
|
37
|
+
PRIORITY_SPEAKER: 1n << 8n,
|
|
38
|
+
STREAM: 1n << 9n,
|
|
39
|
+
VIEW_CHANNEL: 1n << 10n,
|
|
40
|
+
SEND_MESSAGES: 1n << 11n,
|
|
41
|
+
SEND_TTS_MESSAGES: 1n << 12n,
|
|
42
|
+
MANAGE_MESSAGES: 1n << 13n,
|
|
43
|
+
EMBED_LINKS: 1n << 14n,
|
|
44
|
+
ATTACH_FILES: 1n << 15n,
|
|
45
|
+
READ_MESSAGE_HISTORY: 1n << 16n,
|
|
46
|
+
MENTION_EVERYONE: 1n << 17n,
|
|
47
|
+
USE_EXTERNAL_EMOJIS: 1n << 18n,
|
|
48
|
+
VIEW_GUILD_INSIGHTS: 1n << 19n,
|
|
49
|
+
CONNECT: 1n << 20n,
|
|
50
|
+
SPEAK: 1n << 21n,
|
|
51
|
+
MUTE_MEMBERS: 1n << 22n,
|
|
52
|
+
DEAFEN_MEMBERS: 1n << 23n,
|
|
53
|
+
MOVE_MEMBERS: 1n << 24n,
|
|
54
|
+
USE_VAD: 1n << 25n,
|
|
55
|
+
CHANGE_NICKNAME: 1n << 26n,
|
|
56
|
+
MANAGE_NICKNAMES: 1n << 27n,
|
|
57
|
+
MANAGE_ROLES: 1n << 28n,
|
|
58
|
+
MANAGE_WEBHOOKS: 1n << 29n,
|
|
59
|
+
MANAGE_GUILD_EXPRESSIONS: 1n << 30n,
|
|
60
|
+
USE_APPLICATION_COMMANDS: 1n << 31n,
|
|
61
|
+
REQUEST_TO_SPEAK: 1n << 32n,
|
|
62
|
+
MANAGE_EVENTS: 1n << 33n,
|
|
63
|
+
MANAGE_THREADS: 1n << 34n,
|
|
64
|
+
CREATE_PUBLIC_THREADS: 1n << 35n,
|
|
65
|
+
CREATE_PRIVATE_THREADS: 1n << 36n,
|
|
66
|
+
USE_EXTERNAL_STICKERS: 1n << 37n,
|
|
67
|
+
SEND_MESSAGES_IN_THREADS: 1n << 38n,
|
|
68
|
+
USE_EMBEDDED_ACTIVITIES: 1n << 39n,
|
|
69
|
+
MODERATE_MEMBERS: 1n << 40n,
|
|
70
|
+
VIEW_CREATOR_MONETIZATION_ANALYTICS: 1n << 41n,
|
|
71
|
+
USE_SOUNDBOARD: 1n << 42n,
|
|
72
|
+
CREATE_GUILD_EXPRESSIONS: 1n << 43n,
|
|
73
|
+
CREATE_EVENTS: 1n << 44n,
|
|
74
|
+
USE_EXTERNAL_SOUNDS: 1n << 45n,
|
|
75
|
+
SEND_VOICE_MESSAGES: 1n << 46n,
|
|
76
|
+
SEND_POLLS: 1n << 49n,
|
|
77
|
+
USE_EXTERNAL_APPS: 1n << 50n,
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
/** Convert comma-separated permission flag names to a bitfield string. */
|
|
81
|
+
export function resolvePermissions(flags: string): string {
|
|
82
|
+
let bits = 0n;
|
|
83
|
+
for (const flag of flags.split(',').map(f => f.trim().toUpperCase())) {
|
|
84
|
+
const val = PERMISSION_FLAGS[flag];
|
|
85
|
+
if (!val) {
|
|
86
|
+
const available = Object.keys(PERMISSION_FLAGS).join(', ');
|
|
87
|
+
throw new Error(`Unknown permission flag: "${flag}". Available: ${available}`);
|
|
88
|
+
}
|
|
89
|
+
bits |= val;
|
|
90
|
+
}
|
|
91
|
+
return bits.toString();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ── Channel type mapping ───────────────────────────────────────
|
|
95
|
+
|
|
96
|
+
export const CHANNEL_TYPES: Record<string, number> = {
|
|
97
|
+
text: 0,
|
|
98
|
+
voice: 2,
|
|
99
|
+
category: 4,
|
|
100
|
+
announcement: 5,
|
|
101
|
+
stage: 13,
|
|
102
|
+
forum: 15,
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
export const CHANNEL_TYPE_NAMES: Record<number, string> = {
|
|
106
|
+
0: 'text',
|
|
107
|
+
2: 'voice',
|
|
108
|
+
4: 'category',
|
|
109
|
+
5: 'announcement',
|
|
110
|
+
10: 'announcement-thread',
|
|
111
|
+
11: 'public-thread',
|
|
112
|
+
12: 'private-thread',
|
|
113
|
+
13: 'stage',
|
|
114
|
+
15: 'forum',
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
// ── Verification levels ────────────────────────────────────────
|
|
118
|
+
|
|
119
|
+
export const VERIFICATION_LEVELS: Record<string, number> = {
|
|
120
|
+
none: 0,
|
|
121
|
+
low: 1,
|
|
122
|
+
medium: 2,
|
|
123
|
+
high: 3,
|
|
124
|
+
very_high: 4,
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
// ── Cache ──────────────────────────────────────────────────────
|
|
128
|
+
|
|
129
|
+
const cache = new Map<string, CacheEntry<unknown>>();
|
|
130
|
+
const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
|
|
131
|
+
|
|
132
|
+
function getCached<T>(key: string): T | null {
|
|
133
|
+
const entry = cache.get(key);
|
|
134
|
+
if (!entry || Date.now() > entry.expiresAt) {
|
|
135
|
+
cache.delete(key);
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
return entry.data as T;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function setCache<T>(key: string, data: T): void {
|
|
142
|
+
cache.set(key, { data, expiresAt: Date.now() + CACHE_TTL });
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function invalidateCache(pattern?: string): void {
|
|
146
|
+
if (!pattern) {
|
|
147
|
+
cache.clear();
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
for (const key of cache.keys()) {
|
|
151
|
+
if (key.includes(pattern)) cache.delete(key);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// ── Token validation ───────────────────────────────────────────
|
|
156
|
+
|
|
157
|
+
function getToken(): string {
|
|
158
|
+
const token = process.env.DISCORD_BOT_TOKEN;
|
|
159
|
+
if (!token) {
|
|
160
|
+
throw new Error(
|
|
161
|
+
'DISCORD_BOT_TOKEN not set. Add it to your .env file:\n' +
|
|
162
|
+
'DISCORD_BOT_TOKEN=your_bot_token_here\n\n' +
|
|
163
|
+
'Create a bot at https://discord.com/developers/applications'
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
return token;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// ── Core HTTP request ──────────────────────────────────────────
|
|
170
|
+
|
|
171
|
+
export async function discordRequest(
|
|
172
|
+
method: string,
|
|
173
|
+
path: string,
|
|
174
|
+
body?: Record<string, unknown>,
|
|
175
|
+
timeout = DEFAULT_TIMEOUT,
|
|
176
|
+
retryCount = 0
|
|
177
|
+
): Promise<DiscordResponse> {
|
|
178
|
+
let token: string;
|
|
179
|
+
try {
|
|
180
|
+
token = getToken();
|
|
181
|
+
} catch (err) {
|
|
182
|
+
return { error: (err as Error).message };
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const controller = new AbortController();
|
|
186
|
+
const timer = setTimeout(() => controller.abort(), timeout);
|
|
187
|
+
|
|
188
|
+
try {
|
|
189
|
+
const opts: RequestInit = {
|
|
190
|
+
method,
|
|
191
|
+
headers: {
|
|
192
|
+
'Authorization': `Bot ${token}`,
|
|
193
|
+
'Content-Type': 'application/json',
|
|
194
|
+
'User-Agent': 'SynaBun-MCP (https://github.com/synabun, 1.0.0)',
|
|
195
|
+
},
|
|
196
|
+
signal: controller.signal,
|
|
197
|
+
};
|
|
198
|
+
if (body && method !== 'GET') {
|
|
199
|
+
opts.body = JSON.stringify(body);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const res = await fetch(`${API_BASE}${path}`, opts);
|
|
203
|
+
|
|
204
|
+
// Rate limited — retry with Retry-After
|
|
205
|
+
if (res.status === 429 && retryCount < MAX_RETRIES) {
|
|
206
|
+
const retryData = await res.json() as { retry_after?: number };
|
|
207
|
+
const retryAfter = retryData.retry_after || 1;
|
|
208
|
+
await new Promise(r => setTimeout(r, retryAfter * 1000));
|
|
209
|
+
return discordRequest(method, path, body, timeout, retryCount + 1);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// No content (successful delete, etc.)
|
|
213
|
+
if (res.status === 204) {
|
|
214
|
+
return { ok: true };
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const data = await res.json() as Record<string, unknown>;
|
|
218
|
+
|
|
219
|
+
if (!res.ok) {
|
|
220
|
+
// Discord error response
|
|
221
|
+
const message = data.message as string || `HTTP ${res.status}`;
|
|
222
|
+
const code = data.code as number;
|
|
223
|
+
return { error: `Discord API error ${code || res.status}: ${message}` };
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return { ok: true, data };
|
|
227
|
+
} catch (err: unknown) {
|
|
228
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
229
|
+
if (msg.includes('abort')) {
|
|
230
|
+
return { error: `Request timed out after ${timeout}ms` };
|
|
231
|
+
}
|
|
232
|
+
return { error: `Discord API unreachable: ${msg}` };
|
|
233
|
+
} finally {
|
|
234
|
+
clearTimeout(timer);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// ── Guild resolution ───────────────────────────────────────────
|
|
239
|
+
|
|
240
|
+
export async function resolveGuildId(guildId?: string): Promise<{ guildId: string } | { error: string }> {
|
|
241
|
+
if (guildId) return { guildId };
|
|
242
|
+
|
|
243
|
+
const envGuild = process.env.DISCORD_GUILD_ID;
|
|
244
|
+
if (envGuild) return { guildId: envGuild };
|
|
245
|
+
|
|
246
|
+
// Auto-detect: if bot is in exactly 1 guild, use it
|
|
247
|
+
const res = await discordRequest('GET', '/users/@me/guilds');
|
|
248
|
+
if (res.error) return { error: res.error };
|
|
249
|
+
const guilds = res.data as { id: string; name: string }[];
|
|
250
|
+
|
|
251
|
+
if (guilds.length === 1) return { guildId: guilds[0].id };
|
|
252
|
+
if (guilds.length === 0) return { error: 'Bot is not in any guilds. Invite it to a server first.' };
|
|
253
|
+
|
|
254
|
+
const list = guilds.map(g => ` ${g.id} — ${g.name}`).join('\n');
|
|
255
|
+
return { error: `Bot is in multiple guilds. Specify guild_id:\n${list}\n\nOr set DISCORD_GUILD_ID in your .env file.` };
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// ── Name resolution helpers ────────────────────────────────────
|
|
259
|
+
|
|
260
|
+
const SNOWFLAKE_PATTERN = /^\d{17,20}$/;
|
|
261
|
+
|
|
262
|
+
function isSnowflake(value: string): boolean {
|
|
263
|
+
return SNOWFLAKE_PATTERN.test(value);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
export async function resolveChannel(
|
|
267
|
+
nameOrId: string,
|
|
268
|
+
guildId: string
|
|
269
|
+
): Promise<{ id: string; name: string; type: number } | { error: string }> {
|
|
270
|
+
if (isSnowflake(nameOrId)) {
|
|
271
|
+
return { id: nameOrId, name: nameOrId, type: -1 };
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const channels = await getGuildChannels(guildId);
|
|
275
|
+
if (!Array.isArray(channels)) return { error: (channels as DiscordResponse).error || 'Failed to fetch channels' };
|
|
276
|
+
|
|
277
|
+
const normalized = nameOrId.toLowerCase().replace(/^#/, '');
|
|
278
|
+
const matches = channels
|
|
279
|
+
.filter(c => c.name.toLowerCase() === normalized);
|
|
280
|
+
|
|
281
|
+
if (matches.length === 0) {
|
|
282
|
+
return { error: `Channel "${nameOrId}" not found in guild.` };
|
|
283
|
+
}
|
|
284
|
+
if (matches.length > 1) {
|
|
285
|
+
const list = matches.map(c => ` ${c.id} — #${c.name} (${CHANNEL_TYPE_NAMES[c.type] || 'unknown'})`).join('\n');
|
|
286
|
+
return { error: `Multiple channels named "${nameOrId}". Specify by ID:\n${list}` };
|
|
287
|
+
}
|
|
288
|
+
return matches[0];
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
export async function resolveRole(
|
|
292
|
+
nameOrId: string,
|
|
293
|
+
guildId: string
|
|
294
|
+
): Promise<{ id: string; name: string } | { error: string }> {
|
|
295
|
+
if (isSnowflake(nameOrId)) {
|
|
296
|
+
return { id: nameOrId, name: nameOrId };
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const roles = await getGuildRoles(guildId);
|
|
300
|
+
if (!Array.isArray(roles)) return { error: (roles as DiscordResponse).error || 'Failed to fetch roles' };
|
|
301
|
+
|
|
302
|
+
const normalized = nameOrId.toLowerCase().replace(/^@/, '');
|
|
303
|
+
const matches = roles
|
|
304
|
+
.filter(r => r.name.toLowerCase() === normalized);
|
|
305
|
+
|
|
306
|
+
if (matches.length === 0) {
|
|
307
|
+
return { error: `Role "${nameOrId}" not found in guild.` };
|
|
308
|
+
}
|
|
309
|
+
if (matches.length > 1) {
|
|
310
|
+
const list = matches.map(r => ` ${r.id} — @${r.name}`).join('\n');
|
|
311
|
+
return { error: `Multiple roles named "${nameOrId}". Specify by ID:\n${list}` };
|
|
312
|
+
}
|
|
313
|
+
return matches[0];
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
export async function resolveUser(
|
|
317
|
+
nameOrTag: string,
|
|
318
|
+
guildId: string
|
|
319
|
+
): Promise<{ id: string; name: string } | { error: string }> {
|
|
320
|
+
if (isSnowflake(nameOrTag)) {
|
|
321
|
+
return { id: nameOrTag, name: nameOrTag };
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Search guild members by query
|
|
325
|
+
const res = await discordRequest('GET', `/guilds/${guildId}/members/search?query=${encodeURIComponent(nameOrTag)}&limit=10`);
|
|
326
|
+
if (res.error) return { error: res.error };
|
|
327
|
+
|
|
328
|
+
const members = res.data as { user: { id: string; username: string; global_name?: string } }[];
|
|
329
|
+
if (members.length === 0) {
|
|
330
|
+
return { error: `User "${nameOrTag}" not found in guild.` };
|
|
331
|
+
}
|
|
332
|
+
if (members.length === 1) {
|
|
333
|
+
return { id: members[0].user.id, name: members[0].user.global_name || members[0].user.username };
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
const list = members.map(m =>
|
|
337
|
+
` ${m.user.id} — ${m.user.global_name || m.user.username} (@${m.user.username})`
|
|
338
|
+
).join('\n');
|
|
339
|
+
return { error: `Multiple users match "${nameOrTag}". Specify by ID:\n${list}` };
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// ── Guild operations ───────────────────────────────────────────
|
|
343
|
+
|
|
344
|
+
export async function getGuild(guildId: string): Promise<DiscordResponse> {
|
|
345
|
+
return discordRequest('GET', `/guilds/${guildId}?with_counts=true`);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
export async function getGuildChannels(guildId: string): Promise<DiscordResponse | { id: string; name: string; type: number }[]> {
|
|
349
|
+
const cacheKey = `channels:${guildId}`;
|
|
350
|
+
const cached = getCached<{ id: string; name: string; type: number }[]>(cacheKey);
|
|
351
|
+
if (cached) return cached;
|
|
352
|
+
|
|
353
|
+
const res = await discordRequest('GET', `/guilds/${guildId}/channels`);
|
|
354
|
+
if (res.error) return { error: res.error };
|
|
355
|
+
|
|
356
|
+
const channels = res.data as { id: string; name: string; type: number }[];
|
|
357
|
+
setCache(cacheKey, channels);
|
|
358
|
+
return channels;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
export async function getGuildMembers(
|
|
362
|
+
guildId: string,
|
|
363
|
+
limit = 100,
|
|
364
|
+
after?: string
|
|
365
|
+
): Promise<DiscordResponse> {
|
|
366
|
+
let path = `/guilds/${guildId}/members?limit=${limit}`;
|
|
367
|
+
if (after) path += `&after=${after}`;
|
|
368
|
+
return discordRequest('GET', path);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
export async function getGuildRoles(guildId: string): Promise<DiscordResponse | { id: string; name: string; color: number; position: number }[]> {
|
|
372
|
+
const cacheKey = `roles:${guildId}`;
|
|
373
|
+
const cached = getCached<{ id: string; name: string; color: number; position: number }[]>(cacheKey);
|
|
374
|
+
if (cached) return cached;
|
|
375
|
+
|
|
376
|
+
const res = await discordRequest('GET', `/guilds/${guildId}/roles`);
|
|
377
|
+
if (res.error) return { error: res.error };
|
|
378
|
+
|
|
379
|
+
const roles = res.data as { id: string; name: string; color: number; position: number }[];
|
|
380
|
+
setCache(cacheKey, roles);
|
|
381
|
+
return roles;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
export async function getAuditLog(
|
|
385
|
+
guildId: string,
|
|
386
|
+
opts?: { action_type?: number; limit?: number }
|
|
387
|
+
): Promise<DiscordResponse> {
|
|
388
|
+
let path = `/guilds/${guildId}/audit-logs?limit=${opts?.limit || 50}`;
|
|
389
|
+
if (opts?.action_type !== undefined) path += `&action_type=${opts.action_type}`;
|
|
390
|
+
return discordRequest('GET', path);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
export async function editGuildSettings(
|
|
394
|
+
guildId: string,
|
|
395
|
+
settings: Record<string, unknown>
|
|
396
|
+
): Promise<DiscordResponse> {
|
|
397
|
+
return discordRequest('PATCH', `/guilds/${guildId}`, settings);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// ── Channel operations ─────────────────────────────────────────
|
|
401
|
+
|
|
402
|
+
export async function createChannel(
|
|
403
|
+
guildId: string,
|
|
404
|
+
data: Record<string, unknown>
|
|
405
|
+
): Promise<DiscordResponse> {
|
|
406
|
+
invalidateCache(`channels:${guildId}`);
|
|
407
|
+
return discordRequest('POST', `/guilds/${guildId}/channels`, data);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
export async function editChannel(
|
|
411
|
+
channelId: string,
|
|
412
|
+
data: Record<string, unknown>
|
|
413
|
+
): Promise<DiscordResponse> {
|
|
414
|
+
invalidateCache('channels:');
|
|
415
|
+
return discordRequest('PATCH', `/channels/${channelId}`, data);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
export async function deleteChannel(channelId: string): Promise<DiscordResponse> {
|
|
419
|
+
invalidateCache('channels:');
|
|
420
|
+
return discordRequest('DELETE', `/channels/${channelId}`);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
export async function setChannelPermissions(
|
|
424
|
+
channelId: string,
|
|
425
|
+
overwriteId: string,
|
|
426
|
+
data: { type: number; allow: string; deny: string }
|
|
427
|
+
): Promise<DiscordResponse> {
|
|
428
|
+
return discordRequest('PUT', `/channels/${channelId}/permissions/${overwriteId}`, data as unknown as Record<string, unknown>);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// ── Role operations ────────────────────────────────────────────
|
|
432
|
+
|
|
433
|
+
export async function createRole(
|
|
434
|
+
guildId: string,
|
|
435
|
+
data: Record<string, unknown>
|
|
436
|
+
): Promise<DiscordResponse> {
|
|
437
|
+
invalidateCache(`roles:${guildId}`);
|
|
438
|
+
return discordRequest('POST', `/guilds/${guildId}/roles`, data);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
export async function editRole(
|
|
442
|
+
guildId: string,
|
|
443
|
+
roleId: string,
|
|
444
|
+
data: Record<string, unknown>
|
|
445
|
+
): Promise<DiscordResponse> {
|
|
446
|
+
invalidateCache(`roles:${guildId}`);
|
|
447
|
+
return discordRequest('PATCH', `/guilds/${guildId}/roles/${roleId}`, data);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
export async function deleteRole(
|
|
451
|
+
guildId: string,
|
|
452
|
+
roleId: string
|
|
453
|
+
): Promise<DiscordResponse> {
|
|
454
|
+
invalidateCache(`roles:${guildId}`);
|
|
455
|
+
return discordRequest('DELETE', `/guilds/${guildId}/roles/${roleId}`);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
export async function addMemberRole(
|
|
459
|
+
guildId: string,
|
|
460
|
+
userId: string,
|
|
461
|
+
roleId: string
|
|
462
|
+
): Promise<DiscordResponse> {
|
|
463
|
+
return discordRequest('PUT', `/guilds/${guildId}/members/${userId}/roles/${roleId}`, {});
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
export async function removeMemberRole(
|
|
467
|
+
guildId: string,
|
|
468
|
+
userId: string,
|
|
469
|
+
roleId: string
|
|
470
|
+
): Promise<DiscordResponse> {
|
|
471
|
+
return discordRequest('DELETE', `/guilds/${guildId}/members/${userId}/roles/${roleId}`);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// ── Message operations ─────────────────────────────────────────
|
|
475
|
+
|
|
476
|
+
export async function sendMessage(
|
|
477
|
+
channelId: string,
|
|
478
|
+
data: Record<string, unknown>
|
|
479
|
+
): Promise<DiscordResponse> {
|
|
480
|
+
return discordRequest('POST', `/channels/${channelId}/messages`, data);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
export async function editMessage(
|
|
484
|
+
channelId: string,
|
|
485
|
+
messageId: string,
|
|
486
|
+
data: Record<string, unknown>
|
|
487
|
+
): Promise<DiscordResponse> {
|
|
488
|
+
return discordRequest('PATCH', `/channels/${channelId}/messages/${messageId}`, data);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
export async function deleteMessage(
|
|
492
|
+
channelId: string,
|
|
493
|
+
messageId: string
|
|
494
|
+
): Promise<DiscordResponse> {
|
|
495
|
+
return discordRequest('DELETE', `/channels/${channelId}/messages/${messageId}`);
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
export async function pinMessage(
|
|
499
|
+
channelId: string,
|
|
500
|
+
messageId: string
|
|
501
|
+
): Promise<DiscordResponse> {
|
|
502
|
+
return discordRequest('PUT', `/channels/${channelId}/pins/${messageId}`, {});
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
export async function unpinMessage(
|
|
506
|
+
channelId: string,
|
|
507
|
+
messageId: string
|
|
508
|
+
): Promise<DiscordResponse> {
|
|
509
|
+
return discordRequest('DELETE', `/channels/${channelId}/pins/${messageId}`);
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
export async function addReaction(
|
|
513
|
+
channelId: string,
|
|
514
|
+
messageId: string,
|
|
515
|
+
emoji: string
|
|
516
|
+
): Promise<DiscordResponse> {
|
|
517
|
+
const encoded = encodeURIComponent(emoji);
|
|
518
|
+
return discordRequest('PUT', `/channels/${channelId}/messages/${messageId}/reactions/${encoded}/@me`, {});
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
export async function getMessages(
|
|
522
|
+
channelId: string,
|
|
523
|
+
opts?: { limit?: number; before?: string; after?: string }
|
|
524
|
+
): Promise<DiscordResponse> {
|
|
525
|
+
let path = `/channels/${channelId}/messages?limit=${opts?.limit || 50}`;
|
|
526
|
+
if (opts?.before) path += `&before=${opts.before}`;
|
|
527
|
+
if (opts?.after) path += `&after=${opts.after}`;
|
|
528
|
+
return discordRequest('GET', path);
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
export async function bulkDeleteMessages(
|
|
532
|
+
channelId: string,
|
|
533
|
+
messageIds: string[]
|
|
534
|
+
): Promise<DiscordResponse> {
|
|
535
|
+
return discordRequest('POST', `/channels/${channelId}/messages/bulk-delete`, { messages: messageIds });
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// ── Member operations ──────────────────────────────────────────
|
|
539
|
+
|
|
540
|
+
export async function getMember(
|
|
541
|
+
guildId: string,
|
|
542
|
+
userId: string
|
|
543
|
+
): Promise<DiscordResponse> {
|
|
544
|
+
return discordRequest('GET', `/guilds/${guildId}/members/${userId}`);
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
export async function kickMember(
|
|
548
|
+
guildId: string,
|
|
549
|
+
userId: string,
|
|
550
|
+
reason?: string
|
|
551
|
+
): Promise<DiscordResponse> {
|
|
552
|
+
const headers = reason ? { 'X-Audit-Log-Reason': encodeURIComponent(reason) } : undefined;
|
|
553
|
+
// Can't pass custom headers through discordRequest — use reason in query if needed
|
|
554
|
+
return discordRequest('DELETE', `/guilds/${guildId}/members/${userId}`);
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
export async function banMember(
|
|
558
|
+
guildId: string,
|
|
559
|
+
userId: string,
|
|
560
|
+
data?: { delete_message_seconds?: number }
|
|
561
|
+
): Promise<DiscordResponse> {
|
|
562
|
+
return discordRequest('PUT', `/guilds/${guildId}/bans/${userId}`, data || {});
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
export async function unbanMember(
|
|
566
|
+
guildId: string,
|
|
567
|
+
userId: string
|
|
568
|
+
): Promise<DiscordResponse> {
|
|
569
|
+
return discordRequest('DELETE', `/guilds/${guildId}/bans/${userId}`);
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
export async function timeoutMember(
|
|
573
|
+
guildId: string,
|
|
574
|
+
userId: string,
|
|
575
|
+
durationMinutes: number
|
|
576
|
+
): Promise<DiscordResponse> {
|
|
577
|
+
const until = durationMinutes > 0
|
|
578
|
+
? new Date(Date.now() + durationMinutes * 60_000).toISOString()
|
|
579
|
+
: null;
|
|
580
|
+
return discordRequest('PATCH', `/guilds/${guildId}/members/${userId}`, {
|
|
581
|
+
communication_disabled_until: until,
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
export async function setNickname(
|
|
586
|
+
guildId: string,
|
|
587
|
+
userId: string,
|
|
588
|
+
nick: string | null
|
|
589
|
+
): Promise<DiscordResponse> {
|
|
590
|
+
return discordRequest('PATCH', `/guilds/${guildId}/members/${userId}`, { nick });
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
// ── Onboarding operations ──────────────────────────────────────
|
|
594
|
+
|
|
595
|
+
export async function getWelcomeScreen(guildId: string): Promise<DiscordResponse> {
|
|
596
|
+
return discordRequest('GET', `/guilds/${guildId}/welcome-screen`);
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
export async function setWelcomeScreen(
|
|
600
|
+
guildId: string,
|
|
601
|
+
data: Record<string, unknown>
|
|
602
|
+
): Promise<DiscordResponse> {
|
|
603
|
+
return discordRequest('PATCH', `/guilds/${guildId}/welcome-screen`, data);
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
export async function getOnboarding(guildId: string): Promise<DiscordResponse> {
|
|
607
|
+
return discordRequest('GET', `/guilds/${guildId}/onboarding`);
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
export async function setOnboarding(
|
|
611
|
+
guildId: string,
|
|
612
|
+
data: Record<string, unknown>
|
|
613
|
+
): Promise<DiscordResponse> {
|
|
614
|
+
return discordRequest('PUT', `/guilds/${guildId}/onboarding`, data);
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
// ── Webhook operations ─────────────────────────────────────────
|
|
618
|
+
|
|
619
|
+
export async function createWebhook(
|
|
620
|
+
channelId: string,
|
|
621
|
+
data: { name: string; avatar?: string }
|
|
622
|
+
): Promise<DiscordResponse> {
|
|
623
|
+
return discordRequest('POST', `/channels/${channelId}/webhooks`, data as unknown as Record<string, unknown>);
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
export async function editWebhook(
|
|
627
|
+
webhookId: string,
|
|
628
|
+
data: Record<string, unknown>
|
|
629
|
+
): Promise<DiscordResponse> {
|
|
630
|
+
return discordRequest('PATCH', `/webhooks/${webhookId}`, data);
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
export async function deleteWebhook(webhookId: string): Promise<DiscordResponse> {
|
|
634
|
+
return discordRequest('DELETE', `/webhooks/${webhookId}`);
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
export async function listChannelWebhooks(channelId: string): Promise<DiscordResponse> {
|
|
638
|
+
return discordRequest('GET', `/channels/${channelId}/webhooks`);
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
export async function listGuildWebhooks(guildId: string): Promise<DiscordResponse> {
|
|
642
|
+
return discordRequest('GET', `/guilds/${guildId}/webhooks`);
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
export async function executeWebhook(
|
|
646
|
+
webhookId: string,
|
|
647
|
+
webhookToken: string,
|
|
648
|
+
data: Record<string, unknown>
|
|
649
|
+
): Promise<DiscordResponse> {
|
|
650
|
+
// Webhook execution uses token auth, not bot auth — make direct fetch call
|
|
651
|
+
const controller = new AbortController();
|
|
652
|
+
const timer = setTimeout(() => controller.abort(), DEFAULT_TIMEOUT);
|
|
653
|
+
|
|
654
|
+
try {
|
|
655
|
+
const res = await fetch(`${API_BASE}/webhooks/${webhookId}/${webhookToken}?wait=true`, {
|
|
656
|
+
method: 'POST',
|
|
657
|
+
headers: { 'Content-Type': 'application/json' },
|
|
658
|
+
signal: controller.signal,
|
|
659
|
+
body: JSON.stringify(data),
|
|
660
|
+
});
|
|
661
|
+
|
|
662
|
+
if (res.status === 204) return { ok: true };
|
|
663
|
+
const result = await res.json() as Record<string, unknown>;
|
|
664
|
+
if (!res.ok) {
|
|
665
|
+
return { error: `Webhook error ${res.status}: ${result.message || 'Unknown error'}` };
|
|
666
|
+
}
|
|
667
|
+
return { ok: true, data: result };
|
|
668
|
+
} catch (err: unknown) {
|
|
669
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
670
|
+
return { error: `Webhook execution failed: ${msg}` };
|
|
671
|
+
} finally {
|
|
672
|
+
clearTimeout(timer);
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
// ── Thread operations ──────────────────────────────────────────
|
|
677
|
+
|
|
678
|
+
export async function createThread(
|
|
679
|
+
channelId: string,
|
|
680
|
+
data: Record<string, unknown>
|
|
681
|
+
): Promise<DiscordResponse> {
|
|
682
|
+
return discordRequest('POST', `/channels/${channelId}/threads`, data);
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
export async function createThreadFromMessage(
|
|
686
|
+
channelId: string,
|
|
687
|
+
messageId: string,
|
|
688
|
+
data: Record<string, unknown>
|
|
689
|
+
): Promise<DiscordResponse> {
|
|
690
|
+
return discordRequest('POST', `/channels/${channelId}/messages/${messageId}/threads`, data);
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
export async function modifyThread(
|
|
694
|
+
threadId: string,
|
|
695
|
+
data: Record<string, unknown>
|
|
696
|
+
): Promise<DiscordResponse> {
|
|
697
|
+
return discordRequest('PATCH', `/channels/${threadId}`, data);
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
export async function deleteThread(threadId: string): Promise<DiscordResponse> {
|
|
701
|
+
return discordRequest('DELETE', `/channels/${threadId}`);
|
|
702
|
+
}
|