# -*- coding:utf-8 -*-
## src/common/connection_handlers_events.py
##
## Copyright (C) 2010-2014 Yann Leboulanger <asterix AT lagaule.org>
##
## This file is part of Gajim.
##
## Gajim is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published
## by the Free Software Foundation; version 3 only.
##
## Gajim is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY 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 Gajim. If not, see <http://www.gnu.org/licenses/>.
##

# pylint: disable=no-init
# pylint: disable=attribute-defined-outside-init

from calendar import timegm
import datetime
import hashlib
import hmac
import logging
import sys
import os
from time import time as time_time

import nbxmpp
from nbxmpp.protocol import NS_CHATSTATES
from gajim.common import atom
from gajim.common import nec
from gajim.common import helpers
from gajim.common import app
from gajim.common import i18n
from gajim.common import dataforms
from gajim.common.zeroconf.zeroconf import Constant
from gajim.common.logger import KindConstant
from gajim.common.pep import SUPPORTED_PERSONAL_USER_EVENTS
from gajim.common.jingle_transport import JingleTransportSocks5
from gajim.common.file_props import FilesProp
from gajim.common.nec import NetworkEvent

if app.HAVE_PYOPENSSL:
    import OpenSSL.crypto

log = logging.getLogger('gajim.c.connection_handlers_events')

CONDITION_TO_CODE = {
    'realjid-public': 100,
    'affiliation-changed': 101,
    'unavailable-shown': 102,
    'unavailable-not-shown': 103,
    'configuration-changed': 104,
    'self-presence': 110,
    'logging-enabled': 170,
    'logging-disabled': 171,
    'non-anonymous': 172,
    'semi-anonymous': 173,
    'fully-anonymous': 174,
    'room-created': 201,
    'nick-assigned': 210,
    'banned': 301,
    'new-nick': 303,
    'kicked': 307,
    'removed-affiliation': 321,
    'removed-membership': 322,
    'removed-shutdown': 332,
}

class HelperEvent:
    def get_jid_resource(self, check_fake_jid=False):
        if check_fake_jid and hasattr(self, 'id_') and \
                self.id_ in self.conn.groupchat_jids:
            self.fjid = self.conn.groupchat_jids[self.id_]
            del self.conn.groupchat_jids[self.id_]
        else:
            self.fjid = helpers.get_full_jid_from_iq(self.stanza)
        self.jid, self.resource = app.get_room_and_nick_from_fjid(self.fjid)

    def get_id(self):
        self.id_ = self.stanza.getID()

    def get_gc_control(self):
        self.gc_control = app.interface.msg_win_mgr.get_gc_control(self.jid,
                                                                     self.conn.name)

        # If gc_control is missing - it may be minimized. Try to get it
        # from there. If it's not there - then it's missing anyway and
        # will remain set to None.
        if not self.gc_control:
            minimized = app.interface.minimized_controls[self.conn.name]
            self.gc_control = minimized.get(self.jid)

    def _generate_timestamp(self, tag):
        # Make sure we use only int/float Epoch time
        if tag is None:
            self.timestamp = time_time()
            return
        try:
            tim = helpers.datetime_tuple(tag)
            self.timestamp = timegm(tim)
        except Exception:
            log.error('wrong timestamp, ignoring it: ' + tag)
            self.timestamp = time_time()

    def get_chatstate(self):
        """
        Extract chatstate from a <message/> stanza
        Requires self.stanza and self.msgtxt
        """
        self.chatstate = None

        # chatstates - look for chatstate tags in a message if not delayed
        delayed = self.stanza.getTag('x', namespace=nbxmpp.NS_DELAY) is not None
        if not delayed:
            children = self.stanza.getChildren()
            for child in children:
                if child.getNamespace() == NS_CHATSTATES:
                    self.chatstate = child.getName()
                    break

    def get_oob_data(self, stanza):
        oob_node = stanza.getTag('x', namespace=nbxmpp.NS_X_OOB)
        if oob_node is not None:
            if 'gajim' not in self.additional_data:
                self.additional_data['gajim'] = {}
            oob_url = oob_node.getTagData('url')
            if oob_url is not None:
                self.additional_data['gajim']['oob_url'] = oob_url
            oob_desc = oob_node.getTagData('desc')
            if oob_desc is not None:
                self.additional_data['gajim']['oob_desc'] = oob_desc

    def get_stanza_id(self, stanza, query=False):
        if query:
            # On a MAM query the stanza-id is maybe not set, so
            # get the id of the stanza
            return stanza.getAttr('id')
        stanza_id, by = stanza.getStanzaIDAttrs()
        if by is None:
            # We can not verify who set this stanza-id, ignore it.
            return
        if stanza.getType() == 'groupchat':
            if stanza.getFrom().bareMatch(by):
                # by attribute must match the server
                return stanza_id
        elif self.conn.get_own_jid().bareMatch(by):
            # by attribute must match the server
            return stanza_id
        return

    @staticmethod
    def get_forwarded_message(stanza):
        forwarded = stanza.getTag('forwarded',
                                  namespace=nbxmpp.NS_FORWARD,
                                  protocol=True)
        if forwarded is not None:
            return forwarded.getTag('message', protocol=True)

    def _is_self_message(self, message):
        if self.self_message is not None:
            return self.self_message
        own_jid = self.conn.get_own_jid()
        frm = message.getFrom()
        to = message.getTo()
        # If 'to' is not set we assume own jid
        self.self_message = frm.bareMatch(to or own_jid)
        return self.self_message

    def _is_muc_pm(self, message):
        if self.muc_pm is not None:
            return self.muc_pm
        self.muc_pm = False
        muc_user = message.getTag('x', namespace=nbxmpp.NS_MUC_USER)
        if muc_user is not None:
            self.muc_pm = muc_user.getChildren() == []
        return self.muc_pm

class HttpAuthReceivedEvent(nec.NetworkIncomingEvent):
    name = 'http-auth-received'
    base_network_events = []

    def generate(self):
        self.opt = app.config.get_per('accounts', self.conn.name, 'http_auth')
        self.iq_id = self.stanza.getTagAttr('confirm', 'id')
        self.method = self.stanza.getTagAttr('confirm', 'method')
        self.url = self.stanza.getTagAttr('confirm', 'url')
        # In case it's a message with a body
        self.msg = self.stanza.getTagData('body')
        return True

class VersionResultReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
    name = 'version-result-received'
    base_network_events = []

    def generate(self):
        self.get_id()
        self.get_jid_resource(check_fake_jid=True)
        if self.id_ in self.conn.version_ids:
            self.conn.version_ids.remove(self.id_)

        self.client_info = ''
        self.os_info = ''

        if self.stanza.getType() == 'error':
            return True

        qp = self.stanza.getTag('query')
        if qp.getTag('name'):
            self.client_info += qp.getTag('name').getData()
        if qp.getTag('version'):
            self.client_info += ' ' + qp.getTag('version').getData()
        if qp.getTag('os'):
            self.os_info += qp.getTag('os').getData()

        return True

class TimeResultReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
    name = 'time-result-received'
    base_network_events = []

    def generate(self):
        self.get_id()
        self.get_jid_resource(check_fake_jid=True)
        if self.id_ in self.conn.entity_time_ids:
            self.conn.entity_time_ids.remove(self.id_)

        self.time_info = ''

        if self.stanza.getType() == 'error':
            return True

        qp = self.stanza.getTag('time')
        if not qp:
            # wrong answer
            return
        tzo = qp.getTag('tzo').getData()
        if tzo.lower() == 'z':
            tzo = '0:0'
        try:
            tzoh, tzom = tzo.split(':')
        except Exception as e:
            # wrong tzo
            return
        utc_time = qp.getTag('utc').getData()
        ZERO = datetime.timedelta(0)
        class UTC(datetime.tzinfo):
            def utcoffset(self, dt):
                return ZERO
            def tzname(self, dt):
                return "UTC"
            def dst(self, dt):
                return ZERO

        class contact_tz(datetime.tzinfo):
            def utcoffset(self, dt):
                return datetime.timedelta(hours=int(tzoh), minutes=int(tzom))
            def tzname(self, dt):
                return "remote timezone"
            def dst(self, dt):
                return ZERO

        if utc_time[-1:] == 'Z':
            # Remove the trailing 'Z'
            utc_time = utc_time[:-1]
        elif utc_time[-6:] == "+00:00":
            # Remove the trailing "+00:00"
            utc_time = utc_time[:-6]
        else:
            log.info("Wrong timezone defintion: %s" % utc_time)
            return
        try:
            t = datetime.datetime.strptime(utc_time, '%Y-%m-%dT%H:%M:%S')
        except ValueError:
            try:
                t = datetime.datetime.strptime(utc_time,
                                               '%Y-%m-%dT%H:%M:%S.%f')
            except ValueError as e:
                log.info('Wrong time format: %s' % str(e))
                return

        t = t.replace(tzinfo=UTC())
        self.time_info = t.astimezone(contact_tz()).strftime('%c')
        return True

class RosterItemExchangeEvent(nec.NetworkIncomingEvent, HelperEvent):
    name = 'roster-item-exchange-received'
    base_network_events = []

    def generate(self):
        self.get_id()
        self.get_jid_resource()
        self.exchange_items_list = {}
        items_list = self.stanza.getTag('x').getChildren()
        if not items_list:
            return
        self.action = items_list[0].getAttr('action')
        if self.action is None:
            self.action = 'add'
        for item in self.stanza.getTag('x', namespace=nbxmpp.NS_ROSTERX).\
        getChildren():
            try:
                jid = helpers.parse_jid(item.getAttr('jid'))
            except helpers.InvalidFormat:
                log.warning('Invalid JID: %s, ignoring it' % item.getAttr('jid'))
                continue
            name = item.getAttr('name')
            contact = app.contacts.get_contact(self.conn.name, jid)
            groups = []
            same_groups = True
            for group in item.getTags('group'):
                groups.append(group.getData())
                # check that all suggested groups are in the groups we have for
                # this contact
                if not contact or group not in contact.groups:
                    same_groups = False
            if contact:
                # check that all groups we have for this contact are in the
                # suggested groups
                for group in contact.groups:
                    if group not in groups:
                        same_groups = False
                if contact.sub in ('both', 'to') and same_groups:
                    continue
            self.exchange_items_list[jid] = []
            self.exchange_items_list[jid].append(name)
            self.exchange_items_list[jid].append(groups)
        if self.exchange_items_list:
            return True

class VersionRequestEvent(nec.NetworkIncomingEvent):
    name = 'version-request-received'
    base_network_events = []

class LastRequestEvent(nec.NetworkIncomingEvent):
    name = 'last-request-received'
    base_network_events = []

class TimeRequestEvent(nec.NetworkIncomingEvent):
    name = 'time-request-received'
    base_network_events = []

class TimeRevisedRequestEvent(nec.NetworkIncomingEvent):
    name = 'time-revised-request-received'
    base_network_events = []

class RosterReceivedEvent(nec.NetworkIncomingEvent):
    name = 'roster-received'
    base_network_events = []

    def generate(self):
        if hasattr(self, 'xmpp_roster'):
            self.version = self.xmpp_roster.version
            self.received_from_server = self.xmpp_roster.received_from_server
            self.roster = {}
            raw_roster = self.xmpp_roster.getRaw()
            our_jid = app.get_jid_from_account(self.conn.name)

            for jid in raw_roster:
                try:
                    j = helpers.parse_jid(jid)
                except Exception:
                    print(_('JID %s is not RFC compliant. It will not be added '
                            'to your roster. Use roster management tools such as '
                            'http://jru.jabberstudio.org/ to remove it') % jid,
                          file=sys.stderr)
                else:
                    infos = raw_roster[jid]
                    if jid != our_jid and (not infos['subscription'] or \
                            infos['subscription'] == 'none') and (not infos['ask'] or \
                            infos['ask'] == 'none') and not infos['name'] and \
                            not infos['groups']:
                        # remove this useless item, it won't be shown in roster
                        # anyway
                        self.conn.connection.getRoster().delItem(jid)
                    elif jid != our_jid: # don't add our jid
                        self.roster[j] = raw_roster[jid]
                        self.roster[j]['avatar_sha'] = None
        else:
            # Roster comes from DB
            self.received_from_server = False
            self.version = app.config.get_per('accounts', self.conn.name,
                'roster_version')
            self.roster = app.logger.get_roster(app.get_jid_from_account(
                self.conn.name))
            if not self.roster:
                app.config.set_per(
                    'accounts', self.conn.name, 'roster_version', '')
        return True

