]> jfr.im git - irc/quakenet/qwebirc.git/blob - qwebirc/ircclient.py
Merge.
[irc/quakenet/qwebirc.git] / qwebirc / ircclient.py
1 import twisted, sys, codecs, traceback
2 from twisted.words.protocols import irc
3 from twisted.internet import reactor, protocol, abstract
4 from twisted.web import resource, server
5 from twisted.protocols import basic
6 from twisted.names.client import Resolver
7 import hmac, time, config, random, qwebirc.config_options as config_options
8 from config import HMACTEMPORAL
9
10 if config.get("CONNECTION_RESOLVER"):
11 CONNECTION_RESOLVER = Resolver(servers=config.get("CONNECTION_RESOLVER"))
12 else:
13 CONNECTION_RESOLVER = None
14
15 if hasattr(config, "WEBIRC_MODE") and config.WEBIRC_MODE == "hmac":
16 HMACKEY = hmac.HMAC(key=config.HMACKEY)
17
18 def hmacfn(*args):
19 h = HMACKEY.copy()
20 h.update("%d %s" % (int(time.time() / HMACTEMPORAL), " ".join(args)))
21 return h.hexdigest()
22
23 def utf8_iso8859_1(data, table=dict((x, x.decode("iso-8859-1")) for x in map(chr, range(0, 256)))):
24 return (table.get(data.object[data.start]), data.start+1)
25
26 codecs.register_error("mixed-iso-8859-1", utf8_iso8859_1)
27
28 def irc_decode(x):
29 try:
30 return x.decode("utf-8", "mixed-iso-8859-1")
31 except UnicodeDecodeError:
32 return x.decode("iso-8859-1", "ignore")
33
34 class QWebIRCClient(basic.LineReceiver):
35 delimiter = "\n"
36 def __init__(self, *args, **kwargs):
37 self.__nickname = "(unregistered)"
38
39 def dataReceived(self, data):
40 basic.LineReceiver.dataReceived(self, data.replace("\r", ""))
41
42 def lineReceived(self, line):
43 line = irc_decode(irc.lowDequote(line))
44
45 try:
46 prefix, command, params = irc.parsemsg(line)
47 self.handleCommand(command, prefix, params)
48 except irc.IRCBadMessage:
49 # emit and ignore
50 traceback.print_exc()
51 return
52
53 if command == "001":
54 self.__nickname = params[0]
55
56 if self.__perform is not None:
57 for x in self.__perform:
58 self.write(x)
59 self.__perform = None
60 elif command == "NICK":
61 nick = prefix.split("!", 1)[0]
62 if nick == self.__nickname:
63 self.__nickname = params[0]
64
65 def handleCommand(self, command, prefix, params):
66 self("c", command, prefix, params)
67
68 def __call__(self, *args):
69 self.factory.publisher.event(args)
70
71 def write(self, data):
72 self.transport.write("%s\r\n" % irc.lowQuote(data.encode("utf-8")))
73
74 def connectionMade(self):
75 basic.LineReceiver.connectionMade(self)
76
77 self.lastError = None
78 f = self.factory.ircinit
79 nick, ident, ip, realname, hostname, pass_ = f["nick"], f["ident"], f["ip"], f["realname"], f["hostname"], f.get("password")
80 self.__nickname = nick
81 self.__perform = f.get("perform")
82
83 if not hasattr(config, "WEBIRC_MODE"):
84 self.write("USER %s bleh bleh %s :%s" % (ident, ip, realname))
85 elif config.WEBIRC_MODE == "hmac":
86 hmac = hmacfn(ident, ip)
87 self.write("USER %s bleh bleh %s %s :%s" % (ident, ip, hmac, realname))
88 elif config.WEBIRC_MODE == "webirc":
89 self.write("WEBIRC %s qwebirc %s %s" % (config.WEBIRC_PASSWORD, hostname, ip))
90 self.write("USER %s bleh %s :%s" % (ident, ip, realname))
91 elif config.WEBIRC_MODE == "cgiirc":
92 self.write("PASS %s_%s_%s" % (config.CGIIRC_STRING, ip, hostname))
93 self.write("USER %s bleh %s :%s" % (ident, ip, realname))
94 elif config.WEBIRC_MODE == config_options.WEBIRC_REALNAME or config.WEBIRC_MODE is None: # last bit is legacy
95 if ip == hostname:
96 dispip = ip
97 else:
98 dispip = "%s/%s" % (hostname, ip)
99
100 self.write("USER %s bleh bleh :%s - %s" % (ident, dispip, realname))
101
102 if pass_ is not None:
103 self.write("PASS :%s" % pass_)
104 self.write("NICK %s" % nick)
105
106 self.factory.client = self
107 self("connect")
108
109 def __str__(self):
110 return "<QWebIRCClient: %s!%s@%s>" % (self.__nickname, self.factory.ircinit["ident"], self.factory.ircinit["ip"])
111
112 def connectionLost(self, reason):
113 if self.lastError:
114 self.disconnect("Connection to IRC server lost: %s" % self.lastError)
115 else:
116 self.disconnect("Connection to IRC server lost.")
117 self.factory.client = None
118 basic.LineReceiver.connectionLost(self, reason)
119
120 def error(self, message):
121 self.lastError = message
122 self.write("QUIT :qwebirc exception: %s" % message)
123 self.transport.loseConnection()
124
125 def disconnect(self, reason):
126 self("disconnect", reason)
127 self.factory.publisher.disconnect()
128
129 class QWebIRCFactory(protocol.ClientFactory):
130 protocol = QWebIRCClient
131 def __init__(self, publisher, **kwargs):
132 self.client = None
133 self.publisher = publisher
134 self.ircinit = kwargs
135
136 def write(self, data):
137 self.client.write(data)
138
139 def error(self, reason):
140 self.client.error(reason)
141
142 def clientConnectionFailed(self, connector, reason):
143 protocol.ClientFactory.clientConnectionFailed(self, connector, reason)
144 self.publisher.event(["disconnect", "Connection to IRC server failed."])
145 self.publisher.disconnect()
146
147 def createIRC(*args, **kwargs):
148 f = QWebIRCFactory(*args, **kwargs)
149
150 tcpkwargs = {}
151 if hasattr(config, "OUTGOING_IP"):
152 tcpkwargs["bindAddress"] = (config.OUTGOING_IP, 0)
153
154 if CONNECTION_RESOLVER is None:
155 reactor.connectTCP(config.IRCSERVER, config.IRCPORT, f, **tcpkwargs)
156 return f
157
158 def callback(result):
159 name, port = random.choice(sorted((str(x.payload.target), x.payload.port) for x in result[0]))
160 reactor.connectTCP(name, port, f, **tcpkwargs)
161 def errback(err):
162 f.clientConnectionFailed(None, err) # None?!
163
164 d = CONNECTION_RESOLVER.lookupService(config.IRCSERVER, (1, 3, 11))
165 d.addCallbacks(callback, errback)
166 return f
167
168 if __name__ == "__main__":
169 e = createIRC(lambda x: 2, nick="slug__moo", ident="mooslug", ip="1.2.3.6", realname="mooooo", hostname="1.2.3.4")
170 reactor.run()