def update_game(self, bot: Bot, source, message, **rest) -> Any: if not message: bot.say("You must specify a game to update to!") return # Resolve game name to game ID game: Optional[ TwitchGame] = self.bot.twitch_helix_api.get_game_by_game_name( message) if not game: bot.say(f"Unable to find a game with the name '{message}'") return return self.generic_update(bot, source, message, "game", {"game_id": game.id})
def generic_update(self, bot: Bot, source, message: str, field: str, extra_args: Dict[str, str]) -> None: if not message: bot.say(f"You must specify a {field} to update to!") return if ("user:edit:broadcast" not in bot.streamer_access_token_manager.token.scope or not self.bot.twitch_helix_api.modify_channel_information( self.bot.streamer_user_id, authorization=bot.streamer_access_token_manager, **extra_args, )): bot.say( "Error: The streamer grants permission to update the game. The streamer needs to be re-authenticated to fix this problem." ) return log_msg = f'{source} updated the {field} to "{message}"' bot.say(log_msg) AdminLogManager.add_entry(f"{field.capitalize()} set", source, log_msg)
def update_game(self, bot: Bot, source, message, **rest) -> Any: auth_error = "Error: The streamer must grant permissions to update the game. The streamer needs to be re-authenticated to fix this problem." if ("user:edit:broadcast" not in bot.streamer_access_token_manager.token.scope and "channel:manage:broadcast" not in bot.streamer_access_token_manager.token.scope): bot.say(auth_error) return game_name = message if not game_name: bot.say("You must specify a game to update to!") return # Resolve game name to game ID game = bot.twitch_helix_api.get_game_by_game_name(game_name) if not game: bot.say(f"Unable to find a game with the name '{game_name}'") return try: bot.twitch_helix_api.modify_channel_information( bot.streamer.id, {"game_id": game.id}, authorization=bot.streamer_access_token_manager, ) except HTTPError as e: if e.response.status_code == 401: log.error( f"Failed to update game to '{game_name}' - auth error") bot.say(auth_error) bot.streamer_access_token_manager.invalidate_token() elif e.response.status_code == 500: log.error( f"Failed to update game to '{game_name}' - internal server error" ) bot.say(f"{source}, Failed to update game! Please try again.") else: log.exception( f"Unhandled HTTPError when updating to {game_name}") return log_msg = f'{source} updated the game to "{game_name}"' bot.say(log_msg) AdminLogManager.add_entry("Game set", source, log_msg)
def update_title(self, bot: Bot, source, message, **rest) -> Any: auth_error = "Error: The streamer must grant permissions to update the title. The streamer needs to be re-authenticated to fix this problem." if ("user:edit:broadcast" not in bot.streamer_access_token_manager.token.scope and "channel:manage:broadcast" not in bot.streamer_access_token_manager.token.scope): bot.say(auth_error) return title = message if not title: bot.say("You must specify a title to update to!") return try: bot.twitch_helix_api.modify_channel_information( bot.streamer.id, {"title": title}, authorization=bot.streamer_access_token_manager, ) except HTTPError as e: if e.response.status_code == 401: log.error(f"Failed to update title to '{title}' - auth error") bot.say(auth_error) bot.streamer_access_token_manager.invalidate_token() elif e.response.status_code == 400: log.error(f"Title '{title}' contains banned words") bot.say( f"{source}, Title contained banned words. Please remove the banned words and try again." ) elif e.response.status_code == 500: log.error( f"Failed to update title to '{title}' - internal server error" ) bot.say( f"{source}, Failed to update the title! Please try again.") else: log.exception(f"Unhandled HTTPError when updating to {title}") return log_msg = f'{source} updated the title to "{title}"' bot.say(log_msg) AdminLogManager.add_entry("Title set", source, log_msg)
def bingo_start(self, bot: Bot, source, message: str, event, args) -> bool: if self.bingo_running: bot.send_message_to_user(source, "A bingo is already running FailFish", event, method="reply") return False emote_instances = args["emote_instances"] known_sets = self.make_known_sets_dict(bot) selected_sets: Set[Tuple[str, Tuple[Emote, ...], bool]] = set() points_reward: Optional[int] = None unparsed_options = [] words_in_message = [s for s in message.split(" ") if len(s) > 0] if len(words_in_message) <= 0: bot.send_message_to_user( source, "You must at least give me some emote sets or emotes to choose from! FailFish", event, method="reply", ) return False emote_index_offset = len("!bingo start ") # we can't iterate using words_in_message here because that would mess up the accompanying index for index, word in iterate_split_with_index(message.split(" ")): if len(word) <= 0: continue # Is the current word an emote? potential_emote_instance = next( (e for e in emote_instances if e.start == index + emote_index_offset), None) if potential_emote_instance is not None: # single-emote set with the name of the emote new_set = (potential_emote_instance.emote.code, (potential_emote_instance.emote, ), True) selected_sets.add(new_set) continue parsed_int: Optional[int] = None # Is the current word a number? try: parsed_int = int(word) except ValueError: pass if parsed_int is not None: # if points_reward is already set this is the second number in the message if points_reward is not None: unparsed_options.append(word) continue points_reward = parsed_int continue # Is the current word a known set? cleaned_key = remove_emotes_suffix(word).lower() if cleaned_key in known_sets: selected_sets.add(known_sets[cleaned_key]) continue unparsed_options.append(word) if len(unparsed_options) > 0: bot.say( "{}, I don't know what to do with the argument{} {} BabyRage". format( source, "" if len(unparsed_options) == 1 else "s", # pluralization join_to_sentence(['"' + s + '"' for s in unparsed_options]), )) return False default_points = self.settings["default_points"] if points_reward is None: points_reward = default_points max_points = self.settings["max_points"] if points_reward > max_points: bot.send_message_to_user( source, f"You can't start a bingo with that many points. FailFish {max_points} are allowed at most.", event, method="reply", ) return False allow_negative_bingo = self.settings["allow_negative_bingo"] if points_reward < 0 and not allow_negative_bingo: bot.send_message_to_user( source, "You can't start a bingo with negative points. FailFish", event, method="reply") return False min_points = -self.settings["max_negative_points"] if points_reward < min_points: bot.send_message_to_user( source, "You can't start a bingo with that many negative points. FailFish {min_points} are allowed at most.", event, method="reply", ) return False if len(selected_sets) <= 0: bot.send_message_to_user( source, "You must at least give me some emotes or emote sets to choose from! FailFish", event, method="reply", ) return False selected_set_names = [] selected_discrete_emote_codes = [] selected_emotes: Set[Emote] = set() for set_name, set_emotes, is_discrete_emote in selected_sets: if is_discrete_emote: selected_discrete_emote_codes.append(set_name) else: selected_set_names.append(set_name) selected_emotes.update(set_emotes) correct_emote = random.choice(list(selected_emotes)) user_messages = [] if len(selected_set_names) > 0: user_messages.append(join_to_sentence(selected_set_names)) if len(selected_discrete_emote_codes) > 0: # the space at the end is so the ! from the below message doesn't stop the last emote from showing up in chat user_messages.append( f"these emotes: {' '.join(selected_discrete_emote_codes)} ") bot.me( f"A bingo has started! ThunBeast Guess the right emote to win {points_reward} points! B) Only one emote per message! Select from {' and '.join(user_messages)}!" ) log.info( f"A Bingo game has begun for {points_reward} points, correct emote is {correct_emote}" ) self.active_game = BingoGame(correct_emote, points_reward) return True
def accept_duel(self, bot: Bot, source: User, **rest: Any) -> None: """ Accepts any active duel requests you've received. How to use: !accept """ if source.id not in self.duel_targets: bot.whisper(source, "You are not being challenged to a duel by anyone.") return with DBManager.create_session_scope() as db_session: requestor = User.find_by_id(db_session, self.duel_targets[source.id]) if not requestor: bot.whisper( source, "The user who challenged you is gone, I don't know where they went!" ) return duel_price = self.duel_request_price[self.duel_targets[source.id]] if not source.can_afford(duel_price) or not requestor.can_afford( duel_price): bot.whisper( source, f"Your duel request with {requestor} was cancelled due to one of you not having enough points.", ) bot.whisper( requestor, f"Your duel request with {source} was cancelled due to one of you not having enough points.", ) del self.duel_requests[requestor.id] del self.duel_request_price[requestor.id] del self.duel_begin_time[requestor.id] del self.duel_targets[source.id] return source.points -= duel_price requestor.points -= duel_price winning_pot = int(duel_price * (1.0 - self.settings["duel_tax"] / 100)) participants = [source, requestor] winner = random.choice(participants) participants.remove(winner) loser = participants.pop() winner.points += duel_price winner.points += winning_pot # Persist duel statistics winner.duel_stats.won(winning_pot) loser.duel_stats.lost(duel_price) arguments = { "winner": winner.name, "loser": loser.name, "total_pot": duel_price, "extra_points": winning_pot, } if duel_price > 0: message = self.get_phrase("message_won_points", **arguments) if duel_price >= 500 and self.settings["show_on_clr"]: bot.websocket_manager.emit( "notification", {"message": f"{winner} won the duel vs {loser}"}) else: message = self.get_phrase("message_won", **arguments) bot.say(message) del self.duel_requests[requestor.id] del self.duel_request_price[requestor.id] del self.duel_begin_time[requestor.id] del self.duel_targets[source.id] HandlerManager.trigger("on_duel_complete", winner=winner, loser=loser, points_won=winning_pot, points_bet=duel_price)