class RosterSetReceivedEvent(nec.NetworkIncomingEvent):
    name = 'roster-set-received'
    base_network_events = []

    def generate(self):
        frm = helpers.get_jid_from_iq(self.stanza)
        our_jid = app.get_jid_from_account(self.conn.name)
        if frm and frm != our_jid and frm != app.get_server_from_jid(our_jid):
            return
        self.version = self.stanza.getTagAttr('query', 'ver')
        self.items = {}
        for item in self.stanza.getTag('query').getChildren():
            try:
                jid = helpers.parse_jid(item.getAttr('jid'))
            except helpers.InvalidFormat:
                log.warning('Invalid JID: %s, ignoring it' % item.getAttr('jid'))
                continue
            name = item.getAttr('name')
            sub = item.getAttr('subscription')
            ask = item.getAttr('ask')
            groups = []
            for group in item.getTags('group'):
                groups.append(group.getData())
            self.items[jid] = {'name': name, 'sub': sub, 'ask': ask,
                'groups': groups}
        if len(self.items) > 1:
            reply = nbxmpp.Iq(typ='error', attrs={'id': self.stanza.getID()},
                to=self.stanza.getFrom(), frm=self.stanza.getTo(), xmlns=None)
            self.conn.connection.send(reply)
            return
        if self.conn.connection and self.conn.connected > 1:
            reply = nbxmpp.Iq(typ='result', attrs={'id': self.stanza.getID()},
                to=self.stanza.getFrom(), frm=self.stanza.getTo(), xmlns=None)
            self.conn.connection.send(reply)
        return True

class RosterInfoEvent(nec.NetworkIncomingEvent):
    name = 'roster-info'
    base_network_events = []

    def init(self):
        self.avatar_sha = None

class MucOwnerReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
    name = 'muc-owner-received'
    base_network_events = []

    def generate(self):
        self.get_jid_resource()
        qp = self.stanza.getQueryPayload()
        self.form_node = None
        for q in qp:
            if q.getNamespace() == nbxmpp.NS_DATA:
                self.form_node = q
                self.dataform = dataforms.ExtendForm(node=self.form_node)
                return True

class MucAdminReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
    name = 'muc-admin-received'
    base_network_events = []

    def generate(self):
        self.get_jid_resource()
        items = self.stanza.getTag('query',
            namespace=nbxmpp.NS_MUC_ADMIN).getTags('item')
        self.users_dict = {}
        for item in items:
            if item.has_attr('jid') and item.has_attr('affiliation'):
                try:
                    jid = helpers.parse_jid(item.getAttr('jid'))
                except helpers.InvalidFormat:
                    log.warning('Invalid JID: %s, ignoring it' % \
                        item.getAttr('jid'))
                    continue
                affiliation = item.getAttr('affiliation')
                self.users_dict[jid] = {'affiliation': affiliation}
                if item.has_attr('nick'):
                    self.users_dict[jid]['nick'] = item.getAttr('nick')
                if item.has_attr('role'):
                    self.users_dict[jid]['role'] = item.getAttr('role')
                reason = item.getTagData('reason')
                if reason:
                    self.users_dict[jid]['reason'] = reason
        return True

class PrivateStorageReceivedEvent(nec.NetworkIncomingEvent):
    name = 'private-storage-received'
    base_network_events = []

    def generate(self):
        query = self.stanza.getTag('query')
        self.storage_node = query.getTag('storage')
        if self.storage_node:
            self.namespace = self.storage_node.getNamespace()
            return True


class BookmarksHelper:
    def parse_bookmarks(self):
        self.bookmarks = []
        NS_GAJIM_BM = 'xmpp:gajim.org/bookmarks'
        confs = self.storage_node.getTags('conference')
        for conf in confs:
            autojoin_val = conf.getAttr('autojoin')
            if not autojoin_val:  # not there (it's optional)
                autojoin_val = False
            minimize_val = conf.getTag('minimize', namespace=NS_GAJIM_BM)
            if not minimize_val:  # not there, try old Gajim behaviour
                minimize_val = conf.getAttr('minimize')
                if not minimize_val:  # not there (it's optional)
                    minimize_val = False
            else:
                minimize_val = minimize_val.getData()

            print_status = conf.getTag('print_status', namespace=NS_GAJIM_BM)
            if not print_status:  # not there, try old Gajim behaviour
                print_status = conf.getTagData('print_status')
                if not print_status:  # not there, try old Gajim behaviour
                    print_status = conf.getTagData('show_status')
            else:
                print_status = print_status.getData()

            try:
                jid = helpers.parse_jid(conf.getAttr('jid'))
            except helpers.InvalidFormat:
                log.warning('Invalid JID: %s, ignoring it'
                            % conf.getAttr('jid'))
                continue

            bm = {'name': conf.getAttr('name'),
                  'jid': jid,
                  'autojoin': autojoin_val,
                  'minimize': minimize_val,
                  'password': conf.getTagData('password'),
                  'nick': conf.getTagData('nick'),
                  'print_status': print_status}

            bm_jids = [b['jid'] for b in self.bookmarks]
            if bm['jid'] not in bm_jids:
                self.bookmarks.append(bm)

class PrivateStorageBookmarksReceivedEvent(nec.NetworkIncomingEvent,
BookmarksHelper):
    name = 'private-storage-bookmarks-received'
    base_network_events = ['private-storage-received']

    def generate(self):
        self.conn = self.base_event.conn
        self.storage_node = self.base_event.storage_node
        if self.base_event.namespace != nbxmpp.NS_BOOKMARKS:
            return
        self.parse_bookmarks()
        return True

class BookmarksReceivedEvent(nec.NetworkIncomingEvent):
    name = 'bookmarks-received'
    base_network_events = ['private-storage-bookmarks-received',
        'pubsub-bookmarks-received']

    def generate(self):
        self.conn = self.base_event.conn
        self.bookmarks = self.base_event.bookmarks
        return True

class PrivateStorageRosternotesReceivedEvent(nec.NetworkIncomingEvent):
    name = 'private-storage-rosternotes-received'
    base_network_events = ['private-storage-received']

    def generate(self):
        self.conn = self.base_event.conn
        if self.base_event.namespace != nbxmpp.NS_ROSTERNOTES:
            return
        notes = self.base_event.storage_node.getTags('note')
        self.annotations = {}
        for note in notes:
            try:
                jid = helpers.parse_jid(note.getAttr('jid'))
            except helpers.InvalidFormat:
                log.warning('Invalid JID: %s, ignoring it' % note.getAttr('jid'))
                continue
            annotation = note.getData()
            self.annotations[jid] = annotation
        if self.annotations:
            return True

class RosternotesReceivedEvent(nec.NetworkIncomingEvent):
    name = 'rosternotes-received'
    base_network_events = ['private-storage-rosternotes-received']

    def generate(self):
        self.conn = self.base_event.conn
        self.annotations = self.base_event.annotations
        return True

class PubsubReceivedEvent(nec.NetworkIncomingEvent):
    name = 'pubsub-received'
    base_network_events = []

    def generate(self):
        self.jid = self.stanza.getFrom()
        self.pubsub_node = self.stanza.getTag('pubsub')
        if not self.pubsub_node:
            return
        self.items_node = self.pubsub_node.getTag('items')
        if not self.items_node:
            return
        return True

class PubsubBookmarksReceivedEvent(nec.NetworkIncomingEvent, BookmarksHelper):
    name = 'pubsub-bookmarks-received'
    base_network_events = ['pubsub-received']

    def generate(self):
        self.conn = self.base_event.conn
        self.item_node = self.base_event.items_node.getTag('item')
        if not self.item_node:
            return
        children = self.item_node.getChildren()
        if not children:
            return
        self.storage_node = children[0]
        ns = self.storage_node.getNamespace()
        if ns != nbxmpp.NS_BOOKMARKS:
            return
        self.parse_bookmarks()
        return True

class SearchFormReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
    name = 'search-form-received'
    base_network_events = []

    def generate(self):
        self.get_jid_resource()
        self.data = None
        self.is_dataform = False
        tag = self.stanza.getTag('query', namespace=nbxmpp.NS_SEARCH)
        if not tag:
            return True
        self.data = tag.getTag('x', namespace=nbxmpp.NS_DATA)
        if self.data:
            self.is_dataform = True
            return True
        self.data = {}
        for i in self.stanza.getQueryPayload():
            self.data[i.getName()] = i.getData()
        return True


class SearchResultReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
    name = 'search-result-received'
    base_network_events = []

    def generate(self):
        self.get_jid_resource()
        self.data = None
        self.is_dataform = False
        tag = self.stanza.getTag('query', namespace=nbxmpp.NS_SEARCH)
        if not tag:
            return True
        self.data = tag.getTag('x', namespace=nbxmpp.NS_DATA)
        if self.data:
            self.is_dataform = True
            return True
        self.data = []
        for item in tag.getTags('item'):
            # We also show attributes. jid is there
            f = item.attrs
            for i in item.getPayload():
                f[i.getName()] = i.getData()
            self.data.append(f)
        return True

class IqErrorReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
    name = 'iq-error-received'
    base_network_events = []

    def generate(self):
        self.get_id()
        self.get_jid_resource(check_fake_jid=True)
        self.errmsg = self.stanza.getErrorMsg()
        self.errcode = self.stanza.getErrorCode()
        return True

class PingReceivedEvent(nec.NetworkIncomingEvent):
    name = 'ping-received'
    base_network_events = []

class StreamReceivedEvent(nec.NetworkIncomingEvent):
    name = 'stream-received'
    base_network_events = []

class StreamConflictReceivedEvent(nec.NetworkIncomingEvent):
    name = 'stream-conflict-received'
    base_network_events = ['stream-received']

    def generate(self):
        if self.base_event.stanza.getTag('conflict'):
            self.conn = self.base_event.conn
            return True

class StreamOtherHostReceivedEvent(nec.NetworkIncomingEvent):
    name = 'stream-other-host-received'
    base_network_events = ['stream-received']

    def generate(self):
        self.conn = self.base_event.conn
        self.stanza = self.base_event.stanza
        other_host = self.stanza.getTag('see-other-host')
        if other_host and self.conn._current_type in ('ssl', 'tls'):
            host = other_host.getData()
            if ':' in host:
                host_l = host.split(':', 1)
                h = host_l[0]
                p = host_l[1]
            else:
                h = host
                p = 5222
            if h.startswith('[') and h.endswith(']'):
                h = h[1:-1]
            self.redirected = {'host': h, 'port': p}
            return True

class PresenceHelperEvent:
    def _generate_show(self):
        self.show = self.stanza.getShow()
        if self.show not in ('chat', 'away', 'xa', 'dnd'):
            self.show = '' # We ignore unknown show
        if not self.ptype and not self.show:
            self.show = 'online'
        elif self.ptype == 'unavailable':
            self.show = 'offline'

    def _generate_ptype(self):
        self.ptype = self.stanza.getType()
        if self.ptype == 'available':
            self.ptype = None
        rfc_types = ('unavailable', 'error', 'subscribe', 'subscribed',
            'unsubscribe', 'unsubscribed')
        if self.ptype and not self.ptype in rfc_types:
            self.ptype = None

