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,237 @@
|
|
|
1
|
+
import { TemplateEngine } from ".";
|
|
2
|
+
import { readFile, mkdir } from "node:fs/promises";
|
|
3
|
+
import { watch } from "chokidar";
|
|
4
|
+
import { basename, join, extname } from "node:path";
|
|
5
|
+
import { readdirSync, readFileSync, existsSync, statSync, writeFileSync } from "node:fs";
|
|
6
|
+
/** Template management system */
|
|
7
|
+
export class TemplateManager {
|
|
8
|
+
_map = new Map();
|
|
9
|
+
_engine = TemplateEngine;
|
|
10
|
+
_watcher = null;
|
|
11
|
+
_supportedExtensions = new Set([`.html`]);
|
|
12
|
+
_path;
|
|
13
|
+
_forceLanguage;
|
|
14
|
+
_isWatchEnabled;
|
|
15
|
+
constructor(options = {}) {
|
|
16
|
+
const { enableWatch = false, path = null, forceLanguage = null } = options;
|
|
17
|
+
this._path = path;
|
|
18
|
+
this._forceLanguage = forceLanguage;
|
|
19
|
+
this._isWatchEnabled = enableWatch;
|
|
20
|
+
this._validatePath();
|
|
21
|
+
this._initializeWatcher();
|
|
22
|
+
this._loadAllFiles();
|
|
23
|
+
}
|
|
24
|
+
/** Validate and ensure template directory exists. @returns Void */
|
|
25
|
+
_validatePath() {
|
|
26
|
+
try {
|
|
27
|
+
if (this._path === null || !existsSync(this._path)) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
const stats = statSync(this._path);
|
|
31
|
+
if (!stats.isDirectory()) {
|
|
32
|
+
throw new Error(`Template path is not a directory: ${this._path}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
throw new Error(`Failed to validate Template path: ${error}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/** Initialize file system watcher for hot reloading. @returns Void */
|
|
40
|
+
_initializeWatcher() {
|
|
41
|
+
if (this._path === null || !this._isWatchEnabled) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
this._watcher = watch(this._path, {
|
|
45
|
+
persistent: true,
|
|
46
|
+
ignoreInitial: true,
|
|
47
|
+
awaitWriteFinish: {
|
|
48
|
+
stabilityThreshold: 100,
|
|
49
|
+
pollInterval: 50
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
this._watcher.on(`change`, this._handleFileChange.bind(this));
|
|
53
|
+
this._watcher.on(`add`, this._handleFileAdd.bind(this));
|
|
54
|
+
this._watcher.on(`unlink`, this._handleFileDelete.bind(this));
|
|
55
|
+
this._watcher.on(`error`, this._handleWatchError.bind(this));
|
|
56
|
+
}
|
|
57
|
+
/** Handle file change events */
|
|
58
|
+
async _handleFileChange(filepath) {
|
|
59
|
+
const filename = basename(filepath);
|
|
60
|
+
if (this._isValidFile(filename)) {
|
|
61
|
+
await this._loadFile(filename, false);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/** Handle file add events */
|
|
65
|
+
async _handleFileAdd(filePath) {
|
|
66
|
+
const fileName = basename(filePath);
|
|
67
|
+
if (this._isValidFile(fileName)) {
|
|
68
|
+
await this._loadFile(fileName, false);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/** Handle file delete events */
|
|
72
|
+
_handleFileDelete(filePath) {
|
|
73
|
+
const fileName = basename(filePath);
|
|
74
|
+
const fileKey = this._getKey(fileName);
|
|
75
|
+
if (this._map.has(fileKey)) {
|
|
76
|
+
this._map.delete(fileKey);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/** Handle watcher errors */
|
|
80
|
+
_handleWatchError(error) {
|
|
81
|
+
// you can overrides
|
|
82
|
+
}
|
|
83
|
+
/** Load all Template files during initialization */
|
|
84
|
+
_loadAllFiles() {
|
|
85
|
+
if (this._path === null || !existsSync(this._path)) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const files = readdirSync(this._path, { encoding: `utf8` });
|
|
89
|
+
const validFiles = files.filter((file) => this._isValidFile(file));
|
|
90
|
+
for (const fileName of validFiles) {
|
|
91
|
+
this._loadFile(fileName, true);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/** Load and compile a single template file */
|
|
95
|
+
async _loadFile(filename, useSync = true) {
|
|
96
|
+
if (this._path === null) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
const path = join(this._path, filename);
|
|
100
|
+
const key = this._getKey(filename);
|
|
101
|
+
const raw = useSync ? readFileSync(path, { encoding: `utf8` }) : await readFile(path, { encoding: `utf8` });
|
|
102
|
+
this.compile(key, raw);
|
|
103
|
+
}
|
|
104
|
+
_isValidFile(filename) {
|
|
105
|
+
const extension = extname(filename).toLowerCase();
|
|
106
|
+
return this._supportedExtensions.has(extension);
|
|
107
|
+
}
|
|
108
|
+
_getKey(filename) {
|
|
109
|
+
return filename.replace(/\.[^/.]+$/, ``);
|
|
110
|
+
}
|
|
111
|
+
compile(key, rawTemplate, keepIfExist) {
|
|
112
|
+
const existing = this._map.get(key);
|
|
113
|
+
const compiled = existing || {};
|
|
114
|
+
let updated = false;
|
|
115
|
+
if (keepIfExist === undefined || keepIfExist?.message === false || existing?.message === undefined) {
|
|
116
|
+
compiled.message = TemplateEngine.compileMessages(rawTemplate);
|
|
117
|
+
updated = true;
|
|
118
|
+
}
|
|
119
|
+
if (keepIfExist === undefined || keepIfExist?.keyboard === false || existing?.keyboard === undefined) {
|
|
120
|
+
compiled.keyboard = TemplateEngine.compileKeyboards(rawTemplate);
|
|
121
|
+
updated = true;
|
|
122
|
+
}
|
|
123
|
+
if (updated) {
|
|
124
|
+
this._map.set(key, compiled);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Get compiled message template
|
|
129
|
+
* @param key Template identifier (filename without extension)
|
|
130
|
+
* @param baseId Base id attr from block
|
|
131
|
+
* @param language Target language code
|
|
132
|
+
* @param data Template variables
|
|
133
|
+
* @returns Rendered message string
|
|
134
|
+
*/
|
|
135
|
+
getMessage(key, baseId = `default`, language = `en`, data) {
|
|
136
|
+
const template = this._map.get(key);
|
|
137
|
+
const func = template?.message?.[baseId]?.[this._forceLanguage || language];
|
|
138
|
+
if (!func) {
|
|
139
|
+
return ``;
|
|
140
|
+
}
|
|
141
|
+
try {
|
|
142
|
+
return func(data);
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
return ``;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Get compiled keyboard template
|
|
150
|
+
* @param key Template identifier (filename without extension)
|
|
151
|
+
* @param baseId Base id attr from block
|
|
152
|
+
* @param language Target language code
|
|
153
|
+
* @param data Template variables
|
|
154
|
+
* @returns Array of rendered button labels
|
|
155
|
+
*/
|
|
156
|
+
getKeyboard(key, baseId = `default`, language = `en`, display = `inline`, data) {
|
|
157
|
+
const template = this._map.get(key);
|
|
158
|
+
const func = template?.keyboard?.[baseId]?.[this._forceLanguage || language]?.[display];
|
|
159
|
+
if (!func) {
|
|
160
|
+
return [];
|
|
161
|
+
}
|
|
162
|
+
try {
|
|
163
|
+
return func(data);
|
|
164
|
+
}
|
|
165
|
+
catch (error) {
|
|
166
|
+
return [];
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Update message template in memory
|
|
171
|
+
* @param key Template identifier (filename without extension)
|
|
172
|
+
* @param rawTemplate Raw template string
|
|
173
|
+
*/
|
|
174
|
+
setMessage(key, rawTemplate) {
|
|
175
|
+
this.compile(key, rawTemplate, { keyboard: true });
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Update keyboard template in memory
|
|
179
|
+
* @param key Template identifier (filename without extension)
|
|
180
|
+
* @param rawTemplate Raw template string
|
|
181
|
+
*/
|
|
182
|
+
setKeyboard(key, rawTemplate) {
|
|
183
|
+
this.compile(key, rawTemplate, { message: true });
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Write template to file and update cache
|
|
187
|
+
* @param filename Target filename (with extension)
|
|
188
|
+
* @param rawTemplate Template to write
|
|
189
|
+
* @returns Promise that resolves when file is written
|
|
190
|
+
*/
|
|
191
|
+
async write(filename, rawTemplate) {
|
|
192
|
+
if (this._path === null) {
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
const filePath = join(this._path, filename);
|
|
196
|
+
await mkdir(this._path, { recursive: true });
|
|
197
|
+
writeFileSync(filePath, rawTemplate, { encoding: `utf8` });
|
|
198
|
+
await this._loadFile(filename, false);
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Get list of available template
|
|
202
|
+
* @returns Array of template names
|
|
203
|
+
*/
|
|
204
|
+
getAvailable() {
|
|
205
|
+
return Array.from(this._map.keys());
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Check if template exists
|
|
209
|
+
* @param key Template identifier
|
|
210
|
+
* @returns True if template exists
|
|
211
|
+
*/
|
|
212
|
+
has(key) {
|
|
213
|
+
return this._map.has(key);
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Get cache statistics
|
|
217
|
+
* @returns Object with cache information
|
|
218
|
+
*/
|
|
219
|
+
getCacheStats() {
|
|
220
|
+
return {
|
|
221
|
+
templateCount: this._map.size,
|
|
222
|
+
totalSize: JSON.stringify(this._map.entries().toArray(), (_, value) => {
|
|
223
|
+
return typeof value === `function` ? value.toString() : value;
|
|
224
|
+
}).length
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Cleanup resources and close watchers
|
|
229
|
+
* @returns Promise void
|
|
230
|
+
*/
|
|
231
|
+
async destroy() {
|
|
232
|
+
if (this._watcher) {
|
|
233
|
+
await this._watcher.close();
|
|
234
|
+
}
|
|
235
|
+
this._map.clear();
|
|
236
|
+
}
|
|
237
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "gramstax",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"private": false,
|
|
5
|
+
"description": "A powerful Telegram bot framework with page-based routing and template system",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"telegram",
|
|
8
|
+
"bot",
|
|
9
|
+
"grammy",
|
|
10
|
+
"telegram-bot",
|
|
11
|
+
"bot-framework",
|
|
12
|
+
"gramstax"
|
|
13
|
+
],
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "https://github.com/gramstax/gramstax"
|
|
17
|
+
},
|
|
18
|
+
"bugs": {
|
|
19
|
+
"url": "https://github.com/gramstax/gramstax/issues"
|
|
20
|
+
},
|
|
21
|
+
"homepage": "https://github.com/gramstax/gramstax",
|
|
22
|
+
"author": "gramstax",
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"type": "module",
|
|
25
|
+
"main": "./dist/index.js",
|
|
26
|
+
"types": "./dist/index.d.ts",
|
|
27
|
+
"exports": {
|
|
28
|
+
".": {
|
|
29
|
+
"types": "./dist/index.d.ts",
|
|
30
|
+
"import": "./dist/index.js",
|
|
31
|
+
"default": "./dist/index.js"
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"files": [
|
|
35
|
+
"dist",
|
|
36
|
+
"src"
|
|
37
|
+
],
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@types/bun": "latest"
|
|
40
|
+
},
|
|
41
|
+
"peerDependencies": {
|
|
42
|
+
"typescript": "^5.9.3"
|
|
43
|
+
},
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"@keyv/redis": "^5.1.3",
|
|
46
|
+
"chokidar": "^4.0.3",
|
|
47
|
+
"grammy": "^1.38.3",
|
|
48
|
+
"keyv": "^5.5.3",
|
|
49
|
+
"logging-pretty": "^3.0.0"
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import LoggingPretty from "logging-pretty"
|
|
2
|
+
|
|
3
|
+
export abstract class BaseGeneral {
|
|
4
|
+
protected static log = new LoggingPretty()
|
|
5
|
+
protected static logError(error: any, func: string) {
|
|
6
|
+
// const name = typeof this.name != `string` ? this.toString().match(/class\s+([^\s{]+)/)?.[1] : this.name
|
|
7
|
+
this.log.error(`[${this.toString().match(/class\s+([^\s{]+)/)?.[1]}:${func}]: ${String(error)}`)
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// protected logError(error: any, func: string, name?: string) {
|
|
11
|
+
// log.error(`[${name || this.constructor.name}:${func}]: ${String(error)}`)
|
|
12
|
+
// }
|
|
13
|
+
|
|
14
|
+
protected logError(error: any, constructorName?: string, funcName?: string) {
|
|
15
|
+
;(this.constructor as any).log.error(`[${constructorName || this.constructor.name}:${funcName}] ${String(error)}`)
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { BasePage } from "./page"
|
|
2
|
+
import type { Ctx } from "../core/ctx"
|
|
3
|
+
|
|
4
|
+
export abstract class BaseGuard extends BasePage {
|
|
5
|
+
public pageData: { name: string; callbackData: (...args: any) => string | any } // because Guard not have static data like Page and public data so we assign to new property
|
|
6
|
+
public constructor(page: { ctx: Ctx; data?: any }) {
|
|
7
|
+
super(page.ctx)
|
|
8
|
+
this.pageData = page.data
|
|
9
|
+
}
|
|
10
|
+
}
|