Logo Search packages:      
Sourcecode: lernid version File versions  Download package

IrcBackend.py

#!/usr/bin/python
# -*- coding: utf-8 -*-
### BEGIN LICENSE
# Copyright (C) 2009 Jono Bacon <jono@ubuntu.com>
# Copyright (C) 2010 Michael Budde <mbudde@gmail.com>
#
#This program is free software: you can redistribute it and/or modify it
#under the terms of the GNU General Public License version 3, as published
#by the Free Software Foundation.
#
#This program is distributed in the hope that it will be useful, but
#WITHOUT ANY WARRANTY; without even the implied warranties of
#MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
#PURPOSE.  See the GNU General Public License for more details.
#
#You should have received a copy of the GNU General Public License along
#with this program.  If not, see <http://www.gnu.org/licenses/>.
### END LICENSE

import os
import gobject
import random
import dbus.mainloop.glib
dbus.mainloop.glib.DBusGMainLoop(set_as_default = True)

import telepathy
from telepathy.interfaces import *
from telepathy.constants import *

import logging

DBUS_PROPERTIES = dbus.PROPERTIES_IFACE

def ignore(*args): pass


_server_instances = {}

00039 class Server(gobject.GObject):

    __gsignals__ = {
        'connected': (
            gobject.SIGNAL_RUN_LAST, None, ()
        ),
        'disconnected': (
            gobject.SIGNAL_RUN_LAST, None, ()
        ),
        'identified': (
            gobject.SIGNAL_RUN_LAST, None, ()
        ),
    }

    DISCONNECTED, CONNECTING, CONNECTED = range(3)

    def __init__(self, server, nick):
        gobject.GObject.__init__(self)
        self._nick = nick
        self._server = server
        self._channels = {}
        self._private_channels = {}
        self._conn = None
        self._state = self.DISCONNECTED
        self._unconnected_channels = {}
        self._unconnected_private_channels = {}
        self._nickidentifier = None
        self._load_manager()
        self._request_connection(server, nick)

    @property
    def nick(self):
        return self._nick

    @nick.setter
    def nick(self, val):
        self._nick = val

    @classmethod
    def get_server(cls, server, nick):
        logging.debug('getting server')
        global _server_instances
        if server in _server_instances:
            server_obj = _server_instances[server]
            server_obj.nick = nick
            server_obj._request_connection(server, nick)
            return server_obj
        else:
            obj = cls(server, nick)
            _server_instances[server] = obj
            return obj

    def get_channel(self, name):
        if name in self._channels:
            return self._channels[name]
        obj = Channel(name)
        self._channels[name] = obj
        self._connect_channel(obj)
        return obj

    def get_private_channel(self, target):
        if target in self._private_channels:
            return self._private_channels[target]
        obj = PrivateChannel(target)
        self._private_channels[target] = obj
        self._connect_private_channel(obj)
        return obj

    def identify_nick(self, password):
        self._nickidentifier = NickServIdentifier(self.nick, password)
        self._connect_private_channel(self._nickidentifier)
        return self._nickidentifier

    def _load_manager(self):
        logging.debug('loading manager')
        reg = telepathy.client.ManagerRegistry()
        reg.LoadManagers()
        self._cm = reg.GetManager('idle') # IRC

    def _request_connection(self, server, nick):
        logging.debug('requesting connection: %s %s' % (server, nick))
        if self._state == self.DISCONNECTED:
            self._state = self.CONNECTING
            self._cm[CONNECTION_MANAGER].RequestConnection('irc',
                {'account': nick, 'server': server},
                reply_handler=self._got_connection,
                error_handler=self.error)
        else:
            logging.debug('ignoring request')

    def _got_connection(self, bus_name, object_path):
        logging.debug('got connection')
        self._conn = telepathy.client.Connection(
            bus_name, object_path,
            ready_handler=self._ready)
        self._conn[CONNECTION].connect_to_signal('StatusChanged',
                                                 self._status_changed)
        self._conn[CONNECTION].Connect(reply_handler=ignore,
                                       error_handler=self.error)

    def _ready(self, connection):
        logging.debug('server ready')
        self._state = self.CONNECTED
        for channel in self._unconnected_channels.keys():
            self._connect_channel(channel)
        self._unconnected_channels = {}
        for channel in self._unconnected_private_channels.keys():
            self._connect_private_channel(channel)
        self._unconnected_private_channels = {}

    def _connect_channel(self, channel):
        if self._state != self.CONNECTED:
            self._unconnected_channels[channel] = True
            return
        def setup_channel(yours, object_path, properties):
            channel.do_connect(self._conn, object_path, properties)
        ircchan = '#' + channel.name if not channel.name[1] in '#&!+' else channel.name
        self._conn[CONNECTION_INTERFACE_REQUESTS].EnsureChannel({
                CHANNEL + ".ChannelType": CHANNEL_TYPE_TEXT,
                CHANNEL + ".TargetHandleType": HANDLE_TYPE_ROOM,
                CHANNEL + ".TargetID": ircchan,
            },
            reply_handler = setup_channel,
            error_handler = self.error)

    def _connect_private_channel(self, channel):
        if self._state != self.CONNECTED:
            self._unconnected_private_channels[channel] = True
            return
        def setup_channel(yours, object_path, properties):
            channel.do_connect(self._conn, object_path, properties)
        self._conn[CONNECTION_INTERFACE_REQUESTS].EnsureChannel({
                CHANNEL + ".ChannelType": CHANNEL_TYPE_TEXT,
                CHANNEL + ".TargetHandleType": HANDLE_TYPE_CONTACT,
                CHANNEL + ".TargetID": channel.target,
            },
            reply_handler = setup_channel,
            error_handler = self.error)

    def _status_changed(self, status, reason):
        if status == CONNECTION_STATUS_CONNECTED:
            logging.debug('connected to server')
            self.emit('connected')
        elif status == CONNECTION_STATUS_DISCONNECTED:
            logging.debug('disconnected from server')
            for chan in self._channels.values():
                self._unconnected_channels[chan] = True
            for chan in self._private_channels.values():
                self._unconnected_private_channels[chan] = True
            if self._nickidentifier:
                self._unconnected_private_channels[self._nickidentifier] = True
            if self._state == self.CONNECTED:
                # Only emit signal if we had a connection before
                self.emit('disconnected')
            self._state = self.DISCONNECTED
            if reason == CONNECTION_STATUS_REASON_NAME_IN_USE:
                self._nick = '{0}{1}'.format(self._nick, random.randint(10, 99))
                self._request_connection(self._server, self._nick)

    def disconnect(self):
        if not self._conn:
            return
        self._conn[CONNECTION].Disconnect(reply_handler=ignore,
                                          error_handler=ignore)
        self._nickidentifier = None

    def error(self, e):
        logging.debug("ERROR:Server:%s:%s", e.get_dbus_name(), e.args[0])
        self._state = self.DISCONNECTED
        if e.get_dbus_name() == 'org.freedesktop.DBus.Error.ServiceUnknown':
            self._load_manager()
            self._request_connection(self._server, self._nick)
        else:
            self.disconnect()