class PresenceReceivedEvent(nec.NetworkIncomingEvent, HelperEvent,
PresenceHelperEvent):
    name = 'presence-received'
    base_network_events = ['raw-pres-received']

    def _generate_keyID(self, sig_tag):
        self.keyID = ''
        if sig_tag and self.conn.USE_GPG and self.ptype != 'error':
            # error presences contain our own signature
            # verify
            sig_msg = sig_tag.getData()
            self.keyID = self.conn.gpg.verify(self.status, sig_msg)
            self.keyID = helpers.prepare_and_validate_gpg_keyID(self.conn.name,
                                                                self.jid,
                                                                self.keyID)

    def _generate_prio(self):
        self.prio = self.stanza.getPriority()
        try:
            self.prio = int(self.prio)
        except Exception:
            self.prio = 0

    def generate(self):
        self.conn = self.base_event.conn
        self.stanza = self.base_event.stanza

        self.need_add_in_roster = False
        self.need_redraw = False

        self.popup = False # Do we want to open chat window ?

        if not self.conn or self.conn.connected < 2:
            log.debug('account is no more connected')
            return

        self._generate_ptype()
        try:
            self.get_jid_resource()
        except Exception:
            log.warning('Invalid JID: %s, ignoring it' % self.stanza.getFrom())
            return
        jid_list = app.contacts.get_jid_list(self.conn.name)
        self.timestamp = None
        self.get_id()
        self.is_gc = False # is it a GC presence ?
        sig_tag = None
        self.avatar_sha = None
        # XEP-0172 User Nickname
        self.user_nick = self.stanza.getTagData('nick') or ''
        self.contact_nickname = None
        self.transport_auto_auth = False
        # XEP-0203
        delay_tag = self.stanza.getTag('delay', namespace=nbxmpp.NS_DELAY2)
        if delay_tag:
            self._generate_timestamp(self.stanza.timestamp)
        # XEP-0319
        self.idle_time = None
        idle_tag = self.stanza.getTag('idle', namespace=nbxmpp.NS_IDLE)
        if idle_tag:
            time_str = idle_tag.getAttr('since')
            tim = helpers.datetime_tuple(time_str)
            self.idle_time = timegm(tim)
        xtags = self.stanza.getTags('x')
        for x in xtags:
            namespace = x.getNamespace()
            if namespace.startswith(nbxmpp.NS_MUC):
                self.is_gc = True
            elif namespace == nbxmpp.NS_SIGNED:
                sig_tag = x
            elif namespace == nbxmpp.NS_VCARD_UPDATE:
                self.avatar_sha = x.getTagData('photo')
                self.contact_nickname = x.getTagData('nickname')
            elif namespace == nbxmpp.NS_DELAY and not self.timestamp:
                # XEP-0091
                self._generate_timestamp(self.stanza.timestamp)
            elif namespace == 'http://delx.cjb.net/protocol/roster-subsync':
                # see http://trac.gajim.org/ticket/326
                agent = app.get_server_from_jid(self.jid)
                if self.conn.connection.getRoster().getItem(agent):
                    # to be sure it's a transport contact
                    self.transport_auto_auth = True

        if not self.is_gc and self.id_ and self.id_.startswith('gajim_muc_') \
        and self.ptype == 'error':
            # Error presences may not include sent stanza, so we don't detect
            # it's a muc presence. So detect it by ID
            h = hmac.new(self.conn.secret_hmac, self.jid.encode('utf-8'),
                         hashlib.md5).hexdigest()[:6]
            if self.id_.split('_')[-1] == h:
                self.is_gc = True
        self.status = self.stanza.getStatus() or ''
        self._generate_show()
        self._generate_prio()
        self._generate_keyID(sig_tag)

        self.errcode = self.stanza.getErrorCode()
        self.errmsg = self.stanza.getErrorMsg()

        if self.is_gc:
            app.nec.push_incoming_event(
                GcPresenceReceivedEvent(
                    None, conn=self.conn, stanza=self.stanza,
                    presence_obj=self))
            return

        if self.ptype == 'subscribe':
            app.nec.push_incoming_event(SubscribePresenceReceivedEvent(None,
                conn=self.conn, stanza=self.stanza, presence_obj=self))
        elif self.ptype == 'subscribed':
            # BE CAREFUL: no con.updateRosterItem() in a callback
            app.nec.push_incoming_event(SubscribedPresenceReceivedEvent(None,
                conn=self.conn, stanza=self.stanza, presence_obj=self))
        elif self.ptype == 'unsubscribe':
            log.debug(_('unsubscribe request from %s') % self.jid)
        elif self.ptype == 'unsubscribed':
            app.nec.push_incoming_event(UnsubscribedPresenceReceivedEvent(
                None, conn=self.conn, stanza=self.stanza, presence_obj=self))
        elif self.ptype == 'error':
            return

        if not self.ptype or self.ptype == 'unavailable':
            our_jid = app.get_jid_from_account(self.conn.name)
            if self.jid == our_jid and self.resource == self.conn.server_resource:
                # We got our own presence
                app.nec.push_incoming_event(OurShowEvent(None, conn=self.conn,
                                                           show=self.show))
            elif self.jid in jid_list or self.jid == our_jid:
                return True

class ZeroconfPresenceReceivedEvent(nec.NetworkIncomingEvent):
    name = 'presence-received'
    base_network_events = []

    def generate(self):
        self.jid, self.resource = app.get_room_and_nick_from_fjid(self.fjid)
        self.resource = 'local'
        self.prio = 0
        self.keyID = None
        self.timestamp = 0
        self.contact_nickname = None
        self.avatar_sha = None
        self.need_add_in_roster = False
        self.need_redraw = False
        if self.show == 'offline':
            self.ptype = 'unavailable'
        else:
            self.ptype = None
        self.is_gc = False
        self.user_nick = ''
        self.transport_auto_auth = False
        self.errcode = None
        self.errmsg = ''
        self.popup = False # Do we want to open chat window ?
        return True

class GcPresenceReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
    name = 'gc-presence-received'
    base_network_events = []

    def generate(self):
        self.ptype = self.presence_obj.ptype
        self.fjid = self.presence_obj.fjid
        self.jid = self.presence_obj.jid
        self.room_jid = self.presence_obj.jid
        self.nick = self.presence_obj.resource
        self.show = self.presence_obj.show
        self.status = self.presence_obj.status
        self.avatar_sha = self.presence_obj.avatar_sha
        self.errcode = self.presence_obj.errcode
        self.errmsg = self.presence_obj.errmsg
        self.errcon = self.stanza.getError()
        self.get_gc_control()
        self.gc_contact = app.contacts.get_gc_contact(self.conn.name,
            self.room_jid, self.nick)

        if self.ptype == 'error':
            return True

        if self.ptype and self.ptype != 'unavailable':
            return
        if app.config.get('log_contact_status_changes') and \
        app.config.should_log(self.conn.name, self.room_jid):
            if self.gc_contact:
                jid = self.gc_contact.jid
            else:
                jid = self.stanza.getJid()
            st = self.status
            if jid:
                # we know real jid, save it in db
                st += ' (%s)' % jid
            show = app.logger.convert_show_values_to_db_api_values(self.show)
            if show is not None:
                fjid = nbxmpp.JID(self.fjid)
                app.logger.insert_into_logs(self.conn.name,
                                            fjid.getStripped(),
                                            time_time(),
                                            KindConstant.GCSTATUS,
                                            contact_name=fjid.getResource(),
                                            message=st,
                                            show=show)


        # NOTE: if it's a gc presence, don't ask vcard here.
        # We may ask it to real jid in gui part.
        self.status_code = []
        ns_muc_user_x = self.stanza.getTag('x', namespace=nbxmpp.NS_MUC_USER)
        if ns_muc_user_x:
            destroy = ns_muc_user_x.getTag('destroy')
        else:
            destroy = None
        if ns_muc_user_x and destroy:
            # Room has been destroyed. see
            # http://www.xmpp.org/extensions/xep-0045.html#destroyroom
            self.reason = _('Room has been destroyed')
            r = destroy.getTagData('reason')
            if r:
                self.reason += ' (%s)' % r
            if destroy.getAttr('jid'):
                try:
                    jid = helpers.parse_jid(destroy.getAttr('jid'))
                    self.reason += '\n' + \
                        _('You can join this room instead: %s') % jid
                except helpers.InvalidFormat:
                    pass
            self.status_code = ['destroyed']
        else:
            self.reason = self.stanza.getReason()
            conditions = self.stanza.getStatusConditions()
            if conditions:
                self.status_code = []
                for condition in conditions:
                    if condition in CONDITION_TO_CODE:
                        self.status_code.append(CONDITION_TO_CODE[condition])
            else:
                self.status_code = self.stanza.getStatusCode()

        self.role = self.stanza.getRole()
        self.affiliation = self.stanza.getAffiliation()
        self.real_jid = self.stanza.getJid()
        self.actor = self.stanza.getActor()
        self.new_nick = self.stanza.getNewNick()
        return True

class SubscribePresenceReceivedEvent(nec.NetworkIncomingEvent):
    name = 'subscribe-presence-received'
    base_network_events = []

    def generate(self):
        self.jid = self.presence_obj.jid
        self.fjid = self.presence_obj.fjid
        self.status = self.presence_obj.status
        self.transport_auto_auth = self.presence_obj.transport_auto_auth
        self.user_nick = self.presence_obj.user_nick
        return True

class SubscribedPresenceReceivedEvent(nec.NetworkIncomingEvent):
    name = 'subscribed-presence-received'
    base_network_events = []

    def generate(self):
        self.jid = self.presence_obj.jid
        self.resource = self.presence_obj.resource
        return True

class UnsubscribedPresenceReceivedEvent(nec.NetworkIncomingEvent):
    name = 'unsubscribed-presence-received'
    base_network_events = []

    def generate(self):
        self.jid = self.presence_obj.jid
        return True

class OurShowEvent(nec.NetworkIncomingEvent):
    name = 'our-show'
    base_network_events = []

class BeforeChangeShowEvent(nec.NetworkIncomingEvent):
    name = 'before-change-show'
    base_network_events = []

class MamMessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
    name = 'mam-message-received'
    base_network_events = ['raw-mam-message-received']

    def __init__(self, name, base_event):
        '''
        Pre-Generated attributes on self:

        :conn:          Connection instance
        :stanza:        Complete stanza Node
        :forwarded:     Forwarded Node
        :result:        Result Node
        '''
        self._set_base_event_vars_as_attributes(base_event)
        self.additional_data = {}
        self.encrypted = False
        self.groupchat = False
        self.nick = None
        self.self_message = None
        self.muc_pm = None

    def generate(self):
        account = self.conn.name
        archive_jid = self.stanza.getFrom()
        own_jid = self.conn.get_own_jid()
        if archive_jid and not archive_jid.bareMatch(own_jid):
            # MAM Message not from our Archive
            return False

        self.msg_ = self.forwarded.getTag('message', protocol=True)

        if self.msg_.getType() == 'groupchat':
            return False

        # use stanza-id as unique-id
        self.unique_id, origin_id = self.get_unique_id()
        self.message_id = self.msg_.getID()

        # Check for duplicates
        if app.logger.find_stanza_id(account,
                                     own_jid.getStripped(),
                                     self.unique_id, origin_id):
            return

        self.msgtxt = self.msg_.getTagData('body')

        frm = self.msg_.getFrom()
        # Some servers dont set the 'to' attribute when
        # we send a message to ourself
        to = self.msg_.getTo()
        if to is None:
            to = own_jid

        if frm.bareMatch(own_jid):
            self.with_ = to
            self.kind = KindConstant.CHAT_MSG_SENT
        else:
            self.with_ = frm
            self.kind = KindConstant.CHAT_MSG_RECV

        delay = self.forwarded.getTagAttr(
            'delay', 'stamp', namespace=nbxmpp.NS_DELAY2)
        if delay is None:
            log.error('Received MAM message without timestamp')
            log.error(self.stanza)
            return

        self.timestamp = helpers.parse_datetime(
            delay, check_utc=True, epoch=True)
        if self.timestamp is None:
            log.error('Received MAM message with invalid timestamp: %s', delay)
            log.error(self.stanza)
            return

        # Save timestamp added by the user
        user_delay = self.msg_.getTagAttr(
            'delay', 'stamp', namespace=nbxmpp.NS_DELAY2)
        if user_delay is not None:
            self.user_timestamp = helpers.parse_datetime(
                user_delay, check_utc=True, epoch=True)
            if self.user_timestamp is None:
                log.warning('Received MAM message with '
                            'invalid user timestamp: %s', user_delay)
                log.warning(self.stanza)

        log.debug('Received mam-message: unique id: %s', self.unique_id)
        return True

    def get_unique_id(self):
        stanza_id = self.get_stanza_id(self.result, query=True)

        if self._is_self_message(self.msg_) or self._is_muc_pm(self.msg_):
            origin_id = self.msg_.getOriginID()
            return stanza_id, origin_id

        if self.conn.get_own_jid().bareMatch(self.msg_.getFrom()):
            # message we sent
            origin_id = self.msg_.getOriginID()
            return stanza_id, origin_id

        # A message we received
        return stanza_id, None

class MamGcMessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
    name = 'mam-gc-message-received'
    base_network_events = ['raw-mam-message-received']

    def __init__(self, name, base_event):
        '''
        Pre-Generated attributes on self:

        :conn:          Connection instance
        :stanza:        Complete stanza Node
        :forwarded:     Forwarded Node
        :result:        Result Node
        :muc_pm:        True, if this is a MUC PM
                        propagated to MamDecryptedMessageReceivedEvent
        '''
        self._set_base_event_vars_as_attributes(base_event)
        self.additional_data = {}
        self.encrypted = False
        self.groupchat = True
        self.kind = KindConstant.GC_MSG

    def generate(self):
        account = self.conn.name
        self.msg_ = self.forwarded.getTag('message', protocol=True)

        if self.msg_.getType() != 'groupchat':
            return False

        try:
            self.room_jid = self.stanza.getFrom().getStripped()
        except AttributeError:
            log.warning('Received GC MAM message '
                        'without from attribute\n%s', self.stanza)
            return False

        self.unique_id = self.get_stanza_id(self.result, query=True)
        self.message_id = self.msg_.getID()

        # Check for duplicates
        if app.logger.find_stanza_id(account,
                                     self.room_jid,
                                     self.unique_id,
                                     groupchat=True):
            return

        self.msgtxt = self.msg_.getTagData('body')
        self.with_ = self.msg_.getFrom().getStripped()
        self.nick = self.msg_.getFrom().getResource()

        # Get the real jid if we have it
        self.real_jid = None
        muc_user = self.msg_.getTag('x', namespace=nbxmpp.NS_MUC_USER)
        if muc_user is not None:
            self.real_jid = muc_user.getTagAttr('item', 'jid')

        delay = self.forwarded.getTagAttr(
            'delay', 'stamp', namespace=nbxmpp.NS_DELAY2)
        if delay is None:
            log.error('Received MAM message without timestamp')
            log.error(self.stanza)
            return

        self.timestamp = helpers.parse_datetime(
            delay, check_utc=True, epoch=True)
        if self.timestamp is None:
            log.error('Received MAM message with invalid timestamp: %s', delay)
            log.error(self.stanza)
            return

        # Save timestamp added by the user
        user_delay = self.msg_.getTagAttr(
            'delay', 'stamp', namespace=nbxmpp.NS_DELAY2)
        if user_delay is not None:
            self.user_timestamp = helpers.parse_datetime(
                user_delay, check_utc=True, epoch=True)
            if self.user_timestamp is None:
                log.warning('Received MAM message with '
                            'invalid user timestamp: %s', user_delay)
                log.warning(self.stanza)

        log.debug('Received mam-gc-message: unique id: %s', self.unique_id)
        return True

class MamDecryptedMessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
    name = 'mam-decrypted-message-received'
    base_network_events = []

    def generate(self):
        self.correct_id = None

        if not self.msgtxt:
            # For example Chatstates, Receipts, Chatmarkers
            log.debug('Received MAM message without text')
            return

        replace = self.msg_.getTag('replace', namespace=nbxmpp.NS_CORRECT)
        if replace is not None:
            self.correct_id = replace.getAttr('id')

        self.get_oob_data(self.msg_)

        if self.groupchat:
            return True

        if not self.muc_pm:
            # muc_pm = False, means only there was no muc#user namespace
            # This could still be a muc pm, we check the database if we
            # know this jid. If not we disco it.
            self.muc_pm = app.logger.jid_is_room_jid(self.with_.getStripped())
            if self.muc_pm is None:
                # Check if this event is triggered after a disco, so we dont
                # run into an endless loop
                if hasattr(self, 'disco'):
                    log.error('JID not known even after sucessful disco')
                    log.error(self.with_.getStripped())
                    return
                # we don't know this JID, we need to disco it.
                server = self.with_.getDomain()
                if server not in self.conn.mam_awaiting_disco_result:
                    self.conn.mam_awaiting_disco_result[server] = [self]
                    self.conn.discoverInfo(server)
                else:
                    self.conn.mam_awaiting_disco_result[server].append(self)
                return

        if self.muc_pm:
            self.with_ = str(self.with_)
        else:
            self.with_ = self.with_.getStripped()
        return True

class MessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
    name = 'message-received'
    base_network_events = ['raw-message-received']

    def init(self):
        self.additional_data = {}

    def generate(self):
        self.conn = self.base_event.conn
        self.stanza = self.base_event.stanza
        self.get_id()
        self.forwarded = False
        self.sent = False
        self.encrypted = False
        self.self_message = None
        self.muc_pm = None
        account = self.conn.name

        if self.stanza.getFrom() == self.conn.get_own_jid(warn=True):
            # Drop messages sent from our own full jid
            # It can happen that when we sent message to our own bare jid
            # that the server routes that message back to us
            log.info('Received message from self: %s, message is dropped',
                     self.stanza.getFrom())
            return

        # check if the message is a roster item exchange (XEP-0144)
        if self.stanza.getTag('x', namespace=nbxmpp.NS_ROSTERX):
            app.nec.push_incoming_event(RosterItemExchangeEvent(None,
                conn=self.conn, stanza=self.stanza))
            return

        # check if the message is a XEP-0070 confirmation request
        if self.stanza.getTag('confirm', namespace=nbxmpp.NS_HTTP_AUTH):
            app.nec.push_incoming_event(HttpAuthReceivedEvent(None,
                conn=self.conn, stanza=self.stanza))
            return

        try:
            self.get_jid_resource()
        except helpers.InvalidFormat:
            log.warning('Invalid JID: %s, ignoring it',
                        self.stanza.getFrom())
            return

        # Check for duplicates
        self.unique_id = self.get_unique_id()
        # Check groupchat messages for duplicates,
        # We do this because of MUC History messages
        type_ = self.stanza.getType()
        if type_ == 'groupchat' or self.self_message or self.muc_pm:
            if type_ == 'groupchat':
                archive_jid = self.stanza.getFrom().getStripped()
            else:
                archive_jid = self.conn.get_own_jid().getStripped()
            if app.logger.find_stanza_id(account,
                                         archive_jid,
                                         self.unique_id,
                                         groupchat=type_ == 'groupchat'):
                return

        address_tag = self.stanza.getTag('addresses',
            namespace=nbxmpp.NS_ADDRESS)
        # Be sure it comes from one of our resource, else ignore address element
        if address_tag and self.jid == app.get_jid_from_account(account):
            address = address_tag.getTag('address', attrs={'type': 'ofrom'})
            if address:
                try:
                    self.fjid = helpers.parse_jid(address.getAttr('jid'))
                except helpers.InvalidFormat:
                    log.warning('Invalid JID: %s, ignoring it',
                                address.getAttr('jid'))
                    return
                self.jid = app.get_jid_without_resource(self.fjid)

        carbon_marker = self.stanza.getTag('sent', namespace=nbxmpp.NS_CARBONS)
        if not carbon_marker:
            carbon_marker = self.stanza.getTag('received',
                namespace=nbxmpp.NS_CARBONS)
        # Be sure it comes from one of our resource, else ignore forward element
        if carbon_marker and self.jid == app.get_jid_from_account(account):
            forward_tag = carbon_marker.getTag('forwarded',
                namespace=nbxmpp.NS_FORWARD)
            if forward_tag:
                msg = forward_tag.getTag('message')
                self.stanza = nbxmpp.Message(node=msg)
                self.get_id()
                if carbon_marker.getName() == 'sent':
                    to = self.stanza.getTo()
                    frm = self.stanza.getFrom()
                    if not frm:
                        frm = app.get_jid_from_account(account)
                    self.stanza.setTo(frm)
                    if not to:
                        to = app.get_jid_from_account(account)
                    self.stanza.setFrom(to)
                    self.sent = True
                elif carbon_marker.getName() == 'received':
                    full_frm = str(self.stanza.getFrom())
                    frm = app.get_jid_without_resource(full_frm)
                    if frm == app.get_jid_from_account(account):
                        # Drop 'received' Carbons from ourself, we already
                        # got the message with the 'sent' Carbon or via the
                        # message itself
                        log.info(
                            'Drop "received"-Carbon from ourself: %s'
                            % full_frm)
                        return
                try:
                    self.get_jid_resource()
                except helpers.InvalidFormat:
                    log.warning('Invalid JID: %s, ignoring it',
                                self.stanza.getFrom())
                    return
                self.forwarded = True

        result = self.stanza.getTag('result', protocol=True)
        if result and result.getNamespace() in (nbxmpp.NS_MAM_1,
                                                nbxmpp.NS_MAM_2):

            if result.getAttr('queryid') not in self.conn.mam_query_ids:
                log.warning('Invalid MAM Message: unknown query id')
                log.debug(self.stanza)
                return

            forwarded = result.getTag('forwarded',
                                      namespace=nbxmpp.NS_FORWARD,
                                      protocol=True)
            if not forwarded:
                log.warning('Invalid MAM Message: no forwarded child')
                return

            app.nec.push_incoming_event(
                NetworkEvent('raw-mam-message-received',
                             conn=self.conn,
                             stanza=self.stanza,
                             forwarded=forwarded,
                             result=result))
            return

        # Mediated invitation?
        muc_user = self.stanza.getTag('x', namespace=nbxmpp.NS_MUC_USER)
        if muc_user:
            if muc_user.getTag('decline'):
                app.nec.push_incoming_event(
                    GcDeclineReceivedEvent(
                        None, conn=self.conn,
                        room_jid=self.fjid, stanza=muc_user))
                return
            if muc_user.getTag('invite'):
                app.nec.push_incoming_event(
                    GcInvitationReceivedEvent(
                        None, conn=self.conn, jid_from=self.fjid,
                        mediated=True, stanza=muc_user))
                return
        else:
            # Direct invitation?
            direct = self.stanza.getTag(
                'x', namespace=nbxmpp.NS_CONFERENCE)
            if direct:
                app.nec.push_incoming_event(
                    GcInvitationReceivedEvent(
                        None, conn=self.conn, jid_from=self.fjid,
                        mediated=False, stanza=direct))
                return

        self.thread_id = self.stanza.getThread()
        self.mtype = self.stanza.getType()
        if not self.mtype or self.mtype not in ('chat', 'groupchat', 'error'):
            self.mtype = 'normal'

        self.msgtxt = self.stanza.getBody()

        self.get_gc_control()

        if self.gc_control and self.jid == self.fjid:
            if self.mtype == 'error':
                self.msgtxt = _('error while sending %(message)s ( %(error)s )'\
                    ) % {'message': self.msgtxt,
                    'error': self.stanza.getErrorMsg()}
                if self.stanza.getTag('html'):
                    self.stanza.delChild('html')
            # message from a gc without a resource
            self.mtype = 'groupchat'

        self.session = None
        if self.mtype != 'groupchat':
            if app.interface.is_pm_contact(self.fjid, account) and \
            self.mtype == 'error':
                self.session = self.conn.find_session(self.fjid, self.thread_id)
                if not self.session:
                    self.session = self.conn.get_latest_session(self.fjid)
                if not self.session:
                    self.session = self.conn.make_new_session(self.fjid,
                                                              self.thread_id,
                                                              type_='pm')
            else:
                self.session = self.conn.get_or_create_session(self.fjid,
                                                               self.thread_id)

            if self.thread_id and not self.session.received_thread_id:
                self.session.received_thread_id = True

            self.session.last_receive = time_time()

        self._generate_timestamp(self.stanza.timestamp)

        return True

    def get_unique_id(self):
        '''
        Messages to self:

        Messages to self are stored multiple times in MAM so we cant use
        stanza-id to deduplicate. We use origin-id instead. Its not perfect
        but there is no better way for now.
        We drop "received"-Carbons of Message to self, so we dont have to
        parse origin-id in that case.

        MUC PMs:

        MUC PMs are also stored multiple times, we also depend on origin-id
        for now.
        '''

        if self.stanza.getType() == 'groupchat':
            # TODO: Disco the MUC check if 'urn:xmpp:mam:2' is announced
            return self.get_stanza_id(self.stanza)

        elif self.stanza.getType() != 'chat':
            return

        # Messages we receive live
        if self.conn.archiving_namespace != nbxmpp.NS_MAM_2:
            # Only mam:2 ensures valid stanza-id
            return

        # Sent Carbon
        sent_carbon = self.stanza.getTag('sent',
                                         namespace=nbxmpp.NS_CARBONS,
                                         protocol=True)
        if sent_carbon is not None:
            message = self.get_forwarded_message(sent_carbon)
            if self._is_self_message(message) or self._is_muc_pm(message):
                return message.getOriginID()
            return self.get_stanza_id(message)

        # Received Carbon
        received_carbon = self.stanza.getTag('received',
                                             namespace=nbxmpp.NS_CARBONS,
                                             protocol=True)
        if received_carbon is not None:
            message = self.get_forwarded_message(received_carbon)
            if self._is_muc_pm(message):
                return message.getOriginID()
            return self.get_stanza_id(message)

        # Normal Message
        if self._is_self_message(self.stanza) or self._is_muc_pm(self.stanza):
            return self.stanza.getOriginID()
        return self.get_stanza_id(self.stanza)

