windsor-bot 0.1.7 → 0.1.10

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 (46) hide show
  1. package/README.md +330 -77
  2. package/assets/wordsearches/animals.txt +404 -0
  3. package/assets/wordsearches/colors.txt +404 -0
  4. package/assets/wordsearches/food.txt +404 -0
  5. package/assets/wordsearches/fruits.txt +404 -0
  6. package/assets/wordsearches/household.txt +404 -0
  7. package/assets/wordsearches/music.txt +404 -0
  8. package/assets/wordsearches/nature.txt +404 -0
  9. package/assets/wordsearches/space.txt +404 -0
  10. package/assets/wordsearches/sports.txt +404 -0
  11. package/assets/wordsearches/travel.txt +404 -0
  12. package/dist/bot.js +82 -9
  13. package/dist/commands/index.js +2 -0
  14. package/dist/commands/pokemon.js +1 -1
  15. package/dist/commands/retry.js +17 -0
  16. package/dist/commands/wordsearch.js +111 -89
  17. package/dist/reactions.js +2 -1
  18. package/dist/web/app.bundle.js +23 -4
  19. package/dist/web/app.js +20 -1
  20. package/package.json +5 -2
  21. package/dist/bot.d.ts +0 -22
  22. package/dist/bot.d.ts.map +0 -1
  23. package/dist/bot.js.map +0 -1
  24. package/dist/commands/hello.d.ts +0 -3
  25. package/dist/commands/hello.d.ts.map +0 -1
  26. package/dist/commands/hello.js +0 -8
  27. package/dist/commands/hello.js.map +0 -1
  28. package/dist/commands/index.d.ts +0 -3
  29. package/dist/commands/index.d.ts.map +0 -1
  30. package/dist/commands/index.js.map +0 -1
  31. package/dist/commands/print.d.ts +0 -9
  32. package/dist/commands/print.d.ts.map +0 -1
  33. package/dist/commands/print.js +0 -46
  34. package/dist/commands/print.js.map +0 -1
  35. package/dist/config.d.ts +0 -8
  36. package/dist/config.d.ts.map +0 -1
  37. package/dist/config.js.map +0 -1
  38. package/dist/index.d.ts +0 -2
  39. package/dist/index.d.ts.map +0 -1
  40. package/dist/index.js.map +0 -1
  41. package/dist/server.d.ts +0 -5
  42. package/dist/server.d.ts.map +0 -1
  43. package/dist/server.js.map +0 -1
  44. package/dist/types.d.ts +0 -55
  45. package/dist/types.d.ts.map +0 -1
  46. package/dist/types.js.map +0 -1
package/README.md CHANGED
@@ -1,135 +1,388 @@
1
1
  # Windsor: Your Friendly Household Helper Bot
2
2
 
3
- You'll need:
4
- * A Raspberry Pi (or equivalent)
5
- * Thermal printer connected to its USB port
3
+ Windsor turns Discord messages into useful paper: shopping lists, reminders, QR
4
+ codes, puzzles, and more. It is designed to run continuously on a Raspberry Pi
5
+ with a USB thermal printer, but it also supports any Node.js machine with a
6
+ serial printer, a CUPS printer, or the browser-based image feed.
6
7
 
7
- ## Initial Setup
8
+ ## What you need
8
9
 
9
- ### Create a Discord Bot
10
+ * A Discord server where you can install an app
11
+ * A computer that can stay online (a Raspberry Pi is ideal!)
12
+ * A compatible printer, connected by USB or available through CUPS
13
+ * (Optional) An OpenAI API key enables certain features
10
14
 
11
- <!-- Write instructions on how to create a Discord bot -->
15
+ ## Installation
12
16
 
13
- ### Invite to Server
17
+ Install the published package globally:
14
18
 
15
- <!-- Write instructions on how to invite the bot to your server -->
19
+ ```bash
20
+ npm install -g windsor-bot
21
+ ```
16
22
 
