示例#1
0
class WarningModule(BaseModule):

    ID = __name__.split(".")[-1]
    NAME = "Warnings"
    DESCRIPTION = "Gives people warnings before timing them out for the full duration for banphrase and stuff"
    CATEGORY = "Moderation"
    ENABLED_DEFAULT = True
    SETTINGS = [
        ModuleSetting(
            key="total_chances",
            label=
            "How many warnings a user can receive before full timeout length",
            type="number",
            required=True,
            placeholder="",
            default=2,
            constraints={
                "min_value": 1,
                "max_value": 10
            },
        ),
        ModuleSetting(
            key="length",
            label="How long warnings last before they expire",
            type="number",
            required=True,
            placeholder="Warning expiration length in seconds",
            default=600,
            constraints={
                "min_value": 60,
                "max_value": 3600
            },
        ),
        ModuleSetting(
            key="base_timeout",
            label="Base timeout for warnings",
            type="number",
            required=True,
            placeholder="Base timeout length for warnings in seconds",
            default=10,
            constraints={
                "min_value": 5,
                "max_value": 30
            },
        ),
        ModuleSetting(
            key="redis_prefix",
            label=
            "Prefix in the redis database. Only touch if you know what you're doing.",
            type="text",
            required=True,
            placeholder="Can be left blank, don't worry!",
            default="",
        ),
    ]
示例#2
0
class WinDuelsQuestModule(BaseQuest):

    ID = "quest-" + __name__.split(".")[-1]
    NAME = "Win duels"
    DESCRIPTION = "Win X duels and make profit in every duel."
    PARENT_MODULE = QuestModule
    CATEGORY = "Quest"
    SETTINGS = [
        ModuleSetting(
            key="quest_limit",
            label="How many duels must a user win.",
            type="number",
            required=True,
            placeholder="",
            default=10,
            constraints={
                "min_value": 1,
                "max_value": 200
            },
        )
    ]

    def get_limit(self):
        return self.settings["quest_limit"]

    def on_duel_complete(self, winner, points_won, **rest):
        if points_won < 1:
            return

        user_progress = self.get_user_progress(winner.username, default=0)
        if user_progress >= self.get_limit():
            return

        user_progress += 1

        redis = RedisManager.get()

        if user_progress == self.get_limit():
            self.finish_quest(redis, winner)

        self.set_user_progress(winner.username, user_progress, redis=redis)

    def start_quest(self):
        HandlerManager.add_handler("on_duel_complete", self.on_duel_complete)

        redis = RedisManager.get()

        self.load_progress(redis=redis)

    def stop_quest(self):
        HandlerManager.remove_handler("on_duel_complete",
                                      self.on_duel_complete)

        redis = RedisManager.get()

        self.reset_progress(redis=redis)

    def get_objective(self):
        return "Win {} duels and make profit in every duel.".format(
            self.get_limit())
示例#3
0
class WinDuelPointsQuestModule(BaseQuest):

    ID = "quest-" + __name__.split(".")[-1]
    NAME = "Win points in duels"
    DESCRIPTION = "You need to win X amount of points in a duel to complete this quest."
    PARENT_MODULE = QuestModule
    SETTINGS = [
        ModuleSetting(
            key="min_value",
            label="Minimum amount of points the user needs to win",
            type="number",
            required=True,
            placeholder="",
            default=250,
            constraints={"min_value": 50, "max_value": 2000},
        ),
        ModuleSetting(
            key="max_value",
            label="Maximum amount of points the user needs to win",
            type="number",
            required=True,
            placeholder="",
            default=750,
            constraints={"min_value": 100, "max_value": 4000},
        ),
    ]

    LIMIT = 1

    def __init__(self, bot):
        super().__init__(bot)
        self.points_required_key = "{streamer}:current_quest_points_required".format(
            streamer=StreamHelper.get_streamer()
        )
        # The points_required variable is randomized at the start of the quest.
        # It will be a value between settings['min_value'] and settings['max_value']
        self.points_required = None
        self.progress = {}

    def on_duel_complete(self, winner, points_won, **rest):
        if points_won < 1:
            # This duel did not award any points.
            # That means it's entirely irrelevant to us
            return

        total_points_won = self.get_user_progress(winner.username, default=0)
        if total_points_won >= self.points_required:
            # The user has already won enough points, and been rewarded already.
            return

        # If we get here, this means the user has not completed the quest yet.
        # And the user won some points in this duel
        total_points_won += points_won

        redis = RedisManager.get()

        if total_points_won >= self.points_required:
            # Reward the user with some tokens
            self.finish_quest(redis, winner)

        # Save the users "points won" progress
        self.set_user_progress(winner.username, total_points_won, redis=redis)

    def start_quest(self):
        HandlerManager.add_handler("on_duel_complete", self.on_duel_complete)

        redis = RedisManager.get()

        self.load_progress(redis=redis)
        self.load_data(redis=redis)

        self.LIMIT = self.points_required

    def load_data(self, redis=None):
        if redis is None:
            redis = RedisManager.get()

        self.points_required = redis.get(self.points_required_key)
        try:
            self.points_required = int(self.points_required)
        except (TypeError, ValueError):
            pass
        if self.points_required is None:
            try:
                self.points_required = random.randint(self.settings["min_value"], self.settings["max_value"] + 1)
            except ValueError:
                # someone f****d up
                self.points_required = 500
            redis.set(self.points_required_key, self.points_required)

    def stop_quest(self):
        HandlerManager.remove_handler("on_duel_complete", self.on_duel_complete)

        redis = RedisManager.get()

        self.reset_progress(redis=redis)
        redis.delete(self.points_required_key)

    def get_objective(self):
        return "Make a profit of {} or more points in one or multiple duels.".format(self.points_required)
