Skip to content

Commit

Permalink
first commit
Browse files Browse the repository at this point in the history
  • Loading branch information
TeaByte committed Mar 2, 2024
0 parents commit aa51e9e
Show file tree
Hide file tree
Showing 12 changed files with 868 additions and 0 deletions.
7 changes: 7 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"deno.enablePaths": [
"./"
],
"deno.enable": true,
"editor.inlayHints.enabled": "off"
}
56 changes: 56 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Telegram Upload API

**Effortlessly upload and fetch files to Telegram 🚀**

---

1. **Clone The Repository**

```sh
git clone https://github.com/TeaByte/telegram-upload-api
cd telegram-upload-api
```

2. **Setup Localhost DenvKV**

Use the following Docker command to mounts a local folder into the container to store the database, and it hosts a denoKV Connect endpoint at `http://localhost:4512` with an access of `234xs266623t`.

```sh
docker run -it --init -p 4512:4512 -v ./data:/data ghcr.io/denoland/denokv --sqlite-path /data/denokv.sqlite serve --access-token 234xs266623t
```

3. **Edit The config.json File**

In the config.json file, update the following fields with your Telegram credentials and bot information:

```json
{
"apiId": "your_api_id",
"apiHash": "your_api_hash",
"chatId": -1002036530000,
"botToken": "your_bot_token",
"serverPort": 8080,
"denoKV": "http://localhost:4512/",
"kvToken": "234xs266623t"
}
```

- Replace `your_api_id` and `your_api_hash` with your Telegram credentials from https://my.telegram.org/auth.
- Obtain a bot token from [@BotFather](https://t.me/BotFather) and replace `your_bot_token`.
- `chatId` is the ID of a Telegram group where files will be saved.

4. **Start The Server**

```sh
deno task start
```

5. **Test The Server Endpoints**

```sh
deno task test
```

---

Feel free to contribute by opening issues or submitting pull requests.
9 changes: 9 additions & 0 deletions config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"apiId": 1,
"apiHash": 1,
"chatId": -1002036530000,
"botToken": "your_bot_token",
"serverPort": 8080,
"denoKV": "http://localhost:4512/",
"kvToken": "234xs266623t"
}
3 changes: 3 additions & 0 deletions config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const config = JSON.parse(Deno.readTextFileSync("./config.json"));

export default config;
31 changes: 31 additions & 0 deletions database.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/// <reference lib="deno.unstable" />
import { cuid } from "https://deno.land/x/cuid@v1.0.0/index.js";
import config from "./config.ts";

type recordId = string;
interface dbRecord {
fileName: string;
fileId: string;
fileSize: number;
}

Deno.env.set("DENO_KV_ACCESS_TOKEN", config["kvToken"]);
const kv = await Deno.openKv(config["denoKV"]);

export async function set(
fileName: string,
fileId: string,
fileSize: number
): Promise<recordId> {
const id = cuid();
await kv.set(["uploads", id], {
fileName,
fileId,
fileSize,
});
return id;
}

export async function get(id: recordId): Promise<dbRecord> {
return (await kv.get(["uploads", id])).value as dbRecord;
}
7 changes: 7 additions & 0 deletions deno.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"tasks": {
"start": "deno run -A --unstable server.ts",
"dev": "deno run -A --watch --unstable server.ts",
"test": "deno test tests.ts -A"
}
}
521 changes: 521 additions & 0 deletions deno.lock

Large diffs are not rendered by default.

96 changes: 96 additions & 0 deletions server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { Hono, Context } from "https://deno.land/x/hono@v4.0.8/mod.ts";

import * as db from "./database.ts";
import { getFromTelegram, uploadToTelegram } from "./telegram.ts";
import { saveToSystem, deleteFromSystem } from "./system.ts";
import config from "./config.ts";

const app = new Hono();

app.post("/upload", async (c: Context) => {
const body = await c.req.parseBody();
const file = body["file"];
if (file instanceof File) {
try {
if (file.size === 0 || file.size > 2000000000) {
return c.json(
{
message: "File size is too large or empty",
},
400
);
}
const savedPath = await saveToSystem(
new Uint8Array(await file.arrayBuffer()),
file.name
);
const fileId = await uploadToTelegram(savedPath);
await deleteFromSystem(savedPath);
const recordId = await db.set(file.name, fileId, file.size);
return c.json(
{
message: "File uploaded successfully",
recordId,
},
200
);
} catch (error) {
console.error(error);
return c.json(
{
message: "Failed to upload the file",
},
500
);
}
} else {
return c.json(
{
message: "Missing or invalid file",
},
400
);
}
});

