chz-telegram-bot 0.0.26 → 0.0.28
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/entities/actions/commandAction.d.ts +2 -2
- package/dist/entities/actions/commandAction.d.ts.map +1 -1
- package/dist/entities/actions/scheduledAction.d.ts +2 -2
- package/dist/entities/actions/scheduledAction.d.ts.map +1 -1
- package/dist/entities/actions/scheduledAction.js +1 -1
- package/dist/entities/botInstance.d.ts.map +1 -1
- package/dist/entities/botInstance.js +3 -2
- package/dist/entities/context/chatContext.d.ts +3 -2
- package/dist/entities/context/chatContext.d.ts.map +1 -1
- package/dist/entities/context/chatContext.js +6 -6
- package/dist/entities/context/messageContext.d.ts +2 -1
- package/dist/entities/context/messageContext.d.ts.map +1 -1
- package/dist/entities/context/messageContext.js +6 -6
- package/dist/entities/responses/imageMessage.d.ts +3 -2
- package/dist/entities/responses/imageMessage.d.ts.map +1 -1
- package/dist/entities/responses/imageMessage.js +2 -2
- package/dist/entities/responses/reaction.d.ts +3 -2
- package/dist/entities/responses/reaction.d.ts.map +1 -1
- package/dist/entities/responses/reaction.js +2 -2
- package/dist/entities/responses/textMessage.d.ts +3 -2
- package/dist/entities/responses/textMessage.d.ts.map +1 -1
- package/dist/entities/responses/textMessage.js +2 -2
- package/dist/entities/responses/unpin.d.ts +3 -2
- package/dist/entities/responses/unpin.d.ts.map +1 -1
- package/dist/entities/responses/unpin.js +2 -2
- package/dist/entities/responses/videoMessage.d.ts +3 -2
- package/dist/entities/responses/videoMessage.d.ts.map +1 -1
- package/dist/entities/responses/videoMessage.js +2 -2
- package/dist/helpers/inverseRecord.d.ts +2 -0
- package/dist/helpers/inverseRecord.d.ts.map +1 -0
- package/dist/helpers/inverseRecord.js +6 -0
- package/dist/services/jsonFileStorage.d.ts +6 -7
- package/dist/services/jsonFileStorage.d.ts.map +1 -1
- package/dist/services/jsonFileStorage.js +29 -23
- package/dist/services/telegramApi.d.ts +1 -1
- package/dist/services/telegramApi.d.ts.map +1 -1
- package/dist/services/telegramApi.js +11 -13
- package/dist/types/actionWithState.d.ts +4 -1
- package/dist/types/actionWithState.d.ts.map +1 -1
- package/dist/types/response.d.ts +2 -1
- package/dist/types/response.d.ts.map +1 -1
- package/dist/types/storage.d.ts +4 -4
- package/dist/types/storage.d.ts.map +1 -1
- package/entities/actions/commandAction.ts +3 -3
- package/entities/actions/scheduledAction.ts +4 -4
- package/entities/botInstance.ts +4 -5
- package/entities/context/chatContext.ts +8 -12
- package/entities/context/messageContext.ts +11 -7
- package/entities/responses/imageMessage.ts +4 -3
- package/entities/responses/reaction.ts +4 -3
- package/entities/responses/textMessage.ts +4 -3
- package/entities/responses/unpin.ts +4 -3
- package/entities/responses/videoMessage.ts +4 -3
- package/helpers/{reverseRecord.ts → inverseRecord.ts} +1 -1
- package/package.json +1 -1
- package/services/jsonFileStorage.ts +41 -27
- package/services/telegramApi.ts +19 -20
- package/types/actionWithState.ts +3 -1
- package/types/response.ts +3 -1
- package/types/storage.ts +4 -4
|
@@ -6,15 +6,15 @@ import { IStorageClient } from '../types/storage';
|
|
|
6
6
|
import { ActionStateBase } from '../entities/states/actionStateBase';
|
|
7
7
|
import { ActionExecutionResult } from '../entities/actionExecutionResult';
|
|
8
8
|
import { IActionState } from '../types/actionState';
|
|
9
|
-
import { IActionWithState } from '../types/actionWithState';
|
|
9
|
+
import { IActionWithState, ActionKey } from '../types/actionWithState';
|
|
10
10
|
|
|
11
11
|
export class JsonFileStorage implements IStorageClient {
|
|
12
|
-
|
|
12
|
+
private locks = new Map<ActionKey, Semaphore>();
|
|
13
13
|
private cache: Map<string, Record<number, IActionState>>;
|
|
14
14
|
private storagePath: string;
|
|
15
15
|
private botName: string;
|
|
16
16
|
|
|
17
|
-
constructor(botName: string, path?: string) {
|
|
17
|
+
constructor(botName: string, actions: IActionWithState[], path?: string) {
|
|
18
18
|
this.cache = new Map<string, Record<number, IActionState>>();
|
|
19
19
|
this.botName = botName;
|
|
20
20
|
this.storagePath = path ?? 'storage';
|
|
@@ -24,19 +24,31 @@ export class JsonFileStorage implements IStorageClient {
|
|
|
24
24
|
recursive: true
|
|
25
25
|
});
|
|
26
26
|
}
|
|
27
|
+
|
|
28
|
+
for (const action of actions) {
|
|
29
|
+
this.locks.set(action.key, new Semaphore(1));
|
|
30
|
+
}
|
|
27
31
|
}
|
|
28
32
|
|
|
29
|
-
private async lock<TType>(action: () => Promise<TType>) {
|
|
30
|
-
|
|
33
|
+
private async lock<TType>(key: ActionKey, action: () => Promise<TType>) {
|
|
34
|
+
const lock = this.locks.get(key);
|
|
35
|
+
|
|
36
|
+
if (!lock) {
|
|
37
|
+
throw new Error(`Lock for action ${key} not found`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
await lock.acquire();
|
|
31
41
|
|
|
32
42
|
try {
|
|
33
43
|
return await action();
|
|
34
44
|
} finally {
|
|
35
|
-
|
|
45
|
+
lock.release();
|
|
36
46
|
}
|
|
37
47
|
}
|
|
38
48
|
|
|
39
|
-
private async loadInternal<TActionState extends IActionState>(
|
|
49
|
+
private async loadInternal<TActionState extends IActionState>(
|
|
50
|
+
key: ActionKey
|
|
51
|
+
) {
|
|
40
52
|
if (!this.cache.has(key)) {
|
|
41
53
|
const targetPath = this.buidPathFromKey(key);
|
|
42
54
|
if (!existsSync(targetPath)) {
|
|
@@ -55,7 +67,7 @@ export class JsonFileStorage implements IStorageClient {
|
|
|
55
67
|
return (this.cache.get(key) ?? {}) as Record<number, TActionState>;
|
|
56
68
|
}
|
|
57
69
|
|
|
58
|
-
private async save(data: Record<number, ActionStateBase>, key:
|
|
70
|
+
private async save(data: Record<number, ActionStateBase>, key: ActionKey) {
|
|
59
71
|
this.cache.delete(key);
|
|
60
72
|
|
|
61
73
|
const targetPath = this.buidPathFromKey(key);
|
|
@@ -68,38 +80,38 @@ export class JsonFileStorage implements IStorageClient {
|
|
|
68
80
|
await writeFile(targetPath, JSON.stringify(data), { flag: 'w+' });
|
|
69
81
|
}
|
|
70
82
|
|
|
71
|
-
private buidPathFromKey(key:
|
|
83
|
+
private buidPathFromKey(key: ActionKey) {
|
|
72
84
|
return `${this.storagePath}/${this.botName}/${key.replaceAll(
|
|
73
85
|
':',
|
|
74
86
|
'/'
|
|
75
87
|
)}.json`;
|
|
76
88
|
}
|
|
77
89
|
|
|
78
|
-
async load<TActionState extends IActionState>(key:
|
|
79
|
-
return await this.lock(async () => {
|
|
90
|
+
async load<TActionState extends IActionState>(key: ActionKey) {
|
|
91
|
+
return await this.lock(key, async () => {
|
|
80
92
|
return this.loadInternal<TActionState>(key);
|
|
81
93
|
});
|
|
82
94
|
}
|
|
83
95
|
|
|
84
96
|
async saveMetadata(actions: IActionWithState[], botName: string) {
|
|
85
|
-
|
|
86
|
-
|
|
97
|
+
const targetPath = this.buidPathFromKey(
|
|
98
|
+
`Metadata-${botName}` as ActionKey
|
|
99
|
+
);
|
|
87
100
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
});
|
|
101
|
+
await writeFile(targetPath, JSON.stringify(actions), {
|
|
102
|
+
flag: 'w+'
|
|
91
103
|
});
|
|
92
104
|
}
|
|
93
105
|
|
|
94
106
|
async getActionState<TActionState extends IActionState>(
|
|
95
|
-
|
|
107
|
+
action: IActionWithState,
|
|
96
108
|
chatId: number
|
|
97
109
|
) {
|
|
98
|
-
return await this.lock(async () => {
|
|
99
|
-
const data = await this.loadInternal(
|
|
110
|
+
return await this.lock(action.key, async () => {
|
|
111
|
+
const data = await this.loadInternal(action.key);
|
|
100
112
|
|
|
101
113
|
return Object.assign(
|
|
102
|
-
|
|
114
|
+
action.stateConstructor(),
|
|
103
115
|
data[chatId]
|
|
104
116
|
) as TActionState;
|
|
105
117
|
});
|
|
@@ -110,7 +122,7 @@ export class JsonFileStorage implements IStorageClient {
|
|
|
110
122
|
chatId: number,
|
|
111
123
|
transactionResult: ActionExecutionResult
|
|
112
124
|
) {
|
|
113
|
-
await this.lock(async () => {
|
|
125
|
+
await this.lock(action.key, async () => {
|
|
114
126
|
const data = await this.loadInternal(action.key);
|
|
115
127
|
|
|
116
128
|
if (transactionResult.shouldUpdate) {
|
|
@@ -121,24 +133,26 @@ export class JsonFileStorage implements IStorageClient {
|
|
|
121
133
|
}
|
|
122
134
|
|
|
123
135
|
async close(): Promise<void> {
|
|
124
|
-
|
|
136
|
+
for (const lock of this.locks.values()) {
|
|
137
|
+
await lock.acquire();
|
|
138
|
+
}
|
|
125
139
|
}
|
|
126
140
|
|
|
127
141
|
async updateStateFor<TActionState extends IActionState>(
|
|
128
|
-
|
|
142
|
+
action: IActionWithState,
|
|
129
143
|
chatId: number,
|
|
130
144
|
update: (state: TActionState) => Promise<void>
|
|
131
145
|
) {
|
|
132
|
-
await this.lock(async () => {
|
|
133
|
-
const data = await this.loadInternal(
|
|
146
|
+
await this.lock(action.key, async () => {
|
|
147
|
+
const data = await this.loadInternal(action.key);
|
|
134
148
|
const state = Object.assign(
|
|
135
|
-
|
|
149
|
+
action.stateConstructor(),
|
|
136
150
|
data[chatId]
|
|
137
151
|
) as TActionState;
|
|
138
152
|
|
|
139
153
|
await update(state);
|
|
140
154
|
|
|
141
|
-
await this.save(data,
|
|
155
|
+
await this.save(data, action.key);
|
|
142
156
|
});
|
|
143
157
|
}
|
|
144
158
|
}
|
package/services/telegramApi.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Message } from 'telegraf/types';
|
|
2
2
|
import { ChatContext } from '../entities/context/chatContext';
|
|
3
3
|
import { MessageContext } from '../entities/context/messageContext';
|
|
4
|
-
import {
|
|
4
|
+
import { inverseRecord as inverseRecord } from '../helpers/inverseRecord';
|
|
5
5
|
import { IStorageClient } from '../types/storage';
|
|
6
6
|
import { Logger } from './logger';
|
|
7
7
|
import { Reaction } from '../entities/responses/reaction';
|
|
@@ -28,6 +28,7 @@ export class TelegramApiService {
|
|
|
28
28
|
telegram: Telegram;
|
|
29
29
|
chats: Record<number, string>;
|
|
30
30
|
storage: IStorageClient;
|
|
31
|
+
interactions: IBotApiInteractions;
|
|
31
32
|
|
|
32
33
|
constructor(
|
|
33
34
|
botName: string,
|
|
@@ -37,8 +38,14 @@ export class TelegramApiService {
|
|
|
37
38
|
) {
|
|
38
39
|
this.telegram = telegram;
|
|
39
40
|
this.botName = botName;
|
|
40
|
-
this.chats =
|
|
41
|
+
this.chats = inverseRecord(chats);
|
|
41
42
|
this.storage = storage;
|
|
43
|
+
|
|
44
|
+
this.interactions = {
|
|
45
|
+
react: (reaction) => this.enqueue(reaction),
|
|
46
|
+
respond: (response) => this.enqueue(response),
|
|
47
|
+
unpin: (unpinMessage) => this.enqueue(unpinMessage)
|
|
48
|
+
} as IBotApiInteractions;
|
|
42
49
|
}
|
|
43
50
|
|
|
44
51
|
async flushResponses() {
|
|
@@ -80,7 +87,7 @@ export class TelegramApiService {
|
|
|
80
87
|
);
|
|
81
88
|
|
|
82
89
|
await this.storage.updateStateFor(
|
|
83
|
-
response.
|
|
90
|
+
response.action,
|
|
84
91
|
response.chatId,
|
|
85
92
|
async (state) => {
|
|
86
93
|
state.pinnedMessages.push(sentMessage.message_id);
|
|
@@ -96,7 +103,7 @@ export class TelegramApiService {
|
|
|
96
103
|
case 'text':
|
|
97
104
|
sentMessage = await this.telegram.sendMessage(
|
|
98
105
|
response.chatId,
|
|
99
|
-
response.content
|
|
106
|
+
response.content,
|
|
100
107
|
{
|
|
101
108
|
reply_to_message_id: response.replyId,
|
|
102
109
|
parse_mode: 'MarkdownV2',
|
|
@@ -110,7 +117,7 @@ export class TelegramApiService {
|
|
|
110
117
|
case 'image':
|
|
111
118
|
sentMessage = await this.telegram.sendPhoto(
|
|
112
119
|
response.chatId,
|
|
113
|
-
response.content
|
|
120
|
+
response.content,
|
|
114
121
|
response.replyId
|
|
115
122
|
? // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
116
123
|
({ reply_to_message_id: response.replyId } as any)
|
|
@@ -122,7 +129,7 @@ export class TelegramApiService {
|
|
|
122
129
|
case 'video':
|
|
123
130
|
sentMessage = await this.telegram.sendVideo(
|
|
124
131
|
response.chatId,
|
|
125
|
-
response.content
|
|
132
|
+
response.content,
|
|
126
133
|
response.replyId
|
|
127
134
|
? // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
128
135
|
({ reply_to_message_id: response.replyId } as any)
|
|
@@ -152,7 +159,7 @@ export class TelegramApiService {
|
|
|
152
159
|
);
|
|
153
160
|
|
|
154
161
|
await this.storage.updateStateFor(
|
|
155
|
-
response.
|
|
162
|
+
response.action,
|
|
156
163
|
response.chatId,
|
|
157
164
|
async (state) => {
|
|
158
165
|
state.pinnedMessages = state.pinnedMessages.filter(
|
|
@@ -168,22 +175,14 @@ export class TelegramApiService {
|
|
|
168
175
|
this.messageQueue.push(response);
|
|
169
176
|
}
|
|
170
177
|
|
|
171
|
-
private getInteractions() {
|
|
172
|
-
return {
|
|
173
|
-
react: (reaction) => this.enqueue(reaction),
|
|
174
|
-
respond: (response) => this.enqueue(response),
|
|
175
|
-
unpin: (unpinMessage) => this.enqueue(unpinMessage)
|
|
176
|
-
} as IBotApiInteractions;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
178
|
createContextForMessage<TActionState extends IActionState>(
|
|
180
179
|
incomingMessage: IncomingMessage,
|
|
181
180
|
command: CommandAction<TActionState>
|
|
182
181
|
) {
|
|
183
182
|
return new MessageContext<TActionState>(
|
|
184
183
|
this.botName,
|
|
185
|
-
command
|
|
186
|
-
this.
|
|
184
|
+
command,
|
|
185
|
+
this.interactions,
|
|
187
186
|
incomingMessage,
|
|
188
187
|
this.storage
|
|
189
188
|
);
|
|
@@ -195,8 +194,8 @@ export class TelegramApiService {
|
|
|
195
194
|
) {
|
|
196
195
|
return new ChatContext<TActionState>(
|
|
197
196
|
this.botName,
|
|
198
|
-
scheduledAction
|
|
199
|
-
this.
|
|
197
|
+
scheduledAction,
|
|
198
|
+
this.interactions,
|
|
200
199
|
chatId,
|
|
201
200
|
this.chats[chatId],
|
|
202
201
|
`Scheduled:${scheduledAction.key}:${chatId}`,
|
package/types/actionWithState.ts
CHANGED
package/types/response.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { Reaction } from '../entities/responses/reaction';
|
|
|
3
3
|
import { TextMessage } from '../entities/responses/textMessage';
|
|
4
4
|
import { UnpinResponse } from '../entities/responses/unpin';
|
|
5
5
|
import { VideoMessage } from '../entities/responses/videoMessage';
|
|
6
|
+
import { IActionWithState } from './actionWithState';
|
|
6
7
|
|
|
7
8
|
export const BotResponseTypes = {
|
|
8
9
|
unpin: 'unpin',
|
|
@@ -23,7 +24,8 @@ export interface IChatResponse {
|
|
|
23
24
|
kind: keyof typeof BotResponseTypes;
|
|
24
25
|
chatId: number;
|
|
25
26
|
traceId: number | string;
|
|
26
|
-
|
|
27
|
+
|
|
28
|
+
action: IActionWithState;
|
|
27
29
|
}
|
|
28
30
|
|
|
29
31
|
export interface IReplyMessage<TType> extends IChatResponse {
|
package/types/storage.ts
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
import { ActionExecutionResult } from '../entities/actionExecutionResult';
|
|
2
2
|
import { IActionState } from './actionState';
|
|
3
|
-
import { IActionWithState } from './actionWithState';
|
|
3
|
+
import { ActionKey, IActionWithState } from './actionWithState';
|
|
4
4
|
|
|
5
5
|
export interface IStorageClient {
|
|
6
6
|
updateStateFor<TActionState extends IActionState>(
|
|
7
|
-
|
|
7
|
+
action: IActionWithState,
|
|
8
8
|
chatId: number,
|
|
9
9
|
update: (state: TActionState) => Promise<void>
|
|
10
10
|
): Promise<void>;
|
|
11
11
|
close(): Promise<void>;
|
|
12
12
|
load<TActionState extends IActionState>(
|
|
13
|
-
key:
|
|
13
|
+
key: ActionKey
|
|
14
14
|
): Promise<Record<number, TActionState>>;
|
|
15
15
|
saveMetadata(actions: IActionWithState[], botName: string): Promise<void>;
|
|
16
16
|
getActionState<TActionState extends IActionState>(
|
|
17
|
-
|
|
17
|
+
action: IActionWithState,
|
|
18
18
|
chatId: number
|
|
19
19
|
): Promise<TActionState>;
|
|
20
20
|
saveActionExecutionResult(
|