Ausgabe der neuen DB Einträge

This commit is contained in:
hubobel 2022-01-02 21:50:48 +01:00
parent bad48e1627
commit cfbbb9ee3d
2399 changed files with 843193 additions and 43 deletions

View file

@ -0,0 +1,8 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Instance Messenger, Pan-protocol chat client.
"""

View file

@ -0,0 +1,62 @@
# -*- Python -*-
#
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
#
class AccountManager:
"""I am responsible for managing a user's accounts.
That is, remembering what accounts are available, their settings,
adding and removal of accounts, etc.
@ivar accounts: A collection of available accounts.
@type accounts: mapping of strings to L{Account<interfaces.IAccount>}s.
"""
def __init__(self):
self.accounts = {}
def getSnapShot(self):
"""A snapshot of all the accounts and their status.
@returns: A list of tuples, each of the form
(string:accountName, boolean:isOnline,
boolean:autoLogin, string:gatewayType)
"""
data = []
for account in self.accounts.values():
data.append((account.accountName, account.isOnline(),
account.autoLogin, account.gatewayType))
return data
def isEmpty(self):
return len(self.accounts) == 0
def getConnectionInfo(self):
connectioninfo = []
for account in self.accounts.values():
connectioninfo.append(account.isOnline())
return connectioninfo
def addAccount(self, account):
self.accounts[account.accountName] = account
def delAccount(self, accountName):
del self.accounts[accountName]
def connect(self, accountName, chatui):
"""
@returntype: Deferred L{interfaces.IClient}
"""
return self.accounts[accountName].logOn(chatui)
def disconnect(self, accountName):
pass
#self.accounts[accountName].logOff() - not yet implemented
def quit(self):
pass
#for account in self.accounts.values():
# account.logOff() - not yet implemented

View file

@ -0,0 +1,512 @@
# -*- test-case-name: twisted.words.test.test_basechat -*-
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Base classes for Instance Messenger clients.
"""
from twisted.words.im.locals import OFFLINE, ONLINE, AWAY
class ContactsList:
"""
A GUI object that displays a contacts list.
@ivar chatui: The GUI chat client associated with this contacts list.
@type chatui: L{ChatUI}
@ivar contacts: The contacts.
@type contacts: C{dict} mapping C{str} to a L{IPerson<interfaces.IPerson>}
provider
@ivar onlineContacts: The contacts who are currently online (have a status
that is not C{OFFLINE}).
@type onlineContacts: C{dict} mapping C{str} to a
L{IPerson<interfaces.IPerson>} provider
@ivar clients: The signed-on clients.
@type clients: C{list} of L{IClient<interfaces.IClient>} providers
"""
def __init__(self, chatui):
"""
@param chatui: The GUI chat client associated with this contacts list.
@type chatui: L{ChatUI}
"""
self.chatui = chatui
self.contacts = {}
self.onlineContacts = {}
self.clients = []
def setContactStatus(self, person):
"""
Inform the user that a person's status has changed.
@param person: The person whose status has changed.
@type person: L{IPerson<interfaces.IPerson>} provider
"""
if person.name not in self.contacts:
self.contacts[person.name] = person
if person.name not in self.onlineContacts and \
(person.status == ONLINE or person.status == AWAY):
self.onlineContacts[person.name] = person
if person.name in self.onlineContacts and \
person.status == OFFLINE:
del self.onlineContacts[person.name]
def registerAccountClient(self, client):
"""
Notify the user that an account client has been signed on to.
@param client: The client being added to your list of account clients.
@type client: L{IClient<interfaces.IClient>} provider
"""
if not client in self.clients:
self.clients.append(client)
def unregisterAccountClient(self, client):
"""
Notify the user that an account client has been signed off or
disconnected from.
@param client: The client being removed from the list of account
clients.
@type client: L{IClient<interfaces.IClient>} provider
"""
if client in self.clients:
self.clients.remove(client)
def contactChangedNick(self, person, newnick):
"""
Update your contact information to reflect a change to a contact's
nickname.
@param person: The person in your contacts list whose nickname is
changing.
@type person: L{IPerson<interfaces.IPerson>} provider
@param newnick: The new nickname for this person.
@type newnick: C{str}
"""
oldname = person.name
if oldname in self.contacts:
del self.contacts[oldname]
person.name = newnick
self.contacts[newnick] = person
if oldname in self.onlineContacts:
del self.onlineContacts[oldname]
self.onlineContacts[newnick] = person
class Conversation:
"""
A GUI window of a conversation with a specific person.
@ivar person: The person who you're having this conversation with.
@type person: L{IPerson<interfaces.IPerson>} provider
@ivar chatui: The GUI chat client associated with this conversation.
@type chatui: L{ChatUI}
"""
def __init__(self, person, chatui):
"""
@param person: The person who you're having this conversation with.
@type person: L{IPerson<interfaces.IPerson>} provider
@param chatui: The GUI chat client associated with this conversation.
@type chatui: L{ChatUI}
"""
self.chatui = chatui
self.person = person
def show(self):
"""
Display the ConversationWindow.
"""
raise NotImplementedError("Subclasses must implement this method")
def hide(self):
"""
Hide the ConversationWindow.
"""
raise NotImplementedError("Subclasses must implement this method")
def sendText(self, text):
"""
Send text to the person with whom the user is conversing.
@param text: The text to be sent.
@type text: C{str}
"""
self.person.sendMessage(text, None)
def showMessage(self, text, metadata=None):
"""
Display a message sent from the person with whom the user is conversing.
@param text: The sent message.
@type text: C{str}
@param metadata: Metadata associated with this message.
@type metadata: C{dict}
"""
raise NotImplementedError("Subclasses must implement this method")
def contactChangedNick(self, person, newnick):
"""
Change a person's name.
@param person: The person whose nickname is changing.
@type person: L{IPerson<interfaces.IPerson>} provider
@param newnick: The new nickname for this person.
@type newnick: C{str}
"""
self.person.name = newnick
class GroupConversation:
"""
A GUI window of a conversation with a group of people.
@ivar chatui: The GUI chat client associated with this conversation.
@type chatui: L{ChatUI}
@ivar group: The group of people that are having this conversation.
@type group: L{IGroup<interfaces.IGroup>} provider
@ivar members: The names of the people in this conversation.
@type members: C{list} of C{str}
"""
def __init__(self, group, chatui):
"""
@param chatui: The GUI chat client associated with this conversation.
@type chatui: L{ChatUI}
@param group: The group of people that are having this conversation.
@type group: L{IGroup<interfaces.IGroup>} provider
"""
self.chatui = chatui
self.group = group
self.members = []
def show(self):
"""
Display the GroupConversationWindow.
"""
raise NotImplementedError("Subclasses must implement this method")
def hide(self):
"""
Hide the GroupConversationWindow.
"""
raise NotImplementedError("Subclasses must implement this method")
def sendText(self, text):
"""
Send text to the group.
@param: The text to be sent.
@type text: C{str}
"""
self.group.sendGroupMessage(text, None)
def showGroupMessage(self, sender, text, metadata=None):
"""
Display to the user a message sent to this group from the given sender.
@param sender: The person sending the message.
@type sender: C{str}
@param text: The sent message.
@type text: C{str}
@param metadata: Metadata associated with this message.
@type metadata: C{dict}
"""
raise NotImplementedError("Subclasses must implement this method")
def setGroupMembers(self, members):
"""
Set the list of members in the group.
@param members: The names of the people that will be in this group.
@type members: C{list} of C{str}
"""
self.members = members
def setTopic(self, topic, author):
"""
Change the topic for the group conversation window and display this
change to the user.
@param topic: This group's topic.
@type topic: C{str}
@param author: The person changing the topic.
@type author: C{str}
"""
raise NotImplementedError("Subclasses must implement this method")
def memberJoined(self, member):
"""
Add the given member to the list of members in the group conversation
and displays this to the user.
@param member: The person joining the group conversation.
@type member: C{str}
"""
if not member in self.members:
self.members.append(member)
def memberChangedNick(self, oldnick, newnick):
"""
Change the nickname for a member of the group conversation and displays
this change to the user.
@param oldnick: The old nickname.
@type oldnick: C{str}
@param newnick: The new nickname.
@type newnick: C{str}
"""
if oldnick in self.members:
self.members.remove(oldnick)
self.members.append(newnick)
def memberLeft(self, member):
"""
Delete the given member from the list of members in the group
conversation and displays the change to the user.
@param member: The person leaving the group conversation.
@type member: C{str}
"""
if member in self.members:
self.members.remove(member)
class ChatUI:
"""
A GUI chat client.
@type conversations: C{dict} of L{Conversation}
@ivar conversations: A cache of all the direct windows.
@type groupConversations: C{dict} of L{GroupConversation}
@ivar groupConversations: A cache of all the group windows.
@type persons: C{dict} with keys that are a C{tuple} of (C{str},
L{IAccount<interfaces.IAccount>} provider) and values that are
L{IPerson<interfaces.IPerson>} provider
@ivar persons: A cache of all the users associated with this client.
@type groups: C{dict} with keys that are a C{tuple} of (C{str},
L{IAccount<interfaces.IAccount>} provider) and values that are
L{IGroup<interfaces.IGroup>} provider
@ivar groups: A cache of all the groups associated with this client.
@type onlineClients: C{list} of L{IClient<interfaces.IClient>} providers
@ivar onlineClients: A list of message sources currently online.
@type contactsList: L{ContactsList}
@ivar contactsList: A contacts list.
"""
def __init__(self):
self.conversations = {}
self.groupConversations = {}
self.persons = {}
self.groups = {}
self.onlineClients = []
self.contactsList = ContactsList(self)
def registerAccountClient(self, client):
"""
Notify the user that an account has been signed on to.
@type client: L{IClient<interfaces.IClient>} provider
@param client: The client account for the person who has just signed on.
@rtype client: L{IClient<interfaces.IClient>} provider
@return: The client, so that it may be used in a callback chain.
"""
self.onlineClients.append(client)
self.contactsList.registerAccountClient(client)
return client
def unregisterAccountClient(self, client):
"""
Notify the user that an account has been signed off or disconnected.
@type client: L{IClient<interfaces.IClient>} provider
@param client: The client account for the person who has just signed
off.
"""
self.onlineClients.remove(client)
self.contactsList.unregisterAccountClient(client)
def getContactsList(self):
"""
Get the contacts list associated with this chat window.
@rtype: L{ContactsList}
@return: The contacts list associated with this chat window.
"""
return self.contactsList
def getConversation(self, person, Class=Conversation, stayHidden=False):
"""
For the given person object, return the conversation window or create
and return a new conversation window if one does not exist.
@type person: L{IPerson<interfaces.IPerson>} provider
@param person: The person whose conversation window we want to get.
@type Class: L{IConversation<interfaces.IConversation>} implementor
@param: The kind of conversation window we want. If the conversation
window for this person didn't already exist, create one of this type.
@type stayHidden: C{bool}
@param stayHidden: Whether or not the conversation window should stay
hidden.
@rtype: L{IConversation<interfaces.IConversation>} provider
@return: The conversation window.
"""
conv = self.conversations.get(person)
if not conv:
conv = Class(person, self)
self.conversations[person] = conv
if stayHidden:
conv.hide()
else:
conv.show()
return conv
def getGroupConversation(self, group, Class=GroupConversation,
stayHidden=False):
"""
For the given group object, return the group conversation window or
create and return a new group conversation window if it doesn't exist.
@type group: L{IGroup<interfaces.IGroup>} provider
@param group: The group whose conversation window we want to get.
@type Class: L{IConversation<interfaces.IConversation>} implementor
@param: The kind of conversation window we want. If the conversation
window for this person didn't already exist, create one of this type.
@type stayHidden: C{bool}
@param stayHidden: Whether or not the conversation window should stay
hidden.
@rtype: L{IGroupConversation<interfaces.IGroupConversation>} provider
@return: The group conversation window.
"""
conv = self.groupConversations.get(group)
if not conv:
conv = Class(group, self)
self.groupConversations[group] = conv
if stayHidden:
conv.hide()
else:
conv.show()
return conv
def getPerson(self, name, client):
"""
For the given name and account client, return an instance of a
L{IGroup<interfaces.IPerson>} provider or create and return a new
instance of a L{IGroup<interfaces.IPerson>} provider.
@type name: C{str}
@param name: The name of the person of interest.
@type client: L{IClient<interfaces.IClient>} provider
@param client: The client account of interest.
@rtype: L{IPerson<interfaces.IPerson>} provider
@return: The person with that C{name}.
"""
account = client.account
p = self.persons.get((name, account))
if not p:
p = account.getPerson(name)
self.persons[name, account] = p
return p
def getGroup(self, name, client):
"""
For the given name and account client, return an instance of a
L{IGroup<interfaces.IGroup>} provider or create and return a new instance
of a L{IGroup<interfaces.IGroup>} provider.
@type name: C{str}
@param name: The name of the group of interest.
@type client: L{IClient<interfaces.IClient>} provider
@param client: The client account of interest.
@rtype: L{IGroup<interfaces.IGroup>} provider
@return: The group with that C{name}.
"""
# I accept 'client' instead of 'account' in my signature for
# backwards compatibility. (Groups changed to be Account-oriented
# in CVS revision 1.8.)
account = client.account
g = self.groups.get((name, account))
if not g:
g = account.getGroup(name)
self.groups[name, account] = g
return g
def contactChangedNick(self, person, newnick):
"""
For the given C{person}, change the C{person}'s C{name} to C{newnick}
and tell the contact list and any conversation windows with that
C{person} to change as well.
@type person: L{IPerson<interfaces.IPerson>} provider
@param person: The person whose nickname will get changed.
@type newnick: C{str}
@param newnick: The new C{name} C{person} will take.
"""
oldnick = person.name
if (oldnick, person.account) in self.persons:
conv = self.conversations.get(person)
if conv:
conv.contactChangedNick(person, newnick)
self.contactsList.contactChangedNick(person, newnick)
del self.persons[oldnick, person.account]
person.name = newnick
self.persons[person.name, person.account] = person

