telegram-badge 1.0.0 → 1.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/README.md +138 -54
- package/README.ru.md +153 -0
- package/dist/api/badge-generator.d.ts +9 -0
- package/dist/api/badge-generator.js +119 -0
- package/dist/api/telegram-badge.d.ts +2 -2
- package/dist/api/telegram-badge.js +50 -15
- package/package.json +20 -14
package/README.md
CHANGED
|
@@ -1,134 +1,218 @@
|
|
|
1
1
|
# 🛡️ Telegram Group Badge Generator
|
|
2
2
|
|
|
3
|
+
[🇷🇺 Русская версия](README.ru.md) | [🇺🇸 English](README.md)
|
|
4
|
+
|
|
3
5
|
[](https://github.com/chatman-media/telegram-badge/actions)
|
|
4
6
|
[](https://badge.fury.io/js/telegram-badge)
|
|
5
7
|
[](https://www.typescriptlang.org/)
|
|
6
8
|
[](https://opensource.org/licenses/MIT)
|
|
7
9
|
|
|
8
|
-
|
|
10
|
+
This project generates SVG badges with the current member count of your Telegram group. Perfect for displaying community activity in GitHub README files or on websites.
|
|
9
11
|
|
|
10
|
-
## 🚀
|
|
12
|
+
## 🚀 Demo
|
|
11
13
|
|
|
12
14
|

|
|
13
15
|
|
|
14
16
|
---
|
|
15
17
|
|
|
16
|
-
## 📦
|
|
18
|
+
## 📦 Tech Stack
|
|
17
19
|
|
|
18
|
-
- Node.js /
|
|
20
|
+
- Node.js / TypeScript
|
|
19
21
|
- Telegram Bot API
|
|
20
22
|
- Vercel (Serverless API)
|
|
23
|
+
- Jest for testing
|
|
21
24
|
|
|
22
25
|
---
|
|
23
26
|
|
|
24
|
-
## 🛠
|
|
27
|
+
## 🛠 Installation
|
|
25
28
|
|
|
26
|
-
1.
|
|
29
|
+
1. Clone the repository:
|
|
27
30
|
|
|
28
31
|
```bash
|
|
29
32
|
git clone https://github.com/chatman-media/telegram-badge.git
|
|
30
33
|
cd telegram-badge
|
|
31
34
|
```
|
|
32
35
|
|
|
33
|
-
2.
|
|
36
|
+
2. Install dependencies:
|
|
34
37
|
|
|
35
38
|
```bash
|
|
36
39
|
npm install
|
|
37
|
-
#
|
|
40
|
+
# or
|
|
38
41
|
bun install
|
|
39
42
|
```
|
|
40
43
|
|
|
41
|
-
3.
|
|
44
|
+
3. Create a .env file and add:
|
|
42
45
|
|
|
43
46
|
```bash
|
|
44
47
|
BOT_TOKEN=your_telegram_bot_token
|
|
45
48
|
CHAT_ID=@your_group_username_or_chat_id
|
|
46
49
|
```
|
|
47
50
|
|
|
48
|
-
|
|
51
|
+
**Note:** For public groups/channels, the bot doesn't need to be added as a member. For private groups, the bot must be added to the group.
|
|
49
52
|
|
|
50
|
-
## 🧪
|
|
53
|
+
## 🧪 Local Development
|
|
51
54
|
|
|
52
55
|
```bash
|
|
53
56
|
npm run dev
|
|
54
|
-
#
|
|
57
|
+
# or
|
|
55
58
|
bun dev
|
|
56
59
|
```
|
|
57
60
|
|
|
58
|
-
|
|
61
|
+
Open in browser: http://localhost:3000/api/telegram-badge
|
|
62
|
+
|
|
63
|
+
## ☁️ Deploy to Vercel
|
|
59
64
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
• CHAT_ID
|
|
65
|
+
1. Deploy the repository to vercel.com
|
|
66
|
+
2. Add environment variables in project settings:
|
|
67
|
+
- BOT_TOKEN
|
|
68
|
+
- CHAT_ID
|
|
65
69
|
|
|
66
|
-
## 🧩
|
|
70
|
+
## 🧩 Usage in GitHub README
|
|
67
71
|
|
|
68
|
-
|
|
72
|
+
Add the following line to your README.md:
|
|
69
73
|
|
|
70
74
|
```markdown
|
|
71
75
|

|
|
72
76
|
```
|
|
73
77
|
|
|
74
|
-
### 🎨
|
|
78
|
+
### 🎨 Styling Parameters
|
|
75
79
|
|
|
76
|
-
|
|
80
|
+
You can customize the badge appearance using the following parameters:
|
|
77
81
|
|
|
78
|
-
|
|
|
79
|
-
|
|
80
|
-
| `style` |
|
|
81
|
-
| `label` |
|
|
82
|
-
| `color` |
|
|
83
|
-
| `labelColor` |
|
|
82
|
+
| Parameter | Description | Default Value |
|
|
83
|
+
|-----------|-------------|---------------|
|
|
84
|
+
| `style` | Badge style | `flat` |
|
|
85
|
+
| `label` | Label text | `Telegram` |
|
|
86
|
+
| `color` | Main badge color | `2AABEE` (Telegram color) |
|
|
87
|
+
| `labelColor` | Label color | `555555` |
|
|
88
|
+
| `logo` | Show Telegram logo | `true` |
|
|
84
89
|
|
|
85
|
-
####
|
|
90
|
+
#### Available styles:
|
|
86
91
|
|
|
87
|
-
- `flat` -
|
|
88
|
-
- `plastic` -
|
|
89
|
-
- `flat-square` -
|
|
90
|
-
- `for-the-badge` -
|
|
91
|
-
- `social` -
|
|
92
|
+
- `flat` - flat style (default)
|
|
93
|
+
- `plastic` - plastic style with gradient
|
|
94
|
+
- `flat-square` - flat square style without rounded corners
|
|
95
|
+
- `for-the-badge` - wide style with uppercase letters
|
|
96
|
+
- `social` - GitHub social style
|
|
92
97
|
|
|
93
|
-
####
|
|
98
|
+
#### Examples:
|
|
94
99
|
|
|
95
|
-
|
|
100
|
+
Standard badge (flat style):
|
|
96
101
|
```
|
|
97
102
|
https://telegram-badge.vercel.app/api/telegram-badge
|
|
98
103
|
```
|
|
104
|
+

|
|
99
105
|
|
|
100
|
-
|
|
106
|
+
Badge with plastic style:
|
|
101
107
|
```
|
|
102
|
-
https://telegram-badge.vercel.app/api/telegram-badge?
|
|
108
|
+
https://telegram-badge.vercel.app/api/telegram-badge?style=plastic
|
|
103
109
|
```
|
|
110
|
+

|
|
104
111
|
|
|
105
|
-
|
|
112
|
+
Badge with flat-square style:
|
|
106
113
|
```
|
|
107
|
-
https://telegram-badge.vercel.app/api/telegram-badge?
|
|
114
|
+
https://telegram-badge.vercel.app/api/telegram-badge?style=flat-square
|
|
108
115
|
```
|
|
116
|
+

|
|
109
117
|
|
|
110
|
-
|
|
118
|
+
Badge with for-the-badge style:
|
|
111
119
|
```
|
|
112
120
|
https://telegram-badge.vercel.app/api/telegram-badge?style=for-the-badge
|
|
113
121
|
```
|
|
122
|
+

|
|
123
|
+
|
|
124
|
+
Badge with social style:
|
|
125
|
+
```
|
|
126
|
+
https://telegram-badge.vercel.app/api/telegram-badge?style=social
|
|
127
|
+
```
|
|
128
|
+

|
|
129
|
+
|
|
130
|
+
Badge with custom label and color:
|
|
131
|
+
```
|
|
132
|
+
https://telegram-badge.vercel.app/api/telegram-badge?label=Join%20Chat&color=00FF00
|
|
133
|
+
```
|
|
134
|
+

|
|
135
|
+
|
|
136
|
+
Fully customized badge:
|
|
137
|
+
```
|
|
138
|
+
https://telegram-badge.vercel.app/api/telegram-badge?style=for-the-badge&label=Community&color=FF5733&labelColor=1A1A1A
|
|
139
|
+
```
|
|
140
|
+

|
|
114
141
|
|
|
115
|
-
|
|
142
|
+
Badge without logo:
|
|
116
143
|
```
|
|
117
|
-
https://telegram-badge.vercel.app/api/telegram-badge?
|
|
144
|
+
https://telegram-badge.vercel.app/api/telegram-badge?logo=false
|
|
118
145
|
```
|
|
146
|
+

|
|
119
147
|
|
|
120
|
-
##
|
|
148
|
+
## ✨ Features
|
|
121
149
|
|
|
122
|
-
- 👥
|
|
123
|
-
- 🎨
|
|
124
|
-
- 🔒
|
|
125
|
-
- ⚡
|
|
126
|
-
- 🛡️
|
|
127
|
-
- 🆓
|
|
128
|
-
- 📡
|
|
150
|
+
- 👥 Real-time member count display
|
|
151
|
+
- 🎨 Full badge appearance customization
|
|
152
|
+
- 🔒 Support for .env and Vercel environment variables for secure token storage
|
|
153
|
+
- ⚡ Optimized caching for fast loading
|
|
154
|
+
- 🛡️ Error handling with informative messages
|
|
155
|
+
- 🆓 Free on Vercel with normal usage
|
|
156
|
+
- 📡 Can be extended to show activity/message count
|
|
157
|
+
- 🧪 Comprehensive test suite with TypeScript
|
|
129
158
|
|
|
130
|
-
|
|
159
|
+
## 🔧 API Usage
|
|
160
|
+
|
|
161
|
+
### As npm package:
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
npm install telegram-badge
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
import badgeHandler from 'telegram-badge';
|
|
169
|
+
|
|
170
|
+
// Use in your serverless function
|
|
171
|
+
export default badgeHandler;
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Direct API calls:
|
|
175
|
+
|
|
176
|
+
```typescript
|
|
177
|
+
GET /api/telegram-badge?style=flat&label=Members&color=2AABEE&labelColor=555555
|
|
178
|
+
```
|
|
131
179
|
|
|
132
|
-
|
|
180
|
+
## 🧪 Testing
|
|
181
|
+
|
|
182
|
+
Run the test suite:
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
npm test
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Run type checking:
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
npm run type-check
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
Build the project:
|
|
195
|
+
|
|
196
|
+
```bash
|
|
197
|
+
npm run build
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## 📚 Documentation
|
|
201
|
+
|
|
202
|
+
For detailed documentation in Russian, see [README.ru.md](README.ru.md).
|
|
203
|
+
|
|
204
|
+
## 🤝 Contributing
|
|
205
|
+
|
|
206
|
+
1. Fork the repository
|
|
207
|
+
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
|
|
208
|
+
3. Commit your changes (`git commit -m 'Add some amazing feature'`)
|
|
209
|
+
4. Push to the branch (`git push origin feature/amazing-feature`)
|
|
210
|
+
5. Open a Pull Request
|
|
211
|
+
|
|
212
|
+
## 📜 License
|
|
213
|
+
|
|
214
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
215
|
+
|
|
216
|
+
---
|
|
133
217
|
|
|
134
|
-
|
|
218
|
+
Made with ❤️ by [Chatman Media](https://github.com/chatman-media)
|
package/README.ru.md
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# 🛡️ Telegram Group Badge Generator
|
|
2
|
+
|
|
3
|
+
[🇷🇺 Русская версия](README.ru.md) | [🇺🇸 English](README.md)
|
|
4
|
+
|
|
5
|
+
[](https://github.com/chatman-media/telegram-badge/actions)
|
|
6
|
+
[](https://badge.fury.io/js/telegram-badge)
|
|
7
|
+
[](https://www.typescriptlang.org/)
|
|
8
|
+
[](https://opensource.org/licenses/MIT)
|
|
9
|
+
|
|
10
|
+
Этот проект генерирует SVG-бейдж с текущим количеством участников вашей Telegram-группы. Идеально подходит для отображения активности сообщества в README на GitHub или на сайте.
|
|
11
|
+
|
|
12
|
+
## 🚀 Демо
|
|
13
|
+
|
|
14
|
+

|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## 📦 Стек
|
|
19
|
+
|
|
20
|
+
- Node.js / Bun
|
|
21
|
+
- Telegram Bot API
|
|
22
|
+
- Vercel (Serverless API)
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## 🛠 Установка
|
|
27
|
+
|
|
28
|
+
1. Клонируйте репозиторий:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
git clone https://github.com/chatman-media/telegram-badge.git
|
|
32
|
+
cd telegram-badge
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
2. Установите зависимости:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npm install
|
|
39
|
+
# или
|
|
40
|
+
bun install
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
3. Создайте .env файл и добавьте:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
BOT_TOKEN=your_telegram_bot_token
|
|
47
|
+
CHAT_ID=@your_group_username_or_chat_id
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
**Примечание:** Для публичных групп/каналов бота не нужно добавлять в группу. Для приватных групп бот должен быть участником.
|
|
51
|
+
|
|
52
|
+
## 🧪 Локальный запуск
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
npm run dev
|
|
56
|
+
# или
|
|
57
|
+
bun dev
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Открой в браузере: http://localhost:3000/api/telegram-badge
|
|
61
|
+
|
|
62
|
+
## ☁️ Деплой на Vercel
|
|
63
|
+
1. Задеплойте репозиторий на vercel.com
|
|
64
|
+
2. В настройках проекта добавьте переменные окружения:
|
|
65
|
+
• BOT_TOKEN
|
|
66
|
+
• CHAT_ID
|
|
67
|
+
|
|
68
|
+
## 🧩 Использование в GitHub README
|
|
69
|
+
|
|
70
|
+
Добавьте следующую строку в ваш README.md:
|
|
71
|
+
|
|
72
|
+
```markdown
|
|
73
|
+

|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### 🎨 Параметры стилизации
|
|
77
|
+
|
|
78
|
+
Вы можете настроить внешний вид бейджа с помощью следующих параметров:
|
|
79
|
+
|
|
80
|
+
| Параметр | Описание | Значение по умолчанию |
|
|
81
|
+
|----------|----------|------------------------|
|
|
82
|
+
| `style` | Стиль бейджа | `flat` |
|
|
83
|
+
| `label` | Текст метки | `Telegram` |
|
|
84
|
+
| `color` | Цвет основной части бейджа | `2AABEE` (цвет Telegram) |
|
|
85
|
+
| `labelColor` | Цвет метки бейджа | `555555` |
|
|
86
|
+
|
|
87
|
+
#### Доступные стили:
|
|
88
|
+
|
|
89
|
+
- `flat` - плоский стиль (по умолчанию)
|
|
90
|
+
- `plastic` - объемный стиль с градиентом
|
|
91
|
+
- `flat-square` - плоский стиль без закруглений
|
|
92
|
+
- `for-the-badge` - широкий стиль с заглавными буквами
|
|
93
|
+
- `social` - стиль как у GitHub
|
|
94
|
+
|
|
95
|
+
#### Примеры:
|
|
96
|
+
|
|
97
|
+
Стандартный бейдж (стиль flat):
|
|
98
|
+
```
|
|
99
|
+
https://telegram-badge.vercel.app/api/telegram-badge
|
|
100
|
+
```
|
|
101
|
+

|
|
102
|
+
|
|
103
|
+
Бейдж со стилем plastic:
|
|
104
|
+
```
|
|
105
|
+
https://telegram-badge.vercel.app/api/telegram-badge?style=plastic
|
|
106
|
+
```
|
|
107
|
+

|
|
108
|
+
|
|
109
|
+
Бейдж со стилем flat-square:
|
|
110
|
+
```
|
|
111
|
+
https://telegram-badge.vercel.app/api/telegram-badge?style=flat-square
|
|
112
|
+
```
|
|
113
|
+

|
|
114
|
+
|
|
115
|
+
Бейдж со стилем for-the-badge:
|
|
116
|
+
```
|
|
117
|
+
https://telegram-badge.vercel.app/api/telegram-badge?style=for-the-badge
|
|
118
|
+
```
|
|
119
|
+

|
|
120
|
+
|
|
121
|
+
Бейдж со стилем social:
|
|
122
|
+
```
|
|
123
|
+
https://telegram-badge.vercel.app/api/telegram-badge?style=social
|
|
124
|
+
```
|
|
125
|
+

|
|
126
|
+
|
|
127
|
+
Бейдж с кастомной меткой и цветом:
|
|
128
|
+
```
|
|
129
|
+
https://telegram-badge.vercel.app/api/telegram-badge?label=Наш%20Чат&color=00FF00
|
|
130
|
+
```
|
|
131
|
+

|
|
132
|
+
|
|
133
|
+
Полностью кастомизированный бейдж:
|
|
134
|
+
```
|
|
135
|
+
https://telegram-badge.vercel.app/api/telegram-badge?style=for-the-badge&label=Сообщество&color=FF5733&labelColor=1A1A1A
|
|
136
|
+
```
|
|
137
|
+

|
|
138
|
+
|
|
139
|
+
## 🧠 Возможности
|
|
140
|
+
|
|
141
|
+
- 👥 Отображение количества участников в реальном времени
|
|
142
|
+
- 🎨 Полная кастомизация внешнего вида бейджа
|
|
143
|
+
- 🔒 Поддержка .env и переменных Vercel для безопасного хранения токенов
|
|
144
|
+
- ⚡ Оптимизированное кэширование для быстрой загрузки
|
|
145
|
+
- 🛡️ Обработка ошибок с информативными сообщениями
|
|
146
|
+
- 🆓 Бесплатно на Vercel при обычной нагрузке
|
|
147
|
+
- 📡 Можно расширить до отображения активности / количества сообщений
|
|
148
|
+
|
|
149
|
+
⸻
|
|
150
|
+
|
|
151
|
+
📜 Лицензия
|
|
152
|
+
|
|
153
|
+
MIT
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generateBadgeSVG = generateBadgeSVG;
|
|
4
|
+
// Telegram logo as base64 encoded SVG
|
|
5
|
+
const TELEGRAM_LOGO = `<svg xmlns="http://www.w3.org/2000/svg" fill="white" viewBox="0 0 24 24" width="14" height="14">
|
|
6
|
+
<path d="M12 0C5.373 0 0 5.373 0 12s5.373 12 12 12 12-5.373 12-12S18.627 0 12 0zm5.894 8.221l-1.97 9.28c-.145.658-.537.818-1.084.508l-3-2.21-1.446 1.394c-.14.18-.357.295-.6.295-.002 0-.003 0-.005 0l.213-3.054 5.56-5.022c.24-.213-.054-.334-.373-.121l-6.869 4.326-2.96-.924c-.64-.203-.658-.64.135-.954l11.566-4.458c.538-.196 1.006.128.832.941z"/>
|
|
7
|
+
</svg>`;
|
|
8
|
+
function generateBadgeSVG(format) {
|
|
9
|
+
const { label, message, color, labelColor, style, logo } = format;
|
|
10
|
+
// Calculate logo space
|
|
11
|
+
const logoSpace = logo ? 20 : 0;
|
|
12
|
+
// Better text width calculation
|
|
13
|
+
const labelWidth = label.length * 6.5 + 20 + logoSpace;
|
|
14
|
+
const messageWidth = message.length * 6.5 + 20;
|
|
15
|
+
const totalWidth = labelWidth + messageWidth;
|
|
16
|
+
// Create logo element
|
|
17
|
+
const logoElement = logo ? `<image x="5" y="3" width="14" height="14" href="data:image/svg+xml;base64,${Buffer.from(TELEGRAM_LOGO).toString('base64')}"/>` : '';
|
|
18
|
+
// For-the-badge style
|
|
19
|
+
if (style === 'for-the-badge') {
|
|
20
|
+
const logoForBadge = logo ? `<image x="8" y="7" width="14" height="14" href="data:image/svg+xml;base64,${Buffer.from(TELEGRAM_LOGO).toString('base64')}"/>` : '';
|
|
21
|
+
const textOffset = logo ? 10 : 0;
|
|
22
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="${totalWidth}" height="28" role="img" aria-label="${label}: ${message}">
|
|
23
|
+
<title>${label}: ${message}</title>
|
|
24
|
+
<g shape-rendering="crispEdges">
|
|
25
|
+
<rect width="${labelWidth}" height="28" fill="${labelColor}"/>
|
|
26
|
+
<rect x="${labelWidth}" width="${messageWidth}" height="28" fill="${color}"/>
|
|
27
|
+
</g>
|
|
28
|
+
${logoForBadge}
|
|
29
|
+
<g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="100">
|
|
30
|
+
<text x="${(labelWidth / 2 + textOffset) * 10}" y="185" transform="scale(.1)" fill="#fff" textLength="${(labelWidth - 20 - logoSpace) * 10}">${label.toUpperCase()}</text>
|
|
31
|
+
<text x="${(labelWidth + messageWidth / 2) * 10}" y="185" font-weight="bold" transform="scale(.1)" fill="#fff" textLength="${(messageWidth - 20) * 10}">${message.toUpperCase()}</text>
|
|
32
|
+
</g>
|
|
33
|
+
</svg>`;
|
|
34
|
+
}
|
|
35
|
+
// Flat-square style
|
|
36
|
+
if (style === 'flat-square') {
|
|
37
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="${totalWidth}" height="20" role="img" aria-label="${label}: ${message}">
|
|
38
|
+
<title>${label}: ${message}</title>
|
|
39
|
+
<g shape-rendering="crispEdges">
|
|
40
|
+
<rect width="${labelWidth}" height="20" fill="${labelColor}"/>
|
|
41
|
+
<rect x="${labelWidth}" width="${messageWidth}" height="20" fill="${color}"/>
|
|
42
|
+
</g>
|
|
43
|
+
<g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110">
|
|
44
|
+
<text x="${labelWidth / 2 * 10}" y="140" transform="scale(.1)" fill="#fff" textLength="${(labelWidth - 10) * 10}">${label}</text>
|
|
45
|
+
<text x="${(labelWidth + messageWidth / 2) * 10}" y="140" transform="scale(.1)" fill="#fff" textLength="${(messageWidth - 10) * 10}">${message}</text>
|
|
46
|
+
</g>
|
|
47
|
+
</svg>`;
|
|
48
|
+
}
|
|
49
|
+
// Social style
|
|
50
|
+
if (style === 'social') {
|
|
51
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="${totalWidth + 4}" height="20" style="border-radius: 3px" role="img" aria-label="${label}: ${message}">
|
|
52
|
+
<title>${label}: ${message}</title>
|
|
53
|
+
<linearGradient id="s" x2="0" y2="100%">
|
|
54
|
+
<stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
|
|
55
|
+
<stop offset="1" stop-opacity=".1"/>
|
|
56
|
+
</linearGradient>
|
|
57
|
+
<clipPath id="r">
|
|
58
|
+
<rect width="${totalWidth + 4}" height="20" rx="3" fill="#fff"/>
|
|
59
|
+
</clipPath>
|
|
60
|
+
<g clip-path="url(#r)">
|
|
61
|
+
<rect width="${labelWidth + 2}" height="20" fill="${labelColor}"/>
|
|
62
|
+
<rect x="${labelWidth + 2}" width="${messageWidth + 2}" height="20" fill="${color}"/>
|
|
63
|
+
<rect width="${totalWidth + 4}" height="20" fill="url(#s)"/>
|
|
64
|
+
</g>
|
|
65
|
+
<g fill="#333" text-anchor="middle" font-family="Helvetica,Arial,sans-serif" font-weight="700" font-size="110">
|
|
66
|
+
<text x="${(labelWidth / 2 + 1) * 10}" y="140" transform="scale(.1)" fill="#fff" textLength="${(labelWidth - 10) * 10}">${label}</text>
|
|
67
|
+
<text x="${(labelWidth + messageWidth / 2 + 2) * 10}" y="140" transform="scale(.1)" fill="#fff" textLength="${(messageWidth - 10) * 10}">${message}</text>
|
|
68
|
+
</g>
|
|
69
|
+
</svg>`;
|
|
70
|
+
}
|
|
71
|
+
// Plastic style
|
|
72
|
+
if (style === 'plastic') {
|
|
73
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="${totalWidth}" height="20" role="img" aria-label="${label}: ${message}">
|
|
74
|
+
<title>${label}: ${message}</title>
|
|
75
|
+
<linearGradient id="s" x2="0" y2="100%">
|
|
76
|
+
<stop offset="0" stop-color="#fff" stop-opacity=".7"/>
|
|
77
|
+
<stop offset=".1" stop-color="#aaa" stop-opacity=".1"/>
|
|
78
|
+
<stop offset=".9" stop-color="#000" stop-opacity=".3"/>
|
|
79
|
+
<stop offset="1" stop-color="#000" stop-opacity=".5"/>
|
|
80
|
+
</linearGradient>
|
|
81
|
+
<clipPath id="r">
|
|
82
|
+
<rect width="${totalWidth}" height="20" rx="4" fill="#fff"/>
|
|
83
|
+
</clipPath>
|
|
84
|
+
<g clip-path="url(#r)">
|
|
85
|
+
<rect width="${labelWidth}" height="20" fill="${labelColor}"/>
|
|
86
|
+
<rect x="${labelWidth}" width="${messageWidth}" height="20" fill="${color}"/>
|
|
87
|
+
<rect width="${totalWidth}" height="20" fill="url(#s)"/>
|
|
88
|
+
</g>
|
|
89
|
+
<g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110">
|
|
90
|
+
<text x="${labelWidth / 2 * 10}" y="140" transform="scale(.1)" fill="#fff" textLength="${(labelWidth - 10) * 10}">${label}</text>
|
|
91
|
+
<text x="${(labelWidth + messageWidth / 2) * 10}" y="140" transform="scale(.1)" fill="#fff" textLength="${(messageWidth - 10) * 10}">${message}</text>
|
|
92
|
+
</g>
|
|
93
|
+
</svg>`;
|
|
94
|
+
}
|
|
95
|
+
// Default flat style
|
|
96
|
+
const textOffset = logo ? 10 : 0;
|
|
97
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="${totalWidth}" height="20" role="img" aria-label="${label}: ${message}">
|
|
98
|
+
<title>${label}: ${message}</title>
|
|
99
|
+
<linearGradient id="s" x2="0" y2="100%">
|
|
100
|
+
<stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
|
|
101
|
+
<stop offset="1" stop-opacity=".1"/>
|
|
102
|
+
</linearGradient>
|
|
103
|
+
<clipPath id="r">
|
|
104
|
+
<rect width="${totalWidth}" height="20" rx="3" fill="#fff"/>
|
|
105
|
+
</clipPath>
|
|
106
|
+
<g clip-path="url(#r)">
|
|
107
|
+
<rect width="${labelWidth}" height="20" fill="${labelColor}"/>
|
|
108
|
+
<rect x="${labelWidth}" width="${messageWidth}" height="20" fill="${color}"/>
|
|
109
|
+
<rect width="${totalWidth}" height="20" fill="url(#s)"/>
|
|
110
|
+
</g>
|
|
111
|
+
${logoElement}
|
|
112
|
+
<g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110">
|
|
113
|
+
<text aria-hidden="true" x="${(labelWidth / 2 + textOffset) * 10}" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="${(labelWidth - 10 - logoSpace) * 10}">${label}</text>
|
|
114
|
+
<text x="${(labelWidth / 2 + textOffset) * 10}" y="140" transform="scale(.1)" fill="#fff" textLength="${(labelWidth - 10 - logoSpace) * 10}">${label}</text>
|
|
115
|
+
<text aria-hidden="true" x="${(labelWidth + messageWidth / 2) * 10}" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="${(messageWidth - 10) * 10}">${message}</text>
|
|
116
|
+
<text x="${(labelWidth + messageWidth / 2) * 10}" y="140" transform="scale(.1)" fill="#fff" textLength="${(messageWidth - 10) * 10}">${message}</text>
|
|
117
|
+
</g>
|
|
118
|
+
</svg>`;
|
|
119
|
+
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export default function handler(req:
|
|
1
|
+
import { VercelRequest, VercelResponse } from '@vercel/node';
|
|
2
|
+
export default function handler(req: VercelRequest, res: VercelResponse): Promise<void>;
|
|
@@ -34,8 +34,8 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.default = handler;
|
|
37
|
-
const badge_maker_1 = require("badge-maker");
|
|
38
37
|
const crypto = __importStar(require("crypto"));
|
|
38
|
+
const badge_generator_1 = require("./badge-generator");
|
|
39
39
|
const logger = {
|
|
40
40
|
info: (message, data = {}) => {
|
|
41
41
|
console.log(`[INFO] ${message}`, data);
|
|
@@ -106,11 +106,12 @@ const validateStyleOptions = (options) => {
|
|
|
106
106
|
const label = options.label || 'Telegram';
|
|
107
107
|
const color = options.color || '2AABEE';
|
|
108
108
|
const labelColor = options.labelColor || '555555';
|
|
109
|
-
|
|
109
|
+
const logo = options.logo !== false; // Logo by default
|
|
110
|
+
return { style, label, color, labelColor, logo };
|
|
110
111
|
};
|
|
111
112
|
const createBadge = (members, options) => {
|
|
112
|
-
const { style, label, color, labelColor } = validateStyleOptions(options);
|
|
113
|
-
logger.debug('Creating badge', { style, label, color, labelColor });
|
|
113
|
+
const { style, label, color, labelColor, logo } = validateStyleOptions(options);
|
|
114
|
+
logger.debug('Creating badge', { style, label, color, labelColor, logo });
|
|
114
115
|
const normalizedColor = color.replace(/^#/, '');
|
|
115
116
|
const normalizedLabelColor = labelColor.replace(/^#/, '');
|
|
116
117
|
const format = {
|
|
@@ -118,9 +119,10 @@ const createBadge = (members, options) => {
|
|
|
118
119
|
message: `${members} members`,
|
|
119
120
|
color: `#${normalizedColor}`,
|
|
120
121
|
labelColor: `#${normalizedLabelColor}`,
|
|
121
|
-
style
|
|
122
|
+
style,
|
|
123
|
+
logo
|
|
122
124
|
};
|
|
123
|
-
return (0,
|
|
125
|
+
return (0, badge_generator_1.generateBadgeSVG)(format);
|
|
124
126
|
};
|
|
125
127
|
const createErrorBadge = (errorMessage) => {
|
|
126
128
|
const format = {
|
|
@@ -128,9 +130,10 @@ const createErrorBadge = (errorMessage) => {
|
|
|
128
130
|
message: errorMessage,
|
|
129
131
|
color: '#e05d44',
|
|
130
132
|
labelColor: '#555555',
|
|
131
|
-
style: 'flat'
|
|
133
|
+
style: 'flat',
|
|
134
|
+
logo: false
|
|
132
135
|
};
|
|
133
|
-
return (0,
|
|
136
|
+
return (0, badge_generator_1.generateBadgeSVG)(format);
|
|
134
137
|
};
|
|
135
138
|
const setCacheHeaders = (res, svg) => {
|
|
136
139
|
res.setHeader("Content-Type", "image/svg+xml");
|
|
@@ -146,12 +149,43 @@ const setCacheHeaders = (res, svg) => {
|
|
|
146
149
|
logger.debug('Cache headers set');
|
|
147
150
|
};
|
|
148
151
|
async function handler(req, res) {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
152
|
+
// Global error handler
|
|
153
|
+
process.on('uncaughtException', (error) => {
|
|
154
|
+
console.error('[UNCAUGHT EXCEPTION]', error);
|
|
155
|
+
if (!res.headersSent) {
|
|
156
|
+
const errorBadge = createErrorBadge('Uncaught Error');
|
|
157
|
+
res.status(500).send(errorBadge);
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
process.on('unhandledRejection', (reason) => {
|
|
161
|
+
console.error('[UNHANDLED REJECTION]', reason);
|
|
162
|
+
if (!res.headersSent) {
|
|
163
|
+
const errorBadge = createErrorBadge('Unhandled Rejection');
|
|
164
|
+
res.status(500).send(errorBadge);
|
|
165
|
+
}
|
|
153
166
|
});
|
|
154
167
|
try {
|
|
168
|
+
logger.info('Function started', {
|
|
169
|
+
query: req.query,
|
|
170
|
+
userAgent: req.headers['user-agent'],
|
|
171
|
+
referer: req.headers['referer'] || 'unknown',
|
|
172
|
+
env: {
|
|
173
|
+
hasToken: !!process.env.BOT_TOKEN,
|
|
174
|
+
hasChatId: !!process.env.CHAT_ID
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
// Early check for environment variables
|
|
178
|
+
if (!process.env.BOT_TOKEN || !process.env.CHAT_ID) {
|
|
179
|
+
logger.error('Missing environment variables', {
|
|
180
|
+
BOT_TOKEN: !!process.env.BOT_TOKEN,
|
|
181
|
+
CHAT_ID: !!process.env.CHAT_ID
|
|
182
|
+
});
|
|
183
|
+
const errorBadge = createErrorBadge('Missing Config');
|
|
184
|
+
res.setHeader("Content-Type", "image/svg+xml");
|
|
185
|
+
res.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
|
|
186
|
+
res.status(500).send(errorBadge);
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
155
189
|
const { token, chatId } = validateEnvironment();
|
|
156
190
|
logger.debug('Environment validated', { chatId });
|
|
157
191
|
const ifNoneMatch = req.headers['if-none-match'];
|
|
@@ -168,9 +202,10 @@ async function handler(req, res) {
|
|
|
168
202
|
logger.info('Member count fetched', { members });
|
|
169
203
|
const badgeOptions = {
|
|
170
204
|
style: req.query.style,
|
|
171
|
-
label: req.query.label,
|
|
172
|
-
color: req.query.color,
|
|
173
|
-
labelColor: req.query.labelColor
|
|
205
|
+
label: Array.isArray(req.query.label) ? req.query.label[0] : req.query.label,
|
|
206
|
+
color: Array.isArray(req.query.color) ? req.query.color[0] : req.query.color,
|
|
207
|
+
labelColor: Array.isArray(req.query.labelColor) ? req.query.labelColor[0] : req.query.labelColor,
|
|
208
|
+
logo: req.query.logo !== 'false' // Logo enabled by default, only disabled with logo=false
|
|
174
209
|
};
|
|
175
210
|
const svg = createBadge(members, badgeOptions);
|
|
176
211
|
logger.debug('Badge created');
|
package/package.json
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "telegram-badge",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Generate Telegram group member count badges for GitHub README",
|
|
5
|
-
"keywords": [
|
|
5
|
+
"keywords": [
|
|
6
|
+
"telegram",
|
|
7
|
+
"badge",
|
|
8
|
+
"svg",
|
|
9
|
+
"group",
|
|
10
|
+
"members",
|
|
11
|
+
"api"
|
|
12
|
+
],
|
|
6
13
|
"author": "Chatman Media",
|
|
7
14
|
"license": "MIT",
|
|
8
15
|
"repository": {
|
|
@@ -23,25 +30,24 @@
|
|
|
23
30
|
"LICENSE"
|
|
24
31
|
],
|
|
25
32
|
"scripts": {
|
|
26
|
-
"dev": "vercel dev",
|
|
27
|
-
"start": "vercel dev",
|
|
33
|
+
"dev": "node -p \"console.log('Use: npx vercel dev')\"",
|
|
34
|
+
"start": "node -p \"console.log('Use: npx vercel dev')\"",
|
|
28
35
|
"test": "jest",
|
|
29
|
-
"build": "
|
|
36
|
+
"build": "echo 'Build handled by Vercel'",
|
|
37
|
+
"build:local": "tsc",
|
|
30
38
|
"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"
|
|
39
|
+
"prepublishOnly": "npm run build:local && npm test",
|
|
40
|
+
"prepack": "npm run build:local"
|
|
36
41
|
},
|
|
37
42
|
"devDependencies": {
|
|
38
|
-
"@types/jest": "^29.5.
|
|
43
|
+
"@types/jest": "^29.5.14",
|
|
39
44
|
"@types/node": "^24.0.15",
|
|
45
|
+
"@vercel/node": "^2.3.0",
|
|
40
46
|
"dotenv": "^17.2.0",
|
|
41
47
|
"jest": "^30.0.4",
|
|
42
48
|
"node-fetch": "^2.7.0",
|
|
43
|
-
"
|
|
44
|
-
"ts-
|
|
45
|
-
"
|
|
49
|
+
"ts-jest": "^29.4.0",
|
|
50
|
+
"ts-node": "^10.9.2",
|
|
51
|
+
"typescript": "^5.8.3"
|
|
46
52
|
}
|
|
47
53
|
}
|