From d17e3086faa59c2ace34d46b215989e2274b760d Mon Sep 17 00:00:00 2001 From: Jerry Date: Wed, 2 Jan 2019 14:59:04 +0800 Subject: [PATCH] limit the rate of inline keyboard update --- tgmsbot.py | 72 ++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 51 insertions(+), 21 deletions(-) diff --git a/tgmsbot.py b/tgmsbot.py index 5b5fbd1..0ce31b8 100644 --- a/tgmsbot.py +++ b/tgmsbot.py @@ -14,6 +14,11 @@ logger = logging.getLogger(__name__) token = "token_here" updater = Updater(token, workers=16) +job_queue = updater.job_queue +job_queue.start() + +KBD_MIN_INTERVAL = 0.6 +KBD_DELAY_SECS = 0.6 HEIGHT = 8 WIDTH = 8 @@ -57,31 +62,36 @@ class Game(): self.board = board self.group = group self.creator = creator - self.actions = dict() - self.last_player = None + self.__actions = dict() + self.__last_player = None self.start_time = time.time() self.stopped = False - self.extra = {"timeout": 0} + # timestamp of the last update keyboard action, + # it is used to calculate time gap between + # two actions and identify unique actions. + self.last_action = 0 + # number of timeout error catched + self.timeouts = 0 def save_action(self, user, spot): '''spot is supposed to be a tuple''' - self.last_player = user - if self.actions.get(user, None): - self.actions[user].append(spot) + self.__last_player = user + if self.__actions.get(user, None): + self.__actions[user].append(spot) else: - self.actions[user] = [spot,] + self.__actions[user] = [spot,] def actions_sum(self): mysum = 0 - for user in self.actions: - count = len(self.actions.get(user, list())) + for user in self.__actions: + count = len(self.__actions.get(user, list())) mysum += count return mysum def get_last_player(self): - return display_username(self.last_player) + return display_username(self.__last_player) def get_actions(self): '''Convert actions into text''' msg = "" - for user in self.actions: - count = len(self.actions.get(user, list())) + for user in self.__actions: + count = len(self.__actions.get(user, list())) msg = "{}{} - {}项操作\n".format(msg, display_username(user), count) return msg @@ -153,9 +163,9 @@ def send_keyboard(bot, update, args): parse_mode="Markdown", reply_markup=InlineKeyboardMarkup(keyboard)) def send_help(bot, update): + logger.debug("Start from {0}".format(update.message.from_user.id)) msg = update.message msg.reply_text("这是一个扫雷bot\n\n/mine 开始新游戏") - logger.debug("Start from {0}".format(update.message.from_user.id)) def send_source(bot, update): logger.debug("Source from {0}".format(update.message.from_user.id)) @@ -166,7 +176,24 @@ def send_status(bot, update): count = game_manager.count() update.message.reply_text('当前进行的游戏: {}'.format(count)) -def update_keyboard(bot, bhash, game, chat_id, message_id): +def update_keyboard_request(bot, bhash, game, chat_id, message_id): + current_action_timestamp = time.time() + if current_action_timestamp - game.last_action <= KBD_MIN_INTERVAL: + logger.debug('Rate limit triggered.') + game.last_action = current_action_timestamp + job_queue.run_once(update_keyboard, KBD_DELAY_SECS, + context=(bhash, game, chat_id, message_id, current_action_timestamp)) + else: + game.last_action = current_action_timestamp + update_keyboard(bot, None, noqueue=(bhash, game, chat_id, message_id)) +def update_keyboard(bot, job, noqueue=None): + if noqueue: + (bhash, game, chat_id, message_id) = noqueue + else: + (bhash, game, chat_id, message_id, current_action_timestamp) = job.context + if current_action_timestamp != game.last_action: + logger.debug('New update action requested, abort this one.') + return def gen_keyboard(board): keyboard = list() for row in range(board.height): @@ -192,7 +219,7 @@ def update_keyboard(bot, bhash, game, chat_id, message_id): reply_markup=InlineKeyboardMarkup(keyboard)) except TimedOutError: logger.debug('time out in game {}.'.format(bhash)) - game.extra["timeout"] += 1 + game.timeouts += 1 def handle_button_click(bot, update): msg = update.callback_query.message @@ -205,7 +232,7 @@ def handle_button_click(bot, update): data = data.split(' ') data = [int(i) for i in data] (bhash, row, col) = data - except Exception as err: + except: logger.info('Unknown callback data: {} from user {}'.format(data, user.id)) return game = game_manager.get_game_from_hash(bhash) @@ -219,7 +246,7 @@ def handle_button_click(bot, update): mmap = None board.move((row, col)) game.save_action(user, (row, col)) - update_keyboard(bot, bhash, game, chat_id, msg.message_id) + update_keyboard_request(bot, bhash, game, chat_id, msg.message_id) else: mmap = deepcopy(board.map) board.move((row, col)) @@ -228,13 +255,13 @@ def handle_button_click(bot, update): # if this is the first move, there's no mmap if mmap is not None: game.save_action(user, (row, col)) - update_keyboard(bot, bhash, game, chat_id, msg.message_id) + update_keyboard_request(bot, bhash, game, chat_id, msg.message_id) (s_op, s_is, s_3bv) = board.gen_statistics() ops_count = game.actions_sum() ops_list = game.get_actions() last_player = game.get_last_player() time_used = time.time() - game.start_time - timeouts = game.extra["timeout"] + timeouts = game.timeouts if board.state == 2: template = WIN_TEXT_TEMPLATE else: @@ -242,11 +269,14 @@ def handle_button_click(bot, update): myreply = template.format(s_op=s_op, s_is=s_is, s_3bv=s_3bv, ops_count=ops_count, ops_list=ops_list, last_player=last_player, time=round(time_used, 4), timeouts=timeouts) - msg.reply_text(myreply, parse_mode="Markdown") + try: + msg.reply_text(myreply, parse_mode="Markdown") + except TimedOutError: + logger.debug('timeout sending report for game {}'.format(bhash)) game_manager.remove(bhash) elif mmap is not None and (not array_equal(board.map, mmap)): game.save_action(user, (row, col)) - update_keyboard(bot, bhash, game, chat_id, msg.message_id) + update_keyboard_request(bot, bhash, game, chat_id, msg.message_id)