Skip to content

Commit cabbce6

Browse files
authored
Add files via upload
1 parent 831e378 commit cabbce6

File tree

1 file changed

+253
-0
lines changed

1 file changed

+253
-0
lines changed

TGStreamer.py

+253
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
1+
#!/usr/bin/env python3
2+
# -*- encoding: utf-8 -*-
3+
# Author: MoeClub.org
4+
5+
# pip3 install pyrogram tgcrypto aiohttp
6+
7+
import os
8+
import math
9+
import asyncio
10+
from aiohttp import web
11+
from typing import Union
12+
from pyrogram import Client, raw
13+
from pyrogram.session import Session, Auth
14+
from pyrogram import file_id
15+
16+
17+
class TGSteamer:
18+
# https://my.telegram.org/ # apiId, apiHash
19+
# https://telegram.me/BotFather # botToken
20+
21+
cacheFileId = {}
22+
lock = asyncio.Lock()
23+
24+
@classmethod
25+
async def chunk_size(cls, length):
26+
return 2 ** max(min(math.ceil(math.log2(length / 1024)), 10), 2) * 1024
27+
28+
@classmethod
29+
async def offset_fix(cls, offset, chunkSize):
30+
offset -= offset % chunkSize
31+
return offset
32+
33+
@classmethod
34+
async def get_client(cls, apiId, apiHash, botToken, appName=os.path.basename(os.path.abspath(__file__)).split(".")[0]):
35+
_client = Client(
36+
name=appName,
37+
api_id=int(str(apiId).strip()),
38+
api_hash=str(apiHash).strip(),
39+
bot_token=str(botToken).strip(),
40+
in_memory=True,
41+
)
42+
await _client.start()
43+
assert _client.is_connected and _client.is_initialized
44+
return _client
45+
46+
@classmethod
47+
async def get_file_properties(cls, fileId):
48+
async with cls.lock:
49+
fileProperties = cls.cacheFileId.get(fileId, None)
50+
if fileProperties is None:
51+
fileProperties = file_id.FileId.decode(fileId)
52+
setattr(fileProperties, "file_size", getattr(fileProperties, "file_size", 0))
53+
setattr(fileProperties, "file_name", getattr(fileProperties, "file_name", ""))
54+
cls.cacheFileId[fileId] = fileProperties
55+
return fileProperties
56+
57+
@classmethod
58+
async def get_session(cls, client: Client, data: file_id.FileId):
59+
async with client.media_sessions_lock:
60+
session = client.media_sessions.get(data.dc_id, None)
61+
62+
if session is None:
63+
test_mode = await client.storage.test_mode()
64+
dc_id = await client.storage.dc_id()
65+
if data.dc_id != dc_id:
66+
auth = await Auth(client, data.dc_id, test_mode).create()
67+
else:
68+
auth = await client.storage.auth_key()
69+
70+
session = Session(client, data.dc_id, auth, test_mode, is_media=True, is_cdn=False)
71+
72+
try:
73+
await session.start()
74+
if data.dc_id != dc_id:
75+
exported = await client.invoke(raw.functions.auth.ExportAuthorization(dc_id=data.dc_id))
76+
await session.invoke(raw.functions.auth.ImportAuthorization(id=exported.id, bytes=exported.bytes))
77+
client.media_sessions[data.dc_id] = session
78+
except Exception as e:
79+
session = None
80+
81+
return session
82+
83+
@classmethod
84+
async def get_location(cls, data: file_id.FileId):
85+
file_type = data.file_type
86+
87+
if file_type == file_id.FileType.PHOTO:
88+
location = raw.types.InputPhotoFileLocation(
89+
id=data.media_id,
90+
access_hash=data.access_hash,
91+
file_reference=data.file_reference,
92+
thumb_size=data.thumbnail_size
93+
)
94+
else:
95+
location = raw.types.InputDocumentFileLocation(
96+
id=data.media_id,
97+
access_hash=data.access_hash,
98+
file_reference=data.file_reference,
99+
thumb_size=data.thumbnail_size
100+
)
101+
102+
return location
103+
104+
@classmethod
105+
async def yield_bytes(cls, client: Client, fileId: file_id.FileId, offset: int, chunkSize: int) -> Union[str, None]:
106+
data = cls.get_file_properties(fileId) if isinstance(fileId, str) else fileId
107+
location = await cls.get_location(data)
108+
session = await cls.get_session(client, data)
109+
110+
if session is None:
111+
raise Exception("InvalidSession")
112+
113+
r = await session.send(
114+
raw.functions.upload.GetFile(
115+
location=location,
116+
offset=offset,
117+
limit=chunkSize
118+
),
119+
)
120+
121+
if isinstance(r, raw.types.upload.File):
122+
while True:
123+
chunk = r.bytes
124+
if not chunk:
125+
break
126+
127+
offset += chunkSize
128+
yield chunk
129+
130+
r = await session.send(
131+
raw.functions.upload.GetFile(
132+
location=location,
133+
offset=offset,
134+
limit=chunkSize
135+
),
136+
)
137+
138+
@classmethod
139+
async def download_as_bytesio(cls, client, fileId, chunkSize=1024 * 1024):
140+
data = cls.get_file_properties(fileId) if isinstance(fileId, str) else fileId
141+
location = await cls.get_location(data)
142+
session = await cls.get_session(client, data)
143+
144+
if session is None:
145+
raise Exception("InvalidSession")
146+
147+
offset = 0
148+
149+
r = await session.send(
150+
raw.functions.upload.GetFile(
151+
location=location,
152+
offset=offset,
153+
limit=chunkSize
154+
)
155+
)
156+
157+
Bytes = []
158+
if isinstance(r, raw.types.upload.File):
159+
while True:
160+
chunk = r.bytes
161+
162+
if not chunk:
163+
break
164+
165+
Bytes += chunk
166+
167+
offset += chunkSize
168+
169+
r = await session.send(
170+
raw.functions.upload.GetFile(
171+
location=location,
172+
offset=offset,
173+
limit=chunkSize
174+
)
175+
)
176+
177+
return Bytes
178+
179+
180+
class Web:
181+
TelegramFile = TGSteamer()
182+
TelegramFileClient = None
183+
Index = "TelegramFile"
184+
185+
@classmethod
186+
def Headers(cls, **kwargs):
187+
headers = {
188+
"Server": "TelegramFile"
189+
}
190+
for item in kwargs:
191+
headers[item] = kwargs[item]
192+
return headers
193+
194+
@classmethod
195+
async def fileHandler(cls, request: web.Request):
196+
try:
197+
_fileId = str(request.match_info["fileId"]).strip("/")
198+
assert len(_fileId) > 0
199+
try:
200+
fileId = await cls.TelegramFile.get_file_properties(_fileId)
201+
except Exception as e:
202+
raise Exception("Invalid FileId")
203+
return await cls.streamer(request=request, fileId=fileId)
204+
except Exception as e:
205+
return web.Response(text=str(e).strip(), status=404, headers={"Server": cls.Index}, content_type="text/plain")
206+
207+
@classmethod
208+
async def streamer(cls, request: web.Request, fileId: file_id.FileId):
209+
range_header = request.headers.get("Range", 0)
210+
file_size = fileId.file_size
211+
rangeSupport = True if file_size > 0 else False
212+
file_name = str(fileId.media_id).strip() if fileId.file_name == "" else str(fileId.file_name).strip()
213+
214+
headers = {
215+
"Content-Type": "application/octet-stream",
216+
"Content-Disposition": f'attachment; filename="{file_name}"',
217+
"Server": cls.Index,
218+
}
219+
220+
try:
221+
assert range_header and rangeSupport
222+
from_bytes, until_bytes = range_header.replace("bytes=", "").split("-")
223+
from_bytes = int(from_bytes) if int(from_bytes) >= 0 else 0
224+
until_bytes = int(until_bytes) if until_bytes and int(until_bytes) > from_bytes else file_size - 1
225+
req_length = until_bytes - from_bytes + 1
226+
headers["Accept-Ranges"] = "bytes"
227+
headers["Content-Length"] = str(req_length),
228+
headers["Content-Range"] = f"bytes {from_bytes}-{until_bytes}/{file_size}"
229+
except:
230+
from_bytes = 0
231+
232+
chunk_size = 1024 * 1024 if file_size <= 0 else cls.TelegramFile.chunk_size(file_size)
233+
offset = from_bytes - (from_bytes % chunk_size)
234+
235+
body = cls.TelegramFile.yield_bytes(cls.TelegramFileClient, fileId, offset, chunk_size)
236+
code = 206 if rangeSupport else 200
237+
238+
return web.Response(status=code, body=body, headers=headers)
239+
240+
241+
if __name__ == "__main__":
242+
loop = asyncio.get_event_loop()
243+
Web.TelegramFileClient = loop.run_until_complete(Web.TelegramFile.get_client(
244+
int("appId"),
245+
str("appHash"),
246+
str("botToken")
247+
))
248+
app = web.Application()
249+
app.add_routes([web.get(path=str("/{}").format(str(Web.Index).strip("/")) + '{fileId:/[-_\w]+}', handler=Web.fileHandler, allow_head=False)])
250+
251+
logging_format = '%t %a %s %r [%Tfs]'
252+
web.run_app(app=app, host="0.0.0.0", port=63838, access_log_format=logging_format, loop=loop)
253+

0 commit comments

Comments
 (0)