@codebam/cf-workers-telegram-bot 12.6.1 → 12.6.4

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/README.md CHANGED
@@ -119,6 +119,33 @@ npx wrangler deploy
119
119
 
120
120
  To automate deployments, use the [Wrangler Action](https://github.com/cloudflare/wrangler-action) or Cloudflare's built-in [GitHub integration](https://developers.cloudflare.com/workers/ci-cd/github-actions/).
121
121
 
122
+ ## Development
123
+
124
+ ### Setup
125
+
126
+ 1. **Install dependencies**:
127
+ ```sh
128
+ npm install
129
+ ```
130
+
131
+ 2. **Set up Git hooks**:
132
+ This project uses custom Git hooks for quality control. Run the following script to enable them:
133
+ ```sh
134
+ ./setup_hooks.sh
135
+ ```
136
+
137
+ ### Scripts
138
+
139
+ - `npm run lint`: Run ESLint on the source code.
140
+ - `npm run format`: Format the code using Prettier.
141
+ - `npm run build`: Compile TypeScript and run type checks.
142
+ - `npm run test`: Run unit tests with Vitest.
143
+ - `npm run lint:all`: Run linting for the root project and all subprojects.
144
+ - `npm run build:all`: Run build for the root project and all subprojects.
145
+ - `npm run test:all`: Run tests for the root project and all subprojects.
146
+
147
+ The pre-commit hook automatically runs formatting and linting on staged files (via `lint-staged`), followed by a full project type check and tests before every commit.
148
+
122
149
  ## API Documentation
123
150
 
124
151
  Detailed API documentation is available at [cf-workers-telegram-bot.codebam.ca](https://cf-workers-telegram-bot.codebam.ca).
@@ -36,7 +36,7 @@ export class HistoryManager {
36
36
  history.push({ role: 'assistant', content: response });
37
37
  const trimmedHistory = history.slice(-20);
38
38
  await this.kv.put(this.getKey(userId, threadId), JSON.stringify(trimmedHistory), {
39
- expirationTtl: 86400
39
+ expirationTtl: 86400,
40
40
  });
41
41
  }
42
42
  /**
@@ -9,7 +9,7 @@ export interface TelegramApiBaseParams {
9
9
  /** Interface for message parameters */
10
10
  export interface SendMessageParams extends TelegramApiBaseParams {
11
11
  text: string;
12
- parse_mode: string;
12
+ parse_mode?: string;
13
13
  reply_to_message_id?: number | string;
14
14
  disable_web_page_preview?: boolean;
15
15
  disable_notification?: boolean;
@@ -1,5 +1,5 @@
1
1
  import { Update as TelegramUpdate, InlineQueryResult as TelegramInlineQueryResult } from '@grammyjs/types';
2
- import TelegramApi from './telegram_api.js';
2
+ import TelegramApi, { SendMessageParams, SendPhotoParams, SendVideoParams, SendVoiceParams, SendMessageDraftParams } from './telegram_api.js';
3
3
  import TelegramBot from './telegram_bot.js';
4
4
  /** Class representing the context of execution */
5
5
  export default class TelegramExecutionContext {
@@ -83,7 +83,7 @@ export default class TelegramExecutionContext {
83
83
  * Helper to handle business connection fallbacks
84
84
  */
85
85
  private withBusinessFallback;
86
- replyVideo(video: string, options?: Record<string, number | string | boolean>): Promise<Response | null>;
86
+ replyVideo(video: string, options?: Partial<SendVideoParams>): Promise<Response | null>;
87
87
  /**
88
88
  * Get File from telegram file_id
89
89
  * @param file_id - telegram file_id
@@ -97,7 +97,7 @@ export default class TelegramExecutionContext {
97
97
  * @param options - any additional options to pass to sendPhoto
98
98
  * @returns Promise with the API response
99
99
  */
100
- replyPhoto(photo: string, caption?: string, options?: Record<string, number | string | boolean>): Promise<Response | null>;
100
+ replyPhoto(photo: string, caption?: string, options?: Partial<SendPhotoParams>): Promise<Response | null>;
101
101
  /**
102
102
  * Reply to the last message with a voice message
103
103
  * @param voice - url or file_id to voice
@@ -105,7 +105,7 @@ export default class TelegramExecutionContext {
105
105
  * @param options - any additional options to pass to sendVoice
106
106
  * @returns Promise with the API response
107
107
  */
108
- replyVoice(voice: string, caption?: string, options?: Record<string, number | string | boolean>): Promise<Response | null>;
108
+ replyVoice(voice: string, caption?: string, options?: Partial<SendVoiceParams>): Promise<Response | null>;
109
109
  /**
110
110
  * Send typing in a chat
111
111
  * @returns Promise with the API response
@@ -166,8 +166,8 @@ export default class TelegramExecutionContext {
166
166
  * @param options - any additional options to pass to sendMessage/editMessageText
167
167
  * @returns Promise with the API response
168
168
  */
169
- streamReply(message: string, draft_id: number, parse_mode?: string, options?: Record<string, number | string | boolean | object>, finish?: boolean): Promise<Response | null>;
170
- reply(message: string, parse_mode?: string, reply?: boolean, options?: Record<string, number | string | boolean>): Promise<Response | null>;
169
+ streamReply(message: string, draft_id: number, parse_mode?: string, options?: Partial<SendMessageDraftParams>, finish?: boolean): Promise<Response | null>;
170
+ reply(message: string, parse_mode?: string, reply?: boolean, options?: Partial<SendMessageParams>): Promise<Response | null>;
171
171
  /**
172
172
  * Send an invoice for Telegram Stars
173
173
  * @param title - product name
@@ -52,10 +52,7 @@ export default class TelegramExecutionContext {
52
52
  * @returns True if the sender is a bot
53
53
  */
54
54
  get isBot() {
55
- return (this.update.message?.from?.is_bot ??
56
- this.update.business_message?.from?.is_bot ??
57
- this.update.guest_message?.from?.is_bot ??
58
- false);
55
+ return (this.update.message?.from?.is_bot ?? this.update.business_message?.from?.is_bot ?? this.update.guest_message?.from?.is_bot ?? false);
59
56
  }
60
57
  /**
61
58
  * Parse arguments from the update
@@ -66,7 +63,7 @@ export default class TelegramExecutionContext {
66
63
  case 'message':
67
64
  case 'business_message':
68
65
  case 'guest_message':
69
- return (this.update.message?.text ?? this.update.business_message?.text ?? this.update.guest_message?.text)?.toString().split(' ') ?? [];
66
+ return ((this.update.message?.text ?? this.update.business_message?.text ?? this.update.guest_message?.text)?.toString().split(' ') ?? []);
70
67
  case 'inline':
71
68
  return this.update.inline_query?.query.split(' ') ?? [];
72
69
  default:
@@ -97,7 +94,7 @@ export default class TelegramExecutionContext {
97
94
  try {
98
95
  const response = await this.api.getBusinessConnection(this.bot.api.toString(), connectionId);
99
96
  if (response.status === 200) {
100
- const json = await response.json();
97
+ const json = (await response.json());
101
98
  if (json.ok && json.result) {
102
99
  ownerId = json.result.user?.id || json.result.user_chat_id;
103
100
  if (ownerId) {
@@ -217,7 +214,7 @@ export default class TelegramExecutionContext {
217
214
  try {
218
215
  const response = await this.api.getBusinessConnection(this.bot.api.toString(), connectionId);
219
216
  if (response.status === 200) {
220
- const json = await response.json();
217
+ const json = (await response.json());
221
218
  if (json.ok && json.result) {
222
219
  ownerId = json.result.user?.id || json.result.user_chat_id;
223
220
  if (ownerId) {
@@ -278,7 +275,9 @@ export default class TelegramExecutionContext {
278
275
  return await this.api.answerInline(this.bot.api.toString(), {
279
276
  ...options,
280
277
  inline_query_id: this.update.inline_query?.id.toString() ?? '',
281
- results: [{ type: 'video', id: crypto.randomUUID(), video_url: video, mime_type: 'video/mp4', thumbnail_url: video, title: 'Video' }],
278
+ results: [
279
+ { type: 'video', id: crypto.randomUUID(), video_url: video, mime_type: 'video/mp4', thumbnail_url: video, title: 'Video' },
280
+ ],
282
281
  });
283
282
  }
284
283
  return await this.api.sendVideo(this.bot.api.toString(), params);
@@ -377,7 +376,14 @@ export default class TelegramExecutionContext {
377
376
  if (this.update_type === 'inline') {
378
377
  return await this.api.answerInline(this.bot.api.toString(), {
379
378
  inline_query_id: this.update.inline_query?.id.toString() ?? '',
380
- results: [{ type: 'article', id: crypto.randomUUID(), title: title ?? '', input_message_content: { message_text: message, parse_mode: parse_mode } }],
379
+ results: [
380
+ {
381
+ type: 'article',
382
+ id: crypto.randomUUID(),
383
+ title: title ?? '',
384
+ input_message_content: { message_text: message, parse_mode: parse_mode },
385
+ },
386
+ ],
381
387
  });
382
388
  }
383
389
  return null;
@@ -400,7 +406,12 @@ export default class TelegramExecutionContext {
400
406
  * @returns Promise with the API response
401
407
  */
402
408
  async answerGuestQueryText(message, parse_mode = '') {
403
- return await this.answerGuestQuery({ type: 'article', id: crypto.randomUUID(), title: 'Response', input_message_content: { message_text: message, parse_mode: parse_mode } });
409
+ return await this.answerGuestQuery({
410
+ type: 'article',
411
+ id: crypto.randomUUID(),
412
+ title: 'Response',
413
+ input_message_content: { message_text: message, parse_mode: parse_mode },
414
+ });
404
415
  }
405
416
  /**
406
417
  * Answer a guest query with a photo
@@ -410,7 +421,14 @@ export default class TelegramExecutionContext {
410
421
  * @returns Promise with the API response
411
422
  */
412
423
  async answerGuestQueryPhoto(photo, caption = '', parse_mode = '') {
413
- return await this.answerGuestQuery({ type: 'photo', id: crypto.randomUUID(), photo_url: photo, thumbnail_url: photo, caption, parse_mode: parse_mode });
424
+ return await this.answerGuestQuery({
425
+ type: 'photo',
426
+ id: crypto.randomUUID(),
427
+ photo_url: photo,
428
+ thumbnail_url: photo,
429
+ caption,
430
+ parse_mode: parse_mode,
431
+ });
414
432
  }
415
433
  /**
416
434
  * Answer a guest query with a video
@@ -420,7 +438,16 @@ export default class TelegramExecutionContext {
420
438
  * @returns Promise with the API response
421
439
  */
422
440
  async answerGuestQueryVideo(video, caption = '', parse_mode = '') {
423
- return await this.answerGuestQuery({ type: 'video', id: crypto.randomUUID(), video_url: video, mime_type: 'video/mp4', thumbnail_url: video, title: 'Video', caption, parse_mode: parse_mode });
441
+ return await this.answerGuestQuery({
442
+ type: 'video',
443
+ id: crypto.randomUUID(),
444
+ video_url: video,
445
+ mime_type: 'video/mp4',
446
+ thumbnail_url: video,
447
+ title: 'Video',
448
+ caption,
449
+ parse_mode: parse_mode,
450
+ });
424
451
  }
425
452
  /**
426
453
  * Answer a guest query with a voice message
@@ -430,7 +457,14 @@ export default class TelegramExecutionContext {
430
457
  * @returns Promise with the API response
431
458
  */
432
459
  async answerGuestQueryVoice(voice, caption = '', parse_mode = '') {
433
- return await this.answerGuestQuery({ type: 'voice', id: crypto.randomUUID(), voice_url: voice, title: 'Voice', caption, parse_mode: parse_mode });
460
+ return await this.answerGuestQuery({
461
+ type: 'voice',
462
+ id: crypto.randomUUID(),
463
+ voice_url: voice,
464
+ title: 'Voice',
465
+ caption,
466
+ parse_mode: parse_mode,
467
+ });
434
468
  }
435
469
  /** Map of draft IDs to message IDs for streaming */
436
470
  drafts = new Map();
package/dist/utils.js CHANGED
@@ -21,7 +21,7 @@ export async function markdownToHtml(s) {
21
21
  let result = '';
22
22
  for (let i = 0; i < items.length; i++) {
23
23
  const item = items[i];
24
- const prefix = ordered ? `${(start !== '' && start !== undefined) ? Number(start) + i : i + 1}. ` : '• ';
24
+ const prefix = ordered ? `${start !== '' && start !== undefined ? Number(start) + i : i + 1}. ` : '• ';
25
25
  result += `${prefix}${renderer.listitem(item)}\n`;
26
26
  }
27
27
  return result;
@@ -50,8 +50,21 @@ export async function markdownToHtml(s) {
50
50
  // html tag pass-through for supported tags or escaping
51
51
  renderer.html = ({ text }) => {
52
52
  const allowedTags = [
53
- 'b', 'strong', 'i', 'em', 'u', 'ins', 's', 'strike', 'del',
54
- 'span', 'tg-spoiler', 'a', 'code', 'pre', 'blockquote'
53
+ 'b',
54
+ 'strong',
55
+ 'i',
56
+ 'em',
57
+ 'u',
58
+ 'ins',
59
+ 's',
60
+ 'strike',
61
+ 'del',
62
+ 'span',
63
+ 'tg-spoiler',
64
+ 'a',
65
+ 'code',
66
+ 'pre',
67
+ 'blockquote',
55
68
  ];
56
69
  const match = /^<\/?([a-z0-9-]+)(?:\s+[^>]*)?>/i.exec(text);
57
70
  if (match) {
@@ -87,9 +100,9 @@ export const fetchTool = {
87
100
  url: { type: 'string', description: 'The URL to fetch' },
88
101
  method: { type: 'string', enum: ['GET', 'POST', 'PUT', 'DELETE'], default: 'GET' },
89
102
  headers: { type: 'object', description: 'HTTP headers to include in the request' },
90
- body: { type: 'string', description: 'The request body' }
103
+ body: { type: 'string', description: 'The request body' },
91
104
  },
92
- required: ['url']
105
+ required: ['url'],
93
106
  },
94
107
  function: async ({ url, method, headers, body }) => {
95
108
  try {
@@ -97,9 +110,9 @@ export const fetchTool = {
97
110
  method: method || 'GET',
98
111
  headers: {
99
112
  'User-Agent': 'Mozilla/5.0 (Cloudflare Worker Telegram Bot)',
100
- ...headers
113
+ ...headers,
101
114
  },
102
- body: body ? (typeof body === 'string' ? body : JSON.stringify(body)) : undefined
115
+ body: body ? (typeof body === 'string' ? body : JSON.stringify(body)) : undefined,
103
116
  });
104
117
  const text = await res.text();
105
118
  return text.slice(0, 10000);
@@ -107,5 +120,5 @@ export const fetchTool = {
107
120
  catch (e) {
108
121
  return `Error executing fetch: ${String(e)}`;
109
122
  }
110
- }
123
+ },
111
124
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codebam/cf-workers-telegram-bot",
3
- "version": "12.6.1",
3
+ "version": "12.6.4",
4
4
  "description": "serverless telegram bot on cf workers",
5
5
  "main": "./dist/main.js",
6
6
  "module": "./dist/main.js",
@@ -22,11 +22,18 @@
22
22
  },
23
23
  "scripts": {
24
24
  "build": "tsc --project tsconfig.json",
25
+ "build:all": "npm run build && npm run build --prefix ai-workflow && npm run build --prefix consumer && npm run build --prefix webapp",
25
26
  "lint": "eslint src",
27
+ "lint:all": "npm run lint && npm run lint --prefix ai-workflow && npm run lint --prefix consumer && npm run lint --prefix webapp",
26
28
  "test": "vitest --config vitest.config.js",
29
+ "test:all": "npm test && npm test --prefix ai-workflow && npm test --prefix consumer && npm test --prefix webapp",
30
+ "format": "prettier --write src test *.json *.js *.mjs",
31
+ "format:check": "prettier --check src test *.json *.js *.mjs",
27
32
  "docs": "typedoc --options typedoc.json",
28
33
  "deploy:docs": "npm run docs && wrangler pages deploy docs",
29
- "ncu": "npx npm-check-updates -i --root -u"
34
+ "ncu": "npx npm-check-updates -u --root",
35
+ "ncu:interactive": "npx npm-check-updates -i --root",
36
+ "prepare": "./setup_hooks.sh"
30
37
  },
31
38
  "author": "codebam",
32
39
  "license": "Apache-2.0",
@@ -35,13 +42,15 @@
35
42
  "url": "git+https://github.com/codebam/cf-workers-telegram-bot.git"
36
43
  },
37
44
  "devDependencies": {
38
- "@cloudflare/workers-types": "^4.20260511.1",
45
+ "@cloudflare/vitest-pool-workers": "^0.16.6",
39
46
  "@eslint/js": "^10.0.1",
47
+ "@types/node": "^25.8.0",
40
48
  "@typescript-eslint/eslint-plugin": "^8.59.3",
41
49
  "@typescript-eslint/parser": "^8.59.3",
42
- "eslint": "^10.3.0",
50
+ "eslint": "^10.4.0",
43
51
  "eslint-config-prettier": "^10.1.8",
44
52
  "globals": "^17.6.0",
53
+ "lint-staged": "^17.0.5",
45
54
  "npm-check-updates": "^22.2.0",
46
55
  "prettier": "^3.8.3",
47
56
  "typedoc": "^0.28.19",
@@ -49,16 +58,22 @@
49
58
  "typescript": "^6.0.3",
50
59
  "typescript-eslint": "^8.59.3",
51
60
  "vitest": "^4.1.6",
52
- "wrangler": "^4.90.1"
61
+ "wrangler": "^4.92.0"
53
62
  },
54
63
  "dependencies": {
55
64
  "@eslint/eslintrc": "^3.3.5",
56
- "@grammyjs/types": "^3.27.2",
65
+ "@grammyjs/types": "^3.27.3",
57
66
  "marked": "^18.0.3"
58
67
  },
59
68
  "typedocOptions": {
60
69
  "entryPoints": [
61
70
  "./src/main.ts"
62
71
  ]
72
+ },
73
+ "lint-staged": {
74
+ "*.{ts,js,mjs,json}": [
75
+ "prettier --write",
76
+ "eslint --fix"
77
+ ]
63
78
  }
64
79
  }