示例#4
0
class PredictModule(BaseModule):

    ID = __name__.split(".")[-1]
    NAME = "Prediction module"
    DESCRIPTION = "Handles predictions of arena wins"
    CATEGORY = "Feature"
    SETTINGS = [
        ModuleSetting(
            key="challenge_name",
            label="The name of the challenge",
            type="text",
            required=True,
            placeholder="The name of the challenge",
            default="100in10 arena",
        ),
        ModuleSetting(
            key="max_wins",
            label="The maximum amount of wins the user can predict",
            type="number",
            required=True,
            placeholder="The maximum amount of wins the user can bet",
            default=120,
            constraints={"min_value": 0},
        ),
        ModuleSetting(key="sub_only",
                      label="Sub only",
                      type="boolean",
                      required=True,
                      default=True),
        ModuleSetting(
            key="mini_command",
            label="Mini predict command (Leave empty to disable)",
            type="text",
            required=True,
            default="",
        ),
        ModuleSetting(
            key="mini_max_wins",
            label=
            "The maximum amount of wins the user can predict in the mini prediction",
            type="number",
            required=True,
            placeholder="The maximum amount of wins the user can bet",
            default=12,
            constraints={"min_value": 0},
        ),
    ]

    def load_commands(self, **options):
        self.commands["predict"] = Command.multiaction_command(
            level=100,
            default="vote",
            fallback="vote",
            delay_all=0,
            delay_user=0,
            commands={
                "vote":
                Command.raw_command(
                    self.predict,
                    delay_all=0,
                    delay_user=10,
                    sub_only=self.settings["sub_only"],
                    can_execute_with_whisper=True,
                    description="Predict how many wins will occur in the " +
                    self.settings["challenge_name"] + " challenge",
                ),
                "new":
                Command.raw_command(
                    self.new_predict,
                    delay_all=10,
                    delay_user=10,
                    description="Starts a new " +
                    self.settings["challenge_name"] + " run",
                    level=750,
                ),
                "end":
                Command.raw_command(
                    self.end_predict,
                    delay_all=10,
                    delay_user=10,
                    description="Ends a " + self.settings["challenge_name"] +
                    " run",
                    level=750,
                ),
                "close":
                Command.raw_command(
                    self.close_predict,
                    delay_all=10,
                    delay_user=10,
                    description="Close submissions to the latest " +
                    self.settings["challenge_name"] + " run",
                    level=750,
                ),
            },
        )

        # XXX: DEPRECATED, WILL BE REMOVED
        self.commands["newpredict"] = Command.raw_command(
            self.new_predict_depr,
            delay_all=10,
            delay_user=10,
            description="Starts a new " + self.settings["challenge_name"] +
            " run",
            level=750,
        )
        self.commands["endpredict"] = Command.raw_command(
            self.end_predict_depr,
            delay_all=10,
            delay_user=10,
            description="Ends a " + self.settings["challenge_name"] + " run",
            level=750,
        )
        self.commands["closepredict"] = Command.raw_command(
            self.close_predict_depr,
            delay_all=10,
            delay_user=10,
            description="Close submissions to the latest " +
            self.settings["challenge_name"] + " run",
            level=750,
        )

        mini_command = self.settings["mini_command"].lower().replace(
            "!", "").replace(" ", "")
        if len(mini_command) > 0:
            self.commands[mini_command] = Command.multiaction_command(
                level=100,
                default="vote",
                fallback="vote",
                delay_all=0,
                delay_user=0,
                commands={
                    "vote":
                    Command.raw_command(
                        self.mini_predict,
                        delay_all=0,
                        delay_user=10,
                        sub_only=self.settings["sub_only"],
                        can_execute_with_whisper=True,
                        description="Predict how many wins will occur in the "
                        + self.settings["challenge_name"] + " challenge",
                    ),
                    "new":
                    Command.raw_command(
                        self.mini_new_predict,
                        delay_all=10,
                        delay_user=10,
                        description="Starts a new " +
                        self.settings["challenge_name"] + " run",
                        level=750,
                    ),
                    "end":
                    Command.raw_command(
                        self.mini_end_predict,
                        delay_all=10,
                        delay_user=10,
                        description="Ends a " +
                        self.settings["challenge_name"] + " run",
                        level=750,
                    ),
                    "close":
                    Command.raw_command(
                        self.mini_close_predict,
                        delay_all=10,
                        delay_user=10,
                        description="Close submissions to the latest " +
                        self.settings["challenge_name"] + " run",
                        level=750,
                    ),
                },
            )

    def new_predict_depr(self, **options):
        bot = options["bot"]
        source = options["source"]

        bot.whisper(
            source.username,
            'This command is deprecated, please use "!predict new" in the future.'
        )
        self.new_predict(**options)

    def end_predict_depr(self, **options):
        bot = options["bot"]
        source = options["source"]

        bot.whisper(
            source.username,
            'This command is deprecated, please use "!predict end" in the future.'
        )
        self.end_predict(**options)

    def close_predict_depr(self, **options):
        bot = options["bot"]
        source = options["source"]

        bot.whisper(
            source.username,
            'This command is deprecated, please use "!predict close" in the future.'
        )
        self.close_predict(**options)

    def shared_predict(self, bot, source, message, type):
        if type == 0:
            max_wins = self.settings["max_wins"]
        else:
            max_wins = self.settings["mini_max_wins"]
        example_wins = round(max_wins / 2)
        bad_command_message = "{username}, Missing or invalid argument to command. Valid argument could be {example_wins} where {example_wins} is a number between 0 and {max_wins} (inclusive).".format(
            username=source.username_raw,
            example_wins=example_wins,
            max_wins=max_wins)

        if source.id is None:
            log.warning(
                "Source ID is NONE, attempting to salvage by commiting users to the database."
            )
            log.info("New ID is: {}".format(source.id))
            bot.whisper(source.username,
                        "uuh, please try the command again :D")
            return False

        prediction_number = None

        if message is None or len(message) < 0:
            bot.say(bad_command_message)
            return True

        try:
            prediction_number = int(message.split(" ")[0])
        except (KeyError, ValueError):
            bot.say(bad_command_message)
            return True

        if prediction_number < 0 or prediction_number > max_wins:
            bot.say(bad_command_message)
            return True

        with DBManager.create_session_scope() as db_session:
            # Get the current open prediction
            current_prediction_run = (
                db_session.query(PredictionRun).filter_by(
                    ended=None, open=True, type=type).one_or_none())
            if current_prediction_run is None:
                bot.say(
                    "{}, There is no {} run active that accepts predictions right now."
                    .format(source.username_raw,
                            self.settings["challenge_name"]))
                return True

            user_entry = (db_session.query(PredictionRunEntry).filter_by(
                prediction_run_id=current_prediction_run.id,
                user_id=source.id).one_or_none())
            if user_entry is not None:
                old_prediction_num = user_entry.prediction
                user_entry.prediction = prediction_number
                bot.say("{}, Updated your prediction for run {} from {} to {}".
                        format(source.username_raw, current_prediction_run.id,
                               old_prediction_num, prediction_number))
            else:
                user_entry = PredictionRunEntry(current_prediction_run.id,
                                                source.id, prediction_number)
                db_session.add(user_entry)
                bot.say(
                    "{}, Your prediction for {} wins in run {} has been submitted."
                    .format(source.username_raw, prediction_number,
                            current_prediction_run.id))

    @staticmethod
    def shared_new_predict(bot, source, type):
        with DBManager.create_session_scope() as db_session:
            # Check if there is already an open prediction
            current_prediction_run = (
                db_session.query(PredictionRun).filter_by(
                    ended=None, open=True, type=type).one_or_none())
            if current_prediction_run is not None:
                bot.say(
                    "{}, There is already a prediction run accepting submissions, close it before you can start a new run."
                    .format(source.username_raw))
                return True

            new_prediction_run = PredictionRun(type)
            db_session.add(new_prediction_run)
            db_session.commit()
            bot.say(
                "A new prediction run has been started, and is now accepting submissions. Prediction run ID: {}"
                .format(new_prediction_run.id))

    @staticmethod
    def shared_end_predict(bot, source, type):
        with DBManager.create_session_scope() as db_session:
            # Check if there is a non-ended, but closed prediction run we can end
            predictions = db_session.query(PredictionRun).filter_by(
                ended=None, open=False, type=type).all()
            if len(predictions) == 0:
                bot.say(
                    "{}, There is no closed prediction runs we can end right now."
                    .format(source.username_raw))
                return True

            for prediction in predictions:
                prediction.ended = utils.now()
            bot.say("Closed predictions with IDs {}".format(", ".join(
                [str(p.id) for p in predictions])))

    @staticmethod
    def shared_close_predict(bot, source, type):
        with DBManager.create_session_scope() as db_session:
            # Check if there is a non-ended, but closed prediction run we can end
            current_prediction_run = (
                db_session.query(PredictionRun).filter_by(
                    ended=None, open=True, type=type).one_or_none())
            if current_prediction_run is None:
                bot.say(
                    "{}, There is no open prediction runs we can close right now."
                    .format(source.username_raw))
                return True

            current_prediction_run.open = False
            bot.say(
                "{}, Predictions are no longer accepted for prediction run {}".
                format(source.username_raw, current_prediction_run.id))

    def predict(self, **options):
        bot = options["bot"]
        message = options["message"]
        source = options["source"]
        self.shared_predict(bot, source, message, 0)

    def mini_predict(self, **options):
        bot = options["bot"]
        message = options["message"]
        source = options["source"]
        self.shared_predict(bot, source, message, 1)

    def new_predict(self, **options):
        bot = options["bot"]
        source = options["source"]
        self.shared_new_predict(bot, source, 0)

    def mini_new_predict(self, **options):
        bot = options["bot"]
        source = options["source"]
        self.shared_new_predict(bot, source, 1)

    def end_predict(self, **options):
        bot = options["bot"]
        source = options["source"]
        self.shared_end_predict(bot, source, 0)

    def mini_end_predict(self, **options):
        bot = options["bot"]
        source = options["source"]
        self.shared_end_predict(bot, source, 1)

    def close_predict(self, **options):
        bot = options["bot"]
        source = options["source"]
        self.shared_close_predict(bot, source, 0)

    def mini_close_predict(self, **options):
        bot = options["bot"]
        source = options["source"]
        self.shared_close_predict(bot, source, 1)
