├── .env.example ├── commands ├── test_command.py ├── start_command.py ├── stop_command.py ├── import_module.py ├── filterstart_command.py ├── filterstop_command.py ├── filterclear_command.py ├── filterlist_command.py ├── message_command.py ├── filter_command.py ├── unfilter_command.py ├── helpmessage_command.py ├── listchannels_command.py ├── deleteall_command.py ├── leave_command.py ├── new_command.py └── join_command.py ├── .gitignore ├── requirements.txt ├── Dockerfile ├── docker-compose.yaml ├── TelegramCommand.py ├── telegregator.ini ├── database.py ├── readme.md └── agregator.py /.env.example: -------------------------------------------------------------------------------- 1 | API_ID= 2 | API_HASH= 3 | MASTER_ACCOUNT= 4 | BOT_TOKEN= 5 | -------------------------------------------------------------------------------- /commands/test_command.py: -------------------------------------------------------------------------------- 1 | async def main(client, logger, **kwargs): 2 | print('test command import') 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | venv 2 | *.log 3 | *.session 4 | *.db 5 | .vscode 6 | __pycache__ 7 | *.session-journal 8 | .env 9 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | peewee==3.13.3 2 | pyaes==1.6.1 3 | pyasn1==0.4.8 4 | python-dotenv==0.14.0 5 | rsa==4.6 6 | Telethon==1.15.0 7 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:slim 2 | 3 | WORKDIR /usr/src/app 4 | 5 | COPY requirements.txt ./ 6 | RUN pip install --no-cache-dir -r requirements.txt 7 | 8 | # CMD [ "python", "./agregator.py" ] -------------------------------------------------------------------------------- /docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | tggt: 4 | build: ./ 5 | image: tggt:1.0 6 | container_name: tggt 7 | volumes: 8 | - ./:/usr/src/app 9 | command: python agregator.py 10 | restart: unless-stopped -------------------------------------------------------------------------------- /commands/start_command.py: -------------------------------------------------------------------------------- 1 | from database import * 2 | 3 | async def main(client, logger, **kwargs): 4 | 5 | # print(kwargs) 6 | chat = kwargs['answer_to'] 7 | 8 | feed_id = chat.chat_id 9 | feedupdate = Feed.update({Feed.is_enable: 1}).where(Feed.feed_id == feed_id).execute() 10 | await client.send_message(chat, 'Включили поток') 11 | -------------------------------------------------------------------------------- /commands/stop_command.py: -------------------------------------------------------------------------------- 1 | from database import * 2 | 3 | async def main(client, logger, **kwargs): 4 | 5 | # print(kwargs) 6 | chat = kwargs['answer_to'] 7 | 8 | feed_id = chat.chat_id 9 | feedupdate = Feed.update({Feed.is_enable: 0}).where(Feed.feed_id == feed_id).execute() 10 | await client.send_message(chat, 'Выключили поток') 11 | -------------------------------------------------------------------------------- /commands/import_module.py: -------------------------------------------------------------------------------- 1 | import importlib 2 | 3 | async def call_command(**kwargs): 4 | print(kwargs) 5 | try: 6 | module = importlib.import_module('commands.{}_command'.format(kwargs['module'])) 7 | except Exception as e: 8 | print(e) 9 | else: 10 | # print(args[1:]) 11 | await module.main(client, logger, **kwargs) 12 | -------------------------------------------------------------------------------- /commands/filterstart_command.py: -------------------------------------------------------------------------------- 1 | from database import * 2 | 3 | async def main(client, logger, **kwargs): 4 | # print(kwargs) 5 | chat = kwargs['answer_to'] 6 | 7 | feed_id = chat.chat_id 8 | feedupdate = Feed.update({Feed.is_filter: 1}).where(Feed.feed_id == feed_id).execute() 9 | response = 'Включили фильтрацию' 10 | 11 | await client.send_message(chat, response) 12 | logger.info(response) 13 | -------------------------------------------------------------------------------- /commands/filterstop_command.py: -------------------------------------------------------------------------------- 1 | from database import * 2 | 3 | async def main(client, logger, **kwargs): 4 | # print(kwargs) 5 | chat = kwargs['answer_to'] 6 | 7 | feed_id = chat.chat_id 8 | feedupdate = Feed.update({Feed.is_filter: 0}).where(Feed.feed_id == feed_id).execute() 9 | response = 'Выключили фильтрацию' 10 | 11 | await client.send_message(chat, response) 12 | logger.info(response) 13 | -------------------------------------------------------------------------------- /commands/filterclear_command.py: -------------------------------------------------------------------------------- 1 | from database import * 2 | 3 | async def main(client, logger, **kwargs): 4 | # print(kwargs) 5 | chat = kwargs['answer_to'] 6 | contact_id = kwargs['contact_id'] 7 | 8 | filters = Filter.delete().where(Filter.contact_id == contact_id) 9 | deleted = filters.execute() 10 | response = '**Удалено фильтров**:\r\n' + str(deleted) 11 | 12 | await client.send_message(chat, response) 13 | logger.info(response) 14 | -------------------------------------------------------------------------------- /commands/filterlist_command.py: -------------------------------------------------------------------------------- 1 | from database import * 2 | 3 | async def main(client, logger, **kwargs): 4 | # print(kwargs) 5 | chat = kwargs['answer_to'] 6 | contact_id = kwargs['contact_id'] 7 | 8 | filters = Filter.select().where(Filter.contact_id == contact_id) 9 | filterlist = [filter.word.lower() for filter in filters] 10 | response = '**Добавленные фильтры**:\r\n' + '\r\n'.join(filterlist) 11 | 12 | await client.send_message(chat, response) 13 | logger.info(response) 14 | -------------------------------------------------------------------------------- /commands/message_command.py: -------------------------------------------------------------------------------- 1 | from database import * 2 | 3 | async def main(client, logger, **kwargs): 4 | 5 | # print(kwargs) 6 | message = kwargs['message'] 7 | 8 | notification_text = '**Сообщение от админа:**\r\n' + message 9 | feeds = Feed.select() 10 | print(', '.join([str(feed.feed_id) + ' ' + str(feed.contact_id) for feed in feeds])) 11 | for feed in feeds: 12 | if feed.contact_id == 388822642: 13 | await client.send_message(feed.feed_id, notification_text) 14 | -------------------------------------------------------------------------------- /TelegramCommand.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | class TelegramCommand(): 4 | """Обработчик входящих команд""" 5 | 6 | def __init__(self): 7 | """Инициализирует исходное сообщение""" 8 | self.command = None 9 | self.arg = None 10 | self.command_mask = '^\/([-a-zA-Z]+)\s?@?([-а-яА-Яa-zA-Z0-9@:.\/\\_\s]+)?' 11 | 12 | def parseMessage(self, message): 13 | """разбирает входящее сообщение на команду а аргумент""" 14 | m = re.search(self.command_mask, message) 15 | if m: 16 | self.arg = m.group(2) 17 | self.command = m.group(1) 18 | return self.command, self.arg -------------------------------------------------------------------------------- /commands/filter_command.py: -------------------------------------------------------------------------------- 1 | from database import * 2 | 3 | async def main(client, logger, **kwargs): 4 | # print(kwargs) 5 | chat = kwargs['answer_to'] 6 | contact_id = kwargs['contact_id'] 7 | 8 | if not kwargs['word']: 9 | response = 'Не указано слово' 10 | else: 11 | word = kwargs['word'].lower() 12 | logger.info('прислали слово {}'.format(word)) 13 | filter, created = Filter.get_or_create(word=word, contact_id = contact_id) 14 | if created: 15 | response = 'Добавили в фильтры слово {}'.format(word) 16 | else: 17 | response = 'Уже есть слово {}'.format(word) 18 | 19 | await client.send_message(chat, response) 20 | logger.info(response) 21 | -------------------------------------------------------------------------------- /commands/unfilter_command.py: -------------------------------------------------------------------------------- 1 | from database import * 2 | 3 | async def main(client, logger, **kwargs): 4 | # print(kwargs) 5 | chat = kwargs['answer_to'] 6 | contact_id = kwargs['contact_id'] 7 | 8 | if not kwargs['word']: 9 | response = 'Не указано слово' 10 | else: 11 | word = kwargs['word'].lower() 12 | logger.info('прислали слово {}'.format(word)) 13 | filters = Filter.delete().where((Filter.contact_id == contact_id) and (Filter.word == word)) 14 | deleted = filters.execute() 15 | if deleted: 16 | response = '**Удален фильтр**:\r\n' + word 17 | else: 18 | response = 'Нет такого фильтра' 19 | 20 | await client.send_message(chat, response) 21 | logger.info(response) 22 | -------------------------------------------------------------------------------- /commands/helpmessage_command.py: -------------------------------------------------------------------------------- 1 | async def main(client, logger, **kwargs): 2 | print(kwargs) 3 | response = """**Работающие команды**: 4 | /join имя_канала, /add - добавить канал в текущий поток. Также можно переслать сюда сообщение из канала 5 | /leave имя_канала, /remove - удалить канал из текущего потока 6 | /list - показать список каналов, добавленных в поток 7 | /stop, /pause - приостановить пересылку 8 | /start, resume - продолжить пересылку 9 | /filter слово - добавить стоп слово 10 | /unfilter слово - удалить стоп слово 11 | /clearfilter - очистить фильтр 12 | /filterlist - список фильтров 13 | /filterstop, /filterstart - отключить/включить фильтрацию 14 | /help - список команд 15 | 16 | **Команды в работе**: 17 | /exit, /delete - удалить все каналы и остановить пересылку 18 | /roll - показать случайный пост из каналов в потоке 19 | /random - показать случайный пост из всех каналов (даже других пользователей) 20 | /top - топ самых популярных каналов 21 | /rare - топ самых редких каналов 22 | /recommend - предлагает случайный канал""" 23 | # /log off/on - переключение режима логирования в отельный канал 24 | # /stat - статистика потоков, юзеров и каналов 25 | await client.send_message(kwargs['answer_to'], response) 26 | -------------------------------------------------------------------------------- /commands/listchannels_command.py: -------------------------------------------------------------------------------- 1 | from telethon import functions 2 | from database import * 3 | 4 | async def main(client, logger, **kwargs): 5 | try: 6 | # TODO оптимизировать в один запрос через join 7 | forwards = Forward.select(Forward.channel_id).where(Forward.feed_id == kwargs['feed_id']) 8 | except Forward.DoesNotExist: 9 | logger.info('Sender is not in forwarded channels') 10 | forwards = None 11 | else: 12 | logger.info("forwarding...") 13 | 14 | feed_channels = Channel.select().where(Channel.channel_id << forwards) 15 | feed_list = 'Подписаны на следcующие каналы:\r\n{}'.format('\r\n'.join([channel.channel_title + ' @' + channel.channel_name for channel in feed_channels])) 16 | logger.info('отправляем юзеру список его подписок') 17 | logger.info(feed_list) 18 | await client.send_message(kwargs['answer_to'], feed_list) 19 | # https://lonamiwebs.github.io/Telethon/methods/messages/forward_messages.html 20 | # https://telethon.readthedocs.io/en/latest/extra/examples/telegram-client.html?highlight=forward#forwarding-messages 21 | # https://prosto-tak.ru/adminer/?sqlite=&username=&db=%2Fvar%2Fdb%2Fcontacts.db&select=favorite 22 | -------------------------------------------------------------------------------- /telegregator.ini: -------------------------------------------------------------------------------- 1 | [program:telegregator] 2 | command=/home/experiment/telegregatorbot/venv/bin/python /home/experiment/telegregatorbot/agregator.py 3 | process_name=%(program_name)s ; process_name expr (default %(program_name)s) 4 | numprocs=1 ; number of processes copies to start (def 1) 5 | directory=/home/experiment/telegregatorbot/ ; directory to cwd to before exec (def no cwd) 6 | priority=10 ; the relative start priority (default 999) 7 | autostart=true ; start at supervisord start (default: true) 8 | autorestart=true ; retstart at unexpected quit (default: true) 9 | startsecs=1 ; number of secs prog must stay running (def. 1) 10 | startretries=3 ; max # of serial start failures (default 3) 11 | user=experiment ; setuid to this UNIX account to run the program 12 | redirect_stderr=true ; redirect proc stderr to stdout (default false) 13 | stdout_logfile=/var/log/telegregator.log ; stdout log path, NONE for none; default AUTO 14 | stdout_logfile_maxbytes=10MB ; max # logfile bytes b4 rotation (default 50MB) 15 | stdout_logfile_backups=10 ; # of stdout logfile backups (default 10) 16 | stdout_capture_maxbytes=1MB ; number of bytes in 'capturemode' (default 0) 17 | stderr_logfile=/var/log/telegregator.log ; stderr log path, NONE for none; default AUTO 18 | stderr_logfile_maxbytes=1MB ; max # logfile bytes b4 rotation (default 50MB) 19 | stderr_logfile_backups=10 ; # of stderr logfile backups (default 10) 20 | stderr_capture_maxbytes=1MB ; number of bytes in 'capturemode' (default 0) 21 | stopsignal=KILL -------------------------------------------------------------------------------- /database.py: -------------------------------------------------------------------------------- 1 | from peewee import * 2 | import datetime 3 | import logging 4 | 5 | # https://peewee.readthedocs.io/en/latest/peewee/models.html#field-types-table 6 | # https://peewee.readthedocs.io/en/latest/peewee/relationships.html#model-definitions 7 | db = SqliteDatabase('telegregator.db') 8 | class BaseModel(Model): 9 | class Meta: 10 | database = db 11 | class Contact(BaseModel): 12 | contact_id = IntegerField(unique=True) 13 | added_at = DateTimeField(default=datetime.datetime.now) 14 | class Channel(BaseModel): 15 | channel_id = IntegerField(unique=True) 16 | channel_name = CharField(default='') 17 | channel_title = CharField(default='') 18 | class Feed(BaseModel): 19 | feed_id = IntegerField(unique=True) 20 | contact = ForeignKeyField(Contact, backref='contacts') 21 | is_filter = BooleanField(default=True) 22 | is_enable = BooleanField(default=True) 23 | feed_title = CharField(default='') 24 | class Forward(BaseModel): 25 | feed = ForeignKeyField(Feed, backref='feeds') 26 | channel = ForeignKeyField(Channel, backref='channels') 27 | class Filter(BaseModel): 28 | word = CharField(unique=True) 29 | contact = ForeignKeyField(Contact, backref='contacts') 30 | 31 | # Contact.create_table() 32 | # Channel.create_table() 33 | # Feed.create_table() 34 | # Forward.create_table() 35 | # Filter.create_table() 36 | 37 | # db.create_tables([Contact, Channel, Forward, Feed, Filter]) 38 | 39 | # DEBUG 40 | # loggerdb = logging.getLogger('peewee') 41 | # loggerdb.addHandler(logging.StreamHandler()) 42 | # loggerdb.setLevel(logging.DEBUG) 43 | 44 | def create_tables(): 45 | with db: 46 | db.create_tables([Contact, Channel, Forward, Feed, Filter]) 47 | -------------------------------------------------------------------------------- /commands/deleteall_command.py: -------------------------------------------------------------------------------- 1 | from telethon import functions 2 | from database import * 3 | 4 | async def main(client, logger, **kwargs): 5 | 6 | # print(kwargs) 7 | chat = kwargs['answer_to'] 8 | 9 | # определяем, в какой поток прислана команда 10 | try: 11 | feed_id = chat.chat_id 12 | feed = Feed.get(Feed.feed_id == feed_id) 13 | except Feed.DoesNotExist: 14 | logger.error('Feed.DoesNotExist') 15 | await client.send_message(chat, 'Feed.DoesNotExist') 16 | feed = None 17 | else: 18 | logger.info('feed.get {}'.format(feed)) 19 | 20 | # если поток существует 21 | if feed: 22 | logger.info('Удаляем все связанные подписки') 23 | try: 24 | forwards = Forward.delete().where(Forward.feed_id == feed_id).execute() 25 | except Forward.DoesNotExist: 26 | logger.info('ошибка удаления подписок {}'.format(feed)) 27 | 28 | #нельзя удалять, т.к. потом хз как добавить заново 29 | # logger.info('Затем удаляем сам поток {}'.format(feed)) 30 | # feed.delete_instance() 31 | 32 | response = 'Удалили все подписки' 33 | await client.send_message(chat, response) 34 | logger.info(response) 35 | 36 | 37 | try: 38 | channelEntity = await client.get_input_entity(feed_id) 39 | except Exception as e: 40 | logger.error('channelEntity ERROR: {}'.format(e)) 41 | else: 42 | logger.info('Краткая сущность channelEntity {}'.format(channelEntity)) 43 | 44 | # TODO выкинуть юзера из канала и самому удалиться 45 | try: 46 | result = await client(functions.channels.DeleteChannelRequest( 47 | channel=feed_id, 48 | )) 49 | except Exception as e: 50 | logger.error(e) 51 | 52 | else: 53 | try: 54 | await client.send_message(chat, 'Ошибка, обратитесь к администратору') 55 | except Exception as e: 56 | logger.error(e) 57 | 58 | logger.info("ошибка удаления потока") 59 | -------------------------------------------------------------------------------- /commands/leave_command.py: -------------------------------------------------------------------------------- 1 | from telethon import functions 2 | from database import * 3 | 4 | async def main(client, logger, **kwargs): 5 | 6 | # print(kwargs) 7 | command_arg = kwargs['channel_name'] 8 | chat = kwargs['answer_to'] 9 | 10 | try: 11 | channelEntity = await client.get_input_entity(command_arg) 12 | except Exception as e: 13 | logger.error('channelEntity ERROR: {}'.format(e)) 14 | else: 15 | logger.info('Краткая сущность channelEntity {}'.format(channelEntity)) 16 | 17 | # ищем канал, указанный в команде 18 | try: 19 | channel = Channel.get(channel_id=channelEntity.channel_id) 20 | except Channel.DoesNotExist: 21 | logger.error('Channel.DoesNotExist') 22 | else: 23 | logger.info('Channel.get {}'.format(channel)) 24 | 25 | # определяем, в какой поток прислана команда 26 | try: 27 | feed_id = chat.chat_id 28 | forward = Forward.get(channel = channel.channel_id, feed = feed_id) 29 | except Forward.DoesNotExist: 30 | logger.error('Forward.DoesNotExist') 31 | forward = None 32 | else: 33 | logger.info('Forward.get {}'.format(forward)) 34 | 35 | # logger.info("Отписались от канала %s, подписка была %s" % (channel, forward)) 36 | 37 | # если поток был ранее добавлен 38 | if forward: 39 | logger.info('Удаляем подписку {}'.format(forward)) 40 | # удаляем запись о необходимости пересылки сообщений из канала в поток 41 | forward.delete_instance() 42 | 43 | # отписываемся от канала. ВАЖНО!: сейчас не надо отписываться, т.к. другие могут быть подписаны, или подписаться в будущем. ну и для истории 44 | # try: 45 | # await client(functions.channels.LeaveChannelRequest(channelEntity)) 46 | # except Exception as e: 47 | # print(e) 48 | # await event.reply('Отписались от канала') 49 | await client.send_message(chat, 'Удалили канал из этого потока') 50 | logger.info("отписался от канала {}".format(channelEntity.channel_id)) 51 | else: 52 | try: 53 | await client.send_message(chat, 'Канал не добавлен в этот поток') 54 | except Exception as e: 55 | logger.error(e) 56 | 57 | logger.info("не подписан на канал {}".format(channelEntity.channel_id)) 58 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Telegregator 2 | Телегрегатор - агрегатор телеграм-каналов. 3 | Он умеет подписываться на каналы и группы (даже закрытые), и объединять их в общую ленту. Мы называем это поток (feed). 4 | Для того, чтобы Телегретатор начал формировать поток, достаточно переслать ему сообщение из канала. 5 | 6 | Еще Телегрегатор умеет фильтровать сообщения из оригинальных каналов по стоп-словам, избавляя вас от надоедливой рекламы 7 | 8 | ## Требования 9 | - python 3.6 10 | - pip 11 | - telethon 12 | - файл requirements.txt 13 | - наличие свободного аккаунта Telegram, который будет выступать в роли юзербота. 14 | 15 | ## Установка 16 | - создаем окружение: `python36 -m venv myvenv` 17 | - активируем окружение: `source myvenv/bin/activate` 18 | - устанавливаем зависимости: `pip install --no-cache-dir -r requirements.txt` 19 | - клонируем репозиторий 20 | - копируем и заполняем .env настройки. Как получить api id описано здесь: https://telethon.readthedocs.io/en/latest/basic/signing-in.html 21 | - выполняем миграцию: `python database.py` 22 | - запускаем: `python agregator.py` 23 | - **первый раз бота запускать надо именно в терминале**, т.к. будет запрос номера телефона и смс. В докер я это поленился заворачивать. 24 | 25 | 26 | ### Docker (см. известные проблемы внизу) 27 | - `docker build . -t tggt:1.0` 28 | - `docker run --name tggt -v "$PWD":/usr/src/myapp -w /usr/src/myapp -d --restart unless-stopped tggt:1.0 python agregator.py` 29 | или 30 | - `docker-compose build` 31 | - `docker-compose up -d` 32 | 33 | логи посмотреть командой `docker logs -f tggt` 34 | 35 | ## Использование без докера 36 | Для работы приложения в фоновом режиме можно использовать supervisor, который [нужно предварительно установить и настроить](https://dev.xfor.top/2019-12-gotovim-centos-7-ustanovka-i-nastroyka-supervisord-na-vorker.html) 37 | После установки супервизора, создаем файл `/etc/supervisord.d/telegregator.ini` с содержимым: 38 | 39 | ``` 40 | [program:telegregator] 41 | command=/home/telegregatorbot/venv/bin/python /home/telegregatorbot/agregator.py ; путь к скрипту 42 | process_name=%(program_name)s ; process_name expr (default %(program_name)s) 43 | numprocs=1 ; number of processes copies to start (def 1) 44 | directory=/home/telegregatorbot/ ; directory to cwd to before exec (def no cwd) 45 | priority=10 ; the relative start priority (default 999) 46 | autostart=true ; start at supervisord start (default: true) 47 | autorestart=true ; retstart at unexpected quit (default: true) 48 | startsecs=1 ; number of secs prog must stay running (def. 1) 49 | startretries=3 ; max # of serial start failures (default 3) 50 | user=experiment ; setuid to this UNIX account to run the program 51 | redirect_stderr=true ; redirect proc stderr to stdout (default false) 52 | stdout_logfile=/var/log/telegregator.log ; stdout log path, NONE for none; default AUTO 53 | stdout_logfile_maxbytes=10MB ; max # logfile bytes b4 rotation (default 50MB) 54 | stdout_logfile_backups=10 ; # of stdout logfile backups (default 10) 55 | stdout_capture_maxbytes=1MB ; number of bytes in 'capturemode' (default 0) 56 | stderr_logfile=/var/log/telegregator.log ; stderr log path, NONE for none; default AUTO 57 | stderr_logfile_maxbytes=1MB ; max # logfile bytes b4 rotation (default 50MB) 58 | stderr_logfile_backups=10 ; # of stderr logfile backups (default 10) 59 | stderr_capture_maxbytes=1MB ; number of bytes in 'capturemode' (default 0) 60 | stopsignal=KILL 61 | ``` 62 | 63 | Перезапускаем: `systemctl restart supervisord` 64 | 65 | # Известные проблемы 66 | - у одного аккаунта не может быть больше 500 каналов в подписках (ограничения телеграма. пока не знаю, как это обойти) 67 | - когда телегрегатор запущен в докере, то не все каналы пересылаются. пока не разобрался, в чем дело, так что лучше запускать через supervisord 68 | -------------------------------------------------------------------------------- /commands/new_command.py: -------------------------------------------------------------------------------- 1 | from telethon import functions, types 2 | from database import * 3 | 4 | async def main(client, logger, **kwargs): 5 | 6 | contact = kwargs['contact'] 7 | group_name = kwargs['group_name'] 8 | 9 | if not group_name: 10 | group_name = 'TGFeed' 11 | 12 | try: 13 | result = await client(functions.channels.CreateChannelRequest( 14 | title=group_name, 15 | about='', 16 | )) 17 | # 18 | # код ниже не работает, выдает всегда ошибку Not enough users (to create a chat, for example) (caused by CreateChatRequest) 19 | # me = utils.get_input_peer(entity) 20 | # me = await client.get_input_entity('me') 21 | # guest = await client.get_input_entity(contact.contact_id) 22 | # print(me.stringify()) 23 | # print(guest.stringify()) 24 | # result = await client(functions.messages.CreateChatRequest( 25 | # users=['me', 'parotikov', 'Telegregator'], 26 | # title='Feed' 27 | # )) 28 | except Exception as e: 29 | logger.error(e) 30 | 31 | else: 32 | logger.info(result.stringify()) 33 | new_chat = result.chats[0] 34 | 35 | logger.info("Добавили в новый поток, сохраняем в БД...") 36 | feed, created = Feed.get_or_create(feed_id=new_chat.id, contact_id = contact.contact_id, feed_title=new_chat.title) 37 | if created: 38 | loginfo = "зарегали новый поток {}".format(feed.id) 39 | else: 40 | loginfo = "уже есть поток {}".format(feed.id) 41 | logger.info(loginfo) 42 | 43 | response = """**Добро пожаловать!** 44 | Это Telegretator - агрегатор телеграм-каналов. 45 | Он умеет подписываться на каналы и группы (даже закрытые), и объединять их в общую ленту. Мы называем это поток (feed). 46 | Для того, чтобы Телегретатор начал формировать поток, перешлите сюда сообщение из любого канала, либо воспользуетесь командами ниже. 47 | 48 | Еще Телегрегатор умеет фильтровать сообщения по стоп-словам: таким образом вы можете избавиться от надоедливой рекламы, просто выполнив команду: /filter стоп_слово.""" 49 | await client.send_message(new_chat, response) 50 | # await response_help(new_chat) 51 | 52 | # await client.send_message(chat, 'Создали новый канал') 53 | # # Добавляем юзера 54 | # только для чата, но создание чата не работает, см. выше ошибку Not enough users 55 | # result = await client(functions.messages.AddChatUserRequest( 56 | # chat_id=new_chat.id, 57 | # user_id=contact.contact_id, 58 | # fwd_limit=10 59 | # )) 60 | 61 | logger.info('приглашаем юзера в канал') 62 | 63 | try: 64 | result = await client(functions.channels.InviteToChannelRequest( 65 | channel=new_chat.id, 66 | users=[contact.contact_id] 67 | )) 68 | except Exception as e: 69 | logger.error(e) 70 | response = str(e) + '\r\nОтключите в разделе "Конфиденциальность - Группы и каналы" запрет на приглашение Вас в группы и выполните команду new еще раз' 71 | await client.send_message(kwargs['answer_to'], response) 72 | else: 73 | logger.info(result.stringify()) 74 | 75 | logger.info('даем права приглашенному юзеру') 76 | 77 | rights = types.ChatAdminRights( 78 | post_messages=True, 79 | add_admins=True, 80 | invite_users=True, 81 | change_info=True, 82 | ban_users=True, 83 | delete_messages=True, 84 | pin_messages=True, 85 | ) 86 | 87 | user_peer = await client.get_input_entity(contact.contact_id) 88 | try: 89 | permission_result = await client(functions.channels.EditAdminRequest(new_chat, user_peer, rights)) 90 | except Exception as e: 91 | logger.error(e) 92 | else: 93 | logger.info(permission_result.stringify()) 94 | -------------------------------------------------------------------------------- /commands/join_command.py: -------------------------------------------------------------------------------- 1 | from telethon import functions 2 | from database import * 3 | import re 4 | 5 | async def main(client, logger, **kwargs): 6 | 7 | # print(kwargs) 8 | command_arg = kwargs['channel_name'] 9 | chat = kwargs['answer_to'] 10 | 11 | # определяем по имени канала его краткую сущность 12 | try: 13 | channelEntity = await client.get_input_entity(command_arg) 14 | except Exception as e: 15 | logger.error('channelEntity ERROR: {}'.format(e)) 16 | channelEntity = None 17 | else: 18 | logger.info('Краткая сущность channelEntity {}'.format(channelEntity)) 19 | 20 | # https://t.me/joinchat/AAAAAFJZZ_f91J_H7ht4GQ 21 | # TODO добавить обработку инвайтов: https://telethon.readthedocs.io/en/latest/examples/chats-and-channels.html 22 | # если вместо названия передали инвайт ссылку 23 | if 'joinchat' in command_arg: 24 | logger.info('прислали инвайт-ссылку {}'.format(command_arg)) 25 | invite = re.search('([^\/]+)\/?$', command_arg) 26 | # invite = command_arg.split("/")[-1] 27 | 28 | logger.info('инвайт - %s' % invite[0]) 29 | try: 30 | logger.info('пробуем подписаться на закрытый канал по инвайту {}'.format(invite[0])) 31 | updates = await client(functions.messages.ImportChatInviteRequest(invite[0])) 32 | # TODO остановился на этом 33 | # https://docs.telethon.dev/en/latest/examples/chats-and-channels.html?highlight=private#joining-a-private-chat-or-channel 34 | # await asyncio.sleep(1) 35 | except Exception as e: 36 | logger.error('ImportChatInviteRequest: {}'.format(e)) 37 | updates = None 38 | else: 39 | logger.info('подписались, updated {}'.format(updated)) 40 | 41 | # определяем по имени канала его полную сущность. Для закрытых не сработает, там надо сначала подписаться 42 | try: 43 | channelFullEntity = await client.get_entity(command_arg) 44 | except Exception as e: 45 | logger.error('channelFullEntity ERROR: {}'.format(e)) 46 | channelFullEntity = None 47 | else: 48 | logger.info('Полная сущность channelFullEntity {} {}'.format(channelFullEntity.title, channelFullEntity.username)) 49 | 50 | 51 | 52 | # Добавляем в аккаунт 53 | # добавляем канал в систему (InputPeerChat,InputPeerChannel, 'string' 54 | try: 55 | await client(functions.channels.JoinChannelRequest(channelEntity)) 56 | except Exception as e: 57 | logger.error(e) 58 | else: 59 | logger.info("Отправили запрос JoinChannelRequest {}".format(channelEntity.channel_id)) 60 | 61 | # в случае успешного добавления канала в аккаунт, сохраняем его в бд с полученным названием 62 | try: 63 | channel, is_channel_joined = Channel.get_or_create(channel_id=channelEntity.channel_id, channel_name=channelFullEntity.username or command_arg, channel_title=channelFullEntity.title) 64 | if is_channel_joined: 65 | loginfo = "Сохранили канал {} {} {} в БД".format(channel.channel_title, channel.channel_name, channel.channel_id) 66 | else: 67 | loginfo = "Канал {} {} {} уже добавлен в БД".format(channel.channel_title, channel.channel_name, channel.channel_id) 68 | logger.info(loginfo) 69 | except Exception as e: 70 | logger.error(e) 71 | 72 | 73 | # теперь добавляем канал в поток, где была выполнена команда join 74 | 75 | logger.info("chat {}".format(chat.stringify())) 76 | 77 | # для группы (чата) 78 | if hasattr(chat, 'chat_id'): 79 | feed_id = chat.chat_id 80 | logger.info("Теперь сохраним chat_id как feed_id %s в БД Forward" % feed_id) 81 | 82 | 83 | #для канала 84 | if hasattr(chat, 'channel_id'): 85 | feed_id = chat.channel_id 86 | logger.info("Теперь сохраним channel_id как feed_id %s в БД Forward" % feed_id) 87 | 88 | # print(chat.stringify()) 89 | 90 | try: 91 | forward, forward_created = Forward.get_or_create(channel_id=channel.channel_id, feed_id=feed_id) 92 | if forward_created: 93 | response = 'Добавили канал {} ({}) в этот поток'.format(channel.channel_title, channel.channel_name) 94 | else: 95 | response = 'Канал {} ({}) уже добавлен в этот поток'.format(channel.channel_title, channel.channel_name) 96 | 97 | logger.info(response) 98 | 99 | await client.send_message(chat, response) 100 | 101 | except Exception as e: 102 | logger.error(e) 103 | else: 104 | logger.info("не получилось обработать {}".format(channel.channel_id)) 105 | 106 | 107 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /agregator.py: -------------------------------------------------------------------------------- 1 | from telethon import TelegramClient, events, sync, functions, types, utils 2 | from telethon.tl.custom import Button 3 | import asyncio 4 | import os 5 | import logging 6 | from TelegramCommand import * 7 | from database import * 8 | import importlib 9 | from dotenv import load_dotenv 10 | load_dotenv() 11 | 12 | logger = logging.getLogger("tggt") 13 | logger.setLevel(logging.DEBUG) 14 | 15 | fh = logging.FileHandler("app.log") 16 | formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') 17 | fh.setFormatter(formatter) 18 | logger.addHandler(fh) 19 | 20 | api_id = os.getenv("API_ID") 21 | api_hash = os.getenv("API_HASH") 22 | master_account = os.getenv("MASTER_ACCOUNT") 23 | bot_token = os.getenv("BOT_TOKEN") 24 | 25 | client = TelegramClient('telegregator_session', api_id, api_hash) 26 | 27 | bot = TelegramClient('bot', api_id, api_hash).start(bot_token=bot_token) 28 | 29 | # client.start() 30 | # https://github.com/LonamiWebs/Telethon/blob/master/readthedocs/extra/examples/telegram-client.rst#sending-messages 31 | 32 | async def add_contact(contact_id): 33 | contact, created = Contact.get_or_create(contact_id=contact_id) 34 | # print("add contact get or create", contact, created) 35 | if created: 36 | logger.info("зарегали нового пользователя %d" % contact.contact_id) 37 | else: 38 | logger.info("написал существующий пользователь %d" % contact.contact_id) 39 | return contact 40 | 41 | # async def log(message): 42 | # async client.send_message(, message) 43 | 44 | async def call_command(**kwargs): 45 | # print(kwargs) 46 | logger.info("call_command {}".format(kwargs)) 47 | try: 48 | module = importlib.import_module('commands.{}_command'.format(kwargs['module'])) 49 | except Exception as e: 50 | logger.error(e) 51 | else: 52 | # print(args[1:]) 53 | await module.main(client, logger, **kwargs) 54 | 55 | async def send_read(chat, message): 56 | try: 57 | chat_read = await client.get_input_entity(chat) 58 | await client.send_read_acknowledge(chat_read, message) 59 | except Exception as e: 60 | logger.error(e) 61 | 62 | async def is_our_channel(channel_id): 63 | destination = await client.get_entity(channel_id) 64 | # logger.info(destination.stringify()) 65 | if destination.creator: 66 | return True 67 | # TODO разобраться, почему здесь не прерывается 68 | else: 69 | return False 70 | 71 | async def handle_message(event): 72 | try: 73 | logger.info('ищем в БД поток, где channel_id = %s' % event.message.to_id.channel_id) 74 | forwards = Forward.select().where(Forward.channel_id == event.message.to_id.channel_id) 75 | # chat = await client.get_input_entity(event.message.to_id.channel_id) 76 | # await client.send_read_acknowledge(chat, event.message) 77 | # logger.info("%s" % forwards) 78 | except Forward.DoesNotExist: 79 | logger.info('Канала нет среди пересылаемых') 80 | forwards = None 81 | return 82 | if not len(forwards): 83 | logger.info('нет подписок на этот канал') 84 | return 85 | 86 | logger.info("Нашли следующие потоки, подписанные на канал channel_id %s" % event.message.to_id.channel_id) 87 | logger.info(forwards) 88 | 89 | chat = await client.get_input_entity(event.message.to_id) 90 | await client.send_read_acknowledge(chat, event.message) 91 | 92 | for forward in forwards: 93 | # определяем надо ли пересылать 94 | try: 95 | feed = Feed.get(Feed.feed_id == forward.feed_id) 96 | except Feed.DoesNotExist: 97 | logger.info('Поток не найден') 98 | feed = None 99 | return 100 | 101 | if not feed.is_enable: 102 | logger.info("Поток был отключен, не пересылаем") 103 | return 104 | # Определяем создателя потока, если сообщение пришло в поток 105 | # logger.info('Определяем создателя потока, куда должен транслироваться канал, откуда пришло сообщение') 106 | log_message = 'Сообщение пришло в канал {}'.format(event.message.to_id) 107 | if hasattr(event.message.to_id, 'channel_id'): 108 | feed_id = forward.feed_id 109 | try: 110 | owner = Contact.select().join(Feed, on=(Contact.contact_id == Feed.contact_id)).where(Feed.feed_id == feed_id).get() 111 | except Contact.DoesNotExist: 112 | log_message = log_message + ', владелец канала не определен' 113 | owner = None 114 | else: 115 | log_message = log_message + ', id владельца канала: {}'.format(owner.contact_id) 116 | logger.info(log_message) 117 | else: 118 | logger.error('нет параметра event.message.to_id.chat_id') 119 | 120 | log_message = 'пробуем фильтровать.' 121 | filterlist = [] 122 | # print('contact.contact_id %s' % contact.contact_id) 123 | 124 | # TODO получать для конкретного юзера. нужно будет поменять логику и порядок, перенести на 438 строку 125 | try: 126 | filters = Filter.select().where(Filter.contact_id == owner.contact_id) 127 | except filterlist.DoesNotExist: 128 | log_message = log_message + ' нет фильтров' 129 | filters = [] 130 | else: 131 | log_message = log_message + ' найдены фильтры для юзера' 132 | filterlist = [filter.word.lower() for filter in filters] 133 | # logger.info(filterlist) 134 | blacklistword = filterlist + ['запрещенный', 'выиграй', 'удалю', 'читать продолжение', 'joinchat', 'подписаться', 'подписывайся', 'подпишитесь', 'переходи', 'перейти в канал', 'подписываемся', 'дамы и господа', 'автор канала'] 135 | # logger.info(blacklistword) 136 | message = event.message 137 | message_text = message.message 138 | # print(message.entities) 139 | if message.entities: 140 | # print(message.entities) 141 | for entity in message.entities: 142 | # print(entity.__class__.__name__) 143 | if entity.__class__.__name__ == 'MessageEntityTextUrl': 144 | # print(entity.url) 145 | message_text = message_text + entity.url 146 | if message.reply_markup: 147 | for row in message.reply_markup.rows: 148 | for button in row.buttons: 149 | if hasattr(button, 'url'): 150 | message_text = message_text + button.url 151 | if hasattr(button, 'text'): 152 | message_text = message_text + button.text 153 | # else: 154 | # print('no buttons') 155 | 156 | if any([word in message_text.lower() for word in blacklistword]): # ищем стоп слова во всех текстах и ссылках 157 | log_message = log_message + "Найдены стоп-слова, не пересылаем" 158 | logger.info(log_message) 159 | return 160 | logger.info(log_message) 161 | logger.info("Фильтры прошли, пересылаем из канала %s в поток %s" % (forward.channel_id, forward.feed_id)) 162 | await event.message.forward_to(forward.feed_id) 163 | # return 164 | 165 | 166 | 167 | 168 | logger.info('\r\n\r\n\r\n------------start client------------------') 169 | 170 | with client, bot: 171 | @client.on(events.ChatAction) 172 | async def my_event_handler(event): 173 | 174 | response_message = """**Добро пожаловать!** 175 | Это Telegretator - агрегатор телеграм-каналов. 176 | Он умеет подписываться на каналы и группы (даже закрытые), и объединять их в общую ленту. Мы называем это поток (feed). 177 | Для того, чтобы Телегретатор начал формировать поток, перешлите сюда сообщение из любого канала, либо воспользуетесь командами ниже. Наберите /help для списка команд 178 | 179 | Еще Телегрегатор умеет фильтровать сообщения из оригинальных каналов по стоп-словам: таким образом вы можете избавиться от надоедливой рекламы, просто выполнив команду: /filter стоп_слово.""" 180 | 181 | async def add_group_as_feed(feed_id, contact_id): 182 | logger.info("Добавляем id " + str(feed_id) + " в новый поток, сохраняем в БД...") 183 | feed, created = Feed.get_or_create(feed_id=feed_id, contact_id = contact_id) 184 | if created: 185 | loginfo = "зарегали новый поток {}".format(feed.id) 186 | else: 187 | loginfo = "уже есть поток {}".format(feed.id) 188 | logger.info(loginfo) 189 | # logger.info('добавился') 190 | 191 | await client.send_message(event.action_message.to_id, response_message) 192 | 193 | # logger.info("Сработал event ChatAction %s" % event.stringify()) 194 | 195 | # original_update это уникальный атрибут при добавлении в канал. в чате(группе) такого нет 196 | if event.original_update: 197 | logger.info("user_joined to channel_id %s" % event.original_update.channel_id) 198 | channel = await client.get_input_entity(event.original_update.channel_id) 199 | logger.info("channel joined %s" % channel) 200 | ## TODO здесь прерывается приглашение по инвайту 201 | 202 | # result = client(functions.channels.GetParticipantsRequest( 203 | # channel=channel, 204 | # filter=types.ChannelParticipantsRecent(), 205 | # # offset=42, 206 | # limit=100, 207 | # hash=0 208 | # )) 209 | # logger.info("GetParticipantsRequest %s" % result.stringify()) 210 | channel_admin = False 211 | async for user in client.iter_participants(channel, filter=types.ChannelParticipantsAdmins): 212 | logger.info("user %s" % user) 213 | if user.is_self: 214 | channel_admin = True 215 | else: 216 | owner = user 217 | logger.info("owner is %s" % owner) 218 | 219 | logger.info("Is Telegregator admin? %s" % channel_admin) 220 | if(channel_admin): 221 | await client.send_message(channel, response_message) 222 | # добавляем владельца в контакты 223 | if owner: 224 | contact = await add_contact(owner.id) 225 | 226 | # tggt добавили в канал (публичный и приватный) 227 | if channel_admin: 228 | await add_group_as_feed(feed_id=event.original_update.channel_id, contact_id=contact.contact_id) 229 | 230 | # для группы это 231 | # to_id=PeerChat( 232 | # chat_id=476968657 233 | # ), 234 | # для канала это 235 | # to_id=PeerChannel( 236 | # channel_id=1278799151 237 | # ), 238 | 239 | 240 | logger.info("action_message %s" % event.action_message) 241 | logger.info("action_message.from_id %s" % event.action_message.from_id) 242 | 243 | ## Определяем кто к нам постучался 244 | # для группы (чата) 245 | if event.action_message.from_id: 246 | contact = await add_contact(event.action_message.from_id) 247 | 248 | # tggt добавили в чат (группу) 249 | if event.action_message.action.__class__.__name__ in ['MessageActionChatAddUser', 'MessageActionChatCreate']: 250 | await add_group_as_feed(feed_id=event.action_message.to_id.chat_id, contact_id=contact.contact_id) 251 | 252 | # tggt удаляют из чата 253 | if(event.action_message.action.__class__.__name__ == 'MessageActionChatDeleteUser'): 254 | logger.info("Удалили из потока, обновляем в БД...") 255 | feed = Feed.delete().where(Feed.feed_id == event.action_message.to_id.chat_id).execute() 256 | logger.info(feed) 257 | 258 | @client.on(events.NewMessage) 259 | async def my_event_handler(event): 260 | # отладка 261 | logger.info("\r\n\r\n\r\n-------------Новое сообщение----------------") 262 | logger.info(event.message.stringify()) 263 | # logger.info(event.message) 264 | 265 | ## Определяем кто к нам постучался 266 | if event.message.from_id: 267 | contact = await add_contact(event.message.from_id) 268 | 269 | # личка бота 270 | management = 0 271 | handled = 0 272 | 273 | if hasattr(event.message.to_id, 'channel_id'): 274 | handled = 1 275 | # logger.info('сообщение пришло в канал (тип channel_id), нужно обрабатывать для пересылки') 276 | 277 | try: 278 | chat = await client.get_input_entity(event.message.to_id) 279 | except Exception as e: 280 | logger.error(e) 281 | 282 | if hasattr(event.message.to_id, 'user_id'): 283 | if event.message.to_id.user_id == 887053090 or event.message.to_id.user_id == None: 284 | logger.info("сообщение в личку телегрегатора, обрабатывать как управляющую команду") 285 | management = 1 286 | try: 287 | chat = await client.get_input_entity(event.message.from_id) 288 | await client.send_read_acknowledge(chat, event.message) 289 | except Exception as e: 290 | logger.error(e) 291 | else: 292 | try: 293 | chat = await client.get_input_entity(event.message.to_id) 294 | except Exception as e: 295 | logger.error(e) 296 | if hasattr(event.message.to_id, 'chat_id'): 297 | logger.info('сообщение в группу (chat_id) %s, нужно обрабатывать как команду' % event.message.to_id.chat_id) 298 | try: 299 | chat = await client.get_input_entity(event.message.to_id) 300 | except Exception as e: 301 | logger.error(e) 302 | 303 | if hasattr(event.message.to_id, 'channel_id'): 304 | logger.info('сообщение в канал (channel_id) %s, нужно обрабатывать как команду' % event.message.to_id.channel_id) 305 | 306 | try: 307 | chat = await client.get_input_entity(event.message.to_id) 308 | except Exception as e: 309 | logger.error(e) 310 | 311 | # для отладки, пересылаем сообщение админу (удалить таб, чтобы пересылать все. сейчас только команды в чат, см. проверку на строке 270) 312 | await event.message.forward_to(master_account) 313 | 314 | # mark message as read 315 | await client.send_read_acknowledge(chat, event.message) 316 | 317 | #Парсим команду 318 | command_handler = TelegramCommand() 319 | command, command_arg = command_handler.parseMessage(event.message.message) 320 | # если распознана команда 321 | if command: 322 | # прочитали 323 | await send_read(event.message.from_id, event.message) 324 | 325 | chat_command = command 326 | logger.info('Команда: {}, аргумент: {}'.format(chat_command, command_arg)) 327 | # message = 'I see chat_command "{}" and channel name is "{}"'.format(chat_command, command_arg) 328 | # await client.send_message(chat, message) 329 | # на присоединение к каналу 330 | if chat_command == 'help': 331 | await call_command(module='helpmessage', answer_to=chat) 332 | if chat_command == 'new': 333 | await call_command(module='new', answer_to=chat, contact=contact, group_name = command_arg) 334 | if chat_command == 'test': 335 | await call_command(module='test') 336 | if chat_command in ['join', 'add']: 337 | channels_list = command_arg.split(' ') 338 | for channel in channels_list: 339 | await call_command(module='join', answer_to=chat, channel_name=channel) 340 | # на описку от канала 341 | if chat_command in ['leave', 'remove', 'delete']: 342 | channels_list = command_arg.split(' ') 343 | for channel in channels_list: 344 | await call_command(module='leave', answer_to=chat, channel_name=channel) 345 | # на список подписанных каналов 346 | if chat_command == 'list': 347 | await call_command(module='listchannels', answer_to=chat, feed_id=event.message.to_id.chat_id) 348 | ## Вкл/выкл поток 349 | if chat_command in ['stop', 'pause']: 350 | await call_command(module='stop', answer_to=chat) 351 | if chat_command in ['start', 'resume']: 352 | await call_command(module='start', answer_to=chat) 353 | if chat_command in ['deleteall', 'exit']: 354 | await call_command(module='deleteall', answer_to=chat) 355 | ## Фильтры и стоп слова 356 | if chat_command == 'filter': 357 | await call_command(module='filter', answer_to=chat, word = command_arg, contact_id = contact.contact_id) 358 | if chat_command == 'filterremove' or chat_command == 'removefilter' or chat_command == 'unfilter': 359 | await call_command(module='unfilter', answer_to=chat, word = command_arg, contact_id = contact.contact_id) 360 | if chat_command == 'filterlist': 361 | await call_command(module='filterlist', answer_to=chat, contact_id = contact.contact_id) 362 | if chat_command == 'filterclear' or chat_command == 'clearfilter': 363 | await call_command(module='filterclear', answer_to=chat, contact_id = contact.contact_id) 364 | if chat_command == 'filterstop' or chat_command == 'stopfilter': 365 | await call_command(module='filterstop', answer_to=chat) 366 | if chat_command == 'filterstart' or chat_command == 'sartfilter': 367 | await call_command(module='filterstart', answer_to=chat) 368 | if chat_command == 'message': 369 | await call_command(module='message', message=command_arg) 370 | 371 | 372 | # if chat_command == 'all': 373 | # print('all') 374 | # await client.send_message(chat, 'Подписаны на следующие каналы:\r\n{}'.format('\r\n'.join([str(dialog.name) for dialog in client.iter_dialogs()]))) 375 | 376 | # если в сообщении нет команды 377 | if management: 378 | pass 379 | 380 | # если переслали сообщение 381 | # if event.message.fwd_from and event.message.fwd_from.channel_id and event.message.from_id == 388822642: 382 | if event.message.fwd_from and event.message.fwd_from.channel_id and event.message.to_id.__class__.__name__ == 'PeerChat': 383 | logger.info("Вижу репост канала в группу") 384 | try: 385 | channelInfo = await client.get_entity(event.message.fwd_from.channel_id) 386 | except Exception as e: 387 | logger.error(e) 388 | logger.error("не можем получить channelInfo для канала %s" % event.message.fwd_from.channel_id) 389 | channelInfo = None 390 | # logger.info("не можем получить channelInfo для канала %s" % channelEntity) 391 | await event.reply('не могу подписаться. Если канал закрытый, попробуйте по инвайту') 392 | else: 393 | 394 | # print(event.message.to_id.__class__.__name__) 395 | channelEntity = await client.get_input_entity(event.message.fwd_from.channel_id) 396 | logger.info('channelEntity {}'.format(channelEntity)) 397 | logger.info("Репост канала {} (@{}), id {}".format(channelInfo.title, channelInfo.username, channelInfo.id)) 398 | await call_command(module='join', answer_to=chat, channel_name=channelInfo.username) 399 | 400 | 401 | if event.message.fwd_from and event.message.fwd_from.channel_id and event.message.to_id.__class__.__name__ == 'PeerChannel': 402 | logger.info("Вижу репост канала в канал") 403 | try: 404 | channelInfo = await client.get_entity(event.message.fwd_from.channel_id) 405 | except Exception as e: 406 | logger.error(e) 407 | logger.error("не можем получить channelInfo для канала %s" % event.message.fwd_from.channel_id) 408 | channelInfo = None 409 | # logger.info("не можем получить channelInfo для канала %s" % channelEntity) 410 | await event.reply('не могу подписаться. Если канал закрытый, попробуйте по инвайту') 411 | else: 412 | 413 | # print(event.message.to_id.__class__.__name__) 414 | channelEntity = await client.get_input_entity(event.message.fwd_from.channel_id) 415 | logger.info('channelEntity {}'.format(channelEntity)) 416 | logger.info("Репост канала {} (@{}), id {}".format(channelInfo.title, channelInfo.username, channelInfo.id)) 417 | await call_command(module='join', answer_to=chat, channel_name=channelInfo.username) 418 | 419 | 420 | if handled: 421 | is_our = await is_our_channel(event.message.to_id) 422 | if not is_our: 423 | log_message = 'не мы' 424 | await handle_message(event) 425 | else: 426 | log_message = 'мы, сообщение нужно обрабатывать как команду' 427 | # TODO проверять написавшего админа, возможно это репост для добавления канала в поток 428 | 429 | logger.info("проверим кто создатель канала: " + log_message) 430 | 431 | 432 | client.start() 433 | # client.send_message(master_account, message='test', buttons=[['test']]) 434 | client.send_message(master_account, 'Telegregator is online!') 435 | # https://telethon.readthedocs.io/en/latest/modules/client.html#telethon.client.buttons.ButtonMethods 436 | # https://github.com/LonamiWebs/Telethon/blob/e47f3ec1d6db80964054bfe438c473c42f75392c/telethon/tl/custom/button.py 437 | # markup = bot.build_reply_markup([ 438 | # Button.switch_inline('/1', query='123', same_peer=True), 439 | # Button.switch_inline('/2', query='321', same_peer=True), 440 | # Button.switch_inline('/3', query='333', same_peer=True) 441 | # ]) 442 | # bot.send_message(-319949754, 'select command', buttons=markup) 443 | client.run_until_disconnected() 444 | --------------------------------------------------------------------------------