17
- ### Set up Raspberry Pi
23
+ The global install provides the `windsor-bot` command. Windsor creates
24
+ `windsor.config.json` in its working directory, so choose a permanent directory
25
+ for the bot before starting it.
26
+
27
+ ## Initial setup
28
+
29
+ ### 1. Create a Discord bot
30
+
31
+ 1. Open the [Discord Developer Portal](https://discord.com/developers/applications)
32
+ and select **New Application**.
33
+ 2. Give the application a name and open its **Bot** page.
34
+ 3. Click **Reset Token**, then copy the token. Treat it like a password: do
35
+ not commit it, paste it into public issues, or share it. Discord will not
36
+ show the token again without resetting it.
37
+ 4. On the **Bot** page, enable the **Message Content Intent**. Windsor reads
38
+ ordinary message text, so this privileged intent is required. Windsor also
39
+ uses guilds, guild messages, and guild message reactions.
18
40
 
19
- <!-- Write high-level instructions on how to set up a headless Raspberry Pi -->
41
+ ### 2. Invite the bot to your server
42
+
43
+ From the application's **Installation** or **OAuth2 > URL Generator** page,
44
+ create an installation URL with the `bot` scope. Grant these permissions:
45
+
46
+ * View Channels
47
+ * Send Messages
48
+ * Read Message History
49
+ * Add Reactions
50
+
51
+ Open the generated URL, choose the target server, and authorize it. The person
52
+ installing the bot must have permission to manage the server. The bot must be
53
+ able to view and read history in every channel that Windsor monitors.
54
+
55
+ ### 3. Set up a Raspberry Pi (headless)
56
+
57
+ These steps assume a Raspberry Pi 4 or 5, a blank microSD card, and a wired or
58
+ wireless network. The [official Raspberry Pi OS documentation](https://www.raspberrypi.com/documentation/computers/getting-started.html)
59
+ has screenshots and troubleshooting for each step.
60
+
61
+ 1. Use [Raspberry Pi Imager](https://www.raspberrypi.com/software/) to write
62
+ **Raspberry Pi OS Lite (64-bit)** to the microSD card. Before writing,
63
+ open the OS customization screen and set:
64
+ * a hostname such as `windsor-pi`
65
+ * a username such as `piuser` (the Linux account that will run Windsor)
66
+ * a strong password
67
+ * your Wi-Fi country, network name, and password (if using Wi-Fi)
68
+ * the correct time zone and keyboard layout
69
+ * **Enable SSH**, using password authentication for the first login
70
+ 2. Eject the card, insert it into the Pi, connect the printer, and power on the
71
+ Pi. Wait two to five minutes for the first boot.
72
+ 3. From another computer on the same network, connect using the hostname:
73
+
74
+ ```bash
75
+ ssh piuser@windsor-pi.local
76
+ ```
77
+
78
+ If that name does not resolve, find the Pi in the router's client list and
79
+ use its address instead:
80
+
81
+ ```bash
82
+ ssh piuser@192.168.1.50
83
+ ```
84
+
85
+ See the
86
+ [Raspberry Pi SSH guide](https://www.raspberrypi.com/documentation/computers/remote-access.html#ssh)
87
+ if hostname discovery does not work.
88
+ 4. Update Raspberry Pi OS and install Node.js, npm, and the optional CUPS
89
+ printing tools:
90
+
91
+ ```bash
92
+ sudo apt update && sudo apt upgrade -y
93
+ sudo apt install -y nodejs npm cups
94
+ node --version
95
+ npm --version
96
+ ```
97
+
98
+ 5. Install Windsor globally:
99
+
100
+ ```bash
101
+ sudo npm install --global windsor-bot
102
+ command -v windsor-bot
103
+ ```
104
+
105
+ 6. Create a dedicated working directory. Configuration and cached icons will
106
+ be stored here:
107
+
108
+ ```bash
109
+ mkdir -p ~/windsor
110
+ cd ~/windsor
111
+ ```
112
+
113
+ 7. Connect the USB printer. For a direct ESC/P printer, identify the device:
114
+
115
+ ```bash
116
+ ls -l /dev/ttyUSB* /dev/ttyACM* 2>/dev/null
117
+ ```
118
+
119
+ The usual result is `/dev/ttyUSB0` or `/dev/ttyACM0`. Add the login user to
120
+ the device-access group, then reconnect over SSH:
121
+
122
+ ```bash
123
+ sudo usermod -aG dialout "$USER"
124
+ exit
125
+ ssh piuser@windsor-pi.local
126
+ cd ~/windsor
127
+ ```
128
+
129
+ For a CUPS printer, enable the service and list configured printers:
130
+
131
+ ```bash
132
+ sudo systemctl enable --now cups
133
+ lpstat -p
134
+ ```
135
+
136
+ Add the printer in CUPS, then use the name returned by `lpstat -p` in the
137
+ Windsor control panel.
138
+
139
+ ### 4. Install Windsor as a boot-time service
20
140
 
21
- ### Configure Credentials
141
+ Windsor should run under `systemd`, so it starts automatically whenever the Pi
142
+ boots and restarts if the process exits. `systemd` does not use your interactive
143
+ shell's username or `nvm` settings, so use the actual account and absolute
144
+ binary path. Record them while logged in as the account that should run
145
+ Windsor:
22
146
 
23
- <!-- Write instructions on how to get the right kind of discord API token here -->
147
+ ```bash
148
+ id -un
149
+ echo "$HOME"
150
+ command -v windsor-bot
151
+ ```
24
152
 
25
- ## Raspbery Pi Setup
153
+ The first command must print an existing Linux username, and the last command
154
+ must print an executable path. In the example below the account is `piuser`,
155
+ the home directory is `/home/piuser`, and the installed command is
156
+ `/usr/bin/windsor-bot`. If your output is different, substitute your values
157
+ everywhere in the service file. For example, a Pi account named `windsor`
158
+ uses `User=windsor` and `/home/windsor/...`; do not leave `User=piuser` unless
159
+ that account actually exists.
26
160
 
27
- Windsor runs great on a Raspberry Pi.
161
+ Create `/etc/systemd/system/windsor.service`:
28
162
 
163
+ ```bash
164
+ sudo nano /etc/systemd/system/windsor.service
165
+ ```
29
166
 
30
- ## Automatic Behaviors
167
+ Paste the following, replacing the three example values with the output from
168
+ the commands above:
169
+
170
+ ```ini
171
+ [Unit]
172
+ Description=Windsor Bot
173
+ After=network-online.target
174
+ Wants=network-online.target
175
+
176
+ [Service]
177
+ Type=simple
178
+ User=piuser
179
+ WorkingDirectory=/home/piuser/windsor
180
+ ExecStart=/usr/bin/windsor-bot
181
+ Restart=on-failure
182
+ RestartSec=10
183
+
184
+ [Install]
185
+ WantedBy=multi-user.target
186
+ ```
31
187
 
32
- Windsor will automatically convert all URLs in messages to QR codes. These always appear after the message text, regardless of where they appeared in the message. If the URL was in the middle of the message, it'll be replaced with `[link]`.
188
+ Enable and start the service:
33
189
 
34
- ## Channel Configuration
190
+ ```bash
191
+ sudo systemctl daemon-reload
192
+ sudo systemctl enable windsor
193
+ sudo systemctl start windsor
194
+ sudo systemctl status windsor
195
+ ```
35
196
 
36
- Windsor is primarily configured by assigning a *channel behaviors* to your Discord server's channels.
37
- You don't need to assign a behavior for every channel; channels without a behavior won't be monitored.
197
+ The service starts Windsor on every boot. View its logs with:
38
198
 
39
- The following channel behaviors are supported.
199
+ ```bash
200
+ journalctl -u windsor -f
201
+ ```
40
202
 
41
- ### 🖨️ Immediate Print
203
+ If the unit was already started with the wrong username or path, edit the unit,
204
+ then run `sudo systemctl daemon-reload`, `sudo systemctl reset-failed windsor`,
205
+ and `sudo systemctl restart windsor`.
42
206
 
43
- In *immediate print* mode, messages posted to the channel are immediately printed off.
44
- This is ideal for "to do" items.
207
+ ### 5. Configure credentials
45
208
 
46
- <!-- TODO (human): photo of a sample -->
209
+ Open [http://localhost:8080](http://localhost:8080) (or port 8080 on the Pi's
210
+ hostname/IP) from a computer on the same network. The service starts with no
211
+ Discord token, but the control panel is available immediately. Enter:
47
212
 
48
- You can enable these settings in the configuration panel:
49
- * **Header**: Include this text at the top of every printout.
50
- * **Icon**: Includes an image based on the message content. Requires OpenAI key.
51
- * **Metadata Footer**: Include footer lines with timestamp and message initiator.
52
- * **Footer**: Include this text at the bottom of every printout.
213
+ 1. The Discord bot token.
214
+ 2. Optionally, the server ID to restrict Windsor to one server. To copy
215
+ IDs in Discord, enable **Developer Mode**, then right-click the server and
216
+ choose **Copy Server ID**.
217
+ 3. Optionally enter an OpenAI API key. It is required for AI-generated icons
218
+ and recurring schedules.
219
+ 4. Set a control-panel password in the **Security** section.
220
+ 5. Save, then click **Restart Server**. This restarts the bot while leaving the
221
+ boot service enabled.
53
222
 
54
- ### 🛒 Accumulating List
223
+ Credentials may instead be supplied through environment variables:
55
224
 
56
- In *accumulating list* mode, each printout is a list of messages since the prior printout.
57
- This is ideal for shopping lists.
225
+ | Variable | Purpose |
226
+ | --- | --- |
227
+ | `DISCORD_TOKEN` | Discord bot token |
228
+ | `SERVER_ID` | Optional Discord server ID |
229
+ | `OPENAI_API_KEY` | Optional OpenAI API key |
230
+ | `DIAGNOSTICS_PORT` | Control-panel port, default `8080` |
231
+ | `WINDSOR_CONFIG_PATH` | Path to the configuration file |
58
232
 
59
- Trigger a printout of all unprinted items by posting the message "print" in the channel.
233
+ The control panel listens on all interfaces. Set a control-panel password in
234
+ the **Security** section before exposing it beyond your trusted local network.
235
+ `windsor.config.json` is gitignored because it contains secrets.
60
236
 
61
- <!-- TODO (human): photo of a sample -->
237
+ ## Configure printing
62
238
 
63
- You can enable these settings in the configuration panel:
239
+ Open the **Print Mode** section in the control panel, select an output mode, and
240
+ save it. Use **Print Test Page** before configuring channels.
64
241
 
65
- * **Header**: Include this text at the top of every printout.
66
- * **Checklist**: Include checkmark boxes next to each line
67
- * **Metadata Footer**: Include footer lines with timestamp and message initiator
68
- * **Footer**: Include this text at the bottom of every printout.
242
+ * **ESC/P via Serial Port** sends thermal-printer commands directly to a
243
+ device such as `/dev/ttyUSB0` (or `COM3` on Windows).
244
+ * **PDF via CUPS** renders a PDF and submits it with `lp`. Enter the CUPS
245
+ printer name, paper size (for example `Letter`, `A4`, or `80x297mm`), and
246
+ the display label for the paper.
247
+ * **Image Feed** renders jobs as PNGs instead of printing them. View them at
248
+ `/feed`; this is useful for development and printer-independent testing.
69
249
 
70
- ### 🔄 Recurring Print
250
+ ## Configure channels
71
251
 
72
- *Recurring print* lets you set up automatic recurring printouts.
73
- These work the same way as *immediate print*, but with a repeating schedule.
74
- This feature requires an OpenAI key.
252
+ Windsor only monitors channels that have a behavior assigned. In **Channel
253
+ Behaviors**, choose a Discord text channel, select a behavior, and click
254
+ **Add**. Expand a channel row to edit its options. Use **Refresh Discord
255
+ Channels** after creating or renaming channels.
75
256
 
76
- If you have an OpenAI key, the recurrence schedule will be determined automatically based on the text of the message. For example, you can write:
257
+ ### 🖨️ Immediate Print
77
258
 
78
- > Take out the trash every other Tuesday at 7 PM
259
+ Every eligible message is printed immediately. This is useful for to-do items
260
+ and quick notes. Available options:
79
261
 
80
- AI will understand almost any syntax, e.g. you can write messges like any of these:
262
+ * **Header** and **Footer**: fixed text around each job
263
+ * **Include metadata footer**: includes the author and timestamp
264
+ * **Include AI-generated icon**: creates an icon from the message (requires an
265
+ OpenAI key)
81
266
 
82
- > Every other Tuesday, run the dishwasher clean cycle
83
- >
84
- > Change out the batteries on the 3rd of the month at 2 PM
85
- >
86
- > Call Bob a week before his birthday (6/12)
267
+ Messages can contain up to 800 non-URL characters. Up to five `https://` URLs
268
+ are extracted, replaced in the text with `[link]` (or `[link 1]`, etc.), and
269
+ printed as QR codes after the message.
87
270
 
88
- If time of day isn't specified, the default is 8:00 AM local time.
271
+ ### 🛒 Accumulating List
89
272
 
90
- Windsor will reply to you with its understood schedule. If you were misinterpreted, delete your message and rephrase in a new one.
273
+ Messages are collected until someone posts `print` (case-insensitive; extra
274
+ punctuation is allowed). Windsor prints the items since the previous successful
275
+ print and marks the trigger with a reaction. This is useful for shopping lists.
91
276
 
92
- ### On-Demand
277
+ Available options are **Header**, **Footer**, **Include metadata footer**, and
278
+ **Include checklist boxes**.
93
279
 
94
- In on-demand channels, only specific commands will be processed. Other messages will be ignored. See the list in this document for supported commands.
280
+ ### 🔄 Recurring Print
95
281
 
96
- ## On-Demand Commands
282
+ Each message becomes a scheduled print. This behavior requires an OpenAI key
283
+ because Windsor uses it to interpret natural-language schedules. Examples:
97
284
 
98
- Windsor includes many fun commands for printing one-off items
285
+ ```text
286
+ Take out the trash every other Tuesday at 7 PM
287
+ Change out the batteries on the 3rd of the month at 2 PM
288
+ Call Bob a week before his birthday (6/12)
289
+ ```
99
290
 
100
- ### `/sudoku [kid | easy | medium | hard]`
291
+ Windsor replies with the interpreted schedule. If it is wrong, delete the
292
+ original message and post a corrected version. If no time is specified, the
293
+ default is 8:00 AM local time. Schedules are restored from recent channel
294
+ history when the bot starts.
101
295
 
102
- Prints a Sudoku puzzle at the specified difficulty (defaults to `easy`).
296
+ ### 💬 On-Demand
103
297
 
104
- ### `/wordsearch`
298
+ Only recognized commands are processed. Messages without a command prefix are
299
+ ignored. Commands accept either `!` or `/`; these are message prefixes, not
300
+ registered Discord slash commands.
105
301
 
106
- Prints a themed word search puzzle.
107
- Themes are chosen randomly and are always kid-appropriate.
302
+ ## On-demand commands
108
303
 
109
- ## Local Development
304
+ | Command | Aliases | Description |
305
+ | --- | --- | --- |
306
+ | `/sudoku [kid\|easy\|medium\|hard]` | — | Prints a puzzle; defaults to `easy`. |
307
+ | `/wordsearch` | — | Prints a randomly themed, kid-appropriate word search. |
308
+ | `/pokemon [name\|1-151]` | `/kanto` | Prints a random Kanto Pokémon, or the named/numbered Pokémon. |
309
+ | `/hello` | `/helloworld` | Replies with a greeting without printing. |
110
310
 
111
- ## Control panel and local config
311
+ For example, send `!sudoku hard` or `/pokemon Pikachu` in an on-demand
312
+ channel.
112
313
 
113
- Run the bot with:
314
+ Wordsearches are pre-generated offline from the theme banks in
315
+ `scripts/wordsearch-themes`; the generator validates words against the
316
+ ROT13-encoded family-friendly denylist in
317
+ `scripts/wordsearch-blocklist.rot13.txt`. Run `npm run generate-wordsearches`
318
+ after changing the banks or generator.
114
319
 
115
- ```bash
116
- npm run dev
117
- ```
320
+ ## Control panel and local configuration
118
321
 
119
- Then open `http://localhost:8080` to use the web control panel.
322
+ The diagnostics server provides:
120
323
 
121
- When installed globally, run `windsor-bot` from the directory containing
122
- `windsor.config.json`, or set `WINDSOR_CONFIG_PATH` to its full path:
324
+ * Bot connection status and recent events
325
+ * Discord credentials, server selection, and diagnostics port
326
+ * Channel behavior mappings
327
+ * Printer mode and test printing
328
+ * A password for the control panel
329
+ * Server restart and image-feed access
330
+
331
+ The panel saves settings to `windsor.config.json` in the current working
332
+ directory unless `WINDSOR_CONFIG_PATH` is set. The file is JSON and can be
333
+ backed up, but keep it private.
334
+
335
+ ## Updating a production installation
336
+
337
+ Keep the Discord token and OpenAI key in the protected configuration file, or
338
+ configure them once through the panel. Upgrade the global package, then
339
+ restart the boot service:
123
340
 
124
341
  ```bash
125
- WINDSOR_CONFIG_PATH=/path/to/windsor.config.json windsor-bot
342
+ sudo npm install --global windsor-bot
343
+ sudo systemctl restart windsor
126
344
  ```
127
345
 
128
- The control panel is a Preact app served by the diagnostics server and can:
346
+ Check the service after an update with `sudo systemctl status windsor` and view
347
+ logs with `journalctl -u windsor -f`.
348
+
349
+ ## Troubleshooting
350
+
351
+ * **The bot is connected but ignores messages:** verify the channel has a
352
+ behavior, the bot can view/read the channel, and **Message Content Intent**
353
+ is enabled.
354
+ * **The server is missing from the panel:** verify the token, installation,
355
+ and optional `SERVER_ID`; then use **Refresh Discord Channels**.
356
+ * **Printing fails:** use **Print Test Page**, confirm the selected mode, check
357
+ the serial device permissions, or verify the CUPS name with `lpstat -p`.
358
+ * **The control panel cannot be reached:** confirm Windsor is running, check
359
+ the configured port, and use the Pi's reachable hostname or IP instead of
360
+ `localhost`.
361
+ * **AI features fail:** confirm `OPENAI_API_KEY` is present and restart Windsor
362
+ after changing it.
363
+ * **`status=217/USER`:** the `User=` account in
364
+ `/etc/systemd/system/windsor.service` does not exist. Set it to the username
365
+ printed by `id -un`, update `WorkingDirectory` to that user's home directory,
366
+ reload systemd, and restart the service.
367
+
368
+ ## Local development
369
+
370
+ The normal installation does not require a checkout. These commands are for
371
+ contributors working from the source tree:
372
+
373
+ ```bash
374
+ npm run dev # run Windsor and the diagnostics server
375
+ npm run build # type-check and build the web panel
376
+ npm test # run the Node test suite
377
+ npm run gen-image -- a friendly shopping list # generate test.png using the local config
378
+ ```
129
379
 
130
- 1. Show runtime bot status and recent events.
131
- 2. Save basic setup into a local `windsor.config.json` file (Discord token, server id, etc).
380
+ The control-panel source is `src/web/app.tsx`. The browser app is bundled into
381
+ the npm package during `npm run build`.
132
382
 
133
- The browser app source lives in `src/web/app.tsx`. It is bundled into the npm package during the build and served by the diagnostics server; development runs can still bundle the source on demand.
383
+ ## Sources
134
384
 
135
- `windsor.config.json` is gitignored because it can contain secrets.
385
+ * [Discord: Building your first Discord bot](https://docs.discord.com/developers/quick-start/getting-started)
386
+ * [Discord: OAuth2 and bot authorization](https://docs.discord.com/developers/topics/oauth2#bot-authorization-flow)
387
+ * [Raspberry Pi: Getting started](https://www.raspberrypi.com/documentation/computers/getting-started.html)
388
+ * [Raspberry Pi: Remote access over SSH](https://www.raspberrypi.com/documentation/computers/remote-access.html#ssh)