示例#5
0
class WinHsBetPointsQuestModule(BaseQuest):

    ID = "quest-" + __name__.split(".")[-1]
    NAME = "HsBet Points"
    DESCRIPTION = "Win X points with Hearthstone bets."
    PARENT_MODULE = QuestModule
    CATEGORY = "Quest"
    SETTINGS = [
        ModuleSetting(
            key="min_value",
            label="Minimum amount of points the user needs to win",
            type="number",
            required=True,
            placeholder="",
            default=200,
            constraints={
                "min_value": 25,
                "max_value": 2000
            },
        ),
        ModuleSetting(
            key="max_value",
            label="Maximum amount of points the user needs to win",
            type="number",
            required=True,
            placeholder="",
            default=650,
            constraints={
                "min_value": 100,
                "max_value": 4000
            },
        ),
    ]

    LIMIT = 1

    def __init__(self, bot):
        super().__init__(bot)
        self.hsbet_points_key = f"{StreamHelper.get_streamer()}:current_quest_hsbet_points"
        self.hsbet_points_required = None
        self.progress = {}

    def on_user_win_hs_bet(self, user, points_won, **rest):
        if points_won < 1:
            return

        user_progress = self.get_user_progress(user, default=0)
        if user_progress >= self.hsbet_points_required:
            return

        user_progress += points_won

        redis = RedisManager.get()

        if user_progress >= self.hsbet_points_required:
            self.finish_quest(redis, user)

        self.set_user_progress(user, user_progress, redis=redis)

    def start_quest(self):
        HandlerManager.add_handler("on_user_win_hs_bet",
                                   self.on_user_win_hs_bet)

        redis = RedisManager.get()

        self.load_progress(redis=redis)
        self.load_data(redis=redis)

        self.LIMIT = self.hsbet_points_required

    def load_data(self, redis=None):
        if redis is None:
            redis = RedisManager.get()

        self.hsbet_points_required = redis.get(self.hsbet_points_key)
        try:
            self.hsbet_points_required = int(self.hsbet_points_required)
        except (TypeError, ValueError):
            pass
        if self.hsbet_points_required is None:
            try:
                self.hsbet_points_required = random.randint(
                    self.settings["min_value"], self.settings["max_value"])
            except ValueError:
                self.hsbet_points_required = 500
            redis.set(self.hsbet_points_key, self.hsbet_points_required)

    def stop_quest(self):
        HandlerManager.remove_handler("on_user_win_hs_bet",
                                      self.on_user_win_hs_bet)

        redis = RedisManager.get()

        self.reset_progress(redis=redis)
        redis.delete(self.hsbet_points_key)

    def get_objective(self):
        return f"Make a profit of {self.hsbet_points_required} or more points in one or multiple hearthstone bets."
