TGStreamer.py 8.6 KB

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