atagen

malice

public
Star0
Watch0
e4c64c36f1052d9c227ad62e5ed668935204b76d
2
ssh://brewery@pls.lobotomise.me:33/atagen/malice.git
web/src/lib/commands.tsTypescript67 lines2.1 KB
1
import type { Person } from './types';
2
3
export interface ParsedCommand {
4
  name: string;
5
  args: string;
6
}
7
8
export const COMMANDS = [
9
  'me',
10
  'msg',
11
  'join',
12
  'leave',
13
  'invite',
14
  'disinvite',
15
  'kick',
16
  'ban',
17
  'unban',
18
  'powerlevel',
19
  'delete',
20
  'globalnick',
21
  'globalavatar'
22
] as const;
23
export type CommandName = (typeof COMMANDS)[number];
24
25
/** A leading `/word`, or null for plain text. `//text` sends `/text` as a message. */
26
export function parseCommand(text: string): ParsedCommand | null {
27
  const match = /^\/([a-z]+)(?:\s+([\s\S]*))?$/i.exec(text.trim());
28
  if (!match) return null;
29
  return { name: match[1].toLowerCase(), args: (match[2] ?? '').trim() };
30
}
31
32
export function isKnownCommand(name: string): name is CommandName {
33
  return (COMMANDS as readonly string[]).includes(name);
34
}
35
36
export function unescapeCommand(text: string): string {
37
  return text.startsWith('//') ? text.slice(1) : text;
38
}
39
40
export type Target = { user_id: string; rest: string } | { error: string };
41
42
/** The user an argument string starts with, by matrix id or by a known display name. */
43
export function splitTarget(args: string, people: Person[]): Target {
44
  if (!args) return { error: 'Name someone' };
45
  if (args.startsWith('@')) {
46
    const [id, ...rest] = args.split(/\s+/);
47
    if (/^@[^:\s]+:\S+$/.test(id)) return { user_id: id, rest: rest.join(' ') };
48
  }
49
  const lower = args.toLowerCase();
50
  let best: Person[] = [];
51
  let bestLength = 0;
52
  for (const person of people) {
53
    const name = person.name.toLowerCase();
54
    if (!name || name.length < bestLength) continue;
55
    if (lower !== name && !lower.startsWith(`${name} `)) continue;
56
    if (name.length > bestLength) {
57
      best = [person];
58
      bestLength = name.length;
59
    } else {
60
      best.push(person);
61
    }
62
  }
63
  const ids = [...new Set(best.map((p) => p.user_id))];
64
  if (ids.length === 0) return { error: `No one here is called ${args.split(/\s+/)[0]}` };
65
  if (ids.length > 1) return { error: `Several people are called ${best[0].name}, use their @id` };
66
  return { user_id: ids[0], rest: args.slice(bestLength).trim() };
67
}