def invite_handler(bot, sender, user, channel): """Common control logic for invite commands received from anywhere.""" sender = tools.Identifier(sender) user = tools.Identifier(user) channel = tools.Identifier(channel) # Sanity checks, in case someone reuses this function from outside the plugin if not sender.is_nick(): raise ValueError("Invite sender must be a nick, not a channel.") if not user.is_nick(): raise ValueError("User to invite must be a nick, not a channel.") if channel.is_nick(): raise ValueError("Target channel name must not be a nick.") # Sopel must be in the target channel if channel not in bot.channels or bot.nick not in bot.channels[channel].privileges: return bot.reply("I'm not in {}!".format(channel)) privs = bot.channels[channel].privileges # Sopel must have sufficient privileges in the target channel to send invites if privs[bot.nick] < MIN_PRIV: return bot.reply("I don't have permission to invite anyone into {}.".format(channel)) # The sender must be in the target channel if sender not in privs: return bot.reply("You're not in {}.".format(channel)) # The sender must have sufficient privileges in the target channel to send invites if privs[sender] < MIN_PRIV: return bot.reply("You don't have permission to invite anyone into {}.".format(channel)) # Sopel and the sender both passed permission checks. # DDDDOOOO IIIITTTT bot.write(['INVITE', user, channel])
def is_self(bot, nick, target): nick = tools.Identifier(nick) target = tools.Identifier(target) if nick == target: return True # shortcut to catch common goofballs try: nick_id = bot.db.get_nick_id(nick, False) target_id = bot.db.get_nick_id(target, False) except ValueError: return False # if either nick doesn't have an ID, they can't be in a group return nick_id == target_id
def f_remind(bot, trigger): """Give someone a message the next time they're seen""" teller = trigger.nick verb = trigger.group(1) if not trigger.group(3): bot.reply("%s whom?" % verb) return tellee = trigger.group(3).rstrip('.,:;') msg = trigger.group(2).lstrip(tellee).lstrip() if not msg: bot.reply("%s %s what?" % (verb, tellee)) return tellee = tools.Identifier(tellee) if not os.path.exists(bot.tell_filename): return if len(tellee) > 30: # TODO: use server NICKLEN here when available bot.reply('That nickname is too long.') return if tellee[0] == '@': tellee = tellee[1:] if tellee == bot.nick: bot.reply("I'm here now; you can tell me whatever you want!") return if tellee not in (tools.Identifier(teller), bot.nick, 'me'): tz = get_timezone(bot.db, bot.config, None, tellee) timenow = format_time(bot.db, bot.config, tz, tellee) with bot.memory['tell_lock']: if tellee not in bot.memory['reminders']: bot.memory['reminders'][tellee] = [(teller, verb, timenow, msg) ] else: bot.memory['reminders'][tellee].append( (teller, verb, timenow, msg)) # save the reminders dump_reminders(bot.tell_filename, bot.memory['reminders']) response = "I'll pass that on when %s is around." % tellee bot.reply(response) elif tools.Identifier(teller) == tellee: bot.reply('You can %s yourself that.' % verb) else: bot.reply("Hey, I'm not as stupid as Monty you know!")
def seen(bot, trigger): """Reports when and where the user was last seen.""" if not trigger.group(2): bot.reply("Use `%sseen <nick>` to know when <nick> was last seen." % bot.settings.core.help_prefix) return nick = trigger.group(2).strip() if nick == bot.nick: bot.reply("I'm right here!") return timestamp = bot.db.get_nick_value(nick, 'seen_timestamp') if not timestamp: bot.reply("Sorry, I haven't seen {nick} around.".format(nick=nick)) return channel = bot.db.get_nick_value(nick, 'seen_channel') message = bot.db.get_nick_value(nick, 'seen_message') action = bot.db.get_nick_value(nick, 'seen_action') saw = datetime.datetime.utcfromtimestamp(timestamp) delta = seconds_to_human((trigger.time - saw).total_seconds()) msg = "I last saw " + nick if tools.Identifier(channel) == trigger.sender: if action: msg += " in here {since}, doing: {nick} {action}".format( since=delta, nick=nick, action=message) else: msg += " in here {since}, saying: {message}".format( since=delta, message=message) else: msg += " in another channel {since}.".format(since=delta) bot.say(msg)
def take_comment(bot, trigger): """ Log a comment, to be shown with other comments when a chair uses .comments. Intended to allow commentary from those outside the primary group of people in the meeting. Used in private message only, as `.comment <#channel> <comment to add>` See [meetbot module usage]({% link _usage/meetbot-module.md %}) """ if not trigger.group(4): # <2 arguements were given bot.say( "Usage: {}comment <#channel> <comment to add>".format( bot.config.core.help_prefix ) ) return target, message = trigger.group(2).split(None, 1) target = tools.Identifier(target) if not is_meeting_running(target): bot.say("There is no active meeting in that channel.") else: meetings_dict[trigger.group(3)]["comments"].append((trigger.nick, message)) bot.say( "Your comment has been recorded. It will be shown when the " "chairs tell me to show the comments." ) bot.say( "A new comment has been recorded.", meetings_dict[trigger.group(3)]["head"] )
def insult(bot, trigger): """Insults another user.""" url = "https://evilinsult.com/generate_insult.php" params = {"lang": "en", "type": "json"} target = trigger.group(3) if not target: bot.reply("I need someone to insult, dipshit.") return target = tools.Identifier(target) if target == bot.nick: bot.reply("Nice try, retard.") return if target not in bot.channels[trigger.sender].users: bot.reply("I need someone to insult, dipshit.") return try: insult = requests.get(url, params=params).json()['insult'] insult_escaped = html.unescape(insult) bot.say("{}: {}".format(target, insult_escaped)) except: bot.reply("There was an error. F**k you.")
def trigger_account(bot): line = '@account=egg :[email protected] PRIVMSG #Sopel :Hello, world' return Trigger( bot.config, PreTrigger(tools.Identifier('egg'), line), None, 'egg')
def kickban(bot, trigger): """Kick and ban a user from the channel The bot must be a channel operator for this command to work. """ text = trigger.group().split() argc = len(text) if argc < 4: return opt = tools.Identifier(text[1]) nick = opt mask = text[2] channel = trigger.sender reasonidx = 3 if not opt.is_nick(): if argc < 5: return channel = opt nick = text[2] mask = text[3] reasonidx = 4 reason = ' '.join(text[reasonidx:]) mask = configureHostMask(mask) if mask == '': return bot.write(['MODE', channel, '+b', mask]) bot.kick(nick, channel, reason)
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 __init__(self, settings): # private properties: access as read-only properties self._nick = tools.Identifier(settings.core.nick) self._user = settings.core.user self._name = settings.core.name self._isupport = ISupport() self._myinfo = None self.backend = None """IRC Connection Backend.""" self.connection_registered = False """Is the IRC Connection registered yet?""" self.settings = settings """Bot settings.""" self.enabled_capabilities = set() """A set containing the IRCv3 capabilities that the bot has enabled.""" self._cap_reqs = dict() """A dictionary of capability names to a list of requests.""" # internal machinery self.sending = threading.RLock() self.last_error_timestamp = None self.error_count = 0 self.stack = {} self.hasquit = False self.last_raw_line = '' # last raw line received
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 literal_dick_measuring(bot, trigger): """100% accurate penis measuring.""" target = trigger.group(3) or trigger.nick if not target: bot.reply("How in the hell did you do this?") return # Set Case Insensitivity target = tools.Identifier(target) # Lock in the random state state = random.getstate() # Check user is in channel if target not in bot.channels[trigger.sender].users: bot.reply("I need someone in chat to measure. ( ͡° ͜ʖ ͡°)") return # Get dick length if target == bot.nick: length = 20 else: random.seed(str(target).lower()) length = random.randint(0, 10) dick_length = "8{}D".format("=" * length) # Restore random state random.setstate(state) # Tell user their dick length bot.say("{}'s dick size: {}".format(target, dick_length))
def test_bot_legacy_permissions(configfactory, botfactory, triggerfactory): """ Make sure permissions match after being updated from both RPL_NAMREPLY and RPL_WHOREPLY, #1482 """ mockbot = botfactory(configfactory('default.cfg', TMP_CONFIG)) nick = tools.Identifier("Admin") # RPL_NAMREPLY mockwrapper = triggerfactory.wrapper( mockbot, ":test.example.com 353 Foo = #test :Foo ~@Admin") coretasks.handle_names(mockwrapper, mockwrapper._trigger) assert '#test' in mockbot.channels assert nick in mockbot.channels["#test"].privileges assert '#test' in mockbot.privileges assert nick in mockbot.privileges["#test"] channel_privileges = mockbot.channels["#test"].privileges[nick] privileges = mockbot.privileges["#test"][nick] assert channel_privileges == privileges # RPL_WHOREPLY mockwrapper = triggerfactory.wrapper( mockbot, ":test.example.com 352 Foo #test " "~Admin adminhost test.example.com Admin Hr~ :0 Admin") coretasks.recv_who(mockwrapper, mockwrapper._trigger) channel_privileges = mockbot.channels["#test"].privileges[nick] privileges = mockbot.privileges["#test"][nick] assert channel_privileges == privileges assert mockbot.users.get(nick) is not None
def weather(bot, trigger): if not bot.config.climacell.climacell_api_key or bot.config.climacell.climacell_api_key == '': return bot.reply( "No ClimaCell API key found, please configure this plugin.") if not bot.config.climacell.google_api_key or bot.config.climacell.google_api_key == '': return bot.reply( "No Google API key found, please configure this plugin.") location = trigger.group(2) if not location: latitude = bot.db.get_nick_value(trigger.nick, 'latitude') longitude = bot.db.get_nick_value(trigger.nick, 'longitude') location = bot.db.get_nick_value(trigger.nick, 'location') if not location: return bot.say( ("I don't know where you live. " "Give me a location, like {pfx}{command} London, " "or tell me where you live by saying {pfx}setlocation " "London, for example.").format( command=trigger.group(1), pfx=bot.config.core.help_prefix)) else: user_input = trigger.group(2).strip().lower() if user_input == "chaz": user_input = "capitol hill seattle" latitude, longitude, location = get_latlon( user_location=user_input, api_key=bot.config.climacell.google_api_key, ) if not latitude: bot.reply("I couldn't find a location by that name.") return NOLIMIT api_key = bot.config.climacell.climacell_api_key channel_or_nick = tools.Identifier(trigger.nick) zone = _get_timezone(latitude, longitude, pendulum.now().int_timestamp, bot.config.climacell.google_api_key) bundle = { 'location': location, 'latitude': latitude, 'longitude': longitude, 'api_key': api_key, 'fields': ",".join(bot.config.climacell.now_info_items), 'units': bot.config.climacell.units, 'tz': zone } reply = get_weather(bundle) if len(repr(reply)) > 475: reply = reply.split(' | ') div = int(len(reply) / 2) bot.say(' | '.join(reply[:div])) bot.say(' | '.join(reply[div:])) return else: return bot.say(reply)
def change_current_nick(self, new_nick): """Change the current nick without configuration modification. :param str new_nick: new nick to be used by the bot """ self._nick = tools.Identifier(new_nick) LOGGER.debug('Sending nick "%s"', self.nick) self.backend.send_nick(self.nick)
def verified_nick(bot, nick, channel): # Stolen and slightly modified from my sopel-rep plugin if not all([nick, channel]): # `bot` is always going to be a thing, but `nick` or `channel` could be empty # and that means verification should immediately fail return '' # not None; see below nick = re.search(r_nick, nick).group(0) if not nick: return '' # returning None would mean the returned value can't be compared with == nick = tools.Identifier(nick) if nick.lower() not in bot.channels[channel.lower()].privileges: if nick.endswith('--'): if tools.Identifier(nick[:-2]).lower() in bot.channels[ channel.lower()].privileges: return tools.Identifier(nick[:-2]) return '' # see above return nick
def spacex(bot, trigger): """Fetches next scheduled SpaceX rocket launch.""" args = trigger.group(2) if args: args = args.split() zone = None if args: tmp_args = args for idx, arg in enumerate(tmp_args): if arg.strip().lower() == "--utc": zone = "UTC" args.pop(idx) channel_or_nick = tools.Identifier(trigger.nick) zone = zone or get_nick_timezone(bot.db, channel_or_nick) if not zone: channel_or_nick = tools.Identifier(trigger.sender) zone = get_channel_timezone(bot.db, channel_or_nick) b_url = "https://spacelaunchnow.me/api/3.3.0/launch/upcoming/?format=json&limit=3&search=spacex" try: data = requests.get(b_url).json() except: return bot.reply("I couldn't fetch data from the API") if not data.get("results"): return bot.reply("No results returned from the API") if args: tmp_args = " ".join(args) try: parsed_data = _parse_results(data, "SpaceX", idx=int(tmp_args.strip()) - 1, tz=zone) except: parsed_data = _parse_results(data, "SpaceX", tz=zone) else: parsed_data = _parse_results(data, "SpaceX", tz=zone) for line in parsed_data: bot.say(line, max_messages=2)
def unexclude(bot, trigger): """ Re-enable other users' ability to duel you (admins: or another user) """ if not trigger.group(3): target = trigger.nick else: target = tools.Identifier(trigger.group(3)) if not trigger.admin and target != trigger.nick: bot.say("Only bot admins can mark other users as duelable.") return set_unduelable(bot, target, False) bot.say("Enabled duels for %s." % target)
def timely_reset(bot, trigger): """Reset a user's timely timer for whatever reason.""" target = trigger.group(3) if not target: return bot.reply("I need someone's timely timer to reset.") target = tools.Identifier(target) if target not in bot.channels[trigger.sender].users: return bot.reply("Please provide a valid user.") bot.db.delete_nick_value(target, "currency_timely") bot.say("{}'s timely timer has been reset.".format(target))
def kick(self, bot, trigger): if trigger.nick != self.owner and not trigger.admin: bot.say(STRINGS['CANT_KICK'] % self.owner) return player = tools.Identifier(trigger.group(3)) with lock: if player not in self.players: return if player == trigger.nick: return self.quit(bot, trigger) playernum = self.playerOrder.index(player) + 1 bot.say(STRINGS['PLAYER_KICK'] % (player, playernum, trigger.nick)) return self.remove_player(bot, player)
def delete_money(bot, trigger): """Bot admin can make it so a user never had any money.""" # We want to be able to delete money regardless of any checks. # Could be the user is gone from the server/channel. target = trigger.group(3) if not target: return bot.reply("I need someone's wealth to eliminate.") target = tools.Identifier(target) bot.db.delete_nick_value(target, "currency_amount") bot.db.delete_nick_value(target, "currency_timely") bot.say("{}'s wealth has been deleted from existence.".format(target))
def test_sopel_identifier_memory_channel_str(): channel = tools.Identifier('#adminchannel') memory = tools.SopelIdentifierMemory() test_value = 'perfect' memory['#adminchannel'] = test_value assert channel in memory assert '#adminchannel' in memory assert '#AdminChannel' in memory assert 'adminchannel' not in memory assert 'Exirel' not in memory assert memory[channel] == test_value assert memory['#adminchannel'] == test_value assert memory['#AdminChannel'] == test_value
def test_sopel_identifier_memory_id(): user = tools.Identifier('Exirel') memory = tools.SopelIdentifierMemory() test_value = 'king' memory[user] = test_value assert user in memory assert 'Exirel' in memory assert 'exirel' in memory assert 'exi' not in memory assert '#channel' not in memory assert memory[user] == test_value assert memory['Exirel'] == test_value assert memory['exirel'] == test_value
def kick(bot, trigger): """Kick a user from the channel.""" text = trigger.group().split() argc = len(text) if argc < 2: return opt = tools.Identifier(text[1]) nick = opt channel = trigger.sender reasonidx = 2 if not opt.is_nick(): if argc < 3: return nick = text[2] channel = opt reasonidx = 3 reason = ' '.join(text[reasonidx:]) if nick != bot.config.core.nick: bot.kick(nick, channel, reason)
def unquiet(bot, trigger): """Unquiet a user The bot must be a channel operator for this command to work. """ text = trigger.group().split() argc = len(text) if argc < 2: return opt = tools.Identifier(text[1]) quietmask = opt channel = trigger.sender if not opt.is_nick(): if argc < 3: return quietmask = text[2] channel = opt quietmask = configureHostMask(quietmask) if quietmask == '': return bot.write(['MODE', channel, '-q', quietmask])
def unban(bot, trigger): """Unban a user from the channel The bot must be a channel operator for this command to work. """ text = trigger.group().split() argc = len(text) if argc < 2: return opt = tools.Identifier(text[1]) banmask = opt channel = trigger.sender if not opt.is_nick(): if argc < 3: return channel = opt banmask = text[2] banmask = configureHostMask(banmask) if banmask == '': return bot.write(['MODE', channel, '-b', banmask])
def convert_score_file(self, bot): scores = {} with lock: try: with open(self.scoreFile, 'r+') as scorefile: for line in scorefile: tokens = line.replace('\n', '').split(' ') if len(tokens) < 4: continue if len(tokens) == 4: tokens.append(0) scores[tools.Identifier(tokens[0])] = { 'games': int(tokens[1]), 'wins': int(tokens[2]), 'points': int(tokens[3]), 'playtime': int(tokens[4]), } except Exception, e: bot.say("Score conversion error: %s" % e) return else:
def kick(bot, trigger): """Kick a user from the channel.""" if bot.channels[trigger.sender].privileges[bot.nick] < plugin.HALFOP: bot.reply(ERROR_MESSAGE_NOT_OP) return text = trigger.group().split() argc = len(text) if argc < 2: return opt = tools.Identifier(text[1]) nick = opt channel = trigger.sender reasonidx = 2 if not opt.is_nick(): if argc < 3: return nick = text[2] channel = opt reasonidx = 3 reason = ' '.join(text[reasonidx:]) if nick != bot.config.core.nick: bot.kick(nick, channel, reason)
def __init__(self, nick, admin=False, owner=False): self.nick = nick self.user = "******" channel = tools.Identifier("#Sopel") self.channels = tools.SopelIdentifierMemory() self.channels[channel] = tools.target.Channel(channel) self.users = tools.SopelIdentifierMemory() self.privileges = tools.SopelMemory() self.memory = tools.SopelMemory() self.memory['url_callbacks'] = tools.SopelMemory() self.config = MockConfig() self._init_config() self.output = [] if admin: self.config.core.admins = [self.nick] if owner: self.config.core.owner = self.nick
def unquiet(bot, trigger): """Unquiet a user The bot must be a channel operator for this command to work. """ if bot.channels[trigger.sender].privileges[bot.nick] < plugin.OP: bot.reply(ERROR_MESSAGE_NOT_OP) return text = trigger.group().split() argc = len(text) if argc < 2: return opt = tools.Identifier(text[1]) quietmask = opt channel = trigger.sender if not opt.is_nick(): if argc < 3: return quietmask = text[2] channel = opt quietmask = configureHostMask(quietmask) if quietmask == '': return bot.write(['MODE', channel, '-q', quietmask])