async def zip(event, session): """Create 1.5GB zips with all files collectd in this chat.""" to_id, to_type = get_peer_information(event.message.to_id) subscriber = Subscriber.get_or_create(session, to_id, to_type, event.message) chat_path = get_chat_path(subscriber.chat_name) if not os.path.exists(chat_path): return "No files for this chat yet." zip_dir = init_zip_dir(subscriber.chat_name) text = f"Zipping started, this might take some time. Please don't issue this command again until I'm finished." await event.respond(text) create_zips(subscriber.chat_name, zip_dir, chat_path) text = "Zipping is completed. I'll now start uploading." await event.respond(text) for zip_file in os.listdir(zip_dir): zip_file_path = os.path.join(zip_dir, zip_file) await archive.send_file(event.message.to_id, zip_file_path) shutil.rmtree(zip_dir) return "All files are uploaded :)"
async def stop(event, session): """Stop the bot.""" to_id, to_type = get_peer_information(event.message.to_id) subscriber = Subscriber.get_or_create(session, to_id, to_type, event.message) subscriber.active = False session.add(subscriber) return "Files won't be archived any longer."
async def start(event, session): """Start the bot.""" to_id, to_type = get_peer_information(event.message.to_id) subscriber = Subscriber.get_or_create(session, to_id, to_type, event.message) subscriber.active = True session.add(subscriber) return 'Files posted in this chat will now be archived.'
async def process_message(session, subscriber, message, event, full_scan=False): """Process a single message. Check if it has a file we want to download.""" to_id, to_type = get_peer_information(message.to_id) try: # If this message is forwarded, get the original sender. if message.forward and message.forward.sender_id is not None: user_id = message.forward.sender_id user = await archive.get_entity(user_id) # A channel can be a sender as well, early return if the sender is no User if not isinstance(user, types.User): return else: # Ignore messages with no sent user if message.from_id is None: return user_id = message.from_id user = await archive.get_entity(message.from_id) # Check if we should accept this message if not await should_accept_message(event, message, user, subscriber): return # Create a new file. If it's not possible or not wanted, return None new_file = await create_file(session, event, subscriber, message, user, full_scan) if new_file is None: return None # Download the file success = await message.download_media(str(new_file.file_path)) # Download succeeded, if the result is not None if success is not None: # Mark the file as succeeded new_file.success = True session.commit() except ValueError: # Handle broadcast channels those have None for user_id: if user_id is None: user_id = subscriber.chat_name user = UnknownUser(user_id) # We got a flood wait error. Wait for the specified time and recursively retry except FloodWaitError as e: time.sleep(e.seconds + 1) process_message(session, subscriber, message, event, full_scan) return
async def process(event, session): """Check if we received any files.""" to_id, to_type = get_peer_information(event.message.to_id) subscriber = Subscriber.get_or_create(session, to_id, to_type, event.message) try: await process_message(session, subscriber, event.message, event) except BadMessageError: # Ignore bad message errors return
async def process(event, session): """Main entry for processing messages and downloading files.""" to_id, to_type = get_peer_information(event.message.to_id) subscriber = Subscriber.get_or_create(session, to_id, to_type, event.message) try: await process_message(session, subscriber, event.message, event) except BadMessageError: # Ignore bad message errors return
async def create_file(session, event, subscriber, message, user, full_scan): """Create a file object from a message.""" to_id, to_type = get_peer_information(message.to_id) file_type, file_id = await get_file_information(event, message, subscriber, user, full_scan) if not file_type: return None # Check if this exact file from the same message is already downloaded. # This is a hard constraint which shouldn't be violated. if File.exists(session, subscriber, file_id): return None # The file path is depending on the media type. # In case such a file already exists and duplicate files are disabled, the function returns None. # In that case we will return early. file_path, file_name = get_file_path(subscriber, get_username(user), message) # Don't check zipped files from ourselves. # Otherwise we would double in size on each /scan_chat /zip command combination me = await event.client.get_me() splitted = file_name.rsplit('.', maxsplit=2) if user.id == me.id and len(splitted) == 3 and splitted[1] == '7z': return None if file_path is None: # Inform the user about duplicate files if subscriber.verbose: text = f"File with name {file_name} already exists." await event.respond(text) sentry.captureMessage("File already exists", extra={ 'file_path': file_path, 'file_name': file_name, 'chat': subscriber.chat_name, 'user': get_username(user) }, tags={'level': 'info'}) return None # The file path is depending on the media type. new_file = File(file_id, to_id, user.id, subscriber, to_type, message.id, file_type, file_name, file_path) session.add(new_file) session.commit() return new_file
async def scan_chat(event, session): """Check if we received any files.""" to_id, to_type = get_peer_information(event.message.to_id) subscriber = Subscriber.get_or_create(session, to_id, to_type, event.message) async for message in archive.iter_messages(event.message.to_id): try: await process_message(session, subscriber, message, event) except BadMessageError: # Ignore bad message errors return return "Chat scan successful."
async def scan_chat(event, session): """Scan the whole chat for files. Necessary for getting old files.""" to_id, to_type = get_peer_information(event.message.to_id) subscriber = Subscriber.get_or_create(session, to_id, to_type, event.message) await event.respond("Starting full chat scan.") async for message in archive.iter_messages(event.message.to_id): try: await process_message(session, subscriber, message, event, full_scan=True) except BadMessageError: # Ignore bad message errors return return "Full chat scan successful."
async def set_name(event, session): """Set the name of the current chat (also affects the saving directory.""" to_id, to_type = get_peer_information(event.message.to_id) new_chat_name = event.message.message.split(" ", maxsplit=1)[1].strip() # We already save to zips, prevent that if new_chat_name == "zips": return "Invalid chat name. Pick another." subscriber = Subscriber.get_or_create( session, to_id, to_type, event.message, chat_name=new_chat_name ) old_chat_path = get_chat_path(subscriber.chat_name) new_chat_path = get_chat_path(new_chat_name) # Handle any command that tries to escape the download directory new_real_path = os.path.realpath(new_chat_path) target_real_path = os.path.realpath(config["download"]["target_dir"]) if ( not new_real_path.startswith(target_real_path) or new_real_path == target_real_path ): user = await archive.get_entity(types.PeerUser(event.message.from_id)) sentry.captureMessage( "User tried to escape directory.", extra={ "new_chat_name": new_chat_name, "chat": subscriber.chat_name, "user": get_username(user), }, tags={"level": "info"}, ) return "Please stop fooling around and don't try to escape the directory. I have been notified about this." # Check whether we already have a chat with this name if ( session.query(Subscriber) .filter(Subscriber.chat_name == new_chat_name) .one_or_none() ): return "Chat name already exists. Please choose another one." # Move the old directory to the new location elif old_chat_path != new_chat_path: subscriber.chat_name = new_chat_name if os.path.exists(old_chat_path): os.rename(old_chat_path, new_chat_path) return "Chat name changed."
async def clear_history(event, session): """Stop the bot.""" to_id, to_type = get_peer_information(event.message.to_id) subscriber = Subscriber.get_or_create(session, to_id, to_type, event.message) chat_path = get_chat_path(subscriber.chat_name) for known_file in subscriber.files: session.delete(known_file) if os.path.exists(chat_path): shutil.rmtree(chat_path) session.commit() return "All files from this chat have been deleted."
async def accepted_media_types(event, session): """Set query attributes.""" to_id, to_type = get_peer_information(event.message.to_id) subscriber = Subscriber.get_or_create(session, to_id, to_type, event.message) # Convert the incoming text into an boolean arguments = event.message.message.lower().split(' ')[1:] accepted_media = set() for argument in arguments: if argument in possible_media: accepted_media.add(argument) accepted_media = list(accepted_media) accepted_media.sort() subscriber.accepted_media = ' '.join(accepted_media) return f"Now accepting following media types: {accepted_media}."
async def process_message(session, subscriber, message, event): """Process a single message.""" to_id, to_type = get_peer_information(message.to_id) try: # If this message is forwarded, get the original sender. if message.forward: user_id = message.forward.sender_id user = await message.forward.get_sender() else: # Ignore messages with no sent user if message.from_id is None: return user_id = message.from_id user = await archive.get_entity(message.from_id) except ValueError: # Handle channels. Channels always have None for user_id: if user_id is None: user_id = subscriber.channel_name user = UnknownUser(user_id) # Check if we should accept this message if not await should_accept_message(event, message, user, subscriber): return # Ignore users with absolutely no name if user.last_name is None or user.first_name is None or user.username is None: return # Create a new file. If it's not possible or not wanted, return None new_file = await create_file(session, event, subscriber, message, user) if new_file is None: return None # Download the file success = await message.download_media(str(new_file.file_path)) # Download succeeded, if the result is not None if success is not None: # Mark the file as succeeded new_file.success = True session.commit()
async def set_name(event, session): """Set query attributes.""" to_id, to_type = get_peer_information(event.message.to_id) new_channel_name = event.message.message.split(' ', maxsplit=1)[1].strip() if new_channel_name == 'zips': return "Invalid channel name. Pick another." subscriber = Subscriber.get_or_create(session, to_id, to_type, event.message, channel_name=new_channel_name) old_channel_path = get_channel_path(subscriber.channel_name) new_channel_path = get_channel_path(new_channel_name) new_real_path = os.path.realpath(new_channel_path) target_real_path = os.path.realpath(config.TARGET_DIR) if not new_real_path.startswith(target_real_path) or \ new_real_path == target_real_path: user = await archive.get_entity(event.message.from_id) sentry.captureMessage("User tried to escape directory.", extra={ 'new_channel_name': new_channel_name, 'channel': subscriber.channel_name, 'user': get_username(user) }, tags={'level': 'info'}) return "Please stop fooling around and try to escape the directory. I have been notified as well." if session.query(Subscriber) \ .filter(Subscriber.channel_name == new_channel_name) \ .one_or_none(): return "Channel name already exists. Please choose another one." elif old_channel_path != new_channel_path: subscriber.channel_name = new_channel_name if os.path.exists(old_channel_path): os.rename(old_channel_path, new_channel_path) return "Channel name changed."
async def process(event, session): """Main entry for processing messages and downloading files.""" to_id, to_type = get_peer_information(event.message.to_id) subscriber = Subscriber.get_or_create(session, to_id, to_type, event.message) tries = 3 current_try = 0 while tries < current_try: try: await process_message(session, subscriber, event.message, event) return except BadMessageError: # Ignore bad message errors return except FloodWaitError as e: # We got a flood wait error. Wait for the specified time and recursively retry time.sleep(e.seconds + 1) except TimeoutError: # Timeout error. Just wait for 10 secs. time.sleep(10) current_try += 1
async def info(event, session): """Send a the information about the current user settings.""" to_id, to_type = get_peer_information(event.message.to_id) subscriber = Subscriber.get_or_create(session, to_id, to_type, event.message) return get_info_text(subscriber)
async def info(event, session): """Send a help text.""" to_id, to_type = get_peer_information(event.message.to_id) subscriber = Subscriber.get_or_create(session, to_id, to_type, event.message) return get_info_text(subscriber)