@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 +47 -31
- package/dist/index.d.ts +18 -18
- package/dist/index.js +45 -30
- package/package.json +5 -2
- package/readme.md +500 -0
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
|
-
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
|
|
3615
|
-
|
|
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.
|
|
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:
|
|
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
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
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* (
|
|
3768
|
-
|
|
3769
|
-
|
|
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
|
-
|
|
3784
|
-
|
|
3785
|
-
|
|
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
|
|
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
|
|
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(
|
|
3884
|
+
provideContext(contextWithClient)
|
|
3870
3885
|
);
|
|
3871
3886
|
const reload = (mode) => service2.runBotInBackground.pipe(
|
|
3872
3887
|
provideService(BotUpdateHandlersTag, mode),
|
|
3873
|
-
provideContext(
|
|
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 |
|
|
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,
|
|
84
|
-
declare const handleOneUpdate: (updateObject: Update, handlers: BotUpdatesHandlers) => Micro.Micro<HandleUpdateError | undefined,
|
|
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,
|
|
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 |
|
|
102
|
-
readonly getFiber: () => Micro.MicroFiber<BatchUpdateResult,
|
|
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,
|
|
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
|
-
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
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.
|
|
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
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
|
|
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*
|
|
3728
|
-
|
|
3729
|
-
|
|
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
|
-
|
|
3744
|
-
|
|
3745
|
-
|
|
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 {
|
|
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
|
|
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(
|
|
3843
|
+
provideContext(contextWithClient)
|
|
3830
3844
|
);
|
|
3831
3845
|
const reload = (mode) => service2.runBotInBackground.pipe(
|
|
3832
3846
|
provideService(BotUpdateHandlersTag, mode),
|
|
3833
|
-
provideContext(
|
|
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.
|
|
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.
|
|
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
|
+
[](https://www.npmjs.com/package/@effect-ak/tg-bot)
|
|
4
|
+

|
|
5
|
+
[](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)
|