use 'latest' library versions for maximum lifespan as an example
[gae-samples.git] / gdata_feedfetcher / feedfetcher.py
blob58761c5c5cfa7d21f0eb67ad61564ec3f4d9beec
1 # Copyright (C) 2008 Google Inc.
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
7 # http://www.apache.org/licenses/LICENSE-2.0
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
16 __author__ = 'api.jscudder (Jeffrey Scudder)'
19 import cgi
20 import wsgiref.handlers
21 from google.appengine.api import users
22 from google.appengine.ext import webapp
23 from google.appengine.ext import db
24 from google.appengine.api import urlfetch
25 import urllib # Used to unescape URL parameters.
26 import gdata.service
27 import gdata.alt.appengine
28 import gdata.auth
29 import atom
30 import atom.http_interface
31 import atom.token_store
32 import atom.url
33 import settings
36 class Fetcher(webapp.RequestHandler):
38 def get(self):
39 self.response.headers['Content-Type'] = 'text/html'
41 self.response.out.write("""<!DOCTYPE html><html><head>
42 <title>Google Data Feed Fetcher: read Google Data API Atom feeds
43 </title>
44 <link rel="stylesheet" type="text/css"
45 href="static/feedfetcher.css"/>
46 </head><body>""")
48 self.response.out.write("""<div id="nav"><a href="/">Home</a>""")
49 if users.get_current_user():
50 self.response.out.write('<a href="%s">Sign Out</a>' % (
51 users.create_logout_url('http://%s/' % settings.HOST_NAME)))
52 else:
53 self.response.out.write('<a href="%s">Sign In</a>' % (
54 users.create_login_url('http://%s/' % settings.HOST_NAME)))
55 self.response.out.write('</div>')
57 # Initialize a client to talk to Google Data API services.
58 client = gdata.service.GDataService()
59 gdata.alt.appengine.run_on_appengine(client)
61 session_token = None
62 # Find the AuthSub token and upgrade it to a session token.
63 auth_token = gdata.auth.extract_auth_sub_token_from_url(self.request.uri)
64 if auth_token:
65 # Upgrade the single-use AuthSub token to a multi-use session token.
66 session_token = client.upgrade_to_session_token(auth_token)
67 if session_token and users.get_current_user():
68 # If there is a current user, store the token in the datastore and
69 # associate it with the current user. Since we told the client to
70 # run_on_appengine, the add_token call will automatically store the
71 # session token if there is a current_user.
72 client.token_store.add_token(session_token)
73 elif session_token:
74 # Since there is no current user, we will put the session token
75 # in a property of the client. We will not store the token in the
76 # datastore, since we wouldn't know which user it belongs to.
77 # Since a new client object is created with each get call, we don't
78 # need to worry about the anonymous token being used by other users.
79 client.current_token = session_token
81 # Get the URL for the desired feed and get the display option.
82 feed_url = self.request.get('feed_url')
83 erase_tokens = self.request.get('erase_tokens')
84 if erase_tokens:
85 self.EraseStoredTokens()
86 show_xml = self.request.get('xml')
88 if show_xml:
89 checked_string = 'checked'
90 else:
91 checked_string = ''
93 self.response.out.write("""<div id="wrap"><div id="header">
94 <h1>Google Data Feed Fetcher</h1>
95 <form action="/" method="get">
96 <label id="feed_url_label" for="feed_url">Target URL:</label>
97 <input type="text" size="60" name="feed_url" id="feed_url"
98 value="%s"></input>
99 <input type="submit" value="Fetch Atom"></input>
100 <label for="xml">Show XML:</label>
101 <input type="checkbox" id="xml" name="xml" value="true" %s></input>
102 </form></div>""" % ((feed_url or ''), checked_string))
104 self.response.out.write('<div id="main">')
105 if not feed_url:
106 self.ShowInstructions()
107 else:
108 self.FetchFeed(client, feed_url, show_xml)
109 self.response.out.write('</div>')
111 if users.get_current_user():
112 self.response.out.write("""<div id="sidebar"><div id="scopes">
113 <h4>Request a token for some common scopes</h4><ul>
114 <li><a href="%s">Blogger</a></li>
115 <li><a href="%s">Calendar</a></li>
116 <li><a href="%s">Google Documents</a></li>
117 </ul></div><div id="tokens">""" % (
118 self.GenerateScopeRequestLink(client,
119 'http://www.blogger.com/feeds/'),
120 self.GenerateScopeRequestLink(client,
121 'http://www.google.com/calendar/feeds'),
122 self.GenerateScopeRequestLink(client,
123 'http://docs.google.com/feeds/')))
125 self.DisplayAuthorizedUrls()
126 self.response.out.write('</div>')
127 self.response.out.write('</div></div></body></html>')
129 def GenerateScopeRequestLink(self, client, scope):
130 return client.GenerateAuthSubURL('http://%s/' % (
131 settings.HOST_NAME,),
132 scope, secure=False, session=True)
134 def ShowInstructions(self):
135 self.response.out.write("""<p>This sample application illustrates the
136 use of <a
137 href="http://code.google.com/apis/accounts/docs/AuthForWebApps.html">
138 AuthSub authentication</a> to access
139 <a href="http://code.google.com/apis/gdata/">Google Data feeds</a>.</p>
141 <p>To start using this sample, you can enter the URL for an Atom feed
142 or entry in the input box above, or you can click on one of the
143 example feeds below. Some of these feeds require you to authorize this
144 app to have access. To avoid authorizing over and over, sign in to
145 this application above and previous authorization tokens will be stored
146 for future use.</p>
148 <h4>Sample Feed URLs</h4>
149 <h5>Publicly viewable feeds</h5>
150 <ul><li><a href="%s"
151 >Recent posts in the Google App Engine blog</a></li>
152 <li><a href="%s"
153 >Recent posts in the Google Data APIs blog</a></li>
154 <li><a href="%s"
155 >See events on the Google Developer Events calendar</a></li></ul>
156 <h5>Feeds which require authorization</h5>
157 <ul><li><a href="%s"
158 >List your Google Documents</a></li>
159 <li><a href="%s"
160 >List the Google Calendars that you own</a></li>
161 <li><a href="%s"
162 >List your Google Calendars</a></li>
163 <li><a href="%s"
164 >List your blogs on Blogger</a></li>
165 <!--<li><a href="%s"
166 >List your Gmail Contacts</a></li>--></ul>
168 <p>To learn more about how this sample works, read the article on
169 <a href="http://code.google.com/appengine/articles/gdata.html"
170 >Retrieving Authenticated Google Data Feeds with
171 Google App Engine</a>.</p>
173 <p>In addition to reading information in Google Data feeds,
174 it is also possible to write to some of the Google Data services
175 once the user has granted permission to your app. For more details
176 on the capabilities of the different Google Data service see the
177 <a href="http://code.google.com/apis/gdata/">home page</a> for
178 Google Data APIs.</p>
180 <p>This app uses the <a
181 href="http://code.google.com/p/gdata-python-client/"
182 >gdata-python-client</a> library.</p>
183 """ % (
184 self.GenerateFeedRequestLink(
185 'http://www.blogger.com/feeds/8501956666581132164/posts/default'),
186 self.GenerateFeedRequestLink(
187 'http://googledataapis.blogspot.com/feeds/posts/default'),
188 self.GenerateFeedRequestLink(
189 'http://www.google.com/calendar/feeds/developer-calendar@google.com/public/basic'),
190 self.GenerateFeedRequestLink(
191 'http://docs.google.com/feeds/documents/private/full'),
192 self.GenerateFeedRequestLink(
193 'http://www.google.com/calendar/feeds/default/owncalendars/full'),
194 self.GenerateFeedRequestLink(
195 'http://www.google.com/calendar/feeds/default/allcalendars/full'),
196 self.GenerateFeedRequestLink(
197 'http://www.blogger.com/feeds/default/blogs'),
198 self.GenerateFeedRequestLink(
199 'http://www.google.com/m8/feeds/contacts/default/base')))
201 def GenerateFeedRequestLink(self, feed_url):
202 return atom.url.Url('http', settings.HOST_NAME, path='/',
203 params={'feed_url':feed_url}).to_string()
205 def FetchFeed(self, client, feed_url, show_xml=False):
206 # Attempt to fetch the feed.
207 try:
208 if show_xml:
209 response = client.Get(feed_url, converter=str)
210 response = response.decode('UTF-8')
211 self.response.out.write(cgi.escape(response))
212 else:
213 response = client.Get(feed_url)
214 if isinstance(response, atom.Feed):
215 self.RenderFeed(response)
216 elif isinstance(response, atom.Entry):
217 self.RenderEntry(response)
218 else:
219 self.response.out.write(cgi.escape(response.read()))
220 except gdata.service.RequestError, request_error:
221 # If fetching fails, then tell the user that they need to login to
222 # authorize this app by logging in at the following URL.
223 if request_error[0]['status'] == 401:
224 # Get the URL of the current page so that our AuthSub request will
225 # send the user back to here.
226 next = self.request.uri
227 auth_sub_url = client.GenerateAuthSubURL(next, feed_url,
228 secure=False, session=True)
229 self.response.out.write('<a href="%s">' % (auth_sub_url))
230 self.response.out.write(
231 'Click here to authorize this application to view the feed</a>')
232 else:
233 self.response.out.write(
234 'Something else went wrong, here is the error object: %s ' % (
235 str(request_error[0])))
237 def RenderFeed(self, feed):
238 self.response.out.write('<h2>Feed Title: %s</h2>' % (
239 feed.title.text.decode('UTF-8')))
240 for link in feed.link:
241 self.RenderLink(link)
242 for entry in feed.entry:
243 self.RenderEntry(entry)
245 def RenderEntry(self, entry):
246 self.response.out.write('<h3>Entry Title: %s</h3>' % (
247 entry.title.text.decode('UTF-8')))
248 if entry.content and entry.content.text:
249 self.response.out.write('<p>Content: %s</p>' % (
250 entry.content.text.decode('UTF-8')))
251 elif entry.summary and entry.summary.text:
252 self.response.out.write('<p>Summary: %s</p>' % (
253 entry.summary.text.decode('UTF-8')))
254 for link in entry.link:
255 self.RenderLink(link)
257 def RenderLink(self, link):
258 if link.rel == 'alternate' and link.type == 'text/html':
259 self.response.out.write(
260 'Link: <a href="%s">alternate HTML</a><br/>' % link.href)
261 elif link.type == 'application/atom+xml':
262 self.response.out.write(
263 'Link: <a href="/?feed_url=%s">Fetch %s link (%s)</a><br/>' % (
264 urllib.quote_plus(link.href), link.rel, link.type))
265 else:
266 self.response.out.write(
267 'Link: <a href="%s">%s link (%s)</a><br/>' % (link.href, link.rel,
268 link.type))
270 def DisplayAuthorizedUrls(self):
271 self.response.out.write('<h4>Stored Authorization Tokens</h4><ul>')
272 tokens = gdata.alt.appengine.load_auth_tokens()
273 for token_scope in tokens:
274 self.response.out.write('<li><a href="/?feed_url=%s">%s*</a></li>' % (
275 urllib.quote_plus(str(token_scope)), str(token_scope)))
276 self.response.out.write(
277 '</ul>To erase your stored tokens, <a href="%s">click here</a>' % (
278 atom.url.Url('http', settings.HOST_NAME, path='/',
279 params={'erase_tokens':'true'}).to_string()))
281 def EraseStoredTokens(self):
282 gdata.alt.appengine.save_auth_tokens({})
285 class Acker(webapp.RequestHandler):
286 """Simulates an HTML page to prove ownership of this domain for AuthSub
287 registration."""
289 def get(self):
290 self.response.headers['Content-Type'] = 'text/plain'
291 self.response.out.write('This file present for AuthSub registration.')
294 def main():
295 application = webapp.WSGIApplication([('/', Fetcher),
296 ('/google72db3d6838b4c438.html', Acker)],
297 debug=True)
298 wsgiref.handlers.CGIHandler().run(application)
301 if __name__ == '__main__':
302 main()