class Controller: albums = {} active_posting = True def __init__(self, session, api_id, api_hash, mode, proxy=None): """ Initializes the InteractiveTelegramClient. :param session: Name of the *.session file. :param api_id: Telegram's api_id acquired through my.telegram.org. :param api_hash: Telegram's api_hash. :param mode: development or production mode :param proxy: Optional proxy tuple/dictionary. """ if mode == "dev": self.client = TelegramClient( session, api_id, api_hash, proxy=proxy, connection=connection. ConnectionTcpMTProxyRandomizedIntermediate) elif mode == "prod": self.client = TelegramClient(session, api_id, api_hash) # Use the client in a `with` block. It calls `start/disconnect` automatically. self.database = Database() with self.client: self.client.add_event_handler( self.forward_album_legacy, events.NewMessage(from_users=('@Ordicyn', '@lazycat90210'), func=lambda e: e.grouped_id)) self.client.add_event_handler( self.forward_msg, events.NewMessage(from_users=('@Ordicyn', '@lazycat90210'), func=lambda e: e.grouped_id is None)) # Task to print alive-messages every 5 minutes loop.create_task(self.print_forever()) # Task to grab the most popular posts from channels (table "channels") every day at 08:00 GMT+3 loop.create_task(self.do_dump_schedule()) # Task to post 2 times in hour 3 random media from posts (table "posts") in period 09:00-23:00 GMT+3 loop.create_task(self.do_post_schedule()) self.client.run_until_disconnected() async def forward_album_legacy(self, event): logger.info('Recieved message with album') await event.mark_read() pair = (event.chat_id, event.grouped_id) if pair in self.albums: self.albums[pair].append(event.message) return self.albums[pair] = [event.message] await asyncio.sleep(0.3) messages = self.albums.pop(pair) logger.info('%s %s', 'Album contains photos:', str(len(messages))) await event.respond(f'Got {len(messages)} photos!') medias = [] for msg in messages: medias.append(msg.media) await self.client.send_file( chat, medias, caption='✅ [Сохранёнки](https://t.me/savedmemess)') async def forward_msg(self, event): await event.mark_read() logger.info('%s %s', "Event", str(event)) sender = await event.get_sender() logger.info('%s %s', "Recieved new message for forwarding from", str(sender.username)) msg = event.message logger.info('%s %s', "Message", str(msg)) if msg.media is not None and (isinstance(msg.media, MessageMediaPhoto) or isinstance(msg.media, MessageMediaDocument)): logger.info('Message contains media photo or video') media = msg.media await self.client.send_file( chat, media, caption='✅ [Сохранёнки](https://t.me/savedmemess)') else: logger.info("Message doesn't contain media photo or video") if msg.message.lower() == 'help': logger.info('Message is help request') with codecs.open('help.html', "r", encoding='utf-8') as help_file: help_msg = help_file.read() await event.respond(help_msg, parse_mode='html') elif msg.message.lower() == 'list': logger.info('Message is channels list request') try: channels = self.database.getAllChannels() response = "Now is listening following channels:\n" + "\n".join( map(str, channels)) logger.info(response) await event.respond(message=response, link_preview=False) except Exception as ex: error_msg = "Failed to get chanel list with exception: " + str( ex) logger.error(error_msg) elif msg.message.lower().startswith('add'): logger.info('Message is request to add channel to list') try: channel_url = msg.message.lower().split(' ')[1] channel_entity = await self.client.get_entity(channel_url) if self.database.getChannelByID(channel_entity.id) is None: channel = Channel(channel_entity.id, channel_entity.title, channel_url, True) self.database.addChannel(channel) success_msg = channel_entity.title + ' was added to database. Dumping will start at 8:00 GMT+3' logger.info(success_msg) await event.respond(success_msg) else: error_msg = 'Channel with ID ' + str( channel_entity.id) + ' already in database' logger.error(error_msg) await event.respond(error_msg) except Exception as ex: error_msg = "Failed to add channel to list with exception: " + str( ex) logger.error(error_msg) await event.respond(error_msg) elif msg.message.lower().startswith('delete'): logger.info('Message is request to delete channel from list') try: channel_id = msg.message.lower().split(' ')[1] if self.database.getChannelByID(channel_id) is not None: channel_entity = await self.client.get_input_entity( self.database.getChannelByID(channel_id).link) await self.client( LeaveChannelRequest(channel=channel_entity)) self.database.delChannelByID(channel_id) success_msg = channel_id + ', channel with this id was successfully deleted from the database.' \ 'Media from this channel was deleted too and bot leave channel' logger.info(success_msg) await event.respond(success_msg) else: error_msg = 'Channel with ID ' + str( channel_id) + ' not in database' logger.error(error_msg) await event.respond(error_msg) except Exception as ex: error_msg = "Failed to delete channel from list with exception: " + str( ex) logger.error(error_msg) await event.respond(error_msg) elif msg.message.lower() == 'dump': logger.info( 'Message is request dump messages from channel manually') try: await self.do_dump() success_msg = 'Request dump messages from channel manually was handled success' logger.info(success_msg) await event.respond(success_msg) except Exception as ex: error_msg = "Failed dump messages from channels " + str(ex) logger.error(error_msg) await event.respond(error_msg) elif msg.message.lower() == 'post': logger.info('Message is request to do 3 posts manually') try: await self.do_post() success_msg = 'Request to do 3 posts manually was handled success' logger.info(success_msg) await event.respond(success_msg) except Exception as ex: error_msg = "Failed to do 3 posts manually " + str(ex) logger.error(error_msg) await event.respond(error_msg) elif msg.message.lower() == 'start': logger.info('Message is request to start automatic posting') if self.active_posting is True: logger.info('Automatic posting is active already') await event.respond('Automatic posting is active already') else: self.active_posting = True logger.info('Automatic posting is set true') await event.respond('Automatic posting is set true') elif msg.message.lower() == 'stop': logger.info('Message is request to stop automatic posting') if self.active_posting is False: logger.info('Automatic posting is stop already') await event.respond('Automatic posting is stop already') else: self.active_posting = False logger.info('Automatic posting stopped') await event.respond('Automatic posting stopped') elif msg.message.lower() == 'stats': logger.info('Message is request to get bot statistic') try: logger.info('Get information about posts database') total, posted, not_posted = self.database.getPostsInfo() msg = 'Bot statistic:\nPost database contains posts: ' + str( total) + '\nPosted count: ' + str( posted) + '\nNot posted count: ' + str( not_posted ) + '\nInformation about last 10 revisions:\n' logger.info('Get information about last 10 revisions') revisions = self.database.getLast10Revisions() for revision in revisions: msg += 'Channel ID: ' + str(revision[0]) + ', channel name: ' + revision[1] + ', collected: ' \ + str(revision[2]) + ', time(GMT+3): ' \ + str( revision[3].astimezone(pytz.timezone("Europe/Moscow")).strftime("%Y-%m-%d %H:%M:%S")) + '\n' # print(revisions) logger.info(msg) await event.respond(msg) except Exception as ex: error_msg = "Failed to to get bot statistic " + str(ex) logger.error(error_msg) await event.respond(error_msg) else: logger.info('Command is unrecognized. Use help command') await event.respond('Command is unrecognized. Use help command' ) async def join_channel(self): channels = self.database.getAllChannels() for channel in channels: try: await self.client(JoinChannelRequest(channel.channel_id)) logger.info('%s %s', 'success join to the channel', channel.title) except Exception as ex: logger.error('%s %s %s', 'failed join to the channel', channel.title, str(ex)) async def do_dump_schedule(self): while True: logger.info("Get current time in UTC") current_time_utc = datetime.time(datetime.now(pytz.utc)) logger.info('%s %s', 'Now: ', str(current_time_utc)) dump_time_utc = time(hour=5, minute=0) if current_time_utc <= dump_time_utc: logger.info("Current time less than 08:00 GMT+3(05:00 UTC)") remaining = ( datetime.combine(datetime.date(datetime.now(pytz.utc)), dump_time_utc) - datetime.combine(datetime.date(datetime.now(pytz.utc)), current_time_utc)).total_seconds() else: logger.info("Current time greater than 08:00 GMT+3(05:00 UTC)") remaining = (datetime.combine( datetime.date(datetime.now(pytz.utc)) + timedelta(days=1), dump_time_utc) - datetime.combine( datetime.date(datetime.now(pytz.utc)), current_time_utc)).total_seconds() logger.info('%s %s', "Now go sleep for: ", str(remaining)) await asyncio.sleep(remaining) logger.info('Now 08:00 GMT+3. Dump process wake up!') try: await self.do_dump() except Exception as ex: error_msg = "Failed dump messages from channels " + str(ex) logger.error(error_msg) async def do_dump(self): logger.info('Task#1 - clear messages table') try: r = self.database.clearPosts() logger.info('%s %s', str(r), ' posts was cleared') except Exception as ex: error_msg = "Failed to clear messages table with exception: " + str( ex) logger.error(error_msg) logger.info('Task#2 - join to channels') try: await self.join_channel() except Exception as ex: error_msg = "Failed to join to channels with exception: " + str(ex) logger.error(error_msg) logger.info( 'Task#3 - Get last 200 messages from channels in date range' ' [current date-1 21:00; current date-2 21:00]') # Current date in UTC current_date = datetime.date(datetime.now(pytz.utc)) logger.info('%s %s', 'Current date in UTC ', str(current_date)) # 18:00 in UTC = 21:00 in GMT + 3 time_dump = time(hour=18, minute=0) # before datetime = current date-1 21:00 dt_before = datetime.combine(current_date - timedelta(days=1), time_dump).replace(tzinfo=pytz.UTC) logger.info('%s %s', 'before datetime = current date-1 21:00 ', str(dt_before)) # after datetime = current date-2 21:00 dt_after = datetime.combine(current_date - timedelta(days=2), time_dump).replace(tzinfo=pytz.UTC) logger.info('%s %s', 'after datetime = current date-2 21:00 ', str(dt_after)) # global posts list posts_list_global = [] try: logger.info('Get actual channel list') channels = self.database.getAllChannels() for channel in channels: logger.info('%s %s', 'Try to dump message from channel', channel.title) try: logger.info('This channel hasnt been dumped yet') logger.info('Init telethon request') channel_entity = await self.client.get_input_entity( channel.channel_id) posts_list = [] # Get first 100 messages logger.info('Get first 100 messages') posts = await self.client( GetHistoryRequest(peer=channel_entity, limit=100, offset_date=dt_before, offset_id=0, max_id=0, min_id=0, add_offset=0, hash=0)) logger.info('%s %s', 'Got messages: ', str(len(posts.messages))) posts_list.extend(posts.messages) offset_id = posts_list[99].id logger.info('%s %s', 'Offset message id: ', str(offset_id)) logger.info('Get another 100 messages') posts = await self.client( GetHistoryRequest(peer=channel_entity, limit=100, offset_date=dt_before, offset_id=offset_id, max_id=0, min_id=0, add_offset=0, hash=0)) logger.info('%s %s', 'Got messages: ', str(len(posts.messages))) posts_list.extend(posts.messages) logger.info('%s %s', 'Totally got messages: ', str(len(posts_list))) logger.info( 'Filter messages that not album with media photo or video and text without invite link and not reply' ) filtered_posts_list = list( filter( lambda msg: (dt_after <= msg.date and dt_before >= msg.date ) and (msg.grouped_id is None) and (msg.media is not None) and (msg.reply_markup is None) and (isinstance(msg.media, MessageMediaPhoto) or isinstance(msg.media, MessageMediaDocument)) and (not any(s in msg.message for s in ["https", ".shop", ".com", ".ru"])), posts_list)) logger.info('%s %s', 'After filtering messages list contain: ', str(len(filtered_posts_list))) # for x in filtered_posts_list: logger.info(str(x)) logger.info( 'Sort list by views and save 50% first more popular post to global' ) filtered_posts_list.sort(key=lambda msg: msg.views, reverse=True) # for x in filtered_posts_list[:int(len(filtered_posts_list)/2)]: logger.info(str(x)) posts_list_global.extend( filtered_posts_list[:int(len(filtered_posts_list) / 2)]) logger.info( '%s %s', 'Add revision record about channel for this date', channel.title) revision = Revision(channel.channel_id, datetime.now(pytz.utc), len(filtered_posts_list)) try: self.database.addRevision(revision) except Exception as ex: error_msg = "Failed to store revision to database with exception " + str( ex) logger.error(error_msg) except Exception as ex: error_msg = "Failed to dump message from channel " + channel.title + " : " + str( ex) logger.error(error_msg) except Exception as ex: error_msg = "Failed to get last 200 messages from channels in general: " + str( ex) logger.error(error_msg) logger.info('%s %s', 'Totally from all channels got messages: ', str(len(posts_list_global))) logger.info('Task#4 - Now we should cast class Message to Posts') logger.info('Now we should cast class Message to Post') # logger.info(posts_list_global[0]) filtered_posts_list_global_in_post = list( map( lambda msg: Post(msg.to_id.channel_id, msg.id, "", msg.date, False), posts_list_global)) # for x in filtered_posts_list_global_in_post: print(x) self.database.addPosts(filtered_posts_list_global_in_post) # for post in filtered_posts_list_global_in_post: # await self.client.send_file('test_channel_5', pickle.loads(post.media), # caption='✅ [Сохранёнки](https://t.me/savedmemess)') # await asyncio.sleep(5) # await asyncio.sleep(80) async def do_post_schedule(self): while True: logger.info( "Function post 2 times in hour 3 random media from database in period 09:00-23:00 GMT+3 " "or 06:00-20:00 UTC") logger.info("Get current time in UTC") current_time_utc = datetime.time(datetime.now(pytz.utc)) logger.info('%s %s', 'Now: ', str(current_time_utc)) after_time_utc = time(hour=6, minute=0) before_time_utc = time(hour=20, minute=0) if current_time_utc < after_time_utc: logger.info("Current time less than 09:00 GMT+3(06:00 UTC)") remaining = ( datetime.combine(datetime.date(datetime.now(pytz.utc)), after_time_utc) - datetime.combine(datetime.date(datetime.now(pytz.utc)), current_time_utc)).total_seconds() logger.info('%s %s', "Now go sleep for: ", str(remaining)) await asyncio.sleep(remaining) if current_time_utc > before_time_utc: logger.info("Current time greater than 23:00 GMT+3(20:00 UTC)") remaining = (datetime.combine( datetime.date(datetime.now(pytz.utc)) + timedelta(days=1), after_time_utc) - datetime.combine( datetime.date(datetime.now(pytz.utc)), current_time_utc)).total_seconds() logger.info('%s %s', "Now go sleep for: ", str(remaining)) await asyncio.sleep(remaining) else: if self.active_posting is True: logger.info('Automatic posting is active now') # self.database.printAllPosts() logger.info("Time to post!") await self.do_post() logger.info("Done! Now sleep for 29 minutes") await asyncio.sleep(1740) else: logger.info('Automatic posting is inactive now') logger.info("Now sleep for 30 minutes") await asyncio.sleep(1800) async def do_post(self): for i in range(0, 3): try: post = self.database.getRandomPost() logger.info("Select from database following random post:") logger.info(str(post)) logger.info("Trying to retrive message from channel:") channel_entity = await self.client.get_input_entity( post.channel_id) logger.info(str(channel_entity)) msg = await self.client( GetMessagesRequest(channel=channel_entity, id=[post.message_id])) logger.info(str(msg)) media = msg.messages[0].media await self.client.send_file( bot, media, caption='✅ [Сохранёнки](https://t.me/savedmemess)') logger.info("Post was send. Now mark it in database as marked") try: self.database.setPostPosted(post) except Exception as ex: error_msg = "Failed with exception: " + str(ex) logger.error(error_msg) except Exception as ex: error_msg = "Failed to post exception: " + str(ex) logger.error(error_msg) finally: await asyncio.sleep(5) async def print_forever(self): while True: logger.info("Await(alive) function") current_time_utc = datetime.time(datetime.now(pytz.utc)) logger.info('%s %s', 'Now: ', str(current_time_utc)) await asyncio.sleep(300)