示例#6
0
class CLROverlayModule(BaseModule):
    ID = "clroverlay-group"
    NAME = "CLR Overlay"
    DESCRIPTION = "A collection of overlays that can be used in the streaming software of choice"
    CATEGORY = "Feature"
    ENABLED_DEFAULT = True
    MODULE_TYPE = ModuleType.TYPE_ALWAYS_ENABLED

    BASE_ALLOWLIST_LABEL: str = "Allowlisted emotes (separate by spaces). Leave empty to use the blocklist."
    BASE_BLOCKLIST_LABEL: str = "Blocklisted emotes (separate by spaces). Leave empty to allow all emotes."
    ALLOWLIST_LABEL: str = f"{BASE_ALLOWLIST_LABEL} If this and the blocklist are empty, the parent module's allow and blocklist will be used."
    BLOCKLIST_LABEL: str = f"{BASE_BLOCKLIST_LABEL} If this and the allowlist are empty, the parent module's allow and blocklist will be used."

    EMOTELIST_PLACEHOLDER_TEXT: str = "e.g. Kappa Keepo PogChamp KKona"

    SETTINGS = [
        ModuleSetting(
            key="emote_allowlist",
            label=BASE_ALLOWLIST_LABEL,
            type="text",
            required=True,
            placeholder=EMOTELIST_PLACEHOLDER_TEXT,
            default="",
        ),
        ModuleSetting(
            key="emote_blocklist",
            label=BASE_BLOCKLIST_LABEL,
            type="text",
            required=True,
            placeholder=EMOTELIST_PLACEHOLDER_TEXT,
            default="",
        ),
    ]

    def __init__(self, bot: Optional[Bot]) -> None:
        super().__init__(bot)

        self.allowlisted_emotes: Set[str] = set()
        self.blocklisted_emotes: Set[str] = set()

    def on_loaded(self) -> None:
        self.allowlisted_emotes = set(
            self.settings["emote_allowlist"].strip().split(" ") if self.settings["emote_allowlist"] else []
        )
        self.blocklisted_emotes = set(
            self.settings["emote_blocklist"].strip().split(" ") if self.settings["emote_blocklist"] else []
        )

    def is_emote_allowed(self, emote_code: str) -> bool:
        if len(self.allowlisted_emotes) > 0:
            return emote_code in self.allowlisted_emotes

        return emote_code not in self.blocklisted_emotes

    @classmethod
    def convert(cls, obj: Optional[BaseModule]) -> None:
        if obj is None:
            return

        if obj is not CLROverlayModule:
            raise RuntimeError("Paraneter sent to CLROverlay.convert must be a CLROverlayModule")

        obj.__class__ = CLROverlayModule
示例#7
0
class AsciiProtectionModule(BaseModule):

    ID = __name__.split(".")[-1]
    NAME = "Ascii Protection"
    DESCRIPTION = "Times out users who post messages that contain too many ASCII characters."
    CATEGORY = "Filter"
    SETTINGS = [
        ModuleSetting(
            key="min_msg_length",
            label="Minimum message length to be considered bad",
            type="number",
            required=True,
            placeholder="",
            default=70,
            constraints={
                "min_value": 20,
                "max_value": 1000
            },
        ),
        ModuleSetting(
            key="timeout_length",
            label="Timeout length",
            type="number",
            required=True,
            placeholder="Timeout length in seconds",
            default=120,
            constraints={
                "min_value": 30,
                "max_value": 3600
            },
        ),
        ModuleSetting(
            key="bypass_level",
            label="Level to bypass module",
            type="number",
            required=True,
            placeholder="",
            default=500,
            constraints={
                "min_value": 100,
                "max_value": 1000
            },
        ),
        ModuleSetting(
            key="whisper_offenders",
            label="Send offenders a whisper explaining the timeout",
            type="boolean",
            required=True,
            default=False,
        ),
    ]

    @staticmethod
    def check_message(message):
        non_alnum = sum(not c.isalnum() for c in message)
        ratio = non_alnum / len(message)
        log.debug(message)
        log.debug(ratio)
        if (len(message) > 240 and ratio > 0.8) or ratio > 0.93:
            return True
        return False

    def on_pubmsg(self, source, message, **rest):
        if source.level >= self.settings[
                "bypass_level"] or source.moderator is True:
            return

        if len(message) <= self.settings["min_msg_length"]:
            return

        if AsciiProtectionModule.check_message(message) is False:
            return

        duration, punishment = self.bot.timeout_warn(
            source,
            self.settings["timeout_length"],
            reason="Too many ASCII characters")
        """ We only send a notification to the user if he has spent more than
        one hour watching the stream. """
        if self.settings[
                "whisper_offenders"] and duration > 0 and source.minutes_in_chat_online > 60:
            self.bot.whisper(
                source.username,
                "You have been {punishment} because your message contained too many ascii characters."
                .format(punishment=punishment),
            )

        return False

    def enable(self, bot):
        HandlerManager.add_handler("on_pubmsg", self.on_pubmsg)

    def disable(self, bot):
        HandlerManager.remove_handler("on_pubmsg", self.on_pubmsg)
