Implement a single function for all callbacks from IRC hooks and send a seperate notification if an action takes place that concerns a bot

This commit is contained in:
Mark Veidemanis 2019-08-11 20:52:10 +01:00
parent 56840e0060
commit 2757256d4f
10 changed files with 106 additions and 113 deletions

1
.gitignore vendored
View File

@ -5,7 +5,6 @@ conf/config.json
conf/pool.json conf/pool.json
conf/wholist.json conf/wholist.json
conf/counters.json conf/counters.json
conf/masterbuf.json
conf/monitor.json conf/monitor.json
conf/alias.json conf/alias.json
conf/relay.json conf/relay.json

View File

@ -13,7 +13,6 @@ class Cmd:
return return
commands = {spl[3]: [" ".join(spl[4:])]} commands = {spl[3]: [" ".join(spl[4:])]}
print(" ".join(spl[4:]))
success("Sending commands to relay %s as user %s" % (spl[1], spl[2])) success("Sending commands to relay %s as user %s" % (spl[1], spl[2]))
deliverRelayCommands(spl[1], commands, user=spl[2]) deliverRelayCommands(spl[1], commands, user=spl[2])
return return

View File

@ -15,19 +15,15 @@
"RedisSocket": "/tmp/redis.sock", "RedisSocket": "/tmp/redis.sock",
"UsePassword": true, "UsePassword": true,
"ConnectOnCreate": false, "ConnectOnCreate": false,
"Notifications": {
"Highlight": true,
"Connection": true,
"Query": true
},
"Compat": {
"ZNC": true
},
"Dist": { "Dist": {
"Enabled": true, "Enabled": true,
"SendOutput": false, "SendOutput": false,
"File": "conf/dist.sh" "File": "conf/dist.sh"
}, },
"Toggles": {
"Who": false,
"CycleChans": true
},
"Password": "s", "Password": "s",
"Tweaks": { "Tweaks": {
"ZNC": { "ZNC": {
@ -42,6 +38,5 @@
"Factor": 2.718281828459045, "Factor": 2.718281828459045,
"Jitter": 0.11962656472 "Jitter": 0.11962656472
} }
}, }
"Master": [null, null]
} }

View File

@ -4,7 +4,6 @@
"del": "del <name>", "del": "del <name>",
"mod": "mod <name> [<key>] [<value>]", "mod": "mod <name> [<key>] [<value>]",
"get": "get <name> <variable>", "get": "get <name> <variable>",
"key": "key <master|list|add|del|except|unexcept|listexcept|monitor> [<name>] [<target>] [<key...>] [<on|off>]",
"who": "who <query>", "who": "who <query>",
"join": "join <name> <channel> [<key>]", "join": "join <name> <channel> [<key>]",
"part": "part <name> <channel>", "part": "part <name> <channel>",

View File

