windsor-bot 0.1.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.
Files changed (226) hide show
  1. package/README.md +128 -0
  2. package/SPEC.md +179 -0
  3. package/assets/pokemon-sprites/1.png +0 -0
  4. package/assets/pokemon-sprites/10.png +0 -0
  5. package/assets/pokemon-sprites/100.png +0 -0
  6. package/assets/pokemon-sprites/101.png +0 -0
  7. package/assets/pokemon-sprites/102.png +0 -0
  8. package/assets/pokemon-sprites/103.png +0 -0
  9. package/assets/pokemon-sprites/104.png +0 -0
  10. package/assets/pokemon-sprites/105.png +0 -0
  11. package/assets/pokemon-sprites/106.png +0 -0
  12. package/assets/pokemon-sprites/107.png +0 -0
  13. package/assets/pokemon-sprites/108.png +0 -0
  14. package/assets/pokemon-sprites/109.png +0 -0
  15. package/assets/pokemon-sprites/11.png +0 -0
  16. package/assets/pokemon-sprites/110.png +0 -0
  17. package/assets/pokemon-sprites/111.png +0 -0
  18. package/assets/pokemon-sprites/112.png +0 -0
  19. package/assets/pokemon-sprites/113.png +0 -0
  20. package/assets/pokemon-sprites/114.png +0 -0
  21. package/assets/pokemon-sprites/115.png +0 -0
  22. package/assets/pokemon-sprites/116.png +0 -0
  23. package/assets/pokemon-sprites/117.png +0 -0
  24. package/assets/pokemon-sprites/118.png +0 -0
  25. package/assets/pokemon-sprites/119.png +0 -0
  26. package/assets/pokemon-sprites/12.png +0 -0
  27. package/assets/pokemon-sprites/120.png +0 -0
  28. package/assets/pokemon-sprites/121.png +0 -0
  29. package/assets/pokemon-sprites/122.png +0 -0
  30. package/assets/pokemon-sprites/123.png +0 -0
  31. package/assets/pokemon-sprites/124.png +0 -0
  32. package/assets/pokemon-sprites/125.png +0 -0
  33. package/assets/pokemon-sprites/126.png +0 -0
  34. package/assets/pokemon-sprites/127.png +0 -0
  35. package/assets/pokemon-sprites/128.png +0 -0
  36. package/assets/pokemon-sprites/129.png +0 -0
  37. package/assets/pokemon-sprites/13.png +0 -0
  38. package/assets/pokemon-sprites/130.png +0 -0
  39. package/assets/pokemon-sprites/131.png +0 -0
  40. package/assets/pokemon-sprites/132.png +0 -0
  41. package/assets/pokemon-sprites/133.png +0 -0
  42. package/assets/pokemon-sprites/134.png +0 -0
  43. package/assets/pokemon-sprites/135.png +0 -0
  44. package/assets/pokemon-sprites/136.png +0 -0
  45. package/assets/pokemon-sprites/137.png +0 -0
  46. package/assets/pokemon-sprites/138.png +0 -0
  47. package/assets/pokemon-sprites/139.png +0 -0
  48. package/assets/pokemon-sprites/14.png +0 -0
  49. package/assets/pokemon-sprites/140.png +0 -0
  50. package/assets/pokemon-sprites/141.png +0 -0
  51. package/assets/pokemon-sprites/142.png +0 -0
  52. package/assets/pokemon-sprites/143.png +0 -0
  53. package/assets/pokemon-sprites/144.png +0 -0
  54. package/assets/pokemon-sprites/145.png +0 -0
  55. package/assets/pokemon-sprites/146.png +0 -0
  56. package/assets/pokemon-sprites/147.png +0 -0
  57. package/assets/pokemon-sprites/148.png +0 -0
  58. package/assets/pokemon-sprites/149.png +0 -0
  59. package/assets/pokemon-sprites/15.png +0 -0
  60. package/assets/pokemon-sprites/150.png +0 -0
  61. package/assets/pokemon-sprites/151.png +0 -0
  62. package/assets/pokemon-sprites/16.png +0 -0
  63. package/assets/pokemon-sprites/17.png +0 -0
  64. package/assets/pokemon-sprites/18.png +0 -0
  65. package/assets/pokemon-sprites/19.png +0 -0
  66. package/assets/pokemon-sprites/2.png +0 -0
  67. package/assets/pokemon-sprites/20.png +0 -0
  68. package/assets/pokemon-sprites/21.png +0 -0
  69. package/assets/pokemon-sprites/22.png +0 -0
  70. package/assets/pokemon-sprites/23.png +0 -0
  71. package/assets/pokemon-sprites/24.png +0 -0
  72. package/assets/pokemon-sprites/25.png +0 -0
  73. package/assets/pokemon-sprites/26.png +0 -0
  74. package/assets/pokemon-sprites/27.png +0 -0
  75. package/assets/pokemon-sprites/28.png +0 -0
  76. package/assets/pokemon-sprites/29.png +0 -0
  77. package/assets/pokemon-sprites/3.png +0 -0
  78. package/assets/pokemon-sprites/30.png +0 -0
  79. package/assets/pokemon-sprites/31.png +0 -0
  80. package/assets/pokemon-sprites/32.png +0 -0
  81. package/assets/pokemon-sprites/33.png +0 -0
  82. package/assets/pokemon-sprites/34.png +0 -0
  83. package/assets/pokemon-sprites/35.png +0 -0
  84. package/assets/pokemon-sprites/36.png +0 -0
  85. package/assets/pokemon-sprites/37.png +0 -0
  86. package/assets/pokemon-sprites/38.png +0 -0
  87. package/assets/pokemon-sprites/39.png +0 -0
  88. package/assets/pokemon-sprites/4.png +0 -0
  89. package/assets/pokemon-sprites/40.png +0 -0
  90. package/assets/pokemon-sprites/41.png +0 -0
  91. package/assets/pokemon-sprites/42.png +0 -0
  92. package/assets/pokemon-sprites/43.png +0 -0
  93. package/assets/pokemon-sprites/44.png +0 -0
  94. package/assets/pokemon-sprites/45.png +0 -0
  95. package/assets/pokemon-sprites/46.png +0 -0
  96. package/assets/pokemon-sprites/47.png +0 -0
  97. package/assets/pokemon-sprites/48.png +0 -0
  98. package/assets/pokemon-sprites/49.png +0 -0
  99. package/assets/pokemon-sprites/5.png +0 -0
  100. package/assets/pokemon-sprites/50.png +0 -0
  101. package/assets/pokemon-sprites/51.png +0 -0
  102. package/assets/pokemon-sprites/52.png +0 -0
  103. package/assets/pokemon-sprites/53.png +0 -0
  104. package/assets/pokemon-sprites/54.png +0 -0
  105. package/assets/pokemon-sprites/55.png +0 -0
  106. package/assets/pokemon-sprites/56.png +0 -0
  107. package/assets/pokemon-sprites/57.png +0 -0
  108. package/assets/pokemon-sprites/58.png +0 -0
  109. package/assets/pokemon-sprites/59.png +0 -0
  110. package/assets/pokemon-sprites/6.png +0 -0
  111. package/assets/pokemon-sprites/60.png +0 -0
  112. package/assets/pokemon-sprites/61.png +0 -0
  113. package/assets/pokemon-sprites/62.png +0 -0
  114. package/assets/pokemon-sprites/63.png +0 -0
  115. package/assets/pokemon-sprites/64.png +0 -0
  116. package/assets/pokemon-sprites/65.png +0 -0
  117. package/assets/pokemon-sprites/66.png +0 -0
  118. package/assets/pokemon-sprites/67.png +0 -0
  119. package/assets/pokemon-sprites/68.png +0 -0
  120. package/assets/pokemon-sprites/69.png +0 -0
  121. package/assets/pokemon-sprites/7.png +0 -0
  122. package/assets/pokemon-sprites/70.png +0 -0
  123. package/assets/pokemon-sprites/71.png +0 -0
  124. package/assets/pokemon-sprites/72.png +0 -0
  125. package/assets/pokemon-sprites/73.png +0 -0
  126. package/assets/pokemon-sprites/74.png +0 -0
  127. package/assets/pokemon-sprites/75.png +0 -0
  128. package/assets/pokemon-sprites/76.png +0 -0
  129. package/assets/pokemon-sprites/77.png +0 -0
  130. package/assets/pokemon-sprites/78.png +0 -0
  131. package/assets/pokemon-sprites/79.png +0 -0
  132. package/assets/pokemon-sprites/8.png +0 -0
  133. package/assets/pokemon-sprites/80.png +0 -0
  134. package/assets/pokemon-sprites/81.png +0 -0
  135. package/assets/pokemon-sprites/82.png +0 -0
  136. package/assets/pokemon-sprites/83.png +0 -0
  137. package/assets/pokemon-sprites/84.png +0 -0
  138. package/assets/pokemon-sprites/85.png +0 -0
  139. package/assets/pokemon-sprites/86.png +0 -0
  140. package/assets/pokemon-sprites/87.png +0 -0
  141. package/assets/pokemon-sprites/88.png +0 -0
  142. package/assets/pokemon-sprites/89.png +0 -0
  143. package/assets/pokemon-sprites/9.png +0 -0
  144. package/assets/pokemon-sprites/90.png +0 -0
  145. package/assets/pokemon-sprites/91.png +0 -0
  146. package/assets/pokemon-sprites/92.png +0 -0
  147. package/assets/pokemon-sprites/93.png +0 -0
  148. package/assets/pokemon-sprites/94.png +0 -0
  149. package/assets/pokemon-sprites/95.png +0 -0
  150. package/assets/pokemon-sprites/96.png +0 -0
  151. package/assets/pokemon-sprites/97.png +0 -0
  152. package/assets/pokemon-sprites/98.png +0 -0
  153. package/assets/pokemon-sprites/99.png +0 -0
  154. package/dist/ai.js +193 -0
  155. package/dist/bot.d.ts +22 -0
  156. package/dist/bot.d.ts.map +1 -0
  157. package/dist/bot.js +547 -0
  158. package/dist/bot.js.map +1 -0
  159. package/dist/commands/debug-fail.js +8 -0
  160. package/dist/commands/hello-world.js +12 -0
  161. package/dist/commands/hello.d.ts +3 -0
  162. package/dist/commands/hello.d.ts.map +1 -0
  163. package/dist/commands/hello.js +8 -0
  164. package/dist/commands/hello.js.map +1 -0
  165. package/dist/commands/index.d.ts +3 -0
  166. package/dist/commands/index.d.ts.map +1 -0
  167. package/dist/commands/index.js +12 -0
  168. package/dist/commands/index.js.map +1 -0
  169. package/dist/commands/pokemon.js +132 -0
  170. package/dist/commands/print.d.ts +9 -0
  171. package/dist/commands/print.d.ts.map +1 -0
  172. package/dist/commands/print.js +46 -0
  173. package/dist/commands/print.js.map +1 -0
  174. package/dist/commands/sudoku.js +79 -0
  175. package/dist/commands/util.js +13 -0
  176. package/dist/commands/wordsearch.js +109 -0
  177. package/dist/config.d.ts +8 -0
  178. package/dist/config.d.ts.map +1 -0
  179. package/dist/config.js +149 -0
  180. package/dist/config.js.map +1 -0
  181. package/dist/index.d.ts +2 -0
  182. package/dist/index.d.ts.map +1 -0
  183. package/dist/index.js +85 -0
  184. package/dist/index.js.map +1 -0
  185. package/dist/printer.js +245 -0
  186. package/dist/printing/cups.js +89 -0
  187. package/dist/printing/escp.js +147 -0
  188. package/dist/printing/imagefeed.js +212 -0
  189. package/dist/printing/index.js +54 -0
  190. package/dist/reactions.js +5 -0
  191. package/dist/server.d.ts +5 -0
  192. package/dist/server.d.ts.map +1 -0
  193. package/dist/server.js +312 -0
  194. package/dist/server.js.map +1 -0
  195. package/dist/tests/config.test.js +78 -0
  196. package/dist/tests/urls.test.js +54 -0
  197. package/dist/types.d.ts +55 -0
  198. package/dist/types.d.ts.map +1 -0
  199. package/dist/types.js +1 -0
  200. package/dist/types.js.map +1 -0
  201. package/dist/web/app.js +325 -0
  202. package/package.json +45 -0
  203. package/scripts/download-pokemon-sprites.mjs +38 -0
  204. package/src/ai.ts +214 -0
  205. package/src/bot.ts +657 -0
  206. package/src/commands/debug-fail.ts +11 -0
  207. package/src/commands/hello-world.ts +15 -0
  208. package/src/commands/index.ts +35 -0
  209. package/src/commands/pokemon.ts +146 -0
  210. package/src/commands/sudoku.ts +94 -0
  211. package/src/commands/util.ts +14 -0
  212. package/src/commands/wordsearch.ts +118 -0
  213. package/src/config.ts +166 -0
  214. package/src/index.ts +98 -0
  215. package/src/printer.ts +268 -0
  216. package/src/printing/cups.ts +104 -0
  217. package/src/printing/escp.ts +158 -0
  218. package/src/printing/imagefeed.ts +246 -0
  219. package/src/printing/index.ts +63 -0
  220. package/src/reactions.ts +6 -0
  221. package/src/server.ts +355 -0
  222. package/src/tests/config.test.ts +88 -0
  223. package/src/tests/urls.test.ts +64 -0
  224. package/src/types.ts +120 -0
  225. package/src/web/app.tsx +690 -0
  226. package/tsconfig.json +25 -0
