├── app.yaml ├── base.html ├── main.py └── opml.xml /app.yaml: -------------------------------------------------------------------------------- 1 | application: opml-generator 2 | version: 1 3 | runtime: python 4 | api_version: 1 5 | 6 | handlers: 7 | - url: /.* 8 | script: main.py 9 | -------------------------------------------------------------------------------- /base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | 6 |An OPML describes a set of RSS feeds, and is a nice way of sharing a 21 | set of feeds with the world. You can use this app to generate an OPML 22 | based on a public Google Spreadsheet. 23 |
For an example, see this published spreadsheet 38 | and the resulting OPML. 39 |
40 |Note: This is not a production-level service, 41 | it it just a tool I put together for use on my 42 | recipe blogs portal. 43 | You can deploy your own version of this or modify it for your needs 44 | by grabbing the source code from github. 45 |
46 | 47 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | """ 2 | Licensed under the Apache License, Version 2.0: 3 | http://www.apache.org/licenses/LICENSE-2.0 4 | """ 5 | 6 | import os 7 | import logging 8 | 9 | from google.appengine.ext import webapp 10 | from google.appengine.ext.webapp.util import run_wsgi_app 11 | from google.appengine.ext.webapp import template 12 | from google.appengine.api import memcache 13 | from google.appengine.api import urlfetch 14 | from django.utils import simplejson 15 | 16 | 17 | class BaseRequestHandler(webapp.RequestHandler): 18 | 19 | def get(self): 20 | page = memcache.get(self.get_cachename()) 21 | if os.environ['SERVER_SOFTWARE'].startswith('Dev'): 22 | page = None 23 | if not page: 24 | path = os.path.join(os.path.dirname(__file__), self.get_filename()) 25 | page = template.render(path, self.get_values()) 26 | memcache.set(self.get_cachename(), page, 60*1) 27 | self.response.headers['Content-Type'] = self.get_mimetype() 28 | self.response.out.write(page) 29 | 30 | def get_mimetype(self): 31 | return 'text/html' 32 | 33 | class HomePage(BaseRequestHandler): 34 | 35 | def get_filename(self): 36 | return 'base.html' 37 | 38 | def get_cachename(self): 39 | return 'index' 40 | 41 | def get_values(self): 42 | return {} 43 | 44 | class FeedOPML(BaseRequestHandler): 45 | 46 | def get_filename(self): 47 | return 'opml.xml' 48 | 49 | def get_cachename(self): 50 | return 'feedopml' + self.request.get('sskey') + self.request.get('wsid', 'od6') 51 | 52 | def get_values(self): 53 | title = self.request.get('title', 'Custom OPML') 54 | return {'feeds': self.get_worksheet_data(), 'title': title} 55 | 56 | def get_mimetype(self): 57 | return 'application/atom+xml' 58 | 59 | def get_worksheet_data(self): 60 | spreadsheet_key = self.request.get('sskey') 61 | worksheet_id = self.request.get('wsid', 'od6') 62 | url = 'https://spreadsheets.google.com/feeds/list/%s/%s/public/values?alt=json' % (spreadsheet_key, worksheet_id) 63 | result = urlfetch.fetch(url) 64 | rows = [] 65 | fields = ['title', 'url'] 66 | if result.status_code == 200: 67 | json = simplejson.loads(result.content) 68 | feed = json['feed'] 69 | entries = [] 70 | if 'entry' in feed: 71 | entries = feed['entry'] 72 | for entry in entries: 73 | row_info = {} 74 | dont_append = False 75 | for field in fields: 76 | if not entry['gsx$' + field] or len(entry['gsx$' + field]['$t']) == 0: 77 | dont_append = True 78 | row_info[field] = entry['gsx$' + field]['$t'] 79 | if not dont_append: 80 | rows.append(row_info) 81 | return rows 82 | 83 | 84 | application = webapp.WSGIApplication( 85 | [('/', HomePage), 86 | ('/opml', FeedOPML)], 87 | debug=True) 88 | 89 | def main(): 90 | run_wsgi_app(application) 91 | 92 | if __name__ == "__main__": 93 | main() 94 | -------------------------------------------------------------------------------- /opml.xml: -------------------------------------------------------------------------------- 1 | 2 |