def h_tell_sent(bot, id, target, sent_msgs, reply_msg=None): if reply_msg is None: reply_msg = 'It shall be done' if len(sent_msgs) > 1: reply(bot, id, target, '%s (%s messages sent).' % (reply_msg, len(sent_msgs))) else: reply(bot, id, target, '%s (1 message sent to "%s").' % (reply_msg, sent_msgs[0].to_nick))
def h_xray(bot, id, chan, args, full_msg): if chan.lower() not in games: return game = games[chan.lower()] if chan.lower() != game.names[game.player].lower(): reply(bot, id, chan, 'Error: it is not your turn.'); return args = args.split() if args: value = args[0] if value in '123': value = int(value) else: reply(bot, id, chan, 'Error: "%s" is not a valid 3-sided die value.' % value); return else: player, opponent = game.player, other_colour(game.player) values = [dv for (dc,dv) in game.dice[player] if dc == opponent] if not values: reply(bot, id, chan, 'Error: %s does not possess any %s dice.' % (player, opponent.lower())); return if not all(v == values[0] for v in values[1:]): reply(bot, id, chan, 'Error: %s has %s dice with different values; you must specify' ' the value to sacrifice.' % (player, opponent.lower())); return value = values[0] try: game.xray(value) except IllegalMove as exc: reply(bot, id, chan, 'Error: %s' % exc) return yield util.sub(end_move(bot, game))
def h_help_seen(bot, reply, args): reply('seen NICK\2 or \2!seen NICK!USER@HOST', 'Tells how long ago any user was seen in the channel with the given NICK,' ' or matching the given hostmask. Both forms may contain the wildcard' ' characters * and ?, which stand for zero or more characters and exactly' ' one character, respectively. Examples: "!seen Alice",' ' "!seen *@*.alicedsl.se".')
def h_dismiss(bot, id, chan, query, full_msg, reply): if chan is None: return reply( 'Error: the !dismiss command may only be used in a channel.') state = get_state() msgs = [m for m in state.msgs if m.channel.lower() == chan.lower() and (not query or match_id(query, m.from_id))] msgs = [m for m in state.msgs if would_deliver(id, chan, m) and (not query or match_id(query, m.from_id))] if not msgs: return reply( 'You have no messages%s to dismiss.' % (query and ' from "%s"' % query)) msg = msgs[-1] state.msgs.remove(msg) state.dismissed_msgs = [m for m in state.dismissed_msgs if (datetime.datetime.utcnow() - m.time_sent).days <= DISMISS_DAYS] state.dismissed_msgs.append(msg) count = len([m for m in state.msgs if would_deliver(id, chan, m)]) msg = ('1 message from %s deleted; you now have %s message%s' ' (you may reverse this using !undismiss).' % (msg.from_id.nick, count, 's' if count != 1 else '')) put_state(state) reply(msg)
def h_tell_undo(bot, id, target, args, full_msg): try: redo_state() except HistoryEmpty: reply(bot, id, target, 'Error: no redo state is available.') else: reply(bot, id, target, 'Done.')
def h_help_undismiss(bot, reply, args): reply('undismiss\2 or \2!undismiss NICK', 'Reverses the effect of \2!dismiss\2, restoring the last dismissed message' ' from NICK, or from anybody if NICK is not specified. This may be done' ' multiple times to restore messages from up to %s days ago. As with' ' \2!dismiss\2, NICK may take the form NICK!USER@HOST, and may contain the' ' wildcard characters * and ?, and alternatives separated by /.' % DISMISS_DAYS)
def h_url_collect_urls(bot, urls, chan, id, orig_msg): if not chan: return chan = chan.lower() delete_indices = [] for url_spec, index in izip(urls, count()): url, is_nsfw = url_collect.url_nsfw(url_spec) for tries in xrange(UPLOAD_RETRIES): if tries: yield runtime.sleep(UPLOAD_RETRY_S) mirror_url = get_mirror_url(url, chan) if mirror_url: break else: continue nsfw_str = '\2NSFW:\2 ' if is_nsfw else '' msg = '%s%s copied to <%s>.' % (nsfw_str, url, mirror_url) message.reply(bot, id, chan, msg, prefix=False) url_collect.history[chan].append([ url_collect.nsfw_url(mirror_url, is_nsfw)]) delete_indices.append(index) new_urls = [u for (i,u) in izip(count(),urls) if i not in delete_indices] for index in xrange(len(url_collect.history[chan])-1, -1, -1): if url_collect.history[chan][index] == urls: if new_urls: url_collect.history[chan][index] = new_urls else: del url_collect.history[chan][index] break
def h_message(bot, id, target, msg): now = time.time() if target and target in last and now < last[target] + PERIOD: return if re.search("(h[eau]+){2,}", msg, re.I): last[target] = now reply(bot, id, target, laugh(), prefix=False)
def untell_nicks(bot, id, target, channel, args): state = get_state() count = dict() def would_cancel(msg, to_nick): if msg.channel.lower() != channel.lower(): return False if msg.to_nick.lower() != to_nick.lower(): return False if msg.from_id != id: return False return True for to_nick in [n.strip() for n in args.split(',')]: msgs = [(would_cancel(m, to_nick), m) for m in state.msgs] msgs_cancel = [m for (b, m) in msgs if b] msgs_keep = [m for (b, m) in msgs if not b] count[to_nick] = len(msgs_cancel) if len(msgs_cancel): state.msgs = msgs_keep total = sum(count.itervalues()) msg = '%s message%s deleted.' % (total, 's' if total != 1 else '') empty = ['"%s"' % nick for (nick, count) in count.iteritems() if not count] if empty: list = ', '.join(empty[:-2] + [' or '.join(empty[-2:])]) msg += (' There were no messages to %s, from %s in %s.' % (list, '%s!%s@%s' % tuple(id), channel)) put_state(state) reply(bot, id, target, msg)
def h_urk(bot, id, target, args, full_msg): reply_url = random.choice(( 'https://i.imgur.com/j9wHs6W.jpg', 'https://i.imgur.com/oHbphbE.jpg', 'https://i.imgur.com/dUKjGVi.jpg', 'https://i.imgur.com/jKLxKdY.jpg', )) message.reply(bot, id, target, '%s Urk!' % reply_url, prefix=False)
def h_help_upoopia(bot, reply, *args): reply('upoopia #CHANNEL [b[lue]|r[ed]]', ' Challenge #CHANNEL to a game of Upoopia:' ' <http://www.unicorn7.org/games/game/553/>. To play on IRC, each player' ' must be in a separate channel with operator status, then each player' ' must send a challenge to the other channel, optionally indicating their' ' preferred colour, where, by convention, Blue moves first.') reply('upoopia', ' With no arguments, start a new game with the last channel played.')
def h_help_convert(bot, reply, args): reply('convert QUANTITY [to] UNIT') reply('cv QUANTITY [to] UNIT', 'Convert a quantity expressed in a certain unit to another unit of' ' measurement. Currently, the following units are supported: metre, inch,' ' foot, yard, mile, nautical mile, league, Planck length; gram, ounce,' ' pound, stone, Planck mass; degree Celsius, Farenheit, Kelvin, Rankine.' ' SI prefixes may be used with suitable metric units. Standard' ' abbreviations may be used.')
def h_resign(bot, id, chan, args, full_msg): if chan.lower() not in games: return game = games[chan.lower()] if chan.lower() != game.names[game.player].lower(): reply(bot, id, chan, 'Error: it is not your turn.'); return try: game.resign() except IllegalMove as exc: reply(bot, id, chan, 'Error: %s' % exc) return yield util.sub(end_move(bot, game))
def h_message_ignored(bot, id, target, msg): if not target: return if random.randrange(IFREQ) != 0: return if len(msg) > MAXLEN or len(msg) < MINLEN: return msg = bum_replace(msg) if not msg: return reply(bot, id, target, msg, prefix=False)
def admin_decd(*args, **kwds): cargs = inspect.getcallargs(func, *args, **kwds) bot, id = cargs['bot'], cargs['id'] is_admin = yield check(bot, id) if is_admin: token = object() bot.link(token, func) bot.drive(token, *args, **kwds) bot.unlink(token, func) elif 'target' in cargs or 'chan' in cargs: target = cargs.get('target') or cargs.get('chan') reply(bot, id, cargs['target'], 'Access denied.')
def h_missed_rolls(bot, id, target, args, full_msg): if target is None: message.reply(bot, id, target, 'Error: this command may not be used by PM; however, see' ' \2!help view-missed-rolls\2 for a version which can.') elif args: message.reply(bot, id, target, 'Error: this command does not accept any parameters. Perhaps you' ' meant \2!view-missed-rolls\2.') else: def reply(msg): message.reply(bot, id, target, msg, prefix=False, wrap=True) yield show_missed_rolls(bot, target, reply, delete=True)
def h_read(bot, id, chan, args, full_msg): if chan is not None: msgs = deliver_msgs(bot, id, chan, explicit=True) if not msgs: reply(bot, id, chan, 'You have no messages.') return state = get_state() all_msgs = [m for m in state.msgs if would_deliver(id, None, m) and m in state.last_notify] earliest = dict() for msg in all_msgs: earliest[msg.channel.lower()] = min( msg.time_sent, earliest.get(msg.channel.lower(), msg.time_sent)) all_msgs = sorted(all_msgs, key=lambda m: earliest[m.channel.lower()]) if not all_msgs: reply(bot, id, chan, 'No messages are available to read.') return msgs = all_msgs[:MAX_DELIVER_PM] remain_msgs = all_msgs[MAX_DELIVER_PM:] while 1 <= sum(1 for m in remain_msgs if m.channel == msgs[-1].channel) \ <= MAX_DELIVER_CHAN: msgs.append(remain_msgs.pop(0)) for msg, index in izip(msgs, count(1)): tag = '%d/%d: ' % (index, len(all_msgs)) deliver_msg(bot, id, None, msg, tag=tag) yield runtime.sleep(0) state = get_state() state.msgs = [m for m in state.msgs if m not in msgs] put_state(state) next_msgs = remain_msgs[:MAX_DELIVER_PM] next_remain_msgs = remain_msgs[MAX_DELIVER_PM:] while any(n <= MAX_DELIVER_CHAN for n in Counter(m.channel for m in next_remain_msgs).itervalues()): next_msgs.append(next_remain_msgs.pop(0)) noun = 'message' if len(remain_msgs) == 1 else 'messages' if next_remain_msgs: reply(bot, id, chan, 'Say \2!read\2 to read the next %d of %d %s.' % ( len(next_msgs), len(remain_msgs), noun)) elif next_msgs: reply(bot, id, chan, 'Say \2!read\2 to read the remaining %d %s.' % ( len(next_msgs), noun)) else: reply(bot, id, chan, 'End of messages.')
def untell_last(bot, id, target, channel): state = get_state() def would_cancel(msg): if msg.channel.lower() != channel.lower(): return False if msg.from_id != id: return False return True cancel_msgs = filter(would_cancel, state.msgs) if cancel_msgs: last_msg = cancel_msgs[-1] state.msgs = [m for m in state.msgs if m is not last_msg] put_state(state) msg = '1 message to "%s" deleted.' % last_msg.to_nick else: msg = 'Error: you have no messages to cancel.' reply(bot, id, target, msg)
def h_cancel(bot, id, chan, args, full_msg): # Cancel any existing challenge. if chan.lower() in challenges: opp_chan, colour = challenges.pop(chan.lower()) reply(bot, id, chan, 'Challenge to %s cancelled.' % opp_chan, prefix=False) yield util.sub(end_session(bot, chan)) # Cancel any existing game. if chan.lower() in games: game = games[chan.lower()] [opp_chan] = [c for c in game.names.values() if c.lower()!=chan.lower()] for game_chan in game.names.itervalues(): del games[game_chan.lower()] bot.send_msg(game_chan, 'The game of Upoopia has been cancelled.', no_link=True) yield util.sub(end_session(bot, chan, opp_chan))
def h_tell_remove(bot, id, target, args, full_msg): state = get_state() msgs = state.msgs if target: msgs = filter(lambda m: m.channel.lower() == target.lower(), msgs) remove_msgs = [] try: for match in re.finditer(r'\S+', args): index = int(match.group()) - 1 remove_msgs.append(msgs[index]) for msg in remove_msgs: state.msgs.remove(msg) except Exception as e: return reply(bot, id, target, repr(e)) put_state(state) reply(bot, id, target, 'Done.')
def h_untell(bot, id, target, args, full_msg): # Secretly, admins may prepend the arguments with the target channel. match = re.match(r'(#\S+)\s+(.*)', args) if match: is_admin = yield auth.check(bot, id) if is_admin: channel, args = match.groups() elif target: channel = target else: reply(bot, id, target, 'Error: the !untell command may only be used in a channel.') return # No return with argument allowed in a generator. if args: untell_nicks(bot, id, target, channel, args) else: untell_last(bot, id, target, channel)
def deliver_msg(bot, id, chan, msg, tag=''): delta = datetime.datetime.utcnow() - msg.time_sent if delta.total_seconds() < 1: return False d_mins, d_secs = divmod(delta.seconds, 60) d_hours, d_mins = divmod(d_mins, 60) reply(bot, id, chan, '%s%s said%s on %s UTC (%s ago):' % ( tag, '%s!%s@%s' % tuple(msg.from_id), (' in %s' % msg.channel) if chan is None else '', msg.time_sent.strftime('%d %b %Y, %H:%M'), '%sd, %02d:%02d:%02d' % (delta.days, d_hours, d_mins, d_secs))) reply(bot, id, chan, "<%s> %s" % (msg.from_id.nick, msg.message), prefix=False) bot.drive('TELL_DELIVERY', bot, msg.from_id, id, chan, msg.message) return True
def h_help_dismiss(bot, reply, args): if args and int(args) == 2: reply('dismiss [NICK] [!dismiss [NICK] ...]', 'If NICK is given, dismisses the most recent message left for you by NICK,' ' preventing it from being delivered; otherwise, dismisses the most recent' ' message left by anybody. Messages may be recovered using \2!undismiss\2.' ' Up to 3 additional !dismiss commands may be given on the same line.', 'NICK may be an IRC nick or a NICK!USER@HOST, may contain the wildcard' ' characters * and ?, and may contain alternatives separated by /, as' ' specified in \2!help tell 2\2; in which case, the most recent matching' ' message is dismissed.') else: reply('dismiss\2 or \2!dismiss NICK', 'Dismisses without showing it the most recent message left for you via' ' the \2!tell\2 command. If NICK is given, dismisses the most recent' ' message left by that nick. For advanced features, see' ' \2!help dismiss 2\2. See also: \2!help undismiss\2.')
def h_dom_query(bot, id, chan, args, full_msg, cont): try: if chan is None: return cobj = state.channels.get(chan.lower()) urls = cobj.games if cobj else [] if urls: for index, url in izip(count(), urls): name = getattr(state.games[url], 'name', None) \ if url in state.games else None message.reply(bot, id, chan, '%d. %s%s%s' % ( index + 1, url, ' (%s)' % name if name is not None else '', ',' if index < len(urls)-1 else '.'), prefix=False) else: message.reply(bot, id, chan, 'None.', prefix=False) finally: yield cont
def notify_msgs(bot, id, chan): state = get_state() msgs = filter(lambda m: would_deliver(id, chan, m), state.msgs) if not msgs: return noun, pronoun = ('messages', 'them') if len(msgs) > 1 else ('message', 'it') if len(msgs) <= MAX_DELIVER_CHAN: reply(bot, id, chan, 'You have %s %s; say anything to read %s.' % ( len(msgs), noun, pronoun)) else: reply(bot, id, chan, 'You have %s %s; use "\2/msg %s !read\2" to read %s.' % ( len(msgs), noun, bot.nick, pronoun)) for msg in msgs: set_last_notify(msg, state) set_state(state)
def h_tell_add(bot, id, target, args, full_msg): args = [a.strip() for a in args.split(',', 4)] if len(args) != 5: return reply(bot, id, target, 'Error: expected: FROM_ID, TO_NICK, CHAN, %s, MESSAGE...' % DATE_FORMAT_SHORT) [from_id, to_nick, channel, time_sent, message] = args try: from_id = util.ID(*re.match(r'(.*?)!(.*?)@(.*)$', from_id).groups()) time_sent = datetime.datetime.strptime(time_sent, DATE_FORMAT_SHORT) except Exception as e: return reply(bot, id, target, repr(e)) msg = Message(from_id=from_id, to_nick=to_nick, channel=channel, time_sent=time_sent, message=message) state = get_state() state.msgs.append(msg) state.msgs.sort(key=lambda m: m.time_sent) put_state(state) reply(bot, id, target, 'Done.')
def h_help_tell(bot, reply, args): if args and int(args) == 2: reply('tell NICK MESSAGE\2 or \2tell NICK[, NICK[, ...]]: MESSAGE', 'Leaves a message for the given NICK, or for each of the listed NICKs,' ' so that it will be delivered to them when next seen in this channel.' ' \2!page\2 may be used as a synonym for \2!tell\2.', 'If NICK contains any occurrence of ! or @, it will be matched against' ' the full NICK!USER@HOST of the recipient instead of just their nick.' ' If NICK contains the wildcard characters * or ?, these will match any' ' sequence of 0 or more characters, or exactly 1 character,' ' respectively.', 'Additionally, NICK may consist of multiple alternatives separated by' ' the forward slash character (/), in which case the message will be' ' delivered to the first of these recipients that is seen.') else: reply('tell NICK MESSAGE', 'Leaves a message for NICK so that it will be delivered to them when' ' next seen in this channel. Example: "!tell alice Hello.". For' ' advanced features, see \2!help tell 2\2. See also: \2!help untell\2' ' and \2!help dismiss\2.')
def h_view_missed_rolls(bot, id, target, args, full_msg): if args: nicks = map(str.lower, channel.track_channels[args.lower()]) if id.nick.lower() not in nicks: message.reply(bot, id, target, 'Error: you must be in "%s" to view its rolls.' % args) return chan = args local = False elif not target: message.reply(bot, id, target, 'Error: you must specify a channel. See' ' \2!help view-missed-rolls\2 for correct usage.') return else: chan = target local = True def reply(msg): message.reply(bot, id, target, msg, prefix=False, wrap=True) yield show_missed_rolls(bot, chan, reply, delete=False, local=local)
def h_dom_add(bot, id, chan, add_spec, full_msg, cont): try: if chan is None: return chan = chan.lower() aurls = re.findall(r'\S+', add_spec.lower()) if chan in state.channels: cobj = state.channels[chan] else: cobj = state.channels[chan] = Channel() for aurl in aurls: if aurl in cobj.games: message.reply(bot, id, chan, 'Error: "%s" is already monitored here.' % aurl) break else: for aurl in aurls: cobj.games.append(aurl) try: state.save_path(STATE_FILE) except Exception as e: message.reply(bot, id, chan, 'Error: %s' % str(e)) raise message.reply(bot, id, chan, '%d game(s) added.' % len(aurls)) yield update_urls(bot, aurls, None) finally: yield cont
def h_undismiss(bot, id, chan, query, *args): if chan == None: return reply(bot, id, chan, 'Error: the !undismiss command may only be used in a channel.') state = get_state() msgs = [m for m in state.dismissed_msgs if would_deliver(id, chan, m) and (not query or match_id(query, m.from_id))] if not msgs: return reply(bot, id, chan, 'You have no dismissed messages%s.' % (query and ' from "%s"' % query)) msg = msgs[-1] state.dismissed_msgs.remove(msg) state.msgs.append(msg) count = len([m for m in state.msgs if would_deliver(id, chan, m)]) msg = ('1 message from %s restored; you now have %s message%s' ' (say anything to read %s).' % (msg.from_id.nick, count, 's' if count != 1 else '', 'them' if count != 1 else 'it')) put_state(state) reply(bot, id, chan, msg)