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