# Twisted/Klein imports from twisted.logger import Logger from twisted.words.protocols import irc from twisted.internet import protocol, reactor, ssl # Other library imports from json import dumps # Project imports from settings import settings class IRCBot(irc.IRCClient): def __init__(self, log): self.log = log self.nickname = settings.IRC.Nick self.password = settings.IRC.Pass self.realname = self.nickname self.username = self.nickname # Don't give away information about our client self.userinfo = None self.fingerReply = None self.versionName = None self.sourceURL = None self.lineRate = None # Don't throttle messages, we may need to send a lot self.prefix = settings.IRC.Prefix self.admins = (settings.IRC.Admins).split("\n") self.highlight = (settings.IRC.Highlight).split("\n") self.channel = settings.IRC.Channel def set_agora(self, agora): self.agora = agora def parse(self, user, host, channel, msg): """ Simple handler for IRC commands. :param user: full user string with host :param host: user's hostname :param channel: channel the message was received on :param msg: the message """ spl = msg.split() # nick = user.split("!")[0] cmd = spl[0] if cmd == "trades" and host in self.admins: # Get details of open trades and post on IRC trades = self.agora.dashboard() for trade_id in trades: self.msg(channel, trade_id) elif cmd == "create" and host in self.admins and len(spl) == 3: # Post an ad on AgoraDesk with the given country and currency code posted = self.agora.create_ad(spl[1], spl[2]) self.msg(channel, dumps(posted)) elif cmd == "messages" and host in self.admins and len(spl) == 1: # Get all messages for all open trades messages = self.agora.get_all_messages() for message_id in messages: for message in messages[message_id]: self.msg(channel, f"{message_id}: {message}") self.msg(channel, "---") # self.msg(channel, dumps(messages)) elif cmd == "messages" and host in self.admins and len(spl) == 2: # Get all messages for a given trade messages = self.agora.get_messages(spl[1]) for message in messages: self.msg(channel, f"{spl[1]}: {message}") elif cmd == "dist" and host in self.admins: # Distribute out our ad to all countries in the config rtrn = self.agora.dist_countries() self.msg(channel, dumps(rtrn)) elif cmd == "find" and host in self.admins and len(spl) == 3: # Find a transaction received by Revolut with the given reference and amount try: int(spl[2]) except ValueError: self.msg(channel, "Amount is not an integer") rtrn = self.tx.find_tx(spl[1], spl[2]) if rtrn == "AMOUNT_INVALID": self.msg(channel, "Reference found but amount invalid") elif not rtrn: self.msg(channel, "Reference not found") else: return dumps(rtrn) def signedOn(self): """ Called when we have signed on to IRC. Join our channel. """ self.log.info("Signed on as %s" % (self.nickname)) self.join(self.channel) def joined(self, channel): """ Called when we have joined a channel. Setup the Agora LoopingCall to get trades. This is here to ensure the IRC client is initialised enough to send the trades. :param channel: channel we joined """ self.agora.setup_loop() self.log.info("Joined channel %s" % (channel)) def privmsg(self, user, channel, msg): """ Called on received PRIVMSGs. Pass through identified commands to the parse function. :param user: full user string with host :param channel: channel the message was received on :param msg: the message """ nick = user.split("!")[0] if channel == self.nickname: channel = nick host = user.split("!")[1] host = host.split("@")[1] ident = user.split("!")[1] ident = ident.split("@")[0] self.log.info("(%s) %s: %s" % (channel, user, msg)) if msg[0] == self.prefix: if len(msg) > 1: if msg.split()[0] != "!": self.parse(user, host, channel, msg[1:]) def noticed(self, user, channel, msg): """ Called on received NOTICEs. :param user: full user string with host :param channel: channel the notice was received on :param msg: the message """ nick = user.split("!")[0] if channel == self.nickname: channel = nick # self.log.info("[%s] %s: %s" % (channel, user, msg)) class IRCBotFactory(protocol.ClientFactory): def __init__(self): self.log = Logger("irc") def set_agora(self, agora): self.agora = agora def buildProtocol(self, addr): """ Custom override for the Twisted buildProtocol so we can access the Protocol instance. Passes through the Agora instance to IRC. :return: IRCBot Protocol instance """ prcol = IRCBot(self.log) self.client = prcol self.client.set_agora(self.agora) return prcol def clientConnectionLost(self, connector, reason): """ Called when connection to IRC server lost. Reconnect. :param connector: connector object :param reason: reason connection lost """ self.log.error("Lost connection (%s), reconnecting" % (reason)) connector.connect() def clientConnectionFailed(self, connector, reason): """ Called when connection to IRC server failed. Reconnect. :param connector: connector object :param reason: reason connection failed """ self.log.error("Could not connect: %s" % (reason)) connector.connect() def bot(): # Load the certificates context = ssl.DefaultOpenSSLContextFactory(settings.IRC.Cert, settings.IRC.Cert) # Define the factory instance factory = IRCBotFactory() reactor.connectSSL(settings.IRC.Host, int(settings.IRC.Port), factory, context) return factory