MoeClub пре 2 година
родитељ
комит
cabbce6af0
1 измењених фајлова са 253 додато и 0 уклоњено
  1. 253 0
      TGStreamer.py

+ 253 - 0
TGStreamer.py

@@ -0,0 +1,253 @@
+#!/usr/bin/env python3
+# -*- encoding: utf-8 -*-
+# Author:  MoeClub.org
+
+# pip3 install pyrogram tgcrypto aiohttp
+
+import os
+import math
+import asyncio
+from aiohttp import web
+from typing import Union
+from pyrogram import Client, raw
+from pyrogram.session import Session, Auth
+from pyrogram import file_id
+
+
+class TGSteamer:
+    # https://my.telegram.org/              # apiId, apiHash
+    # https://telegram.me/BotFather         # botToken
+
+    cacheFileId = {}
+    lock = asyncio.Lock()
+
+    @classmethod
+    async def chunk_size(cls, length):
+        return 2 ** max(min(math.ceil(math.log2(length / 1024)), 10), 2) * 1024
+
+    @classmethod
+    async def offset_fix(cls, offset, chunkSize):
+        offset -= offset % chunkSize
+        return offset
+
+    @classmethod
+    async def get_client(cls, apiId, apiHash, botToken, appName=os.path.basename(os.path.abspath(__file__)).split(".")[0]):
+        _client = Client(
+            name=appName,
+            api_id=int(str(apiId).strip()),
+            api_hash=str(apiHash).strip(),
+            bot_token=str(botToken).strip(),
+            in_memory=True,
+        )
+        await _client.start()
+        assert _client.is_connected and _client.is_initialized
+        return _client
+
+    @classmethod
+    async def get_file_properties(cls, fileId):
+        async with cls.lock:
+            fileProperties = cls.cacheFileId.get(fileId, None)
+            if fileProperties is None:
+                fileProperties = file_id.FileId.decode(fileId)
+                setattr(fileProperties, "file_size", getattr(fileProperties, "file_size", 0))
+                setattr(fileProperties, "file_name", getattr(fileProperties, "file_name", ""))
+                cls.cacheFileId[fileId] = fileProperties
+        return fileProperties
+
+    @classmethod
+    async def get_session(cls, client: Client, data: file_id.FileId):
+        async with client.media_sessions_lock:
+            session = client.media_sessions.get(data.dc_id, None)
+
+            if session is None:
+                test_mode = await client.storage.test_mode()
+                dc_id = await client.storage.dc_id()
+                if data.dc_id != dc_id:
+                    auth = await Auth(client, data.dc_id, test_mode).create()
+                else:
+                    auth = await client.storage.auth_key()
+
+                session = Session(client, data.dc_id, auth, test_mode, is_media=True, is_cdn=False)
+
+                try:
+                    await session.start()
+                    if data.dc_id != dc_id:
+                        exported = await client.invoke(raw.functions.auth.ExportAuthorization(dc_id=data.dc_id))
+                        await session.invoke(raw.functions.auth.ImportAuthorization(id=exported.id, bytes=exported.bytes))
+                    client.media_sessions[data.dc_id] = session
+                except Exception as e:
+                    session = None
+
+        return session
+
+    @classmethod
+    async def get_location(cls, data: file_id.FileId):
+        file_type = data.file_type
+
+        if file_type == file_id.FileType.PHOTO:
+            location = raw.types.InputPhotoFileLocation(
+                id=data.media_id,
+                access_hash=data.access_hash,
+                file_reference=data.file_reference,
+                thumb_size=data.thumbnail_size
+            )
+        else:
+            location = raw.types.InputDocumentFileLocation(
+                id=data.media_id,
+                access_hash=data.access_hash,
+                file_reference=data.file_reference,
+                thumb_size=data.thumbnail_size
+            )
+
+        return location
+
+    @classmethod
+    async def yield_bytes(cls, client: Client, fileId: file_id.FileId, offset: int, chunkSize: int) -> Union[str, None]:
+        data = cls.get_file_properties(fileId) if isinstance(fileId, str) else fileId
+        location = await cls.get_location(data)
+        session = await cls.get_session(client, data)
+
+        if session is None:
+            raise Exception("InvalidSession")
+
+        r = await session.send(
+            raw.functions.upload.GetFile(
+                location=location,
+                offset=offset,
+                limit=chunkSize
+            ),
+        )
+
+        if isinstance(r, raw.types.upload.File):
+            while True:
+                chunk = r.bytes
+                if not chunk:
+                    break
+
+                offset += chunkSize
+                yield chunk
+
+                r = await session.send(
+                    raw.functions.upload.GetFile(
+                        location=location,
+                        offset=offset,
+                        limit=chunkSize
+                    ),
+                )
+
+    @classmethod
+    async def download_as_bytesio(cls, client, fileId, chunkSize=1024 * 1024):
+        data = cls.get_file_properties(fileId) if isinstance(fileId, str) else fileId
+        location = await cls.get_location(data)
+        session = await cls.get_session(client, data)
+
+        if session is None:
+            raise Exception("InvalidSession")
+
+        offset = 0
+
+        r = await session.send(
+            raw.functions.upload.GetFile(
+                location=location,
+                offset=offset,
+                limit=chunkSize
+            )
+        )
+
+        Bytes = []
+        if isinstance(r, raw.types.upload.File):
+            while True:
+                chunk = r.bytes
+
+                if not chunk:
+                    break
+
+                Bytes += chunk
+
+                offset += chunkSize
+
+                r = await session.send(
+                    raw.functions.upload.GetFile(
+                        location=location,
+                        offset=offset,
+                        limit=chunkSize
+                    )
+                )
+
+        return Bytes
+
+
+class Web:
+    TelegramFile = TGSteamer()
+    TelegramFileClient = None
+    Index = "TelegramFile"
+
+    @classmethod
+    def Headers(cls, **kwargs):
+        headers = {
+            "Server": "TelegramFile"
+        }
+        for item in kwargs:
+            headers[item] = kwargs[item]
+        return headers
+
+    @classmethod
+    async def fileHandler(cls, request: web.Request):
+        try:
+            _fileId = str(request.match_info["fileId"]).strip("/")
+            assert len(_fileId) > 0
+            try:
+                fileId = await cls.TelegramFile.get_file_properties(_fileId)
+            except Exception as e:
+                raise Exception("Invalid FileId")
+            return await cls.streamer(request=request, fileId=fileId)
+        except Exception as e:
+            return web.Response(text=str(e).strip(), status=404, headers={"Server": cls.Index}, content_type="text/plain")
+
+    @classmethod
+    async def streamer(cls, request: web.Request, fileId: file_id.FileId):
+        range_header = request.headers.get("Range", 0)
+        file_size = fileId.file_size
+        rangeSupport = True if file_size > 0 else False
+        file_name = str(fileId.media_id).strip() if fileId.file_name == "" else str(fileId.file_name).strip()
+
+        headers = {
+            "Content-Type": "application/octet-stream",
+            "Content-Disposition": f'attachment; filename="{file_name}"',
+            "Server": cls.Index,
+        }
+
+        try:
+            assert range_header and rangeSupport
+            from_bytes, until_bytes = range_header.replace("bytes=", "").split("-")
+            from_bytes = int(from_bytes) if int(from_bytes) >= 0 else 0
+            until_bytes = int(until_bytes) if until_bytes and int(until_bytes) > from_bytes else file_size - 1
+            req_length = until_bytes - from_bytes + 1
+            headers["Accept-Ranges"] = "bytes"
+            headers["Content-Length"] = str(req_length),
+            headers["Content-Range"] = f"bytes {from_bytes}-{until_bytes}/{file_size}"
+        except:
+            from_bytes = 0
+
+        chunk_size = 1024 * 1024 if file_size <= 0 else cls.TelegramFile.chunk_size(file_size)
+        offset = from_bytes - (from_bytes % chunk_size)
+
+        body = cls.TelegramFile.yield_bytes(cls.TelegramFileClient, fileId, offset, chunk_size)
+        code = 206 if rangeSupport else 200
+
+        return web.Response(status=code, body=body, headers=headers)
+
+
+if __name__ == "__main__":
+    loop = asyncio.get_event_loop()
+    Web.TelegramFileClient = loop.run_until_complete(Web.TelegramFile.get_client(
+        int("appId"),
+        str("appHash"),
+        str("botToken")
+    ))
+    app = web.Application()
+    app.add_routes([web.get(path=str("/{}").format(str(Web.Index).strip("/")) + '{fileId:/[-_\w]+}', handler=Web.fileHandler, allow_head=False)])
+
+    logging_format = '%t %a %s %r [%Tfs]'
+    web.run_app(app=app, host="0.0.0.0", port=63838, access_log_format=logging_format, loop=loop)
+