def gambling_checks(bot, trigger): # Set keys to None for checks data = {"bet": None, "msg": None, "target": None} # Channel Checker – perhaps make this configurable in the future if trigger.sender == GCHAN: pass else: data["msg"] = "This command can only be used in {}".format(GCHAN) return data # Target Check # NOTE: This is not near as "universal" as originally thought out... # Could definitely use some improvement in the future. # PROBLEM: This code is basically useless for all of the # actual gambling commands. Unfortunately, just swapping # trigger.group(4) and trigger.nick comes with another set of # issues to deal with. target = plain(trigger.group(4) or trigger.nick) if not target: data["msg"] = "If you're seeing this message...everything is horribly broken." return data if target == bot.nick: data["msg"] = "I just run the place; I don't participate." return data data["target"] = tools.Identifier(target) # "Bet" Parsing and Checking # We're calling everything a "bet" for simplicity. # Many commands below don't involve betting. try: bet = plain(trigger.group(3).replace(",", "").replace("$", "")) if bet.isdigit(): data["bet"] = int(bet) except AttributeError: bet = None if not bet: data["msg"] = "I need an amount of money." return data else: try: # Checks for bets made with letters # Large thanks to @Nachtalb match = re.match("([\\d.]+)([ckmbt])", bet, re.IGNORECASE) # TODO: should be some logic for "all" bet calc = { "C": 1e2, "c": 1e2, "K": 1e3, "k": 1e3, "M": 1e6, "m": 1e6, "B": 1e9, "b": 1e9, "T": 1e12, "t": 1e12 } num, size = match.groups() data["bet"] = int(float(num) * calc[size]) except (AttributeError, ValueError): data["msg"] = "I need an amount of money." return data # return keys: 'msg', 'target', and 'bet' return data
def test_plain_color(): text = 'some text' assert plain(color(text, colors.PINK)) == text assert plain(color(text, colors.PINK, colors.TEAL)) == text tpl = 'b %s a' expected = tpl % text assert plain(tpl % color(text, colors.PINK)) == expected assert plain(tpl % color(text, colors.PINK, colors.TEAL)) == expected
def test_plain_hex_color(): text = 'some text' assert plain(hex_color(text, 'ff0098')) == text assert plain(hex_color(text, 'ff0098', '00b571')) == text tpl = 'b %s a' expected = tpl % text assert plain(tpl % hex_color(text, 'ff0098')) == expected assert plain(tpl % hex_color(text, 'ff0098', '00b571')) == expected
def rainbow_cmd(bot, trigger): """Make text colored. Options are "rainbow", "usa", "commie", and "spooky".""" text = formatting.plain(trigger.group(2) or '') scheme = trigger.group(1).lower() if not text: try: msg = SCHEME_ERRORS[scheme] except KeyError: msg = "How did you do that?!" bot.reply(msg) return try: colors = COLOR_SCHEMES[scheme] except KeyError: # not possible to reach this at time of writing, but who knows? # mistakes happen when updating stuff that needs to be changed in # parallel bot.reply( "I don't know what color sequence to use for '{}'!".format(scheme)) return color_cycle = itertools.cycle(colors) bot.say(''.join(char if unicodedata.category(char) == 'Zs' else formatting.color(char, next(color_cycle)) for char in text), max_messages=4)
def plex_search(bot, trigger): """Search Plex for a movie or show.""" search = formatting.plain(trigger.group(2) or '') if not search: try: msg = "I need something to search..." except KeyError: msg = "How did you do that?!" bot.reply(msg) return plex = plex_test(bot, trigger) if plex is None: return search_results = [] # Search and add Movies to search_results for video in plex.search(search, mediatype="movie", limit=3): search_results.append("[{}] {} ({})".format(video.TYPE.title(), video.title, video.year)) # Search and add TV Shows to search_results for video in plex.search(search, mediatype="show", limit=3): search_results.append("[{}] {} ({})".format(video.TYPE.title(), video.title, video.year)) # verify there's results if not search_results: bot.reply("No results for your query.") else: bot.reply(", ".join(search_results))
def check_money(bot, trigger): """Check how much money you or another user has.""" # We're not using gambling_checks() because it's # tuned for most other commands in this plugin. # Channel Check if trigger.sender == GCHAN: pass else: return bot.reply("This command can only be used in {}".format(GCHAN)) # Target Check target = plain(trigger.group(3) or trigger.nick) if not target: return bot.reply( "If you're seeing this message...everything is horribly broken.") target = tools.Identifier(target) if target == bot.nick: return bot.reply("I just run the place; I don't participate.") if target not in bot.channels[trigger.sender].users: return bot.reply("Please provide a valid user.") # Actual Currency Check currency_amount = bot.db.get_nick_value(target, "currency_amount") if currency_amount is not None: balance = "${:,}".format(currency_amount) bot.say("{} has {}".format(target, bold(balance))) else: bot.say("{} needs to run `.iwantmoney` first.".format(target))
def antonyms(bot, trigger): word = formatting.plain(trigger.group(3)) url = "https://www.dictionaryapi.com/api/v3/references/thesaurus/json/{}".format( word) key = {"key": bot.config.thesaurus.api_key} try: antonyms = requests.get(url, params=key).json()[0]["meta"]["ants"][0] bot.say(", ".join(antonyms), max_messages=2) except IndexError: bot.reply("No results.")
def __init__(self, own_nick, line, url_schemes=None): line = line.strip('\r\n') self.line = line self.urls = tuple() self.plain = '' # Break off IRCv3 message tags, if present self.tags = {} if line.startswith('@'): tagstring, line = line.split(' ', 1) for tag in tagstring[1:].split(';'): tag = tag.split('=', 1) if len(tag) > 1: self.tags[tag[0]] = tag[1] else: self.tags[tag[0]] = None self.time = datetime.datetime.utcnow().replace( tzinfo=datetime.timezone.utc) if 'time' in self.tags: try: self.time = datetime.datetime.strptime( self.tags['time'], "%Y-%m-%dT%H:%M:%S.%fZ", ).replace(tzinfo=datetime.timezone.utc) except ValueError: pass # Server isn't conforming to spec, ignore the server-time # Grabs hostmask from line. # Example: line = ':Sopel!foo@bar PRIVMSG #sopel :foobar!' # print(hostmask) # Sopel!foo@bar # All lines start with ":" except PING. if line.startswith(':'): self.hostmask, line = line[1:].split(' ', 1) else: self.hostmask = None # Parses the line into a list of arguments. # Some events like MODE don't have a secondary string argument, i.e. no ' :' inside the line. # Example 1: line = ':nick!ident@domain PRIVMSG #sopel :foo bar!' # print(text) # 'foo bar!' # print(argstr) # ':nick!ident@domain PRIVMSG #sopel' # print(args) # [':nick!ident@domain', 'PRIVMSG', '#sopel', 'foo bar!'] # Example 2: line = 'irc.libera.chat MODE Sopel +i' # print(text) # '+i' # print(args) # ['irc.libera.chat', 'MODE', 'Sopel', '+i'] if ' :' in line: argstr, self.text = line.split(' :', 1) self.args = argstr.split(' ') self.args.append(self.text) else: self.args = line.split(' ') self.text = self.args[-1] self.event = self.args[0] self.args = self.args[1:] components = PreTrigger.component_regex.match(self.hostmask or '').groups() self.nick, self.user, self.host = components self.nick = tools.Identifier(self.nick) # If we have arguments, the first one is the sender # Unless it's a QUIT event if self.args and self.event != 'QUIT': target = tools.Identifier(self.args[0]) else: target = None # Unless we're messaging the bot directly, in which case that second # arg will be our bot's name. if target and target.lower() == own_nick.lower(): target = self.nick self.sender = target # Parse CTCP into a form consistent with IRCv3 intents if self.event == 'PRIVMSG' or self.event == 'NOTICE': intent_match = PreTrigger.intent_regex.match(self.args[-1]) if intent_match: intent, message = intent_match.groups() self.tags['intent'] = intent self.args[-1] = message or '' # Search URLs after CTCP parsing self.urls = tuple( web.search_urls(self.args[-1], schemes=url_schemes)) # Populate account from extended-join messages if self.event == 'JOIN' and len(self.args) == 3: # Account is the second arg `...JOIN #Sopel account :realname` self.tags['account'] = self.args[1] # get plain text message if self.args: self.plain = formatting.plain(self.args[-1])
def gamble_oddsevens(bot, trigger): """Wager X amount of money on (o)dds or (e)vens. Winning will net you double your bet.""" try: data = gambling_checks(bot, trigger) except Exception as msg: return bot.reply(msg) bet = data["bet"] msg = data["msg"] gambler = trigger.nick if not bet: return bot.reply(msg) # Check if user has enough money to make the gamble... bet_check = bot.db.get_nick_value(gambler, "currency_amount") if bet_check is None: return bot.reply( "You can't gamble yet! Please run the `.iwantmoney` command.") if bet > bet_check: return bot.reply( "You don't have enough money to make this bet. Try a smaller bet.") # Check if user has actually bet (o)dds or (e)vens. user_choice = plain(trigger.group(4) or '') if not user_choice: return bot.reply("You need to bet on (o)dds or (e)vens.") if user_choice in ["o", "e", "odd", "even", "odds", "evens"]: pass else: return bot.reply("You need to bet on (o)dds or (e)vens.") # Take the user's money before continuing spend_on_bet = bet_check - bet bot.db.set_nick_value(gambler, "currency_amount", spend_on_bet) # Set odds or evens if user_choice in ("odd", "even"): pass elif user_choice in ("o", "odds"): user_choice = "odd" elif user_choice in ("e", "evens"): user_choice = "even" # Roll and complete transaction roll_num = secrets.randbelow(101) if (roll_num % 2) == 0: roll = "even" else: roll = "odd" if roll == user_choice: winnings = bet * 2 new_balance = spend_on_bet + winnings bot.db.set_nick_value(gambler, "currency_amount", new_balance) balance = "${:,}".format(new_balance) msg = "I rolled {}. That's {}. You bet on {}. You won ${:,}! Your new balance is {}.".format( roll_num, roll, user_choice, winnings, bold(balance)) else: balance = "${:,}".format(spend_on_bet) msg = "I rolled {}. That's {}. You bet on {}. You lost ${:,}. Your new balance is {}.".format( roll_num, roll, user_choice, bet, bold(balance)) # Stress user with delay bot.action("rolls a bunch of dice or something...") time.sleep(1.5) bot.reply(msg)
def gamble_betflip(bot, trigger): """Wager X amount of money on (h)eads or (t)ails. Winning will net you double your bet.""" try: data = gambling_checks(bot, trigger) except Exception as msg: return bot.reply(msg) bet = data["bet"] msg = data["msg"] gambler = trigger.nick if not bet: return bot.reply(msg) # Check if user has enough money to make the gamble... bet_check = bot.db.get_nick_value(gambler, "currency_amount") if bet_check is None: return bot.reply( "You can't gamble yet! Please run the `.iwantmoney` command.") if bet > bet_check: return bot.reply( "You don't have enough money to make this bet. Try a smaller bet.") # Check if user has actually bet (H)eads or (T)ails. user_choice = plain(trigger.group(4) or '') if not user_choice: return bot.reply("You need to bet on (h)eads or (t)ails.") if user_choice in ["h", "t", "heads", "tails"]: pass else: return bot.reply("You need to bet on (h)eads or (t)ails.") # Take the user's money before continuing spend_on_bet = bet_check - bet bot.db.set_nick_value(gambler, "currency_amount", spend_on_bet) # Set heads or tails if user_choice == "h": user_choice = "heads" if user_choice == "t": user_choice = "tails" # Flip coin and complete transaction heads_or_tails = ["heads", "tails"] flip_result = secrets.choice(heads_or_tails) if flip_result == user_choice: winnings = bet * 2 new_balance = spend_on_bet + winnings bot.db.set_nick_value(gambler, "currency_amount", new_balance) balance = "${:,}".format(new_balance) msg = "Congrats; the coin landed on {}. You won ${:,}! Your new balance is {}.".format( flip_result, winnings, bold(balance)) else: balance = "${:,}".format(spend_on_bet) msg = "Sorry, the coin landed on {}. You lost ${:,}. Your new balance is {}.".format( flip_result, bet, bold(balance)) # Stress user with delay bot.action("flips a coin...") time.sleep(1.5) bot.reply(msg)
def test_plain_italic(): text = 'some text' assert plain(italic(text)) == text
def test_plain_monospace(): text = 'some text' assert plain(monospace(text)) == text
def __init__( self, own_nick: identifiers.Identifier, line: str, url_schemes: Optional[Sequence] = None, identifier_factory: IdentifierFactory = identifiers.Identifier, ): self.make_identifier = identifier_factory line = line.strip('\r\n') self.line: str = line self.urls: Tuple[str, ...] = tuple() self.plain: str = '' self.ctcp: Optional[str] = None # Break off IRCv3 message tags, if present self.tags: Dict[str, Optional[str]] = {} if line.startswith('@'): tagstring, line = line.split(' ', 1) for raw_tag in tagstring[1:].split(';'): tag = raw_tag.split('=', 1) if len(tag) > 1: self.tags[tag[0]] = tag[1] else: self.tags[tag[0]] = None # Client time or server time self.time = datetime.datetime.utcnow().replace( tzinfo=datetime.timezone.utc ) if 'time' in self.tags: # ensure "time" is a string (typecheck) tag_time = self.tags['time'] or '' try: self.time = datetime.datetime.strptime( tag_time, "%Y-%m-%dT%H:%M:%S.%fZ", ).replace(tzinfo=datetime.timezone.utc) except ValueError: pass # Server isn't conforming to spec, ignore the server-time # Grabs hostmask from line. # Example: line = ':Sopel!foo@bar PRIVMSG #sopel :foobar!' # print(hostmask) # Sopel!foo@bar # All lines start with ":" except PING. self.hostmask: Optional[str] if line.startswith(':'): self.hostmask, line = line[1:].split(' ', 1) else: self.hostmask = None # Parses the line into a list of arguments. # Some events like MODE don't have a secondary string argument, i.e. no ' :' inside the line. # Example 1: line = ':nick!ident@domain PRIVMSG #sopel :foo bar!' # print(text) # 'foo bar!' # print(argstr) # ':nick!ident@domain PRIVMSG #sopel' # print(args) # [':nick!ident@domain', 'PRIVMSG', '#sopel', 'foo bar!'] # Example 2: line = 'irc.libera.chat MODE Sopel +i' # print(text) # '+i' # print(args) # ['irc.libera.chat', 'MODE', 'Sopel', '+i'] if ' :' in line: argstr, self.text = line.split(' :', 1) self.args = argstr.split(' ') self.args.append(self.text) else: self.args = line.split(' ') self.text = self.args[-1] self.event = self.args[0] self.args = self.args[1:] # The regex will always match any string, even an empty one components_match = cast( Match, PreTrigger.component_regex.match(self.hostmask or '')) nick, self.user, self.host = components_match.groups() self.nick: identifiers.Identifier = self.make_identifier(nick) # If we have arguments, the first one is the sender # Unless it's a QUIT event target: Optional[identifiers.Identifier] = None if self.args and self.event != 'QUIT': target = self.make_identifier(self.args[0]) # Unless we're messaging the bot directly, in which case that # second arg will be our bot's name. if target.lower() == own_nick.lower(): target = self.nick self.sender = target # Parse CTCP if self.event == 'PRIVMSG' or self.event == 'NOTICE': ctcp_match = PreTrigger.ctcp_regex.match(self.args[-1]) if ctcp_match is not None: ctcp, message = ctcp_match.groups() self.ctcp = ctcp self.args[-1] = message or '' # Search URLs after CTCP parsing self.urls = tuple( web.search_urls(self.args[-1], schemes=url_schemes)) # Populate account from extended-join messages if self.event == 'JOIN' and len(self.args) == 3: # Account is the second arg `...JOIN #Sopel account :realname` self.tags['account'] = self.args[1] # get plain text message if self.args: self.plain = formatting.plain(self.args[-1])
def test_plain_strikethrough(): text = 'some text' assert plain(strikethrough(text)) == text
def test_plain_reverse(): text = 'some text' assert plain(reverse(text)) == text
def test_plain_emoji(): text = 'some emoji 💪 in here' assert plain(text) == text
def test_plain_unknown(): text = 'some \x99text' assert plain(text) == text, 'An unknown control code must not be stripped'
def test_plain_non_printing(code): text = 'some%s text' % code assert plain(text) == 'some text'
def test_plain_reset(): text = 'some%s text' % CONTROL_NORMAL assert plain(text) == 'some text'
def test_plain_bold(): text = 'some text' assert plain(bold(text)) == text
def test_plain_underline(): text = 'some text' assert plain(underline(text)) == text