class ZeroconfMessageReceivedEvent(MessageReceivedEvent):
    name = 'message-received'
    base_network_events = []

    def get_jid_resource(self):
        self.fjid =self.stanza.getFrom()

        if self.fjid is None:
            for key in self.conn.connection.zeroconf.contacts:
                if self.ip == self.conn.connection.zeroconf.contacts[key][
                        Constant.ADDRESS]:
                    self.fjid = key
                    break

        self.jid, self.resource = app.get_room_and_nick_from_fjid(self.fjid)

    def generate(self):
        self.base_event = nec.NetworkIncomingEvent(None, conn=self.conn,
                                                   stanza=self.stanza)
        return super(ZeroconfMessageReceivedEvent, self).generate()

class GcInvitationReceivedEvent(nec.NetworkIncomingEvent):
    name = 'gc-invitation-received'
    base_network_events = []

    def generate(self):
        account = self.conn.name
        if not self.mediated:
            # direct invitation
            try:
                self.room_jid = helpers.parse_jid(self.stanza.getAttr('jid'))
            except helpers.InvalidFormat:
                log.warning('Invalid JID: %s, ignoring it',
                            self.stanza.getAttr('jid'))
                return
            self.reason = self.stanza.getAttr('reason')
            self.password = self.stanza.getAttr('password')
            self.is_continued = False
            self.is_continued = self.stanza.getAttr('continue') == 'true'
        else:
            self.invite = self.stanza.getTag('invite')
            self.room_jid = self.jid_from
            try:
                self.jid_from = helpers.parse_jid(self.invite.getAttr('from'))
            except helpers.InvalidFormat:
                log.warning('Invalid JID: %s, ignoring it',
                            self.invite.getAttr('from'))
                return

            self.reason = self.invite.getTagData('reason')
            self.password = self.stanza.getTagData('password')
            self.is_continued = self.stanza.getTag('continue') is not None

        if self.room_jid in app.gc_connected[account] and \
                app.gc_connected[account][self.room_jid]:
            # We are already in groupchat. Ignore invitation
            return
        jid = app.get_jid_without_resource(self.jid_from)

        ignore = app.config.get_per(
            'accounts', account, 'ignore_unknown_contacts')
        if ignore and not app.contacts.get_contacts(account, jid):
            return

        return True

class GcDeclineReceivedEvent(nec.NetworkIncomingEvent):
    name = 'gc-decline-received'
    base_network_events = []

    def generate(self):
        account = self.conn.name
        decline = self.stanza.getTag('decline')
        try:
            self.jid_from = helpers.parse_jid(decline.getAttr('from'))
        except helpers.InvalidFormat:
            log.warning('Invalid JID: %s, ignoring it',
                        decline.getAttr('from'))
            return
        jid = app.get_jid_without_resource(self.jid_from)
        ignore = app.config.get_per(
            'accounts', account, 'ignore_unknown_contacts')
        if ignore and not app.contacts.get_contacts(account, jid):
            return
        self.reason = decline.getTagData('reason')

        return True

class DecryptedMessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
    name = 'decrypted-message-received'
    base_network_events = []

    def generate(self):
        self.stanza = self.msg_obj.stanza
        self.additional_data = self.msg_obj.additional_data
        self.id_ = self.msg_obj.id_
        self.unique_id = self.msg_obj.unique_id
        self.jid = self.msg_obj.jid
        self.fjid = self.msg_obj.fjid
        self.resource = self.msg_obj.resource
        self.mtype = self.msg_obj.mtype
        self.thread_id = self.msg_obj.thread_id
        self.msgtxt = self.msg_obj.msgtxt
        self.gc_control = self.msg_obj.gc_control
        self.session = self.msg_obj.session
        self.timestamp = self.msg_obj.timestamp
        self.encrypted = self.msg_obj.encrypted
        self.forwarded = self.msg_obj.forwarded
        self.sent = self.msg_obj.sent
        self.conn = self.msg_obj.conn
        self.muc_pm = self.msg_obj.muc_pm
        self.popup = False
        self.msg_log_id = None # id in log database
        self.attention = False # XEP-0224
        self.correct_id = None # XEP-0308
        self.msghash = None

        self.receipt_request_tag = self.stanza.getTag('request',
            namespace=nbxmpp.NS_RECEIPTS)
        self.receipt_received_tag = self.stanza.getTag('received',
            namespace=nbxmpp.NS_RECEIPTS)

        self.subject = self.stanza.getSubject()

        self.displaymarking = None
        self.seclabel = self.stanza.getTag('securitylabel',
            namespace=nbxmpp.NS_SECLABEL)
        if self.seclabel:
            self.displaymarking = self.seclabel.getTag('displaymarking')

        if self.stanza.getTag('attention', namespace=nbxmpp.NS_ATTENTION):
            delayed = self.stanza.getTag('x', namespace=nbxmpp.NS_DELAY) is not\
                None
            if not delayed:
                self.attention = True

        self.form_node = self.stanza.getTag('x', namespace=nbxmpp.NS_DATA)

        if app.config.get('ignore_incoming_xhtml'):
            self.xhtml = None
        else:
            self.xhtml = self.stanza.getXHTML()

        # XEP-0172 User Nickname
        self.user_nick = self.stanza.getTagData('nick') or ''

        self.get_chatstate()

        self.get_oob_data(self.stanza)

        replace = self.stanza.getTag('replace', namespace=nbxmpp.NS_CORRECT)
        if replace:
            self.correct_id = replace.getAttr('id')

        return True

class ChatstateReceivedEvent(nec.NetworkIncomingEvent):
    name = 'chatstate-received'
    base_network_events = []

    def generate(self):
        self.stanza = self.msg_obj.stanza
        self.jid = self.msg_obj.jid
        self.fjid = self.msg_obj.fjid
        self.resource = self.msg_obj.resource
        self.chatstate = self.msg_obj.chatstate
        return True

class GcMessageReceivedEvent(nec.NetworkIncomingEvent):
    name = 'gc-message-received'
    base_network_events = []

    def generate(self):
        self.stanza = self.msg_obj.stanza
        if not hasattr(self.msg_obj, 'additional_data'):
            self.additional_data = {}
        else:
            self.additional_data = self.msg_obj.additional_data
        self.id_ = self.msg_obj.stanza.getID()
        self.unique_id = self.msg_obj.unique_id
        self.fjid = self.msg_obj.fjid
        self.msgtxt = self.msg_obj.msgtxt
        self.jid = self.msg_obj.jid
        self.room_jid = self.msg_obj.jid
        self.nickname = self.msg_obj.resource
        self.timestamp = self.msg_obj.timestamp
        self.xhtml_msgtxt = self.stanza.getXHTML()
        self.encrypted = self.msg_obj.encrypted
        self.correct_id = None # XEP-0308

        if app.config.get('ignore_incoming_xhtml'):
            self.xhtml_msgtxt = None

        if self.msg_obj.resource:
            # message from someone
            self.nick = self.msg_obj.resource
        else:
            # message from server
            self.nick = ''

        self.has_timestamp = bool(self.stanza.timestamp)

        self.subject = self.stanza.getSubject()

        if self.subject is not None:
            app.nec.push_incoming_event(GcSubjectReceivedEvent(None,
                conn=self.conn, msg_event=self))
            return

        conditions = self.stanza.getStatusConditions()
        if conditions:
            self.status_code = []
            for condition in conditions:
                if condition in CONDITION_TO_CODE:
                    self.status_code.append(CONDITION_TO_CODE[condition])
        else:
            self.status_code = self.stanza.getStatusCode()

        if not self.stanza.getTag('body'): # no <body>
            # It could be a config change. See
            # http://www.xmpp.org/extensions/xep-0045.html#roomconfig-notify
            if self.stanza.getTag('x'):
                if self.status_code != []:
                    app.nec.push_incoming_event(GcConfigChangedReceivedEvent(
                        None, conn=self.conn, msg_event=self))
            if self.msg_obj.form_node:
                return True
            return

        self.displaymarking = None
        seclabel = self.stanza.getTag('securitylabel')
        if seclabel and seclabel.getNamespace() == nbxmpp.NS_SECLABEL:
            # Ignore message from room in which we are not
            self.displaymarking = seclabel.getTag('displaymarking')

        self.captcha_form = None
        captcha_tag = self.stanza.getTag('captcha', namespace=nbxmpp.NS_CAPTCHA)
        if captcha_tag:
            self.captcha_form = captcha_tag.getTag('x',
                namespace=nbxmpp.NS_DATA)
            for field in self.captcha_form.getTags('field'):
                for media in field.getTags('media'):
                    for uri in media.getTags('uri'):
                        uri_data = uri.getData()
                        if uri_data.startswith('cid:'):
                            uri_data = uri_data[4:]
                            found = False
                            for data in self.stanza.getTags('data',
                            namespace=nbxmpp.NS_BOB):
                                if data.getAttr('cid') == uri_data:
                                    uri.setData(data.getData())
                                    found = True
                            if not found:
                                self.conn.get_bob_data(uri_data, self.fjid,
                                    self.conn._dispatch_gc_msg_with_captcha,
                                    [self.stanza, self.msg_obj], 0)
                                return

        replace = self.stanza.getTag('replace', namespace=nbxmpp.NS_CORRECT)
        if replace:
            self.correct_id = replace.getAttr('id')

        return True

class GcSubjectReceivedEvent(nec.NetworkIncomingEvent):
    name = 'gc-subject-received'
    base_network_events = []

    def generate(self):
        self.conn = self.msg_event.conn
        self.stanza = self.msg_event.stanza
        self.room_jid = self.msg_event.room_jid
        self.nickname = self.msg_event.nickname
        self.fjid = self.msg_event.fjid
        self.subject = self.msg_event.subject
        self.msgtxt = self.msg_event.msgtxt
        self.has_timestamp = self.msg_event.has_timestamp
        return True

class GcConfigChangedReceivedEvent(nec.NetworkIncomingEvent):
    name = 'gc-config-changed-received'
    base_network_events = []

    def generate(self):
        self.conn = self.msg_event.conn
        self.stanza = self.msg_event.stanza
        self.room_jid = self.msg_event.room_jid
        self.status_code = self.msg_event.status_code
        return True

class MessageSentEvent(nec.NetworkIncomingEvent):
    name = 'message-sent'
    base_network_events = []

    def generate(self):
        if not self.automatic_message:
            self.conn.sent_message_ids.append(self.stanza_id)
            # only record the last 20000 message ids (should be about 1MB [36 byte per uuid]
            # and about 24 hours if you send out a message every 5 seconds)
            self.conn.sent_message_ids = self.conn.sent_message_ids[-20000:]
        return True

class MessageNotSentEvent(nec.NetworkIncomingEvent):
    name = 'message-not-sent'
    base_network_events = []

class MessageErrorEvent(nec.NetworkIncomingEvent, HelperEvent):
    name = 'message-error'
    base_network_events = []

    def generate(self):
        self.get_id()
        #only alert for errors of explicitly sent messages (see https://trac.gajim.org/ticket/8222)
        if self.id_ in self.conn.sent_message_ids:
            self.conn.sent_message_ids.remove(self.id_)
            return True
        return False

class AnonymousAuthEvent(nec.NetworkIncomingEvent):
    name = 'anonymous-auth'
    base_network_events = []

class JingleRequestReceivedEvent(nec.NetworkIncomingEvent):
    name = 'jingle-request-received'
    base_network_events = []

    def generate(self):
        self.fjid = self.jingle_session.peerjid
        self.jid, self.resource = app.get_room_and_nick_from_fjid(self.fjid)
        self.sid = self.jingle_session.sid
        return True

class JingleConnectedReceivedEvent(nec.NetworkIncomingEvent):
    name = 'jingle-connected-received'
    base_network_events = []

    def generate(self):
        self.fjid = self.jingle_session.peerjid
        self.jid, self.resource = app.get_room_and_nick_from_fjid(self.fjid)
        self.sid = self.jingle_session.sid
        return True