@ -6,6 +6,7 @@ from twisted.internet import reactor
from string import digits from string import digits
from random import randint from random import randint
from copy import deepcopy
from modules import userinfo from modules import userinfo
from modules import counters as count from modules import counters as count
@ -73,8 +74,7 @@ class IRCRelay(IRCClient):
def signedOn(self): def signedOn(self):
self.connected = True self.connected = True
log("signed on as a relay: %s" % self.relay) log("signed on as a relay: %s" % self.relay)
if main.config["Notifications"]["Connection"]: #sendRelayNotification("Relay", {"type": "conn", "status": "connected"}) nobody actually cares
sendRelayNotification("Relay", {"type": "conn", "status": "connected"})
for i in self.relayCommands.keys(): for i in self.relayCommands.keys():
for x in self.relayCommands[i]: for x in self.relayCommands[i]:
self.msg(main.config["Tweaks"]["ZNC"]["Prefix"]+i, x) self.msg(main.config["Tweaks"]["ZNC"]["Prefix"]+i, x)
@ -133,29 +133,69 @@ class IRCBot(IRCClient):
return [nick, ident, host] return [nick, ident, host]
def event(self, **cast):
for i in list(cast.keys()): # Make a copy of the .keys() as Python 3 cannot handle iterating over
if cast[i] == "": # a dictionary that changes length with each iteration
del cast[i]
if "muser" in cast.keys():
cast["nick"], cast["ident"], cast["host"] = self.parsen(cast["muser"])
del cast["muser"]
if set(["nick", "ident", "host", "message"]).issubset(set(cast)):
if "message" in cast.keys():
if cast["ident"] == "znc" and cast["host"] == "znc.in":
cast["type"] = "znc"
cast["name"] = self.name
del cast["nick"]
del cast["ident"]
del cast["host"]
del cast["target"]
if not cast["type"] in ["query", "self", "highlight", "znc", "who"]:
if "target" in cast.keys():
if cast["target"] == self.nickname:
#castDup = deepcopy(cast)
cast["mtype"] = cast["type"]
cast["type"] = "query"
cast["name"] = self.name
#self.event(**castDup)
# Don't call self.event for this one because queries are not events on a
# channel, but we still want to see them
if "user" in cast.keys():
if cast["user"] == self.nickname:
castDup = deepcopy(cast)
castDup["mtype"] = cast["type"]
castDup["type"] = "self"
castDup["name"] = self.name
self.event(**castDup)
if "nick" in cast.keys():
if cast["nick"] == self.nickname:
castDup = deepcopy(cast)
castDup["mtype"] = cast["type"]
castDup["type"] = "self"
castDup["name"] = self.name
self.event(**castDup)
if "message" in cast.keys() and not cast["type"] == "query": # Don't highlight queries
if self.nickname in cast["message"]:
castDup = deepcopy(cast)
castDup["mtype"] = cast["type"]
castDup["type"] = "highlight"
castDup["name"] = self.name
self.event(**castDup)
if "name" not in cast.keys():
cast["name"] = self.net
if cast["type"] in ["msg", "notice", "action"]:
userinfo.editUser(self.net, cast["nick"]+"!"+cast["ident"]+"@"+cast["host"])
count.event(self.net, cast["type"])
monitor.event(self.net, self.name, cast)
def privmsg(self, user, channel, msg): def privmsg(self, user, channel, msg):
nick, ident, host = self.parsen(user) self.event(type="msg", muser=user, target=channel, message=msg)
# ZNC adds messages when no users are in the channel, messing up tracking
#userinfo.editUser(self.net, channel, nick, user)
count.event(self.net, "msg")
#event = None
#if self.nickname.lower() in msg.lower():
# event = "highlight"
monitor.event(self.net, self.name, {"type": "msg", "nick": nick, "ident": ident, "host": host, "target": channel, "message": msg})
def noticed(self, user, channel, msg): def noticed(self, user, channel, msg):
nick, ident, host = self.parsen(user) self.event(type="notice", muser=user, target=channel, message=msg)
#userinfo.editUser(self.net, channel, nick, user)
count.event(self.net, "notice")
monitor.event(self.net, self.name, {"type": "notice", "nick": nick, "ident": ident, "host": host, "target": channel, "message": msg})
def action(self, user, channel, msg): def action(self, user, channel, msg):
nick, ident, host = self.parsen(user) self.event(type="action", muser=user, target=channel, message=msg)
#userinfo.editUser(self.net, channel, nick, user)
count.event(self.net, "action")
monitor.event(self.net, self.name, {"type": "action", "nick": nick, "ident": ident, "host": host, "target": channel, "message": msg})
def get(self, var): def get(self, var):
try: try:
@ -177,7 +217,7 @@ class IRCBot(IRCClient):
oldnick, ident, host = self.parsen(olduser) oldnick, ident, host = self.parsen(olduser)
userinfo.renameUser(self.net, oldnick, olduser, newnick, newnick+"!"+ident+"@"+host) userinfo.renameUser(self.net, oldnick, olduser, newnick, newnick+"!"+ident+"@"+host)
self.nickname = newnick self.nickname = newnick
count.event(self.net, "selfnick") self.event(type="self", mtype="nick", nick=oldnick, ident=ident, host=host, user=newnick)
def irc_ERR_NICKNAMEINUSE(self, prefix, params): def irc_ERR_NICKNAMEINUSE(self, prefix, params):
self._attemptedNick = self.alterCollidedNick(self._attemptedNick) self._attemptedNick = self.alterCollidedNick(self._attemptedNick)
@ -197,7 +237,7 @@ class IRCBot(IRCClient):
def irc_RPL_WHOREPLY(self, prefix, params): def irc_RPL_WHOREPLY(self, prefix, params):
channel = params[1] channel = params[1]
user = params[2] ident = params[2]
host = params[3] host = params[3]
server = params[4] server = params[4]
nick = params[5] nick = params[5]
@ -206,9 +246,8 @@ class IRCBot(IRCClient):
if channel not in self._who: if channel not in self._who:
return return
n = self._who[channel][1] n = self._who[channel][1]
n.append([nick, user, host, server, status, realname]) n.append([nick, nick, host, server, status, realname])
count.event(self.net, "whoreply") self.event(type="who", nick=nick, ident=ident, host=host, realname=realname, target=channel, server=server, status=status)
monitor.event(self.net, self.name, {"type": "who", "nick": nick, "ident": user, "host": host, "realname": realname, "target": channel, "server": server, "status": status})
def irc_RPL_ENDOFWHO(self, prefix, params): def irc_RPL_ENDOFWHO(self, prefix, params):
channel = params[1] channel = params[1]
@ -260,7 +299,7 @@ class IRCBot(IRCClient):
newNicklist = [] newNicklist = []
for i in nicklist[1]: for i in nicklist[1]:
for x in i: for x in i:
f = self.sanit(x) f = self.sanit(x) # need to store this as well, or potentially just do not remove it...
if f: if f:
newNicklist.append(f) newNicklist.append(f)
userinfo.initialNames(self.net, nicklist[0], newNicklist) userinfo.initialNames(self.net, nicklist[0], newNicklist)
@ -320,11 +359,8 @@ class IRCBot(IRCClient):
channel = params[0] channel = params[0]
kicked = params[1] kicked = params[1]
message = params[-1] message = params[-1]
if kicked.lower() == self.nickname.lower(): # Checks on whether it was us that was kicked are done in userKicked
# Yikes! self.userKicked(kicked, channel, prefix, message)
self.kickedFrom(channel, prefix, message)
else:
self.userKicked(kicked, channel, prefix, message)
def irc_TOPIC(self, prefix, params): def irc_TOPIC(self, prefix, params):
""" """
@ -339,26 +375,20 @@ class IRCBot(IRCClient):
def signedOn(self): def signedOn(self):
self.connected = True self.connected = True
log("signed on: %s" % self.name) log("signed on: %s" % self.name)
if main.config["Notifications"]["Connection"]: self.event(type="conn", status="connected")
sendRelayNotification(self.name, {"type": "conn", "status": "connected"})
count.event(self.net, "signedon")
def joined(self, channel): def joined(self, channel):
if not channel in self.channels: if not channel in self.channels:
self.channels.append(channel) self.channels.append(channel)
self.names(channel).addCallback(self.got_names) self.names(channel).addCallback(self.got_names)
self.who(channel).addCallback(self.got_who) self.who(channel).addCallback(self.got_who)
count.event(self.net, "selfjoin") if main.config["Toggles"]["Who"]:
if self.name == main.config["Master"][0] and channel == main.config["Master"][1]: lc = LoopingCall(self.who, channel)
for i in range(len(main.masterbuf)): self._getWho[channel] = lc
self.msg(channel, main.masterbuf.pop(0)) intrange = main.config["Tweaks"]["Delays"]["WhoRange"]
main.saveConf("masterbuf") minint = main.config["Tweaks"]["Delays"]["WhoLoop"]
lc = LoopingCall(self.who, channel) interval = randint(minint, minint+intrange)
self._getWho[channel] = lc lc.start(interval)
intrange = main.config["Tweaks"]["Delays"]["WhoRange"]
minint = main.config["Tweaks"]["Delays"]["WhoLoop"]
interval = randint(minint, minint+intrange)
lc.start(interval)
def botLeft(self, channel): def botLeft(self, channel):
if channel in self.channels: if channel in self.channels:
@ -368,79 +398,58 @@ class IRCBot(IRCClient):
lc.stop() lc.stop()
del self._getWho[channel] del self._getWho[channel]
userinfo.delChannel(self.net, channel) userinfo.delChannel(self.net, channel)
log("Can no longer cover %s, removing records" % channel)
def left(self, user, channel, message): def left(self, user, channel, message):
nick, ident, host = self.parsen(user) self.event(type="part", muser=user, target=channel, message=message)
count.event(self.net, "selfpart")
monitor.event(self.net, self.name, {"type": "part", "target": channel, "message": message})
monitor.event(self.net, self.name, {"type": "part", "self": True, "target": channel, "message": message})
self.botLeft(channel)
def kickedFrom(self, channel, kicker, message):
nick, ident, host = self.parsen(kicker)
if channel in self.channels:
self.channels.remove(channel)
count.event(self.net, "selfkick")
monitor.event(self.net, self.name, {"type": "kick", "nick": nick, "ident": ident, "host": host, "target": channel, "message": message})
monitor.event(self.net, self.name, {"type": "kick", "self": True, "nick": nick, "ident": ident, "host": host, "target": channel, "message": message})
self.botLeft(channel) self.botLeft(channel)
def userJoined(self, user, channel): def userJoined(self, user, channel):
nick, ident, host = self.parsen(user) nick, ident, host = self.parsen(user)
userinfo.addUser(self.net, channel, nick, user) userinfo.addUser(self.net, channel, nick, user)
count.event(self.net, "join") self.event(type="join", nick=nick, ident=ident, host=host, target=channel)
monitor.event(self.net, self.name, {"type": "join", "nick": nick, "ident": ident, "host": host, "target": channel})
def userLeft(self, user, channel, message): def userLeft(self, user, channel, message):
nick, ident, host = self.parsen(user) nick, ident, host = self.parsen(user)
userinfo.delUser(self.net, channel, nick, user) userinfo.delUser(self.net, channel, nick, user)
count.event(self.net, "part") self.event(type="part", nick=nick, ident=ident, host=host, target=channel, message=message)
monitor.event(self.net, self.name, {"type": "part", "nick": nick, "ident": ident, "host": host, "target": channel, "message": message})
def userQuit(self, user, quitMessage): def userQuit(self, user, quitMessage):
nick, ident, host = self.parsen(user) nick, ident, host = self.parsen(user)
userinfo.delUserByNetwork(self.net, nick, user) userinfo.delUserByNetwork(self.net, nick, user)
count.event(self.net, "quit") self.event(type="quit", nick=nick, ident=ident, host=host, message=quitMessage)
monitor.event(self.net, self.name, {"type": "quit", "nick": nick, "ident": ident, "host": host, "message": quitMessage})
def botQuit(self, user, quitMessage): def botQuit(self, user, quitMessage):
nick, ident, host = self.parsen(user) nick, ident, host = self.parsen(user)
userinfo.delUserByNetwork(self.net, nick, user) userinfo.delUserByNetwork(self.net, nick, user)
count.event(self.net, "selfquit") self.event(type="quit", nick=nick, ident=ident, host=host, message=quitMessage)
monitor.event(self.net, self.name, {"type": "quit", "nick": nick, "ident": ident, "host": host, "message": quitMessage})
monitor.event(self.net, self.name, {"type": "quit", "self": True, "nick": nick, "ident": ident, "host": host, "message": quitMessage})
def userKicked(self, kickee, channel, kicker, message): def userKicked(self, kickee, channel, kicker, message):
nick, ident, host = self.parsen(kicker) nick, ident, host = self.parsen(kicker)
userinfo.editUser(self.net, channel, nick, kicker) userinfo.editUser(self.net, kicker)
userinfo.delUserByNick(self.net, channel, kickee) userinfo.delUserByNick(self.net, channel, kickee)
count.event(self.net, "kick") if kickee.lower == self.nickname.lower:
self.botLeft(channel)
monitor.event(self.net, self.name, {"type": "kick", "nick": nick, "ident": ident, "host": host, "target": channel, "message": message, "user": kickee}) self.event(type="kick", nick=nick, ident=ident, host=host, target=channel, message=message, user=kickee)
def userRenamed(self, oldname, newname): def userRenamed(self, oldname, newname):
nick, ident, host = self.parsen(oldname) nick, ident, host = self.parsen(oldname)
userinfo.renameUser(self.net, nick, oldname, newname, newname+"!"+ident+"@"+host) userinfo.renameUser(self.net, nick, oldname, newname, newname+"!"+ident+"@"+host)
count.event(self.net, "nick") self.event(type="nick", nick=nick, ident=ident, host=host, user=newname)
monitor.event(self.net, self.name, {"type": "nick", "nick": nick, "ident": ident, "host": host, "user": newname})
def topicUpdated(self, user, channel, newTopic): def topicUpdated(self, user, channel, newTopic):
nick, ident, host = self.parsen(user) nick, ident, host = self.parsen(user)
userinfo.editUser(self.net, channel, nick, user) userinfo.editUser(self.net, user)
count.event(self.net, "topic")
monitor.event(self.net, self.name, {"type": "topic", "nick": nick, "ident": ident, "host": host, "target": channel, "message": newTopic}) self.event(type="topic", nick=nick, ident=ident, host=host, target=channel, message= newTopic)
def modeChanged(self, user, channel, toset, modes, args): def modeChanged(self, user, channel, toset, modes, args):
nick, ident, host = self.parsen(user) nick, ident, host = self.parsen(user)
userinfo.editUser(self.net, channel, nick, user) userinfo.editUser(self.net, user)
count.event(self.net, "mode")
argList = list(args) argList = list(args)
modeList = [i for i in modes] modeList = [i for i in modes]
for a, m in zip(argList, modeList): for a, m in zip(argList, modeList):
monitor.event(self.net, self.name, {"type": "mode", "nick": nick, "ident": ident, "host": host, "target": channel, "modes": m, "modeargs": a}) self.event(type="mode", nick=nick, ident=ident, host=host, target=channel, modes=m, modeargs=a)
class IRCBotFactory(ReconnectingClientFactory): class IRCBotFactory(ReconnectingClientFactory):
def __init__(self, name, relay=None, relayCommands=None, user=None, stage2=None): def __init__(self, name, relay=None, relayCommands=None, user=None, stage2=None):
@ -469,7 +478,7 @@ class IRCBotFactory(ReconnectingClientFactory):
def clientConnectionLost(self, connector, reason): def clientConnectionLost(self, connector, reason):
if not self.relay: if not self.relay:
userinfo.delNetwork(self.net, self.client.channels) userinfo.delNetwork(self.name, self.client.channels)
if not self.client == None: if not self.client == None:
self.client.connected = False self.client.connected = False
self.client.channels = [] self.client.channels = []
@ -477,9 +486,7 @@ class IRCBotFactory(ReconnectingClientFactory):
log("%s: connection lost: %s" % (self.name, error)) log("%s: connection lost: %s" % (self.name, error))
if not self.relay: if not self.relay:
sendAll("%s: connection lost: %s" % (self.name, error)) sendAll("%s: connection lost: %s" % (self.name, error))
if main.config["Notifications"]["Connection"]: sendRelayNotification(self.name, {"type": "conn", "status": "lost", "message": error})
sendRelayNotification(self.name, {"type": "conn", "status": "lost", "reason": error})
if not self.relay:
self.retry(connector) self.retry(connector)
#ReconnectingClientFactory.clientConnectionLost(self, connector, reason) #ReconnectingClientFactory.clientConnectionLost(self, connector, reason)
@ -491,9 +498,7 @@ class IRCBotFactory(ReconnectingClientFactory):
log("%s: connection failed: %s" % (self.name, error)) log("%s: connection failed: %s" % (self.name, error))
if not self.relay: if not self.relay:
sendAll("%s: connection failed: %s" % (self.name, error)) sendAll("%s: connection failed: %s" % (self.name, error))
if main.config["Notifications"]["Connection"]: sendRelayNotification(self.name, {"type": "conn", "status": "failed", "message": error})
sendRelayNotification(self.name, {"type": "conn", "status": "failed", "reason": error})
if not self.relay:
self.retry(connector) self.retry(connector)
#ReconnectingClientFactory.clientConnectionFailed(self, connector, reason) #ReconnectingClientFactory.clientConnectionFailed(self, connector, reason)

