]> jfr.im git - irc/quakenet/qwebirc.git/blob - qwebirc/ircclient.py
Change the way timeouts are processed entirely to make the entire system more resilient.
[irc/quakenet/qwebirc.git] / qwebirc / ircclient.py
1 import twisted, sys
2 from twisted.words.protocols import irc
3 from twisted.internet import reactor, protocol
4 from twisted.web import resource, server
5 from twisted.protocols import basic
6
7 import hmac, time, config
8 from config import HMACTEMPORAL
9 HMACKEY = hmac.HMAC(key=config.HMACKEY)
10
11 def hmacfn(*args):
12 h = HMACKEY.copy()
13 h.update("%d %s" % (int(time.time() / HMACTEMPORAL), " ".join(args)))
14 return h.hexdigest()
15
16 class QWebIRCClient(basic.LineReceiver):
17 delimiter = "\n"
18 def __init__(self, *args, **kwargs):
19 self.__nickname = "(unregistered)"
20
21 def dataReceived(self, data):
22 basic.LineReceiver.dataReceived(self, data.replace("\r", ""))
23
24 def lineReceived(self, line):
25 line = irc.lowDequote(line)
26 try:
27 line = line.decode("utf-8")
28 except UnicodeDecodeError:
29 line = line.decode("iso-8859-1", "ignore")
30
31 try:
32 prefix, command, params = irc.parsemsg(line)
33 self.handleCommand(command, prefix, params)
34 except irc.IRCBadMessage:
35 self.badMessage(line, *sys.exc_info())
36
37 if command == "001":
38 self.__nickname = params[0]
39
40 if self.__perform is not None:
41 for x in self.__perform:
42 self.write(x)
43 self.__perform = None
44 elif command == "NICK":
45 nick = prefix.split("!", 1)[0]
46 if nick == self.__nickname:
47 self.__nickname = params[0]
48
49 def badMessage(self, args):
50 self("badmessage", args)
51
52 def handleCommand(self, command, prefix, params):
53 self("c", command, prefix, params)
54
55 def __call__(self, *args):
56 self.factory.publisher.event(args)
57
58 def write(self, data):
59 self.transport.write("%s\r\n" % irc.lowQuote(data.encode("utf-8")))
60
61 def connectionMade(self):
62 basic.LineReceiver.connectionMade(self)
63
64 self.lastError = None
65 f = self.factory.ircinit
66 nick, ident, ip, realname = f["nick"], f["ident"], f["ip"], f["realname"]
67 self.__nickname = nick
68 self.__perform = f.get("perform")
69
70 hmac = hmacfn(ident, ip)
71 self.write("USER %s bleh bleh %s %s :%s" % (ident, ip, hmac, realname))
72 self.write("NICK %s" % nick)
73
74 self.factory.client = self
75 self("connect")
76
77 def __str__(self):
78 return "<QWebIRCClient: %s!%s@%s>" % (self.__nickname, self.factory.ircinit["ident"], self.factory.ircinit["ip"])
79
80 def connectionLost(self, reason):
81 if self.lastError:
82 self.disconnect("Connection to IRC server lost: %s" % self.lastError)
83 else:
84 self.disconnect("Connection to IRC server lost.")
85 self.factory.client = None
86 basic.LineReceiver.connectionLost(self, reason)
87
88 def error(self, message):
89 self.lastError = message
90 self.write("QUIT :qwebirc exception: %s" % message)
91 self.transport.loseConnection()
92
93 def disconnect(self, reason):
94 self("disconnect", reason)
95 self.factory.publisher.disconnect()
96
97 class QWebIRCFactory(protocol.ClientFactory):
98 protocol = QWebIRCClient
99 def __init__(self, publisher, **kwargs):
100 self.client = None
101 self.publisher = publisher
102 self.ircinit = kwargs
103
104 def write(self, data):
105 self.client.write(data)
106
107 def error(self, reason):
108 self.client.error(reason)
109
110 def clientConnectionFailed(self, connector, reason):
111 protocol.ClientFactory.clientConnectionFailed(self, connector, reason)
112 self.publisher.event(["disconnect", "Connection to IRC server failed."])
113 self.publisher.disconnect()
114
115 def createIRC(*args, **kwargs):
116 f = QWebIRCFactory(*args, **kwargs)
117 reactor.connectTCP(config.IRCSERVER, config.IRCPORT, f)
118 return f
119
120 if __name__ == "__main__":
121 e = createIRC(lambda x: 2, nick="slug__moo", ident="mooslug", ip="1.2.3.6", realname="mooooo")
122 reactor.run()