🔀 Merge pull request #107 from tehcneko/master

Update python-telegram-bot to 13
This commit is contained in:
Jannes Höke 2022-11-14 14:51:33 +01:00 committed by GitHub
commit cb4d9bd5e3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 146 additions and 158 deletions

View file

@ -6,6 +6,8 @@ import card as c
from datetime import datetime from datetime import datetime
from telegram import Message, Chat from telegram import Message, Chat
from telegram.ext import CallbackContext
from apscheduler.jobstores.base import JobLookupError
from config import TIME_REMOVAL_AFTER_SKIP, MIN_FAST_TURN_TIME from config import TIME_REMOVAL_AFTER_SKIP, MIN_FAST_TURN_TIME
from errors import DeckEmptyError, NotEnoughPlayersError from errors import DeckEmptyError, NotEnoughPlayersError
@ -191,7 +193,10 @@ def start_player_countdown(bot, game, job_queue):
if game.mode == 'fast': if game.mode == 'fast':
if game.job: if game.job:
try:
game.job.schedule_removal() game.job.schedule_removal()
except JobLookupError:
pass
job = job_queue.run_once( job = job_queue.run_once(
#lambda x,y: do_skip(bot, player), #lambda x,y: do_skip(bot, player),
@ -205,9 +210,9 @@ def start_player_countdown(bot, game, job_queue):
player.game.job = job player.game.job = job
def skip_job(bot, job): def skip_job(context: CallbackContext):
player = job.context.player player = context.job.context.player
game = player.game game = player.game
if game_is_running(game): if game_is_running(game):
job_queue = job.context.job_queue job_queue = context.job.context.job_queue
do_skip(bot, player, job_queue) do_skip(context.bot, player, job_queue)

183
bot.py
View file

@ -21,9 +21,9 @@ import logging
from datetime import datetime from datetime import datetime
from telegram import ParseMode, InlineKeyboardMarkup, \ from telegram import ParseMode, InlineKeyboardMarkup, \
InlineKeyboardButton InlineKeyboardButton, Update
from telegram.ext import InlineQueryHandler, ChosenInlineResultHandler, \ from telegram.ext import InlineQueryHandler, ChosenInlineResultHandler, \
CommandHandler, MessageHandler, Filters, CallbackQueryHandler CommandHandler, MessageHandler, Filters, CallbackQueryHandler, CallbackContext
from telegram.ext.dispatcher import run_async from telegram.ext.dispatcher import run_async
import card as c import card as c
@ -49,9 +49,10 @@ logging.basicConfig(
level=logging.INFO level=logging.INFO
) )
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
logging.getLogger('apscheduler').setLevel(logging.WARNING)
@user_locale @user_locale
def notify_me(bot, update): def notify_me(update: Update, context: CallbackContext):
"""Handler for /notify_me command, pm people for next game""" """Handler for /notify_me command, pm people for next game"""
chat_id = update.message.chat_id chat_id = update.message.chat_id
if update.message.chat.type == 'private': if update.message.chat.type == 'private':
@ -67,12 +68,12 @@ def notify_me(bot, update):
@user_locale @user_locale
def new_game(bot, update): def new_game(update: Update, context: CallbackContext):
"""Handler for the /new command""" """Handler for the /new command"""
chat_id = update.message.chat_id chat_id = update.message.chat_id
if update.message.chat.type == 'private': if update.message.chat.type == 'private':
help_handler(bot, update) help_handler(update, context)
else: else:
@ -89,88 +90,88 @@ def new_game(bot, update):
game.starter = update.message.from_user game.starter = update.message.from_user
game.owner.append(update.message.from_user.id) game.owner.append(update.message.from_user.id)
game.mode = DEFAULT_GAMEMODE game.mode = DEFAULT_GAMEMODE
send_async(bot, chat_id, send_async(context.bot, chat_id,
text=_("Created a new game! Join the game with /join " text=_("Created a new game! Join the game with /join "
"and start the game with /start")) "and start the game with /start"))
@user_locale @user_locale
def kill_game(bot, update): def kill_game(update: Update, context: CallbackContext):
"""Handler for the /kill command""" """Handler for the /kill command"""
chat = update.message.chat chat = update.message.chat
user = update.message.from_user user = update.message.from_user
games = gm.chatid_games.get(chat.id) games = gm.chatid_games.get(chat.id)
if update.message.chat.type == 'private': if update.message.chat.type == 'private':
help_handler(bot, update) help_handler(update, context)
return return
if not games: if not games:
send_async(bot, chat.id, send_async(context.bot, chat.id,
text=_("There is no running game in this chat.")) text=_("There is no running game in this chat."))
return return
game = games[-1] game = games[-1]
if user_is_creator_or_admin(user, game, bot, chat): if user_is_creator_or_admin(user, game, context.bot, chat):
try: try:
gm.end_game(chat, user) gm.end_game(chat, user)
send_async(bot, chat.id, text=__("Game ended!", multi=game.translate)) send_async(context.bot, chat.id, text=__("Game ended!", multi=game.translate))
except NoGameInChatError: except NoGameInChatError:
send_async(bot, chat.id, send_async(context.bot, chat.id,
text=_("The game is not started yet. " text=_("The game is not started yet. "
"Join the game with /join and start the game with /start"), "Join the game with /join and start the game with /start"),
reply_to_message_id=update.message.message_id) reply_to_message_id=update.message.message_id)
else: else:
send_async(bot, chat.id, send_async(context.bot, chat.id,
text=_("Only the game creator ({name}) and admin can do that.") text=_("Only the game creator ({name}) and admin can do that.")
.format(name=game.starter.first_name), .format(name=game.starter.first_name),
reply_to_message_id=update.message.message_id) reply_to_message_id=update.message.message_id)
@user_locale @user_locale
def join_game(bot, update): def join_game(update: Update, context: CallbackContext):
"""Handler for the /join command""" """Handler for the /join command"""
chat = update.message.chat chat = update.message.chat
if update.message.chat.type == 'private': if update.message.chat.type == 'private':
help_handler(bot, update) help_handler(update, context)
return return
try: try:
gm.join_game(update.message.from_user, chat) gm.join_game(update.message.from_user, chat)
except LobbyClosedError: except LobbyClosedError:
send_async(bot, chat.id, text=_("The lobby is closed")) send_async(context.bot, chat.id, text=_("The lobby is closed"))
except NoGameInChatError: except NoGameInChatError:
send_async(bot, chat.id, send_async(context.bot, chat.id,
text=_("No game is running at the moment. " text=_("No game is running at the moment. "
"Create a new game with /new"), "Create a new game with /new"),
reply_to_message_id=update.message.message_id) reply_to_message_id=update.message.message_id)
except AlreadyJoinedError: except AlreadyJoinedError:
send_async(bot, chat.id, send_async(context.bot, chat.id,
text=_("You already joined the game. Start the game " text=_("You already joined the game. Start the game "
"with /start"), "with /start"),
reply_to_message_id=update.message.message_id) reply_to_message_id=update.message.message_id)
except DeckEmptyError: except DeckEmptyError:
send_async(bot, chat.id, send_async(context.bot, chat.id,
text=_("There are not enough cards left in the deck for " text=_("There are not enough cards left in the deck for "
"new players to join."), "new players to join."),
reply_to_message_id=update.message.message_id) reply_to_message_id=update.message.message_id)
else: else:
send_async(bot, chat.id, send_async(context.bot, chat.id,
text=_("Joined the game"), text=_("Joined the game"),
reply_to_message_id=update.message.message_id) reply_to_message_id=update.message.message_id)
@user_locale @user_locale
def leave_game(bot, update): def leave_game(update: Update, context: CallbackContext):
"""Handler for the /leave command""" """Handler for the /leave command"""
chat = update.message.chat chat = update.message.chat
user = update.message.from_user user = update.message.from_user
@ -178,7 +179,7 @@ def leave_game(bot, update):
player = gm.player_for_user_in_chat(user, chat) player = gm.player_for_user_in_chat(user, chat)
if player is None: if player is None:
send_async(bot, chat.id, text=_("You are not playing in a game in " send_async(context.bot, chat.id, text=_("You are not playing in a game in "
"this group."), "this group."),
reply_to_message_id=update.message.message_id) reply_to_message_id=update.message.message_id)
return return
@ -190,23 +191,23 @@ def leave_game(bot, update):
gm.leave_game(user, chat) gm.leave_game(user, chat)
except NoGameInChatError: except NoGameInChatError:
send_async(bot, chat.id, text=_("You are not playing in a game in " send_async(context.bot, chat.id, text=_("You are not playing in a game in "
"this group."), "this group."),
reply_to_message_id=update.message.message_id) reply_to_message_id=update.message.message_id)
except NotEnoughPlayersError: except NotEnoughPlayersError:
gm.end_game(chat, user) gm.end_game(chat, user)
send_async(bot, chat.id, text=__("Game ended!", multi=game.translate)) send_async(context.bot, chat.id, text=__("Game ended!", multi=game.translate))
else: else:
if game.started: if game.started:
send_async(bot, chat.id, send_async(context.bot, chat.id,
text=__("Okay. Next Player: {name}", text=__("Okay. Next Player: {name}",
multi=game.translate).format( multi=game.translate).format(
name=display_name(game.current_player.user)), name=display_name(game.current_player.user)),
reply_to_message_id=update.message.message_id) reply_to_message_id=update.message.message_id)
else: else:
send_async(bot, chat.id, send_async(context.bot, chat.id,
text=__("{name} left the game before it started.", text=__("{name} left the game before it started.",
multi=game.translate).format( multi=game.translate).format(
name=display_name(user)), name=display_name(user)),
@ -214,11 +215,11 @@ def leave_game(bot, update):
@user_locale @user_locale
def kick_player(bot, update): def kick_player(update: Update, context: CallbackContext):
"""Handler for the /kick command""" """Handler for the /kick command"""
if update.message.chat.type == 'private': if update.message.chat.type == 'private':
help_handler(bot, update) help_handler(update, context)
return return
chat = update.message.chat chat = update.message.chat
@ -228,20 +229,20 @@ def kick_player(bot, update):
game = gm.chatid_games[chat.id][-1] game = gm.chatid_games[chat.id][-1]
except (KeyError, IndexError): except (KeyError, IndexError):
send_async(bot, chat.id, send_async(context.bot, chat.id,
text=_("No game is running at the moment. " text=_("No game is running at the moment. "
"Create a new game with /new"), "Create a new game with /new"),
reply_to_message_id=update.message.message_id) reply_to_message_id=update.message.message_id)
return return
if not game.started: if not game.started:
send_async(bot, chat.id, send_async(context.bot, chat.id,
text=_("The game is not started yet. " text=_("The game is not started yet. "
"Join the game with /join and start the game with /start"), "Join the game with /join and start the game with /start"),
reply_to_message_id=update.message.message_id) reply_to_message_id=update.message.message_id)
return return
if user_is_creator_or_admin(user, game, bot, chat): if user_is_creator_or_admin(user, game, context.bot, chat):
if update.message.reply_to_message: if update.message.reply_to_message:
kicked = update.message.reply_to_message.from_user kicked = update.message.reply_to_message.from_user
@ -250,40 +251,40 @@ def kick_player(bot, update):
gm.leave_game(kicked, chat) gm.leave_game(kicked, chat)
except NoGameInChatError: except NoGameInChatError:
send_async(bot, chat.id, text=_("Player {name} is not found in the current game.".format(name=display_name(kicked))), send_async(context.bot, chat.id, text=_("Player {name} is not found in the current game.".format(name=display_name(kicked))),
reply_to_message_id=update.message.message_id) reply_to_message_id=update.message.message_id)
return return
except NotEnoughPlayersError: except NotEnoughPlayersError:
gm.end_game(chat, user) gm.end_game(chat, user)
send_async(bot, chat.id, send_async(context.bot, chat.id,
text=_("{0} was kicked by {1}".format(display_name(kicked), display_name(user)))) text=_("{0} was kicked by {1}".format(display_name(kicked), display_name(user))))
send_async(bot, chat.id, text=__("Game ended!", multi=game.translate)) send_async(context.bot, chat.id, text=__("Game ended!", multi=game.translate))
return return
send_async(bot, chat.id, send_async(context.bot, chat.id,
text=_("{0} was kicked by {1}".format(display_name(kicked), display_name(user)))) text=_("{0} was kicked by {1}".format(display_name(kicked), display_name(user))))
else: else:
send_async(bot, chat.id, send_async(context.bot, chat.id,
text=_("Please reply to the person you want to kick and type /kick again."), text=_("Please reply to the person you want to kick and type /kick again."),
reply_to_message_id=update.message.message_id) reply_to_message_id=update.message.message_id)
return return
send_async(bot, chat.id, send_async(context.bot, chat.id,
text=__("Okay. Next Player: {name}", text=__("Okay. Next Player: {name}",
multi=game.translate).format( multi=game.translate).format(
name=display_name(game.current_player.user)), name=display_name(game.current_player.user)),
reply_to_message_id=update.message.message_id) reply_to_message_id=update.message.message_id)
else: else:
send_async(bot, chat.id, send_async(context.bot, chat.id,
text=_("Only the game creator ({name}) and admin can do that.") text=_("Only the game creator ({name}) and admin can do that.")
.format(name=game.starter.first_name), .format(name=game.starter.first_name),
reply_to_message_id=update.message.message_id) reply_to_message_id=update.message.message_id)
def select_game(bot, update): def select_game(update: Update, context: CallbackContext):
"""Handler for callback queries to select the current game""" """Handler for callback queries to select the current game"""
chat_id = int(update.callback_query.data) chat_id = int(update.callback_query.data)
@ -299,16 +300,15 @@ def select_game(bot, update):
text=_("Game not found.")) text=_("Game not found."))
return return
@run_async def selected():
def selected(bot):
back = [[InlineKeyboardButton(text=_("Back to last group"), back = [[InlineKeyboardButton(text=_("Back to last group"),
switch_inline_query='')]] switch_inline_query='')]]
bot.answerCallbackQuery(update.callback_query.id, context.bot.answerCallbackQuery(update.callback_query.id,
text=_("Please switch to the group you selected!"), text=_("Please switch to the group you selected!"),
show_alert=False, show_alert=False,
timeout=TIMEOUT) timeout=TIMEOUT)
bot.editMessageText(chat_id=update.callback_query.message.chat_id, context.bot.editMessageText(chat_id=update.callback_query.message.chat_id,
message_id=update.callback_query.message.message_id, message_id=update.callback_query.message.message_id,
text=_("Selected group: {group}\n" text=_("Selected group: {group}\n"
"<b>Make sure that you switch to the correct " "<b>Make sure that you switch to the correct "
@ -318,11 +318,11 @@ def select_game(bot, update):
parse_mode=ParseMode.HTML, parse_mode=ParseMode.HTML,
timeout=TIMEOUT) timeout=TIMEOUT)
selected(bot) dispatcher.run_async(selected)
@game_locales @game_locales
def status_update(bot, update): def status_update(update: Update, context: CallbackContext):
"""Remove player from game if user leaves the group""" """Remove player from game if user leaves the group"""
chat = update.message.chat chat = update.message.chat
@ -337,17 +337,17 @@ def status_update(bot, update):
pass pass
except NotEnoughPlayersError: except NotEnoughPlayersError:
gm.end_game(chat, user) gm.end_game(chat, user)
send_async(bot, chat.id, text=__("Game ended!", send_async(context.bot, chat.id, text=__("Game ended!",
multi=game.translate)) multi=game.translate))
else: else:
send_async(bot, chat.id, text=__("Removing {name} from the game", send_async(context.bot, chat.id, text=__("Removing {name} from the game",
multi=game.translate) multi=game.translate)
.format(name=display_name(user))) .format(name=display_name(user)))
@game_locales @game_locales
@user_locale @user_locale
def start_game(bot, update, args, job_queue): def start_game(update: Update, context: CallbackContext):
"""Handler for the /start command""" """Handler for the /start command"""
if update.message.chat.type != 'private': if update.message.chat.type != 'private':
@ -356,16 +356,16 @@ def start_game(bot, update, args, job_queue):
try: try:
game = gm.chatid_games[chat.id][-1] game = gm.chatid_games[chat.id][-1]
except (KeyError, IndexError): except (KeyError, IndexError):
send_async(bot, chat.id, send_async(context.bot, chat.id,
text=_("There is no game running in this chat. Create " text=_("There is no game running in this chat. Create "
"a new one with /new")) "a new one with /new"))
return return
if game.started: if game.started:
send_async(bot, chat.id, text=_("The game has already started")) send_async(context.bot, chat.id, text=_("The game has already started"))
elif len(game.players) < MIN_PLAYERS: elif len(game.players) < MIN_PLAYERS:
send_async(bot, chat.id, send_async(context.bot, chat.id,
text=__("At least {minplayers} players must /join the game " text=__("At least {minplayers} players must /join the game "
"before you can start it").format(minplayers=MIN_PLAYERS)) "before you can start it").format(minplayers=MIN_PLAYERS))
@ -383,23 +383,22 @@ def start_game(bot, update, args, job_queue):
multi=game.translate) multi=game.translate)
.format(name=display_name(game.current_player.user))) .format(name=display_name(game.current_player.user)))
@run_async
def send_first(): def send_first():
"""Send the first card and player""" """Send the first card and player"""
bot.sendSticker(chat.id, context.bot.sendSticker(chat.id,
sticker=c.STICKERS[str(game.last_card)], sticker=c.STICKERS[str(game.last_card)],
timeout=TIMEOUT) timeout=TIMEOUT)
bot.sendMessage(chat.id, context.bot.sendMessage(chat.id,
text=first_message, text=first_message,
reply_markup=InlineKeyboardMarkup(choice), reply_markup=InlineKeyboardMarkup(choice),
timeout=TIMEOUT) timeout=TIMEOUT)
send_first() dispatcher.run_async(send_first)
start_player_countdown(bot, game, job_queue) start_player_countdown(context.bot, game, context.job_queue)
elif len(args) and args[0] == 'select': elif len(context.args) and context.args[0] == 'select':
players = gm.userid_players[update.message.from_user.id] players = gm.userid_players[update.message.from_user.id]
groups = list() groups = list()
@ -414,23 +413,23 @@ def start_game(bot, update, args, job_queue):
callback_data=str(player.game.chat.id))] callback_data=str(player.game.chat.id))]
) )
send_async(bot, update.message.chat_id, send_async(context.bot, update.message.chat_id,
text=_('Please select the group you want to play in.'), text=_('Please select the group you want to play in.'),
reply_markup=InlineKeyboardMarkup(groups)) reply_markup=InlineKeyboardMarkup(groups))
else: else:
help_handler(bot, update) help_handler(update, context)
@user_locale @user_locale
def close_game(bot, update): def close_game(update: Update, context: CallbackContext):
"""Handler for the /close command""" """Handler for the /close command"""
chat = update.message.chat chat = update.message.chat
user = update.message.from_user user = update.message.from_user
games = gm.chatid_games.get(chat.id) games = gm.chatid_games.get(chat.id)
if not games: if not games:
send_async(bot, chat.id, send_async(context.bot, chat.id,
text=_("There is no running game in this chat.")) text=_("There is no running game in this chat."))
return return
@ -438,12 +437,12 @@ def close_game(bot, update):
if user.id in game.owner: if user.id in game.owner:
game.open = False game.open = False
send_async(bot, chat.id, text=_("Closed the lobby. " send_async(context.bot, chat.id, text=_("Closed the lobby. "
"No more players can join this game.")) "No more players can join this game."))
return return
else: else:
send_async(bot, chat.id, send_async(context.bot, chat.id,
text=_("Only the game creator ({name}) and admin can do that.") text=_("Only the game creator ({name}) and admin can do that.")
.format(name=game.starter.first_name), .format(name=game.starter.first_name),
reply_to_message_id=update.message.message_id) reply_to_message_id=update.message.message_id)
@ -451,14 +450,14 @@ def close_game(bot, update):
@user_locale @user_locale
def open_game(bot, update): def open_game(update: Update, context: CallbackContext):
"""Handler for the /open command""" """Handler for the /open command"""
chat = update.message.chat chat = update.message.chat
user = update.message.from_user user = update.message.from_user
games = gm.chatid_games.get(chat.id) games = gm.chatid_games.get(chat.id)
if not games: if not games:
send_async(bot, chat.id, send_async(context.bot, chat.id,
text=_("There is no running game in this chat.")) text=_("There is no running game in this chat."))
return return
@ -466,11 +465,11 @@ def open_game(bot, update):
if user.id in game.owner: if user.id in game.owner:
game.open = True game.open = True
send_async(bot, chat.id, text=_("Opened the lobby. " send_async(context.bot, chat.id, text=_("Opened the lobby. "
"New players may /join the game.")) "New players may /join the game."))
return return
else: else:
send_async(bot, chat.id, send_async(context.bot, chat.id,
text=_("Only the game creator ({name}) and admin can do that.") text=_("Only the game creator ({name}) and admin can do that.")
.format(name=game.starter.first_name), .format(name=game.starter.first_name),
reply_to_message_id=update.message.message_id) reply_to_message_id=update.message.message_id)
@ -478,14 +477,14 @@ def open_game(bot, update):
@user_locale @user_locale
def enable_translations(bot, update): def enable_translations(update: Update, context: CallbackContext):
"""Handler for the /enable_translations command""" """Handler for the /enable_translations command"""
chat = update.message.chat chat = update.message.chat
user = update.message.from_user user = update.message.from_user
games = gm.chatid_games.get(chat.id) games = gm.chatid_games.get(chat.id)
if not games: if not games:
send_async(bot, chat.id, send_async(context.bot, chat.id,
text=_("There is no running game in this chat.")) text=_("There is no running game in this chat."))
return return
@ -493,12 +492,12 @@ def enable_translations(bot, update):
if user.id in game.owner: if user.id in game.owner:
game.translate = True game.translate = True
send_async(bot, chat.id, text=_("Enabled multi-translations. " send_async(context.bot, chat.id, text=_("Enabled multi-translations. "
"Disable with /disable_translations")) "Disable with /disable_translations"))
return return
else: else:
send_async(bot, chat.id, send_async(context.bot, chat.id,
text=_("Only the game creator ({name}) and admin can do that.") text=_("Only the game creator ({name}) and admin can do that.")
.format(name=game.starter.first_name), .format(name=game.starter.first_name),
reply_to_message_id=update.message.message_id) reply_to_message_id=update.message.message_id)
@ -506,14 +505,14 @@ def enable_translations(bot, update):
@user_locale @user_locale
def disable_translations(bot, update): def disable_translations(update: Update, context: CallbackContext):
"""Handler for the /disable_translations command""" """Handler for the /disable_translations command"""
chat = update.message.chat chat = update.message.chat
user = update.message.from_user user = update.message.from_user
games = gm.chatid_games.get(chat.id) games = gm.chatid_games.get(chat.id)
if not games: if not games:
send_async(bot, chat.id, send_async(context.bot, chat.id,
text=_("There is no running game in this chat.")) text=_("There is no running game in this chat."))
return return
@ -521,13 +520,13 @@ def disable_translations(bot, update):
if user.id in game.owner: if user.id in game.owner:
game.translate = False game.translate = False
send_async(bot, chat.id, text=_("Disabled multi-translations. " send_async(context.bot, chat.id, text=_("Disabled multi-translations. "
"Enable them again with " "Enable them again with "
"/enable_translations")) "/enable_translations"))
return return
else: else:
send_async(bot, chat.id, send_async(context.bot, chat.id,
text=_("Only the game creator ({name}) and admin can do that.") text=_("Only the game creator ({name}) and admin can do that.")
.format(name=game.starter.first_name), .format(name=game.starter.first_name),
reply_to_message_id=update.message.message_id) reply_to_message_id=update.message.message_id)
@ -536,14 +535,14 @@ def disable_translations(bot, update):
@game_locales @game_locales
@user_locale @user_locale
def skip_player(bot, update): def skip_player(update: Update, context: CallbackContext):
"""Handler for the /skip command""" """Handler for the /skip command"""
chat = update.message.chat chat = update.message.chat
user = update.message.from_user user = update.message.from_user
player = gm.player_for_user_in_chat(user, chat) player = gm.player_for_user_in_chat(user, chat)
if not player: if not player:
send_async(bot, chat.id, send_async(context.bot, chat.id,
text=_("You are not playing in a game in this chat.")) text=_("You are not playing in a game in this chat."))
return return
@ -558,19 +557,19 @@ def skip_player(bot, update):
# You can skip yourself even if you have time left (you'll still draw) # You can skip yourself even if you have time left (you'll still draw)
if delta < skipped_player.waiting_time and player != skipped_player: if delta < skipped_player.waiting_time and player != skipped_player:
n = skipped_player.waiting_time - delta n = skipped_player.waiting_time - delta
send_async(bot, chat.id, send_async(context.bot, chat.id,
text=_("Please wait {time} second", text=_("Please wait {time} second",
"Please wait {time} seconds", "Please wait {time} seconds",
n) n)
.format(time=n), .format(time=n),
reply_to_message_id=update.message.message_id) reply_to_message_id=update.message.message_id)
else: else:
do_skip(bot, player) do_skip(context.bot, player)
@game_locales @game_locales
@user_locale @user_locale
def reply_to_query(bot, update): def reply_to_query(update: Update, context: CallbackContext):
""" """
Handler for inline queries. Handler for inline queries.
Builds the result list for inline queries and answers to the client. Builds the result list for inline queries and answers to the client.
@ -638,13 +637,13 @@ def reply_to_query(bot, update):
if players and game and len(players) > 1: if players and game and len(players) > 1:
switch = _('Current game: {game}').format(game=game.chat.title) switch = _('Current game: {game}').format(game=game.chat.title)
answer_async(bot, update.inline_query.id, results, cache_time=0, answer_async(context.bot, update.inline_query.id, results, cache_time=0,
switch_pm_text=switch, switch_pm_parameter='select') switch_pm_text=switch, switch_pm_parameter='select')
@game_locales @game_locales
@user_locale @user_locale
def process_result(bot, update, job_queue): def process_result(update: Update, context: CallbackContext):
""" """
Handler for chosen inline results. Handler for chosen inline results.
Checks the players actions and acts accordingly. Checks the players actions and acts accordingly.
@ -671,38 +670,38 @@ def process_result(bot, update, job_queue):
mode = result_id[5:] mode = result_id[5:]
game.set_mode(mode) game.set_mode(mode)
logger.info("Gamemode changed to {mode}".format(mode = mode)) logger.info("Gamemode changed to {mode}".format(mode = mode))
send_async(bot, chat.id, text=__("Gamemode changed to {mode}".format(mode = mode))) send_async(context.bot, chat.id, text=__("Gamemode changed to {mode}".format(mode = mode)))
return return
elif len(result_id) == 36: # UUID result elif len(result_id) == 36: # UUID result
return return
elif int(anti_cheat) != last_anti_cheat: elif int(anti_cheat) != last_anti_cheat:
send_async(bot, chat.id, send_async(context.bot, chat.id,
text=__("Cheat attempt by {name}", multi=game.translate) text=__("Cheat attempt by {name}", multi=game.translate)
.format(name=display_name(player.user))) .format(name=display_name(player.user)))
return return
elif result_id == 'call_bluff': elif result_id == 'call_bluff':
reset_waiting_time(bot, player) reset_waiting_time(context.bot, player)
do_call_bluff(bot, player) do_call_bluff(context.bot, player)
elif result_id == 'draw': elif result_id == 'draw':
reset_waiting_time(bot, player) reset_waiting_time(context.bot, player)
do_draw(bot, player) do_draw(context.bot, player)
elif result_id == 'pass': elif result_id == 'pass':
game.turn() game.turn()
elif result_id in c.COLORS: elif result_id in c.COLORS:
game.choose_color(result_id) game.choose_color(result_id)
else: else:
reset_waiting_time(bot, player) reset_waiting_time(context.bot, player)
do_play_card(bot, player, result_id) do_play_card(context.bot, player, result_id)
if game_is_running(game): if game_is_running(game):
nextplayer_message = ( nextplayer_message = (
__("Next player: {name}", multi=game.translate) __("Next player: {name}", multi=game.translate)
.format(name=display_name(game.current_player.user))) .format(name=display_name(game.current_player.user)))
choice = [[InlineKeyboardButton(text=_("Make your choice!"), switch_inline_query_current_chat='')]] choice = [[InlineKeyboardButton(text=_("Make your choice!"), switch_inline_query_current_chat='')]]
send_async(bot, chat.id, send_async(context.bot, chat.id,
text=nextplayer_message, text=nextplayer_message,
reply_markup=InlineKeyboardMarkup(choice)) reply_markup=InlineKeyboardMarkup(choice))
start_player_countdown(bot, game, job_queue) start_player_countdown(context.bot, game, context.job_queue)
def reset_waiting_time(bot, player): def reset_waiting_time(bot, player):

