1# Copyright (c) Twisted Matrix Laboratories. 2# See LICENSE for details. 3 4""" 5IRC support for Instance Messenger. 6""" 7 8from zope.interface import implementer 9 10from twisted.internet import defer, protocol, reactor 11from twisted.internet.defer import succeed 12from twisted.words.im import basesupport, interfaces, locals 13from twisted.words.im.locals import ONLINE 14from twisted.words.protocols import irc 15 16 17class IRCPerson(basesupport.AbstractPerson): 18 def imperson_whois(self): 19 if self.account.client is None: 20 raise locals.OfflineError 21 self.account.client.sendLine("WHOIS %s" % self.name) 22 23 ### interface impl 24 def isOnline(self): 25 return ONLINE 26 27 def getStatus(self): 28 return ONLINE 29 30 def setStatus(self, status): 31 self.status = status 32 self.chat.getContactsList().setContactStatus(self) 33 34 def sendMessage(self, text, meta=None): 35 if self.account.client is None: 36 raise locals.OfflineError 37 for line in text.split("\n"): 38 if meta and meta.get("style", None) == "emote": 39 self.account.client.ctcpMakeQuery(self.name, [("ACTION", line)]) 40 else: 41 self.account.client.msg(self.name, line) 42 return succeed(text) 43 44 45@implementer(interfaces.IGroup) 46class IRCGroup(basesupport.AbstractGroup): 47 def imgroup_testAction(self): 48 pass 49 50 def imtarget_kick(self, target): 51 if self.account.client is None: 52 raise locals.OfflineError 53 reason = "for great justice!" 54 self.account.client.sendLine(f"KICK #{self.name} {target.name} :{reason}") 55 56 ### Interface Implementation 57 def setTopic(self, topic): 58 if self.account.client is None: 59 raise locals.OfflineError 60 self.account.client.topic(self.name, topic) 61 62 def sendGroupMessage(self, text, meta={}): 63 if self.account.client is None: 64 raise locals.OfflineError 65 if meta and meta.get("style", None) == "emote": 66 self.account.client.ctcpMakeQuery(self.name, [("ACTION", text)]) 67 return succeed(text) 68 # standard shmandard, clients don't support plain escaped newlines! 69 for line in text.split("\n"): 70 self.account.client.say(self.name, line) 71 return succeed(text) 72 73 def leave(self): 74 if self.account.client is None: 75 raise locals.OfflineError 76 self.account.client.leave(self.name) 77 self.account.client.getGroupConversation(self.name, 1) 78 79 80class IRCProto(basesupport.AbstractClientMixin, irc.IRCClient): 81 def __init__(self, account, chatui, logonDeferred=None): 82 basesupport.AbstractClientMixin.__init__(self, account, chatui, logonDeferred) 83 self._namreplies = {} 84 self._ingroups = {} 85 self._groups = {} 86 self._topics = {} 87 88 def getGroupConversation(self, name, hide=0): 89 name = name.lower() 90 return self.chat.getGroupConversation( 91 self.chat.getGroup(name, self), stayHidden=hide 92 ) 93 94 def getPerson(self, name): 95 return self.chat.getPerson(name, self) 96 97 def connectionMade(self): 98 # XXX: Why do I duplicate code in IRCClient.register? 99 try: 100 self.performLogin = True 101 self.nickname = self.account.username 102 self.password = self.account.password 103 self.realname = "Twisted-IM user" 104 105 irc.IRCClient.connectionMade(self) 106 107 for channel in self.account.channels: 108 self.joinGroup(channel) 109 self.account._isOnline = 1 110 if self._logonDeferred is not None: 111 self._logonDeferred.callback(self) 112 self.chat.getContactsList() 113 except BaseException: 114 import traceback 115 116 traceback.print_exc() 117 118 def setNick(self, nick): 119 self.name = nick 120 self.accountName = "%s (IRC)" % nick 121 irc.IRCClient.setNick(self, nick) 122 123 def kickedFrom(self, channel, kicker, message): 124 """ 125 Called when I am kicked from a channel. 126 """ 127 return self.chat.getGroupConversation(self.chat.getGroup(channel[1:], self), 1) 128 129 def userKicked(self, kickee, channel, kicker, message): 130 pass 131 132 def noticed(self, username, channel, message): 133 self.privmsg(username, channel, message, {"dontAutoRespond": 1}) 134 135 def privmsg(self, username, channel, message, metadata=None): 136 if metadata is None: 137 metadata = {} 138 username = username.split("!", 1)[0] 139 if username == self.name: 140 return 141 if channel[0] == "#": 142 group = channel[1:] 143 self.getGroupConversation(group).showGroupMessage( 144 username, message, metadata 145 ) 146 return 147 self.chat.getConversation(self.getPerson(username)).showMessage( 148 message, metadata 149 ) 150 151 def action(self, username, channel, emote): 152 username = username.split("!", 1)[0] 153 if username == self.name: 154 return 155 meta = {"style": "emote"} 156 if channel[0] == "#": 157 group = channel[1:] 158 self.getGroupConversation(group).showGroupMessage(username, emote, meta) 159 return 160 self.chat.getConversation(self.getPerson(username)).showMessage(emote, meta) 161 162 def irc_RPL_NAMREPLY(self, prefix, params): 163 """ 164 RPL_NAMREPLY 165 >> NAMES #bnl 166 << :Arlington.VA.US.Undernet.Org 353 z3p = #bnl :pSwede Dan-- SkOyg AG 167 """ 168 group = params[2][1:].lower() 169 users = params[3].split() 170 for ui in range(len(users)): 171 while users[ui][0] in ["@", "+"]: # channel modes 172 users[ui] = users[ui][1:] 173 if group not in self._namreplies: 174 self._namreplies[group] = [] 175 self._namreplies[group].extend(users) 176 for nickname in users: 177 try: 178 self._ingroups[nickname].append(group) 179 except BaseException: 180 self._ingroups[nickname] = [group] 181 182 def irc_RPL_ENDOFNAMES(self, prefix, params): 183 group = params[1][1:] 184 self.getGroupConversation(group).setGroupMembers( 185 self._namreplies[group.lower()] 186 ) 187 del self._namreplies[group.lower()] 188 189 def irc_RPL_TOPIC(self, prefix, params): 190 self._topics[params[1][1:]] = params[2] 191 192 def irc_333(self, prefix, params): 193 group = params[1][1:] 194 self.getGroupConversation(group).setTopic(self._topics[group], params[2]) 195 del self._topics[group] 196 197 def irc_TOPIC(self, prefix, params): 198 nickname = prefix.split("!")[0] 199 group = params[0][1:] 200 topic = params[1] 201 self.getGroupConversation(group).setTopic(topic, nickname) 202 203 def irc_JOIN(self, prefix, params): 204 nickname = prefix.split("!")[0] 205 group = params[0][1:].lower() 206 if nickname != self.nickname: 207 try: 208 self._ingroups[nickname].append(group) 209 except BaseException: 210 self._ingroups[nickname] = [group] 211 self.getGroupConversation(group).memberJoined(nickname) 212 213 def irc_PART(self, prefix, params): 214 nickname = prefix.split("!")[0] 215 group = params[0][1:].lower() 216 if nickname != self.nickname: 217 if group in self._ingroups[nickname]: 218 self._ingroups[nickname].remove(group) 219 self.getGroupConversation(group).memberLeft(nickname) 220 221 def irc_QUIT(self, prefix, params): 222 nickname = prefix.split("!")[0] 223 if nickname in self._ingroups: 224 for group in self._ingroups[nickname]: 225 self.getGroupConversation(group).memberLeft(nickname) 226 self._ingroups[nickname] = [] 227 228 def irc_NICK(self, prefix, params): 229 fromNick = prefix.split("!")[0] 230 toNick = params[0] 231 if fromNick not in self._ingroups: 232 return 233 for group in self._ingroups[fromNick]: 234 self.getGroupConversation(group).memberChangedNick(fromNick, toNick) 235 self._ingroups[toNick] = self._ingroups[fromNick] 236 del self._ingroups[fromNick] 237 238 def irc_unknown(self, prefix, command, params): 239 pass 240 241 # GTKIM calls 242 def joinGroup(self, name): 243 self.join(name) 244 self.getGroupConversation(name) 245 246 247@implementer(interfaces.IAccount) 248class IRCAccount(basesupport.AbstractAccount): 249 gatewayType = "IRC" 250 251 _groupFactory = IRCGroup 252 _personFactory = IRCPerson 253 254 def __init__( 255 self, accountName, autoLogin, username, password, host, port, channels="" 256 ): 257 basesupport.AbstractAccount.__init__( 258 self, accountName, autoLogin, username, password, host, port 259 ) 260 self.channels = [channel.strip() for channel in channels.split(",")] 261 if self.channels == [""]: 262 self.channels = [] 263 264 def _startLogOn(self, chatui): 265 logonDeferred = defer.Deferred() 266 cc = protocol.ClientCreator(reactor, IRCProto, self, chatui, logonDeferred) 267 d = cc.connectTCP(self.host, self.port) 268 d.addErrback(logonDeferred.errback) 269 return logonDeferred 270 271 def logOff(self): 272 # IAccount.logOff 273 pass 274