class JingleDisconnectedReceivedEvent(nec.NetworkIncomingEvent):
    name = 'jingle-disconnected-received'
    base_network_events = []

    def generate(self):
        self.fjid = self.jingle_session.peerjid
        self.jid, self.resource = app.get_room_and_nick_from_fjid(self.fjid)
        self.sid = self.jingle_session.sid
        return True

class JingleTransferCancelledEvent(nec.NetworkIncomingEvent):
    name = 'jingleFT-cancelled-received'
    base_network_events = []

    def generate(self):
        self.fjid = self.jingle_session.peerjid
        self.jid, self.resource = app.get_room_and_nick_from_fjid(self.fjid)
        self.sid = self.jingle_session.sid
        return True

class JingleErrorReceivedEvent(nec.NetworkIncomingEvent):
    name = 'jingle-error-received'
    base_network_events = []

    def generate(self):
        self.fjid = self.jingle_session.peerjid
        self.jid, self.resource = app.get_room_and_nick_from_fjid(self.fjid)
        self.sid = self.jingle_session.sid
        return True

class ArchivingReceivedEvent(nec.NetworkIncomingEvent):
    name = 'archiving-received'
    base_network_events = []

    def generate(self):
        self.type_ = self.stanza.getType()
        if self.type_ not in ('result', 'set', 'error'):
            return
        return True

class ArchivingErrorReceivedEvent(nec.NetworkIncomingEvent):
    name = 'archiving-error-received'
    base_network_events = ['archiving-received']

    def generate(self):
        self.conn = self.base_event.conn
        self.stanza = self.base_event.stanza
        self.type_ = self.base_event.type_

        if self.type_ == 'error':
            self.error_msg = self.stanza.getErrorMsg()
            return True

class ArchivingCountReceived(nec.NetworkIncomingEvent):
    name = 'archiving-count-received'
    base_network_events = []

    def generate(self):
        return True

class ArchivingIntervalFinished(nec.NetworkIncomingEvent):
    name = 'archiving-interval-finished'
    base_network_events = []

    def generate(self):
        return True

class ArchivingQueryID(nec.NetworkIncomingEvent):
    name = 'archiving-query-id'
    base_network_events = []

    def generate(self):
        return True

class Archiving313PreferencesChangedReceivedEvent(nec.NetworkIncomingEvent):
    name = 'archiving-313-preferences-changed-received'
    base_network_events = ['archiving-received']

    def generate(self):
        self.conn = self.base_event.conn
        self.stanza = self.base_event.stanza
        self.type_ = self.base_event.type_
        self.items = []
        self.default = None
        self.id = self.stanza.getID()
        self.answer = None
        prefs = self.stanza.getTag('prefs')

        if self.type_ != 'result' or not prefs:
            return

        self.default = prefs.getAttr('default')

        for item in prefs.getTag('always').getTags('jid'):
            self.items.append((item.getData(), 'Always'))

        for item in prefs.getTag('never').getTags('jid'):
            self.items.append((item.getData(), 'Never'))

        return True

class AccountCreatedEvent(nec.NetworkIncomingEvent):
    name = 'account-created'
    base_network_events = []

class AccountNotCreatedEvent(nec.NetworkIncomingEvent):
    name = 'account-not-created'
    base_network_events = []

class NewAccountConnectedEvent(nec.NetworkIncomingEvent):
    name = 'new-account-connected'
    base_network_events = []

    def generate(self):
        try:
            self.errnum = self.conn.connection.Connection.ssl_errnum
        except AttributeError:
            self.errnum = 0 # we don't have an errnum
        self.ssl_msg = ''
        if self.errnum > 0:
            from gajim.common.connection import ssl_error
            self.ssl_msg = ssl_error.get(self.errnum,
                _('Unknown SSL error: %d') % self.errnum)
        self.ssl_cert = ''
        self.ssl_fingerprint_sha1 = ''
        self.ssl_fingerprint_sha256 = ''
        if self.conn.connection.Connection.ssl_certificate:
            cert = self.conn.connection.Connection.ssl_certificate
            self.ssl_cert = OpenSSL.crypto.dump_certificate(
                OpenSSL.crypto.FILETYPE_PEM, cert).decode('utf-8')
            self.ssl_fingerprint_sha1 = cert.digest('sha1').decode('utf-8')
            self.ssl_fingerprint_sha256 = cert.digest('sha256').decode('utf-8')
        return True

class NewAccountNotConnectedEvent(nec.NetworkIncomingEvent):
    name = 'new-account-not-connected'
    base_network_events = []

class ConnectionTypeEvent(nec.NetworkIncomingEvent):
    name = 'connection-type'
    base_network_events = []

class VcardPublishedEvent(nec.NetworkIncomingEvent):
    name = 'vcard-published'
    base_network_events = []

class VcardNotPublishedEvent(nec.NetworkIncomingEvent):
    name = 'vcard-not-published'
    base_network_events = []

class StanzaReceivedEvent(nec.NetworkIncomingEvent):
    name = 'stanza-received'
    base_network_events = []
    
    def init(self):
        self.additional_data = {}
        
    def generate(self):
        return True

class StanzaSentEvent(nec.NetworkIncomingEvent):
    name = 'stanza-sent'
    base_network_events = []
    
    def init(self):
        self.additional_data = {}

class AgentRemovedEvent(nec.NetworkIncomingEvent):
    name = 'agent-removed'
    base_network_events = []

    def generate(self):
        self.jid_list = []
        for jid in app.contacts.get_jid_list(self.conn.name):
            if jid.endswith('@' + self.agent):
                self.jid_list.append(jid)
        return True

class BadGPGPassphraseEvent(nec.NetworkIncomingEvent):
    name = 'bad-gpg-passphrase'
    base_network_events = []

    def generate(self):
        self.account = self.conn.name
        self.use_gpg_agent = app.config.get('use_gpg_agent')
        self.keyID = app.config.get_per('accounts', self.conn.name, 'keyid')
        return True

class ConnectionLostEvent(nec.NetworkIncomingEvent):
    name = 'connection-lost'
    base_network_events = []

    def generate(self):
        app.nec.push_incoming_event(OurShowEvent(None, conn=self.conn,
            show='offline'))
        return True

class PingSentEvent(nec.NetworkIncomingEvent):
    name = 'ping-sent'
    base_network_events = []

class PingReplyEvent(nec.NetworkIncomingEvent):
    name = 'ping-reply'
    base_network_events = []

class PingErrorEvent(nec.NetworkIncomingEvent):
    name = 'ping-error'
    base_network_events = []

class CapsPresenceReceivedEvent(nec.NetworkIncomingEvent, HelperEvent,
PresenceHelperEvent):
    name = 'caps-presence-received'
    base_network_events = ['raw-pres-received']

    def _extract_caps_from_presence(self):
        caps_tag = self.stanza.getTag('c', namespace=nbxmpp.NS_CAPS)
        if caps_tag:
            self.hash_method = caps_tag['hash']
            self.node = caps_tag['node']
            self.caps_hash = caps_tag['ver']
        else:
            self.hash_method = self.node = self.caps_hash = None

    def generate(self):
        self.conn = self.base_event.conn
        self.stanza = self.base_event.stanza
        try:
            self.get_jid_resource()
        except Exception:
            return
        self._generate_ptype()
        self._generate_show()
        self._extract_caps_from_presence()
        return True

class CapsDiscoReceivedEvent(nec.NetworkIncomingEvent):
    name = 'caps-disco-received'
    base_network_events = []

class CapsReceivedEvent(nec.NetworkIncomingEvent):
    name = 'caps-received'
    base_network_events = ['caps-presence-received', 'caps-disco-received']

    def generate(self):
        self.conn = self.base_event.conn
        self.fjid = self.base_event.fjid
        self.jid = self.base_event.jid
        self.resource = self.base_event.resource
        self.client_caps = self.base_event.client_caps
        return True

class GPGTrustKeyEvent(nec.NetworkIncomingEvent):
    name = 'gpg-trust-key'
    base_network_events = []

class GPGPasswordRequiredEvent(nec.NetworkIncomingEvent):
    name = 'gpg-password-required'
    base_network_events = []

    def generate(self):
        self.keyid = app.config.get_per('accounts', self.conn.name, 'keyid')
        return True

class PEPReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
    name = 'pep-received'
    base_network_events = []

    def generate(self):
        if not self.stanza.getTag('event'):
            return
        if self.stanza.getTag('error'):
            log.debug('PEPReceivedEvent received error stanza. Ignoring')
            return

        try:
            self.get_jid_resource()
        except Exception:
            return

        self.event_tag = self.stanza.getTag('event')

        for pep_class in SUPPORTED_PERSONAL_USER_EVENTS:
            pep = pep_class.get_tag_as_PEP(self.fjid, self.conn.name,
                self.event_tag)
            if pep:
                self.pep_type = pep.type_
                return True

        items = self.event_tag.getTag('items')
        if items:
            # for each entry in feed (there shouldn't be more than one, but to
            # be sure...
            for item in items.getTags('item'):
                entry = item.getTag('entry', namespace=nbxmpp.NS_ATOM)
                if entry:
                    app.nec.push_incoming_event(AtomEntryReceived(None,
                        conn=self.conn, node=entry))
        raise nbxmpp.NodeProcessed

class AtomEntryReceived(nec.NetworkIncomingEvent):
    name = 'atom-entry-received'
    base_network_events = []

    def generate(self):
        self.atom_entry = atom.OldEntry(node=self.node)
        return True

class PlainConnectionEvent(nec.NetworkIncomingEvent):
    name = 'plain-connection'
    base_network_events = []

class InsecurePasswordEvent(nec.NetworkIncomingEvent):
    name = 'insecure-password'
    base_network_events = []

class InsecureSSLConnectionEvent(nec.NetworkIncomingEvent):
    name = 'insecure-ssl-connection'
    base_network_events = []

class SSLErrorEvent(nec.NetworkIncomingEvent):
    name = 'ssl-error'
    base_network_events = []

class FingerprintErrorEvent(nec.NetworkIncomingEvent):
    name = 'fingerprint-error'
    base_network_events = []

class UniqueRoomIdSupportedEvent(nec.NetworkIncomingEvent):
    name = 'unique-room-id-supported'
    base_network_events = []

class UniqueRoomIdNotSupportedEvent(nec.NetworkIncomingEvent):
    name = 'unique-room-id-not-supported'
    base_network_events = []

class PrivacyListsReceivedEvent(nec.NetworkIncomingEvent):
    name = 'privacy-lists-received'
    base_network_events = []

class PrivacyListReceivedEvent(nec.NetworkIncomingEvent):
    name = 'privacy-list-received'
    base_network_events = []

class PrivacyListRemovedEvent(nec.NetworkIncomingEvent):
    name = 'privacy-list-removed'
    base_network_events = []

class PrivacyListActiveDefaultEvent(nec.NetworkIncomingEvent):
    name = 'privacy-list-active-default'
    base_network_events = []

class NonAnonymousServerErrorEvent(nec.NetworkIncomingEvent):
    name = 'non-anonymous-server-error'
    base_network_events = []

class VcardReceivedEvent(nec.NetworkIncomingEvent):
    name = 'vcard-received'
    base_network_events = []

    def generate(self):
        return True

class UpdateGCAvatarEvent(nec.NetworkIncomingEvent):
    name = 'update-gc-avatar'
    base_network_events = []

    def generate(self):
        return True

class UpdateRosterAvatarEvent(nec.NetworkIncomingEvent):
    name = 'update-roster-avatar'
    base_network_events = []

    def generate(self):
        return True

class PEPConfigReceivedEvent(nec.NetworkIncomingEvent):
    name = 'pep-config-received'
    base_network_events = []

class MetacontactsReceivedEvent(nec.NetworkIncomingEvent):
    name = 'metacontacts-received'
    base_network_events = []

    def generate(self):
        # Metacontact tags
        # http://www.xmpp.org/extensions/xep-0209.html
        self.meta_list = {}
        query = self.stanza.getTag('query')
        storage = query.getTag('storage')
        metas = storage.getTags('meta')
        for meta in metas:
            try:
                jid = helpers.parse_jid(meta.getAttr('jid'))
            except helpers.InvalidFormat:
                continue
            tag = meta.getAttr('tag')
            data = {'jid': jid}
            order = meta.getAttr('order')
            try:
                order = int(order)
            except Exception:
                order = 0
            if order is not None:
                data['order'] = order
            if tag in self.meta_list:
                self.meta_list[tag].append(data)
            else:
                self.meta_list[tag] = [data]
        return True