View file

@ -101,7 +101,7 @@ def __(singular, plural=None, n=1, multi=False):
def user_locale(func): def user_locale(func):
@wraps(func) @wraps(func)
@db_session @db_session
def wrapped(bot, update, *pargs, **kwargs): def wrapped(update, context, *pargs, **kwargs):
user = _user_chat_from_update(update)[0] user = _user_chat_from_update(update)[0]
with db_session: with db_session:
@ -112,7 +112,7 @@ def user_locale(func):
else: else:
_.push('en_US') _.push('en_US')
result = func(bot, update, *pargs, **kwargs) result = func(update, context, *pargs, **kwargs)
_.pop() _.pop()
return result return result
return wrapped return wrapped
@ -121,7 +121,7 @@ def user_locale(func):
def game_locales(func): def game_locales(func):
@wraps(func) @wraps(func)
@db_session @db_session
def wrapped(bot, update, *pargs, **kwargs): def wrapped(update, context, *pargs, **kwargs):
user, chat = _user_chat_from_update(update) user, chat = _user_chat_from_update(update)
player = gm.player_for_user_in_chat(user, chat) player = gm.player_for_user_in_chat(user, chat)
locales = list() locales = list()
@ -141,7 +141,7 @@ def game_locales(func):
_.push(loc) _.push(loc)
locales.append(loc) locales.append(loc)
result = func(bot, update, *pargs, **kwargs) result = func(update, context, *pargs, **kwargs)
while _.code: while _.code:
_.pop() _.pop()
@ -151,21 +151,10 @@ def game_locales(func):
def _user_chat_from_update(update): def _user_chat_from_update(update):
user = update.effective_user
chat = update.effective_chat
try: if chat is None and user is not None and user.id in gm.userid_current:
user = update.message.from_user chat = gm.userid_current.get(user.id).game.chat
chat = update.message.chat
except (NameError, AttributeError):
try:
user = update.inline_query.from_user
chat = gm.userid_current[user.id].game.chat
except KeyError:
chat = None
except (NameError, AttributeError):
try:
user = update.chosen_inline_result.from_user
chat = gm.userid_current[user.id].game.chat
except (NameError, AttributeError, KeyError):
chat = None
return user, chat return user, chat

