From 783e0109561b0dedd869f22ca5657d6b64677571 Mon Sep 17 00:00:00 2001 From: Simon Shi Date: Sat, 19 Aug 2017 05:36:30 +0800 Subject: [PATCH] python-telegram-bot v7 compatability + more (#35) * Remove import of telegram.Emoji * Using a list of filters in MessageHandler is getting deprecated * Update requirements Proudly upgrades to latest python-telegram-bot * Refine readme * Test kill command * Another implement * Add /kill command * initial config support * json config * Add token into json * Typo * Add Admin list * Refine admin & starter * Fix an exception * Fix typo * refine readme * Update help --- .gitignore | 3 ++ README.md | 17 ++++++----- bot.py | 71 +++++++++++++++++++++++++++++++++++---------- card.py | 10 +++---- config.json.example | 8 +++++ credentials.py | 21 -------------- game.py | 14 +++++---- requirements.txt | 2 +- settings.py | 22 +++++++------- shared_vars.py | 11 ++++--- simple_commands.py | 1 + utils.py | 17 +++++------ 12 files changed, 113 insertions(+), 84 deletions(-) create mode 100644 config.json.example delete mode 100644 credentials.py diff --git a/.gitignore b/.gitignore index 49decb9..9881366 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +# Config file +config.json + # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] diff --git a/README.md b/README.md index a7aa7ea..e00fa3d 100644 --- a/README.md +++ b/README.md @@ -2,18 +2,19 @@ [![License: AGPL v3](https://img.shields.io/badge/License-AGPL%20v3-blue.svg)](./LICENSE) -Telegram Bot that allows you to play the popular card game UNO via inline queries. The bot currently runs as [@unobot](http://telegram.me/unobot) +Telegram Bot that allows you to play the popular card game UNO via inline queries. The bot currently runs as [@unobot](http://telegram.me/unobot). To run the bot yourself, you will need: -- Python (tested with 3.4 and 3.5) -- The [python-telegram-bot](https://github.com/python-telegram-bot/python-telegram-bot) module version 5.0.0 +- Python (tested with 3.4+) +- The [python-telegram-bot](https://github.com/python-telegram-bot/python-telegram-bot) module - [Pony ORM](https://ponyorm.com/) ## Setup -- Get a bot token from [@BotFather](http://telegram.me/BotFather) and place it in `credentials.py` -- Convert all language files from .po to .mo using `msgfmt unobot.po -o unobot.mo` -- Use `/setinline` and `/setinlinefeedback` with BotFather for your bot +- Get a bot token from [@BotFather](http://telegram.me/BotFather) and change configurations in `config.json`. +- Convert all language files from `.po` files to `.mo` files using `msgfmt unobot.po -o unobot.mo`. + Also try `find . -maxdepth 2 -type d -name 'LC_MESSAGES' -exec bash -c 'msgfmt {}/unobot.po -o {}/unobot.mo' \;`. +- Use `/setinline` and `/setinlinefeedback` with BotFather for your bot. -Then run the bot with `python3 bot.py` +Then run the bot with `python3 bot.py`. -Code documentation is minimal but there +Code documentation is minimal but there. diff --git a/bot.py b/bot.py index e997a60..996a4ed 100644 --- a/bot.py +++ b/bot.py @@ -17,7 +17,6 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . - import logging from datetime import datetime from random import randint @@ -45,13 +44,16 @@ import settings from simple_commands import help +#import json +#with open("config.json","r") as f: +# config = json.loads(f.read()) +#forbidden = config.get("black_list", None) logging.basicConfig( format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO) logger = logging.getLogger(__name__) - @user_locale def notify_me(bot, update): """Handler for /notify_me command, pm people for next game""" @@ -88,7 +90,8 @@ def new_game(bot, update): del gm.remind_dict[update.message.chat_id] game = gm.new_game(update.message.chat) - game.owner = update.message.from_user + game.starter = update.message.from_user + game.owner.append(update.message.from_user.id) send_async(bot, chat_id, text=_("Created a new game! Join the game with /join " "and start the game with /start")) @@ -96,6 +99,41 @@ def new_game(bot, update): if botan: botan.track(update.message, 'New games') +@user_locale +def kill_game(bot, update): + """Handler for the /kill command""" + chat = update.message.chat + user = update.message.from_user + games = gm.chatid_games.get(chat.id) + + if update.message.chat.type == 'private': + help(bot, update) + return + + if not games: + send_async(bot, chat.id, + text=_("There is no running game in this chat.")) + return + + game = games[-1] + + if user.id in game.owner: + + try: + gm.end_game(chat, user) + send_async(bot, chat.id, text=__("Game ended!", multi=game.translate)) + + except NoGameInChatError: + send_async(bot, chat.id, + text=_("The game is not started yet. " + "Join the game with /join and start the game with /start"), + reply_to_message_id=update.message.message_id) + + else: + send_async(bot, chat.id, + text=_("Only the game creator ({name}) and admin can do that.") + .format(name=game.starter.first_name), + reply_to_message_id=update.message.message_id) @user_locale def join_game(bot, update): @@ -321,7 +359,7 @@ def close_game(bot, update): game = games[-1] - if game.owner.id == user.id: + if user.id in game.owner: game.open = False send_async(bot, chat.id, text=_("Closed the lobby. " "No more players can join this game.")) @@ -329,8 +367,8 @@ def close_game(bot, update): else: send_async(bot, chat.id, - text=_("Only the game creator ({name}) can do that.") - .format(name=game.owner.first_name), + text=_("Only the game creator ({name}) and admin can do that.") + .format(name=game.starter.first_name), reply_to_message_id=update.message.message_id) return @@ -349,15 +387,15 @@ def open_game(bot, update): game = games[-1] - if game.owner.id == user.id: + if user.id in game.owner: game.open = True send_async(bot, chat.id, text=_("Opened the lobby. " "New players may /join the game.")) return else: send_async(bot, chat.id, - text=_("Only the game creator ({name}) can do that") - .format(name=game.owner.first_name), + text=_("Only the game creator ({name}) and admin can do that.") + .format(name=game.starter.first_name), reply_to_message_id=update.message.message_id) return @@ -376,7 +414,7 @@ def enable_translations(bot, update): game = games[-1] - if game.owner.id == user.id: + if user.id in game.owner: game.translate = True send_async(bot, chat.id, text=_("Enabled multi-translations. " "Disable with /disable_translations")) @@ -384,8 +422,8 @@ def enable_translations(bot, update): else: send_async(bot, chat.id, - text=_("Only the game creator ({name}) can do that") - .format(name=game.owner.first_name), + text=_("Only the game creator ({name}) and admin can do that.") + .format(name=game.starter.first_name), reply_to_message_id=update.message.message_id) return @@ -404,7 +442,7 @@ def disable_translations(bot, update): game = games[-1] - if game.owner.id == user.id: + if user.id in game.owner: game.translate = False send_async(bot, chat.id, text=_("Disabled multi-translations. " "Enable them again with " @@ -413,8 +451,8 @@ def disable_translations(bot, update): else: send_async(bot, chat.id, - text=_("Only the game creator ({name}) can do that") - .format(name=game.owner.first_name), + text=_("Only the game creator ({name}) and admin can do that.") + .format(name=game.starter.first_name), reply_to_message_id=update.message.message_id) return @@ -728,6 +766,7 @@ dispatcher.add_handler(ChosenInlineResultHandler(process_result)) dispatcher.add_handler(CallbackQueryHandler(select_game)) dispatcher.add_handler(CommandHandler('start', start_game, pass_args=True)) dispatcher.add_handler(CommandHandler('new', new_game)) +dispatcher.add_handler(CommandHandler('kill', kill_game)) dispatcher.add_handler(CommandHandler('join', join_game)) dispatcher.add_handler(CommandHandler('leave', leave_game)) dispatcher.add_handler(CommandHandler('open', open_game)) @@ -740,7 +779,7 @@ dispatcher.add_handler(CommandHandler('skip', skip_player)) dispatcher.add_handler(CommandHandler('notify_me', notify_me)) simple_commands.register() settings.register() -dispatcher.add_handler(MessageHandler([Filters.status_update], status_update)) +dispatcher.add_handler(MessageHandler(Filters.status_update, status_update)) dispatcher.add_error_handler(error) start_bot(updater) diff --git a/card.py b/card.py index 93d8eb6..20717d8 100644 --- a/card.py +++ b/card.py @@ -18,8 +18,6 @@ # along with this program. If not, see . -from telegram.emoji import Emoji - # Colors RED = 'r' BLUE = 'b' @@ -30,10 +28,10 @@ BLACK = 'x' COLORS = (RED, BLUE, GREEN, YELLOW) COLOR_ICONS = { - RED: Emoji.HEAVY_BLACK_HEART, - BLUE: Emoji.BLUE_HEART, - GREEN: Emoji.GREEN_HEART, - YELLOW: Emoji.YELLOW_HEART, + RED: '❀️', + BLUE: 'πŸ’™', + GREEN: 'πŸ’š', + YELLOW: 'πŸ’›', BLACK: '⬛️' } diff --git a/config.json.example b/config.json.example new file mode 100644 index 0000000..0a851a0 --- /dev/null +++ b/config.json.example @@ -0,0 +1,8 @@ +{ + "token": "token_here", + "botan_token": null, + "admin_list": [0], + "open_lobby": true, + "enable_translations": false, + "workers": 32 +} diff --git a/credentials.py b/credentials.py deleted file mode 100644 index 3b4386d..0000000 --- a/credentials.py +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/env python3 -# -# Telegram bot to play UNO in group chats -# Copyright (c) 2016 Jannes HΓΆke -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . - - -TOKEN = 'TOKEN' -BOTAN_TOKEN = '' # Optional: Add a botan.io token if you want bot statistics diff --git a/game.py b/game.py index 5308fac..b2981c8 100644 --- a/game.py +++ b/game.py @@ -19,23 +19,25 @@ import logging +import json from datetime import datetime - from deck import Deck import card as c - class Game(object): """ This class represents a game of UNO """ current_player = None reversed = False - draw_counter = 0 choosing_color = False started = False - owner = None - open = True - translate = False + draw_counter = 0 players_won = 0 + starter = None + with open("config.json","r") as f: + config = json.loads(f.read()) + owner = config.get("admin_list", None) + open = config.get("open_lobby", True) + translate = config.get("enable_translations", False) def __init__(self, chat): self.chat = chat diff --git a/requirements.txt b/requirements.txt index ae6b2e0..76cafa0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,2 @@ -python-telegram-bot==5 +python-telegram-bot pony diff --git a/settings.py b/settings.py index d10bf38..622d897 100644 --- a/settings.py +++ b/settings.py @@ -18,7 +18,7 @@ # along with this program. If not, see . -from telegram import ReplyKeyboardMarkup, Emoji +from telegram import ReplyKeyboardMarkup from telegram.ext import CommandHandler, RegexHandler from utils import send_async @@ -44,12 +44,12 @@ def show_settings(bot, update): us = UserSetting(id=update.message.from_user.id) if not us.stats: - stats = Emoji.BAR_CHART + ' ' + _("Enable statistics") + stats = 'πŸ“Š' + ' ' + _("Enable statistics") else: - stats = Emoji.CROSS_MARK + ' ' + _("Delete all statistics") + stats = '❌' + ' ' + _("Delete all statistics") - kb = [[stats], [Emoji.EARTH_GLOBE_EUROPE_AFRICA + ' ' + _("Language")]] - send_async(bot, chat.id, text=Emoji.WRENCH + ' ' + _("Settings"), + kb = [[stats], ['🌍' + ' ' + _("Language")]] + send_async(bot, chat.id, text='πŸ”§' + ' ' + _("Settings"), reply_markup=ReplyKeyboardMarkup(keyboard=kb, one_time_keyboard=True)) @@ -60,12 +60,12 @@ def kb_select(bot, update, groups): user = update.message.from_user option = groups[0] - if option == Emoji.BAR_CHART: + if option == 'πŸ“Š': us = UserSetting.get(id=user.id) us.stats = True send_async(bot, chat.id, text=_("Enabled statistics!")) - elif option == Emoji.EARTH_GLOBE_EUROPE_AFRICA: + elif option == '🌍': kb = [[locale + ' - ' + descr] for locale, descr in sorted(available_locales.items())] @@ -73,7 +73,7 @@ def kb_select(bot, update, groups): reply_markup=ReplyKeyboardMarkup(keyboard=kb, one_time_keyboard=True)) - elif option == Emoji.CROSS_MARK: + elif option == '❌': us = UserSetting.get(id=user.id) us.stats = False us.first_places = 0 @@ -98,9 +98,9 @@ def locale_select(bot, update, groups): def register(): dispatcher.add_handler(CommandHandler('settings', show_settings)) - dispatcher.add_handler(RegexHandler('^([' + Emoji.BAR_CHART + - Emoji.EARTH_GLOBE_EUROPE_AFRICA + - Emoji.CROSS_MARK + ']) .+$', + dispatcher.add_handler(RegexHandler('^([' + 'πŸ“Š' + + '🌍' + + '❌' + ']) .+$', kb_select, pass_groups=True)) dispatcher.add_handler(RegexHandler(r'^(\w\w_\w\w) - .*', locale_select, pass_groups=True)) diff --git a/shared_vars.py b/shared_vars.py index 526620d..e966c55 100644 --- a/shared_vars.py +++ b/shared_vars.py @@ -17,21 +17,20 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . - +import json from telegram.ext import Updater from telegram.contrib.botan import Botan from game_manager import GameManager from database import db -from credentials import TOKEN, BOTAN_TOKEN db.bind('sqlite', 'uno.sqlite3', create_db=True) db.generate_mapping(create_tables=True) gm = GameManager() -updater = Updater(token=TOKEN, workers=32) +with open("config.json","r") as f: + config = json.loads(f.read()) +updater = Updater(token=config.get("token"), workers=config.get("workers", 32)) dispatcher = updater.dispatcher -botan = False -if BOTAN_TOKEN: - botan = Botan(BOTAN_TOKEN) +botan = Botan(config.get("botan_token", None)) diff --git a/simple_commands.py b/simple_commands.py index 3c032a0..2aad221 100644 --- a/simple_commands.py +++ b/simple_commands.py @@ -46,6 +46,7 @@ help_text = ("Follow these steps:\n\n" "Other commands (only game creator):\n" "/close - Close lobby\n" "/open - Open lobby\n" + "/kill - Terminate the game\n" "/enable_translations - Translate relevant texts into all " "languages spoken in a game\n" "/disable_translations - Use English for those texts\n\n" diff --git a/utils.py b/utils.py index 134a1b0..2c5f288 100644 --- a/utils.py +++ b/utils.py @@ -20,7 +20,6 @@ import logging -from telegram import Emoji from telegram.ext.dispatcher import run_async from internationalization import _, __ @@ -51,29 +50,29 @@ def display_name(user): def display_color(color): """ Convert a color code to actual color name """ if color == "r": - return _("{emoji} Red").format(emoji=Emoji.HEAVY_BLACK_HEART) + return _("{emoji} Red").format(emoji='❀️') if color == "b": - return _("{emoji} Blue").format(emoji=Emoji.BLUE_HEART) + return _("{emoji} Blue").format(emoji='πŸ’™') if color == "g": - return _("{emoji} Green").format(emoji=Emoji.GREEN_HEART) + return _("{emoji} Green").format(emoji='πŸ’š') if color == "y": - return _("{emoji} Yellow").format(emoji=Emoji.YELLOW_HEART) + return _("{emoji} Yellow").format(emoji='πŸ’›') def display_color_group(color, game): """ Convert a color code to actual color name """ if color == "r": return __("{emoji} Red", game.translate).format( - emoji=Emoji.HEAVY_BLACK_HEART) + emoji='❀️') if color == "b": return __("{emoji} Blue", game.translate).format( - emoji=Emoji.BLUE_HEART) + emoji='πŸ’™') if color == "g": return __("{emoji} Green", game.translate).format( - emoji=Emoji.GREEN_HEART) + emoji='πŸ’š') if color == "y": return __("{emoji} Yellow", game.translate).format( - emoji=Emoji.YELLOW_HEART) + emoji='πŸ’›') def error(bot, update, error):