示例#8
0
文件: quest.py 项目: zneix/pajbot
class QuestModule(BaseModule):

    ID = __name__.split(".")[-1]
    NAME = "Quest system"
    DESCRIPTION = "Give users a single quest at the start of each stream"
    CATEGORY = "Game"
    SETTINGS = [
        ModuleSetting(
            key="action_currentquest",
            label="MessageAction for !currentquest",
            type="options",
            required=True,
            default="say",
            options=["say", "whisper", "me", "reply"],
        ),
        ModuleSetting(
            key="action_tokens",
            label="MessageAction for !tokens",
            type="options",
            required=True,
            default="whisper",
            options=["say", "whisper", "me", "reply"],
        ),
        ModuleSetting(
            key="reward_type",
            label="Reward type",
            type="options",
            required=True,
            default="tokens",
            options=["tokens", "points"],
        ),
        ModuleSetting(key="reward_amount",
                      label="Reward amount",
                      type="number",
                      required=True,
                      default=5),
        ModuleSetting(
            key="max_tokens",
            label="Max tokens",
            type="number",
            required=True,
            default=15,
            constraints={
                "min_value": 1,
                "max_value": 5000
            },
        ),
    ]

    def __init__(self, bot):
        super().__init__(bot)
        self.current_quest = None
        self.current_quest_key = None

    def my_progress(self, bot, source, **rest):
        if self.current_quest is not None:
            quest_progress = self.current_quest.get_user_progress(source)
            quest_limit = self.current_quest.get_limit()

            if quest_limit is not None and quest_progress >= quest_limit:
                bot.whisper(source, "You have completed todays quest!")
            elif quest_progress is not False:
                bot.whisper(
                    source, f"Your current quest progress is {quest_progress}")
            else:
                bot.whisper(source,
                            "You have no progress on the current quest.")
        else:
            bot.say(f"{source}, There is no quest active right now.")

    def get_current_quest(self, bot, event, source, **rest):
        if self.current_quest:
            message_quest = f"the current quest active is {self.current_quest.get_objective()}."
        else:
            message_quest = "there is no quest active right now."

        bot.send_message_to_user(source,
                                 message_quest,
                                 event,
                                 method=self.settings["action_currentquest"])

    def get_user_tokens(self, bot, event, source, **rest):
        message_tokens = f"{source}, you have {source.tokens} tokens."
        # todo use bot.send_message_to_user or similar

        if self.settings["action_tokens"] == "say":
            bot.say(message_tokens)
        elif self.settings["action_tokens"] == "whisper":
            bot.whisper(source, message_tokens)
        elif self.settings["action_tokens"] == "me":
            bot.me(message_tokens)
        elif self.settings["action_tokens"] == "reply":
            if event.type in ["action", "pubmsg"]:
                bot.say(message_tokens)
            elif event.type == "whisper":
                bot.whisper(source, message_tokens)

    def load_commands(self, **options):
        self.commands["myprogress"] = Command.raw_command(
            self.my_progress,
            can_execute_with_whisper=True,
            delay_all=0,
            delay_user=10)
        self.commands["currentquest"] = Command.raw_command(
            self.get_current_quest,
            can_execute_with_whisper=True,
            delay_all=2,
            delay_user=10)
        self.commands["tokens"] = Command.raw_command(
            self.get_user_tokens,
            can_execute_with_whisper=True,
            delay_all=0,
            delay_user=10)

        self.commands["quest"] = self.commands["currentquest"]

    def on_stream_start(self, **rest):
        if not self.current_quest_key:
            log.error(
                "Current quest key not set when on_stream_start event fired, something is wrong"
            )
            return False

        available_quests = list(
            filter(lambda m: m.ID.startswith("quest-"), self.submodules))
        if not available_quests:
            log.error("No quests enabled.")
            return False

        self.current_quest = random.choice(available_quests)
        self.current_quest.quest_module = self
        self.current_quest.start_quest()

        redis = RedisManager.get()

        redis.set(self.current_quest_key, self.current_quest.ID)

        self.bot.say("Stream started, new quest has been chosen!")
        self.bot.say(
            f"Current quest objective: {self.current_quest.get_objective()}")

        return True

    def on_stream_stop(self, **rest):
        if self.current_quest is None:
            log.info("No quest active on stream stop.")
            return False

        if not self.current_quest_key:
            log.error(
                "Current quest key not set when on_stream_stop event fired, something is wrong"
            )
            return False

        self.current_quest.stop_quest()
        self.current_quest = None
        self.bot.say("Stream ended, quest has been reset.")

        redis = RedisManager.get()

        # Remove any mentions of the current quest
        redis.delete(self.current_quest_key)

        last_stream_id = StreamHelper.get_last_stream_id()
        if last_stream_id is False:
            log.error("No last stream ID found.")
            # No last stream ID found. why?
            return False

        return True

    def on_managers_loaded(self, **rest):
        # This function is used to resume a quest in case the bot starts when the stream is already live
        if not self.current_quest_key:
            log.error(
                "Current quest key not set when on_managers_loaded event fired, something is wrong"
            )
            return

        if self.current_quest:
            # There's already a quest chosen for today
            return

        redis = RedisManager.get()

        current_quest_id = redis.get(self.current_quest_key)

        log.info(f"Try to load submodule with ID {current_quest_id}")

        if not current_quest_id:
            # No "current quest" was chosen by an above manager
            return

        current_quest_id = current_quest_id
        quest = find(lambda m: m.ID == current_quest_id, self.submodules)

        if not quest:
            log.info("No quest with id %s found in submodules (%s)",
                     current_quest_id, self.submodules)
            return

        self.current_quest = quest
        self.current_quest.quest_module = self
        self.current_quest.start_quest()
        log.info(f"Resumed quest {quest.get_objective()}")

    def enable(self, bot):
        if self.bot:
            self.current_quest_key = f"{self.bot.streamer}:current_quest"

        HandlerManager.add_handler("on_stream_start", self.on_stream_start)
        HandlerManager.add_handler("on_stream_stop", self.on_stream_stop)
        HandlerManager.add_handler("on_managers_loaded",
                                   self.on_managers_loaded)

    def disable(self, bot):
        HandlerManager.remove_handler("on_stream_start", self.on_stream_start)
        HandlerManager.remove_handler("on_stream_stop", self.on_stream_stop)
        HandlerManager.remove_handler("on_managers_loaded",
                                      self.on_managers_loaded)
示例#9
0
class WinHsBetWinsQuestModule(BaseQuest):

    ID = "quest-" + __name__.split(".")[-1]
    NAME = "HsBet Wins"
    DESCRIPTION = "Bet the right outcome on Hearthstone games X times."
    PARENT_MODULE = QuestModule
    CATEGORY = "Quest"
    SETTINGS = [
        ModuleSetting(
            key="quest_limit",
            label="How many right outcomes must have a user.",
            type="number",
            required=True,
            placeholder="",
            default=4,
            constraints={
                "min_value": 1,
                "max_value": 20
            },
        )
    ]

    def get_limit(self):
        return self.settings["quest_limit"]

    def on_user_win_hs_bet(self, user, **rest):
        # User needs to make 1 point profit at least
        # if points_reward < 1:
        #    return

        user_progress = self.get_user_progress(user, default=0)
        if user_progress >= self.get_limit():
            return

        user_progress += 1

        redis = RedisManager.get()

        if user_progress == self.get_limit():
            self.finish_quest(redis, user)

        self.set_user_progress(user, user_progress, redis=redis)

    def start_quest(self):
        HandlerManager.add_handler("on_user_win_hs_bet",
                                   self.on_user_win_hs_bet)

        redis = RedisManager.get()

        self.load_progress(redis=redis)

    def stop_quest(self):
        HandlerManager.remove_handler("on_user_win_hs_bet",
                                      self.on_user_win_hs_bet)

        redis = RedisManager.get()

        self.reset_progress(redis=redis)

    def get_objective(self):
        return f"Bet the right outcome on {self.get_limit()} Hearthstone games."
示例#10
0
class TypeEmoteQuestModule(BaseQuest):
    ID = "quest-" + __name__.split(".")[-1]
    NAME = "Type X emote Y times"
    DESCRIPTION = "A user needs to type a specific emote Y times to complete this quest."
    PARENT_MODULE = QuestModule
    SETTINGS = [
        ModuleSetting(
            key="quest_limit",
            label="How many emotes you need to type",
            type="number",
            required=True,
            placeholder="How many emotes you need to type (default 100)",
            default=100,
            constraints={
                "min_value": 10,
                "max_value": 200
            },
        )
    ]

    def __init__(self, bot):
        super().__init__(bot)
        self.current_emote_key = f"{StreamHelper.get_streamer()}:current_quest_emote"
        self.current_emote = None
        self.progress = {}

    def get_limit(self):
        return self.settings["quest_limit"]

    def on_message(self, source, emote_instances, **rest):
        typed_emotes = {
            emote_instance.emote
            for emote_instance in emote_instances
        }
        if self.current_emote not in typed_emotes:
            return

        user_progress = self.get_user_progress(source, default=0) + 1

        if user_progress > self.get_limit():
            log.debug(
                f"{source} has already completed the quest. Moving along.")
            # no need to do more
            return

        redis = RedisManager.get()

        if user_progress == self.get_limit():
            self.finish_quest(redis, source)

        self.set_user_progress(source, user_progress, redis=redis)

    def start_quest(self):
        HandlerManager.add_handler("on_message", self.on_message)

        redis = RedisManager.get()

        self.load_progress(redis=redis)
        self.load_data(redis=redis)

    def load_data(self, redis=None):
        if redis is None:
            redis = RedisManager.get()

        redis_json = redis.get(self.current_emote_key)
        if redis_json is None:
            # randomize an emote
            # TODO possibly a setting to allow the user to configure the twitch_global=True, etc
            #      parameters to random_emote?
            self.current_emote = self.bot.emote_manager.random_emote(
                twitch_global=True)
            # If EmoteManager has no global emotes, current_emote will be None
            if self.current_emote is not None:
                redis.set(self.current_emote_key,
                          json.dumps(self.current_emote.jsonify()))
        else:
            self.current_emote = Emote(**json.loads(redis_json))

    def stop_quest(self):
        HandlerManager.remove_handler("on_message", self.on_message)

        redis = RedisManager.get()

        self.reset_progress(redis=redis)
        redis.delete(self.current_emote_key)

    def get_objective(self):
        return f"Use the {self.current_emote.code} emote {self.get_limit()} times"
示例#11
0
class EmoteComboModule(BaseModule):
    ID = __name__.split(".")[-1]
    NAME = "Emote Combo (web interface)"
    DESCRIPTION = "Shows emote combos in the web interface CLR thing"
    CATEGORY = "Feature"
    SETTINGS = [
        ModuleSetting(
            key="min_emote_combo",
            label="Minimum number of emotes required to trigger the combo",
            type="number",
            required=True,
            placeholder="",
            default=5,
            constraints={"min_value": 2, "max_value": 100},
        )
    ]

    def __init__(self, bot):
        super().__init__(bot)
        self.emote_count = 0
        self.current_emote = None

    def inc_emote_count(self):
        self.emote_count += 1
        if self.emote_count >= self.settings["min_emote_combo"]:
            self.bot.websocket_manager.emit(
                "emote_combo", {"emote": self.current_emote.jsonify(), "count": self.emote_count}
            )

    def reset(self):
        self.emote_count = 0
        self.current_emote = None

    def on_message(self, emote_instances, emote_counts, whisper, **rest):
        if whisper:
            return True

        # Check if the message contains exactly one unique emote
        num_unique_emotes = len(emote_counts)
        if num_unique_emotes != 1:
            self.reset()
            return True

        new_emote = emote_instances[0].emote
        new_emote_code = new_emote.code

        # if there is currently a combo...
        if self.current_emote is not None:
            # and this emote is not equal to the combo emote...
            if self.current_emote.code != new_emote_code:
                # The emote of this message is not the one we were previously counting, reset.
                # We do not stop.
                # We start counting this emote instead.
                self.reset()

        if self.current_emote is None:
            self.current_emote = new_emote

        self.inc_emote_count()
        return True

    def enable(self, bot):
        HandlerManager.add_handler("on_message", self.on_message)

    def disable(self, bot):
        HandlerManager.remove_handler("on_message", self.on_message)
示例#12
0
文件: ascii.py 项目: alazymeme/pajbot
class AsciiProtectionModule(BaseModule):

    ID = __name__.split(".")[-1]
    NAME = "ASCII Protection"
    DESCRIPTION = "Times out users who post messages that contain too many ASCII characters."
    CATEGORY = "Moderation"
    SETTINGS = [
        ModuleSetting(
            key="enabled_by_stream_status",
            label="Enable moderation of ASCII characters when the stream is:",
            type="options",
            required=True,
            default="Offline and Online",
            options=["Online Only", "Offline Only", "Offline and Online"],
        ),
        ModuleSetting(
            key="min_msg_length",
            label="Minimum message length to be considered bad",
            type="number",
            required=True,
            placeholder="",
            default=70,
            constraints={"min_value": 20, "max_value": 500},
        ),
        ModuleSetting(
            key="moderation_action",
            label="Moderation action to apply",
            type="options",
            required=True,
            default="Timeout",
            options=["Delete", "Timeout"],
        ),
        ModuleSetting(
            key="timeout_length",
            label="Timeout length",
            type="number",
            required=True,
            placeholder="Timeout length in seconds",
            default=120,
            constraints={"min_value": 1, "max_value": 1209600},
        ),
        ModuleSetting(
            key="bypass_level",
            label="Level to bypass module",
            type="number",
            required=True,
            placeholder="",
            default=500,
            constraints={"min_value": 100, "max_value": 1000},
        ),
        ModuleSetting(
            key="timeout_reason",
            label="Timeout Reason",
            type="text",
            required=False,
            placeholder="",
            default="Too many ASCII characters",
            constraints={},
        ),
        ModuleSetting(
            key="disable_warnings",
            label="Disable warning timeouts",
            type="boolean",
            required=True,
            default=False,
        ),
    ]

    @staticmethod
    def check_message(message):
        if len(message) <= 0:
            return False

        non_alnum = sum(not c.isalnum() for c in message)
        ratio = non_alnum / len(message)
        if (len(message) > 240 and ratio > 0.8) or ratio > 0.93:
            return True
        return False

    def on_pubmsg(self, source, message, tags, **rest):
        if self.settings["enabled_by_stream_status"] == "Online Only" and not self.bot.is_online:
            return

        if self.settings["enabled_by_stream_status"] == "Offline Only" and self.bot.is_online:
            return

        if source.level >= self.settings["bypass_level"] or source.moderator is True:
            return

        if len(message) <= self.settings["min_msg_length"]:
            return

        if AsciiProtectionModule.check_message(message) is False:
            return

        if self.settings["moderation_action"] == "Delete":
            self.bot.delete_message(tags["id"])
        elif self.settings["disable_warnings"] is True and self.settings["moderation_action"] == "Timeout":
            self.bot.timeout(source, self.settings["timeout_length"], reason=self.settings["timeout_reason"])
        else:
            self.bot.timeout_warn(source, self.settings["timeout_length"], reason=self.settings["timeout_reason"])

        return False

    def enable(self, bot):
        HandlerManager.add_handler("on_pubmsg", self.on_pubmsg, priority=150, run_if_propagation_stopped=True)

    def disable(self, bot):
        HandlerManager.remove_handler("on_pubmsg", self.on_pubmsg)
示例#13
0
class TypeMeMessageQuestModule(BaseQuest):
    ID = "quest-" + __name__.split(".")[-1]
    NAME = "Colorful chat /me"
    DESCRIPTION = "Type X /me messages with X message length."
    PARENT_MODULE = QuestModule
    CATEGORY = "Quest"
    SETTINGS = [
        ModuleSetting(
            key="quest_limit",
            label="How many messages does the user needs to type?",
            type="number",
            required=True,
            placeholder="",
            default=100,
            constraints={
                "min_value": 1,
                "max_value": 200
            },
        ),
        ModuleSetting(
            key="quest_message_length",
            label="How many letters minimum should be in the message?",
            type="number",
            required=True,
            placeholder="",
            default=15,
            constraints={
                "min_value": 1,
                "max_value": 500
            },
        ),
    ]

    def get_limit(self):
        return self.settings["quest_limit"]

    def get_quest_message_length(self):
        return self.settings["quest_message_length"]

    def on_message(self, source, message, event, **rest):
        if len(message) < self.get_quest_message_length(
        ) or event.type != "action":
            return

        user_progress = self.get_user_progress(source.username, default=0)

        if user_progress >= self.get_limit():
            return

        user_progress += 1

        redis = RedisManager.get()

        if user_progress == self.get_limit():
            self.finish_quest(redis, source)

        self.set_user_progress(source.username, user_progress, redis=redis)

    def start_quest(self):
        HandlerManager.add_handler("on_message", self.on_message)

        redis = RedisManager.get()

        self.load_progress(redis=redis)

    def stop_quest(self):
        HandlerManager.remove_handler("on_message", self.on_message)

        redis = RedisManager.get()

        self.reset_progress(redis=redis)

    def get_objective(self):
        return "Type {0} /me messages with a length of minimum {1} letters KappaPride ".format(
            self.get_limit(), self.get_quest_message_length())
