84 lines
2.4 KiB
TypeScript
84 lines
2.4 KiB
TypeScript
/*
|
|
* Veskslop communication backend — pure logic (no Discord imports).
|
|
*
|
|
* Keeps a small ring buffer of trigger-qualifying messages: the last 100 from
|
|
* trusted users and the last 30 from untrusted ones. Also parses the
|
|
* `whitelist` commands and normalizes @mentions.
|
|
*/
|
|
|
|
export interface PuppetMessage {
|
|
id: string;
|
|
channelId: string;
|
|
guildId?: string;
|
|
authorId: string;
|
|
authorName: string;
|
|
/** Raw message content as received. */
|
|
content: string;
|
|
timestamp: string;
|
|
trusted: boolean;
|
|
/** Parsed command text when the message is one. */
|
|
command?: string;
|
|
}
|
|
|
|
export const TRUSTED_LOG_LIMIT = 100;
|
|
export const UNTRUSTED_LOG_LIMIT = 30;
|
|
|
|
export class MessageLog {
|
|
trusted: PuppetMessage[] = [];
|
|
untrusted: PuppetMessage[] = [];
|
|
|
|
add(message: PuppetMessage) {
|
|
const bucket = message.trusted ? this.trusted : this.untrusted;
|
|
const limit = message.trusted ? TRUSTED_LOG_LIMIT : UNTRUSTED_LOG_LIMIT;
|
|
|
|
bucket.push(message);
|
|
if (bucket.length > limit) bucket.splice(0, bucket.length - limit);
|
|
}
|
|
|
|
snapshot() {
|
|
return { trusted: [...this.trusted], untrusted: [...this.untrusted] };
|
|
}
|
|
}
|
|
|
|
const USER_MENTION = /<@!?(\d+)>/g;
|
|
|
|
export function extractMentionIds(content: string, mentions?: Array<{ id: string }>): Set<string> {
|
|
const ids = new Set<string>();
|
|
for (const mention of mentions ?? []) ids.add(mention.id);
|
|
for (const match of content.matchAll(USER_MENTION)) ids.add(match[1]);
|
|
return ids;
|
|
}
|
|
|
|
export function stripMentions(content: string, ids: ReadonlySet<string>): string {
|
|
return content
|
|
.replace(USER_MENTION, (full, id) => (ids.has(id) ? "" : full))
|
|
.replace(/\s+/g, " ")
|
|
.trim();
|
|
}
|
|
|
|
export interface ParsedCommand {
|
|
name: string;
|
|
args: string[];
|
|
}
|
|
|
|
export function parseCommand(text: string): ParsedCommand | undefined {
|
|
const trimmed = text.trim();
|
|
if (!trimmed) return undefined;
|
|
|
|
const [name, ...args] = trimmed.split(/\s+/);
|
|
return { name: name.toLowerCase(), args };
|
|
}
|
|
|
|
export function isTrusted(
|
|
userId: string,
|
|
ownerId: string | undefined,
|
|
whitelist: readonly string[] | undefined
|
|
): boolean {
|
|
return userId === ownerId || !!whitelist?.includes(userId);
|
|
}
|
|
|
|
/** Same 500-2800 ms pacing used by the control bridge, for autonomous replies. */
|
|
export function randomActionDelay(): number {
|
|
return 500 + Math.floor(Math.random() * 2301);
|
|
}
|