view libervia_server/blog.py @ 411:a0256b81d367

setup.py: update website (it's not http://www.salut-a-toi.org), and removed pyjamas framework which is not standard in pypi
author Goffi <goffi@goffi.org>
date Tue, 18 Mar 2014 19:18:16 +0100
parents 35a43d0dc032
children bbdbee25123a
line wrap: on
line source

#!/usr/bin/python
# -*- coding: utf-8 -*-

# Libervia: a Salut à Toi frontend
# Copyright (C) 2011, 2012, 2013, 2014 Jérôme Poisson <goffi@goffi.org>

# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.

# This program 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 Affero General Public License for more details.

# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

from sat.core.i18n import _
from sat_frontends.tools.strings import addURLToText
from libervia_server.html_tools import sanitizeHtml
from twisted.internet import defer
from twisted.web import server
from twisted.web.resource import Resource
from twisted.words.protocols.jabber.jid import JID
from datetime import datetime
from constants import Const
import uuid
import re


class MicroBlog(Resource):
    isLeaf = True

    ERROR_TEMPLATE = """
                <html>
                <head profile="http://www.w3.org/2005/10/profile">
                    <link rel="icon" type="image/png" href="%(root)ssat_logo_16.png">
                    <title>MICROBLOG ERROR</title>
                </head>
                <body>
                    <h1 style='text-align: center; color: red;'>%(message)s</h1>
                </body>
                </html>
                """

    def __init__(self, host):
        self.host = host
        Resource.__init__(self)
        if not host.bridge.isConnected("libervia"):  # FIXME: hard coded value for test
            host.bridge.connect("libervia")

    def render_GET(self, request):
        if not request.postpath:
            return MicroBlog.ERROR_TEMPLATE % {'root': '',
                                               'message': "You must indicate a nickname"}
        else:
            prof_requested = request.postpath[0]
            #TODO: char check: only use alphanumerical chars + some extra(_,-,...) here
            prof_found = self.host.bridge.getProfileName(prof_requested)
            if not prof_found or prof_found == 'libervia':
                return MicroBlog.ERROR_TEMPLATE % {'root': '../' * len(request.postpath),
                                                    'message': "Invalid nickname"}
            else:
                def got_jid(pub_jid_s):
                    pub_jid = JID(pub_jid_s)
                    d2 = defer.Deferred()
                    item_id = None
                    if len(request.postpath) > 1:
                        if request.postpath[1] == 'atom.xml':  # return the atom feed
                            d2.addCallbacks(self.render_atom_feed, self.render_error_blog, [request], None, [request, prof_found], None)
                            self.host.bridge.getLastGroupBlogsAtom(pub_jid.userhost(), 10, 'libervia', d2.callback, d2.errback)
                            return
                        try:  # check if the given path is a valid UUID
                            uuid.UUID(request.postpath[1])
                            item_id = request.postpath[1]
                        except ValueError:
                            pass
                    d2.addCallbacks(self.render_html_blog, self.render_error_blog, [request, prof_found], None, [request, prof_found], None)
                    if item_id:  # display one message and its comments
                        self.host.bridge.getGroupBlogsWithComments(pub_jid.userhost(), [item_id], 'libervia', d2.callback, d2.errback)
                    else:  # display the last messages without comment
                        self.host.bridge.getLastGroupBlogs(pub_jid.userhost(), 10, 'libervia', d2.callback, d2.errback)

                d1 = defer.Deferred()
                JID(self.host.bridge.asyncGetParamA('JabberID', 'Connection', 'value', Const.SERVER_SECURITY_LIMIT, prof_found, callback=d1.callback, errback=d1.errback))
                d1.addCallbacks(got_jid)

                return server.NOT_DONE_YET

    def render_html_blog(self, mblog_data, request, profile):
        """Retrieve the blog banner or other user's specific stuff before actually rendering the static blog
        @param mblog_data: list of microblog data or list of couple (microblog data, list of microblog data)
        @param request: HTTP request
        @param profile
        """
        def check_banner(banner):
            """Regexp from http://answers.oreilly.com/topic/280-how-to-validate-urls-with-regular-expressions/"""
            if re.match(r"^(https?|ftp)://[a-z0-9-]+(\.[a-z0-9-]+)+(/[\w-]+)*/[\w-]+\.(gif|png|jpg)$", banner):
                style = {'banner': banner}
            else:
                style = {}
            self.__render_html_blog(mblog_data, style, request, profile)
        eb = lambda failure: self.render_error_blog(failure, request, profile)
        self.host.bridge.asyncGetParamA('Blog banner', 'Misc', 'value', Const.SERVER_SECURITY_LIMIT, profile, callback=check_banner, errback=eb)

    def __render_html_blog(self, mblog_data, style, request, profile):
        """Actually render the static blog. If mblog_data is a list of dict, we are missing
        the comments items so we just display the main items. If mblog_data is a list of couple,
        each couple is associating a main item data with the list of its comments, so we render all.
        @param mblog_data: list of microblog data or list of couple (microblog data, list of microblog data)
        @param style: dict defining the blog's rendering parameters
        @param request: the HTTP request
        @profile
        """
        if not isinstance(style, dict):
            style = {}
        user = sanitizeHtml(profile).encode('utf-8')
        root_url = '../' * len(request.postpath)
        base_url = root_url + 'blog/' + user
        banner = style['banner'].encode('utf-8') if 'banner' in style else ''
        banner_elt = "<img src='%(banner)s' alt='%(user)s'/>" % {'user': user, 'banner': banner} if banner else user
        request.write("""
            <html>
            <head>
                <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
                <link rel="alternate" type="application/atom+xml" href="%(base)s/atom.xml"/>
                <link rel="stylesheet" type="text/css" href="%(root)scss/blog.css" />
                <link rel="icon" type="image/png" href="%(root)ssat_logo_16.png">
                <title>%(user)s's microblog</title>
            </head>
            <body>
            <div class="mblog_title"><a href="%(base)s">%(banner_elt)s</a></div>
            """ % {'base': base_url,
                   'root': root_url,
                   'user': user,
                   'banner_elt': banner_elt})
        mblog_data = [(entry if isinstance(entry, tuple) else (entry, [])) for entry in mblog_data]
        mblog_data = sorted(mblog_data, key=lambda entry: (-float(entry[0].get('published', 0))))
        for entry in mblog_data:
            self.__render_html_entry(entry[0], base_url, request)
            comments = sorted(entry[1], key=lambda entry: (float(entry.get('published', 0))))
            for comment in comments:
                self.__render_html_entry(comment, base_url, request)
        request.write('</body></html>')
        request.finish()

    def __render_html_entry(self, entry, base_url, request):
        """Render one microblog entry.
        @param entry: the microblog entry
        @param base_url: the base url of the blog
        @param request: the HTTP request
        """
        timestamp = float(entry.get('published', 0))
        datetime_ = datetime.fromtimestamp(timestamp)
        is_comment = entry['type'] == 'comment'
        if is_comment:
            author = (_("comment from %s") % entry['author']).encode('utf-8')
            item_link = ''
        else:
            author = '&nbsp;'
            item_link = ("%(base)s/%(item_id)s" % {'base': base_url, 'item_id': entry['id']}).encode('utf-8')

        def getText(key):
            if ('%s_xhtml' % key) in entry:
                return entry['%s_xhtml' % key].encode('utf-8')
            elif key in entry:
                processor = addURLToText if key.startswith('content') else sanitizeHtml
                return processor(entry[key]).encode('utf-8')
            return ''

        def addMainItemLink(elem):
            if not item_link or not elem:
                return elem
            return """<a href="%(link)s" class="item_link">%(elem)s</a>""" % {'link': item_link, 'elem': elem}

        header = addMainItemLink("""<div class="mblog_header">
                                      <div class="mblog_metadata">
                                        <div class="mblog_author">%(author)s</div>
                                        <div class="mblog_timestamp">%(date)s</div>
                                      </div>
                                    </div>""" % {'author': author, 'date': datetime_})

        title = addMainItemLink(getText('title'))
        body = getText('content')
        if title:  # insert the title within the body
            body = """<h1>%(title)s</h1>\n%(body)s""" % {'title': title, 'body': body}

        request.write("""<div class="mblog_entry %(extra_style)s">
                           %(header)s
                           <span class="mblog_content">%(content)s</span>
                         </div>""" %
                         {'extra_style': 'mblog_comment' if entry['type'] == 'comment' else '',
                          'item_link': item_link,
                          'header': header,
                          'content': body})

    def render_atom_feed(self, feed, request):
        request.write(feed.encode('utf-8'))
        request.finish()

    def render_error_blog(self, error, request, profile):
        request.write(MicroBlog.ERROR_TEMPLATE % {'root': '../' * len(request.postpath),
                                                  'message': "Can't access requested data"})
        request.finish()