class ZeroconfNameConflictEvent(nec.NetworkIncomingEvent):
    name = 'zeroconf-name-conflict'
    base_network_events = []

class PasswordRequiredEvent(nec.NetworkIncomingEvent):
    name = 'password-required'
    base_network_events = []

class Oauth2CredentialsRequiredEvent(nec.NetworkIncomingEvent):
    name = 'oauth2-credentials-required'
    base_network_events = []

class FailedDecryptEvent(nec.NetworkIncomingEvent):
    name = 'failed-decrypt'
    base_network_events = []

    def generate(self):
        self.conn = self.msg_obj.conn
        self.fjid = self.msg_obj.fjid
        self.timestamp = self.msg_obj.timestamp
        self.session = self.msg_obj.session
        return True

class SignedInEvent(nec.NetworkIncomingEvent):
    name = 'signed-in'
    base_network_events = []

class RegisterAgentInfoReceivedEvent(nec.NetworkIncomingEvent):
    name = 'register-agent-info-received'
    base_network_events = []

class AgentItemsReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
    name = 'agent-items-received'
    base_network_events = []

    def generate(self):
        q = self.stanza.getTag('query')
        self.node = q.getAttr('node')
        if not self.node:
            self.node = ''
        qp = self.stanza.getQueryPayload()
        self.items = []
        if not qp:
            qp = []
        for i in qp:
            # CDATA payload is not processed, only nodes
            if not isinstance(i, nbxmpp.simplexml.Node):
                continue
            attr = {}
            for key in i.getAttrs():
                attr[key] = i.getAttrs()[key]
            if 'jid' not in attr:
                continue
            try:
                attr['jid'] = helpers.parse_jid(attr['jid'])
            except helpers.InvalidFormat:
                # jid is not conform
                continue
            self.items.append(attr)
        self.get_jid_resource()
        hostname = app.config.get_per('accounts', self.conn.name, 'hostname')
        self.get_id()
        if self.id_ in self.conn.disco_items_ids:
            self.conn.disco_items_ids.remove(self.id_)
        if self.fjid == hostname and self.id_[:6] == 'Gajim_':
            for item in self.items:
                self.conn.discoverInfo(item['jid'], id_prefix='Gajim_')
        else:
            return True

class AgentItemsErrorReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
    name = 'agent-items-error-received'
    base_network_events = []

    def generate(self):
        self.get_jid_resource()
        self.get_id()
        if self.id_ in self.conn.disco_items_ids:
            self.conn.disco_items_ids.remove(self.id_)
        return True

class AgentInfoReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
    name = 'agent-info-received'
    base_network_events = []

    def generate(self):
        self.get_id()
        if self.id_ in self.conn.disco_info_ids:
            self.conn.disco_info_ids.remove(self.id_)
        if self.id_ is None:
            log.warning('Invalid IQ received without an ID. '
                        'Ignoring it: %s', self.stanza)
            return
        # According to XEP-0030:
        # For identity: category, type is mandatory, name is optional.
        # For feature: var is mandatory
        self.identities, self.features, self.data, self.node = self.parse_stanza(self.stanza)

        if not self.identities:
            # ejabberd doesn't send identities when we browse online users
            # see http://www.jabber.ru/bugzilla/show_bug.cgi?id=225
            self.identities = [{'category': 'server', 'type': 'im',
                'name': self.node}]
        self.get_jid_resource()
        return True

    @classmethod
    def parse_stanza(cls, stanza):
        identities, features, data, node = [], [], [], None
        q = stanza.getTag('query')
        node = q.getAttr('node')
        if not node:
            node = ''

        qc = stanza.getQueryChildren()
        if not qc:
            qc = []

        for i in qc:
            if i.getName() == 'identity':
                attr = {}
                for key in i.getAttrs().keys():
                    attr[key] = i.getAttr(key)
                identities.append(attr)
            elif i.getName() == 'feature':
                var = i.getAttr('var')
                if var:
                    features.append(var)
            elif i.getName() == 'x' and i.getNamespace() == nbxmpp.NS_DATA:
                data.append(nbxmpp.DataForm(node=i))

        return identities, features, data, node

class AgentInfoErrorReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
    name = 'agent-info-error-received'
    base_network_events = []

    def generate(self):
        self.get_jid_resource()
        self.get_id()
        if self.id_ in self.conn.disco_info_ids:
            self.conn.disco_info_ids.remove(self.id_)
        return True

class FileRequestReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
    name = 'file-request-received'
    base_network_events = []

    def init(self):
        self.jingle_content = None
        self.FT_content = None

    def generate(self):
        self.get_id()
        self.fjid = self.conn._ft_get_from(self.stanza)
        self.jid = app.get_jid_without_resource(self.fjid)
        if self.jingle_content:
            secu = self.jingle_content.getTag('security')
            self.FT_content.use_security = bool(secu)
            if secu:
                fingerprint = secu.getTag('fingerprint')
                if fingerprint:
                    self.FT_content.x509_fingerprint = fingerprint.getData()
            if not self.FT_content.transport:
                self.FT_content.transport = JingleTransportSocks5()
                self.FT_content.transport.set_our_jid(
                    self.FT_content.session.ourjid)
                self.FT_content.transport.set_connection(
                    self.FT_content.session.connection)
            sid = self.stanza.getTag('jingle').getAttr('sid')
            self.file_props = FilesProp.getNewFileProp(self.conn.name, sid)
            self.file_props.transport_sid = self.FT_content.transport.sid
            self.FT_content.file_props = self.file_props
            self.FT_content.transport.set_file_props(self.file_props)
            self.file_props.streamhosts.extend(
                    self.FT_content.transport.remote_candidates)
            for host in self.file_props.streamhosts:
                host['initiator'] = self.FT_content.session.initiator
                host['target'] = self.FT_content.session.responder
            self.file_props.session_type = 'jingle'
            self.file_props.stream_methods = nbxmpp.NS_BYTESTREAM
            desc = self.jingle_content.getTag('description')
            if self.jingle_content.getAttr('creator') == 'initiator':
                file_tag = desc.getTag('file')
                self.file_props.sender = self.fjid
                self.file_props.receiver = self.conn._ft_get_our_jid()
            else:
                file_tag = desc.getTag('file')
                h = file_tag.getTag('hash')
                h = h.getData() if h else None
                n = file_tag.getTag('name')
                n = n.getData() if n else None
                pjid = app.get_jid_without_resource(self.fjid)
                file_info = self.conn.get_file_info(pjid, hash_=h,
                                                name=n,account=self.conn.name)
                self.file_props.file_name = file_info['file-name']
                self.file_props.sender = self.conn._ft_get_our_jid()
                self.file_props.receiver = self.fjid
                self.file_props.type_ = 's'
            for child in file_tag.getChildren():
                name = child.getName()
                val = child.getData()
                if val is None:
                    continue
                if name == 'name':
                    self.file_props.name = val
                if name == 'size':
                    self.file_props.size = int(val)
                if name == 'hash':
                    self.file_props.algo = child.getAttr('algo')
                    self.file_props.hash_ = val
                if name == 'date':
                    self.file_props.date = val
        else:
            si = self.stanza.getTag('si')
            self.file_props = FilesProp.getNewFileProp(self.conn.name,
                si.getAttr('id'))
            self.file_props.transport_sid = self.file_props.sid
            profile = si.getAttr('profile')
            if profile != nbxmpp.NS_FILE:
                self.conn.send_file_rejection(self.file_props, code='400',
                    typ='profile')
                raise nbxmpp.NodeProcessed
            feature_tag = si.getTag('feature', namespace=nbxmpp.NS_FEATURE)
            if not feature_tag:
                return
            form_tag = feature_tag.getTag('x', namespace=nbxmpp.NS_DATA)
            if not form_tag:
                return
            self.dataform = dataforms.ExtendForm(node=form_tag)
            for f in self.dataform.iter_fields():
                if f.var == 'stream-method' and f.type_ == 'list-single':
                    values = [o[1] for o in f.options]
                    self.file_props.stream_methods = ' '.join(values)
                    if nbxmpp.NS_BYTESTREAM in values or \
                    nbxmpp.NS_IBB in values:
                        break
            else:
                self.conn.send_file_rejection(self.file_props, code='400',
                    typ='stream')
                raise nbxmpp.NodeProcessed
            file_tag = si.getTag('file')
            for name, val in file_tag.getAttrs().items():
                if val is None:
                    continue
                if name == 'name':
                    self.file_props.name = val
                if name == 'size':
                    self.file_props.size = int(val)
            mime_type = si.getAttr('mime-type')
            if mime_type is not None:
                self.file_props.mime_type = mime_type
            self.file_props.sender = self.fjid
            self.file_props.receiver = self.conn._ft_get_our_jid()
        self.file_props.request_id = self.id_
        file_desc_tag = file_tag.getTag('desc')
        if file_desc_tag is not None:
            self.file_props.desc = file_desc_tag.getData()
        self.file_props.transfered_size = []
        return True

class FileRequestErrorEvent(nec.NetworkIncomingEvent):
    name = 'file-request-error'
    base_network_events = []

    def generate(self):
        self.jid = app.get_jid_without_resource(self.jid)
        return True

class FileTransferCompletedEvent(nec.NetworkIncomingEvent):
    name = 'file-transfer-completed'
    base_network_events = []

    def generate(self):
        jid = str(self.file_props.receiver)
        self.jid = app.get_jid_without_resource(jid)
        return True

class GatewayPromptReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
    name = 'gateway-prompt-received'
    base_network_events = []

    def generate(self):
        self.get_jid_resource()
        query = self.stanza.getTag('query')
        if query:
            self.desc = query.getTagData('desc')
            self.prompt = query.getTagData('prompt')
            self.prompt_jid = query.getTagData('jid')
        else:
            self.desc = None
            self.prompt = None
            self.prompt_jid = None
        return True

