ext/blohg_tumblelog.py
author Rafael G. Martins <rafael@rafaelmartins.eng.br>
Sat, 11 May 2013 01:53:20 -0300
changeset 16 f602d09eb25a
parent 3 ab80ad36f498
permissions -rw-r--r--
fix typo
     1 # -*- coding: utf-8 -*-
     2 """
     3     blohg_tumblelog
     4     ~~~~~~~~~~~~~~~
     5 
     6     A blohg extension that adds some reStructuredText directives to allow the
     7     creation of tumblelogs using blohg.
     8 
     9     :copyright: (c) 2012 by Rafael Goncalves Martins
    10     :license: GPL-2, see LICENSE for more details.
    11 """
    12 
    13 from blohg.ext import BlohgExtension
    14 from blohg.rst.directives import SourceCode
    15 from blohg.rst.nodes import opengraph_image
    16 from bs4 import BeautifulSoup
    17 from contextlib import closing
    18 from docutils.parsers.rst import Directive, directives, nodes
    19 from docutils.parsers.rst.directives.body import BlockQuote
    20 from flask import current_app
    21 from oembed import OEmbedConsumer, OEmbedEndpoint, OEmbedError
    22 from urllib2 import urlopen
    23 
    24 ext = BlohgExtension(__name__)
    25 
    26 ## OEmbed providers
    27 providers = {
    28 
    29     # Youtube
    30     'http://www.youtube.com/oembed': [
    31         r'regex:https?://(www\.)?youtube\.com/watch.*',
    32         r'regex:http://youtu\.be/.*'],
    33 
    34     # Vimeo
    35     'http://vimeo.com/api/oembed.{format}': [
    36         r'regex:https?://(www\.)?vimeo\.com/.*'],
    37 
    38     # Rdio
    39     'http://www.rdio.com/api/oembed/': [
    40         r'regex:http://www\.rdio\.com/(artist|people)/.*',
    41         r'regex:http://rd\.io/.*'],
    42 
    43     # Flickr
    44     'http://www.flickr.com/services/oembed': [
    45         r'regex:https?://(www\.)?flickr\.com/.*'],
    46 
    47     # Instagram
    48     'http://api.instagram.com/oembed': [
    49         r'regex:http://instagr(\.am|am\.com)/p/.*'],
    50 
    51     # Imgur
    52     'http://api.imgur.com/oembed': [
    53         r'regex:http://(i\.)?imgur\.com/.*'],
    54 
    55     # Twitter
    56     'https://api.twitter.com/1/statuses/oembed.{format}': [
    57         r'regex:https?://(www\.)?twitter\.com/.+?/status(es)?/.*'],
    58 
    59     # DailyMotion
    60     'http://www.dailymotion.com/services/oembed': [
    61         r'regex:https?://(www\.)?dailymotion\.com/.*'],
    62 
    63     # SmugMug
    64     'http://api.smugmug.com/services/oembed/': [
    65         r'regex:https?://(.+\.)?smugmug\.com/.*'],
    66 
    67     # Hulu
    68     'http://www.hulu.com/api/oembed.{format}': [
    69         r'regex:https?://(www\.)?hulu\.com/watch/.*'],
    70 
    71     # Viddler
    72     'http://lab.viddler.com/services/oembed/': [
    73         r'regex:https?://(www\.)?viddler\.com/.*'],
    74 
    75     # Qik
    76     'http://qik.com/api/oembed.{format}': [
    77         r'regex:http://qik\.com/.*'],
    78 
    79     # Revision3
    80     'http://revision3.com/api/oembed/': [
    81         r'regex:http://revision3\.com/.*'],
    82 
    83     # Photobucket
    84     'http://photobucket.com/oembed': [
    85         r'regex:http://i.*\.photobucket\.com/albums/.*',
    86         r'regex:http://gi.*\.photobucket\.com/groups/.*'],
    87 
    88     # Scribd
    89     'http://www.scribd.com/services/oembed': [
    90         r'regex:https?://(www\.)?scribd\.com/.*'],
    91 
    92     # Wordpress.tv
    93     'http://wordpress.tv/oembed/': [
    94         r'regex:http://wordpress\.tv/.*'],
    95 
    96     # Polldaddy
    97     'http://polldaddy.com/oembed/': [
    98         r'regex:https?://(.+\.)?polldaddy\.com/.*'],
    99 
   100     # Funny or die
   101     'http://www.funnyordie.com/oembed': [
   102         r'regex:https?://(www\.)?funnyordie\.com/videos/.*'],
   103 
   104     # CollegeHumor
   105     'http://www.collegehumor.com/oembed.{format}': [
   106         r'regex:http://www\.collegehumor\.com/video/.*'],
   107 
   108     # Jest
   109     'http://www.jest.com/oembed.{format}': [
   110         r'regex:http://www\.jest\.com/(video|embed)/.*'],
   111 
   112     # Poll Everywhere
   113     'http://www.polleverywhere.com/services/oembed/': [
   114         r'regex:http://www\.polleverywhere\.com/(polls|multiple_choice_polls|'
   115         r'free_text_polls)/.*'],
   116 
   117     # SlideShare
   118     'http://www.slideshare.net/api/oembed/2': [
   119         r'regex:http://www\.slideshare\.net/[^/]+/.*'],
   120 
   121     # CircuitLab
   122     'https://www.circuitlab.com/circuit/oembed/': [
   123         r'regex:https://www\.circuitlab\.com/circuit/.*'],
   124 
   125 }
   126 
   127 ## Load OEmbed providers
   128 consumer = OEmbedConsumer()
   129 for endpoint, urls in providers.iteritems():
   130     consumer.addEndpoint(OEmbedEndpoint(endpoint, urls))
   131 
   132 
   133 def text_field(key, value):
   134     field_name = nodes.field_name(key, key)
   135     field_body_p = nodes.paragraph(value, value)
   136     field_body = nodes.field_body('', field_body_p)
   137     return nodes.field('', field_name, field_body)
   138 
   139 
   140 def reference_field(key, value, value_text=None):
   141     field_name = nodes.field_name(key, key)
   142     field_body_ref = nodes.reference(value, value_text or value, refuri=value)
   143     field_body_p = nodes.paragraph('', '', field_body_ref)
   144     field_body = nodes.field_body('', field_body_p)
   145     return nodes.field('', field_name, field_body)
   146 
   147 
   148 class LinkDirective(Directive):
   149 
   150     required_arguments = 1  # url
   151     option_spec = {'maxwidth': directives.nonnegative_int,
   152                    'maxheight': directives.nonnegative_int,
   153                    'hide-metadata': directives.flag}
   154 
   155     def run(self):
   156         if 'maxheight' not in self.options \
   157            and 'OEMBED_MAXHEIGHT' in current_app.config:
   158             self.options['maxheight'] = current_app.config['OEMBED_MAXHEIGHT']
   159         if 'maxwidth' not in self.options \
   160            and 'OEMBED_MAXWIDTH' in current_app.config:
   161             self.options['maxwidth'] = current_app.config['OEMBED_MAXWIDTH']
   162         try:
   163             response = consumer.embed(directives.uri(self.arguments[0]),
   164                                       **self.options)
   165             data = response.getData()
   166         except OEmbedError, err:
   167             try:
   168                 data = {'type': 'link'}
   169                 with closing(urlopen(directives.uri(self.arguments[0]))) as fp:
   170                     html = BeautifulSoup(fp.read())
   171                 try:
   172                     data['title'] = html.head.title.text
   173                 except:
   174                     pass
   175             except Exception, err:
   176                 raise self.error('Error in "%s" directive: %s' % (self.name,
   177                                                                   err))
   178         except Exception, err:
   179             raise self.error('Error in "%s" directive: %s' % (self.name, err))
   180 
   181         rv = []
   182 
   183         if data['type'] == 'photo':
   184             rv.append(nodes.raw('', '<div class="oembed oembed-photo">',
   185                                 format='html'))
   186             rv.append(nodes.image(data['title'] or '', uri=data['url'],
   187                                   alt=data['title'] or '',
   188                                   width=str(data['width']),
   189                                   height=str(data['height'])))
   190             rv.append(nodes.raw('', '</div>', format='html'))
   191         elif data['type'] == 'rich':
   192             rv.append(nodes.raw('', data['html'], format='html',
   193                                 classes=['oembed', 'oembed-rich']))
   194         elif data['type'] == 'video':
   195             rv.append(nodes.raw('', data['html'], format='html',
   196                                 classes=['oembed', 'oembed-video']))
   197         elif data['type'] == 'link':
   198             uri = directives.uri(self.arguments[0])
   199             label = nodes.strong('Link: ', 'Link: ')
   200             link = nodes.reference(uri, data.get('title', uri), refuri=uri)
   201             rv.append(nodes.paragraph('', '', label, link))
   202             return rv
   203 
   204         if 'hide-metadata' in self.options:
   205             return rv
   206 
   207         rvl = []
   208 
   209         # always use title, if possible.
   210         if 'title' in data and data['title']:
   211             rvl.append(text_field('Title', data['title']))
   212 
   213         # description isn't part of the spec. but should be used if provided.
   214         if 'description' in data:
   215             rvl.append(text_field('Description', data['description']))
   216 
   217         # always use provider and author, if present, using urls whenever
   218         # possible, to create links.
   219         if 'provider_name' in data and data['provider_name']:
   220             if 'provider_url' in data and data['provider_url']:
   221                 rvl.append(reference_field('Provider',
   222                                            data['provider_url'],
   223                                            data['provider_name']))
   224             else:
   225                 rvl.append(text_field('Content from', data['provider_name']))
   226         if 'author_name' in data and data['author_name']:
   227             if 'author_url' in data and data['author_url']:
   228                 rvl.append(reference_field('Author', data['author_url'],
   229                                            data['author_name']))
   230             else:
   231                 rvl.append(text_field('Author', data['author_name']))
   232 
   233         # flickr provides a license field
   234         if 'license' in data and data['license']:
   235             rvl.append(text_field('License', data['license']))
   236 
   237         rv.append(nodes.field_list('', *rvl))
   238 
   239         # if provided a thumbnail, use it for opengraph
   240         if 'thumbnail_url' in data:
   241             rv.append(opengraph_image('', uri=data['thumbnail_url']))
   242 
   243         return rv
   244 
   245 
   246 class QuoteDirective(BlockQuote):
   247 
   248     option_spec = {'author': directives.unchanged}
   249 
   250     def run(self):
   251         rv = BlockQuote.run(self)
   252         if 'author' in self.options:
   253             author = nodes.raw(self.options['author'],
   254                                u'<span class="author">— ' +
   255                                self.options['author'] + '</span>',
   256                                format='html')
   257             rv[-1].append(nodes.paragraph('', '', author))
   258             rv[-1].append(nodes.raw('', '<div class="clear"></div>',
   259                                     format='html'))
   260         return rv
   261 
   262 
   263 class ChatDirective(SourceCode):
   264 
   265     required_arguments = 0
   266     option_spec = {}
   267 
   268     def run(self):
   269         if 'linenos' in self.options:
   270             del self.options['linenos']
   271         self.arguments = ['irc']
   272         return SourceCode.run(self)
   273 
   274 
   275 @ext.setup_extension
   276 def setup_extension(app):
   277     directives.register_directive('link', LinkDirective)
   278     directives.register_directive('quote', QuoteDirective)
   279     directives.register_directive('chat', ChatDirective)