windsor-bot 0.1.7 → 0.1.8

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.
@@ -0,0 +1,404 @@
1
+ WORDSEARCH 1
2
+ THEME Travel
3
+ SIZE 15
4
+ COUNT 20
5
+
6
+ PUZZLE 1
7
+ WORDS SIGHTSEEING,SUITCASE,CAMPING,LUGGAGE,ISLAND,TRAVEL,TICKET,PILOT,FERRY,TRIP
8
+ GRID
9
+ P L V Q D Y F N X Y D X X Q B
10
+ I A E X S N D N A L S I F P S
11
+ L T Q X G N I P M A C E I Q M
12
+ O N E G T A L P D G T R K L Q
13
+ T V T K N U Q Y N R T M O H N
14
+ L H J D C I I M P O V F U C V
15
+ H U H K F I E I W Q D T L V M
16
+ N L G E E H T E I I D K O O O
17
+ P X R G N W H T S X J S X T T
18
+ Y R X C A K O A I T Q M G F X
19
+ Y A T K C G V Q Z Z H X F K F
20
+ I C L F R K E L V H L G C A L
21
+ J Y X V R V R C L Z O T I C E
22
+ K B V H T R A V E L T B L S I
23
+ A T L O S U I T C A S E F E N
24
+ END
25
+
26
+ PUZZLE 2
27
+ WORDS CAMPING,FLIGHT,DESERT,RESORT,TRAVEL,TICKET,SAFARI,HOTEL,TRIP,MAP
28
+ GRID
29
+ R S W D U Z J M H T R P T O J
30
+ E N G R A Y V Y K T U E G Y C
31
+ W L W X N X C L P T I S B G P
32
+ C E L L K L H I R E S O R T Y
33
+ Y T E Y N T R O N S O K X B O
34
+ L O B K W T E I L D V V J M C
35
+ K H E B L M J K R E T Y X S F
36
+ V C G N I P M A C A V N C U L
37
+ Y J X D B M X E N I F A M W I
38
+ M L I E G N F K E G T A R A G
39
+ M L W S M Q L Y N C P R S T H
40
+ L L F E Q S M W G V G J C U T
41
+ V V F R G S E V A U M B J K J
42
+ L H F T T T Q O C S A E H Z P
43
+ U U L I P C R E T N P S A X B
44
+ END
45
+
46
+ PUZZLE 3
47
+ WORDS SOFTBALL,AIRPORT,LUGGAGE,CAMPING,TOURIST,ISLAND,BEACH,FERRY,PILOT,ROAD
48
+ GRID
49
+ X O N A O T V T H W P A F I Z
50
+ E M G E E C O I E D J R K T Z
51
+ I W A K J G T L L R A F I M R
52
+ Y X Q A X O A O I Y E U W T D
53
+ S K D N U I C G J P D Y I D Q
54
+ B Q C R H O J A G U S O I N T
55
+ Q K I K F X J F M U L E T Z G
56
+ I S E K T Y R L I P L V M D T
57
+ T V C B G Z O D K I I G D D D
58
+ D A O R Y E N X Z W V N Z S A
59
+ C V Q O H A T I H E Y C G P A
60
+ A W F C L G Y F E R R Y A S E
61
+ Y F A S C C Y A J I D G C L G
62
+ V E I S L L L A B T F O S H P
63
+ B N S T Z T R O P R I A T T M
64
+ END
65
+
66
+ PUZZLE 4
67
+ WORDS BACKPACK,CAMPING,TOURIST,LUGGAGE,DESERT,ISLAND,TICKET,HOTEL,CABIN,ROAD
68
+ GRID
69
+ I D E S E R T Y S F B Q F B O
70
+ R S L B E L X I P E A J G A Q
71
+ O N L M K S Y J Q M C T D M E
72
+ S B E A D N M G Y G K O S O S
73
+ W N W L N L E T O H P X J A D
74
+ V C T P A D T S J D A P B H R
75
+ N N A S D S E N W E C F O O A
76
+ L A P B I E S X G P K I A M J
77
+ G L H R I U B A Z N I D I G C
78
+ T A U U G N G L A J U J N T N
79
+ J O K Z S G S K K Q S I I Z H
80
+ T Z B E U J J S A N P C W T F
81
+ O E U L K P J X P M K W I Z I
82
+ U P E E A T D W A E A L N U Z
83
+ M Q H W X W B C T U X F X R T
84
+ END
85
+
86
+ PUZZLE 5
87
+ WORDS VACATION,SUITCASE,SOFTBALL,RAILWAY,TOURIST,AIRPORT,SAFARI,TRAVEL,CRUISE,ISLAND
88
+ GRID
89
+ F C W Z I W I Y N L W X F J V
90
+ H P O T P O Q R U H C Y L H U
91
+ G D L R W T V Y Z D K E X P V
92
+ W J O O U T A D A Y C N B U T
93
+ T Y Y P O L N B U W L U V C E
94
+ J Q C R Y N C E Y I L A G S T
95
+ K I S I V R T R V L C I A R E
96
+ E M T A C V Z R U A I C A T P
97
+ P T G U I I G Q T I T N V R T
98
+ I O C D W F B I B I S X O R J
99
+ R U P N X H O E U G Q E C T W
100
+ A R G A B N K S K X B J X B A
101
+ F I Z L I T H L E V A R T Z N
102
+ A S A S L X P Y F C V H P C N
103
+ S T D I Y K C L L A B T F O S
104
+ END
105
+
106
+ PUZZLE 6
107
+ WORDS SIGHTSEEING,BACKPACK,TOURIST,RAILWAY,LUGGAGE,CAMPING,FLIGHT,TRAVEL,FERRY,TRIP
108
+ GRID
109
+ G D N K W F U X G X N F F Z F
110
+ I L D U G D Z G E M M G S O U
111
+ B B B A C K P A C K D C Q T C
112
+ S F A H E Y Z A U Q L X R H I
113
+ U J L Y U G E A A M K I S N F
114
+ J X U I S F W C R Y P I L M H
115
+ I F U B G C K A Q O G G U W R
116
+ C S P Y X H I P N H L N G M G
117
+ O C S K R L T J T E Y I G P B
118
+ P P V O W R O S V N N P A O H
119
+ K R J A H D E A W Z U M G Q Y
120
+ O P Y D H E R F I M W A E M D
121
+ K K F P I T F Q V F B C L L F
122
+ N S V N F M R E Y J J C I U K
123
+ Q C G C Y T S I R U O T T J P
124
+ END
125
+
126
+ PUZZLE 7
127
+ WORDS SIGHTSEEING,BACKPACK,ISLAND,TICKET,BEACH,TRAIN,CABIN,TRIP,ROAD,MAP
128
+ GRID
129
+ P D G I P G F A V Q F Z W B A
130
+ E Z K I L N O N G G Q J L Q A
131
+ A S R W T I C K E T R E W X P
132
+ T T C W M E K L U I S L A N D
133
+ T K J G M E N D U W P H B T U
134
+ Y F W K H S G L P O J E D O S
135
+ A Z Z W Y T K C G A A Y C T Y
136
+ W F W F P H Q G R C M V R V P
137
+ R W Y X E G Q C H H Y A T B F
138
+ T X P L K I Z K G N I B A C K
139
+ I X P I Q S D X A N F F W U T
140
+ N F N W P K C A P K C A B D Q
141
+ P H L S E A X Q N O M W I P M
142
+ C K V D K D A O R F X X W K L
143
+ M Z O A M J U I L V F P Q K S
144
+ END
145
+
146
+ PUZZLE 8
147
+ WORDS SIGHTSEEING,RAILWAY,TOURIST,TRAVEL,FLIGHT,TICKET,CRUISE,TRAIN,FERRY,ROAD
148
+ GRID
149
+ A G H P Q V W S P K C U K P V
150
+ D H I G K A J V W J E R A Q G
151
+ A N L N H T S I R U O T Q N X
152
+ O C F I Q H E R V Q H Y Z Y C
153
+ R I X E Z A R W Y R X K S R H
154
+ E F S E B E Z A C M U C U P W
155
+ V W E S F Q A I I F T I P J Y
156
+ E U F T T W A N E L S A Y Z H
157
+ T I L H B Y V R Q E W I D L Z
158
+ E S I G Y S R R L M R A W G R
159
+ K C G I U Y C R Y S Z W Y R T
160
+ C W H S F L Q B W R X H I R O
161
+ I H T P W P C P K R L Y A Z J
162
+ T L D T R A V E L F G I M S O
163
+ D I S L P S Y M J L N O H N K
164
+ END
165
+
166
+ PUZZLE 9
167
+ WORDS SIGHTSEEING,PASSPORT,JOURNEY,AIRPORT,FLIGHT,DESERT,FERRY,BOAT,ROAD,MAP
168
+ GRID
169
+ E W K T I Y R R E F S K K V S
170
+ D V C W U T C J H Y G U M K V
171
+ G N I E E S T H G I S T E D Q
172
+ G H J T A P A S S P O R T W J
173
+ D W D M P I T D P Z S J X H K
174
+ F E U I Y I R L P D L Z A F R
175
+ E P S M I X D P M Z U X W M F
176
+ Z R P E O S T E O A U T S Q Q
177
+ A T V V R B M J M R I H J Y F
178
+ H F W G N T O S E R T G E E S
179
+ Z A U K W U M D V E U I D L H
180
+ Q Z I W R R R A C W T L J L L
181
+ W A G N V O O Y P A R F U X D
182
+ C L E U Q X L A O M Z H D Q X
183
+ S Y T Z K I W B D Z Z R Z G T
184
+ END
185
+
186
+ PUZZLE 10
187
+ WORDS JOURNEY,AIRPORT,SAFARI,TICKET,ISLAND,CRUISE,CABIN,TRIP,BOAT,ROAD
188
+ GRID
189
+ T E G G K J G R C C A B I N V
190
+ I N A J J H A V I E N O N E I
191
+ P P D J F G A I S L A N D W T
192
+ Y E N R U O J C P I R T R Y V
193
+ M B P D S F F K W F C G B P Q
194
+ G Q I R A F A S Z K E C Y K H
195
+ T A S Z H R D V V E S H M W T
196
+ G C I Q C R K O B T E K C I T
197
+ P C G R S R U E B U W L X C J
198
+ J N S T P R U G G C P I Y U Q
199
+ T K S D J O N I O Y K U L E Y
200
+ O A S P I T R T S Q I I O O K
201
+ D A O R I Y A T U E A Y L Y P
202
+ Z E T L A O R V A O X X F D Y
203
+ O X Q C B S X B N D E T A Y Q
204
+ END
205
+
206
+ PUZZLE 11
207
+ WORDS PASSPORT,SOFTBALL,BACKPACK,JOURNEY,TRAVEL,TICKET,DESERT,TRAIN,FERRY,MAP
208
+ GRID
209
+ M D E S R L C H T O G O L Q P
210
+ V X F D K Y E N R U O J D U R
211
+ R R E V C N J R T A L U N W M
212
+ K C R S A I H B A A Y G M B A
213
+ D M R Q P A Y Z U Y M C N L P
214
+ F C Y X K R I W S A W D L G N
215
+ V B O Q C T Y C U L R A F V S
216
+ A J L G A B T L E M B N K D Q
217
+ W T S J B E M V K T N F A E Z
218
+ X F X F K R A R F B G O L S C
219
+ H I P C P R I O N O V N B E E
220
+ L X I C T R S G X J Y K B R G
221
+ A T L F W S J N L B Y L C T D
222
+ D O V R P A S S P O R T V U X
223
+ K P Z I V K U G B X P X K B C
224
+ END
225
+
226
+ PUZZLE 12
227
+ WORDS SIGHTSEEING,SUITCASE,TOURIST,CRUISE,SAFARI,ISLAND,RESORT,FERRY,CABIN,PILOT
228
+ GRID
229
+ H C E Q J X Q L Z U R V V O C
230
+ Z N A B P E I G O B T O L I P
231
+ O S D B S G B S P T R O S E R
232
+ S Z E I I B K V D L H F D W L
233
+ L I U U F N E H N R Q Y O P E
234
+ A R G E G W N R A E O W U S T
235
+ C T R H T O E A L G M N A A O
236
+ R R P T T Z C F S M T C O K U
237
+ Y A Y I X S B C I P T R D N R
238
+ M V E W D F E O V I Y L C X I
239
+ I S L S K V O E U I T J C R S
240
+ X T E A L T R S I H T X C C T
241
+ J J M G K I E I Z N S I X C T
242
+ F X J C G H C Z R Y G L U S C
243
+ S A F A R I X Q R I M S Y N O
244
+ END
245
+
246
+ PUZZLE 13
247
+ WORDS SUITCASE,SOFTBALL,AIRPORT,JOURNEY,LUGGAGE,CAMPING,TICKET,PILOT,BEACH,BOAT
248
+ GRID
249
+ R L T N Q T X G T F S H R J R
250
+ L D D A R Q Z Y Z O I V W T C
251
+ N U L B O L J H F P G H Z I B
252
+ A Y G G O B F T W A C P I C D
253
+ H O O G B B B J Z A C S O K S
254
+ V A G T A A U H E B U V F E O
255
+ I W Q I L G O B I I K J B T W
256
+ V F D L R C E H T A D N W C F
257
+ J Y G H D C F C O L A C W N N
258
+ V O Z M F W A J J G A K Q K U
259
+ F R U B O S P U R M Q A S V R
260
+ A P I R E A I R P O R T Y K P
261
+ J I T B N F I I T F T O L I P
262
+ T D M V E E N W V C Y R D D L
263
+ I Y R Q S G Y Y K P V G T Y P
264
+ END
265
+
266
+ PUZZLE 14
267
+ WORDS SIGHTSEEING,BACKPACK,SUITCASE,CAMPING,TOURIST,RAILWAY,JOURNEY,AIRPORT,TICKET,TRIP
268
+ GRID
269
+ X F N R M F T K Y A W L I A R
270
+ Q Y T G H T E I C S O M F B S
271
+ W E R B N W R F C L M W Y W C
272
+ U P O I D I M I F K Z J E Y H
273
+ A D P S E Q E T P N E E N H T
274
+ C B R N T I G E A O E T R Z L
275
+ V A I U W Q O A S S P G U P Y
276
+ K C A B B R W M A T K F O N G
277
+ W K A O M P L C U K H P J N T
278
+ S P Z R M L T E P D K G I S Z
279
+ W A K W Q I Y D L K N P I F G
280
+ P C V J U F W E K H M R G S U
281
+ E K S S R C L P X A U I X G Z
282
+ A Y N S D M Q Y C O C C O J R
283
+ D N V O B R T Y T B B J G J L
284
+ END
285
+
286
+ PUZZLE 15
287
+ WORDS VACATION,LUGGAGE,RAILWAY,TOURIST,CAMPING,RESORT,TRAVEL,BOAT,TRIP,MAP
288
+ GRID
289
+ E I S Q Q P O X A U R O P O G
290
+ G F E Y K I C L O Z Q P L D J
291
+ I N V H G Z B T D Y A H N L Z
292
+ X E I Y T I Z S L M Q P X C Q
293
+ N X O P T A Y I U L J G G O N
294
+ U T E T M U H R G D E J J O B
295
+ Q N M X E A S U G N S D I U N
296
+ F I N Y L I C O A D X T C R V
297
+ V T Z J H C C T G Z A J A A I
298
+ R R N Z T P X D E C Z I E T J
299
+ R A R N I P D O A H L B R R L
300
+ W V R R A R Y V W W D N K T U
301
+ H E T X T P H Y A Y Z T K M S
302
+ D L S L J S L Y X Q T A O B E
303
+ A O C R E S O R T Y F A Q I F
304
+ END
305
+
306
+ PUZZLE 16
307
+ WORDS JOURNEY,LUGGAGE,CAMPING,RESORT,FERRY,BEACH,CABIN,TRAIN,PILOT,TRIP
308
+ GRID
309
+ P F G I I F Q U L O Y Z Z W T
310
+ S L S E E X E L E G Q I F R K
311
+ V W G R F S R V O J Q H I F R
312
+ R N R T N E U B V F E P Z E Y
313
+ D Y Q I S H D D J T L Q Q G A
314
+ O U A O V Z U G E O Q L X Q I
315
+ V R R R N E N R D I U J N J L
316
+ T T G Y N I B A C G N R D T X
317
+ Q P O I P C O P G W H I N S G
318
+ Q C C M W F Q A H F C D Z E T
319
+ W L A P N H G J O D A Y N D Y
320
+ L C X I B E T Q L P E P C N R
321
+ O K G L H L E X Y W B R T M L
322
+ M W E O L U P S F G U Y D F Z
323
+ Y C D T L G Y B X O Z Y N P G
324
+ END
325
+
326
+ PUZZLE 17
327
+ WORDS VACATION,SUITCASE,TOURIST,TICKET,DESERT,RESORT,HOTEL,TRAIN,ROAD,MAP
328
+ GRID
329
+ V V A R L L B I I V B U A R S
330
+ D T Q O W D E H S A R G I N D
331
+ F O P G J Y T T X R E S O R T
332
+ P U Q M Q D W U O K Y I N S U
333
+ O R N W A Q Q B F H T R V X Q
334
+ Y I E M O M M Y F A T E X G T
335
+ U S L V J Z X M C R S S C K I
336
+ P T A U D Z V A E Y O A L D C
337
+ R U X A J I V S V M D C I D K
338
+ R F O V F V E H M P I T L Z E
339
+ A R B N V D U H O A W I Z M T
340
+ N F J C R F J A O P N U X W U
341
+ M Y S B R Y Y G J K L S O F Z
342
+ I Q D V O G M A P F G C F R K
343
+ Q X N F I T R A I N G T I K P
344
+ END
345
+
346
+ PUZZLE 18
347
+ WORDS SIGHTSEEING,BACKPACK,VACATION,SOFTBALL,TOURIST,AIRPORT,DESERT,TICKET,TRIP,MAP
348
+ GRID
349
+ J C N N O F L Q T R E S E D E
350
+ K T F N G H B M I C O T L Q S
351
+ Q B B I J N D L V K K Z Q O A
352
+ T T C A Z B O J F U V C F P N
353
+ R X S B C Z S I G F B T B P K
354
+ O W J I L K Y W T R B S P P O
355
+ P B N Y R A P I N A Y S S D Z
356
+ R D M V O U C A L F C P D F E
357
+ I D Z S W K O L C Q H A I V T
358
+ A G A K E T B T O K P U V R Z
359
+ A D A T H C Z K D T B T S I T
360
+ L Z Q T S I G H T S E E I N G
361
+ I Q P B K G H P C F A A F W N
362
+ I L A C P H O W Z M N U J I W
363
+ L S M H R U U I V L U T D R H
364
+ END
365
+
366
+ PUZZLE 19
367
+ WORDS PASSPORT,VACATION,BACKPACK,LUGGAGE,JOURNEY,DESERT,CRUISE,FLIGHT,TRAIN,MAP
368
+ GRID
369
+ Y W H Q E M U Y I R P W A K E
370
+ L F A Z N L D X E D J F S D Y
371
+ Y R E S I U R C D Y R Z P W Z
372
+ T C S V A C A T I O N A G N I
373
+ O H T U Z B G Z B Z S R I Z R
374
+ Y C G P T Z Q A V S K A H H P
375
+ L E L I M W C Y P U R S J I Z
376
+ A D N I L K D O U T A H K X N
377
+ F T E R P F R L U G G A G E D
378
+ Q M J A U T N D P B L X E E I
379
+ K P C I B O S L S O J P S F M
380
+ F K X N Z A J T N G Z E E A I
381
+ D O X Z H A N D M W R E P Q V
382
+ T Y Q X X F Y K N T X Z N W E
383
+ W Z N T J M T Z X X C A K L Q
384
+ END
385
+
386
+ PUZZLE 20
387
+ WORDS VACATION,RAILWAY,CRUISE,ISLAND,FLIGHT,FERRY,CABIN,PILOT,ROAD,TRIP
388
+ GRID
389
+ A V O S G Y W U D U G E Q M D
390
+ G R T P Y B M X T R D T Z X W
391
+ X L I R D C E Z C P A M M R D
392
+ X R R Y R A V E G C O D G O O
393
+ T E B U D S C X B S R V E W V
394
+ F H I L F N W Q Q S R C J I A
395
+ H S W N W X A S W A D A M G I
396
+ E N W V P Q K L P J U B C W H
397
+ M O I W H Q L Z S H C I K R P
398
+ Y I E A Y X L C G I T N A K S
399
+ U T P W P U T S D H U I M V X
400
+ G A V I U B R Q G O L F W H X
401
+ N C H A L J K I H W V L A E L
402
+ X A D W N O L O A X N W H G O
403
+ W V W M A F T Y R V N D Z I L
404
+ END
package/dist/ai.js CHANGED
@@ -58,7 +58,7 @@ function parseOccurrenceDate(str) {
58
58
  }
