changeset 2688:da94375a625c

Towards full data forms support. Not finished, probably broken. Don't merge.
author Karol Nowak <grzywacz@sul.uni.lodz.pl>
date Fri, 17 Aug 2007 03:52:20 +0200
parents 248489d28118
children ef7573c78b43 248a1ff78c24
files MoinMoin/events/jabbernotify.py MoinMoin/events/notification.py jabberbot/xmppbot.py
diffstat 3 files changed, 441 insertions(+), 63 deletions(-) [+]
line wrap: on
line diff
--- a/MoinMoin/events/jabbernotify.py	Thu Aug 16 11:08:32 2007 +0200
+++ b/MoinMoin/events/jabbernotify.py	Fri Aug 17 03:52:20 2007 +0200
@@ -88,8 +88,10 @@
                   {'url': pagelink, 'description': _("Page link")}]
 
         jids = [usr.jid for usr in subscribers[lang]]
+        data['url_list'] = links
+        data['action'] = "file_attached"
 
-        if send_notification(request, jids, data['body'], data['subject'], links, "file_attached"):
+        if send_notification(request, jids, data):
             names.update(recipients)
 
     return notification.Success(names)
@@ -124,8 +126,12 @@
     old_name = event.old_page.page_name
 
     subscribers = page.getSubscribers(request, return_users=1)
+
+    # Change request's page so that we filter subscribers of the OLD page
+    request.page = event.old_page
     notification.filter_subscriber_list(event, subscribers, True)
-    return page_change("page_renamed", request, page, subscribers, oldname=old_name)
+    request.page = page
+    return page_change("page_renamed", request, page, subscribers, old_name=old_name)
 
 
 def handle_user_created(event):
@@ -165,7 +171,12 @@
             msg = notification.page_change_message(change_type, request, page, lang, **kwargs)
             page_url = request.getQualifiedURL(page.url(request, relative=False))
             url = {'url': page_url, 'description': _("Changed page")}
-            result = send_notification(request, jids, msg, _("Page changed"), [url], "page_changed")
+            data = {'action': change_type, 'subject': _('Page changed'),
+                            'url_list': [url], 'text': msg['text'], 'diff': msg.get('diff', ''),
+                            'comment': msg.get('comment', ''), 'editor': msg['editor'],
+                            'old_name': msg.get('old_name', ''), 'page_name': msg.get('page_name', '')}
+
+            result = send_notification(request, jids, data)
 
             if result:
                 recipients.update(names)
@@ -173,7 +184,7 @@
         if recipients:
             return notification.Success(recipients)
 
-def send_notification(request, jids, message, subject="", url_list=[], action=""):
+def send_notification(request, jids, notification):
     """ Send notifications for a single language.
 
     @param jids: an iterable of Jabber IDs to send the message to
@@ -186,12 +197,14 @@
     _ = request.getText
     server = request.cfg.notification_server
 
-    if type(url_list) != list:
+    if type(notification['url_list']) != list:
         raise ValueError("url_list must be of type list!")
 
+    if type(notification) != dict:
+        raise ValueError("notification must be of type dict!")
+
     try:
-        cmd_data = {'text': message, 'subject': subject, 'url_list': url_list, 'action': action}
-        server.send_notification(request.cfg.secret, jids, cmd_data)
+        server.send_notification(request.cfg.secret, jids, notification)
         return True
     except xmlrpclib.Error, err:
         ev.logger.error(_("XML RPC error: %s"), str(err))
--- a/MoinMoin/events/notification.py	Thu Aug 16 11:08:32 2007 +0200
+++ b/MoinMoin/events/notification.py	Fri Aug 17 03:52:20 2007 +0200
@@ -60,12 +60,12 @@
 def page_change_message(msgtype, request, page, lang, **kwargs):
     """Prepare a notification text for a page change of given type
 
-    @param msgtype: a type of message to send (page_changed, attachment_added, ...)
+    @param msgtype: a type of message to send (page_changed, page_renamed, ...)
     @type msgtype: str or unicode
     @param **kwargs: a dictionary of additional parameters, which depend on msgtype
 
