gramstax 0.0.1
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/LICENSE +25 -0
- package/README.md +0 -0
- package/dist/package.json +52 -0
- package/dist/src/base/general.d.ts +7 -0
- package/dist/src/base/general.d.ts.map +1 -0
- package/dist/src/base/general.js +15 -0
- package/dist/src/base/guard.d.ts +13 -0
- package/dist/src/base/guard.d.ts.map +1 -0
- package/dist/src/base/guard.js +8 -0
- package/dist/src/base/index.d.ts +4 -0
- package/dist/src/base/index.d.ts.map +1 -0
- package/dist/src/base/index.js +3 -0
- package/dist/src/base/page.d.ts +263 -0
- package/dist/src/base/page.d.ts.map +1 -0
- package/dist/src/base/page.js +805 -0
- package/dist/src/cache/external.d.ts +10 -0
- package/dist/src/cache/external.d.ts.map +1 -0
- package/dist/src/cache/external.js +16 -0
- package/dist/src/cache/index.d.ts +2 -0
- package/dist/src/cache/index.d.ts.map +1 -0
- package/dist/src/cache/index.js +1 -0
- package/dist/src/core/bot.d.ts +804 -0
- package/dist/src/core/bot.d.ts.map +1 -0
- package/dist/src/core/bot.js +465 -0
- package/dist/src/core/ctx.d.ts +60 -0
- package/dist/src/core/ctx.d.ts.map +1 -0
- package/dist/src/core/ctx.js +175 -0
- package/dist/src/core/index.d.ts +3 -0
- package/dist/src/core/index.d.ts.map +1 -0
- package/dist/src/core/index.js +2 -0
- package/dist/src/grammy/index.d.ts +2 -0
- package/dist/src/grammy/index.d.ts.map +1 -0
- package/dist/src/grammy/index.js +1 -0
- package/dist/src/index.d.ts +5 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +4 -0
- package/dist/src/template/engine.d.ts +34 -0
- package/dist/src/template/engine.d.ts.map +1 -0
- package/dist/src/template/engine.js +122 -0
- package/dist/src/template/index.d.ts +3 -0
- package/dist/src/template/index.d.ts.map +1 -0
- package/dist/src/template/index.js +2 -0
- package/dist/src/template/manager.d.ts +111 -0
- package/dist/src/template/manager.d.ts.map +1 -0
- package/dist/src/template/manager.js +237 -0
- package/package.json +51 -0
- package/src/base/general.ts +17 -0
- package/src/base/guard.ts +10 -0
- package/src/base/index.ts +3 -0
- package/src/base/page.ts +1111 -0
- package/src/cache/external.ts +15 -0
- package/src/cache/index.ts +1 -0
- package/src/core/bot.ts +535 -0
- package/src/core/ctx.ts +177 -0
- package/src/core/index.ts +2 -0
- package/src/grammy/index.ts +1 -0
- package/src/index.ts +4 -0
- package/src/template/engine.ts +167 -0
- package/src/template/index.ts +2 -0
- package/src/template/manager.ts +280 -0
- package/src/types/page.d.ts +4 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Keyv } from "keyv"
|
|
2
|
+
import KeyvRedis from "@keyv/redis"
|
|
3
|
+
|
|
4
|
+
type IMore = Omit<Required<ConstructorParameters<typeof Keyv>>[0], `store` | `namespace` | `ttl`> & { forceStore?: any }
|
|
5
|
+
|
|
6
|
+
export class CacheExternal extends Keyv {
|
|
7
|
+
constructor(public url: string | `memory` = `memory`, namespace?: string, ttl?: number, more?: IMore) {
|
|
8
|
+
let store: any
|
|
9
|
+
if (url.startsWith(`redis`)) store = new KeyvRedis(url)
|
|
10
|
+
if (url === `memory`) store = new Map()
|
|
11
|
+
if (more?.forceStore) store = more.forceStore
|
|
12
|
+
|
|
13
|
+
super({ store, namespace, ttl })
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./external"
|
package/src/core/bot.ts
ADDED
|
@@ -0,0 +1,535 @@
|
|
|
1
|
+
import { Ctx } from "./ctx"
|
|
2
|
+
import { serve } from "bun"
|
|
3
|
+
import { readdirSync } from "node:fs"
|
|
4
|
+
import { join, parse } from "node:path"
|
|
5
|
+
import { CacheExternal } from "../cache"
|
|
6
|
+
import { LoggingPretty } from "logging-pretty"
|
|
7
|
+
import { TemplateManager } from "../template"
|
|
8
|
+
import { Bot, webhookCallback, type BotError, type Context } from "grammy"
|
|
9
|
+
import type { UserFromGetMe } from "grammy/types"
|
|
10
|
+
import type { IPageBase } from "../types/page"
|
|
11
|
+
|
|
12
|
+
export class Gramstax {
|
|
13
|
+
public templateManager: TemplateManager
|
|
14
|
+
public cacheKeyboard: Map<any, any>
|
|
15
|
+
public cacheSession: CacheExternal
|
|
16
|
+
public pages = this.pageLoads()
|
|
17
|
+
public log: LoggingPretty
|
|
18
|
+
public bot: Bot
|
|
19
|
+
|
|
20
|
+
public constructor(params: { token: string; deploy: string; log?: LoggingPretty; cacheSession?: CacheExternal; cacheKeyboard?: Map<any, any>; templateManager?: TemplateManager }) {
|
|
21
|
+
this.log = params.log || new LoggingPretty()
|
|
22
|
+
this.cacheSession = params.cacheSession || new CacheExternal(`memory`, `session`)
|
|
23
|
+
this.cacheKeyboard = params.cacheKeyboard || new Map()
|
|
24
|
+
this.templateManager = params.templateManager || new TemplateManager({ path: null })
|
|
25
|
+
|
|
26
|
+
this.bot = new Bot(params.token)
|
|
27
|
+
this.bot.catch(this.onCatch)
|
|
28
|
+
this.bot.on(`callback_query:data`, this.onCallbackQueryData.bind(this)) // Handle callback queries from inline keyboards
|
|
29
|
+
this.bot.on(`message:text`, this.onMessageText.bind(this)) // Handle global text messages
|
|
30
|
+
this.bot.on(`message:photo`, this.onMessagePhoto.bind(this)) // Handle global photo messages
|
|
31
|
+
this.bot.on(`message:video`, this.onMessageVideo.bind(this)) // Handle global video messages
|
|
32
|
+
this.bot.on(`message:audio`, this.onMessageAudio.bind(this)) // Handle global audio messages
|
|
33
|
+
this.bot.on(`message:document`, this.onMessageDocument.bind(this)) // Handle global document messages
|
|
34
|
+
this.bot.on(`message:animation`, this.onMessageAnimation.bind(this)) // Handle global animation messages
|
|
35
|
+
this.bot.on(`message:voice`, this.onMessageVoice.bind(this)) // Handle global voice messages
|
|
36
|
+
this.bot.on(`message:video_note`, this.onMessageVideoNote.bind(this)) // Handle global video note messages
|
|
37
|
+
this.bot.on(`message:sticker`, this.onMessageSticker.bind(this)) // Handle global sticker messages
|
|
38
|
+
this.bot.on(`message:location`, this.onMessageLocation.bind(this)) // Handle global location messages
|
|
39
|
+
this.bot.on(`message:contact`, this.onMessageContact.bind(this)) // Handle global contact messages
|
|
40
|
+
|
|
41
|
+
// Graceful shutdown
|
|
42
|
+
process.once(`SIGINT`, () => this.bot.stop())
|
|
43
|
+
process.once(`SIGTERM`, () => this.bot.stop())
|
|
44
|
+
|
|
45
|
+
if (params.deploy.startsWith(`polling`)) {
|
|
46
|
+
this.runPolling()
|
|
47
|
+
} else if (params.deploy.startsWith(`webhook`)) {
|
|
48
|
+
this.runWebhook(params.deploy.split(`webhook:`)[1] as string)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
public onCatch(error: BotError<Context>) {
|
|
53
|
+
this.log.error(`[Bot.catch]: ${String(error)}`)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
public onStart(botInfo: UserFromGetMe, method: `polling` | `webhook`, data: string | null) {
|
|
57
|
+
const { username } = botInfo
|
|
58
|
+
if (method == `polling`) {
|
|
59
|
+
this.log.success(`Telegram bot polling started: ${username}`)
|
|
60
|
+
} else {
|
|
61
|
+
this.log.success(`Telegram bot webhook started: ${username} (${data})`)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
public initCtx(ct: Context) {
|
|
66
|
+
return new Ctx(ct, this.templateManager, this.cacheSession, this.cacheKeyboard)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
public async runPolling() {
|
|
70
|
+
this.bot.start({
|
|
71
|
+
onStart: async (botInfo) => {
|
|
72
|
+
this.onStart(botInfo, `polling`, null)
|
|
73
|
+
}
|
|
74
|
+
})
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
public async runWebhook(baseUrl: string) {
|
|
78
|
+
const handleUpdate = webhookCallback(this.bot, `bun`)
|
|
79
|
+
const pathWebhook = `/telegram-webhook`
|
|
80
|
+
serve({
|
|
81
|
+
fetch(req) {
|
|
82
|
+
const { pathname } = new URL(req.url)
|
|
83
|
+
if (pathname === pathWebhook) {
|
|
84
|
+
if (req.method === `POST`) {
|
|
85
|
+
return handleUpdate(req as any)
|
|
86
|
+
}
|
|
87
|
+
return new Response(`OK`, { status: 200 })
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (pathname === `/` && req.method === `GET`) {
|
|
91
|
+
const timestamp = new Date().toISOString()
|
|
92
|
+
return new Response(JSON.stringify({ timestamp }), {
|
|
93
|
+
status: 200,
|
|
94
|
+
headers: { "Content-Type": `application/json` }
|
|
95
|
+
})
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Jika tidak cocok
|
|
99
|
+
return new Response(`Not found`, { status: 404 })
|
|
100
|
+
}
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
const data = await this.bot.api.getWebhookInfo()
|
|
104
|
+
const webhookUrl = baseUrl.endsWith(`/`) ? baseUrl.slice(0, -1) + pathWebhook : baseUrl + pathWebhook
|
|
105
|
+
if (data.url != webhookUrl) {
|
|
106
|
+
await this.bot.api.setWebhook(webhookUrl)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const botInfo = await this.bot.api.getMe()
|
|
110
|
+
this.onStart(botInfo, `webhook`, webhookUrl)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
public pageCompile(Page: IPageBase, redacted = true) {
|
|
114
|
+
if (Page?.template !== undefined) {
|
|
115
|
+
this.templateManager.compile(Page.data.name, Page.template)
|
|
116
|
+
if (redacted) {
|
|
117
|
+
Page.template = undefined // redacted to reduce memory
|
|
118
|
+
}
|
|
119
|
+
} else {
|
|
120
|
+
Page.template = undefined
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
public pageBuildData(Page: IPageBase, filePath: string) {
|
|
125
|
+
if (Page?.data === undefined) {
|
|
126
|
+
Page.data = Page.buildData()
|
|
127
|
+
}
|
|
128
|
+
Page.data.name = parse(filePath).name
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
public pageSorts(Page: IPageBase, pages: typeof this.pages) {
|
|
132
|
+
const name = Page.data.name
|
|
133
|
+
const proto = Page.prototype
|
|
134
|
+
|
|
135
|
+
const partPayload = `Payload`
|
|
136
|
+
const partCommand = `Command`
|
|
137
|
+
const partCaption = `Caption`
|
|
138
|
+
const partFree = `Free`
|
|
139
|
+
|
|
140
|
+
const pd = pages.dynamic
|
|
141
|
+
const pds = pages.dynamicSpesific
|
|
142
|
+
|
|
143
|
+
const upFirst = (s: string) => s.charAt(0).toUpperCase() + s.slice(1)
|
|
144
|
+
const getPureFuncName = (func: string) => func.replace(partPayload, ``).replace(partCommand, ``).replace(partCaption, ``).replace(partFree, ``)
|
|
145
|
+
const buildSessionMethod = (func: string, name: any) => (proto as any)[`buildSessionMethod${upFirst(func)}`](name)
|
|
146
|
+
|
|
147
|
+
// all
|
|
148
|
+
pages.all[name] = Page
|
|
149
|
+
|
|
150
|
+
for (let index = 0; index < pages.lenListFuncStaticSession; index++) {
|
|
151
|
+
const func = pages.listFuncStaticSession[index] as string
|
|
152
|
+
|
|
153
|
+
const ps = pages.static[func as keyof typeof pages.static]
|
|
154
|
+
const psi = pages.staticIntent[func as keyof typeof pages.staticIntent]
|
|
155
|
+
const pss = pages.staticSession[func as keyof typeof pages.staticSession]
|
|
156
|
+
|
|
157
|
+
const value = { name, func } as any
|
|
158
|
+
const p = ps || psi
|
|
159
|
+
if (p !== undefined) {
|
|
160
|
+
const _name = func.includes(partCommand) ? `/${name}` : name
|
|
161
|
+
p[proto.buildIntent(_name, func.startsWith(`callback`))] = value
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// staticSession
|
|
165
|
+
if (pss !== undefined) {
|
|
166
|
+
pss[buildSessionMethod(func, name)] = value
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// dynamicStatic
|
|
170
|
+
if (func.toLowerCase().endsWith(`free`)) {
|
|
171
|
+
pds[getPureFuncName(func) as keyof typeof pds].push(value)
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// all
|
|
176
|
+
if (proto.free) {
|
|
177
|
+
pd.push(name)
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
public pageLoads() {
|
|
182
|
+
this.log.info(`Load pages..`)
|
|
183
|
+
|
|
184
|
+
const path = join(process.cwd(), `src`, `pages`)
|
|
185
|
+
const files = readdirSync(path)
|
|
186
|
+
const initStatic = {} as Record<string, { name: string; func: string }> // { [page.data.name]: { name: Page.data.name, func: routeFunctionName } }
|
|
187
|
+
const initStaticIntent = initStatic // { [intent]: { name: page.data.name, func: routeFunctionName } }
|
|
188
|
+
const initStaticSession = initStatic // { [session.method]: { name: page.data.name, func: routeFunctionName } }
|
|
189
|
+
const initDynamicSpesific = [] as { name: string; func: string }[] //
|
|
190
|
+
|
|
191
|
+
const listFuncStatic = [
|
|
192
|
+
// callback
|
|
193
|
+
`callback`,
|
|
194
|
+
// text
|
|
195
|
+
`text`,
|
|
196
|
+
`textCommand`,
|
|
197
|
+
// photo
|
|
198
|
+
`photoCaption`,
|
|
199
|
+
`photoCaptionCommand`,
|
|
200
|
+
// video
|
|
201
|
+
`videoCaption`,
|
|
202
|
+
`videoCaptionCommand`,
|
|
203
|
+
// audio
|
|
204
|
+
`audioCaption`,
|
|
205
|
+
`audioCaptionCommand`,
|
|
206
|
+
// document
|
|
207
|
+
`documentCaption`,
|
|
208
|
+
`documentCaptionCommand`,
|
|
209
|
+
// animation
|
|
210
|
+
`animationCaption`,
|
|
211
|
+
`animationCaptionCommand`,
|
|
212
|
+
// voice
|
|
213
|
+
`voiceCaption`,
|
|
214
|
+
`voiceCaptionCommand`,
|
|
215
|
+
// videoNote
|
|
216
|
+
`videoNoteCaption`,
|
|
217
|
+
`videoNoteCaptionCommand`
|
|
218
|
+
] as const
|
|
219
|
+
const listFuncStaticIntent = [
|
|
220
|
+
// callback
|
|
221
|
+
`callbackPayload`,
|
|
222
|
+
// text
|
|
223
|
+
`textPayload`,
|
|
224
|
+
`textCommandPayload`,
|
|
225
|
+
// photo
|
|
226
|
+
`photoCaptionPayload`,
|
|
227
|
+
`photoCaptionCommandPayload`,
|
|
228
|
+
// video
|
|
229
|
+
`videoCaptionPayload`,
|
|
230
|
+
`videoCaptionCommandPayload`,
|
|
231
|
+
// audio
|
|
232
|
+
`audioCaptionPayload`,
|
|
233
|
+
`audioCaptionCommandPayload`,
|
|
234
|
+
// document
|
|
235
|
+
`documentCaptionPayload`,
|
|
236
|
+
`documentCaptionCommandPayload`,
|
|
237
|
+
// animation
|
|
238
|
+
`animationCaptionPayload`,
|
|
239
|
+
`animationCaptionCommandPayload`,
|
|
240
|
+
// voice
|
|
241
|
+
`voiceCaptionPayload`,
|
|
242
|
+
`voiceCaptionCommandPayload`,
|
|
243
|
+
// video note
|
|
244
|
+
`videoNoteCaptionPayload`,
|
|
245
|
+
`videoNoteCaptionCommandPayload`
|
|
246
|
+
] as const
|
|
247
|
+
const listFuncStaticSession = [
|
|
248
|
+
...listFuncStatic,
|
|
249
|
+
...listFuncStaticIntent,
|
|
250
|
+
// text
|
|
251
|
+
`textFree`,
|
|
252
|
+
// photo
|
|
253
|
+
`photoFree`,
|
|
254
|
+
// video
|
|
255
|
+
`videoFree`,
|
|
256
|
+
// audio
|
|
257
|
+
`audioFree`,
|
|
258
|
+
// document
|
|
259
|
+
`documentFree`,
|
|
260
|
+
// animation
|
|
261
|
+
`animationFree`,
|
|
262
|
+
// voice
|
|
263
|
+
`voiceFree`,
|
|
264
|
+
// videoNote
|
|
265
|
+
`videoNoteFree`,
|
|
266
|
+
// sticker
|
|
267
|
+
`stickerFree`,
|
|
268
|
+
// location
|
|
269
|
+
`locationFree`,
|
|
270
|
+
// contact
|
|
271
|
+
`contactFree`
|
|
272
|
+
] as const
|
|
273
|
+
|
|
274
|
+
const makeObject = <const T extends readonly string[], V>(keys: T, val: V): { [K in T[number]]: V } => Object.fromEntries(keys.map((k) => [k, { ...val }])) as { [K in T[number]]: V }
|
|
275
|
+
|
|
276
|
+
const pages = {
|
|
277
|
+
all: {} as Record<string, IPageBase>, // (classes) all Page
|
|
278
|
+
dynamic: [] as string[], // (dynamic) all Page function free
|
|
279
|
+
dynamicSpesific: {
|
|
280
|
+
text: initDynamicSpesific,
|
|
281
|
+
photo: initDynamicSpesific,
|
|
282
|
+
video: initDynamicSpesific,
|
|
283
|
+
audio: initDynamicSpesific,
|
|
284
|
+
document: initDynamicSpesific,
|
|
285
|
+
animation: initDynamicSpesific,
|
|
286
|
+
voice: initDynamicSpesific,
|
|
287
|
+
videoNote: initDynamicSpesific,
|
|
288
|
+
sticker: initDynamicSpesific,
|
|
289
|
+
location: initDynamicSpesific,
|
|
290
|
+
contact: initDynamicSpesific
|
|
291
|
+
},
|
|
292
|
+
static: makeObject(listFuncStatic, initStatic),
|
|
293
|
+
staticIntent: makeObject(listFuncStaticIntent, initStaticIntent),
|
|
294
|
+
staticSession: makeObject(listFuncStaticSession, initStaticSession),
|
|
295
|
+
listFuncStatic,
|
|
296
|
+
listFuncStaticIntent,
|
|
297
|
+
listFuncStaticSession,
|
|
298
|
+
lenListFuncStatic: listFuncStatic.length,
|
|
299
|
+
lenListFuncStaticIntent: listFuncStaticIntent.length,
|
|
300
|
+
lenListFuncStaticSession: listFuncStaticSession.length
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
for (const file of files) {
|
|
304
|
+
const filePath = join(path, file)
|
|
305
|
+
const module = require(filePath) // eslint-disable-line
|
|
306
|
+
const exports = Object.values(module)
|
|
307
|
+
const Page = exports[0] as IPageBase // like StartPage. not have default export
|
|
308
|
+
if (Page === undefined) {
|
|
309
|
+
throw `file ${parse(filePath).name} must have export (not default) class page`
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// create if not exist and set name (without ext) according filePath
|
|
313
|
+
this.pageBuildData(Page, filePath)
|
|
314
|
+
|
|
315
|
+
// if the raw template writes directly to the Page class, do a compile
|
|
316
|
+
this.pageCompile(Page)
|
|
317
|
+
|
|
318
|
+
// sort route method
|
|
319
|
+
this.pageSorts(Page, pages)
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
this.log.info(`Finish load pages with total (${Object.keys(pages.all).length})`)
|
|
323
|
+
return pages
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
public async pageRoutes(ctx: Ctx, fromListener: `callback` | keyof typeof this.pages.dynamicSpesific): Promise<any | null> {
|
|
327
|
+
// to be reassigned
|
|
328
|
+
let Page: IPageBase | undefined
|
|
329
|
+
let res: { name: string; func: string } | undefined
|
|
330
|
+
|
|
331
|
+
// (static) priority O(1)
|
|
332
|
+
if (Page === undefined) {
|
|
333
|
+
const cd = ctx.callbackData
|
|
334
|
+
const mt = ctx.msgText
|
|
335
|
+
const mc = ctx.msgCaption
|
|
336
|
+
const ps = this.pages.static
|
|
337
|
+
|
|
338
|
+
for (let index = 0; index < this.pages.lenListFuncStatic; index++) {
|
|
339
|
+
const funcName = this.pages.listFuncStatic[index] as `animationCaption`
|
|
340
|
+
res = ps[funcName][cd || mt || mc]
|
|
341
|
+
if (res) {
|
|
342
|
+
break
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
if (res !== undefined) {
|
|
347
|
+
Page = this.pages.all[res.name]
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// (staticSession) x = method; O(x)
|
|
352
|
+
if (Page === undefined) {
|
|
353
|
+
const session = await ctx.sessionGet()
|
|
354
|
+
if (session) {
|
|
355
|
+
const mtd = session.method
|
|
356
|
+
const pss = this.pages.staticSession
|
|
357
|
+
|
|
358
|
+
for (let index = 0; index < this.pages.lenListFuncStaticSession; index++) {
|
|
359
|
+
const funcName = this.pages.listFuncStaticSession[index] as `animationCaption`
|
|
360
|
+
res = pss[funcName][mtd]
|
|
361
|
+
if (res) {
|
|
362
|
+
break
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
if (res !== undefined) {
|
|
367
|
+
Page = this.pages.all[res.name]
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// (staticIntent) x = startsWith; O(x)
|
|
373
|
+
if (Page === undefined) {
|
|
374
|
+
const cdi = ctx.callbackDataIntent
|
|
375
|
+
const mti = ctx.msgTextIntent
|
|
376
|
+
const mci = ctx.msgCaptionIntent
|
|
377
|
+
const psi = this.pages.staticIntent
|
|
378
|
+
|
|
379
|
+
for (let index = 0; index < this.pages.lenListFuncStaticIntent; index++) {
|
|
380
|
+
const funcName = this.pages.listFuncStaticIntent[index] as `animationCaptionCommandPayload`
|
|
381
|
+
res = psi[funcName][cdi || mti || mci]
|
|
382
|
+
if (res) {
|
|
383
|
+
break
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
if (res !== undefined) {
|
|
388
|
+
Page = this.pages.all[res.name]
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// run
|
|
393
|
+
if (Page !== undefined && res !== undefined) {
|
|
394
|
+
const initPage = new Page(ctx)
|
|
395
|
+
const initRoute = await initPage[res.func as keyof typeof initPage]?.()
|
|
396
|
+
return initRoute
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// (dynamicSpesific) O(n)
|
|
400
|
+
const arrFree = this.pages.dynamicSpesific[fromListener as keyof typeof this.pages.dynamicSpesific] || []
|
|
401
|
+
for (let index = 0; index < arrFree.length; index++) {
|
|
402
|
+
const value = arrFree[index]
|
|
403
|
+
if (value === undefined) {
|
|
404
|
+
continue
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
const Page = this.pages.all[value.func]
|
|
408
|
+
if (Page === undefined) {
|
|
409
|
+
continue
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
const initPage = new Page(ctx)
|
|
413
|
+
const initRoute = await initPage[value.name as keyof typeof initPage]?.()
|
|
414
|
+
// const initRoute = await initPage.route(value.name)
|
|
415
|
+
if (initRoute !== null) {
|
|
416
|
+
return initRoute // stop when return not null
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// (dynamic) O(n)
|
|
421
|
+
for (let index = 0; index < this.pages.dynamic.length; index++) {
|
|
422
|
+
const value = this.pages.dynamic[index]
|
|
423
|
+
if (value === undefined) {
|
|
424
|
+
continue
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
const Page = this.pages.all[value]
|
|
428
|
+
if (Page === undefined) {
|
|
429
|
+
continue
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
const initPage = new Page(ctx)
|
|
433
|
+
const initRoute = await initPage.free?.()
|
|
434
|
+
// const initRoute = await initPage.route(`free`)
|
|
435
|
+
if (initRoute !== null) {
|
|
436
|
+
return initRoute // stop when return not null
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
return null
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
public async hookBeforeRoute(ctx: Ctx, listenerName: string) {
|
|
444
|
+
// must be override
|
|
445
|
+
return true
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
public async hookErrorPage(ctx: Ctx, listenerName: string, error: any, isEdit: boolean) {
|
|
449
|
+
// must be override
|
|
450
|
+
return
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
public async hookErrorInputNotFoundPage(ctx: Ctx) {
|
|
454
|
+
// must be override
|
|
455
|
+
return
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
public async onMessage(ct: Context, listenerName: string): Promise<void> {
|
|
459
|
+
const ctx = this.initCtx(ct)
|
|
460
|
+
try {
|
|
461
|
+
const before = await this.hookBeforeRoute(ctx, listenerName)
|
|
462
|
+
if (before === false) {
|
|
463
|
+
return
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
const route = await this.pageRoutes(ctx, listenerName as any)
|
|
467
|
+
if (route === null) {
|
|
468
|
+
await this.hookErrorInputNotFoundPage(ctx)
|
|
469
|
+
}
|
|
470
|
+
} catch (error) {
|
|
471
|
+
await this.hookErrorPage(ctx, listenerName, error, false)
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
public async onCallbackQueryData(ct: Context): Promise<void> {
|
|
476
|
+
const ctx = this.initCtx(ct)
|
|
477
|
+
try {
|
|
478
|
+
// answer callback query first for fast response
|
|
479
|
+
await ctx.callbackQueryAnswer()
|
|
480
|
+
const before = await this.hookBeforeRoute(ctx, `onCallbackQueryData`)
|
|
481
|
+
if (before === false) {
|
|
482
|
+
return
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// Check all route
|
|
486
|
+
await this.pageRoutes(ctx, `callback`)
|
|
487
|
+
} catch (error) {
|
|
488
|
+
await this.hookErrorPage(ctx, `onCallbackQueryData`, error, true)
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
public async onMessageText(ct: Context): Promise<void> {
|
|
493
|
+
await this.onMessage(ct, `onMessageText`)
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
public async onMessagePhoto(ct: Context): Promise<void> {
|
|
497
|
+
await this.onMessage(ct, `onMessagePhoto`)
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
public async onMessageVideo(ct: Context): Promise<void> {
|
|
501
|
+
await this.onMessage(ct, `onMessageVideo`)
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
public async onMessageAudio(ct: Context): Promise<void> {
|
|
505
|
+
await this.onMessage(ct, `onMessageAudio`)
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
public async onMessageDocument(ct: Context): Promise<void> {
|
|
509
|
+
await this.onMessage(ct, `onMessageDocument`)
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
public async onMessageAnimation(ct: Context): Promise<void> {
|
|
513
|
+
await this.onMessage(ct, `onMessageAnimation`)
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
public async onMessageVoice(ct: Context): Promise<void> {
|
|
517
|
+
await this.onMessage(ct, `onMessageVoice`)
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
public async onMessageVideoNote(ct: Context): Promise<void> {
|
|
521
|
+
await this.onMessage(ct, `onMessageVideoNote`)
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
public async onMessageSticker(ct: Context): Promise<void> {
|
|
525
|
+
await this.onMessage(ct, `onMessageSticker`)
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
public async onMessageLocation(ct: Context): Promise<void> {
|
|
529
|
+
await this.onMessage(ct, `onMessageLocation`)
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
public async onMessageContact(ct: Context): Promise<void> {
|
|
533
|
+
await this.onMessage(ct, `onMessageContact`)
|
|
534
|
+
}
|
|
535
|
+
}
|