Aethel Bot OSS repository!
aethel.xyz
bot
fun
ai
discord
discord-bot
aethel
1export type V2ComponentType = 3 | 4 | 10 | 12 | 18;
2
3export interface V2BaseComponent {
4 type: V2ComponentType;
5}
6
7export interface V2StringSelect extends V2BaseComponent {
8 type: 3;
9 custom_id: string;
10 placeholder?: string;
11 options: Array<{
12 label: string;
13 value: string;
14 description?: string;
15 emoji?: { id?: string; name?: string; animated?: boolean };
16 }>;
17 min_values?: number;
18 max_values?: number;
19}
20
21export interface V2TextInput extends V2BaseComponent {
22 type: 4;
23 custom_id: string;
24 style: 1 | 2;
25 label?: string;
26 placeholder?: string;
27 required?: boolean;
28 min_length?: number;
29 max_length?: number;
30 value?: string;
31}
32
33export interface V2LabeledComponent extends V2BaseComponent {
34 type: 18;
35 label?: string;
36 description?: string;
37 component: V2StringSelect | V2TextInput;
38}
39
40export type V2ModalRow =
41 | V2LabeledComponent
42 | { type: 1; components: V2TextInput[] }
43 | { type: 18; components: V2TextInput[] };
44
45export interface V2SubmissionValueMap {
46 [customId: string]: string | string[];
47}
48
49export interface V2ModalPayload {
50 custom_id: string;
51 title: string;
52 components: V2ModalRow[];
53}
54
55interface V2Component {
56 type: number;
57 custom_id?: string;
58 customId?: string;
59 value?: string | string[];
60 values?: string[];
61 [key: string]: unknown;
62}
63
64interface V2Row {
65 component?: V2Component;
66 components?: V2Component[];
67 type?: number;
68 [key: string]: unknown;
69}
70
71export function buildProviderModal(customId: string, title: string): V2ModalPayload {
72 return {
73 custom_id: customId,
74 title,
75 components: [
76 {
77 type: 18,
78 label: 'AI Provider',
79 description: 'Select an authorized provider',
80 component: {
81 type: 3,
82 custom_id: 'provider',
83 placeholder: 'Choose provider',
84 options: [
85 { label: 'OpenAI', value: 'openai', description: 'api.openai.com' },
86 { label: 'Anthropic', value: 'anthropic', description: 'api.anthropic.com' },
87 { label: 'OpenRouter', value: 'openrouter', description: 'openrouter.ai' },
88 {
89 label: 'Google Gemini',
90 value: 'gemini',
91 description: 'generativelanguage.googleapis.com',
92 },
93 { label: 'DeepSeek', value: 'deepseek', description: 'api.deepseek.com' },
94 { label: 'Moonshot AI', value: 'moonshot', description: 'api.moonshot.ai' },
95 { label: 'Perplexity AI', value: 'perplexity', description: 'api.perplexity.ai' },
96 ],
97 },
98 },
99 {
100 type: 1 as const,
101 components: [
102 {
103 type: 4,
104 custom_id: 'model',
105 label: 'Model',
106 style: 1,
107 required: true,
108 placeholder: 'openai/gpt-4o-mini',
109 min_length: 2,
110 max_length: 100,
111 },
112 ],
113 },
114 {
115 type: 1 as const,
116 components: [
117 {
118 type: 4,
119 custom_id: 'apiKey',
120 label: 'API Key',
121 style: 1,
122 required: true,
123 placeholder: 'sk-... or other',
124 min_length: 10,
125 max_length: 500,
126 },
127 ],
128 },
129 ],
130 };
131}
132
133interface RawModalSubmission {
134 fields?: {
135 getTextInputValue?: (id: string) => string;
136 [key: string]: unknown;
137 };
138 data?: {
139 components?: Array<{
140 component?: V2Component;
141 components?: V2Component[];
142 [key: string]: unknown;
143 }>;
144 [key: string]: unknown;
145 };
146 components?: Array<{
147 component?: V2Component;
148 components?: V2Component[];
149 [key: string]: unknown;
150 }>;
151 message?: {
152 components?: Array<{
153 component?: V2Component;
154 components?: V2Component[];
155 [key: string]: unknown;
156 }>;
157 [key: string]: unknown;
158 };
159 [key: string]: unknown;
160}
161
162export function parseV2ModalSubmission(raw: RawModalSubmission): V2SubmissionValueMap {
163 const result: V2SubmissionValueMap = {};
164 try {
165 const fields = raw?.fields as { getTextInputValue?: (id: string) => string } | undefined;
166 if (fields?.getTextInputValue) {
167 for (const id of ['model', 'apiKey']) {
168 try {
169 const v = fields.getTextInputValue(id);
170 if (v !== undefined && v !== '') result[id] = v;
171 } catch (error) {
172 console.error(`Error getting text input value for ${id}:`, error);
173 }
174 }
175 }
176 } catch (error) {
177 console.error('Error processing text input fields:', error);
178 }
179
180 try {
181 const mergedRows = (
182 [
183 ...(raw?.data?.components || []),
184 ...(raw?.components || []),
185 ...(raw?.message?.components || []),
186 ] as Array<V2Row | undefined>
187 ).filter(Boolean) as V2Row[];
188
189 const flat = mergedRows.flatMap((r) => (r.component ? [r.component] : r.components || []));
190
191 for (const c of flat) {
192 if (!c) continue;
193
194 if (c.customId === 'provider' || c.custom_id === 'provider') {
195 if (Array.isArray((c as { values?: string[] }).values)) {
196 result.provider = (c as { values: string[] }).values;
197 } else if ((c as { value?: string | string[] }).value) {
198 const value = (c as { value: string | string[] }).value;
199 result.provider = Array.isArray(value) ? value : [value];
200 }
201 }
202
203 if (c.type === 4 && (c.custom_id || c.customId) && (c as { value?: unknown }).value) {
204 const value = (c as { value: unknown }).value;
205 if (typeof value === 'string') {
206 result[c.custom_id || c.customId!] = value;
207 }
208 }
209 }
210 } catch (error) {
211 console.error('Error processing component values:', error);
212 }
213 return result;
214}
215
216export const PROVIDER_TO_URL: Record<string, string> = {
217 openai: 'https://api.openai.com/v1',
218 anthropic: 'https://api.anthropic.com/v1',
219 openrouter: 'https://openrouter.ai/api/v1',
220 gemini: 'https://generativelanguage.googleapis.com',
221 deepseek: 'https://api.deepseek.com',
222 moonshot: 'https://api.moonshot.ai',
223 perplexity: 'https://api.perplexity.ai',
224};