chz-telegram-bot 0.7.13 → 0.7.15
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/dtos/propertyProviderSets.d.ts +0 -1
- package/dist/dtos/propertyProviderSets.d.ts.map +1 -1
- package/dist/entities/actions/commandAction.d.ts +1 -1
- package/dist/entities/actions/commandAction.d.ts.map +1 -1
- package/dist/entities/actions/commandAction.js +2 -2
- package/dist/helpers/builders/commandActionBuilder.d.ts.map +1 -1
- package/dist/helpers/builders/commandActionBuilder.js +2 -3
- package/package.json +4 -1
- package/eslint.config.ts +0 -62
- package/src/builtin/helpAction.ts +0 -17
- package/src/dtos/chatHistoryMessage.ts +0 -22
- package/src/dtos/chatInfo.ts +0 -12
- package/src/dtos/commandTriggerCheckResult.ts +0 -40
- package/src/dtos/cooldownInfo.ts +0 -10
- package/src/dtos/incomingMessage.ts +0 -71
- package/src/dtos/incomingQuery.ts +0 -14
- package/src/dtos/messageInfo.ts +0 -15
- package/src/dtos/propertyProviderSets.ts +0 -21
- package/src/dtos/replyInfo.ts +0 -9
- package/src/dtos/responses/delay.ts +0 -28
- package/src/dtos/responses/imageMessage.ts +0 -41
- package/src/dtos/responses/inlineQueryResponse.ts +0 -26
- package/src/dtos/responses/reaction.ts +0 -30
- package/src/dtos/responses/textMessage.ts +0 -44
- package/src/dtos/responses/unpin.ts +0 -27
- package/src/dtos/responses/videoMessage.ts +0 -41
- package/src/dtos/userInfo.ts +0 -8
- package/src/entities/actions/commandAction.ts +0 -275
- package/src/entities/actions/inlineQueryAction.ts +0 -83
- package/src/entities/actions/replyCaptureAction.ts +0 -110
- package/src/entities/actions/scheduledAction.ts +0 -182
- package/src/entities/botInstance.ts +0 -92
- package/src/entities/cachedStateFactory.ts +0 -14
- package/src/entities/context/baseContext.ts +0 -111
- package/src/entities/context/chatContext.ts +0 -135
- package/src/entities/context/inlineQueryContext.ts +0 -63
- package/src/entities/context/messageContext.ts +0 -250
- package/src/entities/context/replyContext.ts +0 -260
- package/src/entities/states/actionStateBase.ts +0 -6
- package/src/entities/taskRecord.ts +0 -11
- package/src/helpers/builders/commandActionBuilder.ts +0 -214
- package/src/helpers/builders/inlineQueryActionBuilder.ts +0 -71
- package/src/helpers/builders/scheduledActionBuilder.ts +0 -143
- package/src/helpers/mapUtils.ts +0 -28
- package/src/helpers/noop.ts +0 -20
- package/src/helpers/objectFromEntries.ts +0 -7
- package/src/helpers/timeConvertions.ts +0 -13
- package/src/helpers/toArray.ts +0 -3
- package/src/helpers/traceFactory.ts +0 -11
- package/src/index.ts +0 -33
- package/src/main.ts +0 -76
- package/src/services/actionProcessingService.ts +0 -125
- package/src/services/actionProcessors/baseProcessor.ts +0 -67
- package/src/services/actionProcessors/commandActionProcessor.ts +0 -231
- package/src/services/actionProcessors/inlineQueryActionProcessor.ts +0 -165
- package/src/services/actionProcessors/scheduledActionProcessor.ts +0 -136
- package/src/services/jsonFileStorage.ts +0 -181
- package/src/services/nodeTimeoutScheduler.ts +0 -79
- package/src/services/responseProcessingQueue.ts +0 -57
- package/src/services/telegramApi.ts +0 -278
- package/src/types/action.ts +0 -15
- package/src/types/actionState.ts +0 -4
- package/src/types/cachedValueAccessor.ts +0 -1
- package/src/types/capture.ts +0 -33
- package/src/types/commandCondition.ts +0 -9
- package/src/types/commandTrigger.ts +0 -1
- package/src/types/events.ts +0 -286
- package/src/types/externalAliases.ts +0 -18
- package/src/types/handlers.ts +0 -26
- package/src/types/inputFile.ts +0 -4
- package/src/types/messageSendingOptions.ts +0 -10
- package/src/types/messageTypes.ts +0 -21
- package/src/types/propertyProvider.ts +0 -14
- package/src/types/response.ts +0 -51
- package/src/types/scheduler.ts +0 -20
- package/src/types/storage.ts +0 -23
- package/src/types/timeValues.ts +0 -33
- package/src/types/trace.ts +0 -5
- package/tests/dtos/commandTriggerCheckResult.test.ts +0 -301
- package/tests/entities/actions/inlineQueryAction.test.ts +0 -359
- package/tests/entities/actions/replyCaptureAction.test.ts +0 -501
- package/tests/entities/cachedStateFactory.test.ts +0 -98
- package/tests/entities/context/chatContext.test.ts +0 -606
- package/tests/entities/context/messageContext.test.ts +0 -370
- package/tests/entities/states/actionStateBase.test.ts +0 -138
- package/tests/entities/taskRecord.test.ts +0 -195
- package/tests/helpers/mapUtils.test.ts +0 -163
- package/tests/helpers/timeConvertions.test.ts +0 -129
- package/tests/services/actionProcessors/baseActionProcessor.test.ts +0 -359
- package/tests/services/actionProcessors/commandActionProcessor.test.ts +0 -268
- package/tests/services/actionProcessors/inlineQueryActionProcessor.test.ts +0 -616
- package/tests/services/actionProcessors/processorTestHelpers.ts +0 -147
- package/tests/services/actionProcessors/scheduledActionProcessor.test.ts +0 -153
- package/tests/services/jsonFileStorage.test.ts +0 -927
- package/tests/services/nodeTimeoutScheduler.test.ts +0 -421
- package/tests/services/responseProcessingQueue.test.ts +0 -388
- package/tsconfig.build.json +0 -8
- package/tsconfig.json +0 -118
|
@@ -1,181 +0,0 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readFileSync } from 'fs';
|
|
2
|
-
import { writeFile } from 'fs/promises';
|
|
3
|
-
import { Sema as Semaphore } from 'async-sema';
|
|
4
|
-
import { IStorageClient } from '../types/storage';
|
|
5
|
-
import { IActionState } from '../types/actionState';
|
|
6
|
-
import { IActionWithState, ActionKey } from '../types/action';
|
|
7
|
-
import { getOrSetIfNotExists } from '../helpers/mapUtils';
|
|
8
|
-
|
|
9
|
-
function buildPath(storagePath: string, botName: string, actionKey: string) {
|
|
10
|
-
return `${storagePath}/${botName}/${actionKey.replaceAll(':', '/')}.json`;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
class CachedDataSource {
|
|
14
|
-
private readonly cache: Map<string, Record<number, IActionState>>;
|
|
15
|
-
private readonly filePaths = new Map<ActionKey, string>();
|
|
16
|
-
private readonly botName: string;
|
|
17
|
-
private readonly storagePath: string;
|
|
18
|
-
|
|
19
|
-
constructor(
|
|
20
|
-
botName: string,
|
|
21
|
-
actions: IActionWithState<IActionState>[],
|
|
22
|
-
path?: string
|
|
23
|
-
) {
|
|
24
|
-
this.cache = new Map<string, Record<number, IActionState>>();
|
|
25
|
-
this.botName = botName;
|
|
26
|
-
this.storagePath = path ?? 'storage';
|
|
27
|
-
|
|
28
|
-
if (!existsSync(`${this.storagePath}/${this.botName}/`)) {
|
|
29
|
-
mkdirSync(`${this.storagePath}/${this.botName}/`, {
|
|
30
|
-
recursive: true
|
|
31
|
-
});
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
for (const action of actions) {
|
|
35
|
-
this.filePaths.set(
|
|
36
|
-
action.key,
|
|
37
|
-
buildPath(this.storagePath, this.botName, action.key)
|
|
38
|
-
);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
private tryGetFromCache<TActionState extends IActionState>(key: ActionKey) {
|
|
43
|
-
const cachedValue = this.cache.get(key) as
|
|
44
|
-
| Record<number, TActionState>
|
|
45
|
-
| undefined;
|
|
46
|
-
|
|
47
|
-
return cachedValue;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
private loadFromFile<TActionState extends IActionState>(key: ActionKey) {
|
|
51
|
-
const targetPath = getOrSetIfNotExists(
|
|
52
|
-
this.filePaths,
|
|
53
|
-
key,
|
|
54
|
-
buildPath(this.storagePath, this.botName, key)
|
|
55
|
-
);
|
|
56
|
-
|
|
57
|
-
const fileContent = readFileSync(targetPath, {
|
|
58
|
-
encoding: 'utf-8',
|
|
59
|
-
flag: 'a+'
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
if (fileContent) {
|
|
63
|
-
const data = JSON.parse(fileContent) as Record<
|
|
64
|
-
number,
|
|
65
|
-
TActionState
|
|
66
|
-
>;
|
|
67
|
-
|
|
68
|
-
this.cache.set(key, data);
|
|
69
|
-
} else {
|
|
70
|
-
this.cache.set(key, {});
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
return this.cache.get(key) as Record<number, TActionState>;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
public load<TActionState extends IActionState>(
|
|
77
|
-
action: IActionWithState<TActionState>
|
|
78
|
-
) {
|
|
79
|
-
return (
|
|
80
|
-
this.tryGetFromCache<TActionState>(action.key) ??
|
|
81
|
-
this.loadFromFile<TActionState>(action.key)
|
|
82
|
-
);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
public async save<TActionState extends IActionState>(
|
|
86
|
-
data: Record<number, TActionState>,
|
|
87
|
-
action: IActionWithState<TActionState>
|
|
88
|
-
) {
|
|
89
|
-
this.cache.set(action.key, data);
|
|
90
|
-
|
|
91
|
-
const targetPath = getOrSetIfNotExists(
|
|
92
|
-
this.filePaths,
|
|
93
|
-
action.key,
|
|
94
|
-
buildPath(this.storagePath, this.botName, action.key)
|
|
95
|
-
);
|
|
96
|
-
|
|
97
|
-
await writeFile(targetPath, JSON.stringify(data), { flag: 'w+' });
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
export class JsonFileStorage implements IStorageClient {
|
|
102
|
-
private readonly locks = new Map<ActionKey, Semaphore>();
|
|
103
|
-
|
|
104
|
-
private readonly data: CachedDataSource;
|
|
105
|
-
|
|
106
|
-
constructor(
|
|
107
|
-
botName: string,
|
|
108
|
-
actions: IActionWithState<IActionState>[],
|
|
109
|
-
path?: string
|
|
110
|
-
) {
|
|
111
|
-
this.data = new CachedDataSource(botName, actions, path ?? 'storage');
|
|
112
|
-
|
|
113
|
-
for (const action of actions) {
|
|
114
|
-
this.locks.set(action.key, new Semaphore(1));
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
private async lock<TType>(key: ActionKey, action: () => Promise<TType>) {
|
|
119
|
-
const lock = getOrSetIfNotExists(this.locks, key, new Semaphore(1));
|
|
120
|
-
|
|
121
|
-
await lock.acquire();
|
|
122
|
-
|
|
123
|
-
try {
|
|
124
|
-
return await action();
|
|
125
|
-
} finally {
|
|
126
|
-
lock.release();
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
load<TActionState extends IActionState>(
|
|
131
|
-
action: IActionWithState<TActionState>
|
|
132
|
-
) {
|
|
133
|
-
return this.data.load<TActionState>(action);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
getActionState<TActionState extends IActionState>(
|
|
137
|
-
action: IActionWithState<TActionState>,
|
|
138
|
-
chatId: number
|
|
139
|
-
) {
|
|
140
|
-
const value = this.data.load<TActionState>(action);
|
|
141
|
-
|
|
142
|
-
return value[chatId] ?? action.stateConstructor();
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
async saveActionExecutionResult<TActionState extends IActionState>(
|
|
146
|
-
action: IActionWithState<TActionState>,
|
|
147
|
-
chatId: number,
|
|
148
|
-
state: TActionState
|
|
149
|
-
) {
|
|
150
|
-
await this.lock(action.key, async () => {
|
|
151
|
-
const data = this.data.load(action);
|
|
152
|
-
|
|
153
|
-
data[chatId] = state;
|
|
154
|
-
|
|
155
|
-
await this.data.save(data, action);
|
|
156
|
-
});
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
async close(): Promise<void> {
|
|
160
|
-
for (const lock of this.locks.values()) {
|
|
161
|
-
await lock.acquire();
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
async updateStateFor<TActionState extends IActionState>(
|
|
166
|
-
action: IActionWithState<TActionState>,
|
|
167
|
-
chatId: number,
|
|
168
|
-
update: (state: TActionState) => Promise<void> | void
|
|
169
|
-
) {
|
|
170
|
-
await this.lock(action.key, async () => {
|
|
171
|
-
const data = this.data.load(action);
|
|
172
|
-
const state = data[chatId];
|
|
173
|
-
|
|
174
|
-
await update(state);
|
|
175
|
-
|
|
176
|
-
data[chatId] = state;
|
|
177
|
-
|
|
178
|
-
await this.data.save(data, action);
|
|
179
|
-
});
|
|
180
|
-
}
|
|
181
|
-
}
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
import { TaskRecord } from '../entities/taskRecord';
|
|
2
|
-
import { createTrace } from '../helpers/traceFactory';
|
|
3
|
-
import { BotEventType, TypedEventEmitter } from '../types/events';
|
|
4
|
-
import { IScheduler } from '../types/scheduler';
|
|
5
|
-
import { Milliseconds } from '../types/timeValues';
|
|
6
|
-
|
|
7
|
-
export class NodeTimeoutScheduler implements IScheduler {
|
|
8
|
-
readonly activeTasks: TaskRecord[] = [];
|
|
9
|
-
|
|
10
|
-
constructor(
|
|
11
|
-
readonly eventEmitter: TypedEventEmitter,
|
|
12
|
-
readonly botName: string
|
|
13
|
-
) {}
|
|
14
|
-
|
|
15
|
-
stopAll() {
|
|
16
|
-
for (const task of this.activeTasks) {
|
|
17
|
-
clearInterval(task.taskId);
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
createTask(
|
|
22
|
-
name: string,
|
|
23
|
-
action: () => unknown,
|
|
24
|
-
interval: Milliseconds,
|
|
25
|
-
executeRightAway: boolean,
|
|
26
|
-
ownerName: string
|
|
27
|
-
) {
|
|
28
|
-
const traceId = createTrace(this, this.botName, name);
|
|
29
|
-
const taskId = setInterval(() => {
|
|
30
|
-
action();
|
|
31
|
-
this.eventEmitter.emit(BotEventType.taskRun, {
|
|
32
|
-
name,
|
|
33
|
-
ownerName,
|
|
34
|
-
interval,
|
|
35
|
-
traceId
|
|
36
|
-
});
|
|
37
|
-
}, interval);
|
|
38
|
-
const task = new TaskRecord(name, taskId, interval, traceId);
|
|
39
|
-
|
|
40
|
-
if (executeRightAway) {
|
|
41
|
-
setImmediate(action);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
this.eventEmitter.emit(BotEventType.taskCreated, {
|
|
45
|
-
name,
|
|
46
|
-
ownerName,
|
|
47
|
-
interval,
|
|
48
|
-
traceId
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
this.activeTasks.push(task);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
createOnetimeTask(
|
|
55
|
-
name: string,
|
|
56
|
-
action: () => unknown,
|
|
57
|
-
delay: Milliseconds,
|
|
58
|
-
ownerName: string
|
|
59
|
-
) {
|
|
60
|
-
const traceId = createTrace(this, this.botName, name);
|
|
61
|
-
const actionWrapper = () => {
|
|
62
|
-
this.eventEmitter.emit(BotEventType.taskRun, {
|
|
63
|
-
name,
|
|
64
|
-
ownerName,
|
|
65
|
-
delay,
|
|
66
|
-
traceId
|
|
67
|
-
});
|
|
68
|
-
action();
|
|
69
|
-
};
|
|
70
|
-
setTimeout(actionWrapper, delay);
|
|
71
|
-
|
|
72
|
-
this.eventEmitter.emit(BotEventType.taskCreated, {
|
|
73
|
-
name,
|
|
74
|
-
ownerName,
|
|
75
|
-
delay,
|
|
76
|
-
traceId
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
}
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import { Milliseconds } from '../types/timeValues';
|
|
2
|
-
import { RateLimit } from 'async-sema';
|
|
3
|
-
|
|
4
|
-
export type QueueItem = {
|
|
5
|
-
priority: number;
|
|
6
|
-
callback: () => Promise<void>;
|
|
7
|
-
};
|
|
8
|
-
|
|
9
|
-
function notEmpty<T>(arr: T[]): arr is [T, ...T[]] {
|
|
10
|
-
return arr.length > 0;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
const TELEGRAM_RATELIMIT_DELAY = 35 as Milliseconds;
|
|
14
|
-
|
|
15
|
-
export class ResponseProcessingQueue {
|
|
16
|
-
rateLimiter = RateLimit(1, { timeUnit: TELEGRAM_RATELIMIT_DELAY });
|
|
17
|
-
items: QueueItem[] = [];
|
|
18
|
-
isFlushing = false;
|
|
19
|
-
|
|
20
|
-
enqueue(item: QueueItem) {
|
|
21
|
-
if (
|
|
22
|
-
this.items.length === 0 ||
|
|
23
|
-
item.priority >= this.items[this.items.length - 1].priority
|
|
24
|
-
) {
|
|
25
|
-
this.items.push(item);
|
|
26
|
-
return;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
let insertIndex = this.items.length;
|
|
30
|
-
while (
|
|
31
|
-
insertIndex > 0 &&
|
|
32
|
-
this.items[insertIndex - 1].priority > item.priority
|
|
33
|
-
) {
|
|
34
|
-
insertIndex--;
|
|
35
|
-
}
|
|
36
|
-
this.items.splice(insertIndex, 0, item);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
async flushReadyItems() {
|
|
40
|
-
if (this.isFlushing) return;
|
|
41
|
-
|
|
42
|
-
this.isFlushing = true;
|
|
43
|
-
|
|
44
|
-
while (notEmpty(this.items)) {
|
|
45
|
-
if (Date.now() >= this.items[0].priority) {
|
|
46
|
-
await this.rateLimiter();
|
|
47
|
-
|
|
48
|
-
const [item] = this.items;
|
|
49
|
-
this.items.shift();
|
|
50
|
-
|
|
51
|
-
await item.callback();
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
this.isFlushing = false;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
@@ -1,278 +0,0 @@
|
|
|
1
|
-
import { IStorageClient } from '../types/storage';
|
|
2
|
-
import {
|
|
3
|
-
BotResponse,
|
|
4
|
-
BotResponseTypes,
|
|
5
|
-
IReplyResponse
|
|
6
|
-
} from '../types/response';
|
|
7
|
-
import { QueueItem, ResponseProcessingQueue } from './responseProcessingQueue';
|
|
8
|
-
import { IReplyCapture } from '../types/capture';
|
|
9
|
-
import { IActionWithState } from '../types/action';
|
|
10
|
-
import { IActionState } from '../types/actionState';
|
|
11
|
-
import { TraceId } from '../types/trace';
|
|
12
|
-
import { ChatInfo } from '../dtos/chatInfo';
|
|
13
|
-
import { TelegramApiClient, TelegramMessage } from '../types/externalAliases';
|
|
14
|
-
import { BotEventType, TypedEventEmitter } from '../types/events';
|
|
15
|
-
|
|
16
|
-
export const TELEGRAM_ERROR_QUOTE_INVALID = 'QUOTE_TEXT_INVALID';
|
|
17
|
-
|
|
18
|
-
export class TelegramApiService {
|
|
19
|
-
private readonly queue = new ResponseProcessingQueue();
|
|
20
|
-
private readonly telegram: TelegramApiClient;
|
|
21
|
-
private readonly storage: IStorageClient;
|
|
22
|
-
private readonly eventEmitter: TypedEventEmitter;
|
|
23
|
-
private readonly captureRegistrationCallback: (
|
|
24
|
-
capture: IReplyCapture,
|
|
25
|
-
parentMessageId: number,
|
|
26
|
-
chatInfo: ChatInfo,
|
|
27
|
-
traceId: TraceId
|
|
28
|
-
) => void;
|
|
29
|
-
|
|
30
|
-
private readonly botName: string;
|
|
31
|
-
|
|
32
|
-
private readonly methodMap: Record<
|
|
33
|
-
'pin' | keyof typeof BotResponseTypes,
|
|
34
|
-
string | null
|
|
35
|
-
> = {
|
|
36
|
-
inlineQuery: 'answerInlineQuery',
|
|
37
|
-
text: 'sendMessage',
|
|
38
|
-
react: 'setMessageReaction',
|
|
39
|
-
unpin: 'unpinChatMessage',
|
|
40
|
-
pin: 'pinChatMessage',
|
|
41
|
-
image: 'sendPhoto',
|
|
42
|
-
video: 'sendVideo',
|
|
43
|
-
delay: null
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
constructor(
|
|
47
|
-
botName: string,
|
|
48
|
-
telegram: TelegramApiClient,
|
|
49
|
-
storage: IStorageClient,
|
|
50
|
-
eventEmitter: TypedEventEmitter,
|
|
51
|
-
captureRegistrationCallback: (
|
|
52
|
-
capture: IReplyCapture,
|
|
53
|
-
parentMessageId: number,
|
|
54
|
-
chatInfo: ChatInfo,
|
|
55
|
-
traceId: TraceId
|
|
56
|
-
) => void
|
|
57
|
-
) {
|
|
58
|
-
this.telegram = telegram;
|
|
59
|
-
this.botName = botName;
|
|
60
|
-
this.storage = storage;
|
|
61
|
-
this.eventEmitter = eventEmitter;
|
|
62
|
-
this.captureRegistrationCallback = captureRegistrationCallback;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
enqueueBatchedResponses(responses: BotResponse[]) {
|
|
66
|
-
let offset = 0;
|
|
67
|
-
for (const response of responses) {
|
|
68
|
-
if (response.kind == 'delay') {
|
|
69
|
-
offset += response.delay;
|
|
70
|
-
continue;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const queueItem: QueueItem = {
|
|
74
|
-
callback: async () => {
|
|
75
|
-
try {
|
|
76
|
-
await this.processResponse(response);
|
|
77
|
-
} catch (e) {
|
|
78
|
-
const error = e as Error;
|
|
79
|
-
if ('message' in (error as { message?: string })) {
|
|
80
|
-
const telegramResponse = error as {
|
|
81
|
-
message: string;
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
if (
|
|
85
|
-
telegramResponse.message.includes(
|
|
86
|
-
TELEGRAM_ERROR_QUOTE_INVALID
|
|
87
|
-
)
|
|
88
|
-
) {
|
|
89
|
-
this.eventEmitter.emit(BotEventType.error, {
|
|
90
|
-
error: new Error(
|
|
91
|
-
'Quote error recieved, retrying without quote'
|
|
92
|
-
),
|
|
93
|
-
traceId: response.traceId
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
try {
|
|
97
|
-
await this.processResponse(response, true);
|
|
98
|
-
} catch (e) {
|
|
99
|
-
const error = e as Error;
|
|
100
|
-
this.eventEmitter.emit(BotEventType.error, {
|
|
101
|
-
error,
|
|
102
|
-
traceId: response.traceId
|
|
103
|
-
});
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
this.eventEmitter.emit(BotEventType.error, {
|
|
111
|
-
error,
|
|
112
|
-
traceId: response.traceId
|
|
113
|
-
});
|
|
114
|
-
}
|
|
115
|
-
},
|
|
116
|
-
priority: response.createdAt + offset
|
|
117
|
-
};
|
|
118
|
-
this.queue.enqueue(queueItem);
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
flushResponses() {
|
|
123
|
-
void this.queue.flushReadyItems();
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
private async pinIfShould(
|
|
127
|
-
response: IReplyResponse,
|
|
128
|
-
message: TelegramMessage
|
|
129
|
-
) {
|
|
130
|
-
if (response.shouldPin) {
|
|
131
|
-
this.eventEmitter.emit(BotEventType.apiRequestSending, {
|
|
132
|
-
response: null,
|
|
133
|
-
telegramMethod: this.methodMap['pin'],
|
|
134
|
-
traceId: response.traceId
|
|
135
|
-
});
|
|
136
|
-
await this.telegram.pinChatMessage(
|
|
137
|
-
response.chatInfo.id,
|
|
138
|
-
message.message_id,
|
|
139
|
-
{ disable_notification: true }
|
|
140
|
-
);
|
|
141
|
-
this.eventEmitter.emit(BotEventType.apiRequestSent, {
|
|
142
|
-
response: null,
|
|
143
|
-
telegramMethod: this.methodMap['pin'],
|
|
144
|
-
traceId: response.traceId
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
await this.storage.updateStateFor(
|
|
148
|
-
response.action as IActionWithState<IActionState>,
|
|
149
|
-
response.chatInfo.id,
|
|
150
|
-
(state) => {
|
|
151
|
-
state.pinnedMessages.push(message.message_id);
|
|
152
|
-
}
|
|
153
|
-
);
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
private async processResponse(response: BotResponse, ignoreQuote = false) {
|
|
158
|
-
const sentMessage = await this.sendApiRequest(response, ignoreQuote);
|
|
159
|
-
this.eventEmitter.emit(BotEventType.apiRequestSent, {
|
|
160
|
-
response,
|
|
161
|
-
telegramMethod: this.methodMap[response.kind],
|
|
162
|
-
traceId: response.traceId
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
if (sentMessage && 'content' in response) {
|
|
166
|
-
await this.pinIfShould(response, sentMessage);
|
|
167
|
-
|
|
168
|
-
for (const capture of response.captures) {
|
|
169
|
-
this.captureRegistrationCallback(
|
|
170
|
-
capture,
|
|
171
|
-
sentMessage.message_id,
|
|
172
|
-
response.chatInfo,
|
|
173
|
-
response.traceId
|
|
174
|
-
);
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
private async sendApiRequest(
|
|
180
|
-
response: BotResponse,
|
|
181
|
-
ignoreQuote: boolean
|
|
182
|
-
): Promise<TelegramMessage | null> {
|
|
183
|
-
this.eventEmitter.emit(BotEventType.apiRequestSending, {
|
|
184
|
-
response,
|
|
185
|
-
telegramMethod: this.methodMap[response.kind],
|
|
186
|
-
traceId: response.traceId
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
switch (response.kind) {
|
|
190
|
-
case 'text':
|
|
191
|
-
return await this.telegram.sendMessage(
|
|
192
|
-
response.chatInfo.id,
|
|
193
|
-
response.content,
|
|
194
|
-
{
|
|
195
|
-
reply_parameters: response.replyInfo
|
|
196
|
-
? {
|
|
197
|
-
message_id: response.replyInfo.id,
|
|
198
|
-
quote: ignoreQuote
|
|
199
|
-
? undefined
|
|
200
|
-
: response.replyInfo.quote
|
|
201
|
-
}
|
|
202
|
-
: undefined,
|
|
203
|
-
parse_mode: 'MarkdownV2',
|
|
204
|
-
link_preview_options: {
|
|
205
|
-
is_disabled: response.disableWebPreview
|
|
206
|
-
},
|
|
207
|
-
reply_markup: response.keyboard
|
|
208
|
-
? {
|
|
209
|
-
inline_keyboard: response.keyboard
|
|
210
|
-
}
|
|
211
|
-
: undefined
|
|
212
|
-
}
|
|
213
|
-
);
|
|
214
|
-
case 'image':
|
|
215
|
-
return await this.telegram.sendPhoto(
|
|
216
|
-
response.chatInfo.id,
|
|
217
|
-
response.content,
|
|
218
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
219
|
-
response.replyInfo?.id
|
|
220
|
-
? ({
|
|
221
|
-
reply_to_message_id: response.replyInfo.id // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
222
|
-
} as any)
|
|
223
|
-
: undefined
|
|
224
|
-
);
|
|
225
|
-
case 'video':
|
|
226
|
-
return await this.telegram.sendVideo(
|
|
227
|
-
response.chatInfo.id,
|
|
228
|
-
response.content,
|
|
229
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
230
|
-
response.replyInfo?.id
|
|
231
|
-
? ({
|
|
232
|
-
reply_to_message_id: response.replyInfo.id // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
233
|
-
} as any)
|
|
234
|
-
: undefined
|
|
235
|
-
);
|
|
236
|
-
case 'react':
|
|
237
|
-
await this.telegram.setMessageReaction(
|
|
238
|
-
response.chatInfo.id,
|
|
239
|
-
response.messageId,
|
|
240
|
-
[
|
|
241
|
-
{
|
|
242
|
-
type: 'emoji',
|
|
243
|
-
emoji: response.emoji
|
|
244
|
-
}
|
|
245
|
-
]
|
|
246
|
-
);
|
|
247
|
-
|
|
248
|
-
return null;
|
|
249
|
-
case 'unpin':
|
|
250
|
-
await this.telegram.unpinChatMessage(
|
|
251
|
-
response.chatInfo.id,
|
|
252
|
-
response.messageId
|
|
253
|
-
);
|
|
254
|
-
|
|
255
|
-
await this.storage.updateStateFor(
|
|
256
|
-
response.action,
|
|
257
|
-
response.chatInfo.id,
|
|
258
|
-
(state) => {
|
|
259
|
-
state.pinnedMessages = state.pinnedMessages.filter(
|
|
260
|
-
(x) => x != response.messageId
|
|
261
|
-
);
|
|
262
|
-
}
|
|
263
|
-
);
|
|
264
|
-
|
|
265
|
-
return null;
|
|
266
|
-
case 'inlineQuery':
|
|
267
|
-
await this.telegram.answerInlineQuery(
|
|
268
|
-
response.queryId,
|
|
269
|
-
response.queryResults,
|
|
270
|
-
{ cache_time: 0 }
|
|
271
|
-
);
|
|
272
|
-
|
|
273
|
-
return null;
|
|
274
|
-
case 'delay':
|
|
275
|
-
return null;
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
}
|
package/src/types/action.ts
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { BaseContextInternal } from '../entities/context/baseContext';
|
|
2
|
-
import { IActionState } from './actionState';
|
|
3
|
-
import { BotResponse } from './response';
|
|
4
|
-
|
|
5
|
-
export type ActionKey = string & { __brand: 'actionKey' };
|
|
6
|
-
|
|
7
|
-
export interface IActionWithState<TActionState extends IActionState>
|
|
8
|
-
extends IAction {
|
|
9
|
-
readonly stateConstructor: () => TActionState;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export interface IAction {
|
|
13
|
-
readonly key: ActionKey;
|
|
14
|
-
exec(ctx: BaseContextInternal<IAction>): Promise<BotResponse[]>;
|
|
15
|
-
}
|
package/src/types/actionState.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export type CachedValueAccessor = <TResult>(key: string) => Promise<TResult>;
|
package/src/types/capture.ts
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
ReplyContext,
|
|
3
|
-
ReplyContextInternal
|
|
4
|
-
} from '../entities/context/replyContext';
|
|
5
|
-
import { IActionState } from './actionState';
|
|
6
|
-
import { CommandTrigger } from './commandTrigger';
|
|
7
|
-
import { IAction } from './action';
|
|
8
|
-
|
|
9
|
-
export interface ICaptureController {
|
|
10
|
-
captureReplies: <TParentActionState extends IActionState>(
|
|
11
|
-
/**
|
|
12
|
-
* Defines action trigger.
|
|
13
|
-
* If `string` or `string[]` is provided, will be triggered only on exact message match.
|
|
14
|
-
* If `RegExp` or `RegExp[]` is provided, will be triggered on successful match.
|
|
15
|
-
*/
|
|
16
|
-
trigger: CommandTrigger[],
|
|
17
|
-
/** Callback that will be called on trigger */
|
|
18
|
-
handler: (
|
|
19
|
-
replyContext: ReplyContext<TParentActionState>
|
|
20
|
-
) => Promise<void>,
|
|
21
|
-
/** Abort controller to abort capturing manually */
|
|
22
|
-
abortController?: AbortController
|
|
23
|
-
) => void;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export interface IReplyCapture {
|
|
27
|
-
trigger: CommandTrigger[];
|
|
28
|
-
handler: (
|
|
29
|
-
replyContext: ReplyContextInternal<IActionState>
|
|
30
|
-
) => Promise<void>;
|
|
31
|
-
abortController: AbortController;
|
|
32
|
-
action: IAction;
|
|
33
|
-
}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { MessageContext } from '../entities/context/messageContext';
|
|
2
|
-
import { IActionState } from './actionState';
|
|
3
|
-
|
|
4
|
-
export type CommandCondition<TActionState extends IActionState> = (
|
|
5
|
-
/** Context of action executed in chat, in response to a message. */
|
|
6
|
-
ctx: MessageContext<TActionState>,
|
|
7
|
-
/** State of an action being executed. */
|
|
8
|
-
state: TActionState
|
|
9
|
-
) => boolean;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export type CommandTrigger = string | RegExp;
|