@effect-ak/tg-bot-client 0.3.2 → 0.4.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.
package/dist/index.js CHANGED
@@ -32,6 +32,7 @@ var src_exports = {};
32
32
  __export(src_exports, {
33
33
  BotFactoryService: () => BotFactoryService,
34
34
  BotFactoryServiceDefault: () => BotFactoryServiceDefault,
35
+ BotResponse: () => BotResponse,
35
36
  BotUpdatePollerService: () => BotUpdatePollerService,
36
37
  BotUpdatesPollerServiceDefault: () => BotUpdatesPollerServiceDefault,
37
38
  MESSAGE_EFFECTS: () => MESSAGE_EFFECTS,
@@ -44,14 +45,19 @@ __export(src_exports, {
44
45
  module.exports = __toCommonJS(src_exports);
45
46
 
46
47
  // src/bot/factory/_service.ts
47
- var Micro7 = __toESM(require("effect/Micro"));
48
- var Context3 = __toESM(require("effect/Context"));
48
+ var Micro6 = __toESM(require("effect/Micro"));
49
+ var Context4 = __toESM(require("effect/Context"));
50
+
51
+ // src/bot/message-handler/_service.ts
52
+ var Context = __toESM(require("effect/Context"));
53
+ var BotMessageHandler = class extends Context.Tag("BotMessageHandler")() {
54
+ };
49
55
 
50
56
  // src/bot/update-poller/_service.ts
51
57
  var Micro4 = __toESM(require("effect/Micro"));
52
- var Context2 = __toESM(require("effect/Context"));
58
+ var Context3 = __toESM(require("effect/Context"));
53
59
 
54
- // src/bot/update-poller/poll-and-handle.ts
60
+ // src/bot/update-poller/poll-updates.ts
55
61
  var Micro3 = __toESM(require("effect/Micro"));
56
62
 
57
63
  // src/bot/update-poller/settings.ts
@@ -59,8 +65,8 @@ var makeSettingsFrom = (input) => {
59
65
  let limit = input.batch_size ?? 10;
60
66
  let timeout = input.timeout ?? 10;
61
67
  let max_empty_responses = input.max_empty_responses;
62
- let update_types = input.update_types;
63
68
  let log_level = input.log_level;
69
+ let on_error = input.on_error;
64
70
  if (limit < 10 || limit > 100) {
65
71
  console.warn("Wrong limit, must be in [10..100], using 10 instead");
66
72
  limit = 10;
@@ -77,24 +83,35 @@ var makeSettingsFrom = (input) => {
77
83
  console.warn("Wrong max_empty_responses, must be in [2..infinity], using infinity");
78
84
  max_empty_responses = void 0;
79
85
  }
80
- if (!update_types) {
81
- console.info("Handling only messages, ignoring others");
82
- update_types = ["message"];
83
- }
84
86
  if (!log_level) {
85
87
  log_level = "info";
86
88
  }
87
- return {
89
+ if (!on_error) {
90
+ on_error = "stop";
91
+ }
92
+ const config = {
88
93
  limit,
89
94
  timeout,
90
95
  max_empty_responses,
91
- update_types,
92
- log_level
96
+ log_level,
97
+ on_error
93
98
  };
99
+ console.log("bot configuration", config);
100
+ return config;
94
101
  };
95
102
 
96
- // src/bot/update-poller/fetch-updates.ts
103
+ // src/bot/update-poller/fetch-and-handle.ts
97
104
  var Micro2 = __toESM(require("effect/Micro"));
105
+ var Data = __toESM(require("effect/Data"));
106
+
107
+ // src/bot/message-handler/types.ts
108
+ var import_Data = require("effect/Data");
109
+ var BotResponse = class _BotResponse extends (0, import_Data.TaggedClass)("BotResponse") {
110
+ static make(result) {
111
+ return new _BotResponse({ response: result });
112
+ }
113
+ static ignore = new _BotResponse({});
114
+ };
98
115
 
99
116
  // src/bot/message-handler/utils.ts
100
117
  var extractUpdate = (input) => {
@@ -115,8 +132,8 @@ var Micro = __toESM(require("effect/Micro"));
115
132
  var String = __toESM(require("effect/String"));
116
133
 
117
134
  // src/client/errors.ts
118
- var Data = __toESM(require("effect/Data"));
119
- var TgBotClientError = class _TgBotClientError extends Data.TaggedError("TgBotClientError") {
135
+ var import_Data2 = require("effect/Data");
136
+ var TgBotClientError = class _TgBotClientError extends (0, import_Data2.TaggedError)("TgBotClientError") {
120
137
  static missingSuccess = new _TgBotClientError({
121
138
  reason: {
122
139
  type: "ClientInternalError",
@@ -126,7 +143,7 @@ var TgBotClientError = class _TgBotClientError extends Data.TaggedError("TgBotCl
126
143
  };
127
144
 
128
145
  // src/client/config.ts
129
- var Context = __toESM(require("effect/Context"));
146
+ var Context2 = __toESM(require("effect/Context"));
130
147
 
131
148
  // src/const.ts
132
149
  var defaultBaseUrl = "https://api.telegram.org";
@@ -148,7 +165,7 @@ var makeTgBotClientConfig = (input) => TgBotClientConfig.of({
148
165
  ...input,
149
166
  base_url: input.base_url ?? defaultBaseUrl
150
167
  });
151
- var TgBotClientConfig = class extends Context.Tag("TgBotClientConfig")() {
168
+ var TgBotClientConfig = class extends Context2.Tag("TgBotClientConfig")() {
152
169
  };
153
170
 
154
171
  // src/client/guards.ts
@@ -212,8 +229,10 @@ var execute = (method, input) => Micro.gen(function* () {
212
229
  return response.result;
213
230
  });
214
231
 
215
- // src/bot/update-poller/fetch-updates.ts
216
- var fetchUpdates = ({ state, settings, handlers }) => Micro2.gen(function* () {
232
+ // src/bot/update-poller/fetch-and-handle.ts
233
+ var HandleUpdateError = class extends Data.TaggedError("HandleUpdateError") {
234
+ };
235
+ var fetchAndHandle = ({ state, settings, handlers }) => Micro2.gen(function* () {
217
236
  const updateId = state.lastUpdateId;
218
237
  if (settings.log_level == "debug") {
219
238
  console.debug("getting updates", state);
@@ -224,82 +243,146 @@ var fetchUpdates = ({ state, settings, handlers }) => Micro2.gen(function* () {
224
243
  }).pipe(
225
244
  Micro2.andThen((_) => _.sort((_2) => _2.update_id))
226
245
  );
227
- let lastSuccessId = void 0;
228
- let hasError = false;
229
- for (const updateObject of updates) {
230
- const update = extractUpdate(updateObject);
231
- if (!update) {
232
- console.warn("Unknown update", update);
233
- hasError = true;
234
- break;
235
- }
236
- const handler = handlers[`on_${update.type}`];
237
- if (!handler) {
238
- if (settings.update_types.includes(update.type)) {
239
- console.error("Handler for update not defined", update);
240
- hasError = true;
241
- break;
242
- } else {
243
- if (settings.log_level == "debug") {
244
- console.debug("Ignored update", update);
245
- }
246
- lastSuccessId = updateObject.update_id;
247
- continue;
248
- }
249
- }
250
- if (update.type == "message" && "text" in update) {
251
- console.info("Got new message", {
252
- chatId: update.chat.id,
253
- chatType: update.chat.type,
254
- message: `${update.text.slice(0, 5)}...`
255
- });
256
- }
257
- const handleResult = handler(update);
258
- if ("chat" in update && handleResult) {
259
- const response = yield* execute(`send_${handleResult.type}`, {
260
- ...handleResult,
261
- chat_id: update.chat.id
262
- });
263
- if (settings.log_level == "debug" && "text") {
264
- console.debug("bot response", response);
265
- }
266
- }
267
- if (!handleResult && settings.log_level == "debug") {
268
- console.debug("handler returned no response for update", { update });
269
- }
270
- ;
271
- lastSuccessId = updateObject.update_id;
246
+ if (updates.length) {
247
+ console.debug(`got a batch of updates (${updates.length})`);
272
248
  }
273
- if (hasError && lastSuccessId) {
249
+ const lastUpdateId = updates.map((_) => _.update_id).sort().at(-1);
250
+ if (!lastUpdateId) return { updates: [], lastUpdateId: void 0 };
251
+ const hasError = yield* Micro2.forEach(
252
+ updates,
253
+ (update) => handleUpdate(update, settings, handlers).pipe(
254
+ Micro2.catchAll((error) => {
255
+ console.log("error", {
256
+ updateId: update.update_id,
257
+ updateKey: Object.keys(update).at(1),
258
+ name: error._tag
259
+ });
260
+ return Micro2.succeed(true);
261
+ })
262
+ ),
263
+ {
264
+ concurrency: 10
265
+ }
266
+ ).pipe(
267
+ Micro2.andThen((result) => result.every((error) => error == null))
268
+ );
269
+ if (lastUpdateId) {
274
270
  yield* execute("get_updates", {
275
- offset: lastSuccessId,
271
+ offset: lastUpdateId,
276
272
  limit: 0
277
273
  });
278
274
  if (settings.log_level == "debug") {
279
- console.debug("committed offset", lastSuccessId);
275
+ console.debug("committed offset", lastUpdateId);
280
276
  }
281
277
  }
282
- return { updates, lastSuccessId, hasError };
278
+ return { updates, lastUpdateId, hasError };
279
+ });
280
+ var handleUpdate = (updateObject, settings, handlers) => Micro2.gen(function* () {
281
+ const update = extractUpdate(updateObject);
282
+ if (!update) {
283
+ return yield* Micro2.fail(
284
+ new HandleUpdateError({
285
+ name: "UnknownUpdate",
286
+ update: updateObject
287
+ })
288
+ );
289
+ }
290
+ const handler = handlers[`on_${update.type}`];
291
+ if (!handler) {
292
+ return yield* Micro2.fail(
293
+ new HandleUpdateError({
294
+ name: "HandlerNotDefined",
295
+ update: updateObject
296
+ })
297
+ );
298
+ }
299
+ if (update.type == "message" && "text" in update) {
300
+ console.info("Got a new text message", {
301
+ chatId: update.chat.id,
302
+ chatType: update.chat.type,
303
+ message: `${update.text.slice(0, 5)}...`
304
+ });
305
+ }
306
+ let handleUpdateError;
307
+ const handleResult = yield* Micro2.try({
308
+ try: () => handler(update),
309
+ catch: (error) => new HandleUpdateError({
310
+ name: "BotHandlerError",
311
+ update: updateObject,
312
+ cause: error
313
+ })
314
+ }).pipe(
315
+ Micro2.andThen((handleResult2) => {
316
+ if (handleResult2 instanceof Promise) {
317
+ return Micro2.tryPromise({
318
+ try: () => handleResult2,
319
+ catch: (error) => new HandleUpdateError({
320
+ name: "BotHandlerError",
321
+ update: updateObject,
322
+ cause: error
323
+ })
324
+ });
325
+ }
326
+ return Micro2.succeed(handleResult2);
327
+ }),
328
+ Micro2.catchAll((error) => {
329
+ handleUpdateError = error;
330
+ return Micro2.succeed(
331
+ BotResponse.make({
332
+ type: "message",
333
+ text: `Some internal error has happend(${error.name}) while handling this message`,
334
+ message_effect_id: MESSAGE_EFFECTS["\u{1F4A9}"],
335
+ ...updateObject.message?.message_id ? {
336
+ reply_parameters: {
337
+ message_id: updateObject.message?.message_id
338
+ }
339
+ } : void 0,
340
+ reply_markup: {
341
+ inline_keyboard: [
342
+ [
343
+ { text: "Repeat", callback_data: "fix:123" }
344
+ ]
345
+ ]
346
+ }
347
+ })
348
+ );
349
+ })
350
+ );
351
+ if (!handleResult && settings.log_level == "debug") {
352
+ console.log(`Bot response is undefined for update with ID #${updateObject.update_id}.`);
353
+ return;
354
+ }
355
+ ;
356
+ if ("chat" in update && handleResult.response) {
357
+ const response = yield* execute(`send_${handleResult.response.type}`, {
358
+ ...handleResult.response,
359
+ chat_id: update.chat.id
360
+ });
361
+ if (settings.log_level == "debug" && "text") {
362
+ console.debug("bot response", response);
363
+ }
364
+ }
365
+ return handleUpdateError;
283
366
  });
284
367
 
285
- // src/bot/update-poller/poll-and-handle.ts
286
- var pollAndHandle = (input) => {
368
+ // src/bot/update-poller/poll-updates.ts
369
+ var pollUpdates = (input) => {
287
370
  const state = {
288
371
  lastUpdateId: void 0,
289
372
  emptyResponses: 0
290
373
  };
291
374
  const settings = makeSettingsFrom(input.settings);
292
375
  return Micro3.delay(1e3)(
293
- fetchUpdates({
376
+ fetchAndHandle({
294
377
  state,
295
378
  settings,
296
379
  handlers: input.settings
297
380
  })
298
381
  ).pipe(
299
382
  Micro3.repeat({
300
- while: ({ updates, lastSuccessId, hasError }) => {
301
- if (hasError) {
302
- console.info("error in handler, quitting");
383
+ while: ({ updates, lastUpdateId, hasError }) => {
384
+ if (hasError === true && settings.on_error == "stop") {
385
+ console.info("Could not handle some messages, quitting");
303
386
  return false;
304
387
  }
305
388
  if (updates.length == 0) {
@@ -312,8 +395,8 @@ var pollAndHandle = (input) => {
312
395
  state.emptyResponses = 0;
313
396
  }
314
397
  ;
315
- if (lastSuccessId) {
316
- state.lastUpdateId = lastSuccessId + 1;
398
+ if (lastUpdateId) {
399
+ state.lastUpdateId = lastUpdateId + 1;
317
400
  }
318
401
  return true;
319
402
  }
@@ -322,24 +405,23 @@ var pollAndHandle = (input) => {
322
405
  };
323
406
 
324
407
  // src/bot/update-poller/_service.ts
325
- var BotUpdatePollerService = class extends Context2.Tag("BotUpdatePollerService")() {
408
+ var BotUpdatePollerService = class extends Context3.Tag("BotUpdatePollerService")() {
326
409
  };
327
410
  var BotUpdatesPollerServiceDefault = Micro4.gen(function* () {
328
411
  console.log("Initiating BotUpdatesPollerServiceDefault");
329
412
  const state = {
330
413
  fiber: void 0
331
414
  };
332
- const runBot = (messageHandler) => Micro4.gen(function* () {
333
- const startFiber = pollAndHandle({
415
+ const runBot = Micro4.gen(function* () {
416
+ console.log("run bot");
417
+ const messageHandler = yield* Micro4.service(BotMessageHandler);
418
+ const startFiber = pollUpdates({
334
419
  settings: messageHandler
335
420
  }).pipe(
336
421
  Micro4.forkDaemon,
337
422
  Micro4.tap(
338
423
  (fiber) => fiber.addObserver((exit) => {
339
424
  console.log("bot's fiber has been closed", exit);
340
- if (messageHandler.onExit) {
341
- messageHandler.onExit(exit);
342
- }
343
425
  })
344
426
  )
345
427
  );
@@ -349,10 +431,10 @@ var BotUpdatesPollerServiceDefault = Micro4.gen(function* () {
349
431
  }
350
432
  state.fiber = yield* startFiber;
351
433
  console.log("Fetching bot updates via long polling...");
352
- return state.fiber;
353
434
  });
354
435
  return {
355
- runBot
436
+ runBot,
437
+ getFiber: () => state.fiber
356
438
  };
357
439
  });
358
440
 
@@ -378,66 +460,53 @@ var makeClientConfigFrom = (input) => Micro5.gen(function* () {
378
460
  return makeTgBotClientConfig(config);
379
461
  });
380
462
 
381
- // src/bot/factory/make-bot.ts
382
- var Micro6 = __toESM(require("effect/Micro"));
383
- var makeBot = (messageHandler) => Micro6.gen(function* () {
384
- const { runBot } = yield* Micro6.service(BotUpdatePollerService);
385
- const fiber = yield* runBot(messageHandler);
386
- const interrupt = Micro6.fiberInterrupt(fiber);
387
- return {
388
- runBot,
389
- interrupt
390
- };
391
- }).pipe(
392
- Micro6.tapError((error) => {
393
- console.error(error);
394
- return Micro6.void;
395
- })
396
- );
397
-
398
463
  // src/bot/factory/_service.ts
399
- var BotFactoryService = class extends Context3.Tag("BotFactoryService")() {
464
+ var BotFactoryService = class extends Context4.Tag("BotFactoryService")() {
400
465
  };
401
466
  var BotFactoryServiceDefault = {
402
- makeBot,
403
- runBot: (input) => Micro7.gen(function* () {
404
- const client = Context3.make(TgBotClientConfig, yield* makeClientConfigFrom(input));
467
+ runBot: (input) => Micro6.gen(function* () {
468
+ const client = Context4.make(TgBotClientConfig, yield* makeClientConfigFrom(input));
405
469
  const poller = yield* BotUpdatesPollerServiceDefault.pipe(
406
- Micro7.provideContext(client)
470
+ Micro6.provideContext(client)
407
471
  );
408
- const bot = yield* makeBot(input).pipe(
409
- Micro7.provideContext(client),
410
- Micro7.provideService(BotUpdatePollerService, poller)
472
+ yield* poller.runBot.pipe(
473
+ Micro6.provideService(BotMessageHandler, input),
474
+ Micro6.provideContext(client)
411
475
  );
412
- const reload = (input2) => bot.runBot(input2).pipe(
413
- Micro7.provideContext(client)
476
+ const reload = (input2) => poller.runBot.pipe(
477
+ Micro6.provideService(BotMessageHandler, input2),
478
+ Micro6.provideContext(client),
479
+ Micro6.runPromise
414
480
  );
415
481
  return {
416
482
  reload,
417
- bot
483
+ fiber: poller.getFiber
418
484
  };
419
485
  })
420
486
  };
421
487
 
422
488
  // src/bot/run.ts
423
- var Micro8 = __toESM(require("effect/Micro"));
424
- var runTgChatBot = (input) => BotFactoryServiceDefault.runBot(input).pipe(Micro8.runPromise);
489
+ var Micro7 = __toESM(require("effect/Micro"));
490
+ var runTgChatBot = (input) => BotFactoryServiceDefault.runBot(input).pipe(
491
+ Micro7.provideService(BotMessageHandler, input),
492
+ Micro7.runPromise
493
+ );
425
494
 
426
495
  // src/client/_client.ts
427
- var Micro11 = __toESM(require("effect/Micro"));
496
+ var Micro10 = __toESM(require("effect/Micro"));
428
497
 
429
498
  // src/client/file/_service.ts
430
- var Micro10 = __toESM(require("effect/Micro"));
431
- var Context4 = __toESM(require("effect/Context"));
499
+ var Micro9 = __toESM(require("effect/Micro"));
500
+ var Context5 = __toESM(require("effect/Context"));
432
501
 
433
502
  // src/client/file/get-file.ts
434
- var Micro9 = __toESM(require("effect/Micro"));
435
- var getFile = (fileId) => Micro9.gen(function* () {
503
+ var Micro8 = __toESM(require("effect/Micro"));
504
+ var getFile = (fileId) => Micro8.gen(function* () {
436
505
  const response = yield* execute("get_file", { file_id: fileId });
437
- const config = yield* Micro9.service(TgBotClientConfig);
506
+ const config = yield* Micro8.service(TgBotClientConfig);
438
507
  const file_path = response.file_path;
439
508
  if (!file_path || file_path.length == 0) {
440
- return yield* Micro9.fail(
509
+ return yield* Micro8.fail(
441
510
  new TgBotClientError({
442
511
  reason: {
443
512
  type: "UnableToGetFile",
@@ -448,7 +517,7 @@ var getFile = (fileId) => Micro9.gen(function* () {
448
517
  }
449
518
  const file_name = file_path.replaceAll("/", "-");
450
519
  const url = `${config.base_url}/file/bot${config.bot_token}/${file_path}`;
451
- const fileContent = yield* Micro9.tryPromise({
520
+ const fileContent = yield* Micro8.tryPromise({
452
521
  try: () => fetch(url).then((_) => _.arrayBuffer()),
453
522
  catch: (cause) => new TgBotClientError({
454
523
  reason: { type: "UnableToGetFile", cause }
@@ -459,9 +528,9 @@ var getFile = (fileId) => Micro9.gen(function* () {
459
528
  });
460
529
 
461
530
  // src/client/file/_service.ts
462
- var ClientFileService = class extends Context4.Tag("ClientFileService")() {
531
+ var ClientFileService = class extends Context5.Tag("ClientFileService")() {
463
532
  };
464
- var ClientFileServiceDefault = Micro10.gen(function* () {
533
+ var ClientFileServiceDefault = Micro9.gen(function* () {
465
534
  return {
466
535
  getFile: (input) => getFile(input.file_id)
467
536
  };
@@ -470,22 +539,22 @@ var ClientFileServiceDefault = Micro10.gen(function* () {
470
539
  // src/client/_client.ts
471
540
  var makeTgBotClient = (input) => {
472
541
  const config = makeTgBotClientConfig(input);
473
- const client = Micro11.gen(function* () {
474
- const file = yield* Micro11.service(ClientFileService);
542
+ const client = Micro10.gen(function* () {
543
+ const file = yield* Micro10.service(ClientFileService);
475
544
  return {
476
545
  execute: (method, input2) => execute(method, input2).pipe(
477
- Micro11.provideService(TgBotClientConfig, config),
478
- Micro11.runPromise
546
+ Micro10.provideService(TgBotClientConfig, config),
547
+ Micro10.runPromise
479
548
  ),
480
549
  getFile: (input2) => file.getFile(input2).pipe(
481
- Micro11.provideService(TgBotClientConfig, config),
482
- Micro11.runPromise
550
+ Micro10.provideService(TgBotClientConfig, config),
551
+ Micro10.runPromise
483
552
  )
484
553
  };
485
554
  }).pipe(
486
- Micro11.provideServiceEffect(ClientFileService, ClientFileServiceDefault),
487
- Micro11.provideService(TgBotClientConfig, config),
488
- Micro11.runSync
555
+ Micro10.provideServiceEffect(ClientFileService, ClientFileServiceDefault),
556
+ Micro10.provideService(TgBotClientConfig, config),
557
+ Micro10.runSync
489
558
  );
490
559
  return client;
491
560
  };
@@ -493,6 +562,7 @@ var makeTgBotClient = (input) => {
493
562
  0 && (module.exports = {
494
563
  BotFactoryService,
495
564
  BotFactoryServiceDefault,
565
+ BotResponse,
496
566
  BotUpdatePollerService,
497
567
  BotUpdatesPollerServiceDefault,
498
568
  MESSAGE_EFFECTS,