├── .vscode └── settings.json ├── README.md ├── config.json ├── deno.json ├── server.ts ├── tests.ts └── utils ├── config.ts ├── messages.ts └── telegram.ts /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "deno.enablePaths": [ 3 | "./" 4 | ], 5 | "deno.enable": true, 6 | "editor.inlayHints.enabled": "off" 7 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Telegram Upload API 2 | 3 | **Effortlessly upload and fetch files from Telegram 🚀** 4 | 5 | - Made using [deno](https://deno.land/), [hono](https://hono.dev/) and [mtkruto](https://mtkruto.deno.dev/). 6 | 7 | --- 8 | 9 | 1. **Clone The Repository** 10 | 11 | ```sh 12 | git clone https://github.com/TeaByte/telegram-upload-api 13 | cd telegram-upload-api 14 | ``` 15 | 16 | 2. **Edit The config.json File** 17 | 18 | In the `config.json` file, update the following fields with your Telegram credentials and bot information: 19 | 20 | ```json 21 | { 22 | "apiId": 143..., 23 | "apiHash": "bd4fed...", 24 | "chatId": -10021442..., 25 | "botToken": "1234585269:AAGL9o....", 26 | "serverPort": 8080 27 | } 28 | ``` 29 | 30 | - Replace `your_api_id` and `your_api_hash` with your Telegram credentials from https://my.telegram.org/auth. 31 | - Obtain a bot token from [@BotFather](https://t.me/BotFather) and replace `your_bot_token`. 32 | - `chatId` is the ID of a Telegram group where files will be saved ( You can get it from [@WhatChatIDBot](https://t.me/WhatChatIDBot) ). 33 | 34 | 3. **Start The Server** 35 | 36 | ```sh 37 | deno task start 38 | ``` 39 | 40 | 4. **Test The Server Endpoints** 41 | 42 | ```sh 43 | deno task test 44 | ``` 45 | 46 | --- 47 | 48 | ## APIs and Usage 49 | 50 | The Telegram Upload API provides the following endpoints: 51 | 52 | ### 1. Upload Endpoint 53 | 54 | - **Endpoint**: `/upload` 55 | - **Method**: `POST` 56 | - **Parameters**: 57 | - `file`: The file to be uploaded (multipart form data) 58 | 59 | #### Example 60 | 61 | ```sh 62 | curl -X POST -F "file=@/path/to/file.txt" http://localhost:8080/upload 63 | ``` 64 | 65 | #### Response 66 | 67 | ```json 68 | { 69 | "message": "File uploaded successfully", 70 | "fileId": "the_file_id", 71 | "file": { 72 | "size": 12345, 73 | "name": "file.txt" 74 | } 75 | } 76 | ``` 77 | 78 | ### 2. Fetch Endpoint 79 | 80 | - **Endpoint**: `/fetch` 81 | - **Method**: `GET` 82 | - **Parameters**: 83 | - `fileId`: The unique identifier for the file record 84 | - `mainFileName` (Optional): The name to use when downloading the file 85 | 86 | #### Example 87 | 88 | ```sh 89 | curl http://localhost:8080/fetch?fileId=the_file_id 90 | ``` 91 | 92 | #### Response 93 | 94 | ``` 95 | The file content is streamed back as a download. 96 | ``` 97 | -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "apiId": 143345, 3 | "apiHash": "bd4fed...", 4 | "chatId": -1002144229689, 5 | "botToken": "1234585269:AAGL9o....", 6 | "serverPort": 8080 7 | } 8 | -------------------------------------------------------------------------------- /deno.json: -------------------------------------------------------------------------------- 1 | { 2 | "lock": false, 3 | "tasks": { 4 | "start": "deno run -A server.ts", 5 | "dev": "deno run -A --watch server.ts", 6 | "test": "deno test -A tests.ts" 7 | }, 8 | "imports": { 9 | "hono": "https://deno.land/x/hono@v4.0.8/mod.ts", 10 | "mtkruto": "https://deno.land/x/mtkruto@0.1.800/mod.ts", 11 | "cuid": "https://deno.land/x/cuid@v1.0.0/index.js" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /server.ts: -------------------------------------------------------------------------------- 1 | import { Hono, Context } from "hono"; 2 | import { cuid } from "cuid"; 3 | 4 | import config from "./utils/config.ts"; 5 | import { uploadToTelegram, fetchFromTelegram } from "./utils/telegram.ts"; 6 | import { errorResponse } from "./utils/messages.ts"; 7 | 8 | const app = new Hono(); 9 | 10 | app.post("/upload", async (c: Context) => { 11 | const body = await c.req.parseBody(); 12 | const file = body["file"]; 13 | 14 | if (!(file instanceof File)) { 15 | return errorResponse(c, "Missing or invalid file"); 16 | } 17 | if (!(file.size && file.size <= 2000000000)) { 18 | return errorResponse(c, "File size is too large or empty"); 19 | } 20 | 21 | try { 22 | const fileId = await uploadToTelegram(file); 23 | return c.json( 24 | { 25 | message: "File uploaded successfully", 26 | fileId, 27 | file: { 28 | size: file.size, 29 | name: file.name, 30 | }, 31 | }, 32 | 200 33 | ); 34 | } catch (error) { 35 | console.error(error); 36 | return errorResponse(c, "Failed to upload the file"); 37 | } 38 | }); 39 | 40 | app.get("/fetch", async (c: Context) => { 41 | const fileId = c.req.query("fileId"); 42 | const mainFileName = c.req.query("mainFileName"); 43 | const tempName = cuid(); 44 | 45 | if (!(fileId && typeof fileId == "string")) { 46 | return errorResponse(c, "FileId is missing"); 47 | } 48 | 49 | try { 50 | const fileData = await fetchFromTelegram(fileId); 51 | const contentLength = fileData.length; 52 | return c.body(fileData, 200, { 53 | "Content-Type": "application/octet-stream", 54 | "Content-Length": contentLength.toString(), 55 | "Content-Disposition": `attachment; filename="${ 56 | mainFileName || tempName 57 | }"`, 58 | }); 59 | } catch (error) { 60 | console.error(error); 61 | return errorResponse(c, "Failed to download the file"); 62 | } 63 | }); 64 | 65 | Deno.serve({ port: config["serverPort"] }, app.fetch); 66 | -------------------------------------------------------------------------------- /tests.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals } from "https://deno.land/std@0.218.0/assert/mod.ts"; 2 | import axiod from "https://deno.land/x/axiod@0.26.0/mod.ts"; 3 | import config from "./utils/config.ts"; 4 | 5 | const port = config["serverPort"]; 6 | 7 | Deno.test("Upload Test", async () => { 8 | const formData = new FormData(); 9 | formData.append("file", new File(["Hello, World!"], "test.txt")); 10 | const uploadResponse = await axiod.post( 11 | `http://localhost:${port}/upload`, 12 | formData 13 | ); 14 | 15 | const result = await uploadResponse.data; 16 | 17 | assertEquals(result["message"], "File uploaded successfully"); 18 | assertEquals(uploadResponse.status, 200); 19 | console.log("Upload Response: ", result); 20 | 21 | const fetchResponse = await axiod.get( 22 | `http://localhost:${port}/fetch?fileId=${result["fileId"]}` 23 | ); 24 | 25 | assertEquals(fetchResponse.data, "Hello, World!"); 26 | assertEquals(fetchResponse.status, 200); 27 | console.log(fetchResponse.data); 28 | }); 29 | 30 | Deno.test("Fetch Non-Existent File Test", async () => { 31 | await axiod 32 | .get(`http://localhost:${port}/fetch?fileId=blablabla&mainFileName=x.txt`) 33 | .catch((e) => { 34 | assertEquals(e.response.data["message"], "Failed to download the file"); 35 | assertEquals(e.response.status, 400); 36 | console.log(e.response.data); 37 | }); 38 | }); 39 | 40 | Deno.test("Upload Empty File Test", async () => { 41 | const formData = new FormData(); 42 | formData.append("file", new File([""], "test.txt")); 43 | await axiod.post(`http://localhost:${port}/upload`, formData).catch((e) => { 44 | assertEquals(e.response.data["message"], "File size is too large or empty"); 45 | assertEquals(e.response.status, 400); 46 | console.log(e.response.data); 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /utils/config.ts: -------------------------------------------------------------------------------- 1 | interface Config { 2 | apiId: number; 3 | apiHash: string; 4 | chatId: number; 5 | botToken: string; 6 | serverPort: number; 7 | } 8 | 9 | function validateConfig(config: Config): void { 10 | if (typeof config.apiId !== "number" || !config.apiId) { 11 | throw new Error("apiId must be a non-empty integer"); 12 | } 13 | 14 | if (typeof config.apiHash !== "string" || !config.apiHash.trim()) { 15 | throw new Error("apiHash must be a non-empty string"); 16 | } 17 | 18 | if (typeof config.chatId !== "number" || isNaN(config.chatId)) { 19 | throw new Error("chatId must be a number"); 20 | } 21 | 22 | if (typeof config.botToken !== "string" || config.botToken.length < 40) { 23 | throw new Error( 24 | "botToken must be a string with a correct length of characters" 25 | ); 26 | } 27 | 28 | if (typeof config.serverPort !== "number" || isNaN(config.serverPort)) { 29 | throw new Error("serverPort must be a number"); 30 | } 31 | } 32 | 33 | const config: Config = JSON.parse(Deno.readTextFileSync("./config.json")); 34 | validateConfig(config); 35 | 36 | export default config; 37 | -------------------------------------------------------------------------------- /utils/messages.ts: -------------------------------------------------------------------------------- 1 | import { Context } from "hono"; 2 | 3 | export function errorResponse(c: Context, errorMessage: string) { 4 | return c.json( 5 | { 6 | message: errorMessage, 7 | }, 8 | 400 9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /utils/telegram.ts: -------------------------------------------------------------------------------- 1 | import { Client, StorageLocalStorage } from "mtkruto"; 2 | import config from "./config.ts"; 3 | 4 | export const client = new Client({ 5 | storage: new StorageLocalStorage("myclient"), 6 | apiHash: config.apiHash, 7 | apiId: config.apiId, 8 | }); 9 | 10 | const onError = (error: unknown) => { 11 | console.error("Failed to start client", error); 12 | Deno.exit(1); 13 | }; 14 | 15 | await client.start({ botToken: config.botToken }).catch(onError); 16 | const me = await client.getMe().catch(onError); 17 | console.log(`Runing as ${me.username}`); 18 | 19 | export async function uploadToTelegram(file: File) { 20 | const fileContent = new Uint8Array(await file.arrayBuffer()); 21 | const rFile = await client.sendDocument(config["chatId"], fileContent, { 22 | fileName: file.name, 23 | mimeType: file.type, 24 | }); 25 | 26 | return rFile.document.fileId; 27 | } 28 | 29 | export async function fetchFromTelegram(fileId: string): Promise { 30 | const chunks: Uint8Array[] = []; 31 | 32 | for await (const chunk of client.download(fileId, { 33 | chunkSize: 512 * 1024, 34 | })) { 35 | chunks.push(chunk); 36 | } 37 | 38 | return chunks.reduce((acc, chunk) => { 39 | const newAcc = new Uint8Array(acc.length + chunk.length); 40 | newAcc.set(acc, 0); 41 | newAcc.set(chunk, acc.length); 42 | return newAcc; 43 | }, new Uint8Array()); 44 | } 45 | --------------------------------------------------------------------------------