mirror of
https://github.com/jlu5/SupyPlugins.git
synced 2025-04-30 15:31:11 -05:00
RelayNext: cleanup, switch to using new msg.tagged('channels') - Closes #40.
- Drop _getAllRelaysForNetwork() and custom state keeping code, as they aren't needed anymore - Hopefully closes #41? (we may never know) - Also, don't lowercase command names in flood prevention announcements: "PRIVMSGs" looks more correct than "privmsgs" IMO
This commit is contained in:
parent
93e558d17c
commit
867e61ea78
@ -80,14 +80,6 @@ class RelayNext(callbacks.Plugin):
|
|||||||
def __init__(self, irc):
|
def __init__(self, irc):
|
||||||
self.__parent = super(RelayNext, self)
|
self.__parent = super(RelayNext, self)
|
||||||
self.__parent.__init__(irc)
|
self.__parent.__init__(irc)
|
||||||
# This part is partly taken from the Relay plugin, and is used to
|
|
||||||
# keep track of quitting users. Since quit messages aren't
|
|
||||||
# channel-specific, we need to keep a cached copy of our IRC state
|
|
||||||
# file and look at that to find whether we should send a quit
|
|
||||||
# message through the relay. The conventional check for
|
|
||||||
# 'irc.state.channels[channel]' won't work either,
|
|
||||||
# because the user in question has already quit.
|
|
||||||
self.ircstates = {}
|
|
||||||
|
|
||||||
# This part facilitates flood protection
|
# This part facilitates flood protection
|
||||||
self.msgcounters = {}
|
self.msgcounters = {}
|
||||||
@ -95,11 +87,10 @@ class RelayNext(callbacks.Plugin):
|
|||||||
|
|
||||||
self.db = {}
|
self.db = {}
|
||||||
self.loadDB()
|
self.loadDB()
|
||||||
|
|
||||||
|
# Add our database exporter to world.flushers, so it's automatically
|
||||||
|
# ran on every flush cycle.
|
||||||
world.flushers.append(self.exportDB)
|
world.flushers.append(self.exportDB)
|
||||||
if irc.afterConnect:
|
|
||||||
for channel in self._getAllRelaysForNetwork(irc):
|
|
||||||
# irc.queueMsg(ircmsgs.who(channel))
|
|
||||||
irc.queueMsg(ircmsgs.names(channel))
|
|
||||||
|
|
||||||
def die(self):
|
def die(self):
|
||||||
self.exportDB()
|
self.exportDB()
|
||||||
@ -109,29 +100,20 @@ class RelayNext(callbacks.Plugin):
|
|||||||
### Relayer core
|
### Relayer core
|
||||||
|
|
||||||
def simpleHash(self, s):
|
def simpleHash(self, s):
|
||||||
"""<text>
|
"""
|
||||||
|
Returns a colorized version of the given text based on a simple hash algorithm
|
||||||
Returns a colorized version of <text> based on a simple hash algorithm
|
(sum of all characters).
|
||||||
(sum of all characters)."""
|
"""
|
||||||
colors = ('02', '03', '04', '05', '06', '07', '08', '09', '10', '11',
|
colors = ('02', '03', '04', '05', '06', '07', '08', '09', '10', '11',
|
||||||
'12', '13')
|
'12', '13')
|
||||||
num = sum([ord(char) for char in s])
|
num = sum([ord(char) for char in s])
|
||||||
num = num % len(colors)
|
num = num % len(colors)
|
||||||
return "\x03%s%s\x03" % (colors[num], s)
|
return "\x03%s%s\x03" % (colors[num], s)
|
||||||
|
|
||||||
def _getAllRelaysForNetwork(self, irc):
|
|
||||||
"""Returns all the relays a network is involved with."""
|
|
||||||
network = irc.network.lower()
|
|
||||||
results = []
|
|
||||||
for relay in self.db.values():
|
|
||||||
for cn in relay:
|
|
||||||
cn = cn.split("@")
|
|
||||||
if cn[1] == network:
|
|
||||||
results.append(cn[0])
|
|
||||||
self.log.debug('RelayNext: got %r for _getAllRelaysForNetwork for %r', results, network)
|
|
||||||
return results
|
|
||||||
|
|
||||||
def _format(self, irc, msg, channel, announcement=False):
|
def _format(self, irc, msg, channel, announcement=False):
|
||||||
|
"""
|
||||||
|
Formats a relay given the IRC object, msg object, and channel.
|
||||||
|
"""
|
||||||
s = ''
|
s = ''
|
||||||
nick = msg.nick
|
nick = msg.nick
|
||||||
userhost = ''
|
userhost = ''
|
||||||
@ -143,15 +125,20 @@ class RelayNext(callbacks.Plugin):
|
|||||||
if color:
|
if color:
|
||||||
nick = self.simpleHash(nick)
|
nick = self.simpleHash(nick)
|
||||||
netname = self.simpleHash(netname)
|
netname = self.simpleHash(netname)
|
||||||
|
|
||||||
|
# Attempt to mitigate highlights (for some clients) by adding
|
||||||
|
# a hyphen in front of the nick.
|
||||||
if noHighlight:
|
if noHighlight:
|
||||||
nick = '-' + nick
|
nick = '-' + nick
|
||||||
|
|
||||||
# Skip hostmask checking if the sender is a server
|
# Skip hostmask checking if the sender is a server
|
||||||
# ('.') present in name
|
# (i.e. a '.' is present in their name)
|
||||||
if useHostmask and '.' not in nick:
|
if useHostmask and '.' not in nick:
|
||||||
try:
|
try:
|
||||||
userhost = ' (%s)' % msg.prefix.split('!', 1)[1]
|
userhost = ' (%s)' % msg.prefix.split('!', 1)[1]
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if announcement:
|
if announcement:
|
||||||
# Announcements use a special syntax
|
# Announcements use a special syntax
|
||||||
s = '*** %s' % announcement
|
s = '*** %s' % announcement
|
||||||
@ -161,6 +148,7 @@ class RelayNext(callbacks.Plugin):
|
|||||||
if color:
|
if color:
|
||||||
newnick = self.simpleHash(newnick)
|
newnick = self.simpleHash(newnick)
|
||||||
s = '- %s is now known as %s' % (nick, newnick)
|
s = '- %s is now known as %s' % (nick, newnick)
|
||||||
|
|
||||||
elif msg.command == 'PRIVMSG':
|
elif msg.command == 'PRIVMSG':
|
||||||
text = msg.args[1]
|
text = msg.args[1]
|
||||||
if re.match('^\x01ACTION .*\x01$', text):
|
if re.match('^\x01ACTION .*\x01$', text):
|
||||||
@ -168,24 +156,31 @@ class RelayNext(callbacks.Plugin):
|
|||||||
s = '* %s %s' % (nick, text)
|
s = '* %s %s' % (nick, text)
|
||||||
else:
|
else:
|
||||||
s = '<%s> %s' % (nick, msg.args[1])
|
s = '<%s> %s' % (nick, msg.args[1])
|
||||||
|
|
||||||
elif msg.command == 'JOIN':
|
elif msg.command == 'JOIN':
|
||||||
s = '- %s%s has joined %s' % (nick, userhost, channel)
|
s = '- %s%s has joined %s' % (nick, userhost, channel)
|
||||||
|
|
||||||
elif msg.command == 'PART':
|
elif msg.command == 'PART':
|
||||||
# Part message isn't a required field and can be empty sometimes
|
# Part message isn't a required field and can be empty
|
||||||
try:
|
try:
|
||||||
partmsg = ' (%s)' % msg.args[1]
|
partmsg = ' (%s)' % msg.args[1]
|
||||||
except:
|
except:
|
||||||
partmsg = ''
|
partmsg = ''
|
||||||
s = '- %s%s has left %s%s' % (nick, userhost, channel, partmsg)
|
s = '- %s%s has left %s%s' % (nick, userhost, channel, partmsg)
|
||||||
|
|
||||||
elif msg.command == 'QUIT':
|
elif msg.command == 'QUIT':
|
||||||
s = '- %s has quit (%s)' % (nick, msg.args[0])
|
s = '- %s has quit (%s)' % (nick, msg.args[0])
|
||||||
|
|
||||||
elif msg.command == 'MODE':
|
elif msg.command == 'MODE':
|
||||||
modes = ' '.join(msg.args[1:])
|
modes = ' '.join(msg.args[1:])
|
||||||
s = '- %s%s set mode %s on %s' % (nick, userhost, modes, channel)
|
s = '- %s%s set mode %s on %s' % (nick, userhost, modes, channel)
|
||||||
|
|
||||||
elif msg.command == 'TOPIC':
|
elif msg.command == 'TOPIC':
|
||||||
s = '- %s set topic on %s to: %s' % (nick, channel, msg.args[1])
|
s = '- %s set topic on %s to: %s' % (nick, channel, msg.args[1])
|
||||||
|
|
||||||
elif msg.command == 'KICK':
|
elif msg.command == 'KICK':
|
||||||
kicked = msg.args[1]
|
kicked = msg.args[1]
|
||||||
|
# Show the host of the kicked user, not the kicker
|
||||||
userhost = irc.state.nickToHostmask(kicked).split('!', 1)[1]
|
userhost = irc.state.nickToHostmask(kicked).split('!', 1)[1]
|
||||||
if color:
|
if color:
|
||||||
kicked = self.simpleHash(kicked)
|
kicked = self.simpleHash(kicked)
|
||||||
@ -199,12 +194,7 @@ class RelayNext(callbacks.Plugin):
|
|||||||
s = s.replace("- -", "-", 1)
|
s = s.replace("- -", "-", 1)
|
||||||
return s
|
return s
|
||||||
|
|
||||||
def keepstate(self):
|
|
||||||
for irc in world.ircs:
|
|
||||||
self.ircstates[irc.network] = deepcopy(irc.state.channels)
|
|
||||||
|
|
||||||
def relay(self, irc, msg, channel=None):
|
def relay(self, irc, msg, channel=None):
|
||||||
self.keepstate()
|
|
||||||
channel = (channel or msg.args[0]).lower()
|
channel = (channel or msg.args[0]).lower()
|
||||||
|
|
||||||
# Check for ignored events first
|
# Check for ignored events first
|
||||||
@ -229,32 +219,57 @@ class RelayNext(callbacks.Plugin):
|
|||||||
self.log.debug("RelayNext: found targets %s for relay %s", targets, relay)
|
self.log.debug("RelayNext: found targets %s for relay %s", targets, relay)
|
||||||
|
|
||||||
if self.registryValue("antiflood.enable", channel):
|
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)
|
timeout = self.registryValue("antiflood.timeout", channel)
|
||||||
seconds = self.registryValue("antiflood.seconds", 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)
|
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:
|
try:
|
||||||
self.msgcounters[(source, msg.command)].enqueue(msg.prefix)
|
self.msgcounters[(source, msg.command)].enqueue(msg.prefix)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
self.msgcounters[(source, msg.command)] = TimeoutQueue(seconds)
|
self.msgcounters[(source, msg.command)] = TimeoutQueue(seconds)
|
||||||
|
|
||||||
|
# Two different limits: one for messages and one for all others
|
||||||
if msg.command == "PRIVMSG":
|
if msg.command == "PRIVMSG":
|
||||||
maximum = self.registryValue("antiflood.maximum", channel)
|
maximum = self.registryValue("antiflood.maximum", channel)
|
||||||
else:
|
else:
|
||||||
maximum = self.registryValue("antiflood.maximum.nonPrivmsgs", channel)
|
maximum = self.registryValue("antiflood.maximum.nonPrivmsgs",
|
||||||
|
channel)
|
||||||
|
|
||||||
if len(self.msgcounters[(source, msg.command)]) > maximum:
|
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 "
|
self.log.debug("RelayNext (%s): message from %s blocked by "
|
||||||
"flood protection.", irc.network, channel)
|
"flood protection.", irc.network, channel)
|
||||||
|
|
||||||
if self.floodTriggered.get((source, msg.command)):
|
if self.floodTriggered.get((source, msg.command)):
|
||||||
|
# However, only send the announcement once.
|
||||||
return
|
return
|
||||||
c = msg.command.lower()
|
|
||||||
|
c = msg.command
|
||||||
e = format("Flood detected on %s (%s %ss/%s seconds), "
|
e = format("Flood detected on %s (%s %ss/%s seconds), "
|
||||||
"not relaying %ss for %s seconds!", channel,
|
"not relaying %ss for %s seconds!", channel,
|
||||||
maximum, c, seconds, c, timeout)
|
maximum, c, seconds, c, timeout)
|
||||||
out_s = self._format(irc, msg, channel, announcement=e)
|
out_s = self._format(irc, msg, channel, announcement=e)
|
||||||
|
|
||||||
self.floodTriggered[(source, msg.command)] = True
|
self.floodTriggered[(source, msg.command)] = True
|
||||||
self.log.info("RelayNext (%s): %s", irc.network, e)
|
self.log.info("RelayNext (%s): %s", irc.network, e)
|
||||||
else:
|
else:
|
||||||
self.floodTriggered[(source, msg.command)] = False
|
self.floodTriggered[(source, msg.command)] = False
|
||||||
|
|
||||||
for cn in targets:
|
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("@")
|
target, net = cn.split("@")
|
||||||
otherIrc = world.getIrc(net)
|
otherIrc = world.getIrc(net)
|
||||||
if otherIrc is None:
|
if otherIrc is None:
|
||||||
@ -271,6 +286,9 @@ class RelayNext(callbacks.Plugin):
|
|||||||
"channel!", target, net)
|
"channel!", target, net)
|
||||||
else:
|
else:
|
||||||
out_msg = ircmsgs.privmsg(target, out_s)
|
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')
|
out_msg.tag('relayedMsg')
|
||||||
otherIrc.queueMsg(out_msg)
|
otherIrc.queueMsg(out_msg)
|
||||||
|
|
||||||
@ -286,17 +304,13 @@ class RelayNext(callbacks.Plugin):
|
|||||||
# NICK and QUIT aren't channel specific, so they require a bit
|
# NICK and QUIT aren't channel specific, so they require a bit
|
||||||
# of extra handling
|
# of extra handling
|
||||||
def doNick(self, irc, msg):
|
def doNick(self, irc, msg):
|
||||||
newnick = msg.args[0]
|
for channel in msg.tagged('channels'):
|
||||||
for channel in self._getAllRelaysForNetwork(irc):
|
if self.registryValue("events.relaynicks", channel):
|
||||||
if self.registryValue("events.relaynicks", channel) and \
|
|
||||||
newnick in irc.state.channels[channel].users:
|
|
||||||
self.relay(irc, msg, channel=channel)
|
self.relay(irc, msg, channel=channel)
|
||||||
|
|
||||||
def doQuit(self, irc, msg):
|
def doQuit(self, irc, msg):
|
||||||
for channel in self._getAllRelaysForNetwork(irc):
|
for channel in msg.tagged('channels'):
|
||||||
if self.registryValue("events.relayquits", channel) and \
|
if self.registryValue("events.relayquits", channel):
|
||||||
(msg.nick in self.ircstates[irc.network][channel].users \
|
|
||||||
or msg.nick == irc.nick):
|
|
||||||
self.relay(irc, msg, channel=channel)
|
self.relay(irc, msg, channel=channel)
|
||||||
|
|
||||||
def outFilter(self, irc, msg):
|
def outFilter(self, irc, msg):
|
||||||
@ -304,10 +318,9 @@ class RelayNext(callbacks.Plugin):
|
|||||||
# useful because Supybot is often a multi-purpose bot!)
|
# useful because Supybot is often a multi-purpose bot!)
|
||||||
try:
|
try:
|
||||||
if msg.command == 'PRIVMSG' and not msg.relayedMsg:
|
if msg.command == 'PRIVMSG' and not msg.relayedMsg:
|
||||||
if msg.args[0].lower() in self._getAllRelaysForNetwork(irc):
|
new_msg = deepcopy(msg)
|
||||||
new_msg = deepcopy(msg)
|
new_msg.nick = irc.nick
|
||||||
new_msg.nick = irc.nick
|
self.relay(irc, new_msg, channel=msg.args[0])
|
||||||
self.relay(irc, new_msg, channel=msg.args[0])
|
|
||||||
except Exception:
|
except Exception:
|
||||||
# We want to log errors, but not block the bot's output
|
# We want to log errors, but not block the bot's output
|
||||||
log.exception("RelayNext: Caught error in outFilter:")
|
log.exception("RelayNext: Caught error in outFilter:")
|
||||||
@ -323,12 +336,15 @@ class RelayNext(callbacks.Plugin):
|
|||||||
itself.
|
itself.
|
||||||
If --count is specified, only the amount of users in the relay is given."""
|
If --count is specified, only the amount of users in the relay is given."""
|
||||||
opts = dict(optlist)
|
opts = dict(optlist)
|
||||||
|
|
||||||
if irc.nested and 'count' not in keys:
|
if irc.nested and 'count' not in keys:
|
||||||
irc.error('This command cannot be nested.', Raise=True)
|
irc.error('This command cannot be nested.', Raise=True)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
c = irc.state.channels[channel]
|
c = irc.state.channels[channel]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
irc.error("Unknown channel '%s'." % channel, Raise=True)
|
irc.error("Unknown channel '%s'." % channel, Raise=True)
|
||||||
|
|
||||||
if msg.nick not in c.users:
|
if msg.nick not in c.users:
|
||||||
self.log.warning('RelayNext: %s on %s attempted to view'
|
self.log.warning('RelayNext: %s on %s attempted to view'
|
||||||
' nicks in %s without being in it.', msg.nick,
|
' nicks in %s without being in it.', msg.nick,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user