@effect-ak/tg-bot 1.1.0 → 1.2.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
@@ -28,6 +28,7 @@ __export(index_exports, {
28
28
  BotTgClientTag: () => BotTgClientTag,
29
29
  BotUpdateHandlersTag: () => BotUpdateHandlersTag,
30
30
  HandleUpdateError: () => HandleUpdateError,
31
+ createBotContext: () => createBotContext,
31
32
  defineBot: () => defineBot,
32
33
  extractUpdate: () => extractUpdate,
33
34
  handleEntireBatch: () => handleEntireBatch,
@@ -2656,6 +2657,26 @@ var BotResponse = class _BotResponse extends TaggedClass("BotResponse") {
2656
2657
  static ignore = new _BotResponse({});
2657
2658
  };
2658
2659
 
2660
+ // src/internal/bot-context.ts
2661
+ var extractCommand = (update) => {
2662
+ if (typeof update !== "object" || update === null) return void 0;
2663
+ const u = update;
2664
+ if (!u.entities || !u.text) return void 0;
2665
+ const entity = u.entities.find((e) => e.type === "bot_command");
2666
+ if (!entity) return void 0;
2667
+ return u.text.slice(entity.offset, entity.offset + entity.length);
2668
+ };
2669
+ var createBotContext = (update) => {
2670
+ const command = extractCommand(update);
2671
+ return {
2672
+ command,
2673
+ reply: (text, options) => BotResponse.make({ type: "message", text, ...options }),
2674
+ replyWithDocument: (document, options) => BotResponse.make({ type: "document", document, ...options }),
2675
+ replyWithPhoto: (photo, options) => BotResponse.make({ type: "photo", photo, ...options }),
2676
+ ignore: BotResponse.ignore
2677
+ };
2678
+ };
2679
+
2659
2680
  // ../../node_modules/.pnpm/effect@3.12.0/node_modules/effect/dist/esm/Effectable.js
2660
2681
  var EffectPrototype2 = EffectPrototype;
2661
2682
 
@@ -3547,6 +3568,36 @@ var BotPollSettingsTag = class extends Reference2()(
3547
3568
  };
3548
3569
 
3549
3570
  // src/internal/handle-update.ts
3571
+ var isGuardedHandler = (handler) => typeof handler === "object" && handler !== null && "handle" in handler;
3572
+ var executeSingleGuard = async (guard, update, ctx) => {
3573
+ const input = { update, ctx };
3574
+ if (guard.match) {
3575
+ const matched = await guard.match(input);
3576
+ if (!matched) return null;
3577
+ }
3578
+ return await guard.handle(input);
3579
+ };
3580
+ var executeGuards = async (guards, update, ctx) => {
3581
+ for (const guard of guards) {
3582
+ const result = await executeSingleGuard(guard, update, ctx);
3583
+ if (result !== null) return result;
3584
+ }
3585
+ return BotResponse.ignore;
3586
+ };
3587
+ var executeHandler = (handler, update, ctx) => {
3588
+ if (typeof handler === "function") {
3589
+ return handler(update);
3590
+ }
3591
+ if (Array.isArray(handler)) {
3592
+ return executeGuards(handler, update, ctx);
3593
+ }
3594
+ if (isGuardedHandler(handler)) {
3595
+ return executeSingleGuard(handler, update, ctx).then(
3596
+ (r) => r ?? BotResponse.ignore
3597
+ );
3598
+ }
3599
+ return BotResponse.ignore;
3600
+ };
3550
3601
  var extractUpdate = (input) => {
3551
3602
  for (const [field, value] of Object.entries(input)) {
3552
3603
  if (field == "update_id") {
@@ -3650,8 +3701,8 @@ var handleOneUpdate = (updateObject, handlers) => gen(function* () {
3650
3701
  })
3651
3702
  );
3652
3703
  }
3653
- const updateHandler = handlers[`on_${update.type}`];
3654
- if (!updateHandler) {
3704
+ const handler = handlers[`on_${update.type}`];
3705
+ if (!handler) {
3655
3706
  return yield* fail3(
3656
3707
  new HandleUpdateError({
3657
3708
  name: "HandlerNotDefined",
@@ -3666,9 +3717,10 @@ var handleOneUpdate = (updateObject, handlers) => gen(function* () {
3666
3717
  message: `${update.text.slice(0, 5)}...`
3667
3718
  });
3668
3719
  }
3720
+ const ctx = createBotContext(update);
3669
3721
  let handleUpdateError;
3670
3722
  const handleResult = yield* try_({
3671
- try: () => updateHandler(update),
3723
+ try: () => executeHandler(handler, update, ctx),
3672
3724
  catch: (error) => new HandleUpdateError({
3673
3725
  name: "BotHandlerError",
3674
3726
  update: updateObject,
@@ -3871,20 +3923,28 @@ var _runBotDaemon = (state) => gen(function* () {
3871
3923
 
3872
3924
  // src/internal/launch.ts
3873
3925
  var import_tg_bot_client2 = require("@effect-ak/tg-bot-client");
3926
+ var extractMode = (input) => {
3927
+ if (input.mode === "batch") {
3928
+ return { type: "batch", on_batch: input.on_batch };
3929
+ }
3930
+ const { bot_token, mode, poll, ...handlers } = input;
3931
+ return { type: "single", ...handlers };
3932
+ };
3874
3933
  var launchBot = (input) => gen(function* () {
3875
3934
  const service2 = yield* service(BotRunService);
3876
3935
  const client = (0, import_tg_bot_client2.makeTgBotClient)({ bot_token: input.bot_token });
3877
3936
  const contextWithClient = make4(BotTgClientTag, client);
3937
+ const mode = extractMode(input);
3878
3938
  yield* service2.runBotInBackground.pipe(
3879
- provideService(BotUpdateHandlersTag, input.mode),
3939
+ provideService(BotUpdateHandlersTag, mode),
3880
3940
  provideService(
3881
3941
  BotPollSettingsTag,
3882
3942
  BotPollSettings.make(input.poll ?? {})
3883
3943
  ),
3884
3944
  provideContext(contextWithClient)
3885
3945
  );
3886
- const reload = (mode) => service2.runBotInBackground.pipe(
3887
- provideService(BotUpdateHandlersTag, mode),
3946
+ const reload = (mode2) => service2.runBotInBackground.pipe(
3947
+ provideService(BotUpdateHandlersTag, mode2),
3888
3948
  provideContext(contextWithClient),
3889
3949
  runPromise
3890
3950
  );
@@ -3911,6 +3971,7 @@ var defineBot = (input) => {
3911
3971
  BotTgClientTag,
3912
3972
  BotUpdateHandlersTag,
3913
3973
  HandleUpdateError,
3974
+ createBotContext,
3914
3975
  defineBot,
3915
3976
  extractUpdate,
3916
3977
  handleEntireBatch,
package/dist/index.d.ts CHANGED
@@ -1,10 +1,10 @@
1
1
  import * as effect_Types from 'effect/Types';
2
2
  import { Api, Update } from '@effect-ak/tg-bot-api';
3
- import * as effect_Cause from 'effect/Cause';
4
- import * as Micro from 'effect/Micro';
5
- import * as Data from 'effect/Data';
6
3
  import * as Context from 'effect/Context';
4
+ import * as Data from 'effect/Data';
7
5
  import { TgBotClient } from '@effect-ak/tg-bot-client';
6
+ import * as effect_Cause from 'effect/Cause';
7
+ import * as Micro from 'effect/Micro';
8
8
 
9
9
  type BotResult = {
10
10
  [K in keyof Api]: K extends `send_${infer R}` ? {
@@ -41,9 +41,15 @@ declare const BotPollSettingsTag_base: Context.ReferenceClass<BotPollSettings, "
41
41
  declare class BotPollSettingsTag extends BotPollSettingsTag_base {
42
42
  }
43
43
 
44
- interface RunBotInput {
44
+ type RunBotInput = RunBotInputSingle | RunBotInputBatch;
45
+ interface RunBotInputSingle extends BotUpdatesHandlers {
46
+ bot_token: string;
47
+ mode: "single";
48
+ poll?: Partial<PollSettings>;
49
+ }
50
+ interface RunBotInputBatch extends HandleBatchUpdateFunction {
45
51
  bot_token: string;
46
- mode: BotMode;
52
+ mode: "batch";
47
53
  poll?: Partial<PollSettings>;
48
54
  }
49
55
  type ExtractedUpdate<K extends AvailableUpdateTypes> = {
@@ -51,8 +57,27 @@ type ExtractedUpdate<K extends AvailableUpdateTypes> = {
51
57
  } & Update[K];
52
58
  type AvailableUpdateTypes = Exclude<keyof Update, "update_id">;
53
59
  type HandleUpdateFunction<U> = (update: U) => BotResponse | PromiseLike<BotResponse>;
60
+ interface BotContext {
61
+ readonly command: string | undefined;
62
+ readonly reply: (text: string, options?: Omit<BotResponseParams<"message">, "text" | "type">) => BotResponse;
63
+ readonly replyWithDocument: (document: BotResponseParams<"document">["document"], options?: Omit<BotResponseParams<"document">, "document" | "type">) => BotResponse;
64
+ readonly replyWithPhoto: (photo: BotResponseParams<"photo">["photo"], options?: Omit<BotResponseParams<"photo">, "photo" | "type">) => BotResponse;
65
+ readonly ignore: BotResponse;
66
+ }
67
+ type BotResponseParams<T extends string> = Extract<Parameters<typeof BotResponse.make>[0], {
68
+ type: T;
69
+ }>;
70
+ interface HandlerInput<U> {
71
+ readonly update: U;
72
+ readonly ctx: BotContext;
73
+ }
74
+ interface GuardedHandler<U> {
75
+ readonly match?: (input: HandlerInput<U>) => boolean | PromiseLike<boolean>;
76
+ readonly handle: (input: HandlerInput<U>) => BotResponse | PromiseLike<BotResponse>;
77
+ }
78
+ type UpdateHandler<U> = HandleUpdateFunction<U> | GuardedHandler<U> | GuardedHandler<U>[];
54
79
  type BotUpdatesHandlers = {
55
- readonly [K in AvailableUpdateTypes as `on_${K}`]?: HandleUpdateFunction<NonNullable<Update[K]>>;
80
+ [K in AvailableUpdateTypes as `on_${K}`]?: UpdateHandler<NonNullable<Update[K]>>;
56
81
  };
57
82
  interface HandleBatchUpdateFunction {
58
83
  readonly on_batch: (update: Update[]) => boolean | PromiseLike<boolean>;
@@ -65,6 +90,8 @@ interface BotBatchMode extends HandleBatchUpdateFunction {
65
90
  }
66
91
  type BotMode = BotSingleMode | BotBatchMode;
67
92
 
93
+ declare const createBotContext: (update: unknown) => BotContext;
94
+
68
95
  declare const extractUpdate: <U extends AvailableUpdateTypes>(input: Update) => ExtractedUpdate<U> | undefined;
69
96
  declare class BatchUpdateResult extends Data.Class<{
70
97
  hasErrors: boolean;
@@ -110,4 +137,4 @@ declare const runTgChatBot: (input: RunBotInput) => Promise<{
110
137
  }>;
111
138
  declare const defineBot: (input: BotUpdatesHandlers) => BotUpdatesHandlers;
112
139
 
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 };
140
+ export { type AvailableUpdateTypes, BatchUpdateResult, type BotBatchMode, type BotContext, type BotInstance, type BotMode, BotPollSettings, BotPollSettingsTag, BotResponse, BotRunService, type BotSingleMode, BotTgClientTag, BotUpdateHandlersTag, type BotUpdatesHandlers, type ExtractedUpdate, type GuardedHandler, type HandleBatchUpdateFunction, HandleUpdateError, type HandleUpdateFunction, type HandlerInput, type PollSettings, type RunBotInput, type RunBotInputBatch, type RunBotInputSingle, type UpdateHandler, createBotContext, defineBot, extractUpdate, handleEntireBatch, handleOneByOne, handleOneUpdate, handleUpdates, launchBot, runTgChatBot };
package/dist/index.js CHANGED
@@ -2615,6 +2615,26 @@ var BotResponse = class _BotResponse extends TaggedClass("BotResponse") {
2615
2615
  static ignore = new _BotResponse({});
2616
2616
  };
2617
2617
 
2618
+ // src/internal/bot-context.ts
2619
+ var extractCommand = (update) => {
2620
+ if (typeof update !== "object" || update === null) return void 0;
2621
+ const u = update;
2622
+ if (!u.entities || !u.text) return void 0;
2623
+ const entity = u.entities.find((e) => e.type === "bot_command");
2624
+ if (!entity) return void 0;
2625
+ return u.text.slice(entity.offset, entity.offset + entity.length);
2626
+ };
2627
+ var createBotContext = (update) => {
2628
+ const command = extractCommand(update);
2629
+ return {
2630
+ command,
2631
+ reply: (text, options) => BotResponse.make({ type: "message", text, ...options }),
2632
+ replyWithDocument: (document, options) => BotResponse.make({ type: "document", document, ...options }),
2633
+ replyWithPhoto: (photo, options) => BotResponse.make({ type: "photo", photo, ...options }),
2634
+ ignore: BotResponse.ignore
2635
+ };
2636
+ };
2637
+
2618
2638
  // ../../node_modules/.pnpm/effect@3.12.0/node_modules/effect/dist/esm/Effectable.js
2619
2639
  var EffectPrototype2 = EffectPrototype;
2620
2640
 
@@ -3506,6 +3526,36 @@ var BotPollSettingsTag = class extends Reference2()(
3506
3526
  };
3507
3527
 
3508
3528
  // src/internal/handle-update.ts
3529
+ var isGuardedHandler = (handler) => typeof handler === "object" && handler !== null && "handle" in handler;
3530
+ var executeSingleGuard = async (guard, update, ctx) => {
3531
+ const input = { update, ctx };
3532
+ if (guard.match) {
3533
+ const matched = await guard.match(input);
3534
+ if (!matched) return null;
3535
+ }
3536
+ return await guard.handle(input);
3537
+ };
3538
+ var executeGuards = async (guards, update, ctx) => {
3539
+ for (const guard of guards) {
3540
+ const result = await executeSingleGuard(guard, update, ctx);
3541
+ if (result !== null) return result;
3542
+ }
3543
+ return BotResponse.ignore;
3544
+ };
3545
+ var executeHandler = (handler, update, ctx) => {
3546
+ if (typeof handler === "function") {
3547
+ return handler(update);
3548
+ }
3549
+ if (Array.isArray(handler)) {
3550
+ return executeGuards(handler, update, ctx);
3551
+ }
3552
+ if (isGuardedHandler(handler)) {
3553
+ return executeSingleGuard(handler, update, ctx).then(
3554
+ (r) => r ?? BotResponse.ignore
3555
+ );
3556
+ }
3557
+ return BotResponse.ignore;
3558
+ };
3509
3559
  var extractUpdate = (input) => {
3510
3560
  for (const [field, value] of Object.entries(input)) {
3511
3561
  if (field == "update_id") {
@@ -3609,8 +3659,8 @@ var handleOneUpdate = (updateObject, handlers) => gen(function* () {
3609
3659
  })
3610
3660
  );
3611
3661
  }
3612
- const updateHandler = handlers[`on_${update.type}`];
3613
- if (!updateHandler) {
3662
+ const handler = handlers[`on_${update.type}`];
3663
+ if (!handler) {
3614
3664
  return yield* fail3(
3615
3665
  new HandleUpdateError({
3616
3666
  name: "HandlerNotDefined",
@@ -3625,9 +3675,10 @@ var handleOneUpdate = (updateObject, handlers) => gen(function* () {
3625
3675
  message: `${update.text.slice(0, 5)}...`
3626
3676
  });
3627
3677
  }
3678
+ const ctx = createBotContext(update);
3628
3679
  let handleUpdateError;
3629
3680
  const handleResult = yield* try_({
3630
- try: () => updateHandler(update),
3681
+ try: () => executeHandler(handler, update, ctx),
3631
3682
  catch: (error) => new HandleUpdateError({
3632
3683
  name: "BotHandlerError",
3633
3684
  update: updateObject,
@@ -3830,20 +3881,28 @@ var _runBotDaemon = (state) => gen(function* () {
3830
3881
 
3831
3882
  // src/internal/launch.ts
3832
3883
  import { makeTgBotClient } from "@effect-ak/tg-bot-client";
3884
+ var extractMode = (input) => {
3885
+ if (input.mode === "batch") {
3886
+ return { type: "batch", on_batch: input.on_batch };
3887
+ }
3888
+ const { bot_token, mode, poll, ...handlers } = input;
3889
+ return { type: "single", ...handlers };
3890
+ };
3833
3891
  var launchBot = (input) => gen(function* () {
3834
3892
  const service2 = yield* service(BotRunService);
3835
3893
  const client = makeTgBotClient({ bot_token: input.bot_token });
3836
3894
  const contextWithClient = make4(BotTgClientTag, client);
3895
+ const mode = extractMode(input);
3837
3896
  yield* service2.runBotInBackground.pipe(
3838
- provideService(BotUpdateHandlersTag, input.mode),
3897
+ provideService(BotUpdateHandlersTag, mode),
3839
3898
  provideService(
3840
3899
  BotPollSettingsTag,
3841
3900
  BotPollSettings.make(input.poll ?? {})
3842
3901
  ),
3843
3902
  provideContext(contextWithClient)
3844
3903
  );
3845
- const reload = (mode) => service2.runBotInBackground.pipe(
3846
- provideService(BotUpdateHandlersTag, mode),
3904
+ const reload = (mode2) => service2.runBotInBackground.pipe(
3905
+ provideService(BotUpdateHandlersTag, mode2),
3847
3906
  provideContext(contextWithClient),
3848
3907
  runPromise
3849
3908
  );
@@ -3869,6 +3928,7 @@ export {
3869
3928
  BotTgClientTag,
3870
3929
  BotUpdateHandlersTag,
3871
3930
  HandleUpdateError,
3931
+ createBotContext,
3872
3932
  defineBot,
3873
3933
  extractUpdate,
3874
3934
  handleEntireBatch,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@effect-ak/tg-bot",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "type": "module",
5
5
  "description": "Telegram Bot runner",
6
6
  "license": "MIT",
@@ -41,6 +41,7 @@
41
41
  "effect": "^3.12.7"
42
42
  },
43
43
  "scripts": {
44
- "build": "tsup"
44
+ "build": "tsup",
45
+ "typecheck": "tsc"
45
46
  }
46
47
  }
package/readme.md CHANGED
@@ -60,21 +60,17 @@ yarn add @effect-ak/tg-bot effect
60
60
  ## Quick Start
61
61
 
62
62
  ```typescript
63
- import { runTgChatBot, BotResponse } from "@effect-ak/tg-bot"
63
+ import { runTgChatBot } from "@effect-ak/tg-bot"
64
64
 
65
65
  runTgChatBot({
66
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
- })
67
+ mode: "single",
68
+ on_message: [
69
+ {
70
+ match: ({ update }) => !!update.text,
71
+ handle: ({ update, ctx }) => ctx.reply(`You said: ${update.text}`)
76
72
  }
77
- }
73
+ ]
78
74
  })
79
75
  ```
80
76
 
@@ -84,6 +80,31 @@ runTgChatBot({
84
80
 
85
81
  In single mode, the bot processes each update individually with a dedicated handler for each update type.
86
82
 
83
+ **Handler Format (v2 with guards):**
84
+
85
+ ```typescript
86
+ on_message: [
87
+ {
88
+ match: ({ update, ctx }) => ctx.command === "/start", // optional filter
89
+ handle: ({ update, ctx }) => ctx.reply("Welcome!") // handler
90
+ },
91
+ {
92
+ match: ({ update }) => !!update.text,
93
+ handle: ({ ctx }) => ctx.reply("Got your message!")
94
+ },
95
+ {
96
+ handle: ({ ctx }) => ctx.ignore // fallback (no match = always runs)
97
+ }
98
+ ]
99
+ ```
100
+
101
+ **Context helpers:**
102
+ - `ctx.reply(text, options?)` - Send a message
103
+ - `ctx.replyWithDocument(document, options?)` - Send a document
104
+ - `ctx.replyWithPhoto(photo, options?)` - Send a photo
105
+ - `ctx.command` - Parsed command (e.g., "/start", "/help")
106
+ - `ctx.ignore` - Skip response
107
+
87
108
  **Available Handlers:**
88
109
  - `on_message` - New incoming message
89
110
  - `on_edited_message` - Message was edited
@@ -100,6 +121,15 @@ In single mode, the bot processes each update individually with a dedicated hand
100
121
  - `on_chat_member` - Chat member status changed
101
122
  - `on_chat_join_request` - Request to join chat
102
123
 
124
+ **Legacy format (v1 - still supported):**
125
+
126
+ ```typescript
127
+ on_message: (message) => {
128
+ if (!message.text) return BotResponse.ignore
129
+ return BotResponse.make({ type: "message", text: "Hello!" })
130
+ }
131
+ ```
132
+
103
133
  ### Batch Mode
104
134
 
105
135
  In batch mode, the bot receives all updates as an array and processes them together.
@@ -107,13 +137,11 @@ In batch mode, the bot receives all updates as an array and processes them toget
107
137
  ```typescript
108
138
  runTgChatBot({
109
139
  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
- }
140
+ mode: "batch",
141
+ on_batch: async (updates) => {
142
+ console.log(`Processing ${updates.length} updates`)
143
+ // Process updates...
144
+ return true // Continue polling
117
145
  }
118
146
  })
119
147
  ```
@@ -155,66 +183,45 @@ All Telegram `send_*` methods are supported: `message`, `photo`, `document`, `vi
155
183
  ### Echo Bot
156
184
 
157
185
  ```typescript
158
- import { runTgChatBot, BotResponse, defineBot } from "@effect-ak/tg-bot"
186
+ import { runTgChatBot, defineBot } from "@effect-ak/tg-bot"
159
187
 
160
188
  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
- }
189
+ on_message: [
190
+ {
191
+ match: ({ update }) => !!update.text,
192
+ handle: ({ update, ctx }) => ctx.reply(update.text!)
193
+ }
194
+ ]
172
195
  })
173
196
 
174
197
  runTgChatBot({
175
198
  bot_token: "YOUR_BOT_TOKEN",
176
- mode: {
177
- type: "single",
178
- ...ECHO_BOT
179
- }
199
+ mode: "single",
200
+ ...ECHO_BOT
180
201
  })
181
202
  ```
182
203
 
183
204
  ### Command Handler
184
205
 
185
206
  ```typescript
186
- import { runTgChatBot, BotResponse } from "@effect-ak/tg-bot"
207
+ import { runTgChatBot } from "@effect-ak/tg-bot"
187
208
  import { MESSAGE_EFFECTS } from "@effect-ak/tg-bot-client"
188
209
 
189
210
  runTgChatBot({
190
211
  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
- }
212
+ mode: "single",
213
+ on_message: [
214
+ {
215
+ match: ({ ctx }) => ctx.command === "/start",
216
+ handle: ({ ctx }) => ctx.reply("Welcome! Send me any message.", {
217
+ message_effect_id: MESSAGE_EFFECTS["🎉"]
218
+ })
219
+ },
220
+ {
221
+ match: ({ ctx }) => ctx.command === "/help",
222
+ handle: ({ ctx }) => ctx.reply("Available commands:\n/start - Start bot\n/help - Show help")
216
223
  }
217
- }
224
+ ]
218
225
  })
219
226
  ```
220
227
 
@@ -228,24 +235,22 @@ const client = makeTgBotClient({ bot_token: "YOUR_BOT_TOKEN" })
228
235
 
229
236
  runTgChatBot({
230
237
  bot_token: "YOUR_BOT_TOKEN",
238
+ mode: "batch",
231
239
  poll: {
232
240
  batch_size: 100,
233
241
  poll_timeout: 60
234
242
  },
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
- })
243
+ on_batch: async (updates) => {
244
+ const messages = updates
245
+ .map(u => u.message)
246
+ .filter(m => m != null)
247
+
248
+ await client.execute("send_message", {
249
+ chat_id: "ADMIN_CHAT_ID",
250
+ text: `Processed ${messages.length} messages`
251
+ })
246
252
 
247
- return true // Continue polling
248
- }
253
+ return true // Continue polling
249
254
  }
250
255
  })
251
256
  ```
@@ -256,32 +261,24 @@ Advanced usage with Effect for composable async operations:
256
261
 
257
262
  ```typescript
258
263
  import { Effect, Micro, pipe } from "effect"
259
- import { BotResponse, launchBot } from "@effect-ak/tg-bot"
264
+ import { launchBot } from "@effect-ak/tg-bot"
260
265
 
261
266
  Effect.gen(function* () {
262
267
  const bot = yield* launchBot({
263
268
  bot_token: "YOUR_BOT_TOKEN",
269
+ mode: "single",
264
270
  poll: {
265
271
  log_level: "debug"
266
272
  },
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
- )
273
+ on_message: [
274
+ {
275
+ match: ({ update }) => !!update.text,
276
+ handle: async ({ ctx }) => {
277
+ await Effect.sleep("2 seconds").pipe(Effect.runPromise)
278
+ return ctx.reply("Delayed response!")
279
+ }
283
280
  }
284
- }
281
+ ]
285
282
  })
286
283
 
287
284
  // Access bot fiber for control
@@ -296,30 +293,31 @@ Effect.gen(function* () {
296
293
  ### Hot Reload
297
294
 
298
295
  ```typescript
299
- import { Effect } from "effect"
300
- import { launchBot, BotResponse } from "@effect-ak/tg-bot"
296
+ import { runTgChatBot } from "@effect-ak/tg-bot"
301
297
 
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
- })
298
+ const bot = await runTgChatBot({
299
+ bot_token: "YOUR_BOT_TOKEN",
300
+ mode: "single",
301
+ on_message: [
302
+ {
303
+ match: ({ update }) => !!update.text,
304
+ handle: ({ ctx }) => ctx.reply("Version 1")
311
305
  }
312
- })
306
+ ]
307
+ })
313
308
 
314
- // Later, reload with new handlers
315
- await bot.reload({
309
+ // Later, reload with new handlers
310
+ setTimeout(() => {
311
+ bot.reload({
316
312
  type: "single",
317
- on_message: (msg) => BotResponse.make({
318
- type: "message",
319
- text: "Version 2 - Hot reloaded!"
320
- })
313
+ on_message: [
314
+ {
315
+ match: ({ update }) => !!update.text,
316
+ handle: ({ ctx }) => ctx.reply("Version 2 - Hot reloaded!")
317
+ }
318
+ ]
321
319
  })
322
- }).pipe(Effect.runPromise)
320
+ }, 5000)
323
321
  ```
324
322
 
325
323
  ## Configuration
@@ -331,6 +329,7 @@ Configure how the bot polls for updates:
331
329
  ```typescript
332
330
  runTgChatBot({
333
331
  bot_token: "YOUR_BOT_TOKEN",
332
+ mode: "single", // or "batch"
334
333
  poll: {
335
334
  log_level: "debug", // "info" | "debug"
336
335
  on_error: "continue", // "stop" | "continue"
@@ -338,7 +337,7 @@ runTgChatBot({
338
337
  poll_timeout: 30, // 2-120 seconds
339
338
  max_empty_responses: 5 // Stop after N empty responses
340
339
  },
341
- mode: { /* ... */ }
340
+ on_message: [/* ... */] // handlers at top level
342
341
  })
343
342
  ```
344
343
 
@@ -366,10 +365,12 @@ Starts the bot with long polling.
366
365
 
367
366
  **Parameters:**
368
367
  - `bot_token` (string, required): Bot token from @BotFather
369
- - `mode` (object, required): Bot mode configuration (single or batch)
368
+ - `mode` (`"single"` | `"batch"`, required): Processing mode
370
369
  - `poll` (object, optional): Polling configuration
370
+ - `on_message`, `on_callback_query`, etc. (optional): Update handlers (for single mode)
371
+ - `on_batch` (required for batch mode): Batch handler function
371
372
 
372
- **Returns:** `Promise<void>`
373
+ **Returns:** `Promise<BotInstance>`
373
374
 
374
375
  ### `launchBot(input)`
375
376
 
@@ -450,13 +451,19 @@ If a handler throws an error, the bot:
450
451
  3. Continues processing other updates (if `on_error: "continue"`)
451
452
 
452
453
  ```typescript
453
- on_message: (msg) => {
454
- if (msg.text === "/error") {
455
- throw new Error("Something went wrong!")
454
+ on_message: [
455
+ {
456
+ match: ({ ctx }) => ctx.command === "/error",
457
+ handle: () => {
458
+ throw new Error("Something went wrong!")
459
+ // Bot will catch this and send error message to user
460
+ }
461
+ },
462
+ {
463
+ match: ({ update }) => !!update.text,
464
+ handle: ({ ctx }) => ctx.reply("OK")
456
465
  }
457
- // Bot will catch this and send error message to user
458
- return BotResponse.make({ type: "message", text: "OK" })
459
- }
466
+ ]
460
467
  ```
461
468
 
462
469
  ### Batch Handler Errors