def __init__(self, irc): self.__parent = super(Herald, self) self.__parent.__init__(irc) self.db = HeraldDB(filename) world.flushers.append(self.db.flush) self.lastParts = plugins.ChannelUserDictionary() splitTimeout = conf.supybot.plugins.Herald.throttle.afterSplit self.splitters = TimeoutQueue(splitTimeout) self.lastHerald = plugins.ChannelUserDictionary()
def _addRelayMsg(self, msg): channel = msg.args[0] if channel in self.lastRelayMsgs: q = self.lastRelayMsgs[channel] else: q = TimeoutQueue(60) # XXX Make this configurable. self.lastRelayMsgs[channel] = q unformatted = ircutils.stripFormatting(msg.args[1]) normalized = utils.str.normalizeWhitespace(unformatted) q.enqueue(normalized)
def __init__(self, irc): self.__parent = super(Mantis, self) self.__parent.__init__(irc) self.saidBugs = ircutils.IrcDict() sayTimeout = self.registryValue('bugSnarferTimeout') for k in irc.state.channels.keys(): self.saidBugs[k] = TimeoutQueue(sayTimeout) self.urlbase = self.registryValue('urlbase') self.privateurlbase = self.registryValue('privateurlbase') if self.privateurlbase != "": serviceUrl = self.privateurlbase + '/api/soap/mantisconnect.php' else: serviceUrl = self.urlbase + '/api/soap/mantisconnect.php' self.server = SOAPProxy(serviceUrl)._ns(namespace) self.username = self.registryValue('username') self.password = self.registryValue('password') self.oldperiodic = self.registryValue('bugPeriodicCheck') self.irc = irc self.lastBug = 0 bugPeriodicCheck = self.oldperiodic if bugPeriodicCheck > 0: schedule.addPeriodicEvent(self._bugPeriodicCheck, bugPeriodicCheck, name=self.name()) reload(sys) sys.setdefaultencoding('utf-8')
def __init__(self, irc): self.__parent = super(Roundup, self) self.__parent.__init__(irc) self.saidBugs = ircutils.IrcDict() self.saidAttachments = ircutils.IrcDict() sayTimeout = self.registryValue('bugSnarferTimeout') for k in irc.state.channels.keys(): self.saidBugs[k] = TimeoutQueue(sayTimeout) self.saidAttachments[k] = TimeoutQueue(sayTimeout) #period = self.registryValue('mboxPollTimeout') #schedule.addPeriodicEvent(self._pollMbox, period, name=self.name(), # now=False) for name in self.registryValue('roundups'): registerRoundup(name) reload(sys) sys.setdefaultencoding('utf-8')
def _shouldSayAttachment(self, attach_id, channel): if channel not in self.saidAttachments: sayTimeout = self.registryValue('bugSnarferTimeout') self.saidAttachments[channel] = TimeoutQueue(sayTimeout) if attach_id in self.saidAttachments[channel]: return False self.saidAttachments[channel].enqueue(attach_id) return True
def __init__(self, irc): self.__parent = super(Greet, self) self.__parent.__init__(irc) self.db = GreetDB(filename) world.flushers.append(self.db.flush) self.lastParts = plugins.ChannelUserDictionary() splitTimeout = conf.supybot.plugins.Greet.throttle.afterSplit self.splitters = TimeoutQueue(splitTimeout) self.lastGreet = plugins.ChannelUserDictionary()
def __init__(self, irc): self.__parent = super(Randomness, self) self.__parent.__init__(irc) self.dotCounter = TimeoutQueue(10) self.vfilename = conf.supybot.directories.data.dirize(irc.network+".votes") try: with open(self.vfilename, "r") as f: self.votes = json.load(f) except IOError: self.votes = {}
def _shouldSayBug(self, bug_id, channel): if channel not in self.saidBugs: sayTimeout = self.registryValue('bugSnarferTimeout') self.saidBugs[channel] = TimeoutQueue(sayTimeout) if bug_id in self.saidBugs[channel]: return False self.saidBugs[channel].enqueue(bug_id) #self.log.debug('After checking bug %s queue is %r' \ # % (bug_id, self.saidBugs[channel])) return True
def __init__(self, irc): self.__parent = super(Redmine, self) self.__parent.__init__(irc) self.saidBugs = ircutils.IrcDict() sayTimeout = self.registryValue('bugSnarferTimeout') for k in irc.state.channels.keys(): self.saidBugs[k] = TimeoutQueue(sayTimeout) self.url = self.registryValue('urlbase') self.auth = BasicAuth(self.registryValue('apikey'), str(random.random())) self.resource = Resource(self.url, filters=[self.auth])
def __init__(self, irc): self.__parent = super(RelayLink, self) self.__parent.__init__(irc) self._loadFromConfig() self.ircstates = {} for IRC in world.ircs: self.addIRC(IRC) floodProtectTimeout = conf.supybot.plugins.RelayLink.antiflood.seconds self.floodCounter = TimeoutQueue(floodProtectTimeout) self.floodActivated = False try: conf.supybot.plugins.RelayLink.substitutes.addCallback( self._loadFromConfig) conf.supybot.plugins.RelayLink.relays.addCallback( self._loadFromConfig) except registry.NonExistentRegistryEntry: log.error("Your version of Supybot is not compatible with " "configuration hooks. So, RelayLink won't be able " "to reload the configuration if you use the Config " "plugin.")
class Randomness(callbacks.Plugin): """Add the help for "@plugin help Randomness" here This should describe *how* to use this plugin.""" threaded = True def __init__(self, irc): self.__parent = super(Randomness, self) self.__parent.__init__(irc) self.dotCounter = TimeoutQueue(10) self.vfilename = conf.supybot.directories.data.dirize(irc.network+".votes") try: with open(self.vfilename, "r") as f: self.votes = json.load(f) except IOError: self.votes = {} def loadVoteDB(self): with open(self.vfilename, "r") as f: self.votes = json.load(f) def exportVoteDB(self): with open(self.vfilename, 'w') as f: json.dump(self.votes, f) f.write("\n") def die(self): self.__parent.die() try: self.exportVoteDB() except IOError as e: self.log.error("Failed to export Votes DB: " + str(e)) # The code below contains automatic replies then turned on. Since this # is a mostly personal plugin, they will only activate on certain # predefined networks. def _attack(self, target): bde = "%sasE%s4"%('B','6') throws = ['poorly written code', 'knives', "Te"+"chman", 'various objects', "Techm"+"ango", 'grenades', "j4j"+"ackj", 'netsplits'] spells = ['fire', 'ice', 'death', '\x02DEATH\x02', 'poison', 'stupid'] attacks = throws + spells + ['bricks', 'knives', "idiots from #freenode", "her army of trolls", "her ~~godly~~ oper powers", 'confusingly bad english', "gbyers' immaturity", "YnJlbmRpIGhpdGxlciBibG9zc29t".decode(bde)] n = random.random() if n >= 0.82: return 'casts %s at %s'%(random.choice(spells), target) elif n >= 0.76: return 'drops the bass on %s'%target elif n >= 0.72: return 'fites %s'%target elif n >= 0.48: return 'attacks %s with %s'%(target, random.choice(attacks)) else: return 'throws %s at %s'%(random.choice(throws),target) def doPrivmsg(self, irc, msg): if ircutils.isChannel(msg.args[0]) and self.registryValue("enable", msg.args[0]): dots = "." * random.randint(0,10) # added emphasis............... ow = "ow"+("w"*random.randint(0,4)) volatile = ("kicks ", "stabs ", "fites ", "bans ", "ddas ", "packets ", "beats ") exclaim = (("!" * random.randint(1,5)) + ("1" * random.randint(0,2))) * \ random.randint(1,2) + ("!" * random.randint(-1,5)) gemotes = ["xD", "=']", "\\o/", ":"+"3"*random.randint(1,4), "^_^"] bemotes = ("-_-", ":|", ":\\", ":/", ":(") semotes = (":<", ";_;", ";-;", "D:", ">:", "x(") if irc.network.lower() == "overdrive-irc": # if msg.nick.lower() == 'gbyers' and msg.args[1].lower() == 'hi lily': # irc.queueMsg(ircmsgs.kick(msg.args[0], msg.nick, "stfu")) if "fishbot" in irc.state.channels[msg.args[0]].users: hurtresponses = [ow, ";_;", ow+" :(", "RIP", "i cry", "ouch", "what was that for "+random.choice(semotes), "!voteban "+msg.nick, "PLS", "rood", "owowowowow", "omg "+random.choice(semotes), "bots have feelings too!", "wtf", "watch it!"] if re.match(r"^\x01ACTION ((attacks|stabs) {n} with |" r"(drops|throws|casts|thwacks) (.*? (at|on|with) " r"{n}|{n} (at|on|with) .*?)|fites {n}).*?\x01$".\ format(n=irc.nick), msg.args[1].lower(), re.I): sleep(0.4) n = random.random() if n >= 0.58: irc.queueMsg(ircmsgs.action(msg.args[0], self._attack(msg.nick))) elif n >= 0.4: irc.queueMsg(ircmsgs.privmsg(msg.args[0], random.choice(hurtresponses))) if "wow" in irc.state.channels[msg.args[0]].ops and \ ircutils.stripFormatting(msg.args[1].lower()).startswith("wow"): wowResponses1 = ["what is it", "hi %s%s" % (msg.nick, dots), "o/", "HI %s%s" % (msg.nick.upper(), dots), "go away "+random.choice(bemotes), "FFS "+random.choice(bemotes), "ffs i'm trying to work", "WHAT DO YOU WANT", "leave me alone "+random.choice(bemotes), "hello, you've reached 'wow'. " "If you actually need to talk to me, " "press 1. if not, PISS OFF!", "stop highlighting me" + dots, "reproted to fbi for harassment" + dots, "-_-", msg.nick + " pls", "need something?", "r u mad", "ur made", "fml", "?", ".", "/join 0", "/part SCREW U GUYS IM LEAVING AND NEVER COMING " "BACK AGAIN!! IT'S ALL %s'S FAULT I FKN HATE " "YOU ALL \x02</3" % msg.nick.upper(), "stop highlighting me!", "\x02%s\x02 added to ignore list." % msg.nick, "!votekline " + msg.nick] n = random.randint(0, 91) if n >= 60: irc.queueMsg(ircmsgs.privmsg("BotServ", "say {} {}".format(msg.args[0],random.choice(wowResponses1)))) elif n >= 50: irc.queueMsg(ircmsgs.privmsg("BotServ", "act {} {}".format(msg.args[0],random.choice(volatile)+msg.nick))) # elif msg.nick.lower().startswith("brend"): # bad = ["chink", "nigr", "nigger", "chinq"] # Seriously though, racism *sucks*. # for w in bad: # if w in ircutils.stripFormatting(msg.args[1].lower()): # irc.queueMsg(ircmsgs.kick(msg.args[0], msg.nick, "RACIST")) # return # alsobad = ["veggie tales", 'w***e', 'wh0re'] # for w in alsobad: # if w in ircutils.stripFormatting(msg.args[1].lower()): # irc.queueMsg(ircmsgs.kick(msg.args[0], msg.nick, "nothx")) if ircutils.stripFormatting(msg.args[1]) == ".": dotresponses = ["r u mad?", "lol r u mad", "mmm dots", ",", "no spam pls" + dots, ":D", "ok"] if len(self.dotCounter) >= 2: r = random.random() if r >= 0.5: irc.queueMsg(ircmsgs.privmsg(msg.args[0], random.choice(dotresponses))) else: self.dotCounter.enqueue([0]) elif ircutils.stripFormatting(msg.args[1]) == "ok": okresponses = ["not ok", "ok", "ko", "okay*", "O.K.", "^why does everyone say that ._.", "\x01ACTION ok's %s\x01" % msg.nick, "no", "Objection! \x02Not\x02 okay!", "meh", "yeah ok w/e man.", "\x01ACTION sighs\x01", "you're pretty ok.", "hmph", "I AGREE WITH YOU, "+msg.nick+dots] r = random.randint(1, 23) if r >= 19: irc.queueMsg(ircmsgs.action(msg.args[0], random.choice(volatile)+msg.nick)) elif r >= 8: irc.queueMsg(ircmsgs.privmsg(msg.args[0], random.choice(okresponses))) if irc.network.lower() in ("overdrive-irc", "stripechat") and \ ('aXRsZXIgYmxvc3NvbQ==').decode('base'+'64') in ircutils.stripFormatting(msg.args[1].lower()): irc.queueMsg(ircmsgs.privmsg(msg.args[0], msg.nick + ": the entire topic changes" + exclaim)) # if irc.network.lower() == "stripechat": # r = random.random() # if msg.args[1].lower().startswith("&topic") and "hackinbot" in msg.args[1].lower() \ # and r >= 0.3: # irc.queueMsg(ircmsgs.privmsg(msg.args[0], "OH, hackinbot! " + random.choice(gemotes))) def _lazyhostmask(self, host): return "*!"+host.split("!",1)[1] def vote(self, irc, msg, args, action): """<thing> Votes for something. It doesn't actually perform any actions directly, but could be an interesting way to get user feedback.""" try: if self._lazyhostmask(msg.prefix) in self.votes[action][1]: irc.reply("You have already voted to %s." % action) return except KeyError: self.votes[action] = [0, []] self.votes[action][0] += 1 try: a, b = action.split(" ",1) except ValueError: irc.error("Not enough arguments.", Raise=True) irc.reply("%s voted to \x02%s\x02 %s. (Votes: \x02%s\x02)" % (msg.nick, a, b, self.votes[action][0])) self.votes[action][1].append(self._lazyhostmask(msg.prefix)) vote = wrap(vote, ['text']) def voteexport(self, irc, msg, args): """takes no arguments. Exports votes stored in memory to file: data/%s(network).votes This is done automatically when the plugin is unloaded or reloaded.""" try: self.exportVoteDB() except IOError as e: irc.error("IOError caught exporting DB: "+str(e)) else: irc.replySuccess() voteexport = wrap(voteexport, ['admin']) def voteimport(self, irc, msg, args): """takes no arguments. Imports the vote database for the current network.""" try: self.loadVoteDB() except IOError as e: irc.error("IOError caught importing DB: "+str(e)) else: irc.replySuccess() voteimport = wrap(voteimport, ['admin']) def voteclear(self, irc, msg, args): """takes no arguments. Clears all votes stored in memory. Use with caution!""" self.votes = {} irc.replySuccess() voteclear = wrap(voteclear, ['admin'])
class Herald(callbacks.Plugin): """This plugin allows you to set welcome messages (heralds) to people who are recognized by the bot when they join a channel.""" def __init__(self, irc): self.__parent = super(Herald, self) self.__parent.__init__(irc) self.db = HeraldDB(filename) world.flushers.append(self.db.flush) self.lastParts = plugins.ChannelUserDictionary() splitTimeout = conf.supybot.plugins.Herald.throttle.afterSplit self.splitters = TimeoutQueue(splitTimeout) self.lastHerald = plugins.ChannelUserDictionary() def die(self): if self.db.flush in world.flushers: world.flushers.remove(self.db.flush) self.db.close() self.__parent.die() def doQuit(self, irc, msg): # We want to observe netsplits and keep from heralding users rejoining # after one. if ircmsgs.isSplit(msg): self.splitters.enqueue(msg.nick) try: id = ircdb.users.getUserId(msg.prefix) self.splitters.enqueue(id) except KeyError: pass def doJoin(self, irc, msg): if ircutils.strEqual(irc.nick, msg.nick): return # It's us. if msg.nick in self.splitters: self.log.debug('Not heralding %s, recent split.', msg.nick) return # Recently split. channel = msg.args[0] irc = callbacks.SimpleProxy(irc, msg) if self.registryValue('heralding', channel): try: id = ircdb.users.getUserId(msg.prefix) if id in self.splitters: self.log.debug('Not heralding id #%s, recent split.', id) return herald = self.db[channel, id] except KeyError: default = self.registryValue('default', channel) if default: default = ircutils.standardSubstitute(irc, msg, default) msgmaker = ircmsgs.privmsg if self.registryValue('default.notice', channel): msgmaker = ircmsgs.notice target = msg.nick if self.registryValue('default.public', channel): target = channel irc.queueMsg(msgmaker(target, default)) return now = time.time() throttle = self.registryValue('throttle', channel) if now - self.lastHerald.get((channel, id), 0) > throttle: if (channel, id) in self.lastParts: i = self.registryValue('throttle.afterPart', channel) if now - self.lastParts[channel, id] < i: return self.lastHerald[channel, id] = now herald = ircutils.standardSubstitute(irc, msg, herald) irc.reply(herald, prefixNick=False) def doPart(self, irc, msg): try: id = self._getId(irc, msg.prefix) self.lastParts[msg.args[0], id] = time.time() except KeyError: pass def _getId(self, irc, userNickHostmask): try: id = ircdb.users.getUserId(userNickHostmask) except KeyError: if not ircutils.isUserHostmask(userNickHostmask): hostmask = irc.state.nickToHostmask(userNickHostmask) id = ircdb.users.getUserId(hostmask) else: raise KeyError return id @internationalizeDocstring def default(self, irc, msg, args, channel, optlist, text): """[<channel>] [--remove|<msg>] If <msg> is given, sets the default herald to <msg>. A <msg> of "" will remove the default herald. If <msg> is not given, returns the current default herald. <channel> is only necessary if the message isn't sent in the channel itself. """ if optlist and text: raise callbacks.ArgumentError for (option, foo) in optlist: if option == 'remove': self.setRegistryValue('default', '', channel) irc.replySuccess() return if text: self.setRegistryValue('default', text, channel) irc.replySuccess() else: resp = self.registryValue('default', channel) or \ _('I do not have a default herald set for %s.') % channel irc.reply(resp) default = wrap( default, ['channel', getopts({'remove': ''}), additional('text')]) @internationalizeDocstring def get(self, irc, msg, args, channel, user): """[<channel>] [<user|nick>] Returns the current herald message for <user> (or the user <nick|hostmask> is currently identified or recognized as). If <user> is not given, defaults to the user giving the command. <channel> is only necessary if the message isn't sent in the channel itself. """ try: herald = self.db[channel, user.id] irc.reply(herald) except KeyError: irc.error(_('I have no herald for %s.') % user.name) get = wrap(get, ['channel', first('otherUser', 'user')]) def _preCheck(self, irc, msg, user): capability = self.registryValue('requireCapability') if capability: try: u = ircdb.users.getUser(msg.prefix) except KeyError: irc.errorNotRegistered(Raise=True) else: if u != user: if not ircdb.checkCapability(msg.prefix, capability): irc.errorNoCapability(capability, Raise=True) # I chose not to make <user|nick> optional in this command because # if it's not a valid username (e.g., if the user tyops and misspells a # username), it may be nice not to clobber the user's herald. @internationalizeDocstring def add(self, irc, msg, args, channel, user, herald): """[<channel>] <user|nick> <msg> Sets the herald message for <user> (or the user <nick|hostmask> is currently identified or recognized as) to <msg>. <channel> is only necessary if the message isn't sent in the channel itself. """ self._preCheck(irc, msg, user) self.db[channel, user.id] = herald irc.replySuccess() add = wrap(add, ['channel', 'otherUser', 'text']) @internationalizeDocstring def remove(self, irc, msg, args, channel, user): """[<channel>] [<user|nick>] Removes the herald message set for <user>, or the user <nick|hostmask> is currently identified or recognized as. If <user> is not given, defaults to the user giving the command. <channel> is only necessary if the message isn't sent in the channel itself. """ self._preCheck(irc, msg, user) try: del self.db[channel, user.id] irc.replySuccess() except KeyError: irc.error(_('I have no herald for that user.')) remove = wrap(remove, ['channel', first('otherUser', 'user')]) @internationalizeDocstring def change(self, irc, msg, args, channel, user, changer): """[<channel>] [<user|nick>] <regexp> Changes the herald message for <user>, or the user <nick|hostmask> is currently identified or recognized as, according to <regexp>. If <user> is not given, defaults to the calling user. <channel> is only necessary if the message isn't sent in the channel itself. """ self._preCheck(irc, msg, user) s = self.db[channel, user.id] newS = changer(s) self.db[channel, user.id] = newS irc.replySuccess() change = wrap( change, ['channel', first('otherUser', 'user'), 'regexpReplacer'])
class Bitbucket(callbacks.PluginRegexp): """Add the help for "@plugin help Bitbucket" here This should describe *how* to use this plugin.""" threaded = True callBefore = ['URL', 'Web'] unaddressedRegexps = ['snarfPullRequest'] def __init__(self, irc): self.__parent = super(Bitbucket, self) self.__parent.__init__(irc) self.timeout_queue = TimeoutQueue(self.registryValue('snarferTimeout')) def _getResponse(self, id): queryurl = 'https://bitbucket.org/api/2.0/repositories/{0}/{1}/pullrequests/{2}'.format( self.registryValue('accountname'), self.registryValue('repo_slug'), id) r = requests.get(queryurl) self.log.info('Getting pull request from %s' % queryurl) if r.status_code != requests.codes.ok: return "pull-request not found: #" + str(id) return str(PullRequest(self, r.json())) def _check_timeout(self, id): if id in self.timeout_queue: return False self.timeout_queue.enqueue(id) return True def open(self, irc, msg, args): """ List open pull request.""" queryurl = 'https://bitbucket.org/api/2.0/repositories/{0}/{1}/pullrequests'.format( self.registryValue('accountname'), self.registryValue('repo_slug')) r = requests.get(queryurl) self.log.info('Getting pull request from %s' % queryurl) if r.status_code != requests.codes.ok: irc.reply("Url not found: " + queryurl) return data = r.json() if 'values' in data: for entry in data['values']: irc.reply(str(PullRequest(self, entry)), prefixNick=False) def snarfPullRequest(self, irc, msg, match): r"""(?P<type>pull request|pull-request|pullrequest)[\s#]*(?P<id>\d+)""" channel = msg.args[0] #if not self.registryValue('bugSnarfer', channel): return id_matches = match.group('id').split() type = match.group('type') self.log.debug('Snarfed pull request ID(s): ' + ' '.join(id_matches)) # Check if the bug has been already snarfed in the last X seconds msgs = [] for id in id_matches: if not self._check_timeout(id): continue response = self._getResponse(id) if response: msgs.append(response) for msg in msgs: irc.reply(msg, prefixNick=False)
def __init__(self, irc): self.__parent = super(Bitbucket, self) self.__parent.__init__(irc) self.timeout_queue = TimeoutQueue(self.registryValue('snarferTimeout'))
def relay(self, irc, msg, channel=None): channel = (channel or msg.args[0]).lower() self.log.debug("RelayNext (%s): got channel %s", irc.network, channel) if not channel in irc.state.channels: return # Check for ignored events first. Checking for "'.' not in msg.nick" is for skipping # ignore checks from servers. ignoredevents = map(str.upper, self.registryValue('events.userIgnored', channel)) if msg.command in ignoredevents and msg.nick != irc.nick and '.' not in msg.nick and\ ircdb.checkIgnored(msg.prefix, channel): self.log.debug("RelayNext (%s): ignoring message from %s", irc.network, msg.prefix) return # Get the source channel source = "%s@%s" % (channel, irc.network) source = source.lower() out_s = self._format(irc, msg, channel) if out_s: for relay in self.db.values(): self.log.debug("RelayNext (%s): check if %s in %s", irc.network, source, relay) if source in relay: # If our channel is in a relay self.log.debug("RelayNext: found %s to be in relay %s", source, relay) # Remove ourselves from the target channels so we don't get duplicated messages targets = list(relay) targets.remove(source) self.log.debug("RelayNext: found targets %s for relay %s", targets, relay) if self.registryValue("antiflood.enable", channel): # Flood prevention timeout - how long commands of a certain type # should cease being relayed after flood prevention triggers timeout = self.registryValue("antiflood.timeout", channel) # If <maximum> messages of the same kind on one channel is # received in <seconds> seconds, flood prevention timeout is # triggered. maximum = self.registryValue("antiflood.maximum", channel) seconds = self.registryValue("antiflood.seconds", channel) # Store the message in a counter, with the keys taking the # form of (source channel@network, command name). If the counter # doesn't already exist, create one here. try: self.msgcounters[(source, msg.command)].enqueue(msg.prefix) except KeyError: self.msgcounters[( source, msg.command)] = TimeoutQueue(seconds) # Two different limits: one for messages and one for all others if msg.command == "PRIVMSG": maximum = self.registryValue( "antiflood.maximum", channel) else: maximum = self.registryValue( "antiflood.maximum.nonPrivmsgs", channel) if len(self.msgcounters[(source, msg.command)]) > maximum: # Amount of messages in the counter surpassed our limit, # announce the flood and block relaying messages of the # same type for X seconds self.log.debug( "RelayNext (%s): message from %s blocked by " "flood protection.", irc.network, channel) if self.floodTriggered.get((source, msg.command)): # However, only send the announcement once. return c = msg.command e = format( "Flood detected on %s (%s %ss/%s seconds), " "not relaying %ss for %s seconds!", channel, maximum, c, seconds, c, timeout) out_s = self._format(irc, msg, channel, announcement=e) self.floodTriggered[(source, msg.command)] = True self.log.info("RelayNext (%s): %s", irc.network, e) else: self.floodTriggered[(source, msg.command)] = False for cn in targets: # Iterate over all the relay targets for this message: # each target is stored internally as a #channel@netname # string. target, net = cn.split("@") otherIrc = world.getIrc(net) if otherIrc is None: self.log.debug( "RelayNext: message to network %r" " dropped, we are not connected " "there!", net) return target_chanobj = otherIrc.state.channels.get(target) if (not target_chanobj ) or otherIrc.nick not in target_chanobj.users: # We're not in the target relay channel! self.log.debug( "RelayNext: message to %s@%s " "dropped, we are not in that " "channel!", target, net) else: out_msg = ircmsgs.privmsg(target, out_s) # Tag the message as relayed so we (and other relayers) don't # try to relay it again. out_msg.tag('relayedMsg') otherIrc.queueMsg(out_msg)
class RelayLink(callbacks.Plugin): # noIgnore = True threaded = True class Relay(): def __init__(self, sourceChannel, sourceNetwork, targetChannel, targetNetwork, channelRegex, networkRegex, messageRegex): self.sourceChannel = sourceChannel self.sourceNetwork = sourceNetwork self.targetChannel = targetChannel self.targetNetwork = targetNetwork self.channelRegex = channelRegex self.networkRegex = networkRegex self.messageRegex = messageRegex self.hasTargetIRC = False self.hasSourceIRCChannels = False def __init__(self, irc): self.__parent = super(RelayLink, self) self.__parent.__init__(irc) self._loadFromConfig() self.ircstates = {} for IRC in world.ircs: self.addIRC(IRC) floodProtectTimeout = conf.supybot.plugins.RelayLink.antiflood.seconds self.floodCounter = TimeoutQueue(floodProtectTimeout) self.floodActivated = False try: conf.supybot.plugins.RelayLink.substitutes.addCallback( self._loadFromConfig) conf.supybot.plugins.RelayLink.relays.addCallback( self._loadFromConfig) except registry.NonExistentRegistryEntry: log.error("Your version of Supybot is not compatible with " "configuration hooks. So, RelayLink won't be able " "to reload the configuration if you use the Config " "plugin.") def _loadFromConfig(self, name=None): self.relays = [] for relay in self.registryValue('relays').split(' || '): if relay.endswith('|'): relay += ' ' relay = relay.split(' | ') if not len(relay) == 5: continue try: self.relays.append(self.Relay(relay[0], relay[1], relay[2], relay[3], re.compile('^%s$' % relay[0], re.I), re.compile('^%s$' % relay[1], re.I), re.compile(relay[4]))) except: log.error('Failed adding relay: %r' % relay) self.nickSubstitutions = {} for substitute in self.registryValue('substitutes').split(' || '): if substitute.endswith('|'): substitute += ' ' substitute = substitute.split(' | ') if not len(substitute) == 2: continue self.nickSubstitutions[substitute[0]] = substitute[1] def simpleHash(self, s): colors = ["05", "04", "03", "09", "02", "12", "06", "13", "10", "11", "07"] num = 0 for i in s: num += ord(i) num = num % 11 return colors[num] def getPrivmsgData(self, channel, nick, text, colored): color = self.simpleHash(nick) nickprefix = '' if nick in self.nickSubstitutions: nick = self.nickSubstitutions[nick] if not self.registryValue('nicks', channel): nick = '' elif self.registryValue('noHighlight', channel): nickprefix = '-' if re.match('^\x01ACTION .*\x01$', text): text = text.strip('\x01') text = text[ 7 : ] if colored: return ('%(network)s* %(nickprefix)s\x03%(color)s%(nick)s\x03 %(text)s', {'nick': nick, 'color': color, 'text': text, 'nickprefix': nickprefix}) else: return ('%(network)s* %(nickprefix)s%(nick)s %(text)s', {'nick': nick, 'text': text, 'nickprefix': nickprefix}) else: if colored: return ('%(network)s<%(nickprefix)s\x03%(color)s%(nick)s\x03> %(text)s', {'color': color, 'nick': nick, 'text': text, 'nickprefix': nickprefix}) else: return ('%(network)s<%(nickprefix)s%(nick)s> %(text)s', {'nick': nick, 'text': text, 'nickprefix': nickprefix}) return s @internationalizeDocstring def list(self, irc, msg, args): """takes no arguments Returns all the defined relay links.""" if irc.nested: irc.error('This command cannot be nested.', Raise=True) elif not self.relays: irc.reply(_('This is no relay enabled. Use "RelayLink add" "RelayLink' ' addall" to add one.')) return for relay in self.relays: if relay.hasTargetIRC: hasIRC = 'Link healthy!' else: hasIRC = '\x0302IRC object not scraped yet.\017' s ='\x02%s\x02 on \x02%s\x02 ==> \x02%s\x02 on \x02%s\x02. %s' if not self.registryValue('color', msg.args[0]): s = s.replace('\x02', '') irc.reply(s % (relay.sourceChannel, relay.sourceNetwork, relay.targetChannel, relay.targetNetwork, hasIRC), private=True) def doPrivmsg(self, irc, msg): self.addIRC(irc) channel = msg.args[0] s = msg.args[1] s, args = self.getPrivmsgData(channel, msg.nick, s, self.registryValue('color', channel)) if channel not in irc.state.channels: # in private # cuts off the end of commands, so that passwords # won't be revealed in relayed PM's if callbacks.addressed(irc.nick, msg): if self.registryValue('color', channel): color = '\x0314' match = '(>\017 \w+) .*' else: color = '' match = '(> \w+) .*' s = re.sub(match, '\\1 %s[%s]' % (color, _('truncated')), s) s = '(via PM) %s' % s self.sendToOthers(irc, channel, s, args, isPrivmsg=True) def outFilter(self, irc, msg): if msg.command == 'PRIVMSG': if not msg.relayedMsg: if msg.args[0] in irc.state.channels: s, args = self.getPrivmsgData(msg.args[0], irc.nick, msg.args[1], self.registryValue('color', msg.args[0])) self.sendToOthers(irc, msg.args[0], s, args, isPrivmsg=True) return msg def doPing(self, irc, msg): self.addIRC(irc) def doMode(self, irc, msg): self.addIRC(irc) args = {'nick': msg.nick, 'channel': msg.args[0], 'mode': ' '.join(msg.args[1:]), 'userhost': ''} if self.registryValue("noHighlight", msg.args[0]): args['nick'] = '-'+msg.nick if self.registryValue('color', msg.args[0]): args['nick'] = '\x03%s%s\x03' % (self.simpleHash(msg.nick), args['nick']) if self.registryValue('hostmasks', msg.args[0]) and "." not in \ msg.nick: args['userhost'] = ' (%s@%s)' % (msg.user, msg.host) s = ('%(network)s%(nick)s%(userhost)s set mode %(mode)s on' ' %(channel)s') self.sendToOthers(irc, msg.args[0], s, args) def doJoin(self, irc, msg): args = {'nick': msg.nick, 'channel': msg.args[0], 'userhost': ''} if self.registryValue("noHighlight", msg.args[0]): args['nick'] = '-'+msg.nick if irc.nick == msg.nick: if self.registryValue('color'): s = '%(network)s\x0309*** Relay joined to %(channel)s' else: s = '%(network)s*** Relay joined to %(channel)s' else: if self.registryValue('color', msg.args[0]): args['nick'] = '\x03%s%s\x03' % (self.simpleHash(msg.nick), args['nick']) if self.registryValue('hostmasks', msg.args[0]): args['userhost'] = ' (%s@%s)' % (msg.user, msg.host) s = '%(network)s%(nick)s%(userhost)s has joined %(channel)s' self.addIRC(irc) self.sendToOthers(irc, msg.args[0], s, args) def doPart(self, irc, msg): args = {'nick': msg.nick, 'channel': msg.args[0], 'message': '', 'userhost': ''} if msg.nick == irc.nick: if self.registryValue('color'): s = '%(network)s\x0308*** Relay parted from %(channel)s' else: s = '%(network)s*** Relay parted from %(channel)s' else: if self.registryValue("noHighlight", msg.args[0]): args['nick'] = '-'+msg.nick self.addIRC(irc) if self.registryValue('color', msg.args[0]): args['nick'] = '\x03%s%s\x03' % (self.simpleHash(msg.nick), args['nick']) if self.registryValue('hostmasks', msg.args[0]): args['userhost'] = ' (%s@%s)' % (msg.user, msg.host) try: args['message'] = ' (%s)' % (msg.args[1]) except IndexError: pass s = '%(network)s%(nick)s%(userhost)s has parted %(channel)s%(message)s' self.sendToOthers(irc, msg.args[0], s, args) def doKick(self, irc, msg): self.addIRC(irc) args = {'kicked': msg.args[1], 'channel': msg.args[0], 'kicker': msg.nick, 'message': msg.args[2], 'userhost': ''} if args['kicked'] == irc.nick: if self.registryValue('color', msg.args[0]): s = '%(network)s\x0308*** Relay kicked from %(channel)s' else: s = '%(network)s*** Relay kicked from %(channel)s' else: if self.registryValue('color', msg.args[0]): args['kicked'] = '\x03%s%s\x03' % (self.simpleHash(msg.args[1]), args['kicked']) if self.registryValue('hostmasks', msg.args[0]): # The IRC protocol only sends the hostmask of the kicker, so we'll need # to use an alternate method to fetch the host of the person being # kicked. (in this case, using ircutils) h = ircutils.splitHostmask(irc.state.nickToHostmask(msg.args[1])) args['userhost'] = ' (%s@%s)' % (h[1], h[2]) s = ('%(network)s%(kicked)s%(userhost)s has been kicked from ' '%(channel)s by %(kicker)s (%(message)s)') self.sendToOthers(irc, msg.args[0], s, args) def doNick(self, irc, msg): self.addIRC(irc) if self.registryValue("noHighlight"): args = {'oldnick': '-'+msg.nick, 'newnick': '-'+msg.args[0]} else: args = {'oldnick': msg.nick, 'newnick': msg.args[0]} if self.registryValue('color'): args['oldnick'] = '\x03%s%s\x03' % (self.simpleHash(msg.nick), args['oldnick']) args['newnick'] = '\x03%s%s\x03' % (self.simpleHash(msg.args[0]), args['newnick']) s = '%(network)s%(oldnick)s is now known as %(newnick)s' try: chandict = irc.state.channels.iteritems() except AttributeError: # Python 3 compatibility chandict = irc.state.channels.items() for (channel, c) in chandict: if msg.args[0] in c.users: self.sendToOthers(irc, channel, s, args) def doQuit(self, irc, msg): args = {'nick': msg.nick, 'message': msg.args[0]} if self.registryValue("noHighlight"): args['nick'] = '-' + msg.nick if msg.nick == irc.nick: # It's us. if self.registryValue('color'): s = '%(network)s\x0304*** ERROR: Relay disconnected...' else: s = '%(network)s*** ERROR: Relay disconnected...' else: if self.registryValue('color'): args['nick'] = '\x03%s%s\x03' % (self.simpleHash(msg.nick), args['nick']) s = '%(network)s%(nick)s has quit (%(message)s)' self.sendToOthers(irc, None, s, args, msg.nick) self.addIRC(irc) def sendToOthers(self, irc, channel, s, args, nick=None, isPrivmsg=False): assert channel is not None or nick is not None def format_(relay, s, args): if 'network' not in args: if self.registryValue('includeNetwork', relay.targetChannel): if self.registryValue('color', relay.targetChannel): args['network'] = "\x02[\x03%s%s\x03]\x02 " % \ (self.simpleHash(irc.network), irc.network) else: args['network'] = "[%s] " % irc.network if not isPrivmsg and not self.registryValue("noHighlight", channel): args['network'] += "- " else: args['network'] = '' return s % args def send(s): if not relay.hasTargetIRC: self.log.info('RelayLink: IRC %s not yet scraped.' % relay.targetNetwork) elif relay.targetIRC.zombie: self.log.info('RelayLink: IRC %s appears to be a zombie'% relay.targetNetwork) elif irc.isChannel(relay.targetChannel) and \ relay.targetChannel not in relay.targetIRC.state.channels: self.log.info('RelayLink: I\'m not in in %s on %s' % (relay.targetChannel, relay.targetNetwork)) else: if isPrivmsg or \ self.registryValue('nonPrivmsgs', channel) == 'privmsg': msg = ircmsgs.privmsg(relay.targetChannel, s) elif self.registryValue('nonPrivmsgs', channel) == 'notice': msg = ircmsgs.notice(relay.targetChannel, s) else: return msg.tag('relayedMsg') relay.targetIRC.sendMsg(msg) msgs = self.registryValue("antiflood.messages") if self.registryValue("antiflood.enable") and msgs and \ len(self.floodCounter) > msgs: if not self.floodActivated: secs = self.registryValue("antiflood.seconds") limit = "({} messages in {} seconds)".format(msgs,secs) self.log.info("RelayLink: flood protection triggered on {} {}".format(irc.network,limit)) s = ("%(network)s*** Flood detected {}. Not relaying messages for {} seconds!".format(limit, secs)) self.floodActivated = True if self.registryValue('antiflood.announce'): for relay in self.relays: new_s = format_(relay, s, args) if relay.channelRegex.match(channel) and \ relay.networkRegex.match(irc.network) and \ relay.messageRegex.search(new_s): send(new_s) return else: self.floodActivated = False if channel is None: for relay in self.relays: if not relay.hasSourceIRCChannels: continue for channel in relay.sourceIRCChannels: new_s = format_(relay, s, args) if nick in relay.sourceIRCChannels[channel].users and \ relay.channelRegex.match(channel) and \ relay.networkRegex.match(irc.network)and \ relay.messageRegex.search(new_s): if nick != irc.nick: self.floodCounter.enqueue(0) send(new_s) else: for relay in self.relays: new_s = format_(relay, s, args) if relay.channelRegex.match(channel) and \ relay.networkRegex.match(irc.network) and \ relay.messageRegex.search(new_s): if nick != irc.nick: if isPrivmsg: self.floodCounter.enqueue(0) else: self.floodCounter.enqueue(0) send(new_s) def addIRC(self, irc): match = False for relay in self.relays: if relay.sourceNetwork == irc.network: relay.sourceIRCChannels = copy.deepcopy(irc.state.channels) relay.hasSourceIRCChannels = True if relay.targetNetwork == irc.network and not relay.hasTargetIRC: relay.targetIRC = irc relay.hasTargetIRC = True @internationalizeDocstring def nicks(self, irc, msg, args, channel, optlist): """[<channel>] [--count] Returns the nicks of the people in the linked channels. <channel> is only necessary if the message isn't sent on the channel itself. If --count is specified, only the amount of """ keys = [option for (option, arg) in optlist] if irc.nested and 'count' not in keys: irc.error('This command cannot be nested.', Raise=True) if msg.nick not in irc.state.channels[channel].users: self.log.warning('RelayLink: %s on %s attempted to view' ' nicks in %s without being in it.' % (msg.nick, irc.network, channel)) irc.error(('You are not in %s.' % channel), Raise=True) # Include the local channel for nicks output c = irc.state.channels[channel] totalUsers = len(c.users) totalChans = 1 users = [] for s in c.users: s = s.strip() if not s: continue if s in c.ops: users.append('@%s' % s) elif s in c.halfops: users.append('%%%s' % s) elif s in c.voices: users.append('+%s' % s) else: users.append(s) s = _('%d users in %s on %s: %s') % (totalUsers, channel, irc.network, utils.str.commaAndify(users)) if 'count' not in keys: irc.reply(s, private=True) for relay in self.relays: if relay.sourceChannel == channel and \ relay.sourceNetwork.lower() == irc.network.lower(): totalChans += 1 if not relay.hasTargetIRC: irc.reply(_('I haven\'t scraped the IRC object for %s ' 'yet. Try again in a minute or two.') % \ relay.targetNetwork) else: users = [] ops = [] halfops = [] voices = [] normals = [] numUsers = 0 target = relay.targetChannel channels = relay.targetIRC.state.channels found = False for key, channel_ in channels.items(): #if re.match(relay.targetChannel, key): if ircutils.toLower(relay.targetChannel) \ == ircutils.toLower(key): found = True break if not found: continue for s in channel_.users: s = s.strip() if not s: continue numUsers += 1 totalUsers += 1 if s in channel_.ops: users.append('@%s' % s) elif s in channel_.halfops: users.append('%%%s' % s) elif s in channel_.voices: users.append('+%s' % s) else: users.append(s) #utils.sortBy(ircutils.toLower, ops) #utils.sortBy(ircutils.toLower, halfops) #utils.sortBy(ircutils.toLower, voices) #utils.sortBy(ircutils.toLower, normals) users.sort() msg.tag('relayedMsg') s = _('%d users in %s on %s: %s') % (numUsers, relay.targetChannel, relay.targetNetwork, utils.str.commaAndify(users)) if 'count' not in keys: irc.reply(s, private=True) if not irc.nested: irc.reply("Total users across %d channels: %d. " % \ (totalChans, totalUsers), private=False if 'count' in keys else True) else: irc.reply(totalUsers) irc.noReply() nicks = wrap(nicks, ['Channel', getopts({'count':''})]) # The following functions handle configuration def _writeToConfig(self, from_, to, regexp, add): from_, to = from_.split('@'), to.split('@') args = from_ args.extend(to) args.append(regexp) s = ' | '.join(args) currentConfig = self.registryValue('relays') config = list(map(ircutils.IrcString, currentConfig.split(' || '))) if add: if s in config: return False if currentConfig == '': self.setRegistryValue('relays', value=s) else: self.setRegistryValue('relays', value=' || '.join((currentConfig, s))) else: if s not in config: return False config.remove(s) self.setRegistryValue('relays', value=' || '.join(config)) return True def _parseOptlist(self, irc, msg, tupleOptlist, batchadd=False): optlist = {} for key, value in tupleOptlist: optlist.update({key: value}) if not batchadd: if 'from' not in optlist and 'to' not in optlist: irc.error(_('You must give at least --from or --to.')) return for name in ('from', 'to'): if name not in optlist: optlist.update({name: '%s@%s' % (msg.args[0], irc.network)}) if 'reciprocal' in optlist: optlist.update({'reciprocal': True}) else: optlist.update({'reciprocal': False}) if not len(optlist['from'].split('@')) == 2: irc.error(_('--from should be like "--from #channel@network"')) return if not len(optlist['to'].split('@')) == 2: irc.error(_('--to should be like "--to #channel@network"')) return if 'regexp' not in optlist: optlist.update({'regexp': ''}) return optlist @internationalizeDocstring def add(self, irc, msg, args, optlist): """[--from <channel>@<network>] [--to <channel>@<network>] [--regexp <regexp>] [--reciprocal] Adds a relay to the list. You must give at least --from or --to; if one of them is not given, it defaults to the current channel@network. Only messages matching <regexp> will be relayed; if <regexp> is not given, everything is relayed. If --reciprocal is given, another relay will be added automatically, in the opposite direction.""" optlist = self._parseOptlist(irc, msg, optlist) if optlist is None: return failedWrites = 0 if not self._writeToConfig(optlist['from'], optlist['to'], optlist['regexp'], True): failedWrites += 1 if optlist['reciprocal']: if not self._writeToConfig(optlist['to'], optlist['from'], optlist['regexp'], True): failedWrites += 1 self._loadFromConfig() if failedWrites == 0: irc.replySuccess() else: irc.error(_('One (or more) relay(s) already exists and has not ' 'been added.')) add = wrap(add, [('checkCapability', 'admin'), getopts({'from': 'something', 'to': 'something', 'regexp': 'something', 'reciprocal': ''})]) def addall(self, irc, msg, args, optlist, channels): """[--regexp <regexp>] <channel1@network1> <channel2@network2> [<channel3@network3>] ... Batch adds all the relays/reciprocals between the channels defined. Useful if you are relaying to more than 2 networks/channels with one bot, as a large amount of reciprocals easily becomes a mess. Only messages matching <regexp> will be relayed; if <regexp> is not given, everything is relayed.""" optlist = self._parseOptlist(irc, msg, optlist, batchadd=True) channels = channels.split() if len(channels) < 2: irc.error('Not enough channels specified to relay! (needs at least 2)', Raise=True) if len(channels) > self.registryValue('addall.max'): irc.error('Too many channels specified, aborting. (see config plugins.RelayLink.addall.max)', Raise=True) for ch in channels: if len(ch.split("@")) != 2: irc.error("Channels must be specified in the format #channel@network", Raise=True) failedWrites = writes = 0 # Get all the channel combinations and try to add them one by one p = itertools.permutations(channels, 2) for c in p: if not self._writeToConfig(c[0], c[1], optlist['regexp'], True): failedWrites += 1 if self.registryValue('logFailedChanges'): self.log.warning("RelayLink: failed to batch add relay: {} -> {}".format(c[0],c[1])) writes += 1 self._loadFromConfig() if failedWrites == 0: irc.replySuccess() else: irc.reply('Finished, though {} out of {} relays failed to be added.'.format(failedWrites, writes)) addall = wrap(addall, [('checkCapability', 'admin'), getopts({'regexp': 'something'}), 'text']) def removeall(self, irc, msg, args, optlist, channels): """[--regexp <regexp>] <channel1@network1> [<channel2@network2>] [<channel3@network3>] ... Batch removes relays. If only one channel@network is given, removes all relays going to and from the channel. Otherwise, removes all relays going between the channels defined (similar to addall).""" optlist = self._parseOptlist(irc, msg, optlist, batchadd=True) channels = channels.split() if len(channels) > self.registryValue('addall.max'): irc.error('Too many channels specified, aborting. (see config plugins.RelayLink.addall.max)', Raise=True) failedWrites = writes = 0 for ch in channels: if len(ch.split("@")) != 2: irc.error("Channels must be specified in the format #channel@network", Raise=True) if len(channels) == 1: c = tuple(channels[0].split('@')) for relay in self.relays: # semi-hack channel matching; not sure if there's a better way to do this if c[0] == relay.sourceChannel and c[1] == relay.sourceNetwork: s = "%s@%s" % (relay.targetChannel, relay.targetNetwork) if not self._writeToConfig(channels[0], s, optlist['regexp'], False): # This shouldn't really ever error, but we'll keep it just in case failedWrites += 1 if self.registryValue('logFailedChanges'): self.log.warning("RelayLink: failed to batch remove relay: {} -> {}".format(c[0],c[1])) writes += 1 elif c[0] == relay.targetChannel and c[1] == relay.targetNetwork: s = "%s@%s" % (relay.sourceChannel, relay.sourceNetwork) if not self._writeToConfig(s, channels[0], optlist['regexp'], False): failedWrites += 1 if self.registryValue('logFailedChanges'): self.log.warning("RelayLink: failed to batch remove relay: {} -> {}".format(c[0],c[1])) writes += 1 if writes == 0: irc.error("No matching relays for %s found." % channels[0], Raise=True) elif len(channels) >= 2: # Get all the channel combinations and try to remove them one by one p = itertools.permutations(channels, 2) for c in p: if not self._writeToConfig(c[0], c[1], optlist['regexp'], False): failedWrites += 1 if self.registryValue('logFailedChanges'): self.log.warning("RelayLink: failed to batch remove relay: {} -> {}".format(c[0],c[1])) writes += 1 self._loadFromConfig() if failedWrites == 0: irc.replySuccess() else: irc.reply('Finished, though {} out of {} relays failed to be removed.'.format(failedWrites, writes)) removeall = wrap(removeall, [('checkCapability', 'admin'), getopts({'regexp': 'something'}), 'text']) @internationalizeDocstring def remove(self, irc, msg, args, optlist): """[--from <channel>@<network>] [--to <channel>@<network>] [--regexp <regexp>] [--reciprocal] Remove a relay from the list. You must give at least --from or --to; if one of them is not given, it defaults to the current channel@network. Only messages matching <regexp> will be relayed; if <regexp> is not given, everything is relayed. If --reciprocal is given, another relay will be removed automatically, in the opposite direction.""" optlist = self._parseOptlist(irc, msg, optlist) if optlist is None: return failedWrites = 0 if not self._writeToConfig(optlist['from'], optlist['to'], optlist['regexp'], False): failedWrites += 1 if optlist['reciprocal']: if not self._writeToConfig(optlist['to'], optlist['from'], optlist['regexp'], False): failedWrites +=1 self._loadFromConfig() if failedWrites == 0: irc.replySuccess() else: irc.error(_('One (or more) relay(s) did not exist and has not ' 'been removed.')) remove = wrap(remove, [('checkCapability', 'admin'), getopts({'from': 'something', 'to': 'something', 'regexp': 'something', 'reciprocal': ''})]) def _getSubstitutes(self): # Get a list of strings substitutes = self.registryValue('substitutes').split(' || ') if substitutes == ['']: return {} # Convert it to a list of tuples substitutes = [tuple(x.split(' | ')) for x in substitutes] # Finally, make a dictionnary substitutes = dict(substitutes) return substitutes def _setSubstitutes(self, substitutes): # Get a list of tuples from the dictionnary substitutes = substitutes.items() # Make it a list of strings substitutes = ['%s | %s' % (x,y) for x,y in substitutes] # Finally, get a string substitutes = ' || '.join(substitutes) self.setRegistryValue('substitutes', value=substitutes) @internationalizeDocstring def substitute(self, irc, msg, args, regexp, to): """<regexp> <replacement> Replaces all nicks that matches the <regexp> by the <replacement> string.""" substitutes = self._getSubstitutes() # Don't check if it is already in the config: if will be overriden # automatically and that is a good thing. substitutes.update({regexp: to}) self._setSubstitutes(substitutes) self._loadFromConfig() irc.replySuccess() substitute = wrap(substitute, [('checkCapability', 'admin'), 'something', 'text']) @internationalizeDocstring def nosubstitute(self, irc, msg, args, regexp): """<regexp> Undo a substitution.""" substitutes = self._getSubstitutes() if regexp not in substitutes: irc.error(_('This regexp was not in the nick substitutions ' 'database')) return # Don't check if it is already in the config: if will be overriden # automatically and that is a good thing. substitutes.pop(regexp) self._setSubstitutes(substitutes) self._loadFromConfig() irc.replySuccess() nosubstitute = wrap(nosubstitute, [('checkCapability', 'admin'), 'something']) def rpm(self, irc, msg, args, remoteuser, otherIrc, text): """<remoteUser> <network> <text> Sends a private message to a user on a remote network.""" found = found2 = False if not self.registryValue("remotepm.enable"): irc.error("This command is not enabled; please set 'config plugins.relaylink.remotepm.enable' " "to True.", Raise=True) for relay in self.relays: channels = otherIrc.state.channels for key, channel_ in channels.items(): if ircutils.toLower(relay.targetChannel) \ == ircutils.toLower(key) and remoteuser in channel_.users: found = True break for ch in irc.state.channels: if ircutils.toLower(relay.sourceChannel) == \ ircutils.toLower(ch) and msg.nick in irc.state.channels[ch].users: found2 = True break if found and found2: prefix = msg.prefix if self.registryValue("remotepm.useHostmasks") else msg.nick if self.registryValue("remotepm.useNotice"): otherIrc.queueMsg(ircmsgs.notice(remoteuser, "Message from %s on %s: %s" % (prefix, irc.network, text))) else: otherIrc.queueMsg(ircmsgs.privmsg(remoteuser, "Message from %s on %s: %s" % (prefix, irc.network, text))) else: irc.error("User '%s' does not exist on %s or you are not sharing " "a channel with them." % (remoteuser, otherIrc.network), Raise=True) rpm = wrap(rpm, ['nick', ('networkIrc', True), 'text'])
class Greet(callbacks.Plugin): def __init__(self, irc): self.__parent = super(Greet, self) self.__parent.__init__(irc) self.db = GreetDB(filename) world.flushers.append(self.db.flush) self.lastParts = plugins.ChannelUserDictionary() splitTimeout = conf.supybot.plugins.Greet.throttle.afterSplit self.splitters = TimeoutQueue(splitTimeout) self.lastGreet = plugins.ChannelUserDictionary() def die(self): if self.db.flush in world.flushers: world.flushers.remove(self.db.flush) self.db.close() self.__parent.die() def doQuit(self, irc, msg): # We want to observe netsplits and keep from greeting users rejoining # after one. if ircmsgs.isSplit(msg): self.splitters.enqueue(msg.nick) try: id = ircdb.users.getUserId(msg.prefix) self.splitters.enqueue(id) except KeyError: pass def doJoin(self, irc, msg): if ircutils.strEqual(irc.nick, msg.nick): return # It's us. if msg.nick in self.splitters: self.log.debug('Not greeting %s, recent split.', msg.nick) return # Recently split. channel = msg.args[0] irc = callbacks.SimpleProxy(irc, msg) if self.registryValue('greeting', channel): try: id = ircdb.users.getUserId(msg.prefix) if id in self.splitters: self.log.debug('Not greeting id #%s, recent split.', id) return greet = self.db[channel, id] except KeyError: default = self.registryValue('default', channel) if default: default = ircutils.standardSubstitute(irc, msg, default) msgmaker = ircmsgs.privmsg if self.registryValue('default.notice', channel): msgmaker = ircmsgs.notice target = msg.nick if self.registryValue('default.public', channel): target = channel irc.queueMsg(msgmaker(target, default)) return now = time.time() throttle = self.registryValue('throttle', channel) if now - self.lastGreet.get((channel, id), 0) > throttle: if (channel, id) in self.lastParts: i = self.registryValue('throttle.afterPart', channel) if now - self.lastParts[channel, id] < i: return self.lastGreet[channel, id] = now greet = ircutils.standardSubstitute(irc, msg, greet) if re.search(r"/me ", greet, re.I): greet = greet.replace("/me ","") irc.reply(greet, prefixNick=False, action=True) else: irc.reply(greet, prefixNick=True) def doPart(self, irc, msg): try: id = self._getId(irc, msg.prefix) self.lastParts[msg.args[0], id] = time.time() except KeyError: pass def _getId(self, irc, userNickHostmask): try: id = ircdb.users.getUserId(userNickHostmask) except KeyError: if not ircutils.isUserHostmask(userNickHostmask): hostmask = irc.state.nickToHostmask(userNickHostmask) id = ircdb.users.getUserId(hostmask) else: raise KeyError return id def default(self, irc, msg, args, channel, optlist, text): """[<channel>] [--remove|<msg>] If <msg> is given, sets the default greet to <msg>. A <msg> of "" will remove the default greet. If <msg> is not given, returns the current default greet. <channel> is only necessary if the message isn't sent in the channel itself. """ if optlist and text: raise callbacks.ArgumentError for (option, _) in optlist: if option == 'remove': self.setRegistryValue('default', '', channel) irc.replySuccess() return if text: self.setRegistryValue('default', text, channel) irc.replySuccess() else: resp = self.registryValue('default', channel) or \ 'I do not have a default greet set for %s.' % channel irc.reply(resp) default = wrap(default, ['channel', getopts({'remove': ''}), additional('text')]) def get(self, irc, msg, args, channel, user): """[<channel>] [<user|nick>] Returns the current greet message for <user> (or the user <nick|hostmask> is currently identified or recognized as). If <user> is not given, defaults to the user giving the command. <channel> is only necessary if the message isn't sent in the channel itself. """ try: greet = self.db[channel, user.id] irc.reply(greet) except KeyError: irc.error('I have no greet for %s.' % user.name) get = wrap(get, ['channel', first('otherUser', 'user')]) def _preCheck(self, irc, msg, user): capability = self.registryValue('requireCapability') if capability: try: u = ircdb.users.getUser(msg.prefix) except KeyError: irc.errorNotRegistered(Raise=True) else: if u != user: if not ircdb.checkCapability(msg.prefix, capability): irc.errorNoCapability(capability, Raise=True) # I chose not to make <user|nick> optional in this command because # if it's not a valid username (e.g., if the user tyops and misspells a # username), it may be nice not to clobber the user's greet. def greet(self, irc, msg, args, channel, user, greet): """[<channel>] <user|nick> <msg> Sets the greet message for <user> (or the user <nick|hostmask> is currently identified or recognized as) to <msg>. <channel> is only necessary if the message isn't sent in the channel itself. """ self._preCheck(irc, msg, user) self.db[channel, user.id] = greet irc.replySuccess() greet = wrap(greet, ['channel', 'otherUser', 'text']) def remove(self, irc, msg, args, channel, user): """[<channel>] [<user|nick>] Removes the greet message set for <user>, or the user <nick|hostmask> is currently identified or recognized as. If <user> is not given, defaults to the user giving the command. <channel> is only necessary if the message isn't sent in the channel itself. """ self._preCheck(irc, msg, user) try: del self.db[channel, user.id] irc.replySuccess() except KeyError: irc.error('I have no greet for that user.') remove = wrap(remove, ['channel', first('otherUser', 'user')]) def change(self, irc, msg, args, channel, user, changer): """[<channel>] [<user|nick>] <regexp> Changes the greet message for <user>, or the user <nick|hostmask> is currently identified or recognized as, according to <regexp>. If <user> is not given, defaults to the calling user. <channel> is only necessary if the message isn't sent in the channel itself. """ self._preCheck(irc, msg, user) s = self.db[channel, user.id] newS = changer(s) self.db[channel, user.id] = newS irc.replySuccess() change = wrap(change, ['channel', first('otherUser', 'user'), 'regexpReplacer'])
class Randomness(callbacks.Plugin): """This plugin contains commands for my own personal use.""" threaded = True def __init__(self, irc): self.__parent = super(Randomness, self) self.__parent.__init__(irc) self.dotCounter = TimeoutQueue(10) # The code below contains automatic replies then turned on. Since this # is a mostly personal plugin, they will only activate on certain # predefined networks. def _attack(self, target): throws = ['poorly written code', 'knives', "Te"+"chman", 'various objects', 'rocks', "Techm"+"ango", 'grenades', "IE6", 'axes', 'evil things', 'hammers', 'Unicode', 'spears', 'spikes', 'sharp things', 'whips', 'moldy bread', "j4j"+"ackj", 'netsplits', "mojibake", "floppy disks"] spells = ['fire', 'ice', 'death', '\x02DEATH\x02', 'poison', 'stupid'] attacks = throws + spells + ['bricks', 'knives', "idiots from #freenode", "her army of trolls", "her ~~godly~~ oper powers", "GNOME 3", 'confusingly bad english', "Windows Me", "gbyers' immaturity", "Quicktime for Windows", "\x0309,01-Oblivi\x020\x02n Script by mIRCsKripterz-\x03", "brendi hi"+"tler blossom", "segmentation faults", "???", "relentless spyware", "nsa technology"] throws += ['nails', 'planets', 'thorns', 'skulls', "a fresh, unboxed copy of Windows Me"] n = random.random() if n >= 0.82: return 'casts %s at %s'%(random.choice(spells), target) elif n >= 0.76: return 'drops the bass on %s'%target elif n >= 0.72: return 'fites %s'%target elif n >= 0.48: return 'attacks %s with %s'%(target, random.choice(attacks)) else: return 'throws %s at %s'%(random.choice(throws),target) def doPrivmsg(self, irc, msg): if ircutils.isChannel(msg.args[0]) and self.registryValue("enable", msg.args[0]): dots = "." * random.randint(0,10) # added emphasis............... ow = "ow"+("w"*random.randint(0,4)) volatile = ("kicks ", "stabs ", "fites ", "bans ", "ddas ", "packets ", "beats ") exclaim = (("!" * random.randint(1,5)) + ("1" * random.randint(0,2))) * \ random.randint(1,2) + ("!" * random.randint(-1,5)) gemotes = ["xD", "=']", "\\o/", ":"+"3"*random.randint(1,4), "^_^"] meh = (";/", ":\\", ":/") bemotes = meh + (":(", ":|", '-_-') semotes = (":<", ";_;", ";-;", "D:", ">:", "x(") if irc.network.lower() == "overdrive-irc": if "fishbot" in irc.state.channels[msg.args[0]].users: hurtresponses = [ow, random.choice(semotes), ow+random.choice(semotes), "RIP", "i cry", "ouch", "what was that for "+random.choice(semotes), "!voteban "+msg.nick, "PLS", "rood", "owowowowow", "omg "+random.choice(semotes), "hey, bots have feelings too!"+random.choice(semotes), "wtf", "watch it!", "wow"] if re.match(r"^\x01ACTION ((attacks|stabs) {n} with |" r"(drops|throws|casts|thwacks) (.*? (at|on|with) " r"{n}|{n} (at|on|with) .*?)|fites {n}).*?\x01$".\ format(n=irc.nick), msg.args[1].lower(), re.I): sleep(0.4) n = random.random() if n >= 0.45: irc.queueMsg(ircmsgs.action(msg.args[0], self._attack(msg.nick))) else: irc.queueMsg(ircmsgs.privmsg(msg.args[0], random.choice(hurtresponses))) if "wow" in irc.state.channels[msg.args[0]].ops and \ ircutils.stripFormatting(msg.args[1].lower().split()[0]) == "wow": wowResponses1 = ["what is it", "hi %s%s" % (msg.nick, dots), "o/", "HI %s%s" % (msg.nick.upper(), dots), "go away "+random.choice(bemotes), "FFS "+random.choice(bemotes), "ffs i'm trying to work", "WHAT DO YOU WANT", "leave me alone "+random.choice(bemotes), "hello, you've reached 'wow'. " "If you actually need to talk to me, " "press 1. if not, PISS OFF!", "stop highlighting me" + dots, "reproted to fbi for harassment" + dots, "-_-", msg.nick + " pls", "need something?", "r u mad", "ur made", "fml", "?", ".", "meh "+random.choice(meh), "/join 0", "/part SCREW U GUYS IM LEAVING AND NEVER COMING " "BACK AGAIN!! IT'S ALL %s'S FAULT I FKN HATE " "YOU ALL \x02</3" % msg.nick.upper(), "stop highlighting me!", "\x02%s\x02 added to ignore list." % msg.nick, "!votekline " + msg.nick] n = random.randint(0, 91) if n >= 60: irc.queueMsg(ircmsgs.privmsg("BotServ", "say {} {}".format(msg.args[0],random.choice(wowResponses1)))) elif n >= 50: irc.queueMsg(ircmsgs.privmsg("BotServ", "act {} {}".format(msg.args[0],random.choice(volatile)+msg.nick))) if ircutils.stripFormatting(msg.args[1]) == ".": dotresponses = ["r u mad?", "lol r u mad", "mmm dots", ",", "no spam pls" + dots, ":D", "ok"] if len(self.dotCounter) >= 2: r = random.random() if r >= 0.5: irc.queueMsg(ircmsgs.privmsg(msg.args[0], random.choice(dotresponses))) else: self.dotCounter.enqueue([0]) elif ircutils.stripFormatting(msg.args[1]) == "ok": okresponses = ["not ok", "ok", "ko", "okay*", "O.K.", "^why does everyone say that ._.", "\x01ACTION ok's %s\x01" % msg.nick, "no", "Objection! \x02Not\x02 okay!", "meh "+random.choice(meh), "yeah ok w/e man.", "\x01ACTION sighs\x01", "you're pretty ok.", "hmph", "I AGREE WITH YOU, "+msg.nick+dots] r = random.randint(1, 23) if r >= 19: irc.queueMsg(ircmsgs.action(msg.args[0], random.choice(volatile)+msg.nick)) elif r >= 7: irc.queueMsg(ircmsgs.privmsg(msg.args[0], random.choice(okresponses))) if irc.network.lower() in ("overdrive-irc", "stripechat") and \ "hitl"+"er blossom" in ircutils.stripFormatting(msg.args[1].lower()): irc.queueMsg(ircmsgs.privmsg(msg.args[0], msg.nick + ": the entire topic changes" + exclaim)) # if irc.network.lower() == "stripechat": # r = random.random() # if msg.args[1].lower().startswith("&topic") and "hackinbot" in msg.args[1].lower() \ # and r >= 0.3: # irc.queueMsg(ircmsgs.privmsg(msg.args[0], "OH, hackinbot! " + random.choice(gemotes))) def attack(self, irc, msg, args, user): """<nick> Attacks <nick>.""" irc.reply(self._attack(user), action=True) attack = wrap(attack, ['text'])
def __init__(self, irc): self.__parent = super(Randomness, self) self.__parent.__init__(irc) self.dotCounter = TimeoutQueue(10)
class Bitbucket(callbacks.PluginRegexp): """Add the help for "@plugin help Bitbucket" here This should describe *how* to use this plugin.""" threaded = True callBefore = ['URL', 'Web'] unaddressedRegexps = ['snarfPullRequest'] def __init__(self, irc): self.__parent = super(Bitbucket, self) self.__parent.__init__(irc) self.timeout_queue = TimeoutQueue(self.registryValue('snarferTimeout')) def _getResponse(self, id): queryurl = 'https://bitbucket.org/api/2.0/repositories/{0}/{1}/pullrequests/{2}'.format( self.registryValue('accountname'),self.registryValue('repo_slug'),id) r = requests.get(queryurl) self.log.info('Getting pull request from %s' % queryurl) if r.status_code != requests.codes.ok: return "pull-request not found: #" + str(id) return str(PullRequest(self,r.json())) def _check_timeout(self, id): if id in self.timeout_queue: return False self.timeout_queue.enqueue(id) return True def open(self, irc, msg, args): """ List open pull request.""" queryurl = 'https://bitbucket.org/api/2.0/repositories/{0}/{1}/pullrequests'.format( self.registryValue('accountname'),self.registryValue('repo_slug')) r = requests.get(queryurl) self.log.info('Getting pull request from %s' % queryurl) if r.status_code != requests.codes.ok: irc.reply("Url not found: " + queryurl) return data = r.json() if 'values' in data: for entry in data['values']: irc.reply(str(PullRequest(self,entry)), prefixNick=False) def snarfPullRequest(self, irc, msg, match): r"""(?P<type>pull request|pull-request|pullrequest)[\s#]*(?P<id>\d+)""" channel = msg.args[0] #if not self.registryValue('bugSnarfer', channel): return id_matches = match.group('id').split() type = match.group('type') self.log.debug('Snarfed pull request ID(s): ' + ' '.join(id_matches)) # Check if the bug has been already snarfed in the last X seconds msgs = [] for id in id_matches: if not self._check_timeout(id): continue response = self._getResponse(id) if response: msgs.append(response) for msg in msgs: irc.reply(msg, prefixNick=False)