| 1 |
|
|---|
| 2 |
|
|---|
| 3 |
|
|---|
| 4 |
|
|---|
| 5 |
""" |
|---|
| 6 |
A module that needs a better name. |
|---|
| 7 |
|
|---|
| 8 |
Implements new cred things for words. |
|---|
| 9 |
|
|---|
| 10 |
How does this thing work? |
|---|
| 11 |
|
|---|
| 12 |
- Network connection on some port expecting to speak some protocol |
|---|
| 13 |
|
|---|
| 14 |
- Protocol-specific authentication, resulting in some kind of credentials object |
|---|
| 15 |
|
|---|
| 16 |
- twisted.cred.portal login using those credentials for the interface |
|---|
| 17 |
IUser and with something implementing IChatClient as the mind |
|---|
| 18 |
|
|---|
| 19 |
- successful login results in an IUser avatar the protocol can call |
|---|
| 20 |
methods on, and state added to the realm such that the mind will have |
|---|
| 21 |
methods called on it as is necessary |
|---|
| 22 |
|
|---|
| 23 |
- protocol specific actions lead to calls onto the avatar; remote events |
|---|
| 24 |
lead to calls onto the mind |
|---|
| 25 |
|
|---|
| 26 |
- protocol specific hangup, realm is notified, user is removed from active |
|---|
| 27 |
play, the end. |
|---|
| 28 |
""" |
|---|
| 29 |
|
|---|
| 30 |
from time import time, ctime |
|---|
| 31 |
|
|---|
| 32 |
from zope.interface import implements |
|---|
| 33 |
|
|---|
| 34 |
from twisted.words import iwords, ewords |
|---|
| 35 |
|
|---|
| 36 |
from twisted.python.components import registerAdapter |
|---|
| 37 |
from twisted.cred import portal, credentials, error as ecred |
|---|
| 38 |
from twisted.spread import pb |
|---|
| 39 |
from twisted.words.protocols import irc |
|---|
| 40 |
from twisted.internet import defer, protocol |
|---|
| 41 |
from twisted.python import log, failure, reflect |
|---|
| 42 |
from twisted import copyright |
|---|
| 43 |
|
|---|
| 44 |
|
|---|
| 45 |
class Group(object): |
|---|
| 46 |
implements(iwords.IGroup) |
|---|
| 47 |
|
|---|
| 48 |
def __init__(self, name): |
|---|
| 49 |
self.name = name |
|---|
| 50 |
self.users = {} |
|---|
| 51 |
self.meta = { |
|---|
| 52 |
"topic": "", |
|---|
| 53 |
"topic_author": "", |
|---|
| 54 |
} |
|---|
| 55 |
|
|---|
| 56 |
|
|---|
| 57 |
def _ebUserCall(self, err, p): |
|---|
| 58 |
return failure.Failure(Exception(p, err)) |
|---|
| 59 |
|
|---|
| 60 |
|
|---|
| 61 |
def _cbUserCall(self, results): |
|---|
| 62 |
for (success, result) in results: |
|---|
| 63 |
if not success: |
|---|
| 64 |
user, err = result.value |
|---|
| 65 |
self.remove(user, err.getErrorMessage()) |
|---|
| 66 |
|
|---|
| 67 |
|
|---|
| 68 |
def add(self, user): |
|---|
| 69 |
assert iwords.IChatClient.providedBy(user), "%r is not a chat client" % (user,) |
|---|
| 70 |
if user.name not in self.users: |
|---|
| 71 |
additions = [] |
|---|
| 72 |
self.users[user.name] = user |
|---|
| 73 |
for p in self.users.itervalues(): |
|---|
| 74 |
if p is not user: |
|---|
| 75 |
d = defer.maybeDeferred(p.userJoined, self, user) |
|---|
| 76 |
d.addErrback(self._ebUserCall, p=p) |
|---|
| 77 |
additions.append(d) |
|---|
| 78 |
defer.DeferredList(additions).addCallback(self._cbUserCall) |
|---|
| 79 |
return defer.succeed(None) |
|---|
| 80 |
|
|---|
| 81 |
|
|---|
| 82 |
def remove(self, user, reason=None): |
|---|
| 83 |
assert reason is None or isinstance(reason, unicode) |
|---|
| 84 |
try: |
|---|
| 85 |
del self.users[user.name] |
|---|
| 86 |
except KeyError: |
|---|
| 87 |
pass |
|---|
| 88 |
else: |
|---|
| 89 |
removals = [] |
|---|
| 90 |
for p in self.users.itervalues(): |
|---|
| 91 |
if p is not user: |
|---|
| 92 |
d = defer.maybeDeferred(p.userLeft, self, user, reason) |
|---|
| 93 |
d.addErrback(self._ebUserCall, p=p) |
|---|
| 94 |
removals.append(d) |
|---|
| 95 |
defer.DeferredList(removals).addCallback(self._cbUserCall) |
|---|
| 96 |
return defer.succeed(None) |
|---|
| 97 |
|
|---|
| 98 |
|
|---|
| 99 |
def size(self): |
|---|
| 100 |
return defer.succeed(len(self.users)) |
|---|
| 101 |
|
|---|
| 102 |
|
|---|
| 103 |
def receive(self, sender, recipient, message): |
|---|
| 104 |
assert recipient is self |
|---|
| 105 |
receives = [] |
|---|
| 106 |
for p in self.users.itervalues(): |
|---|
| 107 |
if p is not sender: |
|---|
| 108 |
d = defer.maybeDeferred(p.receive, sender, self, message) |
|---|
| 109 |
d.addErrback(self._ebUserCall, p=p) |
|---|
| 110 |
receives.append(d) |
|---|
| 111 |
defer.DeferredList(receives).addCallback(self._cbUserCall) |
|---|
| 112 |
return defer.succeed(None) |
|---|
| 113 |
|
|---|
| 114 |
|
|---|
| 115 |
def setMetadata(self, meta): |
|---|
| 116 |
self.meta = meta |
|---|
| 117 |
sets = [] |
|---|
| 118 |
for p in self.users.itervalues(): |
|---|
| 119 |
d = defer.maybeDeferred(p.groupMetaUpdate, self, meta) |
|---|
| 120 |
d.addErrback(self._ebUserCall, p=p) |
|---|
| 121 |
sets.append(d) |
|---|
| 122 |
defer.DeferredList(sets).addCallback(self._cbUserCall) |
|---|
| 123 |
return defer.succeed(None) |
|---|
| 124 |
|
|---|
| 125 |
|
|---|
| 126 |
def iterusers(self): |
|---|
| 127 |
|
|---|
| 128 |
return iter(self.users.values()) |
|---|
| 129 |
|
|---|
| 130 |
|
|---|
| 131 |
class User(object): |
|---|
| 132 |
implements(iwords.IUser) |
|---|
| 133 |
|
|---|
| 134 |
realm = None |
|---|
| 135 |
mind = None |
|---|
| 136 |
|
|---|
| 137 |
def __init__(self, name): |
|---|
| 138 |
self.name = name |
|---|
| 139 |
self.groups = [] |
|---|
| 140 |
self.lastMessage = time() |
|---|
| 141 |
|
|---|
| 142 |
|
|---|
| 143 |
def loggedIn(self, realm, mind): |
|---|
| 144 |
self.realm = realm |
|---|
| 145 |
self.mind = mind |
|---|
| 146 |
self.signOn = time() |
|---|
| 147 |
|
|---|
| 148 |
|
|---|
| 149 |
def join(self, group): |
|---|
| 150 |
def cbJoin(result): |
|---|
| 151 |
self.groups.append(group) |
|---|
| 152 |
return result |
|---|
| 153 |
return group.add(self.mind).addCallback(cbJoin) |
|---|
| 154 |
|
|---|
| 155 |
|
|---|
| 156 |
def leave(self, group, reason=None): |
|---|
| 157 |
def cbLeave(result): |
|---|
| 158 |
self.groups.remove(group) |
|---|
| 159 |
return result |
|---|
| 160 |
return group.remove(self.mind, reason).addCallback(cbLeave) |
|---|
| 161 |
|
|---|
| 162 |
|
|---|
| 163 |
def send(self, recipient, message): |
|---|
| 164 |
self.lastMessage = time() |
|---|
| 165 |
return recipient.receive(self.mind, recipient, message) |
|---|
| 166 |
|
|---|
| 167 |
|
|---|
| 168 |
def itergroups(self): |
|---|
| 169 |
return iter(self.groups) |
|---|
| 170 |
|
|---|
| 171 |
|
|---|
| 172 |
def logout(self): |
|---|
| 173 |
for g in self.groups[:]: |
|---|
| 174 |
self.leave(g) |
|---|
| 175 |
|
|---|
| 176 |
|
|---|
| 177 |
NICKSERV = 'NickServ!NickServ@services' |
|---|
| 178 |
|
|---|
| 179 |
|
|---|
| 180 |
class IRCUser(irc.IRC): |
|---|
| 181 |
""" |
|---|
| 182 |
Protocol instance representing an IRC user connected to the server. |
|---|
| 183 |
""" |
|---|
| 184 |
implements(iwords.IChatClient) |
|---|
| 185 |
|
|---|
| 186 |
|
|---|
| 187 |
groups = None |
|---|
| 188 |
|
|---|
| 189 |
|
|---|
| 190 |
logout = None |
|---|
| 191 |
|
|---|
| 192 |
|
|---|
| 193 |
avatar = None |
|---|
| 194 |
|
|---|
| 195 |
|
|---|
| 196 |
realm = None |
|---|
| 197 |
|
|---|
| 198 |
|
|---|
| 199 |
encoding = 'utf-8' |
|---|
| 200 |
|
|---|
| 201 |
|
|---|
| 202 |
def connectionMade(self): |
|---|
| 203 |
self.irc_PRIVMSG = self.irc_NICKSERV_PRIVMSG |
|---|
| 204 |
self.realm = self.factory.realm |
|---|
| 205 |
self.hostname = self.realm.name |
|---|
| 206 |
|
|---|
| 207 |
|
|---|
| 208 |
def connectionLost(self, reason): |
|---|
| 209 |
if self.logout is not None: |
|---|
| 210 |
self.logout() |
|---|
| 211 |
self.avatar = None |
|---|
| 212 |
|
|---|
| 213 |
|
|---|
| 214 |
|
|---|
| 215 |
def sendMessage(self, command, *parameter_list, **kw): |
|---|
| 216 |
if not kw.has_key('prefix'): |
|---|
| 217 |
kw['prefix'] = self.hostname |
|---|
| 218 |
if not kw.has_key('to'): |
|---|
| 219 |
kw['to'] = self.name.encode(self.encoding) |
|---|
| 220 |
|
|---|
| 221 |
arglist = [self, command, kw['to']] + list(parameter_list) |
|---|
| 222 |
irc.IRC.sendMessage(*arglist, **kw) |
|---|
| 223 |
|
|---|
| 224 |
|
|---|
| 225 |
|
|---|
| 226 |
def userJoined(self, group, user): |
|---|
| 227 |
self.join( |
|---|
| 228 |
"%s!%s@%s" % (user.name, user.name, self.hostname), |
|---|
| 229 |
'#' + group.name) |
|---|
| 230 |
|
|---|
| 231 |
|
|---|
| 232 |
def userLeft(self, group, user, reason=None): |
|---|
| 233 |
assert reason is None or isinstance(reason, unicode) |
|---|
| 234 |
self.part( |
|---|
| 235 |
"%s!%s@%s" % (user.name, user.name, self.hostname), |
|---|
| 236 |
'#' + group.name, |
|---|
| 237 |
(reason or u"leaving").encode(self.encoding, 'replace')) |
|---|
| 238 |
|
|---|
| 239 |
|
|---|
| 240 |
def receive(self, sender, recipient, message): |
|---|
| 241 |
|
|---|
| 242 |
|
|---|
| 243 |
|
|---|
| 244 |
if iwords.IGroup.providedBy(recipient): |
|---|
| 245 |
recipientName = '#' + recipient.name |
|---|
| 246 |
else: |
|---|
| 247 |
recipientName = recipient.name |
|---|
| 248 |
|
|---|
| 249 |
text = message.get('text', '<an unrepresentable message>') |
|---|
| 250 |
for L in text.splitlines(): |
|---|
| 251 |
self.privmsg( |
|---|
| 252 |
'%s!%s@%s' % (sender.name, sender.name, self.hostname), |
|---|
| 253 |
recipientName, |
|---|
| 254 |
L) |
|---|
| 255 |
|
|---|
| 256 |
|
|---|
| 257 |
def groupMetaUpdate(self, group, meta): |
|---|
| 258 |
if 'topic' in meta: |
|---|
| 259 |
topic = meta['topic'] |
|---|
| 260 |
author = meta.get('topic_author', '') |
|---|
| 261 |
self.topic( |
|---|
| 262 |
self.name, |
|---|
| 263 |
'#' + group.name, |
|---|
| 264 |
topic, |
|---|
| 265 |
'%s!%s@%s' % (author, author, self.hostname) |
|---|
| 266 |
) |
|---|
| 267 |
|
|---|
| 268 |
|
|---|
| 269 |
nickname = None |
|---|
| 270 |
password = None |
|---|
| 271 |
|
|---|
| 272 |
def irc_PASS(self, prefix, params): |
|---|
| 273 |
"""Password message -- Register a password. |
|---|
| 274 |
|
|---|
| 275 |
Parameters: <password> |
|---|
| 276 |
|
|---|
| 277 |
[REQUIRED] |
|---|
| 278 |
|
|---|
| 279 |
Note that IRC requires the client send this *before* NICK |
|---|
| 280 |
and USER. |
|---|
| 281 |
""" |
|---|
| 282 |
self.password = params[-1] |
|---|
| 283 |
|
|---|
| 284 |
|
|---|
| 285 |
def irc_NICK(self, prefix, params): |
|---|
| 286 |
"""Nick message -- Set your nickname. |
|---|
| 287 |
|
|---|
| 288 |
Parameters: <nickname> |
|---|
| 289 |
|
|---|
| 290 |
[REQUIRED] |
|---|
| 291 |
""" |
|---|
| 292 |
try: |
|---|
| 293 |
nickname = params[0].decode(self.encoding) |
|---|
| 294 |
except UnicodeDecodeError: |
|---|
| 295 |
self.privmsg( |
|---|
| 296 |
NICKSERV, |
|---|
| 297 |
nickname, |
|---|
| 298 |
'Your nickname is cannot be decoded. Please use ASCII or UTF-8.') |
|---|
| 299 |
self.transport.loseConnection() |
|---|
| 300 |
return |
|---|
| 301 |
|
|---|
| 302 |
self.nickname = nickname |
|---|
| 303 |
self.name = nickname |
|---|
| 304 |
|
|---|
| 305 |
for code, text in self._motdMessages: |
|---|
| 306 |
self.sendMessage(code, text % self.factory._serverInfo) |
|---|
| 307 |
|
|---|
| 308 |
if self.password is None: |
|---|
| 309 |
self.privmsg( |
|---|
| 310 |
NICKSERV, |
|---|
| 311 |
nickname, |
|---|
| 312 |
'Password?') |
|---|
| 313 |
else: |
|---|
| 314 |
password = self.password |
|---|
| 315 |
self.password = None |
|---|
| 316 |
self.logInAs(nickname, password) |
|---|
| 317 |
|
|---|
| 318 |
|
|---|
| 319 |
def irc_USER(self, prefix, params): |
|---|
| 320 |
"""User message -- Set your realname. |
|---|
| 321 |
|
|---|
| 322 |
Parameters: <user> <mode> <unused> <realname> |
|---|
| 323 |
""" |
|---|
| 324 |
|
|---|
| 325 |
|
|---|
| 326 |
|
|---|
| 327 |
self.realname = params[-1] |
|---|
| 328 |
|
|---|
| 329 |
|
|---|
| 330 |
def irc_NICKSERV_PRIVMSG(self, prefix, params): |
|---|
| 331 |
"""Send a (private) message. |
|---|
| 332 |
|
|---|
| 333 |
Parameters: <msgtarget> <text to be sent> |
|---|
| 334 |
""" |
|---|
| 335 |
target = params[0] |
|---|
| 336 |
password = params[-1] |
|---|
| 337 |
|
|---|
| 338 |
if self.nickname is None: |
|---|
| 339 |
|
|---|
| 340 |
self.transport.loseConnection() |
|---|
| 341 |
elif target.lower() != "nickserv": |
|---|
| 342 |
self.privmsg( |
|---|
| 343 |
NICKSERV, |
|---|
| 344 |
self.nickname, |
|---|
| 345 |
"Denied. Please send me (NickServ) your password.") |
|---|
| 346 |
else: |
|---|
| 347 |
nickname = self.nickname |
|---|
| 348 |
self.nickname = None |
|---|
| 349 |
self.logInAs(nickname, password) |
|---|
| 350 |
|
|---|
| 351 |
|
|---|
| 352 |
def logInAs(self, nickname, password): |
|---|
| 353 |
d = self.factory.portal.login( |
|---|
| 354 |
credentials.UsernamePassword(nickname, password), |
|---|
| 355 |
self, |
|---|
| 356 |
iwords.IUser) |
|---|
| 357 |
d.addCallbacks(self._cbLogin, self._ebLogin, errbackArgs=(nickname,)) |
|---|
| 358 |
|
|---|
| 359 |
|
|---|
| 360 |
_welcomeMessages = [ |
|---|
| 361 |
(irc.RPL_WELCOME, |
|---|
| 362 |
":connected to Twisted IRC"), |
|---|
| 363 |
(irc.RPL_YOURHOST, |
|---|
| 364 |
":Your host is %(serviceName)s, running version %(serviceVersion)s"), |
|---|
| 365 |
(irc.RPL_CREATED, |
|---|
| 366 |
":This server was created on %(creationDate)s"), |
|---|
| 367 |
|
|---|
| 368 |
|
|---|
| 369 |
|
|---|
| 370 |
|
|---|
| 371 |
(irc.RPL_MYINFO, |
|---|
| 372 |
|
|---|
| 373 |
|
|---|
| 374 |
"%(serviceName)s %(serviceVersion)s w n") |
|---|
| 375 |
] |
|---|
| 376 |
|
|---|
| 377 |
_motdMessages = [ |
|---|
| 378 |
(irc.RPL_MOTDSTART, |
|---|
| 379 |
":- %(serviceName)s Message of the Day - "), |
|---|
| 380 |
(irc.RPL_ENDOFMOTD, |
|---|
| 381 |
":End of /MOTD command.") |
|---|
| 382 |
] |
|---|
| 383 |
|
|---|
| 384 |
def _cbLogin(self, (iface, avatar, logout)): |
|---|
| 385 |
assert iface is iwords.IUser, "Realm is buggy, got %r" % (iface,) |
|---|
| 386 |
|
|---|
| 387 |
|
|---|
| 388 |
del self.irc_PRIVMSG |
|---|
| 389 |
|
|---|
| 390 |
self.avatar = avatar |
|---|
| 391 |
self.logout = logout |
|---|
| 392 |
for code, text in self._welcomeMessages: |
|---|
| 393 |
self.sendMessage(code, text % self.factory._serverInfo) |
|---|
| 394 |
|
|---|
| 395 |
|
|---|
| 396 |
def _ebLogin(self, err, nickname): |
|---|
| 397 |
if err.check(ewords.AlreadyLoggedIn): |
|---|
| 398 |
self.privmsg( |
|---|
| 399 |
NICKSERV, |
|---|
| 400 |
nickname, |
|---|
| 401 |
"Already logged in. No pod people allowed!") |
|---|
| 402 |
elif err.check(ecred.UnauthorizedLogin): |
|---|
| 403 |
self.privmsg( |
|---|
| 404 |
NICKSERV, |
|---|
| 405 |
nickname, |
|---|
| 406 |
"Login failed. Goodbye.") |
|---|
| 407 |
else: |
|---|
| 408 |
log.msg("Unhandled error during login:") |
|---|
| 409 |
log.err(err) |
|---|
| 410 |
self.privmsg( |
|---|
| 411 |
NICKSERV, |
|---|
| 412 |
nickname, |
|---|
| 413 |
"Server error during login. Sorry.") |
|---|
| 414 |
self.transport.loseConnection() |
|---|
| 415 |
|
|---|
| 416 |
|
|---|
| 417 |
|
|---|
| 418 |
|
|---|
| 419 |
def irc_PING(self, prefix, params): |
|---|
| 420 |
"""Ping message |
|---|
| 421 |
|
|---|
| 422 |
Parameters: <server1> [ <server2> ] |
|---|
| 423 |
""" |
|---|
| 424 |
if self.realm is not None: |
|---|
| 425 |
self.sendMessage('PONG', self.hostname) |
|---|
| 426 |
|
|---|
| 427 |
|
|---|
| 428 |
def irc_QUIT(self, prefix, params): |
|---|
| 429 |
"""Quit |
|---|
| 430 |
|
|---|
| 431 |
Parameters: [ <Quit Message> ] |
|---|
| 432 |
""" |
|---|
| 433 |
self.transport.loseConnection() |
|---|
| 434 |
|
|---|
| 435 |
|
|---|
| 436 |
def _channelMode(self, group, modes=None, *args): |
|---|
| 437 |
if modes: |
|---|
| 438 |
self.sendMessage( |
|---|
| 439 |
irc.ERR_UNKNOWNMODE, |
|---|
| 440 |
":Unknown MODE flag.") |
|---|
| 441 |
else: |
|---|
| 442 |
self.channelMode(self.name, '#' + group.name, '+') |
|---|
| 443 |
|
|---|
| 444 |
|
|---|
| 445 |
def _userMode(self, user, modes=None): |
|---|
| 446 |
if modes: |
|---|
| 447 |
self.sendMessage( |
|---|
| 448 |
irc.ERR_UNKNOWNMODE, |
|---|
| 449 |
":Unknown MODE flag.") |
|---|
| 450 |
elif user is self.avatar: |
|---|
| 451 |
self.sendMessage( |
|---|
| 452 |
irc.RPL_UMODEIS, |
|---|
| 453 |
"+") |
|---|
| 454 |
else: |
|---|
| 455 |
self.sendMessage( |
|---|
| 456 |
irc.ERR_USERSDONTMATCH, |
|---|
| 457 |
":You can't look at someone else's modes.") |
|---|
| 458 |
|
|---|
| 459 |
|
|---|
| 460 |
def irc_MODE(self, prefix, params): |
|---|
| 461 |
"""User mode message |
|---|
| 462 |
|
|---|
| 463 |
Parameters: <nickname> |
|---|
| 464 |
*( ( "+" / "-" ) *( "i" / "w" / "o" / "O" / "r" ) ) |
|---|
| 465 |
|
|---|
| 466 |
""" |
|---|
| 467 |
try: |
|---|
| 468 |
channelOrUser = params[0].decode(self.encoding) |
|---|
| 469 |
except UnicodeDecodeError: |
|---|
| 470 |
self.sendMessage( |
|---|
| 471 |
irc.ERR_NOSUCHNICK, params[0], |
|---|
| 472 |
":No such nickname (could not decode your unicode!)") |
|---|
| 473 |
return |
|---|
| 474 |
|
|---|
| 475 |
if channelOrUser.startswith('#'): |
|---|
| 476 |
def ebGroup(err): |
|---|
| 477 |
err.trap(ewords.NoSuchGroup) |
|---|
| 478 |
self.sendMessage( |
|---|
| 479 |
irc.ERR_NOSUCHCHANNEL, params[0], |
|---|
| 480 |
":That channel doesn't exist.") |
|---|
| 481 |
d = self.realm.lookupGroup(channelOrUser[1:]) |
|---|
| 482 |
d.addCallbacks( |
|---|
| 483 |
self._channelMode, |
|---|
| 484 |
ebGroup, |
|---|
| 485 |
callbackArgs=tuple(params[1:])) |
|---|
| 486 |
else: |
|---|
| 487 |
def ebUser(err): |
|---|
| 488 |
self.sendMessage( |
|---|
| 489 |
irc.ERR_NOSUCHNICK, |
|---|
| 490 |
":No such nickname.") |
|---|
| 491 |
|
|---|
| 492 |
d = self.realm.lookupUser(channelOrUser) |
|---|
| 493 |
d.addCallbacks( |
|---|
| 494 |
self._userMode, |
|---|
| 495 |
ebUser, |
|---|
| 496 |
callbackArgs=tuple(params[1:])) |
|---|
| 497 |
|
|---|
| 498 |
|
|---|
| 499 |
def irc_USERHOST(self, prefix, params): |
|---|
| 500 |
"""Userhost message |
|---|
| 501 |
|
|---|
| 502 |
Parameters: <nickname> *( SPACE <nickname> ) |
|---|
| 503 |
|
|---|
| 504 |
[Optional] |
|---|
| 505 |
""" |
|---|
| 506 |
pass |
|---|
| 507 |
|
|---|
| 508 |
|
|---|
| 509 |
def irc_PRIVMSG(self, prefix, params): |
|---|
| 510 |
"""Send a (private) message. |
|---|
| 511 |
|
|---|
| 512 |
Parameters: <msgtarget> <text to be sent> |
|---|
| 513 |
""" |
|---|
| 514 |
try: |
|---|
| 515 |
targetName = params[0].decode(self.encoding) |
|---|
| 516 |
except UnicodeDecodeError: |
|---|
| 517 |
self.sendMessage( |
|---|
| 518 |
irc.ERR_NOSUCHNICK, targetName, |
|---|
| 519 |
":No such nick/channel (could not decode your unicode!)") |
|---|
| 520 |
return |
|---|
| 521 |
|
|---|
| 522 |
messageText = params[-1] |
|---|
| 523 |
if targetName.startswith('#'): |
|---|
| 524 |
target = self.realm.lookupGroup(targetName[1:]) |
|---|
| 525 |
else: |
|---|
| 526 |
target = self.realm.lookupUser(targetName).addCallback(lambda user: user.mind) |
|---|
| 527 |
|
|---|
| 528 |
def cbTarget(targ): |
|---|
| 529 |
if targ is not None: |
|---|
| 530 |
return self.avatar.send(targ, {"text": messageText}) |
|---|
| 531 |
|
|---|
| 532 |
def ebTarget(err): |
|---|
| 533 |
self.sendMessage( |
|---|
| 534 |
irc.ERR_NOSUCHNICK, targetName, |
|---|
| 535 |
":No such nick/channel.") |
|---|
| 536 |
|
|---|
| 537 |
target.addCallbacks(cbTarget, ebTarget) |
|---|
| 538 |
|
|---|
| 539 |
|
|---|
| 540 |
def irc_JOIN(self, prefix, params): |
|---|
| 541 |
"""Join message |
|---|
| 542 |
|
|---|
| 543 |
Parameters: ( <channel> *( "," <channel> ) [ <key> *( "," <key> ) ] ) |
|---|
| 544 |
""" |
|---|
| 545 |
try: |
|---|
| 546 |
groupName = params[0].decode(self.encoding) |
|---|
| 547 |
except UnicodeDecodeError: |
|---|
| 548 |
self.sendMessage( |
|---|
| 549 |
irc.IRC_NOSUCHCHANNEL, params[0], |
|---|
| 550 |
":No such channel (could not decode your unicode!)") |
|---|
| 551 |
return |
|---|
| 552 |
|
|---|
| 553 |
if groupName.startswith('#'): |
|---|
| 554 |
groupName = groupName[1:] |
|---|
| 555 |
|
|---|
| 556 |
def cbGroup(group): |
|---|
| 557 |
def cbJoin(ign): |
|---|
| 558 |
self.userJoined(group, self) |
|---|
| 559 |
self.names( |
|---|
| 560 |
self.name, |
|---|
| 561 |
'#' + group.name, |
|---|
| 562 |
[user.name for user in group.iterusers()]) |
|---|
| 563 |
self._sendTopic(group) |
|---|
| 564 |
return self.avatar.join(group).addCallback(cbJoin) |
|---|
| 565 |
|
|---|
| 566 |
def ebGroup(err): |
|---|
| 567 |
self.sendMessage( |
|---|
| 568 |
irc.ERR_NOSUCHCHANNEL, '#' + groupName, |
|---|
| 569 |
":No such channel.") |
|---|
| 570 |
|
|---|
| 571 |
self.realm.getGroup(groupName).addCallbacks(cbGroup, ebGroup) |
|---|
| 572 |
|
|---|
| 573 |
|
|---|
| 574 |
def irc_PART(self, prefix, params): |
|---|
| 575 |
"""Part message |
|---|
| 576 |
|
|---|
| 577 |
Parameters: <channel> *( "," <channel> ) [ <Part Message> ] |
|---|
| 578 |
""" |
|---|
| 579 |
try: |
|---|
| 580 |
groupName = params[0].decode(self.encoding) |
|---|
| 581 |
except UnicodeDecodeError: |
|---|
| 582 |
self.sendMessage( |
|---|
| 583 |
irc.ERR_NOTONCHANNEL, params[0], |
|---|
| 584 |
":Could not decode your unicode!") |
|---|
| 585 |
return |
|---|
| 586 |
|
|---|
| 587 |
if groupName.startswith('#'): |
|---|
| 588 |
groupName = groupName[1:] |
|---|
| 589 |
|
|---|
| 590 |
if len(params) > 1: |
|---|
| 591 |
reason = params[1].decode('utf-8') |
|---|
| 592 |
else: |
|---|
| 593 |
reason = None |
|---|
| 594 |
|
|---|
| 595 |
def cbGroup(group): |
|---|
| 596 |
def cbLeave(result): |
|---|
| 597 |
self.userLeft(group, self, reason) |
|---|
| 598 |
return self.avatar.leave(group, reason).addCallback(cbLeave) |
|---|
| 599 |
|
|---|
| 600 |
def ebGroup(err): |
|---|
| 601 |
err.trap(ewords.NoSuchGroup) |
|---|
| 602 |
self.sendMessage( |
|---|
| 603 |
irc.ERR_NOTONCHANNEL, |
|---|
| 604 |
'#' + groupName, |
|---|
| 605 |
":" + err.getErrorMessage()) |
|---|
| 606 |
|
|---|
| 607 |
self.realm.lookupGroup(groupName).addCallbacks(cbGroup, ebGroup) |
|---|
| 608 |
|
|---|
| 609 |
|
|---|
| 610 |
def irc_NAMES(self, prefix, params): |
|---|
| 611 |
"""Names message |
|---|
| 612 |
|
|---|
| 613 |
Parameters: [ <channel> *( "," <channel> ) [ <target> ] ] |
|---|
| 614 |
""" |
|---|
| 615 |
|
|---|
| 616 |
|
|---|
| 617 |
|
|---|
| 618 |
try: |
|---|
| 619 |
channel = params[-1].decode(self.encoding) |
|---|
| 620 |
except UnicodeDecodeError: |
|---|
| 621 |
self.sendMessage( |
|---|
| 622 |
irc.ERR_NOSUCHCHANNEL, params[-1], |
|---|
| 623 |
":No such channel (could not decode your unicode!)") |
|---|
| 624 |
return |
|---|
| 625 |
|
|---|
| 626 |
if channel.startswith('#'): |
|---|
| 627 |
channel = channel[1:] |
|---|
| 628 |
|
|---|
| 629 |
def cbGroup(group): |
|---|
| 630 |
self.names( |
|---|
| 631 |
self.name, |
|---|
| 632 |
'#' + group.name, |
|---|
| 633 |
[user.name for user in group.iterusers()]) |
|---|
| 634 |
|
|---|
| 635 |
def ebGroup(err): |
|---|
| 636 |
err.trap(ewords.NoSuchGroup) |
|---|
| 637 |
|
|---|
| 638 |
self.names( |
|---|
| 639 |
self.name, |
|---|
| 640 |
'#' + channel, |
|---|
| 641 |
[]) |
|---|
| 642 |
|
|---|
| 643 |
self.realm.lookupGroup(channel).addCallbacks(cbGroup, ebGroup) |
|---|
| 644 |
|
|---|
| 645 |
|
|---|
| 646 |
def irc_TOPIC(self, prefix, params): |
|---|
| 647 |
"""Topic message |
|---|
| 648 |
|
|---|
| 649 |
Parameters: <channel> [ <topic> ] |
|---|
| 650 |
""" |
|---|
| 651 |
try: |
|---|
| 652 |
channel = params[0].decode(self.encoding) |
|---|
| 653 |
except UnicodeDecodeError: |
|---|
| 654 |
self.sendMessage( |
|---|
| 655 |
irc.ERR_NOSUCHCHANNEL, |
|---|
| 656 |
":That channel doesn't exist (could not decode your unicode!)") |
|---|
| 657 |
return |
|---|
| 658 |
|
|---|
| 659 |
if channel.startswith('#'): |
|---|
| 660 |
channel = channel[1:] |
|---|
| 661 |
|
|---|
| 662 |
if len(params) > 1: |
|---|
| 663 |
self._setTopic(channel, params[1]) |
|---|
| 664 |
else: |
|---|
| 665 |
self._getTopic(channel) |
|---|
| 666 |
|
|---|
| 667 |
|
|---|
| 668 |
def _sendTopic(self, group): |
|---|
| 669 |
""" |
|---|
| 670 |
Send the topic of the given group to this user, if it has one. |
|---|
| 671 |
""" |
|---|
| 672 |
topic = group.meta.get("topic") |
|---|
| 673 |
if topic: |
|---|
| 674 |
author = group.meta.get("topic_author") or "<noone>" |
|---|
| 675 |
date = group.meta.get("topic_date", 0) |
|---|
| 676 |
self.topic(self.name, '#' + group.name, topic) |
|---|
| 677 |
self.topicAuthor(self.name, '#' + group.name, author, date) |
|---|
| 678 |
|
|---|
| 679 |
|
|---|
| 680 |
def _getTopic(self, channel): |
|---|
| 681 |
|
|---|
| 682 |
|
|---|
| 683 |
|
|---|
| 684 |
def ebGroup(err): |
|---|
| 685 |
err.trap(ewords.NoSuchGroup) |
|---|
| 686 |
self.sendMessage( |
|---|
| 687 |
irc.ERR_NOSUCHCHANNEL, '=', channel, |
|---|
| 688 |
":That channel doesn't exist.") |
|---|
| 689 |
|
|---|
| 690 |
self.realm.lookupGroup(channel).addCallbacks(self._sendTopic, ebGroup) |
|---|
| 691 |
|
|---|
| 692 |
|
|---|
| 693 |
def _setTopic(self, channel, topic): |
|---|
| 694 |
|
|---|
| 695 |
|
|---|
| 696 |
|
|---|
| 697 |
def cbGroup(group): |
|---|
| 698 |
newMeta = group.meta.copy() |
|---|
| 699 |
newMeta['topic'] = topic |
|---|
| 700 |
newMeta['topic_author'] = self.name |
|---|
| 701 |
newMeta['topic_date'] = int(time()) |
|---|
| 702 |
|
|---|
| 703 |
def ebSet(err): |
|---|
| 704 |
self.sendMessage( |
|---|
| 705 |
irc.ERR_CHANOPRIVSNEEDED, |
|---|
| 706 |
"#" + group.name, |
|---|
| 707 |
":You need to be a channel operator to do that.") |
|---|
| 708 |
|
|---|
| 709 |
return group.setMetadata(newMeta).addErrback(ebSet) |
|---|
| 710 |
|
|---|
| 711 |
def ebGroup(err): |
|---|
| 712 |
err.trap(ewords.NoSuchGroup) |
|---|
| 713 |
self.sendMessage( |
|---|
| 714 |
irc.ERR_NOSUCHCHANNEL, '=', channel, |
|---|
| 715 |
":That channel doesn't exist.") |
|---|
| 716 |
|
|---|
| 717 |
self.realm.lookupGroup(channel).addCallbacks(cbGroup, ebGroup) |
|---|
| 718 |
|
|---|
| 719 |
|
|---|
| 720 |
def list(self, channels): |
|---|
| 721 |
"""Send a group of LIST response lines |
|---|
| 722 |
|
|---|
| 723 |
@type channel: C{list} of C{(str, int, str)} |
|---|
| 724 |
@param channel: Information about the channels being sent: |
|---|
| 725 |
their name, the number of participants, and their topic. |
|---|
| 726 |
""" |
|---|
| 727 |
for (name, size, topic) in channels: |
|---|
| 728 |
self.sendMessage(irc.RPL_LIST, name, str(size), ":" + topic) |
|---|
| 729 |
self.sendMessage(irc.RPL_LISTEND, ":End of /LIST") |
|---|
| 730 |
|
|---|
| 731 |
|
|---|
| 732 |
def irc_LIST(self, prefix, params): |
|---|
| 733 |
"""List query |
|---|
| 734 |
|
|---|
| 735 |
Return information about the indicated channels, or about all |
|---|
| 736 |
channels if none are specified. |
|---|
| 737 |
|
|---|
| 738 |
Parameters: [ <channel> *( "," <channel> ) [ <target> ] ] |
|---|
| 739 |
""" |
|---|
| 740 |
|
|---|
| 741 |
|
|---|
| 742 |
|
|---|
| 743 |
|
|---|
| 744 |
if params: |
|---|
| 745 |
|
|---|
| 746 |
try: |
|---|
| 747 |
channels = params[0].decode(self.encoding).split(',') |
|---|
| 748 |
except UnicodeDecodeError: |
|---|
| 749 |
self.sendMessage( |
|---|
| 750 |
irc.ERR_NOSUCHCHANNEL, params[0], |
|---|
| 751 |
":No such channel (could not decode your unicode!)") |
|---|
| 752 |
return |
|---|
| 753 |
|
|---|
| 754 |
groups = [] |
|---|
| 755 |
for ch in channels: |
|---|
| 756 |
if ch.startswith('#'): |
|---|
| 757 |
ch = ch[1:] |
|---|
| 758 |
groups.append(self.realm.lookupGroup(ch)) |
|---|
| 759 |
|
|---|
| 760 |
groups = defer.DeferredList(groups, consumeErrors=True) |
|---|
| 761 |
groups.addCallback(lambda gs: [r for (s, r) in gs if s]) |
|---|
| 762 |
else: |
|---|
| 763 |
|
|---|
| 764 |
groups = self.realm.itergroups() |
|---|
| 765 |
|
|---|
| 766 |
def cbGroups(groups): |
|---|
| 767 |
def gotSize(size, group): |
|---|
| 768 |
return group.name, size, group.meta.get('topic') |
|---|
| 769 |
d = defer.DeferredList([ |
|---|
| 770 |
group.size().addCallback(gotSize, group) for group in groups]) |
|---|
| 771 |
d.addCallback(lambda results: self.list([r for (s, r) in results if s])) |
|---|
| 772 |
return d |
|---|
| 773 |
groups.addCallback(cbGroups) |
|---|
| 774 |
|
|---|
| 775 |
|
|---|
| 776 |
def _channelWho(self, group): |
|---|
| 777 |
self.who(self.name, '#' + group.name, |
|---|
| 778 |
[(m.name, self.hostname, self.realm.name, m.name, "H", 0, m.name) for m in group.iterusers()]) |
|---|
| 779 |
|
|---|
| 780 |
|
|---|
| 781 |
def _userWho(self, user): |
|---|
| 782 |
self.sendMessage(irc.RPL_ENDOFWHO, |
|---|
| 783 |
":User /WHO not implemented") |
|---|
| 784 |
|
|---|
| 785 |
|
|---|
| 786 |
def irc_WHO(self, prefix, params): |
|---|
| 787 |
"""Who query |
|---|
| 788 |
|
|---|
| 789 |
Parameters: [ <mask> [ "o" ] ] |
|---|
| 790 |
""" |
|---|
| 791 |
|
|---|
| 792 |
|
|---|
| 793 |
|
|---|
| 794 |
|
|---|
| 795 |
|
|---|
| 796 |
|
|---|
| 797 |
|
|---|
| 798 |
|
|---|
| 799 |
|
|---|
| 800 |
if not params: |
|---|
| 801 |
self.sendMessage(irc.RPL_ENDOFWHO, ":/WHO not supported.") |
|---|
| 802 |
return |
|---|
| 803 |
|
|---|
| 804 |
try: |
|---|
| 805 |
channelOrUser = params[0].decode(self.encoding) |
|---|
| 806 |
except UnicodeDecodeError: |
|---|
| 807 |
self.sendMessage( |
|---|
| 808 |
irc.RPL_ENDOFWHO, params[0], |
|---|
| 809 |
":End of /WHO list (could not decode your unicode!)") |
|---|
| 810 |
return |
|---|
| 811 |
|
|---|
| 812 |
if channelOrUser.startswith('#'): |
|---|
| 813 |
def ebGroup(err): |
|---|
| 814 |
err.trap(ewords.NoSuchGroup) |
|---|
| 815 |
self.sendMessage( |
|---|
| 816 |
irc.RPL_ENDOFWHO, channelOrUser, |
|---|
| 817 |
":End of /WHO list.") |
|---|
| 818 |
d = self.realm.lookupGroup(channelOrUser[1:]) |
|---|
| 819 |
d.addCallbacks(self._channelWho, ebGroup) |
|---|
| 820 |
else: |
|---|
| 821 |
def ebUser(err): |
|---|
| 822 |
err.trap(ewords.NoSuchUser) |
|---|
| 823 |
self.sendMessage( |
|---|
| 824 |
irc.RPL_ENDOFWHO, channelOrUser, |
|---|
| 825 |
":End of /WHO list.") |
|---|
| 826 |
d = self.realm.lookupUser(channelOrUser) |
|---|
| 827 |
d.addCallbacks(self._userWho, ebUser) |
|---|
| 828 |
|
|---|
| 829 |
|
|---|
| 830 |
|
|---|
| 831 |
def irc_WHOIS(self, prefix, params): |
|---|
| 832 |
"""Whois query |
|---|
| 833 |
|
|---|
| 834 |
Parameters: [ <target> ] <mask> *( "," <mask> ) |
|---|
| 835 |
""" |
|---|
| 836 |
def cbUser(user): |
|---|
| 837 |
self.whois( |
|---|
| 838 |
self.name, |
|---|
| 839 |
user.name, user.name, self.realm.name, |
|---|
| 840 |
user.name, self.realm.name, 'Hi mom!', False, |
|---|
| 841 |
int(time() - user.lastMessage), user.signOn, |
|---|
| 842 |
['#' + group.name for group in user.itergroups()]) |
|---|
| 843 |
|
|---|
| 844 |
def ebUser(err): |
|---|
| 845 |
err.trap(ewords.NoSuchUser) |
|---|
| 846 |
self.sendMessage( |
|---|
| 847 |
irc.ERR_NOSUCHNICK, |
|---|
| 848 |
params[0], |
|---|
| 849 |
":No such nick/channel") |
|---|
| 850 |
|
|---|
| 851 |
try: |
|---|
| 852 |
user = params[0].decode(self.encoding) |
|---|
| 853 |
except UnicodeDecodeError: |
|---|
| 854 |
self.sendMessage( |
|---|
| 855 |
irc.ERR_NOSUCHNICK, |
|---|
| 856 |
params[0], |
|---|
| 857 |
":No such nick/channel") |
|---|
| 858 |
return |
|---|
| 859 |
|
|---|
| 860 |
self.realm.lookupUser(user).addCallbacks(cbUser, ebUser) |
|---|
| 861 |
|
|---|
| 862 |
|
|---|
| 863 |
|
|---|
| 864 |
def irc_OPER(self, prefix, params): |
|---|
| 865 |
"""Oper message |
|---|
| 866 |
|
|---|
| 867 |
Parameters: <name> <password> |
|---|
| 868 |
""" |
|---|
| 869 |
self.sendMessage(irc.ERR_NOOPERHOST, ":O-lines not applicable") |
|---|
| 870 |
|
|---|
| 871 |
|
|---|
| 872 |
class IRCFactory(protocol.ServerFactory): |
|---|
| 873 |
""" |
|---|
| 874 |
IRC server that creates instances of the L{IRCUser} protocol. |
|---|
| 875 |
|
|---|
| 876 |
@ivar _serverInfo: A dictionary mapping: |
|---|
| 877 |
"serviceName" to the name of the server, |
|---|
| 878 |
"serviceVersion" to the copyright version, |
|---|
| 879 |
"creationDate" to the time that the server was started. |
|---|
| 880 |
""" |
|---|
| 881 |
protocol = IRCUser |
|---|
| 882 |
|
|---|
| 883 |
def __init__(self, realm, portal): |
|---|
| 884 |
self.realm = realm |
|---|
| 885 |
self.portal = portal |
|---|
| 886 |
self._serverInfo = { |
|---|
| 887 |
"serviceName": self.realm.name, |
|---|
| 888 |
"serviceVersion": copyright.version, |
|---|
| 889 |
"creationDate": ctime() |
|---|
| 890 |
} |
|---|
| 891 |
|
|---|
| 892 |
|
|---|
| 893 |
|
|---|
| 894 |
class PBMind(pb.Referenceable): |
|---|
| 895 |
def __init__(self): |
|---|
| 896 |
pass |
|---|
| 897 |
|
|---|
| 898 |
def jellyFor(self, jellier): |
|---|
| 899 |
return reflect.qual(PBMind), jellier.invoker.registerReference(self) |
|---|
| 900 |
|
|---|
| 901 |
def remote_userJoined(self, user, group): |
|---|
| 902 |
pass |
|---|
| 903 |
|
|---|
| 904 |
def remote_userLeft(self, user, group, reason): |
|---|
| 905 |
pass |
|---|
| 906 |
|
|---|
| 907 |
def remote_receive(self, sender, recipient, message): |
|---|
| 908 |
pass |
|---|
| 909 |
|
|---|
| 910 |
def remote_groupMetaUpdate(self, group, meta): |
|---|
| 911 |
pass |
|---|
| 912 |
|
|---|
| 913 |
|
|---|
| 914 |
class PBMindReference(pb.RemoteReference): |
|---|
| 915 |
implements(iwords.IChatClient) |
|---|
| 916 |
|
|---|
| 917 |
def receive(self, sender, recipient, message): |
|---|
| 918 |
if iwords.IGroup.providedBy(recipient): |
|---|
| 919 |
rec = PBGroup(self.realm, self.avatar, recipient) |
|---|
| 920 |
else: |
|---|
| 921 |
rec = PBUser(self.realm, self.avatar, recipient) |
|---|
| 922 |
return self.callRemote( |
|---|
| 923 |
'receive', |
|---|
| 924 |
PBUser(self.realm, self.avatar, sender), |
|---|
| 925 |
rec, |
|---|
| 926 |
message) |
|---|
| 927 |
|
|---|
| 928 |
def groupMetaUpdate(self, group, meta): |
|---|
| 929 |
return self.callRemote( |
|---|
| 930 |
'groupMetaUpdate', |
|---|
| 931 |
PBGroup(self.realm, self.avatar, group), |
|---|
| 932 |
meta) |
|---|
| 933 |
|
|---|
| 934 |
def userJoined(self, group, user): |
|---|
| 935 |
return self.callRemote( |
|---|
| 936 |
'userJoined', |
|---|
| 937 |
PBGroup(self.realm, self.avatar, group), |
|---|
| 938 |
PBUser(self.realm, self.avatar, user)) |
|---|
| 939 |
|
|---|
| 940 |
def userLeft(self, group, user, reason=None): |
|---|
| 941 |
assert reason is None or isinstance(reason, unicode) |
|---|
| 942 |
return self.callRemote( |
|---|
| 943 |
'userLeft', |
|---|
| 944 |
PBGroup(self.realm, self.avatar, group), |
|---|
| 945 |
PBUser(self.realm, self.avatar, user), |
|---|
| 946 |
reason) |
|---|
| 947 |
pb.setUnjellyableForClass(PBMind, PBMindReference) |
|---|
| 948 |
|
|---|
| 949 |
|
|---|
| 950 |
class PBGroup(pb.Referenceable): |
|---|
| 951 |
def __init__(self, realm, avatar, group): |
|---|
| 952 |
self.realm = realm |
|---|
| 953 |
self.avatar = avatar |
|---|
| 954 |
self.group = group |
|---|
| 955 |
|
|---|
| 956 |
|
|---|
| 957 |
def processUniqueID(self): |
|---|
| 958 |
return hash((self.realm.name, self.avatar.name, self.group.name)) |
|---|
| 959 |
|
|---|
| 960 |
|
|---|
| 961 |
def jellyFor(self, jellier): |
|---|
| 962 |
return reflect.qual(self.__class__), self.group.name.encode('utf-8'), jellier.invoker.registerReference(self) |
|---|
| 963 |
|
|---|
| 964 |
|
|---|
| 965 |
def remote_leave(self, reason=None): |
|---|
| 966 |
return self.avatar.leave(self.group, reason) |
|---|
| 967 |
|
|---|
| 968 |
|
|---|
| 969 |
def remote_send(self, message): |
|---|
| 970 |
return self.avatar.send(self.group, message) |
|---|
| 971 |
|
|---|
| 972 |
|
|---|
| 973 |
class PBGroupReference(pb.RemoteReference): |
|---|
| 974 |
implements(iwords.IGroup) |
|---|
| 975 |
|
|---|
| 976 |
def unjellyFor(self, unjellier, unjellyList): |
|---|
| 977 |
clsName, name, ref = unjellyList |
|---|
| 978 |
self.name = name.decode('utf-8') |
|---|
| 979 |
return pb.RemoteReference.unjellyFor(self, unjellier, [clsName, ref]) |
|---|
| 980 |
|
|---|
| 981 |
def leave(self, reason=None): |
|---|
| 982 |
return self.callRemote("leave", reason) |
|---|
| 983 |
|
|---|
| 984 |
def send(self, message): |
|---|
| 985 |
return self.callRemote("send", message) |
|---|
| 986 |
pb.setUnjellyableForClass(PBGroup, PBGroupReference) |
|---|
| 987 |
|
|---|
| 988 |
class PBUser(pb.Referenceable): |
|---|
| 989 |
def __init__(self, realm, avatar, user): |
|---|
| 990 |
self.realm = realm |
|---|
| 991 |
self.avatar = avatar |
|---|
| 992 |
self.user = user |
|---|
| 993 |
|
|---|
| 994 |
def processUniqueID(self): |
|---|
| 995 |
return hash((self.realm.name, self.avatar.name, self.user.name)) |
|---|
| 996 |
|
|---|
| 997 |
|
|---|
| 998 |
class ChatAvatar(pb.Referenceable): |
|---|
| 999 |
implements(iwords.IChatClient) |
|---|
| 1000 |
|
|---|
| 1001 |
def __init__(self, avatar): |
|---|
| 1002 |
self.avatar = avatar |
|---|
| 1003 |
|
|---|
| 1004 |
|
|---|
| 1005 |
def jellyFor(self, jellier): |
|---|
| 1006 |
return reflect.qual(self.__class__), jellier.invoker.registerReference(self) |
|---|
| 1007 |
|
|---|
| 1008 |
|
|---|
| 1009 |
def remote_join(self, groupName): |
|---|
| 1010 |
assert isinstance(groupName, unicode) |
|---|
| 1011 |
def cbGroup(group): |
|---|
| 1012 |
def cbJoin(ignored): |
|---|
| 1013 |
return PBGroup(self.avatar.realm, self.avatar, group) |
|---|
| 1014 |
d = self.avatar.join(group) |
|---|
| 1015 |
d.addCallback(cbJoin) |
|---|
| 1016 |
return d |
|---|
| 1017 |
d = self.avatar.realm.getGroup(groupName) |
|---|
| 1018 |
d.addCallback(cbGroup) |
|---|
| 1019 |
return d |
|---|
| 1020 |
registerAdapter(ChatAvatar, iwords.IUser, pb.IPerspective) |
|---|
| 1021 |
|
|---|
| 1022 |
class AvatarReference(pb.RemoteReference): |
|---|
| 1023 |
def join(self, groupName): |
|---|
| 1024 |
return self.callRemote('join', groupName) |
|---|
| 1025 |
|
|---|
| 1026 |
def quit(self): |
|---|
| 1027 |
d = defer.Deferred() |
|---|
| 1028 |
self.broker.notifyOnDisconnect(lambda: d.callback(None)) |
|---|
| 1029 |
self.broker.transport.loseConnection() |
|---|
| 1030 |
return d |
|---|
| 1031 |
|
|---|
| 1032 |
pb.setUnjellyableForClass(ChatAvatar, AvatarReference) |
|---|
| 1033 |
|
|---|
| 1034 |
|
|---|
| 1035 |
class WordsRealm(object): |
|---|
| 1036 |
implements(portal.IRealm, iwords.IChatService) |
|---|
| 1037 |
|
|---|
| 1038 |
_encoding = 'utf-8' |
|---|
| 1039 |
|
|---|
| 1040 |
def __init__(self, name): |
|---|
| 1041 |
self.name = name |
|---|
| 1042 |
|
|---|
| 1043 |
|
|---|
| 1044 |
def userFactory(self, name): |
|---|
| 1045 |
return User(name) |
|---|
| 1046 |
|
|---|
| 1047 |
|
|---|
| 1048 |
def groupFactory(self, name): |
|---|
| 1049 |
return Group(name) |
|---|
| 1050 |
|
|---|
| 1051 |
|
|---|
| 1052 |
def logoutFactory(self, avatar, facet): |
|---|
| 1053 |
def logout(): |
|---|
| 1054 |
|
|---|
| 1055 |
getattr(facet, 'logout', lambda: None)() |
|---|
| 1056 |
avatar.realm = avatar.mind = None |
|---|
| 1057 |
return logout |
|---|
| 1058 |
|
|---|
| 1059 |
|
|---|
| 1060 |
def requestAvatar(self, avatarId, mind, *interfaces): |
|---|
| 1061 |
if isinstance(avatarId, str): |
|---|
| 1062 |
avatarId = avatarId.decode(self._encoding) |
|---|
| 1063 |
|
|---|
| 1064 |
def gotAvatar(avatar): |
|---|
| 1065 |
if avatar.realm is not None: |
|---|
| 1066 |
raise ewords.AlreadyLoggedIn() |
|---|
| 1067 |
for iface in interfaces: |
|---|
| 1068 |
facet = iface(avatar, None) |
|---|
| 1069 |
if facet is not None: |
|---|
| 1070 |
avatar.loggedIn(self, mind) |
|---|
| 1071 |
mind.name = avatarId |
|---|
| 1072 |
mind.realm = self |
|---|
| 1073 |
mind.avatar = avatar |
|---|
| 1074 |
return iface, facet, self.logoutFactory(avatar, facet) |
|---|
| 1075 |
raise NotImplementedError(self, interfaces) |
|---|
| 1076 |
|
|---|
| 1077 |
return self.getUser(avatarId).addCallback(gotAvatar) |
|---|
| 1078 |
|
|---|
| 1079 |
|
|---|
| 1080 |
|
|---|
| 1081 |
createGroupOnRequest = False |
|---|
| 1082 |
createUserOnRequest = True |
|---|
| 1083 |
|
|---|
| 1084 |
def lookupUser(self, name): |
|---|
| 1085 |
raise NotImplementedError |
|---|
| 1086 |
|
|---|
| 1087 |
|
|---|
| 1088 |
def lookupGroup(self, group): |
|---|
| 1089 |
raise NotImplementedError |
|---|
| 1090 |
|
|---|
| 1091 |
|
|---|
| 1092 |
def addUser(self, user): |
|---|
| 1093 |
"""Add the given user to this service. |
|---|
| 1094 |
|
|---|
| 1095 |
This is an internal method intented to be overridden by |
|---|
| 1096 |
L{WordsRealm} subclasses, not called by external code. |
|---|
| 1097 |
|
|---|
| 1098 |
@type user: L{IUser} |
|---|
| 1099 |
|
|---|
| 1100 |
@rtype: L{twisted.internet.defer.Deferred} |
|---|
| 1101 |
@return: A Deferred which fires with C{None} when the user is |
|---|
| 1102 |
added, or which fails with |
|---|
| 1103 |
L{twisted.words.ewords.DuplicateUser} if a user with the |
|---|
| 1104 |
same name exists already. |
|---|
| 1105 |
""" |
|---|
| 1106 |
raise NotImplementedError |
|---|
| 1107 |
|
|---|
| 1108 |
|
|---|
| 1109 |
def addGroup(self, group): |
|---|
| 1110 |
"""Add the given group to this service. |
|---|
| 1111 |
|
|---|
| 1112 |
@type group: L{IGroup} |
|---|
| 1113 |
|
|---|
| 1114 |
@rtype: L{twisted.internet.defer.Deferred} |
|---|
| 1115 |
@return: A Deferred which fires with C{None} when the group is |
|---|
| 1116 |
added, or which fails with |
|---|
| 1117 |
L{twisted.words.ewords.DuplicateGroup} if a group with the |
|---|
| 1118 |
same name exists already. |
|---|
| 1119 |
""" |
|---|
| 1120 |
raise NotImplementedError |
|---|
| 1121 |
|
|---|
| 1122 |
|
|---|
| 1123 |
def getGroup(self, name): |
|---|
| 1124 |
assert isinstance(name, unicode) |
|---|
| 1125 |
if self.createGroupOnRequest: |
|---|
| 1126 |
def ebGroup(err): |
|---|
| 1127 |
err.trap(ewords.DuplicateGroup) |
|---|
| 1128 |
return self.lookupGroup(name) |
|---|
| 1129 |
return self.createGroup(name).addErrback(ebGroup) |
|---|
| 1130 |
return self.lookupGroup(name) |
|---|
| 1131 |
|
|---|
| 1132 |
|
|---|
| 1133 |
def getUser(self, name): |
|---|
| 1134 |
assert isinstance(name, unicode) |
|---|
| 1135 |
if self.createUserOnRequest: |
|---|
| 1136 |
def ebUser(err): |
|---|
| 1137 |
err.trap(ewords.DuplicateUser) |
|---|
| 1138 |
return self.lookupUser(name) |
|---|
| 1139 |
return self.createUser(name).addErrback(ebUser) |
|---|
| 1140 |
return self.lookupUser(name) |
|---|
| 1141 |
|
|---|
| 1142 |
|
|---|
| 1143 |
def createUser(self, name): |
|---|
| 1144 |
assert isinstance(name, unicode) |
|---|
| 1145 |
def cbLookup(user): |
|---|
| 1146 |
return failure.Failure(ewords.DuplicateUser(name)) |
|---|
| 1147 |
def ebLookup(err): |
|---|
| 1148 |
err.trap(ewords.NoSuchUser) |
|---|
| 1149 |
return self.userFactory(name) |
|---|
| 1150 |
|
|---|
| 1151 |
name = name.lower() |
|---|
| 1152 |
d = self.lookupUser(name) |
|---|
| 1153 |
d.addCallbacks(cbLookup, ebLookup) |
|---|
| 1154 |
d.addCallback(self.addUser) |
|---|
| 1155 |
return d |
|---|
| 1156 |
|
|---|
| 1157 |
|
|---|
| 1158 |
def createGroup(self, name): |
|---|
| 1159 |
assert isinstance(name, unicode) |
|---|
| 1160 |
def cbLookup(group): |
|---|
| 1161 |
return failure.Failure(ewords.DuplicateGroup(name)) |
|---|
| 1162 |
def ebLookup(err): |
|---|
| 1163 |
err.trap(ewords.NoSuchGroup) |
|---|
| 1164 |
return self.groupFactory(name) |
|---|
| 1165 |
|
|---|
| 1166 |
name = name.lower() |
|---|
| 1167 |
d = self.lookupGroup(name) |
|---|
| 1168 |
d.addCallbacks(cbLookup, ebLookup) |
|---|
| 1169 |
d.addCallback(self.addGroup) |
|---|
| 1170 |
return d |
|---|
| 1171 |
|
|---|
| 1172 |
|
|---|
| 1173 |
class InMemoryWordsRealm(WordsRealm): |
|---|
| 1174 |
def __init__(self, *a, **kw): |
|---|
| 1175 |
super(InMemoryWordsRealm, self).__init__(*a, **kw) |
|---|
| 1176 |
self.users = {} |
|---|
| 1177 |
self.groups = {} |
|---|
| 1178 |
|
|---|
| 1179 |
|
|---|
| 1180 |
def itergroups(self): |
|---|
| 1181 |
return defer.succeed(self.groups.itervalues()) |
|---|
| 1182 |
|
|---|
| 1183 |
|
|---|
| 1184 |
def addUser(self, user): |
|---|
| 1185 |
if user.name in self.users: |
|---|
| 1186 |
return defer.fail(failure.Failure(ewords.DuplicateUser())) |
|---|
| 1187 |
self.users[user.name] = user |
|---|
| 1188 |
return defer.succeed(user) |
|---|
| 1189 |
|
|---|
| 1190 |
|
|---|
| 1191 |
def addGroup(self, group): |
|---|
| 1192 |
if group.name in self.groups: |
|---|
| 1193 |
return defer.fail(failure.Failure(ewords.DuplicateGroup())) |
|---|
| 1194 |
self.groups[group.name] = group |
|---|
| 1195 |
return defer.succeed(group) |
|---|
| 1196 |
|
|---|
| 1197 |
|
|---|
| 1198 |
def lookupUser(self, name): |
|---|
| 1199 |
assert isinstance(name, unicode) |
|---|
| 1200 |
name = name.lower() |
|---|
| 1201 |
try: |
|---|
| 1202 |
user = self.users[name] |
|---|
| 1203 |
except KeyError: |
|---|
| 1204 |
return defer.fail(failure.Failure(ewords.NoSuchUser(name))) |
|---|
| 1205 |
else: |
|---|
| 1206 |
return defer.succeed(user) |
|---|
| 1207 |
|
|---|
| 1208 |
|
|---|
| 1209 |
def lookupGroup(self, name): |
|---|
| 1210 |
assert isinstance(name, unicode) |
|---|
| 1211 |
name = name.lower() |
|---|
| 1212 |
try: |
|---|
| 1213 |
group = self.groups[name] |
|---|
| 1214 |
except KeyError: |
|---|
| 1215 |
return defer.fail(failure.Failure(ewords.NoSuchGroup(name))) |
|---|
| 1216 |
else: |
|---|
| 1217 |
return defer.succeed(group) |
|---|
| 1218 |
|
|---|
| 1219 |
__all__ = [ |
|---|
| 1220 |
'Group', 'User', |
|---|
| 1221 |
|
|---|
| 1222 |
'WordsRealm', 'InMemoryWordsRealm', |
|---|
| 1223 |
] |
|---|