View file

@ -0,0 +1,269 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
#
"""Instance Messenger base classes for protocol support.
You will find these useful if you're adding a new protocol to IM.
"""
# Abstract representation of chat "model" classes
from twisted.words.im.locals import OFFLINE, OfflineError
from twisted.internet.protocol import Protocol
from twisted.python.reflect import prefixedMethods
from twisted.persisted import styles
from twisted.internet import error
class AbstractGroup:
def __init__(self, name, account):
self.name = name
self.account = account
def getGroupCommands(self):
"""finds group commands
these commands are methods on me that start with imgroup_; they are
called with no arguments
"""
return prefixedMethods(self, "imgroup_")
def getTargetCommands(self, target):
"""finds group commands
these commands are methods on me that start with imgroup_; they are
called with a user present within this room as an argument
you may want to override this in your group in order to filter for
appropriate commands on the given user
"""
return prefixedMethods(self, "imtarget_")
def join(self):
if not self.account.client:
raise OfflineError
self.account.client.joinGroup(self.name)
def leave(self):
if not self.account.client:
raise OfflineError
self.account.client.leaveGroup(self.name)
def __repr__(self):
return '<%s %r>' % (self.__class__, self.name)
def __str__(self):
return '%s@%s' % (self.name, self.account.accountName)
class AbstractPerson:
def __init__(self, name, baseAccount):
self.name = name
self.account = baseAccount
self.status = OFFLINE
def getPersonCommands(self):
"""finds person commands
these commands are methods on me that start with imperson_; they are
called with no arguments
"""
return prefixedMethods(self, "imperson_")
def getIdleTime(self):
"""
Returns a string.
"""
return '--'
def __repr__(self):
return '<%s %r/%s>' % (self.__class__, self.name, self.status)
def __str__(self):
return '%s@%s' % (self.name, self.account.accountName)
class AbstractClientMixin:
"""Designed to be mixed in to a Protocol implementing class.
Inherit from me first.
@ivar _logonDeferred: Fired when I am done logging in.
"""
def __init__(self, account, chatui, logonDeferred):
for base in self.__class__.__bases__:
if issubclass(base, Protocol):
self.__class__._protoBase = base
break
else:
pass
self.account = account
self.chat = chatui
self._logonDeferred = logonDeferred
def connectionMade(self):
self._protoBase.connectionMade(self)
def connectionLost(self, reason):
self.account._clientLost(self, reason)
self.unregisterAsAccountClient()
return self._protoBase.connectionLost(self, reason)
def unregisterAsAccountClient(self):
"""Tell the chat UI that I have `signed off'.
"""
self.chat.unregisterAccountClient(self)
class AbstractAccount(styles.Versioned):
"""Base class for Accounts.
I am the start of an implementation of L{IAccount<interfaces.IAccount>}, I
implement L{isOnline} and most of L{logOn}, though you'll need to implement
L{_startLogOn} in a subclass.
@cvar _groupFactory: A Callable that will return a L{IGroup} appropriate
for this account type.
@cvar _personFactory: A Callable that will return a L{IPerson} appropriate
for this account type.
@type _isConnecting: boolean
@ivar _isConnecting: Whether I am in the process of establishing a
connection to the server.
@type _isOnline: boolean
@ivar _isOnline: Whether I am currently on-line with the server.
@ivar accountName:
@ivar autoLogin:
@ivar username:
@ivar password:
@ivar host:
@ivar port:
"""
_isOnline = 0
_isConnecting = 0
client = None
_groupFactory = AbstractGroup
_personFactory = AbstractPerson
persistanceVersion = 2
def __init__(self, accountName, autoLogin, username, password, host, port):
self.accountName = accountName
self.autoLogin = autoLogin
self.username = username
self.password = password
self.host = host
self.port = port
self._groups = {}
self._persons = {}
def upgrateToVersion2(self):
# Added in CVS revision 1.16.
for k in ('_groups', '_persons'):
if not hasattr(self, k):
setattr(self, k, {})
def __getstate__(self):
state = styles.Versioned.__getstate__(self)
for k in ('client', '_isOnline', '_isConnecting'):
try:
del state[k]
except KeyError:
pass
return state
def isOnline(self):
return self._isOnline
def logOn(self, chatui):
"""Log on to this account.
Takes care to not start a connection if a connection is
already in progress. You will need to implement
L{_startLogOn} for this to work, and it would be a good idea
to override L{_loginFailed} too.
@returntype: Deferred L{interfaces.IClient}
"""
if (not self._isConnecting) and (not self._isOnline):
self._isConnecting = 1
d = self._startLogOn(chatui)
d.addCallback(self._cb_logOn)
# if chatui is not None:
# (I don't particularly like having to pass chatUI to this function,
# but we haven't factored it out yet.)
d.addCallback(chatui.registerAccountClient)
d.addErrback(self._loginFailed)
return d
else:
raise error.ConnectError("Connection in progress")
def getGroup(self, name):
"""Group factory.
@param name: Name of the group on this account.
@type name: string
"""
group = self._groups.get(name)
if group is None:
group = self._groupFactory(name, self)
self._groups[name] = group
return group
def getPerson(self, name):
"""Person factory.
@param name: Name of the person on this account.
@type name: string
"""
person = self._persons.get(name)
if person is None:
person = self._personFactory(name, self)
self._persons[name] = person
return person
def _startLogOn(self, chatui):
"""Start the sign on process.
Factored out of L{logOn}.
@returntype: Deferred L{interfaces.IClient}
"""
raise NotImplementedError()
def _cb_logOn(self, client):
self._isConnecting = 0
self._isOnline = 1
self.client = client
return client
def _loginFailed(self, reason):
"""Errorback for L{logOn}.
@type reason: Failure
@returns: I{reason}, for further processing in the callback chain.
@returntype: Failure
"""
self._isConnecting = 0
self._isOnline = 0 # just in case
return reason
def _clientLost(self, client, reason):
self.client = None
self._isConnecting = 0
self._isOnline = 0
return reason
def __repr__(self):
return "<%s: %s (%s@%s:%s)>" % (self.__class__,
self.accountName,
self.username,
self.host,
self.port)

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,398 @@
# -*- Python -*-
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Pan-protocol chat client.
"""
from zope.interface import Interface, Attribute
# (Random musings, may not reflect on current state of code:)
#
# Accounts have Protocol components (clients)
# Persons have Conversation components
# Groups have GroupConversation components
# Persons and Groups are associated with specific Accounts
# At run-time, Clients/Accounts are slaved to a User Interface
# (Note: User may be a bot, so don't assume all UIs are built on gui toolkits)
class IAccount(Interface):
"""
I represent a user's account with a chat service.
"""
client = Attribute('The L{IClient} currently connecting to this account, if any.')
gatewayType = Attribute('A C{str} that identifies the protocol used by this account.')
def __init__(accountName, autoLogin, username, password, host, port):
"""
@type accountName: string
@param accountName: A name to refer to the account by locally.
@type autoLogin: boolean
@type username: string
@type password: string
@type host: string
@type port: integer
"""
def isOnline():
"""
Am I online?
@rtype: boolean
"""
def logOn(chatui):
"""
Go on-line.
@type chatui: Implementor of C{IChatUI}
@rtype: L{Deferred} with an eventual L{IClient} result.
"""
def logOff():
"""
Sign off.
"""
def getGroup(groupName):
"""
@rtype: L{Group<IGroup>}
"""
def getPerson(personName):
"""
@rtype: L{Person<IPerson>}
"""
class IClient(Interface):
account = Attribute('The L{IAccount} I am a Client for')
def __init__(account, chatui, logonDeferred):
"""
@type account: L{IAccount}
@type chatui: L{IChatUI}
@param logonDeferred: Will be called back once I am logged on.
@type logonDeferred: L{Deferred<twisted.internet.defer.Deferred>}
"""
def joinGroup(groupName):
"""
@param groupName: The name of the group to join.
@type groupName: string
"""
def leaveGroup(groupName):
"""
@param groupName: The name of the group to leave.
@type groupName: string
"""
def getGroupConversation(name, hide=0):
pass
def getPerson(name):
pass
class IPerson(Interface):
def __init__(name, account):
"""
Initialize me.
@param name: My name, as the server knows me.
@type name: string
@param account: The account I am accessed through.
@type account: I{Account}
"""
def isOnline():
"""
Am I online right now?
@rtype: boolean
"""
def getStatus():
"""
What is my on-line status?
@return: L{locals.StatusEnum}
"""
def getIdleTime():
"""
@rtype: string (XXX: How about a scalar?)
"""
def sendMessage(text, metadata=None):
"""
Send a message to this person.
@type text: string
@type metadata: dict
"""
class IGroup(Interface):
"""
A group which you may have a conversation with.
Groups generally have a loosely-defined set of members, who may
leave and join at any time.
"""
name = Attribute('My C{str} name, as the server knows me.')
account = Attribute('The L{Account<IAccount>} I am accessed through.')
def __init__(name, account):
"""
Initialize me.
@param name: My name, as the server knows me.
@type name: str
@param account: The account I am accessed through.
@type account: L{Account<IAccount>}
"""
def setTopic(text):
"""
Set this Groups topic on the server.
@type text: string
"""
def sendGroupMessage(text, metadata=None):
"""
Send a message to this group.
@type text: str
@type metadata: dict
@param metadata: Valid keys for this dictionary include:
- C{'style'}: associated with one of:
- C{'emote'}: indicates this is an action
"""
def join():
"""
Join this group.
"""
def leave():
"""
Depart this group.
"""
class IConversation(Interface):
"""
A conversation with a specific person.
"""
def __init__(person, chatui):
"""
@type person: L{IPerson}
"""
def show():
"""
doesn't seem like it belongs in this interface.
"""
def hide():
"""
nor this neither.
"""
def sendText(text, metadata):
pass
def showMessage(text, metadata):
pass
def changedNick(person, newnick):
"""
@param person: XXX Shouldn't this always be Conversation.person?
"""
class IGroupConversation(Interface):
def show():
"""
doesn't seem like it belongs in this interface.
"""
def hide():
"""
nor this neither.
"""
def sendText(text, metadata):
pass
def showGroupMessage(sender, text, metadata):
pass
def setGroupMembers(members):
"""
Sets the list of members in the group and displays it to the user.
"""
def setTopic(topic, author):
"""
Displays the topic (from the server) for the group conversation window.
@type topic: string
@type author: string (XXX: Not Person?)
"""
def memberJoined(member):
"""
Adds the given member to the list of members in the group conversation
and displays this to the user,
@type member: string (XXX: Not Person?)
"""
def memberChangedNick(oldnick, newnick):
"""
Changes the oldnick in the list of members to C{newnick} and displays this
change to the user,
@type oldnick: string (XXX: Not Person?)
@type newnick: string
"""
def memberLeft(member):
"""
Deletes the given member from the list of members in the group
conversation and displays the change to the user.
@type member: string (XXX: Not Person?)
"""
class IChatUI(Interface):
def registerAccountClient(client):
"""
Notifies user that an account has been signed on to.
@type client: L{Client<IClient>}
"""
def unregisterAccountClient(client):
"""
Notifies user that an account has been signed off or disconnected.
@type client: L{Client<IClient>}
"""
def getContactsList():
"""
@rtype: L{ContactsList}
"""
# WARNING: You'll want to be polymorphed into something with
# intrinsic stoning resistance before continuing.
def getConversation(person, Class, stayHidden=0):
"""
For the given person object, returns the conversation window
or creates and returns a new conversation window if one does not exist.
@type person: L{Person<IPerson>}
@type Class: L{Conversation<IConversation>} class
@type stayHidden: boolean
@rtype: L{Conversation<IConversation>}
"""
def getGroupConversation(group, Class, stayHidden=0):
"""
For the given group object, returns the group conversation window or
creates and returns a new group conversation window if it doesn't exist.
@type group: L{Group<interfaces.IGroup>}
@type Class: L{Conversation<interfaces.IConversation>} class
@type stayHidden: boolean
@rtype: L{GroupConversation<interfaces.IGroupConversation>}
"""
def getPerson(name, client):
"""
Get a Person for a client.
Duplicates L{IAccount.getPerson}.
@type name: string
@type client: L{Client<IClient>}
@rtype: L{Person<IPerson>}
"""
def getGroup(name, client):
"""
Get a Group for a client.
Duplicates L{IAccount.getGroup}.
@type name: string
@type client: L{Client<IClient>}
@rtype: L{Group<IGroup>}
"""
def contactChangedNick(oldnick, newnick):
"""
For the given person, changes the person's name to newnick, and
tells the contact list and any conversation windows with that person
to change as well.
@type oldnick: string
@type newnick: string
"""

View file

@ -0,0 +1,293 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
IRC support for Instance Messenger.
"""
from twisted.words.protocols import irc
from twisted.words.im.locals import ONLINE
from twisted.internet import defer, reactor, protocol
from twisted.internet.defer import succeed
from twisted.words.im import basesupport, interfaces, locals
from zope.interface import implementer
class IRCPerson(basesupport.AbstractPerson):
def imperson_whois(self):
if self.account.client is None:
raise locals.OfflineError
self.account.client.sendLine("WHOIS %s" % self.name)
### interface impl
def isOnline(self):
return ONLINE
def getStatus(self):
return ONLINE
def setStatus(self,status):
self.status=status
self.chat.getContactsList().setContactStatus(self)
def sendMessage(self, text, meta=None):
if self.account.client is None:
raise locals.OfflineError
for line in text.split('\n'):
if meta and meta.get("style", None) == "emote":
self.account.client.ctcpMakeQuery(self.name,[('ACTION', line)])
else:
self.account.client.msg(self.name, line)
return succeed(text)
@implementer(interfaces.IGroup)
class IRCGroup(basesupport.AbstractGroup):
def imgroup_testAction(self):
pass
def imtarget_kick(self, target):
if self.account.client is None:
raise locals.OfflineError
reason = "for great justice!"
self.account.client.sendLine("KICK #%s %s :%s" % (
self.name, target.name, reason))
### Interface Implementation
def setTopic(self, topic):
if self.account.client is None:
raise locals.OfflineError
self.account.client.topic(self.name, topic)
def sendGroupMessage(self, text, meta={}):
if self.account.client is None:
raise locals.OfflineError
if meta and meta.get("style", None) == "emote":
self.account.client.ctcpMakeQuery(self.name,[('ACTION', text)])
return succeed(text)
#standard shmandard, clients don't support plain escaped newlines!
for line in text.split('\n'):
self.account.client.say(self.name, line)
return succeed(text)
def leave(self):
if self.account.client is None:
raise locals.OfflineError
self.account.client.leave(self.name)
self.account.client.getGroupConversation(self.name,1)
class IRCProto(basesupport.AbstractClientMixin, irc.IRCClient):
def __init__(self, account, chatui, logonDeferred=None):
basesupport.AbstractClientMixin.__init__(self, account, chatui,
logonDeferred)
self._namreplies={}
self._ingroups={}
self._groups={}
self._topics={}
def getGroupConversation(self, name, hide=0):
name = name.lower()
return self.chat.getGroupConversation(self.chat.getGroup(name, self),
stayHidden=hide)
def getPerson(self,name):
return self.chat.getPerson(name, self)
def connectionMade(self):
# XXX: Why do I duplicate code in IRCClient.register?
try:
self.performLogin = True
self.nickname = self.account.username
self.password = self.account.password
self.realname = "Twisted-IM user"
irc.IRCClient.connectionMade(self)
for channel in self.account.channels:
self.joinGroup(channel)
self.account._isOnline=1
if self._logonDeferred is not None:
self._logonDeferred.callback(self)
self.chat.getContactsList()
except:
import traceback
traceback.print_exc()
def setNick(self,nick):
self.name=nick
self.accountName="%s (IRC)"%nick
irc.IRCClient.setNick(self,nick)
def kickedFrom(self, channel, kicker, message):
"""
Called when I am kicked from a channel.
"""
return self.chat.getGroupConversation(
self.chat.getGroup(channel[1:], self), 1)
def userKicked(self, kickee, channel, kicker, message):
pass
def noticed(self, username, channel, message):
self.privmsg(username, channel, message, {"dontAutoRespond": 1})
def privmsg(self, username, channel, message, metadata=None):
if metadata is None:
metadata = {}
username = username.split('!',1)[0]
if username==self.name: return
if channel[0]=='#':
group=channel[1:]
self.getGroupConversation(group).showGroupMessage(username, message, metadata)
return
self.chat.getConversation(self.getPerson(username)).showMessage(message, metadata)
def action(self,username,channel,emote):
username = username.split('!',1)[0]
if username==self.name: return
meta={'style':'emote'}
if channel[0]=='#':
group=channel[1:]
self.getGroupConversation(group).showGroupMessage(username, emote, meta)
return
self.chat.getConversation(self.getPerson(username)).showMessage(emote,meta)
def irc_RPL_NAMREPLY(self,prefix,params):
"""
RPL_NAMREPLY
>> NAMES #bnl
<< :Arlington.VA.US.Undernet.Org 353 z3p = #bnl :pSwede Dan-- SkOyg AG
"""
group = params[2][1:].lower()
users = params[3].split()
for ui in range(len(users)):
while users[ui][0] in ["@","+"]: # channel modes
users[ui]=users[ui][1:]
if group not in self._namreplies:
self._namreplies[group]=[]
self._namreplies[group].extend(users)
for nickname in users:
try:
self._ingroups[nickname].append(group)
except:
self._ingroups[nickname]=[group]
def irc_RPL_ENDOFNAMES(self,prefix,params):
group=params[1][1:]
self.getGroupConversation(group).setGroupMembers(self._namreplies[group.lower()])
del self._namreplies[group.lower()]
def irc_RPL_TOPIC(self,prefix,params):
self._topics[params[1][1:]]=params[2]
def irc_333(self,prefix,params):
group=params[1][1:]
self.getGroupConversation(group).setTopic(self._topics[group],params[2])
del self._topics[group]
def irc_TOPIC(self,prefix,params):
nickname = prefix.split("!")[0]
group = params[0][1:]
topic = params[1]
self.getGroupConversation(group).setTopic(topic,nickname)
def irc_JOIN(self,prefix,params):
nickname = prefix.split("!")[0]
group = params[0][1:].lower()
if nickname!=self.nickname:
try:
self._ingroups[nickname].append(group)
except:
self._ingroups[nickname]=[group]
self.getGroupConversation(group).memberJoined(nickname)
def irc_PART(self,prefix,params):
nickname = prefix.split("!")[0]
group = params[0][1:].lower()
if nickname!=self.nickname:
if group in self._ingroups[nickname]:
self._ingroups[nickname].remove(group)
self.getGroupConversation(group).memberLeft(nickname)
def irc_QUIT(self,prefix,params):
nickname = prefix.split("!")[0]
if nickname in self._ingroups:
for group in self._ingroups[nickname]:
self.getGroupConversation(group).memberLeft(nickname)
self._ingroups[nickname]=[]
def irc_NICK(self, prefix, params):
fromNick = prefix.split("!")[0]
toNick = params[0]
if fromNick not in self._ingroups:
return
for group in self._ingroups[fromNick]:
self.getGroupConversation(group).memberChangedNick(fromNick, toNick)
self._ingroups[toNick] = self._ingroups[fromNick]
del self._ingroups[fromNick]
def irc_unknown(self, prefix, command, params):
pass
# GTKIM calls
def joinGroup(self,name):
self.join(name)
self.getGroupConversation(name)
@implementer(interfaces.IAccount)
class IRCAccount(basesupport.AbstractAccount):
gatewayType = "IRC"
_groupFactory = IRCGroup
_personFactory = IRCPerson
def __init__(self, accountName, autoLogin, username, password, host, port,
channels=''):
basesupport.AbstractAccount.__init__(self, accountName, autoLogin,
username, password, host, port)
self.channels = [channel.strip() for channel in channels.split(',')]
if self.channels == ['']:
self.channels = []
def _startLogOn(self, chatui):
logonDeferred = defer.Deferred()
cc = protocol.ClientCreator(reactor, IRCProto, self, chatui,
logonDeferred)
d = cc.connectTCP(self.host, self.port)
d.addErrback(logonDeferred.errback)
return logonDeferred

