Mercurial > moin > 1.9
changeset 2699:98f7c5c64e91
Merge main
author | Alexander Schremmer <alex AT alexanderweb DOT de> |
---|---|
date | Sun, 19 Aug 2007 17:01:35 +0200 |
parents | 701a36397035 (current diff) 0a6f74a01646 (diff) |
children | a0b85757e5d0 |
files | |
diffstat | 10 files changed, 750 insertions(+), 236 deletions(-) [+] |
line wrap: on
line diff
--- a/MoinMoin/PageEditor.py Sun Aug 19 16:57:38 2007 +0200 +++ b/MoinMoin/PageEditor.py Sun Aug 19 17:01:35 2007 +0200 @@ -63,6 +63,8 @@ # exceptions for .saveText() class SaveError(error.Error): pass + class RevertError(SaveError): + pass class AccessDenied(SaveError): pass class Immutable(AccessDenied): @@ -627,6 +629,41 @@ else: return False, _('Could not rename page because of file system error: %s.') % unicode(err) + + def revertPage(self, revision): + """ Reverts page to the given revision + + @param revision: revision to revert to + @type revision: int + + """ + _ = self.request.getText + + if not self.request.user.may.revert(self.page_name): + raise self.RevertError(_('You are not allowed to revert this page!')) + elif revision is None: + raise self.RevertError(_('You were viewing the current revision of this page when you called the revert action. ' + 'If you want to revert to an older revision, first view that older revision and ' + 'then call revert to this (older) revision again.')) + else: + revstr = '%08d' % revision + pg = Page(self.request, self.page_name, rev=revision) + msg = self.saveText(pg.get_raw_body(), 0, extra=revstr, action="SAVE/REVERT", notify=False) + + # Remove cache entry (if exists) + from MoinMoin import caching + pg = Page(self.request, self.page_name) + key = self.request.form.get('key', ['text_html'])[0] + caching.CacheEntry(self.request, pg, key, scope='item').remove() + caching.CacheEntry(self.request, pg, "pagelinks", scope='item').remove() + + # Notify observers + from MoinMoin.events import PageRevertedEvent, send_event + e = PageRevertedEvent(self.request, self.page_name, revision, revstr) + send_event(e) + + return msg + def deletePage(self, comment=None): """ Delete the current version of the page (making a backup before deletion and keeping the backups, logs and attachments).
--- a/MoinMoin/action/revert.py Sun Aug 19 16:57:38 2007 +0200 +++ b/MoinMoin/action/revert.py Sun Aug 19 17:01:35 2007 +0200 @@ -11,41 +11,13 @@ def execute(pagename, request): """ restore another revision of a page as a new current revision """ from MoinMoin.PageEditor import PageEditor - _ = request.getText - msg = None rev = request.rev - pg = Page(request, pagename, rev=rev) - - if not request.user.may.revert(pagename): - msg = _('You are not allowed to revert this page!') - elif rev is None: - msg = _('You were viewing the current revision of this page when you called the revert action. ' - 'If you want to revert to an older revision, first view that older revision and ' - 'then call revert to this (older) revision again.') - else: - newpg = PageEditor(request, pagename) + pg = PageEditor(request, pagename) - revstr = '%08d' % rev - try: - msg = newpg.saveText(pg.get_raw_body(), 0, extra=revstr, action="SAVE/REVERT", notify=False) - pg = newpg - except newpg.SaveError, msg: - msg = unicode(msg) - request.reset() - - key = request.form.get('key', ['text_html'])[0] + try: + msg = pg.revertPage(rev) + except PageEditor.RevertError, error: + msg = unicode(error) - # Remove cache entry (if exists) - pg = Page(request, pagename) - from MoinMoin import caching - caching.CacheEntry(request, pg, key, scope='item').remove() - caching.CacheEntry(request, pg, "pagelinks", scope='item').remove() - - # Notify observers - from MoinMoin.events import PageRevertedEvent, send_event - e = PageRevertedEvent(request, pagename, request.rev, revstr) - send_event(e) - - if request.action != "xmlrpc": - pg.send_page(msg=msg) - + request.reset() + pg.send_page(msg=msg)
--- a/MoinMoin/events/jabbernotify.py Sun Aug 19 16:57:38 2007 +0200 +++ b/MoinMoin/events/jabbernotify.py Sun Aug 19 17:01:35 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,13 @@ 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', ''), + 'revision': msg.get('revision', '')} + + result = send_notification(request, jids, data) if result: recipients.update(names) @@ -173,7 +185,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 +198,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 Sun Aug 19 16:57:38 2007 +0200 +++ b/MoinMoin/events/notification.py Sun Aug 19 17:01:35 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 = {'page_name': page.page_name, 'revision': str(page.getRevList()[0])} 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,45 @@ # 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'] + 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 +156,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 +179,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/MoinMoin/xmlrpc/__init__.py Sun Aug 19 16:57:38 2007 +0200 +++ b/MoinMoin/xmlrpc/__init__.py Sun Aug 19 17:01:35 2007 +0200 @@ -570,13 +570,16 @@ if not self.request.user.may.write(pagename): return xmlrpclib.Fault(1, "You are not allowed to edit this page") - from MoinMoin.action import revert - self.request.rev = int(self._instr(revision)) - msg = revert.execute(pagename, self.request) - if msg: - return xmlrpclib.Fault(1, "Revert failed: %s" % (msg, )) - else: - return xmlrpclib.Boolean(1) + from MoinMoin.PageEditor import PageEditor + rev = int(self._instr(revision)) + editor = PageEditor(self.request, pagename) + + try: + editor.revertPage(rev) + except PageEditor.SaveError, error: + return xmlrpclib.Fault(1, "Revert failed: %s" % (str(error), )) + + return xmlrpclib.Boolean(1) def xmlrpc_searchPages(self, query_string): """ Searches pages for query_string.
--- a/docs/CHANGES.jabber Sun Aug 19 16:57:38 2007 +0200 +++ b/docs/CHANGES.jabber Sun Aug 19 17:01:35 2007 +0200 @@ -5,83 +5,77 @@ using until now. Otherwise you might miss some important upgrading and configuration hints. -Overview: -=============================================== - MoinMoin 1.7 includes a brand new notification system based on - a separate process running a Jabber/XMPP notification bot. See - http://www.jabber.org and http://www.xmpp.org for more information - on this protocol. - - The bot can be used to send notifications about various events - occuring in your Wiki, or to work with the Wiki interactively. - - As it's a separate process, it doesn't block waiting for all - notifications to be sent, to this solution should be suitable for - large sites that have many users subscribed to particular changes. - -Features: -=============================================== - * Notification sent when pages are changed in various ways (content - change, page rename, deletion, page copy), users being created - (visible to super user only!), attachments being added and users - subscribing to pages... - - * Users can choose which events they're interested in being notified - about. This applied both to (old) email and jabber notifications. - - * Interactive Jabber bot allows to perform various simple operations - on a Wiki from within your IM client (possibly in response to - received notification). This includes getting raw and html-formatted - page contents, querying detailed page information (last author, - revision, date of the last change...), getting a list of pages, - performing searches and reverts. - - The bot uses Data Forms (XEP-004) and Out of Band Data (XEP-066) - extensions if they're supported by the client to further extend - available communication options - -Getting help: -=============================================== - There's a sample wikiconfig in MOINDIR/wiki/config/more_examples +Version 1.7.current: + New Features: + * The first version that supports jabber notifications. + * Overview: + MoinMoin 1.7 includes a brand new notification system based on + a separate process running a Jabber/XMPP notification bot. See + http://www.jabber.org and http://www.xmpp.org for more information + on this protocol. - You can read more about the notification bot on following pages: - * http://moinmo.in/JabberSupport - * http://moinmo.in/MoinMoinTodo/Release_1.7/HelpOnNotification - -Known main issues with jabber bot: -=============================================== - * You need a development version of pyxmpp, 1.0 won't work. You can - get it directly from svn repository with: - - svn checkout http://pyxmpp.jajcus.net/svn/pyxmpp/trunk pyxmpp - - Add the resulting `pyxmpp` directory to your PYTHONPATH or perform - a "full installation" as described on http://pyxmpp.jajcus.net/: - - To build the package just invoke: - python setup.py build - - To install it: - python setup.py install + The bot can be used to send notifications about various events + occuring in your Wiki, or to work with the Wiki interactively. - If you had some older version of PyXMPP it is better to uninstall it - (delete pyxmpp subdirectory os your site-packages directory) before - installing this one or things may not work correctly. - - You may also try: - make - - and: - make install + As it's a separate process, it doesn't block waiting for all + notifications to be sent, to this solution should be suitable for + large sites that have many users subscribed to particular changes. - instead. + * Features: + * Notification sent when pages are changed in various ways (content + change, page rename, deletion, page copy), users being created + (visible to super user only!), attachments being added and users + subscribing to pages... - * Jabber servers usually have rather tight data rate limits, so if - your site generates a lot of traffic, the notification bot may become - unstable and/or unusable. If such condition occurs, you should - consider running your own Jabber/XMPP server with relaxed limits. + * Users can choose which events they're interested in being notified + about. This applied both to (old) email and jabber notifications. -Changes -=============================================== - Version 1.7.current: - The first version that supports jabber notifications. + * Interactive Jabber bot allows to perform various simple operations + on a Wiki from within your IM client (possibly in response to + received notification). This includes getting raw and html-formatted + page contents, querying detailed page information (last author, + revision, date of the last change...), getting a list of pages, + performing searches and reverts. + + The bot uses Data Forms (XEP-004) and Out of Band Data (XEP-066) + extensions if they're supported by the client to further extend + available communication options + + * Getting help: + * There's a sample wikiconfig in MOINDIR/wiki/config/more_examples + + * You can read more about the notification bot on following pages: + * http://moinmo.in/JabberSupport + * http://moinmo.in/MoinMoinTodo/Release_1.7/HelpOnNotification + + * Known main issues with jabber bot: + * You need a development version of pyxmpp, 1.0 won't work. You can + get it directly from svn repository with: + + svn checkout http://pyxmpp.jajcus.net/svn/pyxmpp/trunk pyxmpp + + Add the resulting `pyxmpp` directory to your PYTHONPATH or perform + a "full installation" as described on http://pyxmpp.jajcus.net/: + + To build the package just invoke: + python setup.py build + + To install it: + python setup.py install + + If you had some older version of PyXMPP it is better to uninstall it + (delete pyxmpp subdirectory os your site-packages directory) before + installing this one or things may not work correctly. + + You may also try: + make + + and: + make install + + instead. + + * Jabber servers usually have rather tight data rate limits, so if + your site generates a lot of traffic, the notification bot may become + unstable and/or unusable. If such condition occurs, you should + consider running your own Jabber/XMPP server with relaxed limits.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jabberbot/_tests/test_xmlrpcbot.py Sun Aug 19 17:01:35 2007 +0200 @@ -0,0 +1,34 @@ +# -*- coding: iso-8859-1 -*- +""" + MoinMoin - XMLRPC bot tests + + @copyright: 2007 by Karol Nowak <grywacz@gmail.com> + @license: GNU GPL, see COPYING for details. +""" +from Queue import Queue + +try: + import pyxmpp +except ImportError: + py.test.skip("Skipping jabber bot tests - pyxmpp is not installed") + +import jabberbot.xmlrpcbot as xmlrpcbot +from jabberbot.config import BotConfig + + +class TestXMLRPCBotAPIs: + def setup_class(self): + self.queue_in = Queue() + self.queue_out = Queue() + self.bot = xmlrpcbot.XMLRPCClient(BotConfig, self.queue_in, self.queue_out) + + def testReportError(self): + print "report_error() should put a command in the output queue" + self.bot.report_error(["dude@example.com"], "Error %(err)s!", data={'err': 'bar!'}) + self.queue_out.get(False) + + def testWanrNoCredentials(self): + print "warn_no_credentials() should put a command in the output queue" + self.bot.warn_no_credentials(["dude@example.com"]) + self.queue_out.get(False) +
--- a/jabberbot/commands.py Sun Aug 19 16:57:38 2007 +0200 +++ b/jabberbot/commands.py Sun Aug 19 17:01:35 2007 +0200 @@ -83,14 +83,18 @@ # Parameter list in a human-readable format parameter_list = u"" - def __init__(self, jid): + def __init__(self, jid, presentation=u"text"): """A constructor @param jid: Jabber ID to send the reply to + @param presentation: how to display results; "text" or "dataforms" @type jid: unicode + @type presentation: unicode + """ self.jid = jid self.data = None + self.presentation = presentation class GetPage(BaseDataCommand): @@ -123,8 +127,8 @@ description = u"show detailed information about a page" parameter_list = u"pagename" - def __init__(self, jid, pagename): - BaseDataCommand.__init__(self, jid) + def __init__(self, jid, pagename, presentation=u"text"): + BaseDataCommand.__init__(self, jid, presentation) self.pagename = pagename class Search(BaseDataCommand):
--- a/jabberbot/xmlrpcbot.py Sun Aug 19 16:57:38 2007 +0200 +++ b/jabberbot/xmlrpcbot.py Sun Aug 19 17:01:35 2007 +0200 @@ -134,6 +134,14 @@ self.do_revert(command) def report_error(self, jid, text, data={}): + """Reports an internal error + + @param jid: Jabber ID that should be informed about the error condition + @param text: description of the error + @param data: dictionary used to substitute strings in translated message + @type data: dict + + """ # Dummy function, so that the string appears in a .po file _ = lambda x: x
--- a/jabberbot/xmppbot.py Sun Aug 19 16:57:38 2007 +0200 +++ b/jabberbot/xmppbot.py Sun Aug 19 17:01:35 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,19 @@ 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': + self.handle_renamed_action(cmd_data, jid, contact) + 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 @@ -349,32 +354,7 @@ self.send_message(command.jid, {'text': msg % (pagelist, )}) elif isinstance(command, cmd.GetPageInfo): - intro = _("""Following detailed information on page "%(pagename)s" \ -is available:""") - - if command.data['author'].startswith("Self:"): - author = command.data['author'][5:] - else: - author = command.data['author'] - - datestr = str(command.data['lastModified']) - date = u"%(year)s-%(month)s-%(day)s at %(time)s" % { - 'year': datestr[:4], - 'month': datestr[4:6], - 'day': datestr[6:8], - 'time': datestr[9:17], - } - - msg = _("""Last author: %(author)s -Last modification: %(modification)s -Current version: %(version)s""") % { - 'author': author, - 'modification': date, - 'version': command.data['version'], - } - - self.send_message(command.jid, {'text': intro % {'pagename': command.pagename}}) - self.send_message(command.jid, {'text': msg}) + self.handle_page_info(command) elif isinstance(command, cmd.GetUserLanguage): if command.jid in self.contacts: @@ -405,26 +385,106 @@ self.send_message(command.jid, data, u"chat") else: form_title = _("Search results").encode("utf-8") + help_form = _("Submit this form to perform a wiki search").encode("utf-8") + form = forms.Form(xmlnode_or_type="result", title=form_title, instructions=help_form) - warnings = [] + action_label = _("What to do next") + do_nothing = forms.Option("n", _("Do nothing")) + search_again = forms.Option("s", _("Search again")) + for no, warning in enumerate(warnings): - field = forms.Field(name="warning", field_type="fixed", value=warning) - warnings.append(forms.Item([field])) - - reported = [forms.Field(name="url", field_type="text-single"), forms.Field(name="description", field_type="text-single")] - if warnings: - reported.append(forms.Field(name="warning", field_type="fixed")) - - form = forms.Form(xmlnode_or_type="result", title=form_title, reported_fields=reported) + form.add_field(name="warning", field_type="fixed", value=warning) for no, result in enumerate(results): - url = forms.Field(name="url", value=result["url"], field_type="text-single") - description = forms.Field(name="description", value=result["description"], field_type="text-single") - item = forms.Item([url, description]) - form.add_item(item) + field_name = "url%d" % (no, ) + form.add_field(name=field_name, value=unicode(result["url"]), label=result["description"].encode("utf-8"), field_type="text-single") + + # Selection of a following action + form.add_field(name="options", field_type="list-single", options=[do_nothing, search_again], label=action_label) 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 +523,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 +542,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): @@ -498,9 +560,12 @@ _ = self.get_text(JID(jid).bare().as_unicode()) - warning = _("If you see this, your client probably doesn't support Data Forms.") - message = Message(to_jid=jid, body=warning, subject=subject) + message = Message(to_jid=jid, 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,29 +596,388 @@ 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") action2 = _("Revert change") action3 = _("View page info") + action4 = _("Perform a search") do_nothing = forms.Option("n", action1) revert = forms.Option("r", action2) view_info = forms.Option("v", action3) + search = forms.Option("s", action4) 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="options", field_type="list-single", options=[do_nothing, revert, view_info], label=action_label) + form.add_field(name='revision', field_type='hidden', value=msg_data['revision']) + form.add_field(name='page_name', field_type='hidden', value=msg_data['page_name']) + 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")) - self.send_form(jid, form, _("Page change notification")) + # 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, search], label=action_label) + + 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 = _("Perform a search") + + do_nothing = forms.Option("n", action1) + search = forms.Option("s", action2) + + form = forms.Form(xmlnode_or_type="form", title=form_title, instructions=instructions) + 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, search], 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 = _("View page info") + action3 = _("Perform a search") + + do_nothing = forms.Option("n", action1) + view_info = forms.Option("v", action2) + search = forms.Option("s", action3) + + form = forms.Form(xmlnode_or_type="form", title=form_title, instructions=instructions) + form.add_field(name='page_name', field_type='hidden', value=msg_data['page_name']) + 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, view_info, search], 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") + action4 = _("Perform a search") + + do_nothing = forms.Option("n", action1) + revert = forms.Option("r", action2) + view_info = forms.Option("v", action3) + search = forms.Option("s", action4) + + form = forms.Form(xmlnode_or_type="form", title=form_title, instructions=instructions) + form.add_field(name='revision', field_type='hidden', value=msg_data['revision']) + form.add_field(name='page_name', field_type='hidden', value=msg_data['page_name']) + 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, search], 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 handle_page_info(self, command): + """Handles GetPageInfo commands + + @param command: a command instance + @type command: jabberbot.commands.GetPageInfo + + """ + # Process command data first so it can be directly usable + if command.data['author'].startswith("Self:"): + command.data['author'] = command.data['author'][5:] + + datestr = str(command.data['lastModified']) + command.data['lastModified'] = u"%(year)s-%(month)s-%(day)s at %(time)s" % { + 'year': datestr[:4], + 'month': datestr[4:6], + 'day': datestr[6:8], + 'time': datestr[9:17], + } + + if command.presentation == u"text": + self.send_pageinfo_text(command) + elif command.presentation == u"dataforms": + self.send_pageinfo_form(command) + + else: + raise ValueError("presentation value '%s' is not supported!" % (command.presentation, )) + + def send_pageinfo_text(self, command): + """Sends detailed page info with plain text + + @param command: command with detailed data + @type command: jabberbot.command.GetPageInfo + + """ + _ = self.get_text(command.jid) + + intro = _("""Following detailed information on page "%(pagename)s" \ +is available:""") + + msg = _("""Last author: %(author)s +Last modification: %(modification)s +Current version: %(version)s""") % { + 'author': command.data['author'], + 'modification': command.data['lastModified'], + 'version': command.data['version'], + } + + self.send_message(command.jid, {'text': intro % {'pagename': command.pagename}}) + self.send_message(command.jid, {'text': msg}) + + def send_pageinfo_form(self, command): + """Sends page info using Data Forms + + + """ + _ = self.get_text(command.jid) + data = command.data + + form_title = _("Detailed page information").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 = _("Get page contents") + action3 = _("Get page contents (HTML)") + action4 = _("Perform a search") + + do_nothing = forms.Option("n", action1) + get_content = forms.Option("c", action2) + get_content_html = forms.Option("h", action3) + search = forms.Option("s", action4) + + form = forms.Form(xmlnode_or_type="form", title=form_title, instructions=instructions) + form.add_field(name='pagename', field_type='text-single', value=command.pagename, label=_("Page name")) + form.add_field(name="changed", field_type='text-single', value=data['lastModified'], label=_("Last changed")) + form.add_field(name='editor', field_type='text-single', value=data['author'], label=_("Last editor")) + form.add_field(name='version', field_type='text-single', value=data['version'], label=_("Current version")) + +# 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, get_content, get_content_html, search], label=action_label) + + self.send_form(command.jid, form, _("Detailed page information")) def is_internal(self, command): """Check if a given command is internal @@ -620,18 +1044,34 @@ if form.type != u"submit": return - try: + if "action" in form: action = form["action"].value - except KeyError: - data = {'text': _('The form you submitted was invalid!'), 'subject': _('Invalid data')} - self.send_message(jid.as_unicode(), data, u"message") - return + if action == u"search": + self.handle_search_form(jid, form) + else: + data = {'text': _('The form you submitted was invalid!'), 'subject': _('Invalid data')} + self.send_message(jid.as_unicode(), data, u"message") + elif "options" in form: + option = form["options"].value - if action == u"search": - self.handle_search_form(jid, form) - else: - data = {'text': _('The form you submitted was invalid!'), 'subject': _('Invalid data')} - self.send_message(jid.as_unicode(), data, u"message") + # View page info + if option == "v": + command = cmd.GetPageInfo(jid.as_unicode(), form["page_name"].value, presentation="dataforms") + self.from_commands.put_nowait(command) + + # Perform an another search + elif option == "s": + self.handle_internal_command(jid, ["searchform"]) + + # Revert a change + elif option == "r": + revision = int(form["revision"].value) + + # We can't really revert creation of a page, right? + if revision == 1: + return + + self.handle_xmlrpc_command(jid, ["revertpage", form["page_name"].value, "%d" % (revision - 1, )]) def handle_search_form(self, jid, form): @@ -671,7 +1111,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 +1155,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 +1229,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 +1265,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 +1311,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 +1323,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 +1382,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):