def join_chan(cli, rawnick, chan, account=None, realname=None): """Handle a user joining a channel, which may be the bot itself. Ordering and meaning of arguments for a channel JOIN: 0 - The IRCClient instance (like everywhere else) 1 - The raw nick (nick!ident@host) of the user joining the channel 2 - The channel the user joined The following two arguments are optional and only present if the server supports the extended-join capability (we will have requested it when we connected if it was supported): 3 - The account the user is identified to, or "*" if none 4 - The realname (gecos) of the user, or "" if none """ if account == "*": account = None if realname == "": realname = None ch = channels.add(chan, cli) user = users.get(nick=rawnick, account=account, allow_bot=True, allow_none=True, allow_ghosts=True) if user is None: user = users.add(cli, nick=rawnick, account=account) if account: # ensure we work for the case when user left, changed accounts, then rejoined as a different account user.account = account user = users.get(nick=rawnick, account=account) ch.users.add(user) user.channels[ch] = set() # mark the user as here, in case they used to be connected before but left user.disconnected = False Event("chan_join", {}).dispatch(ch, user) if user is users.Bot: ch.mode() ch.mode(Features["CHANMODES"][0]) ch.who()
def on_whois_user(cli, bot_server, bot_nick, nick, ident, host, sep, realname): """Set up the user for a WHOIS reply. Ordering and meaning of arguments for a WHOIS user reply: 0 - The IRCClient instance (like everywhere else) 1 - The server the requester (i.e. the bot) is on 2 - The nickname of the requester (i.e. the bot) 3 - The nickname of the target 4 - The ident of the target 5 - The host of the target 6 - The literal character '*' 7 - The realname of the target This does not fire an event by itself, but sets up the proper data for the "endofwhois" listener to fire an event. """ user = users.get(nick, ident, host, allow_bot=True, allow_none=True) if user is None: user = users.add(cli, nick=nick, ident=ident, host=host) _whois_pending[nick] = { "user": user, "account": None, "away": False, "channels": set() }
def mode_change(cli, rawnick, chan, mode, *targets): """Update the channel and user modes whenever a mode change occurs. Ordering and meaning of arguments for a MODE change: 0 - The IRCClient instance (like everywhere else) 1 - The raw nick of the mode setter/actor 2 - The channel (target) of the mode change 3 - The mode changes * - The targets of the modes (if any) This takes care of properly updating all relevant users and the channel modes to make sure we remain internally consistent. """ if chan == users.Bot.nick: # we only see user modes set to ourselves users.Bot.modes.update(mode) return if "!" not in rawnick: # Only sync modes if a server changed modes because # 1) human ops probably know better # 2) other bots might start a fight over modes # 3) recursion; we see our own mode changes. evt = Event("sync_modes", {}) evt.dispatch() return actor = users.get(rawnick, allow_none=True) target = channels.add(chan, cli) target.queue("mode_change", { "mode": mode, "targets": targets }, (actor, target))
def end_who(cli, bot_server, bot_nick, target, rest): """Handle the end of WHO/WHOX responses from the server. Ordering and meaning of arguments for the end of a WHO/WHOX request: 0 - The IRCClient instance (like everywhere else) 1 - The server the requester (i.e. the bot) is on 2 - The nickname of the requester (i.e. the bot) 3 - The target the request was made against 4 - A string containing some information; traditionally "End of /WHO list." This fires off the "who_end" event, and dispatches it with one argument: The channel or user the request was made to, or None if it could not be resolved. """ try: target = channels.get(target) except KeyError: try: target = users.get(target) except KeyError: target = None else: target.dispatch_queue() old = _who_old.get(target.name, target) _who_old.clear() Event("who_end", {}, old=old).dispatch(target)
def on_chghost(cli, rawnick, ident, host): """Handle a user changing host without a quit. Ordering and meaning of arguments for CHGHOST: 0 - The IRCClient instance (like everywhere else) 1 - The raw nick (nick!ident@host) of the user switching 2 - The new ident for the user (or same if unchanged) 3 - The new host for the user (or same if unchanged) """ user = users.get(rawnick, allow_bot=True) old_ident = user.ident old_host = user.host # we avoid multiple swaps if we change the rawnick instead of ident and host separately new_rawnick = "{0}!{1}@{2}".format(user.nick, ident, host) user.rawnick = new_rawnick new_user = users.get(new_rawnick, allow_bot=True) Event("host_change", {}, old=user).dispatch(new_user, old_ident, old_host)
def on_nick_change(cli, old_rawnick, nick): """Handle a user changing nicks, which may be the bot itself. Ordering and meaning of arguments for a NICK change: 0 - The IRCClient instance (like everywhere else) 1 - The old (raw) nickname the user changed from 2 - The new nickname the user changed to """ user = users.get(old_rawnick, allow_bot=True) old_nick = user.nick user.nick = nick new_user = users.get(nick, user.ident, user.host, user.account, allow_bot=True) Event("nick_change", {}, old=user).dispatch(new_user, old_nick)
def kicked_from_chan(cli, rawnick, chan, target, reason): """Handle a user being kicked from a channel. Ordering and meaning of arguments for a channel KICK: 0 - The IRCClient instance (like everywhere else) 1 - The raw nick (nick!ident@host) of the user performing the kick 2 - The channel the kick was performed on 3 - The target of the kick 4 - The reason given for the kick (always present) """ ch = channels.add(chan, cli) actor = users.get(rawnick, allow_none=True) user = users.get(target, allow_bot=True) Event("chan_kick", {}).dispatch(ch, actor, user, reason) if user is users.Bot: ch.clear() else: ch.remove_user(user)
def on_account_change(cli, rawnick, account): """Handle a user changing accounts, if enabled. Ordering and meaning of arguments for an ACCOUNT change: 0 - The IRCClient instance (like everywhere else) 1 - The raw nick (nick!ident@host) of the user changing accounts 2 - The account the user changed to We don't see our own account changes, so be careful! """ user = users.get(rawnick) old_account = user.account user.account = account new_user = users.get(user.nick, user.ident, user.host, account, allow_bot=True) Event("account_change", {}, old=user).dispatch(new_user, old_account)
def who_reply(cli, bot_server, bot_nick, chan, ident, host, server, nick, status, hopcount_gecos): """Handle WHO replies for servers without WHOX support. Ordering and meaning of arguments for a bare WHO response: 0 - The IRCClient instance (like everywhere else) 1 - The server the requester (i.e. the bot) is on 2 - The nickname of the requester (i.e. the bot) 3 - The channel the request was made on 4 - The ident of the user in this reply 5 - The hostname of the user in this reply 6 - The server the user in this reply is on 7 - The nickname of the user in this reply 8 - The status (H = Not away, G = Away, * = IRC operator, @ = Opped in the channel in 4, + = Voiced in the channel in 4) 9 - The hop count and realname (gecos) This fires off the "who_result" event, and dispatches it with two arguments, a Channel and a User. Less important attributes can be accessed via the event.params namespace. """ hop, realname = hopcount_gecos.split(" ", 1) # We throw away the information about the operness of the user, but we probably don't need to care about that # We also don't directly pass which modes they have, since that's already on the channel/user is_away = ("G" in status) modes = {Features["PREFIX"].get(s) for s in status} - {None} user = users.get(nick, ident, host, allow_bot=True, allow_none=True) if user is None: user = users.add(cli, nick=nick, ident=ident, host=host) ch = channels.get(chan, allow_none=True) if ch is not None and ch not in user.channels: user.channels[ch] = modes ch.users.add(user) for mode in modes: if mode not in ch.modes: ch.modes[mode] = set() ch.modes[mode].add(user) _who_old[user.nick] = user event = Event("who_result", {}, away=is_away, data=0, old=user) event.dispatch(ch, user)
def on_privmsg(cli, rawnick, chan, msg, *, notice=False): if notice and "!" not in rawnick or not rawnick: # server notice; we don't care about those return _ignore_locals_ = False if var.USER_DATA_LEVEL == 0 or var.CHANNEL_DATA_LEVEL == 0: _ignore_locals_ = True # don't expose in tb if we're trying to anonymize stuff user = users.get(rawnick, allow_none=True) ch = chan.lstrip("".join(Features["PREFIX"])) if users.equals(chan, users.Bot.nick): # PM target = users.Bot else: target = channels.get(ch, allow_none=True) if user is None or target is None: return wrapper = MessageDispatcher(user, target) if wrapper.public and botconfig.IGNORE_HIDDEN_COMMANDS and not chan.startswith( tuple(Features["CHANTYPES"])): return if (notice and ((wrapper.public and not botconfig.ALLOW_NOTICE_COMMANDS) or (wrapper.private and not botconfig.ALLOW_PRIVATE_NOTICE_COMMANDS))): return # not allowed in settings for fn in decorators.COMMANDS[""]: fn.caller(var, wrapper, msg) parts = msg.split(sep=" ", maxsplit=1) key = parts[0].lower() if len(parts) > 1: message = parts[1].strip() else: message = "" if wrapper.public and not key.startswith(botconfig.CMD_CHAR): return # channel message but no prefix; ignore parse_and_dispatch(var, wrapper, key, message)
def on_quit(cli, rawnick, reason): """Handle a user quitting the IRC server. Ordering and meaning of arguments for a server QUIT: 0 - The IRCClient instance (like everywhere else) 1 - The raw nick (nick!ident@host) of the user quitting 2 - The reason for the quit (always present) """ user = users.get(rawnick, allow_bot=True) Event("server_quit", {}).dispatch(user, reason) for chan in set(user.channels): if user is users.Bot: chan._clear() else: chan.remove_user(user)
def part_chan(cli, rawnick, chan, reason=""): """Handle a user leaving a channel, which may be the bot itself. Ordering and meaning of arguments for a channel PART: 0 - The IRCClient instance (like everywhere else) 1 - The raw nick (nick!ident@host) of the user leaving the channel 2 - The channel being left The following argument may or may not be present: 3 - The reason the user gave for parting (if any) """ ch = channels.add(chan, cli) user = users.get(rawnick, allow_bot=True) Event("chan_part", {}).dispatch(ch, user, reason) if user is users.Bot: # oh snap! we're no longer in the channel! ch.clear() else: ch.remove_user(user)
def on_whois_end(cli, bot_server, bot_nick, nick, message): """Handle the end of WHOIS and fire events. Ordering and meaning of arguments for an end of WHOIS reply: 0 - The IRCClient instance (like everywhere else) 1 - The server the requester (i.e. the bot) is on 2 - The nickname of the requester (i.e. the bot) 3 - The nickname of the target 4 - A human-friendly message, usually "End of /WHOIS list." This uses data accumulated from the above WHOIS listeners, and fires the "who_result" event (once per shared channel with the bot) and the "who_end" event with the relevant User instance as the arg. """ values = _whois_pending.pop(nick) # check for account change new_user = user = values["user"] if {user.account, values["account"]} != {None} and not context.equals( user.account, values["account"]): # first check tests if both are None, and skips over this if so old_account = user.account user.account = values["account"] new_user = users.get(user.nick, user.ident, user.host, values["account"], allow_bot=True) Event("account_change", {}, old=user).dispatch(new_user, old_account) event = Event("who_result", {}, away=values["away"], data=0, old=user) for chan in values["channels"]: event.dispatch(chan, new_user) Event("who_end", {}, old=user).dispatch(new_user)
def extended_who_reply(cli, bot_server, bot_nick, data, chan, ident, ip_address, host, server, nick, status, hop, idle, account, realname): """Handle WHOX responses for servers that support it. An extended WHO (WHOX) is characterised by a second parameter to the request That parameter must be '%' followed by at least one of 'tcuihsnfdlar' If the 't' specifier is present, the specifiers must be followed by a comma and at most 3 bytes This is the ordering if all parameters are present, but not all of them are required If a parameter depends on a specifier, it will be stated at the front If a specifier is not given, the parameter will be omitted in the reply Ordering and meaning of arguments for an extended WHO (WHOX) response: 0 - - The IRCClient instance (like everywhere else) 1 - - The server the requester (i.e. the bot) is on 2 - - The nickname of the requester (i.e. the bot) 3 - t - The data sent alongside the request 4 - c - The channel the request was made on 5 - u - The ident of the user in this reply 6 - i - The IP address of the user in this reply 7 - h - The hostname of the user in this reply 8 - s - The server the user in this reply is on 9 - n - The nickname of the user in this reply 10 - f - Status (H = Not away, G = Away, * = IRC operator, @ = Opped in the channel in 5, + = Voiced in the channel in 5) 11 - d - The hop count 12 - l - The idle time (or 0 for users on other servers) 13 - a - The services account name (or 0 if none/not logged in) 14 - r - The realname (gecos) This fires off the "who_result" event, and dispatches it with two arguments, a Channel and a User. Less important attributes can be accessed via the event.params namespace. """ if account == "0": account = None is_away = ("G" in status) data = int.from_bytes(data.encode(Features["CHARSET"]), "little") modes = {Features["PREFIX"].get(s) for s in status} - {None} # WHOX may be issued to retrieve updated account info so exclude account from users.get() # we handle the account change differently below and don't want to add duplicate users user = users.get(nick, ident, host, allow_bot=True, allow_none=True) if user is None: user = users.add(cli, nick=nick, ident=ident, host=host, account=account) new_user = user if {user.account, account} != {None} and not context.equals( user.account, account): # first check tests if both are None, and skips over this if so old_account = user.account user.account = account new_user = users.get(nick, ident, host, account, allow_bot=True) Event("account_change", {}, old=user).dispatch(new_user, old_account) ch = channels.get(chan, allow_none=True) if ch is not None and ch not in user.channels: user.channels[ch] = modes ch.users.add(user) for mode in modes: if mode not in ch.modes: ch.modes[mode] = set() ch.modes[mode].add(user) _who_old[new_user.nick] = user event = Event("who_result", {}, away=is_away, data=data, old=user) event.dispatch(ch, new_user)
def update_modes(self, actor, mode, targets): """Update the channel's mode registry with the new modes. This is called whenever a MODE event is received. All of the modes are kept up-to-date in the channel, even if we don't need it. For instance, banlists are updated properly when the bot receives them. We don't need all the mode information, but it's better to have everything stored than only some parts. """ set_time = int(time.time()) # for list modes timestamp list_modes, all_set, only_set, no_set = Features["CHANMODES"] status_modes = Features["PREFIX"].values() all_modes = list_modes + all_set + only_set + no_set + "".join( status_modes) if self.state is not _States.Joined: # not joined, modes won't have the value no_set += all_set + only_set only_set = "" all_set = "" i = 0 prefix = None for c in mode: if c in ("+", "-"): prefix = c continue elif c not in all_modes: # some broken ircds have modes without telling us about them in ISUPPORT # ignore such modes but emit a warning stream("Broken ircd detected: unrecognized channel mode +{}". format(c), level="warning") continue if prefix == "+": if c in status_modes: # op/voice status; keep it here and update the user's registry too if c not in self.modes: self.modes[c] = set() user = users.get(targets[i], allow_bot=True) self.modes[c].add(user) user.channels[self].add(c) if user in var.OLD_MODES: var.OLD_MODES[user].discard(c) i += 1 elif c in list_modes: # stuff like bans, quiets, and ban and invite exempts if c not in self.modes: self.modes[c] = {} self.modes[c][targets[i]] = ((actor.rawnick if actor is not None else None), set_time) i += 1 else: if c in no_set: # everything else; e.g. +m, +i, etc. targ = None else: # +f, +l, +j, +k targ = targets[i] i += 1 if c in only_set and targ.isdigit(): # +l/+j targ = int(targ) self.modes[c] = targ elif prefix == "-": if c in status_modes: if c in self.modes: user = users.get(targets[i], allow_bot=True) self.modes[c].discard(user) user.channels[self].discard(c) if not self.modes[c]: del self.modes[c] i += 1 elif c in list_modes: if c in self.modes: self.modes[c].pop(targets[i], None) if not self.modes[c]: del self.modes[c] i += 1 else: if c in all_set: i += 1 # -k needs a target, but we don't care about it del self.modes[c] if "k" in mode: self._key = self.modes.get("k", "")
def check_settings(): if settings.GA_ORDER_NAME is None: utils.logError('settings.GA_ORDER_NAME cannot be None.') return False if settings.GA_ADVERTISER_NAME is None: utils.logError('settings.GA_ADVERTISER_NAME cannot be None.') return False if settings.GA_USER_EMAIL is None: utils.logError('settings.GA_USER_EMAIL cannot be None.') return False if settings.GA_CREATIVE_ID is None: utils.logError('settings.GA_CREATIVE_ID cannot be None.') return False if not settings.GA_USE_EXISTING_ORDER: order = orders.get(settings.GA_ORDER_NAME) if order is not None: utils.logError( 'Order with name "%s" already exists. You must use a different name.' % order['name']) return False advertiser = advertisers.get(settings.GA_ADVERTISER_NAME) if (advertiser is None): utils.logError('Advertiser with id "%s" does not exists.' % settings.GA_ADVERTISER_NAME) user = users.get(settings.GA_USER_EMAIL) if (user is None): utils.logError('Advertiser with email "%s" does not exists.' % settings.GA_USER_EMAIL) creative = creatives.get(settings.GA_CREATIVE_ID) if (creative is None): utils.logError( 'Creative with id "%s" does not exists. You must specify an existing creative id.' % settings.GA_CREATIVE_ID) # check data structure of PREBID_PRICE_BUCKETS if settings.PREBID_PRICE_BUCKETS == 'auto': settings.PREBID_PRICE_BUCKETS = [{ 'min': 0, 'max': 5, 'increment': 0.05, 'precision': 2, }, { 'min': 5, 'max': 10, 'increment': 0.1, 'precision': 2, }, { 'min': 10, 'max': 20, 'increment': 0.5, 'precision': 2, }] else: for bucket in settings.PREBID_PRICE_BUCKETS: if 'min' not in bucket or 'max' not in bucket or 'increment' not in bucket: utils.logError( 'settings.PREBID_PRICE_BUCKETS invalid format. Requires "min", "max", and "increment"' ) return False if 'precision' not in bucket: bucket['precision'] = 2 for name in ['min', 'max', 'increment', 'precision']: if not isinstance(bucket[name], numbers.Number): utils.logError( '"%s" must be a number in settings.PREBID_PRICE_BUCKETS' % name) return False return True