diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..cb238f7 --- /dev/null +++ b/.env.example @@ -0,0 +1,4 @@ +export BOT_TOKEN=meu_token_123 +export BOT_PROD_TOKEN=meu_token_123 +export MODE=cmd +export SERVER_URL=https://jerimumhsbot.herokuapp.com diff --git a/.gitignore b/.gitignore index 23e900f..8745e4a 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,8 @@ config.json .vscode __pycache__ *.pyc + +.env + +env/ +.idea/ diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..482973c --- /dev/null +++ b/Makefile @@ -0,0 +1,2 @@ +run: + python run.py diff --git a/Procfile b/Procfile new file mode 100644 index 0000000..fe26464 --- /dev/null +++ b/Procfile @@ -0,0 +1 @@ +web: python run.py diff --git a/bot.py b/bot.py new file mode 100644 index 0000000..6dd6ed3 --- /dev/null +++ b/bot.py @@ -0,0 +1,13 @@ +from core import BotTelegramCore +from mixins import (BaseCommandsBotMixin, CallbackBotMixin, ErrorBotMixin, + MessageBotMixin, StickerBotMixin) + + +class JerimumBot(BaseCommandsBotMixin, CallbackBotMixin, ErrorBotMixin, + MessageBotMixin, StickerBotMixin): + """Bot Controller""" + + def config_handlers(self): + for BaseClass in self.__class__.__bases__: + assert issubclass(BaseClass, BotTelegramCore) + BaseClass.config_handlers(self) diff --git a/bot/__init__.py b/bot/__init__.py deleted file mode 100644 index 82ce6f7..0000000 --- a/bot/__init__.py +++ /dev/null @@ -1,12 +0,0 @@ -import os -import ujson - -CONFIG_FILE = os.environ.get('JHS_CONFIG_FILE') - -if CONFIG_FILE: - with open(CONFIG_FILE) as file_pointer: - config = ujson.load(file_pointer) -else: - config = {} - raise Exception( - "You didn't set \"JHS_CONFIG_FILE\" enviroment variable") diff --git a/bot/bot.py b/bot/bot.py deleted file mode 100644 index 99c7be0..0000000 --- a/bot/bot.py +++ /dev/null @@ -1,172 +0,0 @@ -from telegram import InlineKeyboardButton, InlineKeyboardMarkup -from telegram.ext import (Updater, CommandHandler, - MessageHandler, Filters, CallbackQueryHandler) -import logging -from pymongo import MongoClient - -from . import config - -logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', - level=logging.INFO) - -logger = logging.getLogger(__name__) - -cliente = MongoClient('localhost', 27017) -banco = cliente['JerimumHSBot'] -mensagens = banco['mensagens'] - - -def load_messages(group_id, message): - custom = mensagens.find_one({"_id": group_id}) - if custom and custom.get(message): - return custom[message] - default = mensagens.find_one({"_id": "default"}) - return default[message] - -def start(bot, update): - """Send a message when the command /start is issued.""" - start = (load_messages( - update.message.chat_id, "start")) - update.message.reply_text(start) - - -def help(bot, update): - """Send a message when the command /help is issued.""" - help = (load_messages( - update.message.chat_id, "help")) - update.message.reply_text(help) - - -def welcome(bot, update): - """Send a message when a new user join the group.""" - welcome = (load_messages( - update.message.chat_id, "welcome")).format( - full_name=update.message.new_chat_members[0].full_name) - - keyboard = [ - [ - InlineKeyboardButton( - "Resumo das regras!", - callback_data='rules') - ], - - [ - InlineKeyboardButton( - "Nosso site!", - callback_data='site', - url="http://jerimumhacker.space/"), - - InlineKeyboardButton( - "Nosso Facebook!", - callback_data='site', - url="https://www.facebook.com/JerimumHS/") - ], - - [ - InlineKeyboardButton( - "Nosso GitHub!", - callback_data='site', - url="https://github.com/jerimumhs/") - ] - ] - - reply_markup = InlineKeyboardMarkup(keyboard) - update.message.reply_text(welcome, reply_markup=reply_markup) - - -def button(bot, update): - query = update.callback_query - - if query.data == "rules": - bot.answer_callback_query( - callback_query_id=query.id, - text=( - (load_messages( - update.message.chat_id, "rules_bit")) - ), - show_alert=True - ) - elif query.data == "site": - bot.answer_callback_query( - callback_query_id=query.id - ) - -def bye(bot, update): - """Send a message when a user leaves the group.""" - bye = (load_messages( - update.message.chat_id, "bye")).format( - full_name=update.message.left_chat_member.full_name) - update.message.reply_text(bye) - -def rules(bot, update): - """Send a message with the group rules.""" - rules = (load_messages( - update.message.chat_id, "rules_complete")) - if adm_verify(update): - update.message.reply_text(rules) - else: - bot.sendMessage( - chat_id=update.message.from_user.id, - text=rules) - - -def description(bot, update): - """Send a message with the group description.""" - description = (load_messages( - update.message.chat_id, "description")) - if adm_verify(update): - update.message.reply_text(description) - else: - bot.sendMessage( - chat_id=update.message.from_user.id, - text=description) - - -def xinga(bot, update): - """Send the Guilherme picture.""" - bot.send_sticker(sticker="CAADAQADCgEAAmOWFQq4zU4TMS08AwI", - chat_id=update.message.chat_id) - - -def error(bot, update, error): - """Log Errors caused by Updates.""" - if error.message == "Forbidden: bot can't initiate conversation with a user": - update.message.reply_text(load_messages( - update.message.chat_id, "error_initiate")) - elif error.message == "Forbidden: bot was blocked by the user": - update.message.reply_text(load_messages( - update.message.chat_id, "error_blocked")) - else: - logger.warning('Update "%s" caused error "%s"', update, error) - -def adm_verify(update): - if update.message.chat.get_member(update.message.from_user.id).status in ('creator', 'administrator'): - return True - return False - - -def run_bot(): - """Start the bot.""" - updater = Updater(config['telegram']['token']) - - dp = updater.dispatcher - - dp.add_handler(CommandHandler("start", start)) - dp.add_handler(CommandHandler("ajuda", help)) - dp.add_handler(CommandHandler("regras", rules)) - dp.add_handler(CommandHandler("xinga", xinga)) - dp.add_handler(CommandHandler("descricao", description)) - - dp.add_handler(MessageHandler( - Filters.status_update.new_chat_members, welcome)) - - dp.add_handler(MessageHandler( - Filters.status_update.left_chat_member, bye)) - - dp.add_handler(CallbackQueryHandler(button)) - - dp.add_error_handler(error) - - updater.start_polling() - - updater.idle() diff --git a/core/__init__.py b/core/__init__.py new file mode 100644 index 0000000..94d3e7c --- /dev/null +++ b/core/__init__.py @@ -0,0 +1,2 @@ +from core.auth import adm_verify +from core.telegram import BotTelegramCore diff --git a/core/auth.py b/core/auth.py new file mode 100644 index 0000000..ddd829e --- /dev/null +++ b/core/auth.py @@ -0,0 +1,3 @@ + +def adm_verify(update): + return update.message.chat.get_member(update.message.from_user.id).status in ('creator', 'administrator') diff --git a/core/telegram.py b/core/telegram.py new file mode 100644 index 0000000..e50b2e5 --- /dev/null +++ b/core/telegram.py @@ -0,0 +1,46 @@ +from abc import ABC, abstractmethod +import logging + +from telegram.ext import Updater + + +logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', + level=logging.INFO) + +logger = logging.getLogger(__name__) + + +class BotTelegramCore(ABC): + def __init__(self, token, port, server_url): + logging.info('Inicializando o bot...') + self.token = token + self.port = port + self.server_url = server_url + + self.updater = Updater(self.token) + self.config_handlers() + + @abstractmethod + def config_handlers(self): + raise NotImplementedError('Cannot call config_handler from BotCore') + + def run_web(self): + """Start the bot as a webhook server""" + + self.updater.start_webhook( + listen="0.0.0.0", + port=self.port, + url_path=self.token + ) + + self.updater.bot.set_webhook(f"{self.server_url}/{self.token}") + + logging.info('Bot está rodando como um webserver!') + self.updater.idle() + + def run_cmd(self): + """Start the bot as a python script loop""" + self.updater.start_polling() + + logging.info('Bot está rodando como um script python!') + self.updater.idle() diff --git a/bot/imgs/guilherme.jpg b/imgs/guilherme.jpg similarity index 100% rename from bot/imgs/guilherme.jpg rename to imgs/guilherme.jpg diff --git a/bot/scripts/default_messages.py b/messages.py similarity index 79% rename from bot/scripts/default_messages.py rename to messages.py index 3bac78e..cc41b56 100644 --- a/bot/scripts/default_messages.py +++ b/messages.py @@ -1,10 +1,4 @@ -from pymongo import MongoClient - -cliente = MongoClient('localhost', 27017) -banco = cliente['JerimumHSBot'] -mensagens = banco['mensagens'] - -rules_complete = ( +RULES_COMPLETE = ( "1. Não haver discriminação em nenhum sentido, raça, religião, " "sexo ou linguagem de programação.\n" "2. Esse não é um grupo para discussões de política ou religião, " @@ -25,7 +19,7 @@ "Att. Jerimum Hacker Bot <3" ) -rules_bit = ( +RULES_BIT = ( "REGRAS:\n\n" "1.Respeitar os membros do grupo\n" "2.Não compartilhar conteúdo sem autorização\n" @@ -34,7 +28,7 @@ "5.Havendo qualquer restrição às regras, será banido" ) -description = ( +DESCRIPTION = ( "O Jerimum Hackerspace é um local aberto e colaborativo que " "fomenta a troca de conhecimento e experiências, onde as pessoas " "podem se encontrar, socializar, compartilhar e colaborar. " @@ -44,47 +38,31 @@ "ou o que mais a criatividade permitir." ) -welcome = ( +WELCOME = ( "Olá {full_name}, seja bem-vindo ao Jerimum Hackerspace\n\n" "Somos um grupo de pessoas interessadas em usar, remixar e compartilhar " "tecnologia, aprendizado, diversão e cultura de forma colaborativa e indiscriminada.\n\n" "Leia nossas /regras e agora porque você não fala um pouco sobre você?" ) -bye = ( +BYE = ( "{full_name} acabou de sair do grupo, uma palminha, e uma vainha...\n\n" "UUUuuuUUuUUUuUUUuu" ) -help = ( +HELP = ( "Está com duvidas? Fale com nossos membros!\n" "Em caso de duvidas mais especificas procure nossos Administradores." ) -start = ( +START = ( "Para começar, basta digitar!" ) -error_initiate = ( +ERROR_INITIATE = ( "Por favor, inicie uma conversa comigo para que eu possa te enviar uma mensagem!" ) -error_blocked = ( +ERROR_BLOCKED = ( "Você me bloqueou?! Tsc tsc. Que feio!!!🙄" ) - -mensagens.update_one({ - "_id": "default"}, - {"$set":{ - "start": start, - "rules_complete": rules_complete, - "rules_bit": rules_bit, - "description": description, - "welcome": welcome, - "bye": bye, - "help": help, - "error_initiate": error_initiate, - "error_blocked": error_blocked - } - }, upsert=True) - diff --git a/mixins/__init__.py b/mixins/__init__.py new file mode 100644 index 0000000..9ccc50f --- /dev/null +++ b/mixins/__init__.py @@ -0,0 +1,5 @@ +from mixins.base_commands import BaseCommandsBotMixin +from mixins.callback import CallbackBotMixin +from mixins.error import ErrorBotMixin +from mixins.message import MessageBotMixin +from mixins.sticker import StickerBotMixin diff --git a/mixins/base_commands.py b/mixins/base_commands.py new file mode 100644 index 0000000..23a1c4c --- /dev/null +++ b/mixins/base_commands.py @@ -0,0 +1,44 @@ +import logging + +from telegram.ext import CommandHandler + +from core import BotTelegramCore, adm_verify +from messages import START, HELP, DESCRIPTION, RULES_COMPLETE + + +logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', + level=logging.INFO) + +logger = logging.getLogger(__name__) + + +class BaseCommandsBotMixin(BotTelegramCore): + def config_handlers(self): + logging.info('Configurando comandos base do bot...') + dp = self.updater.dispatcher + + dp.add_handler(CommandHandler("regras", self.__class__.rules)) + dp.add_handler(CommandHandler("descricao", self.__class__.description)) + + dp.add_handler(CommandHandler("start", lambda bot, update: update.message.reply_text(START))) + dp.add_handler(CommandHandler("ajuda", lambda bot, update: update.message.reply_text(HELP))) + + @staticmethod + def description(bot, update): + """Send a message with the group description.""" + if adm_verify(update): + update.message.reply_text(DESCRIPTION) + else: + bot.sendMessage( + chat_id=update.message.from_user.id, + text=DESCRIPTION) + + @staticmethod + def rules(bot, update): + """Send a message with the group rules.""" + if adm_verify(update): + update.message.reply_text(RULES_COMPLETE) + else: + bot.sendMessage( + chat_id=update.message.from_user.id, + text=RULES_COMPLETE) diff --git a/mixins/callback.py b/mixins/callback.py new file mode 100644 index 0000000..63cee50 --- /dev/null +++ b/mixins/callback.py @@ -0,0 +1,33 @@ +import logging + +from telegram.ext import CallbackQueryHandler + +from core import BotTelegramCore +from messages import RULES_BIT + + +logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', + level=logging.INFO) + +logger = logging.getLogger(__name__) + + +class CallbackBotMixin(BotTelegramCore): + def config_handlers(self): + logging.info('Configurando callback handler do bot...') + self.updater.dispatcher.add_handler(CallbackQueryHandler(self.__class__.handle_callback)) + + @staticmethod + def handle_callback(bot, update): + query = update.callback_query + + if query.data == "rules": + bot.answer_callback_query( + callback_query_id=query.id, + text=RULES_BIT, + show_alert=True + ) + elif query.data == "site": + bot.answer_callback_query( + callback_query_id=query.id + ) diff --git a/mixins/error.py b/mixins/error.py new file mode 100644 index 0000000..5c1a008 --- /dev/null +++ b/mixins/error.py @@ -0,0 +1,26 @@ +import logging + +from core import BotTelegramCore +from messages import ERROR_BLOCKED, ERROR_INITIATE + + +logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', + level=logging.INFO) + +logger = logging.getLogger(__name__) + + +class ErrorBotMixin(BotTelegramCore): + def config_handlers(self): + logging.info('Configurando error handler do bot...') + self.updater.dispatcher.add_error_handler(self.__class__.error) + + @staticmethod + def error(bot, update, err): + """Log Errors caused by Updates.""" + if err.message == "Forbidden: bot can't initiate conversation with a user": + update.message.reply_text(ERROR_INITIATE) + elif err.message == "Forbidden: bot was blocked by the user": + update.message.reply_text(ERROR_BLOCKED) + else: + logger.warning('Update "%s" caused error "%s"', update, err) diff --git a/mixins/message.py b/mixins/message.py new file mode 100644 index 0000000..88a5391 --- /dev/null +++ b/mixins/message.py @@ -0,0 +1,61 @@ +import logging + +from telegram import InlineKeyboardButton, InlineKeyboardMarkup +from telegram.ext import MessageHandler, Filters + +from core import BotTelegramCore +from messages import BYE, WELCOME + + +logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', + level=logging.INFO) + +logger = logging.getLogger(__name__) + + +class MessageBotMixin(BotTelegramCore): + def config_handlers(self): + logging.info('Configurando message handlers do bot...') + + self.updater.dispatcher.add_handler(MessageHandler( + Filters.status_update.new_chat_members, self.__class__.welcome)) + + self.updater.dispatcher.add_handler(MessageHandler( + Filters.status_update.left_chat_member, + lambda bot, update: update.message.reply_text( + BYE.format(full_name=update.message.left_chat_member.full_name)))) + + @staticmethod + def welcome(bot, update): + """Send a message when a new user join the group.""" + welcome_message = WELCOME.format(full_name=update.message.new_chat_members[0].full_name) + + keyboard = [ + [ + InlineKeyboardButton( + "Resumo das regras!", + callback_data='rules') + ], + + [ + InlineKeyboardButton( + "Nosso site!", + callback_data='site', + url="http://jerimumhacker.space/"), + + InlineKeyboardButton( + "Nosso Facebook!", + callback_data='site', + url="https://www.facebook.com/JerimumHS/") + ], + + [ + InlineKeyboardButton( + "Nosso GitHub!", + callback_data='site', + url="https://github.com/jerimumhs/") + ] + ] + + reply_markup = InlineKeyboardMarkup(keyboard) + update.message.reply_text(welcome_message, reply_markup=reply_markup) diff --git a/mixins/sticker.py b/mixins/sticker.py new file mode 100644 index 0000000..678dd64 --- /dev/null +++ b/mixins/sticker.py @@ -0,0 +1,20 @@ +import logging + +from telegram.ext import (CommandHandler) + +from core import BotTelegramCore + + +logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', + level=logging.INFO) + +logger = logging.getLogger(__name__) + + +class StickerBotMixin(BotTelegramCore): + def config_handlers(self): + logging.info('Configurando comandos de sticker do bot...') + + self.updater.dispatcher.add_handler(CommandHandler("xinga", lambda bot, update: bot.send_sticker( + sticker="CAADAQADCgEAAmOWFQq4zU4TMS08AwI", + chat_id=update.message.chat_id))) diff --git a/requirements.txt b/requirements.txt index 73fb081..81ed9df 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,3 +2,4 @@ python_telegram_bot==10.0.2 ujson==1.35 pymongo==3.6.1 telegram==0.0.1 +python-decouple==3.1 \ No newline at end of file diff --git a/run.py b/run.py new file mode 100644 index 0000000..2f2d522 --- /dev/null +++ b/run.py @@ -0,0 +1,29 @@ +import logging + +from decouple import config + +from bot import JerimumBot + + +if __name__ == '__main__': + instance = JerimumBot( + token=config('BOT_TOKEN', default='??'), + port=config('PORT', default=8443, cast=int), + server_url=config('SERVER_URL', default='??') + ) + + try: + mode = config('MODE', default='cmd') + if mode == 'cmd': + instance.run_cmd() + elif mode == 'web': + instance.run_web() + else: + raise Exception('O modo passado não foi reconhecido') + + except Exception as e: + logging.error(f'Modo: {config("MODE", default="cmd")}') + logging.error(f'token: {instance.token}') + logging.error(f'Port: {instance.port}') + logging.error(f'heroku app name: {instance.server_url}') + raise e diff --git a/run_bot.py b/run_bot.py deleted file mode 100644 index cf97d83..0000000 --- a/run_bot.py +++ /dev/null @@ -1,5 +0,0 @@ -from bot.bot import run_bot - - -if __name__ == '__main__': - run_bot() \ No newline at end of file