class NotificationEvent(nec.NetworkIncomingEvent):
    name = 'notification'
    base_network_events = ['decrypted-message-received', 'gc-message-received',
        'presence-received']

    def detect_type(self):
        if self.base_event.name == 'decrypted-message-received':
            self.notif_type = 'msg'
        if self.base_event.name == 'gc-message-received':
            self.notif_type = 'gc-msg'
        if self.base_event.name == 'presence-received':
            self.notif_type = 'pres'

    def get_focused(self):
        self.control_focused = False
        if self.control:
            parent_win = self.control.parent_win
            if parent_win and self.control == parent_win.get_active_control() \
            and parent_win.window.get_property('has-toplevel-focus'):
                self.control_focused = True

    def handle_incoming_msg_event(self, msg_obj):
        # don't alert for carbon copied messages from ourselves
        if msg_obj.sent:
            return
        if not msg_obj.msgtxt:
            return
        self.jid = msg_obj.jid
        if msg_obj.session:
            self.control = msg_obj.session.control
        else:
            self.control = None
        self.get_focused()
        # This event has already been added to event list
        if not self.control and len(app.events.get_events(self.conn.name, \
        self.jid, [msg_obj.mtype])) <= 1:
            self.first_unread = True

        if msg_obj.mtype == 'pm':
            nick = msg_obj.resource
        else:
            nick = app.get_name_from_jid(self.conn.name, self.jid)

        if self.first_unread:
            self.sound_event = 'first_message_received'
        elif self.control_focused:
            self.sound_event = 'next_message_received_focused'
        else:
            self.sound_event = 'next_message_received_unfocused'

        if app.config.get('notification_preview_message'):
            self.popup_text = msg_obj.msgtxt
            if self.popup_text and (self.popup_text.startswith('/me ') or \
            self.popup_text.startswith('/me\n')):
                self.popup_text = '* ' + nick + self.popup_text[3:]
        else:
            # We don't want message preview, do_preview = False
            self.popup_text = ''
        if msg_obj.mtype == 'normal': # single message
            self.popup_msg_type = 'normal'
            self.popup_event_type = _('New Single Message')
            self.popup_image = 'gajim-single_msg_recv'
            self.popup_title = _('New Single Message from %(nickname)s') % \
                {'nickname': nick}
        elif msg_obj.mtype == 'pm':
            self.popup_msg_type = 'pm'
            self.popup_event_type = _('New Private Message')
            self.popup_image = 'gajim-priv_msg_recv'
            self.popup_title = _('New Private Message from group chat %s') % \
                msg_obj.jid
            if self.popup_text:
                self.popup_text = _('%(nickname)s: %(message)s') % \
                    {'nickname': nick, 'message': self.popup_text}
            else:
                self.popup_text = _('Messaged by %(nickname)s') % \
                    {'nickname': nick}
        else: # chat message
            self.popup_msg_type = 'chat'
            self.popup_event_type = _('New Message')
            self.popup_image = 'gajim-chat_msg_recv'
            self.popup_title = _('New Message from %(nickname)s') % \
                {'nickname': nick}


        if app.config.get('notify_on_new_message'):
            if self.first_unread or (app.config.get('autopopup_chat_opened') \
            and not self.control_focused):
                if app.config.get('autopopupaway'):
                    # always show notification
                    self.do_popup = True
                if app.connections[self.conn.name].connected in (2, 3):
                    # we're online or chat
                    self.do_popup = True

        if msg_obj.attention and not app.config.get(
        'ignore_incoming_attention'):
            self.popup_timeout = 0
            self.do_popup = True
        else:
            self.popup_timeout = app.config.get('notification_timeout')

        if msg_obj.attention and not app.config.get(
        'ignore_incoming_attention') and app.config.get_per('soundevents',
        'attention_received', 'enabled'):
            self.sound_event = 'attention_received'
            self.do_sound = True
        elif self.first_unread and helpers.allow_sound_notification(
        self.conn.name, 'first_message_received'):
            self.do_sound = True
        elif not self.first_unread and self.control_focused and \
        helpers.allow_sound_notification(self.conn.name,
        'next_message_received_focused'):
            self.do_sound = True
        elif not self.first_unread and not self.control_focused and \
        helpers.allow_sound_notification(self.conn.name,
        'next_message_received_unfocused'):
            self.do_sound = True

    def handle_incoming_gc_msg_event(self, msg_obj):
        if not msg_obj.msg_obj.gc_control:
            # we got a message from a room we're not in? ignore it
            return
        self.jid = msg_obj.jid
        sound = msg_obj.msg_obj.gc_control.highlighting_for_message(
            msg_obj.msgtxt, msg_obj.timestamp)[1]

        if msg_obj.nickname != msg_obj.msg_obj.gc_control.nick:
            self.do_sound = True
            if sound == 'received':
                self.sound_event = 'muc_message_received'
            elif sound == 'highlight':
                self.sound_event = 'muc_message_highlight'
            else:
                self.do_sound = False
        else:
            self.do_sound = False

        self.do_popup = False

    def get_path_to_generic_or_avatar(self, generic, jid=None, suffix=None):
        """
        Choose between avatar image and default image

        Returns full path to the avatar image if it exists, otherwise returns full
        path to the image.  generic must be with extension and suffix without
        """
        if jid:
            # we want an avatar
            puny_jid = helpers.sanitize_filename(jid)
            path_to_file = os.path.join(app.AVATAR_PATH, puny_jid) + suffix
            path_to_local_file = path_to_file + '_local'
            for extension in ('.png', '.jpeg'):
                path_to_local_file_full = path_to_local_file + extension
                if os.path.exists(path_to_local_file_full):
                    return path_to_local_file_full
            for extension in ('.png', '.jpeg'):
                path_to_file_full = path_to_file + extension
                if os.path.exists(path_to_file_full):
                    return path_to_file_full
        return os.path.abspath(generic)

    def handle_incoming_pres_event(self, pres_obj):
        if app.jid_is_transport(pres_obj.jid):
            return True
        account = pres_obj.conn.name
        self.jid = pres_obj.jid
        resource = pres_obj.resource or ''
        # It isn't an agent
        for c in pres_obj.contact_list:
            if c.resource == resource:
                # we look for other connected resources
                continue
            if c.show not in ('offline', 'error'):
                return True


        # no other resource is connected, let's look in metacontacts
        family = app.contacts.get_metacontacts_family(account, self.jid)
        for info in family:
            acct_ = info['account']
            jid_ = info['jid']
            c_ = app.contacts.get_contact_with_highest_priority(acct_, jid_)
            if not c_:
                continue
            if c_.jid == self.jid:
                continue
            if c_.show not in ('offline', 'error'):
                return True

        if pres_obj.old_show < 2 and pres_obj.new_show > 1:
            event = 'contact_connected'
            show_image = 'online.png'
            suffix = '_notif_size_colored'
            server = app.get_server_from_jid(self.jid)
            account_server = account + '/' + server
            block_transport = False
            if account_server in app.block_signed_in_notifications and \
            app.block_signed_in_notifications[account_server]:
                block_transport = True
            if helpers.allow_showing_notification(account, 'notify_on_signin') \
            and not app.block_signed_in_notifications[account] and \
            not block_transport:
                self.do_popup = True
            if app.config.get_per('soundevents', 'contact_connected',
            'enabled') and not app.block_signed_in_notifications[account] and\
            not block_transport and helpers.allow_sound_notification(account,
            'contact_connected'):
                self.sound_event = event
                self.do_sound = True

        elif pres_obj.old_show > 1 and pres_obj.new_show < 2:
            event = 'contact_disconnected'
            show_image = 'offline.png'
            suffix = '_notif_size_bw'
            if helpers.allow_showing_notification(account, 'notify_on_signout'):
                self.do_popup = True
            if app.config.get_per('soundevents', 'contact_disconnected',
            'enabled') and helpers.allow_sound_notification(account, event):
                self.sound_event = event
                self.do_sound = True
        # Status change (not connected/disconnected or error (<1))
        elif pres_obj.new_show > 1:
            event = 'status_change'
            # FIXME: we don't always 'online.png', but we first need 48x48 for
            # all status
            show_image = 'online.png'
            suffix = '_notif_size_colored'
        else:
            return True

        transport_name = app.get_transport_name_from_jid(self.jid)
        img_path = None
        if transport_name:
            img_path = os.path.join(helpers.get_transport_path(
                transport_name), '48x48', show_image)
        if not img_path or not os.path.isfile(img_path):
            iconset = app.config.get('iconset')
            img_path = os.path.join(helpers.get_iconset_path(iconset),
                '48x48', show_image)
        self.popup_image_path = self.get_path_to_generic_or_avatar(img_path,
            jid=self.jid, suffix=suffix)

        self.popup_timeout = app.config.get('notification_timeout')

        nick = i18n.direction_mark + app.get_name_from_jid(account, self.jid)
        if event == 'status_change':
            self.popup_title = _('%(nick)s Changed Status') % \
                {'nick': nick}
            self.popup_text = _('%(nick)s is now %(status)s') % \
                {'nick': nick, 'status': helpers.get_uf_show(pres_obj.show)}
            if pres_obj.status:
                self.popup_text = self.popup_text + " : " + pres_obj.status
            self.popup_event_type = _('Contact Changed Status')
        elif event == 'contact_connected':
            self.popup_title = _('%(nickname)s Signed In') % {'nickname': nick}
            self.popup_text = ''
            if pres_obj.status:
                self.popup_text = pres_obj.status
            self.popup_event_type = _('Contact Signed In')
        elif event == 'contact_disconnected':
            self.popup_title = _('%(nickname)s Signed Out') % {'nickname': nick}
            self.popup_text = ''
            if pres_obj.status:
                self.popup_text = pres_obj.status
            self.popup_event_type = _('Contact Signed Out')

    def generate(self):
        # what's needed to compute output
        self.conn = self.base_event.conn
        self.jid = ''
        self.control = None
        self.control_focused = False
        self.first_unread = False

        # For output
        self.do_sound = False
        self.sound_file = ''
        self.sound_event = '' # gajim sound played if not sound_file is set
        self.show_popup = False

        self.do_popup = False
        self.popup_title = ''
        self.popup_text = ''
        self.popup_event_type = ''
        self.popup_msg_type = ''
        self.popup_image = ''
        self.popup_image_path = ''
        self.popup_timeout = -1

        self.do_command = False
        self.command = ''

        self.show_in_notification_area = False
        self.show_in_roster = False

        self.detect_type()

        if self.notif_type == 'msg':
            self.handle_incoming_msg_event(self.base_event)
        elif self.notif_type == 'gc-msg':
            self.handle_incoming_gc_msg_event(self.base_event)
        elif self.notif_type == 'pres':
            self.handle_incoming_pres_event(self.base_event)
        return True

class MessageOutgoingEvent(nec.NetworkOutgoingEvent):
    name = 'message-outgoing'
    base_network_events = []

    def init(self):
        self.additional_data = {}
        self.message = None
        self.keyID = None
        self.type_ = 'chat'
        self.kind = None
        self.timestamp = None
        self.subject = ''
        self.chatstate = None
        self.stanza_id = None
        self.resource = None
        self.user_nick = None
        self.xhtml = None
        self.label = None
        self.session = None
        self.forward_from = None
        self.form_node = None
        self.delayed = None
        self.callback = None
        self.callback_args = []
        self.now = False
        self.is_loggable = True
        self.control = None
        self.attention = False
        self.correct_id = None
        self.automatic_message = True
        self.encryption = ''
        self.encrypted = False

    def get_full_jid(self):
        if self.resource:
            return self.jid + '/' + self.resource
        if self.session:
            return self.session.get_to()
        return self.jid

    def generate(self):
        if self.type_ == 'chat':
            self.kind = KindConstant.CHAT_MSG_SENT
        else:
            self.kind = KindConstant.SINGLE_MSG_SENT
        return True

class StanzaMessageOutgoingEvent(nec.NetworkOutgoingEvent):
    name='stanza-message-outgoing'
    base_network_events = []

    def generate(self):
        return True

class GcStanzaMessageOutgoingEvent(nec.NetworkOutgoingEvent):
    name='gc-stanza-message-outgoing'
    base_network_events = []

    def generate(self):
        return True

class GcMessageOutgoingEvent(nec.NetworkOutgoingEvent):
    name = 'gc-message-outgoing'
    base_network_events = []

    def init(self):
        self.additional_data = {}
        self.message = ''
        self.chatstate = None
        self.xhtml = None
        self.stanza_id = None
        self.label = None
        self.callback = None
        self.callback_args = []
        self.is_loggable = True
        self.control = None
        self.correct_id = None
        self.automatic_message = True

    def generate(self):
        return True


class ClientCertPassphraseEvent(nec.NetworkIncomingEvent):
    name = 'client-cert-passphrase'
    base_network_events = []

class InformationEvent(nec.NetworkIncomingEvent):
    name = 'information'
    base_network_events = []

    def init(self):
        self.args = None
        self.kwargs = {}
        self.dialog_name = None
        self.popup = True

    def generate(self):
        if self.args is None:
            self.args = ()
        else:
            self.args = (self.args,)
        return True

class BlockingEvent(nec.NetworkIncomingEvent):
    name = 'blocking'
    base_network_events = []

    def init(self):
        self.blocklist = []
        self.blocked_jids = []
        self.unblocked_jids = []
        self.unblock_all = False

    def generate(self):
        block_list = self.stanza.getTag(
            'blocklist', namespace=nbxmpp.NS_BLOCKING)
        if block_list is not None:
            for item in block_list.getTags('item'):
                self.blocklist.append(item.getAttr('jid'))
            app.log('blocking').info(
                'Blocklist Received: %s', self.blocklist)
            return True

        block_tag = self.stanza.getTag('block', namespace=nbxmpp.NS_BLOCKING)
        if block_tag is not None:
            for item in block_tag.getTags('item'):
                self.blocked_jids.append(item.getAttr('jid'))
            app.log('blocking').info(
                'Blocking Push - blocked JIDs: %s', self.blocked_jids)

        unblock_tag = self.stanza.getTag(
            'unblock', namespace=nbxmpp.NS_BLOCKING)
        if unblock_tag is not None:
            if not unblock_tag.getTags('item'):
                self.unblock_all = True
                app.log('blocking').info('Blocking Push - unblocked all')
                return True
            for item in unblock_tag.getTags('item'):
                self.unblocked_jids.append(item.getAttr('jid'))
            app.log('blocking').info(
                'Blocking Push - unblocked JIDs: %s', self.unblocked_jids)
        return True

class HTTPUploadStartEvent(nec.NetworkIncomingEvent):
    name = 'httpupload-start'
    base_network_events = []

    def generate(self):
        return True

class HTTPUploadProgressEvent(nec.NetworkIncomingEvent):
    name = 'httpupload-progress'
    base_network_events = []

    def generate(self):
        return True