Advertisement
Guest User

Untitled

a guest
Sep 18th, 2018
126
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 15.17 KB | None | 0 0
  1. import os
  2. import sys
  3. import asyncio
  4. from funcy import *
  5.  
  6. from telethon import TelegramClient
  7. from telethon.network.connection import tcpabridged
  8.  
  9. from telethon.crypto import CdnDecrypter
  10. from telethon.errors import SessionPasswordNeededError, FileMigrateError
  11. from telethon.tl.functions.upload import GetFileRequest
  12. from telethon.tl.types import (DocumentAttributeAudio,
  13.                                DocumentAttributeFilename, InputDocumentFileLocation)
  14. from telethon.tl.types.upload import FileCdnRedirect
  15. from telethon.utils import get_display_name
  16. from telethon.tl.types import InputMessagesFilterMusic
  17. from telethon import utils
  18. from telethon import errors
  19. from telethon import functions
  20.  
  21. from argparse import ArgumentParser
  22. import stat
  23. import logging
  24. import errno
  25. import llfuse
  26.  
  27. #
  28. #
  29. #
  30. #
  31.  
  32. api_id =
  33. api_hash =
  34. phone =
  35. session = 'session1'
  36.  
  37. basedir = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]), '..'))
  38. if (os.path.exists(os.path.join(basedir, 'setup.py')) and
  39.         os.path.exists(os.path.join(basedir, 'src', 'llfuse.pyx'))):
  40.     sys.path.insert(0, os.path.join(basedir, 'src'))
  41.  
  42. try:
  43.     import faulthandler
  44. except ImportError:
  45.     pass
  46. else:
  47.     faulthandler.enable()
  48.  
  49. ENTRY = 0
  50. MSG = 1
  51. DOC = 2
  52.  
  53. loop = asyncio.get_event_loop()
  54.  
  55. logvfs = logging.getLogger('TGFS')
  56. logtg = logging.getLogger('telethon')
  57.  
  58.  
  59. def each(func, seq):
  60.     for s in seq:
  61.         func(s)
  62.  
  63.  
  64. class TelegramFsClient(TelegramClient):
  65.     def __init__(self, session_user_id, user_phone, api_id, api_hash):
  66.  
  67.         super().__init__(
  68.             session_user_id,
  69.             api_id,
  70.             api_hash,
  71.             connection=tcpabridged.ConnectionTcpAbridged,
  72.             # update_workers=1
  73.         )
  74.  
  75.         self.user_phone = user_phone
  76.         self.api_id = api_id
  77.         self.api_hash = api_hash
  78.  
  79.     async def auth(self):
  80.         logtg.debug('Connecting to Telegram servers...')
  81.  
  82.         try:
  83.             await self.connect()
  84.         except ConnectionError:
  85.             logtg.debug('Initial connection failed. Retrying...')
  86.             if not await self.connect():
  87.                 logtg.debug('Could not connect to Telegram servers.')
  88.                 return
  89.  
  90.         logtg.debug('Connected')
  91.  
  92.         if not await self.is_user_authorized():
  93.             logtg.debug('First run. Sending code request...')
  94.             await self.sign_in(self.user_phone)
  95.  
  96.             self_user = None
  97.             while self_user is None:
  98.                 code = input('Enter the code you just received: ')
  99.                 try:
  100.                     self_user = await self.sign_in(code=code)
  101.                 except SessionPasswordNeededError:
  102.                     pw = input('Two step verification is enabled. '
  103.                                'Please enter your password: ')
  104.  
  105.                     self_user = await self.sign_in(password=pw)
  106.  
  107.     async def get_dialogs_map(self, limit=150):
  108.         dialogs = await self.get_dialogs(limit=limit)
  109.         entities = walk(lambda d: d.entity, dialogs)
  110.  
  111.         return dict(zip(walk(get_display_name, entities), entities))
  112.  
  113.     async def get_file_chunk(self, msg, offset, limit):
  114.  
  115.         input_location = InputDocumentFileLocation(id=msg.media.document.id,
  116.                                                    access_hash=msg.media.document.access_hash,
  117.                                                    version=msg.media.document.version)
  118.  
  119.         file_size = msg.media.document.size
  120.         chunk = bytes()
  121.         _offset = working_offset = offset
  122.  
  123.         assert not _offset % 4096
  124.  
  125.         if offset % 1024 != 0:
  126.             working_offset = (offset // 1024) * 1024
  127.  
  128.         part_size_kb = 64  # get_appropriated_part_size(size)
  129.         part_size = int(part_size_kb * 1024)
  130.  
  131.         dc_id, input_location = utils.get_input_location(input_location)
  132.         exported = dc_id and self.session.dc_id != dc_id
  133.  
  134.         if exported:
  135.             try:
  136.                 sender = await self._borrow_exported_sender(dc_id)
  137.             except errors.DcIdInvalidError:
  138.                 # Can't export a sender for the ID we are currently in
  139.                 config = await self(functions.help.GetConfigRequest())
  140.                 for option in config.dc_options:
  141.                     if option.ip_address == self.session.server_address:
  142.                         self.session.set_dc(
  143.                             option.id, option.ip_address, option.port)
  144.                         self.session.save()
  145.                         break
  146.  
  147.                 # TODO Figure out why the session may have the wrong DC ID
  148.                 sender = self._sender
  149.                 exported = False
  150.         else:
  151.             # The used sender will also change if ``FileMigrateError`` occurs
  152.             sender = self._sender
  153.  
  154.         try:
  155.             _offset = working_offset
  156.             while len(chunk) < limit + (offset - working_offset):
  157.  
  158.                 _offset_of_offset = 0
  159.  
  160.                 if not _offset // (1024 * 1024) == (_offset + part_size - 1) // (1024 * 1024):
  161.                     edge = (_offset // (1024 * 1024) + 1) * (1024 * 1024)
  162.                     before = edge - offset
  163.                     temp_offset = edge - part_size
  164.  
  165.                     _offset = temp_offset
  166.                     _offset_of_offset = before
  167.  
  168.                 try:
  169.  
  170.                     result = await sender.send(functions.upload.GetFileRequest(
  171.                         input_location, _offset, part_size
  172.                     ))
  173.  
  174.                     # print(result)
  175.  
  176.                     if isinstance(result, FileCdnRedirect):
  177.                         raise NotImplementedError
  178.                         # cdn_decrypter, result = \
  179.                         #     await CdnDecrypter.prepare_decrypter(
  180.                         #         client, await self._get_cdn_client(result), result
  181.                         #     )
  182.  
  183.                 except FileMigrateError as e:
  184.                     sender = await self._borrow_exported_sender(e.new_dc)
  185.                     exported = True
  186.                     continue
  187.  
  188.                 _offset += part_size
  189.                 if not result.bytes:
  190.                     return getattr(result, 'type', '')
  191.  
  192.                 chunk += result.bytes[-_offset_of_offset:]
  193.         except Exception as e:
  194.             logging.debug("zero chunk")
  195.         finally:
  196.             if exported:
  197.                 await self._return_exported_sender(sender)
  198.             elif sender != self._sender:
  199.                 await sender.disconnect()
  200.  
  201.             # if cdn_decrypter:
  202.             #     try:
  203.             #         await cdn_decrypter.client.disconnect()
  204.             #     except:
  205.             #         pass
  206.  
  207.             ret_data = chunk[offset - working_offset:offset - working_offset + limit]
  208.             if len(ret_data) == 0:
  209.                 logging.debug("zero chunk")
  210.             return ret_data
  211.  
  212.     async def get_documents(self, entity, limit, offset_id=None, music=False):
  213.  
  214.         documents = []
  215.         messages = await self.get_messages(entity, limit=limit, offset_id=0,
  216.                                            filter=InputMessagesFilterMusic if music else None)
  217.  
  218.         for msg in messages:
  219.  
  220.             if getattr(msg, 'media', None):
  221.                 if getattr(msg.media, 'document', None):
  222.  
  223.                     document = msg.media.document
  224.                     document_data = dict.fromkeys(
  225.                         ['id', 'date', 'mime_type', 'size', 'type', 'attributes', 'download_func'])
  226.                     document_atrributes = dict.fromkeys(['file_name', 'title', 'performer', 'duration'])
  227.                     document_data['attributes'] = document_atrributes
  228.                     document_data['download_func'] = func_partial(self.get_file_chunk, msg)
  229.  
  230.                     document_data.update(id=document.id, date=document.date, mime_type=document.mime_type,
  231.                                          size=document.size, type=None)
  232.  
  233.                     for attr in msg.media.document.attributes:
  234.                         if isinstance(attr, DocumentAttributeAudio):
  235.  
  236.                             if getattr(attr, 'title', None):
  237.                                 document_atrributes['title'] = attr.title
  238.  
  239.                             if getattr(attr, 'performer', None):
  240.                                 document_atrributes['performer'] = attr.performer
  241.  
  242.                             if getattr(attr, 'duration', None):
  243.                                 document_atrributes['duration'] = int(attr.duration)
  244.  
  245.                         elif isinstance(attr, DocumentAttributeFilename):
  246.                             document_atrributes['file_name'] = attr.file_name
  247.  
  248.                     documents.append((msg, document_data))
  249.  
  250.         return documents
  251.  
  252.  
  253. class tgfsFile(object):
  254.     def __init__(self, msg, doc, inode=None, attr=None):
  255.         self.inode = inode
  256.         self.msg = msg
  257.         self.doc = doc
  258.  
  259.         self.fname = (doc['attributes'].get('file_name') or "msg_%s_doc" % msg.id).encode()
  260.         self.attr = attr
  261.  
  262.  
  263. class TestTelegramFs(llfuse.Operations):
  264.     def __init__(self, documents):
  265.         super(TestTelegramFs, self).__init__()
  266.  
  267.         self.documents = documents
  268.  
  269.         self.files = self.documents_to_tgfsfiles(documents, llfuse.ROOT_INODE + 1)
  270.         self.inodes = list(self.files.keys())
  271.         self.file_by_name = walk_keys(lambda inode: self.files[inode].fname, self.files)
  272.  
  273.     def create_tgfsfile(self, msg, doc, inode):
  274.  
  275.         entry = llfuse.EntryAttributes()
  276.  
  277.         entry.st_mode = (stat.S_IFREG | 0o644)
  278.         entry.st_size = doc.get('size')
  279.  
  280.         stamp = int(doc['date'].timestamp() * 1e9) if 'date' in doc else int(1438467123.985654 * 1e9)
  281.         entry.st_atime_ns = stamp
  282.         entry.st_ctime_ns = stamp
  283.         entry.st_mtime_ns = stamp
  284.         entry.st_gid = os.getgid()
  285.         entry.st_uid = os.getuid()
  286.         entry.st_ino = inode
  287.  
  288.         file = tgfsFile(msg, doc, inode, entry)
  289.  
  290.         return file
  291.  
  292.     def documents_to_tgfsfiles(self, msg_documents, starting_inode):
  293.  
  294.         files = {}
  295.  
  296.         for inode, msg in enumerate(msg_documents, starting_inode):
  297.             files[inode] = self.create_tgfsfile(msg[0], msg[1], inode)
  298.  
  299.         return files
  300.  
  301.     def getattr(self, inode, ctx=None):
  302.  
  303.         if inode == llfuse.ROOT_INODE:
  304.             entry = llfuse.EntryAttributes()
  305.             entry.st_mode = (stat.S_IFDIR | 0o755)
  306.             entry.st_size = 0
  307.             stamp = int(1438467123.985654 * 1e9)
  308.             entry.st_atime_ns = stamp
  309.             entry.st_ctime_ns = stamp
  310.             entry.st_mtime_ns = stamp
  311.             entry.st_gid = os.getgid()
  312.             entry.st_uid = os.getuid()
  313.             entry.st_ino = llfuse.ROOT_INODE
  314.  
  315.             return entry
  316.         elif inode in self.files:
  317.             return self.files[inode].attr
  318.         else:
  319.             raise llfuse.FUSEError(errno.ENOENT)
  320.  
  321.     def lookup(self, parent_inode, name, ctx=None):
  322.         if parent_inode != llfuse.ROOT_INODE or name not in self.file_by_name:
  323.             raise llfuse.FUSEError(errno.ENOENT)
  324.  
  325.         return self.file_by_name[name].attr
  326.  
  327.     def opendir(self, inode, ctx):
  328.         if inode != llfuse.ROOT_INODE:
  329.             raise llfuse.FUSEError(errno.ENOENT)
  330.         return inode
  331.  
  332.     def readdir(self, fh, off):
  333.         assert fh == llfuse.ROOT_INODE
  334.         logvfs.debug("readdir(%s,%s)" % (fh, off))
  335.  
  336.         if off == 0:
  337.             others = self.inodes
  338.         else:
  339.             others = self.inodes[self.inodes.index(off):]
  340.  
  341.         for inode, inext in with_next(others):
  342.             if inext:
  343.                 file = self.files[inode]
  344.                 yield (file.fname, file.attr, inext)
  345.  
  346.     def open(self, inode, flags, ctx):
  347.         if flags & os.O_RDWR or flags & os.O_WRONLY:
  348.             raise llfuse.FUSEError(errno.EPERM)
  349.         return inode
  350.  
  351.     def read(self, fh, off, size):
  352.         print("read: %s %s %s = %s; " % (fh, off, size, off + size), end='')
  353.         doc = self.files[fh].doc
  354.         reading_func = doc['download_func']
  355.         chunk = loop.run_until_complete(reading_func(off, size))
  356.         print("read: %s" % len(chunk))
  357.         return chunk
  358.  
  359.  
  360. def init_logging(debug=False):
  361.     formatter = logging.Formatter('%(asctime)s.%(msecs)03d %(threadName)s: '
  362.                                   '[%(name)s] %(message)s', datefmt="%Y-%m-%d %H:%M:%S")
  363.  
  364.     _log_handler = logging.StreamHandler()
  365.  
  366.     _log_handler.setFormatter(logging.Formatter('%(asctime)s.%(msecs)03d %(threadName)s: \t\t\t\t\t\t\t\t\t\t'
  367.                                                 '[%(name)s] %(message)s', datefmt="%Y-%m-%d %H:%M:%S"))
  368.  
  369.     logvfs.addHandler(_log_handler)
  370.  
  371.     handler = logging.StreamHandler()
  372.     handler.setFormatter(formatter)
  373.     root_logger = logging.getLogger()
  374.     # logging.getLogger('telethon').setLevel(logging.INFO)
  375.     if debug:
  376.         handler.setLevel(logging.DEBUG)
  377.         root_logger.setLevel(logging.DEBUG)
  378.         logtg.setLevel(logging.DEBUG)
  379.     else:
  380.         handler.setLevel(logging.INFO)
  381.         root_logger.setLevel(logging.INFO)
  382.         logtg.setLevel(logging.INFO)
  383.  
  384.     root_logger.addHandler(handler)
  385.  
  386.  
  387. def parse_args():
  388.     '''Parse command line'''
  389.  
  390.     parser = ArgumentParser()
  391.  
  392.     parser.add_argument('--dir', type=str, required='--id' in sys.argv,
  393.                         help='Where to mount the file system')
  394.  
  395.     parser.add_argument('--debug', action='store_true', default=False,
  396.                         help='Enable debugging output')
  397.  
  398.     parser.add_argument('--debug-fuse', action='store_true', default=True,
  399.                         help='Enable FUSE debugging output')
  400.  
  401.     parser.add_argument('--list-dialogs', action='store_true', default=False,
  402.                         help='List available telegram dialogs')
  403.  
  404.     parser.add_argument('--music', action='store_true', default=False,
  405.                         help='Only music')
  406.  
  407.     parser.add_argument('--id', default=None, required='--dir' in sys.argv,
  408.                         help='Dialog id')
  409.  
  410.     parser.add_argument('--limit', default=10000,
  411.                         help='limit messages')
  412.  
  413.     return parser.parse_args()
  414.  
  415.  
  416. def main():
  417.     options = parse_args()
  418.     init_logging(options.debug)
  419.  
  420.     client = TelegramFsClient(session, phone, api_id, api_hash)
  421.     loop.run_until_complete(client.auth())
  422.  
  423.     if options.list_dialogs:
  424.         dialogs = loop.run_until_complete(client.get_dialogs_map())
  425.  
  426.         for d in dialogs:
  427.             print(d, dialogs[d].id)
  428.  
  429.         return
  430.  
  431.     else:
  432.         dialogs = loop.run_until_complete(client.get_dialogs_map())
  433.  
  434.         cykabot = loop.run_until_complete(client.get_entity(int(options.id)))
  435.         documents = loop.run_until_complete(
  436.             client.get_documents(cykabot, limit=int(options.limit), music=options.music))
  437.  
  438.         testfs = TestTelegramFs(documents)
  439.         fuse_options = set(llfuse.default_options)
  440.         fuse_options.add('fsname=lltest')
  441.  
  442.         if options.debug_fuse:
  443.             fuse_options.add('debug')
  444.  
  445.         llfuse.init(testfs, options.dir, fuse_options)
  446.         try:
  447.             llfuse.main(workers=1)
  448.         except:
  449.             llfuse.close(unmount=False)
  450.             raise
  451.  
  452.         llfuse.close()
  453.  
  454.  
  455. if __name__ == '__main__':
  456.     main()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement