Exemple #1
0
class LeagueRankModule(BaseModule):
    ID = __name__.split('.')[-1]
    NAME = 'LeagueRank module'
    DESCRIPTION = 'Enable this to check the rank of others in League of Legends in the chat.'
    SETTINGS = [
            ModuleSetting(
                key='riot_api_key',
                label='Riot developer api key',
                type='text',
                required=True,
                placeholder='i.e. 1e3415de-1234-5432-f331-67abb0454d12',
                default=''),
            ModuleSetting(
                key='default_summoner',
                label='Default summoner name',
                type='text',
                required=True,
                placeholder='i.e. moregainfreeman (remove space)',
                default=''),
            ModuleSetting(
                key='default_region',
                label='Default region',
                type='text',
                required=True,
                placeholder='i.e. euw/eune/na/br/oce',
                default=''),
            ]

    def load_commands(self, **options):
        # TODO: Aliases should be set in settings?
        #       This way, it can be run alongside other modules
        self.commands['rank'] = Command.raw_command(self.league_rank,
                delay_all=15,
                delay_user=30,
                description='Check streamer\'s or other players League of Legends rank in chat.',
                examples=[
                    CommandExample(None, 'Check streamer\'s rank',
                        chat='user:!rank\n'
                        'bot: The Summoner Moregain Freeman on region EUW is currently in PLATINUM IV with 62 LP 4Head',
                        description='Bot says broadcaster\'s region, League-tier, division and LP').parse(),
                    CommandExample(None, 'Check other player\'s rank on default region',
                        chat='user:!rank forsen\n'
                        'bot: The Summoner forsen on region EUW is currently in SILVER IV with 36 LP 4Head',
                        description='Bot says player\'s region, League-tier, division and LP').parse(),
                    CommandExample(None, 'Check other player\'s rank on another region',
                        chat='user:!rank imaqtpie na\n'
                        'bot: The Summoner Imaqtpie on region NA is currently in CHALLENGER I with 441 LP 4Head',
                        description='Bot says player\'s region, League-tier, division and LP. Other regions to use as arguments: euw, eune, na, oce, br, kr, las, lan, ru, tr').parse(),
                    ],
                )
        self.commands['lolrank'] = self.commands['rank']
        self.commands['ranklol'] = self.commands['rank']
        self.commands['leaguerank'] = self.commands['rank']

    def league_rank(self, **options):
        try:
            from riotwatcher import RiotWatcher, LoLException
        except ImportError:
            log.error('Missing required module for League Rank module: riotwatcher')
            return False

        source = options['source']
        message = options['message']
        bot = options['bot']

        riot_api_key = self.settings['riot_api_key']
        summoner_name = self.settings['default_summoner']
        def_region = self.settings['default_region']
        region_list = ['br', 'eune', 'euw', 'kr', 'lan', 'las', 'na', 'oce', 'ru', 'tr']

        if message:
            summoner_name = message.split()[0]
            try:
                region = message.split()[1].lower()
            except IndexError:
                region = def_region.lower()

            if region not in region_list:
                bot.whisper(source.username, 'Region is not valid. Please enter a valid region, region is optional and the default region is {}'.format(def_region.upper()))
                return False
            else:
                pass
        else:
            region = def_region.lower()

        error_404 = "Game data not found"
        error_429 = "Too many requests"

        try:
            rw = RiotWatcher(riot_api_key, default_region=region)

            summoner = rw.get_summoner(name=summoner_name)
            summoner_id = str(summoner['id'])
            summoner_name = summoner['name']

        except LoLException as e:
            if e == error_429:
                bot.say('Too many requests. Try again in {} seconds'.format(e.headers['Retry-After']))
                return False
            elif e == error_404:
                bot.say('The summoner not found. Use a valid summoner name (remove spaces) and region FailFish')
                return False

        try:
            summoner_league = rw.get_league_entry(summoner_ids=(summoner_id, ))

            tier = summoner_league[summoner_id][0]['tier']
            division = summoner_league[summoner_id][0]['entries'][0]['division']
            league_points = summoner_league[summoner_id][0]['entries'][0]['leaguePoints']

            bot.say('The Summoner {} on region {} is currently in {} {} with {} LP 4Head'.format(summoner_name, region.upper(), tier, division, league_points))
        except LoLException as e:
            if e == error_429:
                bot.say('Too many requests. Try again in {} seconds'.format(e.headers['Retry-After']))
                return False
            elif e == error_404:
                bot.say('The Summoner {} on region {} is currently UNRANKED.. FeelsBadMan'.format(summoner_name, region.upper()))
                return False
            else:
                bot.say('Trouble fetching summoner rank.. Kappa Try again later!')
                return False
Exemple #2
0
class LeagueRankModule(BaseModule):
    ID = __name__.split(".")[-1]
    NAME = "LeagueRank module"
    DESCRIPTION = "Enable this to check the rank of others in League of Legends in the chat."
    CATEGORY = "Feature"
    SETTINGS = [
        ModuleSetting(
            key="riot_api_key",
            label="Riot developer api key",
            type="text",
            required=True,
            placeholder="i.e. 1e3415de-1234-5432-f331-67abb0454d12",
            default="",
        ),
        ModuleSetting(
            key="default_summoner",
            label="Default summoner name",
            type="text",
            required=True,
            placeholder="i.e. moregainfreeman (remove space)",
            default="",
        ),
        ModuleSetting(
            key="default_region",
            label="Default region",
            type="text",
            required=True,
            placeholder="i.e. euw/eune/na/br/oce",
            default="",
        ),
        ModuleSetting(
            key="online_global_cd",
            label="Global cooldown (seconds)",
            type="number",
            required=True,
            placeholder="",
            default=15,
            constraints={
                "min_value": 0,
                "max_value": 120
            },
        ),
        ModuleSetting(
            key="online_user_cd",
            label="Per-user cooldown (seconds)",
            type="number",
            required=True,
            placeholder="",
            default=30,
            constraints={
                "min_value": 0,
                "max_value": 240
            },
        ),
    ]

    def load_commands(self, **options):
        self.commands["lolrank"] = Command.raw_command(
            self.league_rank,
            delay_all=self.settings["online_global_cd"],
            delay_user=self.settings["online_user_cd"],
            description=
            "Check streamer's or other players League of Legends rank in chat.",
            examples=[
                CommandExample(
                    None,
                    "Check streamer's rank",
                    chat="user:!rank\n"
                    "bot: The Summoner Moregain Freeman on region EUW is currently in PLATINUM IV with 62 LP 4Head",
                    description=
                    "Bot says broadcaster's region, League-tier, division and LP",
                ).parse(),
                CommandExample(
                    None,
                    "Check other player's rank on default region",
                    chat="user:!rank forsen\n"
                    "bot: The Summoner forsen on region EUW is currently in SILVER IV with 36 LP 4Head",
                    description=
                    "Bot says player's region, League-tier, division and LP",
                ).parse(),
                CommandExample(
                    None,
                    "Check other player's rank on another region",
                    chat="user:!rank imaqtpie na\n"
                    "bot: The Summoner Imaqtpie on region NA is currently in CHALLENGER I with 441 LP 4Head",
                    description=
                    "Bot says player's region, League-tier, division and LP. Other regions to use as arguments: euw, eune, na, oce, br, kr, las, lan, ru, tr",
                ).parse(),
            ],
        )
        self.commands["ranklol"] = self.commands["lolrank"]
        self.commands["leaguerank"] = self.commands["lolrank"]

    def league_rank(self, bot, source, message, **rest):
        try:
            from riotwatcher import RiotWatcher, LoLException
        except ImportError:
            log.error(
                "Missing required module for League Rank module: riotwatcher")
            return False

        riot_api_key = self.settings["riot_api_key"]
        summoner_name = self.settings["default_summoner"]
        def_region = self.settings["default_region"]

        if len(riot_api_key) == 0:
            log.error("Missing riot API key in settings.")
            return False

        region_list = [
            "br", "eune", "euw", "kr", "lan", "las", "na", "oce", "ru", "tr"
        ]

        if message:
            summoner_name = message.split()[0]
            try:
                region = message.split()[1].lower()
            except IndexError:
                region = def_region.lower()

            if region not in region_list:
                bot.whisper(
                    source,
                    f"Region is not valid. Please enter a valid region, region is optional and the default region is {def_region.upper()}",
                )
                return False
            else:
                pass
        else:
            region = def_region.lower()

        if len(summoner_name) == 0 or len(region) == 0:
            return False

        error_404 = "Game data not found"
        error_429 = "Too many requests"

        try:
            rw = RiotWatcher(riot_api_key, default_region=region)

            summoner = rw.get_summoner(name=summoner_name)
            summoner_id = str(summoner["id"])
            summoner_name = summoner["name"]

        except LoLException as e:
            if e == error_429:
                bot.say(
                    f"Too many requests. Try again in {e.headers['Retry-After']} seconds"
                )
                return False
            elif e == error_404:
                bot.say(
                    "The summoner not found. Use a valid summoner name (remove spaces) and region FailFish"
                )
                return False
            else:
                log.info(f"Something unknown went wrong: {e}")
                return False

        try:
            summoner_league = rw.get_league_entry(summoner_ids=(summoner_id, ))

            tier = summoner_league[summoner_id][0]["tier"]
            division = summoner_league[summoner_id][0]["entries"][0][
                "division"]
            league_points = summoner_league[summoner_id][0]["entries"][0][
                "leaguePoints"]

            bot.say(
                f"The Summoner {summoner_name} on region {region.upper()} is currently in {tier} {division} with {league_points} LP 4Head"
            )
        except LoLException as e:
            if e == error_429:
                bot.say(
                    f"Too many requests. Try again in {e.headers['Retry-After']} seconds"
                )
                return False
            elif e == error_404:
                bot.say(
                    f"The Summoner {summoner_name} on region {region.upper()} is currently UNRANKED.. FeelsBadMan"
                )
                return False
            else:
                bot.say(
                    "Trouble fetching summoner rank.. Kappa Try again later!")
                return False
Exemple #3
0
class DuelModule(BaseModule):

    ID = __name__.split('.')[-1]
    NAME = 'Duel (mini game)'
    DESCRIPTION = 'Let players duel to win or lose points.'
    CATEGORY = 'Game'
    SETTINGS = [
        ModuleSetting(key='max_pot',
                      label='How many points you can duel for at most',
                      type='number',
                      required=True,
                      placeholder='',
                      default=420,
                      constraints={
                          'min_value': 0,
                          'max_value': 69000,
                      }),
        ModuleSetting(
            key='message_won',
            label='Winner message | Available arguments: {winner}, {loser}',
            type='text',
            required=True,
            placeholder='{winner} won the duel vs {loser} PogChamp',
            default='{winner} won the duel vs {loser} PogChamp',
            constraints={
                'min_str_len': 10,
                'max_str_len': 400,
            }),
        ModuleSetting(
            key='message_won_points',
            label=
            'Points message | Available arguments: {winner}, {loser}, {total_pot}, {extra_points}',
            type='text',
            required=True,
            placeholder=
            '{winner} won the duel vs {loser} PogChamp . The pot was {total_pot}, the winner gets their bet back + {extra_points} points',
            default=
            '{winner} won the duel vs {loser} PogChamp . The pot was {total_pot}, the winner gets their bet back + {extra_points} points',
            constraints={
                'min_str_len': 10,
                'max_str_len': 400,
            }),
        ModuleSetting(key='online_global_cd',
                      label='Global cooldown (seconds)',
                      type='number',
                      required=True,
                      placeholder='',
                      default=0,
                      constraints={
                          'min_value': 0,
                          'max_value': 120,
                      }),
        ModuleSetting(key='online_user_cd',
                      label='Per-user cooldown (seconds)',
                      type='number',
                      required=True,
                      placeholder='',
                      default=5,
                      constraints={
                          'min_value': 0,
                          'max_value': 240,
                      }),
        ModuleSetting(key='show_on_clr',
                      label='Show duels on the clr overlay',
                      type='boolean',
                      required=True,
                      default=True),
    ]

    def load_commands(self, **options):
        self.commands['duel'] = pajbot.models.command.Command.raw_command(
            self.initiate_duel,
            delay_all=self.settings['online_global_cd'],
            delay_user=self.settings['online_user_cd'],
            description='Initiate a duel with a user',
            examples=[
                pajbot.models.command.CommandExample(
                    None,
                    '0-point duel',
                    chat='user:!duel Karl_Kons\n'
                    'bot>user:You have challenged Karl_Kons for 0 points',
                    description='Duel Karl_Kons for 0 points').parse(),
                pajbot.models.command.CommandExample(
                    None,
                    '69-point duel',
                    chat='user:!duel Karl_Kons 69\n'
                    'bot>user:You have challenged Karl_Kons for 69 points',
                    description='Duel Karl_Kons for 69 points').parse(),
            ],
        )
        self.commands[
            'cancelduel'] = pajbot.models.command.Command.raw_command(
                self.cancel_duel,
                delay_all=0,
                delay_user=10,
                description='Cancel your duel request')
        self.commands['accept'] = pajbot.models.command.Command.raw_command(
            self.accept_duel,
            delay_all=0,
            delay_user=0,
            description='Accept a duel request')
        self.commands['decline'] = pajbot.models.command.Command.raw_command(
            self.decline_duel,
            delay_all=0,
            delay_user=0,
            description='Decline a duel request')
        self.commands['deny'] = self.commands['decline']
        self.commands[
            'duelstatus'] = pajbot.models.command.Command.raw_command(
                self.status_duel,
                delay_all=0,
                delay_user=5,
                description='Current duel request info')
        self.commands['duelstats'] = pajbot.models.command.Command.raw_command(
            self.get_duel_stats,
            delay_all=0,
            delay_user=120,
            description='Get your duel statistics')

    def __init__(self):
        super().__init__()
        self.duel_requests = {}
        self.duel_request_price = {}
        self.duel_targets = {}

    def initiate_duel(self, **options):
        """
        Initiate a duel with a user.
        You can also bet points on the winner.
        By default, the maximum amount of points you can spend is 420.

        How to add: !add funccommand duel initiate_duel --cd 0 --usercd 5
        How to use: !duel USERNAME POINTS_TO_BET
        """

        bot = options['bot']
        source = options['source']
        message = options['message']

        if message is None:
            return False

        max_pot = self.settings['max_pot']

        msg_split = message.split()
        username = msg_split[0]
        user = bot.users.find(username)
        duel_price = 0
        if user is None:
            # No user was found with this username
            return False

        if len(msg_split) > 1:
            try:
                duel_price = int(msg_split[1])
                if duel_price < 0:
                    return False

                if duel_price > max_pot:
                    duel_price = max_pot
            except ValueError:
                pass

        if source.username in self.duel_requests:
            bot.whisper(
                source.username,
                'You already have a duel request active with {}. Type !cancelduel to cancel your duel request.'
                .format(self.duel_requests[source.username]))
            return False

        if user == source:
            # You cannot duel yourself
            return False

        if user.last_active is None or (
                datetime.datetime.now() -
                user._last_active).total_seconds() > 5 * 60:
            bot.whisper(
                source.username,
                'This user has not been active in chat within the last 5 minutes. Get them to type in chat before sending another challenge'
            )
            return False

        if not user.can_afford(duel_price) or not source.can_afford(
                duel_price):
            bot.whisper(
                source.username,
                'You or your target do not have more than {} points, therefore you cannot duel for that amount.'
                .format(duel_price))
            return False

        if user.username in self.duel_targets:
            bot.whisper(
                source.username,
                'This person is already being challenged by {}. Ask them to answer the offer by typing !deny or !accept'
                .format(self.duel_targets[user.username]))
            return False

        self.duel_targets[user.username] = source.username
        self.duel_requests[source.username] = user.username
        self.duel_request_price[source.username] = duel_price
        bot.whisper(
            user.username,
            'You have been challenged to a duel by {} for {} points. You can either !accept or !deny this challenge.'
            .format(source.username_raw, duel_price))
        bot.whisper(
            source.username, 'You have challenged {} for {} points'.format(
                user.username_raw, duel_price))

    def cancel_duel(self, **options):
        """
        Cancel any duel requests you've sent.

        How to add: !add funccomand cancelduel|duelcancel cancel_duel --cd 0 --usercd 10
        How to use: !cancelduel
        """

        bot = options['bot']
        source = options['source']

        if source.username not in self.duel_requests:
            bot.whisper(source.username, 'You have not sent any duel requests')
            return

        bot.whisper(
            source.username, 'You have cancelled the duel vs {}'.format(
                self.duel_requests[source.username]))

        del self.duel_targets[self.duel_requests[source.username]]
        del self.duel_requests[source.username]

    def accept_duel(self, **options):
        """
        Accepts any active duel requests you've received.

        How to add: !add funccommand accept accept_duel --cd 0 --usercd 0
        How to use: !accept
        """

        bot = options['bot']
        source = options['source']
        duel_tax = 0.3  # 30% tax

        if source.username not in self.duel_targets:
            bot.whisper(source.username,
                        'You are not being challenged to a duel by anyone.')
            return

        requestor = bot.users[self.duel_targets[source.username]]
        duel_price = self.duel_request_price[self.duel_targets[
            source.username]]

        if not source.can_afford(duel_price) or not requestor.can_afford(
                duel_price):
            bot.whisper(
                source.username,
                'Your duel request with {} was cancelled due to one of you not having enough points.'
                .format(requestor.username_raw))
            bot.whisper(
                requestor.username,
                'Your duel request with {} was cancelled due to one of you not having enough points.'
                .format(source.username_raw))

            del self.duel_requests[self.duel_targets[source.username]]
            del self.duel_targets[source.username]

            return False

        source.points -= duel_price
        requestor.points -= duel_price
        winning_pot = int(duel_price * (1.0 - duel_tax))
        participants = [source, requestor]
        winner = random.choice(participants)
        participants.remove(winner)
        loser = participants.pop()
        winner.points += duel_price
        winner.points += winning_pot

        winner.save()
        loser.save()

        DuelManager.user_won(winner, winning_pot)
        DuelManager.user_lost(loser, duel_price)

        arguments = {
            'winner': winner.username,
            'loser': loser.username,
            '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':
                        '{} won the duel vs {}'.format(winner.username_raw,
                                                       loser.username_raw)
                    })
        else:
            message = self.get_phrase('message_won', **arguments)
        bot.say(message)

        del self.duel_requests[self.duel_targets[source.username]]
        del self.duel_targets[source.username]

        HandlerManager.trigger('on_duel_complete', winner, loser, winning_pot,
                               duel_price)

    def decline_duel(self, **options):
        """
        Declines any active duel requests you've received.

        How to add: !add funccommand deny|decline decline_duel --cd 0 --usercd 0
        How to use: !decline
        """

        bot = options['bot']
        source = options['source']

        if source.username not in self.duel_targets:
            bot.whisper(source.username,
                        'You are not being challenged to a duel')
            return False

        requestor_username = self.duel_targets[source.username]

        bot.whisper(
            source.username,
            'You have declined the duel vs {}'.format(requestor_username))
        bot.whisper(
            requestor_username,
            '{} declined the duel challenge with you.'.format(
                source.username_raw))

        del self.duel_targets[source.username]
        del self.duel_requests[requestor_username]

    def status_duel(self, **options):
        """
        Whispers you the current status of your active duel requests/duel targets

        How to add: !add funccommand duelstatus|statusduel status_duel --cd 0 --usercd 5
        How to use: !duelstatus
        """

        bot = options['bot']
        source = options['source']

        msg = []
        if source.username in self.duel_requests:
            msg.append('You have a duel request for {} points by {}'.format(
                self.duel_request_price[source.username],
                self.duel_requests[source.username]))

        if source.username in self.duel_targets:
            msg.append(
                'You have a pending duel request from {} for {} points'.format(
                    self.duel_targets[source.username],
                    self.duel_request_price[self.duel_targets[
                        source.username]]))

        if len(msg) > 0:
            bot.whisper(source.username, '. '.join(msg))
        else:
            bot.whisper(
                source.username,
                'You have no duel request or duel target. Type !duel USERNAME POT to duel someone!'
            )

    def get_duel_stats(self, **options):
        """
        Whispers the users duel winratio to the user
        """

        bot = options['bot']
        source = options['source']

        with DBManager.create_session_scope(
                expire_on_commit=False) as db_session:
            db_session.add(source.user_model)

            if source.duel_stats is None:
                bot.whisper(source.username, 'You have no recorded duels.')
                return True

            bot.whisper(
                source.username,
                'duels: {ds.duels_total} winrate: {ds.winrate:.2f}% streak: {ds.current_streak} profit: {ds.profit}'
                .format(ds=source.duel_stats))
Exemple #4
0
class EmoteTimeoutModule(BaseModule):
    ID = __name__.split(".")[-1]
    NAME = "Emote timeout"
    DESCRIPTION = "Times out users who post emotes from Twitch, BTTV or FFZ"
    CATEGORY = "Filter"
    SETTINGS = [
        ModuleSetting(key="timeout_twitch",
                      label="Timeout any twitch emotes",
                      type="boolean",
                      required=True,
                      default=False),
        ModuleSetting(key="timeout_ffz",
                      label="Timeout any FFZ emotes",
                      type="boolean",
                      required=True,
                      default=False),
        ModuleSetting(key="timeout_bttv",
                      label="Timeout any BTTV emotes",
                      type="boolean",
                      required=True,
                      default=False),
        ModuleSetting(
            key="bypass_level",
            label="Level to bypass module",
            type="number",
            required=True,
            placeholder="",
            default=420,
            constraints={
                "min_value": 100,
                "max_value": 1000
            },
        ),
        ModuleSetting(
            key="moderation_action",
            label="Moderation action to apply",
            type="options",
            required=True,
            default="Delete",
            options=["Delete", "Timeout"],
        ),
        ModuleSetting(
            key="timeout_duration",
            label="Timeout duration (if moderation action is timeout)",
            type="number",
            required=True,
            placeholder="",
            default=5,
            constraints={
                "min_value": 3,
                "max_value": 120
            },
        ),
        ModuleSetting(key="online_chat_only",
                      label="Only enabled in online chat",
                      type="boolean",
                      required=True,
                      default=True),
    ]

    def delete_or_timeout(self, user, msg_id, reason):
        if self.settings["moderation_action"] == "Delete":
            self.bot.delete_message(msg_id)
        elif self.settings["moderation_action"] == "Timeout":
            self.bot.timeout_user_once(user, self.settings["timeout_duration"],
                                       reason)

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

        if self.settings["online_chat_only"] and not self.bot.is_online:
            return True

        if self.settings["timeout_twitch"] and any(e.emote.provider == "twitch"
                                                   for e in emote_instances):
            self.delete_or_timeout(source, msg_id, "No Twitch emotes allowed")
            return False

        if self.settings["timeout_ffz"] and any(e.emote.provider == "ffz"
                                                for e in emote_instances):
            self.delete_or_timeout(source, msg_id, "No FFZ emotes allowed")
            return False

        if self.settings["timeout_bttv"] and any(e.emote.provider == "bttv"
                                                 for e in emote_instances):
            self.delete_or_timeout(source, msg_id, "No BTTV emotes allowed")
            return False

        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)
Exemple #5
0
class PlaySoundTokenCommandModule(BaseModule):

    ID = 'tokencommand-' + __name__.split('.')[-1]
    NAME = '!playsound'
    DESCRIPTION = 'Play a sound on stream'
    PARENT_MODULE = QuestModule
    SETTINGS = [
            ModuleSetting(
                key='point_cost',
                label='Point cost',
                type='number',
                required=True,
                placeholder='Point cost',
                default=0,
                constraints={
                    'min_value': 0,
                    'max_value': 999999,
                    }),
            ModuleSetting(
                key='token_cost',
                label='Token cost',
                type='number',
                required=True,
                placeholder='Token cost',
                default=3,
                constraints={
                    'min_value': 0,
                    'max_value': 15,
                    }),
            ModuleSetting(
                key='sample_cd',
                label='Cooldown for the same sample (seconds)',
                type='number',
                required=True,
                placeholder='',
                default=20,
                constraints={
                    'min_value': 5,
                    'max_value': 120,
                    }),
            ModuleSetting(
                key='sub_only',
                label='Subscribers only',
                type='boolean',
                required=True,
                default=True),
            ]

    def __init__(self):
        super().__init__()
        self.valid_samples = Samples.valid_samples
        self.sample_cache = []

    def play_sound(self, **options):
        bot = options['bot']
        message = options['message']
        source = options['source']

        if message:
            sample = message.split(' ')[0].lower()

            if sample in self.sample_cache:
                bot.whisper(source.username, 'The sample {0} was played too recently. Please wait before trying to use it again'.format(sample))
                return False

            if sample == 'random':
                sample = random.choice(self.valid_samples.keys())

            if sample in self.valid_samples:
                log.debug('Played sound: {0}'.format(sample))
                payload = {'sample': sample}
                bot.websocket_manager.emit('play_sound', payload)
                if not (source.username == 'pajlada') or True:
                    self.sample_cache.append(sample)
                    bot.execute_delayed(self.settings['sample_cd'], self.sample_cache.remove, ('{0}'.format(sample), ))
                return True

        bot.whisper(source.username, 'Your sample is not valid. Check out all the valid samples here: https://pajbot.com/playsounds')
        return False

    def load_commands(self, **options):
        self.commands['#playsound'] = pajbot.models.command.Command.raw_command(
                self.play_sound,
                tokens_cost=self.settings['token_cost'],
                cost=self.settings['point_cost'],
                sub_only=self.settings['sub_only'],
                description='Play a sound on stream! Costs {} tokens, sub only for now.'.format(self.settings['token_cost']),
                can_execute_with_whisper=True,
                examples=[
                    pajbot.models.command.CommandExample(None, 'Play the "cumming" sample',
                        chat='user:!#playsound cumming\n'
                        'bot>user:Successfully played your sample cumming').parse(),
                    pajbot.models.command.CommandExample(None, 'Play the "fuckyou" sample',
                        chat='user:!#playsound fuckyou\n'
                        'bot>user:Successfully played your sample fuckyou').parse(),
                    ],
                )

        self.commands['#playsound'].long_description = 'Playsounds can be tried out <a href="https://pajbot.com/playsounds">here</a>'
Exemple #6
0
class DotaBetModule(BaseModule):
    AUTHOR = "DatGuy1"
    ID = __name__.split(".")[-1]
    NAME = "DotA Betting"
    DESCRIPTION = "Enables betting on DotA 2 games with !dotabet"
    CATEGORY = "Game"

    SETTINGS = [
        ModuleSetting( # Not required
            key="max_return",
            label="Maximum return odds",
            type="number",
            placeholder="",
            default="20"),
        ModuleSetting( # Not required
            key="min_return",
            label="Minimum return odds",
            type="text",
            placeholder="",
            default="1.10"),
        ModuleSetting(
            key="steam3_id",
            label="Steam 3 ID of streamer (number only)",
            type="number",
            required=True,
            placeholder="",
            default=""),
        ModuleSetting(
            key="api_key",
            label="Steam API Key",
            type="text",
            required=True,
            placeholder="",
            default=""),
    ]

    def __init__(self, bot):
        super().__init__(bot)
        self.action_queue = ActionQueue()
        self.action_queue.start()
        self.bets = {}
        self.betting_open = False
        self.message_closed = True
        self.isRadiant = False
        self.matchID = 0
        self.oldID = 0
        self.winPoints = 0
        self.lossPoints = 0
        self.winBetters = 0
        self.lossBetters = 0
        self.gettingTeam = False
        self.secondAttempt = False
        self.calibrating = True
        self.calibratingSecond = True
        self.jobPaused = False
        self.spectating = False

        self.job = ScheduleManager.execute_every(25, self.poll_webapi)
        self.job.pause()

        self.reminder_job = ScheduleManager.execute_every(
            200, self.reminder_bet)
        self.reminder_job.pause()

        # self.finish_job = ScheduleManager.execute_every(60, self.get_game)
        # self.finish_job.pause()

        # self.close_job = ScheduleManager.execute_every(1200, self.bot.websocket_manager.emit, ("dotabet_close_game", ))
        # self.close_job.pause()

    def reminder_bet(self):
        if self.betting_open:
            self.bot.me("monkaS 👉 🕒 place your bets people")
            self.bot.websocket_manager.emit(
                "notification", {
                    "message": "monkaS 👉 🕒 place your bets people"})
        else:
            if not self.message_closed:
                winRatio, lossRatio = self.get_odds_ratio(self.winPoints, self.lossPoints)
                self.bot.me("The betting for the current game has been closed! Winners can expect a {:0.2f} (win betters) or {:0.2f} (loss betters) return "
                            "ratio".format(winRatio, lossRatio))
                self.bot.websocket_manager.emit(
                    "notification", {
                        "message": "The betting for the current game has been closed!"})
                if not self.spectating:
                    self.bot.execute_delayed(
                        15, self.bot.websocket_manager.emit, ("dotabet_close_game", ))
                self.message_closed = True

    def reinit_params(self):
        self.winBetters = 0
        self.winPoints = 0
        self.lossBetters = 0
        self.lossPoints = 0

    def get_odds_ratio(self, winPoints, lossPoints):
        solveFormula = lambda x,y: 1.+ (float(x) / (float(y)))
        winRatio = solveFormula(lossPoints, winPoints)
        lossRatio = solveFormula(winPoints, lossPoints)

        ratioList = [winRatio, lossRatio]

        for c, curRatio in enumerate(ratioList):
            if self.maxReturn and curRatio > self.maxReturn:
                ratioList[c] = self.maxReturn
            if self.minReturn and curRatio < self.minReturn:
                ratioList[c] = self.minReturn

        return tuple(ratioList)

    def spread_points(self, gameResult):
        winners = 0
        losers = 0
        total_winnings = 0
        total_losings = 0

        if gameResult == "win":
            solveFormula = self.get_odds_ratio(self.winPoints, self.lossPoints)[0]
        else:
            solveFormula = self.get_odds_ratio(self.winPoints, self.lossPoints)[1]

        with DBManager.create_session_scope() as db_session:
            db_bets = {}
            for username in self.bets:
                bet_for_win, betPoints = self.bets[username]
                points = int(betPoints * solveFormula)

                user = self.bot.users.find(username, db_session=db_session)
                if user is None:
                    continue

                correct_bet = (
                    gameResult == "win" and bet_for_win is True) or (
                    gameResult == "loss" and bet_for_win is False)

                db_bets[username] = DotaBetBet(
                    user.id, "win" if bet_for_win else "loss", betPoints, 0)

                if correct_bet:
                    winners += 1
                    total_winnings += points - betPoints
                    db_bets[username].profit = points
                    user.points += points
                    self.bot.whisper(
                        user.username,
                        "You bet {} points on the correct outcome and gained an extra {} points, "
                        "you now have {} points PogChamp".format(
                            betPoints,
                            points - betPoints,
                            user.points))
                else:
                    losers += 1
                    total_losings += betPoints
                    db_bets[username].profit = -betPoints
                    self.bot.whisper(
                        user.username,
                        "You bet {} points on the wrong outcome, so you lost it all. :(".format(betPoints))

        startString = "The game ended as a {}. {} users won an extra {} points, while {}" \
            " lost {} points. Winners can expect a {:0.2f} return ratio.".format(gameResult, winners, total_winnings,
                                                                                 losers, total_losings, solveFormula)

        if self.spectating:
            resultString = startString[:20] + "radiant " + startString[20:]
        else:
            resultString = startString

        # for username in db_bets:
        #     bet = db_bets[username]
        #     db_session.add(bet)
        #     db_session.commit()

        # self.bets = {}
        self.betting_open = False
        self.message_closed = True
        self.reinit_params()

        bet_game = DotaBetGame(
            gameResult,
            total_winnings -
            total_losings,
            winners,
            losers)

        db_session.add(bet_game)

        self.bot.websocket_manager.emit(
            "notification", {
                "message": resultString, "length": 8})
        self.bot.me(resultString)

    def get_game(self):
        gameResult = "loss"
        # log.debug(self.isRadiant)

        odURL = "https://api.opendota.com/api/players/{}/recentMatches".format(
            self.settings["steam3_id"])
        gameHistory = requests.get(odURL).json()[0]

        if gameHistory["match_id"] != self.matchID:
            self.matchID = gameHistory["match_id"]

            if self.calibrating:
                self.calibrating = False
                return

            if self.isRadiant and gameHistory["radiant_win"]:
                gameResult = "win"
            else:
                if not self.isRadiant and not gameHistory["radiant_win"]:
                    gameResult = "win"
                else:
                    gameResult = "loss"
            # log.error(gameResult)
            self.spread_points(gameResult)

    def poll_webapi(self):
        serverID = ""

        with open("/srv/admiralbullbot/configs/currentID.txt", "r") as f:
            serverID = f.read()

        try:
            serverID = int(serverID)
        except ValueError:
            return False

        if self.calibratingSecond and serverID != 0:
            self.calibratingSecond = False
            return False

        if serverID == 0:
            self.bot.execute_delayed(100, self.close_shit)
            return False

        if self.oldID == serverID:
            return False

        self.oldID = serverID

        self.bot.execute_delayed(12, self.get_team, (serverID, ))

    def startGame(self):
        if not self.betting_open:
            self.bets = {}
            self.betting_open = True
            self.message_closed = False
            self.bot.websocket_manager.emit("dotabet_new_game")

        bulldogTeam = "radiant" if self.isRadiant else "dire"
        openString = "A new game has begun! Bulldog is on {}. Vote with !dotabet win/lose POINTS".format(
            bulldogTeam)

        self.bot.websocket_manager.emit(
            "notification", {"message": openString})
        # self.bot.websocket_manager.emit("dotabet_new_game")

        self.bot.me(openString)

    def get_team(self, serverID):
        attempts = 0
        if not serverID:
            return

        webURL = "https://api.steampowered.com/IDOTA2MatchStats_570/GetRealtimeStats/v1?" \
                 "server_steam_id={}&key={}".format(serverID, self.settings["api_key"])
        jsonText = requests.get(webURL).json()

        try:
            while not jsonText:  # Could bug and not return anything
                if attempts > 60:
                    if not self.secondAttempt:
                        self.bot.execute_delayed(
                            20, self.get_team, (serverID, ))
                        self.secondAttempt = True
                    else:
                        self.bot.say(
                            "Couldn\"t find which team Bulldog is on for this game. Mods - handle this round manually :)")
                        self.job.pause()
                        self.jobPaused = True
                        self.secondAttempt = False

                    attempts = 0
                    return

                attempts += 1
                jsonText = requests.get(webURL).json()
                log.debug(jsonText)

                try:
                    self.gettingTeam = True
                    for i in range(2):
                        for player in jsonText["teams"][i]["players"]:
                            log.debug(player["name"])
                            if player["accountid"] == self.settings["steam3_id"]:
                                if i == 0:
                                    self.isRadiant = True
                                else:
                                    self.isRadiant = False
                                self.bot.me(
                                    "Is bulldog on radiant? {}. If he isn\"t then tag a mod with BabyRage fix "
                                    "bet".format(
                                        self.isRadiant))
                                raise ExitLoop
                except KeyError:
                    jsonText = ""

        except ExitLoop:
            pass

        self.gettingTeam = False
        self.betting_open = True
        self.secondAttempt = False
        self.startGame()

    def command_open(self, **options):
        openString = "Betting has been opened"
        bot = options["bot"]
        message = options["message"]
        self.calibrating = True

        if message:
            if "dire" in message:
                self.isRadiant = False
            elif "radi" in message:
                self.isRadiant = True
            elif "spectat" in message:
                self.isRadiant = True
                self.spectating = True
                openString += ". Reminder to bet with radiant/dire instead of win/loss"
                self.calibrating = False

        if not self.betting_open:
            bot.websocket_manager.emit("notification", {"message": openString})
            if not self.spectating:
                bot.websocket_manager.emit("dotabet_new_game")
            bot.me(openString)

        self.betting_open = True
        self.message_closed = False
        self.job.pause()
        self.jobPaused = True

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

        bot.say(
            "{}/{} betters on {}/{} points".format(self.winBetters, self.lossBetters, self.winPoints, self.lossPoints))

    def close_shit(self):
        if self.jobPaused:
            return False

        self.betting_open = False
        self.reminder_bet()

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

        if self.betting_open:
            count_down = 15
            if message and message.isdigit():
                count_down = int(message)
            if count_down > 0:
                bot.me("Betting will be locked in {} seconds! Place your bets people monkaS".format(count_down))
            bot.execute_delayed(count_down, self.lock_bets, (bot,))
        elif message:
            if "l" in message.lower() or "dire" in message.lower():
                self.spread_points("loss")
            elif "w" in message.lower() or "radi" in message.lower():
                self.spread_points("win")
            else:
                bot.whisper(source.username, "Are you pretending?")
                return False

            self.calibrating = True
            self.spectating = False

    def lock_bets(self, bot):
        self.betting_open = False
        self.reminder_bet()
        self.job.resume()
        self.jobPaused = False

    def command_restart(self, **options):
        bot = options["bot"]
        message = options["message"]
        source = options["source"]
        reason = ""

        if not message:
            reason = "No reason given EleGiggle"
        else:
            reason = message

        with DBManager.create_session_scope() as db_session:
            for username in self.bets:
                bet_for_win, betPoints = self.bets[username]
                user = self.bot.users.find(username, db_session=db_session)
                if not user:
                    continue

                user.points += betPoints
                bot.whisper(
                    user.username,
                    "Your {} points bet has been refunded. The reason given is: "
                    "{}".format(
                        betPoints,
                        reason))

        self.bets = {}
        self.betting_open = False
        self.message_closed = True

        self.winBetters = 0
        self.lossBetters = 0

        bot.me("All your bets have been refunded and betting has been restarted.")

    def command_resetbet(self, **options):
        self.bets = {}
        options["bot"].me("The bets have been reset :)")
        self.winBetters = 0
        self.lossBetters = 0

    def command_betstatus(self, **options):
        bot = options["bot"]
        if self.betting_open:
            bot.say("Betting is open")
        elif self.winBetters > 0 or self.lossBetters > 0:
            bot.say("There is currently a bet with points not given yet")
        else:
            bot.say("There is no bet running")

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

        if message is None:
            return False

        if not self.betting_open:
            bot.whisper(
                source.username,
                "Betting is not currently open. Wait until the next game :\\")
            return False

        msg_parts = message.split(" ")
        if len(msg_parts) < 2:
            bot.whisper(
                source.username,
                "Invalid bet. You must do !dotabet radiant/dire POINTS (if spectating a game) "
                "or !dotabet win/loss POINTS (if playing)")
            return False

        if source.username in self.bets:
            bot.whisper(
                source.username,
                "You have already bet on this game. Wait until the next game starts!")
            return False

        points = 0
        try:
            points = utils.parse_points_amount(source, msg_parts[1])
            if points > 1500:
                points = 1500
        except InvalidPointAmount as e:
            bot.whisper(
                source.username,
                "Invalid bet. You must do !dotabet radiant/dire POINTS (if spectating a game) "
                "or !dotabet win/loss POINTS (if playing) {}".format(e))
            return False

        if points < 1:
            bot.whisper(source.username, "You can't bet less than 1 point you goddamn pleb Bruh")
            return False

        if not source.can_afford(points):
            bot.whisper(
                source.username,
                "You don't have {} points to bet".format(points))
            return False

        outcome = msg_parts[0].lower()
        bet_for_win = False

        if "w" in outcome or "radi" in outcome:
            bet_for_win = True

        elif "l" in outcome or "dire" in outcome:
            bet_for_win = False
        else:
            bot.whisper(
                source.username,
                "Invalid bet. You must do !dotabet radiant/dire POINTS (if spectating a game) "
                "or !dotabet win/loss POINTS (if playing)")
            return False

        if bet_for_win:
            self.winBetters += 1
            self.winPoints += points
        else:
            self.lossBetters += 1
            self.lossPoints += points


        source.points -= points
        self.bets[source.username] = (bet_for_win, points)

        payload = {
            "win_betters": self.winBetters,
            "loss_betters": self.lossBetters,
            "win_points": self.winPoints,
            "loss_points": self.lossPoints
        }

        if not self.spectating:
            bot.websocket_manager.emit("dotabet_update_data", data=payload)

        finishString = "You have bet {} points on this game resulting in a ".format(
            points)
        if self.spectating:
            finishString = finishString + "radiant "

        bot.whisper(
            source.username,
            "{}{}".format(
                finishString,
                "win" if bet_for_win else "loss"))

    def load_commands(self, **options):
        self.commands["dotabet"] = Command.raw_command(
            self.command_bet,
            delay_all=0,
            delay_user=0,
            can_execute_with_whisper=True,
            description="Bet points",
            examples=[
                CommandExample(
                    None,
                    "Bet 69 points on a win",
                    chat="user:!dotabet win 69\n"
                    "bot>user: You have bet 69 points on this game resulting in a win.",
                    description="Bet that the streamer will win for 69 points").parse(),
            ],
        )
        self.commands["bet"] = self.commands["dotabet"]

        self.commands["openbet"] = Command.raw_command(
            self.command_open,
            level=420,
            delay_all=0,
            delay_user=0,
            can_execute_with_whisper=True,
            description="Open bets",
        )
        self.commands["restartbet"] = Command.raw_command(
            self.command_restart,
            level=420,
            delay_all=0,
            delay_user=0,
            can_execute_with_whisper=True,
            description="Restart bets",
        )
        self.commands["closebet"] = Command.raw_command(
            self.command_close,
            level=420,
            delay_all=0,
            delay_user=0,
            can_execute_with_whisper=True,
            description="Close bets",
        )
        self.commands["resetbet"] = Command.raw_command(
            self.command_resetbet,
            level=500,
            can_execute_with_whisper=True,
            description="Reset bets",
        )
        self.commands["betstatus"] = Command.raw_command(
            self.command_betstatus,
            level=420,
            can_execute_with_whisper=True,
            description="Status of bets",
        )
        self.commands["currentbets"] = Command.raw_command(
            self.command_stats,
            level=100,
            delay_all=0,
            delay_user=10,
            can_execute_with_whisper=True,
        )
        # self.commands["betstats"]

    def enable(self, bot):
        if bot:
            self.job.resume()
            self.reminder_job.resume()
            # self.finish_job.resume()
            # self.close_job.resume()
        self.bot = bot

        # Move this to somewhere better
        self.maxReturn = self.settings["max_return"] if "max_return" in self.settings else None
        self.minReturn = float(self.settings["min_return"]) if "min_return" in self.settings else None

    def disable(self, bot):
        if bot:
            self.job.pause()
            self.reminder_job.pause()
Exemple #7
0
class BingoModule(BaseModule):

    ID = __name__.split('.')[-1]
    NAME = 'Bingo Games'
    DESCRIPTION = 'Chat Bingo Game for Twitch and BTTV Emotes'
    ENABLED_DEFAULT = False
    CATEGORY = 'Game'
    SETTINGS = [
            ModuleSetting(
                key='max_points',
                label='Max points for a bingo',
                type='number',
                required=True,
                placeholder='',
                default=3000,
                constraints={
                    'min_value': 0,
                    'max_value': 35000,
                    }),
            ModuleSetting(
                key='allow_negative_bingo',
                label='Allow negative bingo',
                type='boolean',
                required=True,
                default=True),
            ModuleSetting(
                key='max_negative_points',
                label='Max negative points for a bingo',
                type='number',
                required=True,
                placeholder='',
                default=1500,
                constraints={
                    'min_value': 1,
                    'max_value': 35000,
                    })
            ]

    def __init__(self):
        super().__init__()
        self.bot = None

        self.bingo_running = False
        self.bingo_bttv_twitch_running = False

    def load_commands(self, **options):
        self.commands['bingo'] = Command.multiaction_command(
                level=500,
                default=None,
                command='bingo',
                commands={
                    'emotes': Command.raw_command(self.bingo_emotes,
                        level=500,
                        delay_all=15,
                        delay_user=15,
                        description='Start an emote bingo with BTTV and TWITCH global emotes',
                        examples=[
                            CommandExample(None, 'Emote bingo for 100 points',
                                chat='user:!bingo emotes\n'
                                'bot: A bingo has started! Guess the right target to win 100 points! Only one target per message! ',
                                description='').parse(),
                            CommandExample(None, 'Emote bingo for 222 points',
                                chat='user:!bingo emotes 222\n'
                                'bot: A bingo has started! Guess the right target to win 222 points! Only one target per message! ',
                                description='').parse(),
                            ]),
                    'bttv': Command.raw_command(self.bingo_bttv,
                        level=500,
                        delay_all=15,
                        delay_user=15,
                        description='Start an emote bingo with BTTV global emotes',
                        examples=[
                            CommandExample(None, 'Emote bingo for 100 points',
                                chat='user:!bingo bttv\n'
                                'bot: A bingo has started! Guess the right target to win 100 points! Only one target per message! Use BTTV global emotes. ',
                                description='').parse(),
                            CommandExample(None, 'Emote bingo for 222 points',
                                chat='user:!bingo bttv 222\n'
                                'bot: A bingo has started! Guess the right target to win 222 points! Only one target per message! Use BTTV global emotes. ',
                                description='').parse(),
                            ]),
                    'twitch': Command.raw_command(self.bingo_twitch,
                        level=500,
                        delay_all=15,
                        delay_user=15,
                        description='Start an emote bingo with TWITCH global emotes',
                        examples=[
                            CommandExample(None, 'Emote bingo for 100 points',
                                chat='user:!bingo twitch\n'
                                'bot: A bingo has started! Guess the right target to win 100 points! Only one target per message! Use TWITCH global emotes. ',
                                description='').parse(),
                            CommandExample(None, 'Emote bingo for 222 points',
                                chat='user:!bingo twitch 222\n'
                                'bot: A bingo has started! Guess the right target to win 222 points! Only one target per message! Use TWITCH global emotes. ',
                                description='').parse(),
                            ]),
                    'cancel': Command.raw_command(self.bingo_cancel,
                        level=500,
                        delay_all=15,
                        delay_user=15,
                        description='Cancel a running bingo',
                        examples=[
                            CommandExample(None, 'Cancel a bingo',
                                chat='user:!bingo cancel\n'
                                'bot: Bingo cancelled by pajlada FeelsBadMan',
                                description='').parse(),
                            ]),
                    'help': Command.raw_command(self.bingo_help_random,
                        level=500,
                        delay_all=15,
                        delay_user=15,
                        description='The bot will help the chat with a random letter from the bingo target',
                        examples=[
                            CommandExample(None, 'Get a random letter from the bingo target',
                                chat='user:!bingo help\n'
                                'bot: A bingo for 100 points is still running. You should maybe use a a a a a for the target',
                                description='').parse(),
                            ]),
                    'cheat': Command.raw_command(self.bingo_help_first,
                        level=500,
                        delay_all=15,
                        delay_user=15,
                        description='The bot will help the chat with the first letter from the bingo target',
                        examples=[
                            CommandExample(None, 'Get the first letter from the bingo target',
                                chat='user:!bingo cheat\n'
                                'bot: A bingo for 100 points is still running. You should use W W W W W as the first letter for the target',
                                description='').parse(),
                            ]),
                    })

    def set_bingo_target(self, target, bingo_points_win):
        self.bingo_target = target
        self.bingo_running = True
        self.bingo_points = bingo_points_win
        log.debug('Bingo target set: {0} for {1} points'.format(target, bingo_points_win))

    def set_bingo_target_bttv(self, target_bttv, bingo_points_win):
        self.bingo_target = target_bttv
        self.bingo_running = True
        self.bingo_points = bingo_points_win
        log.debug('Bingo Bttv target set: {0} for {1} points'.format(target_bttv, bingo_points_win))

    def bingo_emotes(self, bot, source, message, event, args):
        """ Twitch and BTTV emotes """
        if hasattr(self, 'bingo_running') and self.bingo_running is True:
            bot.me('{0}, a bingo is already running OMGScoots'.format(source.username_raw))
            return False

        self.bingo_bttv_twitch_running = True
        start_random_emote_bingo = random.choice(['1', '2'])
        if start_random_emote_bingo == '1':
            return self.bingo_twitch(bot, source, message, event, args)
        elif start_random_emote_bingo == '2':
            return self.bingo_bttv(bot, source, message, event, args)

    def bingo_bttv(self, bot, source, message, event, args):
        """ BTTV emotes """
        if hasattr(self, 'bingo_running') and self.bingo_running is True:
            bot.me('{0}, a bingo is already running OMGScoots'.format(source.username_raw))
            return False

        bingo_points_win = 100

        try:
            if message is not None and self.settings['allow_negative_bingo'] is True:
                bingo_points_win = int(message.split()[0])
            if message is not None and self.settings['allow_negative_bingo'] is False:
                if int(message.split()[0]) >= 0:
                    bingo_points_win = int(message.split()[0])
        except (IndexError, TypeError, ValueError):
            pass

        if bingo_points_win >= 0:
            bingo_points_win = min(bingo_points_win, self.settings['max_points'])
        if bingo_points_win <= -1:
            bingo_points_win = max(bingo_points_win, -self.settings['max_negative_points'])

        self.emotes_bttv = bot.emotes.get_global_bttv_emotes()

        target_bttv = random.choice(self.emotes_bttv)
        self.set_bingo_target_bttv(target_bttv, bingo_points_win)
        if hasattr(self, 'bingo_bttv_twitch_running') and self.bingo_bttv_twitch_running is True:
            bot.me('A bingo has started! Guess the right target to win {0} points! Only one target per message! Use BTTV and TWITCH global emotes.'.format(bingo_points_win))
            bot.websocket_manager.emit('notification', {'message': 'A bingo has started!'})
            bot.execute_delayed(0.75, bot.websocket_manager.emit, ('notification', {'message': 'Guess the target, win the prize!'}))
            return True
        else:
            bot.me('A bingo has started! Guess the right target to win {0} points! Only one target per message! Use BTTV global emotes.'.format(bingo_points_win))
            bot.websocket_manager.emit('notification', {'message': 'A bingo has started!'})
            bot.execute_delayed(0.75, bot.websocket_manager.emit, ('notification', {'message': 'Guess the target, win the prize!'}))
            return False

    def bingo_twitch(self, bot, source, message, event, args):
        """ Twitch emotes """
        if hasattr(self, 'bingo_running') and self.bingo_running is True:
            bot.me('{0}, a bingo is already running OMGScoots'.format(source.username_raw))
            return False

        bingo_points_win = 100

        try:
            if message is not None and self.settings['allow_negative_bingo'] is True:
                bingo_points_win = int(message.split()[0])
            if message is not None and self.settings['allow_negative_bingo'] is False:
                if int(message.split()[0]) >= 0:
                    bingo_points_win = int(message.split()[0])
        except (IndexError, TypeError, ValueError):
            pass

        if bingo_points_win >= 0:
            bingo_points_win = min(bingo_points_win, self.settings['max_points'])
        if bingo_points_win <= -1:
            bingo_points_win = max(bingo_points_win, -self.settings['max_negative_points'])

        self.emotes = bot.emotes.get_global_emotes()

        target = random.choice(self.emotes)
        self.set_bingo_target(target, bingo_points_win)
        if hasattr(self, 'bingo_bttv_twitch_running') and self.bingo_bttv_twitch_running is True:
            bot.me('A bingo has started! Guess the right target to win {0} points! Only one target per message! Use BTTV and TWITCH global emotes.'.format(bingo_points_win))
            bot.websocket_manager.emit('notification', {'message': 'A bingo has started!'})
            bot.execute_delayed(0.75, bot.websocket_manager.emit, ('notification', {'message': 'Guess the target, win the prize!'}))
            return True
        else:
            bot.me('A bingo has started! Guess the right target to win {0} points! Only one target per message! Use TWITCH global emotes.'.format(bingo_points_win))
            bot.websocket_manager.emit('notification', {'message': 'A bingo has started!'})
            bot.execute_delayed(0.75, bot.websocket_manager.emit, ('notification', {'message': 'Guess the target, win the prize!'}))
            return False

    def bingo_cancel(self, bot, source, message, event, args):
        """ cancel a bingo """
        if hasattr(self, 'bingo_running') and self.bingo_running is True:
            bot.me('Bingo cancelled by {0} FeelsBadMan'.format(source.username_raw))
            log.debug('Bingo cancelled by {0}'.format(source.username_raw))
            self.bingo_running = False
            self.bingo_bttv_twitch_running = False
            return True
        else:
            bot.me('{0}, no bingo is currently running FailFish'.format(source.username_raw))
            return False

    def bingo_help_random(self, bot, source, message, event, args):
        """ Random letter of the target """
        if hasattr(self, 'bingo_running') and self.bingo_running is True:
            target_split_random = random.choice(list(self.bingo_target.lower()))
            bot.me('A bingo for {0} points is still running. You should maybe use the letter {1} {1} {1} {1} {1} for the target'.format(self.bingo_points, target_split_random))
            log.debug('Bingo help: {0}'.format(target_split_random))
            return True
        else:
            bot.me('{0}, no bingo is currently running FailFish'.format(source.username_raw))
            return False

    def bingo_help_first(self, bot, source, message, event, args):
        """ First letter of the target """
        if hasattr(self, 'bingo_running') and self.bingo_running is True:
            target_first_letter = ' '.join(list(self.bingo_target)[:1])
            bot.me('A bingo for {0} points is still running. You should use {1} {1} {1} {1} {1} as the first letter for the target'.format(self.bingo_points, target_first_letter))
            log.debug('Bingo help: {0}'.format(target_first_letter))
            return True
        else:
            bot.me('{0}, no bingo is currently running FailFish'.format(source.username_raw))
            return False

    def on_message(self, source, msg_raw, message_emotes, whisper, urls, event):
        if len(message_emotes) > 0:
            if hasattr(self, 'bingo_running') and self.bingo_running is True:
                if len(message_emotes) == 1 and len(msg_raw.split(' ')) == 1:
                    if message_emotes[0]['code'] == self.bingo_target:
                        HandlerManager.trigger('on_bingo_win', source, self.bingo_points, self.bingo_target)
                        self.bingo_running = False
                        self.bingo_bttv_twitch_running = False
                        self.bot.me('{0} won the bingo! {1} was the target. Congrats, {2} points to you PogChamp'.format(source.username_raw, self.bingo_target, self.bingo_points))
                        source.points += self.bingo_points
                        log.info('{0} won the bingo for {1} points!'.format(source.username_raw, self.bingo_points))

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

        self.bot = bot

    def disable(self, bot):
        HandlerManager.remove_handler('on_message', self.on_message)
