telegram-badge 1.0.0
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 +134 -0
- package/dist/api/telegram-badge.d.ts +2 -0
- package/dist/api/telegram-badge.js +213 -0
- package/dist/tests/setup.d.ts +0 -0
- package/dist/tests/setup.js +6 -0
- package/dist/tests/telegram-badge.test.d.ts +1 -0
- package/dist/tests/telegram-badge.test.js +181 -0
- package/dist/types/index.d.ts +49 -0
- package/dist/types/index.js +2 -0
- package/package.json +47 -0
- package/types/index.ts +57 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Alexander Kireyev
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# π‘οΈ Telegram Group Badge Generator
|
|
2
|
+
|
|
3
|
+
[](https://github.com/chatman-media/telegram-badge/actions)
|
|
4
|
+
[](https://badge.fury.io/js/telegram-badge)
|
|
5
|
+
[](https://www.typescriptlang.org/)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
|
|
8
|
+
ΠΡΠΎΡ ΠΏΡΠΎΠ΅ΠΊΡ Π³Π΅Π½Π΅ΡΠΈΡΡΠ΅Ρ SVG-Π±Π΅ΠΉΠ΄ΠΆ Ρ ΡΠ΅ΠΊΡΡΠΈΠΌ ΠΊΠΎΠ»ΠΈΡΠ΅ΡΡΠ²ΠΎΠΌ ΡΡΠ°ΡΡΠ½ΠΈΠΊΠΎΠ² Π²Π°ΡΠ΅ΠΉ Telegram-Π³ΡΡΠΏΠΏΡ. ΠΠ΄Π΅Π°Π»ΡΠ½ΠΎ ΠΏΠΎΠ΄Ρ
ΠΎΠ΄ΠΈΡ Π΄Π»Ρ ΠΎΡΠΎΠ±ΡΠ°ΠΆΠ΅Π½ΠΈΡ Π°ΠΊΡΠΈΠ²Π½ΠΎΡΡΠΈ ΡΠΎΠΎΠ±ΡΠ΅ΡΡΠ²Π° Π² README Π½Π° GitHub ΠΈΠ»ΠΈ Π½Π° ΡΠ°ΠΉΡΠ΅.
|
|
9
|
+
|
|
10
|
+
## π ΠΠ΅ΠΌΠΎ
|
|
11
|
+
|
|
12
|
+

|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## π¦ Π‘ΡΠ΅ΠΊ
|
|
17
|
+
|
|
18
|
+
- Node.js / Bun
|
|
19
|
+
- Telegram Bot API
|
|
20
|
+
- Vercel (Serverless API)
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## π Π£ΡΡΠ°Π½ΠΎΠ²ΠΊΠ°
|
|
25
|
+
|
|
26
|
+
1. ΠΠ»ΠΎΠ½ΠΈΡΡΠΉΡΠ΅ ΡΠ΅ΠΏΠΎΠ·ΠΈΡΠΎΡΠΈΠΉ:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
git clone https://github.com/chatman-media/telegram-badge.git
|
|
30
|
+
cd telegram-badge
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
2. Π£ΡΡΠ°Π½ΠΎΠ²ΠΈΡΠ΅ Π·Π°Π²ΠΈΡΠΈΠΌΠΎΡΡΠΈ:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
npm install
|
|
37
|
+
# ΠΈΠ»ΠΈ
|
|
38
|
+
bun install
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
3. Π‘ΠΎΠ·Π΄Π°ΠΉΡΠ΅ .env ΡΠ°ΠΉΠ» ΠΈ Π΄ΠΎΠ±Π°Π²ΡΡΠ΅:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
BOT_TOKEN=your_telegram_bot_token
|
|
45
|
+
CHAT_ID=@your_group_username_or_chat_id
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Π£Π±Π΅Π΄ΠΈΡΠ΅ΡΡ, ΡΡΠΎ Π±ΠΎΡ Π΄ΠΎΠ±Π°Π²Π»Π΅Π½ Π² Π³ΡΡΠΏΠΏΡ ΠΊΠ°ΠΊ Π°Π΄ΠΌΠΈΠ½.
|
|
49
|
+
|
|
50
|
+
## π§ͺ ΠΠΎΠΊΠ°Π»ΡΠ½ΡΠΉ Π·Π°ΠΏΡΡΠΊ
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
npm run dev
|
|
54
|
+
# ΠΈΠ»ΠΈ
|
|
55
|
+
bun dev
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
ΠΡΠΊΡΠΎΠΉ Π² Π±ΡΠ°ΡΠ·Π΅ΡΠ΅: http://localhost:3000/api/telegram-badge
|
|
59
|
+
|
|
60
|
+
## βοΈ ΠΠ΅ΠΏΠ»ΠΎΠΉ Π½Π° Vercel
|
|
61
|
+
1. ΠΠ°Π΄Π΅ΠΏΠ»ΠΎΠΉΡΠ΅ ΡΠ΅ΠΏΠΎΠ·ΠΈΡΠΎΡΠΈΠΉ Π½Π° vercel.com
|
|
62
|
+
2. Π Π½Π°ΡΡΡΠΎΠΉΠΊΠ°Ρ
ΠΏΡΠΎΠ΅ΠΊΡΠ° Π΄ΠΎΠ±Π°Π²ΡΡΠ΅ ΠΏΠ΅ΡΠ΅ΠΌΠ΅Π½Π½ΡΠ΅ ΠΎΠΊΡΡΠΆΠ΅Π½ΠΈΡ:
|
|
63
|
+
β’ BOT_TOKEN
|
|
64
|
+
β’ CHAT_ID
|
|
65
|
+
|
|
66
|
+
## π§© ΠΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°Π½ΠΈΠ΅ Π² GitHub README
|
|
67
|
+
|
|
68
|
+
ΠΠΎΠ±Π°Π²ΡΡΠ΅ ΡΠ»Π΅Π΄ΡΡΡΡΡ ΡΡΡΠΎΠΊΡ Π² Π²Π°Ρ README.md:
|
|
69
|
+
|
|
70
|
+
```markdown
|
|
71
|
+

|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### π¨ ΠΠ°ΡΠ°ΠΌΠ΅ΡΡΡ ΡΡΠΈΠ»ΠΈΠ·Π°ΡΠΈΠΈ
|
|
75
|
+
|
|
76
|
+
ΠΡ ΠΌΠΎΠΆΠ΅ΡΠ΅ Π½Π°ΡΡΡΠΎΠΈΡΡ Π²Π½Π΅ΡΠ½ΠΈΠΉ Π²ΠΈΠ΄ Π±Π΅ΠΉΠ΄ΠΆΠ° Ρ ΠΏΠΎΠΌΠΎΡΡΡ ΡΠ»Π΅Π΄ΡΡΡΠΈΡ
ΠΏΠ°ΡΠ°ΠΌΠ΅ΡΡΠΎΠ²:
|
|
77
|
+
|
|
78
|
+
| ΠΠ°ΡΠ°ΠΌΠ΅ΡΡ | ΠΠΏΠΈΡΠ°Π½ΠΈΠ΅ | ΠΠ½Π°ΡΠ΅Π½ΠΈΠ΅ ΠΏΠΎ ΡΠΌΠΎΠ»ΡΠ°Π½ΠΈΡ |
|
|
79
|
+
|----------|----------|------------------------|
|
|
80
|
+
| `style` | Π‘ΡΠΈΠ»Ρ Π±Π΅ΠΉΠ΄ΠΆΠ° | `flat` |
|
|
81
|
+
| `label` | Π’Π΅ΠΊΡΡ ΠΌΠ΅ΡΠΊΠΈ | `Telegram` |
|
|
82
|
+
| `color` | Π¦Π²Π΅Ρ ΠΎΡΠ½ΠΎΠ²Π½ΠΎΠΉ ΡΠ°ΡΡΠΈ Π±Π΅ΠΉΠ΄ΠΆΠ° | `2AABEE` (ΡΠ²Π΅Ρ Telegram) |
|
|
83
|
+
| `labelColor` | Π¦Π²Π΅Ρ ΠΌΠ΅ΡΠΊΠΈ Π±Π΅ΠΉΠ΄ΠΆΠ° | `555555` |
|
|
84
|
+
|
|
85
|
+
#### ΠΠΎΡΡΡΠΏΠ½ΡΠ΅ ΡΡΠΈΠ»ΠΈ:
|
|
86
|
+
|
|
87
|
+
- `flat` - ΠΏΠ»ΠΎΡΠΊΠΈΠΉ ΡΡΠΈΠ»Ρ (ΠΏΠΎ ΡΠΌΠΎΠ»ΡΠ°Π½ΠΈΡ)
|
|
88
|
+
- `plastic` - ΠΎΠ±ΡΠ΅ΠΌΠ½ΡΠΉ ΡΡΠΈΠ»Ρ
|
|
89
|
+
- `flat-square` - ΠΏΠ»ΠΎΡΠΊΠΈΠΉ ΠΊΠ²Π°Π΄ΡΠ°ΡΠ½ΡΠΉ ΡΡΠΈΠ»Ρ
|
|
90
|
+
- `for-the-badge` - ΡΠΈΡΠΎΠΊΠΈΠΉ ΡΡΠΈΠ»Ρ Ρ Π·Π°Π³Π»Π°Π²Π½ΡΠΌΠΈ Π±ΡΠΊΠ²Π°ΠΌΠΈ
|
|
91
|
+
- `social` - ΡΠΎΡΠΈΠ°Π»ΡΠ½ΡΠΉ ΡΡΠΈΠ»Ρ
|
|
92
|
+
|
|
93
|
+
#### ΠΡΠΈΠΌΠ΅ΡΡ:
|
|
94
|
+
|
|
95
|
+
Π‘ΡΠ°Π½Π΄Π°ΡΡΠ½ΡΠΉ Π±Π΅ΠΉΠ΄ΠΆ:
|
|
96
|
+
```
|
|
97
|
+
https://telegram-badge.vercel.app/api/telegram-badge
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
ΠΠ΅ΠΉΠ΄ΠΆ Ρ ΠΊΠ°ΡΡΠΎΠΌΠ½ΠΎΠΉ ΠΌΠ΅ΡΠΊΠΎΠΉ:
|
|
101
|
+
```
|
|
102
|
+
https://telegram-badge.vercel.app/api/telegram-badge?label=Our%20Group
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
ΠΠ΅ΠΉΠ΄ΠΆ Ρ ΠΊΠ°ΡΡΠΎΠΌΠ½ΡΠΌ ΡΠ²Π΅ΡΠΎΠΌ:
|
|
106
|
+
```
|
|
107
|
+
https://telegram-badge.vercel.app/api/telegram-badge?color=FF0000
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
ΠΠ΅ΠΉΠ΄ΠΆ Ρ ΠΊΠ°ΡΡΠΎΠΌΠ½ΡΠΌ ΡΡΠΈΠ»Π΅ΠΌ:
|
|
111
|
+
```
|
|
112
|
+
https://telegram-badge.vercel.app/api/telegram-badge?style=for-the-badge
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
ΠΠΎΠ»Π½ΠΎΡΡΡΡ ΠΊΠ°ΡΡΠΎΠΌΠΈΠ·ΠΈΡΠΎΠ²Π°Π½Π½ΡΠΉ Π±Π΅ΠΉΠ΄ΠΆ:
|
|
116
|
+
```
|
|
117
|
+
https://telegram-badge.vercel.app/api/telegram-badge?style=social&label=Join%20Us&color=FF5733&labelColor=333333
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## π§ ΠΠΎΠ·ΠΌΠΎΠΆΠ½ΠΎΡΡΠΈ
|
|
121
|
+
|
|
122
|
+
- π₯ ΠΡΠΎΠ±ΡΠ°ΠΆΠ΅Π½ΠΈΠ΅ ΠΊΠΎΠ»ΠΈΡΠ΅ΡΡΠ²Π° ΡΡΠ°ΡΡΠ½ΠΈΠΊΠΎΠ² Π² ΡΠ΅Π°Π»ΡΠ½ΠΎΠΌ Π²ΡΠ΅ΠΌΠ΅Π½ΠΈ
|
|
123
|
+
- π¨ ΠΠΎΠ»Π½Π°Ρ ΠΊΠ°ΡΡΠΎΠΌΠΈΠ·Π°ΡΠΈΡ Π²Π½Π΅ΡΠ½Π΅Π³ΠΎ Π²ΠΈΠ΄Π° Π±Π΅ΠΉΠ΄ΠΆΠ°
|
|
124
|
+
- π ΠΠΎΠ΄Π΄Π΅ΡΠΆΠΊΠ° .env ΠΈ ΠΏΠ΅ΡΠ΅ΠΌΠ΅Π½Π½ΡΡ
Vercel Π΄Π»Ρ Π±Π΅Π·ΠΎΠΏΠ°ΡΠ½ΠΎΠ³ΠΎ Ρ
ΡΠ°Π½Π΅Π½ΠΈΡ ΡΠΎΠΊΠ΅Π½ΠΎΠ²
|
|
125
|
+
- β‘ ΠΠΏΡΠΈΠΌΠΈΠ·ΠΈΡΠΎΠ²Π°Π½Π½ΠΎΠ΅ ΠΊΡΡΠΈΡΠΎΠ²Π°Π½ΠΈΠ΅ Π΄Π»Ρ Π±ΡΡΡΡΠΎΠΉ Π·Π°Π³ΡΡΠ·ΠΊΠΈ
|
|
126
|
+
- π‘οΈ ΠΠ±ΡΠ°Π±ΠΎΡΠΊΠ° ΠΎΡΠΈΠ±ΠΎΠΊ Ρ ΠΈΠ½ΡΠΎΡΠΌΠ°ΡΠΈΠ²Π½ΡΠΌΠΈ ΡΠΎΠΎΠ±ΡΠ΅Π½ΠΈΡΠΌΠΈ
|
|
127
|
+
- π ΠΠ΅ΡΠΏΠ»Π°ΡΠ½ΠΎ Π½Π° Vercel ΠΏΡΠΈ ΠΎΠ±ΡΡΠ½ΠΎΠΉ Π½Π°Π³ΡΡΠ·ΠΊΠ΅
|
|
128
|
+
- π‘ ΠΠΎΠΆΠ½ΠΎ ΡΠ°ΡΡΠΈΡΠΈΡΡ Π΄ΠΎ ΠΎΡΠΎΠ±ΡΠ°ΠΆΠ΅Π½ΠΈΡ Π°ΠΊΡΠΈΠ²Π½ΠΎΡΡΠΈ / ΠΊΠΎΠ»ΠΈΡΠ΅ΡΡΠ²Π° ΡΠΎΠΎΠ±ΡΠ΅Π½ΠΈΠΉ
|
|
129
|
+
|
|
130
|
+
βΈ»
|
|
131
|
+
|
|
132
|
+
π ΠΠΈΡΠ΅Π½Π·ΠΈΡ
|
|
133
|
+
|
|
134
|
+
MIT
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.default = handler;
|
|
37
|
+
const badge_maker_1 = require("badge-maker");
|
|
38
|
+
const crypto = __importStar(require("crypto"));
|
|
39
|
+
const logger = {
|
|
40
|
+
info: (message, data = {}) => {
|
|
41
|
+
console.log(`[INFO] ${message}`, data);
|
|
42
|
+
},
|
|
43
|
+
warn: (message, data = {}) => {
|
|
44
|
+
console.warn(`[WARN] ${message}`, data);
|
|
45
|
+
},
|
|
46
|
+
error: (message, error = null) => {
|
|
47
|
+
console.error(`[ERROR] ${message}`, error);
|
|
48
|
+
},
|
|
49
|
+
debug: (message, data = {}) => {
|
|
50
|
+
if (process.env.DEBUG) {
|
|
51
|
+
console.log(`[DEBUG] ${message}`, data);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
const validateEnvironment = () => {
|
|
56
|
+
const token = process.env.BOT_TOKEN;
|
|
57
|
+
const chatId = process.env.CHAT_ID;
|
|
58
|
+
if (!token) {
|
|
59
|
+
throw new Error("Missing BOT_TOKEN environment variable");
|
|
60
|
+
}
|
|
61
|
+
if (!chatId) {
|
|
62
|
+
throw new Error("Missing CHAT_ID environment variable");
|
|
63
|
+
}
|
|
64
|
+
return { token, chatId };
|
|
65
|
+
};
|
|
66
|
+
const getMemberCount = async (token, chatId) => {
|
|
67
|
+
const apiUrl = `https://api.telegram.org/bot${token}/getChatMemberCount?chat_id=${encodeURIComponent(chatId)}`;
|
|
68
|
+
logger.debug('Fetching member count', { chatId });
|
|
69
|
+
const controller = new AbortController();
|
|
70
|
+
const timeoutId = setTimeout(() => controller.abort(), 5000);
|
|
71
|
+
try {
|
|
72
|
+
const response = await fetch(apiUrl, {
|
|
73
|
+
signal: controller.signal,
|
|
74
|
+
headers: {
|
|
75
|
+
'Accept': 'application/json',
|
|
76
|
+
'User-Agent': 'TelegramBadgeGenerator/1.0'
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
clearTimeout(timeoutId);
|
|
80
|
+
if (!response.ok) {
|
|
81
|
+
throw new Error(`HTTP error: ${response.status} ${response.statusText}`);
|
|
82
|
+
}
|
|
83
|
+
const data = await response.json();
|
|
84
|
+
if (!data.ok) {
|
|
85
|
+
throw new Error(`Telegram API error: ${data.description}`);
|
|
86
|
+
}
|
|
87
|
+
logger.debug('Member count received', { count: data.result });
|
|
88
|
+
return data.result;
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
if (error instanceof Error && error.name === 'AbortError') {
|
|
92
|
+
logger.error('Request timeout', error);
|
|
93
|
+
throw new Error('Request timeout: Telegram API took too long to respond');
|
|
94
|
+
}
|
|
95
|
+
logger.error('Error fetching member count', error);
|
|
96
|
+
throw error;
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
const validateStyleOptions = (options) => {
|
|
100
|
+
const validStyles = ['flat', 'plastic', 'flat-square', 'for-the-badge', 'social'];
|
|
101
|
+
let style = options.style || 'flat';
|
|
102
|
+
if (!validStyles.includes(style)) {
|
|
103
|
+
logger.warn(`Invalid style: ${style}, using default 'flat'`);
|
|
104
|
+
style = 'flat';
|
|
105
|
+
}
|
|
106
|
+
const label = options.label || 'Telegram';
|
|
107
|
+
const color = options.color || '2AABEE';
|
|
108
|
+
const labelColor = options.labelColor || '555555';
|
|
109
|
+
return { style, label, color, labelColor };
|
|
110
|
+
};
|
|
111
|
+
const createBadge = (members, options) => {
|
|
112
|
+
const { style, label, color, labelColor } = validateStyleOptions(options);
|
|
113
|
+
logger.debug('Creating badge', { style, label, color, labelColor });
|
|
114
|
+
const normalizedColor = color.replace(/^#/, '');
|
|
115
|
+
const normalizedLabelColor = labelColor.replace(/^#/, '');
|
|
116
|
+
const format = {
|
|
117
|
+
label,
|
|
118
|
+
message: `${members} members`,
|
|
119
|
+
color: `#${normalizedColor}`,
|
|
120
|
+
labelColor: `#${normalizedLabelColor}`,
|
|
121
|
+
style
|
|
122
|
+
};
|
|
123
|
+
return (0, badge_maker_1.makeBadge)(format);
|
|
124
|
+
};
|
|
125
|
+
const createErrorBadge = (errorMessage) => {
|
|
126
|
+
const format = {
|
|
127
|
+
label: 'Error',
|
|
128
|
+
message: errorMessage,
|
|
129
|
+
color: '#e05d44',
|
|
130
|
+
labelColor: '#555555',
|
|
131
|
+
style: 'flat'
|
|
132
|
+
};
|
|
133
|
+
return (0, badge_maker_1.makeBadge)(format);
|
|
134
|
+
};
|
|
135
|
+
const setCacheHeaders = (res, svg) => {
|
|
136
|
+
res.setHeader("Content-Type", "image/svg+xml");
|
|
137
|
+
res.setHeader("Cache-Control", "max-age=300, s-maxage=600, stale-while-revalidate=86400");
|
|
138
|
+
const etag = crypto
|
|
139
|
+
.createHash('md5')
|
|
140
|
+
.update(svg)
|
|
141
|
+
.digest('hex');
|
|
142
|
+
res.setHeader("ETag", `"${etag}"`);
|
|
143
|
+
const expiresDate = new Date();
|
|
144
|
+
expiresDate.setSeconds(expiresDate.getSeconds() + 300);
|
|
145
|
+
res.setHeader("Expires", expiresDate.toUTCString());
|
|
146
|
+
logger.debug('Cache headers set');
|
|
147
|
+
};
|
|
148
|
+
async function handler(req, res) {
|
|
149
|
+
logger.info('Received badge request', {
|
|
150
|
+
query: req.query,
|
|
151
|
+
userAgent: req.headers['user-agent'],
|
|
152
|
+
referer: req.headers['referer'] || 'unknown'
|
|
153
|
+
});
|
|
154
|
+
try {
|
|
155
|
+
const { token, chatId } = validateEnvironment();
|
|
156
|
+
logger.debug('Environment validated', { chatId });
|
|
157
|
+
const ifNoneMatch = req.headers['if-none-match'];
|
|
158
|
+
const requestEtag = `"${crypto
|
|
159
|
+
.createHash('md5')
|
|
160
|
+
.update(JSON.stringify({ token, chatId, query: req.query, time: Math.floor(Date.now() / 300000) }))
|
|
161
|
+
.digest('hex')}"`;
|
|
162
|
+
if (ifNoneMatch && ifNoneMatch === requestEtag) {
|
|
163
|
+
logger.info('Returning 304 Not Modified');
|
|
164
|
+
res.status(304).end();
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
const members = await getMemberCount(token, chatId);
|
|
168
|
+
logger.info('Member count fetched', { members });
|
|
169
|
+
const badgeOptions = {
|
|
170
|
+
style: req.query.style,
|
|
171
|
+
label: req.query.label,
|
|
172
|
+
color: req.query.color,
|
|
173
|
+
labelColor: req.query.labelColor
|
|
174
|
+
};
|
|
175
|
+
const svg = createBadge(members, badgeOptions);
|
|
176
|
+
logger.debug('Badge created');
|
|
177
|
+
setCacheHeaders(res, svg);
|
|
178
|
+
res.status(200).send(svg);
|
|
179
|
+
logger.info('Badge sent successfully');
|
|
180
|
+
}
|
|
181
|
+
catch (err) {
|
|
182
|
+
logger.error('Error processing request', err);
|
|
183
|
+
let errorBadge;
|
|
184
|
+
let statusCode = 500;
|
|
185
|
+
if (err instanceof Error) {
|
|
186
|
+
if (err.message.includes("Missing BOT_TOKEN") || err.message.includes("Missing CHAT_ID")) {
|
|
187
|
+
errorBadge = createErrorBadge('Configuration Error');
|
|
188
|
+
logger.error(`Configuration error: ${err.message}`);
|
|
189
|
+
}
|
|
190
|
+
else if (err.message.includes("Telegram API error")) {
|
|
191
|
+
errorBadge = createErrorBadge('API Error');
|
|
192
|
+
logger.error(`Telegram API error: ${err.message}`);
|
|
193
|
+
}
|
|
194
|
+
else if (err.message.includes("Request timeout")) {
|
|
195
|
+
errorBadge = createErrorBadge('Timeout');
|
|
196
|
+
statusCode = 503;
|
|
197
|
+
logger.error(`Timeout error: ${err.message}`);
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
errorBadge = createErrorBadge('Server Error');
|
|
201
|
+
logger.error(`Server error: ${err.message}`);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
errorBadge = createErrorBadge('Server Error');
|
|
206
|
+
logger.error('Unknown error occurred');
|
|
207
|
+
}
|
|
208
|
+
res.setHeader("Content-Type", "image/svg+xml");
|
|
209
|
+
res.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
|
|
210
|
+
res.status(statusCode).send(errorBadge);
|
|
211
|
+
logger.info(`Error badge sent with status ${statusCode}`);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
const telegram_badge_1 = __importDefault(require("../api/telegram-badge"));
|
|
40
|
+
const crypto = __importStar(require("crypto"));
|
|
41
|
+
// Mock fetch globally
|
|
42
|
+
global.fetch = jest.fn();
|
|
43
|
+
// Mock console methods for clean test output
|
|
44
|
+
console.log = jest.fn();
|
|
45
|
+
console.error = jest.fn();
|
|
46
|
+
console.warn = jest.fn();
|
|
47
|
+
describe('Telegram Badge API', () => {
|
|
48
|
+
let req;
|
|
49
|
+
let res;
|
|
50
|
+
beforeEach(() => {
|
|
51
|
+
jest.clearAllMocks();
|
|
52
|
+
// Mock environment variables
|
|
53
|
+
process.env.BOT_TOKEN = 'test_token';
|
|
54
|
+
process.env.CHAT_ID = '@test_chat';
|
|
55
|
+
// Mock request and response objects
|
|
56
|
+
req = {
|
|
57
|
+
query: {},
|
|
58
|
+
headers: {}
|
|
59
|
+
};
|
|
60
|
+
res = {
|
|
61
|
+
status: jest.fn().mockReturnThis(),
|
|
62
|
+
send: jest.fn(),
|
|
63
|
+
setHeader: jest.fn(),
|
|
64
|
+
end: jest.fn()
|
|
65
|
+
};
|
|
66
|
+
// Mock successful Telegram API response
|
|
67
|
+
fetch.mockResolvedValue({
|
|
68
|
+
ok: true,
|
|
69
|
+
json: jest.fn().mockResolvedValue({
|
|
70
|
+
ok: true,
|
|
71
|
+
result: 100
|
|
72
|
+
})
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
test('Π΄ΠΎΠ»ΠΆΠ΅Π½ Π²ΠΎΠ·Π²ΡΠ°ΡΠ°ΡΡ SVG Π±Π΅ΠΉΠ΄ΠΆ Ρ ΠΊΠΎΠ»ΠΈΡΠ΅ΡΡΠ²ΠΎΠΌ ΡΡΠ°ΡΡΠ½ΠΈΠΊΠΎΠ²', async () => {
|
|
76
|
+
await (0, telegram_badge_1.default)(req, res);
|
|
77
|
+
// Check that fetch was called with correct parameters
|
|
78
|
+
expect(fetch).toHaveBeenCalledWith(expect.stringContaining('https://api.telegram.org/bottest_token/getChatMemberCount'), expect.any(Object));
|
|
79
|
+
// Check that headers were set
|
|
80
|
+
expect(res.setHeader).toHaveBeenCalledWith('Content-Type', 'image/svg+xml');
|
|
81
|
+
expect(res.setHeader).toHaveBeenCalledWith('Cache-Control', expect.any(String));
|
|
82
|
+
// Check that response was sent with status 200
|
|
83
|
+
expect(res.status).toHaveBeenCalledWith(200);
|
|
84
|
+
expect(res.send).toHaveBeenCalled();
|
|
85
|
+
});
|
|
86
|
+
test('Π΄ΠΎΠ»ΠΆΠ΅Π½ ΠΎΠ±ΡΠ°Π±Π°ΡΡΠ²Π°ΡΡ ΠΎΡΡΡΡΡΡΠ²ΠΈΠ΅ ΠΏΠ΅ΡΠ΅ΠΌΠ΅Π½Π½ΡΡ
ΠΎΠΊΡΡΠΆΠ΅Π½ΠΈΡ', async () => {
|
|
87
|
+
// Remove environment variables
|
|
88
|
+
delete process.env.BOT_TOKEN;
|
|
89
|
+
await (0, telegram_badge_1.default)(req, res);
|
|
90
|
+
// Check that error response was sent
|
|
91
|
+
expect(res.status).toHaveBeenCalledWith(500);
|
|
92
|
+
expect(res.send).toHaveBeenCalledWith(expect.any(String));
|
|
93
|
+
});
|
|
94
|
+
test('Π΄ΠΎΠ»ΠΆΠ΅Π½ ΠΎΠ±ΡΠ°Π±Π°ΡΡΠ²Π°ΡΡ ΠΎΡΠΈΠ±ΠΊΠΈ Telegram API', async () => {
|
|
95
|
+
// Mock Telegram API error
|
|
96
|
+
fetch.mockResolvedValue({
|
|
97
|
+
ok: true,
|
|
98
|
+
json: jest.fn().mockResolvedValue({
|
|
99
|
+
ok: false,
|
|
100
|
+
description: 'Test error'
|
|
101
|
+
})
|
|
102
|
+
});
|
|
103
|
+
await (0, telegram_badge_1.default)(req, res);
|
|
104
|
+
// Check that error response was sent
|
|
105
|
+
expect(res.status).toHaveBeenCalledWith(500);
|
|
106
|
+
expect(res.send).toHaveBeenCalledWith(expect.any(String));
|
|
107
|
+
});
|
|
108
|
+
test('Π΄ΠΎΠ»ΠΆΠ΅Π½ ΠΎΠ±ΡΠ°Π±Π°ΡΡΠ²Π°ΡΡ ΡΠ΅ΡΠ΅Π²ΡΠ΅ ΠΎΡΠΈΠ±ΠΊΠΈ', async () => {
|
|
109
|
+
// Mock network error
|
|
110
|
+
fetch.mockRejectedValue(new Error('Network error'));
|
|
111
|
+
await (0, telegram_badge_1.default)(req, res);
|
|
112
|
+
// Check that error response was sent
|
|
113
|
+
expect(res.status).toHaveBeenCalledWith(500);
|
|
114
|
+
expect(res.send).toHaveBeenCalledWith(expect.any(String));
|
|
115
|
+
});
|
|
116
|
+
test('Π΄ΠΎΠ»ΠΆΠ΅Π½ ΠΏΡΠΈΠΌΠ΅Π½ΡΡΡ ΠΏΠ°ΡΠ°ΠΌΠ΅ΡΡΡ ΡΡΠΈΠ»ΠΈΠ·Π°ΡΠΈΠΈ', async () => {
|
|
117
|
+
// Set styling parameters
|
|
118
|
+
req.query = {
|
|
119
|
+
style: 'flat-square',
|
|
120
|
+
label: 'Custom Label',
|
|
121
|
+
color: 'FF0000',
|
|
122
|
+
labelColor: '000000'
|
|
123
|
+
};
|
|
124
|
+
await (0, telegram_badge_1.default)(req, res);
|
|
125
|
+
// Check that response was sent with status 200
|
|
126
|
+
expect(res.status).toHaveBeenCalledWith(200);
|
|
127
|
+
expect(res.send).toHaveBeenCalled();
|
|
128
|
+
});
|
|
129
|
+
test('Π΄ΠΎΠ»ΠΆΠ΅Π½ Π²ΠΎΠ·Π²ΡΠ°ΡΠ°ΡΡ 304 Not Modified ΠΏΡΠΈ ΡΠΎΠ²ΠΏΠ°Π΄Π΅Π½ΠΈΠΈ ETag', async () => {
|
|
130
|
+
// Set If-None-Match header
|
|
131
|
+
const testEtag = '\"test-etag\"';
|
|
132
|
+
req.headers['if-none-match'] = testEtag;
|
|
133
|
+
// Mock the specific environment to create a predictable ETag
|
|
134
|
+
const originalEnv = process.env;
|
|
135
|
+
process.env = {
|
|
136
|
+
...originalEnv,
|
|
137
|
+
BOT_TOKEN: 'test_token_for_etag',
|
|
138
|
+
CHAT_ID: '@test_chat_for_etag'
|
|
139
|
+
};
|
|
140
|
+
// We need to create the exact same hash that would be generated
|
|
141
|
+
const fixedTime = 1234567890000; // Fixed timestamp
|
|
142
|
+
jest.spyOn(Date, 'now').mockReturnValue(fixedTime);
|
|
143
|
+
// Create expected ETag based on our known values
|
|
144
|
+
const expectedHash = crypto
|
|
145
|
+
.createHash('md5')
|
|
146
|
+
.update(JSON.stringify({
|
|
147
|
+
token: 'test_token_for_etag',
|
|
148
|
+
chatId: '@test_chat_for_etag',
|
|
149
|
+
query: {},
|
|
150
|
+
time: Math.floor(fixedTime / 300000)
|
|
151
|
+
}))
|
|
152
|
+
.digest('hex');
|
|
153
|
+
req.headers['if-none-match'] = `"${expectedHash}"`;
|
|
154
|
+
await (0, telegram_badge_1.default)(req, res);
|
|
155
|
+
// Restore environment
|
|
156
|
+
process.env = originalEnv;
|
|
157
|
+
// Check that response was sent with status 304
|
|
158
|
+
expect(res.status).toHaveBeenCalledWith(304);
|
|
159
|
+
expect(res.end).toHaveBeenCalled();
|
|
160
|
+
});
|
|
161
|
+
test('Π΄ΠΎΠ»ΠΆΠ΅Π½ ΠΎΠ±ΡΠ°Π±Π°ΡΡΠ²Π°ΡΡ ΡΠ°ΠΉΠΌΠ°ΡΡΡ Π·Π°ΠΏΡΠΎΡΠΎΠ²', async () => {
|
|
162
|
+
// Mock timeout error
|
|
163
|
+
const abortError = new Error('Request timeout');
|
|
164
|
+
abortError.name = 'AbortError';
|
|
165
|
+
fetch.mockRejectedValue(abortError);
|
|
166
|
+
await (0, telegram_badge_1.default)(req, res);
|
|
167
|
+
// Check that error response was sent with service unavailable status
|
|
168
|
+
expect(res.status).toHaveBeenCalledWith(503);
|
|
169
|
+
expect(res.send).toHaveBeenCalledWith(expect.any(String));
|
|
170
|
+
});
|
|
171
|
+
test('Π΄ΠΎΠ»ΠΆΠ΅Π½ Π²Π°Π»ΠΈΠ΄ΠΈΡΠΎΠ²Π°ΡΡ ΡΡΠΈΠ»ΠΈ Π±Π΅ΠΉΠ΄ΠΆΠ°', async () => {
|
|
172
|
+
// Set invalid style
|
|
173
|
+
req.query = {
|
|
174
|
+
style: 'invalid-style'
|
|
175
|
+
};
|
|
176
|
+
await (0, telegram_badge_1.default)(req, res);
|
|
177
|
+
// Should still work with default style
|
|
178
|
+
expect(res.status).toHaveBeenCalledWith(200);
|
|
179
|
+
expect(res.send).toHaveBeenCalled();
|
|
180
|
+
});
|
|
181
|
+
});
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
export interface BadgeOptions {
|
|
2
|
+
style?: 'flat' | 'plastic' | 'flat-square' | 'for-the-badge' | 'social';
|
|
3
|
+
label?: string;
|
|
4
|
+
color?: string;
|
|
5
|
+
labelColor?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface BadgeFormat {
|
|
8
|
+
label: string;
|
|
9
|
+
message: string;
|
|
10
|
+
color: string;
|
|
11
|
+
labelColor: string;
|
|
12
|
+
style: 'flat' | 'plastic' | 'flat-square' | 'for-the-badge' | 'social';
|
|
13
|
+
}
|
|
14
|
+
export interface TelegramApiResponse {
|
|
15
|
+
ok: boolean;
|
|
16
|
+
result?: number;
|
|
17
|
+
description?: string;
|
|
18
|
+
}
|
|
19
|
+
export interface Logger {
|
|
20
|
+
info: (message: string, data?: Record<string, any>) => void;
|
|
21
|
+
warn: (message: string, data?: Record<string, any>) => void;
|
|
22
|
+
error: (message: string, error?: any) => void;
|
|
23
|
+
debug: (message: string, data?: Record<string, any>) => void;
|
|
24
|
+
}
|
|
25
|
+
export interface RequestQuery {
|
|
26
|
+
style?: string;
|
|
27
|
+
label?: string;
|
|
28
|
+
color?: string;
|
|
29
|
+
labelColor?: string;
|
|
30
|
+
}
|
|
31
|
+
export interface RequestHeaders {
|
|
32
|
+
'if-none-match'?: string;
|
|
33
|
+
'user-agent'?: string;
|
|
34
|
+
referer?: string;
|
|
35
|
+
}
|
|
36
|
+
export interface Request {
|
|
37
|
+
query: RequestQuery;
|
|
38
|
+
headers: RequestHeaders;
|
|
39
|
+
}
|
|
40
|
+
export interface Response {
|
|
41
|
+
status: (code: number) => Response;
|
|
42
|
+
send: (data: string) => void;
|
|
43
|
+
end: () => void;
|
|
44
|
+
setHeader: (name: string, value: string) => void;
|
|
45
|
+
}
|
|
46
|
+
export interface Environment {
|
|
47
|
+
token: string;
|
|
48
|
+
chatId: string;
|
|
49
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "telegram-badge",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Generate Telegram group member count badges for GitHub README",
|
|
5
|
+
"keywords": ["telegram", "badge", "svg", "group", "members", "api"],
|
|
6
|
+
"author": "Chatman Media",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/chatman-media/telegram-badge.git"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://github.com/chatman-media/telegram-badge",
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/chatman-media/telegram-badge/issues"
|
|
15
|
+
},
|
|
16
|
+
"type": "commonjs",
|
|
17
|
+
"main": "dist/api/telegram-badge.js",
|
|
18
|
+
"types": "dist/api/telegram-badge.d.ts",
|
|
19
|
+
"files": [
|
|
20
|
+
"dist",
|
|
21
|
+
"types",
|
|
22
|
+
"README.md",
|
|
23
|
+
"LICENSE"
|
|
24
|
+
],
|
|
25
|
+
"scripts": {
|
|
26
|
+
"dev": "vercel dev",
|
|
27
|
+
"start": "vercel dev",
|
|
28
|
+
"test": "jest",
|
|
29
|
+
"build": "tsc",
|
|
30
|
+
"type-check": "tsc --noEmit",
|
|
31
|
+
"prepublishOnly": "npm run build && npm test",
|
|
32
|
+
"prepack": "npm run build"
|
|
33
|
+
},
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"badge-maker": "^5.0.2"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@types/jest": "^29.5.12",
|
|
39
|
+
"@types/node": "^24.0.15",
|
|
40
|
+
"dotenv": "^17.2.0",
|
|
41
|
+
"jest": "^30.0.4",
|
|
42
|
+
"node-fetch": "^2.7.0",
|
|
43
|
+
"typescript": "^5.5.4",
|
|
44
|
+
"ts-jest": "^29.2.5",
|
|
45
|
+
"ts-node": "^10.9.2"
|
|
46
|
+
}
|
|
47
|
+
}
|
package/types/index.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
export interface BadgeOptions {
|
|
2
|
+
style?: 'flat' | 'plastic' | 'flat-square' | 'for-the-badge' | 'social';
|
|
3
|
+
label?: string;
|
|
4
|
+
color?: string;
|
|
5
|
+
labelColor?: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface BadgeFormat {
|
|
9
|
+
label: string;
|
|
10
|
+
message: string;
|
|
11
|
+
color: string;
|
|
12
|
+
labelColor: string;
|
|
13
|
+
style: 'flat' | 'plastic' | 'flat-square' | 'for-the-badge' | 'social';
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface TelegramApiResponse {
|
|
17
|
+
ok: boolean;
|
|
18
|
+
result?: number;
|
|
19
|
+
description?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface Logger {
|
|
23
|
+
info: (message: string, data?: Record<string, any>) => void;
|
|
24
|
+
warn: (message: string, data?: Record<string, any>) => void;
|
|
25
|
+
error: (message: string, error?: any) => void;
|
|
26
|
+
debug: (message: string, data?: Record<string, any>) => void;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface RequestQuery {
|
|
30
|
+
style?: string;
|
|
31
|
+
label?: string;
|
|
32
|
+
color?: string;
|
|
33
|
+
labelColor?: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface RequestHeaders {
|
|
37
|
+
'if-none-match'?: string;
|
|
38
|
+
'user-agent'?: string;
|
|
39
|
+
referer?: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface Request {
|
|
43
|
+
query: RequestQuery;
|
|
44
|
+
headers: RequestHeaders;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface Response {
|
|
48
|
+
status: (code: number) => Response;
|
|
49
|
+
send: (data: string) => void;
|
|
50
|
+
end: () => void;
|
|
51
|
+
setHeader: (name: string, value: string) => void;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface Environment {
|
|
55
|
+
token: string;
|
|
56
|
+
chatId: string;
|
|
57
|
+
}
|