settings UI added, save locale to database

This commit is contained in:
Jannes Höke 2016-05-22 14:45:51 +02:00
parent cddf13dc5d
commit 6c610c1aeb
8 changed files with 388 additions and 147 deletions

164
bot.py
View file

@ -20,19 +20,14 @@
import logging import logging
from datetime import datetime from datetime import datetime
from functools import wraps
from random import randint from random import randint
from telegram import ParseMode, Message, Chat, InlineKeyboardMarkup, \ from telegram import ParseMode, Message, Chat, InlineKeyboardMarkup, \
InlineKeyboardButton InlineKeyboardButton
from telegram.ext import Updater, InlineQueryHandler, \ from telegram.ext import InlineQueryHandler, ChosenInlineResultHandler, \
ChosenInlineResultHandler, CommandHandler, MessageHandler, Filters, \ CommandHandler, MessageHandler, Filters, CallbackQueryHandler
CallbackQueryHandler
from telegram.ext.dispatcher import run_async 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 start_bot import start_bot
from results import (add_call_bluff, add_choose_color, add_draw, add_gameinfo, from results import (add_call_bluff, add_choose_color, add_draw, add_gameinfo,
add_no_game, add_not_started, add_other_cards, add_pass, add_no_game, add_not_started, add_other_cards, add_pass,
@ -41,111 +36,18 @@ from utils import display_name
import card as c import card as c
from errors import (NoGameInChatError, LobbyClosedError, AlreadyJoinedError, from errors import (NoGameInChatError, LobbyClosedError, AlreadyJoinedError,
NotEnoughPlayersError, DeckEmptyError) 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( logging.basicConfig(
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
level=logging.DEBUG) level=logging.DEBUG)
logger = logging.getLogger(__name__) 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 <code>@mau_mau_bot</code> into your chat box and hit "
"<b>space</b>, or click the <code>via @mau_mau_bot</code> text "
"next to messages. You will see your cards (some greyed out), "
"any extra options like drawing, and a <b>?</b> to see the "
"current game state. The <b>greyed out cards</b> are those you "
"<b>can not play</b> 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"
"<b>Experimental:</b> Play in multiple groups at the same time. "
"Press the <code>Current game: ...</code> button and select the "
"group you want to play a card in.\n"
"If you enjoy this bot, "
"<a href=\"https://telegram.me/storebot?start=mau_mau_bot\">"
"rate me</a>, join the "
"<a href=\"https://telegram.me/unobotupdates\">update channel</a>"
" 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 @user_locale
def new_game(bot, update): def new_game(bot, update):
@ -487,29 +389,6 @@ def skip_player(bot, update):
gm.end_game(chat.id, skipped_player.user) 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 @game_locales
@user_locale @user_locale
def reply_to_query(bot, update): 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 # Add all handlers to the dispatcher and run the bot
dp.add_handler(InlineQueryHandler(reply_to_query)) dispatcher.add_handler(InlineQueryHandler(reply_to_query))
dp.add_handler(ChosenInlineResultHandler(process_result)) dispatcher.add_handler(ChosenInlineResultHandler(process_result))
dp.add_handler(CallbackQueryHandler(select_game)) dispatcher.add_handler(CallbackQueryHandler(select_game))
dp.add_handler(CommandHandler('start', start_game, pass_args=True)) dispatcher.add_handler(CommandHandler('start', start_game, pass_args=True))
dp.add_handler(CommandHandler('new', new_game)) dispatcher.add_handler(CommandHandler('new', new_game))
dp.add_handler(CommandHandler('join', join_game)) dispatcher.add_handler(CommandHandler('join', join_game))
dp.add_handler(CommandHandler('leave', leave_game)) dispatcher.add_handler(CommandHandler('leave', leave_game))
dp.add_handler(CommandHandler('open', open_game)) dispatcher.add_handler(CommandHandler('open', open_game))
dp.add_handler(CommandHandler('close', close_game)) dispatcher.add_handler(CommandHandler('close', close_game))
dp.add_handler(CommandHandler('skip', skip_player)) dispatcher.add_handler(CommandHandler('skip', skip_player))
dp.add_handler(CommandHandler('help', help)) dispatcher.add_handler(MessageHandler([Filters.status_update], status_update))
dp.add_handler(CommandHandler('source', source)) dispatcher.add_error_handler(error)
dp.add_handler(CommandHandler('news', news))
dp.add_handler(MessageHandler([Filters.status_update], status_update))
dp.add_error_handler(error)
start_bot(u) start_bot(updater)
u.idle() updater.idle()

View file

@ -350,3 +350,39 @@ msgstr "Passe"
#: results.py:148 #: results.py:148
msgid "I'm calling your bluff!" msgid "I'm calling your bluff!"
msgstr "Ich glaube du bluffst!" 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!"

View file

@ -294,3 +294,39 @@ msgstr ""
msgid "I'm calling your bluff!" msgid "I'm calling your bluff!"
msgstr "" 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 ""

104
settings.py Normal file
View file

@ -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 <uno@jhoeke.de>
#
# 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 <http://www.gnu.org/licenses/>.
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))

37
shared_vars.py Normal file
View file

@ -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 <uno@jhoeke.de>
#
# 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 <http://www.gnu.org/licenses/>.
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)

83
simple_commands.py Normal file
View file

@ -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 <uno@jhoeke.de>
#
# 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 <http://www.gnu.org/licenses/>.
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 <code>@mau_mau_bot</code> into your chat box and hit "
"<b>space</b>, or click the <code>via @mau_mau_bot</code> text "
"next to messages. You will see your cards (some greyed out), "
"any extra options like drawing, and a <b>?</b> to see the "
"current game state. The <b>greyed out cards</b> are those you "
"<b>can not play</b> 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"
"<b>Experimental:</b> Play in multiple groups at the same time. "
"Press the <code>Current game: ...</code> button and select the "
"group you want to play a card in.\n"
"If you enjoy this bot, "
"<a href=\"https://telegram.me/storebot?start=mau_mau_bot\">"
"rate me</a>, join the "
"<a href=\"https://telegram.me/unobotupdates\">update channel</a>"
" 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))

View file

@ -18,14 +18,15 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from database import db, Optional, Required, PrimaryKey from database import db, Optional, Required, PrimaryKey, db_session
class UserSetting(db.Entity): 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 lang = Optional(str, default='en') # The language setting for this user
stats = Optional(bool, default=False) # Opt-in to keep game statistics stats = Optional(bool, default=False) # Opt-in to keep game statistics
first_places = Optional(int, default=0) # Nr. of games won in first place first_places = Optional(int, default=0) # Nr. of games won in first place
games_played = Optional(int, default=0) # Nr. of games completed 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)

View file

@ -19,18 +19,24 @@
import logging import logging
from functools import wraps
from flufl.i18n import registry from flufl.i18n import registry
from flufl.i18n import PackageStrategy from flufl.i18n import PackageStrategy
from telegram import Emoji from telegram import Emoji
from telegram.ext.dispatcher import run_async
import locales import locales
from database import db_session
from user_setting import UserSetting
strategy = PackageStrategy('unobot', locales) strategy = PackageStrategy('unobot', locales)
application = registry.register(strategy) application = registry.register(strategy)
_ = application._ _ = application._
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
TIMEOUT = 2.5
def __(string): def __(string):
"""Translates text into all locales on the stack""" """Translates text into all locales on the stack"""
@ -85,3 +91,65 @@ def display_color(color):
return Emoji.GREEN_HEART + " Green" return Emoji.GREEN_HEART + " Green"
if color == "y": if color == "y":
return Emoji.YELLOW_HEART + " Yellow" 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