00215 class Channel(gobject.GObject):

    TYPE_NORMAL = 0
    TYPE_ACTION = 1

    __gsignals__ = {
        'joined': (
            gobject.SIGNAL_RUN_LAST, None, ()
        ),
        'message-received': (
            gobject.SIGNAL_RUN_LAST, None, (str, str, int)
        ),
        'message-sent': (
            gobject.SIGNAL_RUN_LAST, None, (str,)
        ),
        'members-changed': (
            gobject.SIGNAL_RUN_LAST, None, (object,)
        ),
    }

00235     def __init__(self, name):
        """Channel objects should not be instancted directly, use
        Server.get_channel function."""
        logging.debug('channel created')
        gobject.GObject.__init__(self)
        self._name = name

00242     def do_connect(self, connection, object_path, properties):
        """Should only be called by Server"""
        logging.debug('connecting channel')
        self._members = {}
        self._conn = connection
        self._chan = telepathy.client.Channel(
            connection.service_name,
            object_path,
            ready_handler=self._ready)

    @property
    def name(self):
        return self._name

    def get_members(self):
        return self._members.values()

    def send_message(self, message):
        if not self._chan:
            return
        def ack():
            logging.debug('message sent to %s' % self.name)
            self.emit('message-sent', message)
        self._chan[CHANNEL_TYPE_TEXT].Send(0, message,
                                           reply_handler=ack,
                                           error_handler=self._conn.error)

    def is_moderated(self):
        try:
            return bool(self._chan[PROPERTIES_INTERFACE].GetProperties([3])[0][1])
        except Exception, e:
            logging.debug('could not acess "moderated" property: {0}'.format(e))
            return False