-    @return: a formatted, ready to send message
-    @rtype: unicode
+    @return: dictionary containing data about the changed page
+    @rtype: dict
 
     """
     from MoinMoin.action.AttachFile import getAttachUrl
@@ -73,6 +73,7 @@
     _ = request.getText
     page._ = lambda s, formatted=True, r=request, l=lang: r.getText(s, formatted=formatted, lang=l)
     querystr = {}
+    changes = {}
 
     if msgtype == "page_changed":
         revisions = kwargs['revisions']
@@ -84,7 +85,7 @@
     pagelink = page_link(request, page, querystr)
 
     if msgtype == "page_changed":
-        msg_body = _("Dear Wiki user,\n\n"
+        changes['text'] = _("Dear Wiki user,\n\n"
         'You have subscribed to a wiki page or wiki category on "%(sitename)s" for change notification.\n\n'
         'The "%(pagename)s" page has been changed by %(editor)s:\n\n', formatted=False) % {
             'pagename': page.page_name,
@@ -94,45 +95,46 @@
 
         # append a diff (or append full page text if there is no diff)
         if len(revisions) < 2:
-            messageBody = msg_body + \
-                _("New page:\n", formatted=False) + \
-                page.get_raw_body()
+            changes['diff'] = _("New page:\n", formatted=False) + page.get_raw_body()
         else:
             lines = wikiutil.pagediff(request, page.page_name, revisions[1],
                                       page.page_name, revisions[0])
             if lines:
-                msg_body = msg_body + "%s\n%s\n" % (("-" * 78), '\n'.join(lines))
+                changes['diff'] = '\n'.join(lines)
             else:
-                msg_body = msg_body + _("No differences found!\n", formatted=False)
+                changes['diff'] = _("No differences found!\n", formatted=False)
 
     elif msgtype == "page_deleted":
-        msg_body = _("Dear wiki user,\n\n"
+        changes['text'] = _("Dear wiki user,\n\n"
             'You have subscribed to a wiki page "%(sitename)s" for change notification.\n\n'
-            'The page "%(pagename)" has been deleted by %(editor)s:\n\n', formatted=False) % {
+            'The page "%(pagename)s" has been deleted by %(editor)s:\n\n', formatted=False) % {
                 'pagename': page.page_name,
                 'editor': page.uid_override or user.getUserIdentification(request),
                 'sitename': page.cfg.sitename or request.getBaseURL(),
         }
 
     elif msgtype == "page_renamed":
-        msg_body = _("Dear wiki user,\n\n"
+        changes['text'] = _("Dear wiki user,\n\n"
             'You have subscribed to a wiki page "%(sitename)s" for change notification.\n\n'
-            'The page "%(pagename)" has been renamed from %(oldname)s by %(editor)s:\n',
+            'The page "%(pagename)s" has been renamed from "%(oldname)s" by %(editor)s:\n',
             formatted=False) % {
                 'editor': page.uid_override or user.getUserIdentification(request),
                 'pagename': page.page_name,
                 'sitename': page.cfg.sitename or request.getBaseURL(),
                 'oldname': kwargs['old_name']
         }
+
+        changes['old_name'] = kwargs['old_name']
+        changes['page_name'] = page.page_name
+
     else:
         raise UnknownChangeType()
 
+    changes['editor'] = page.uid_override or user.getUserIdentification(request)
     if 'comment' in kwargs and kwargs['comment']:
-        msg_body = msg_body + \
-            _("The comment on the change is:\n%(comment)s",
-              formatted=False) % {'comment': kwargs['comment']}
+        changes['comment'] = kwargs['comment']
 
-    return msg_body
+    return changes
 
 def user_created_message(request, sitename, username, email):
     """Formats a message used to notify about accounts being created
@@ -155,17 +157,18 @@
     """Formats a message used to notify about new attachments
 
     @param _: a gettext function
-    @return: a dict containing message body and subject
+    @return: a dict with notification data
 
     """
     page = Page(request, page_name)
+    data = {}
 
