├── .env.example ├── .gitignore ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── bot.ts └── preview.gif /.env.example: -------------------------------------------------------------------------------- 1 | BOT_TOKEN="123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11" 2 | ADMIN_ID=123456789 3 | API_ROOT="http://localhost:8081" -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | test.ts -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "deno.enable": true, 3 | "deno.lint": true, 4 | "deno.unstable": false 5 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 dcdunkan 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. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
${sanitize(filename)}
from ${
78 | sanitize(path)
79 | }
`,
80 | { parse_mode: "HTML" },
81 | );
82 |
83 | await ctx.replyWithDocument(new InputFile(path, filename), {
84 | caption: `Filename: ${sanitize(filename)}
` +
85 | `\nPath: ${sanitize(path)}
` +
86 | `\nSize: ${bytes(exists.size)}` +
87 | `\nCreated at: ${exists.birthtime?.toUTCString()}
`,
88 | parse_mode: "HTML",
89 | });
90 |
91 | return await ctx.api.deleteMessage(ctx.chat.id, message_id);
92 | }
93 |
94 | // Directory upload.
95 | const files = await getFileList(path);
96 | if (files.length === 0) return await ctx.reply("No files found.");
97 |
98 | await ctx.api.editMessageText(
99 | ctx.chat.id,
100 | message_id,
101 | `Uploading ${files.length} file${files.length > 1 ? "s" : ""} from ${
102 | sanitize(path)
103 | }
`,
104 | { parse_mode: "HTML" },
105 | );
106 |
107 | await ctx
108 | .pinChatMessage(message_id, { disable_notification: true })
109 | .catch((e) => e);
110 |
111 | const uploadedFiles: UploadedFileList[] = [];
112 |
113 | for (let i = 0; i < files.length; i++) {
114 | const file = files[i];
115 |
116 | // Double check.
117 | if (!await fileExists(file.path)) {
118 | await ctx.reply(
119 | `'${
120 | sanitize(file.name)
121 | }
' not found. Skipping.\nPath: ${
122 | sanitize(file.path)
123 | }
`,
124 | { parse_mode: "HTML" },
125 | );
126 | continue;
127 | }
128 |
129 | // Update progress message.
130 | if (ctx.chat.type === "private") {
131 | await ctx.api.editMessageText(
132 | ctx.chat.id,
133 | message_id,
134 | `📤 [${i + 1}/${files.length}] ${
135 | sanitize(files[i].name)
136 | }
from ${sanitize(path)}
`,
137 | { parse_mode: "HTML" },
138 | );
139 | }
140 |
141 | try {
142 | const { message_id: fileMsgId } = await ctx.replyWithDocument(
143 | new InputFile(file.path, file.name),
144 | {
145 | caption: `Filename: ${sanitize(file.name)}
` +
146 | `\nPath: ${sanitize(file.path)}
` +
147 | `\nSize: ${bytes(file.size)}` +
148 | `\nCreated at: ${file.created_at}
`,
149 | parse_mode: "HTML",
150 | },
151 | );
152 |
153 | // Why not private? There's no such link to messages in private chats.
154 | if (ctx.chat.type !== "private") {
155 | uploadedFiles.push({
156 | name: sanitize(file.name),
157 | link: `https://t.me/c/${
158 | ctx.chat.id.toString().startsWith("-100")
159 | ? ctx.chat.id.toString().substring(4)
160 | : ctx.chat.id
161 | }/${fileMsgId}`,
162 | });
163 | }
164 | // Pause for a bit.
165 | await pause(ctx.chat.type);
166 | } catch (error) {
167 | await ctx.reply(`Failed to upload ${sanitize(file.name)}
.`);
168 | await pause(ctx.chat.type);
169 | console.error(error);
170 | continue;
171 | }
172 | }
173 |
174 | await ctx.api.editMessageText(ctx.chat.id, message_id, "Uploaded!");
175 | await ctx.unpinChatMessage(message_id).catch((e) => e);
176 |
177 | await ctx.reply(
178 | `Successfully uploaded ${files.length} file${
179 | files.length > 1 ? "s" : ""
180 | } from ${sanitize(path)}
`,
181 | { parse_mode: "HTML" },
182 | );
183 |
184 | if (uploadedFiles.length < 1) return;
185 | const indexMessages = createIndexMessages(uploadedFiles);
186 |
187 | for (let i = 0; i < indexMessages.length; i++) {
188 | const { message_id: idxMsgId } = await ctx.reply(
189 | indexMessages[i],
190 | { parse_mode: "HTML" },
191 | );
192 | await pause(ctx.chat.type);
193 | if (i !== 0) continue;
194 | await ctx.api.editMessageText(
195 | ctx.chat.id,
196 | message_id,
197 | `Uploaded! See index`,
202 | { parse_mode: "HTML" },
203 | );
204 | }
205 | });
206 |
207 | function createIndexMessages(fileList: UploadedFileList[]): string[] {
208 | const messages: string[] = [""];
209 | let index = 0;
210 | for (let i = 0; i < fileList.length; i++) {
211 | const file = fileList[i];
212 | const text = `\n${i + 1}. ${file.name}`;
213 | const length = messages[index].length + text.length;
214 |
215 | if (length > 4096) index++;
216 |
217 | if (messages[index] === undefined) messages[index] = "";
218 | messages[index] += `\n${i + 1}. ${file.name}`;
219 | }
220 | return messages;
221 | }
222 |
223 | interface FileList {
224 | name: string;
225 | path: string;
226 | size: number;
227 | created_at: string;
228 | }
229 |
230 | async function getFileList(path: string): Promise