rubika 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/README.md +54 -0
- package/package.json +19 -0
- package/src/bot.ts +197 -0
- package/src/filters.ts +92 -0
- package/src/index.ts +8 -0
- package/src/methods/advanced/builder.ts +23 -0
- package/src/methods/advanced/index.ts +3 -0
- package/src/methods/bot/getMe.ts +8 -0
- package/src/methods/bot/index.ts +3 -0
- package/src/methods/chat/getChat.ts +7 -0
- package/src/methods/chat/index.ts +3 -0
- package/src/methods/files/_sendFile.ts +47 -0
- package/src/methods/files/getFile.ts +7 -0
- package/src/methods/files/index.ts +6 -0
- package/src/methods/files/requestSendFile.ts +8 -0
- package/src/methods/files/uploadFile.ts +88 -0
- package/src/methods/index.ts +223 -0
- package/src/methods/messages/deleteMessage.ts +14 -0
- package/src/methods/messages/editChatKeypad.ts +20 -0
- package/src/methods/messages/editMessageKeypad.ts +19 -0
- package/src/methods/messages/editMessageText.ts +16 -0
- package/src/methods/messages/forwardMessage.ts +18 -0
- package/src/methods/messages/index.ts +36 -0
- package/src/methods/messages/sendContact.ts +40 -0
- package/src/methods/messages/sendFile.ts +30 -0
- package/src/methods/messages/sendGif.ts +30 -0
- package/src/methods/messages/sendImage.ts +30 -0
- package/src/methods/messages/sendLocation.ts +38 -0
- package/src/methods/messages/sendMessage.ts +41 -0
- package/src/methods/messages/sendMusic.ts +30 -0
- package/src/methods/messages/sendPoll.ts +12 -0
- package/src/methods/messages/sendSticker.ts +38 -0
- package/src/methods/messages/sendVideo.ts +30 -0
- package/src/methods/messages/sendVoice.ts +30 -0
- package/src/methods/settings/index.ts +4 -0
- package/src/methods/settings/setCommands.ts +9 -0
- package/src/methods/settings/updateBotEndpoints.ts +15 -0
- package/src/methods/utilities/getUpdates.ts +7 -0
- package/src/methods/utilities/handleUpdates.ts +49 -0
- package/src/methods/utilities/index.ts +7 -0
- package/src/methods/utilities/polling.ts +63 -0
- package/src/methods/utilities/run.ts +21 -0
- package/src/methods/utilities/start.ts +23 -0
- package/src/methods/utilities/webhook.ts +55 -0
- package/src/network.ts +46 -0
- package/src/types/enums.ts +125 -0
- package/src/types/handlers.ts +16 -0
- package/src/types/interfaces.ts +263 -0
- package/src/types/methods.ts +21 -0
- package/src/types/utils.ts +9 -0
- package/src/utils/checkFilter.ts +19 -0
- package/src/utils/parser.ts +127 -0
- package/src/utils/prompt.ts +29 -0
- package/src/utils.ts +13 -0
package/README.md
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<h1 style="color:#fff;">› rubika-bot Framework</h1>
|
|
3
|
+
<p><b>فریمورک قدرتمند و پرسرعت جاوااسکریپت برای رباتهای روبیکا</b></p>
|
|
4
|
+
</p>
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## › معرفی rubika-bot
|
|
9
|
+
|
|
10
|
+
**rubika-bot** یک فریمورک سبک، مدرن و کاملاً **غیرهمزمان** برای توسعه رباتهای روبیکا است.
|
|
11
|
+
با معماری **Filter-Base** و **Type-Safe**، شما میتوانید:
|
|
12
|
+
|
|
13
|
+
- رباتهایی سریع و کمحجم بسازید
|
|
14
|
+
- سیستم فرماندهی و مدیریت پیام حرفهای داشته باشید
|
|
15
|
+
- با قابلیت **اسکِیبل و قابل گسترش**، پروژههای بزرگ هم مدیریت کنید
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## › نصب و شروع سریع
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
bun add rubika-bot
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## › نمونه کد ساده
|
|
26
|
+
|
|
27
|
+
```js
|
|
28
|
+
const { Bot, Filters } = require("rubika-bot");
|
|
29
|
+
|
|
30
|
+
const bot = new Bot("rubika-bot");
|
|
31
|
+
|
|
32
|
+
bot.command("/start", async (ctx, bot) => {
|
|
33
|
+
await bot.sendMessage(ctx.chat_id, "🤖 ربات استارت شد");
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
bot.on("message", [Filters.isText], async (ctx, bot) => {
|
|
37
|
+
await bot.sendMessage(ctx.chat_id, "سلام 😎");
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
bot.run();
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## › ویژگیهای کلیدی
|
|
44
|
+
|
|
45
|
+
| ویژگی | توضیح |
|
|
46
|
+
| -------------- | ---------------------------------------------- |
|
|
47
|
+
| ⚡ Super-Speed | استفاده از معماری غیرهمزمان برای پاسخدهی سریع |
|
|
48
|
+
| 🛡️ Type-Safe | کمک به جلوگیری از خطاهای متداول جاوااسکریپت |
|
|
49
|
+
| 🔧 Filter-Base | سیستم فیلترینگ قدرتمند برای مدیریت پیامها |
|
|
50
|
+
| 📂 Modular | ساختار ماژولار و قابل گسترش |
|
|
51
|
+
|
|
52
|
+
## 🌐 لینکهای مهم
|
|
53
|
+
|
|
54
|
+
- [لینک گیتهاب](https://github.com/hadi-rostami/rubika-bot)
|
package/package.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "rubika",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"author": "Hadi Rostami",
|
|
5
|
+
"main": "src/index.ts",
|
|
6
|
+
"description": "A modern library for Rubika built with Bun.",
|
|
7
|
+
"files": [
|
|
8
|
+
"src/",
|
|
9
|
+
"README.md"
|
|
10
|
+
],
|
|
11
|
+
"keywords": [
|
|
12
|
+
"javascript",
|
|
13
|
+
"Rubika",
|
|
14
|
+
"bun",
|
|
15
|
+
"rubika"
|
|
16
|
+
],
|
|
17
|
+
"license": "MIT",
|
|
18
|
+
"type": "module"
|
|
19
|
+
}
|
package/src/bot.ts
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import Network from "./network";
|
|
2
|
+
import Methods from "./methods";
|
|
3
|
+
import type { BotInfo, Update } from "./types/interfaces";
|
|
4
|
+
import type { ContextMap, Handler, NestedFilter } from "./types/handlers";
|
|
5
|
+
|
|
6
|
+
class Bot extends Methods {
|
|
7
|
+
protected initialize: boolean = false;
|
|
8
|
+
protected network: Network;
|
|
9
|
+
public BASE_URL: string;
|
|
10
|
+
public bot?: BotInfo;
|
|
11
|
+
|
|
12
|
+
public handlers: {
|
|
13
|
+
[K in keyof ContextMap]: Handler<ContextMap[K]>[];
|
|
14
|
+
} = { inline: [], update: [] };
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* نمونهای از کلاس ربات را ایجاد میکند.
|
|
18
|
+
*
|
|
19
|
+
* این سازنده اصلیترین بخش برای شروع کار با ربات است.
|
|
20
|
+
* با دریافت توکن و تنظیم زمان انتظار، پایهی ارتباط با ایپیای روبیکا را فراهم میکند.
|
|
21
|
+
*
|
|
22
|
+
* @param token - توکن اختصاصی ربات که برای احراز هویت در ایپیای روبیکا استفاده میشود.
|
|
23
|
+
* @param timeout - زمان انتظار برای درخواستها به سرور (بر حسب میلیثانیه). پیشفرض 10000 میلیثانیه.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```ts
|
|
27
|
+
* const bot = new Bot("your-token-here", 15000);
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
constructor(public token: string, timeout: number = 10000) {
|
|
31
|
+
super();
|
|
32
|
+
this.BASE_URL = `https://botapi.rubika.ir/v3/${token}`;
|
|
33
|
+
this.network = new Network(this.BASE_URL, timeout);
|
|
34
|
+
|
|
35
|
+
this.start();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* یک رویداد (event) را ثبت میکند و با ورودیهای مشخص، دستورات را اجرا میکند.
|
|
40
|
+
*
|
|
41
|
+
* این متد به شما امکان میدهد تا به ازای یک نوع خاص از رویداد (مثل پیام جدید، ورود کاربر و غیره)
|
|
42
|
+
* یک تابع پردازش (handler) تعیین کنید که زمان رخ دادن آن نوع از رویداد فراخوانی میشود.
|
|
43
|
+
*
|
|
44
|
+
* @param type - نوع رویدادی که میخواهید به آن پاسخ دهید (مثلاً "inline"، "update" و غیره).
|
|
45
|
+
* @param handler - (اختیاری) در صورتی که فیلتر وجود داشته باشد، این تابع پردازش خواهد بود.
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```ts
|
|
49
|
+
* bot.on("inline", (ctx) => {
|
|
50
|
+
* console.log("پیام جدید دریافت شد:", ctx.text);
|
|
51
|
+
* });
|
|
52
|
+
*
|
|
53
|
+
* bot.on("update", [filter.isText], (ctx) => {
|
|
54
|
+
* console.log("پیام شامل متن است:", ctx.text);
|
|
55
|
+
* });
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
on<T extends keyof typeof this.handlers>(
|
|
59
|
+
type: T,
|
|
60
|
+
handler: (ctx: ContextMap[T], bot: Bot) => Promise<void>
|
|
61
|
+
): void;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* یک رویداد (event) را ثبت میکند و با ورودیهای مشخص، دستورات را اجرا میکند.
|
|
65
|
+
*
|
|
66
|
+
* این متد به شما امکان میدهد تا به ازای یک نوع خاص از رویداد (مثل پیام جدید، ورود کاربر و غیره)
|
|
67
|
+
* یک تابع پردازش (handler) تعیین کنید که زمان رخ دادن آن نوع از رویداد فراخوانی میشود.
|
|
68
|
+
*
|
|
69
|
+
* @param type - نوع رویدادی که میخواهید به آن پاسخ دهید (مثلاً "inline"، "update" و غیره).
|
|
70
|
+
* @param filters - میتواند چند فیلتر یا تابع پردازش باشد.
|
|
71
|
+
* @param handler - (اختیاری) در صورتی که فیلتر وجود داشته باشد، این تابع پردازش خواهد بود.
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* ```ts
|
|
75
|
+
* bot.on("inline", (ctx) => {
|
|
76
|
+
* console.log("پیام جدید دریافت شد:", ctx.text);
|
|
77
|
+
* });
|
|
78
|
+
*
|
|
79
|
+
* bot.on("update", [filter.isText], (ctx) => {
|
|
80
|
+
* console.log("پیام شامل متن است:", ctx.text);
|
|
81
|
+
* });
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
84
|
+
on<T extends keyof typeof this.handlers>(
|
|
85
|
+
type: T,
|
|
86
|
+
filters: NestedFilter<ContextMap[T]>,
|
|
87
|
+
handler: (ctx: ContextMap[T], bot: Bot) => Promise<void>
|
|
88
|
+
): void;
|
|
89
|
+
|
|
90
|
+
on<T extends keyof typeof this.handlers>(
|
|
91
|
+
type: T,
|
|
92
|
+
filtersOrHandler:
|
|
93
|
+
| NestedFilter<ContextMap[T]>
|
|
94
|
+
| ((ctx: ContextMap[T], bot: Bot) => Promise<void>),
|
|
95
|
+
maybeHandler?: (ctx: ContextMap[T], bot: Bot) => Promise<void>
|
|
96
|
+
): void {
|
|
97
|
+
if (typeof filtersOrHandler === "function") {
|
|
98
|
+
this.handlers[type].push({
|
|
99
|
+
filters: [],
|
|
100
|
+
handler: filtersOrHandler,
|
|
101
|
+
});
|
|
102
|
+
} else if (Array.isArray(filtersOrHandler) && maybeHandler) {
|
|
103
|
+
this.handlers[type].push({
|
|
104
|
+
filters: filtersOrHandler,
|
|
105
|
+
handler: maybeHandler,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* یک دستور (command) را ثبت میکند.
|
|
112
|
+
*
|
|
113
|
+
* این متد به شما امکان میدهد تا به دستوراتی با پیشوند مشخص (مثل `/start` یا `!help`) پاسخ دهید.
|
|
114
|
+
* اگر پیام کاربر با پیشوند مطابقت داشته باشد، تابع پردازش مربوطه فراخوانی میشود.
|
|
115
|
+
*
|
|
116
|
+
* @param prefix - پیشوند دستور، که میتواند یک رشته یا یک عبارت منظم (RegExp) باشد.
|
|
117
|
+
* @param handler - (اختیاری) در صورتی که فیلتر وجود داشته باشد، این تابع پردازش خواهد بود.
|
|
118
|
+
*
|
|
119
|
+
* @example
|
|
120
|
+
* ```ts
|
|
121
|
+
* bot.command("/start", (ctx) => {
|
|
122
|
+
* ctx.reply("سلام! خوش اومدی.");
|
|
123
|
+
* });
|
|
124
|
+
*
|
|
125
|
+
* bot.command(/!help/, (ctx) => {
|
|
126
|
+
* ctx.reply("راهنمایی دریافت شد.");
|
|
127
|
+
* });
|
|
128
|
+
*
|
|
129
|
+
* bot.command("/ban", [filter.isAdmin], (ctx) => {
|
|
130
|
+
* // فقط ادمین میتونه این دستور رو اجرا کنه
|
|
131
|
+
* ctx.reply("کاربر مسدود شد.");
|
|
132
|
+
* });
|
|
133
|
+
* ```
|
|
134
|
+
*/
|
|
135
|
+
command(
|
|
136
|
+
prefix: string | RegExp,
|
|
137
|
+
handler: (ctx: Update, bot: Bot) => Promise<void>
|
|
138
|
+
): void;
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* یک دستور (command) را ثبت میکند.
|
|
142
|
+
*
|
|
143
|
+
* این متد به شما امکان میدهد تا به دستوراتی با پیشوند مشخص (مثل `/start` یا `!help`) پاسخ دهید.
|
|
144
|
+
* اگر پیام کاربر با پیشوند مطابقت داشته باشد، تابع پردازش مربوطه فراخوانی میشود.
|
|
145
|
+
*
|
|
146
|
+
* @param prefix - پیشوند دستور، که میتواند یک رشته یا یک عبارت منظم (RegExp) باشد.
|
|
147
|
+
* @param filters - میتواند چند فیلتر یا تابع پردازش باشد.
|
|
148
|
+
* @param handler - (اختیاری) در صورتی که فیلتر وجود داشته باشد، این تابع پردازش خواهد بود.
|
|
149
|
+
*
|
|
150
|
+
* @example
|
|
151
|
+
* ```ts
|
|
152
|
+
* bot.command("/start", (ctx) => {
|
|
153
|
+
* ctx.reply("سلام! خوش اومدی.");
|
|
154
|
+
* });
|
|
155
|
+
*
|
|
156
|
+
* bot.command(/!help/, (ctx) => {
|
|
157
|
+
* ctx.reply("راهنمایی دریافت شد.");
|
|
158
|
+
* });
|
|
159
|
+
*
|
|
160
|
+
* bot.command("/ban", [filter.isAdmin], (ctx) => {
|
|
161
|
+
* // فقط ادمین میتونه این دستور رو اجرا کنه
|
|
162
|
+
* ctx.reply("کاربر مسدود شد.");
|
|
163
|
+
* });
|
|
164
|
+
* ```
|
|
165
|
+
*/
|
|
166
|
+
command(
|
|
167
|
+
prefix: string | RegExp,
|
|
168
|
+
filters: NestedFilter<ContextMap["update"]>,
|
|
169
|
+
handler: (ctx: Update, bot: Bot) => Promise<void>
|
|
170
|
+
): void;
|
|
171
|
+
|
|
172
|
+
command(
|
|
173
|
+
prefix: string | RegExp,
|
|
174
|
+
filtersOrHandler:
|
|
175
|
+
| NestedFilter<ContextMap["update"]>
|
|
176
|
+
| ((ctx: Update, bot: Bot) => Promise<void>),
|
|
177
|
+
maybeHandler?: (ctx: Update, bot: Bot) => Promise<void>
|
|
178
|
+
) {
|
|
179
|
+
if (typeof filtersOrHandler === "function") {
|
|
180
|
+
this.handlers.update.push({
|
|
181
|
+
filters: [],
|
|
182
|
+
handler: filtersOrHandler,
|
|
183
|
+
prefix,
|
|
184
|
+
});
|
|
185
|
+
} else if (Array.isArray(filtersOrHandler) && maybeHandler) {
|
|
186
|
+
this.handlers.update.push({
|
|
187
|
+
filters: filtersOrHandler,
|
|
188
|
+
handler: maybeHandler,
|
|
189
|
+
prefix,
|
|
190
|
+
});
|
|
191
|
+
} else {
|
|
192
|
+
throw new Error("Invalid arguments for command()");
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export default Bot;
|
package/src/filters.ts
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { Update } from "./types/interfaces";
|
|
2
|
+
|
|
3
|
+
export default class Filters {
|
|
4
|
+
static findKey(message: any, key: string): any {
|
|
5
|
+
if (!message || typeof message !== "object") {
|
|
6
|
+
return undefined;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const messageKeys = Object.keys(message);
|
|
10
|
+
if (messageKeys.includes(key)) {
|
|
11
|
+
return message[key];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
for (const messageKey of messageKeys) {
|
|
15
|
+
const value = message[messageKey];
|
|
16
|
+
|
|
17
|
+
if (Array.isArray(value)) {
|
|
18
|
+
for (const item of value) {
|
|
19
|
+
if (typeof item === "object") {
|
|
20
|
+
const found = Filters.findKey(item, key);
|
|
21
|
+
if (found !== undefined) return found;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (typeof value === "object") {
|
|
27
|
+
const found = Filters.findKey(value, key);
|
|
28
|
+
if (found !== undefined) return found;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
static isText(message: Update): boolean {
|
|
36
|
+
return !!Filters.findKey(message, "text");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
static isLocation(message: Update): boolean {
|
|
40
|
+
return !!Filters.findKey(message, "location");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
static isSticker(message: Update): boolean {
|
|
44
|
+
return !!Filters.findKey(message, "sticker");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
static isForward(message: Update): boolean {
|
|
48
|
+
return !!Filters.findKey(message, "forwarded_from");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
static isReply(message: Update): boolean {
|
|
52
|
+
return !!Filters.findKey(message, "reply_to_message_id");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
static isContact(message: Update): boolean {
|
|
56
|
+
return !!Filters.findKey(message, "contact_message");
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
static isPoll(message: Update): boolean {
|
|
60
|
+
return !!Filters.findKey(message, "poll");
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
static isLiveLocation(message: Update): boolean {
|
|
64
|
+
return !!Filters.findKey(message, "live_location");
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
static isFile(message: Update): boolean {
|
|
68
|
+
return !!Filters.findKey(message, "file");
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
static isMention(message: Update): boolean {
|
|
72
|
+
return !!Filters.findKey(
|
|
73
|
+
message.new_message?.metadata?.meta_data_parts,
|
|
74
|
+
"link"
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
static isMarkdown(message: Update): boolean {
|
|
79
|
+
return !!Filters.findKey(message, "metadata");
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
static isDelete(message: Update): boolean {
|
|
83
|
+
return !!Filters.findKey(message, "removed_message_id");
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
static kypadID(button_id: string): (message: Update) => boolean {
|
|
87
|
+
return (message: Update) => {
|
|
88
|
+
const res = Filters.findKey(message, "button_id");
|
|
89
|
+
return res === button_id;
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import Bot from "../..";
|
|
2
|
+
|
|
3
|
+
async function builder(
|
|
4
|
+
this: Bot,
|
|
5
|
+
method: string,
|
|
6
|
+
input: object = {}
|
|
7
|
+
): Promise<any> {
|
|
8
|
+
if (!this.token) {
|
|
9
|
+
throw new Error(
|
|
10
|
+
"[builder] Bot token is not set. Please provide a valid token."
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const response: any = await this.network.request(method, input);
|
|
15
|
+
|
|
16
|
+
if (response?.status === "OK") {
|
|
17
|
+
return response.data;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
throw new Error(`[builder] error in request ${method}:\n ${response} `);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export default builder;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import Bot from "../..";
|
|
2
|
+
import Markdown from "../../utils/parser";
|
|
3
|
+
import { FileSource, SendType } from "../../types/methods";
|
|
4
|
+
import { InlineKeypad, Keypad } from "../../types/interfaces";
|
|
5
|
+
import { ChatKeypadTypeEnum, FileTypeEnum } from "../../types/enums";
|
|
6
|
+
|
|
7
|
+
async function _sendFile(
|
|
8
|
+
this: Bot,
|
|
9
|
+
chat_id: string,
|
|
10
|
+
file: FileSource,
|
|
11
|
+
text?: string,
|
|
12
|
+
type: FileTypeEnum = FileTypeEnum.File,
|
|
13
|
+
chat_keypad?: Keypad,
|
|
14
|
+
inline_keypad?: InlineKeypad,
|
|
15
|
+
disable_notification = false,
|
|
16
|
+
reply_to_message_id?: string,
|
|
17
|
+
chat_keypad_type?: ChatKeypadTypeEnum
|
|
18
|
+
) {
|
|
19
|
+
const { upload_url } = await this.requestSendFile(type);
|
|
20
|
+
const {
|
|
21
|
+
data: { file_id },
|
|
22
|
+
} = await this.uploadFile(upload_url, file);
|
|
23
|
+
|
|
24
|
+
let data: SendType = {
|
|
25
|
+
chat_id,
|
|
26
|
+
file_id,
|
|
27
|
+
disable_notification,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
if (text) data = { ...data, ...Markdown.toMetadata(text.trim()) };
|
|
31
|
+
if (chat_keypad) data.chat_keypad = chat_keypad;
|
|
32
|
+
if (inline_keypad) data.inline_keypad = inline_keypad;
|
|
33
|
+
if (chat_keypad_type) data.chat_keypad_type = chat_keypad_type;
|
|
34
|
+
if (reply_to_message_id) data.reply_to_message_id = reply_to_message_id;
|
|
35
|
+
|
|
36
|
+
if (chat_keypad && !chat_keypad_type) {
|
|
37
|
+
data.chat_keypad_type = ChatKeypadTypeEnum.New;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (inline_keypad && chat_keypad_type) {
|
|
41
|
+
data.chat_keypad_type = ChatKeypadTypeEnum.None;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return await this.builder("sendFile", data);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export default _sendFile;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { fetch } from "bun";
|
|
2
|
+
import { basename } from "path";
|
|
3
|
+
import Bot from "../..";
|
|
4
|
+
import { FileSource } from "../../types/methods";
|
|
5
|
+
|
|
6
|
+
async function uploadFile(
|
|
7
|
+
this: Bot,
|
|
8
|
+
url: string,
|
|
9
|
+
source: FileSource,
|
|
10
|
+
filename?: string
|
|
11
|
+
) {
|
|
12
|
+
let fileData: ArrayBuffer;
|
|
13
|
+
let detectedFilename: string;
|
|
14
|
+
|
|
15
|
+
if (typeof source === "string") {
|
|
16
|
+
if (source.startsWith("http://") || source.startsWith("https://")) {
|
|
17
|
+
// download file
|
|
18
|
+
console.log(`[ uploadFile ] Downloading from URL: ${source}`);
|
|
19
|
+
const res = await fetch(source);
|
|
20
|
+
if (!res.ok) {
|
|
21
|
+
throw new Error(
|
|
22
|
+
`Failed to download file: ${res.status} ${res.statusText}`
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
fileData = await res.arrayBuffer();
|
|
27
|
+
detectedFilename =
|
|
28
|
+
filename || getFilenameFromUrl(source) || "downloaded_file";
|
|
29
|
+
} else {
|
|
30
|
+
// file path
|
|
31
|
+
if (!Bun.file(source).size) {
|
|
32
|
+
throw new Error(`File not found: ${source}`);
|
|
33
|
+
}
|
|
34
|
+
fileData = await Bun.file(source).arrayBuffer();
|
|
35
|
+
detectedFilename = filename || basename(source);
|
|
36
|
+
}
|
|
37
|
+
} else {
|
|
38
|
+
// binery data
|
|
39
|
+
if (source instanceof Buffer) {
|
|
40
|
+
fileData = source.buffer.slice(
|
|
41
|
+
source.byteOffset,
|
|
42
|
+
source.byteOffset + source.byteLength
|
|
43
|
+
) as ArrayBuffer;
|
|
44
|
+
} else if (source instanceof Uint8Array) {
|
|
45
|
+
fileData = source.buffer.slice(
|
|
46
|
+
source.byteOffset,
|
|
47
|
+
source.byteOffset + source.byteLength
|
|
48
|
+
) as ArrayBuffer;
|
|
49
|
+
} else {
|
|
50
|
+
fileData = source;
|
|
51
|
+
}
|
|
52
|
+
detectedFilename = filename || "binary_file";
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// FormData
|
|
56
|
+
const formData = new FormData();
|
|
57
|
+
formData.append("file", new Blob([fileData]), detectedFilename);
|
|
58
|
+
|
|
59
|
+
const res = await fetch(url, {
|
|
60
|
+
method: "POST",
|
|
61
|
+
body: formData,
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
if (!res.ok) {
|
|
65
|
+
const text = await res.text();
|
|
66
|
+
throw new Error(`HTTP ${res.status}: ${text}`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const response = await res.json();
|
|
70
|
+
|
|
71
|
+
if (response.status !== "OK") {
|
|
72
|
+
throw new Error(`Upload failed: ${JSON.stringify(response)}`);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return response;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function getFilenameFromUrl(url: string): string | null {
|
|
79
|
+
try {
|
|
80
|
+
const path = new URL(url).pathname;
|
|
81
|
+
const filename = basename(path);
|
|
82
|
+
return filename !== "." ? filename : null;
|
|
83
|
+
} catch {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export default uploadFile;
|