-    subject = _("New attachment added to page %(pagename)s on %(sitename)s") % {
+    data['subject'] = _("New attachment added to page %(pagename)s on %(sitename)s") % {
                 'pagename': page_name,
                 'sitename': request.cfg.sitename or request.getBaseURL(),
                 }
 
-    body = _("Dear Wiki user,\n\n"
+    data['text'] = _("Dear Wiki user,\n\n"
     'You have subscribed to a wiki page "%(page_name)s" for change notification. '
     "An attachment has been added to that page by %(editor)s. "
     "Following detailed information is available:\n\n"
@@ -177,7 +180,12 @@
         'attach_size': attach_size,
     }
 
-    return {'body': body, 'subject': subject}
+    data['editor'] = user.getUserIdentification(request)
+    data['page_name'] = page_name
+    data['attach_size'] = attach_size
+    data['attach_name'] = attach_name
+
+    return data
 
 
 # XXX: clean up this method to take a notification type instead of bool for_jabber
--- a/jabberbot/xmppbot.py	Thu Aug 16 11:08:32 2007 +0200
+++ b/jabberbot/xmppbot.py	Fri Aug 17 03:52:20 2007 +0200
@@ -66,7 +66,7 @@
         @param priority: priority of the given resource
 
         """
-        self.resources[resource] = {'show': show, 'priority': priority, supports: []}
+        self.resources[resource] = {'show': show, 'priority': priority, 'supports': []}
         self.last_online = None
 
     def set_supports(self, resource, extension):
@@ -80,7 +80,7 @@
         priority among currently connected.
 
         """
-        if resource:
+        if resource and resource in self.resources:
             return extension in self.resources[resource]['supports']
         else:
             resource = self.max_prio_resource()
@@ -156,7 +156,7 @@
     def __str__(self):
         retval = "%s (%s) has %d queued messages"
         res = ", ".join([name + " is " + res['show'] for name, res in self.resources.items()])
-        return retval % (self.jid.as_utf8(), res, len(self.messages))
+        return retval % (self.jid.as_unicode(), res, len(self.messages))
 
 class XMPPBot(Client, Thread):
     """A simple XMPP bot"""
@@ -255,9 +255,9 @@
         """
         language = "en"
         if isinstance(jid, str) or isinstance(jid, unicode):
-            jid = JID(jid).bare().as_utf8()
+            jid = JID(jid).bare().as_unicode()
         else:
-            jid = jid.bare().as_utf8()
+            jid = jid.bare().as_unicode()
 
         if jid in self.contacts:
             language = self.contacts[jid].language
@@ -289,22 +289,22 @@
         # Handle normal notifications
         if isinstance(command, cmd.NotificationCommand):
             cmd_data = command.notification
+            original_text = cmd_data.get('text', '')
+            original_subject = cmd_data.get('subject', '')
 
             for recipient in command.jids:
                 jid = JID(recipient)
-                jid_text = jid.bare().as_utf8()
-
-                text = cmd_data['text']
-                subject = cmd_data.get('subject', '')
-                msg_data = command.notification
+                jid_text = jid.bare().as_unicode()
 
                 if isinstance(command, cmd.NotificationCommandI18n):
                     # Translate&interpolate the message with data
                     gettext_func = self.get_text(jid_text)
                     text, subject = command.translate(gettext_func)
-                    msg_data = {'text': text, 'subject': subject,
-                                'url_list': cmd_data.get('url_list', []),
-                                'action': cmd_data.get('action', '')}
+                    cmd_data['text'] = text
+                    cmd_data['subject'] = subject
+                else:
+                    cmd_data['text'] = original_text
+                    cmd_data['subject'] = original_subject
 
                 # Check if contact is DoNotDisturb.
                 # If so, queue the message for delayed delivery.
@@ -314,14 +314,23 @@
                         contact.messages.append(command)
                         return
 
-                    if contact.supports(jid.resource, u"jabber:x:data"):
-                        action = msg_data.get('action', '')
-                        if action == "page_changed":
-                            self.send_change_form(jid, msg_data)
-                        else:
-                            self.send_message(jid, msg_data, command.msg_type)
+                action = cmd_data.get('action', '')
+                if action == u'page_changed':
+                    self.handle_changed_action(cmd_data, jid, contact)
+                elif action == u'page_deleted':
+                    self.handle_deleted_action(cmd_data, jid, contact)
+                elif action == u'file_attached':
+                    self.handle_attached_action(cmd_data, jid, contact)
+                elif action == u'page_renamed':
+                    try:
+                        self.handle_renamed_action(cmd_data, jid, contact)
+                    except Exception, e:
+                        import traceback
+                        traceback.print_exc(e)
+                elif action == u'user_created':
+                    self.handle_user_created_action(cmd_data, jid, contact)
                 else:
-                    self.send_message(jid, msg_data, command.msg_type)
+                    self.send_message(jid, cmd_data, command.msg_type)
 
             return
 
@@ -425,6 +434,87 @@
 
                 self.send_form(command.jid, form, _("Search results"))
 
+    def handle_changed_action(self, cmd_data, jid, contact):
+        """Handles a notification command with 'page_changed' action
+
+        @param cmd_data: notification command data
+        @param jid: jid to send the notification to
+        @param contact: a roster contact
+        @type cmd_data: dict
+        @type jid: pyxmpp.jid.JID
+        @type contact: Contact
+
+        """
+        if contact and contact.supports(jid.resource, u"jabber:x:data"):
+            self.send_change_form(jid.as_unicode(), cmd_data)
+            return
+        else:
+            self.send_change_text(jid.as_unicode(), cmd_data)
+
+    def handle_deleted_action(self, cmd_data, jid, contact):
+        """Handles a notification cmd_data with 'page_deleted' action
+
+        @param cmd_data: notification cmd_data
+        @param jid: jid to send the notification to
+        @param contact: a roster contact
+        @type cmd_data: dict
+        @type jid: pyxmpp.jid.JID
+        @type contact: Contact
+
+        """
+        if contact and contact.supports(jid.resource, u"jabber:x:data"):
+            self.send_deleted_form(jid.as_unicode(), cmd_data)
+            return
+        else:
+            self.send_deleted_text(jid.as_unicode(), cmd_data)
+
+
+    def handle_attached_action(self, cmd_data, jid, contact):
+        """Handles a notification cmd_data with 'file_attached' action
+
+        @param cmd_data: notification cmd_data
+        @param jid: jid to send the notification to
+        @param contact: a roster contact
+        @type cmd_data: dict
+        @type jid: pyxmpp.jid.JID
+        @type contact: Contact
+
+        """
+        if contact and contact.supports(jid.resource, u"jabber:x:data"):
+            self.send_attached_form(jid.as_unicode(), cmd_data)
+            return
+        else:
+            self.send_attached_text(jid.as_unicode(), cmd_data)
+
+    def handle_renamed_action(self, cmd_data, jid, contact):
+        """Handles a notification cmd_data with 'page_renamed' action
+
+        @param cmd_data: notification cmd_data
+        @param jid: jid to send the notification to
+        @param contact: a roster contact
+        @type cmd_data: dict
+        @type jid: pyxmpp.jid.JID
+        @type contact: Contact
+
+        """
+        if contact and contact.supports(jid.resource, u"jabber:x:data"):
+            self.send_renamed_form(jid.as_unicode(), cmd_data)
+            return
+        else:
+            self.send_renamed_text(jid.as_unicode(), cmd_data)
+
+    def handle_user_created_action(self, cmd_data, jid, contact):
+        """Handles a notification cmd_data with 'user_created' action
+
+        @param cmd_data: notification cmd_data
+        @param jid: jid to send the notification to
+        @param contact: a roster contact
+        @type cmd_data: dict
+        @type jid: pyxmpp.jid.JID
+        @type contact: Contact
+
+        """
+        raise NotImplemented()
 
     def ask_for_subscription(self, jid):
         """Sends a <presence/> stanza with type="subscribe"
@@ -463,7 +553,7 @@
         jid = JID(jid_text)
 
         if data.has_key('url_list') and data['url_list']:
-            jid_bare = jid.bare().as_utf8()
+            jid_bare = jid.bare().as_unicode()
             contact = self.contacts.get(jid_bare, None)
             if contact and contact.supports(jid.resource, u'jabber:x:oob'):
                 use_oob = True
@@ -482,15 +572,17 @@
 
         self.get_stream().send(message)
 
-    def send_form(self, jid, form, subject):
+    def send_form(self, jid, form, subject, url_list=[]):
         """Send a data form
 
         @param jid: jid to send the form to (full)
         @param form: the form to send
         @param subject: subject of the message
+        @param url_list: list of urls to use with OOB
         @type jid: unicode
         @type form: pyxmpp.jabber.dataforms.Form
         @type subject: unicode
+        @type url_list: list
 
         """
         if not isinstance(form, forms.Form):
@@ -501,6 +593,10 @@
         warning = _("If you see this, your client probably doesn't support Data Forms.")
         message = Message(to_jid=jid, body=warning, subject=subject)
         message.add_content(form)
+
+        if url_list:
+            oob.add_urls(message, url_list)
+
         self.get_stream().send(message)
 
     def send_search_form(self, jid):
@@ -531,13 +627,18 @@
         self.send_form(jid, form, _("Wiki search"))
 
     def send_change_form(self, jid, msg_data):
-        """Sends a page change notification using Data Forms"""
+        """Sends a page change notification using Data Forms
+
+        @param jid: a Jabber ID to send the notification to
+        @type jid: unicode
+        @param msg_data: dictionary with notification data
+        @type msg_data: dict
+
+        """
         _ = self.get_text(jid)
 
         form_title = _("Page changed notification").encode("utf-8")
         instructions = _("Submit this form with a specified action to continue.").encode("utf-8")
-        url_label = _("URL")
-        pagename_label = _("Page name")
         action_label = _("What to do next")
 
         action1 = _("Do nothing")
@@ -550,10 +651,265 @@
 
         form = forms.Form(xmlnode_or_type="form", title=form_title, instructions=instructions)
         form.add_field(name="action", field_type="hidden", value="change_notify_action")
-        form.add_field(name="notification", field_type="fixed", value=msg_data['text'])
+        form.add_field(name='editor', field_type='text-single', value=msg_data['editor'], label=_("Editor"))
+        form.add_field(name='comment', field_type='text-single', value=msg_data.get('comment', ''), label=_("Comment"))
+
+        # Add lines of text as separate values, as recommended in XEP
+        diff_lines = msg_data['diff'].split('\n')
+        form.add_field(name="diff", field_type="text-multi", values=diff_lines, label=("Diff"))
+
+        full_jid = JID(jid)
+        bare_jid = full_jid.bare().as_unicode()
+        resource = full_jid.resource
+
+        # Add URLs as OOB data if it's supported and as separate fields otherwise
+        if bare_jid in self.contacts and self.contacts[bare_jid].supports(resource, u'jabber:x:oob'):
+            url_list = msg_data['url_list']
+        else:
+            url_list = []
+
+            for number, url in enumerate(msg_data['url_list']):
+                field_name = "url%d" % (number, )
+                form.add_field(name=field_name, field_type="text-single", value=url["url"], label=url["description"])
+
+        # Selection of a following action
         form.add_field(name="options", field_type="list-single", options=[do_nothing, revert, view_info], label=action_label)
 
-        self.send_form(jid, form, _("Page change notification"))
+        self.send_form(jid, form, _("Page change notification"), url_list)
+
+    def send_change_text(self, jid, msg_data):
+        """Sends a simple, text page change notification
+
+        @param jid: a Jabber ID to send the notification to
+        @type jid: unicode
+        @param msg_data: dictionary with notification data
+        @type msg_data: dict
+
+        """
+        _ = self.get_text(jid)
+        separator = '-' * 78
+        urls_text = '\n'.join(["%s - %s" % (url["description"], url["url"]) for url in msg_data['url_list']])
+        message = _("%(preamble)s\nComment: %(comment)s\n%(separator)s\n%(diff)s\n%(separator)s\n%(links)s") % {
+                    'preamble': msg_data['text'],
+                    'separator': separator,
+                    'diff': msg_data['diff'],
+                    'comment': msg_data.get('comment', _('no comment')),
+                    'links': urls_text,
+                  }
+
+        data = {'text': message, 'subject': msg_data.get('subject', '')}
+        self.send_message(jid, data, u"message")
+
+    def send_deleted_form(self, jid, msg_data):
+        """Sends a page deleted notification using Data Forms
+
+        @param jid: a Jabber ID to send the notification to
+        @type jid: unicode
+        @param msg_data: dictionary with notification data
+        @type msg_data: dict
+
+        """
+        _ = self.get_text(jid)
+
+        form_title = _("Page deletion notification").encode("utf-8")
+        instructions = _("Submit this form with a specified action to continue.").encode("utf-8")
+        action_label = _("What to do next")
+
+        action1 = _("Do nothing")
+        action2 = _("Revert change")
+        action3 = _("View page info")
+
+        do_nothing = forms.Option("n", action1)
+        revert = forms.Option("r", action2)
+        view_info = forms.Option("v", action3)
+
+        form = forms.Form(xmlnode_or_type="form", title=form_title, instructions=instructions)
+        form.add_field(name="action", field_type="hidden", value="delete_notify_action")
+        form.add_field(name='editor', field_type='text-single', value=msg_data['editor'], label=_("Editor"))
+        form.add_field(name='comment', field_type='text-single', value=msg_data.get('comment', ''), label=_("Comment"))
+
+        full_jid = JID(jid)
+        bare_jid = full_jid.bare().as_unicode()
+        resource = full_jid.resource
+
+        # Add URLs as OOB data if it's supported and as separate fields otherwise
+        if bare_jid in self.contacts and self.contacts[bare_jid].supports(resource, u'jabber:x:oob'):
+            url_list = msg_data['url_list']
+        else:
+            url_list = []
+
+            for number, url in enumerate(msg_data['url_list']):
+                field_name = "url%d" % (number, )
+                form.add_field(name=field_name, field_type="text-single", value=url["url"], label=url["description"])
+
+        # Selection of a following action
+        form.add_field(name="options", field_type="list-single", options=[do_nothing, revert, view_info], label=action_label)
+
+        self.send_form(jid, form, _("Page deletion notification"), url_list)
+
+    def send_deleted_text(self, jid, msg_data):
+        """Sends a simple, text page deletion notification
+
+        @param jid: a Jabber ID to send the notification to
+        @type jid: unicode
+        @param msg_data: dictionary with notification data
+        @type msg_data: dict
+
+        """
+        _ = self.get_text(jid)
+        separator = '-' * 78
+        urls_text = '\n'.join(["%s - %s" % (url["description"], url["url"]) for url in msg_data['url_list']])
+        message = _("%(preamble)s\nComment: %(comment)s\n%(separator)s\n%(links)s") % {
+                    'preamble': msg_data['text'],
+                    'separator': separator,
+                    'comment': msg_data.get('comment', _('no comment')),
+                    'links': urls_text,
+                  }
+
+        data = {'text': message, 'subject': msg_data.get('subject', '')}
+        self.send_message(jid, data, u"message")
+
+    def send_attached_form(self, jid, msg_data):
+        """Sends a new attachment notification using Data Forms
+
+        @param jid: a Jabber ID to send the notification to
+        @type jid: unicode
+        @param msg_data: dictionary with notification data
+        @type msg_data: dict
+
+        """
+        _ = self.get_text(jid)
+
+        form_title = _("File attached notification").encode("utf-8")
+        instructions = _("Submit this form with a specified action to continue.").encode("utf-8")
+        action_label = _("What to do next")
+
+        action1 = _("Do nothing")
+        action2 = _("Revert change")
+        action3 = _("View page info")
+
+        do_nothing = forms.Option("n", action1)
+        revert = forms.Option("r", action2)
+        view_info = forms.Option("v", action3)
+
+        form = forms.Form(xmlnode_or_type="form", title=form_title, instructions=instructions)
+        form.add_field(name="action", field_type="hidden", value="change_notify_action")
+        form.add_field(name='editor', field_type='text-single', value=msg_data['editor'], label=_("Editor"))
+        form.add_field(name='page', field_type='text-single', value=msg_data['page_name'], label=_("Page name"))
+        form.add_field(name='name', field_type='text-single', value=msg_data['attach_name'], label=_("File name"))
+        form.add_field(name='size', field_type='text-single', value=msg_data['attach_size'], label=_("File size"))
+
+        full_jid = JID(jid)
+        bare_jid = full_jid.bare().as_unicode()
+        resource = full_jid.resource
+
+        # Add URLs as OOB data if it's supported and as separate fields otherwise
+        if bare_jid in self.contacts and self.contacts[bare_jid].supports(resource, u'jabber:x:oob'):
+            url_list = msg_data['url_list']
+        else:
+            url_list = []
+
+            for number, url in enumerate(msg_data['url_list']):
+                field_name = "url%d" % (number, )
+                form.add_field(name=field_name, field_type="text-single", value=url["url"], label=url["description"])
+
+        # Selection of a following action
+        form.add_field(name="options", field_type="list-single", options=[do_nothing, revert, view_info], label=action_label)
+
+        self.send_form(jid, form, _("File attached notification"), url_list)
+
+    def send_attached_text(self, jid, msg_data):
+        """Sends a simple, text page deletion notification
+
+        @param jid: a Jabber ID to send the notification to
+        @type jid: unicode
+        @param msg_data: dictionary with notification data
+        @type msg_data: dict
+
+        """
+        _ = self.get_text(jid)
+        separator = '-' * 78
+        urls_text = '\n'.join(["%s - %s" % (url["description"], url["url"]) for url in msg_data['url_list']])
+        message = _("%(preamble)s\n%(separator)s\n%(links)s") % {
+                    'preamble': msg_data['text'],
+                    'separator': separator,
+                    'links': urls_text,
+                  }
+
+        data = {'text': message, 'subject': msg_data['subject']}
+        self.send_message(jid, data, u"message")
+
+    def send_renamed_form(self, jid, msg_data):
+        """Sends a page rename notification using Data Forms
+
+        @param jid: a Jabber ID to send the notification to
+        @type jid: unicode
+        @param msg_data: dictionary with notification data
+        @type msg_data: dict
+
+        """
+        _ = self.get_text(jid)
+
+        form_title = _("Page rename notification").encode("utf-8")
+        instructions = _("Submit this form with a specified action to continue.").encode("utf-8")
+        action_label = _("What to do next")
+
+        action1 = _("Do nothing")
+        action2 = _("Revert change")
+        action3 = _("View page info")
+
+        do_nothing = forms.Option("n", action1)
+        revert = forms.Option("r", action2)
+        view_info = forms.Option("v", action3)
+
+        form = forms.Form(xmlnode_or_type="form", title=form_title, instructions=instructions)
+        form.add_field(name="action", field_type="hidden", value="change_notify_action")
+        form.add_field(name='editor', field_type='text-single', value=msg_data['editor'], label=_("Editor"))
+        form.add_field(name='comment', field_type='text-single', value=msg_data.get('comment', ''), label=_("Comment"))
+        form.add_field(name='old', field_type='text-single', value=msg_data['old_name'], label=_("Old name"))
+        form.add_field(name='new', field_type='text-single', value=msg_data['page_name'], label=_("New name"))
+
+        full_jid = JID(jid)
+        bare_jid = full_jid.bare().as_unicode()
+        resource = full_jid.resource
+
+        # Add URLs as OOB data if it's supported and as separate fields otherwise
+        if bare_jid in self.contacts and self.contacts[bare_jid].supports(resource, u'jabber:x:oob'):
+            url_list = msg_data['url_list']
+        else:
+            url_list = []
+
+            for number, url in enumerate(msg_data['url_list']):
+                field_name = "url%d" % (number, )
+                form.add_field(name=field_name, field_type="text-single", value=url["url"], label=url["description"])
+
+        # Selection of a following action
+        form.add_field(name="options", field_type="list-single", options=[do_nothing, revert, view_info], label=action_label)
+
+        self.send_form(jid, form, _("Page rename notification"), url_list)
+
+    def send_renamed_text(self, jid, msg_data):
+        """Sends a simple, text page rename notification
+
+        @param jid: a Jabber ID to send the notification to
+        @type jid: unicode
+        @param msg_data: dictionary with notification data
+        @type msg_data: dict
+
+        """
+        _ = self.get_text(jid)
+        separator = '-' * 78
+        urls_text = '\n'.join(["%s - %s" % (url["description"], url["url"]) for url in msg_data['url_list']])
+        message = _("%(preamble)s\nComment: %(comment)s\n%(separator)s\n%(links)s") % {
+                    'preamble': msg_data['text'],
+                    'separator': separator,
+                    'comment': msg_data.get('comment', _('no comment')),
+                    'links': urls_text,
+                  }
+
+        data = {'text': message, 'subject': msg_data['subject']}
+        self.send_message(jid, data, u"message")
+
 
     def is_internal(self, command):
         """Check if a given command is internal
@@ -671,7 +1027,7 @@
 
         """
         if self.config.verbose:
-            msg = "Message from %s." % (message.get_from_jid().as_utf8(), )
+            msg = "Message from %s." % (message.get_from_jid().as_unicode(), )
             self.log.debug(msg)
 
         form = self.contains_form(message)
@@ -715,7 +1071,7 @@
             else:
                 return self.help_on(sender, command[1])
         elif command[0] == "searchform":
-            jid = sender.bare().as_utf8()
+            jid = sender.bare().as_unicode()
             resource = sender.resource
 
             # Assume that outsiders know what they are doing. Clients that don't support
@@ -789,7 +1145,7 @@
         command_class = self.xmlrpc_commands[command[0]]
 
         # Add sender's JID to the argument list
-        command.insert(1, sender.as_utf8())
+        command.insert(1, sender.as_unicode())
 
         try:
             instance = command_class.__new__(command_class)
@@ -825,7 +1181,7 @@
         self.log.debug("Handling unavailable presence.")
 
         jid = stanza.get_from_jid()
-        bare_jid = jid.bare().as_utf8()
+        bare_jid = jid.bare().as_unicode()
 
         # If we get presence, this contact should already be known
         if bare_jid in self.contacts:
@@ -871,7 +1227,7 @@
 
         priority = presence.get_priority()
         jid = presence.get_from_jid()
-        bare_jid = jid.bare().as_utf8()
+        bare_jid = jid.bare().as_unicode()
 
         if bare_jid in self.contacts:
             contact = self.contacts[bare_jid]
@@ -883,8 +1239,9 @@
             # Unknown resource, add it to the list
             else:
                 contact.add_resource(jid.resource, show, priority)
+
                 # Discover capabilities of the newly connected client
-                contact.service_discovery(jid)
+                self.service_discovery(jid)
 
             if self.config.verbose:
                 self.log.debug(contact)
@@ -941,12 +1298,12 @@
         supports = payload.xpathEval('//*[@var="jabber:x:data"]')
         if supports:
             jid = stanza.get_from_jid()
-            self.contacts[jid.bare().as_utf8()].set_supports(jid.resource, u"jabber:x:data")
+            self.contacts[jid.bare().as_unicode()].set_supports(jid.resource, u"jabber:x:data")
 
         supports = payload.xpathEval('//*[@var="jabber:x:oob"]')
         if supports:
             jid = stanza.get_from_jid()
-            self.contacts[jid.bare().as_utf8()].set_supports(jid.resource, u"jabber:x:oob")
+            self.contacts[jid.bare().as_unicode()].set_supports(jid.resource, u"jabber:x:oob")
 
 
     def send_queued_messages(self, contact, ignore_dnd=False):