@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.mjs CHANGED
@@ -1,12 +1,17 @@
1
1
  // src/bot/factory/_service.ts
2
- import * as Micro7 from "effect/Micro";
3
- import * as Context3 from "effect/Context";
2
+ import * as Micro6 from "effect/Micro";
3
+ import * as Context4 from "effect/Context";
4
+
5
+ // src/bot/message-handler/_service.ts
6
+ import * as Context from "effect/Context";
7
+ var BotMessageHandler = class extends Context.Tag("BotMessageHandler")() {
8
+ };
4
9
 
5
10
  // src/bot/update-poller/_service.ts
6
11
  import * as Micro4 from "effect/Micro";
7
- import * as Context2 from "effect/Context";
12
+ import * as Context3 from "effect/Context";
8
13
 
9
- // src/bot/update-poller/poll-and-handle.ts
14
+ // src/bot/update-poller/poll-updates.ts
10
15
  import * as Micro3 from "effect/Micro";
11
16
 
12
17
  // src/bot/update-poller/settings.ts
@@ -14,8 +19,8 @@ var makeSettingsFrom = (input) => {
14
19
  let limit = input.batch_size ?? 10;
15
20
  let timeout = input.timeout ?? 10;
16
21
  let max_empty_responses = input.max_empty_responses;
17
- let update_types = input.update_types;
18
22
  let log_level = input.log_level;
23
+ let on_error = input.on_error;
19
24
  if (limit < 10 || limit > 100) {
20
25
  console.warn("Wrong limit, must be in [10..100], using 10 instead");
21
26
  limit = 10;
@@ -32,24 +37,35 @@ var makeSettingsFrom = (input) => {
32
37
  console.warn("Wrong max_empty_responses, must be in [2..infinity], using infinity");
33
38
  max_empty_responses = void 0;
34
39
  }
35
- if (!update_types) {
36
- console.info("Handling only messages, ignoring others");
37
- update_types = ["message"];
38
- }
39
40
  if (!log_level) {
40
41
  log_level = "info";
41
42
  }
42
- return {
43
+ if (!on_error) {
44
+ on_error = "stop";
45
+ }
46
+ const config = {
43
47
  limit,
44
48
  timeout,
45
49
  max_empty_responses,
46
- update_types,
47
- log_level
50
+ log_level,
51
+ on_error
48
52
  };
53
+ console.log("bot configuration", config);
54
+ return config;
49
55
  };
50
56
 
51
- // src/bot/update-poller/fetch-updates.ts
57
+ // src/bot/update-poller/fetch-and-handle.ts
52
58
  import * as Micro2 from "effect/Micro";
59
+ import * as Data from "effect/Data";
60
+
61
+ // src/bot/message-handler/types.ts
62
+ import { TaggedClass } from "effect/Data";
63
+ var BotResponse = class _BotResponse extends TaggedClass("BotResponse") {
64
+ static make(result) {
65
+ return new _BotResponse({ response: result });
66
+ }
67
+ static ignore = new _BotResponse({});
68
+ };
53
69
 
54
70
  // src/bot/message-handler/utils.ts
55
71
  var extractUpdate = (input) => {
@@ -70,8 +86,8 @@ import * as Micro from "effect/Micro";
70
86
  import * as String from "effect/String";
71
87
 
72
88
  // src/client/errors.ts
73
- import * as Data from "effect/Data";
74
- var TgBotClientError = class _TgBotClientError extends Data.TaggedError("TgBotClientError") {
89
+ import { TaggedError } from "effect/Data";
90
+ var TgBotClientError = class _TgBotClientError extends TaggedError("TgBotClientError") {
75
91
  static missingSuccess = new _TgBotClientError({
76
92
  reason: {
77
93
  type: "ClientInternalError",
@@ -81,7 +97,7 @@ var TgBotClientError = class _TgBotClientError extends Data.TaggedError("TgBotCl
81
97
  };
82
98
 
83
99
  // src/client/config.ts
84
- import * as Context from "effect/Context";
100
+ import * as Context2 from "effect/Context";
85
101
 
86
102
  // src/const.ts
87
103
  var defaultBaseUrl = "https://api.telegram.org";
@@ -103,7 +119,7 @@ var makeTgBotClientConfig = (input) => TgBotClientConfig.of({
103
119
  ...input,
104
120
  base_url: input.base_url ?? defaultBaseUrl
105
121
  });
106
- var TgBotClientConfig = class extends Context.Tag("TgBotClientConfig")() {
122
+ var TgBotClientConfig = class extends Context2.Tag("TgBotClientConfig")() {
107
123
  };
108
124
 
109
125
  // src/client/guards.ts
@@ -167,8 +183,10 @@ var execute = (method, input) => Micro.gen(function* () {
167
183
  return response.result;
168
184
  });
169
185
 
170
- // src/bot/update-poller/fetch-updates.ts
171
- var fetchUpdates = ({ state, settings, handlers }) => Micro2.gen(function* () {
186
+ // src/bot/update-poller/fetch-and-handle.ts
187
+ var HandleUpdateError = class extends Data.TaggedError("HandleUpdateError") {
188
+ };
189
+ var fetchAndHandle = ({ state, settings, handlers }) => Micro2.gen(function* () {
172
190
  const updateId = state.lastUpdateId;
173
191
  if (settings.log_level == "debug") {
174
192
  console.debug("getting updates", state);
@@ -179,82 +197,146 @@ var fetchUpdates = ({ state, settings, handlers }) => Micro2.gen(function* () {
179
197
  }).pipe(
180
198
  Micro2.andThen((_) => _.sort((_2) => _2.update_id))
181
199
  );
182
- let lastSuccessId = void 0;
183
- let hasError = false;
184
- for (const updateObject of updates) {
185
- const update = extractUpdate(updateObject);
186
- if (!update) {
187
- console.warn("Unknown update", update);
188
- hasError = true;
189
- break;
190
- }
191
- const handler = handlers[`on_${update.type}`];
192
- if (!handler) {
193
- if (settings.update_types.includes(update.type)) {
194
- console.error("Handler for update not defined", update);
195
- hasError = true;
196
- break;
197
- } else {
198
- if (settings.log_level == "debug") {
199
- console.debug("Ignored update", update);
200
- }
201
- lastSuccessId = updateObject.update_id;
202
- continue;
203
- }
204
- }
205
- if (update.type == "message" && "text" in update) {
206
- console.info("Got new message", {
207
- chatId: update.chat.id,
208
- chatType: update.chat.type,
209
- message: `${update.text.slice(0, 5)}...`
210
- });
211
- }
212
- const handleResult = handler(update);
213
- if ("chat" in update && handleResult) {
214
- const response = yield* execute(`send_${handleResult.type}`, {
215
- ...handleResult,
216
- chat_id: update.chat.id
217
- });
218
- if (settings.log_level == "debug" && "text") {
219
- console.debug("bot response", response);
220
- }
221
- }
222
- if (!handleResult && settings.log_level == "debug") {
223
- console.debug("handler returned no response for update", { update });
224
- }
225
- ;
226
- lastSuccessId = updateObject.update_id;
200
+ if (updates.length) {
201
+ console.debug(`got a batch of updates (${updates.length})`);
227
202
  }
228
- if (hasError && lastSuccessId) {
203
+ const lastUpdateId = updates.map((_) => _.update_id).sort().at(-1);
204
+ if (!lastUpdateId) return { updates: [], lastUpdateId: void 0 };
205
+ const hasError = yield* Micro2.forEach(
206
+ updates,
207
+ (update) => handleUpdate(update, settings, handlers).pipe(
208
+ Micro2.catchAll((error) => {
209
+ console.log("error", {
210
+ updateId: update.update_id,
211
+ updateKey: Object.keys(update).at(1),
212
+ name: error._tag
213
+ });
214
+ return Micro2.succeed(true);
215
+ })
216
+ ),
217
+ {
218
+ concurrency: 10
219
+ }
220
+ ).pipe(
221
+ Micro2.andThen((result) => result.every((error) => error == null))
222
+ );
223
+ if (lastUpdateId) {
229
224
  yield* execute("get_updates", {
230
- offset: lastSuccessId,
225
+ offset: lastUpdateId,
231
226
  limit: 0
232
227
  });
233
228
  if (settings.log_level == "debug") {
234
- console.debug("committed offset", lastSuccessId);
229
+ console.debug("committed offset", lastUpdateId);
235
230
  }
236
231
  }
237
- return { updates, lastSuccessId, hasError };
232
+ return { updates, lastUpdateId, hasError };
233
+ });
234
+ var handleUpdate = (updateObject, settings, handlers) => Micro2.gen(function* () {
235
+ const update = extractUpdate(updateObject);
236
+ if (!update) {
237
+ return yield* Micro2.fail(
238
+ new HandleUpdateError({
239
+ name: "UnknownUpdate",
240
+ update: updateObject
241
+ })
242
+ );
243
+ }
244
+ const handler = handlers[`on_${update.type}`];
245
+ if (!handler) {
246
+ return yield* Micro2.fail(
247
+ new HandleUpdateError({
248
+ name: "HandlerNotDefined",
249
+ update: updateObject
250
+ })
251
+ );
252
+ }
253
+ if (update.type == "message" && "text" in update) {
254
+ console.info("Got a new text message", {
255
+ chatId: update.chat.id,
256
+ chatType: update.chat.type,
257
+ message: `${update.text.slice(0, 5)}...`
258
+ });
259
+ }
260
+ let handleUpdateError;
261
+ const handleResult = yield* Micro2.try({
262
+ try: () => handler(update),
263
+ catch: (error) => new HandleUpdateError({
264
+ name: "BotHandlerError",
265
+ update: updateObject,
266
+ cause: error
267
+ })
268
+ }).pipe(
269
+ Micro2.andThen((handleResult2) => {
270
+ if (handleResult2 instanceof Promise) {
271
+ return Micro2.tryPromise({
272
+ try: () => handleResult2,
273
+ catch: (error) => new HandleUpdateError({
274
+ name: "BotHandlerError",
275
+ update: updateObject,
276
+ cause: error
277
+ })
278
+ });
279
+ }
280
+ return Micro2.succeed(handleResult2);
281
+ }),
282
+ Micro2.catchAll((error) => {
283
+ handleUpdateError = error;
284
+ return Micro2.succeed(
285
+ BotResponse.make({
286
+ type: "message",
287
+ text: `Some internal error has happend(${error.name}) while handling this message`,
288
+ message_effect_id: MESSAGE_EFFECTS["\u{1F4A9}"],
289
+ ...updateObject.message?.message_id ? {
290
+ reply_parameters: {
291
+ message_id: updateObject.message?.message_id
292
+ }
293
+ } : void 0,
294
+ reply_markup: {
295
+ inline_keyboard: [
296
+ [
297
+ { text: "Repeat", callback_data: "fix:123" }
298
+ ]
299
+ ]
300
+ }
301
+ })
302
+ );
303
+ })
304
+ );
305
+ if (!handleResult && settings.log_level == "debug") {
306
+ console.log(`Bot response is undefined for update with ID #${updateObject.update_id}.`);
307
+ return;
308
+ }
309
+ ;
310
+ if ("chat" in update && handleResult.response) {
311
+ const response = yield* execute(`send_${handleResult.response.type}`, {
312
+ ...handleResult.response,
313
+ chat_id: update.chat.id
314
+ });
315
+ if (settings.log_level == "debug" && "text") {
316
+ console.debug("bot response", response);
317
+ }
318
+ }
319
+ return handleUpdateError;
238
320
  });
239
321
 
240
- // src/bot/update-poller/poll-and-handle.ts
241
- var pollAndHandle = (input) => {
322
+ // src/bot/update-poller/poll-updates.ts
323
+ var pollUpdates = (input) => {
242
324
  const state = {
243
325
  lastUpdateId: void 0,
244
326
  emptyResponses: 0
245
327
  };
246
328
  const settings = makeSettingsFrom(input.settings);
247
329
  return Micro3.delay(1e3)(
248
- fetchUpdates({
330
+ fetchAndHandle({
249
331
  state,
250
332
  settings,
251
333
  handlers: input.settings
252
334
  })
253
335
  ).pipe(
254
336
  Micro3.repeat({
255
- while: ({ updates, lastSuccessId, hasError }) => {
256
- if (hasError) {
257
- console.info("error in handler, quitting");
337
+ while: ({ updates, lastUpdateId, hasError }) => {
338
+ if (hasError === true && settings.on_error == "stop") {
339
+ console.info("Could not handle some messages, quitting");
258
340
  return false;
259
341
  }
260
342
  if (updates.length == 0) {
@@ -267,8 +349,8 @@ var pollAndHandle = (input) => {
267
349
  state.emptyResponses = 0;
268
350
  }
269
351
  ;
270
- if (lastSuccessId) {
271
- state.lastUpdateId = lastSuccessId + 1;
352
+ if (lastUpdateId) {
353
+ state.lastUpdateId = lastUpdateId + 1;
272
354
  }
273
355
  return true;
274
356
  }
@@ -277,24 +359,23 @@ var pollAndHandle = (input) => {
277
359
  };
278
360
 
279
361
  // src/bot/update-poller/_service.ts
280
- var BotUpdatePollerService = class extends Context2.Tag("BotUpdatePollerService")() {
362
+ var BotUpdatePollerService = class extends Context3.Tag("BotUpdatePollerService")() {
281
363
  };
282
364
  var BotUpdatesPollerServiceDefault = Micro4.gen(function* () {
283
365
  console.log("Initiating BotUpdatesPollerServiceDefault");
284
366
  const state = {
285
367
  fiber: void 0
286
368
  };
287
- const runBot = (messageHandler) => Micro4.gen(function* () {
288
- const startFiber = pollAndHandle({
369
+ const runBot = Micro4.gen(function* () {
370
+ console.log("run bot");
371
+ const messageHandler = yield* Micro4.service(BotMessageHandler);
372
+ const startFiber = pollUpdates({
289
373
  settings: messageHandler
290
374
  }).pipe(
291
375
  Micro4.forkDaemon,
292
376
  Micro4.tap(
293
377
  (fiber) => fiber.addObserver((exit) => {
294
378
  console.log("bot's fiber has been closed", exit);
295
- if (messageHandler.onExit) {
296
- messageHandler.onExit(exit);
297
- }
298
379
  })
299
380
  )
300
381
  );
@@ -304,10 +385,10 @@ var BotUpdatesPollerServiceDefault = Micro4.gen(function* () {
304
385
  }
305
386
  state.fiber = yield* startFiber;
306
387
  console.log("Fetching bot updates via long polling...");
307
- return state.fiber;
308
388
  });
309
389
  return {
310
- runBot
390
+ runBot,
391
+ getFiber: () => state.fiber
311
392
  };
312
393
  });
313
394
 
@@ -333,66 +414,53 @@ var makeClientConfigFrom = (input) => Micro5.gen(function* () {
333
414
  return makeTgBotClientConfig(config);
334
415
  });
335
416
 
336
- // src/bot/factory/make-bot.ts
337
- import * as Micro6 from "effect/Micro";
338
- var makeBot = (messageHandler) => Micro6.gen(function* () {
339
- const { runBot } = yield* Micro6.service(BotUpdatePollerService);
340
- const fiber = yield* runBot(messageHandler);
341
- const interrupt = Micro6.fiberInterrupt(fiber);
342
- return {
343
- runBot,
344
- interrupt
345
- };
346
- }).pipe(
347
- Micro6.tapError((error) => {
348
- console.error(error);
349
- return Micro6.void;
350
- })
351
- );
352
-
353
417
  // src/bot/factory/_service.ts
354
- var BotFactoryService = class extends Context3.Tag("BotFactoryService")() {
418
+ var BotFactoryService = class extends Context4.Tag("BotFactoryService")() {
355
419
  };
356
420
  var BotFactoryServiceDefault = {
357
- makeBot,
358
- runBot: (input) => Micro7.gen(function* () {
359
- const client = Context3.make(TgBotClientConfig, yield* makeClientConfigFrom(input));
421
+ runBot: (input) => Micro6.gen(function* () {
422
+ const client = Context4.make(TgBotClientConfig, yield* makeClientConfigFrom(input));
360
423
  const poller = yield* BotUpdatesPollerServiceDefault.pipe(
361
- Micro7.provideContext(client)
424
+ Micro6.provideContext(client)
362
425
  );
363
- const bot = yield* makeBot(input).pipe(
364
- Micro7.provideContext(client),
365
- Micro7.provideService(BotUpdatePollerService, poller)
426
+ yield* poller.runBot.pipe(
427
+ Micro6.provideService(BotMessageHandler, input),
428
+ Micro6.provideContext(client)
366
429
  );
367
- const reload = (input2) => bot.runBot(input2).pipe(
368
- Micro7.provideContext(client)
430
+ const reload = (input2) => poller.runBot.pipe(
431
+ Micro6.provideService(BotMessageHandler, input2),
432
+ Micro6.provideContext(client),
433
+ Micro6.runPromise
369
434
  );
370
435
  return {
371
436
  reload,
372
- bot
437
+ fiber: poller.getFiber
373
438
  };
374
439
  })
375
440
  };
376
441
 
377
442
  // src/bot/run.ts
378
- import * as Micro8 from "effect/Micro";
379
- var runTgChatBot = (input) => BotFactoryServiceDefault.runBot(input).pipe(Micro8.runPromise);
443
+ import * as Micro7 from "effect/Micro";
444
+ var runTgChatBot = (input) => BotFactoryServiceDefault.runBot(input).pipe(
445
+ Micro7.provideService(BotMessageHandler, input),
446
+ Micro7.runPromise
447
+ );
380
448
 
381
449
  // src/client/_client.ts
382
- import * as Micro11 from "effect/Micro";
450
+ import * as Micro10 from "effect/Micro";
383
451
 
384
452
  // src/client/file/_service.ts
385
- import * as Micro10 from "effect/Micro";
386
- import * as Context4 from "effect/Context";
453
+ import * as Micro9 from "effect/Micro";
454
+ import * as Context5 from "effect/Context";
387
455
 
388
456
  // src/client/file/get-file.ts
389
- import * as Micro9 from "effect/Micro";
390
- var getFile = (fileId) => Micro9.gen(function* () {
457
+ import * as Micro8 from "effect/Micro";
458
+ var getFile = (fileId) => Micro8.gen(function* () {
391
459
  const response = yield* execute("get_file", { file_id: fileId });
392
- const config = yield* Micro9.service(TgBotClientConfig);
460
+ const config = yield* Micro8.service(TgBotClientConfig);
393
461
  const file_path = response.file_path;
394
462
  if (!file_path || file_path.length == 0) {
395
- return yield* Micro9.fail(
463
+ return yield* Micro8.fail(
396
464
  new TgBotClientError({
397
465
  reason: {
398
466
  type: "UnableToGetFile",
@@ -403,7 +471,7 @@ var getFile = (fileId) => Micro9.gen(function* () {
403
471
  }
404
472
  const file_name = file_path.replaceAll("/", "-");
405
473
  const url = `${config.base_url}/file/bot${config.bot_token}/${file_path}`;
406
- const fileContent = yield* Micro9.tryPromise({
474
+ const fileContent = yield* Micro8.tryPromise({
407
475
  try: () => fetch(url).then((_) => _.arrayBuffer()),
408
476
  catch: (cause) => new TgBotClientError({
409
477
  reason: { type: "UnableToGetFile", cause }
@@ -414,9 +482,9 @@ var getFile = (fileId) => Micro9.gen(function* () {
414
482
  });
415
483
 
416
484
  // src/client/file/_service.ts
417
- var ClientFileService = class extends Context4.Tag("ClientFileService")() {
485
+ var ClientFileService = class extends Context5.Tag("ClientFileService")() {
418
486
  };
419
- var ClientFileServiceDefault = Micro10.gen(function* () {
487
+ var ClientFileServiceDefault = Micro9.gen(function* () {
420
488
  return {
421
489
  getFile: (input) => getFile(input.file_id)
422
490
  };
@@ -425,28 +493,29 @@ var ClientFileServiceDefault = Micro10.gen(function* () {
425
493
  // src/client/_client.ts
426
494
  var makeTgBotClient = (input) => {
427
495
  const config = makeTgBotClientConfig(input);
428
- const client = Micro11.gen(function* () {
429
- const file = yield* Micro11.service(ClientFileService);
496
+ const client = Micro10.gen(function* () {
497
+ const file = yield* Micro10.service(ClientFileService);
430
498
  return {
431
499
  execute: (method, input2) => execute(method, input2).pipe(
432
- Micro11.provideService(TgBotClientConfig, config),
433
- Micro11.runPromise
500
+ Micro10.provideService(TgBotClientConfig, config),
501
+ Micro10.runPromise
434
502
  ),
435
503
  getFile: (input2) => file.getFile(input2).pipe(
436
- Micro11.provideService(TgBotClientConfig, config),
437
- Micro11.runPromise
504
+ Micro10.provideService(TgBotClientConfig, config),
505
+ Micro10.runPromise
438
506
  )
439
507
  };
440
508
  }).pipe(
441
- Micro11.provideServiceEffect(ClientFileService, ClientFileServiceDefault),
442
- Micro11.provideService(TgBotClientConfig, config),
443
- Micro11.runSync
509
+ Micro10.provideServiceEffect(ClientFileService, ClientFileServiceDefault),
510
+ Micro10.provideService(TgBotClientConfig, config),
511
+ Micro10.runSync
444
512
  );
445
513
  return client;
446
514
  };
447
515
  export {
448
516
  BotFactoryService,
449
517
  BotFactoryServiceDefault,
518
+ BotResponse,
450
519
  BotUpdatePollerService,
451
520
  BotUpdatesPollerServiceDefault,
452
521
  MESSAGE_EFFECTS,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@effect-ak/tg-bot-client",
3
- "version": "0.3.2",
3
+ "version": "0.4.0",
4
4
  "homepage": "https://effect-ak.github.io/telegram-bot-client",
5
5
  "author": {
6
6
  "name": "Aleksandr Kondaurov",