def download_contact(message_media_contact, file_path, add_extension=True): """Downloads a media contact using the vCard 4.0 format""" first_name = message_media_contact.first_name last_name = message_media_contact.last_name phone_number = message_media_contact.phone_number # The only way we can save a contact in an understandable # way by phones is by using the .vCard format if add_extension: file_path += '.vcard' # Ensure that we'll be able to download the contact utils.ensure_parent_dir_exists(file_path) with open(file_path, 'w', encoding='utf-8') as file: file.write('BEGIN:VCARD\n') file.write('VERSION:4.0\n') file.write('N:{};{};;;\n'.format(first_name, last_name if last_name else '')) file.write('FN:{}\n'.format(' '.join((first_name, last_name)))) file.write( 'TEL;TYPE=cell;VALUE=uri:tel:+{}\n'.format(phone_number)) file.write('END:VCARD\n') return file_path
def download_file_loc(self, input_location, file_path, part_size_kb=64, file_size=None, progress_callback=None): """Downloads media from the given input_file_location to the specified file_path. If a progress_callback function is given, it will be called taking two arguments (downloaded bytes count and total file size)""" if not part_size_kb: if not file_size: raise ValueError('A part size value must be provided') else: part_size_kb = get_appropiate_part_size(file_size) part_size = int(part_size_kb * 1024) if part_size % 1024 != 0: raise ValueError('The part size must be evenly divisible by 1024') # Ensure that we'll be able to download the media utils.ensure_parent_dir_exists(file_path) # Start with an offset index of 0 offset_index = 0 with open(file_path, 'wb') as file: while True: # The current offset equals the offset_index multiplied by the part size offset = offset_index * part_size result = self.invoke( GetFileRequest(input_location, offset, part_size)) offset_index += 1 # If we have received no data (0 bytes), the file is over # So there is nothing left to download and write if not result.bytes: return result.type # Return some extra information file.write(result.bytes) if progress_callback: progress_callback(file.tell(), file_size)
def download_file(self, input_location, file=None, part_size_kb=None, file_size=None, progress_callback=None): """ Downloads the given input location to a file. Args: input_location (:tl:`InputFileLocation`): The file location from which the file will be downloaded. file (`str` | `file`): The output file path, directory, or stream-like object. If the path exists and is a file, it will be overwritten. part_size_kb (`int`, optional): Chunk size when downloading files. The larger, the less requests will be made (up to 512KB maximum). file_size (`int`, optional): The file size that is about to be downloaded, if known. Only used if ``progress_callback`` is specified. progress_callback (`callable`, optional): A callback function accepting two parameters: ``(downloaded bytes, total)``. Note that the ``total`` is the provided ``file_size``. """ if not part_size_kb: if not file_size: part_size_kb = 64 # Reasonable default else: part_size_kb = utils.get_appropriated_part_size(file_size) part_size = int(part_size_kb * 1024) # https://core.telegram.org/api/files says: # > part_size % 1024 = 0 (divisible by 1KB) # # But https://core.telegram.org/cdn (more recent) says: # > limit must be divisible by 4096 bytes # So we just stick to the 4096 limit. if part_size % 4096 != 0: raise ValueError( 'The part size must be evenly divisible by 4096.') in_memory = file is None if in_memory: f = io.BytesIO() elif isinstance(file, str): # Ensure that we'll be able to download the media helpers.ensure_parent_dir_exists(file) f = open(file, 'wb') else: f = file # The used client will change if FileMigrateError occurs client = self cdn_decrypter = None input_location = utils.get_input_location(input_location) download_thread = [] q_request = [] __log__.info('Downloading file in chunks of %d bytes', part_size) threads_count = 2 + int((self._download_threads_count - 2) * float(file_size) / (1024 * 1024 * 10)) threads_count = min(threads_count, self._download_threads_count) # threads_count = 1 # threads_count = min(part_count, threads_count) try: offset = 0 result = None try: request = GetFileRequest(input_location, offset, part_size) result = client(request) if isinstance(result, FileCdnRedirect): __log__.info('File lives in a CDN') cdn_decrypter, result = CdnDecrypter.prepare_decrypter(client, self._get_cdn_client(result), result) else: f.write(result.bytes) offset += part_size except FileMigrateError as e: __log__.info('File lives in another DC') client = self._get_exported_client(e.new_dc) # if cdn_decrypter: # result = cdn_decrypter.get_file() # if not result.bytes: # return getattr(result, 'type', '') # f.write(result.bytes) __log__.debug('Saved %d more bytes', len(result.bytes)) if progress_callback: progress_callback(f.tell(), file_size) # spawn threads for i in range(threads_count): q_request.append(Queue()) thread_dl = self.ProcessDownload('thread {0}'.format(i), self, q_request[i]) thread_dl.start() download_thread.append(thread_dl) # offset += part_size while True: for i in range(threads_count): if cdn_decrypter: q_request[i].put(cdn_decrypter) else: request = GetFileRequest(input_location, offset, part_size) q_request[i].put(request) offset += part_size for q in q_request: q.join() for th in download_thread: if th.result and th.result.bytes: f.write(th.result.bytes) if progress_callback: progress_callback(f.tell(), file_size) else: for i in range(threads_count): q_request[i].put(None) for th in download_thread: th.join() return getattr(th.result, 'type', '') finally: if client != self: client.disconnect() if cdn_decrypter: try: cdn_decrypter.client.disconnect() except: pass if isinstance(file, str): f.close()