telegix 1.1.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 +21 -0
- package/README.md +1534 -0
- package/index.d.ts +539 -0
- package/index.js +38 -0
- package/lib/album.js +57 -0
- package/lib/api.js +1840 -0
- package/lib/chataction.js +40 -0
- package/lib/cluster.js +68 -0
- package/lib/composer.js +419 -0
- package/lib/context.js +970 -0
- package/lib/errors.js +67 -0
- package/lib/format.js +115 -0
- package/lib/i18n.js +158 -0
- package/lib/inline-debounce.js +49 -0
- package/lib/inline.js +79 -0
- package/lib/markdownv2.js +29 -0
- package/lib/markup.js +321 -0
- package/lib/payment.js +91 -0
- package/lib/polling.js +101 -0
- package/lib/prompt.js +62 -0
- package/lib/ratelimit.js +59 -0
- package/lib/rich.js +609 -0
- package/lib/scenes.js +206 -0
- package/lib/serialize.js +141 -0
- package/lib/session.js +145 -0
- package/lib/telegix.js +176 -0
- package/lib/webapp.js +65 -0
- package/lib/webhook.js +86 -0
- package/package.json +42 -0
package/lib/errors.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Telegix - Telegram Bot API Error Classes
|
|
3
|
+
* @module telegix/errors
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export class TelegixError extends Error {
|
|
7
|
+
/**
|
|
8
|
+
* @param {string} message
|
|
9
|
+
*/
|
|
10
|
+
constructor(message) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.name = 'TelegixError';
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export class TelegramError extends TelegixError {
|
|
17
|
+
/**
|
|
18
|
+
* @param {object} response - Telegram API response
|
|
19
|
+
* @param {number} [response.error_code] - HTTP/Telegram error code (e.g. 400, 401, 403, 404, 429)
|
|
20
|
+
* @param {string} [response.description] - Description from Telegram
|
|
21
|
+
* @param {object} [response.parameters] - Extra parameters (e.g. retry_after, migrate_to_chat_id)
|
|
22
|
+
* @param {string} [method] - The Telegram method that was called
|
|
23
|
+
* @param {object} [payload] - The payload that was sent
|
|
24
|
+
*/
|
|
25
|
+
constructor(response, method = '', payload = {}) {
|
|
26
|
+
const errorCode = response?.error_code || 500;
|
|
27
|
+
const description = response?.description || 'Unknown Telegram API Error';
|
|
28
|
+
super(`Telegram API Error [${errorCode}]: ${description} (Method: ${method})`);
|
|
29
|
+
this.name = 'TelegramError';
|
|
30
|
+
this.errorCode = errorCode;
|
|
31
|
+
this.description = description;
|
|
32
|
+
this.parameters = response?.parameters || {};
|
|
33
|
+
this.method = method;
|
|
34
|
+
this.payload = payload;
|
|
35
|
+
|
|
36
|
+
if (this.parameters.retry_after) {
|
|
37
|
+
this.retryAfter = this.parameters.retry_after;
|
|
38
|
+
}
|
|
39
|
+
if (this.parameters.migrate_to_chat_id) {
|
|
40
|
+
this.migrateToChatId = this.parameters.migrate_to_chat_id;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export class NetworkError extends TelegixError {
|
|
46
|
+
/**
|
|
47
|
+
* @param {Error} error - Underlying network error
|
|
48
|
+
* @param {string} [method] - The Telegram method
|
|
49
|
+
*/
|
|
50
|
+
constructor(error, method = '') {
|
|
51
|
+
super(`Network Error while calling ${method}: ${error.message}`);
|
|
52
|
+
this.name = 'NetworkError';
|
|
53
|
+
this.cause = error;
|
|
54
|
+
this.method = method;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export class PollingError extends TelegixError {
|
|
59
|
+
/**
|
|
60
|
+
* @param {Error} error - Original error
|
|
61
|
+
*/
|
|
62
|
+
constructor(error) {
|
|
63
|
+
super(`Polling Error: ${error.message}`);
|
|
64
|
+
this.name = 'PollingError';
|
|
65
|
+
this.cause = error;
|
|
66
|
+
}
|
|
67
|
+
}
|
package/lib/format.js
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Telegix - Text Formatting Engine (HTML & MarkdownV2)
|
|
3
|
+
* @module telegix/format
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Escape HTML special characters for Telegram Bot API
|
|
8
|
+
* @param {string} text
|
|
9
|
+
* @returns {string}
|
|
10
|
+
*/
|
|
11
|
+
export function escapeHtml(text) {
|
|
12
|
+
if (text === null || text === undefined) return '';
|
|
13
|
+
return String(text)
|
|
14
|
+
.replace(/&/g, '&')
|
|
15
|
+
.replace(/</g, '<')
|
|
16
|
+
.replace(/>/g, '>');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Escape MarkdownV2 special characters for Telegram Bot API
|
|
21
|
+
* Characters: _ * [ ] ( ) ~ ` > # + - = | { } . !
|
|
22
|
+
* @param {string} text
|
|
23
|
+
* @returns {string}
|
|
24
|
+
*/
|
|
25
|
+
export function escapeMarkdown(text) {
|
|
26
|
+
if (text === null || text === undefined) return '';
|
|
27
|
+
return String(text).replace(/([_*\[\]()~`>#+\-=|{}.!\\])/g, '\\$1');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* HTML Formatting Helpers
|
|
32
|
+
*/
|
|
33
|
+
export const html = {
|
|
34
|
+
escape: escapeHtml,
|
|
35
|
+
bold: (text) => `<b>${escapeHtml(text)}</b>`,
|
|
36
|
+
italic: (text) => `<i>${escapeHtml(text)}</i>`,
|
|
37
|
+
underline: (text) => `<u>${escapeHtml(text)}</u>`,
|
|
38
|
+
strikethrough: (text) => `<s>${escapeHtml(text)}</s>`,
|
|
39
|
+
spoiler: (text) => `<span class="tg-spoiler">${escapeHtml(text)}</span>`,
|
|
40
|
+
code: (text) => `<code>${escapeHtml(text)}</code>`,
|
|
41
|
+
pre: (codeText, language = '') => {
|
|
42
|
+
const langAttr = language ? ` class="language-${escapeHtml(language)}"` : '';
|
|
43
|
+
return `<pre><code${langAttr}>${escapeHtml(codeText)}</code></pre>`;
|
|
44
|
+
},
|
|
45
|
+
link: (text, url) => `<a href="${escapeHtml(url)}">${escapeHtml(text)}</a>`,
|
|
46
|
+
mention: (text, userId) => `<a href="tg://user?id=${userId}">${escapeHtml(text)}</a>`,
|
|
47
|
+
customEmoji: (text, customEmojiId) => `<tg-emoji emoji-id="${customEmojiId}">${escapeHtml(text)}</tg-emoji>`,
|
|
48
|
+
quote: (text) => `<blockquote>${escapeHtml(text)}</blockquote>`,
|
|
49
|
+
expandableBlockquote: (text) => `<blockquote expandable>${escapeHtml(text)}</blockquote>`,
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* MarkdownV2 Formatting Helpers
|
|
54
|
+
*/
|
|
55
|
+
export const markdown = {
|
|
56
|
+
escape: escapeMarkdown,
|
|
57
|
+
bold: (text) => `*${escapeMarkdown(text)}*`,
|
|
58
|
+
italic: (text) => `_${escapeMarkdown(text)}_`,
|
|
59
|
+
underline: (text) => `__${escapeMarkdown(text)}__`,
|
|
60
|
+
strikethrough: (text) => `~${escapeMarkdown(text)}~`,
|
|
61
|
+
spoiler: (text) => `||${escapeMarkdown(text)}||`,
|
|
62
|
+
code: (text) => `\`${escapeMarkdown(text)}\``,
|
|
63
|
+
pre: (codeText, language = '') => `\`\`\`${language}\n${codeText.replace(/\\/g, '\\\\').replace(/`/g, '\\`')}\n\`\`\``,
|
|
64
|
+
link: (text, url) => `[${escapeMarkdown(text)}](${url.replace(/([)\\])/g, '\\$1')})`,
|
|
65
|
+
mention: (text, userId) => `[${escapeMarkdown(text)}](tg://user?id=${userId})`,
|
|
66
|
+
customEmoji: (text, customEmojiId) => ``,
|
|
67
|
+
quote: (text) => text.split('\n').map((line) => `>${escapeMarkdown(line)}`).join('\n'),
|
|
68
|
+
expandableBlockquote: (text) => `**>${escapeMarkdown(text)}||`,
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Tagged template literal for safe HTML formatting
|
|
73
|
+
* Example: fmt`Hello <b>${username}</b>! Your balance is ${100} Stars.`
|
|
74
|
+
* @param {TemplateStringsArray} strings
|
|
75
|
+
* @param {...any} values
|
|
76
|
+
* @returns {string}
|
|
77
|
+
*/
|
|
78
|
+
export function fmt(strings, ...values) {
|
|
79
|
+
let result = '';
|
|
80
|
+
for (let i = 0; i < strings.length; i++) {
|
|
81
|
+
result += strings[i];
|
|
82
|
+
if (i < values.length) {
|
|
83
|
+
const val = values[i];
|
|
84
|
+
// If it's already an HTML string with formatting or null/undefined
|
|
85
|
+
if (val === null || val === undefined) {
|
|
86
|
+
// do nothing
|
|
87
|
+
} else if (typeof val === 'object' && val.rawHtml) {
|
|
88
|
+
result += val.rawHtml;
|
|
89
|
+
} else {
|
|
90
|
+
result += escapeHtml(String(val));
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return result;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Attach helper methods directly to fmt
|
|
98
|
+
fmt.bold = html.bold;
|
|
99
|
+
fmt.italic = html.italic;
|
|
100
|
+
fmt.underline = html.underline;
|
|
101
|
+
fmt.strikethrough = html.strikethrough;
|
|
102
|
+
fmt.spoiler = html.spoiler;
|
|
103
|
+
fmt.code = html.code;
|
|
104
|
+
fmt.pre = html.pre;
|
|
105
|
+
fmt.link = html.link;
|
|
106
|
+
fmt.mention = html.mention;
|
|
107
|
+
fmt.customEmoji = html.customEmoji;
|
|
108
|
+
fmt.quote = html.quote;
|
|
109
|
+
fmt.expandableBlockquote = html.expandableBlockquote;
|
|
110
|
+
fmt.escape = escapeHtml;
|
|
111
|
+
fmt.html = html;
|
|
112
|
+
fmt.markdown = markdown;
|
|
113
|
+
fmt.raw = (str) => ({ rawHtml: String(str) });
|
|
114
|
+
|
|
115
|
+
export const Format = fmt;
|
package/lib/i18n.js
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Telegix - Internationalization (i18n) Middleware & Helper
|
|
3
|
+
* @module telegix/i18n
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
function getNestedValue(obj, path) {
|
|
7
|
+
if (!obj || typeof obj !== 'object') return undefined;
|
|
8
|
+
if (obj[path] !== undefined) return obj[path];
|
|
9
|
+
|
|
10
|
+
const keys = path.split('.');
|
|
11
|
+
let current = obj;
|
|
12
|
+
for (const k of keys) {
|
|
13
|
+
if (current && typeof current === 'object' && k in current) {
|
|
14
|
+
current = current[k];
|
|
15
|
+
} else {
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return current;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export class I18n {
|
|
23
|
+
/**
|
|
24
|
+
* @param {object} [options]
|
|
25
|
+
* @param {string} [options.defaultLocale='en']
|
|
26
|
+
* @param {object} [options.translations={}]
|
|
27
|
+
* @param {Function} [options.localeFn]
|
|
28
|
+
* @param {boolean} [options.useSession=true]
|
|
29
|
+
*/
|
|
30
|
+
constructor(options = {}) {
|
|
31
|
+
this.defaultLocale = options.defaultLocale || 'en';
|
|
32
|
+
this.translations = options.translations || {};
|
|
33
|
+
this.useSession = options.useSession !== false;
|
|
34
|
+
this.localeFn =
|
|
35
|
+
options.localeFn ||
|
|
36
|
+
((ctx) => {
|
|
37
|
+
if (this.useSession && ctx.session) {
|
|
38
|
+
const sessionLocale = ctx.session.__locale || ctx.session.locale || ctx.session.language;
|
|
39
|
+
if (sessionLocale) return sessionLocale;
|
|
40
|
+
}
|
|
41
|
+
return ctx.from?.language_code || this.defaultLocale;
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Register or add translations for a locale
|
|
47
|
+
* @param {string} locale
|
|
48
|
+
* @param {object} dict
|
|
49
|
+
*/
|
|
50
|
+
addTranslation(locale, dict) {
|
|
51
|
+
this.translations[locale] = {
|
|
52
|
+
...(this.translations[locale] || {}),
|
|
53
|
+
...dict,
|
|
54
|
+
};
|
|
55
|
+
return this;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Add multiple translation dictionaries
|
|
60
|
+
* @param {Record<string, object>} translations
|
|
61
|
+
*/
|
|
62
|
+
addTranslations(translations = {}) {
|
|
63
|
+
for (const [locale, dict] of Object.entries(translations)) {
|
|
64
|
+
this.addTranslation(locale, dict);
|
|
65
|
+
}
|
|
66
|
+
return this;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Translate a key with optional interpolation params and pluralization
|
|
71
|
+
* @param {string} locale
|
|
72
|
+
* @param {string} key
|
|
73
|
+
* @param {object} [params]
|
|
74
|
+
* @returns {string}
|
|
75
|
+
*/
|
|
76
|
+
t(locale, key, params = {}) {
|
|
77
|
+
const activeDict = this.translations[locale] || {};
|
|
78
|
+
const defaultDict = this.translations[this.defaultLocale] || {};
|
|
79
|
+
|
|
80
|
+
let val = getNestedValue(activeDict, key);
|
|
81
|
+
if (val === undefined) {
|
|
82
|
+
val = getNestedValue(defaultDict, key);
|
|
83
|
+
}
|
|
84
|
+
if (val === undefined) {
|
|
85
|
+
val = key;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Handle pluralization if val is an object (e.g. { one: '1 item', other: '{{count}} items' })
|
|
89
|
+
if (typeof val === 'object' && val !== null) {
|
|
90
|
+
const count = Number(params.count);
|
|
91
|
+
if (!isNaN(count)) {
|
|
92
|
+
if (count === 0 && val.zero) {
|
|
93
|
+
val = val.zero;
|
|
94
|
+
} else if (count === 1 && val.one) {
|
|
95
|
+
val = val.one;
|
|
96
|
+
} else if (val.other) {
|
|
97
|
+
val = val.other;
|
|
98
|
+
} else {
|
|
99
|
+
val = JSON.stringify(val);
|
|
100
|
+
}
|
|
101
|
+
} else {
|
|
102
|
+
val = JSON.stringify(val);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
let text = String(val);
|
|
107
|
+
|
|
108
|
+
// Interpolate variables like {{name}} or {name}
|
|
109
|
+
for (const [k, v] of Object.entries(params)) {
|
|
110
|
+
text = text.replace(new RegExp(`{{\\s*${k}\\s*}}`, 'g'), String(v));
|
|
111
|
+
text = text.replace(new RegExp(`{\\s*${k}\\s*}`, 'g'), String(v));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return text;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Create i18n middleware for Telegix
|
|
119
|
+
*/
|
|
120
|
+
middleware() {
|
|
121
|
+
return (ctx, next) => {
|
|
122
|
+
let rawLocale = this.localeFn(ctx) || this.defaultLocale;
|
|
123
|
+
// If locale like 'en-US' or 'id-ID', check exact first, then language prefix
|
|
124
|
+
let activeLocale = this.defaultLocale;
|
|
125
|
+
if (this.translations[rawLocale]) {
|
|
126
|
+
activeLocale = rawLocale;
|
|
127
|
+
} else {
|
|
128
|
+
const prefix = String(rawLocale).split('-')[0].toLowerCase();
|
|
129
|
+
if (this.translations[prefix]) {
|
|
130
|
+
activeLocale = prefix;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const self = this;
|
|
135
|
+
const i18nContext = {
|
|
136
|
+
get locale() {
|
|
137
|
+
return activeLocale;
|
|
138
|
+
},
|
|
139
|
+
set locale(newLocale) {
|
|
140
|
+
activeLocale = newLocale;
|
|
141
|
+
if (self.useSession && ctx.session) {
|
|
142
|
+
ctx.session.__locale = newLocale;
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
setLocale(newLocale) {
|
|
146
|
+
this.locale = newLocale;
|
|
147
|
+
return activeLocale;
|
|
148
|
+
},
|
|
149
|
+
t: (key, params) => self.t(activeLocale, key, params),
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
ctx.i18n = i18nContext;
|
|
153
|
+
ctx.t = (key, params) => i18nContext.t(key, params);
|
|
154
|
+
|
|
155
|
+
return next();
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Telegix - Inline Query Debouncer & Cache Helper
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const queryCache = new Map();
|
|
6
|
+
const debounceTimers = new Map();
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Middleware or helper to debounce inline queries and cache results
|
|
10
|
+
* @param {object} [options] - { windowMs?: number, cacheTtlMs?: number }
|
|
11
|
+
*/
|
|
12
|
+
export function inlineDebounceMiddleware(options = {}) {
|
|
13
|
+
const windowMs = options.windowMs || 300;
|
|
14
|
+
const cacheTtlMs = options.cacheTtlMs || 60000;
|
|
15
|
+
|
|
16
|
+
return async (ctx, next) => {
|
|
17
|
+
if (!ctx.inlineQuery) {
|
|
18
|
+
return next();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const userId = ctx.from?.id;
|
|
22
|
+
const query = ctx.inlineQuery.query || '';
|
|
23
|
+
if (!userId) return next();
|
|
24
|
+
|
|
25
|
+
const cacheKey = `${userId}:${query}`;
|
|
26
|
+
const now = Date.now();
|
|
27
|
+
|
|
28
|
+
// Check cache
|
|
29
|
+
if (queryCache.has(cacheKey)) {
|
|
30
|
+
const cached = queryCache.get(cacheKey);
|
|
31
|
+
if (now - cached.timestamp < cacheTtlMs) {
|
|
32
|
+
return ctx.answerInlineQuery(cached.results, cached.options);
|
|
33
|
+
} else {
|
|
34
|
+
queryCache.delete(cacheKey);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Attach cache helper to ctx
|
|
39
|
+
ctx.cacheInlineResults = (results, extraOptions = {}) => {
|
|
40
|
+
queryCache.set(cacheKey, {
|
|
41
|
+
results,
|
|
42
|
+
options: extraOptions,
|
|
43
|
+
timestamp: Date.now(),
|
|
44
|
+
});
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
return next();
|
|
48
|
+
};
|
|
49
|
+
}
|
package/lib/inline.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Telegix - Inline Query Pagination & Result Builders
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export class InlineQueryResultBuilder {
|
|
6
|
+
static article(id, title, messageText, options = {}) {
|
|
7
|
+
return {
|
|
8
|
+
type: 'article',
|
|
9
|
+
id: String(id),
|
|
10
|
+
title,
|
|
11
|
+
input_message_content: {
|
|
12
|
+
message_text: messageText,
|
|
13
|
+
parse_mode: options.parseMode || 'HTML',
|
|
14
|
+
...options.inputMessageContent,
|
|
15
|
+
},
|
|
16
|
+
description: options.description,
|
|
17
|
+
thumb_url: options.thumbUrl,
|
|
18
|
+
thumb_width: options.thumbWidth,
|
|
19
|
+
thumb_height: options.thumbHeight,
|
|
20
|
+
reply_markup: options.replyMarkup,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
static photo(id, photoUrl, options = {}) {
|
|
25
|
+
return {
|
|
26
|
+
type: 'photo',
|
|
27
|
+
id: String(id),
|
|
28
|
+
photo_url: photoUrl,
|
|
29
|
+
thumb_url: options.thumbUrl || photoUrl,
|
|
30
|
+
caption: options.caption,
|
|
31
|
+
parse_mode: options.parseMode || 'HTML',
|
|
32
|
+
caption_entities: options.captionEntities,
|
|
33
|
+
description: options.description,
|
|
34
|
+
title: options.title,
|
|
35
|
+
reply_markup: options.replyMarkup,
|
|
36
|
+
photo_width: options.photoWidth,
|
|
37
|
+
photo_height: options.photoHeight,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
static document(id, documentUrl, title, options = {}) {
|
|
42
|
+
return {
|
|
43
|
+
type: 'document',
|
|
44
|
+
id: String(id),
|
|
45
|
+
title,
|
|
46
|
+
document_url: documentUrl,
|
|
47
|
+
mime_type: options.mimeType || 'application/pdf',
|
|
48
|
+
caption: options.caption,
|
|
49
|
+
description: options.description,
|
|
50
|
+
reply_markup: options.replyMarkup,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Helper to paginate and answer inline queries easily
|
|
57
|
+
* @param {object} ctx - Context
|
|
58
|
+
* @param {Array} items - All items to paginate
|
|
59
|
+
* @param {Function} formatterFn - Function(item, index) returning InlineQueryResult
|
|
60
|
+
* @param {object} [options] - { limit: 10, cacheTime: 300, isPersonal: true }
|
|
61
|
+
*/
|
|
62
|
+
export async function paginateInlineQuery(ctx, items, formatterFn, options = {}) {
|
|
63
|
+
const limit = options.limit || 10;
|
|
64
|
+
const cacheTime = options.cacheTime !== undefined ? options.cacheTime : 300;
|
|
65
|
+
const isPersonal = options.isPersonal !== undefined ? options.isPersonal : true;
|
|
66
|
+
|
|
67
|
+
const offset = parseInt(ctx.inlineQuery?.offset || '0', 10) || 0;
|
|
68
|
+
const pageItems = items.slice(offset, offset + limit);
|
|
69
|
+
|
|
70
|
+
const results = pageItems.map((item, index) => formatterFn(item, offset + index));
|
|
71
|
+
const nextOffset = offset + limit < items.length ? String(offset + limit) : '';
|
|
72
|
+
|
|
73
|
+
return ctx.answerInlineQuery(results, {
|
|
74
|
+
next_offset: nextOffset,
|
|
75
|
+
cache_time: cacheTime,
|
|
76
|
+
is_personal: isPersonal,
|
|
77
|
+
...options.extra,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Telegix - MarkdownV2 Escape Utility
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Escapes special characters for Telegram MarkdownV2 format.
|
|
7
|
+
* Characters to escape: _ * [ ] ( ) ~ ` > # + - = | { } . !
|
|
8
|
+
* @param {string} str - Raw text string to escape
|
|
9
|
+
* @returns {string} Escaped string safe for MarkdownV2
|
|
10
|
+
*/
|
|
11
|
+
export function escapeMarkdownV2(str) {
|
|
12
|
+
if (typeof str !== 'string') return '';
|
|
13
|
+
return str.replace(/[_*[\]()~`>#+\-=|{}.!]/g, '\\$&');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Helper to build safe MarkdownV2 snippets
|
|
18
|
+
*/
|
|
19
|
+
export const mdv2 = {
|
|
20
|
+
escape: escapeMarkdownV2,
|
|
21
|
+
bold: (text) => `*${escapeMarkdownV2(text)}*`,
|
|
22
|
+
italic: (text) => `_${escapeMarkdownV2(text)}_`,
|
|
23
|
+
underline: (text) => `__${escapeMarkdownV2(text)}__`,
|
|
24
|
+
strikethrough: (text) => `~${escapeMarkdownV2(text)}~`,
|
|
25
|
+
spoiler: (text) => `||${escapeMarkdownV2(text)}||`,
|
|
26
|
+
code: (text) => `\`${text.replace(/`/g, '\\`')}\``,
|
|
27
|
+
pre: (text, language = '') => `\`\`\`${language}\n${text}\n\`\`\``,
|
|
28
|
+
link: (text, url) => `[${escapeMarkdownV2(text)}](${url})`,
|
|
29
|
+
};
|