View file

@ -1,2 +1,2 @@
python-telegram-bot==8.1.1 python-telegram-bot==13.11
pony pony

View file

@ -18,8 +18,8 @@
# 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 telegram import ReplyKeyboardMarkup from telegram import ReplyKeyboardMarkup, Update
from telegram.ext import CommandHandler, RegexHandler from telegram.ext import CommandHandler, Filters, MessageHandler, CallbackContext
from utils import send_async from utils import send_async
from user_setting import UserSetting from user_setting import UserSetting
@ -29,11 +29,11 @@ from internationalization import _, user_locale
@user_locale @user_locale
def show_settings(bot, update): def show_settings(update: Update, context: CallbackContext):
chat = update.message.chat chat = update.message.chat
if update.message.chat.type != 'private': if update.message.chat.type != 'private':
send_async(bot, chat.id, send_async(context.bot, chat.id,
text=_("Please edit your settings in a private chat with " text=_("Please edit your settings in a private chat with "
"the bot.")) "the bot."))
return return
@ -49,27 +49,27 @@ def show_settings(bot, update):
stats = '' + ' ' + _("Delete all statistics") stats = '' + ' ' + _("Delete all statistics")
kb = [[stats], ['🌍' + ' ' + _("Language")]] kb = [[stats], ['🌍' + ' ' + _("Language")]]
send_async(bot, chat.id, text='🔧' + ' ' + _("Settings"), send_async(context.bot, chat.id, text='🔧' + ' ' + _("Settings"),
reply_markup=ReplyKeyboardMarkup(keyboard=kb, reply_markup=ReplyKeyboardMarkup(keyboard=kb,
one_time_keyboard=True)) one_time_keyboard=True))
@user_locale @user_locale
def kb_select(bot, update, groups): def kb_select(update: Update, context: CallbackContext):
chat = update.message.chat chat = update.message.chat
user = update.message.from_user user = update.message.from_user
option = groups[0] option = context.match[1]
if option == '📊': if option == '📊':
us = UserSetting.get(id=user.id) us = UserSetting.get(id=user.id)
us.stats = True us.stats = True
send_async(bot, chat.id, text=_("Enabled statistics!")) send_async(context.bot, chat.id, text=_("Enabled statistics!"))
elif option == '🌍': elif option == '🌍':
kb = [[locale + ' - ' + descr] kb = [[locale + ' - ' + descr]
for locale, descr for locale, descr
in sorted(available_locales.items())] in sorted(available_locales.items())]
send_async(bot, chat.id, text=_("Select locale"), send_async(context.bot, chat.id, text=_("Select locale"),
reply_markup=ReplyKeyboardMarkup(keyboard=kb, reply_markup=ReplyKeyboardMarkup(keyboard=kb,
one_time_keyboard=True)) one_time_keyboard=True))
@ -79,28 +79,27 @@ def kb_select(bot, update, groups):
us.first_places = 0 us.first_places = 0
us.games_played = 0 us.games_played = 0
us.cards_played = 0 us.cards_played = 0
send_async(bot, chat.id, text=_("Deleted and disabled statistics!")) send_async(context.bot, chat.id, text=_("Deleted and disabled statistics!"))
@user_locale @user_locale
def locale_select(bot, update, groups): def locale_select(update: Update, context: CallbackContext):
chat = update.message.chat chat = update.message.chat
user = update.message.from_user user = update.message.from_user
option = groups[0] option = context.match[1]
if option in available_locales: if option in available_locales:
us = UserSetting.get(id=user.id) us = UserSetting.get(id=user.id)
us.lang = option us.lang = option
_.push(option) _.push(option)
send_async(bot, chat.id, text=_("Set locale!")) send_async(context.bot, chat.id, text=_("Set locale!"))
_.pop() _.pop()
def register(): def register():
dispatcher.add_handler(CommandHandler('settings', show_settings)) dispatcher.add_handler(CommandHandler('settings', show_settings))
dispatcher.add_handler(RegexHandler('^([' + '📊' + dispatcher.add_handler(MessageHandler(Filters.regex('^([' + '📊' +
'🌍' + '🌍' +
'' + ']) .+$', '' + ']) .+$'),
kb_select, pass_groups=True)) kb_select))
dispatcher.add_handler(RegexHandler(r'^(\w\w_\w\w) - .*', dispatcher.add_handler(MessageHandler(Filters.regex(r'^(\w\w_\w\w) - .*'),
locale_select, pass_groups=True)) locale_select))