View File

@ -6,9 +6,7 @@ from datetime import datetime
import main import main
from utils.logging.log import * from utils.logging.log import *
validTypes = ["msg", "notice", "action", "who", "part", "join", "kick", "quit", "nick", "topic", "mode", "conn", "znc", "query", "self", "highlight", "monitor", "err"] validTypes = ["msg", "notice", "action", "who", "part", "join", "kick", "quit", "nick", "topic", "mode", "conn", "znc", "query", "self", "highlight", "monitor", "err", "query", "self", "highlight"]
selfTypes = ["query", "self", "highlight"]
class Relay(Protocol): class Relay(Protocol):
def __init__(self, addr): def __init__(self, addr):
@ -137,7 +135,7 @@ class RelayFactory(Factory):
def sendRelayNotification(name, cast): def sendRelayNotification(name, cast):
for i in main.relayConnections.keys(): for i in main.relayConnections.keys():
if main.relayConnections[i].authed: if main.relayConnections[i].authed:
if cast["type"] in main.relayConnections[i].subscriptions or set(main.relayConnections[i].subscriptions).issubset(cast): if cast["type"] in main.relayConnections[i].subscriptions:
newCast = deepcopy(cast) newCast = deepcopy(cast)
newCast["name"] = name newCast["name"] = name
newCast["time"] = str(datetime.now()) newCast["time"] = str(datetime.now())

