gramstax 0.0.1 → 0.0.3

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.
Files changed (62) hide show
  1. package/dist/src/index.cjs +2 -0
  2. package/dist/src/index.cjs.map +1 -0
  3. package/dist/src/{core/bot.d.ts → index.d.cts} +501 -13
  4. package/dist/src/index.d.ts +1292 -5
  5. package/dist/src/index.js +2 -4
  6. package/dist/src/index.js.map +1 -0
  7. package/package.json +10 -9
  8. package/dist/package.json +0 -52
  9. package/dist/src/base/general.d.ts +0 -7
  10. package/dist/src/base/general.d.ts.map +0 -1
  11. package/dist/src/base/general.js +0 -15
  12. package/dist/src/base/guard.d.ts +0 -13
  13. package/dist/src/base/guard.d.ts.map +0 -1
  14. package/dist/src/base/guard.js +0 -8
  15. package/dist/src/base/index.d.ts +0 -4
  16. package/dist/src/base/index.d.ts.map +0 -1
  17. package/dist/src/base/index.js +0 -3
  18. package/dist/src/base/page.d.ts +0 -263
  19. package/dist/src/base/page.d.ts.map +0 -1
  20. package/dist/src/base/page.js +0 -805
  21. package/dist/src/cache/external.d.ts +0 -10
  22. package/dist/src/cache/external.d.ts.map +0 -1
  23. package/dist/src/cache/external.js +0 -16
  24. package/dist/src/cache/index.d.ts +0 -2
  25. package/dist/src/cache/index.d.ts.map +0 -1
  26. package/dist/src/cache/index.js +0 -1
  27. package/dist/src/core/bot.d.ts.map +0 -1
  28. package/dist/src/core/bot.js +0 -465
  29. package/dist/src/core/ctx.d.ts +0 -60
  30. package/dist/src/core/ctx.d.ts.map +0 -1
  31. package/dist/src/core/ctx.js +0 -175
  32. package/dist/src/core/index.d.ts +0 -3
  33. package/dist/src/core/index.d.ts.map +0 -1
  34. package/dist/src/core/index.js +0 -2
  35. package/dist/src/grammy/index.d.ts +0 -2
  36. package/dist/src/grammy/index.d.ts.map +0 -1
  37. package/dist/src/grammy/index.js +0 -1
  38. package/dist/src/index.d.ts.map +0 -1
  39. package/dist/src/template/engine.d.ts +0 -34
  40. package/dist/src/template/engine.d.ts.map +0 -1
  41. package/dist/src/template/engine.js +0 -122
  42. package/dist/src/template/index.d.ts +0 -3
  43. package/dist/src/template/index.d.ts.map +0 -1
  44. package/dist/src/template/index.js +0 -2
  45. package/dist/src/template/manager.d.ts +0 -111
  46. package/dist/src/template/manager.d.ts.map +0 -1
  47. package/dist/src/template/manager.js +0 -237
  48. package/src/base/general.ts +0 -17
  49. package/src/base/guard.ts +0 -10
  50. package/src/base/index.ts +0 -3
  51. package/src/base/page.ts +0 -1111
  52. package/src/cache/external.ts +0 -15
  53. package/src/cache/index.ts +0 -1
  54. package/src/core/bot.ts +0 -535
  55. package/src/core/ctx.ts +0 -177
  56. package/src/core/index.ts +0 -2
  57. package/src/grammy/index.ts +0 -1
  58. package/src/index.ts +0 -4
  59. package/src/template/engine.ts +0 -167
  60. package/src/template/index.ts +0 -2
  61. package/src/template/manager.ts +0 -280
  62. package/src/types/page.d.ts +0 -4
@@ -1,15 +0,0 @@
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
- }
@@ -1 +0,0 @@
1
- export * from "./external"
package/src/core/bot.ts DELETED
@@ -1,535 +0,0 @@
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
- }