@effect-ak/tg-bot 1.0.0 → 1.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.
package/dist/index.cjs CHANGED
@@ -25,6 +25,7 @@ __export(index_exports, {
25
25
  BotPollSettingsTag: () => BotPollSettingsTag,
26
26
  BotResponse: () => BotResponse,
27
27
  BotRunService: () => BotRunService,
28
+ BotTgClientTag: () => BotTgClientTag,
28
29
  BotUpdateHandlersTag: () => BotUpdateHandlersTag,
29
30
  HandleUpdateError: () => HandleUpdateError,
30
31
  defineBot: () => defineBot,
@@ -3491,11 +3492,12 @@ var runPromise = (effect, options) => runPromiseExit(effect, options).then((exit
3491
3492
 
3492
3493
  // src/internal/handle-update.ts
3493
3494
  var import_tg_bot_client = require("@effect-ak/tg-bot-client");
3494
- var import_tg_bot_client2 = require("@effect-ak/tg-bot-client");
3495
3495
 
3496
3496
  // src/internal/poll-settings.ts
3497
3497
  var BotUpdateHandlersTag = class extends Tag2("BotUpdateHandlers")() {
3498
3498
  };
3499
+ var BotTgClientTag = class extends Tag2("BotTgClient")() {
3500
+ };
3499
3501
  var BotPollSettings = class _BotPollSettings extends Class2 {
3500
3502
  static make(input) {
3501
3503
  let batch_size = input.batch_size ?? 10;
@@ -3603,17 +3605,24 @@ var handleEntireBatch = (updates, handlers) => try_({
3603
3605
  })
3604
3606
  );
3605
3607
  var HandleUpdateError = class extends TaggedError("HandleUpdateError") {
3608
+ logInfo() {
3609
+ return {
3610
+ updateId: this.update.update_id,
3611
+ updateKey: Object.keys(this.update).at(1),
3612
+ name: this._tag,
3613
+ ...this.cause instanceof Error && { error: this.cause.message }
3614
+ };
3615
+ }
3606
3616
  };
3607
3617
  var handleOneByOne = (updates, handlers, pollSettings) => forEach3(
3608
3618
  updates,
3609
3619
  (update) => handleOneUpdate(update, handlers).pipe(
3610
3620
  catchAll((error) => {
3611
- console.log("update handle error", {
3612
- updateId: update.update_id,
3613
- updateKey: Object.keys(update).at(1),
3614
- name: error._tag,
3615
- ...error.cause instanceof Error ? { error: error.cause.message } : void 0
3616
- });
3621
+ if (error instanceof HandleUpdateError) {
3622
+ console.warn("update handle error", error.logInfo());
3623
+ } else {
3624
+ console.warn("unknown error", error);
3625
+ }
3617
3626
  return succeed(error);
3618
3627
  })
3619
3628
  ),
@@ -3681,17 +3690,12 @@ var handleOneUpdate = (updateObject, handlers) => gen(function* () {
3681
3690
  }),
3682
3691
  catchAll((error) => {
3683
3692
  handleUpdateError = error;
3684
- console.log("error", {
3685
- updateId: updateObject.update_id,
3686
- updateKey: Object.keys(updateObject).at(1),
3687
- name: error._tag,
3688
- ...error.cause instanceof Error ? { error: error.cause.message } : void 0
3689
- });
3693
+ console.warn("error", error.logInfo());
3690
3694
  return succeed(
3691
3695
  BotResponse.make({
3692
3696
  type: "message",
3693
3697
  text: `Some internal error has happend(${error.name}) while handling this message`,
3694
- message_effect_id: import_tg_bot_client2.MESSAGE_EFFECTS["\u{1F4A9}"],
3698
+ message_effect_id: import_tg_bot_client.MESSAGE_EFFECTS["\u{1F4A9}"],
3695
3699
  ...updateObject.message?.message_id ? {
3696
3700
  reply_parameters: {
3697
3701
  message_id: updateObject.message?.message_id
@@ -3709,13 +3713,15 @@ var handleOneUpdate = (updateObject, handlers) => gen(function* () {
3709
3713
  return;
3710
3714
  }
3711
3715
  if ("chat" in update && handleResult.response) {
3712
- const response = yield* (0, import_tg_bot_client.executeTgBotMethod)(
3713
- `send_${handleResult.response.type}`,
3714
- {
3715
- ...handleResult.response,
3716
+ const client = yield* service(BotTgClientTag);
3717
+ const responsePayload = handleResult.response;
3718
+ const response = yield* tryPromise({
3719
+ try: () => client.execute(`send_${responsePayload.type}`, {
3720
+ ...responsePayload,
3716
3721
  chat_id: update.chat.id
3717
- }
3718
- );
3722
+ }),
3723
+ catch: (error) => error
3724
+ });
3719
3725
  if (pollSettings.log_level == "debug" && "text") {
3720
3726
  console.debug("bot response", response);
3721
3727
  }
@@ -3724,7 +3730,6 @@ var handleOneUpdate = (updateObject, handlers) => gen(function* () {
3724
3730
  });
3725
3731
 
3726
3732
  // src/service/fetch-updates.ts
3727
- var import_tg_bot_client3 = require("@effect-ak/tg-bot-client");
3728
3733
  var BotFetchUpdatesService = class extends Reference2()(
3729
3734
  "BotFetchUpdatesService",
3730
3735
  {
@@ -3755,6 +3760,7 @@ var FetchUpdatesError = class extends TaggedError("FetchUpdatesError") {
3755
3760
  };
3756
3761
  var _fetchUpdates = (pollState) => gen(function* () {
3757
3762
  const pollSettings = yield* service(BotPollSettingsTag);
3763
+ const client = yield* service(BotTgClientTag);
3758
3764
  if (pollSettings.max_empty_responses && pollState.emptyResponses == pollSettings.max_empty_responses) {
3759
3765
  return yield* fail3(
3760
3766
  new FetchUpdatesError({ name: "TooManyEmptyResponses" })
@@ -3764,9 +3770,12 @@ var _fetchUpdates = (pollState) => gen(function* () {
3764
3770
  if (pollSettings.log_level == "debug") {
3765
3771
  console.debug("getting updates", pollState);
3766
3772
  }
3767
- const updates = yield* (0, import_tg_bot_client3.executeTgBotMethod)("get_updates", {
3768
- timeout: pollSettings.poll_timeout,
3769
- ...updateId ? { offset: updateId } : void 0
3773
+ const updates = yield* tryPromise({
3774
+ try: () => client.execute("get_updates", {
3775
+ timeout: pollSettings.poll_timeout,
3776
+ ...updateId ? { offset: updateId } : void 0
3777
+ }),
3778
+ catch: (error) => error
3770
3779
  }).pipe(andThen((_) => _.sort((_2) => _2.update_id)));
3771
3780
  if (updates.length) {
3772
3781
  console.debug(`got a batch of updates (${updates.length})`);
@@ -3780,9 +3789,14 @@ var _fetchUpdates = (pollState) => gen(function* () {
3780
3789
  var _commitLastBatch = (pollState) => gen(function* () {
3781
3790
  console.log("commit", { pollState });
3782
3791
  if (pollState.lastUpdateId) {
3783
- return yield* (0, import_tg_bot_client3.executeTgBotMethod)("get_updates", {
3784
- offset: pollState.lastUpdateId,
3785
- limit: 0
3792
+ const client = yield* service(BotTgClientTag);
3793
+ const offset = pollState.lastUpdateId;
3794
+ return yield* tryPromise({
3795
+ try: () => client.execute("get_updates", {
3796
+ offset,
3797
+ limit: 0
3798
+ }),
3799
+ catch: (error) => error
3786
3800
  }).pipe(
3787
3801
  andThen(
3788
3802
  andThen(service(BotPollSettingsTag), (pollSettings) => {
@@ -3856,21 +3870,22 @@ var _runBotDaemon = (state) => gen(function* () {
3856
3870
  });
3857
3871
 
3858
3872
  // src/internal/launch.ts
3859
- var import_tg_bot_client4 = require("@effect-ak/tg-bot-client");
3873
+ var import_tg_bot_client2 = require("@effect-ak/tg-bot-client");
3860
3874
  var launchBot = (input) => gen(function* () {
3861
3875
  const service2 = yield* service(BotRunService);
3862
- const contextWithToken = make4(import_tg_bot_client4.TgBotApiToken, input.bot_token);
3876
+ const client = (0, import_tg_bot_client2.makeTgBotClient)({ bot_token: input.bot_token });
3877
+ const contextWithClient = make4(BotTgClientTag, client);
3863
3878
  yield* service2.runBotInBackground.pipe(
3864
3879
  provideService(BotUpdateHandlersTag, input.mode),
3865
3880
  provideService(
3866
3881
  BotPollSettingsTag,
3867
3882
  BotPollSettings.make(input.poll ?? {})
3868
3883
  ),
3869
- provideContext(contextWithToken)
3884
+ provideContext(contextWithClient)
3870
3885
  );
3871
3886
  const reload = (mode) => service2.runBotInBackground.pipe(
3872
3887
  provideService(BotUpdateHandlersTag, mode),
3873
- provideContext(contextWithToken),
3888
+ provideContext(contextWithClient),
3874
3889
  runPromise
3875
3890
  );
3876
3891
  return {
@@ -3893,6 +3908,7 @@ var defineBot = (input) => {
3893
3908
  BotPollSettingsTag,
3894
3909
  BotResponse,
3895
3910
  BotRunService,
3911
+ BotTgClientTag,
3896
3912
  BotUpdateHandlersTag,
3897
3913
  HandleUpdateError,
3898
3914
  defineBot,
package/dist/index.d.ts CHANGED
@@ -1,11 +1,10 @@
1
1
  import * as effect_Types from 'effect/Types';
2
2
  import { Api, Update } from '@effect-ak/tg-bot-api';
3
3
  import * as effect_Cause from 'effect/Cause';
4
- import * as _effect_ak_tg_bot_client from '@effect-ak/tg-bot-client';
5
- import { TgBotClientError } from '@effect-ak/tg-bot-client';
6
4
  import * as Micro from 'effect/Micro';
7
5
  import * as Data from 'effect/Data';
8
6
  import * as Context from 'effect/Context';
7
+ import { TgBotClient } from '@effect-ak/tg-bot-client';
9
8
 
10
9
  type BotResult = {
11
10
  [K in keyof Api]: K extends `send_${infer R}` ? {
@@ -25,6 +24,9 @@ declare class BotResponse extends BotResponse_base<{
25
24
  declare const BotUpdateHandlersTag_base: Context.TagClass<BotUpdateHandlersTag, "BotUpdateHandlers", BotMode>;
26
25
  declare class BotUpdateHandlersTag extends BotUpdateHandlersTag_base {
27
26
  }
27
+ declare const BotTgClientTag_base: Context.TagClass<BotTgClientTag, "BotTgClient", TgBotClient>;
28
+ declare class BotTgClientTag extends BotTgClientTag_base {
29
+ }
28
30
  interface PollSettings {
29
31
  log_level: "info" | "debug";
30
32
  on_error: "stop" | "continue";
@@ -69,7 +71,7 @@ declare class BatchUpdateResult extends Data.Class<{
69
71
  updates: Update[];
70
72
  }> {
71
73
  }
72
- declare const handleUpdates: (updates: Update[]) => Micro.Micro<BatchUpdateResult, never, BotUpdateHandlersTag | _effect_ak_tg_bot_client.TgBotApiToken>;
74
+ declare const handleUpdates: (updates: Update[]) => Micro.Micro<BatchUpdateResult, never, BotUpdateHandlersTag | BotTgClientTag>;
73
75
  declare const handleEntireBatch: (updates: Update[], handlers: HandleBatchUpdateFunction) => Micro.Micro<BatchUpdateResult, never, never>;
74
76
  declare const HandleUpdateError_base: new <A extends Record<string, any> = {}>(args: effect_Types.Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }) => effect_Cause.YieldableError & {
75
77
  readonly _tag: "HandleUpdateError";
@@ -79,35 +81,33 @@ declare class HandleUpdateError extends HandleUpdateError_base<{
79
81
  update: Update;
80
82
  cause?: unknown;
81
83
  }> {
84
+ logInfo(): {
85
+ error?: string;
86
+ updateId: number;
87
+ updateKey: string | undefined;
88
+ name: "HandleUpdateError";
89
+ };
82
90
  }
83
- declare const handleOneByOne: (updates: Update[], handlers: BotUpdatesHandlers, pollSettings: PollSettings) => Micro.Micro<BatchUpdateResult, never, _effect_ak_tg_bot_client.TgBotApiToken>;
84
- declare const handleOneUpdate: (updateObject: Update, handlers: BotUpdatesHandlers) => Micro.Micro<HandleUpdateError | undefined, HandleUpdateError | _effect_ak_tg_bot_client.TgBotClientError, _effect_ak_tg_bot_client.TgBotApiToken>;
85
-
86
- declare const FetchUpdatesError_base: new <A extends Record<string, any> = {}>(args: effect_Types.Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }) => effect_Cause.YieldableError & {
87
- readonly _tag: "FetchUpdatesError";
88
- } & Readonly<A>;
89
- declare class FetchUpdatesError extends FetchUpdatesError_base<{
90
- name: "TooManyEmptyResponses" | "NoUpdatesToCommit";
91
- }> {
92
- }
91
+ declare const handleOneByOne: (updates: Update[], handlers: BotUpdatesHandlers, pollSettings: PollSettings) => Micro.Micro<BatchUpdateResult, never, BotTgClientTag>;
92
+ declare const handleOneUpdate: (updateObject: Update, handlers: BotUpdatesHandlers) => Micro.Micro<HandleUpdateError | undefined, unknown, BotTgClientTag>;
93
93
 
94
94
  type BotInstance = Micro.Micro.Success<ReturnType<typeof launchBot>>;
95
95
  declare const launchBot: (input: RunBotInput) => Micro.Micro<{
96
96
  readonly reload: (mode: BotMode) => Promise<void>;
97
- readonly fiber: () => Micro.MicroFiber<BatchUpdateResult, _effect_ak_tg_bot_client.TgBotClientError | FetchUpdatesError> | undefined;
97
+ readonly fiber: () => Micro.MicroFiber<BatchUpdateResult, unknown> | undefined;
98
98
  }, never, never>;
99
99
 
100
100
  declare const BotRunService_base: Context.ReferenceClass<BotRunService, "BotRunService", {
101
- readonly runBotInBackground: Micro.Micro<void, never, BotUpdateHandlersTag | _effect_ak_tg_bot_client.TgBotApiToken>;
102
- readonly getFiber: () => Micro.MicroFiber<BatchUpdateResult, TgBotClientError | FetchUpdatesError> | undefined;
101
+ readonly runBotInBackground: Micro.Micro<void, never, BotUpdateHandlersTag | BotTgClientTag>;
102
+ readonly getFiber: () => Micro.MicroFiber<BatchUpdateResult, unknown> | undefined;
103
103
  }>;
104
104
  declare class BotRunService extends BotRunService_base {
105
105
  }
106
106
 
107
107
  declare const runTgChatBot: (input: RunBotInput) => Promise<{
108
108
  readonly reload: (mode: BotMode) => Promise<void>;
109
- readonly fiber: () => Micro.MicroFiber<BatchUpdateResult, _effect_ak_tg_bot_client.TgBotClientError | FetchUpdatesError> | undefined;
109
+ readonly fiber: () => Micro.MicroFiber<BatchUpdateResult, unknown> | undefined;
110
110
  }>;
111
111
  declare const defineBot: (input: BotUpdatesHandlers) => BotUpdatesHandlers;
112
112
 
113
- export { type AvailableUpdateTypes, BatchUpdateResult, type BotBatchMode, type BotInstance, type BotMode, BotPollSettings, BotPollSettingsTag, BotResponse, BotRunService, type BotSingleMode, BotUpdateHandlersTag, type BotUpdatesHandlers, type ExtractedUpdate, type HandleBatchUpdateFunction, HandleUpdateError, type HandleUpdateFunction, type PollSettings, type RunBotInput, defineBot, extractUpdate, handleEntireBatch, handleOneByOne, handleOneUpdate, handleUpdates, launchBot, runTgChatBot };
113
+ export { type AvailableUpdateTypes, BatchUpdateResult, type BotBatchMode, type BotInstance, type BotMode, BotPollSettings, BotPollSettingsTag, BotResponse, BotRunService, type BotSingleMode, BotTgClientTag, BotUpdateHandlersTag, type BotUpdatesHandlers, type ExtractedUpdate, type HandleBatchUpdateFunction, HandleUpdateError, type HandleUpdateFunction, type PollSettings, type RunBotInput, defineBot, extractUpdate, handleEntireBatch, handleOneByOne, handleOneUpdate, handleUpdates, launchBot, runTgChatBot };
package/dist/index.js CHANGED
@@ -3450,12 +3450,13 @@ var runPromise = (effect, options) => runPromiseExit(effect, options).then((exit
3450
3450
  });
3451
3451
 
3452
3452
  // src/internal/handle-update.ts
3453
- import { executeTgBotMethod } from "@effect-ak/tg-bot-client";
3454
3453
  import { MESSAGE_EFFECTS } from "@effect-ak/tg-bot-client";
3455
3454
 
3456
3455
  // src/internal/poll-settings.ts
3457
3456
  var BotUpdateHandlersTag = class extends Tag2("BotUpdateHandlers")() {
3458
3457
  };
3458
+ var BotTgClientTag = class extends Tag2("BotTgClient")() {
3459
+ };
3459
3460
  var BotPollSettings = class _BotPollSettings extends Class2 {
3460
3461
  static make(input) {
3461
3462
  let batch_size = input.batch_size ?? 10;
@@ -3563,17 +3564,24 @@ var handleEntireBatch = (updates, handlers) => try_({
3563
3564
  })
3564
3565
  );
3565
3566
  var HandleUpdateError = class extends TaggedError("HandleUpdateError") {
3567
+ logInfo() {
3568
+ return {
3569
+ updateId: this.update.update_id,
3570
+ updateKey: Object.keys(this.update).at(1),
3571
+ name: this._tag,
3572
+ ...this.cause instanceof Error && { error: this.cause.message }
3573
+ };
3574
+ }
3566
3575
  };
3567
3576
  var handleOneByOne = (updates, handlers, pollSettings) => forEach3(
3568
3577
  updates,
3569
3578
  (update) => handleOneUpdate(update, handlers).pipe(
3570
3579
  catchAll((error) => {
3571
- console.log("update handle error", {
3572
- updateId: update.update_id,
3573
- updateKey: Object.keys(update).at(1),
3574
- name: error._tag,
3575
- ...error.cause instanceof Error ? { error: error.cause.message } : void 0
3576
- });
3580
+ if (error instanceof HandleUpdateError) {
3581
+ console.warn("update handle error", error.logInfo());
3582
+ } else {
3583
+ console.warn("unknown error", error);
3584
+ }
3577
3585
  return succeed(error);
3578
3586
  })
3579
3587
  ),
@@ -3641,12 +3649,7 @@ var handleOneUpdate = (updateObject, handlers) => gen(function* () {
3641
3649
  }),
3642
3650
  catchAll((error) => {
3643
3651
  handleUpdateError = error;
3644
- console.log("error", {
3645
- updateId: updateObject.update_id,
3646
- updateKey: Object.keys(updateObject).at(1),
3647
- name: error._tag,
3648
- ...error.cause instanceof Error ? { error: error.cause.message } : void 0
3649
- });
3652
+ console.warn("error", error.logInfo());
3650
3653
  return succeed(
3651
3654
  BotResponse.make({
3652
3655
  type: "message",
@@ -3669,13 +3672,15 @@ var handleOneUpdate = (updateObject, handlers) => gen(function* () {
3669
3672
  return;
3670
3673
  }
3671
3674
  if ("chat" in update && handleResult.response) {
3672
- const response = yield* executeTgBotMethod(
3673
- `send_${handleResult.response.type}`,
3674
- {
3675
- ...handleResult.response,
3675
+ const client = yield* service(BotTgClientTag);
3676
+ const responsePayload = handleResult.response;
3677
+ const response = yield* tryPromise({
3678
+ try: () => client.execute(`send_${responsePayload.type}`, {
3679
+ ...responsePayload,
3676
3680
  chat_id: update.chat.id
3677
- }
3678
- );
3681
+ }),
3682
+ catch: (error) => error
3683
+ });
3679
3684
  if (pollSettings.log_level == "debug" && "text") {
3680
3685
  console.debug("bot response", response);
3681
3686
  }
@@ -3684,7 +3689,6 @@ var handleOneUpdate = (updateObject, handlers) => gen(function* () {
3684
3689
  });
3685
3690
 
3686
3691
  // src/service/fetch-updates.ts
3687
- import { executeTgBotMethod as executeTgBotMethod2 } from "@effect-ak/tg-bot-client";
3688
3692
  var BotFetchUpdatesService = class extends Reference2()(
3689
3693
  "BotFetchUpdatesService",
3690
3694
  {
@@ -3715,6 +3719,7 @@ var FetchUpdatesError = class extends TaggedError("FetchUpdatesError") {
3715
3719
  };
3716
3720
  var _fetchUpdates = (pollState) => gen(function* () {
3717
3721
  const pollSettings = yield* service(BotPollSettingsTag);
3722
+ const client = yield* service(BotTgClientTag);
3718
3723
  if (pollSettings.max_empty_responses && pollState.emptyResponses == pollSettings.max_empty_responses) {
3719
3724
  return yield* fail3(
3720
3725
  new FetchUpdatesError({ name: "TooManyEmptyResponses" })
@@ -3724,9 +3729,12 @@ var _fetchUpdates = (pollState) => gen(function* () {
3724
3729
  if (pollSettings.log_level == "debug") {
3725
3730
  console.debug("getting updates", pollState);
3726
3731
  }
3727
- const updates = yield* executeTgBotMethod2("get_updates", {
3728
- timeout: pollSettings.poll_timeout,
3729
- ...updateId ? { offset: updateId } : void 0
3732
+ const updates = yield* tryPromise({
3733
+ try: () => client.execute("get_updates", {
3734
+ timeout: pollSettings.poll_timeout,
3735
+ ...updateId ? { offset: updateId } : void 0
3736
+ }),
3737
+ catch: (error) => error
3730
3738
  }).pipe(andThen((_) => _.sort((_2) => _2.update_id)));
3731
3739
  if (updates.length) {
3732
3740
  console.debug(`got a batch of updates (${updates.length})`);
@@ -3740,9 +3748,14 @@ var _fetchUpdates = (pollState) => gen(function* () {
3740
3748
  var _commitLastBatch = (pollState) => gen(function* () {
3741
3749
  console.log("commit", { pollState });
3742
3750
  if (pollState.lastUpdateId) {
3743
- return yield* executeTgBotMethod2("get_updates", {
3744
- offset: pollState.lastUpdateId,
3745
- limit: 0
3751
+ const client = yield* service(BotTgClientTag);
3752
+ const offset = pollState.lastUpdateId;
3753
+ return yield* tryPromise({
3754
+ try: () => client.execute("get_updates", {
3755
+ offset,
3756
+ limit: 0
3757
+ }),
3758
+ catch: (error) => error
3746
3759
  }).pipe(
3747
3760
  andThen(
3748
3761
  andThen(service(BotPollSettingsTag), (pollSettings) => {
@@ -3816,21 +3829,22 @@ var _runBotDaemon = (state) => gen(function* () {
3816
3829
  });
3817
3830
 
3818
3831
  // src/internal/launch.ts
3819
- import { TgBotApiToken } from "@effect-ak/tg-bot-client";
3832
+ import { makeTgBotClient } from "@effect-ak/tg-bot-client";
3820
3833
  var launchBot = (input) => gen(function* () {
3821
3834
  const service2 = yield* service(BotRunService);
3822
- const contextWithToken = make4(TgBotApiToken, input.bot_token);
3835
+ const client = makeTgBotClient({ bot_token: input.bot_token });
3836
+ const contextWithClient = make4(BotTgClientTag, client);
3823
3837
  yield* service2.runBotInBackground.pipe(
3824
3838
  provideService(BotUpdateHandlersTag, input.mode),
3825
3839
  provideService(
3826
3840
  BotPollSettingsTag,
3827
3841
  BotPollSettings.make(input.poll ?? {})
3828
3842
  ),
3829
- provideContext(contextWithToken)
3843
+ provideContext(contextWithClient)
3830
3844
  );
3831
3845
  const reload = (mode) => service2.runBotInBackground.pipe(
3832
3846
  provideService(BotUpdateHandlersTag, mode),
3833
- provideContext(contextWithToken),
3847
+ provideContext(contextWithClient),
3834
3848
  runPromise
3835
3849
  );
3836
3850
  return {
@@ -3852,6 +3866,7 @@ export {
3852
3866
  BotPollSettingsTag,
3853
3867
  BotResponse,
3854
3868
  BotRunService,
3869
+ BotTgClientTag,
3855
3870
  BotUpdateHandlersTag,
3856
3871
  HandleUpdateError,
3857
3872
  defineBot,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@effect-ak/tg-bot",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "type": "module",
5
5
  "description": "Telegram Bot runner",
6
6
  "license": "MIT",
@@ -34,9 +34,12 @@
34
34
  "dist/*.d.ts"
35
35
  ],
36
36
  "dependencies": {
37
- "@effect-ak/tg-bot-client": "1.0.0",
37
+ "@effect-ak/tg-bot-client": "^1.1.0",
38
38
  "@effect-ak/tg-bot-api": "0.9.2"
39
39
  },
40
+ "peerDependencies": {
41
+ "effect": "^3.12.7"
42
+ },
40
43
  "scripts": {
41
44
  "build": "tsup"
42
45
  }
package/readme.md ADDED
@@ -0,0 +1,500 @@
1
+ # @effect-ak/tg-bot
2
+
3
+ [![NPM Version](https://img.shields.io/npm/v/%40effect-ak%2Ftg-bot)](https://www.npmjs.com/package/@effect-ak/tg-bot)
4
+ ![NPM Downloads](https://img.shields.io/npm/dw/%40effect-ak%2Ftg-bot?link=)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
+
7
+ Effect-based Telegram bot runner that handles long polling, update processing, and error management automatically.
8
+
9
+ ## Table of Contents
10
+
11
+ - [Features](#features)
12
+ - [Installation](#installation)
13
+ - [Quick Start](#quick-start)
14
+ - [Core Concepts](#core-concepts)
15
+ - [Single Mode](#single-mode)
16
+ - [Batch Mode](#batch-mode)
17
+ - [Bot Response](#bot-response)
18
+ - [Usage Examples](#usage-examples)
19
+ - [Echo Bot](#echo-bot)
20
+ - [Command Handler](#command-handler)
21
+ - [Batch Processing](#batch-processing)
22
+ - [Using Effect](#using-effectjs)
23
+ - [Hot Reload](#hot-reload)
24
+ - [Configuration](#configuration)
25
+ - [API Reference](#api-reference)
26
+ - [How It Works](#how-it-works)
27
+ - [Error Handling](#error-handling)
28
+ - [Playground](#playground)
29
+ - [Related Packages](#related-packages)
30
+ - [License](#license)
31
+
32
+ ## Features
33
+
34
+ - **Effect-based**: Built on top of [Effect](https://effect.website/) for powerful functional programming patterns
35
+ - **Two Processing Modes**: Handle updates one-by-one or in batches
36
+ - **Automatic Long Polling**: Manages connection to Telegram servers
37
+ - **Type-Safe Handlers**: Full TypeScript support for all update types
38
+ - **Error Recovery**: Configurable error handling strategies
39
+ - **Concurrent Processing**: Process multiple updates in parallel (up to 10 concurrent handlers)
40
+ - **Hot Reload**: Reload bot handlers without restarting
41
+ - **Built-in Logging**: Configurable logging levels
42
+ - **No Public URL Required**: Uses pull model - run bots anywhere, even in a browser
43
+
44
+ ## Installation
45
+
46
+ ```bash
47
+ npm install @effect-ak/tg-bot effect
48
+ ```
49
+
50
+ ```bash
51
+ pnpm add @effect-ak/tg-bot effect
52
+ ```
53
+
54
+ ```bash
55
+ yarn add @effect-ak/tg-bot effect
56
+ ```
57
+
58
+ **Note:** `effect` is a peer dependency and must be installed separately.
59
+
60
+ ## Quick Start
61
+
62
+ ```typescript
63
+ import { runTgChatBot, BotResponse } from "@effect-ak/tg-bot"
64
+
65
+ runTgChatBot({
66
+ bot_token: "YOUR_BOT_TOKEN",
67
+ mode: {
68
+ type: "single",
69
+ on_message: (message) => {
70
+ if (!message.text) return BotResponse.ignore
71
+
72
+ return BotResponse.make({
73
+ type: "message",
74
+ text: `You said: ${message.text}`
75
+ })
76
+ }
77
+ }
78
+ })
79
+ ```
80
+
81
+ ## Core Concepts
82
+
83
+ ### Single Mode
84
+
85
+ In single mode, the bot processes each update individually with a dedicated handler for each update type.
86
+
87
+ **Available Handlers:**
88
+ - `on_message` - New incoming message
89
+ - `on_edited_message` - Message was edited
90
+ - `on_channel_post` - New channel post
91
+ - `on_edited_channel_post` - Channel post was edited
92
+ - `on_inline_query` - Inline query
93
+ - `on_chosen_inline_result` - Chosen inline result
94
+ - `on_callback_query` - Callback query from inline keyboard
95
+ - `on_shipping_query` - Shipping query
96
+ - `on_pre_checkout_query` - Pre-checkout query
97
+ - `on_poll` - Poll state update
98
+ - `on_poll_answer` - User changed their answer in a poll
99
+ - `on_my_chat_member` - Bot's chat member status changed
100
+ - `on_chat_member` - Chat member status changed
101
+ - `on_chat_join_request` - Request to join chat
102
+
103
+ ### Batch Mode
104
+
105
+ In batch mode, the bot receives all updates as an array and processes them together.
106
+
107
+ ```typescript
108
+ runTgChatBot({
109
+ bot_token: "YOUR_BOT_TOKEN",
110
+ mode: {
111
+ type: "batch",
112
+ on_batch: async (updates) => {
113
+ console.log(`Processing ${updates.length} updates`)
114
+ // Process updates...
115
+ return true // Continue polling
116
+ }
117
+ }
118
+ })
119
+ ```
120
+
121
+ ### Bot Response
122
+
123
+ Handlers return a `BotResponse` object that describes what to send back to the user.
124
+
125
+ **Creating Responses:**
126
+
127
+ ```typescript
128
+ import { BotResponse } from "@effect-ak/tg-bot"
129
+
130
+ // Send a message
131
+ BotResponse.make({
132
+ type: "message",
133
+ text: "Hello!"
134
+ })
135
+
136
+ // Send a photo
137
+ BotResponse.make({
138
+ type: "photo",
139
+ photo: {
140
+ file_content: photoBuffer,
141
+ file_name: "image.jpg"
142
+ },
143
+ caption: "Check this out!"
144
+ })
145
+
146
+ // Ignore update (don't send anything)
147
+ BotResponse.ignore
148
+ ```
149
+
150
+ **Supported Response Types:**
151
+ All Telegram `send_*` methods are supported: `message`, `photo`, `document`, `video`, `audio`, `voice`, `sticker`, `dice`, etc.
152
+
153
+ ## Usage Examples
154
+
155
+ ### Echo Bot
156
+
157
+ ```typescript
158
+ import { runTgChatBot, BotResponse, defineBot } from "@effect-ak/tg-bot"
159
+
160
+ const ECHO_BOT = defineBot({
161
+ on_message: (message) => {
162
+ if (!message.text) return BotResponse.ignore
163
+
164
+ return BotResponse.make({
165
+ type: "message",
166
+ text: message.text,
167
+ reply_parameters: {
168
+ message_id: message.message_id
169
+ }
170
+ })
171
+ }
172
+ })
173
+
174
+ runTgChatBot({
175
+ bot_token: "YOUR_BOT_TOKEN",
176
+ mode: {
177
+ type: "single",
178
+ ...ECHO_BOT
179
+ }
180
+ })
181
+ ```
182
+
183
+ ### Command Handler
184
+
185
+ ```typescript
186
+ import { runTgChatBot, BotResponse } from "@effect-ak/tg-bot"
187
+ import { MESSAGE_EFFECTS } from "@effect-ak/tg-bot-client"
188
+
189
+ runTgChatBot({
190
+ bot_token: "YOUR_BOT_TOKEN",
191
+ mode: {
192
+ type: "single",
193
+ on_message: (msg) => {
194
+ const command = msg.entities?.find(e => e.type === "bot_command")
195
+ const commandText = command
196
+ ? msg.text?.slice(command.offset, command.length)
197
+ : undefined
198
+
199
+ switch (commandText) {
200
+ case "/start":
201
+ return BotResponse.make({
202
+ type: "message",
203
+ text: "Welcome! Send me any message.",
204
+ message_effect_id: MESSAGE_EFFECTS["🎉"]
205
+ })
206
+
207
+ case "/help":
208
+ return BotResponse.make({
209
+ type: "message",
210
+ text: "Available commands:\n/start - Start bot\n/help - Show help"
211
+ })
212
+
213
+ default:
214
+ return BotResponse.ignore
215
+ }
216
+ }
217
+ }
218
+ })
219
+ ```
220
+
221
+ ### Batch Processing
222
+
223
+ ```typescript
224
+ import { runTgChatBot } from "@effect-ak/tg-bot"
225
+ import { makeTgBotClient } from "@effect-ak/tg-bot-client"
226
+
227
+ const client = makeTgBotClient({ bot_token: "YOUR_BOT_TOKEN" })
228
+
229
+ runTgChatBot({
230
+ bot_token: "YOUR_BOT_TOKEN",
231
+ poll: {
232
+ batch_size: 100,
233
+ poll_timeout: 60
234
+ },
235
+ mode: {
236
+ type: "batch",
237
+ on_batch: async (updates) => {
238
+ const messages = updates
239
+ .map(u => u.message)
240
+ .filter(m => m != null)
241
+
242
+ await client.execute("send_message", {
243
+ chat_id: "ADMIN_CHAT_ID",
244
+ text: `Processed ${messages.length} messages`
245
+ })
246
+
247
+ return true // Continue polling
248
+ }
249
+ }
250
+ })
251
+ ```
252
+
253
+ ### Using Effect
254
+
255
+ Advanced usage with Effect for composable async operations:
256
+
257
+ ```typescript
258
+ import { Effect, Micro, pipe } from "effect"
259
+ import { BotResponse, launchBot } from "@effect-ak/tg-bot"
260
+
261
+ Effect.gen(function* () {
262
+ const bot = yield* launchBot({
263
+ bot_token: "YOUR_BOT_TOKEN",
264
+ poll: {
265
+ log_level: "debug"
266
+ },
267
+ mode: {
268
+ type: "single",
269
+ on_message: (message) => {
270
+ if (!message.text) return BotResponse.ignore
271
+
272
+ // Use Effect for async operations
273
+ return pipe(
274
+ Effect.sleep("2 seconds"),
275
+ Effect.andThen(() =>
276
+ BotResponse.make({
277
+ type: "message",
278
+ text: "Delayed response!"
279
+ })
280
+ ),
281
+ Effect.runPromise
282
+ )
283
+ }
284
+ }
285
+ })
286
+
287
+ // Access bot fiber for control
288
+ yield* pipe(
289
+ Micro.fiberAwait(bot.fiber()!),
290
+ Effect.andThen(Effect.logInfo("Bot stopped")),
291
+ Effect.forkDaemon
292
+ )
293
+ }).pipe(Effect.runPromise)
294
+ ```
295
+
296
+ ### Hot Reload
297
+
298
+ ```typescript
299
+ import { Effect } from "effect"
300
+ import { launchBot, BotResponse } from "@effect-ak/tg-bot"
301
+
302
+ Effect.gen(function* () {
303
+ const bot = yield* launchBot({
304
+ bot_token: "YOUR_BOT_TOKEN",
305
+ mode: {
306
+ type: "single",
307
+ on_message: (msg) => BotResponse.make({
308
+ type: "message",
309
+ text: "Version 1"
310
+ })
311
+ }
312
+ })
313
+
314
+ // Later, reload with new handlers
315
+ await bot.reload({
316
+ type: "single",
317
+ on_message: (msg) => BotResponse.make({
318
+ type: "message",
319
+ text: "Version 2 - Hot reloaded!"
320
+ })
321
+ })
322
+ }).pipe(Effect.runPromise)
323
+ ```
324
+
325
+ ## Configuration
326
+
327
+ ### Poll Settings
328
+
329
+ Configure how the bot polls for updates:
330
+
331
+ ```typescript
332
+ runTgChatBot({
333
+ bot_token: "YOUR_BOT_TOKEN",
334
+ poll: {
335
+ log_level: "debug", // "info" | "debug"
336
+ on_error: "continue", // "stop" | "continue"
337
+ batch_size: 50, // 10-100
338
+ poll_timeout: 30, // 2-120 seconds
339
+ max_empty_responses: 5 // Stop after N empty responses
340
+ },
341
+ mode: { /* ... */ }
342
+ })
343
+ ```
344
+
345
+ **Options:**
346
+
347
+ - `log_level` (default: `"info"`): Logging verbosity
348
+ - `"info"` - Basic logging (new messages, errors)
349
+ - `"debug"` - Detailed logging (all updates, responses)
350
+
351
+ - `on_error` (default: `"stop"`): Error handling strategy
352
+ - `"stop"` - Stop bot on error
353
+ - `"continue"` - Continue polling after errors
354
+
355
+ - `batch_size` (default: `10`): Number of updates to fetch per poll (10-100)
356
+
357
+ - `poll_timeout` (default: `10`): Long polling timeout in seconds (2-120)
358
+
359
+ - `max_empty_responses` (default: `undefined`): Stop after N consecutive empty responses (useful for testing)
360
+
361
+ ## API Reference
362
+
363
+ ### `runTgChatBot(input)`
364
+
365
+ Starts the bot with long polling.
366
+
367
+ **Parameters:**
368
+ - `bot_token` (string, required): Bot token from @BotFather
369
+ - `mode` (object, required): Bot mode configuration (single or batch)
370
+ - `poll` (object, optional): Polling configuration
371
+
372
+ **Returns:** `Promise<void>`
373
+
374
+ ### `launchBot(input)`
375
+
376
+ Launches bot and returns a bot instance for advanced control.
377
+
378
+ **Returns:** `Micro<BotInstance>`
379
+ - `BotInstance.reload(mode)` - Hot reload handlers
380
+ - `BotInstance.fiber()` - Access underlying Effect fiber
381
+
382
+ ### `defineBot(handlers)`
383
+
384
+ Helper to define bot handlers with type checking and validation.
385
+
386
+ **Parameters:**
387
+ - `handlers` (object): Handler functions for different update types
388
+
389
+ **Returns:** `BotUpdatesHandlers`
390
+
391
+ ### `BotResponse.make(response)`
392
+
393
+ Creates a bot response.
394
+
395
+ **Parameters:**
396
+ - `response` (object): Response configuration with `type` and parameters
397
+
398
+ **Returns:** `BotResponse`
399
+
400
+ ### `BotResponse.ignore`
401
+
402
+ Singleton instance for ignoring updates (no response).
403
+
404
+ ## How It Works
405
+
406
+ ### Pull Model Architecture
407
+
408
+ The Telegram bot API supports both **push** and **pull** notification models. This package uses the **pull** model for several key advantages:
409
+
410
+ - **Run bots anywhere without public URLs:** No need to expose public ports or configure webhooks. You can run bots locally, in a browser, or behind firewalls.
411
+ - **Leverage Telegram's infrastructure:** Telegram stores updates for 24 hours, giving you plenty of time to process them.
412
+ - **Simpler deployment:** No SSL certificates, no webhook configuration, no reverse proxies required.
413
+
414
+ ### Architecture Diagram
415
+
416
+ ```mermaid
417
+ graph TD
418
+ User[User] -->|Sends message| TgBot[Telegram Bot]
419
+ TgBot -->|Stores for 24h| Queue[Updates Queue<br/>api.telegram.org/bot/updates]
420
+
421
+ subgraph Your Code
422
+ Runner[Bot Runner<br/>@effect-ak/tg-bot]
423
+ Handler[Your Handler Function]
424
+ end
425
+
426
+ Runner -->|Long polling| Queue
427
+ Runner -->|Invokes with update| Handler
428
+ Handler -->|Returns BotResponse| Runner
429
+ Runner -->|Sends response| TgBot
430
+ ```
431
+
432
+ **How it works:**
433
+ 1. User sends a message to your bot
434
+ 2. Telegram stores the update in a queue for 24 hours
435
+ 3. Bot runner polls the queue using long polling
436
+ 4. Runner invokes your handler function with the update
437
+ 5. Handler returns a `BotResponse`
438
+ 6. Runner sends the response back to Telegram
439
+ 7. Runner tracks the last processed update ID to avoid duplicates
440
+
441
+ ## Error Handling
442
+
443
+ The bot automatically handles errors at different levels:
444
+
445
+ ### Update Handler Errors
446
+
447
+ If a handler throws an error, the bot:
448
+ 1. Logs the error with update details
449
+ 2. Sends an error message to the user (in single mode)
450
+ 3. Continues processing other updates (if `on_error: "continue"`)
451
+
452
+ ```typescript
453
+ on_message: (msg) => {
454
+ if (msg.text === "/error") {
455
+ throw new Error("Something went wrong!")
456
+ }
457
+ // Bot will catch this and send error message to user
458
+ return BotResponse.make({ type: "message", text: "OK" })
459
+ }
460
+ ```
461
+
462
+ ### Batch Handler Errors
463
+
464
+ In batch mode, returning `false` stops the bot:
465
+
466
+ ```typescript
467
+ on_batch: async (updates) => {
468
+ try {
469
+ // Process updates
470
+ return true // Continue
471
+ } catch (error) {
472
+ console.error(error)
473
+ return false // Stop bot
474
+ }
475
+ }
476
+ ```
477
+
478
+ ### Concurrent Processing
479
+
480
+ In single mode, up to 10 updates are processed concurrently. If some handlers fail, others continue processing.
481
+
482
+ ## Playground
483
+
484
+ Develop and test your bot directly in the browser:
485
+
486
+ **[Chat Bot Playground](https://effect-ak.github.io/telegram-bot-playground/)**
487
+
488
+ No installation required - perfect for quick prototyping and learning!
489
+
490
+ ## Related Packages
491
+
492
+ This package is part of the `tg-bot-client` monorepo:
493
+
494
+ - **[@effect-ak/tg-bot-client](../client)** - Type-safe HTTP client for Telegram Bot API
495
+ - **[@effect-ak/tg-bot-api](../api)** - TypeScript types for Telegram Bot API and Mini Apps
496
+ - **[@effect-ak/tg-bot-codegen](../codegen)** - Code generator that parses official documentation
497
+
498
+ ## License
499
+
500
+ MIT © [Aleksandr Kondaurov](https://github.com/effect-ak)