def smwae_check(chat_id, msg_text, user: User, parse_mode='html', reply_markup=None): try: _special_bot.send_message(chat_id or user.user_id, msg_text, reply_markup=reply_markup, parse_mode=parse_mode) return True except ApiException as e: res = loads(e.result.text) if res['description'].find('was blocked') != -1: competitor: Competitor = user.check_association() if not competitor: # Some strange shit just happened here send_msg_to_admin( get_translation_for( 'admin_notification_tg_user_blocked_and_competitor_vanished' ).format(user.str_repr())) return False competitor.change_status(COMPETITOR_STATUS.INACTIVE, bot=_special_bot, do_not_notify_admin=True) competitor.save() send_msg_to_admin( get_translation_for( 'admin_notification_tg_user_blocked').format( user.str_repr(), competitor.name)) return False else: raise e
def process_message(self, message: Message, user: User, bot: TeleBot, competitor: Competitor): score = to_int(message.text, None) if score is None: return super().process_message(message, user, bot) res: Result = user.check_result() if res.result is not None: return RET.OK, None, None, None # TODO: checks if len(res.scores) > 10: bot.send_message( user.user_id, get_translation_for('results_maximum_scores_entered_msg'), reply_markup=self.__base_keyboard()) return RET.OK, None, None, None if score < 0 or score > 15: bot.send_message( user.user_id, get_translation_for('results_incorrect_score_msg'), reply_markup=self.__base_keyboard()) return RET.OK, None, None, None res.scores.append(score) res.save() return self.process_result(message, user, bot, competitor)
def update_and_paginate(self, message: Message, user: User, bot: TeleBot, competitor: Competitor, page=1, update=False, updateText=False): if guard.get_allowed()[0] and guard.update_allowed()[0]: UsersSheet.update_model() possible_levels = ChallengeSendState.get_possible_levels( competitor.level) available_competitors = Competitor.objects( status__in=(COMPETITOR_STATUS.ACTIVE, COMPETITOR_STATUS.PASSIVE), level__in=possible_levels, id__ne=competitor.latest_challenge_sent_to.id if competitor.latest_challenge_sent_to is not None else ObjectId(), associated_user_vanished__ne=True).paginate(page=1, per_page=10) if not render_pagination(available_competitors, message, bot, get_translation_for('challenge_send_msg'), self.__base_keyboard, update=update, updateText=updateText): bot.send_message( message.chat.id, get_translation_for('challenge_no_applicable_competitors_msg')) return False return True
def get_challenge_confirmation_keyboard(**kwargs): keyboard = types.ReplyKeyboardMarkup(resize_keyboard=True) keyboard.row( get_translation_for('challenge_received_accept_btn'), get_translation_for('challenge_received_dismiss_btn') ) keyboard.row(get_translation_for('back_btn')) return keyboard
def inactive_btn(self, message: Message, user: User, bot: TeleBot): bot.send_message( message.chat.id, get_translation_for('menu_moved_to_inactive_pseudo1_btn') + '\n' + get_translation_for('menu_moved_to_inactive_pseudo2_btn'), parse_mode='html' ) return RET.OK, None, None, None
def get_result_confirmation_keyboard(**kwargs): keyboard = types.ReplyKeyboardMarkup(resize_keyboard=True) keyboard.row( get_translation_for('result_confirmation_confirm_btn'), get_translation_for('result_confirmation_dismiss_btn') ) keyboard.row(get_translation_for('back_btn')) return keyboard
def __confirmation_keyboard(self, id): keyboard = InlineKeyboardMarkup() keyboard.row( InlineKeyboardButton( get_translation_for('challenge_send_confirm_yes_btn'), callback_data=f'challenge_to_confirm_send={id}'), InlineKeyboardButton( get_translation_for('challenge_send_confirm_no_btn'), callback_data=f'challenge_to_cancel_send')) return keyboard
def confirm_result(self, message: Message, user: User, bot: TeleBot, competitor: Competitor): res = user.check_result() if not len(res.scores) or len(res.scores) % 2: bot.send_message( message.chat.id, get_translation_for('result_scores_count_must_be_odd_msg')) return RET.OK, None, None, None if res.result is None: bot.send_message( message.chat.id, get_translation_for('result_winner_must_be_specified_msg')) return RET.OK, None, None, None opponent, opponent_user = get_opponent_and_opponent_user(competitor) if not opponent or not opponent_user: res.delete() return teardown_challenge( competitor, message, user, bot, 'challenge_confirm_cannot_find_opponent_msg' if not opponent else 'challenge_confirm_cannot_fin_opponents_user_msg') opponent.previous_challenge_status = opponent.status opponent.change_status( COMPETITOR_STATUS.CHALLENGE_NEED_RESULTS_CONFIRMATION) opponent.save() ores = opponent_user.check_result() if ores: ores.delete() opponent_user.current_result = res opponent_user.states.append('ChallengeConfirmResultsState') if len(opponent_user.states) > STATES_HISTORY_LEN: del opponent_user.states[0] opponent_user.save() res.sent = True res.save() if not smwae_check( opponent_user.user_id, get_translation_for('result_confirmation_msg') + '\n' + render_result(res, final=False), opponent_user, reply_markup=get_result_confirmation_keyboard(), ): teardown_challenge( competitor, message, user, bot, 'error_bot_blocked_by_opponent_challenge_canceled_msg') return RET.GO_TO_STATE, 'MenuState', message, user bot.send_message(message.chat.id, get_translation_for('results_sent_to_opponent_msg')) return RET.GO_TO_STATE, 'MenuState', message, user
def confirm_cancellation_opponent(self, message: Message, user: User, bot: TeleBot, competitor: Competitor): if competitor.status != COMPETITOR_STATUS.CHALLENGE_NEED_CANCELLATION_CONFIRMATION: return RET.OK, None, None, None if not user.dismiss_confirmed: bot.send_message( message.chat.id, get_translation_for('challenge_cancel_msg'), reply_markup=self.__base_keyboard(status=competitor.status) ) user.dismiss_confirmed = True user.save() return RET.OK, None, None, None else: user.dismiss_confirmed = False user.save() opponent, opponent_user = get_opponent_and_opponent_user(competitor) if not opponent or not opponent_user: return teardown_challenge( competitor, message, user, bot, 'challenge_confirm_cannot_find_opponent_msg' if not opponent else 'challenge_confirm_cannot_fin_opponents_user_msg' ) opponent.change_status(opponent.previous_status) opponent.previous_status = None opponent.previous_challenge_status = None opponent.in_challenge_with = None opponent.challenge_started_at = None opponent.save() competitor.change_status(competitor.previous_status) competitor.previous_status = None competitor.previous_challenge_status = None competitor.in_challenge_with = None competitor.challenge_started_at = None competitor.save() smwae_check( opponent_user.user_id, get_translation_for('challenge_canceled_msg').format(competitor.name), opponent_user, reply_markup=self.__base_keyboard(status=opponent.status) ) bot.send_message( message.chat.id, get_translation_for('challenge_canceled_msg').format(opponent.name), reply_markup=self.__base_keyboard(status=competitor.status) ) LogsSheet.glog(get_translation_for('gsheet_log_game_canceled').format(opponent.name, competitor.name)) return RET.OK, None, None, None
def end_sick_leave(self, message: Message, user: User, bot: TeleBot, competitor: Competitor): if competitor.status != COMPETITOR_STATUS.INJUIRY: return RET.OK, None, None, None competitor.change_status(competitor.previous_status) competitor.save() bot.send_message( message.chat.id, get_translation_for('menu_on_sick_leave_end_msg'), reply_markup=self.__base_keyboard(status=competitor.status) ) LogsSheet.glog(get_translation_for('gsheet_log_on_injuiry_ended').format(competitor.name)) return RET.OK, None, None, None
def dismiss_results(self, message: Message, user: User, bot: TeleBot, competitor: Competitor): res = user.check_result() if not res: pass # TODO opponent, opponent_user = get_opponent_and_opponent_user(competitor) if not opponent or not opponent_user: res.delete() return teardown_challenge( competitor, message, user, bot, 'challenge_confirm_cannot_find_opponent_msg' if not opponent else 'challenge_confirm_cannot_fin_opponents_user_msg') if opponent.previous_challenge_status: opponent.change_status(opponent.previous_challenge_status) opponent.previous_challenge_status = None opponent.save() competitor.change_status(competitor.previous_challenge_status) competitor.previous_challenge_status = None competitor.save() if not smwae_check( opponent_user.user_id, get_translation_for( 'result_confirmation_dismissed_opponent_msg'), opponent_user, reply_markup=get_menu_keyboard(status=opponent.status)): teardown_challenge( competitor, message, user, bot, 'error_bot_blocked_by_opponent_challenge_canceled_msg') res.delete() opponent_user.reload() opponent_user.current_result = None opponent_user.save() user.current_result = None user.save() return RET.GO_TO_STATE, 'MenuState', message, user bot.send_message( message.chat.id, get_translation_for('result_confirmation_dismissed_msg')) res.delete() opponent_user.reload() opponent_user.current_result = None opponent_user.save() user.current_result = None user.save() return RET.GO_TO_STATE, 'MenuState', message, user
def process_message(self, message: Message, user: User, bot: TeleBot, competitor: Competitor = None): if not competitor: competitor = user.check_association() if competitor.status != COMPETITOR_STATUS.CHALLENGE_NEED_RESPONSE: bot.send_message( message.chat.id, get_translation_for('challenge_received_not_found') ) return RET.GO_TO_STATE, 'MenuState', message, user else: bot.send_message(message.chat.id, get_translation_for('use_keyboard_msg'), reply_markup=self.__base_keyboard() ) return RET.OK, None, None, None
def entry(self, message: Message, user: User, bot: TeleBot, competitor: Competitor = None): if competitor is None: competitor = user.check_association() res = user.check_result() if not res: pass # TODO opponent, opponent_user = get_opponent_and_opponent_user(competitor) if not opponent or not opponent_user: res.delete() return teardown_challenge( competitor, message, user, bot, 'challenge_confirm_cannot_find_opponent_msg' if not opponent else 'challenge_confirm_cannot_fin_opponents_user_msg') bot.send_message(message.chat.id, get_translation_for('result_confirmation_msg') + '\n' + render_result(res, final=False), reply_markup=get_result_confirmation_keyboard(), parse_mode='html') return RET.OK, None, None, None
def __base_keyboard(self, **kwargs): keyboard = InlineKeyboardMarkup(row_width=1) for name, id_ in kwargs.get('names', []): keyboard.add( InlineKeyboardButton(name, callback_data=f'challenge_to_user={id_}')) if kwargs.get('has_pages', None) and kwargs.get('cur_page', None): end_row = [ InlineKeyboardButton( '<', callback_data= f'challenge_to_paginate={kwargs["cur_page"] - 1}' if kwargs.get('has_prev', None) else 'challenge_to_none'), InlineKeyboardButton( f'{kwargs["cur_page"]}{(" / " + str(kwargs["pages"]) if kwargs.get("pages", None) else "")}', callback_data='challenge_to_none'), InlineKeyboardButton( '>', callback_data= f'challenge_to_paginate={kwargs["cur_page"] + 1}' if kwargs.get('has_next', None) else 'challenge_to_none') ] keyboard.row(*end_row) keyboard.row( InlineKeyboardButton(get_translation_for('back_btn'), callback_data='challenge_to_back'), InlineKeyboardButton('🔄', callback_data='challenge_to_refresh')) return keyboard
def entry(self, message: Message, user: User, bot: TeleBot): if user.check_association(): return RET.GO_TO_STATE, 'MenuState', message, user if guard.get_allowed()[0] and guard.update_allowed()[0]: UsersSheet.update_model() available_competitors = Competitor.objects( status=COMPETITOR_STATUS.UNAUTHORIZED).paginate(page=1, per_page=10) if not render_pagination(available_competitors, message, bot, get_translation_for('authentication_msg'), self.__base_keyboard): bot.send_message( message.chat.id, get_translation_for( 'authentication_cannot_find_competitors_msg')) return RET.OK, None, None, None
def upload_canceled_result(winner: Competitor, looser: Competitor, level_change=None, was_dismissed=False, was_ignored=False, at_row=None): try: if not was_dismissed and not was_ignored: logger.error( 'upload_canceled_result called without both was_dismissed and was_ignored - one must be set') return if was_dismissed and was_ignored: logger.error( 'upload_canceled_result called with both was_dismissed and was_ignored - only one must be set') return cfg = get_config() if not cfg.spreadsheet_results_sheet: hr_logger.error( 'Не вдалося надіслати результат матчу в гуглтаблицю - у налаштуваннях відсутня назва листа') logger.error('Cannot insert result into a google sheet - sheet name is missing') return if not at_row: results = ResultsSheet.get_all_canceled_results() if results is None: results = [] at_row = len(results) + 2 t = '' if was_ignored: t = get_translation_for('gsheet_technical_win_ignored_str') if was_dismissed: t = get_translation_for('gsheet_technical_win_dismissed_str') update_data( cfg.spreadsheet_id, f'{cfg.spreadsheet_results_sheet}!H{at_row}:L', values=[ [ datetime.now(tz=_k_tz).strftime('%d/%m/%Y'), looser.name, t, winner.name, level_change or '-' ] ] ) except: logger.exception('Exception occurred while uploading canceled match results')
def get_results_keyboard(**kwargs): keyboard = types.ReplyKeyboardMarkup(resize_keyboard=True, row_width=4) if kwargs.get('confirmation_stage', None): keyboard.row( get_translation_for('result_competitor_win_btn'), get_translation_for('result_opponent_win_btn') ) keyboard.row( get_translation_for('results_confirm_btn') ) else: for i in range(4): keyboard.row( str(4 * i), str(4 * i + 1), str(4 * i + 2), str(4 * i + 3), ) keyboard.row(get_translation_for('results_scores_confirm_btn')) keyboard.row( get_translation_for('to_menu_btn'), get_translation_for('back_btn'), get_translation_for('results_clear_btn') ) return keyboard
def render_result(res: Result, final=False): text = get_translation_for('result_current_str') + ':\n' rr = res.scores scorepairs = [[]] for s in rr: if len(scorepairs[-1]) >= 2: scorepairs.append([s]) else: scorepairs[-1].append(s) if len(scorepairs[-1]) == 2 and not final and res.result is None: scorepairs.append([]) for sp in scorepairs: if len(sp) == 0: text += f'X-_' if len(sp) == 1: text += f'{sp[0]}-X' if len(sp) == 2: text += f'{sp[0]}-{sp[1]}' text += '\n' text += '\n' if final: if res.result is not None: text += get_translation_for( 'result_to_change_winner_press_again_str') else: text += get_translation_for('result_select_winner_str') text += '\n' if res.result is not None: text += '\n' text += f'<b>{get_translation_for("result_match_result_str")}: ' c = None if res.result == RESULT.A_WINS: c = f'{res.player_a.fetch().name} {get_translation_for("result_wins_str")} {res.player_b.fetch().name}' elif res.result == RESULT.B_WINS: c = f'{res.player_b.fetch().name} {get_translation_for("result_wins_str")} {res.player_a.fetch().name}' elif res.result == RESULT.CANCELED: c = get_translation_for('result_challenge_canceled_str') if c: text += c text += '</b>' return text
def confirm_scores(self, message: Message, user: User, bot: TeleBot, competitor: Competitor): res = user.check_result() if not len(res.scores) or len(res.scores) % 2: bot.send_message( message.chat.id, get_translation_for('result_scores_count_must_be_odd_msg'), reply_markup=self.__base_keyboard()) return RET.OK, None, None, None return self.process_result(message, user, bot, competitor, True)
def entry(self, message: Message, user: User, bot: TeleBot, competitor=None): if not competitor: logger.warning('Check_wrapper not provided Competitor object') competitor = user.associated_with.fetch() bot.send_message( message.chat.id, get_translation_for('menu_msg'), reply_markup=self.__base_keyboard(status=competitor.status) ) return RET.OK, None, None, None
def dismiss_cancellation_opponent(self, message: Message, user: User, bot: TeleBot, competitor: Competitor): if competitor.status != COMPETITOR_STATUS.CHALLENGE_NEED_CANCELLATION_CONFIRMATION: return RET.OK, None, None, None opponent, opponent_user = get_opponent_and_opponent_user(competitor) if not opponent or not opponent_user: return teardown_challenge( competitor, message, user, bot, 'challenge_confirm_cannot_find_opponent_msg' if not opponent else 'challenge_confirm_cannot_fin_opponents_user_msg' ) competitor.change_status(competitor.previous_challenge_status) competitor.previous_challenge_status = None competitor.save() user.dismiss_confirmed = False user.save() if not smwae_check( opponent_user.user_id, get_translation_for('challenge_cancellation_denied_opponent_msg').format(competitor.name), opponent_user, reply_markup=self.__base_keyboard(status=opponent.status) ): return teardown_challenge( competitor, message, user, bot, 'error_bot_blocked_by_opponent_challenge_canceled_msg' ) bot.send_message( user.user_id, get_translation_for('challenge_cancellation_denied_msg').format(opponent.name), reply_markup=self.__base_keyboard(status=competitor.status) ) return RET.OK, None, None, None
def cancel_challenge_request(self, message: Message, user: User, bot: TeleBot, competitor: Competitor): if competitor.status != COMPETITOR_STATUS.CHALLENGE_INITIATED: return RET.OK, None, None, None if not user.dismiss_confirmed: bot.send_message( message.chat.id, get_translation_for('challenge_request_cancel_confirm_msg'), reply_markup=self.__base_keyboard(status=competitor.status) ) user.dismiss_confirmed = True user.save() return RET.OK, None, None, None else: user.dismiss_confirmed = False user.save() opponent, opponent_user = get_opponent_and_opponent_user(competitor) LogsSheet.glog( get_translation_for('gsheet_log_player_canceled_challenge_for_player').format(competitor.name) ) if not opponent or not opponent_user: return teardown_challenge( competitor, message, user, bot, 'challenge_request_canceled_to_competitor_msg', canceled_by_bot=False ) else: return teardown_challenge( competitor, message, user, bot, 'challenge_request_canceled_to_competitor_msg', canceled_by_bot=False, opponent=opponent, opponent_msg_key='challenge_request_canceled_to_opponent_msg' )
def entry(self, message: Message, user: User, bot: TeleBot, competitor: Competitor = None): if competitor is None: competitor = user.check_association() if competitor.status not in (COMPETITOR_STATUS.CHALLENGE_STARTER, COMPETITOR_STATUS.CHALLENGE_RECEIVER): logger.error( f"User ({user.user_id}) - {competitor.name} entered ChallengeSendResultsState with incorrect competitor status {competitor.status}" ) return RET.GO_TO_STATE, 'MenuState', message, user opponent, opponent_user = get_opponent_and_opponent_user(competitor) if not opponent or not opponent_user: return teardown_challenge( competitor, message, user, bot, 'challenge_confirm_cannot_find_opponent_msg' if not opponent else 'challenge_confirm_cannot_fin_opponents_user_msg') if not user.check_result(): res = Result(player_a=competitor, player_b=opponent, confirmed=False) res.save() user.current_result = res user.save() elif not user.check_result().sent: bot.send_message(user.user_id, get_translation_for('results_enter_results_msg')) else: bot.send_message(message.chat.id, get_translation_for('results_already_sent_msg')) return RET.GO_TO_STATE, 'MenuState', message, user return self.process_result(message, user, bot, competitor, final=user.check_result().result is not None)
def go_on_vacation(self, message: Message, user: User, bot: TeleBot, competitor: Competitor): if competitor.status not in (COMPETITOR_STATUS.ACTIVE, COMPETITOR_STATUS.PASSIVE): return RET.OK, None, None, None config = get_config() if timedelta(seconds=competitor.used_vacation_time).days >= config.vacation_time: bot.send_message( message.chat.id, get_translation_for('menu_vacation_no_days_left_msg'), reply_markup=self.__base_keyboard(status=competitor.status) ) return RET.OK, None, None, None competitor.previous_status = competitor.status competitor.change_status(COMPETITOR_STATUS.VACATION) competitor.vacation_started_at = datetime.now(tz=timezone('Europe/Kiev')) competitor.save() bot.send_message( message.chat.id, get_translation_for('menu_on_vacation_start_msg'), reply_markup=self.__base_keyboard(status=competitor.status) ) LogsSheet.glog(get_translation_for('gsheet_log_player_started_vacation').format(competitor.name)) return RET.OK, None, None, None
def entry(self, message: Message, user: User, bot: TeleBot, competitor: Competitor = None): if not competitor: competitor = user.check_association() if not competitor: bot.send_message( message.chat.id, get_translation_for('competitor_record_vanished_msg')) return RET.GO_TO_STATE, 'AuthenticationState', message, user if not self.update_and_paginate(message, user, bot, competitor): return RET.GO_TO_STATE, 'MenuState', message, user return RET.OK, None, None, None
def end_vacation(self, message: Message, user: User, bot: TeleBot, competitor: Competitor): if competitor.status != COMPETITOR_STATUS.VACATION: return RET.OK, None, None, None if not competitor.vacation_started_at: logger.error(f"Cannot calculate user's ({user.user_id}) time on vacation - cannot find vacation_started_at") delta = 0 else: tz = timezone('Europe/Kiev') now = datetime.now(tz=tz) delta = now - mongo_time_to_local(competitor.vacation_started_at, tz) delta = delta.total_seconds() if competitor.used_vacation_time is None: competitor.used_vacation_time = delta else: competitor.used_vacation_time += delta competitor.change_status(competitor.previous_status) competitor.save() bot.send_message( message.chat.id, get_translation_for('menu_on_vacation_end_manual_msg'), reply_markup=self.__base_keyboard(status=competitor.status) ) LogsSheet.glog(get_translation_for('gsheet_log_player_finished_vacation').format(competitor.name)) return RET.OK, None, None, None
def competitor_check(message: Message, user: User, bot: TeleBot, send_message=True): competitor = user.check_association() if not competitor: if send_message: bot.send_message( message.chat.id, get_translation_for('competitor_record_vanished_msg'), reply_markup=get_keyboard_remover()) return { 'success': False, 'tuple': (RET.GO_TO_STATE, 'AuthenticationState', message, user) } return {'success': True, 'competitor': competitor}
def process_message_with_buttons(self, message: Message, user: User, bot: TeleBot): # if self.authentication_required(user): # return RET_GO_TO_STATE, 'AuthenticationState', message, user if message.text: backed_ret = None for k, v in self._buttons.items(): assert user.language is not None if message.text == get_translation_for(k): ret = v(message, user, bot) if isinstance(ret, tuple): if ret[0] == RET.OK: backed_ret = ret # Let's check if there is another button for this text continue return ret if backed_ret: return backed_ret return self.process_message(message=message, user=user, bot=bot)
def submit_results(self, message: Message, user: User, bot: TeleBot, competitor: Competitor): if competitor.status not in (COMPETITOR_STATUS.CHALLENGE_STARTER, COMPETITOR_STATUS.CHALLENGE_RECEIVER): return RET.OK, None, None, None opponent, opponent_user = get_opponent_and_opponent_user(competitor) if not opponent or not opponent_user: return teardown_challenge( competitor, message, user, bot, 'challenge_confirm_cannot_find_opponent_msg' if not opponent else 'challenge_confirm_cannot_fin_opponents_user_msg' ) if opponent.status == COMPETITOR_STATUS.CHALLENGE_NEED_RESULTS_CONFIRMATION: bot.send_message( message.chat.id, get_translation_for('results_already_sent_msg') ) return RET.OK, None, None, None return RET.GO_TO_STATE, 'ChallengeSendResultsState', message, user
def change_status(self, new_status, bot=None, do_not_notify_admin=False): from google_integration.sheets.users import UsersSheet from localization.translations import get_translation_for from bot.settings_interface import get_config from telebot import TeleBot from config import BOT_TOKEN from logger_settings import hr_logger, logger update_sheet = False if self.status_code_to_str_dict[self.status] != self.status_code_to_str_dict[new_status]: update_sheet = True cfg = get_config() if update_sheet and cfg.admin_username and not do_not_notify_admin: try: if self.status in (COMPETITOR_STATUS.INJUIRY, COMPETITOR_STATUS.INACTIVE) or \ new_status in (COMPETITOR_STATUS.INJUIRY, COMPETITOR_STATUS.INACTIVE): admin_user = User.objects(username=cfg.admin_username).first() if admin_user: name = self.name associated_user = self.get_relevant_user() if associated_user: name = f'<a href="tg://user?id={associated_user.user_id}">{name}</a>' t = get_translation_for('admin_notification_competitor_changed_status').format( name, self.status_code_to_str_dict[self.status], self.status_code_to_str_dict[new_status] ) if bot is None: bot = TeleBot(BOT_TOKEN, threaded=False) bot.send_message( admin_user.user_id, t, parse_mode='html' ) except: logger.exception('Exception occurred while sending message to admin') hr_logger.error(f'Не вдалося надіслати адміністратору повідомлення про зміну стану користувача {self.name}') self.status = new_status if update_sheet: UsersSheet.update_competitor_status(self)