app.post("/get", async (c: Context) => {
const body = await c.req.json();
const recordId = body["recordId"];
if (recordId) {
try {
const dbData = await db.get(recordId);
if (!dbData) {
return c.json(
{
message: "File not found",
},
404
);
}
const path = await getFromTelegram(dbData["fileId"], dbData["fileName"]);
const file = await Deno.readFile(path);
await deleteFromSystem(path);
return c.body(file.buffer, 200, {
"Content-Type": "application/octet-stream",
"Content-Disposition": `attachment; filename="${dbData["fileName"]}"`,
});
} catch (error) {
console.error(error);
return c.json(
{
message: "Failed to download the file",
},
500
);
}
} else {
return c.json(
{
message: "File ID is required",
},
400
);
}
});

Deno.serve({ port: config["serverPort"] }, app.fetch);
18 changes: 18 additions & 0 deletions system.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
export async function saveToSystem(
buffer: Uint8Array,
fileName: string
): Promise<string> {
const path = `./temp/${fileName}`;
const file = await Deno.open(path, {
write: true,
create: true,
truncate: true,
});
await file.write(buffer);
await file.close();
return path;
}

export async function deleteFromSystem(path: string) {
await Deno.remove(path);
}
49 changes: 49 additions & 0 deletions telegram.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import {
Client,
StorageLocalStorage,
} from "https://deno.land/x/mtkruto@0.1.155/mod.ts";

import config from "./config.ts";

export const client = new Client(
new StorageLocalStorage("client"),
config["apiId"],
config["apiHash"]
);

await client
.start(config["botToken"])
.catch((error) => console.error("Failed to start client", error));

try {
const me = await client.getMe();
console.log(me);
} catch {
console.error(
"Failed to get me make sure the bot token is valid and apiId and apiHash are correct"
);
}

export async function uploadToTelegram(path: string) {
const file = await client.sendDocument(config["chatId"], path);
return file.document.fileId;
}

export async function getFromTelegram(fileId: string, fileName: string) {
const path = `./temp/${fileName}`;
const outFile = await Deno.open(path, {
write: true,
create: true,
truncate: true,
});
try {
for await (const chunk of client.download(fileId, {
chunkSize: 256 * 1024,
})) {
await Deno.write(outFile.rid, chunk);
}
} finally {
await Deno.close(outFile.rid);
}
return path;
}
21 changes: 21 additions & 0 deletions test/test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import requests


def upload():
r = requests.post("http://localhost:8000/upload",
files={"file": open("deno.json", "rb")})

print(r.text)


def get():
r = requests.post("http://localhost:8000/get", json={
"recordId": "clta660bb0000s0ug07zqm5nq"})

print(r.text)
print(r.status_code)
print(r.headers)


if __name__ == "__main__":
get()
50 changes: 50 additions & 0 deletions tests.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { assertEquals } from "https://deno.land/std@0.218.0/assert/mod.ts";
import axiod from "https://deno.land/x/axiod@0.26.0/mod.ts";
import config from "./config.ts";

const port = config["serverPort"];

Deno.test("Upload Test", async () => {
const formData = new FormData();
formData.append("file", new File(["Hello, World!"], "test.txt"));
const uploadResponse = await axiod.post(
`http://localhost:${port}/upload`,
formData
);

const result = await uploadResponse.data;

assertEquals(result["message"], "File uploaded successfully");
assertEquals(uploadResponse.status, 200);
console.log("Upload Response: ", result);

const getResponse = await axiod.post(`http://localhost:${port}/get`, {
recordId: result["recordId"],
});

assertEquals(getResponse.data, "Hello, World!");
assertEquals(getResponse.status, 200);
console.log(getResponse.data);
});

Deno.test("Get Non-Existent File Test", async () => {
await axiod
.post(`http://localhost:${port}/get`, {
recordId: "i-don't-exist",
})
.catch((e) => {
assertEquals(e.response.data["message"], "File not found");
assertEquals(e.response.status, 404);
console.log(e.response.data);
});
});

Deno.test("Upload Empty File Test", async () => {
const formData = new FormData();
formData.append("file", new File([""], "test.txt"));
await axiod.post(`http://localhost:${port}/upload`, formData).catch((e) => {
assertEquals(e.response.data["message"], "File size is too large or empty");
assertEquals(e.response.status, 400);
console.log(e.response.data);
});
});

0 comments on commit aa51e9e

Please sign in to comment.