Moved files to subdirectory
This commit is contained in:
871
legacy/core/bot.py
Normal file
871
legacy/core/bot.py
Normal file
@@ -0,0 +1,871 @@
|
||||
import sys
|
||||
from copy import deepcopy
|
||||
from datetime import datetime
|
||||
from random import randint
|
||||
|
||||
from twisted.internet import reactor
|
||||
from twisted.internet.defer import Deferred
|
||||
from twisted.internet.protocol import ReconnectingClientFactory
|
||||
from twisted.internet.task import LoopingCall
|
||||
from twisted.words.protocols.irc import (
|
||||
IRCBadMessage,
|
||||
IRCClient,
|
||||
lowDequote,
|
||||
numeric_to_symbolic,
|
||||
)
|
||||
|
||||
import main
|
||||
from core.relay import sendRelayNotification
|
||||
from modules import chankeep, counters, helpers, monitor, regproc, userinfo
|
||||
from utils.dedup import dedup
|
||||
from utils.logging.debug import debug, trace
|
||||
from utils.logging.log import error, log, warn
|
||||
from utils.logging.send import sendAll
|
||||
from utils.parsing import parsen
|
||||
|
||||
|
||||
# Copied from the Twisted source so we can fix a bug
|
||||
def parsemsg(s):
|
||||
"""
|
||||
Breaks a message from an IRC server into its prefix, command, and
|
||||
arguments.
|
||||
@param s: The message to break.
|
||||
@type s: L{bytes}
|
||||
@return: A tuple of (prefix, command, args).
|
||||
@rtype: L{tuple}
|
||||
"""
|
||||
prefix = ""
|
||||
trailing = []
|
||||
if not s:
|
||||
raise IRCBadMessage("Empty line.")
|
||||
if s[0:1] == ":":
|
||||
prefix, s = s[1:].split(" ", 1)
|
||||
if s.find(" :") != -1:
|
||||
s, trailing = s.split(" :", 1)
|
||||
args = s.split(" ") # Twisted bug fixed by adding an argument to split()
|
||||
args.append(trailing)
|
||||
else:
|
||||
args = s.split(" ") # And again
|
||||
command = args.pop(0)
|
||||
return prefix, command, args
|
||||
|
||||
|
||||
class IRCBot(IRCClient):
|
||||
def __init__(self, net, num):
|
||||
self.isconnected = False
|
||||
self.channels = []
|
||||
self.net = net
|
||||
self.authenticated = not regproc.needToAuth(self.net)
|
||||
self.num = num
|
||||
self.buffer = ""
|
||||
self.name = net + str(num)
|
||||
alias = main.alias[num]
|
||||
relay = main.network[self.net].relays[num]
|
||||
self.netinst = main.network[self.net]
|
||||
self.nickname = alias["nick"]
|
||||
self.realname = alias["realname"]
|
||||
self.username = alias["nick"].lower() + "/" + relay["net"]
|
||||
self.password = main.config["Relay"]["Password"]
|
||||
self.userinfo = None #
|
||||
self.fingerReply = None #
|
||||
self.versionName = None # Don't give out information
|
||||
self.versionNum = None #
|
||||
self.versionEnv = None #
|
||||
self.sourceURL = None #
|
||||
|
||||
self._getWho = {} # LoopingCall objects -- needed to be able to stop them
|
||||
|
||||
self._tempWho = {} # temporary storage for gathering WHO info
|
||||
self._tempNames = {} # temporary storage for gathering NAMES info
|
||||
self._tempList = ([], []) # temporary storage for gathering LIST info
|
||||
self.listOngoing = False # we are currently receiving a LIST
|
||||
self.listRetried = False # we asked and got nothing so asked again
|
||||
self.listAttempted = False # we asked for a list
|
||||
self.listSimple = False # after asking again we got the list, so use the simple
|
||||
# syntax from now on
|
||||
self.wantList = False # we want to send a LIST, but not all relays are active yet
|
||||
self.chanlimit = 0
|
||||
self.prefix = {}
|
||||
self.servername = None
|
||||
|
||||
self._regAttempt = None
|
||||
self._negativePass = None
|
||||
|
||||
def lineReceived(self, line):
|
||||
if bytes != str and isinstance(line, bytes):
|
||||
# decode bytes from transport to unicode
|
||||
line = line.decode("utf-8", "replace")
|
||||
|
||||
line = lowDequote(line)
|
||||
try:
|
||||
prefix, command, params = parsemsg(line)
|
||||
if command in numeric_to_symbolic:
|
||||
command = numeric_to_symbolic[command]
|
||||
try:
|
||||
self.handleCommand(command, prefix, params)
|
||||
except Exception as err:
|
||||
error(err)
|
||||
except IRCBadMessage:
|
||||
self.badMessage(line, *sys.exc_info())
|
||||
|
||||
def joinChannels(self, channels):
|
||||
sleeptime = 0.0
|
||||
increment = 0.8
|
||||
for i in channels:
|
||||
if i not in self.channels:
|
||||
if self.net in main.blacklist.keys():
|
||||
if i in main.blacklist[self.net]:
|
||||
debug("Not joining blacklisted channel %s on %s - %i" % (i, self.net, self.num))
|
||||
continue
|
||||
trace(self.net, "-", self.num, ": joining", i, "in", sleeptime, "seconds")
|
||||
reactor.callLater(sleeptime, self.join, i)
|
||||
sleeptime += increment
|
||||
if sleeptime == 10:
|
||||
sleeptime = 0.0
|
||||
increment = 0.7
|
||||
increment += 0.1
|
||||
else:
|
||||
error("%s - Cannot join channel we are already on: %s - %i" % (i, self.net, self.num))
|
||||
|
||||
def checkChannels(self):
|
||||
if chankeep.allRelaysActive(self.net):
|
||||
debug(f"checkChannels() all relays active for {self.net}")
|
||||
else:
|
||||
debug("checkChannels() skipping channel check as we have inactive relays: %s - %i" % (self.net, self.num))
|
||||
return
|
||||
if self.net in main.TempChan.keys():
|
||||
if self.num in main.TempChan[self.net].keys():
|
||||
self.joinChannels(main.TempChan[self.net][self.num])
|
||||
del main.TempChan[self.net][self.num]
|
||||
if not main.TempChan[self.net]:
|
||||
del main.TempChan[self.net]
|
||||
|
||||
def event(self, **cast):
|
||||
if "ts" not in cast.keys():
|
||||
cast["ts"] = str(datetime.now().isoformat())
|
||||
|
||||
# remove odd stuff
|
||||
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]
|
||||
# remove server stuff
|
||||
if "muser" in cast.keys():
|
||||
if cast["muser"] == self.servername:
|
||||
cast["type"] = "conn"
|
||||
if "channel" in cast.keys():
|
||||
if cast["channel"] == "*":
|
||||
cast["type"] = "conn"
|
||||
##
|
||||
|
||||
# expand out the hostmask
|
||||
if not {"nick", "ident", "host"}.issubset(set(cast.keys())):
|
||||
if "muser" in cast.keys():
|
||||
cast["nick"], cast["ident"], cast["host"] = parsen(cast["muser"])
|
||||
|
||||
# handle ZNC stuff
|
||||
if {"nick", "ident", "host", "msg"}.issubset(set(cast)):
|
||||
if cast["ident"] == "znc" and cast["host"] == "znc.in":
|
||||
cast["type"] = "znc"
|
||||
cast["num"] = self.num
|
||||
del cast["nick"]
|
||||
del cast["ident"]
|
||||
del cast["host"]
|
||||
del cast["channel"]
|
||||
del cast["muser"]
|
||||
if "Disconnected from IRC" in cast["msg"]:
|
||||
log("ZNC disconnected on %s - %i" % (self.net, self.num))
|
||||
self.isconnected = False
|
||||
self.authenticated = False
|
||||
if "Connected!" in cast["msg"]:
|
||||
log("ZNC connected on %s - %i" % (self.net, self.num))
|
||||
self.isconnected = True
|
||||
if "could not be joined, disabling it" in cast["msg"]:
|
||||
error(cast["msg"])
|
||||
#
|
||||
|
||||
# don't reprocess the same message twice
|
||||
# if the type is in that list, it's already been here, don't run it again
|
||||
if not cast["type"] in {"query", "self", "highlight", "znc", "who", "conn"}:
|
||||
cast["num"] = self.num
|
||||
if "channel" in cast.keys():
|
||||
if cast["type"] == "mode":
|
||||
if cast["channel"].lower() == self.nickname.lower():
|
||||
# castDup = deepcopy(cast)
|
||||
cast["mtype"] = cast["type"]
|
||||
cast["type"] = "self"
|
||||
# self.event(**castDup)
|
||||
if cast["modearg"]: # check if modearg is non-NoneType
|
||||
if self.nickname.lower() == cast["modearg"].lower():
|
||||
castDup = deepcopy(cast)
|
||||
castDup["mtype"] = cast["type"]
|
||||
castDup["type"] = "highlight"
|
||||
self.event(**castDup)
|
||||
else:
|
||||
if cast["channel"].lower() == self.nickname.lower() or not cast["channel"].startswith("#"):
|
||||
cast["mtype"] = cast["type"]
|
||||
cast["type"] = "query"
|
||||
# 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 cast["channel"] == "AUTH":
|
||||
cast["type"] = "conn"
|
||||
cast["mtype"] = cast["type"]
|
||||
|
||||
# TODO: better way to do this
|
||||
# as we changed the types above, check again
|
||||
if not cast["type"] in {"query", "self", "highlight", "znc", "who", "conn"}:
|
||||
# we have been kicked
|
||||
if "user" in cast.keys():
|
||||
if cast["user"].lower() == self.nickname.lower():
|
||||
castDup = deepcopy(cast)
|
||||
castDup["mtype"] = cast["type"]
|
||||
castDup["type"] = "self"
|
||||
if self.net in main.blacklist.keys():
|
||||
if cast["channel"] not in main.blacklist[self.net]:
|
||||
main.blacklist[self.net].append(cast["channel"])
|
||||
else:
|
||||
main.blacklist[self.net] = [cast["channel"]]
|
||||
main.saveConf("blacklist")
|
||||
self.event(**castDup)
|
||||
|
||||
# we sent a message/left/joined/kick someone/quit
|
||||
if "nick" in cast.keys():
|
||||
if cast["nick"].lower() == self.nickname.lower():
|
||||
castDup = deepcopy(cast)
|
||||
castDup["mtype"] = cast["type"]
|
||||
castDup["type"] = "self"
|
||||
|
||||
# we have been mentioned in a msg/notice/action/part/quit/topic message
|
||||
if "msg" in cast.keys(): # Don't highlight queries
|
||||
if cast["msg"] is not None:
|
||||
if self.nickname.lower() in cast["msg"].lower():
|
||||
castDup = deepcopy(cast)
|
||||
castDup["mtype"] = cast["type"]
|
||||
castDup["type"] = "highlight"
|
||||
self.event(**castDup)
|
||||
|
||||
if "net" not in cast.keys():
|
||||
cast["net"] = self.net
|
||||
if "num" not in cast.keys():
|
||||
cast["num"] = self.num
|
||||
if not self.authenticated:
|
||||
regproc.registerTest(cast)
|
||||
counters.event(self.net, cast["type"])
|
||||
monitor.event(self.net, cast)
|
||||
|
||||
def privmsg(self, user, channel, msg):
|
||||
self.event(type="msg", muser=user, channel=channel, msg=msg)
|
||||
|
||||
def noticed(self, user, channel, msg):
|
||||
self.event(type="notice", muser=user, channel=channel, msg=msg)
|
||||
|
||||
def action(self, user, channel, msg):
|
||||
self.event(type="action", muser=user, channel=channel, msg=msg)
|
||||
|
||||
def sendmsg(self, channel, msg, in_query=False):
|
||||
query = f"{self.nickname}!*@*"
|
||||
res = userinfo.getWhoSingle(self.net, query)
|
||||
if not res:
|
||||
res = []
|
||||
us = list(res)
|
||||
if len(us) > 0:
|
||||
hostmask = us[0]
|
||||
else:
|
||||
# Close enough...
|
||||
hostmask = f"{self.nickname}!{self.nickname}@{self.servername}"
|
||||
warn(f"Could not get a hostname, using {hostmask}")
|
||||
nick, ident, host = parsen(hostmask)
|
||||
# We sent someone a query reply
|
||||
if in_query:
|
||||
self.event(type="self", mtype="msg", channel=self.nickname, nick=channel, ident=ident, host=host, msg=msg)
|
||||
else:
|
||||
self.event(type="self", mtype="msg", channel=channel, nick=self.nickname, ident=ident, host=host, msg=msg)
|
||||
self.event(type="msg", channel=channel, nick=self.nickname, ident=ident, host=host, msg=msg)
|
||||
self.msg(channel, msg)
|
||||
|
||||
def get(self, var):
|
||||
try:
|
||||
result = getattr(self, var)
|
||||
except AttributeError:
|
||||
result = None
|
||||
return result
|
||||
|
||||
def setNick(self, nickname):
|
||||
self._attemptedNick = nickname
|
||||
self.sendLine("NICK %s" % nickname)
|
||||
self.nickname = nickname
|
||||
|
||||
def alterCollidedNick(self, nickname):
|
||||
newnick = nickname + "_"
|
||||
return newnick
|
||||
|
||||
def nickChanged(self, olduser, newnick):
|
||||
self.nickname = newnick
|
||||
self.event(type="self", mtype="nick", muser=olduser, user=newnick)
|
||||
|
||||
def irc_ERR_NICKNAMEINUSE(self, prefix, params):
|
||||
self._attemptedNick = self.alterCollidedNick(self._attemptedNick)
|
||||
self.setNick(self._attemptedNick)
|
||||
|
||||
def irc_ERR_PASSWDMISMATCH(self, prefix, params):
|
||||
log("%s - %i: password mismatch as %s" % (self.net, self.num, self.username))
|
||||
sendAll("%s - %i: password mismatch as %s" % (self.net, self.num, self.username))
|
||||
|
||||
def _who(self, channel):
|
||||
d = Deferred()
|
||||
if channel not in self._tempWho:
|
||||
self._tempWho[channel] = ([], [])
|
||||
self._tempWho[channel][0].append(d)
|
||||
self.sendLine("WHO %s" % channel)
|
||||
return d
|
||||
|
||||
def who(self, channel):
|
||||
self._who(channel).addCallback(self.got_who)
|
||||
|
||||
def irc_RPL_WHOREPLY(self, prefix, params):
|
||||
channel = params[1]
|
||||
ident = params[2]
|
||||
host = params[3]
|
||||
server = params[4]
|
||||
nick = params[5]
|
||||
status = params[6]
|
||||
realname = params[7]
|
||||
if channel not in self._tempWho:
|
||||
return
|
||||
n = self._tempWho[channel][1]
|
||||
n.append([nick, nick, host, server, status, realname])
|
||||
self.event(
|
||||
type="who",
|
||||
nick=nick,
|
||||
ident=ident,
|
||||
host=host,
|
||||
realname=realname,
|
||||
channel=channel,
|
||||
server=server,
|
||||
status=status,
|
||||
)
|
||||
|
||||
def irc_RPL_ENDOFWHO(self, prefix, params):
|
||||
channel = params[1]
|
||||
if channel not in self._tempWho:
|
||||
return
|
||||
callbacks, info = self._tempWho[channel]
|
||||
for cb in callbacks:
|
||||
cb.callback((channel, info))
|
||||
del self._tempWho[channel]
|
||||
|
||||
def got_who(self, whoinfo):
|
||||
userinfo.initialUsers(self.net, whoinfo[0], whoinfo[1])
|
||||
|
||||
def sanit(self, data):
|
||||
if len(data) >= 1:
|
||||
if data[0] in self.prefix.keys():
|
||||
return (
|
||||
self.prefix[data[0]],
|
||||
data[1:],
|
||||
) # would use a set but it's possible these are the same
|
||||
return (None, data)
|
||||
else:
|
||||
return (None, False)
|
||||
|
||||
def names(self, channel):
|
||||
d = Deferred()
|
||||
if channel not in self._tempNames:
|
||||
self._tempNames[channel] = ([], [])
|
||||
self._tempNames[channel][0].append(d)
|
||||
self.sendLine("NAMES %s" % channel)
|
||||
return d
|
||||
|
||||
def irc_RPL_NAMREPLY(self, prefix, params):
|
||||
channel = params[2]
|
||||
nicklist = params[3].split(" ")
|
||||
if channel not in self._tempNames:
|
||||
return
|
||||
n = self._tempNames[channel][1]
|
||||
n.append(nicklist)
|
||||
|
||||
def irc_RPL_ENDOFNAMES(self, prefix, params):
|
||||
channel = params[1]
|
||||
if channel not in self._tempNames:
|
||||
return
|
||||
callbacks, namelist = self._tempNames[channel]
|
||||
for cb in callbacks:
|
||||
cb.callback((channel, namelist))
|
||||
del self._tempNames[channel]
|
||||
|
||||
def got_names(self, nicklist):
|
||||
newNicklist = []
|
||||
for i in nicklist[1]:
|
||||
for x in i:
|
||||
mode, nick = self.sanit(x)
|
||||
if nick:
|
||||
newNicklist.append((mode, nick))
|
||||
userinfo.initialNames(self.net, nicklist[0], newNicklist)
|
||||
|
||||
def myInfo(self, servername, version, umodes, cmodes):
|
||||
self.servername = servername
|
||||
|
||||
def _list(self, noargs):
|
||||
d = Deferred()
|
||||
self._tempList = ([], [])
|
||||
self._tempList[0].append(d)
|
||||
if self.listSimple:
|
||||
self.sendLine("LIST")
|
||||
return d # return early if we know what to do
|
||||
|
||||
if noargs:
|
||||
self.sendLine("LIST")
|
||||
else:
|
||||
self.sendLine("LIST >0")
|
||||
return d
|
||||
|
||||
def list(self, noargs=False, nocheck=False):
|
||||
if not self.authenticated:
|
||||
debug("Will not send LIST, unauthenticated: %s - %i" % (self.net, self.num))
|
||||
return
|
||||
if self.listAttempted:
|
||||
debug("List request dropped, already asked for LIST - %s - %i" % (self.net, self.num))
|
||||
return
|
||||
else:
|
||||
self.listAttempted = True
|
||||
if self.listOngoing:
|
||||
debug("LIST request dropped, already ongoing - %s - %i" % (self.net, self.num))
|
||||
return
|
||||
else:
|
||||
if nocheck:
|
||||
allRelays = True # override the system - if this is
|
||||
else: # specified, we already did this
|
||||
allRelays = chankeep.allRelaysActive(self.net)
|
||||
if not allRelays:
|
||||
self.wantList = True
|
||||
debug("Not all relays were active for LIST request")
|
||||
return
|
||||
self._list(noargs).addCallback(self.got_list)
|
||||
|
||||
def irc_RPL_LISTSTART(self, prefix, params):
|
||||
self.listAttempted = False
|
||||
self.listOngoing = True
|
||||
self.wantList = False
|
||||
|
||||
def irc_RPL_LIST(self, prefix, params):
|
||||
channel = params[1]
|
||||
users = params[2]
|
||||
topic = params[3]
|
||||
self._tempList[1].append([channel, users, topic])
|
||||
|
||||
def irc_RPL_LISTEND(self, prefix, params):
|
||||
if not len(self._tempList[0]) > 0: # there are no callbacks, can't do anything there
|
||||
debug("We didn't ask for this LIST, discarding")
|
||||
self._tempList[0].clear()
|
||||
self._tempList[1].clear()
|
||||
return
|
||||
callbacks, info = self._tempList
|
||||
self.listOngoing = False
|
||||
for cb in callbacks:
|
||||
cb.callback((info))
|
||||
noResults = False
|
||||
if len(self._tempList[1]) == 0:
|
||||
noResults = True
|
||||
self._tempList[0].clear()
|
||||
self._tempList[1].clear()
|
||||
if noResults:
|
||||
if self.listRetried:
|
||||
warn("LIST still empty after retry: %s - %i" % (self.net, self.num))
|
||||
self.listRetried = False
|
||||
return
|
||||
else:
|
||||
self.list(True)
|
||||
self.listRetried = True
|
||||
else:
|
||||
if self.listRetried:
|
||||
self.listRetried = False
|
||||
debug("List received after retry - defaulting to simple list syntax: %s - %i" % (self.net, self.num))
|
||||
self.listSimple = True
|
||||
|
||||
def got_list(self, listinfo):
|
||||
if len(listinfo) == 0: # probably ngircd not supporting LIST >0
|
||||
return
|
||||
if main.config["ChanKeep"]["Enabled"]:
|
||||
chankeep.initialList(self.net, self.num, listinfo)
|
||||
|
||||
def recheckList(self):
|
||||
if not main.config["ChanKeep"]["Enabled"]:
|
||||
return
|
||||
allRelays = chankeep.allRelaysActive(self.net)
|
||||
debug(f"recheckList() all relays for {self.net} {allRelays}")
|
||||
if allRelays:
|
||||
debug(f"recheckList() all relays active for {self.net}")
|
||||
first_relay = helpers.get_first_relay(self.net)
|
||||
debug(f"recheckList() first relay for {self.net}: {first_relay.num}")
|
||||
if first_relay:
|
||||
if first_relay.wantList is True:
|
||||
first_relay.list(nocheck=True)
|
||||
debug(f"recheckList() asking for a list for {self.net} after final relay {self.num} connected")
|
||||
else:
|
||||
debug(f"recheckList() first relay wantList is False for {self.net} ({first_relay.num})")
|
||||
# name = self.net + "1"
|
||||
|
||||
# if self.num == 1: # Only one instance should do a list
|
||||
if helpers.is_first_relay(self.net, self.num):
|
||||
debug(f"recheckList() we are the first relay for {self.net} ({self.num})")
|
||||
if self.chanlimit:
|
||||
if allRelays:
|
||||
self.list()
|
||||
debug(f"recheckList() requested a list for {self.net} from {self.num}")
|
||||
else:
|
||||
debug(f"recheckList() not all relays active for {self.net}")
|
||||
self.wantList = True
|
||||
else:
|
||||
debug("recheckList() aborting LIST due to bad chanlimit")
|
||||
self.checkChannels()
|
||||
|
||||
def seed_chanlimit(self, chanlimit):
|
||||
if not main.network[self.net].relays[self.num][
|
||||
"registered"
|
||||
]: # TODO: add check for register request sent, only send it once
|
||||
if main.config["AutoReg"]:
|
||||
if not self.authenticated:
|
||||
self._regAttempt = reactor.callLater(5, regproc.registerAccount, self.net, self.num)
|
||||
# regproc.registerAccount(self.net, self.num)
|
||||
try:
|
||||
self.chanlimit = int(chanlimit)
|
||||
except TypeError:
|
||||
warn("Invalid chanlimit: %s" % chanlimit)
|
||||
if self.chanlimit == 0:
|
||||
self.chanlimit = 200 # don't take the piss if it's not limited
|
||||
net_inst_chanlimit = self.netinst.chanlimit
|
||||
if net_inst_chanlimit:
|
||||
if self.chanlimit > net_inst_chanlimit:
|
||||
self.chanlimit = net_inst_chanlimit
|
||||
# warn(f"Chanlimit on {self.net} too high, setting to {self.chanlimit}")
|
||||
|
||||
if not regproc.needToRegister(self.net): # if we need to register, only recheck on auth confirmation
|
||||
if main.config["ChanKeep"]["Enabled"]:
|
||||
self.recheckList()
|
||||
|
||||
def seed_prefix(self, prefix):
|
||||
prefix = prefix.replace(")", "")
|
||||
prefix = prefix.replace("(", "")
|
||||
length = len(prefix)
|
||||
half = int(length / 2)
|
||||
prefixToMode = dict(zip(prefix[half:], prefix[:half]))
|
||||
self.prefix = prefixToMode
|
||||
|
||||
def isupport(self, options):
|
||||
interested = {"CHANLIMIT", "MAXCHANNELS", "PREFIX"}
|
||||
newOptions = {x for x in options if any(y in x for y in interested)}
|
||||
if len(newOptions) == 0:
|
||||
return
|
||||
if not self.isconnected:
|
||||
log("endpoint connected: %s - %i" % (self.net, self.num))
|
||||
self.isconnected = True
|
||||
for i in newOptions:
|
||||
if i.startswith("PREFIX"):
|
||||
if "=" in i:
|
||||
split = i.split("=")
|
||||
if len(split) == 2:
|
||||
prefix = split[1]
|
||||
self.seed_prefix(prefix)
|
||||
elif i.startswith("CHANLIMIT"):
|
||||
if ":" in i:
|
||||
split = i.split(":")
|
||||
if len(split) >= 2:
|
||||
chanlimit = split[1]
|
||||
self.seed_chanlimit(chanlimit)
|
||||
elif i.startswith("MAXCHANNELS"):
|
||||
if "=" in i:
|
||||
split = i.split("=")
|
||||
if len(split) == 2:
|
||||
chanlimit = split[1]
|
||||
self.seed_chanlimit(chanlimit)
|
||||
|
||||
# We need to override these functions as Twisted discards
|
||||
# the hostname and other useful information in the functions
|
||||
# that these call by default
|
||||
def irc_JOIN(self, prefix, params):
|
||||
nick = prefix.split("!")[0]
|
||||
channel = params[-1]
|
||||
if nick == self.nickname:
|
||||
self.joined(channel)
|
||||
else:
|
||||
self.userJoined(prefix, channel)
|
||||
|
||||
def irc_PART(self, prefix, params):
|
||||
nick = prefix.split("!")[0]
|
||||
channel = params[0]
|
||||
if len(params) >= 2:
|
||||
message = params[1]
|
||||
else:
|
||||
message = None
|
||||
if nick == self.nickname:
|
||||
self.left(prefix, channel, message)
|
||||
else:
|
||||
self.userLeft(prefix, channel, message)
|
||||
|
||||
def irc_QUIT(self, prefix, params):
|
||||
# nick = prefix.split("!")[0]
|
||||
self.userQuit(prefix, params[0])
|
||||
|
||||
def irc_NICK(self, prefix, params):
|
||||
nick = prefix.split("!", 1)[0]
|
||||
if nick == self.nickname:
|
||||
self.nickChanged(prefix, params[0])
|
||||
else:
|
||||
self.userRenamed(prefix, params[0])
|
||||
|
||||
def irc_KICK(self, prefix, params):
|
||||
channel = params[0]
|
||||
kicked = params[1]
|
||||
message = params[-1]
|
||||
# Checks on whether it was us that was kicked are done in userKicked
|
||||
self.userKicked(kicked, channel, prefix, message)
|
||||
|
||||
def irc_TOPIC(self, prefix, params):
|
||||
channel = params[0]
|
||||
newtopic = params[1]
|
||||
self.topicUpdated(prefix, channel, newtopic)
|
||||
|
||||
# End of Twisted hackery
|
||||
|
||||
def regPing(self, negativepass=None, reset=True):
|
||||
if not main.config["AutoReg"]:
|
||||
return
|
||||
if self.authenticated:
|
||||
return
|
||||
if not regproc.needToAuth(self.net):
|
||||
self.authenticated = True
|
||||
return
|
||||
sinst = regproc.substitute(self.net, self.num)
|
||||
if not sinst:
|
||||
error(f"regPing() {self.net}: registration ping failed for {self.num}")
|
||||
return
|
||||
if reset:
|
||||
self._negativePass = None
|
||||
|
||||
debug(f"regPing() {self.net} - {self.num}: _negativePass:{self._negativePass} negativepass:{negativepass}")
|
||||
if self._negativePass is None:
|
||||
# We have failed, the blacklisted message has been found
|
||||
if negativepass is False:
|
||||
self._negativePass = False
|
||||
debug(
|
||||
(
|
||||
f"regPing() {self.net} - {self.num} not passed negative:checknegativemsg "
|
||||
f"check, {sinst['checknegativemsg']} present in message"
|
||||
)
|
||||
)
|
||||
return
|
||||
# End of negative output reached with no blacklisted message
|
||||
elif negativepass is True:
|
||||
if self._negativePass is None: # check if it's not failed yet
|
||||
self._negativePass = True
|
||||
debug(
|
||||
(
|
||||
f"regPing() {self.net} - {self.num} passed negative:checkendnegative "
|
||||
f"check, {sinst['checkendnegative']} present in message"
|
||||
)
|
||||
)
|
||||
else:
|
||||
debug(f"regPing() {self.net}: negative registration check - {self.num}")
|
||||
return
|
||||
else:
|
||||
if sinst["negative"]:
|
||||
self.msg(sinst["entity"], sinst["negativemsg"])
|
||||
debug(
|
||||
(
|
||||
f"regPing() {self.net}: sent negativemsg "
|
||||
f"'{sinst['negativemsg']}' to {sinst['entity']} - {self.num}"
|
||||
)
|
||||
)
|
||||
return
|
||||
else:
|
||||
self._negativePass = True # if it's disabled, we still want the next block to run
|
||||
|
||||
# Only run if negativepass has completed, or exempted as above
|
||||
if self._negativePass:
|
||||
if sinst["check"]:
|
||||
if sinst["ping"]:
|
||||
self.msg(sinst["entity"], sinst["pingmsg"])
|
||||
debug(f"regPing() {self.net}: sent ping '{sinst['pingmsg']}' to {sinst['entity']} - {self.num}")
|
||||
return
|
||||
else:
|
||||
self.authenticated = True
|
||||
|
||||
def signedOn(self):
|
||||
log("signed on: %s - %i" % (self.net, self.num))
|
||||
ctime = str(datetime.now().isoformat())
|
||||
sendRelayNotification(
|
||||
{
|
||||
"type": "conn",
|
||||
"net": self.net,
|
||||
"num": self.num,
|
||||
"status": "signedon",
|
||||
"ts": ctime,
|
||||
}
|
||||
)
|
||||
if not self.authenticated:
|
||||
reactor.callLater(10, self.regPing)
|
||||
|
||||
def setup_who_loop(self, channel):
|
||||
# if main.config["Toggles"]["Who"]:
|
||||
lc = LoopingCall(self.who, channel)
|
||||
self._getWho[channel] = lc
|
||||
intrange = main.config["Tweaks"]["Delays"]["WhoRange"]
|
||||
minint = main.config["Tweaks"]["Delays"]["WhoLoop"]
|
||||
interval = randint(minint, minint + intrange)
|
||||
lc.start(interval)
|
||||
|
||||
def joined(self, channel):
|
||||
if channel not in self.channels:
|
||||
self.channels.append(channel)
|
||||
self.names(channel).addCallback(self.got_names)
|
||||
if main.config["Toggles"]["Who"]:
|
||||
reactor.callLater(main.config["Tweaks"]["Delays"]["WhoDelay"], self.setup_who_loop, channel)
|
||||
# lc = LoopingCall(self.who, channel)
|
||||
# self._getWho[channel] = lc
|
||||
# intrange = main.config["Tweaks"]["Delays"]["WhoRange"]
|
||||
# minint = main.config["Tweaks"]["Delays"]["WhoLoop"]
|
||||
# interval = randint(minint, minint + intrange)
|
||||
# lc.start(interval)
|
||||
|
||||
def botLeft(self, channel):
|
||||
if channel in self.channels:
|
||||
self.channels.remove(channel)
|
||||
if channel in self._getWho.keys():
|
||||
lc = self._getWho[channel]
|
||||
lc.stop()
|
||||
del self._getWho[channel]
|
||||
userinfo.delChannels(self.net, [channel]) # < we do not need to deduplicate this
|
||||
# log("Can no longer cover %s, removing records" % channel)# as it will only be matched once --
|
||||
# other bots have different nicknames so
|
||||
|
||||
def left(self, user, channel, message): # even if they saw it, they wouldn't react
|
||||
self.event(type="part", muser=user, channel=channel, msg=message)
|
||||
self.botLeft(channel)
|
||||
|
||||
def userJoined(self, user, channel):
|
||||
self.event(type="join", muser=user, channel=channel)
|
||||
|
||||
def userLeft(self, user, channel, message):
|
||||
self.event(type="part", muser=user, channel=channel, msg=message)
|
||||
|
||||
def userQuit(self, user, quitMessage):
|
||||
self.chanlessEvent({"type": "quit", "muser": user, "msg": quitMessage})
|
||||
|
||||
def userKicked(self, kickee, channel, kicker, message):
|
||||
if kickee.lower() == self.nickname.lower():
|
||||
self.botLeft(channel)
|
||||
self.event(type="kick", muser=kicker, channel=channel, msg=message, user=kickee)
|
||||
|
||||
def chanlessEvent(self, cast):
|
||||
cast["ts"] = str(datetime.now().isoformat())
|
||||
cast["nick"], cast["ident"], cast["host"] = parsen(cast["muser"])
|
||||
if dedup(self.name, cast): # Needs to be kept self.name until the dedup
|
||||
# function is converted to the new net, num
|
||||
# format
|
||||
return # stop right there sir!
|
||||
chans = userinfo.getChanList(self.net, cast["nick"])
|
||||
if chans is None:
|
||||
error("No channels returned for chanless event: %s" % cast)
|
||||
# self.event(**cast) -- no, should NEVER happen
|
||||
return
|
||||
# getChansSingle returns all channels of the user, we only want to use
|
||||
# ones we have common with them
|
||||
realChans = set(chans).intersection(set(self.channels))
|
||||
for i in realChans:
|
||||
cast["channel"] = i
|
||||
self.event(**cast)
|
||||
|
||||
def userRenamed(self, oldname, newname):
|
||||
self.chanlessEvent({"type": "nick", "muser": oldname, "user": newname})
|
||||
|
||||
def topicUpdated(self, user, channel, newTopic):
|
||||
self.event(type="topic", muser=user, channel=channel, msg=newTopic)
|
||||
|
||||
def modeChanged(self, user, channel, toset, modes, args):
|
||||
argList = list(args)
|
||||
modeList = [i for i in modes]
|
||||
for a, m in zip(argList, modeList):
|
||||
self.event(
|
||||
type="mode",
|
||||
muser=user,
|
||||
channel=channel,
|
||||
mode=m,
|
||||
status=toset,
|
||||
modearg=a,
|
||||
)
|
||||
|
||||
|
||||
# TODO: strip out relay functionality
|
||||
class IRCBotFactory(ReconnectingClientFactory):
|
||||
def __init__(self, net, num=None, relayCommands=None, user=None, stage2=None):
|
||||
if net is None:
|
||||
self.num = num
|
||||
self.net = None
|
||||
self.name = "relay - %i" % num
|
||||
self.relay = True
|
||||
else:
|
||||
self.name = net + str(num)
|
||||
self.num = num
|
||||
self.net = net
|
||||
self.relay = False
|
||||
self.client = None
|
||||
self.maxDelay = main.config["Tweaks"]["Delays"]["MaxDelay"]
|
||||
self.initialDelay = main.config["Tweaks"]["Delays"]["InitialDelay"]
|
||||
self.factor = main.config["Tweaks"]["Delays"]["Factor"]
|
||||
self.jitter = main.config["Tweaks"]["Delays"]["Jitter"]
|
||||
|
||||
self.relayCommands, self.user, self.stage2 = relayCommands, user, stage2
|
||||
|
||||
def buildProtocol(self, addr):
|
||||
entry = IRCBot(self.net, self.num)
|
||||
main.IRCPool[self.name] = entry
|
||||
|
||||
self.client = entry
|
||||
return entry
|
||||
|
||||
def clientConnectionLost(self, connector, reason):
|
||||
if not self.relay:
|
||||
userinfo.delChannels(self.net, self.client.channels)
|
||||
if self.client is not None:
|
||||
self.client.isconnected = False
|
||||
self.client.authenticated = False
|
||||
self.client.channels = []
|
||||
error = reason.getErrorMessage()
|
||||
log("%s - %i: connection lost: %s" % (self.net, self.num, error))
|
||||
sendAll("%s - %i: connection lost: %s" % (self.net, self.num, error))
|
||||
ctime = str(datetime.now().isoformat())
|
||||
sendRelayNotification(
|
||||
{
|
||||
"type": "conn",
|
||||
"net": self.net,
|
||||
"num": self.num,
|
||||
"status": "lost",
|
||||
"message": error,
|
||||
"ts": ctime,
|
||||
}
|
||||
)
|
||||
self.retry(connector)
|
||||
# ReconnectingClientFactory.clientConnectionLost(self, connector, reason)
|
||||
|
||||
def clientConnectionFailed(self, connector, reason):
|
||||
if self.client is not None:
|
||||
self.client.isconnected = False
|
||||
self.client.authenticated = False
|
||||
self.client.channels = []
|
||||
error = reason.getErrorMessage()
|
||||
log("%s - %i: connection failed: %s" % (self.net, self.num, error))
|
||||
if not self.relay:
|
||||
sendAll("%s - %s: connection failed: %s" % (self.net, self.num, error))
|
||||
ctime = str(datetime.now().isoformat())
|
||||
sendRelayNotification(
|
||||
{
|
||||
"type": "conn",
|
||||
"net": self.net,
|
||||
"num": self.num,
|
||||
"status": "failed",
|
||||
"message": error,
|
||||
"ts": ctime,
|
||||
}
|
||||
)
|
||||
self.retry(connector)
|
||||
# ReconnectingClientFactory.clientConnectionFailed(self, connector, reason)
|
||||
28
legacy/core/logstash.py
Normal file
28
legacy/core/logstash.py
Normal file
@@ -0,0 +1,28 @@
|
||||
import logging
|
||||
from json import dumps
|
||||
|
||||
import logstash
|
||||
|
||||
import main
|
||||
|
||||
logger = None
|
||||
|
||||
|
||||
def init_logstash():
|
||||
global logger
|
||||
logger = logging.getLogger("ingest")
|
||||
logger.setLevel(logging.INFO)
|
||||
logger.addHandler(
|
||||
logstash.TCPLogstashHandler(
|
||||
main.config["Logstash"]["Host"],
|
||||
int(main.config["Logstash"]["Port"]),
|
||||
version=1,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def sendLogstashNotification(text):
|
||||
if logger is not None:
|
||||
logger.info(dumps(text))
|
||||
return True
|
||||
return False
|
||||
25
legacy/core/parser.py
Normal file
25
legacy/core/parser.py
Normal file
@@ -0,0 +1,25 @@
|
||||
import main
|
||||
from utils.logging.log import warn
|
||||
from utils.logging.send import incorrectUsage, sendFailure, sendInfo, sendSuccess
|
||||
|
||||
|
||||
def parseCommand(addr, authed, data):
|
||||
# call command modules with: (addr, authed, data, spl, success, failure, info, incUsage, length)
|
||||
spl = data.split()
|
||||
if addr in main.connections.keys():
|
||||
obj = main.connections[addr]
|
||||
else:
|
||||
warn("Got connection object with no instance in the address pool")
|
||||
return
|
||||
|
||||
success = lambda data: sendSuccess(addr, data) # noqa: E731
|
||||
failure = lambda data: sendFailure(addr, data) # noqa: E731
|
||||
info = lambda data: sendInfo(addr, data) # noqa: E731
|
||||
|
||||
incUsage = lambda mode: incorrectUsage(addr, mode) # noqa: E731
|
||||
length = len(spl)
|
||||
if spl[0] in main.CommandMap.keys():
|
||||
main.CommandMap[spl[0]](addr, authed, data, obj, spl, success, failure, info, incUsage, length)
|
||||
return
|
||||
incUsage(None)
|
||||
return
|
||||
161
legacy/core/relay.py
Normal file
161
legacy/core/relay.py
Normal file
@@ -0,0 +1,161 @@
|
||||
from json import dumps, loads
|
||||
|
||||
from twisted.internet.protocol import Factory, Protocol
|
||||
|
||||
import main
|
||||
from utils.logging.log import log, warn
|
||||
|
||||
validTypes = [
|
||||
"msg",
|
||||
"notice",
|
||||
"action",
|
||||
"who",
|
||||
"part",
|
||||
"join",
|
||||
"kick",
|
||||
"quit",
|
||||
"nick",
|
||||
"topic",
|
||||
"mode",
|
||||
"conn",
|
||||
"znc",
|
||||
"query",
|
||||
"self",
|
||||
"highlight",
|
||||
]
|
||||
|
||||
|
||||
class Relay(Protocol):
|
||||
def __init__(self, addr):
|
||||
self.addr = addr
|
||||
self.authed = False
|
||||
self.subscriptions = []
|
||||
|
||||
def send(self, data):
|
||||
data += "\r\n"
|
||||
data = data.encode("utf-8", "replace")
|
||||
self.transport.write(data)
|
||||
|
||||
def sendErr(self, data):
|
||||
self.send(dumps({"type": "error", "reason": data}))
|
||||
return
|
||||
|
||||
def sendMsg(self, data):
|
||||
self.send(dumps(data))
|
||||
|
||||
def dataReceived(self, data):
|
||||
data = data.decode("utf-8", "replace")
|
||||
try:
|
||||
parsed = loads(data)
|
||||
except: # noqa: E722
|
||||
self.sendErr("MALFORMED")
|
||||
return
|
||||
if "type" not in parsed.keys():
|
||||
self.sendErr("NOTYPE")
|
||||
return
|
||||
if parsed["type"] == "hello":
|
||||
if set(["key", "hello"]).issubset(set(parsed)):
|
||||
self.handleHello(parsed)
|
||||
else:
|
||||
self.sendErr("WRONGFIELDS")
|
||||
return
|
||||
return
|
||||
elif parsed["type"] == "control":
|
||||
if "subscribe" in parsed.keys():
|
||||
if self.authed:
|
||||
self.handleSubscribe(parsed["subscribe"])
|
||||
return
|
||||
else:
|
||||
self.sendErr("DENIED")
|
||||
return
|
||||
elif "unsubscribe" in parsed.keys():
|
||||
if self.authed:
|
||||
self.handleUnsubscribe(parsed["unsubscribe"])
|
||||
return
|
||||
else:
|
||||
self.sendErr("DENIED")
|
||||
return
|
||||
else:
|
||||
self.sendErr("UNCLEAR")
|
||||
return
|
||||
else:
|
||||
self.sendErr("UNCLEAR")
|
||||
return
|
||||
|
||||
def handleSubscribe(self, lst):
|
||||
if not isinstance(lst, list):
|
||||
self.sendErr("NOTLIST")
|
||||
return
|
||||
for i in lst:
|
||||
if i not in validTypes:
|
||||
self.sendErr("NONEXISTANT")
|
||||
return
|
||||
if i in self.subscriptions:
|
||||
self.sendErr("SUBSCRIBED")
|
||||
return
|
||||
self.subscriptions.append(i)
|
||||
self.sendMsg({"type": "success"})
|
||||
return
|
||||
|
||||
def handleUnubscribe(self, lst):
|
||||
if not isinstance(lst, list):
|
||||
self.sendErr("NOTLIST")
|
||||
return
|
||||
for i in lst:
|
||||
if i not in validTypes:
|
||||
self.sendErr("NONEXISTANT")
|
||||
return
|
||||
if i not in self.subscriptions:
|
||||
self.sendErr("NOTSUBSCRIBED")
|
||||
return
|
||||
del self.subscriptions[i]
|
||||
self.sendMsg({"type": "success"})
|
||||
return
|
||||
|
||||
def handleHello(self, parsed):
|
||||
if parsed["key"] in main.tokens.keys():
|
||||
if (
|
||||
parsed["hello"] == main.tokens[parsed["key"]]["hello"]
|
||||
and main.tokens[parsed["key"]]["usage"] == "relay"
|
||||
):
|
||||
self.sendMsg({"type": "hello", "hello": main.tokens[parsed["key"]]["counter"]})
|
||||
self.authed = True
|
||||
else:
|
||||
self.transport.loseConnection()
|
||||
return
|
||||
else:
|
||||
self.sendErr("NOKEY")
|
||||
return
|
||||
|
||||
def connectionMade(self):
|
||||
log("Relay connection from %s:%s" % (self.addr.host, self.addr.port))
|
||||
# self.send("Greetings.")
|
||||
|
||||
def connectionLost(self, reason):
|
||||
self.authed = False
|
||||
log("Relay connection lost from %s:%s -- %s" % (self.addr.host, self.addr.port, reason.getErrorMessage()))
|
||||
if self.addr in main.relayConnections.keys():
|
||||
del main.relayConnections[self.addr]
|
||||
else:
|
||||
warn("Tried to remove a non-existant relay connection.")
|
||||
|
||||
|
||||
class RelayFactory(Factory):
|
||||
def buildProtocol(self, addr):
|
||||
entry = Relay(addr)
|
||||
main.relayConnections[addr] = entry
|
||||
return entry
|
||||
|
||||
def send(self, addr, data):
|
||||
if addr in main.relayConnections.keys():
|
||||
connection = main.relayConnections[addr]
|
||||
connection.send(data)
|
||||
else:
|
||||
return
|
||||
|
||||
|
||||
def sendRelayNotification(cast):
|
||||
for i in main.relayConnections.keys():
|
||||
if main.relayConnections[i].authed:
|
||||
if cast["type"] in main.relayConnections[i].subscriptions:
|
||||
main.relayConnections[i].send(dumps(cast))
|
||||
55
legacy/core/server.py
Normal file
55
legacy/core/server.py
Normal file
@@ -0,0 +1,55 @@
|
||||
from twisted.internet.protocol import Factory, Protocol
|
||||
|
||||
import main
|
||||
from core.parser import parseCommand
|
||||
from utils.logging.log import log, warn
|
||||
|
||||
|
||||
class Server(Protocol):
|
||||
def __init__(self, addr):
|
||||
self.addr = addr
|
||||
self.authed = False
|
||||
if main.config["UsePassword"] is False:
|
||||
self.authed = True
|
||||
|
||||
def send(self, data):
|
||||
data += "\r\n"
|
||||
data = data.encode("utf-8", "replace")
|
||||
self.transport.write(data)
|
||||
|
||||
def dataReceived(self, data):
|
||||
data = data.decode("utf-8", "replace")
|
||||
# log("Data received from %s:%s -- %s" % (self.addr.host, self.addr.port, repr(data)))
|
||||
if "\n" in data:
|
||||
splitData = [x for x in data.split("\n") if x]
|
||||
if "\n" in data:
|
||||
for i in splitData:
|
||||
parseCommand(self.addr, self.authed, i)
|
||||
return
|
||||
parseCommand(self.addr, self.authed, data)
|
||||
|
||||
def connectionMade(self):
|
||||
log("Connection from %s:%s" % (self.addr.host, self.addr.port))
|
||||
self.send("Greetings.")
|
||||
|
||||
def connectionLost(self, reason):
|
||||
self.authed = False
|
||||
log("Connection lost from %s:%s -- %s" % (self.addr.host, self.addr.port, reason.getErrorMessage()))
|
||||
if self.addr in main.connections.keys():
|
||||
del main.connections[self.addr]
|
||||
else:
|
||||
warn("Tried to remove a non-existant connection.")
|
||||
|
||||
|
||||
class ServerFactory(Factory):
|
||||
def buildProtocol(self, addr):
|
||||
entry = Server(addr)
|
||||
main.connections[addr] = entry
|
||||
return entry
|
||||
|
||||
def send(self, addr, data):
|
||||
if addr in main.connections.keys():
|
||||
connection = main.connections[addr]
|
||||
connection.send(data)
|
||||
else:
|
||||
return
|
||||
Reference in New Issue
Block a user