View file

@ -30,5 +30,5 @@ db.bind('sqlite', os.getenv('UNO_DB', 'uno.sqlite3'), create_db=True)
db.generate_mapping(create_tables=True) db.generate_mapping(create_tables=True)
gm = GameManager() gm = GameManager()
updater = Updater(token=TOKEN, workers=WORKERS) updater = Updater(token=TOKEN, workers=WORKERS, use_context=True)
dispatcher = updater.dispatcher dispatcher = updater.dispatcher

View file

@ -17,8 +17,8 @@
# You should have received a copy of the GNU Affero General Public License # 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/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from telegram import ParseMode from telegram import ParseMode, Update
from telegram.ext import CommandHandler from telegram.ext import CommandHandler, CallbackContext
from user_setting import UserSetting from user_setting import UserSetting
from utils import send_async from utils import send_async
@ -26,7 +26,7 @@ from shared_vars import dispatcher
from internationalization import _, user_locale from internationalization import _, user_locale
@user_locale @user_locale
def help_handler(bot, update): def help_handler(update: Update, context: CallbackContext):
"""Handler for the /help command""" """Handler for the /help command"""
help_text = _("Follow these steps:\n\n" help_text = _("Follow these steps:\n\n"
"1. Add this bot to a group\n" "1. Add this bot to a group\n"
@ -64,11 +64,11 @@ def help_handler(bot, update):
"<a href=\"https://telegram.me/unobotupdates\">update channel</a>" "<a href=\"https://telegram.me/unobotupdates\">update channel</a>"
" and buy an UNO card game.") " and buy an UNO card game.")
send_async(bot, update.message.chat_id, text=help_text, send_async(context.bot, update.message.chat_id, text=help_text,
parse_mode=ParseMode.HTML, disable_web_page_preview=True) parse_mode=ParseMode.HTML, disable_web_page_preview=True)
@user_locale @user_locale
def modes(bot, update): def modes(update: Update, context: CallbackContext):
"""Handler for the /help command""" """Handler for the /help command"""
modes_explanation = _("This UNO bot has four game modes: Classic, Sanic, Wild and Text.\n\n" modes_explanation = _("This UNO bot has four game modes: Classic, Sanic, Wild and Text.\n\n"
" 🎻 The Classic mode uses the conventional UNO deck and there is no auto skip.\n" " 🎻 The Classic mode uses the conventional UNO deck and there is no auto skip.\n"
@ -77,11 +77,11 @@ def modes(bot, update):
" ✍️ The Text mode uses the conventional UNO deck but instead of stickers it uses the text.\n\n" " ✍️ The Text mode uses the conventional UNO deck but instead of stickers it uses the text.\n\n"
"To change the game mode, the GAME CREATOR has to type the bot nickname and a space, " "To change the game mode, the GAME CREATOR has to type the bot nickname and a space, "
"just like when playing a card, and all gamemode options should appear.") "just like when playing a card, and all gamemode options should appear.")
send_async(bot, update.message.chat_id, text=modes_explanation, send_async(context.bot, update.message.chat_id, text=modes_explanation,
parse_mode=ParseMode.HTML, disable_web_page_preview=True) parse_mode=ParseMode.HTML, disable_web_page_preview=True)
@user_locale @user_locale
def source(bot, update): def source(update: Update, context: CallbackContext):
"""Handler for the /help command""" """Handler for the /help command"""
source_text = _("This bot is Free Software and licensed under the AGPL. " source_text = _("This bot is Free Software and licensed under the AGPL. "
"The code is available here: \n" "The code is available here: \n"
@ -94,25 +94,25 @@ def source(bot, update):
"Originals available on http://game-icons.net\n" "Originals available on http://game-icons.net\n"
"Icons edited by ɳick") "Icons edited by ɳick")
send_async(bot, update.message.chat_id, text=source_text + '\n' + send_async(context.bot, update.message.chat_id, text=source_text + '\n' +
attributions, attributions,
parse_mode=ParseMode.HTML, disable_web_page_preview=True) parse_mode=ParseMode.HTML, disable_web_page_preview=True)
@user_locale @user_locale
def news(bot, update): def news(update: Update, context: CallbackContext):
"""Handler for the /news command""" """Handler for the /news command"""
send_async(bot, update.message.chat_id, send_async(context.bot, update.message.chat_id,
text=_("All news here: https://telegram.me/unobotupdates"), text=_("All news here: https://telegram.me/unobotupdates"),
disable_web_page_preview=True) disable_web_page_preview=True)
@user_locale @user_locale
def stats(bot, update): def stats(update: Update, context: CallbackContext):
user = update.message.from_user user = update.message.from_user
us = UserSetting.get(id=user.id) us = UserSetting.get(id=user.id)
if not us or not us.stats: if not us or not us.stats:
send_async(bot, update.message.chat_id, send_async(context.bot, update.message.chat_id,
text=_("You did not enable statistics. Use /settings in " text=_("You did not enable statistics. Use /settings in "
"a private chat with the bot to enable them.")) "a private chat with the bot to enable them."))
else: else:
@ -140,7 +140,7 @@ def stats(bot, update):
n).format(number=n) n).format(number=n)
) )
send_async(bot, update.message.chat_id, send_async(context.bot, update.message.chat_id,
text='\n'.join(stats_text)) text='\n'.join(stats_text))