Exemple #8
0
class BingoModule(BaseModule):
    ID = __name__.split(".")[-1]
    NAME = "Bingo"
    DESCRIPTION = "Chat Bingo Game for Twitch, FFZ, BTTV and 7TV Emotes"
    ENABLED_DEFAULT = False
    CATEGORY = "Game"
    SETTINGS = [
        ModuleSetting(
            key="default_points",
            label="Defaults points reward for a bingo",
            type="number",
            required=True,
            placeholder="",
            default=100,
            constraints={"min_value": 0, "max_value": 35000},
        ),
        ModuleSetting(
            key="max_points",
            label="Max points for a bingo",
            type="number",
            required=True,
            placeholder="",
            default=3000,
            constraints={"min_value": 0, "max_value": 35000},
        ),
        ModuleSetting(
            key="allow_negative_bingo", label="Allow negative bingo", type="boolean", required=True, default=True
        ),
        ModuleSetting(
            key="max_negative_points",
            label="Max negative points for a bingo",
            type="number",
            required=True,
            placeholder="",
            default=1500,
            constraints={"min_value": 1, "max_value": 35000},
        ),
    ]

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

    @property
    def bingo_running(self):
        return self.active_game is not None

    @staticmethod
    def make_twitch_sets(manager):
        tier_one_emotes = ("Tier 1 sub emotes", manager.tier_one_emotes)
        tier_two_emotes = ("Tier 2 sub emotes", manager.tier_two_emotes)
        tier_three_emotes = ("Tier 3 sub emotes", manager.tier_three_emotes)
        global_emotes = ("Global Twitch emotes", manager.global_emotes)
        all_emotes = ("Global + Twitch tier 1 sub emotes", manager.tier_one_emotes + manager.global_emotes)
        return {
            "twitch": tier_one_emotes,
            "sub": tier_one_emotes,
            "tier1": tier_one_emotes,
            "tier2": tier_two_emotes,
            "tier3": tier_three_emotes,
            **two_word_variations("twitch", "sub", tier_one_emotes),
            **two_word_variations("twitch", "tier1", tier_one_emotes),
            **two_word_variations("twitch", "tier2", tier_two_emotes),
            **two_word_variations("twitch", "tier3", tier_three_emotes),
            **two_word_variations("twitch", "global", global_emotes),
            **two_word_variations("twitch", "channel", tier_one_emotes),
            **two_word_variations("twitch", "all", all_emotes),
        }

    @staticmethod
    def make_bttv_ffz_7tv_sets(manager):
        friendly_name = manager.friendly_name
        channel_emotes = (f"Channel {friendly_name} emotes", manager.channel_emotes)
        global_emotes = (f"Global {friendly_name} emotes", manager.global_emotes)
        all_emotes = (f"Global + Channel {friendly_name} emotes", manager.channel_emotes + manager.global_emotes)

        key = friendly_name.lower()
        return {
            key: channel_emotes,
            **two_word_variations(key, "global", global_emotes),
            **two_word_variations(key, "channel", channel_emotes),
            **two_word_variations(key, "all", all_emotes),
        }

    def make_known_sets_dict(self):
        # we first make a dict containing lists as the list of emotes (because it's less to type...)
        list_dict = {
            **self.make_twitch_sets(self.bot.emote_manager.twitch_emote_manager),
            **self.make_bttv_ffz_7tv_sets(self.bot.emote_manager.ffz_emote_manager),
            **self.make_bttv_ffz_7tv_sets(self.bot.emote_manager.bttv_emote_manager),
            **self.make_bttv_ffz_7tv_sets(self.bot.emote_manager.seventv_emote_manager),
            "all": (
                "FFZ, BTTV and 7TV Channel emotes + Tier 1 subemotes",
                self.bot.emote_manager.ffz_emote_manager.channel_emotes
                + self.bot.emote_manager.bttv_emote_manager.channel_emotes
                + self.bot.emote_manager.seventv_emote_manager.channel_emotes
                + self.bot.emote_manager.twitch_emote_manager.tier_one_emotes,
            ),
        }

        # then convert all the lists to tuples so they are hashable
        # and can be stored in a set of "selected sets" later
        return {key: (set_name, tuple(set_emotes), False) for key, (set_name, set_emotes) in list_dict.items()}

    def bingo_start(self, bot, source, message, event, args):
        if self.bingo_running:
            bot.say(f"{source}, a bingo is already running FailFish")
            return False

        emote_instances = args["emote_instances"]
        known_sets = self.make_known_sets_dict()

        selected_sets = set()
        points_reward = None
        unparsed_options = []

        words_in_message = [s for s in message.split(" ") if len(s) > 0]
        if len(words_in_message) <= 0:
            bot.say(f"{source}, You must at least give me some emote sets or emotes to choose from! FailFish")
            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

            # Is the current word a number?
            try:
                parsed_int = int(word)
            except ValueError:
                parsed_int = None

            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.say(
                f"{source}, You can't start a bingo with that many points. FailFish {max_points} are allowed at most."
            )
            return False

        allow_negative_bingo = self.settings["allow_negative_bingo"]
        if points_reward < 0 and not allow_negative_bingo:
            bot.say(f"{source}, You can't start a bingo with negative points. FailFish")
            return False

        min_points = -self.settings["max_negative_points"]
        if points_reward < min_points:
            bot.say(
                f"{source}, You can't start a bingo with that many negative points. FailFish {min_points} are allowed at most."
            )
            return False

        if len(selected_sets) <= 0:
            bot.say(f"{source}, You must at least give me some emotes or emote sets to choose from! FailFish")
            return False

        selected_set_names = []
        selected_discrete_emote_codes = []
        selected_emotes = 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)

    def bingo_cancel(self, bot, source, message, event, args):
        if not self.bingo_running:
            bot.say(f"{source}, no bingo is running FailFish")
            return False

        self.active_game = None
        bot.me(f"Bingo cancelled by {source} FeelsBadMan")

    def bingo_help_random(self, bot, source, message, event, args):
        if not self.bingo_running:
            bot.say(f"{source}, no bingo is running FailFish")
            return False

        correct_emote_code = self.active_game.correct_emote.code
        random_letter = random.choice(correct_emote_code)

        bot.me(
            f"A bingo for {self.active_game.points_reward} points is still running. You should maybe use {random_letter} {random_letter} {random_letter} {random_letter} {random_letter} for the target"
        )

    def bingo_help_first(self, bot, source, message, event, args):
        if not self.bingo_running:
            bot.say(f"{source}, no bingo is running FailFish")
            return False

        correct_emote_code = self.active_game.correct_emote.code
        first_letter = correct_emote_code[0]

        bot.me(
            f"A bingo for {self.active_game.points_reward} points is still running. You should maybe use {first_letter} {first_letter} {first_letter} {first_letter} {first_letter} for the target"
        )

    def on_message(self, source, message, emote_instances, **rest):
        if not self.bingo_running:
            return

        if len(emote_instances) != 1:
            return

        correct_emote = self.active_game.correct_emote
        correct_emote_code = correct_emote.code

        typed_emote = emote_instances[0].emote
        typed_emote_code = typed_emote.code

        # we check for BOTH exact match (which works by comparing provider and ID, see __eq__ and __hash__ in
        # the Emote class) and for code-only match because we want to allow equal-named sub and ffz/bttv/7tv emotes
        # to be treated equally (e.g. sub-emote pajaL vs bttv emote pajaL)
        # The reason exact match can differ from code match is in case of regex twitch emotes, such as :) and :-)
        # If the "correct_emote" was chosen from the list of global twitch emotes, then its code will be the regex
        # for the emote (If the bingo was started by specifying :) as an explicit emote, then the code will be
        # :)). To make sure we don't trip on this we only compare by provider and provider ID.
        exact_match = correct_emote == typed_emote
        only_code_match = correct_emote_code == typed_emote_code
        if not (exact_match or only_code_match):
            return

        # user guessed the emote
        HandlerManager.trigger("on_bingo_win", source, self.active_game)
        points_reward = self.active_game.points_reward
        source.points += points_reward
        self.active_game = None

        self.bot.me(
            f"{source} won the bingo! {correct_emote_code} was the target. Congrats, {points_reward} points to you PogChamp"
        )

    def load_commands(self, **options):
        self.commands["bingo"] = Command.multiaction_command(
            level=500,
            default=None,
            command="bingo",
            commands={
                "start": Command.raw_command(
                    self.bingo_start,
                    level=500,
                    delay_all=15,
                    delay_user=15,
                    description="Start an emote bingo with specified emote sets",
                    examples=[
                        CommandExample(
                            None,
                            "Emote bingo for default points",
                            chat="user:!bingo start bttv\n"
                            "bot: A bingo has started! Guess the right target to win 100 points! "
                            "Only one target per message! Select from Channel BTTV Emotes!",
                            description="",
                        ).parse(),
                        CommandExample(
                            None,
                            "Emote bingo for 222 points",
                            chat="user:!bingo start bttv 222\n"
                            "bot: A bingo has started! Guess the right target to win 222 points! "
                            "Only one target per message! Select from Channel BTTV Emotes!",
                            description="",
                        ).parse(),
                    ],
                ),
                "cancel": Command.raw_command(
                    self.bingo_cancel,
                    level=500,
                    delay_all=15,
                    delay_user=15,
                    description="Cancel a running bingo",
                    examples=[
                        CommandExample(
                            None,
                            "Cancel a bingo",
                            chat="user:!bingo cancel\n" "bot: Bingo cancelled by pajlada FeelsBadMan",
                            description="",
                        ).parse()
                    ],
                ),
                "help": Command.raw_command(
                    self.bingo_help_random,
                    level=500,
                    delay_all=15,
                    delay_user=15,
                    description="The bot will help the chat with a random letter from the bingo target",
                    examples=[
                        CommandExample(
                            None,
                            "Get a random letter from the bingo target",
                            chat="user:!bingo help\n"
                            "bot: A bingo for 100 points is still running. You should maybe use a a a a a for the target",
                            description="",
                        ).parse()
                    ],
                ),
                "cheat": Command.raw_command(
                    self.bingo_help_first,
                    level=500,
                    delay_all=15,
                    delay_user=15,
                    description="The bot will help the chat with the first letter from the bingo target",
                    examples=[
                        CommandExample(
                            None,
                            "Get the first letter from the bingo target",
                            chat="user:!bingo cheat\n"
                            "bot: A bingo for 100 points is still running. You should use W W W W W as the first letter for the target",
                            description="",
                        ).parse()
                    ],
                ),
            },
        )

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

    def disable(self, bot):
        HandlerManager.remove_handler("on_message", self.on_message)
Exemple #9
0
class MassPingProtectionModule(BaseModule):

    ID = __name__.split(".")[-1]
    NAME = "Mass Ping Protection"
    DESCRIPTION = "Times out users who post messages that mention too many users at once."
    CATEGORY = "Filter"
    SETTINGS = [
        ModuleSetting(
            key="max_ping_count",
            label="Maximum number of pings allowed in each message",
            type="number",
            required=True,
            placeholder="",
            default=5,
            constraints={
                "min_value": 3,
                "max_value": 100
            },
        ),
        ModuleSetting(
            key="timeout_length_base",
            label="Base Timeout length (seconds)",
            type="number",
            required=True,
            placeholder="",
            default=120,
            constraints={
                "min_value": 30,
                "max_value": 3600
            },
        ),
        ModuleSetting(
            key="extra_timeout_length_per_ping",
            label=
            "Timeout length per extra (disallowed extra) ping in the message (seconds)",
            type="number",
            required=True,
            placeholder="",
            default=30,
            constraints={
                "min_value": 0,
                "max_value": 600
            },
        ),
        ModuleSetting(
            key="whisper_offenders",
            label="Send offenders a whisper explaining the timeout",
            type="boolean",
            required=True,
            default=True,
        ),
        ModuleSetting(
            key="bypass_level",
            label="Level to bypass module",
            type="number",
            required=True,
            placeholder="",
            default=420,
            constraints={
                "min_value": 100,
                "max_value": 2000
            },
        ),
    ]

    def __init__(self, bot):
        super().__init__(bot)

    @staticmethod
    def is_known_user(username):
        streamer = StreamHelper.get_streamer()
        return RedisManager.get().hexists(
            "{streamer}:users:last_seen".format(streamer=streamer), username)

    @staticmethod
    def count_pings(message, source, emote_instances):
        pings = set()

        for match in username_in_message_pattern.finditer(message):
            matched_part = match.group()
            start_idx = match.start()
            end_idx = match.end()

            potential_emote = next(
                (e for e in emote_instances
                 if e.start == start_idx and e.end == end_idx), None)
            # this "username" is an emote. skip
            if potential_emote is not None:
                continue

            matched_part = matched_part.lower()

            # this is the sending user. We allow people to "ping" themselves
            if matched_part == source.username or matched_part == source.username_raw.lower(
            ):
                continue

            # check that this word is a known user (we have seen this username before)
            if not MassPingProtectionModule.is_known_user(matched_part):
                continue

            pings.add(matched_part)

        return len(pings)

    def determine_timeout_length(self, message, source, emote_instances):
        ping_count = MassPingProtectionModule.count_pings(
            message, source, emote_instances)
        pings_too_many = ping_count - self.settings["max_ping_count"]

        if pings_too_many <= 0:
            return 0

        return self.settings["timeout_length_base"] + self.settings[
            "extra_timeout_length_per_ping"] * pings_too_many

    def check_message(self, message, source):
        emote_instances, _ = self.bot.emote_manager.parse_all_emotes(message)

        # returns False if message is good,
        # True if message is bad.
        return self.determine_timeout_length(message, source,
                                             emote_instances) > 0

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

        timeout_duration = self.determine_timeout_length(
            message, source, emote_instances)

        if timeout_duration <= 0:
            return

        self.bot.timeout_user(source,
                              timeout_duration,
                              reason="Too many users pinged in message")

        if self.settings["whisper_offenders"]:
            self.bot.whisper(
                source.username,
                ("You have been timed out for {} seconds because your message mentioned too many users at once."
                 ).format(timeout_duration),
            )

        return False

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

    def disable(self, bot):
        HandlerManager.remove_handler("on_message", self.on_pubmsg)
Exemple #10
0
class ClipCommandModule(BaseModule):

    ID = __name__.split(".")[-1]
    NAME = "Clip"
    DESCRIPTION = "Enables the usage of the !clip command"
    CATEGORY = "Feature"
    PARENT_MODULE = BasicCommandsModule
    SETTINGS = [
        # TODO: Add discord support
        ModuleSetting(
            key="subscribers_only",
            label="Only allow subscribers to use the !clip command.",
            type="boolean",
            required=True,
            default=False,
        ),
        ModuleSetting(
            key="delay_clip",
            label=
            "Add a delay before the clip is captured (to account for the brief delay between the broadcaster's stream and the viewer's experience).",
            type="boolean",
            required=True,
            default=True,
        ),
        ModuleSetting(
            key="thumbnail_check",
            label=
            "Delay the bot response by 5 seconds to ensure the clip thumbnail has been generated for webchat users.",
            type="boolean",
            required=True,
            default=False,
        ),
        ModuleSetting(
            key="online_response",
            label=
            "Message response while the streamer is online | Available arguments: {streamer}, {clip}",
            type="text",
            required=True,
            placeholder="",
            default="New clip PogChamp 👉 {clip}",
            constraints={
                "min_str_len": 1,
                "max_str_len": 400
            },
        ),
        ModuleSetting(
            key="offline_response",
            label=
            "Message response if the streamer is offline. Remove text to disable message | Available arguments: {streamer}",
            type="text",
            required=False,
            placeholder="",
            default="Cannot clip while {streamer} is offline! BibleThump",
            constraints={"max_str_len": 400},
        ),
        ModuleSetting(
            key="response_method",
            label="Method of response to command usage",
            type="options",
            required=True,
            default="say",
            options=["say", "whisper", "reply"],
        ),
        ModuleSetting(
            key="global_cd",
            label="Global cooldown (seconds)",
            type="number",
            required=True,
            placeholder="",
            default=30,
            constraints={
                "min_value": 0,
                "max_value": 120
            },
        ),
        ModuleSetting(
            key="user_cd",
            label="Per-user cooldown (seconds)",
            type="number",
            required=True,
            placeholder="",
            default=60,
            constraints={
                "min_value": 0,
                "max_value": 240
            },
        ),
        ModuleSetting(
            key="level",
            label="Level required to use the command",
            type="number",
            required=True,
            placeholder="",
            default=100,
            constraints={
                "min_value": 100,
                "max_value": 2000
            },
        ),
    ]

    def load_commands(self, **options):
        self.commands["clip"] = Command.raw_command(
            self.clip,
            sub_only=self.settings["subscribers_only"],
            delay_all=self.settings["global_cd"],
            delay_user=self.settings["user_cd"],
            level=self.settings["level"],
            can_execute_with_whisper=bool(
                self.settings["response_method"] == "reply"),
            command="clip",
            examples=[
                CommandExample(
                    None,
                    "Make a new clip while the stream is online",
                    chat="user:!clip\n"
                    "bot: " + self.settings["online_response"].format(
                        source="pajlada",
                        streamer=StreamHelper.get_streamer(),
                        clip=
                        "https://clips.twitch.tv/ExpensiveWonderfulClamArsonNoSexy",
                    ),
                    description="",
                ).parse()
            ],
        )

    def clip(self, bot, event, source, **rest):
        if self.settings["subscribers_only"] and source.subscriber is False:
            return True

        if not self.bot.is_online:
            if self.settings["offline_response"] != "":
                bot.send_message_to_user(
                    source,
                    self.settings["offline_response"].format(
                        source="{source}", streamer=bot.streamer_display),
                    event,
                    method=self.settings["response_method"],
                )
            return True

        try:
            if self.settings["delay_clip"] or (
                    source.name == StreamHelper.get_streamer()) is True:
                clip_id = self.bot.twitch_helix_api.create_clip(
                    StreamHelper.get_streamer_id(),
                    self.bot.bot_token_manager,
                    has_delay=True)
            else:
                clip_id = self.bot.twitch_helix_api.create_clip(
                    StreamHelper.get_streamer_id(), self.bot.bot_token_manager)
        except HTTPError as e:
            if e.response.status_code == 503:
                bot.send_message_to_user(
                    source,
                    "Failed to create clip! Does the streamer have clips disabled?",
                    event,
                    method=self.settings["response_method"],
                )
            elif e.response.status_code != 401:
                bot.send_message_to_user(
                    source,
                    "Failed to create clip! Please try again.",
                    event,
                    method=self.settings["response_method"],
                )
            else:
                bot.send_message_to_user(
                    source,
                    "Error: The bot token does not grant permission to create clips. The bot needs to be re-authenticated to fix this problem.",
                    event,
                    method=self.settings["response_method"],
                )
            return True

        clip_url = f"https://clips.twitch.tv/{clip_id}"
        if self.settings["thumbnail_check"] is True:
            self.bot.execute_delayed(
                5,
                bot.send_message_to_user,
                source,
                self.settings["online_response"].format(
                    source="{source}",
                    streamer=bot.streamer_display,
                    clip=clip_url),
                event,
                method=self.settings["response_method"],
            )
        else:
            bot.send_message_to_user(
                source,
                self.settings["online_response"].format(
                    source="{source}",
                    streamer=bot.streamer_display,
                    clip=clip_url),
                event,
            )
Exemple #11
0
class PlaySoundTokenCommandModule(BaseModule):

    ID = 'tokencommand-' + __name__.split('.')[-1]
    NAME = '!playsound'
    DESCRIPTION = 'Play a sound on stream'
    PARENT_MODULE = QuestModule
    SETTINGS = [
        ModuleSetting(key='point_cost',
                      label='Point cost',
                      type='number',
                      required=True,
                      placeholder='Point cost',
                      default=0,
                      constraints={
                          'min_value': 0,
                          'max_value': 999999,
                      }),
        ModuleSetting(key='token_cost',
                      label='Token cost',
                      type='number',
                      required=True,
                      placeholder='Token cost',
                      default=3,
                      constraints={
                          'min_value': 0,
                          'max_value': 15,
                      }),
        ModuleSetting(key='sample_cd',
                      label='Cooldown for the same sample (seconds)',
                      type='number',
                      required=True,
                      placeholder='',
                      default=20,
                      constraints={
                          'min_value': 5,
                          'max_value': 120,
                      }),
    ]

    def __init__(self):
        super().__init__()
        Samples.all_samples.sort()
        self.valid_samples = [sample.command for sample in Samples.all_samples]
        self.sample_cache = []

    def play_sound(self, **options):
        bot = options['bot']
        message = options['message']
        source = options['source']

        if message:
            sample = message.split(' ')[0].lower()

            if sample in self.sample_cache:
                return False

            if sample == 'random':
                sample = random.choice(self.valid_samples)

            if sample in self.valid_samples:
                log.debug('Played sound: {0}'.format(sample))
                payload = {'sample': sample}
                bot.websocket_manager.emit('play_sound', payload)
                bot.whisper(
                    source.username,
                    'Successfully played your sample {0}'.format(sample))
                self.sample_cache.append(sample)
                bot.execute_delayed(self.settings['sample_cd'],
                                    self.sample_cache.remove,
                                    ('{0}'.format(sample), ))
                return True

        bot.whisper(
            source.username,
            'Your sample is not valid. Check out all the valid samples here: {0}/commands/playsound'
            .format(bot.domain))
        return False

    def load_commands(self, **options):
        self.commands[
            '#playsound'] = pajbot.models.command.Command.raw_command(
                self.play_sound,
                tokens_cost=self.settings['token_cost'],
                cost=self.settings['point_cost'],
                sub_only=True,
                description=
                'Play a sound on stream! Costs {} tokens, sub only for now.'.
                format(self.settings['token_cost']),
                can_execute_with_whisper=True,
                examples=[
                    pajbot.models.command.CommandExample(
                        None,
                        'Play the "cumming" sample',
                        chat='user:!#playsound cumming\n'
                        'bot>user:Successfully played your sample cumming').
                    parse(),
                    pajbot.models.command.CommandExample(
                        None,
                        'Play the "fuckyou" sample',
                        chat='user:!#playsound fuckyou\n'
                        'bot>user:Successfully played your sample fuckyou').
                    parse(),
                ],
            )
        global_script = """<script>
            function playOrStopSound(elem, audio) {
                if(elem.innerHTML=="Play") {
                    elem.innerHTML="Stop";
                    audio.play();
                } else {
                    elem.innerHTML="Play";
                    audio.pause();
                    audio.currentTime=0;
                }
            }
            </script>"""
        local_script = """<script>
            var elem{0.command}=document.getElementById('btnTogglePlay{0.command}');
            var snd{0.command} = new Audio("{0.href}");
            snd{0.command}.onended=function(){{elem{0.command}.innerHTML='Play';}};
            elem{0.command}.addEventListener("click", function(){{ playOrStopSound(elem{0.command}, snd{0.command}); }});
        </script>"""
        html_valid_samples = global_script
        for sample in Samples.all_samples:
            parsed_sample = local_script.format(sample)
            html_valid_samples += ''.join([
                '<tr><td class="command-sample{1}">!#playsound {0.command}</td><td><button id="btnTogglePlay{0.command}">Play</button>{2}</td></tr>'
                .format(sample, ' new' if sample.new else '', parsed_sample)
            ])
        self.commands[
            '#playsound'].long_description = '<h5 style="margin-top: 20px;">Valid samples</h5><table>{}</table>'.format(
                html_valid_samples)
Exemple #12
0
class ChattersRefreshModule(BaseModule):
    ID = __name__.split(".")[-1]
    NAME = "Chatters refresh"
    DESCRIPTION = "Fetches a list of chatters and updates points/time accordingly - this is required to be turned on in order for the bot to record user time and/or points"
    ENABLED_DEFAULT = True
    CATEGORY = "Internal"
    SETTINGS = [
        ModuleSetting(
            key="base_points_pleb",
            label=
            "Award this points amount every 10 minutes to non-subscribers",
            type="number",
            required=True,
            placeholder="",
            default=2,
            constraints={
                "min_value": 0,
                "max_value": 500000
            },
        ),
        ModuleSetting(
            key="base_points_sub",
            label="Award this points amount every 10 minutes to subscribers",
            type="number",
            required=True,
            placeholder="",
            default=10,
            constraints={
                "min_value": 0,
                "max_value": 500000
            },
        ),
        ModuleSetting(
            key="offline_chat_multiplier",
            label=
            "Apply this multiplier to the awarded points if the stream is currently offline (in percent, 100 = same as online chat, 0 = nothing)",
            type="number",
            required=True,
            placeholder="",
            default=0,
            constraints={
                "min_value": 0,
                "max_value": 1000
            },
        ),
    ]

    UPDATE_INTERVAL = 10  # minutes

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

    def update_chatters_cmd(self, bot, source, **rest):
        # TODO if you wanted to improve this: Provide the user with feedback
        #   whether the update succeeded, and if yes, how many users were updated
        bot.whisper(source, "Reloading list of chatters...")
        bot.action_queue.submit(self._update_chatters, only_last_seen=True)

    @time_method
    def _update_chatters(self, only_last_seen=False):
        chatter_logins = self.bot.twitch_tmi_api.get_chatter_logins_by_login(
            self.bot.streamer)
        chatter_basics = self.bot.twitch_helix_api.bulk_get_user_basics_by_login(
            chatter_logins)

        # filter out invalid/deleted/etc. users
        chatter_basics = [e for e in chatter_basics if e is not None]

        is_stream_online = self.bot.stream_manager.online

        if is_stream_online:
            add_time_in_chat_online = timedelta(minutes=self.UPDATE_INTERVAL)
            add_time_in_chat_offline = timedelta(minutes=0)
        else:
            add_time_in_chat_online = timedelta(minutes=0)
            add_time_in_chat_offline = timedelta(minutes=self.UPDATE_INTERVAL)

        add_points_pleb = self.settings["base_points_pleb"]
        add_points_sub = self.settings["base_points_sub"]

        if not is_stream_online:
            offline_chat_multiplier = self.settings[
                "offline_chat_multiplier"] / 100
            add_points_pleb = int(
                round(add_points_pleb * offline_chat_multiplier))
            add_points_sub = int(
                round(add_points_sub * offline_chat_multiplier))

        if only_last_seen:
            add_time_in_chat_online = timedelta(minutes=0)
            add_time_in_chat_offline = timedelta(minutes=0)
            add_points_pleb = 0
            add_points_sub = 0

        update_values = [{
            **basics.jsonify(),
            "add_points_pleb": add_points_pleb,
            "add_points_sub": add_points_sub,
            "add_time_in_chat_online": add_time_in_chat_online,
            "add_time_in_chat_offline": add_time_in_chat_offline,
        } for basics in chatter_basics]

        with DBManager.create_session_scope() as db_session:
            db_session.execute(
                text("""
INSERT INTO "user"(id, login, name, points, time_in_chat_online, time_in_chat_offline, last_seen)
    VALUES (:id, :login, :name, :add_points_pleb, :add_time_in_chat_online, :add_time_in_chat_offline, now())
ON CONFLICT (id) DO UPDATE SET
    points = "user".points + CASE WHEN "user".subscriber THEN :add_points_sub ELSE :add_points_pleb END,
    time_in_chat_online = "user".time_in_chat_online + :add_time_in_chat_online,
    time_in_chat_offline = "user".time_in_chat_offline + :add_time_in_chat_offline,
    last_seen = now()
            """),
                update_values,
            )

        log.info(f"Successfully updated {len(chatter_basics)} chatters")

    def load_commands(self, **options):
        self.commands["reload"] = Command.multiaction_command(
            command="reload",
            commands={
                "chatters":
                Command.raw_command(
                    self.update_chatters_cmd,
                    delay_all=120,
                    delay_user=120,
                    level=1000,
                    examples=[
                        CommandExample(
                            None,
                            f"Reload who is currently chatting",
                            chat=
                            f"user:!reload chatters\nbot>user: Reloading list of chatters...",
                            description=
                            "Note: Updates only last_seen values, does not award points for watching the stream.",
                        ).parse()
                    ],
                )
            },
        )

    def enable(self, bot):
        # Web interface, nothing to do
        if not bot:
            return

        # every 10 minutes, add the chatters update to the action queue
        self.scheduled_job = ScheduleManager.execute_every(
            self.UPDATE_INTERVAL * 60,
            lambda: self.bot.action_queue.submit(self._update_chatters))

    def disable(self, bot):
        # Web interface, nothing to do
        if not bot:
            return

        self.scheduled_job.remove()