示例#14
0
class AsciiProtectionModule(BaseModule):

    ID = __name__.split(".")[-1]
    NAME = "ASCII Protection"
    DESCRIPTION = "Times out users who post messages that contain too many ASCII characters."
    CATEGORY = "Moderation"
    SETTINGS = [
        ModuleSetting(
            key="enabled_by_stream_status",
            label="Enable moderation of ASCII characters when the stream is:",
            type="options",
            required=True,
            default="Offline and Online",
            options=["Online Only", "Offline Only", "Offline and Online"],
        ),
        ModuleSetting(
            key="min_msg_length",
            label="Minimum message length to be considered bad",
            type="number",
            required=True,
            placeholder="",
            default=70,
            constraints={
                "min_value": 20,
                "max_value": 1000
            },
        ),
        ModuleSetting(
            key="moderation_action",
            label="Moderation action to apply",
            type="options",
            required=True,
            default="Timeout",
            options=["Delete", "Timeout"],
        ),
        ModuleSetting(
            key="timeout_length",
            label="Timeout length",
            type="number",
            required=True,
            placeholder="Timeout length in seconds",
            default=120,
            constraints={
                "min_value": 30,
                "max_value": 3600
            },
        ),
        ModuleSetting(
            key="bypass_level",
            label="Level to bypass module",
            type="number",
            required=True,
            placeholder="",
            default=500,
            constraints={
                "min_value": 100,
                "max_value": 1000
            },
        ),
        ModuleSetting(
            key="timeout_reason",
            label="Timeout Reason",
            type="text",
            required=False,
            placeholder="",
            default="Too many ASCII characters",
            constraints={},
        ),
        ModuleSetting(
            key="whisper_offenders",
            label="Send offenders a whisper explaining the timeout",
            type="boolean",
            required=True,
            default=False,
        ),
        ModuleSetting(
            key="whisper_timeout_reason",
            label="Whisper Timeout Reason | Available arguments: {punishment}",
            type="text",
            required=False,
            placeholder="",
            default=
            "You have been {punishment} because your message contained too many ascii characters.",
            constraints={},
        ),
    ]

    @staticmethod
    def check_message(message):
        if len(message) <= 0:
            return False

        non_alnum = sum(not c.isalnum() for c in message)
        ratio = non_alnum / len(message)
        if (len(message) > 240 and ratio > 0.8) or ratio > 0.93:
            return True
        return False

    def on_pubmsg(self, source, message, tags, **rest):
        if self.settings[
                "enabled_by_stream_status"] == "Online Only" and not self.bot.is_online:
            return

        if self.settings[
                "enabled_by_stream_status"] == "Offline Only" and self.bot.is_online:
            return

        if source.level >= self.settings[
                "bypass_level"] or source.moderator is True:
            return

        if len(message) <= self.settings["min_msg_length"]:
            return

        if AsciiProtectionModule.check_message(message) is False:
            return

        if self.settings["moderation_action"] == "Delete":
            self.bot.delete_message(tags["id"])
        else:
            duration, punishment = self.bot.timeout_warn(
                source,
                self.settings["timeout_length"],
                reason=self.settings["timeout_reason"])
            """ We only send a notification to the user if he has spent more than
            one hour watching the stream. """
            if self.settings[
                    "whisper_offenders"] and duration > 0 and source.time_in_chat_online >= timedelta(
                        hours=1):
                self.bot.whisper(
                    source, self.settings["whisper_timeout_reason"].format(
                        punishment=punishment))

        return False

    def enable(self, bot):
        HandlerManager.add_handler("on_pubmsg", self.on_pubmsg)

    def disable(self, bot):
        HandlerManager.remove_handler("on_pubmsg", self.on_pubmsg)
示例#15
0
class EmoteComboModule(BaseModule):
    ID = __name__.rsplit(".", maxsplit=1)[-1]
    NAME = "Emote Combos"
    DESCRIPTION = "Shows emote combos on the CLR pajbot overlay"
    CATEGORY = "Feature"
    PARENT_MODULE = CLROverlayModule
    SETTINGS = [
        ModuleSetting(
            key="min_emote_combo",
            label="Minimum number of emotes required to trigger the combo",
            type="number",
            required=True,
            placeholder="",
            default=5,
            constraints={"min_value": 2, "max_value": 100},
        ),
        ModuleSetting(
            key="emote_allowlist",
            label=CLROverlayModule.ALLOWLIST_LABEL,
            type="text",
            required=True,
            placeholder=CLROverlayModule.EMOTELIST_PLACEHOLDER_TEXT,
            default="",
        ),
        ModuleSetting(
            key="emote_blocklist",
            label=CLROverlayModule.BLOCKLIST_LABEL,
            type="text",
            required=True,
            placeholder=CLROverlayModule.EMOTELIST_PLACEHOLDER_TEXT,
            default="",
        ),
    ]

    def __init__(self, bot: Optional[Bot]) -> None:
        super().__init__(bot)

        self.allowlisted_emotes: Set[str] = set()
        self.blocklisted_emotes: Set[str] = set()

        self.parent_module: Optional[CLROverlayModule] = (
            CLROverlayModule.convert(self.bot.module_manager["clroverlay-group"]) if self.bot else None
        )

        self.emote_count: int = 0
        self.current_emote: Optional[Emote] = None

    def on_loaded(self) -> None:
        self.allowlisted_emotes = set(
            self.settings["emote_allowlist"].strip().split(" ") if self.settings["emote_allowlist"] else []
        )
        self.blocklisted_emotes = set(
            self.settings["emote_blocklist"].strip().split(" ") if self.settings["emote_blocklist"] else []
        )

    def is_emote_allowed(self, emote_code: str) -> bool:
        if len(self.allowlisted_emotes) > 0:
            return emote_code in self.allowlisted_emotes

        if len(self.blocklisted_emotes) > 0:
            return emote_code not in self.blocklisted_emotes

        if not self.parent_module:
            return True

        return self.parent_module.is_emote_allowed(emote_code)

    def inc_emote_count(self) -> None:
        if self.bot is None:
            log.warning("EmoteCombo inc_emote_count called when bot is none")
            return

        assert self.current_emote is not None

        self.emote_count += 1

        if self.emote_count >= self.settings["min_emote_combo"]:
            self.bot.websocket_manager.emit(
                "emote_combo", {"emote": self.current_emote.jsonify(), "count": self.emote_count}
            )

    def reset(self) -> None:
        self.emote_count = 0
        self.current_emote = None

    def on_message(
        self, emote_instances: List[EmoteInstance], emote_counts: EmoteInstanceCountMap, whisper: bool, **rest: Any
    ) -> bool:
        if whisper:
            return True

        # Check if the message contains exactly one unique emote
        num_unique_emotes = len(emote_counts)
        if num_unique_emotes != 1:
            self.reset()
            return True

        new_emote = emote_instances[0].emote
        new_emote_code = new_emote.code

        if self.is_emote_allowed(new_emote_code) is False:
            self.reset()
            return True

        # if there is currently a combo...
        if self.current_emote is not None:
            # and this emote is not equal to the combo emote...
            if self.current_emote.code != new_emote_code:
                # The emote of this message is not the one we were previously counting, reset.
                # We do not stop.
                # We start counting this emote instead.
                self.reset()

        if self.current_emote is None:
            self.current_emote = new_emote

        self.inc_emote_count()
        return True

    def enable(self, bot: Optional[Bot]) -> None:
        HandlerManager.add_handler("on_message", self.on_message)

    def disable(self, bot: Optional[Bot]) -> None:
        HandlerManager.remove_handler("on_message", self.on_message)