View file

@ -0,0 +1,26 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
class Enum:
group = None
def __init__(self, label):
self.label = label
def __repr__(self):
return '<%s: %s>' % (self.group, self.label)
def __str__(self):
return self.label
class StatusEnum(Enum):
group = 'Status'
OFFLINE = Enum('Offline')
ONLINE = Enum('Online')
AWAY = Enum('Away')
class OfflineError(Exception):
"""The requested action can't happen while offline."""

View file

@ -0,0 +1,262 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
L{twisted.words} support for Instance Messenger.
"""
from __future__ import print_function
from twisted.internet import defer
from twisted.internet import error
from twisted.python import log
from twisted.python.failure import Failure
from twisted.spread import pb
from twisted.words.im.locals import ONLINE, OFFLINE, AWAY
from twisted.words.im import basesupport, interfaces
from zope.interface import implementer
class TwistedWordsPerson(basesupport.AbstractPerson):
"""I a facade for a person you can talk to through a twisted.words service.
"""
def __init__(self, name, wordsAccount):
basesupport.AbstractPerson.__init__(self, name, wordsAccount)
self.status = OFFLINE
def isOnline(self):
return ((self.status == ONLINE) or
(self.status == AWAY))
def getStatus(self):
return self.status
def sendMessage(self, text, metadata):
"""Return a deferred...
"""
if metadata:
d=self.account.client.perspective.directMessage(self.name,
text, metadata)
d.addErrback(self.metadataFailed, "* "+text)
return d
else:
return self.account.client.perspective.callRemote('directMessage',self.name, text)
def metadataFailed(self, result, text):
print("result:",result,"text:",text)
return self.account.client.perspective.directMessage(self.name, text)
def setStatus(self, status):
self.status = status
self.chat.getContactsList().setContactStatus(self)
@implementer(interfaces.IGroup)
class TwistedWordsGroup(basesupport.AbstractGroup):
def __init__(self, name, wordsClient):
basesupport.AbstractGroup.__init__(self, name, wordsClient)
self.joined = 0
def sendGroupMessage(self, text, metadata=None):
"""Return a deferred.
"""
#for backwards compatibility with older twisted.words servers.
if metadata:
d=self.account.client.perspective.callRemote(
'groupMessage', self.name, text, metadata)
d.addErrback(self.metadataFailed, "* "+text)
return d
else:
return self.account.client.perspective.callRemote('groupMessage',
self.name, text)
def setTopic(self, text):
self.account.client.perspective.callRemote(
'setGroupMetadata',
{'topic': text, 'topic_author': self.client.name},
self.name)
def metadataFailed(self, result, text):
print("result:",result,"text:",text)
return self.account.client.perspective.callRemote('groupMessage',
self.name, text)
def joining(self):
self.joined = 1
def leaving(self):
self.joined = 0
def leave(self):
return self.account.client.perspective.callRemote('leaveGroup',
self.name)
class TwistedWordsClient(pb.Referenceable, basesupport.AbstractClientMixin):
"""In some cases, this acts as an Account, since it a source of text
messages (multiple Words instances may be on a single PB connection)
"""
def __init__(self, acct, serviceName, perspectiveName, chatui,
_logonDeferred=None):
self.accountName = "%s (%s:%s)" % (acct.accountName, serviceName, perspectiveName)
self.name = perspectiveName
print("HELLO I AM A PB SERVICE", serviceName, perspectiveName)
self.chat = chatui
self.account = acct
self._logonDeferred = _logonDeferred
def getPerson(self, name):
return self.chat.getPerson(name, self)
def getGroup(self, name):
return self.chat.getGroup(name, self)
def getGroupConversation(self, name):
return self.chat.getGroupConversation(self.getGroup(name))
def addContact(self, name):
self.perspective.callRemote('addContact', name)
def remote_receiveGroupMembers(self, names, group):
print('received group members:', names, group)
self.getGroupConversation(group).setGroupMembers(names)
def remote_receiveGroupMessage(self, sender, group, message, metadata=None):
print('received a group message', sender, group, message, metadata)
self.getGroupConversation(group).showGroupMessage(sender, message, metadata)
def remote_memberJoined(self, member, group):
print('member joined', member, group)
self.getGroupConversation(group).memberJoined(member)
def remote_memberLeft(self, member, group):
print('member left')
self.getGroupConversation(group).memberLeft(member)
def remote_notifyStatusChanged(self, name, status):
self.chat.getPerson(name, self).setStatus(status)
def remote_receiveDirectMessage(self, name, message, metadata=None):
self.chat.getConversation(self.chat.getPerson(name, self)).showMessage(message, metadata)
def remote_receiveContactList(self, clist):
for name, status in clist:
self.chat.getPerson(name, self).setStatus(status)
def remote_setGroupMetadata(self, dict_, groupName):
if "topic" in dict_:
self.getGroupConversation(groupName).setTopic(dict_["topic"], dict_.get("topic_author", None))
def joinGroup(self, name):
self.getGroup(name).joining()
return self.perspective.callRemote('joinGroup', name).addCallback(self._cbGroupJoined, name)
def leaveGroup(self, name):
self.getGroup(name).leaving()
return self.perspective.callRemote('leaveGroup', name).addCallback(self._cbGroupLeft, name)
def _cbGroupJoined(self, result, name):
groupConv = self.chat.getGroupConversation(self.getGroup(name))
groupConv.showGroupMessage("sys", "you joined")
self.perspective.callRemote('getGroupMembers', name)
def _cbGroupLeft(self, result, name):
print('left',name)
groupConv = self.chat.getGroupConversation(self.getGroup(name), 1)
groupConv.showGroupMessage("sys", "you left")
def connected(self, perspective):
print('Connected Words Client!', perspective)
if self._logonDeferred is not None:
self._logonDeferred.callback(self)
self.perspective = perspective
self.chat.getContactsList()
pbFrontEnds = {
"twisted.words": TwistedWordsClient,
"twisted.reality": None
}
@implementer(interfaces.IAccount)
class PBAccount(basesupport.AbstractAccount):
gatewayType = "PB"
_groupFactory = TwistedWordsGroup
_personFactory = TwistedWordsPerson
def __init__(self, accountName, autoLogin, username, password, host, port,
services=None):
"""
@param username: The name of your PB Identity.
@type username: string
"""
basesupport.AbstractAccount.__init__(self, accountName, autoLogin,
username, password, host, port)
self.services = []
if not services:
services = [('twisted.words', 'twisted.words', username)]
for serviceType, serviceName, perspectiveName in services:
self.services.append([pbFrontEnds[serviceType], serviceName,
perspectiveName])
def logOn(self, chatui):
"""
@returns: this breaks with L{interfaces.IAccount}
@returntype: DeferredList of L{interfaces.IClient}s
"""
# Overriding basesupport's implementation on account of the
# fact that _startLogOn tends to return a deferredList rather
# than a simple Deferred, and we need to do registerAccountClient.
if (not self._isConnecting) and (not self._isOnline):
self._isConnecting = 1
d = self._startLogOn(chatui)
d.addErrback(self._loginFailed)
def registerMany(results):
for success, result in results:
if success:
chatui.registerAccountClient(result)
self._cb_logOn(result)
else:
log.err(result)
d.addCallback(registerMany)
return d
else:
raise error.ConnectionError("Connection in progress")
def _startLogOn(self, chatui):
print('Connecting...', end=' ')
d = pb.getObjectAt(self.host, self.port)
d.addCallbacks(self._cbConnected, self._ebConnected,
callbackArgs=(chatui,))
return d
def _cbConnected(self, root, chatui):
print('Connected!')
print('Identifying...', end=' ')
d = pb.authIdentity(root, self.username, self.password)
d.addCallbacks(self._cbIdent, self._ebConnected,
callbackArgs=(chatui,))
return d
def _cbIdent(self, ident, chatui):
if not ident:
print('falsely identified.')
return self._ebConnected(Failure(Exception("username or password incorrect")))
print('Identified!')
dl = []
for handlerClass, sname, pname in self.services:
d = defer.Deferred()
dl.append(d)
handler = handlerClass(self, sname, pname, chatui, d)
ident.callRemote('attach', sname, pname, handler).addCallback(handler.connected)
return defer.DeferredList(dl)
def _ebConnected(self, error):
print('Not connected.')
return error