59
59
  return null;
60
60
  }
61
- export async function parseRecurringSchedule(userMessage, now) {
61
+ export async function parseRecurringSchedule(userMessage, now, onRemoteStart) {
62
62
  const client = getClient();
63
63
  if (!client)
64
64
  return null;
@@ -68,6 +68,7 @@ export async function parseRecurringSchedule(userMessage, now) {
68
68
  const prompt = `The user has asked for a recurring printout. It is currently ${nowStr}. Tell me the next time that I should do this, and what the non-schedule part of the message was (verbatim). If the user didn't specify a time of day, use 8:00 AM. Here's the user's message: ${userMessage}`;
69
69
  const results = [];
70
70
  const ATTEMPTS = 5;
71
+ await onRemoteStart?.();
71
72
  for (let i = 0; i < ATTEMPTS; i++) {
72
73
  try {
73
74
  const completion = await client.chat.completions.create({
@@ -130,11 +131,11 @@ function formatScheduleDate(date) {
130
131
  return `${year}-${month}-${day} ${hours}:${minutes} ${ampm}`;
131
132
  }
132
133
  export { formatScheduleDate };
133
- export async function getNextOccurrence(originalMessage, now) {
134
- const result = await parseRecurringSchedule(originalMessage, now);
134
+ export async function getNextOccurrence(originalMessage, now, onRemoteStart) {
135
+ const result = await parseRecurringSchedule(originalMessage, now, onRemoteStart);
135
136
  return result ? result.nextOccurrence : null;
136
137
  }
137
- export async function generateIcon(text, cacheDir) {
138
+ export async function generateIcon(text, cacheDir, onRemoteStart) {
138
139
  const prompt = `Black-on-transparent line drawing icon for the TODO item: "${text}". Do not produce any text. Use big, thick lines. No fine detailing.`;
139
140
  const hash = createHash('sha1').update(prompt).digest('hex').slice(0, 9);
140
141
  const filename = `${hash}.png`;
@@ -152,6 +153,7 @@ export async function generateIcon(text, cacheDir) {
152
153
  return null;
153
154
  if (await isOverTokenLimit())
154
155
  return null;
156
+ await onRemoteStart?.();
155
157
  for (let attempt = 0; attempt < 3; attempt++) {
156
158
  try {
157
159
  const response = await client.images.generate({
package/dist/bot.js CHANGED
@@ -55,22 +55,42 @@ async function hasReaction(message, emoji) {
55
55
  return Boolean(reaction?.me);
56
56
  }
57
57
  async function hasAnyReaction(message) {
58
- try {
59
- await message.fetch();
60
- }
61
- catch {
62
- // ignore
63
- }
64
58
  for (const v of Object.values(Reaction)) {
59
+ if (v === Reaction.thinking)
60
+ continue;
65
61
  if (message.reactions.cache.get(v)) {
66
62
  return true;
67
63
  }
68
64
  }
69
65
  return false;
70
66
  }
67
+ async function refreshMessage(message) {
68
+ try {
69
+ await message.fetch();
70
+ }
71
+ catch {
72
+ // continue with the cached message if refresh fails
73
+ }
74
+ }
75
+ async function removeReactionSafe(message, emoji) {
76
+ try {
77
+ const reaction = message.reactions.cache.get(emoji);
78
+ if (reaction?.me)
79
+ await reaction.users.remove();
80
+ }
81
+ catch {
82
+ // ignore
83
+ }
84
+ }
71
85
  async function reactSafe(message, emoji) {
72
86
  try {
73
87
  await message.react(emoji);
88
+ if (emoji !== Reaction.thinking) {
89
+ await removeReactionSafe(message, Reaction.thinking);
90
+ }
91
+ if (emoji === Reaction.ok) {
92
+ await removeReactionSafe(message, Reaction.fail);
93
+ }
74
94
  }
75
95
  catch {
76
96
  // ignore
@@ -87,6 +107,10 @@ async function replySafe(message, content) {
87
107
  function isPrintTrigger(content) {
88
108
  return content.replace(/[^a-zA-Z0-9]/g, '').toLowerCase() === 'print';
89
109
  }
110
+ function isCommandMessage(content) {
111
+ const trimmed = content.trim();
112
+ return trimmed.startsWith('!') || trimmed.startsWith('/');
113
+ }
90
114
  export function createWindsorBot() {
91
115
  const client = new Client({
92
116
  intents: [
@@ -199,6 +223,10 @@ export function createWindsorBot() {
199
223
  continue;
200
224
  if (msg.author.id === botId)
201
225
  continue;
226
+ if (isCommandMessage(msg.content)) {
227
+ await handleOnDemand(msg);
228
+ continue;
229
+ }
202
230
  if (await hasAnyReaction(msg))
203
231
  continue;
204
232
  await handleImmediatePrint(msg, config);
@@ -286,7 +314,12 @@ export function createWindsorBot() {
286
314
  return;
287
315
  switch (mapping.config.type) {
288
316
  case 'immediate-print':
289
- await handleImmediatePrint(message, mapping.config);
317
+ if (isCommandMessage(message.content)) {
318
+ await handleOnDemand(message);
319
+ }
320
+ else {
321
+ await handleImmediatePrint(message, mapping.config);
322
+ }
290
323
  break;
291
324
  case 'accumulating-list': {
292
325
  if (isPrintTrigger(message.content)) {
@@ -305,7 +338,7 @@ export function createWindsorBot() {
305
338
  break;
306
339
  }
307
340
  }
308
- async function handleImmediatePrint(message, config) {
341
+ async function handleImmediatePrint(message, config, retryFailed = false) {
309
342
  const rawContent = message.content;
310
343
  const urls = extractUrls(rawContent);
311
344
  const textWithLinkLabels = replaceUrlsInText(rawContent, urls);
@@ -329,10 +362,17 @@ export function createWindsorBot() {
329
362
  }
330
363
  if (config.includeIcon) {
331
364
  const iconCacheDir = getCurrentConfig().iconCacheDir ?? './icon-cache';
332
- const iconPath = await generateIcon(strippedText, iconCacheDir);
365
+ const iconPath = await generateIcon(strippedText, iconCacheDir, () => reactSafe(message, Reaction.thinking));
333
366
  if (iconPath)
334
367
  job.iconPath = iconPath;
335
368
  }
369
+ await refreshMessage(message);
370
+ if (await hasAnyReaction(message)) {
371
+ if (!retryFailed || !message.reactions.cache.get(Reaction.fail)?.me) {
372
+ await removeReactionSafe(message, Reaction.thinking);
373
+ return;
374
+ }
375
+ }
336
376
  try {
337
377
  await printJob(job);
338
378
  await reactSafe(message, Reaction.ok);
@@ -390,6 +430,9 @@ export function createWindsorBot() {
390
430
  if (config.includeMetadata) {
391
431
  job.metadataLines = [`Printed at ${formatTimestamp(new Date())}`];
392
432
  }
433
+ await refreshMessage(triggerMessage);
434
+ if (await hasAnyReaction(triggerMessage))
435
+ return;
393
436
  try {
394
437
  await printJob(job);
395
438
  await reactSafe(triggerMessage, Reaction.ok);
@@ -404,7 +447,7 @@ export function createWindsorBot() {
404
447
  async function handleRecurringSetup(message, config) {
405
448
  const rawContent = message.content;
406
449
  try {
407
- const parsed = await parseRecurringSchedule(rawContent, new Date());
450
+ const parsed = await parseRecurringSchedule(rawContent, new Date(), () => reactSafe(message, Reaction.thinking));
408
451
  if (!parsed) {
409
452
  await reactSafe(message, Reaction.what);
410
453
  await replySafe(message, '⁉️ Could not parse a schedule from your message. Please try again with a clearer schedule.');
@@ -443,21 +486,27 @@ export function createWindsorBot() {
443
486
  }
444
487
  if (config.includeIcon) {
445
488
  const iconCacheDir = getCurrentConfig().iconCacheDir ?? './icon-cache';
446
- const iconPath = await generateIcon(strippedText, iconCacheDir);
489
+ const iconPath = await generateIcon(strippedText, iconCacheDir, () => reactSafe(scheduleReply, Reaction.thinking));
447
490
  if (iconPath)
448
491
  job.iconPath = iconPath;
449
492
  }
493
+ await refreshMessage(scheduleReply);
494
+ if (await hasAnyReaction(scheduleReply)) {
495
+ await removeReactionSafe(scheduleReply, Reaction.thinking);
496
+ return;
497
+ }
450
498
  const printedAt = formatTimestamp(new Date());
451
499
  try {
452
500
  await printJob(job);
453
501
  }
454
502
  catch (err) {
503
+ await removeReactionSafe(scheduleReply, Reaction.thinking);
455
504
  logEvent('error', `Recurring print failed: ${err}`);
456
505
  return;
457
506
  }
458
507
  logEvent('print', `Printed recurring task: ${text}`);
459
508
  const originalUserMessage = scheduleReply.content.replace(/^Got it\. I will print ⟪.+?⟫ at /, '');
460
- const nextOccurrence = await getNextOccurrence(originalUserMessage, new Date());
509
+ const nextOccurrence = await getNextOccurrence(originalUserMessage, new Date(), () => reactSafe(scheduleReply, Reaction.thinking));
461
510
  let statusReply;
462
511
  if (!nextOccurrence) {
463
512
  statusReply = await replySafe(scheduleReply, 'This occurrence has expired and no more prints are scheduled');
@@ -469,6 +518,7 @@ export function createWindsorBot() {
469
518
  if (statusReply) {
470
519
  await reactSafe(statusReply, Reaction.ok);
471
520
  }
521
+ await removeReactionSafe(scheduleReply, Reaction.thinking);
472
522
  if (nextOccurrence && statusReply) {
473
523
  scheduleRecurringTask(scheduleReply, nextOccurrence, config, bot);
474
524
  }
@@ -477,9 +527,10 @@ export function createWindsorBot() {
477
527
  const match = /⟪(.+?)⟫/.exec(scheduleReply.content);
478
528
  if (!match?.[1])
479
529
  return;
480
- const nextOccurrence = await getNextOccurrence(match[1], new Date());
530
+ const nextOccurrence = await getNextOccurrence(match[1], new Date(), () => reactSafe(latestStatusReply, Reaction.thinking));
481
531
  if (!nextOccurrence) {
482
532
  await replySafe(latestStatusReply, 'This occurrence has expired and no more prints are scheduled');
533
+ await removeReactionSafe(latestStatusReply, Reaction.thinking);
483
534
  return;
484
535
  }
485
536
  const nextStr = formatScheduleDate(nextOccurrence);
@@ -487,6 +538,22 @@ export function createWindsorBot() {
487
538
  await reactSafe(latestStatusReply, Reaction.ok);
488
539
  scheduleRecurringTask(scheduleReply, nextOccurrence, config, bot);
489
540
  }
541
+ async function retryFailedMessages(message) {
542
+ const mapping = getCurrentConfig().channels.find(m => m.channelId === message.channelId);
543
+ if (!mapping || mapping.config.type !== 'immediate-print') {
544
+ throw new Error('!retry is only available in immediate-print channels');
545
+ }
546
+ const channel = message.channel;
547
+ const messages = await channel.messages.fetch({ limit: 20, before: message.id });
548
+ const failed = [...messages.values()].filter(msg => !msg.author.bot &&
549
+ !msg.reference &&
550
+ !isCommandMessage(msg.content) &&
551
+ msg.reactions.cache.get(Reaction.fail)?.me);
552
+ for (const failedMessage of failed) {
553
+ await handleImmediatePrint(failedMessage, mapping.config, true);
554
+ }
555
+ return failed.length;
556
+ }
490
557
  async function handleOnDemand(message) {
491
558
  const content = message.content.trim();
492
559
  if (!content.startsWith('!') && !content.startsWith('/'))
@@ -500,7 +567,13 @@ export function createWindsorBot() {
500
567
  if (cmd.aliases.some(a => commandName.localeCompare(a, undefined, { sensitivity: "base" }) == 0)) {
501
568
  foundCommand = true;
502
569
  logEvent('command', `Command '${commandName}' invoked by ${message.author.username}`);
503
- const ctx = { printJob };
570
+ await refreshMessage(message);
571
+ if (await hasAnyReaction(message))
572
+ return;
573
+ const ctx = {
574
+ printJob,
575
+ retryFailedMessages: () => retryFailedMessages(message),
576
+ };
504
577
  const result = await cmd.invoke(args, ctx);
505
578
  if (result.kind === 'pass') {
506
579
  await reactSafe(message, "✅");
@@ -1,6 +1,7 @@
1
1
  import { debugFail } from "./debug-fail.js";
2
2
  import { helloWorld } from "./hello-world.js";
3
3
  import { printPokemon } from "./pokemon.js";
4
+ import { retry } from "./retry.js";
4
5
  import { printSudoku } from "./sudoku.js";
5
6
  import { printWordsearch } from "./wordsearch.js";
6
7
  export const Commands = [
@@ -8,5 +9,6 @@ export const Commands = [
8
9
  printSudoku,
9
10
  printWordsearch,
10
11
  printPokemon,
12
+ retry,
11
13
  debugFail
12
14
  ];
@@ -131,7 +131,7 @@ async function pokemonWorker(args, ctx) {
131
131
  iconPath: spritePath,
132
132
  };
133
133
  await ctx.printJob(job);
134
- return { kind: 'pass', reply: `Printing #${numStr} ${name}!` };
134
+ return { kind: 'pass' };
135
135
  }
136
136
  export const printPokemon = {
137
137
  aliases: ['pokemon', 'kanto'],
@@ -0,0 +1,17 @@
1
+ import { tryExecCommandFunction } from "./util.js";
2
+ async function retryWorker(_args, ctx) {
3
+ if (!ctx.retryFailedMessages) {
4
+ throw new Error("The retry command is not available in this channel");
5
+ }
6
+ const count = await ctx.retryFailedMessages();
7
+ return {
8
+ kind: "pass",
9
+ reply: count === 0
10
+ ? "No failed messages found in the previous 20 messages."
11
+ : `Retried ${count} failed message${count === 1 ? "" : "s"}.`,
12
+ };
13
+ }
14
+ export const retry = {
15
+ aliases: ["retry"],
16
+ invoke: tryExecCommandFunction(retryWorker),
17
+ };