Mbonea-Mjema commited on
Commit
00c68f3
1 Parent(s): 49a605b
Dockerfile ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ FROM python:latest
2
+ WORKDIR /usr/src/nginx
3
+ COPY . .
4
+
5
+ RUN pip install -r requirements.txt
6
+ CMD export PYTHONPATH="${PYTHONPATH}:./src" && python3 ./src/app.py
7
+ EXPOSE 8080
src/Classes/Download.py ADDED
@@ -0,0 +1,115 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import math, asyncio, subprocess
2
+ from telethon import TelegramClient
3
+ from aiohttp import web
4
+
5
+ import logging
6
+
7
+ logging.getLogger(__name__)
8
+ logging.basicConfig(level=logging.INFO)
9
+
10
+
11
+ class Download:
12
+ client: TelegramClient
13
+ route: str
14
+ offset: int
15
+ handler: None
16
+ file: None
17
+ limit: int
18
+ file_size: float
19
+
20
+ def __init__(self, handler):
21
+ self.handler = handler
22
+ self.file = handler.message.media
23
+ self.file_size = handler.message.file.size
24
+ self.limit = handler.sanity.limit
25
+ self.offset = handler.sanity.offset
26
+ self.client = handler.client
27
+ self.mime_type = handler.message.file.mime_type
28
+
29
+ async def download(self):
30
+ part_size = int(512 * 1024) * 2
31
+ first_part_cut = self.offset % part_size
32
+ first_part = math.floor(self.offset / part_size)
33
+ last_part_cut = part_size - (self.limit % part_size)
34
+ last_part = math.ceil(self.limit / part_size)
35
+ part_count = math.ceil(self.file_size / part_size)
36
+ part = first_part
37
+ try:
38
+ async for chunk in self.client.iter_download(
39
+ self.file, offset=first_part * part_size, request_size=part_size
40
+ ):
41
+
42
+ if part == first_part:
43
+ yield chunk[first_part_cut:]
44
+ elif part == last_part:
45
+ yield chunk[:last_part_cut]
46
+ else:
47
+ yield chunk
48
+ logging.debug(f"Part {part}/{last_part} (total {part_count}) served!")
49
+ part += 1
50
+ logging.debug("serving finished")
51
+ except (GeneratorExit, StopAsyncIteration, asyncio.CancelledError):
52
+ logging.debug("file serve interrupted")
53
+
54
+ raise
55
+ except Exception as e:
56
+ print(e)
57
+ logging.debug("file serve errored", exc_info=True)
58
+
59
+ async def handle_request(self):
60
+ headers = {
61
+ "content-type": self.mime_type,
62
+ "content-range": f"bytes {self.offset}-{self.limit-1}/{self.file_size}",
63
+ "content-length": str(self.limit - self.offset),
64
+ "accept-ranges": "bytes",
65
+ "content-transfer-encoding": "Binary",
66
+ "content-disposition": f'{self.handler.route}; filename="{self.handler.message.file.name}"',
67
+ }
68
+ logging.info(
69
+ f"Serving file in {self.handler.message.file.name}) ; Range: {self.offset} - {self.limit}"
70
+ )
71
+ if self.handler.head:
72
+ body = None
73
+ else:
74
+
75
+ body = self.download()
76
+ # if body:
77
+
78
+ # ffmpeg = "ffmpeg"
79
+
80
+ # cmd = [
81
+ # ffmpeg,
82
+ # "ffmpeg",
83
+ # "-i",
84
+
85
+
86
+ # "pipe:0",
87
+ # "-c",
88
+ # "copy",
89
+ # "-re",
90
+
91
+ # "pipe:1",
92
+ # ]
93
+ # ffmpeg_cmd = subprocess.Popen(
94
+ # cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, shell=False
95
+ # )
96
+ # body = await body.__anext__()
97
+ # ffmpeg_cmd.stdin.write(body.tobytes())
98
+ # ffmpeg_cmd.stdin.close()
99
+ # body = b""
100
+ # while True:
101
+ # print(body)
102
+ # output = ffmpeg_cmd.stdout.read()
103
+
104
+ # if len(output) > 0:
105
+ # body += output
106
+ # ffmpeg_cmd.stdout.close()
107
+
108
+ # else:
109
+ # error_msg = ffmpeg_cmd.poll()
110
+ # if error_msg is not None:
111
+ # break
112
+
113
+ return web.Response(
114
+ body=body, headers=headers, status=206 if self.offset else 200
115
+ )
src/Classes/Hander.py ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from src.Classes.Sanity import Sanity
2
+ from aiohttp.web import Request
3
+ from aiohttp import web
4
+ from telethon import TelegramClient
5
+ from telethon.tl.types import Message
6
+ from src.Classes.Download import Download
7
+
8
+
9
+ class Handler:
10
+ req: Request
11
+ client: TelegramClient
12
+ chat_id = -1001278111932
13
+ message: Message
14
+ route: str
15
+ head = False
16
+ sanity: Sanity
17
+
18
+ def __init__(self, req: Request, client, route=None, head=False):
19
+ self.head = head
20
+ self.req = req
21
+ self.client = client
22
+ self.sanity = Sanity()
23
+ self.sanity.client = self.client
24
+ self.sanity.chat_id = self.chat_id
25
+ self.sanity.req = self.req
26
+ self.sanity.file_id = int(self.req.match_info["id"])
27
+
28
+ async def sanity_checks(self):
29
+ self.message = await self.sanity.file_exists()
30
+
31
+ try:
32
+ if not self.message.media:
33
+ return web.json_response(
34
+ status=404,
35
+ data={"Error": "File Does not Exist", "route": self.route},
36
+ )
37
+ except:
38
+ return web.json_response(
39
+ status=404, data={"Error": "File Does not Exist", "route": self.route}
40
+ )
41
+
42
+ if self.sanity.check_ranges() == False:
43
+ return web.json_response(
44
+ status=416,
45
+ text="416: Range Not Satisfiable",
46
+ headers={"Content-Range": f"bytes */{self.message.file.size}"},
47
+ )
48
+
49
+ async def process_request(self):
50
+ response = await self.sanity_checks()
51
+ if type(response) is web.Response:
52
+ return response
53
+
54
+ # download/stream
55
+ return await Download(self).handle_request()
src/Classes/Sanity.py ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from telethon import TelegramClient
2
+ from aiohttp.web import Request
3
+
4
+
5
+ class Sanity:
6
+
7
+ client: TelegramClient
8
+ media = None
9
+ chat_id: int
10
+ file_id: int
11
+ req: Request
12
+ limit: int
13
+ offset: int
14
+
15
+ async def file_exists(self):
16
+ try:
17
+ self.media = await self.client.get_messages(
18
+ entity=self.chat_id, ids=self.file_id
19
+ )
20
+ return self.media
21
+ except Exception as e:
22
+ pass
23
+
24
+ def check_ranges(self):
25
+
26
+ offset = self.req.http_range.start or 0
27
+
28
+ limit = self.req.http_range.stop or self.media.file.size
29
+ self.offset = offset
30
+ self.limit = limit
31
+
32
+ if (limit > self.media.file.size) or (offset < 0) or (limit < offset):
33
+
34
+ return False
35
+ else:
36
+
37
+ return True
src/__main__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ import app
src/app.py ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ import asyncio
3
+ from aiohttp import web
4
+ import asyncio
5
+ from telethon import TelegramClient
6
+ from src.Classes.Hander import Handler
7
+ import os
8
+
9
+ routes = web.RouteTableDef()
10
+ client = TelegramClient("stream_bot", 870972, "ce2efaca02dfcd110941be6025e9ac0d")
11
+
12
+
13
+ @routes.get(r"/")
14
+ async def get_status(req: web.Request):
15
+ return web.json_response(data={"status": "working"}, status=200)
16
+
17
+
18
+ @routes.get(r"/stream/{id:\d+}")
19
+ async def handle_get_request(req: web.Request) -> web.Response:
20
+ hander = Handler(req=req, client=client)
21
+ hander.route = "inline"
22
+ return await hander.process_request()
23
+
24
+
25
+ @routes.head(r"/download/{id:\d+}")
26
+ async def handle_get_request(req: web.Request) -> web.Response:
27
+ hander = Handler(req=req, client=client)
28
+ hander.head = True
29
+ hander.route = "attachment"
30
+ return await hander.process_request()
31
+
32
+
33
+ @routes.get(r"/download/{id:\d+}")
34
+ async def handle_get_request(req: web.Request) -> web.Response:
35
+ hander = Handler(req=req, client=client)
36
+ hander.route = "attachment"
37
+ return await hander.process_request()
38
+
39
+
40
+ async def main():
41
+ port = int(os.environ.get("PORT", "8080"))
42
+ await client.start(bot_token="384248541:AAFRkVeRyCUvlO_JuIzsE5qbWGt7-Mi7WKk")
43
+ server = web.Application()
44
+ server.add_routes(routes)
45
+ runner = web.AppRunner(server)
46
+ await runner.setup()
47
+
48
+ await web.TCPSite(runner, port=port).start()
49
+
50
+
51
+ if __name__ == "__main__":
52
+ loop = asyncio.get_event_loop()
53
+ loop.run_until_complete(main())
54
+ loop.run_forever()
55
+