View file

@ -20,11 +20,9 @@
import logging import logging
from telegram.ext.dispatcher import run_async
from internationalization import _, __ from internationalization import _, __
from mwt import MWT from mwt import MWT
from shared_vars import gm from shared_vars import gm, dispatcher
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -82,26 +80,24 @@ def error(bot, update, error):
logger.exception(error) logger.exception(error)
@run_async
def send_async(bot, *args, **kwargs): def send_async(bot, *args, **kwargs):
"""Send a message asynchronously""" """Send a message asynchronously"""
if 'timeout' not in kwargs: if 'timeout' not in kwargs:
kwargs['timeout'] = TIMEOUT kwargs['timeout'] = TIMEOUT
try: try:
bot.sendMessage(*args, **kwargs) dispatcher.run_async(bot.sendMessage, *args, **kwargs)
except Exception as e: except Exception as e:
error(None, None, e) error(None, None, e)
@run_async
def answer_async(bot, *args, **kwargs): def answer_async(bot, *args, **kwargs):
"""Answer an inline query asynchronously""" """Answer an inline query asynchronously"""
if 'timeout' not in kwargs: if 'timeout' not in kwargs:
kwargs['timeout'] = TIMEOUT kwargs['timeout'] = TIMEOUT
try: try:
bot.answerInlineQuery(*args, **kwargs) dispatcher.run_async(bot.answerInlineQuery, *args, **kwargs)
except Exception as e: except Exception as e:
error(None, None, e) error(None, None, e)