feat: add signed Discord command foundation and daily check-ins
This commit is contained in:
35
src/discord/test-fixture.ts
Normal file
35
src/discord/test-fixture.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { generateKeyPairSync, sign } from 'node:crypto';
|
||||
import { eq } from 'drizzle-orm';
|
||||
import { fixture } from '../habits/test-fixture';
|
||||
import { users } from '../db/schema';
|
||||
import { createDiscordRoutes } from './routes';
|
||||
import type { Interaction } from './types';
|
||||
export function discordFixture() {
|
||||
const f = fixture();
|
||||
const { publicKey, privateKey } = generateKeyPairSync('ed25519');
|
||||
const key = publicKey.export({ format: 'der', type: 'spki' }).subarray(-32).toString('hex');
|
||||
let time = Date.parse('2026-09-04T12:00:00Z'), sequence = 10000000000000000n;
|
||||
const alice = '111111111111111111', bob = '222222222222222222', applicationId = '333333333333333333';
|
||||
f.db.update(users).set({ discordId: alice }).where(eq(users.id, 'alice')).run();
|
||||
f.db.update(users).set({ discordId: bob }).where(eq(users.id, 'bob')).run();
|
||||
const changed: string[] = [];
|
||||
let app = createDiscordRoutes(f.db, { publicKey: key, applicationId, origin: f.origin }, () => time, id => changed.push(id));
|
||||
function payload(data: Partial<Interaction>, guild = false, user = alice): Interaction {
|
||||
const profile = { id: user, username: user === alice ? 'alice' : 'bob' };
|
||||
return { id: String(++sequence), application_id: applicationId, type: 2, ...(guild ? { guild_id: '444444444444444444', member: { user: profile } } : { user: profile }), ...data };
|
||||
}
|
||||
async function request(i: Interaction, headers: Record<string, string> = {}) {
|
||||
const body = JSON.stringify(i), timestamp = String(time / 1000);
|
||||
return app.request('http://localhost/', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Signature-Timestamp': timestamp, 'X-Signature-Ed25519': sign(null, Buffer.from(timestamp + body), privateKey).toString('hex'), ...headers }, body });
|
||||
}
|
||||
async function run(data: Partial<Interaction>, guild = false, user = alice): Promise<any> { return (await request(payload(data, guild, user))).json(); }
|
||||
return { ...f, alice, bob, changed, payload, requestDiscord: request, run, restart: () => { app = createDiscordRoutes(f.db, { publicKey: key, applicationId, origin: f.origin }, () => time, id => changed.push(id)); }, setDiscordTime: (value: string) => { time = Date.parse(value); f.setTime(value); } };
|
||||
}
|
||||
export function control(reply: any, label: string): string {
|
||||
const item = reply.data.components.flatMap((r: any) => r.components ?? []).find((c: any) => c.label === label || c.placeholder === label);
|
||||
if (!item) throw new Error(`Missing control ${label}: ${JSON.stringify(reply)}`);
|
||||
return item.custom_id;
|
||||
}
|
||||
export function submit(custom_id: string, values: Record<string, string>) {
|
||||
return { type: 5, message: { id: '123' }, data: { custom_id, components: Object.entries(values).map(([custom_id, value]) => ({ type: 1, components: [{ type: 4, custom_id, value }] })) } };
|
||||
}
|
||||
Reference in New Issue
Block a user