Exemple #13
0
class DuelModule(BaseModule):

    ID = __name__.split(".")[-1]
    NAME = "Duel"
    DESCRIPTION = "Let users duel to win or lose points."
    CATEGORY = "Game"
    SETTINGS = [
        ModuleSetting(
            key="message_won",
            label="Winner message | Available arguments: {winner}, {loser}",
            type="text",
            required=True,
            placeholder="{winner} won the duel vs {loser} PogChamp",
            default="{winner} won the duel vs {loser} PogChamp",
            constraints={
                "min_str_len": 10,
                "max_str_len": 400
            },
        ),
        ModuleSetting(
            key="message_won_points",
            label=
            "Points message | Available arguments: {winner}, {loser}, {total_pot}, {extra_points}",
            type="text",
            required=True,
            placeholder=
            "{winner} won the duel vs {loser} PogChamp . The pot was {total_pot}, the winner gets their bet back + {extra_points} points",
            default=
            "{winner} won the duel vs {loser} PogChamp . The pot was {total_pot}, the winner gets their bet back + {extra_points} points",
            constraints={
                "min_str_len": 10,
                "max_str_len": 400
            },
        ),
        ModuleSetting(
            key="duel_tax",
            label="Duel tax (deduct this percent value from the win)",
            type="number",
            required=True,
            placeholder="",
            default=30,
            constraints={
                "min_value": 0,
                "max_value": 100
            },
        ),
        ModuleSetting(
            key="online_global_cd",
            label="Global cooldown (seconds)",
            type="number",
            required=True,
            placeholder="",
            default=0,
            constraints={
                "min_value": 0,
                "max_value": 120
            },
        ),
        ModuleSetting(
            key="online_user_cd",
            label="Per-user cooldown (seconds)",
            type="number",
            required=True,
            placeholder="",
            default=5,
            constraints={
                "min_value": 0,
                "max_value": 240
            },
        ),
        ModuleSetting(key="show_on_clr",
                      label="Show duels on the clr overlay",
                      type="boolean",
                      required=True,
                      default=True),
        ModuleSetting(
            key="max_duel_age",
            label="Auto-cancel duels after this many minutes",
            type="number",
            required=True,
            placeholder="",
            default=5,
            constraints={
                "min_value": 1,
                "max_value": 60
            },
        ),
    ]

    def load_commands(self, **options):
        self.commands["duel"] = Command.raw_command(
            self.initiate_duel,
            delay_all=self.settings["online_global_cd"],
            delay_user=self.settings["online_user_cd"],
            description="Initiate a duel with a user",
            examples=[
                CommandExample(
                    None,
                    "0-point duel",
                    chat="user:!duel Karl_Kons\n"
                    "bot>user:You have challenged Karl_Kons for 0 points",
                    description="Duel Karl_Kons for 0 points",
                ).parse(),
                CommandExample(
                    None,
                    "69-point duel",
                    chat="user:!duel Karl_Kons 69\n"
                    "bot>user:You have challenged Karl_Kons for 69 points",
                    description="Duel Karl_Kons for 69 points",
                ).parse(),
            ],
        )
        self.commands["cancelduel"] = Command.raw_command(
            self.cancel_duel,
            delay_all=0,
            delay_user=10,
            description="Cancel your duel request")
        self.commands["accept"] = Command.raw_command(
            self.accept_duel,
            delay_all=0,
            delay_user=0,
            description="Accept a duel request")
        self.commands["decline"] = Command.raw_command(
            self.decline_duel,
            delay_all=0,
            delay_user=0,
            description="Decline a duel request")
        self.commands["deny"] = self.commands["decline"]
        self.commands["duelstatus"] = Command.raw_command(
            self.status_duel,
            delay_all=0,
            delay_user=5,
            description="Current duel request info")
        self.commands["duelstats"] = Command.raw_command(
            self.get_duel_stats,
            delay_all=0,
            delay_user=120,
            description="Get your duel statistics")

    def __init__(self, bot):
        super().__init__(bot)
        self.duel_requests = {}
        self.duel_request_price = {}
        self.duel_targets = {}
        self.blUsers = ["admiralbulldog", "infinitegachi"]
        self.duel_begin_time = {}

        self.gc_job = None

    def initiate_duel(self, bot, source, message, **rest):
        """
        Initiate a duel with a user.
        You can also bet points on the winner.
        By default, the maximum amount of points you can spend is 420.

        How to use: !duel USERNAME POINTS_TO_BET
        """

        if message is None:
            return False

        msg_split = message.split()
        input = msg_split[0]

        with DBManager.create_session_scope() as db_session:
            user = User.find_by_user_input(db_session, input)
            if user is None:
                # No user was found with this username
                return False

            duel_price = 1
            if len(msg_split) > 1:
                try:
                    duel_price = utils.parse_points_amount(
                        source, msg_split[1])
                    if duel_price < 1:
                        # bot.whisper(source, f"Really? {duel_price} points?")
                        # bot.whisper(
                        #     user,
                        #     f"{source} tried to duel you for {duel_price} points. What a cheapskate EleGiggle",
                        # )
                        return False
                except InvalidPointAmount as e:
                    bot.whisper(source, f"{e}. Usage: !duel USERNAME POINTS")
                    return False

            if source.id in self.duel_requests:
                currently_duelling = User.find_by_id(
                    db_session, self.duel_requests[source.id])
                if currently_duelling is None:
                    del self.duel_requests[source.id]
                    return False

                bot.whisper(
                    source,
                    f"You already have a duel request active with {currently_duelling}. Type !cancelduel to cancel your duel request.",
                )
                return False

            if user == source:
                # You cannot duel yourself
                return False

            if user.last_active is None or (
                    utils.now() - user.last_active) > timedelta(minutes=5):
                bot.whisper(
                    source,
                    "This user has not been active in chat within the last 5 minutes. Get them to type in chat before sending another challenge",
                )
                return False

            if user.login in self.blUsers:
                return True

            if not user.can_afford(duel_price) or not source.can_afford(
                    duel_price):
                bot.whisper(
                    source,
                    f"You or your target do not have more than {duel_price} points, therefore you cannot duel for that amount.",
                )
                return False

            if user.id in self.duel_targets:
                challenged_by = User.find_by_id(db_session,
                                                self.duel_requests[user.id])
                bot.whisper(
                    source,
                    f"This person is already being challenged by {challenged_by}. Ask them to answer the offer by typing !deny or !accept",
                )
                return False

            self.duel_targets[user.id] = source.id
            self.duel_requests[source.id] = user.id
            self.duel_request_price[source.id] = duel_price
            self.duel_begin_time[source.id] = utils.now()
            bot.whisper(
                user,
                f"You have been challenged to a duel by {source} for {duel_price} points. You can either !accept or !deny this challenge.",
            )
            bot.whisper(source,
                        f"You have challenged {user} for {duel_price} points")

    def cancel_duel(self, bot, source, **rest):
        """
        Cancel any duel requests you've sent.

        How to use: !cancelduel
        """

        if source.id not in self.duel_requests:
            bot.whisper(source, "You have not sent any duel requests")
            return

        with DBManager.create_session_scope() as db_session:
            challenged = User.find_by_id(db_session,
                                         self.duel_requests[source.id])
            bot.whisper(source, f"You have cancelled the duel vs {challenged}")

            del self.duel_targets[challenged.id]
            del self.duel_request_price[source.id]
            del self.duel_begin_time[source.id]
            del self.duel_requests[source.id]

    def accept_duel(self, bot, source, **rest):
        """
        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])
            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 False

            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)

    def decline_duel(self, bot, source, **options):
        """
        Declines any active duel requests you've received.

        How to use: !decline
        """

        if source.id not in self.duel_targets:
            bot.whisper(source, "You are not being challenged to a duel")
            return False

        with DBManager.create_session_scope() as db_session:
            requestor = User.find_by_id(db_session,
                                        self.duel_targets[source.id])

            bot.whisper(source, f"You have declined the duel vs {requestor}")
            bot.whisper(requestor,
                        f"{source} declined the duel challenge with you.")

            del self.duel_targets[source.id]
            del self.duel_requests[requestor.id]
            del self.duel_request_price[requestor.id]
            del self.duel_begin_time[requestor.id]

    def status_duel(self, bot, source, **rest):
        """
        Whispers you the current status of your active duel requests/duel targets

        How to use: !duelstatus
        """

        with DBManager.create_session_scope() as db_session:
            msg = []
            if source.id in self.duel_requests:
                duelling = User.find_by_id(db_session,
                                           self.duel_requests[source.id])
                msg.append(
                    f"You have a duel request for {self.duel_request_price[source.id]} points by {duelling}"
                )

            if source.id in self.duel_targets:
                challenger = User.find_by_id(db_session,
                                             self.duel_targets[source.id])
                msg.append(
                    f"You have a pending duel request from {challenger} for {self.duel_request_price[self.duel_targets[source.id]]} points"
                )

            if len(msg) > 0:
                bot.whisper(source, ". ".join(msg))
            else:
                bot.whisper(
                    source,
                    "You have no duel request or duel target. Type !duel USERNAME POT to duel someone!"
                )

    @staticmethod
    def get_duel_stats(bot, source, **rest):
        """
        Whispers the users duel winratio to the user
        """
        if source.duel_stats is None:
            bot.whisper(source, "You have no recorded duels.")
            return True

        bot.whisper(
            source,
            f"duels: {source.duel_stats.duels_total} winrate: {source.duel_stats.winrate:.2f}% streak: {source.duel_stats.current_streak} profit: {source.duel_stats.profit}",
        )

    def _cancel_expired_duels(self):
        now = utils.now()
        for source_id, started_at in self.duel_begin_time.items():
            duel_age = now - started_at
            if duel_age <= timedelta(minutes=self.settings["max_duel_age"]):
                # Duel is not too old
                continue

            with DBManager.create_session_scope() as db_session:
                source = User.find_by_id(db_session, source_id)
                challenged = User.find_by_id(db_session,
                                             self.duel_requests[source.id])

                if source is not None and challenged is not None:
                    self.bot.whisper(
                        source,
                        f"{challenged} didn't accept your duel request in time, so the duel has been cancelled. Ditched pepeLaugh",
                    )

                del self.duel_targets[self.duel_requests[source.id]]
                del self.duel_requests[source.id]
                del self.duel_request_price[source.id]
                del self.duel_begin_time[source.id]

    def enable(self, bot):
        if not bot:
            return

        # We can't use bot.execute_every directly since we can't later cancel jobs created through bot.execute_every
        self.gc_job = ScheduleManager.execute_every(
            30, lambda: self.bot.execute_now(self._cancel_expired_duels))

    def disable(self, bot):
        if not bot:
            return

        self.gc_job.remove()
        self.gc_job = None
Exemple #14
0
class ActionCheckerModule(BaseModule):
    ID = __name__.split(".")[-1]
    NAME = "Action Command Moderation"
    DESCRIPTION = "Dis/allows messages who use the /me command."
    CATEGORY = "Moderation"
    SETTINGS = [
        ModuleSetting(
            key="only_allow_action_messages",
            label="Only allow /me messages",
            type="boolean",
            required=True,
            default=False,
        ),
        ModuleSetting(
            key="allow_timeout_reason",
            label="Timeout Reason",
            type="text",
            required=False,
            placeholder="",
            default="Lack of /me usage",
            constraints={},
        ),
        ModuleSetting(
            key="disallow_action_messages",
            label="Disallow /me messages",
            type="boolean",
            required=True,
            default=True,
        ),
        ModuleSetting(
            key="disallow_timeout_reason",
            label="Timeout Reason",
            type="text",
            required=False,
            placeholder="",
            default="No /me usage allowed!",
            constraints={},
        ),
        ModuleSetting(
            key="enabled_by_stream_status",
            label="Enable moderation of the /me command when the stream is:",
            type="options",
            required=True,
            default="Offline and Online",
            options=["Online Only", "Offline Only", "Offline and Online"],
        ),
        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=30,
            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},
        ),
    ]

    def delete_or_timeout(self, user, msg_id, reason):
        if self.settings["moderation_action"] == "Delete":
            self.bot.delete_message(msg_id)
        elif self.settings["moderation_action"] == "Timeout":
            self.bot.timeout(user, self.settings["timeout_length"], reason, once=True)

    def on_message(self, source, message, event, msg_id, **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 event.type == "action" and self.settings["disallow_action_messages"] is True:
            self.delete_or_timeout(source, msg_id, self.settings["disallow_timeout_reason"])
            return False
        elif event.type != "action" and self.settings["only_allow_action_messages"] is True:
            self.delete_or_timeout(source, msg_id, self.settings["allow_timeout_reason"])
            return False

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

    def disable(self, bot):
        HandlerManager.remove_handler("on_message", self.on_message)
Exemple #15
0
class PlaySoundTokenCommandModule(BaseModule):
    ID = 'tokencommand-' + __name__.split('.')[-1]
    NAME = '!playsound'
    DESCRIPTION = 'Play a sound on stream'
    SETTINGS = [
        ModuleSetting(key='point_cost',
                      label='Point cost',
                      type='number',
                      required=True,
                      placeholder='Point cost',
                      default=0,
                      constraints={
                          'min_value': 0,
                          'max_value': 999999,
                      }),
        ModuleSetting(key='token_cost',
                      label='Token cost',
                      type='number',
                      required=True,
                      placeholder='Token cost',
                      default=3,
                      constraints={
                          'min_value': 0,
                          'max_value': 15,
                      }),
        ModuleSetting(key='sample_cd',
                      label='Cooldown for the same sample (seconds)',
                      type='number',
                      required=True,
                      placeholder='',
                      default=20,
                      constraints={
                          'min_value': 5,
                          'max_value': 120,
                      }),
        ModuleSetting(key='sub_only',
                      label='Subscribers only',
                      type='boolean',
                      required=True,
                      default=True),
        ModuleSetting(key='global_cd',
                      label='Global playsound cooldown (seconds)',
                      type='number',
                      required=True,
                      placeholder='',
                      default=2,
                      constraints={
                          'min_value': 0,
                          'max_value': 600,
                      }),
    ]

    def __init__(self):
        super().__init__()
        self.valid_samples = []
        self.sample_cache = []
        possibleCategories = [
            'bulldog', 'gachi', 'others', 'personalities', 'weeb'
        ]

        for category in possibleCategories:
            for sampleName, sampleURL in RedisManager.get().hgetall(
                    'playsounds:{}'.format(category)).items():
                self.valid_samples.append(sampleName)

    def refresh_sounds(self, **options):
        self.valid_samples = []
        possibleCategories = [
            'bulldog', 'gachi', 'others', 'personalities', 'weeb'
        ]

        for category in possibleCategories:
            for sampleName, sampleURL in RedisManager.get().hgetall(
                    'playsounds:{}'.format(category)).items():
                self.valid_samples.append(sampleName)

    def play_sound(self, **options):
        bot = options['bot']
        message = options['message']
        source = options['source']

        if message:
            sample = message.split(' ')[0].lower()

            if sample in self.sample_cache:
                bot.whisper(
                    source.username,
                    'The sample {0} was played too recently. Please wait before trying to use it again'
                    .format(sample))
                return False

            if sample == 'random':
                sample = random.choice(self.valid_samples)

            if sample in self.valid_samples:
                log.debug('Played sound: {0}'.format(sample))
                bot.whisper(source.username, 'Played sound: {}'.format(sample))
                payload = {'sample': sample}
                bot.websocket_manager.emit('play_sound', payload)
                if not (source.username.lower() == 'datguy1' or
                        source.username.lower() == 'admiralbulldog') or True:
                    self.sample_cache.append(sample)
                    bot.execute_delayed(self.settings['sample_cd'],
                                        self.sample_cache.remove,
                                        ('{0}'.format(sample), ))
                return True

        bot.whisper(
            source.username,
            'Your sample is not valid. Check out all the valid samples here: http://chatbot.admiralbulldog.live/playsound'
        )
        return False

    def load_commands(self, **options):
        self.commands['playsound'] = pajbot.models.command.Command.raw_command(
            self.play_sound,
            cost=self.settings['point_cost'],
            sub_only=self.settings['sub_only'],
            delay_all=self.settings['global_cd'],
            description='Play a sound on stream! Costs {} points.'.format(
                self.settings['point_cost']),
            can_execute_with_whisper=True,
            examples=[
                pajbot.models.command.CommandExample(
                    None,
                    'Play the "cumming" sample',
                    chat='user:!playsound cumming\n'
                    'bot>user:Successfully played your sample cumming').parse(
                    ),
                pajbot.models.command.CommandExample(
                    None,
                    'Play the "fuckyou" sample',
                    chat='user:!playsound fuckyou\n'
                    'bot>user:Successfully played your sample fuckyou').parse(
                    ),
            ],
        )

        self.commands[
            'playsound'].long_description = 'Playsounds can be tried out <a href="http://chatbot.admiralbulldog.live/playsound">here</a>'
        self.commands[
            'refreshsound'] = pajbot.models.command.Command.raw_command(
                self.refresh_sounds, level=500, can_execute_with_whisper=True)
Exemple #16
0
class RaffleModule(BaseModule):

    ID = __name__.split('.')[-1]
    NAME = 'Raffle'
    DESCRIPTION = 'Users can participate in a raffle to win points.'
    CATEGORY = 'Game'
    SETTINGS = [
            ModuleSetting(
                key='single_max_points',
                label='Max points for a single raffle',
                type='number',
                required=True,
                placeholder='',
                default=3000,
                constraints={
                    'min_value': 0,
                    'max_value': 35000,
                    }),
            ModuleSetting(
                key='max_length',
                label='Max length for a single raffle in seconds',
                type='number',
                required=True,
                placeholder='',
                default=120,
                constraints={
                    'min_value': 0,
                    'max_value': 1200,
                    }),
            ModuleSetting(
                key='allow_negative_raffles',
                label='Allow negative raffles',
                type='boolean',
                required=True,
                default=True),
            ModuleSetting(
                key='max_negative_points',
                label='Max negative points for a single raffle',
                type='number',
                required=True,
                placeholder='',
                default=3000,
                constraints={
                    'min_value': 1,
                    'max_value': 35000,
                    }),
            ModuleSetting(
                key='multi_enabled',
                label='Enable multi-raffles (!multiraffle/!mraffle)',
                type='boolean',
                required=True,
                default=True),
            ModuleSetting(
                key='multi_max_points',
                label='Max points for a multi raffle',
                type='number',
                required=True,
                placeholder='',
                default=100000,
                constraints={
                    'min_value': 0,
                    'max_value': 1000000,
                    }),
            ModuleSetting(
                key='multi_max_length',
                label='Max length for a multi raffle in seconds',
                type='number',
                required=True,
                placeholder='',
                default=600,
                constraints={
                    'min_value': 0,
                    'max_value': 1200,
                    }),
            ModuleSetting(
                key='multi_allow_negative_raffles',
                label='Allow negative multi raffles',
                type='boolean',
                required=True,
                default=True),
            ModuleSetting(
                key='multi_max_negative_points',
                label='Max negative points for a multi raffle',
                type='number',
                required=True,
                placeholder='',
                default=10000,
                constraints={
                    'min_value': 1,
                    'max_value': 100000,
                    }),
            ModuleSetting(
                key='multi_raffle_on_sub',
                label='Start a multi raffle when someone subscribes',
                type='boolean',
                required=True,
                default=False),
            ModuleSetting(
                key='default_raffle_type',
                label='Default raffle (What raffle type !raffle should invoke)',
                type='options',
                required=True,
                default='Single Raffle',
                options=[
                    'Single Raffle',
                    'Multi Raffle',
                    ]),
            ]

    def __init__(self):
        super().__init__()

        self.raffle_running = False
        self.raffle_users = []
        self.raffle_points = 0
        self.raffle_length = 0

    def load_commands(self, **options):
        self.commands['singleraffle'] = Command.raw_command(self.raffle,
                delay_all=0,
                delay_user=0,
                level=500,
                description='Start a raffle for points',
                command='raffle',
                examples=[
                    CommandExample(None, 'Start a raffle for 69 points',
                        chat='user:!raffle 69\n'
                        'bot:A raffle has begun for 69 points. Type !join to join the raffle! The raffle will end in 60 seconds.',
                        description='Start a 60-second raffle for 69 points').parse(),
                    CommandExample(None, 'Start a raffle with a different length',
                        chat='user:!raffle 69 30\n'
                        'bot:A raffle has begun for 69 points. Type !join to join the raffle! The raffle will end in 30 seconds.',
                        description='Start a 30-second raffle for 69 points').parse(),
                    ],
                )
        self.commands['sraffle'] = self.commands['singleraffle']
        self.commands['join'] = Command.raw_command(self.join,
                delay_all=0,
                delay_user=5,
                description='Join a running raffle',
                examples=[
                    CommandExample(None, 'Join a running raffle',
                        chat='user:!join',
                        description='You don\'t get confirmation whether you joined the raffle or not.').parse(),
                    ],
                )
        if self.settings['multi_enabled']:
            self.commands['multiraffle'] = Command.raw_command(self.multi_raffle,
                    delay_all=0,
                    delay_user=0,
                    level=500,
                    description='Start a multi-raffle for points',
                    command='multiraffle',
                    examples=[
                        CommandExample(None, 'Start a multi-raffle for 69 points',
                            chat='user:!multiraffle 69\n'
                            'bot:A multi-raffle has begun for 69 points. Type !join to join the raffle! The raffle will end in 60 seconds.',
                            description='Start a 60-second raffle for 69 points').parse(),
                        CommandExample(None, 'Start a multi-raffle with a different length',
                            chat='user:!multiraffle 69 30\n'
                            'bot:A multi-raffle has begun for 69 points. Type !join to join the raffle! The raffle will end in 30 seconds.',
                            description='Start a 30-second multi-raffle for 69 points').parse(),
                        ],
                    )
            self.commands['mraffle'] = self.commands['multiraffle']

        if self.settings['default_raffle_type'] == 'Multi Raffle' and self.settings['multi_enabled']:
            self.commands['raffle'] = self.commands['multiraffle']
        else:
            self.commands['raffle'] = self.commands['singleraffle']

    def raffle(self, **options):
        bot = options['bot']
        source = options['source']
        message = options['message']

        if self.raffle_running is True:
            bot.say('{0}, a raffle is already running OMGScoots'.format(source.username_raw))
            return False

        self.raffle_users = []
        self.raffle_running = True
        self.raffle_points = 100
        self.raffle_length = 60

        try:
            if message is not None and self.settings['allow_negative_raffles'] is True:
                self.raffle_points = int(message.split()[0])
            if message is not None and self.settings['allow_negative_raffles'] is False:
                if int(message.split()[0]) >= 0:
                    self.raffle_points = int(message.split()[0])
        except (IndexError, ValueError, TypeError):
            pass

        try:
            if message is not None:
                if int(message.split()[1]) >= 5:
                    self.raffle_length = int(message.split()[1])
        except (IndexError, ValueError, TypeError):
            pass

        if self.raffle_points >= 0:
            self.raffle_points = min(self.raffle_points, self.settings['single_max_points'])
        if self.raffle_points <= -1:
            self.raffle_points = max(self.raffle_points, -self.settings['max_negative_points'])

        self.raffle_length = min(self.raffle_length, self.settings['max_length'])

        bot.websocket_manager.emit('notification', {'message': 'A raffle has been started!'})
        bot.execute_delayed(0.75, bot.websocket_manager.emit, ('notification', {'message': 'Type !join to enter!'}))

        bot.me('A raffle has begun for {} points. type !join to join the raffle! The raffle will end in {} seconds'.format(self.raffle_points, self.raffle_length))
        bot.execute_delayed(self.raffle_length * 0.25, bot.me, ('The raffle for {} points ends in {} seconds! Type !join to join the raffle!'.format(self.raffle_points, round(self.raffle_length * 0.75)), ))
        bot.execute_delayed(self.raffle_length * 0.50, bot.me, ('The raffle for {} points ends in {} seconds! Type !join to join the raffle!'.format(self.raffle_points, round(self.raffle_length * 0.50)), ))
        bot.execute_delayed(self.raffle_length * 0.75, bot.me, ('The raffle for {} points ends in {} seconds! Type !join to join the raffle!'.format(self.raffle_points, round(self.raffle_length * 0.25)), ))

        bot.execute_delayed(self.raffle_length, self.end_raffle)

    def join(self, **options):
        source = options['source']
        if not self.raffle_running:
            return False

        for user in self.raffle_users:
            if user == source:
                return False

        # Added user to the raffle
        self.raffle_users.append(source)

    def end_raffle(self):
        if not self.raffle_running:
            return False

        self.raffle_running = False

        if len(self.raffle_users) == 0:
            self.bot.me('Wow, no one joined the raffle DansGame')
            return False

        winner = random.choice(self.raffle_users)

        self.raffle_users = []

        self.bot.websocket_manager.emit('notification', {'message': '{} won {} points in the raffle!'.format(winner.username_raw, self.raffle_points)})
        self.bot.me('The raffle has finished! {0} won {1} points! PogChamp'.format(winner.username_raw, self.raffle_points))

        winner.points += self.raffle_points

        HandlerManager.trigger('on_raffle_win', winner, self.raffle_points)

    def multi_start_raffle(self, points, length):
        if self.raffle_running:
            return False

        self.raffle_users = []
        self.raffle_running = True
        self.raffle_points = points
        self.raffle_length = length

        if self.raffle_points >= 0:
            self.raffle_points = min(self.raffle_points, self.settings['multi_max_points'])
        if self.raffle_points <= -1:
            self.raffle_points = max(self.raffle_points, -self.settings['multi_max_negative_points'])

        self.raffle_length = min(self.raffle_length, self.settings['multi_max_length'])

        self.bot.websocket_manager.emit('notification', {'message': 'A raffle has been started!'})
        self.bot.execute_delayed(0.75, self.bot.websocket_manager.emit, ('notification', {'message': 'Type !join to enter!'}))

        self.bot.me('A multi-raffle has begun, {} points will be split among the winners. type !join to join the raffle! The raffle will end in {} seconds'.format(self.raffle_points, self.raffle_length))
        self.bot.execute_delayed(self.raffle_length * 0.25, self.bot.me, ('The multi-raffle for {} points ends in {} seconds! Type !join to join the raffle!'.format(self.raffle_points, round(self.raffle_length * 0.75)), ))
        self.bot.execute_delayed(self.raffle_length * 0.50, self.bot.me, ('The multi-raffle for {} points ends in {} seconds! Type !join to join the raffle!'.format(self.raffle_points, round(self.raffle_length * 0.50)), ))
        self.bot.execute_delayed(self.raffle_length * 0.75, self.bot.me, ('The multi-raffle for {} points ends in {} seconds! Type !join to join the raffle!'.format(self.raffle_points, round(self.raffle_length * 0.25)), ))

        self.bot.execute_delayed(self.raffle_length, self.multi_end_raffle)

    def multi_raffle(self, **options):
        bot = options['bot']
        source = options['source']
        message = options['message']

        if self.raffle_running is True:
            bot.say('{0}, a raffle is already running OMGScoots'.format(source.username_raw))
            return False

        points = 100
        try:
            if message is not None and self.settings['multi_allow_negative_raffles'] is True:
                points = int(message.split()[0])
            if message is not None and self.settings['multi_allow_negative_raffles'] is False:
                if int(message.split()[0]) >= 0:
                    points = int(message.split()[0])
        except (IndexError, ValueError, TypeError):
            pass

        length = 60
        try:
            if message is not None:
                if int(message.split()[1]) >= 5:
                    length = int(message.split()[1])
        except (IndexError, ValueError, TypeError):
            pass

        self.multi_start_raffle(points, length)

    def multi_end_raffle(self):
        if not self.raffle_running:
            return False

        self.raffle_running = False

        if len(self.raffle_users) == 0:
            self.bot.me('Wow, no one joined the raffle DansGame')
            return False

        # Shuffle the list of participants
        random.shuffle(self.raffle_users)

        num_participants = len(self.raffle_users)

        abs_points = abs(self.raffle_points)

        max_winners = min(num_participants, 200)
        min_point_award = 100
        negative = self.raffle_points < 0

        # Decide how we should pick the winners
        log.info('Num participants: {}'.format(num_participants))
        for winner_percentage in [x * 0.01 for x in range(1, 26)]:
            log.info('Winner percentage: {}'.format(winner_percentage))
            num_winners = math.ceil(num_participants * winner_percentage)
            points_per_user = math.ceil(abs_points / num_winners)
            log.info('nw: {}, ppu: {}'.format(num_winners, points_per_user))

            if num_winners > max_winners:
                num_winners = max_winners
                points_per_user = math.ceil(abs_points / num_winners)
                break
            elif points_per_user < min_point_award:
                num_winners = max(1, min(math.floor(abs_points / min_point_award), num_participants))
                points_per_user = math.ceil(abs_points / num_winners)
                break

        log.info('k done. got {} winners'.format(num_winners))
        winners = self.raffle_users[:num_winners]
        self.raffle_users = []

        if negative:
            points_per_user *= -1

        self.bot.me('The multi-raffle has finished! {0} users won {1} points each! PogChamp'.format(len(winners), points_per_user))

        winners_arr = []
        for winner in winners:
            winner.points += points_per_user
            winners_arr.append(winner)

            winners_str = generate_winner_list(winners_arr)
            if len(winners_str) > 300:
                self.bot.me('{} won {} points each!'.format(winners_str, points_per_user))
                winners_arr = []

        if len(winners_arr) > 0:
            winners_str = generate_winner_list(winners_arr)
            self.bot.me('{} won {} points each!'.format(winners_str, points_per_user))

        HandlerManager.trigger('on_multiraffle_win', winners, points_per_user)

    def on_user_sub(self, user):
        if self.settings['multi_raffle_on_sub'] is False:
            return

        MAX_REWARD = 10000

        points = StreamHelper.get_viewers() * 5
        if points == 0:
            points = 100
        length = 30

        points = min(points, MAX_REWARD)

        self.multi_start_raffle(points, length)

    def on_user_resub(self, user, num_months):
        if self.settings['multi_raffle_on_sub'] is False:
            return

        MAX_REWARD = 10000

        points = StreamHelper.get_viewers() * 5
        if points == 0:
            points = 100
        length = 30

        points = min(points, MAX_REWARD)

        points += (num_months - 1) * 500

        self.multi_start_raffle(points, length)

    def enable(self, bot):
        self.bot = bot

        HandlerManager.add_handler('on_user_sub', self.on_user_sub)
        HandlerManager.add_handler('on_user_resub', self.on_user_resub)

    def disable(self, bot):
        HandlerManager.remove_handler('on_user_sub', self.on_user_sub)
        HandlerManager.remove_handler('on_user_resub', self.on_user_resub)
Exemple #17
0
class LinkCheckerModule(BaseModule):

    ID = __name__.split(".")[-1]
    NAME = "Link Checker"
    DESCRIPTION = "Checks links if they're bad"
    ENABLED_DEFAULT = True
    CATEGORY = "Filter"
    SETTINGS = [
        ModuleSetting(
            key="ban_pleb_links",
            label="Disallow links from non-subscribers",
            type="boolean",
            required=True,
            default=False,
        ),
        ModuleSetting(key="ban_sub_links",
                      label="Disallow links from subscribers",
                      type="boolean",
                      required=True,
                      default=False),
        ModuleSetting(
            key="timeout_length",
            label="Timeout length",
            type="number",
            required=True,
            placeholder="Timeout length in seconds",
            default=60,
            constraints={
                "min_value": 1,
                "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
            },
        ),
    ]

    def __init__(self, bot):
        super().__init__(bot)
        self.db_session = None
        self.links = {}

        self.blacklisted_links = []
        self.whitelisted_links = []

        self.cache = LinkCheckerCache(
        )  # cache[url] = True means url is safe, False means the link is bad

        if bot and "safebrowsingapi" in bot.config["main"]:
            # XXX: This should be loaded as a setting instead.
            # There needs to be a setting for settings to have them as "passwords"
            # so they're not displayed openly
            self.safe_browsing_api = SafeBrowsingAPI(
                bot.config["main"]["safebrowsingapi"])
        else:
            self.safe_browsing_api = None

    def enable(self, bot):
        if not bot:
            return

        HandlerManager.add_handler("on_message", self.on_message, priority=100)
        HandlerManager.add_handler("on_commit", self.on_commit)

        if self.db_session is not None:
            self.db_session.commit()
            self.db_session.close()
            self.db_session = None
        self.db_session = DBManager.create_session()
        self.blacklisted_links = []
        for link in self.db_session.query(BlacklistedLink):
            self.blacklisted_links.append(link)

        self.whitelisted_links = []
        for link in self.db_session.query(WhitelistedLink):
            self.whitelisted_links.append(link)

    def disable(self, bot):
        if not bot:
            return

        pajbot.managers.handler.HandlerManager.remove_handler(
            "on_message", self.on_message)
        pajbot.managers.handler.HandlerManager.remove_handler(
            "on_commit", self.on_commit)

        if self.db_session is not None:
            self.db_session.commit()
            self.db_session.close()
            self.db_session = None
            self.blacklisted_links = []
            self.whitelisted_links = []

    def reload(self):

        log.info(
            f"Loaded {len(self.blacklisted_links)} bad links and {len(self.whitelisted_links)} good links"
        )
        return self

    super_whitelist = ["pajlada.se", "pajlada.com", "forsen.tv", "pajbot.com"]

    def on_message(self, source, whisper, urls, **rest):
        if whisper:
            return

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

        if len(urls) > 0:
            do_timeout = False
            ban_reason = "You are not allowed to post links in chat"
            whisper_reason = "??? KKona"

            if self.settings[
                    "ban_pleb_links"] is True and source.subscriber is False:
                do_timeout = True
                whisper_reason = "You cannot post non-verified links in chat if you're a pleb"
            elif self.settings[
                    "ban_sub_links"] is True and source.subscriber is True:
                do_timeout = True
                whisper_reason = "You cannot post non-verified links in chat if you're a subscriber"

            if do_timeout is True:
                # Check if the links are in our super-whitelist. i.e. on the pajlada.se domain o forsen.tv
                for url in urls:
                    parsed_url = Url(url)
                    if len(parsed_url.parsed.netloc.split(".")) < 2:
                        continue
                    whitelisted = False
                    for whitelist in self.super_whitelist:
                        if is_subdomain(parsed_url.parsed.netloc, whitelist):
                            whitelisted = True
                            break

                    if whitelisted is False and self.is_whitelisted(url):
                        whitelisted = True

                    if whitelisted is False:
                        self.bot.timeout(source,
                                         self.settings["timeout_length"],
                                         reason=ban_reason)
                        if source.time_in_chat_online >= timedelta(hours=1):
                            self.bot.whisper(source, whisper_reason)
                        return False

        for url in urls:
            # Action which will be taken when a bad link is found
            def action():
                self.bot.timeout(source,
                                 self.settings["timeout_length"],
                                 reason="Banned link")

            # First we perform a basic check
            if self.simple_check(url, action) == self.RET_FURTHER_ANALYSIS:
                # If the basic check returns no relevant data, we queue up a proper check on the URL
                self.bot.action_queue.submit(self.check_url, url, action)

    def on_commit(self, **rest):
        if self.db_session is not None:
            self.db_session.commit()

    def delete_from_cache(self, url):
        if url in self.cache:
            del self.cache[url]

    def cache_url(self, url, safe):
        if url in self.cache and self.cache[url] == safe:
            return

        self.cache[url] = safe
        self.bot.execute_delayed(20, self.delete_from_cache, url)

    def counteract_bad_url(self,
                           url,
                           action=None,
                           want_to_cache=True,
                           want_to_blacklist=False):
        log.debug(f"LinkChecker: BAD URL FOUND {url.url}")
        if action:
            action()
        if want_to_cache:
            self.cache_url(url.url, False)
        if want_to_blacklist:
            self.blacklist_url(url.url, url.parsed)
            return True

    def blacklist_url(self, url, parsed_url=None, level=0):
        if not (url.lower().startswith("http://")
                or url.lower().startswith("https://")):
            url = "http://" + url

        if parsed_url is None:
            parsed_url = urllib.parse.urlparse(url)

        if self.is_blacklisted(url, parsed_url):
            return False

        domain = parsed_url.netloc.lower()
        path = parsed_url.path.lower()

        if domain.startswith("www."):
            domain = domain[4:]
        if path.endswith("/"):
            path = path[:-1]
        if path == "":
            path = "/"

        link = BlacklistedLink(domain, path, level)
        self.db_session.add(link)
        self.blacklisted_links.append(link)
        self.db_session.commit()

    def whitelist_url(self, url, parsed_url=None):
        if not (url.lower().startswith("http://")
                or url.lower().startswith("https://")):
            url = "http://" + url
        if parsed_url is None:
            parsed_url = urllib.parse.urlparse(url)
        if self.is_whitelisted(url, parsed_url):
            return

        domain = parsed_url.netloc.lower()
        path = parsed_url.path.lower()

        if domain.startswith("www."):
            domain = domain[4:]
        if path.endswith("/"):
            path = path[:-1]
        if path == "":
            path = "/"

        link = WhitelistedLink(domain, path)
        self.db_session.add(link)
        self.whitelisted_links.append(link)
        self.db_session.commit()

    def is_blacklisted(self, url, parsed_url=None, sublink=False):
        if parsed_url is None:
            parsed_url = urllib.parse.urlparse(url)
        domain = parsed_url.netloc.lower()
        path = parsed_url.path.lower()
        if path == "":
            path = "/"

        domain_split = domain.split(".")
        if len(domain_split) < 2:
            return False

        for link in self.blacklisted_links:
            if link.is_subdomain(domain):
                if link.is_subpath(path):
                    if not sublink:
                        return True
                    elif (
                            link.level >= 1
                    ):  # if it's a sublink, but the blacklisting level is 0, we don't consider it blacklisted
                        return True

        return False

    def is_whitelisted(self, url, parsed_url=None):
        if parsed_url is None:
            parsed_url = urllib.parse.urlparse(url)
        domain = parsed_url.netloc.lower()
        path = parsed_url.path.lower()
        if path == "":
            path = "/"

        domain_split = domain.split(".")
        if len(domain_split) < 2:
            return False

        for link in self.whitelisted_links:
            if link.is_subdomain(domain):
                if link.is_subpath(path):
                    return True

        return False

    RET_BAD_LINK = -1
    RET_FURTHER_ANALYSIS = 0
    RET_GOOD_LINK = 1

    def basic_check(self, url, action, sublink=False):
        """
        Check if the url is in the cache, or if it's
        Return values:
        1 = Link is OK
        -1 = Link is bad
        0 = Link needs further analysis
        """
        if url.url in self.cache:
            if not self.cache[url.url]:  # link is bad
                self.counteract_bad_url(url, action, False, False)
                return self.RET_BAD_LINK

            return self.RET_GOOD_LINK

        if self.is_blacklisted(url.url, url.parsed, sublink):
            self.counteract_bad_url(url, action, want_to_blacklist=False)
            return self.RET_BAD_LINK

        if self.is_whitelisted(url.url, url.parsed):
            self.cache_url(url.url, True)
            return self.RET_GOOD_LINK

        return self.RET_FURTHER_ANALYSIS

    def simple_check(self, url, action):
        url = Url(url)
        if len(url.parsed.netloc.split(".")) < 2:
            # The URL is broken, ignore it
            return self.RET_FURTHER_ANALYSIS

        return self.basic_check(url, action)

    def check_url(self, url, action):
        url = Url(url)
        if len(url.parsed.netloc.split(".")) < 2:
            # The URL is broken, ignore it
            return

        try:
            self._check_url(url, action)
        except:
            log.exception("LinkChecker unhandled exception while _check_url")

    def _check_url(self, url, action):
        # XXX: The basic check is currently performed twice on links found in messages. Solve
        res = self.basic_check(url, action)
        if res == self.RET_GOOD_LINK:
            return
        elif res == self.RET_BAD_LINK:
            return

        connection_timeout = 2
        read_timeout = 1
        try:
            r = requests.head(url.url,
                              allow_redirects=True,
                              timeout=connection_timeout,
                              headers={"User-Agent": self.bot.user_agent})
        except:
            self.cache_url(url.url, True)
            return

        checkcontenttype = "content-type" in r.headers and r.headers[
            "content-type"] == "application/octet-stream"
        checkdispotype = "disposition-type" in r.headers and r.headers[
            "disposition-type"] == "attachment"

        if checkcontenttype or checkdispotype:  # triggering a download not allowed
            self.counteract_bad_url(url, action)
            return

        redirected_url = Url(r.url)
        if is_same_url(url, redirected_url) is False:
            res = self.basic_check(redirected_url, action)
            if res == self.RET_GOOD_LINK:
                return
            elif res == self.RET_BAD_LINK:
                return

        if self.safe_browsing_api and self.safe_browsing_api.is_url_bad(
                redirected_url.url):  # harmful url detected
            log.debug("Google Safe Browsing API lists URL")
            self.counteract_bad_url(url, action, want_to_blacklist=False)
            self.counteract_bad_url(redirected_url, want_to_blacklist=False)
            return

        if "content-type" not in r.headers or not r.headers[
                "content-type"].startswith("text/html"):
            return  # can't analyze non-html content
        maximum_size = 1024 * 1024 * 10  # 10 MB
        receive_timeout = 3

        html = ""
        try:
            response = requests.get(
                url=url.url,
                stream=True,
                timeout=(connection_timeout, read_timeout),
                headers={"User-Agent": self.bot.user_agent},
            )

            content_length = response.headers.get("Content-Length")
            if content_length and int(
                    response.headers.get("Content-Length")) > maximum_size:
                log.error("This file is too big!")
                return

            size = 0
            start = pajbot.utils.now().timestamp()

            for chunk in response.iter_content(1024):
                if pajbot.utils.now().timestamp() - start > receive_timeout:
                    log.error("The site took too long to load")
                    return

                size += len(chunk)
                if size > maximum_size:
                    log.error("This file is too big! (fake header)")
                    return
                html += str(chunk)

        except requests.exceptions.ConnectTimeout:
            log.warning(f"Connection timed out while checking {url.url}")
            self.cache_url(url.url, True)
            return
        except requests.exceptions.ReadTimeout:
            log.warning(f"Reading timed out while checking {url.url}")
            self.cache_url(url.url, True)
            return
        except:
            log.exception("Unhandled exception")
            return

        try:
            soup = BeautifulSoup(html, "html.parser")
        except:
            return

        original_url = url
        original_redirected_url = redirected_url
        urls = []
        for link in soup.find_all(
                "a"):  # get a list of links to external sites
            url = link.get("href")
            if url is None:
                continue
            if url.startswith("//"):
                urls.append("http:" + url)
            elif url.startswith("http://") or url.startswith("https://"):
                urls.append(url)

        for url in urls:  # check if the site links to anything dangerous
            url = Url(url)

            if is_subdomain(url.parsed.netloc, original_url.parsed.netloc):
                # log.debug('Skipping because internal link')
                continue

            res = self.basic_check(url, action, sublink=True)
            if res == self.RET_BAD_LINK:
                self.counteract_bad_url(url)
                self.counteract_bad_url(original_url, want_to_blacklist=False)
                self.counteract_bad_url(original_redirected_url,
                                        want_to_blacklist=False)
                return
            elif res == self.RET_GOOD_LINK:
                continue

            try:
                r = requests.head(
                    url.url,
                    allow_redirects=True,
                    timeout=connection_timeout,
                    headers={"User-Agent": self.bot.user_agent},
                )
            except:
                continue

            redirected_url = Url(r.url)
            if not is_same_url(url, redirected_url):
                res = self.basic_check(redirected_url, action, sublink=True)
                if res == self.RET_BAD_LINK:
                    self.counteract_bad_url(url)
                    self.counteract_bad_url(original_url,
                                            want_to_blacklist=False)
                    self.counteract_bad_url(original_redirected_url,
                                            want_to_blacklist=False)
                    return
                elif res == self.RET_GOOD_LINK:
                    continue

            if self.safe_browsing_api and self.safe_browsing_api.is_url_bad(
                    redirected_url.url):  # harmful url detected
                log.debug(f"Evil sublink {url} by google API")
                self.counteract_bad_url(original_url, action)
                self.counteract_bad_url(original_redirected_url)
                self.counteract_bad_url(url)
                self.counteract_bad_url(redirected_url)
                return

        # if we got here, the site is clean for our standards
        self.cache_url(original_url.url, True)
        self.cache_url(original_redirected_url.url, True)
        return

    def load_commands(self, **options):
        self.commands["add"] = Command.multiaction_command(
            level=100,
            delay_all=0,
            delay_user=0,
            default=None,
            command="add",
            commands={
                "link":
                Command.multiaction_command(
                    level=500,
                    delay_all=0,
                    delay_user=0,
                    default=None,
                    commands={
                        "blacklist":
                        Command.raw_command(
                            self.add_link_blacklist,
                            level=500,
                            delay_all=0,
                            delay_user=0,
                            description="Blacklist a link",
                            examples=[
                                CommandExample(
                                    None,
                                    "Add a link to the blacklist for a shallow search",
                                    chat=
                                    "user:!add link blacklist --shallow scamlink.lonk/\n"
                                    "bot>user:Successfully added your links",
                                    description=
                                    "Added the link scamlink.lonk/ to the blacklist for a shallow search",
                                ).parse(),
                                CommandExample(
                                    None,
                                    "Add a link to the blacklist for a deep search",
                                    chat=
                                    "user:!add link blacklist --deep scamlink.lonk/\n"
                                    "bot>user:Successfully added your links",
                                    description=
                                    "Added the link scamlink.lonk/ to the blacklist for a deep search",
                                ).parse(),
                            ],
                        ),
                        "whitelist":
                        Command.raw_command(
                            self.add_link_whitelist,
                            level=500,
                            delay_all=0,
                            delay_user=0,
                            description="Whitelist a link",
                            examples=[
                                CommandExample(
                                    None,
                                    "Add a link to the whitelist",
                                    chat=
                                    "user:!add link whitelink safelink.lonk/\n"
                                    "bot>user:Successfully added your links",
                                    description=
                                    "Added the link safelink.lonk/ to the whitelist",
                                ).parse()
                            ],
                        ),
                    },
                )
            },
        )

        self.commands["remove"] = Command.multiaction_command(
            level=100,
            delay_all=0,
            delay_user=0,
            default=None,
            command="remove",
            commands={
                "link":
                Command.multiaction_command(
                    level=500,
                    delay_all=0,
                    delay_user=0,
                    default=None,
                    commands={
                        "blacklist":
                        Command.raw_command(
                            self.remove_link_blacklist,
                            level=500,
                            delay_all=0,
                            delay_user=0,
                            description="Remove a link from the blacklist.",
                            examples=[
                                CommandExample(
                                    None,
                                    "Remove a link from the blacklist.",
                                    chat="user:!remove link blacklist 20\n"
                                    "bot>user:Successfully removed blacklisted link with id 20",
                                    description=
                                    "Remove a link from the blacklist with an ID",
                                ).parse()
                            ],
                        ),
                        "whitelist":
                        Command.raw_command(
                            self.remove_link_whitelist,
                            level=500,
                            delay_all=0,
                            delay_user=0,
                            description="Remove a link from the whitelist.",
                            examples=[
                                CommandExample(
                                    None,
                                    "Remove a link from the whitelist.",
                                    chat="user:!remove link whitelist 12\n"
                                    "bot>user:Successfully removed blacklisted link with id 12",
                                    description=
                                    "Remove a link from the whitelist with an ID",
                                ).parse()
                            ],
                        ),
                    },
                )
            },
        )

    def add_link_blacklist(self, bot, source, message, **rest):
        options, new_links = self.parse_link_blacklist_arguments(message)

        if new_links:
            parts = new_links.split(" ")
            try:
                for link in parts:
                    if len(link) > 1:
                        self.blacklist_url(link, **options)
                        AdminLogManager.post("Blacklist link added", source,
                                             link)
                bot.whisper(source, "Successfully added your links")
                return True
            except:
                log.exception("Unhandled exception in add_link_blacklist")
                bot.whisper(source,
                            "Some error occurred while adding your links")
                return False
        else:
            bot.whisper(source, "Usage: !add link blacklist LINK")
            return False

    def add_link_whitelist(self, bot, source, message, **rest):
        parts = message.split(" ")
        try:
            for link in parts:
                self.whitelist_url(link)
                AdminLogManager.post("Whitelist link added", source, link)
        except:
            log.exception("Unhandled exception in add_link")
            bot.whisper(source, "Some error occurred white adding your links")
            return False

        bot.whisper(source, "Successfully added your links")

    def remove_link_blacklist(self, bot, source, message, **rest):
        if not message:
            bot.whisper(source, "Usage: !remove link blacklist ID")
            return False

        id = None
        try:
            id = int(message)
        except ValueError:
            pass

        link = self.db_session.query(BlacklistedLink).filter_by(
            id=id).one_or_none()

        if link:
            self.blacklisted_links.remove(link)
            self.db_session.delete(link)
            self.db_session.commit()
        else:
            bot.whisper(source, "No link with the given id found")
            return False

        AdminLogManager.post("Blacklist link removed", source, link.domain)
        bot.whisper(
            source, f"Successfully removed blacklisted link with id {link.id}")

    def remove_link_whitelist(self, bot, source, message, **rest):
        if not message:
            bot.whisper(source, "Usage: !remove link whitelist ID")
            return False

        id = None
        try:
            id = int(message)
        except ValueError:
            pass

        link = self.db_session.query(WhitelistedLink).filter_by(
            id=id).one_or_none()

        if link:
            self.whitelisted_links.remove(link)
            self.db_session.delete(link)
            self.db_session.commit()
        else:
            bot.whisper(source, "No link with the given id found")
            return False

        AdminLogManager.post("Whitelist link removed", source, link.domain)
        bot.whisper(
            source, f"Successfully removed whitelisted link with id {link.id}")

    @staticmethod
    def parse_link_blacklist_arguments(message):
        parser = argparse.ArgumentParser()
        parser.add_argument("--deep", dest="level", action="store_true")
        parser.add_argument("--shallow", dest="level", action="store_false")
        parser.set_defaults(level=False)

        try:
            args, unknown = parser.parse_known_args(message.split())
        except SystemExit:
            return False, False
        except:
            log.exception("Unhandled exception in add_link_blacklist")
            return False, False

        # Strip options of any values that are set as None
        options = {k: v for k, v in vars(args).items() if v is not None}
        response = " ".join(unknown)

        if "level" in options:
            options["level"] = int(options["level"])

        return options, response
Exemple #18
0
class SlotMachineModule(BaseModule):

    ID = __name__.split(".")[-1]
    NAME = "Slot Machine"
    DESCRIPTION = "Lets players play slot machines for points"
    CATEGORY = "Game"
    SETTINGS = [
        ModuleSetting(
            key="message_won",
            label=
            "Won message | Available arguments: {bet}, {points}, {user}, {emotes}, {result}",
            type="text",
            required=True,
            placeholder="{user} | {emotes} | won {result} points PogChamp",
            default="{user} | {emotes} | won {result} points PogChamp",
            constraints={
                "min_str_len": 10,
                "max_str_len": 400
            },
        ),
        ModuleSetting(
            key="message_lost",
            label=
            "Lost message | Available arguments: {bet}, {points}, {user}, {emotes}",
            type="text",
            required=True,
            placeholder="{user} | {emotes} | lost {bet} points LUL",
            default="{user} | {emotes} | lost {bet} points LUL",
            constraints={
                "min_str_len": 10,
                "max_str_len": 400
            },
        ),
        ModuleSetting(
            key="low_tier_emotes",
            label=
            "Low tier emotes, space-separated. Low-tier emote are 3 times as likely to appear as high tier emotes (they get 3 slots compared to high emotes 1 slot per roll)",
            type="text",
            required=True,
            placeholder="KKona 4Head NaM",
            default="KKona 4Head NaM",
            constraints={
                "min_str_len": 0,
                "max_str_len": 400
            },
        ),
        ModuleSetting(
            key="high_tier_emotes",
            label="High tier emotes, space-separated",
            type="text",
            required=True,
            placeholder="OpieOP EleGiggle",
            default="OpieOP EleGiggle",
            constraints={
                "min_str_len": 0,
                "max_str_len": 400
            },
        ),
        ModuleSetting(
            key="ltsw",
            label="Low tier small win (Percentage) 22.6% with 2 low 2 high",
            type="number",
            required=True,
            placeholder="",
            default=125,
            constraints={
                "min_value": 0,
                "max_value": 1000000
            },
        ),
        ModuleSetting(
            key="ltbw",
            label="Low tier big win (Percentage) 0.98% with 2 low 2 high",
            type="number",
            required=True,
            placeholder="",
            default=175,
            constraints={
                "min_value": 0,
                "max_value": 1000000
            },
        ),
        ModuleSetting(
            key="htsw",
            label="High tier small win (Percentage) 0.14% with 2 low 2 high",
            type="number",
            required=True,
            placeholder="",
            default=225,
            constraints={
                "min_value": 0,
                "max_value": 1000000
            },
        ),
        ModuleSetting(
            key="htbw",
            label="High tier big win (Percentage) 0.07% with 2 low 2 high",
            type="number",
            required=True,
            placeholder="",
            default=400,
            constraints={
                "min_value": 0,
                "max_value": 1000000
            },
        ),
        ModuleSetting(
            key="online_global_cd",
            label="Global cooldown (seconds)",
            type="number",
            required=True,
            placeholder="",
            default=0,
            constraints={
                "min_value": 0,
                "max_value": 120
            },
        ),
        ModuleSetting(
            key="online_user_cd",
            label="Per-user cooldown (seconds)",
            type="number",
            required=True,
            placeholder="",
            default=60,
            constraints={
                "min_value": 0,
                "max_value": 240
            },
        ),
        ModuleSetting(
            key="min_bet",
            label="Minimum bet",
            type="number",
            required=True,
            placeholder="",
            default=1,
            constraints={
                "min_value": 1,
                "max_value": 1000000
            },
        ),
        ModuleSetting(
            key="can_execute_with_whisper",
            label="Allow users to use the module from whispers",
            type="boolean",
            required=True,
            default=False,
        ),
        ModuleSetting(
            key="options_output",
            label="Result output options",
            type="options",
            required=True,
            default="1. Show results in chat",
            options=[
                "1. Show results in chat",
                "2. Show results in whispers",
                "3. Show results in chat if it's over X points else it will be whispered.",
                "4. Combine output in chat",
            ],
        ),
        ModuleSetting(
            key="min_show_points",
            label="Min points you need to win or lose (if options 3)",
            type="number",
            required=True,
            placeholder="",
            default=100,
            constraints={
                "min_value": 1,
                "max_value": 1000000
            },
        ),
        ModuleSetting(
            key="only_slots_after_sub",
            label="Only allow slots after sub",
            type="boolean",
            required=True,
            default=False,
        ),
        ModuleSetting(
            key="after_sub_slots_time",
            label=
            "How long after a sub people can use the slot machine (seconds)",
            type="number",
            required=True,
            placeholder="",
            default=30,
            constraints={
                "min_value": 5,
                "max_value": 3600
            },
        ),
        ModuleSetting(
            key="alert_message_after_sub",
            label=
            "Message to announce the allowance of slotmachine usage after re/sub, leave empty to disable the message. | Available arguments: {seconds}",
            type="text",
            required=True,
            default=
            "Slot machine is now allowed for {seconds} seconds! PogChamp",
            constraints={
                "min_str_len": 0,
                "max_str_len": 300
            },
        ),
    ]

    def __init__(self, bot):
        super().__init__(bot)
        self.last_sub = None
        self.output_buffer = ""
        self.output_buffer_args = []
        self.last_add = None

    def load_commands(self, **options):
        self.commands["slotmachine"] = Command.raw_command(
            self.pull,
            delay_all=self.settings["online_global_cd"],
            delay_user=self.settings["online_user_cd"],
            description="play slot machine for points",
            can_execute_with_whisper=self.settings["can_execute_with_whisper"],
            examples=[
                CommandExample(
                    None,
                    "SlotMachine for 69 points",
                    chat="user:!slotmachine 69\n"
                    "bot:pajlada won 69 points in slotmachine xd! FeelsGoodMan",
                    description="Do a slot machine pull for 69 points",
                ).parse()
            ],
        )
        self.commands["smp"] = self.commands["slotmachine"]
        self.commands["slots"] = self.commands["slotmachine"]

    def pull(self, bot, source, message, **rest):
        if self.settings["only_slots_after_sub"]:
            if self.last_sub is None:
                return False
            if utils.now() - self.last_sub > datetime.timedelta(
                    seconds=self.settings["after_sub_slots_time"]):
                return False

        if message is None:
            bot.whisper(
                source,
                "I didn't recognize your bet! Usage: !slotmachine 150 to bet 150 points"
            )
            return False

        low_tier_emotes = self.settings["low_tier_emotes"].split()
        high_tier_emotes = self.settings["high_tier_emotes"].split()

        if len(low_tier_emotes) == 0 or len(high_tier_emotes) == 0:
            return False

        msg_split = message.split(" ")
        try:
            bet = pajbot.utils.parse_points_amount(source, msg_split[0])
        except pajbot.exc.InvalidPointAmount as e:
            bot.whisper(source, str(e))
            return False

        if not source.can_afford(bet):
            bot.whisper(
                source,
                f"You don't have enough points to do a slot machine pull for {bet} points :("
            )
            return False

        if bet < self.settings["min_bet"]:
            bot.whisper(
                source,
                f"You have to bet at least {self.settings['min_bet']} point! :("
            )
            return False

        # how much of the users point they're expected to get back (basically how much the house yoinks)
        expected_return = 1.0

        ltsw = self.settings["ltsw"] / 100.0
        htsw = self.settings["htsw"] / 100.0
        ltbw = self.settings["ltbw"] / 100.0
        htbw = self.settings["htbw"] / 100.0

        bet_return, randomized_emotes = pull_lol(low_tier_emotes,
                                                 high_tier_emotes, bet,
                                                 expected_return, ltsw, htsw,
                                                 ltbw, htbw)

        # Calculating the result
        if bet_return <= 0.0:
            points = -bet
        else:
            points = bet * bet_return

        source.points += points

        arguments = {
            "bet": bet,
            "result": points,
            "user": source.name,
            "points": source.points,
            "win": points > 0,
            "emotes": " ".join(randomized_emotes),
        }

        if points > 0:
            out_message = self.get_phrase("message_won", **arguments)
        else:
            out_message = self.get_phrase("message_lost", **arguments)

        if self.settings["options_output"] == "4. Combine output in chat":
            if bot.is_online:
                self.add_message(bot, arguments)
            else:
                bot.me(out_message)
        if self.settings["options_output"] == "1. Show results in chat":
            bot.me(out_message)
        if self.settings["options_output"] == "2. Show results in whispers":
            bot.whisper(source, out_message)
        if (self.settings["options_output"] ==
                "3. Show results in chat if it's over X points else it will be whispered."
            ):
            if abs(points) >= self.settings["min_show_points"]:
                bot.me(out_message)
            else:
                bot.whisper(source, out_message)

        HandlerManager.trigger("on_slot_machine_finish",
                               user=source,
                               points=points)

    def on_tick(self, **rest):
        if self.output_buffer == "":
            return

        if self.last_add is None:
            return

        diff = utils.now() - self.last_add

        if diff.seconds > 3:
            self.flush_output_buffer()

    def flush_output_buffer(self):
        msg = self.output_buffer
        self.bot.me(msg)
        self.output_buffer = ""
        self.output_buffer_args = []

    def add_message(self, bot, arguments):
        parts = []
        new_buffer = "SlotMachine: "
        win_emote = "forsenPls"
        lose_emote = "forsenSWA"
        for arg in self.output_buffer_args:
            parts.append(
                f"{win_emote if arg['win'] else lose_emote} {arg['user']} {'+' if arg['win'] else '-'}{arg['bet']}"
            )

        parts.append(
            f"{win_emote if arguments['win'] else lose_emote} {arguments['user']} {'+' if arguments['win'] else '-'}{arguments['bet']}"
        )

        log.debug(parts)
        new_buffer += ", ".join(parts)

        if len(new_buffer) > 480:
            self.flush_output_buffer()
        else:
            self.output_buffer = new_buffer
            log.info("Set output buffer to " + new_buffer)

        self.output_buffer_args.append(arguments)

        self.last_add = utils.now()

    def on_user_sub_or_resub(self, **rest):
        now = utils.now()

        # True if we already announced the alert_message_after_sub within the last 5 seconds. Prevents
        # spam after bulk sub gifts.
        skip_message = self.last_sub is not None and now - self.last_sub < datetime.timedelta(
            seconds=5)

        self.last_sub = now
        if (self.settings["only_slots_after_sub"]
                and self.settings["alert_message_after_sub"] != ""
                and not skip_message):
            self.bot.say(self.settings["alert_message_after_sub"].format(
                seconds=self.settings["after_sub_slots_time"]))

    def enable(self, bot):
        HandlerManager.add_handler("on_user_sub", self.on_user_sub_or_resub)
        HandlerManager.add_handler("on_user_resub", self.on_user_sub_or_resub)
        HandlerManager.add_handler("on_tick", self.on_tick)

    def disable(self, bot):
        HandlerManager.remove_handler("on_user_sub", self.on_user_sub_or_resub)
        HandlerManager.remove_handler("on_user_resub",
                                      self.on_user_sub_or_resub)
        HandlerManager.remove_handler("on_tick", self.on_tick)
Exemple #19
0
class AbCommandModule(BaseModule):

    ID = __name__.split(".")[-1]
    NAME = "Add Between"
    DESCRIPTION = "Inject an emote inbetween each letter/word in message via the !ab command"
    CATEGORY = "Feature"
    PARENT_MODULE = BasicCommandsModule
    SETTINGS = [
        ModuleSetting(
            key="level",
            label="minimum level (make sure people don't abuse this command)",
            type="number",
            required=True,
            placeholder="",
            default=250,
            constraints={
                "min_value": 100,
                "max_value": 2000
            },
        ),
        ModuleSetting(
            key="global_cd",
            label="Global cooldown (seconds)",
            type="number",
            required=True,
            placeholder="",
            default=15,
            constraints={
                "min_value": 0,
                "max_value": 240
            },
        ),
        ModuleSetting(
            key="user_cd",
            label="Per-user cooldown (seconds)",
            type="number",
            required=True,
            placeholder="",
            default=30,
            constraints={
                "min_value": 0,
                "max_value": 240
            },
        ),
    ]

    @staticmethod
    def ab(bot, source, message, **rest):
        if not message:
            return False

        # check if there is a link in the message
        check_message = find_unique_urls(URL_REGEX, message)
        if len(check_message) > 0:
            return False

        msg_parts = message.split(" ")
        if len(msg_parts) >= 2:
            outer_str = msg_parts[0]
            inner_str = f" {outer_str} ".join(
                msg_parts[1:] if len(msg_parts) >= 3 else msg_parts[1])
            bot.say(f"{source}, {outer_str} {inner_str} {outer_str}")

    def load_commands(self, **options):
        self.commands["ab"] = Command.raw_command(
            self.ab,
            delay_all=self.settings["global_cd"],
            delay_user=self.settings["user_cd"],
            level=self.settings["level"],
            description="Inject emote inbetween each letter/word in message",
            command="ab",
            examples=[
                CommandExample(
                    None,
                    "Inject emote inbetween each letter in message",
                    chat="user:!ab Keepo KEEPO\n"
                    "bot:pajlada, Keepo K Keepo E Keepo E Keepo P Keepo O Keepo",
                    description="",
                ).parse(),
                CommandExample(
                    None,
                    "Inject emote inbetween each word in message",
                    chat="user:!ab Kreygasm NOW THATS WHAT I CALL MUSIC\n"
                    "bot:pajlada, Kreygasm NOW Kreygasm THATS Kreygasm WHAT Kreygasm I Kreygasm CALL Kreygasm MUSIC Kreygasm",
                    description="",
                ).parse(),
            ],
        )
        self.commands["abc"] = self.commands["ab"]
Exemple #20
0
class DubtrackModule(BaseModule):
    AUTHOR = 'TalVivian @ github.com/TalVivian'
    ID = __name__.split('.')[-1]
    NAME = 'Dubtrack module'
    DESCRIPTION = 'Gets currently playing song from dubtrack'
    CATEGORY = 'Feature'
    SETTINGS = [
        ModuleSetting(key='room_name',
                      label='Dubtrack room',
                      type='text',
                      required=True,
                      placeholder='Dubtrack room (i.e. pajlada)',
                      default='pajlada',
                      constraints={
                          'min_str_len': 1,
                          'max_str_len': 70,
                      }),
        ModuleSetting(
            key='phrase_room_link',
            label='Room link message | Available arguments: {room_name}',
            type='text',
            required=True,
            placeholder=
            'Request your songs at https://dubtrack.fm/join/{room_name}',
            default=
            'Request your songs at https://dubtrack.fm/join/{room_name}',
            constraints={
                'min_str_len': 1,
                'max_str_len': 400,
            }),
        ModuleSetting(
            key='phrase_current_song',
            label=
            'Current song message | Available arguments: {song_name}, {song_link}',
            type='text',
            required=True,
            placeholder='Current song: {song_name}, link: {song_link}',
            default='Current song: {song_name}, link: {song_link}',
            constraints={
                'min_str_len': 1,
                'max_str_len': 400,
            }),
        ModuleSetting(
            key='phrase_current_song_no_link',
            label=
            'Current song message if no song link is available | Available arguments: {song_name}',
            type='text',
            required=True,
            placeholder='Current song: {song_name}',
            default='Current song: {song_name}',
            constraints={
                'min_str_len': 1,
                'max_str_len': 400,
            }),
        ModuleSetting(
            key='phrase_no_current_song',
            label='Current song message when there\'s nothing playing',
            type='text',
            required=True,
            placeholder='There\'s no song playing right now FeelsBadMan',
            default='There\'s no song playing right now FeelsBadMan',
            constraints={
                'min_str_len': 1,
                'max_str_len': 400,
            }),
        ModuleSetting(key='global_cd',
                      label='Global cooldown (seconds)',
                      type='number',
                      required=True,
                      placeholder='',
                      default=5,
                      constraints={
                          'min_value': 0,
                          'max_value': 120,
                      }),
        ModuleSetting(key='user_cd',
                      label='Per-user cooldown (seconds)',
                      type='number',
                      required=True,
                      placeholder='',
                      default=15,
                      constraints={
                          'min_value': 0,
                          'max_value': 240,
                      }),
        ModuleSetting(
            key='if_dt_alias',
            label='Allow !dt as !dubtrack',
            type='boolean',
            required=True,
            default=True,
        ),
        ModuleSetting(
            key='if_short_alias',
            label='Allow !dubtrack [s, l, u] as !dubtrack [song, link, update]',
            type='boolean',
            required=True,
            default=True,
        ),
        ModuleSetting(
            key='if_song_alias',
            label='Allow !song as !dubtrack song',
            type='boolean',
            required=True,
            default=True,
        ),
    ]

    def __init__(self, **options):
        super().__init__()
        self.clear()

    def link(self, **options):
        bot = options['bot']
        arguments = {'room_name': self.settings['room_name']}
        bot.say(self.get_phrase('phrase_room_link', **arguments))

    def clear(self):
        self.song_name = None
        self.song_id = None
        self.song_link = None

    def update_song(self, force=False):
        if force:
            self.clear()

        url = 'https://api.dubtrack.fm/room/' + self.settings['room_name']

        r = requests.get(url)
        if r.status_code != 200:
            log.warning('Dubtrack api not responding')
            self.clear()
            return

        text = json.loads(r.text)
        if text['code'] != 200:
            log.warning('Dubtrack api invalid response')
            self.clear()
            return

        data = text['data']['currentSong']
        if data is None:
            # No song playing
            self.clear()
            return

        if self.song_id == data['songid']:
            # No need to update song
            return

        raw_song_name = data['name']
        self.song_name = html.unescape(raw_song_name)
        self.song_id = data['songid']

        if data['type'] == 'youtube':
            self.song_link = 'https://youtu.be/' + data['fkid']
        elif data['type'] == 'soundcloud':
            url = 'https://api.dubtrack.fm/song/' + data['songid'] + '/redirect'

            self.song_link = None

            r = requests.get(url, allow_redirects=False)
            if r.status_code != 301:
                log.warning('Couldn\'t resolve soundcloud link')
                return

            new_song_link = r.headers['Location']
            self.song_link = re.sub('^http', 'https', new_song_link)
        else:
            log.warning('Unknown link type')
            self.song_link = None

    def say_song(self, bot):
        if self.song_name is None:
            bot.say(self.get_phrase('phrase_no_current_song'))
            return

        arguments = {'song_name': self.song_name}

        if self.song_link:
            arguments['song_link'] = self.song_link
            bot.say(self.get_phrase('phrase_current_song', **arguments))
        else:
            bot.say(self.get_phrase('phrase_current_song_no_link',
                                    **arguments))

    def song(self, **options):
        self.update_song()
        self.say_song(options['bot'])

    def update(self, **options):
        self.update_song(force=True)
        self.say_song(options['bot'])

    def load_commands(self, **options):
        commands = {
            'link':
            Command.raw_command(
                self.link,
                level=100,
                delay_all=self.settings['global_cd'],
                delay_user=self.settings['user_cd'],
                description='Get link to your dubtrack',
                examples=[
                    CommandExample(
                        None,
                        'Ask bot for dubtrack link',
                        chat='user:!dubtrack link\n'
                        'bot:Request your songs at https://dubtrack.fm/join/pajlada'
                    ).parse(),
                ],
            ),
            'song':
            Command.raw_command(
                self.song,
                level=100,
                delay_all=self.settings['global_cd'],
                delay_user=self.settings['user_cd'],
                description='Get current song',
                run_in_thread=True,
                examples=[
                    CommandExample(
                        None,
                        'Ask bot for current song (youtube)',
                        chat='user:!dubtrack song\n'
                        'bot:Current song: NOMA - Brain Power, link: https://youtu.be/9R8aSKwTEMg'
                    ).parse(),
                    CommandExample(
                        None,
                        'Ask bot for current song (soundcloud)',
                        chat='user:!dubtrack song\n'
                        'bot:Current song: This is Bondage, link: https://soundcloud.com/razq35/nightlife'
                    ).parse(),
                    CommandExample(
                        None,
                        'Ask bot for current song (nothing playing)',
                        chat='user:!dubtrack song\n'
                        'bot:There\'s no song playing right now FeelsBadMan').
                    parse(),
                ],
            ),
            'update':
            Command.raw_command(
                self.update,
                level=500,
                delay_all=self.settings['global_cd'],
                delay_user=self.settings['user_cd'],
                description='Force reloading the song and get current song',
                run_in_thread=True,
            ),
        }
        if self.settings['if_short_alias']:
            commands['l'] = commands['link']
            commands['s'] = commands['song']
            commands['u'] = commands['update']

        self.commands['dubtrack'] = Command.multiaction_command(
            level=100,
            default='link',  # If the user does not input any argument
            fallback='link',  # If the user inputs an invalid argument
            command='dubtrack',
            commands=commands,
        )

        if self.settings['if_dt_alias']:
            self.commands['dt'] = self.commands['dubtrack']

        if self.settings['if_song_alias']:
            self.commands['song'] = commands['song']
Exemple #21
0
class ShowEmoteModule(BaseModule):
    ID = __name__.split(".")[-1]
    NAME = "Show Emote"
    DESCRIPTION = "Show a single emote on screen for a few seconds using !#showemote"
    CATEGORY = "Feature"
    SETTINGS = [
        ModuleSetting(
            key="point_cost",
            label="Point cost",
            type="number",
            required=True,
            placeholder="Point cost",
            default=100,
            constraints={
                "min_value": 0,
                "max_value": 999999
            },
        ),
        ModuleSetting(
            key="token_cost",
            label="Token cost",
            type="number",
            required=True,
            placeholder="Token cost",
            default=0,
            constraints={
                "min_value": 0,
                "max_value": 15
            },
        ),
        ModuleSetting(key="sub_only",
                      label="Subscribers only",
                      type="boolean",
                      required=True,
                      default=False),
        ModuleSetting(key="can_whisper",
                      label="Command can be whispered",
                      type="boolean",
                      required=True,
                      default=True),
        ModuleSetting(
            key="global_cd",
            label="Global cooldown (seconds)",
            type="number",
            required=True,
            placeholder="",
            default=5,
            constraints={
                "min_value": 0,
                "max_value": 600
            },
        ),
        ModuleSetting(
            key="user_cd",
            label="Per-user cooldown (seconds)",
            type="number",
            required=True,
            placeholder="",
            default=15,
            constraints={
                "min_value": 0,
                "max_value": 1200
            },
        ),
        ModuleSetting(
            key="command_name",
            label="Command name (i.e. #showemote)",
            type="text",
            required=True,
            placeholder="Command name (no !)",
            default="#showemote",
            constraints={
                "min_str_len": 1,
                "max_str_len": 20
            },
        ),
        ModuleSetting(
            key="emote_whitelist",
            label=
            "Whitelisted emotes (separate by spaces). Leave empty to use the blacklist.",
            type="text",
            required=True,
            placeholder="i.e. Kappa Keepo PogChamp KKona",
            default="",
        ),
        ModuleSetting(
            key="emote_blacklist",
            label=
            "Blacklisted emotes (separate by spaces). Leave empty to allow all emotes.",
            type="text",
            required=True,
            placeholder="i.e. Kappa Keepo PogChamp KKona",
            default="",
        ),
        ModuleSetting(
            key="emote_opacity",
            label="Emote opacity (in percent)",
            type="number",
            required=True,
            placeholder="",
            default=100,
            constraints={
                "min_value": 0,
                "max_value": 100
            },
        ),
        ModuleSetting(
            key="emote_persistence_time",
            label="Time in milliseconds until emotes disappear on screen",
            type="number",
            required=True,
            placeholder="",
            default=5000,
            constraints={
                "min_value": 500,
                "max_value": 60000
            },
        ),
        ModuleSetting(
            key="emote_onscreen_scale",
            label="Scale emotes onscreen by this factor (100 = normal size)",
            type="number",
            required=True,
            placeholder="",
            default=100,
            constraints={
                "min_value": 0,
                "max_value": 100000
            },
        ),
        ModuleSetting(
            key="success_whisper",
            label="Send a whisper when emote was successfully sent",
            type="boolean",
            required=True,
            default=True,
        ),
    ]

    def is_emote_allowed(self, emote_code):
        if len(self.settings["emote_whitelist"].strip()) > 0:
            return emote_code in self.settings["emote_whitelist"]

        return emote_code not in self.settings["emote_blacklist"]

    def show_emote(self, bot, source, args, **rest):
        emote_instances = args["emote_instances"]

        if len(emote_instances) <= 0:
            # No emotes in the given message
            bot.whisper(source, "No valid emotes were found in your message.")
            return False

        first_emote = emote_instances[0].emote

        # request to show emote is ignored but return False ensures user is refunded tokens/points
        if not self.is_emote_allowed(first_emote.code):
            return False

        self.bot.websocket_manager.emit(
            "new_emotes",
            {
                "emotes": [first_emote.jsonify()],
                "opacity": self.settings["emote_opacity"],
                "persistence_time": self.settings["emote_persistence_time"],
                "scale": self.settings["emote_onscreen_scale"],
            },
        )

        if self.settings["success_whisper"]:
            bot.whisper(
                source,
                f"Successfully sent the emote {first_emote.code} to the stream!"
            )

    def load_commands(self, **options):
        self.commands[self.settings["command_name"]] = Command.raw_command(
            self.show_emote,
            delay_all=self.settings["global_cd"],
            delay_user=self.settings["user_cd"],
            tokens_cost=self.settings["token_cost"],
            cost=self.settings["point_cost"],
            description="Show an emote on stream!",
            sub_only=self.settings["sub_only"],
            can_execute_with_whisper=self.settings["can_whisper"],
            examples=[
                CommandExample(
                    None,
                    "Show an emote on stream.",
                    chat=f"user:!{self.settings['command_name']} Keepo\n"
                    "bot>user: Successfully sent the emote Keepo to the stream!",
                    description="",
                ).parse()
            ],
        )
Exemple #22
0
class GivePointsModule(BaseModule):

    ID = __name__.split(".")[-1]
    NAME = "Give Points"
    DESCRIPTION = "Allows users to donate points to others"
    CATEGORY = "Feature"
    SETTINGS = [
        ModuleSetting(
            key="command_name",
            label="Command name (i.e. givepoints)",
            type="text",
            required=True,
            placeholder="Command name (no !)",
            default="givepoints",
            constraints={"min_str_len": 2, "max_str_len": 25},
        ),
        ModuleSetting(
            key="source_requires_sub",
            label="Users need to be subbed to give away points",
            type="boolean",
            required=True,
            default=True,
        ),
        ModuleSetting(
            key="target_requires_sub",
            label="Target needs to be subbed to receive points",
            type="boolean",
            required=True,
            default=False,
        ),
    ]

    def give_points(self, bot, source, message, **rest):
        if message is None or len(message) == 0:
            # The user did not supply any arguments
            return False

        msg_split = message.split(" ")
        if len(msg_split) < 2:
            # The user did not supply enough arguments
            bot.whisper(source, f"Usage: !{self.command_name} USERNAME POINTS")
            return False

        input = msg_split[0]

        try:
            num_points = utils.parse_points_amount(source, msg_split[1])
        except InvalidPointAmount as e:
            bot.whisper(source, f"{e}. Usage: !{self.command_name} USERNAME POINTS")
            return False

        if num_points <= 0:
            # The user tried to specify a negative amount of points
            bot.whisper(source, "You cannot give away negative points admiralCute")
            return True
        # elif num_points < 250:
        #     bot.whisper(source, "You must give 250 points or more :) Be charitable :)")
        #     return True

        if not source.can_afford(num_points):
            # The user tried giving away more points than he owns
            bot.whisper(source, f"You cannot give away more points than you have. You have {source.points} points.")
            return False

        with DBManager.create_session_scope() as db_session:
            target = User.find_by_user_input(db_session, input)
            if target is None:
                # The user tried donating points to someone who doesn't exist in our database
                bot.whisper(source, "This user does not exist FailFish")
                return False

            if target == "admiralbulldog":
                bot.whisper(source, "But why?")
                return False

            if target == source:
                # The user tried giving points to themselves
                bot.whisper(source, "You can't give points to yourself Bruh")
                return True

            if self.settings["target_requires_sub"] is True and target.subscriber is False:
                # Settings indicate that the target must be a subscriber, which he isn't
                bot.whisper(source, "Your target must be a subscriber.")
                return False

            source.points -= num_points
            target.points += num_points

            bot.whisper(source, f"Successfully gave away {num_points} points to {target}")
            bot.whisper(target, f"{source} just gave you {num_points} points! You should probably thank them ;-)")

    def load_commands(self, **options):
        self.command_name = self.settings["command_name"].lower().replace("!", "").replace(" ", "")
        self.commands[self.command_name] = Command.raw_command(
            self.give_points,
            sub_only=self.settings["source_requires_sub"],
            delay_all=0,
            delay_user=60,
            can_execute_with_whisper=True,
            examples=[
                CommandExample(
                    None,
                    "Give points to a user.",
                    chat=f"user:!{self.command_name} pajapaja 4444\n"
                    "bot>user: Successfully gave away 4444 points to pajapaja",
                    description="",
                ).parse()
            ],
        )
Exemple #23
0
class RouletteModule(BaseModule):

    ID = __name__.split(".")[-1]
    NAME = "Roulette"
    DESCRIPTION = "Lets players roulette with themselves for points"
    CATEGORY = "Game"
    SETTINGS = [
        ModuleSetting(
            key="command_name",
            label="Command name (e.g. roulette)",
            type="text",
            required=True,
            placeholder="Command name (no !)",
            default="roulette",
            constraints={
                "min_str_len": 2,
                "max_str_len": 15
            },
        ),
        ModuleSetting(
            key="message_won",
            label="Won message | Available arguments: {bet}, {points}, {user}",
            type="text",
            required=True,
            placeholder=
            "{user} won {bet} points in roulette and now has {points} points! FeelsGoodMan",
            default=
            "{user} won {bet} points in roulette and now has {points} points! FeelsGoodMan",
            constraints={
                "min_str_len": 10,
                "max_str_len": 400
            },
        ),
        ModuleSetting(
            key="message_lost",
            label="Lost message | Available arguments: {bet}, {points}, {user}",
            type="text",
            required=True,
            placeholder=
            "{user} lost {bet} points in roulette and now has {points} points! FeelsBadMan",
            default=
            "{user} lost {bet} points in roulette and now has {points} points! FeelsBadMan",
            constraints={
                "min_str_len": 10,
                "max_str_len": 400
            },
        ),
        ModuleSetting(
            key="rigged_percentage",
            label=
            "Rigged %, lower = more chance of winning. 50 = 50% of winning. 25 = 75% of winning",
            type="number",
            required=True,
            placeholder="",
            default=50,
            constraints={
                "min_value": 1,
                "max_value": 100
            },
        ),
        ModuleSetting(
            key="online_global_cd",
            label="Global cooldown (seconds)",
            type="number",
            required=True,
            placeholder="",
            default=0,
            constraints={
                "min_value": 0,
                "max_value": 120
            },
        ),
        ModuleSetting(
            key="online_user_cd",
            label="Per-user cooldown (seconds)",
            type="number",
            required=True,
            placeholder="",
            default=60,
            constraints={
                "min_value": 0,
                "max_value": 240
            },
        ),
        ModuleSetting(
            key="min_roulette_amount",
            label="Minimum roulette amount",
            type="number",
            required=True,
            placeholder="",
            default=1,
            constraints={
                "min_value": 1,
                "max_value": 3000
            },
        ),
        ModuleSetting(
            key="can_execute_with_whisper",
            label="Allow users to roulette in whispers",
            type="boolean",
            required=True,
            default=False,
        ),
        ModuleSetting(
            key="options_output",
            label="Result output options",
            type="options",
            required=True,
            default="1. Show results in chat",
            options=[
                "1. Show results in chat",
                "2. Show results in whispers",
                "3. Show results in chat if it's over X points else it will be whispered.",
                "4. Combine output in chat",
            ],
        ),
        ModuleSetting(
            key="min_show_points",
            label="Min points you need to win or lose (if options 3)",
            type="number",
            required=True,
            placeholder="",
            default=100,
            constraints={
                "min_value": 1,
                "max_value": 150000
            },
        ),
        ModuleSetting(
            key="only_roulette_after_sub",
            label="Only allow roulettes after sub",
            type="boolean",
            required=True,
            default=False,
        ),
        ModuleSetting(
            key="after_sub_roulette_time",
            label="How long after a sub people can roulette (seconds)",
            type="number",
            required=True,
            placeholder="",
            default=30,
            constraints={
                "min_value": 5,
                "max_value": 3600
            },
        ),
        ModuleSetting(
            key="alert_message_after_sub",
            label=
            "Message to announce rouletting has been enabled after a sub or resub, leave empty to disable message. | Available arguments: {seconds}",
            type="text",
            required=True,
            default="Rouletting is now allowed for {seconds} seconds! PogChamp",
            constraints={
                "min_str_len": 0,
                "max_str_len": 300
            },
        ),
    ]

    def __init__(self, bot):
        super().__init__(bot)
        self.last_sub = None
        self.output_buffer = ""
        self.output_buffer_args = []
        self.last_add = None

    def load_commands(self, **options):
        self.commands[self.settings["command_name"].lower(
        ).replace("!", "").replace(" ", "")] = Command.raw_command(
            self.roulette,
            delay_all=self.settings["online_global_cd"],
            delay_user=self.settings["online_user_cd"],
            description="Roulette for points",
            can_execute_with_whisper=self.settings["can_execute_with_whisper"],
            examples=[
                CommandExample(
                    None,
                    "Roulette for 69 points",
                    chat="user:!" + self.settings["command_name"] + " 69\n"
                    "bot:pajlada won 69 points in roulette! FeelsGoodMan",
                    description="Do a roulette for 69 points",
                ).parse()
            ],
        )

    def rigged_random_result(self):
        return random.randint(1, 100) > self.settings["rigged_percentage"]

    def roulette(self, bot, source, message, **rest):
        if self.settings["only_roulette_after_sub"]:
            if self.last_sub is None:
                return False
            if utils.now() - self.last_sub > datetime.timedelta(
                    seconds=self.settings["after_sub_roulette_time"]):
                return False

        if message is None:
            bot.whisper(
                source,
                "I didn't recognize your bet! Usage: !" +
                self.settings["command_name"] + " 150 to bet 150 points",
            )
            return False

        msg_split = message.split(" ")
        try:
            bet = utils.parse_points_amount(source, msg_split[0])
        except pajbot.exc.InvalidPointAmount as e:
            bot.whisper(source, str(e))
            return False

        if not source.can_afford(bet):
            bot.whisper(
                source,
                f"You don't have enough points to do a roulette for {bet} points :("
            )
            return False

        if bet < self.settings["min_roulette_amount"]:
            bot.whisper(
                source,
                f"You have to bet at least {self.settings['min_roulette_amount']} point! :("
            )
            return False

        # Calculating the result
        result = self.rigged_random_result()
        points = bet if result else -bet
        source.points += points

        with DBManager.create_session_scope() as db_session:
            r = Roulette(source.id, points)
            db_session.add(r)

        arguments = {
            "bet": bet,
            "user": source.name,
            "points": source.points,
            "win": points > 0
        }

        if points > 0:
            out_message = self.get_phrase("message_won", **arguments)
        else:
            out_message = self.get_phrase("message_lost", **arguments)

        if self.settings["options_output"] == "4. Combine output in chat":
            if bot.is_online:
                self.add_message(bot, arguments)
            else:
                bot.me(out_message)
        if self.settings["options_output"] == "1. Show results in chat":
            bot.me(out_message)
        if self.settings["options_output"] == "2. Show results in whispers":
            bot.whisper(source, out_message)
        if (self.settings["options_output"] ==
                "3. Show results in chat if it's over X points else it will be whispered."
            ):
            if abs(points) >= self.settings["min_show_points"]:
                bot.me(out_message)
            else:
                bot.whisper(source, out_message)

        HandlerManager.trigger("on_roulette_finish",
                               user=source,
                               points=points)

    def on_tick(self, **rest):
        if self.output_buffer == "":
            return

        if self.last_add is None:
            return

        diff = utils.now() - self.last_add

        if diff.seconds > 3:
            self.flush_output_buffer()

    def flush_output_buffer(self):
        msg = self.output_buffer
        self.bot.me(msg)
        self.output_buffer = ""
        self.output_buffer_args = []

    def add_message(self, bot, arguments):
        parts = []
        new_buffer = "Roulette: "
        win_emote = "forsenPls"
        lose_emote = "forsenSWA"
        for arg in self.output_buffer_args:
            parts.append(
                f"{win_emote if arg['win'] else lose_emote} {arg['user']} {'+' if arg['win'] else '-'}{arg['bet']}"
            )

        parts.append(
            f"{win_emote if arguments['win'] else lose_emote} {arguments['user']} {'+' if arguments['win'] else '-'}{arguments['bet']}"
        )

        log.debug(parts)
        new_buffer += ", ".join(parts)

        if len(new_buffer) > 480:
            self.flush_output_buffer()
        else:
            self.output_buffer = new_buffer
            log.info("Set output buffer to " + new_buffer)

        self.output_buffer_args.append(arguments)

        self.last_add = utils.now()

    def on_user_sub_or_resub(self, **rest):
        now = utils.now()

        # True if we already announced the alert_message_after_sub within the last 5 seconds. Prevents
        # spam after bulk sub gifts.
        skip_message = self.last_sub is not None and now - self.last_sub < datetime.timedelta(
            seconds=5)

        self.last_sub = now
        if (self.settings["only_roulette_after_sub"]
                and self.settings["alert_message_after_sub"] != ""
                and not skip_message):
            self.bot.say(self.settings["alert_message_after_sub"].format(
                seconds=self.settings["after_sub_roulette_time"]))

    def enable(self, bot):
        HandlerManager.add_handler("on_user_sub", self.on_user_sub_or_resub)
        HandlerManager.add_handler("on_user_resub", self.on_user_sub_or_resub)
        HandlerManager.add_handler("on_tick", self.on_tick)

    def disable(self, bot):
        HandlerManager.remove_handler("on_user_sub", self.on_user_sub_or_resub)
        HandlerManager.remove_handler("on_user_resub",
                                      self.on_user_sub_or_resub)
        HandlerManager.remove_handler("on_tick", self.on_tick)
Exemple #24
0
class SongrequestModule(BaseModule):
    ID = __name__.split(".")[-1]
    NAME = "Songrequest"
    DESCRIPTION = "Request Songs"
    CATEGORY = "Feature"
    SETTINGS = [
        ModuleSetting(key="youtube_key",
                      label="Youtube developer key",
                      type="text",
                      required=True,
                      default=""),
        ModuleSetting(
            key="max_song_length",
            label="Max song length (in seconds)",
            type="number",
            required=True,
            placeholder="Max song length (in seconds)",
            default=360,
            constraints={
                "min_value": 1,
                "max_value": 3600
            },
        ),
        ModuleSetting(
            key="point_cost",
            label="Point costs for requesting a song",
            type="number",
            required=True,
            default=500,
            constraints={
                "min_value": 0,
                "max_value": 250000
            },
        ),
        ModuleSetting(
            key="backup_playlist_id",
            label=
            "Songs to play when no song is being requested backup playlist id",
            type="text",
            required=True,
            default="",
        ),
        ModuleSetting(
            key="volume",
            label="Default volume for song requests",
            type="number",
            required=True,
            default=100,
            constraints={
                "min_value": 0,
                "max_value": 100
            },
        ),
        ModuleSetting(
            key="volume_multiplier",
            label="Volume multiplier",
            type="number",
            required=True,
            default="100",
            constraints={
                "min_value": 0,
                "max_value": 100
            },
        ),
        ModuleSetting(
            key="use_spotify",
            label="Checks Spotify for current song if no song is playing",
            type="boolean",
            required=True,
            default=True,
        ),
        ModuleSetting(
            key="send_message_in_chat",
            label="Send a message in chat upon a song request",
            type="boolean",
            required=True,
            default=True,
        ),
        ModuleSetting(
            key="message_in_chat",
            label=
            "Message sent in chat after someone requests a song {username} is the requestor, {title} is the song title, {current_pos} is the current queue position, {playing_in} is how long until the song is played",
            type="text",
            required=True,
            default=
            '{username} just requested the song "{title}" to be played KKona',
        ),
        ModuleSetting(
            key="message_in_chat_no_songs_playing",
            label="Message sent when no songs are playing",
            type="text",
            required=True,
            default="No songs are currently playing",
        ),
        ModuleSetting(
            key="message_in_chat_when_song_is_playing",
            label=
            "Message sent when a song is playing, {title} is the title of the song, {requestor} is the person who requested, {time_left} is the time left for playing",
            type="text",
            required=True,
            default="The current song is {title} requested by {requestor}",
        ),
        ModuleSetting(
            key="message_in_chat_when_song_is_playing_spotify",
            label=
            "Message sent when a song is playing, {title} is the title of the song, {artists} is the list of artists",
            type="text",
            required=True,
            default="The current song is {title} by {artists}",
        ),
        ModuleSetting(
            key="message_in_chat_when_next_song",
            label=
            "Message sent when a next song is requested, {title} is the title of the song, {requestor} is the person who requested, {playing_in} is when the song will play",
            type="text",
            required=True,
            default="The next song is {title} requested by {requestor}",
        ),
        ModuleSetting(
            key="message_in_chat_when_next_song_none",
            label=
            "Message sent when a next song is requested but there isn't one",
            type="text",
            required=True,
            default="There are no songs currently queued",
        ),
        ModuleSetting(
            key="send_message_on_open",
            label="Send message when song request is opened",
            type="boolean",
            required=True,
            default=True,
        ),
        ModuleSetting(
            key="message_sent_on_open",
            label="Message sent when song request is opened",
            type="text",
            required=True,
            default="Song Request has been opened!",
        ),
        ModuleSetting(
            key="send_message_on_close",
            label="Send message when song request is closed",
            type="boolean",
            required=True,
            default=True,
        ),
        ModuleSetting(
            key="message_sent_on_close",
            label="Message sent when song request is closed",
            type="text",
            required=True,
            default="Song Request has been closed!",
        ),
    ]

    def getBackUpListSongs(self, next_page=None):
        songs = []
        urlin = (
            f"https://www.googleapis.com/youtube/v3/playlistItems?part=snippet&maxResults=50&playlistId={self.settings['backup_playlist_id']}&key={self.settings['youtube_key']}"
            + (f"&pageToken={next_page}" if next_page else ""))
        with urllib.request.urlopen(urlin) as url:
            data = json.loads(url.read().decode())
            for song in data["items"]:
                songs.append(song["snippet"]["resourceId"]["videoId"])
            try:
                next_page = data["nextPageToken"]
                return songs + self.getBackUpListSongs(next_page)
            except:
                return songs

    def create_song_request_queue(self, video_id, bot, source):
        with DBManager.create_session_scope() as db_session:
            song_info = SongRequestSongInfo._create_or_get(
                db_session, video_id, self.youtube)
            if not song_info:
                log.error("There was an error!")
                return False
            if song_info.banned:
                bot.whisper(source, "That song is banned! FeelsWeirdMan")
                return False
            skip_after = (self.settings["max_song_length"] if
                          song_info.duration > self.settings["max_song_length"]
                          else None)
            songrequest_queue = SongrequestQueue._create(
                db_session, video_id, skip_after, source.id)
            db_session.commit()
            m, s = divmod(int(songrequest_queue.playing_in(db_session)), 60)
            m = int(m)
            s = int(s)
            playing_in = f"{m:02d}:{s:02d}"
            if self.settings["send_message_in_chat"]:
                bot.say(self.settings["message_in_chat"].format(
                    username=source.username_raw,
                    title=song_info.title,
                    current_pos=songrequest_queue.queue +
                    (1
                     if SongrequestQueue._get_current_song(db_session) else 0),
                    playing_in=playing_in,
                ))
        self.bot.songrequest_manager._playlist()
        return True

    def add_song(self, bot, source, message, **rest):
        if not message:
            self.bot.whisper(
                source, "Could not find a valid youtube ID in your argument.")
            return False
        # 1. Find youtube ID in message
        msg_split = message.split(" ")
        youtube_id = find_youtube_id_in_string(msg_split[0])

        if youtube_id is False:
            youtube_id = find_youtube_video_by_search(message)
            if youtube_id is None:
                self.bot.whisper(
                    source,
                    "Could not find a valid youtube ID in your argument.")
                return False

        # 2. Make sure the stream is live
        stream_id = StreamHelper.get_current_stream_id()
        if stream_id is None or stream_id is False:
            self.bot.whisper(
                source,
                "You cannot request songs while the stream is offline.")
            return False

        return self.create_song_request_queue(youtube_id, bot, source)

    def get_current_song(self, bot, source, message, **rest):
        with DBManager.create_session_scope() as db_session:
            current_song = SongrequestQueue._get_current_song(db_session)
            if current_song:
                m, s = divmod(current_song.playing_in(db_session), 60)
                m = int(m)
                s = int(s)
                time_left = f"{m:02d}:{s:02d}"
                if current_song.requested_by:
                    bot.say(
                        self.settings["message_in_chat_when_song_is_playing"].
                        format(
                            title=current_song.song_info.title,
                            requestor=current_song.requested_by.username_raw,
                            time_left=time_left,
                        ))
                    return True
                bot.say(self.settings["message_in_chat_when_song_is_playing"].
                        format(title=current_song.song_info.title,
                               requestor="Backup Playlist",
                               time_left=time_left))
                return True
            if self.settings["use_spotify"]:
                is_playing, title, artistsArr = bot.spotify_api.state(
                    bot.spotify_token_manager)
                if is_playing:
                    bot.say(self.settings[
                        "message_in_chat_when_song_is_playing_spotify"].format(
                            title=title,
                            artists=", ".join(
                                [str(artist) for artist in artistsArr])))
                    return True
        bot.say(self.settings["message_in_chat_no_songs_playing"])
        return True

    def get_next_song(self, bot, source, message, **rest):
        with DBManager.create_session_scope() as db_session:
            next_song = SongrequestQueue._get_next_song(db_session)
            if next_song:
                m, s = divmod(next_song.playing_in(db_session), 60)
                m = int(m)
                s = int(s)
                playing_in = f"{m:02d}:{s:02d}"
                if next_song.requestor:
                    bot.say(
                        self.settings["message_in_chat_when_next_song"].format(
                            title=next_song.song_info.title,
                            requestor=next_song.requestor.username_raw,
                            playing_in=playing_in,
                        ))
                    return True
                bot.say(self.settings["message_in_chat_when_next_song"].format(
                    title=next_song.song_info.title,
                    requestor="Backup Playlist",
                    playing_in=playing_in))
                return True
        bot.say(self.settings["message_in_chat_when_next_song_none"])
        return True

    def open_module(self, bot, source, message, **rest):
        if self.bot.songrequest_manager.open_module_function():
            if self.settings["send_message_on_open"]:
                bot.whisper(source, self.settings["message_sent_on_open"])
                bot.say(self.settings["message_sent_on_open"])
                return
        bot.whisper(source, "Song request is already open!")

    def close_module(self, bot, source, message, **rest):
        if self.bot.songrequest_manager.close_module_function():
            if self.settings["send_message_on_open"]:
                bot.whisper(source, self.settings["message_sent_on_close"])
                bot.say(self.settings["message_sent_on_close"])
                return
        bot.whisper(source, "Song request is already closed!")

    def skip(self, bot, source, message, **rest):
        if self.bot.songrequest_manager.skip_function(source.login):
            bot.whisper(source, "Song has been skipped!")
            return
        bot.whisper(source, "No song is playing!")

    def pause(self, bot, source, message, **rest):
        if self.bot.songrequest_manager.pause_function():
            bot.whisper(source, "Song has been paused")
            return
        bot.whisper(source, "Song is already paused!")

    def resume(self, bot, source, message, **rest):
        if self.bot.songrequest_manager.resume_function():
            bot.whisper(source, "Song has been resumed")
            return
        bot.whisper(source, "Song is already playing!")

    def volume(self, bot, source, message, **rest):
        if not message:
            bot.say(
                f"The current volume is {self.bot.songrequest_manager.volume_val()}%"
            )
            return True
        try:
            val = int(message)
            if val < 0 or val > 100:
                bot.whisper(
                    source,
                    "Invalid volume setting enter a volume between 0-100")
                return False
        except:
            bot.whisper(source,
                        "Invalid volume setting enter a volume between 0-100")
            return False
        self.bot.songrequest_manager.volume_function(val)
        bot.whisper(source, "Volume has been changed to " + message + "%")
        return True

    def show_video(self, bot, source, message, **rest):
        if self.bot.songrequest_manager.show_function():
            bot.whisper(source, "The video has been shown!")
            return True
        bot.whisper(source, "The video is already showing!")
        return True

    def hide_video(self, bot, source, message, **rest):
        if self.bot.songrequest_manager.hide_function():
            bot.whisper(source, "The video has been hidden!")
            return True
        bot.whisper(source, "The video is already hidden!")
        return True

    def load_commands(self, **options):
        self.commands["sr"] = self.commands[
            "songrequest"] = Command.raw_command(
                self.add_song,
                delay_all=0,
                delay_user=3,
                notify_on_error=True,
                cost=self.settings["point_cost"])
        self.commands["song"] = Command.raw_command(self.get_current_song,
                                                    delay_all=0,
                                                    delay_user=3,
                                                    notify_on_error=True)
        self.commands["next"] = Command.raw_command(self.get_next_song,
                                                    delay_all=0,
                                                    delay_user=3,
                                                    notify_on_error=True)
        self.commands["opensr"] = Command.raw_command(self.open_module,
                                                      delay_all=0,
                                                      delay_user=3,
                                                      level=500,
                                                      notify_on_error=True)
        self.commands["closesr"] = Command.raw_command(self.close_module,
                                                       delay_all=0,
                                                       delay_user=3,
                                                       level=500,
                                                       notify_on_error=True)
        self.commands["skip"] = Command.raw_command(self.skip,
                                                    delay_all=0,
                                                    delay_user=3,
                                                    level=500,
                                                    notify_on_error=True)
        self.commands["pause"] = Command.raw_command(self.pause,
                                                     delay_all=0,
                                                     delay_user=3,
                                                     level=500,
                                                     notify_on_error=True)
        self.commands["resume"] = Command.raw_command(self.resume,
                                                      delay_all=0,
                                                      delay_user=3,
                                                      level=500,
                                                      notify_on_error=True)
        self.commands["volume"] = Command.raw_command(self.volume,
                                                      delay_all=0,
                                                      delay_user=3,
                                                      level=500,
                                                      notify_on_error=True)
        self.commands["showvideo"] = Command.raw_command(self.show_video,
                                                         delay_all=0,
                                                         delay_user=3,
                                                         level=500,
                                                         notify_on_error=True)
        self.commands["hidevideo"] = Command.raw_command(self.hide_video,
                                                         delay_all=0,
                                                         delay_user=3,
                                                         level=500,
                                                         notify_on_error=True)

    def enable(self, bot):
        if not self.bot:
            return

        import apiclient
        from apiclient.discovery import build

        def build_request(_, *args, **kwargs):
            import httplib2

            new_http = httplib2.Http()
            return apiclient.http.HttpRequest(new_http, *args, **kwargs)

        self.youtube = build("youtube",
                             "v3",
                             developerKey=self.settings["youtube_key"],
                             requestBuilder=build_request)

        with DBManager.create_session_scope() as db_session:
            SongrequestQueue._clear_backup_songs(db_session)
            if self.settings["backup_playlist_id"] and self.settings[
                    "backup_playlist_id"] != "":
                backup_songs = self.getBackUpListSongs()
                random.shuffle(backup_songs)
                SongrequestQueue._load_backup_songs(db_session, backup_songs,
                                                    self.youtube,
                                                    self.settings)
            db_session.commit()
        SongrequestQueue._update_queue()
        self.bot.songrequest_manager.enable(self.settings, self.youtube)
        HandlerManager.add_handler(
            "on_stream_stop",
            self.bot.songrequest_manager.close_module_function)

    def disable(self, bot):
        if not self.bot:
            return
        self.bot.songrequest_manager.disable()
        self.bot.songrequest_manager.disable()
        HandlerManager.remove_handler(
            "on_stream_stop",
            self.bot.songrequest_manager.close_module_function)
Exemple #25
0
class EmoteTimeoutModule(BaseModule):
    ID = __name__.split(".")[-1]
    NAME = "Emote Timeout"
    DESCRIPTION = "Times out users who post emoji or Twitch, BTTV, FFZ or 7TV emotes"
    CATEGORY = "Moderation"
    SETTINGS = [
        ModuleSetting(
            key="timeout_twitch", label="Timeout any Twitch emotes", type="boolean", required=True, default=False
        ),
        ModuleSetting(key="timeout_ffz", label="Timeout any FFZ emotes", type="boolean", required=True, default=False),
        ModuleSetting(
            key="timeout_bttv", label="Timeout any BTTV emotes", type="boolean", required=True, default=False
        ),
        ModuleSetting(key="timeout_7tv", label="Timeout any 7TV emotes", type="boolean", required=True, default=False),
        ModuleSetting(
            key="timeout_emoji", label="Timeout any unicode emoji", type="boolean", required=True, default=False
        ),
        ModuleSetting(
            key="bypass_level",
            label="Level to bypass module",
            type="number",
            required=True,
            placeholder="",
            default=420,
            constraints={"min_value": 100, "max_value": 1000},
        ),
        ModuleSetting(
            key="moderation_action",
            label="Moderation action to apply",
            type="options",
            required=True,
            default="Delete",
            options=["Delete", "Timeout"],
        ),
        ModuleSetting(
            key="timeout_duration",
            label="Timeout duration (if moderation action is timeout)",
            type="number",
            required=True,
            placeholder="",
            default=5,
            constraints={"min_value": 1, "max_value": 1209600},
        ),
        ModuleSetting(
            key="enable_in_online_chat", label="Enabled in online chat", type="boolean", required=True, default=True
        ),
        ModuleSetting(
            key="enable_in_offline_chat", label="Enabled in offline chat", type="boolean", required=True, default=True
        ),
        ModuleSetting(
            key="twitch_timeout_reason",
            label="Twitch Emote Timeout Reason",
            type="text",
            required=False,
            placeholder="",
            default="No Twitch emotes allowed",
            constraints={},
        ),
        ModuleSetting(
            key="ffz_timeout_reason",
            label="FFZ Emote Timeout Reason",
            type="text",
            required=False,
            placeholder="",
            default="No FFZ emotes allowed",
            constraints={},
        ),
        ModuleSetting(
            key="bttv_timeout_reason",
            label="BTTV Emote Timeout Reason",
            type="text",
            required=False,
            placeholder="",
            default="No BTTV emotes allowed",
            constraints={},
        ),
        ModuleSetting(
            key="7tv_timeout_reason",
            label="7TV Emote Timeout Reason",
            type="text",
            required=False,
            placeholder="",
            default="No 7TV emotes allowed",
            constraints={},
        ),
        ModuleSetting(
            key="emoji_timeout_reason",
            label="Emoji Timeout Reason",
            type="text",
            required=False,
            placeholder="",
            default="No emoji allowed",
            constraints={},
        ),
        ModuleSetting(
            key="disable_warnings",
            label="Disable warning timeouts",
            type="boolean",
            required=True,
            default=False,
        ),
    ]

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

        if self.bot.is_online and not self.settings["enable_in_online_chat"]:
            return True

        if not self.bot.is_online and not self.settings["enable_in_offline_chat"]:
            return True

        if self.settings["timeout_twitch"] and any(e.emote.provider == "twitch" for e in emote_instances):
            self.bot.delete_or_timeout(
                source,
                self.settings["moderation_action"],
                msg_id,
                self.settings["timeout_duration"],
                self.settings["twitch_timeout_reason"],
                disable_warnings=self.settings["disable_warnings"],
                once=True,
            )
            return False

        if self.settings["timeout_ffz"] and any(e.emote.provider == "ffz" for e in emote_instances):
            self.bot.delete_or_timeout(
                source,
                self.settings["moderation_action"],
                msg_id,
                self.settings["timeout_duration"],
                self.settings["ffz_timeout_reason"],
                disable_warnings=self.settings["disable_warnings"],
                once=True,
            )
            return False

        if self.settings["timeout_bttv"] and any(e.emote.provider == "bttv" for e in emote_instances):
            self.bot.delete_or_timeout(
                source,
                self.settings["moderation_action"],
                msg_id,
                self.settings["timeout_duration"],
                self.settings["bttv_timeout_reason"],
                disable_warnings=self.settings["disable_warnings"],
                once=True,
            )
            return False

        if self.settings["timeout_7tv"] and any(e.emote.provider == "7tv" for e in emote_instances):
            self.bot.delete_or_timeout(
                source,
                self.settings["moderation_action"],
                msg_id,
                self.settings["timeout_duration"],
                self.settings["7tv_timeout_reason"],
                disable_warnings=self.settings["disable_warnings"],
                once=True,
            )
            return False

        if self.settings["timeout_emoji"] and any(emoji in message for emoji in ALL_EMOJI):
            self.bot.delete_or_timeout(
                source,
                self.settings["moderation_action"],
                msg_id,
                self.settings["timeout_duration"],
                self.settings["emoji_timeout_reason"],
                disable_warnings=self.settings["disable_warnings"],
                once=True,
            )
            return False

        return True

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

    def disable(self, bot):
        HandlerManager.remove_handler("on_message", self.on_message)
Exemple #26
0
class EightBallModule(BaseModule):

    ID = __name__.split('.')[-1]
    NAME = '8-ball'
    DESCRIPTION = 'Gives users access to the !8ball command!'
    CATEGORY = 'Game'
    SETTINGS = [
            ModuleSetting(
                key='online_global_cd',
                label='Global cooldown (seconds)',
                type='number',
                required=True,
                placeholder='',
                default=4,
                constraints={
                    'min_value': 0,
                    'max_value': 120,
                    }),
            ModuleSetting(
                key='online_user_cd',
                label='Per-user cooldown (seconds)',
                type='number',
                required=True,
                placeholder='',
                default=10,
                constraints={
                    'min_value': 0,
                    'max_value': 240,
                    }),
            ]

    def __init__(self):
        super().__init__()
        self.phrases = [
            'sure',
            'are you kidding?!',
            'yeah',
            'no',
            'i think so',
            'don\'t bet on it',
            'ja',
            'doubtful',
            'for sure',
            'forget about it',
            'nein',
            'maybe',
            'Kappa Keepo PogChamp',
            'sure',
            'i dont think so',
            'it is so',
            'leaning towards no',
            'look deep in your heart and you will see the answer',
            'most definitely',
            'most likely',
            'my sources say yes',
            'never',
            'nah m8',
            'might actually be yes',
            'no.',
            'outlook good',
            'outlook not so good',
            'perhaps',
            'mayhaps',
            'that\'s a tough one',
            'idk kev',
            'don\'t ask that',
            'the answer to that isn\'t pretty',
            'the heavens point to yes',
            'who knows?',
            'without a doubt',
            'yesterday it would\'ve been a yes, but today it\'s a yep',
            'you will have to wait'
            ]

        self.emotes = [
            'Kappa',
            'Keepo',
            'xD',
            'KKona',
            '4Head',
            'EleGiggle',
            'DansGame',
            'KappaCool',
            'BrokeBack',
            'OpieOP',
            'KappaRoss',
            'KappaPride',
            'FeelsBadMan',
            'FeelsGoodMan',
            'PogChamp',
            'VisLaud',
            'OhMyDog',
            'FrankerZ',
            'DatSheffy',
            'BabyRage',
            'VoHiYo',
            'haHAA',
            'FeelsBirthdayMan',
            'LUL'
            ]

    def eightball_command(self, **options):
        source = options['source']
        bot = options['bot']
        message = options['message']

        if message and len(message) > 0:
            phrase = random.choice(self.phrases)
            emote = random.choice(self.emotes)
            bot.me('{source.username_raw}, the 8-ball says... {phrase} {emote}'.format(source=source, phrase=phrase, emote=emote))
        else:
            return False

    def load_commands(self, **options):
        self.commands['8ball'] = pajbot.models.command.Command.raw_command(self.eightball_command,
                delay_all=self.settings['online_global_cd'],
                delay_user=self.settings['online_user_cd'],
                description='Need help with a decision? Use the !8ball command!',
                examples=[
                    pajbot.models.command.CommandExample(None, '!8ball',
                        chat='user:!8ball Should I listen to gachimuchi?\n'
                        'bot:pajlada, the 8-ball says... Of course you should!',
                        description='Ask the 8ball an important question').parse(),
                    ],
                )
Exemple #27
0
class CaseCheckerModule(BaseModule):
    ID = __name__.split(".")[-1]
    NAME = "Case Checker"
    DESCRIPTION = "Times out users who post messages that contain lowercase/uppercase letters."
    CATEGORY = "Moderation"
    SETTINGS = [
        ModuleSetting(
            key="timeout_uppercase",
            label="Timeout any uppercase in messages",
            type="boolean",
            required=True,
            default=False,
        ),
        ModuleSetting(
            key="timeout_lowercase",
            label="Timeout any lowercase in messages",
            type="boolean",
            required=True,
            default=False,
        ),
        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_duration",
            label="Timeout duration",
            type="number",
            required=True,
            placeholder="",
            default=3,
            constraints={
                "min_value": 3,
                "max_value": 120
            },
        ),
        ModuleSetting(key="online_chat_only",
                      label="Only enabled in online chat",
                      type="boolean",
                      required=True,
                      default=True),
        ModuleSetting(
            key="uppercase_timeout_reason",
            label="Uppercase Timeout Reason",
            type="text",
            required=False,
            placeholder="",
            default="no uppercase characters allowed",
            constraints={},
        ),
        ModuleSetting(
            key="lowercase_timeout_reason",
            label="Lowercase Timeout Reason",
            type="text",
            required=False,
            placeholder="",
            default="NO LOWERCASE CHARACTERS ALLOWED",
            constraints={},
        ),
    ]

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

        if self.settings["online_chat_only"] and not self.bot.is_online:
            return True

        if self.settings["timeout_uppercase"] and any(c.isupper()
                                                      for c in message):
            self.bot.timeout(source,
                             self.settings["timeout_duration"],
                             reason=self.settings["uppercase_timeout_reason"],
                             once=True)
            return False

        if self.settings["timeout_lowercase"] and any(c.islower()
                                                      for c in message):
            self.bot.timeout(source,
                             self.settings["timeout_duration"],
                             reason=self.settings["lowercase_timeout_reason"],
                             once=True)
            return False

        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)
Exemple #28
0
class SubAlertModule(BaseModule):
    ID = __name__.split(".")[-1]
    NAME = "Subscription Alert - Chat"
    DESCRIPTION = "Prints a message in chat/whispers when a user re/subscribes"
    CATEGORY = "Feature"
    ENABLED_DEFAULT = True
    SETTINGS = [
        ModuleSetting(
            key="chat_message",
            label="Enable a chat message for someone who subscribed",
            type="boolean",
            required=True,
            default=True,
        ),
        ModuleSetting(
            key="new_sub",
            label="New sub chat message | Available arguments: {username}",
            type="text",
            required=True,
            placeholder="Sub hype! {username} just subscribed PogChamp",
            default="Sub hype! {username} just subscribed PogChamp",
            constraints={
                "min_str_len": 10,
                "max_str_len": 400
            },
        ),
        ModuleSetting(
            key="new_prime_sub",
            label=
            "New prime sub chat message | Available arguments: {username}",
            type="text",
            required=True,
            placeholder=
            "Thank you for smashing that prime button! {username} PogChamp",
            default=
            "Thank you for smashing that prime button! {username} PogChamp",
            constraints={
                "min_str_len": 10,
                "max_str_len": 400
            },
        ),
        ModuleSetting(
            key="new_gift_sub",
            label=
            "New gift sub chat message | Available arguments: {username}, {gifted_by}",
            type="text",
            required=True,
            placeholder=
            "{gifted_by} gifted a fresh sub to {username}! PogChamp",
            default="{gifted_by} gifted a fresh sub to {username}! PogChamp",
            constraints={
                "min_str_len": 10,
                "max_str_len": 400
            },
        ),
        ModuleSetting(
            key="resub",
            label=
            "Resub chat message | Available arguments: {username}, {num_months}, {substreak_string}",
            type="text",
            required=True,
            placeholder=
            "Resub hype! {username} just subscribed, {num_months} months in a row PogChamp <3",
            default=
            "Resub hype! {username} just subscribed, {num_months} months in a row PogChamp <3",
            constraints={
                "min_str_len": 10,
                "max_str_len": 400
            },
        ),
        ModuleSetting(
            key="resub_prime",
            label=
            "Resub chat message (Prime sub) | Available arguments: {username}, {num_months}, {substreak_string}",
            type="text",
            required=True,
            placeholder=
            "Thank you for smashing it {num_months} in a row {username}",
            default=
            "Thank you for smashing it {num_months} in a row {username}",
            constraints={
                "min_str_len": 10,
                "max_str_len": 400
            },
        ),
        ModuleSetting(
            key="resub_gift",
            label=
            "Resub chat message (Gift sub) | Available arguments: {username}, {num_months}, {gifted_by}, {substreak_string}",
            type="text",
            required=True,
            placeholder=
            "{username} got gifted a resub by {gifted_by}, that's {num_months} months in a row PogChamp",
            default=
            "{username} got gifted a resub by {gifted_by}, that's {num_months} months in a row PogChamp",
            constraints={
                "min_str_len": 10,
                "max_str_len": 400
            },
        ),
        ModuleSetting(
            key="substreak_string",
            label=
            "Sub streak string. Empty if streak was not shared | Available arguments: {username}, {num_months}",
            type="text",
            required=True,
            placeholder="{num_months} in a row PogChamp",
            default="{num_months} in a row PogChamp",
            constraints={
                "min_str_len": 10,
                "max_str_len": 400
            },
        ),
        ModuleSetting(
            key="whisper_message",
            label="Enable a whisper message for someone who subscribed",
            type="boolean",
            required=True,
            default=False,
        ),
        ModuleSetting(
            key="whisper_after",
            label="Whisper the message after X seconds",
            type="number",
            required=True,
            placeholder="",
            default=5,
            constraints={
                "min_value": 1,
                "max_value": 120
            },
        ),
        ModuleSetting(
            key="new_sub_whisper",
            label=
            "Whisper message for new subs | Available arguments: {username}",
            type="text",
            required=True,
            placeholder="Thank you for subscribing {username} <3",
            default="Thank you for subscribing {username} <3",
            constraints={
                "min_str_len": 10,
                "max_str_len": 400
            },
        ),
        ModuleSetting(
            key="resub_whisper",
            label=
            "Whisper message for resubs | Available arguments: {username}, {num_months}",
            type="text",
            required=True,
            placeholder=
            "Thank you for subscribing for {num_months} months in a row {username} <3",
            default=
            "Thank you for subscribing for {num_months} months in a row {username} <3",
            constraints={
                "min_str_len": 10,
                "max_str_len": 400
            },
        ),
        ModuleSetting(
            key="grant_points_on_sub",
            label=
            "Give points to user when they subscribe/resubscribe. 0 = off",
            type="number",
            required=True,
            placeholder="",
            default=0,
            constraints={
                "min_value": 0,
                "max_value": 50000
            },
        ),
        ModuleSetting(
            key="message_on_give_points",
            label=
            "Enable posting a message to chat upon user getting points due to sub",
            type="boolean",
            required=True,
            default=True,
        ),
        ModuleSetting(
            key="message_on_give_points_value",
            label=
            "Message to user upon getting points due to sub | Available arguments: {username}, {points_given} ",
            type="text",
            required=True,
            placeholder="",
            default=
            "{username} was given {points_given} points for subscribing! FeelsAmazingMan",
            constraints={
                "min_str_len": 0,
                "max_str_len": 400
            },
        ),
        ModuleSetting(
            key="grant_points_on_bits",
            label=
            "Give points to user when they cheer with bits (per bit). 0 = OFF ",
            type="number",
            required=True,
            placeholder="",
            default=1,
            constraints={
                "min_value": 0,
                "max_value": 50000
            },
        ),
        ModuleSetting(
            key="grant_points_on_donate",
            label="Give points to user when they donate (per USD). 0 = OFF ",
            type="number",
            required=True,
            placeholder="",
            default=100,
            constraints={
                "min_value": 0,
                "max_value": 50000
            },
        ),
    ]

    def __init__(self, bot):
        super().__init__(bot)

    def on_sub_shared(self, user):
        if self.settings["grant_points_on_sub"] <= 0:
            return

        user.points += self.settings["grant_points_on_sub"]
        if self.settings["message_on_give_points"]:
            points_given = self.settings["grant_points_on_sub"]
            username = user
            self.bot.say(self.settings["message_on_give_points_value"].format(
                points_given=points_given, username=username))

    def on_new_sub(self, user, sub_type, gifted_by=None):
        """
        A new user just subscribed.
        Send the event to the websocket manager, and send a customized message in chat.
        Also increase the number of active subscribers in the database by one.
        """
        if gifted_by:
            self.on_sub_shared(gifted_by)
            gifted_name = gifted_by.name
        else:
            self.on_sub_shared(user)
            gifted_name = ""

        self.bot.kvi["active_subs"].inc()
        payload = {"username": user.name, "gifted_by": gifted_name}

        if self.settings["chat_message"] is True:
            if sub_type == "Prime":
                self.bot.say(self.get_phrase("new_prime_sub", **payload))
            else:
                if gifted_by:
                    self.bot.say(self.get_phrase("new_gift_sub", **payload))
                else:
                    self.bot.say(self.get_phrase("new_sub", **payload))

        if self.settings["whisper_message"] is True:
            self.bot.execute_delayed(
                self.settings["whisper_after"], self.bot.whisper, user,
                self.get_phrase("new_sub_whisper", **payload))

    def on_resub(self,
                 user,
                 num_months,
                 sub_type,
                 gifted_by=None,
                 substreak_count=0):
        """
        A user just re-subscribed.
        Send the event to the websocket manager, and send a customized message in chat.
        """

        if gifted_by:
            self.on_sub_shared(gifted_by)
            gifted_name = gifted_by.name
        else:
            self.on_sub_shared(user)
            gifted_name = ""

        payload = {
            "username": user.name,
            "num_months": num_months,
            "gifted_by": gifted_name
        }
        if substreak_count and substreak_count > 0:
            payload["substreak_string"] = self.get_phrase(
                "substreak_string",
                username=user.name,
                num_months=substreak_count,
                gifted_by=gifted_by)
        else:
            payload["substreak_string"] = ""

        if self.settings["chat_message"] is True:
            if sub_type == "Prime":
                self.bot.say(self.get_phrase("resub_prime", **payload))
            else:
                if gifted_by:
                    self.bot.say(self.get_phrase("resub_gift", **payload))
                else:
                    self.bot.say(self.get_phrase("resub", **payload))

        if self.settings["whisper_message"] is True:
            self.bot.execute_delayed(
                self.settings["whisper_after"], self.bot.whisper, user,
                self.get_phrase("resub_whisper", **payload))

    def on_usernotice(self, source, tags, **rest):
        if "msg-id" not in tags:
            return

        if tags["msg-id"] == "resub":
            num_months = -1
            substreak_count = 0
            if "msg-param-months" in tags:
                num_months = int(tags["msg-param-months"])
            if "msg-param-cumulative-months" in tags:
                num_months = int(tags["msg-param-cumulative-months"])
            if "msg-param-streak-months" in tags:
                substreak_count = int(tags["msg-param-streak-months"])
            if "msg-param-should-share-streak" in tags:
                should_share = bool(tags["msg-param-should-share-streak"])
                if not should_share:
                    substreak_count = 0

            if "msg-param-sub-plan" not in tags:
                log.debug(
                    f"subalert msg-id is resub, but missing msg-param-sub-plan: {tags}"
                )
                return

            # log.debug('msg-id resub tags: {}'.format(tags))

            # TODO: Should we check room id with streamer ID here? Maybe that's for pajbot2 instead
            self.on_resub(source, num_months, tags["msg-param-sub-plan"], None,
                          substreak_count)
            HandlerManager.trigger("on_user_resub",
                                   user=source,
                                   num_months=num_months)
        elif tags["msg-id"] == "subgift":
            num_months = 0
            substreak_count = 0
            if "msg-param-months" in tags:
                num_months = int(tags["msg-param-months"])
            if "msg-param-cumulative-months" in tags:
                num_months = int(tags["msg-param-cumulative-months"])
            if "msg-param-streak-months" in tags:
                substreak_count = int(tags["msg-param-streak-months"])
            if "msg-param-should-share-streak" in tags:
                should_share = bool(tags["msg-param-should-share-streak"])
                if not should_share:
                    substreak_count = 0

            if "display-name" not in tags:
                log.debug(
                    f"subalert msg-id is subgift, but missing display-name: {tags}"
                )
                return

            with DBManager.create_session_scope() as db_session:
                receiver_id = tags["msg-param-recipient-id"]
                receiver_login = tags["msg-param-recipient-user-name"]
                receiver_name = tags["msg-param-recipient-display-name"]
                receiver = User.from_basics(
                    db_session,
                    UserBasics(receiver_id, receiver_login, receiver_name))

                if num_months > 1:
                    # Resub
                    self.on_resub(receiver, num_months,
                                  tags["msg-param-sub-plan"], source,
                                  substreak_count)
                    HandlerManager.trigger("on_user_resub",
                                           user=receiver,
                                           num_months=num_months)
                else:
                    # New sub
                    self.on_new_sub(receiver, tags["msg-param-sub-plan"],
                                    source)
                    HandlerManager.trigger("on_user_sub", user=receiver)
        elif tags["msg-id"] == "sub":
            if "msg-param-sub-plan" not in tags:
                log.debug(
                    f"subalert msg-id is sub, but missing msg-param-sub-plan: {tags}"
                )
                return

            self.on_new_sub(source, tags["msg-param-sub-plan"])
            HandlerManager.trigger("on_user_sub", user=source)
        else:
            log.debug(f"Unhandled msg-id: {tags['msg-id']} - tags: {tags}")

    def on_cheer(self, user, bits_cheered, **rest):
        if self.settings["grant_points_on_bits"] <= 0:
            return
        points_to_give = int(
            bits_cheered) * self.settings["grant_points_on_bits"]
        user.points += points_to_give
        self.bot.whisper(
            user, "You have been given " + str(points_to_give) +
            " points for cheering " + str(bits_cheered) + " bits")

    def on_donate(self, user, amount, **rest):
        if self.settings["grant_points_on_donate"] <= 0:
            return
        points_to_give = int(amount * self.settings["grant_points_on_donate"])
        user.points += points_to_give
        self.bot.whisper(
            user,
            f"You have been given {points_to_give} points for donating ${amount:.2f}"
        )

    def enable(self, bot):
        HandlerManager.add_handler("on_usernotice", self.on_usernotice)
        HandlerManager.add_handler("on_donate", self.on_donate)
        HandlerManager.add_handler("on_cheer", self.on_cheer)

    def disable(self, bot):
        HandlerManager.remove_handler("on_usernotice", self.on_usernotice)
        HandlerManager.remove_handler("on_donate", self.on_donate)
        HandlerManager.remove_handler("on_cheer", self.on_cheer)
Exemple #29
0
class DuelModule(BaseModule):

    ID = __name__.split(".")[-1]
    NAME = "Duel (mini game)"
    DESCRIPTION = "Let players duel to win or lose points."
    CATEGORY = "Game"
    SETTINGS = [
        ModuleSetting(
            key="max_pot",
            label="How many points you can duel for at most",
            type="number",
            required=True,
            placeholder="",
            default=420,
            constraints={
                "min_value": 0,
                "max_value": 69000
            },
        ),
        ModuleSetting(
            key="message_won",
            label="Winner message | Available arguments: {winner}, {loser}",
            type="text",
            required=True,
            placeholder="{winner} won the duel vs {loser} PogChamp",
            default="{winner} won the duel vs {loser} PogChamp",
            constraints={
                "min_str_len": 10,
                "max_str_len": 400
            },
        ),
        ModuleSetting(
            key="message_won_points",
            label=
            "Points message | Available arguments: {winner}, {loser}, {total_pot}, {extra_points}",
            type="text",
            required=True,
            placeholder=
            "{winner} won the duel vs {loser} PogChamp . The pot was {total_pot}, the winner gets their bet back + {extra_points} points",
            default=
            "{winner} won the duel vs {loser} PogChamp . The pot was {total_pot}, the winner gets their bet back + {extra_points} points",
            constraints={
                "min_str_len": 10,
                "max_str_len": 400
            },
        ),
        ModuleSetting(
            key="online_global_cd",
            label="Global cooldown (seconds)",
            type="number",
            required=True,
            placeholder="",
            default=0,
            constraints={
                "min_value": 0,
                "max_value": 120
            },
        ),
        ModuleSetting(
            key="online_user_cd",
            label="Per-user cooldown (seconds)",
            type="number",
            required=True,
            placeholder="",
            default=5,
            constraints={
                "min_value": 0,
                "max_value": 240
            },
        ),
        ModuleSetting(key="show_on_clr",
                      label="Show duels on the clr overlay",
                      type="boolean",
                      required=True,
                      default=True),
    ]

    def load_commands(self, **options):
        self.commands["duel"] = Command.raw_command(
            self.initiate_duel,
            delay_all=self.settings["online_global_cd"],
            delay_user=self.settings["online_user_cd"],
            description="Initiate a duel with a user",
            examples=[
                CommandExample(
                    None,
                    "0-point duel",
                    chat="user:!duel Karl_Kons\n"
                    "bot>user:You have challenged Karl_Kons for 0 points",
                    description="Duel Karl_Kons for 0 points",
                ).parse(),
                CommandExample(
                    None,
                    "69-point duel",
                    chat="user:!duel Karl_Kons 69\n"
                    "bot>user:You have challenged Karl_Kons for 69 points",
                    description="Duel Karl_Kons for 69 points",
                ).parse(),
            ],
        )
        self.commands["cancelduel"] = Command.raw_command(
            self.cancel_duel,
            delay_all=0,
            delay_user=10,
            description="Cancel your duel request")
        self.commands["accept"] = Command.raw_command(
            self.accept_duel,
            delay_all=0,
            delay_user=0,
            description="Accept a duel request")
        self.commands["decline"] = Command.raw_command(
            self.decline_duel,
            delay_all=0,
            delay_user=0,
            description="Decline a duel request")
        self.commands["deny"] = self.commands["decline"]
        self.commands["duelstatus"] = Command.raw_command(
            self.status_duel,
            delay_all=0,
            delay_user=5,
            description="Current duel request info")
        self.commands["duelstats"] = Command.raw_command(
            self.get_duel_stats,
            delay_all=0,
            delay_user=120,
            description="Get your duel statistics")

    def __init__(self, bot):
        super().__init__(bot)
        self.duel_requests = {}
        self.duel_request_price = {}
        self.duel_targets = {}
        self.blUsers = ['admiralbulldog', 'infinitegachi']

    def initiate_duel(self, **options):
        """
        Initiate a duel with a user.
        You can also bet points on the winner.
        By default, the maximum amount of points you can spend is 420.

        How to add: !add funccommand duel initiate_duel --cd 0 --usercd 5
        How to use: !duel USERNAME POINTS_TO_BET
        """

        bot = options["bot"]
        source = options["source"]
        message = options["message"]

        if message is None:
            return False

        max_pot = self.settings["max_pot"]

        msg_split = message.split()
        username = msg_split[0]
        user = bot.users.find(username)
        duel_price = 300
        if user is None:
            # No user was found with this username
            return False

        if len(msg_split) > 1:
            try:
                duel_price = pajbot.utils.parse_points_amount(
                    source, msg_split[1])
                if duel_price < 300:
                    bot.whisper(source.username,
                                'You may only duel for 300+ points, no pussy')
                    bot.whisper(user.username, '{} tried to duel you for less than 300 points. What a ' \
                                               'cheapskate EleGiggle'.format(source.username_raw))
                    return False

            except pajbot.exc.InvalidPointAmount as e:
                bot.whisper(user.username, str(e))
                return False

        if source.username in self.duel_requests:
            bot.whisper(
                source.username,
                "You already have a duel request active with {}. Type !cancelduel to cancel your duel request."
                .format(self.duel_requests[source.username]),
            )
            return False

        if user == source:
            # You cannot duel yourself
            return False

        if user.username in self.blUsers:
            return True

        if user.last_active is None or (
                utils.now() - user._last_active).total_seconds() > 5 * 60:
            bot.whisper(
                source.username,
                "This user has not been active in chat within the last 5 minutes. Get them to type in chat before sending another challenge",
            )
            return False

        if not user.can_afford(duel_price) or not source.can_afford(
                duel_price):
            bot.whisper(
                source.username,
                "You or your target do not have more than {} points, therefore you cannot duel for that amount."
                .format(duel_price),
            )
            return False

        if user.username in self.duel_targets:
            bot.whisper(
                source.username,
                "This person is already being challenged by {}. Ask them to answer the offer by typing !deny or !accept"
                .format(self.duel_targets[user.username]),
            )
            return False

        self.duel_targets[user.username] = source.username
        self.duel_requests[source.username] = user.username
        self.duel_request_price[source.username] = duel_price
        bot.whisper(
            user.username,
            "You have been challenged to a duel by {} for {} points. You can either !accept or !deny this challenge."
            .format(source.username_raw, duel_price),
        )
        bot.whisper(
            source.username, "You have challenged {} for {} points".format(
                user.username_raw, duel_price))
        bot.execute_delayed(60, self.time_expired,
                            (source.username, user.username, bot))

    def time_expired(self, initiator, target, bot):
        if target in self.duel_targets and initiator in self.duel_requests:
            del self.duel_targets[self.duel_requests[initiator]]
            del self.duel_requests[initiator]

            bot.whisper(
                initiator,
                'Your duel request against {} has expired. Ditched OMEGALUL'.
                format(target))
            bot.whisper(
                target,
                'Chu ignoring {} for, his duel request against you expired cmonBruh'
                .format(initiator))

    def cancel_duel(self, **options):
        """
        Cancel any duel requests you've sent.

        How to add: !add funccomand cancelduel|duelcancel cancel_duel --cd 0 --usercd 10
        How to use: !cancelduel
        """

        bot = options["bot"]
        source = options["source"]

        if source.username not in self.duel_requests:
            bot.whisper(source.username, "You have not sent any duel requests")
            return

        bot.whisper(
            source.username, "You have cancelled the duel vs {}".format(
                self.duel_requests[source.username]))

        del self.duel_targets[self.duel_requests[source.username]]
        del self.duel_requests[source.username]

    def accept_duel(self, **options):
        """
        Accepts any active duel requests you've received.

        How to add: !add funccommand accept accept_duel --cd 0 --usercd 0
        How to use: !accept
        """

        bot = options["bot"]
        source = options["source"]

        if source.username not in self.duel_targets:
            bot.whisper(source.username,
                        "You are not being challenged to a duel by anyone.")
            return

        requestor = bot.users[self.duel_targets[source.username]]
        duel_price = self.duel_request_price[self.duel_targets[
            source.username]]

        if not source.can_afford(duel_price) or not requestor.can_afford(
                duel_price):
            bot.whisper(
                source.username,
                "Your duel request with {} was cancelled due to one of you not having enough points."
                .format(requestor.username_raw),
            )
            bot.whisper(
                requestor.username,
                "Your duel request with {} was cancelled due to one of you not having enough points."
                .format(source.username_raw),
            )

            del self.duel_requests[self.duel_targets[source.username]]
            del self.duel_targets[source.username]

            return False

        source.points -= duel_price
        requestor.points -= duel_price
        participants = [source, requestor]
        winner = random.choice(participants)
        participants.remove(winner)
        loser = participants.pop()
        winner.points += duel_price * 2

        winner.save()
        loser.save()

        DuelManager.user_won(winner, duel_price * 2)
        DuelManager.user_lost(loser, duel_price)

        arguments = {
            "winner": winner.username,
            "loser": loser.username,
            "total_pot": duel_price
        }

        message = self.get_phrase("message_won_points", **arguments)

        bot.say(message)

        del self.duel_requests[self.duel_targets[source.username]]
        del self.duel_targets[source.username]

        # HandlerManager.trigger("on_duel_complete", winner, loser, winning_pot, duel_price)

    def decline_duel(self, **options):
        """
        Declines any active duel requests you've received.

        How to add: !add funccommand deny|decline decline_duel --cd 0 --usercd 0
        How to use: !decline
        """

        bot = options["bot"]
        source = options["source"]

        if source.username not in self.duel_targets:
            bot.whisper(source.username,
                        "You are not being challenged to a duel")
            return False

        requestor_username = self.duel_targets[source.username]

        bot.whisper(
            source.username,
            "You have declined the duel vs {}".format(requestor_username))
        bot.whisper(
            requestor_username,
            "{} declined the duel challenge with you.".format(
                source.username_raw))

        del self.duel_targets[source.username]
        del self.duel_requests[requestor_username]

    def status_duel(self, **options):
        """
        Whispers you the current status of your active duel requests/duel targets

        How to add: !add funccommand duelstatus|statusduel status_duel --cd 0 --usercd 5
        How to use: !duelstatus
        """

        bot = options["bot"]
        source = options["source"]

        msg = []
        if source.username in self.duel_requests:
            msg.append("You have a duel request for {} points by {}".format(
                self.duel_request_price[source.username],
                self.duel_requests[source.username]))

        if source.username in self.duel_targets:
            msg.append(
                "You have a pending duel request from {} for {} points".format(
                    self.duel_targets[source.username],
                    self.duel_request_price[self.duel_targets[
                        source.username]]))

        if len(msg) > 0:
            bot.whisper(source.username, ". ".join(msg))
        else:
            bot.whisper(
                source.username,
                "You have no duel request or duel target. Type !duel USERNAME POT to duel someone!"
            )

    @staticmethod
    def get_duel_stats(**options):
        """
        Whispers the users duel winratio to the user
        """

        bot = options["bot"]
        source = options["source"]

        with DBManager.create_session_scope(
                expire_on_commit=False) as db_session:
            db_session.add(source.user_model)

            if source.duel_stats is None:
                bot.whisper(source.username, "You have no recorded duels.")
                return True

            bot.whisper(
                source.username,
                "duels: {ds.duels_total} winrate: {ds.winrate:.2f}% streak: {ds.current_streak} profit: {ds.profit}"
                .format(ds=source.duel_stats),
            )
Exemple #30
0
class TriviaModule(BaseModule):

    ID = __name__.split(".")[-1]
    NAME = "Trivia"
    DESCRIPTION = "Trivia!"
    CATEGORY = "Game"
    SETTINGS = [
        ModuleSetting(
            key="hint_count",
            label=
            "How many hints the user should get before the question is ruined.",
            type="number",
            required=True,
            default=2,
            constraints={
                "min_value": 0,
                "max_value": 4
            },
        ),
        ModuleSetting(
            key="step_delay",
            label=
            "Time between each step (step_delay*(hint_count+1) = length of each question)",
            type="number",
            required=True,
            placeholder="",
            default=10,
            constraints={
                "min_value": 5,
                "max_value": 45
            },
        ),
        ModuleSetting(
            key="default_point_bounty",
            label="Default point bounty per right answer",
            type="number",
            required=True,
            placeholder="",
            default=0,
            constraints={
                "min_value": 0,
                "max_value": 50
            },
        ),
        ModuleSetting(
            key="question_delay",
            label="Delay between questions in seconds.",
            type="number",
            required=True,
            placeholder="",
            default=0,
            constraints={
                "min_value": 0,
                "max_value": 600
            },
        ),
    ]

    def __init__(self, bot):
        super().__init__(bot)

        self.job = None

        self.trivia_running = False
        self.last_question = None
        self.question = None
        self.step = 0
        self.last_step = None

        self.point_bounty = 0

    def poll_trivia(self):
        if self.question is None and (self.last_question is None
                                      or utils.now() - self.last_question >=
                                      datetime.timedelta(seconds=12)):
            url = "http://jservice.io/api/random"
            r = requests.get(url, headers={"User-Agent": self.bot.user_agent})
            self.question = r.json()[0]
            self.question["answer"] = (self.question["answer"].replace(
                "<i>", "").replace("</i>", "").replace("\\", "").replace(
                    "(", "").replace(")", "").strip('"').strip("."))

            if (len(self.question["answer"]) == 0
                    or len(self.question["question"]) <= 1
                    or "href=" in self.question["answer"]):
                self.question = None
                return

            self.step = 0
            self.last_step = None

        # Is it time for the next step?
        condition = self.last_question is None or utils.now(
        ) - self.last_question >= datetime.timedelta(
            seconds=self.settings["question_delay"])
        if (self.last_step is None and condition) or (
                self.last_step is not None and utils.now() - self.last_step >=
                datetime.timedelta(seconds=self.settings["step_delay"])):
            self.last_step = utils.now()
            self.step += 1

            if self.step == 1:
                self.step_announce()
            elif self.step < self.settings["hint_count"] + 2:
                self.step_hint()
            else:
                self.step_end()

    def step_announce(self):
        try:
            self.bot.safe_me(
                f'KKona A new question has begun! In the category "{self.question["category"]["title"]}", the question/hint/clue is "{self.question["question"]}" KKona'
            )
        except:
            self.step = 0
            self.question = None
            pass

    def step_hint(self):
        # find out what % of the answer should be revealed
        full_hint_reveal = int(math.floor(len(self.question["answer"]) / 2))
        current_hint_reveal = int(
            math.floor(((self.step) / self.settings["hint_count"]) *
                       full_hint_reveal))
        hint_arr = []
        index = 0
        for c in self.question["answer"]:
            if c == " ":
                hint_arr.append(" ")
            else:
                if index < current_hint_reveal:
                    hint_arr.append(self.question["answer"][index])
                else:
                    hint_arr.append("_")
            index += 1
        hint_str = "".join(hint_arr)

        self.bot.safe_me(f'OpieOP Here\'s a hint, "{hint_str}" OpieOP')

    def step_end(self):
        if self.question is not None:
            self.bot.safe_me(
                f'MingLee No one could answer the trivia! The answer was "{self.question["answer"]}" MingLee'
            )
            self.question = None
            self.step = 0
            self.last_question = utils.now()

    def command_start(self, bot, source, message, **rest):
        if self.trivia_running:
            bot.safe_me(f"{source}, a trivia is already running")
            return

        self.trivia_running = True
        self.job = ScheduleManager.execute_every(1, self.poll_trivia)

        try:
            self.point_bounty = int(message)
            if self.point_bounty < 0:
                self.point_bounty = 0
            elif self.point_bounty > 50:
                self.point_bounty = 50
        except:
            self.point_bounty = self.settings["default_point_bounty"]

        if self.point_bounty > 0:
            bot.safe_me(
                f"The trivia has started! {self.point_bounty} points for each right answer!"
            )
        else:
            bot.safe_me("The trivia has started!")

        HandlerManager.add_handler("on_message", self.on_message)

    def command_stop(self, bot, source, **rest):
        if not self.trivia_running:
            bot.safe_me(f"{source}, no trivia is active right now")
            return

        self.job.remove()
        self.job = None
        self.trivia_running = False
        self.step_end()

        bot.safe_me("The trivia has been stopped.")

        HandlerManager.remove_handler("on_message", self.on_message)

    def on_message(self, source, message, whisper, **rest):
        if not message or whisper:
            return

        if self.question:
            right_answer = self.question["answer"].lower()
            user_answer = message.lower()
            if len(right_answer) <= 5:
                correct = right_answer == user_answer
            else:
                ratio = Levenshtein.ratio(right_answer, user_answer)
                correct = ratio >= 0.94

            if correct:
                if self.point_bounty > 0:
                    self.bot.safe_me(
                        f"{source} got the answer right! The answer was {self.question['answer']} FeelsGoodMan They get {self.point_bounty} points! PogChamp"
                    )
                    source.points += self.point_bounty
                else:
                    self.bot.safe_me(
                        f"{source} got the answer right! The answer was {self.question['answer']} FeelsGoodMan"
                    )

                self.question = None
                self.step = 0
                self.last_question = utils.now()

    def load_commands(self, **options):
        self.commands["trivia"] = Command.multiaction_command(
            level=100,
            delay_all=0,
            delay_user=0,
            can_execute_with_whisper=True,
            commands={
                "start":
                Command.raw_command(self.command_start,
                                    level=500,
                                    delay_all=0,
                                    delay_user=10,
                                    can_execute_with_whisper=True),
                "stop":
                Command.raw_command(self.command_stop,
                                    level=500,
                                    delay_all=0,
                                    delay_user=0,
                                    can_execute_with_whisper=True),
            },
        )