1 # -*- coding: utf-8 -*-
6 A blohg extension that adds some reStructuredText directives to allow the
7 creation of tumblelogs using blohg.
9 :copyright: (c) 2012 by Rafael Goncalves Martins
10 :license: GPL-2, see LICENSE for more details.
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
24 ext = BlohgExtension(__name__)
30 'http://www.youtube.com/oembed': [
31 r'regex:https?://(www\.)?youtube\.com/watch.*',
32 r'regex:http://youtu\.be/.*'],
35 'http://vimeo.com/api/oembed.{format}': [
36 r'regex:https?://(www\.)?vimeo\.com/.*'],
39 'http://www.rdio.com/api/oembed/': [
40 r'regex:http://www\.rdio\.com/(artist|people)/.*',
41 r'regex:http://rd\.io/.*'],
44 'http://www.flickr.com/services/oembed': [
45 r'regex:https?://(www\.)?flickr\.com/.*'],
48 'http://api.instagram.com/oembed': [
49 r'regex:http://instagr(\.am|am\.com)/p/.*'],
52 'http://api.imgur.com/oembed': [
53 r'regex:http://(i\.)?imgur\.com/.*'],
56 'https://api.twitter.com/1/statuses/oembed.{format}': [
57 r'regex:https?://(www\.)?twitter\.com/.+?/status(es)?/.*'],
60 'http://www.dailymotion.com/services/oembed': [
61 r'regex:https?://(www\.)?dailymotion\.com/.*'],
64 'http://api.smugmug.com/services/oembed/': [
65 r'regex:https?://(.+\.)?smugmug\.com/.*'],
68 'http://www.hulu.com/api/oembed.{format}': [
69 r'regex:https?://(www\.)?hulu\.com/watch/.*'],
72 'http://lab.viddler.com/services/oembed/': [
73 r'regex:https?://(www\.)?viddler\.com/.*'],
76 'http://qik.com/api/oembed.{format}': [
77 r'regex:http://qik\.com/.*'],
80 'http://revision3.com/api/oembed/': [
81 r'regex:http://revision3\.com/.*'],
84 'http://photobucket.com/oembed': [
85 r'regex:http://i.*\.photobucket\.com/albums/.*',
86 r'regex:http://gi.*\.photobucket\.com/groups/.*'],
89 'http://www.scribd.com/services/oembed': [
90 r'regex:https?://(www\.)?scribd\.com/.*'],
93 'http://wordpress.tv/oembed/': [
94 r'regex:http://wordpress\.tv/.*'],
97 'http://polldaddy.com/oembed/': [
98 r'regex:https?://(.+\.)?polldaddy\.com/.*'],
101 'http://www.funnyordie.com/oembed': [
102 r'regex:https?://(www\.)?funnyordie\.com/videos/.*'],
105 'http://www.collegehumor.com/oembed.{format}': [
106 r'regex:http://www\.collegehumor\.com/video/.*'],
109 'http://www.jest.com/oembed.{format}': [
110 r'regex:http://www\.jest\.com/(video|embed)/.*'],
113 'http://www.polleverywhere.com/services/oembed/': [
114 r'regex:http://www\.polleverywhere\.com/(polls|multiple_choice_polls|'
115 r'free_text_polls)/.*'],
118 'http://www.slideshare.net/api/oembed/2': [
119 r'regex:http://www\.slideshare\.net/[^/]+/.*'],
122 'https://www.circuitlab.com/circuit/oembed/': [
123 r'regex:https://www\.circuitlab\.com/circuit/.*'],
127 ## Load OEmbed providers
128 consumer = OEmbedConsumer()
129 for endpoint, urls in providers.iteritems():
130 consumer.addEndpoint(OEmbedEndpoint(endpoint, urls))
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)
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)
148 class LinkDirective(Directive):
150 required_arguments = 1 # url
151 option_spec = {'maxwidth': directives.nonnegative_int,
152 'maxheight': directives.nonnegative_int,
153 'hide-metadata': directives.flag}
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']
163 response = consumer.embed(directives.uri(self.arguments[0]),
165 data = response.getData()
166 except OEmbedError, err:
168 data = {'type': 'link'}
169 with closing(urlopen(directives.uri(self.arguments[0]))) as fp:
170 html = BeautifulSoup(fp.read())
172 data['title'] = html.head.title.text
175 except Exception, err:
176 raise self.error('Error in "%s" directive: %s' % (self.name,
178 except Exception, err:
179 raise self.error('Error in "%s" directive: %s' % (self.name, err))
183 if data['type'] == 'photo':
184 rv.append(nodes.raw('', '<div class="oembed oembed-photo">',
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))
204 if 'hide-metadata' in self.options:
209 # always use title, if possible.
210 if 'title' in data and data['title']:
211 rvl.append(text_field('Title', data['title']))
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']))
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']))
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']))
231 rvl.append(text_field('Author', data['author_name']))
233 # flickr provides a license field
234 if 'license' in data and data['license']:
235 rvl.append(text_field('License', data['license']))
237 rv.append(nodes.field_list('', *rvl))
239 # if provided a thumbnail, use it for opengraph
240 if 'thumbnail_url' in data:
241 rv.append(opengraph_image('', uri=data['thumbnail_url']))
246 class QuoteDirective(BlockQuote):
248 option_spec = {'author': directives.unchanged}
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>',
257 rv[-1].append(nodes.paragraph('', '', author))
258 rv[-1].append(nodes.raw('', '<div class="clear"></div>',
263 class ChatDirective(SourceCode):
265 required_arguments = 0
269 if 'linenos' in self.options:
270 del self.options['linenos']
271 self.arguments = ['irc']
272 return SourceCode.run(self)
276 def setup_extension(app):
277 directives.register_directive('link', LinkDirective)
278 directives.register_directive('quote', QuoteDirective)
279 directives.register_directive('chat', ChatDirective)