aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/python/Twisted/py3/twisted/words/service.py
diff options
context:
space:
mode:
authorshmel1k <shmel1k@ydb.tech>2023-11-26 18:16:14 +0300
committershmel1k <shmel1k@ydb.tech>2023-11-26 18:43:30 +0300
commitb8cf9e88f4c5c64d9406af533d8948deb050d695 (patch)
tree218eb61fb3c3b96ec08b4d8cdfef383104a87d63 /contrib/python/Twisted/py3/twisted/words/service.py
parent523f645a83a0ec97a0332dbc3863bb354c92a328 (diff)
downloadydb-b8cf9e88f4c5c64d9406af533d8948deb050d695.tar.gz
add kikimr_configure
Diffstat (limited to 'contrib/python/Twisted/py3/twisted/words/service.py')
-rw-r--r--contrib/python/Twisted/py3/twisted/words/service.py1278
1 files changed, 1278 insertions, 0 deletions
diff --git a/contrib/python/Twisted/py3/twisted/words/service.py b/contrib/python/Twisted/py3/twisted/words/service.py
new file mode 100644
index 0000000000..d65a425f94
--- /dev/null
+++ b/contrib/python/Twisted/py3/twisted/words/service.py
@@ -0,0 +1,1278 @@
+# -*- test-case-name: twisted.words.test.test_service -*-
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+A module that needs a better name.
+
+Implements new cred things for words.
+
+How does this thing work?
+
+ - Network connection on some port expecting to speak some protocol
+
+ - Protocol-specific authentication, resulting in some kind of credentials object
+
+ - twisted.cred.portal login using those credentials for the interface
+ IUser and with something implementing IChatClient as the mind
+
+ - successful login results in an IUser avatar the protocol can call
+ methods on, and state added to the realm such that the mind will have
+ methods called on it as is necessary
+
+ - protocol specific actions lead to calls onto the avatar; remote events
+ lead to calls onto the mind
+
+ - protocol specific hangup, realm is notified, user is removed from active
+ play, the end.
+"""
+
+from time import ctime, time
+
+from zope.interface import implementer
+
+from twisted import copyright
+from twisted.cred import credentials, error as ecred, portal
+from twisted.internet import defer, protocol
+from twisted.python import failure, log, reflect
+from twisted.python.components import registerAdapter
+from twisted.spread import pb
+from twisted.words import ewords, iwords
+from twisted.words.protocols import irc
+
+
+@implementer(iwords.IGroup)
+class Group:
+ def __init__(self, name):
+ self.name = name
+ self.users = {}
+ self.meta = {
+ "topic": "",
+ "topic_author": "",
+ }
+
+ def _ebUserCall(self, err, p):
+ return failure.Failure(Exception(p, err))
+
+ def _cbUserCall(self, results):
+ for success, result in results:
+ if not success:
+ user, err = result.value # XXX
+ self.remove(user, err.getErrorMessage())
+
+ def add(self, user):
+ assert iwords.IChatClient.providedBy(user), "{!r} is not a chat client".format(
+ user
+ )
+ if user.name not in self.users:
+ additions = []
+ self.users[user.name] = user
+ for p in self.users.values():
+ if p is not user:
+ d = defer.maybeDeferred(p.userJoined, self, user)
+ d.addErrback(self._ebUserCall, p=p)
+ additions.append(d)
+ defer.DeferredList(additions).addCallback(self._cbUserCall)
+ return defer.succeed(None)
+
+ def remove(self, user, reason=None):
+ try:
+ del self.users[user.name]
+ except KeyError:
+ pass
+ else:
+ removals = []
+ for p in self.users.values():
+ if p is not user:
+ d = defer.maybeDeferred(p.userLeft, self, user, reason)
+ d.addErrback(self._ebUserCall, p=p)
+ removals.append(d)
+ defer.DeferredList(removals).addCallback(self._cbUserCall)
+ return defer.succeed(None)
+
+ def size(self):
+ return defer.succeed(len(self.users))
+
+ def receive(self, sender, recipient, message):
+ assert recipient is self
+ receives = []
+ for p in self.users.values():
+ if p is not sender:
+ d = defer.maybeDeferred(p.receive, sender, self, message)
+ d.addErrback(self._ebUserCall, p=p)
+ receives.append(d)
+ defer.DeferredList(receives).addCallback(self._cbUserCall)
+ return defer.succeed(None)
+
+ def setMetadata(self, meta):
+ self.meta = meta
+ sets = []
+ for p in self.users.values():
+ d = defer.maybeDeferred(p.groupMetaUpdate, self, meta)
+ d.addErrback(self._ebUserCall, p=p)
+ sets.append(d)
+ defer.DeferredList(sets).addCallback(self._cbUserCall)
+ return defer.succeed(None)
+
+ def iterusers(self):
+ # XXX Deferred?
+ return iter(self.users.values())
+
+
+@implementer(iwords.IUser)
+class User:
+ realm = None
+ mind = None
+
+ def __init__(self, name):
+ self.name = name
+ self.groups = []
+ self.lastMessage = time()
+
+ def loggedIn(self, realm, mind):
+ self.realm = realm
+ self.mind = mind
+ self.signOn = time()
+
+ def join(self, group):
+ def cbJoin(result):
+ self.groups.append(group)
+ return result
+
+ return group.add(self.mind).addCallback(cbJoin)
+
+ def leave(self, group, reason=None):
+ def cbLeave(result):
+ self.groups.remove(group)
+ return result
+
+ return group.remove(self.mind, reason).addCallback(cbLeave)
+
+ def send(self, recipient, message):
+ self.lastMessage = time()
+ return recipient.receive(self.mind, recipient, message)
+
+ def itergroups(self):
+ return iter(self.groups)
+
+ def logout(self):
+ for g in self.groups[:]:
+ self.leave(g)
+
+
+NICKSERV = "NickServ!NickServ@services"
+
+
+@implementer(iwords.IChatClient)
+class IRCUser(irc.IRC):
+ """
+ Protocol instance representing an IRC user connected to the server.
+ """
+
+ # A list of IGroups in which I am participating
+ groups = None
+
+ # A no-argument callable I should invoke when I go away
+ logout = None
+
+ # An IUser we use to interact with the chat service
+ avatar = None
+
+ # To whence I belong
+ realm = None
+
+ # How to handle unicode (TODO: Make this customizable on a per-user basis)
+ encoding = "utf-8"
+
+ # Twisted callbacks
+ def connectionMade(self):
+ self.irc_PRIVMSG = self.irc_NICKSERV_PRIVMSG
+ self.realm = self.factory.realm
+ self.hostname = self.realm.name
+
+ def connectionLost(self, reason):
+ if self.logout is not None:
+ self.logout()
+ self.avatar = None
+
+ # Make sendMessage a bit more useful to us
+ def sendMessage(self, command, *parameter_list, **kw):
+ if "prefix" not in kw:
+ kw["prefix"] = self.hostname
+ if "to" not in kw:
+ kw["to"] = self.name.encode(self.encoding)
+
+ arglist = [self, command, kw["to"]] + list(parameter_list)
+ arglistUnicode = []
+ for arg in arglist:
+ if isinstance(arg, bytes):
+ arg = arg.decode("utf-8")
+ arglistUnicode.append(arg)
+ irc.IRC.sendMessage(*arglistUnicode, **kw)
+
+ # IChatClient implementation
+ def userJoined(self, group, user):
+ self.join(f"{user.name}!{user.name}@{self.hostname}", "#" + group.name)
+
+ def userLeft(self, group, user, reason=None):
+ self.part(
+ f"{user.name}!{user.name}@{self.hostname}",
+ "#" + group.name,
+ (reason or "leaving"),
+ )
+
+ def receive(self, sender, recipient, message):
+ # >> :glyph!glyph@adsl-64-123-27-108.dsl.austtx.swbell.net PRIVMSG glyph_ :hello
+
+ # omg???????????
+ if iwords.IGroup.providedBy(recipient):
+ recipientName = "#" + recipient.name
+ else:
+ recipientName = recipient.name
+
+ text = message.get("text", "<an unrepresentable message>")
+ for L in text.splitlines():
+ self.privmsg(
+ f"{sender.name}!{sender.name}@{self.hostname}",
+ recipientName,
+ L,
+ )
+
+ def groupMetaUpdate(self, group, meta):
+ if "topic" in meta:
+ topic = meta["topic"]
+ author = meta.get("topic_author", "")
+ self.topic(
+ self.name,
+ "#" + group.name,
+ topic,
+ f"{author}!{author}@{self.hostname}",
+ )
+
+ # irc.IRC callbacks - starting with login related stuff.
+ nickname = None
+ password = None
+
+ def irc_PASS(self, prefix, params):
+ """
+ Password message -- Register a password.
+
+ Parameters: <password>
+
+ [REQUIRED]
+
+ Note that IRC requires the client send this *before* NICK
+ and USER.
+ """
+ self.password = params[-1]
+
+ def irc_NICK(self, prefix, params):
+ """
+ Nick message -- Set your nickname.
+
+ Parameters: <nickname>
+
+ [REQUIRED]
+ """
+ nickname = params[0]
+ try:
+ if isinstance(nickname, bytes):
+ nickname = nickname.decode(self.encoding)
+ except UnicodeDecodeError:
+ self.privmsg(
+ NICKSERV,
+ repr(nickname),
+ "Your nickname cannot be decoded. Please use ASCII or UTF-8.",
+ )
+ self.transport.loseConnection()
+ return
+
+ self.nickname = nickname
+ self.name = nickname
+
+ for code, text in self._motdMessages:
+ self.sendMessage(code, text % self.factory._serverInfo)
+
+ if self.password is None:
+ self.privmsg(NICKSERV, nickname, "Password?")
+ else:
+ password = self.password
+ self.password = None
+ self.logInAs(nickname, password)
+
+ def irc_USER(self, prefix, params):
+ """
+ User message -- Set your realname.
+
+ Parameters: <user> <mode> <unused> <realname>
+ """
+ # Note: who gives a crap about this? The IUser has the real
+ # information we care about. Save it anyway, I guess, just
+ # for fun.
+ self.realname = params[-1]
+
+ def irc_NICKSERV_PRIVMSG(self, prefix, params):
+ """
+ Send a (private) message.
+
+ Parameters: <msgtarget> <text to be sent>
+ """
+ target = params[0]
+ password = params[-1]
+
+ if self.nickname is None:
+ # XXX Send an error response here
+ self.transport.loseConnection()
+ elif target.lower() != "nickserv":
+ self.privmsg(
+ NICKSERV,
+ self.nickname,
+ "Denied. Please send me (NickServ) your password.",
+ )
+ else:
+ nickname = self.nickname
+ self.nickname = None
+ self.logInAs(nickname, password)
+
+ def logInAs(self, nickname, password):
+ d = self.factory.portal.login(
+ credentials.UsernamePassword(nickname, password), self, iwords.IUser
+ )
+ d.addCallbacks(self._cbLogin, self._ebLogin, errbackArgs=(nickname,))
+
+ _welcomeMessages = [
+ (irc.RPL_WELCOME, ":connected to Twisted IRC"),
+ (
+ irc.RPL_YOURHOST,
+ ":Your host is %(serviceName)s, running version %(serviceVersion)s",
+ ),
+ (irc.RPL_CREATED, ":This server was created on %(creationDate)s"),
+ # "Bummer. This server returned a worthless 004 numeric.
+ # I'll have to guess at all the values"
+ # -- epic
+ (
+ irc.RPL_MYINFO,
+ # w and n are the currently supported channel and user modes
+ # -- specify this better
+ "%(serviceName)s %(serviceVersion)s w n",
+ ),
+ ]
+
+ _motdMessages = [
+ (irc.RPL_MOTDSTART, ":- %(serviceName)s Message of the Day - "),
+ (irc.RPL_ENDOFMOTD, ":End of /MOTD command."),
+ ]
+
+ def _cbLogin(self, result):
+ (iface, avatar, logout) = result
+ assert iface is iwords.IUser, f"Realm is buggy, got {iface!r}"
+
+ # Let them send messages to the world
+ del self.irc_PRIVMSG
+
+ self.avatar = avatar
+ self.logout = logout
+ for code, text in self._welcomeMessages:
+ self.sendMessage(code, text % self.factory._serverInfo)
+
+ def _ebLogin(self, err, nickname):
+ if err.check(ewords.AlreadyLoggedIn):
+ self.privmsg(
+ NICKSERV, nickname, "Already logged in. No pod people allowed!"
+ )
+ elif err.check(ecred.UnauthorizedLogin):
+ self.privmsg(NICKSERV, nickname, "Login failed. Goodbye.")
+ else:
+ log.msg("Unhandled error during login:")
+ log.err(err)
+ self.privmsg(NICKSERV, nickname, "Server error during login. Sorry.")
+ self.transport.loseConnection()
+
+ # Great, now that's out of the way, here's some of the interesting
+ # bits
+ def irc_PING(self, prefix, params):
+ """
+ Ping message
+
+ Parameters: <server1> [ <server2> ]
+ """
+ if self.realm is not None:
+ self.sendMessage("PONG", self.hostname)
+
+ def irc_QUIT(self, prefix, params):
+ """
+ Quit
+
+ Parameters: [ <Quit Message> ]
+ """
+ self.transport.loseConnection()
+
+ def _channelMode(self, group, modes=None, *args):
+ if modes:
+ self.sendMessage(irc.ERR_UNKNOWNMODE, ":Unknown MODE flag.")
+ else:
+ self.channelMode(self.name, "#" + group.name, "+")
+
+ def _userMode(self, user, modes=None):
+ if modes:
+ self.sendMessage(irc.ERR_UNKNOWNMODE, ":Unknown MODE flag.")
+ elif user is self.avatar:
+ self.sendMessage(irc.RPL_UMODEIS, "+")
+ else:
+ self.sendMessage(
+ irc.ERR_USERSDONTMATCH, ":You can't look at someone else's modes."
+ )
+
+ def irc_MODE(self, prefix, params):
+ """
+ User mode message
+
+ Parameters: <nickname>
+ *( ( "+" / "-" ) *( "i" / "w" / "o" / "O" / "r" ) )
+
+ """
+ try:
+ channelOrUser = params[0]
+ if isinstance(channelOrUser, bytes):
+ channelOrUser = channelOrUser.decode(self.encoding)
+ except UnicodeDecodeError:
+ self.sendMessage(
+ irc.ERR_NOSUCHNICK,
+ params[0],
+ ":No such nickname (could not decode your unicode!)",
+ )
+ return
+
+ if channelOrUser.startswith("#"):
+
+ def ebGroup(err):
+ err.trap(ewords.NoSuchGroup)
+ self.sendMessage(
+ irc.ERR_NOSUCHCHANNEL, params[0], ":That channel doesn't exist."
+ )
+
+ d = self.realm.lookupGroup(channelOrUser[1:])
+ d.addCallbacks(self._channelMode, ebGroup, callbackArgs=tuple(params[1:]))
+ else:
+
+ def ebUser(err):
+ self.sendMessage(irc.ERR_NOSUCHNICK, ":No such nickname.")
+
+ d = self.realm.lookupUser(channelOrUser)
+ d.addCallbacks(self._userMode, ebUser, callbackArgs=tuple(params[1:]))
+
+ def irc_USERHOST(self, prefix, params):
+ """
+ Userhost message
+
+ Parameters: <nickname> *( SPACE <nickname> )
+
+ [Optional]
+ """
+ pass
+
+ def irc_PRIVMSG(self, prefix, params):
+ """
+ Send a (private) message.
+
+ Parameters: <msgtarget> <text to be sent>
+ """
+ try:
+ targetName = params[0]
+ if isinstance(targetName, bytes):
+ targetName = targetName.decode(self.encoding)
+ except UnicodeDecodeError:
+ self.sendMessage(
+ irc.ERR_NOSUCHNICK,
+ params[0],
+ ":No such nick/channel (could not decode your unicode!)",
+ )
+ return
+
+ messageText = params[-1]
+ if targetName.startswith("#"):
+ target = self.realm.lookupGroup(targetName[1:])
+ else:
+ target = self.realm.lookupUser(targetName).addCallback(
+ lambda user: user.mind
+ )
+
+ def cbTarget(targ):
+ if targ is not None:
+ return self.avatar.send(targ, {"text": messageText})
+
+ def ebTarget(err):
+ self.sendMessage(irc.ERR_NOSUCHNICK, targetName, ":No such nick/channel.")
+
+ target.addCallbacks(cbTarget, ebTarget)
+
+ def irc_JOIN(self, prefix, params):
+ """
+ Join message
+
+ Parameters: ( <channel> *( "," <channel> ) [ <key> *( "," <key> ) ] )
+ """
+ try:
+ groupName = params[0]
+ if isinstance(groupName, bytes):
+ groupName = groupName.decode(self.encoding)
+ except UnicodeDecodeError:
+ self.sendMessage(
+ irc.ERR_NOSUCHCHANNEL,
+ params[0],
+ ":No such channel (could not decode your unicode!)",
+ )
+ return
+
+ if groupName.startswith("#"):
+ groupName = groupName[1:]
+
+ def cbGroup(group):
+ def cbJoin(ign):
+ self.userJoined(group, self)
+ self.names(
+ self.name,
+ "#" + group.name,
+ [user.name for user in group.iterusers()],
+ )
+ self._sendTopic(group)
+
+ return self.avatar.join(group).addCallback(cbJoin)
+
+ def ebGroup(err):
+ self.sendMessage(
+ irc.ERR_NOSUCHCHANNEL, "#" + groupName, ":No such channel."
+ )
+
+ self.realm.getGroup(groupName).addCallbacks(cbGroup, ebGroup)
+
+ def irc_PART(self, prefix, params):
+ """
+ Part message
+
+ Parameters: <channel> *( "," <channel> ) [ <Part Message> ]
+ """
+ try:
+ groupName = params[0]
+ if isinstance(params[0], bytes):
+ groupName = params[0].decode(self.encoding)
+ except UnicodeDecodeError:
+ self.sendMessage(
+ irc.ERR_NOTONCHANNEL, params[0], ":Could not decode your unicode!"
+ )
+ return
+
+ if groupName.startswith("#"):
+ groupName = groupName[1:]
+
+ if len(params) > 1:
+ reason = params[1]
+ if isinstance(reason, bytes):
+ reason = reason.decode("utf-8")
+ else:
+ reason = None
+
+ def cbGroup(group):
+ def cbLeave(result):
+ self.userLeft(group, self, reason)
+
+ return self.avatar.leave(group, reason).addCallback(cbLeave)
+
+ def ebGroup(err):
+ err.trap(ewords.NoSuchGroup)
+ self.sendMessage(
+ irc.ERR_NOTONCHANNEL, "#" + groupName, ":" + err.getErrorMessage()
+ )
+
+ self.realm.lookupGroup(groupName).addCallbacks(cbGroup, ebGroup)
+
+ def irc_NAMES(self, prefix, params):
+ """
+ Names message
+
+ Parameters: [ <channel> *( "," <channel> ) [ <target> ] ]
+ """
+ # << NAMES #python
+ # >> :benford.openprojects.net 353 glyph = #python :Orban ... @glyph ... Zymurgy skreech
+ # >> :benford.openprojects.net 366 glyph #python :End of /NAMES list.
+ try:
+ channel = params[-1]
+ if isinstance(channel, bytes):
+ channel = channel.decode(self.encoding)
+ except UnicodeDecodeError:
+ self.sendMessage(
+ irc.ERR_NOSUCHCHANNEL,
+ params[-1],
+ ":No such channel (could not decode your unicode!)",
+ )
+ return
+
+ if channel.startswith("#"):
+ channel = channel[1:]
+
+ def cbGroup(group):
+ self.names(
+ self.name, "#" + group.name, [user.name for user in group.iterusers()]
+ )
+
+ def ebGroup(err):
+ err.trap(ewords.NoSuchGroup)
+ # No group? Fine, no names!
+ self.names(self.name, "#" + channel, [])
+
+ self.realm.lookupGroup(channel).addCallbacks(cbGroup, ebGroup)
+
+ def irc_TOPIC(self, prefix, params):
+ """
+ Topic message
+
+ Parameters: <channel> [ <topic> ]
+ """
+ try:
+ channel = params[0]
+ if isinstance(params[0], bytes):
+ channel = channel.decode(self.encoding)
+ except UnicodeDecodeError:
+ self.sendMessage(
+ irc.ERR_NOSUCHCHANNEL,
+ ":That channel doesn't exist (could not decode your unicode!)",
+ )
+ return
+
+ if channel.startswith("#"):
+ channel = channel[1:]
+
+ if len(params) > 1:
+ self._setTopic(channel, params[1])
+ else:
+ self._getTopic(channel)
+
+ def _sendTopic(self, group):
+ """
+ Send the topic of the given group to this user, if it has one.
+ """
+ topic = group.meta.get("topic")
+ if topic:
+ author = group.meta.get("topic_author") or "<noone>"
+ date = group.meta.get("topic_date", 0)
+ self.topic(self.name, "#" + group.name, topic)
+ self.topicAuthor(self.name, "#" + group.name, author, date)
+
+ def _getTopic(self, channel):
+ # << TOPIC #python
+ # >> :benford.openprojects.net 332 glyph #python :<churchr> I really did. I sprained all my toes.
+ # >> :benford.openprojects.net 333 glyph #python itamar|nyc 994713482
+ def ebGroup(err):
+ err.trap(ewords.NoSuchGroup)
+ self.sendMessage(
+ irc.ERR_NOSUCHCHANNEL, "=", channel, ":That channel doesn't exist."
+ )
+
+ self.realm.lookupGroup(channel).addCallbacks(self._sendTopic, ebGroup)
+
+ def _setTopic(self, channel, topic):
+ # << TOPIC #divunal :foo
+ # >> :glyph!glyph@adsl-64-123-27-108.dsl.austtx.swbell.net TOPIC #divunal :foo
+
+ def cbGroup(group):
+ newMeta = group.meta.copy()
+ newMeta["topic"] = topic
+ newMeta["topic_author"] = self.name
+ newMeta["topic_date"] = int(time())
+
+ def ebSet(err):
+ self.sendMessage(
+ irc.ERR_CHANOPRIVSNEEDED,
+ "#" + group.name,
+ ":You need to be a channel operator to do that.",
+ )
+
+ return group.setMetadata(newMeta).addErrback(ebSet)
+
+ def ebGroup(err):
+ err.trap(ewords.NoSuchGroup)
+ self.sendMessage(
+ irc.ERR_NOSUCHCHANNEL, "=", channel, ":That channel doesn't exist."
+ )
+
+ self.realm.lookupGroup(channel).addCallbacks(cbGroup, ebGroup)
+
+ def list(self, channels):
+ """
+ Send a group of LIST response lines
+
+ @type channels: C{list} of C{(str, int, str)}
+ @param channels: Information about the channels being sent:
+ their name, the number of participants, and their topic.
+ """
+ for name, size, topic in channels:
+ self.sendMessage(irc.RPL_LIST, name, str(size), ":" + topic)
+ self.sendMessage(irc.RPL_LISTEND, ":End of /LIST")
+
+ def irc_LIST(self, prefix, params):
+ """
+ List query
+
+ Return information about the indicated channels, or about all
+ channels if none are specified.
+
+ Parameters: [ <channel> *( "," <channel> ) [ <target> ] ]
+ """
+ # << list #python
+ # >> :orwell.freenode.net 321 exarkun Channel :Users Name
+ # >> :orwell.freenode.net 322 exarkun #python 358 :The Python programming language
+ # >> :orwell.freenode.net 323 exarkun :End of /LIST
+ if params:
+ # Return information about indicated channels
+ try:
+ allChannels = params[0]
+ if isinstance(allChannels, bytes):
+ allChannels = allChannels.decode(self.encoding)
+ channels = allChannels.split(",")
+ except UnicodeDecodeError:
+ self.sendMessage(
+ irc.ERR_NOSUCHCHANNEL,
+ params[0],
+ ":No such channel (could not decode your unicode!)",
+ )
+ return
+
+ groups = []
+ for ch in channels:
+ if ch.startswith("#"):
+ ch = ch[1:]
+ groups.append(self.realm.lookupGroup(ch))
+
+ groups = defer.DeferredList(groups, consumeErrors=True)
+ groups.addCallback(lambda gs: [r for (s, r) in gs if s])
+ else:
+ # Return information about all channels
+ groups = self.realm.itergroups()
+
+ def cbGroups(groups):
+ def gotSize(size, group):
+ return group.name, size, group.meta.get("topic")
+
+ d = defer.DeferredList(
+ [group.size().addCallback(gotSize, group) for group in groups]
+ )
+ d.addCallback(lambda results: self.list([r for (s, r) in results if s]))
+ return d
+
+ groups.addCallback(cbGroups)
+
+ def _channelWho(self, group):
+ self.who(
+ self.name,
+ "#" + group.name,
+ [
+ (m.name, self.hostname, self.realm.name, m.name, "H", 0, m.name)
+ for m in group.iterusers()
+ ],
+ )
+
+ def _userWho(self, user):
+ self.sendMessage(irc.RPL_ENDOFWHO, ":User /WHO not implemented")
+
+ def irc_WHO(self, prefix, params):
+ """
+ Who query
+
+ Parameters: [ <mask> [ "o" ] ]
+ """
+ # << who #python
+ # >> :x.opn 352 glyph #python aquarius pc-62-31-193-114-du.blueyonder.co.uk y.opn Aquarius H :3 Aquarius
+ # ...
+ # >> :x.opn 352 glyph #python foobar europa.tranquility.net z.opn skreech H :0 skreech
+ # >> :x.opn 315 glyph #python :End of /WHO list.
+ ### also
+ # << who glyph
+ # >> :x.opn 352 glyph #python glyph adsl-64-123-27-108.dsl.austtx.swbell.net x.opn glyph H :0 glyph
+ # >> :x.opn 315 glyph glyph :End of /WHO list.
+ if not params:
+ self.sendMessage(irc.RPL_ENDOFWHO, ":/WHO not supported.")
+ return
+
+ try:
+ channelOrUser = params[0]
+ if isinstance(channelOrUser, bytes):
+ channelOrUser = channelOrUser.decode(self.encoding)
+ except UnicodeDecodeError:
+ self.sendMessage(
+ irc.RPL_ENDOFWHO,
+ params[0],
+ ":End of /WHO list (could not decode your unicode!)",
+ )
+ return
+
+ if channelOrUser.startswith("#"):
+
+ def ebGroup(err):
+ err.trap(ewords.NoSuchGroup)
+ self.sendMessage(irc.RPL_ENDOFWHO, channelOrUser, ":End of /WHO list.")
+
+ d = self.realm.lookupGroup(channelOrUser[1:])
+ d.addCallbacks(self._channelWho, ebGroup)
+ else:
+
+ def ebUser(err):
+ err.trap(ewords.NoSuchUser)
+ self.sendMessage(irc.RPL_ENDOFWHO, channelOrUser, ":End of /WHO list.")
+
+ d = self.realm.lookupUser(channelOrUser)
+ d.addCallbacks(self._userWho, ebUser)
+
+ def irc_WHOIS(self, prefix, params):
+ """
+ Whois query
+
+ Parameters: [ <target> ] <mask> *( "," <mask> )
+ """
+
+ def cbUser(user):
+ self.whois(
+ self.name,
+ user.name,
+ user.name,
+ self.realm.name,
+ user.name,
+ self.realm.name,
+ "Hi mom!",
+ False,
+ int(time() - user.lastMessage),
+ user.signOn,
+ ["#" + group.name for group in user.itergroups()],
+ )
+
+ def ebUser(err):
+ err.trap(ewords.NoSuchUser)
+ self.sendMessage(irc.ERR_NOSUCHNICK, params[0], ":No such nick/channel")
+
+ try:
+ user = params[0]
+ if isinstance(user, bytes):
+ user = user.decode(self.encoding)
+ except UnicodeDecodeError:
+ self.sendMessage(irc.ERR_NOSUCHNICK, params[0], ":No such nick/channel")
+ return
+
+ self.realm.lookupUser(user).addCallbacks(cbUser, ebUser)
+
+ # Unsupported commands, here for legacy compatibility
+ def irc_OPER(self, prefix, params):
+ """
+ Oper message
+
+ Parameters: <name> <password>
+ """
+ self.sendMessage(irc.ERR_NOOPERHOST, ":O-lines not applicable")
+
+
+class IRCFactory(protocol.ServerFactory):
+ """
+ IRC server that creates instances of the L{IRCUser} protocol.
+
+ @ivar _serverInfo: A dictionary mapping:
+ "serviceName" to the name of the server,
+ "serviceVersion" to the copyright version,
+ "creationDate" to the time that the server was started.
+ """
+
+ protocol = IRCUser
+
+ def __init__(self, realm, portal):
+ self.realm = realm
+ self.portal = portal
+ self._serverInfo = {
+ "serviceName": self.realm.name,
+ "serviceVersion": copyright.version,
+ "creationDate": ctime(),
+ }
+
+
+class PBMind(pb.Referenceable):
+ def __init__(self):
+ pass
+
+ def jellyFor(self, jellier):
+ qual = reflect.qual(PBMind)
+ if isinstance(qual, str):
+ qual = qual.encode("utf-8")
+ return qual, jellier.invoker.registerReference(self)
+
+ def remote_userJoined(self, user, group):
+ pass
+
+ def remote_userLeft(self, user, group, reason):
+ pass
+
+ def remote_receive(self, sender, recipient, message):
+ pass
+
+ def remote_groupMetaUpdate(self, group, meta):
+ pass
+
+
+@implementer(iwords.IChatClient)
+class PBMindReference(pb.RemoteReference):
+ name = ""
+
+ def receive(self, sender, recipient, message):
+ if iwords.IGroup.providedBy(recipient):
+ rec = PBGroup(self.realm, self.avatar, recipient)
+ else:
+ rec = PBUser(self.realm, self.avatar, recipient)
+ return self.callRemote(
+ "receive", PBUser(self.realm, self.avatar, sender), rec, message
+ )
+
+ def groupMetaUpdate(self, group, meta):
+ return self.callRemote(
+ "groupMetaUpdate", PBGroup(self.realm, self.avatar, group), meta
+ )
+
+ def userJoined(self, group, user):
+ return self.callRemote(
+ "userJoined",
+ PBGroup(self.realm, self.avatar, group),
+ PBUser(self.realm, self.avatar, user),
+ )
+
+ def userLeft(self, group, user, reason=None):
+ return self.callRemote(
+ "userLeft",
+ PBGroup(self.realm, self.avatar, group),
+ PBUser(self.realm, self.avatar, user),
+ reason,
+ )
+
+
+pb.setUnjellyableForClass(PBMind, PBMindReference)
+
+
+class PBGroup(pb.Referenceable):
+ def __init__(self, realm, avatar, group):
+ self.realm = realm
+ self.avatar = avatar
+ self.group = group
+
+ def processUniqueID(self):
+ return hash((self.realm.name, self.avatar.name, self.group.name))
+
+ def jellyFor(self, jellier):
+ qual = reflect.qual(self.__class__)
+ if isinstance(qual, str):
+ qual = qual.encode("utf-8")
+ group = self.group.name
+ if isinstance(group, str):
+ group = group.encode("utf-8")
+ return qual, group, jellier.invoker.registerReference(self)
+
+ def remote_leave(self, reason=None):
+ return self.avatar.leave(self.group, reason)
+
+ def remote_send(self, message):
+ return self.avatar.send(self.group, message)
+
+
+@implementer(iwords.IGroup)
+class PBGroupReference(pb.RemoteReference):
+ def unjellyFor(self, unjellier, unjellyList):
+ clsName, name, ref = unjellyList
+ self.name = name
+ if bytes != str and isinstance(self.name, bytes):
+ self.name = self.name.decode("utf-8")
+ return pb.RemoteReference.unjellyFor(self, unjellier, [clsName, ref])
+
+ def leave(self, reason=None):
+ return self.callRemote("leave", reason)
+
+ def send(self, message):
+ return self.callRemote("send", message)
+
+ def add(self, user):
+ # IGroup.add
+ pass
+
+ def iterusers(self):
+ # IGroup.iterusers
+ pass
+
+ def receive(self, sender, recipient, message):
+ # IGroup.receive
+ pass
+
+ def remove(self, user, reason=None):
+ # IGroup.remove
+ pass
+
+ def setMetadata(self, meta):
+ # IGroup.setMetadata
+ pass
+
+ def size(self):
+ # IGroup.size
+ pass
+
+
+pb.setUnjellyableForClass(PBGroup, PBGroupReference)
+
+
+class PBUser(pb.Referenceable):
+ def __init__(self, realm, avatar, user):
+ self.realm = realm
+ self.avatar = avatar
+ self.user = user
+
+ def processUniqueID(self):
+ return hash((self.realm.name, self.avatar.name, self.user.name))
+
+
+@implementer(iwords.IChatClient)
+class ChatAvatar(pb.Referenceable):
+ def __init__(self, avatar):
+ self.avatar = avatar
+
+ def jellyFor(self, jellier):
+ qual = reflect.qual(self.__class__)
+ if isinstance(qual, str):
+ qual = qual.encode("utf-8")
+ return qual, jellier.invoker.registerReference(self)
+
+ def remote_join(self, groupName):
+ def cbGroup(group):
+ def cbJoin(ignored):
+ return PBGroup(self.avatar.realm, self.avatar, group)
+
+ d = self.avatar.join(group)
+ d.addCallback(cbJoin)
+ return d
+
+ d = self.avatar.realm.getGroup(groupName)
+ d.addCallback(cbGroup)
+ return d
+
+ @property
+ def name(self):
+ # IChatClient.name
+ pass
+
+ @name.setter
+ def name(self, value):
+ # IChatClient.name
+ pass
+
+ def groupMetaUpdate(self, group, meta):
+ # IChatClient.groupMetaUpdate
+ pass
+
+ def receive(self, sender, recipient, message):
+ # IChatClient.receive
+ pass
+
+ def userJoined(self, group, user):
+ # IChatClient.userJoined
+ pass
+
+ def userLeft(self, group, user, reason=None):
+ # IChatClient.userLeft
+ pass
+
+
+registerAdapter(ChatAvatar, iwords.IUser, pb.IPerspective)
+
+
+class AvatarReference(pb.RemoteReference):
+ def join(self, groupName):
+ return self.callRemote("join", groupName)
+
+ def quit(self):
+ d = defer.Deferred()
+ self.broker.notifyOnDisconnect(lambda: d.callback(None))
+ self.broker.transport.loseConnection()
+ return d
+
+
+pb.setUnjellyableForClass(ChatAvatar, AvatarReference)
+
+
+@implementer(portal.IRealm, iwords.IChatService)
+class WordsRealm:
+ _encoding = "utf-8"
+
+ def __init__(self, name):
+ self.name = name
+
+ def userFactory(self, name):
+ return User(name)
+
+ def groupFactory(self, name):
+ return Group(name)
+
+ def logoutFactory(self, avatar, facet):
+ def logout():
+ # XXX Deferred support here
+ getattr(facet, "logout", lambda: None)()
+ avatar.realm = avatar.mind = None
+
+ return logout
+
+ def requestAvatar(self, avatarId, mind, *interfaces):
+ if isinstance(avatarId, bytes):
+ avatarId = avatarId.decode(self._encoding)
+
+ def gotAvatar(avatar):
+ if avatar.realm is not None:
+ raise ewords.AlreadyLoggedIn()
+ for iface in interfaces:
+ facet = iface(avatar, None)
+ if facet is not None:
+ avatar.loggedIn(self, mind)
+ mind.name = avatarId
+ mind.realm = self
+ mind.avatar = avatar
+ return iface, facet, self.logoutFactory(avatar, facet)
+ raise NotImplementedError(self, interfaces)
+
+ return self.getUser(avatarId).addCallback(gotAvatar)
+
+ def itergroups(self):
+ # IChatServer.itergroups
+ pass
+
+ # IChatService, mostly.
+ createGroupOnRequest = False
+ createUserOnRequest = True
+
+ def lookupUser(self, name):
+ raise NotImplementedError
+
+ def lookupGroup(self, group):
+ raise NotImplementedError
+
+ def addUser(self, user):
+ """
+ Add the given user to this service.
+
+ This is an internal method intended to be overridden by
+ L{WordsRealm} subclasses, not called by external code.
+
+ @type user: L{IUser}
+
+ @rtype: L{twisted.internet.defer.Deferred}
+ @return: A Deferred which fires with L{None} when the user is
+ added, or which fails with
+ L{twisted.words.ewords.DuplicateUser} if a user with the
+ same name exists already.
+ """
+ raise NotImplementedError
+
+ def addGroup(self, group):
+ """
+ Add the given group to this service.
+
+ @type group: L{IGroup}
+
+ @rtype: L{twisted.internet.defer.Deferred}
+ @return: A Deferred which fires with L{None} when the group is
+ added, or which fails with
+ L{twisted.words.ewords.DuplicateGroup} if a group with the
+ same name exists already.
+ """
+ raise NotImplementedError
+
+ def getGroup(self, name):
+ if self.createGroupOnRequest:
+
+ def ebGroup(err):
+ err.trap(ewords.DuplicateGroup)
+ return self.lookupGroup(name)
+
+ return self.createGroup(name).addErrback(ebGroup)
+ return self.lookupGroup(name)
+
+ def getUser(self, name):
+ if self.createUserOnRequest:
+
+ def ebUser(err):
+ err.trap(ewords.DuplicateUser)
+ return self.lookupUser(name)
+
+ return self.createUser(name).addErrback(ebUser)
+ return self.lookupUser(name)
+
+ def createUser(self, name):
+ def cbLookup(user):
+ return failure.Failure(ewords.DuplicateUser(name))
+
+ def ebLookup(err):
+ err.trap(ewords.NoSuchUser)
+ return self.userFactory(name)
+
+ name = name.lower()
+ d = self.lookupUser(name)
+ d.addCallbacks(cbLookup, ebLookup)
+ d.addCallback(self.addUser)
+ return d
+
+ def createGroup(self, name):
+ def cbLookup(group):
+ return failure.Failure(ewords.DuplicateGroup(name))
+
+ def ebLookup(err):
+ err.trap(ewords.NoSuchGroup)
+ return self.groupFactory(name)
+
+ name = name.lower()
+ d = self.lookupGroup(name)
+ d.addCallbacks(cbLookup, ebLookup)
+ d.addCallback(self.addGroup)
+ return d
+
+
+class InMemoryWordsRealm(WordsRealm):
+ def __init__(self, *a, **kw):
+ super().__init__(*a, **kw)
+ self.users = {}
+ self.groups = {}
+
+ def itergroups(self):
+ return defer.succeed(self.groups.values())
+
+ def addUser(self, user):
+ if user.name in self.users:
+ return defer.fail(failure.Failure(ewords.DuplicateUser()))
+ self.users[user.name] = user
+ return defer.succeed(user)
+
+ def addGroup(self, group):
+ if group.name in self.groups:
+ return defer.fail(failure.Failure(ewords.DuplicateGroup()))
+ self.groups[group.name] = group
+ return defer.succeed(group)
+
+ def lookupUser(self, name):
+ name = name.lower()
+ try:
+ user = self.users[name]
+ except KeyError:
+ return defer.fail(failure.Failure(ewords.NoSuchUser(name)))
+ else:
+ return defer.succeed(user)
+
+ def lookupGroup(self, name):
+ name = name.lower()
+ try:
+ group = self.groups[name]
+ except KeyError:
+ return defer.fail(failure.Failure(ewords.NoSuchGroup(name)))
+ else:
+ return defer.succeed(group)
+
+
+__all__ = [
+ "Group",
+ "User",
+ "WordsRealm",
+ "InMemoryWordsRealm",
+]