00276     def _ready(self, channel):
        """We have joined the channel."""
        logging.debug('connected channel %s' % self.name)
        self._chan[CHANNEL_INTERFACE_GROUP].connect_to_signal('MembersChanged',
                                                              self._members_changed)
        self._chan[CHANNEL_INTERFACE_GROUP].GetMembers(reply_handler=self._update_members,
                                                       error_handler=self._conn.error)
        self._chan[CHANNEL_TYPE_TEXT].connect_to_signal('Received', self._received_message)
        self.emit('joined')

    def _update_members(self, handles):
        def update_cb(ids):
            self._members.update(zip(handles, ids))
            logging.debug('members updated')
            self.emit('members-changed', self.get_members())
        self._conn[CONNECTION].InspectHandles(
            HANDLE_TYPE_CONTACT,
            handles,
            reply_handler=update_cb,
            error_handler=self._conn.error)

    def _members_changed(self, message, added, removed,
                         local_pending, remote_pending, actor, reason):
        logging.debug('members changed in %s' % self.name)
        for h in removed:
            if h in self._members:
                del self._members[h]
        self._update_members(added)

    def _received_message(self, id, timestamp, sender_handle, msgtype, flags, text):
        logging.debug('message received in %s' % self.name)
        sender = self._members.get(sender_handle, "???")
        if msgtype == CHANNEL_TEXT_MESSAGE_TYPE_ACTION:
            msgtype = self.TYPE_ACTION
        else:
            msgtype = self.TYPE_NORMAL
        self.emit('message-received', sender, text, msgtype)


00315 class PrivateChannel(gobject.GObject):

    __gsignals__ = {
        'joined': (
            gobject.SIGNAL_RUN_LAST, None, ()
        ),
        'message-received': (
            gobject.SIGNAL_RUN_LAST, None, (str, str)
        ),
        'message-sent': (
            gobject.SIGNAL_RUN_LAST, None, (str,)
        ),
    }

    @property
    def target(self):
        return self._target

00333     def __init__(self, target):
        """PrivateChannel objects should not be instanced directly, use
        Server.get_private_channel function."""
        logging.debug('private channel created')
        gobject.GObject.__init__(self)
        self._target = target
        self._conn = None
        self._chan = None

00342     def do_connect(self, connection, object_path, properties):
        """Should only be called by Server"""
        logging.debug('connecting private channel')
        self._conn = connection
        self._chan = telepathy.client.Channel(
            connection.service_name,
            object_path,
            ready_handler=self._ready)

    def send_message(self, message):
        if not self._chan:
            return False
        def ack():
            logging.debug('message sent to %s' % self.target)
            self.emit('message-sent', message)
        self._chan[CHANNEL_TYPE_TEXT].Send(0, message,
                                           reply_handler=ack,
                                           error_handler=self._conn.error)
        return True

00362     def _ready(self, channel):
        """We have joined the channel."""
        logging.debug('connected private channel %s' % self.target)
        self._chan[CHANNEL_TYPE_TEXT].connect_to_signal('Received', self._received_message)
        self.emit('joined')

    def _received_message(self, id, timestamp, sender_handle, msgtype, flags, text):
        logging.debug('private message received by %s' % self.target)
        self.emit('message-received', self.target, text)


00373 class NickServIdentifier(PrivateChannel):
    """A class for identifying with NickServ.
    Should only be instanciated by Server object
    """

    __gsignals__ = {
        'identified': (
            gobject.SIGNAL_RUN_LAST, None, ()
        ),
        'invalid-password': (
            gobject.SIGNAL_RUN_LAST, None, ()
        ),
    }

    def __init__(self, nick, password):
        PrivateChannel.__init__(self, 'NickServ')
        self._nick = nick
        self.connect('joined', self._joined, password)
        self.connect('message-received', self._msg_received)

00393     def retry(self, password):
        """Try indentifying with NickServ again.
        To be used if initial identification fails because of invalid password.
        """
        self._identify(password)

    def _joined(self, chan, password):
        self._identify(password)

    def _identify(self, password):
        self.send_message('identify {0} {1}'.format(self._nick, password))

    def _msg_received(self, chan, sender, msg):
        if msg.startswith('You are now identified'):
            logging.debug('identified nick')
            self.emit('identified')
        elif msg.startswith('Invalid password'):
            logging.debug('invalid password')
            self.emit('invalid-password')

Generated by  Doxygen 1.6.0   Back to index