View File

@ -11,7 +11,6 @@ filemap = {
"pool": ["pool.json", "network, alias and relay mappings"], "pool": ["pool.json", "network, alias and relay mappings"],
"help": ["help.json", "command help"], "help": ["help.json", "command help"],
"counters": ["counters.json", "counters file"], "counters": ["counters.json", "counters file"],
"masterbuf": ["masterbuf.json", "master buffer"],
"monitor": ["monitor.json", "monitoring database"], "monitor": ["monitor.json", "monitoring database"],
"alias": ["alias.json", "alias details"], "alias": ["alias.json", "alias details"],
"relay": ["relay.json", "relay list"], "relay": ["relay.json", "relay list"],

View File

@ -55,11 +55,6 @@ def event(name, numberedName, cast, event=None):
target = cast["target"] target = cast["target"]
else: else:
target = None target = None
if set(["nick", "ident", "host", "message"]).issubset(set(cast)):
if main.config["Compat"]["ZNC"] and "message" in cast.keys():
if cast["nick"][0] == main.config["Tweaks"]["ZNC"]["Prefix"] and cast["ident"] == "znc" and cast["host"] == "znc.in":
sendRelayNotification(numberedName, {"type": "znc", "message": cast["message"]})
return
sendRelayNotification(name, cast) sendRelayNotification(name, cast)
monitorGroups = testNetTarget(name, target) monitorGroups = testNetTarget(name, target)

View File

@ -39,6 +39,9 @@ def provisionNetworkData(relay, alias, network, host, port, security, auth, pass
if not main.config["ConnectOnCreate"]: if not main.config["ConnectOnCreate"]:
stage3commands["status"] = [] stage3commands["status"] = []
stage3commands["status"].append("Disconnect") stage3commands["status"].append("Disconnect")
if main.config["Toggles"]["CycleChans"]:
stage2commands["status"] = []
stage2commands["status"].append("LoadMod disconkick")
deliverRelayCommands(relay, commands, deliverRelayCommands(relay, commands,
stage2=[[alias+"/"+network, stage2commands], stage2=[[alias+"/"+network, stage2commands],
[alias+"/"+network, stage3commands]]) [alias+"/"+network, stage3commands]])

View File

@ -76,7 +76,7 @@ def initialNames(name, channel, names):
p.sadd("live.chan."+name+"."+i, channel) p.sadd("live.chan."+name+"."+i, channel)
p.execute() p.execute()
def editUser(name, channel, nick, user): def editUser(name, user):
gnamespace = "live.who.%s" % name gnamespace = "live.who.%s" % name
main.r.sadd(gnamespace, user) main.r.sadd(gnamespace, user)
@ -151,7 +151,7 @@ def delUserByNetwork(name, nick, user):
p.delete(chanspace) p.delete(chanspace)
p.execute() p.execute()
def delChannel(name, channel): def delChannel(name, channel): # This function is extremely expensive, look to replace
gnamespace = "live.who.%s" % name gnamespace = "live.who.%s" % name
namespace = "live.who.%s.%s" % (name, channel) namespace = "live.who.%s.%s" % (name, channel)
p = main.r.pipeline() p = main.r.pipeline()
@ -171,4 +171,5 @@ def delNetwork(name, channels):
log("Purging channels for %s" % name) log("Purging channels for %s" % name)
for i in channels: for i in channels:
delChannel(name, i) delChannel(name, i)
log("Finished purging channels for %s" % name)
return return