From 6c610c1aeba11f4d3036634ead2b2ca6f461631f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jannes=20H=C3=B6ke?= Date: Sun, 22 May 2016 14:45:51 +0200 Subject: [PATCH] settings UI added, save locale to database --- bot.py | 164 ++++------------------------ locales/de_DE/LC_MESSAGES/unobot.po | 36 ++++++ locales/unobot.pot | 36 ++++++ settings.py | 104 ++++++++++++++++++ shared_vars.py | 37 +++++++ simple_commands.py | 83 ++++++++++++++ user_setting.py | 7 +- utils.py | 68 ++++++++++++ 8 files changed, 388 insertions(+), 147 deletions(-) create mode 100644 settings.py create mode 100644 shared_vars.py create mode 100644 simple_commands.py diff --git a/bot.py b/bot.py index 0566d21..e1bdefa 100644 --- a/bot.py +++ b/bot.py @@ -20,19 +20,14 @@ import logging from datetime import datetime -from functools import wraps from random import randint from telegram import ParseMode, Message, Chat, InlineKeyboardMarkup, \ InlineKeyboardButton -from telegram.ext import Updater, InlineQueryHandler, \ - ChosenInlineResultHandler, CommandHandler, MessageHandler, Filters, \ - CallbackQueryHandler +from telegram.ext import InlineQueryHandler, ChosenInlineResultHandler, \ + CommandHandler, MessageHandler, Filters, CallbackQueryHandler from telegram.ext.dispatcher import run_async -from telegram.utils.botan import Botan -from game_manager import GameManager -from credentials import TOKEN, BOTAN_TOKEN from start_bot import start_bot from results import (add_call_bluff, add_choose_color, add_draw, add_gameinfo, add_no_game, add_not_started, add_other_cards, add_pass, @@ -41,111 +36,18 @@ from utils import display_name import card as c from errors import (NoGameInChatError, LobbyClosedError, AlreadyJoinedError, NotEnoughPlayersError, DeckEmptyError) -from utils import _, __ +from utils import _, __, send_async, answer_async, user_locale, game_locales, \ + error, TIMEOUT +from shared_vars import botan, gm, updater, dispatcher +import simple_commands, settings -TIMEOUT = 2.5 logging.basicConfig( format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.DEBUG) logger = logging.getLogger(__name__) -gm = GameManager() -u = Updater(token=TOKEN, workers=32) -dp = u.dispatcher - -botan = False -if BOTAN_TOKEN: - botan = Botan(BOTAN_TOKEN) - -help_text = ("Follow these steps:\n\n" - "1. Add this bot to a group\n" - "2. In the group, start a new game with /new or join an already" - " running game with /join\n" - "3. After at least two players have joined, start the game with" - " /start\n" - "4. Type @mau_mau_bot into your chat box and hit " - "space, or click the via @mau_mau_bot text " - "next to messages. You will see your cards (some greyed out), " - "any extra options like drawing, and a ? to see the " - "current game state. The greyed out cards are those you " - "can not play at the moment. Tap an option to execute " - "the selected action.\n" - "Players can join the game at any time. To leave a game, " - "use /leave. If a player takes more than 90 seconds to play, " - "you can use /skip to skip that player.\n\n" - "Other commands (only game creator):\n" - "/close - Close lobby\n" - "/open - Open lobby\n\n" - "Experimental: Play in multiple groups at the same time. " - "Press the Current game: ... button and select the " - "group you want to play a card in.\n" - "If you enjoy this bot, " - "" - "rate me, join the " - "update channel" - " and buy an UNO card game.") - -source_text = ("This bot is Free Software and licensed under the AGPL. " - "The code is available here: \n" - "https://github.com/jh0ker/mau_mau_bot") - - -def user_locale(func): - @wraps(func) - def wrapped(*pargs, **kwargs): - _.push('de_DE') # TODO: Get user locale from Database - result = func(*pargs, **kwargs) - _.pop() - return result - return wrapped - - -def game_locales(func): - @wraps(func) - def wrapped(*pargs, **kwargs): - num_locales = 0 - for loc in ('en_US', 'de_DE'): # TODO: Get user locales from Database - _.push(loc) - num_locales += 1 - - result = func(*pargs, **kwargs) - - for i in range(num_locales): - _.pop() - return result - return wrapped - - -@run_async -def send_async(bot, *args, **kwargs): - """Send a message asynchronously""" - if 'timeout' not in kwargs: - kwargs['timeout'] = TIMEOUT - - try: - bot.sendMessage(*args, **kwargs) - except Exception as e: - error(None, None, e) - - -@run_async -def answer_async(bot, *args, **kwargs): - """Answer an inline query asynchronously""" - if 'timeout' not in kwargs: - kwargs['timeout'] = TIMEOUT - - try: - bot.answerInlineQuery(*args, **kwargs) - except Exception as e: - error(None, None, e) - - -def error(bot, update, error): - """Simple error handler""" - logger.exception(error) - @user_locale def new_game(bot, update): @@ -487,29 +389,6 @@ def skip_player(bot, update): gm.end_game(chat.id, skipped_player.user) - -@user_locale -def help(bot, update): - """Handler for the /help command""" - send_async(bot, update.message.chat_id, text=_(help_text), - parse_mode=ParseMode.HTML, disable_web_page_preview=True) - - -@user_locale -def source(bot, update): - """Handler for the /help command""" - send_async(bot, update.message.chat_id, text=_(source_text), - parse_mode=ParseMode.HTML, disable_web_page_preview=True) - - -@user_locale -def news(bot, update): - """Handler for the /news command""" - send_async(bot, update.message.chat_id, - text=_("All news here: https://telegram.me/unobotupdates"), - disable_web_page_preview=True) - - @game_locales @user_locale def reply_to_query(bot, update): @@ -712,21 +591,18 @@ def do_call_bluff(bot, player): # Add all handlers to the dispatcher and run the bot -dp.add_handler(InlineQueryHandler(reply_to_query)) -dp.add_handler(ChosenInlineResultHandler(process_result)) -dp.add_handler(CallbackQueryHandler(select_game)) -dp.add_handler(CommandHandler('start', start_game, pass_args=True)) -dp.add_handler(CommandHandler('new', new_game)) -dp.add_handler(CommandHandler('join', join_game)) -dp.add_handler(CommandHandler('leave', leave_game)) -dp.add_handler(CommandHandler('open', open_game)) -dp.add_handler(CommandHandler('close', close_game)) -dp.add_handler(CommandHandler('skip', skip_player)) -dp.add_handler(CommandHandler('help', help)) -dp.add_handler(CommandHandler('source', source)) -dp.add_handler(CommandHandler('news', news)) -dp.add_handler(MessageHandler([Filters.status_update], status_update)) -dp.add_error_handler(error) +dispatcher.add_handler(InlineQueryHandler(reply_to_query)) +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('join', join_game)) +dispatcher.add_handler(CommandHandler('leave', leave_game)) +dispatcher.add_handler(CommandHandler('open', open_game)) +dispatcher.add_handler(CommandHandler('close', close_game)) +dispatcher.add_handler(CommandHandler('skip', skip_player)) +dispatcher.add_handler(MessageHandler([Filters.status_update], status_update)) +dispatcher.add_error_handler(error) -start_bot(u) -u.idle() +start_bot(updater) +updater.idle() diff --git a/locales/de_DE/LC_MESSAGES/unobot.po b/locales/de_DE/LC_MESSAGES/unobot.po index 63bc3d7..89a0232 100644 --- a/locales/de_DE/LC_MESSAGES/unobot.po +++ b/locales/de_DE/LC_MESSAGES/unobot.po @@ -350,3 +350,39 @@ msgstr "Passe" #: results.py:148 msgid "I'm calling your bluff!" msgstr "Ich glaube du bluffst!" + +#: settings.py:39 +msgid "Please edit your settings in a private chat with the bot." +msgstr "Bitte ändere deine Einstellungen in einem privaten Chat mit dem Bot." + +#: settings.py:49 +msgid "Enable statistics" +msgstr "Statistiken aktivieren" + +#: settings.py:51 +msgid "Delete all statistics" +msgstr "Alle Statistiken löschen" + +#: settings.py:53 +msgid "Language" +msgstr "Sprache" + +#: settings.py:54 +msgid "Settings" +msgstr "Einstellungen" + +#: settings.py:68 +msgid "Enabled statistics!" +msgstr "Statistiken aktiviert!" + +#: settings.py:70 +msgid "Select locale" +msgstr "Bitte Sprache auswählen" + +#: settings.py:81 +msgid "Deleted and disabled statistics!" +msgstr "Alle Statistiken gelöscht und deaktiviert!" + +#: settings.py:94 +msgid "Set locale!" +msgstr "Sprache gesetzt!" diff --git a/locales/unobot.pot b/locales/unobot.pot index 71b93a8..2742045 100644 --- a/locales/unobot.pot +++ b/locales/unobot.pot @@ -294,3 +294,39 @@ msgstr "" msgid "I'm calling your bluff!" msgstr "" +#: settings.py:39 +msgid "Please edit your settings in a private chat with the bot." +msgstr "" + +#: settings.py:49 +msgid "Enable statistics" +msgstr "" + +#: settings.py:51 +msgid "Delete all statistics" +msgstr "" + +#: settings.py:53 +msgid "Language" +msgstr "" + +#: settings.py:54 +msgid "Settings" +msgstr "" + +#: settings.py:68 +msgid "Enabled statistics!" +msgstr "" + +#: settings.py:70 +msgid "Select locale" +msgstr "" + +#: settings.py:81 +msgid "Deleted and disabled statistics!" +msgstr "" + +#: settings.py:94 +msgid "Set locale!" +msgstr "" + diff --git a/settings.py b/settings.py new file mode 100644 index 0000000..b98600d --- /dev/null +++ b/settings.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# 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 . + + +import logging + +from telegram import ReplyKeyboardMarkup, Emoji +from telegram.ext import CommandHandler, RegexHandler + +from utils import send_async +from user_setting import UserSetting +from utils import _, user_locale +from shared_vars import dispatcher + +available_locales = [['en_US', 'de_DE']] + +@user_locale +def show_settings(bot, update): + chat = update.message.chat + + if update.message.chat.type != 'private': + send_async(bot, chat.id, + text=_("Please edit your settings in a private chat with " + "the bot.")) + return + + us = UserSetting.get(id=update.message.from_user.id) + + if not us: + us = UserSetting(id=update.message.from_user.id) + + if not us.stats: + stats = Emoji.BAR_CHART + ' ' + _("Enable statistics") + else: + stats = Emoji.CROSS_MARK + ' ' + _("Delete all statistics") + + kb = [[stats], [Emoji.EARTH_GLOBE_EUROPE_AFRICA + ' ' + _("Language")]] + send_async(bot, chat.id, text=Emoji.WRENCH + ' ' + _("Settings"), + reply_markup=ReplyKeyboardMarkup(keyboard=kb, + one_time_keyboard=True)) + + +@user_locale +def kb_select(bot, update, groups): + chat = update.message.chat + user = update.message.from_user + option = groups[0] + + if option == Emoji.BAR_CHART: + us = UserSetting.get(id=user.id) + us.stats = True + send_async(bot, chat.id, text=_("Enabled statistics!")) + + elif option == Emoji.EARTH_GLOBE_EUROPE_AFRICA: + send_async(bot, chat.id, text=_("Select locale"), + reply_markup=ReplyKeyboardMarkup(keyboard=available_locales, + one_time_keyboard=True)) + + elif option == Emoji.CROSS_MARK: + us = UserSetting.get(id=user.id) + us.stats = False + us.first_places = 0 + us.games_played = 0 + us.cards_played = 0 + send_async(bot, chat.id, text=_("Deleted and disabled statistics!")) + + +@user_locale +def locale_select(bot, update, groups): + chat = update.message.chat + user = update.message.from_user + option = groups[0] + + if option in [locale for row in available_locales for locale in row]: + us = UserSetting.get(id=user.id) + us.lang = option + _.push(option) + send_async(bot, chat.id, text=_("Set locale!")) + _.pop() + + +dispatcher.add_handler(CommandHandler('settings', show_settings)) +dispatcher.add_handler(RegexHandler('^([' + Emoji.BAR_CHART + + Emoji.EARTH_GLOBE_EUROPE_AFRICA + + Emoji.CROSS_MARK + ']) .+$', + 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 new file mode 100644 index 0000000..ceda668 --- /dev/null +++ b/shared_vars.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# 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 . + + +from telegram.ext import Updater +from telegram.utils.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) +dispatcher = updater.dispatcher + +botan = False +if BOTAN_TOKEN: + botan = Botan(BOTAN_TOKEN) diff --git a/simple_commands.py b/simple_commands.py new file mode 100644 index 0000000..5fb9d13 --- /dev/null +++ b/simple_commands.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# 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 . + +from telegram import ParseMode +from telegram.ext import CommandHandler + +from utils import _, send_async, user_locale +from shared_vars import dispatcher + +help_text = ("Follow these steps:\n\n" + "1. Add this bot to a group\n" + "2. In the group, start a new game with /new or join an already" + " running game with /join\n" + "3. After at least two players have joined, start the game with" + " /start\n" + "4. Type @mau_mau_bot into your chat box and hit " + "space, or click the via @mau_mau_bot text " + "next to messages. You will see your cards (some greyed out), " + "any extra options like drawing, and a ? to see the " + "current game state. The greyed out cards are those you " + "can not play at the moment. Tap an option to execute " + "the selected action.\n" + "Players can join the game at any time. To leave a game, " + "use /leave. If a player takes more than 90 seconds to play, " + "you can use /skip to skip that player.\n\n" + "Other commands (only game creator):\n" + "/close - Close lobby\n" + "/open - Open lobby\n\n" + "Experimental: Play in multiple groups at the same time. " + "Press the Current game: ... button and select the " + "group you want to play a card in.\n" + "If you enjoy this bot, " + "" + "rate me, join the " + "update channel" + " and buy an UNO card game.") + +source_text = ("This bot is Free Software and licensed under the AGPL. " + "The code is available here: \n" + "https://github.com/jh0ker/mau_mau_bot") + + +@user_locale +def help(bot, update): + """Handler for the /help command""" + send_async(bot, update.message.chat_id, text=_(help_text), + parse_mode=ParseMode.HTML, disable_web_page_preview=True) + + +@user_locale +def source(bot, update): + """Handler for the /help command""" + send_async(bot, update.message.chat_id, text=_(source_text), + parse_mode=ParseMode.HTML, disable_web_page_preview=True) + + +@user_locale +def news(bot, update): + """Handler for the /news command""" + send_async(bot, update.message.chat_id, + text=_("All news here: https://telegram.me/unobotupdates"), + disable_web_page_preview=True) + + +dispatcher.add_handler(CommandHandler('help', help)) +dispatcher.add_handler(CommandHandler('source', source)) +dispatcher.add_handler(CommandHandler('news', news)) diff --git a/user_setting.py b/user_setting.py index f44bd82..5eb1a58 100644 --- a/user_setting.py +++ b/user_setting.py @@ -18,14 +18,15 @@ # along with this program. If not, see . -from database import db, Optional, Required, PrimaryKey +from database import db, Optional, Required, PrimaryKey, db_session class UserSetting(db.Entity): - id = PrimaryKey(int, auto=False) # Telegram User ID + id = PrimaryKey(int, auto=False, size=64) # Telegram User ID lang = Optional(str, default='en') # The language setting for this user stats = Optional(bool, default=False) # Opt-in to keep game statistics first_places = Optional(int, default=0) # Nr. of games won in first place games_played = Optional(int, default=0) # Nr. of games completed - cards_played = Optional(int, default=0) # Nr. of cards played + cards_played = Optional(int, default=0) # Nr. of cards played total + use_keyboards = Optional(bool, default=False) # Use keyboards (unused) diff --git a/utils.py b/utils.py index 66fa333..1d0676f 100644 --- a/utils.py +++ b/utils.py @@ -19,18 +19,24 @@ import logging +from functools import wraps from flufl.i18n import registry from flufl.i18n import PackageStrategy from telegram import Emoji +from telegram.ext.dispatcher import run_async import locales +from database import db_session +from user_setting import UserSetting strategy = PackageStrategy('unobot', locales) application = registry.register(strategy) _ = application._ logger = logging.getLogger(__name__) +TIMEOUT = 2.5 + def __(string): """Translates text into all locales on the stack""" @@ -85,3 +91,65 @@ def display_color(color): return Emoji.GREEN_HEART + " Green" if color == "y": return Emoji.YELLOW_HEART + " Yellow" + + +def error(bot, update, error): + """Simple error handler""" + logger.exception(error) + + +@run_async +def send_async(bot, *args, **kwargs): + """Send a message asynchronously""" + if 'timeout' not in kwargs: + kwargs['timeout'] = TIMEOUT + + try: + bot.sendMessage(*args, **kwargs) + except Exception as e: + error(None, None, e) + + +@run_async +def answer_async(bot, *args, **kwargs): + """Answer an inline query asynchronously""" + if 'timeout' not in kwargs: + kwargs['timeout'] = TIMEOUT + + try: + bot.answerInlineQuery(*args, **kwargs) + except Exception as e: + error(None, None, e) + + +def user_locale(func): + @wraps(func) + @db_session + def wrapped(bot, update, *pargs, **kwargs): + with db_session: + us = UserSetting.get(id=update.message.from_user.id) + if us: + _.push(us.lang) + else: + _.push('en_US') + result = func(bot, update, *pargs, **kwargs) + _.pop() + return result + return wrapped + + +def game_locales(func): + @wraps(func) + @db_session + def wrapped(*pargs, **kwargs): + num_locales = 0 + for loc in ('en_US', 'de_DE'): # TODO: Get user locales from Database + _.push(loc) + num_locales += 1 + + result = func(*pargs, **kwargs) + + for i in range(num_locales): + _.pop() + return result + return wrapped \ No newline at end of file