"""IRC interface to L{twisted.words}.
I implement an IRC server so you can connect to a L{twisted.words}
service with a regular IRC client.
"""
import string
import time
from twisted.protocols import irc
from twisted.internet import protocol
from twisted.spread import pb
from twisted.python import log
from twisted import copyright
import service
class IRCChatter(irc.IRC, service.WordsClient):
nickname = '*'
passwd = None
participant = None
pendingLogin = None
hostname = "nowhere"
servicename = "twisted.words"
def callRemote(self, key, *args, **kw):
apply(getattr(self, key), args, kw)
def receiveContactList(self, contactList):
for name, status in contactList:
self.notifyStatusChanged(name, status)
def notifyStatusChanged(self, name, status):
self.sendMessage('NOTICE', ":%s is %s"
% (name, service.statuses[status]),
prefix='ContactServ!services@%s'
% (self.servicename,))
def memberJoined(self, member, group):
self.sendLine(":%s!%s@%s JOIN :#%s" %
(member, member, self.servicename, group))
def memberLeft(self, member, group):
self.sendLine(":%s!%s@%s PART #%s :(leaving)" %
(member, member, self.servicename, group))
def receiveDirectMessage(self, senderName, message, metadata=None):
if metadata is not None and metadata.has_key('style'):
message = irc.ctcpStringify([(wordsToCtcp(metadata['style']),
message)])
for line in string.split(message, '\n'):
self.sendLine(":%s!%s@%s PRIVMSG %s :%s" %
(senderName, senderName, self.servicename,
self.nickname, line))
def receiveGroupMessage(self, sender, group, message, metadata=None):
if metadata is not None and metadata.has_key('style'):
message = irc.ctcpStringify([(wordsToCtcp(metadata['style']),
message)])
if sender is not self:
for line in string.split(message, '\n'):
self.sendLine(":%s!%s@%s PRIVMSG #%s :%s" %
(sender, sender, self.servicename,
group, line))
def setGroupMetadata(self, metadata, groupName):
if metadata.has_key('topic'):
topic = metadata['topic']
sender = metadata['topic_author']
irc.IRC.sendMessage(self, 'TOPIC',
'#' + groupName,
':' + irc.lowQuote(topic),
prefix="%s@%s!%s" %
(sender, sender, self.servicename))
def connectionLost(self, reason):
log.msg( "%s lost connection" % self.nickname )
if self.participant:
self.participant.detached(self, self.identity)
def sendMessage(self, command, *parameter_list, **kw):
if not kw.has_key('prefix'):
kw['prefix'] = self.servicename
if not kw.has_key('to'):
kw['to'] = self.nickname
arglist = [self, command, kw['to']] + list(parameter_list)
apply(irc.IRC.sendMessage, arglist, kw)
def irc_unknown(self, prefix, command, params):
log.msg('unknown irc proto msg!')
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.passwd = params[-1]
def irc_NICK(self, prefix, params):
"""Nick message -- Set your nickname.
Parameters: <nickname>
[REQUIRED]
This is also probably the first thing the client sends us
(except possibly PASS), so the rest of the sign-on junk is in
here.
"""
nickname = params[0]
if not self.participant and not self.pendingLogin:
try:
participant = self.service.getPerspectiveNamed(nickname)
except service.UserNonexistantError:
self.sendMessage(irc.ERR_ERRONEUSNICKNAME,
nickname, ":this username is invalid",
to=nickname)
self.transport.loseConnection()
else:
self.nickname = nickname
self.sendMessage(irc.RPL_WELCOME,
":connected to Twisted IRC")
self.sendMessage(irc.RPL_YOURHOST,
":Your host is %s, running version %s" %
(self.servicename, copyright.version))
self.sendMessage(irc.RPL_CREATED,
":This server was created %s" %
"by a very sick man." or 'XXX: on date')
self.sendMessage(irc.RPL_MYINFO,
self.servicename, copyright.version,
'w', 'n') if self.passwd is None:
self.receiveDirectMessage("NickServ", "Password?")
self.pendingLogin = participant
else:
self.logInAs(participant, self.passwd)
else:
self.sendMessage(irc.ERR_NICKNAMEINUSE,
nickname, ":this username is invalid")
def logInAs(self, participant, password):
"""Spawn appropriate callbacks to log in as this participant.
"""
self.pendingLogin = participant
self.pendingPassword = password
req = participant.getIdentityRequest()
req.addCallbacks(self.loggedInAs, self.notLoggedIn)
def loggedInAs(self, ident):
"""Successfully logged in.
"""
pwrq = ident.verifyPlainPassword(self.pendingPassword)
pwrq.addCallback(self.successfulLogin, ident)
pwrq.addErrback(self.failedLogin)
def successfulLogin(self, msg, ident):
self.identity = ident
self.pendingLogin.attached(self, self.identity)
self.participant = self.pendingLogin
self.sendMessage('NOTICE', ":Authentication accepted. "
"Thank you.", prefix='NickServ!services@%s'
% (self.servicename,))
self.sendMessage('376', ':No /MOTD.',prefix=self.servicename)
del self.pendingLogin
del self.pendingPassword
def failedLogin(self, error):
self.notLoggedIn(error)
del self.pendingLogin
del self.pendingPassword
def notLoggedIn(self, message):
"""Login failed.
"""
self.receiveDirectMessage("NickServ", "Login failed: %s" % message)
def irc_USER(self, prefix, params):
"""User message -- Set your realname.
Parameters: <user> <mode> <unused> <realname>
[REQUIRED] for backwards compatibility to IRC clients.
"""
self.realname = params[-1]
def irc_OPER(self, prefix, params):
"""Oper message
Parameters: <name> <password>
[REQUIRED]
"""
self.sendMessage(irc.ERR_NOOPERHOST,
":O-lines not applicable to Words service.")
def irc_MODE(self, prefix, params):
"""User mode message
Parameters: <nickname>
*( ( "+" / "-" ) *( "i" / "w" / "o" / "O" / "r" ) )
[REQUIRED]
"""
if string.lower(params[0]) != string.lower(self.nickname):
return self.irc_channelMODE(prefix, params)
def irc_SERVICE(self, prefix, params):
"""Service message
Parameters: <nickname> <reserved> <distribution> <type>
[REQUIRED]
"""
self.sendMessage(irc.ERR_NOSERVICEHOST,
":No support for signing up services "
"via the IRC interface.")
def irc_QUIT(self, prefix, params):
"""Quit
Parameters: [ <Quit Message> ]
[REQUIRED]
"""
self.sendMessage('ERROR', ":You QUIT")
self.transport.loseConnection()
def irc_SQUIT(self, prefix, params):
"""SQuit
Parameters: <server> <comment>
[REQUIRED]
"""
self.sendMessage(irc.ERR_NOPRIVILEGES, ":No.")
def irc_JOIN(self, prefix, params):
"""Join message
Parameters: ( <channel> *( "," <channel> ) [ <key> *( "," <key> ) ] )
[REQUIRED]
"""
try:
channame = params[0][1:]
self.participant.joinGroup(channame)
except pb.Error:
pass
else:
self.memberJoined(self.nickname, channame)
self.irc_NAMES('', [params[0]])
self.irc_TOPIC('', [params[0]])
def irc_PART(self, prefix, params):
"""Part message
Parameters: <channel> *( "," <channel> ) [ <Part Message> ]
[REQUIRED]
"""
channame = params[0][1:]
try:
self.participant.leaveGroup(channame)
except pb.Error, e:
self.sendMessage(irc.ERR_NOTONCHANNEL,
"#" + channame, ":%s" % (e,))
else:
self.memberLeft(self.nickname, channame)
def irc_channelMODE(self, prefix, params):
"""Channel mode message
Parameters: <channel> *( ( "-" / "+" ) *<modes> *<modeparams> )
[REQUIRED]
"""
name = params[0]
log.msg('mode? %s %s' % (prefix, params))
if name[0] == '#':
self.sendMessage(irc.RPL_CHANNELMODEIS, name, "+")
def irc_TOPIC(self, prefix, params):
"""Topic message
Parameters: <channel> [ <topic> ]
[REQUIRED]
"""
log.msg('topic %s %s' % (prefix, params))
if len(params) == 1:
if params[0][0] != '#':
self.receiveDirectMessage("*error*", "invalid channel name")
return
channame = params[0][1:]
group = self.service.getGroup(channame)
self.sendMessage(irc.RPL_TOPIC,
"#" + group.name, ":" + group.metadata['topic'])
self.sendMessage('333', "#" + group.name, group.metadata['topic_author'], "1")
else:
groupName = params[0][1:]
group = self.service.getGroup(groupName)
group.setMetadata({'topic': params[-1],
'topic_author': self.nickname})
def irc_NAMES(self, prefix, params):
"""Names message
Parameters: [ <channel> *( "," <channel> ) [ <target> ] ]
[REQUIRED]
"""
channame = params[-1][1:]
group = self.service.groups.get(channame)
if group:
prefixLength = len(self.servicename) + len(self.nickname) + 11
namesLength = 512 - prefixLength
names = [member.name for member in group.members]
lastName = 0
curLength = 0
for i, L in zip(range(len(names)), map(len, names)):
if curLength + L > namesLength:
self.sendMessage(
irc.RPL_NAMREPLY, "=", "#" + channame,
":" + ' '.join(names[lastName:i])
)
lastName = i
curLength = 0
else:
curLength = curLength + L
if curLength:
self.sendMessage(
irc.RPL_NAMREPLY, "=", "#" + channame,
":" + ' '.join(names[lastName:])
)
self.sendMessage(irc.RPL_ENDOFNAMES,
"#" + channame, ":End of /NAMES list.")
def irc_LIST(self, prefix, params):
"""List message
Parameters: [ <channel> *( "," <channel> ) [ <target> ] ]
[REQUIRED]
"""
log.msg('list %s %s' % (prefix, params))
self.sendMessage(irc.RPL_LISTEND, ":End of LIST")
def irc_INVITE(self, prefix, params):
"""Invite message
Parameters: <nickname> <channel>
[REQUIRED]
"""
pass
def irc_KICK(self, prefix, params):
"""Kick command
Parameters: <channel> *( "," <channel> ) <user> *( "," <user> )
[REQUIRED]
"""
pass
def irc_PRIVMSG(self, prefix, params):
"""Send a (private) message.
Parameters: <msgtarget> <text to be sent>
[REQUIRED]
"""
name = params[0]
text = params[-1]
if self.participant:
if text[0]==irc.X_DELIM:
if name[0] == '#':
name_ = name[1:]
msgMethod = self.participant.groupMessage
else:
name_ = name
msgMethod = self.participant.directMessage
m = irc.ctcpExtract(text)
for tag, data in m['extended']:
metadata = {'style': ctcpToWords(tag)}
try:
msgMethod(name_, data, metadata)
except pb.Error, e:
self.receiveDirectMessage("*error*", str(e))
log.msg('error sending CTCP query:',str(e))
if not m['normal']:
return
text = string.join(m['normal'], ' ')
if string.lower(name) == 'contactserv':
cmds = string.split(text, ' ', 1)
if cmds[0] == 'add':
self.participant.addContact(cmds[1])
elif cmds[0] == 'remove':
self.participant.removeContact(cmds[1])
else:
self.receiveDirectMessage("ContactServ", "unknown command")
elif name[0] == '#':
log.msg( 'talking to channel %s %s %s ' % (self.nickname, prefix, params ))
channame = name[1:]
try:
self.participant.groupMessage(channame, text)
except pb.Error, e:
self.receiveDirectMessage("*error*", str(e))
log.msg('error chatting to channel:',str(e))
else:
try:
self.participant.directMessage(name, text)
except service.WordsError, e:
self.sendMessage(irc.ERR_NOSUCHNICK, name,
":%s" % (e,))
else:
if string.lower(name) == 'nickserv':
self.logInAs(self.pendingLogin, text)
else:
self.receiveDirectMessage("NickServ", "You haven't logged in yet.")
def irc_NOTICE(self, prefix, params):
"""Notice
Parameters: <msgtarget> <text>
[REQUIRED]
"""
pass
def irc_MOTD(self, prefix, params):
"""Motd message
Parameters: [ <target> ]
[REQUIRED]
"""
pass
def irc_LUSERS(self, prefix, params):
"""Lusers message
Parameters: [ <mask> [ <target> ] ]
[REQUIRED]
"""
self.sendMessage(irc.RPL_LUSERCLIENT,
":There are %s users on %s hosts." %
("some", "all the"))
self.sendMessage(irc.RPL_LUSERME,
":All your base are belong to us.")
def irc_VERSION(self, prefix, params):
"""Version message
Parameters: [ <target> ]
[REQUIRED]
"""
self.sendMessage(irc.RPL_VERSION, copyright.version,
self.servicename, ":http://twistedmatrix.com/")
def irc_STATS(self, prefix, params):
"""Stats message
Parameters: [ <query> [ <target> ] ]
[REQUIRED]
"""
if params:
letter = params[0]
else:
letter = ''
self.sendMessage(irc.RPL_ENDOFSTATS, letter,
":That is all.")
def irc_LINKS(self, prefix, params):
"""Links message
Parameters: [ [ <remote server> ] <server mask> ]
[REQUIRED]
"""
self.sendMessage(irc.RPL_ENDOFLINKS, '*',
":End of links.")
def irc_TIME(self, prefix, params):
"""Time message
Parameters: [ <target> ]
[REQUIRED]
"""
self.sendMessage(irc.RPL_TIME, ":%s" %
(time.asctime(time.localtime()),))
def irc_CONNECT(self, prefix, params):
"""Connect message
Parameters: <target server> <port> [ <remote server> ]
[REQUIRED]
"""
self.sendMessage(irc.ERR_NOPRIVILEGES, ":No.")
def irc_TRACE(self, prefix, params):
"""Trace message
Parameters: [ <target> ]
[REQUIRED]
"""
self.sendMessage(irc.RPL_TRACENEWTYPE, "Trace a path over")
self.sendMessage(irc.RPL_TRACEUNKNOWN, "references intangible,")
self.sendMessage(irc.RPL_TRACEEND, self.servicename,
copyright.version, ":internet come home.")
def irc_INFO(self, prefix, params):
"""Admin command
Parameters: [ <target> ]
[REQUIRED]
"""
self.sendMessage(irc.RPL_INFO, ":Info requested")
self.sendMessage(irc.RPL_INFO, ":valuable commodity")
self.sendMessage(irc.RPL_ENDOFINFO, ":What's it worth to you?")
def irc_SERVLIST(self, prefix, params):
"""Servlist message
Parameters: [ <mask> [ <type> ] ]
[REQUIRED]
"""
self.sendMessage(irc.RPL_SERVLISTEND,
":I'd tell you, but this is an unsecured line.")
def irc_SQUERY(self, prefix, params):
"""Squery
Parameters: <servicename> <text>
[REQUIRED]
"""
pass
def irc_WHO(self, prefix, params):
"""Who query
Parameters: [ <mask> [ "o" ] ]
[REQUIRED]
"""
if not params:
self.sendMessage(irc.RPL_ENDOFWHO, ":/WHO not supported.")
return
name = params[0]
if name[0] == '#':
channame = name[1:]
group = self.service.getGroup(channame)
for member in group.members:
self.sendMessage(irc.RPL_WHOREPLY, "#" + group.name,
member.name, self.servicename,
self.servicename, member.name,
"H",":0", member.name)
self.sendMessage(irc.RPL_ENDOFWHO, "#" + group.name,
":End of /WHO list.")
else:
self.sendMessage(irc.RPL_ENDOFWHO,
":User /WHO not implemented")
def irc_WHOIS(self, prefix, params):
"""Whois query
Parameters: [ <target> ] <mask> *( "," <mask> )
[REQUIRED]
"""
if params:
target = params[0]
else:
target = self.nickname
def irc_WHOWAS(self, prefix, params):
"""Whowas
Parameters: <nickname> *( "," <nickname> ) [ <count> [ <target> ] ]
[REQUIRED]
"""
pass
def irc_KILL(self, prefix, params):
"""Kill message
Parameters: <nickname> <comment>
[REQUIRED]
"""
if not params or not params[0]:
self.sendMessage(irc.ERR_NEEDMOREPARAMS,
':The voices... "kill," they say. "kill."')
else:
object = string.join(params)
self.sendMessage(irc.ERR_NOPRIVILEGES,
":I don't think the %s would "
"like that very much." % (object,))
def irc_PING(self, prefix, params):
"""Ping message
Parameters: <server1> [ <server2> ]
[REQUIRED]
"""
self.sendMessage('PONG', self.servicename)
def irc_PONG(self, prefix, params):
"""Pong message
Parameters: <server> [ <server2> ]
[REQUIRED]
"""
pass
def irc_ERROR(self, prefix, params):
"""Error
Parameters: <error message>
[REQUIRED]
"""
pass
def irc_AWAY(self, prefix, params):
"""Away
Parameters: [ <text> ]
[Optional]
"""
if params and params[0]: self.participant.changeStatus(service.AWAY)
self.sendMessage(irc.RPL_NOWAWAY, "You are now away.")
else:
self.participant.changeStatus(service.ONLINE)
self.sendMessage(irc.RPL_UNAWAY, "You are no longer away.")
def irc_REHASH(self, prefix, params):
"""Rehash message
Parameters: None
[Optional]
"""
pass
def irc_DIE(self, prefix, params):
"""Die message
Parameters: None
[Optional]
"""
self.sendMessage(irc.ERR_NOPRIVILEGES, ":After you.")
def irc_RESTART(self, prefix, params):
"""Restart message
Parameters: None
[Optional]
"""
pass
def irc_SUMMON(self, prefix, params):
"""Summon message
Parameters: <user> [ <target> [ <channel> ] ]
[Optional]
"""
self.sendMessage(irc.ERR_SUMMONDISABLED,
":You don't have enough mana to cast that spell.")
def irc_USERS(self, prefix, params):
"""Users
Parameters: [ <target> ]
[Optional]
"""
pass
def irc_WALLOPS(self, prefix, params):
"""Operwall message
Parameters: <Text to be sent>
[Optional]
"""
pass
def irc_USERHOST(self, prefix, params):
"""Userhost message
Parameters: <nickname> *( SPACE <nickname> )
[Optional]
"""
pass
def irc_ISON(self, prefix, params):
"""Ison message
Parameters: <nickname> *( SPACE <nickname> )
[Optional]
"""
ison = params[:]
for nick in params:
pass
class IRCGateway(protocol.Factory):
def __init__(self, service):
self.service = service
def buildProtocol(self, connection):
"""Build an IRC protocol to talk to my chat service.
"""
i = IRCChatter()
i.service = self.service
return i
mapCtcpToWords = {
'ACTION': 'emote',
}
mapWordsToCtcp = {}
for _k, _v in mapCtcpToWords.items():
mapWordsToCtcp[_v] = _k
def ctcpToWords(query_tag):
return mapCtcpToWords.get(query_tag, query_tag)
def wordsToCtcp(style):
return mapWordsToCtcp.get(style, style)