@@ -0,0 +1,54 @@
1
+ import { getCurrentConfig } from '../config.js';
2
+ import { printJobEscp } from './escp.js';
3
+ import { printJobCups } from './cups.js';
4
+ import { printJobImageFeed, printTestJobImageFeed } from './imagefeed.js';
5
+ export async function printJob(job) {
6
+ const cfg = getCurrentConfig();
7
+ const printConfig = cfg.printConfig;
8
+ if (!printConfig) {
9
+ throw new Error('No print mode configured. Set a print mode in the Control Panel.');
10
+ }
11
+ switch (printConfig.mode) {
12
+ case 'escp':
13
+ await printJobEscp(job, printConfig.serialPort);
14
+ break;
15
+ case 'cups':
16
+ await printJobCups(job, printConfig);
17
+ break;
18
+ case 'imagefeed':
19
+ await printJobImageFeed(job);
20
+ break;
21
+ default: {
22
+ const never = printConfig;
23
+ throw new Error(`Unknown print mode: ${never.mode}`);
24
+ }
25
+ }
26
+ }
27
+ export async function printTestPage() {
28
+ const cfg = getCurrentConfig();
29
+ const printConfig = cfg.printConfig;
30
+ if (!printConfig) {
31
+ throw new Error('No print mode configured.');
32
+ }
33
+ const testJob = {
34
+ header: 'Windsor Test Page',
35
+ lines: [
36
+ 'Normal text — The quick brown fox jumps over the lazy dog.',
37
+ ],
38
+ urls: ['https://github.com/SeaRyanC/d2p'],
39
+ footer: 'Footer text here',
40
+ metadataLines: [`Printed at: ${new Date().toLocaleString()}`, `Mode: ${printConfig.mode}`],
41
+ };
42
+ switch (printConfig.mode) {
43
+ case 'escp':
44
+ await printJobEscp(testJob, printConfig.serialPort);
45
+ break;
46
+ case 'cups':
47
+ await printJobCups(testJob, printConfig);
48
+ break;
49
+ case 'imagefeed':
50
+ await printTestJobImageFeed(testJob);
51
+ break;
52
+ }
53
+ }
54
+ export { getImageFeed, clearImageFeed, renderFeedHtml } from './imagefeed.js';
@@ -0,0 +1,5 @@
1
+ export const Reaction = {
2
+ ok: "✅",
3
+ what: "❓",
4
+ fail: "❌"
5
+ };
@@ -0,0 +1,5 @@
1
+ import type { DiagnosticEvent, BotStatus, PublicRuntimeConfig, RuntimeConfigPatch } from './types.js';
2
+ export declare const status: BotStatus;
3
+ export declare function logEvent(type: DiagnosticEvent['type'], message: string): void;
4
+ export declare function startDiagnosticsServer(port: number, getConfig: () => PublicRuntimeConfig, updateConfig: (patch: RuntimeConfigPatch) => Promise<PublicRuntimeConfig>): void;
5
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACR,eAAe,EACf,SAAS,EACT,mBAAmB,EACnB,kBAAkB,EACrB,MAAM,YAAY,CAAC;AAKpB,eAAO,MAAM,MAAM,EAAE,SAMpB,CAAC;AAEF,wBAAgB,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAK7E;AAqND,wBAAgB,sBAAsB,CAClC,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,mBAAmB,EACpC,YAAY,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,OAAO,CAAC,mBAAmB,CAAC,GAC1E,IAAI,CAiGN"}
package/dist/server.js ADDED
@@ -0,0 +1,312 @@
1
+ import { createServer } from 'http';
2
+ import { build } from 'esbuild';
3
+ import { stat } from 'fs/promises';
4
+ import { join } from 'path';
5
+ import { getCurrentConfig, updateConfig, checkPassword, hashPassword, getConfigFilePath, } from './config.js';
6
+ import { printTestPage, getImageFeed, clearImageFeed, renderFeedHtml } from './printing/index.js';
7
+ const MAX_EVENTS = 200;
8
+ const events = [];
9
+ export const status = {
10
+ connected: false,
11
+ tag: null,
12
+ startedAt: null,
13
+ guilds: [],
14
+ configuredServerId: null,
15
+ };
16
+ export function logEvent(type, message) {
17
+ const event = { timestamp: new Date().toISOString(), type, message };
18
+ events.push(event);
19
+ if (events.length > MAX_EVENTS)
20
+ events.shift();
21
+ console.log(`[${event.timestamp}] [${type.toUpperCase()}] ${message}`);
22
+ }
23
+ function checkAuth(req) {
24
+ const config = getCurrentConfig();
25
+ if (!config.passwordHash)
26
+ return true;
27
+ const authHeader = req.headers['authorization'];
28
+ if (!authHeader || !authHeader.startsWith('Basic '))
29
+ return false;
30
+ const base64 = authHeader.slice(6);
31
+ const decoded = Buffer.from(base64, 'base64').toString('utf8');
32
+ const colonIdx = decoded.indexOf(':');
33
+ const password = colonIdx === -1 ? decoded : decoded.slice(colonIdx + 1);
34
+ return checkPassword(password);
35
+ }
36
+ function requireAuth(req, res) {
37
+ if (!checkAuth(req)) {
38
+ res.writeHead(401, {
39
+ 'WWW-Authenticate': 'Basic realm="Windsor"',
40
+ 'Content-Type': 'application/json',
41
+ });
42
+ res.end(JSON.stringify({ error: 'Unauthorized' }));
43
+ return false;
44
+ }
45
+ return true;
46
+ }
47
+ function sendJson(res, statusCode, payload) {
48
+ const body = JSON.stringify(payload, null, 2);
49
+ res.writeHead(statusCode, {
50
+ 'Content-Type': 'application/json; charset=utf-8',
51
+ 'Content-Length': Buffer.byteLength(body),
52
+ });
53
+ res.end(body);
54
+ }
55
+ async function readJsonBody(req) {
56
+ const chunks = [];
57
+ let total = 0;
58
+ for await (const chunk of req) {
59
+ const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
60
+ total += buf.byteLength;
61
+ if (total > 1_000_000)
62
+ throw new Error('Request body too large');
63
+ chunks.push(buf);
64
+ }
65
+ if (chunks.length === 0)
66
+ return {};
67
+ const raw = Buffer.concat(chunks).toString('utf8');
68
+ const parsed = JSON.parse(raw);
69
+ if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
70
+ throw new Error('JSON body must be an object');
71
+ }
72
+ return parsed;
73
+ }
74
+ let appBundleCache = null;
75
+ async function loadAppScript() {
76
+ const entryPoint = join(process.cwd(), 'src', 'web', 'app.tsx');
77
+ const sourceStat = await stat(entryPoint);
78
+ if (appBundleCache && appBundleCache.mtimeMs === sourceStat.mtimeMs) {
79
+ return appBundleCache.script;
80
+ }
81
+ const result = await build({
82
+ entryPoints: [entryPoint],
83
+ bundle: true,
84
+ write: false,
85
+ platform: 'browser',
86
+ format: 'esm',
87
+ target: ['es2022'],
88
+ jsx: 'automatic',
89
+ jsxImportSource: 'preact',
90
+ sourcemap: 'inline',
91
+ });
92
+ const firstFile = result.outputFiles[0];
93
+ if (!firstFile)
94
+ throw new Error('Failed to generate app bundle');
95
+ appBundleCache = { script: firstFile.text, mtimeMs: sourceStat.mtimeMs };
96
+ return firstFile.text;
97
+ }
98
+ function renderHtmlShell() {
99
+ return `<!DOCTYPE html>
100
+ <html>
101
+ <head>
102
+ <meta charset="utf-8"/>
103
+ <meta name="viewport" content="width=device-width, initial-scale=1"/>
104
+ <title>Windsor Control Panel</title>
105
+ <style>
106
+ :root { color-scheme: dark; }
107
+ body { margin: 0; font-family: ui-sans-serif, system-ui, -apple-system, sans-serif; background: #0f1115; color: #f5f7fa; }
108
+ a { color: #8ab4ff; }
109
+ </style>
110
+ </head>
111
+ <body>
112
+ <div id="app"></div>
113
+ <script type="module" src="/app.js"></script>
114
+ </body>
115
+ </html>`;
116
+ }
117
+ /** Registered Discord channels (set by bot on startup) */
118
+ let discordChannels = [];
119
+ export function setDiscordChannels(channels) {
120
+ discordChannels = channels;
121
+ }
122
+ let _restartHandler = null;
123
+ export function setRestartHandler(fn) {
124
+ _restartHandler = fn;
125
+ }
126
+ let _refreshChannelsHandler = null;
127
+ export function setRefreshChannelsHandler(fn) {
128
+ _refreshChannelsHandler = fn;
129
+ }
130
+ export function startDiagnosticsServer(port) {
131
+ const server = createServer(async (req, res) => {
132
+ try {
133
+ const method = req.method ?? 'GET';
134
+ const rawUrl = req.url ?? '/';
135
+ const urlPath = rawUrl.split('?')[0] ?? '/';
136
+ // Static assets (no auth required)
137
+ if (method === 'GET' && urlPath === '/app.js') {
138
+ const script = await loadAppScript();
139
+ res.writeHead(200, { 'Content-Type': 'application/javascript; charset=utf-8' });
140
+ res.end(script);
141
+ return;
142
+ }
143
+ if (method === 'GET' && urlPath === '/') {
144
+ const html = renderHtmlShell();
145
+ res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
146
+ res.end(html);
147
+ return;
148
+ }
149
+ if (method === 'GET' && urlPath === '/feed') {
150
+ res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
151
+ res.end(renderFeedHtml());
152
+ return;
153
+ }
154
+ if (method === 'POST' && urlPath === '/feed/clear') {
155
+ clearImageFeed();
156
+ res.writeHead(303, { 'Location': '/feed' });
157
+ res.end();
158
+ return;
159
+ }
160
+ // All API routes require auth
161
+ if (!requireAuth(req, res))
162
+ return;
163
+ if (method === 'GET' && urlPath === '/api/events') {
164
+ sendJson(res, 200, { status, events: [...events].reverse() });
165
+ return;
166
+ }
167
+ if (method === 'GET' && urlPath === '/api/status') {
168
+ sendJson(res, 200, status);
169
+ return;
170
+ }
171
+ if (method === 'GET' && urlPath === '/api/config') {
172
+ const cfg = getCurrentConfig();
173
+ sendJson(res, 200, {
174
+ discordToken: cfg.discordToken ?? '',
175
+ openaiKey: cfg.openaiKey ?? '',
176
+ serverId: cfg.serverId ?? null,
177
+ diagnosticsPort: cfg.diagnosticsPort,
178
+ channels: cfg.channels,
179
+ configPath: getConfigFilePath(),
180
+ });
181
+ return;
182
+ }
183
+ if ((method === 'POST' || method === 'PUT') && urlPath === '/api/config') {
184
+ const body = await readJsonBody(req);
185
+ const patch = {};
186
+ if (Object.hasOwn(body, 'discordToken') && typeof body['discordToken'] === 'string') {
187
+ patch['discordToken'] = body['discordToken'].trim() || undefined;
188
+ }
189
+ if (Object.hasOwn(body, 'serverId')) {
190
+ const v = body['serverId'];
191
+ patch['serverId'] = typeof v === 'string' && v.trim() ? v.trim() : undefined;
192
+ }
193
+ if (Object.hasOwn(body, 'openaiKey') && typeof body['openaiKey'] === 'string') {
194
+ patch['openaiKey'] = body['openaiKey'].trim() || undefined;
195
+ }
196
+ if (Object.hasOwn(body, 'diagnosticsPort') && typeof body['diagnosticsPort'] === 'number') {
197
+ patch['diagnosticsPort'] = body['diagnosticsPort'];
198
+ }
199
+ if (Object.hasOwn(body, 'password') && typeof body['password'] === 'string') {
200
+ const pw = body['password'].trim();
201
+ if (pw) {
202
+ patch['passwordHash'] = hashPassword(pw);
203
+ }
204
+ else {
205
+ patch['passwordHash'] = undefined;
206
+ }
207
+ }
208
+ await updateConfig(patch);
209
+ const updated = getCurrentConfig();
210
+ sendJson(res, 200, {
211
+ discordToken: updated.discordToken ?? '',
212
+ openaiKey: updated.openaiKey ?? '',
213
+ serverId: updated.serverId ?? null,
214
+ diagnosticsPort: updated.diagnosticsPort,
215
+ channels: updated.channels,
216
+ configPath: getConfigFilePath(),
217
+ });
218
+ return;
219
+ }
220
+ if (method === 'GET' && urlPath === '/api/channels') {
221
+ const config = getCurrentConfig();
222
+ sendJson(res, 200, {
223
+ channels: config.channels,
224
+ discordChannels,
225
+ unmapped: discordChannels.filter(dc => !config.channels.find(m => m.channelId === dc.id)),
226
+ });
227
+ return;
228
+ }
229
+ if (method === 'POST' && urlPath === '/api/channels') {
230
+ const body = await readJsonBody(req);
231
+ const config = getCurrentConfig();
232
+ const newMapping = body;
233
+ const channels = [...config.channels, newMapping];
234
+ await updateConfig({ channels });
235
+ sendJson(res, 200, { channels: getCurrentConfig().channels });
236
+ return;
237
+ }
238
+ const channelDeleteMatch = /^\/api\/channels\/([^/]+)$/.exec(urlPath);
239
+ if (channelDeleteMatch) {
240
+ const channelId = channelDeleteMatch[1];
241
+ if (method === 'DELETE') {
242
+ const config = getCurrentConfig();
243
+ const channels = config.channels.filter(m => m.channelId !== channelId);
244
+ await updateConfig({ channels });
245
+ sendJson(res, 200, { channels: getCurrentConfig().channels });
246
+ return;
247
+ }
248
+ if (method === 'PUT') {
249
+ const body = await readJsonBody(req);
250
+ const config = getCurrentConfig();
251
+ const channels = config.channels.map(m => m.channelId === channelId ? { ...m, ...body } : m);
252
+ await updateConfig({ channels });
253
+ sendJson(res, 200, { channels: getCurrentConfig().channels });
254
+ return;
255
+ }
256
+ }
257
+ if (method === 'GET' && urlPath === '/api/print-config') {
258
+ const cfg = getCurrentConfig();
259
+ sendJson(res, 200, { printConfig: cfg.printConfig ?? null });
260
+ return;
261
+ }
262
+ if ((method === 'POST' || method === 'PUT') && urlPath === '/api/print-config') {
263
+ const body = await readJsonBody(req);
264
+ const printConfig = body['printConfig'];
265
+ const patch = {};
266
+ if (printConfig != null)
267
+ patch.printConfig = printConfig;
268
+ await updateConfig(patch);
269
+ sendJson(res, 200, { printConfig: getCurrentConfig().printConfig ?? null });
270
+ return;
271
+ }
272
+ if (method === 'POST' && urlPath === '/api/printer/test') {
273
+ await printTestPage();
274
+ logEvent('print', 'Test page printed');
275
+ sendJson(res, 200, { ok: true });
276
+ return;
277
+ }
278
+ if (method === 'POST' && urlPath === '/api/restart') {
279
+ logEvent('info', 'Restart requested via API');
280
+ sendJson(res, 200, { ok: true });
281
+ if (_restartHandler) {
282
+ void _restartHandler();
283
+ }
284
+ return;
285
+ }
286
+ if (method === 'POST' && urlPath === '/api/channels/refresh') {
287
+ if (_refreshChannelsHandler) {
288
+ await _refreshChannelsHandler();
289
+ const config = getCurrentConfig();
290
+ sendJson(res, 200, {
291
+ channels: config.channels,
292
+ discordChannels,
293
+ unmapped: discordChannels.filter(dc => !config.channels.find(m => m.channelId === dc.id)),
294
+ });
295
+ }
296
+ else {
297
+ sendJson(res, 503, { error: 'Bot not connected' });
298
+ }
299
+ return;
300
+ }
301
+ sendJson(res, 404, { error: 'Not found' });
302
+ }
303
+ catch (err) {
304
+ const message = err instanceof Error ? err.message : String(err);
305
+ sendJson(res, 400, { error: message });
306
+ logEvent('error', `API error: ${message}`);
307
+ }
308
+ });
309
+ server.listen(port, '0.0.0.0', () => {
310
+ logEvent('startup', `Diagnostics server listening on http://0.0.0.0:${port}`);
311
+ });
312
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,78 @@
1
+ import { test } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { reconcileChannels, getCurrentConfig, updateConfig, hashPassword } from '../config.js';
4
+ // Setup: initialize config with some channel mappings
5
+ async function withChannels(mappings, fn) {
6
+ const original = getCurrentConfig().channels;
7
+ await updateConfig({ channels: mappings });
8
+ try {
9
+ await fn();
10
+ }
11
+ finally {
12
+ await updateConfig({ channels: original });
13
+ }
14
+ }
15
+ test('reconcileChannels: matches by ID and updates name', async () => {
16
+ await withChannels([
17
+ { channelId: '111', channelName: 'old-name', config: { type: 'on-demand' } },
18
+ ], async () => {
19
+ await reconcileChannels([{ id: '111', name: 'new-name' }]);
20
+ const channels = getCurrentConfig().channels;
21
+ assert.equal(channels.length, 1);
22
+ assert.equal(channels[0]?.channelName, 'new-name');
23
+ assert.equal(channels[0]?.channelId, '111');
24
+ });
25
+ });
26
+ test('reconcileChannels: matches by name and updates ID', async () => {
27
+ await withChannels([
28
+ { channelId: '111', channelName: 'my-channel', config: { type: 'on-demand' } },
29
+ ], async () => {
30
+ await reconcileChannels([{ id: '222', name: 'my-channel' }]);
31
+ const channels = getCurrentConfig().channels;
32
+ assert.equal(channels.length, 1);
33
+ assert.equal(channels[0]?.channelId, '222');
34
+ assert.equal(channels[0]?.channelName, 'my-channel');
35
+ });
36
+ });
37
+ test('reconcileChannels: deletes unmatched channels', async () => {
38
+ await withChannels([
39
+ { channelId: '111', channelName: 'gone-channel', config: { type: 'on-demand' } },
40
+ ], async () => {
41
+ await reconcileChannels([{ id: '999', name: 'different-channel' }]);
42
+ const channels = getCurrentConfig().channels;
43
+ assert.equal(channels.length, 0);
44
+ });
45
+ });
46
+ test('reconcileChannels: does NOT delete if all discord channels missing (server blip)', async () => {
47
+ await withChannels([
48
+ { channelId: '111', channelName: 'my-channel', config: { type: 'on-demand' } },
49
+ ], async () => {
50
+ await reconcileChannels([]);
51
+ const channels = getCurrentConfig().channels;
52
+ assert.equal(channels.length, 1);
53
+ });
54
+ });
55
+ test('hashPassword produces consistent hash', () => {
56
+ const hash1 = hashPassword('secret');
57
+ const hash2 = hashPassword('secret');
58
+ assert.equal(hash1, hash2);
59
+ });
60
+ test('hashPassword produces different hashes for different passwords', () => {
61
+ assert.notEqual(hashPassword('abc'), hashPassword('xyz'));
62
+ });
63
+ test('token usage 24h tracking', async () => {
64
+ // Setup with stale and recent entries
65
+ const now = new Date();
66
+ const twoHoursAgo = new Date(now.getTime() - 2 * 60 * 60 * 1000).toISOString();
67
+ const twoDaysAgo = new Date(now.getTime() - 48 * 60 * 60 * 1000).toISOString();
68
+ await updateConfig({
69
+ tokenUsage: [
70
+ { timestamp: twoDaysAgo, tokens: 10000 },
71
+ { timestamp: twoHoursAgo, tokens: 5000 },
72
+ ],
73
+ });
74
+ const { getTokenUsage24h } = await import('../ai.js');
75
+ const used = getTokenUsage24h();
76
+ // Should only count the 2-hours-ago entry (5000), not the 2-days-ago one
77
+ assert.equal(used, 5000);
78
+ });
@@ -0,0 +1,54 @@
1
+ import { test } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { extractUrls, stripUrls, replaceUrlsInText } from '../bot.js';
4
+ test('extracts a single URL', () => {
5
+ const urls = extractUrls('Check this out https://example.com for more info');
6
+ assert.deepEqual(urls, ['https://example.com']);
7
+ });
8
+ test('strips trailing punctuation from URLs', () => {
9
+ const urls = extractUrls('See https://example.com. For details.');
10
+ assert.deepEqual(urls, ['https://example.com']);
11
+ });
12
+ test('extracts multiple URLs', () => {
13
+ const urls = extractUrls('https://foo.com and https://bar.com are cool');
14
+ assert.deepEqual(urls, ['https://foo.com', 'https://bar.com']);
15
+ });
16
+ test('caps at 5 URLs', () => {
17
+ const text = 'https://a.com https://b.com https://c.com https://d.com https://e.com https://f.com';
18
+ const urls = extractUrls(text);
19
+ assert.equal(urls.length, 5);
20
+ });
21
+ test('ignores non-https URLs', () => {
22
+ const urls = extractUrls('Visit http://example.com or ftp://files.com');
23
+ assert.deepEqual(urls, []);
24
+ });
25
+ test('replaces single URL with [link]', () => {
26
+ const text = 'See https://example.com for info';
27
+ const urls = extractUrls(text);
28
+ const result = replaceUrlsInText(text, urls);
29
+ assert.equal(result, 'See [link] for info');
30
+ });
31
+ test('replaces multiple URLs with [link N]', () => {
32
+ const text = 'See https://foo.com and https://bar.com';
33
+ const urls = extractUrls(text);
34
+ const result = replaceUrlsInText(text, urls);
35
+ assert.ok(result.includes('[link 1]'));
36
+ assert.ok(result.includes('[link 2]'));
37
+ });
38
+ test('strips all URLs from text', () => {
39
+ const text = 'Buy this https://shop.com today!';
40
+ const result = stripUrls(text);
41
+ assert.ok(!result.includes('https://'));
42
+ assert.ok(result.includes('Buy this'));
43
+ });
44
+ test('message over 800 chars after URL strip is too long', () => {
45
+ const longText = 'A'.repeat(801);
46
+ const stripped = stripUrls(longText);
47
+ assert.ok(stripped.length > 800);
48
+ });
49
+ test('message exactly 800 chars is not too long', () => {
50
+ const text = 'A'.repeat(800);
51
+ const stripped = stripUrls(text);
52
+ assert.equal(stripped.length, 800);
53
+ assert.ok(stripped.length <= 800);
54
+ });
@@ -0,0 +1,55 @@
1
+ export interface CommandContext {
2
+ /** The name of the channel the message was sent in */
3
+ channelName: string;
4
+ /** Everything after `!commandName ` */
5
+ args: string;
6
+ /** The full original message content */
7
+ rawMessage: string;
8
+ messageId: string;
9
+ channelId: string;
10
+ guildId: string;
11
+ }
12
+ export interface BotCommand {
13
+ /** Command name (the word immediately after `!`) */
14
+ name: string;
15
+ /**
16
+ * Channel names this command is active in.
17
+ * Empty array means the command is active in all channels.
18
+ */
19
+ channels: string[];
20
+ description: string;
21
+ execute(context: CommandContext): Promise<void>;
22
+ }
23
+ export type DiagnosticEventType = 'startup' | 'command' | 'success' | 'error' | 'info';
24
+ export interface DiagnosticEvent {
25
+ timestamp: string;
26
+ type: DiagnosticEventType;
27
+ message: string;
28
+ }
29
+ export interface BotStatus {
30
+ connected: boolean;
31
+ tag: string | null;
32
+ startedAt: string | null;
33
+ guilds: string[];
34
+ configuredServerId: string | null;
35
+ }
36
+ export interface RuntimeConfig {
37
+ discordToken: string | null;
38
+ serverId: string | null;
39
+ diagnosticsPort: number;
40
+ printerName: string | null;
41
+ }
42
+ export interface PublicRuntimeConfig {
43
+ hasDiscordToken: boolean;
44
+ serverId: string | null;
45
+ diagnosticsPort: number;
46
+ printerName: string | null;
47
+ configPath: string;
48
+ }
49
+ export interface RuntimeConfigPatch {
50
+ discordToken?: string | null;
51
+ serverId?: string | null;
52
+ diagnosticsPort?: number;
53
+ printerName?: string | null;
54
+ }
55
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,cAAc;IAC3B,sDAAsD;IACtD,WAAW,EAAE,MAAM,CAAC;IACpB,uCAAuC;IACvC,IAAI,EAAE,MAAM,CAAC;IACb,wCAAwC;IACxC,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,UAAU;IACvB,oDAAoD;IACpD,IAAI,EAAE,MAAM,CAAC;IACb;;;OAGG;IACH,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACnD;AAED,MAAM,MAAM,mBAAmB,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,GAAG,MAAM,CAAC;AAEvF,MAAM,WAAW,eAAe;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,mBAAmB,CAAC;IAC1B,OAAO,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,SAAS;IACtB,SAAS,EAAE,OAAO,CAAC;IACnB,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;CACrC;AAED,MAAM,WAAW,aAAa;IAC1B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AAED,MAAM,WAAW,mBAAmB;IAChC,eAAe,EAAE,OAAO,CAAC;IACzB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,UAAU,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,kBAAkB;IAC/B,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B"}
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}