+2008-10-17 Christopher Blizzard <blizzard@0xdeadbeef.com>
+
+ * whoisi/utils/sites.py (site_value): Add youtube to the ordering
+ of sites in a profile.
+
+ * whoisi/utils/youtube.py (youtube_get_user): Returns a user for a
+ standard videos.rss-style youtube feed.
+
+ * whoisi/templates/unseen.mak: Add support for youtube.
+
+ * whoisi/templates/follow.mak: Add support for youtube.
+
+ * whoisi/templates/person-widget.mak: Add support for youtube.
+
+ * whoisi/templates/picasa-widget.mak: Support the thumb/JSON
+ format in display_cache.
+
+ * whoisi/templates/youtube-widget.mak (else): Widget for
+ displaying youtube.
+
+ * whoisi/controllers.py (Root.getDisplayDepth): Add support for
+ youtube.
+ (Root.rendersite): Add support for youtube.
+
+ * whoisi/static/images/sites/youtube-favicon.png: Icon for youtube.
+
+ * utils/convert-display-cache.py: Convert display cache for
+ picasa, not flickr.
+
+ * utils/convert-flickr-feeds.py: Script that converts flickr feeds
+ from atom to rss2 in the db.
+
+ * feed-parse-service (FeedParseProtocol.runCommand): Set the thumb
+ property in the display_cache if media_thumbnail is set.
+
+ * lib/feedparser.py: Patch to detect thumbnails.
+
+ * tests/nose/test_newsite.py (TestNewSite.test_youtube): New tests
+ for detecting youtube urls. Also somewhat future-proofed for
+ eventual user detection.
+
+ * smoketest.txt: Test a youtube url.
+
+ * patches/README: Readme for new thumbnail patch.
+
+ * patches/feedparser-thumbnail.patch: Patch that adds support for
+ the media:thumbnail property to feedparser. Taken from an
+ upstream bug.
+
+ * services/command/controller.py (PreviewSiteManager.__init__):
+ Don't call FlickrPreviewThumbnails anymore - we get it directly
+ from the feed now.
+
+ * services/command/newsite.py (NewSiteTryURL.getFeedType): If it's
+ a youtube url, set the type.
+
+ * services/command/flickr.py (Flickr.getPreferredFeed): We now use
+ the rss2 feed instead of the atom feed - it contains a thumbnail
+ url.
+
+ * services/command/picasa.py (Picasa.photoFeedForUser): Make
+ picasa work like flickr - set a "thumb" object as a JSON object in
+ the database row instead of just a raw url.
+
+ * services/command/youtube.py (Youtube): Class that supports the
+ current site model for detecting youtube feeds. It's also
+ future-proofed to support urls and usernames at some point.
+
+ * services/master/database.py (DatabaseManager.__init__): Disable
+ flickr scan on startup. New support for images doesn't require us
+ to do it anymore. Yay!
+
+ * services/master/newsite.py (NewSite.normalize): Placeholder for
+ eventual normalization for youtube. Doesn't do anything right
+ now.
+
2008-10-07 Christopher Blizzard <blizzard@0xdeadbeef.com>
* services/command/controller.py (ProtoManager.start): Remove dead
le["updated"] = self.parsedTimeToSeconds(e, "updated_parsed")
le["summary"] = e.get("summary", None)
le["content"] = e.get("content", None)
- le["display_cache"] = None
+
+ display_cache = {}
+
+ try:
+ display_cache["thumb"] = e["media_thumbnail"][0]["url"]
+ except:
+ pass
+
+ if len(display_cache.keys()):
+ le["display_cache"] = simplejson.dumps(display_cache)
+
data["entries"].append(le)
tmpfd.write(simplejson.dumps(data))
'http://www.itunes.com/DTDs/PodCast-1.0.dtd': 'itunes',
'http://example.com/DTDs/PodCast-1.0.dtd': 'itunes',
'http://purl.org/rss/1.0/modules/link/': 'l',
- 'http://search.yahoo.com/mrss': 'media',
+ 'http://search.yahoo.com/mrss/': 'media',
'http://madskills.com/public/xml/rss/module/pingback/': 'pingback',
'http://prismstandard.org/namespaces/1.2/basic/': 'prism',
'http://www.w3.org/1999/02/22-rdf-syntax-ns#': 'rdf',
value = self.pop('itunes_explicit', 0)
self._getContext()['itunes_explicit'] = (value == 'yes') and 1 or 0
+ def _start_media_content(self, attrsD):
+ context = self._getContext()
+ context.setdefault('media_content', [])
+ context['media_content'].append(attrsD)
+
+ def _start_media_thumbnail(self, attrsD):
+ context = self._getContext()
+ context.setdefault('media_thumbnail', [])
+ self.push('url', 1) # new
+ context['media_thumbnail'].append(attrsD)
+
+ def _end_media_thumbnail(self):
+ url = self.pop('url')
+ context = self._getContext()
+ if url != None and len(url.strip()) != 0:
+ if not context['media_thumbnail'][-1].has_key('url'):
+ context['media_thumbnail'][-1]['url'] = url
+
if _XML_AVAILABLE:
class _StrictFeedParser(_FeedParserMixin, xml.sax.handler.ContentHandler):
def __init__(self, baseuri, baselang, encoding):
http://code.google.com/p/feedparser/issues/detail?id=18
+o feedparser-thumbnail.patch
+
+Fix so we can get feeds that contains <media:thumbnail>
+
+http://code.google.com/p/feedparser/issues/detail?id=100
\ No newline at end of file
--- /dev/null
+Index: feedparser.py
+===================================================================
+--- feedparser.py (revision 4)
++++ feedparser.py (working copy)
+@@ -323,7 +323,7 @@
+ 'http://www.itunes.com/DTDs/PodCast-1.0.dtd': 'itunes',
+ 'http://example.com/DTDs/PodCast-1.0.dtd': 'itunes',
+ 'http://purl.org/rss/1.0/modules/link/': 'l',
+- 'http://search.yahoo.com/mrss': 'media',
++ 'http://search.yahoo.com/mrss/': 'media',
+ 'http://madskills.com/public/xml/rss/module/pingback/': 'pingback',
+ 'http://prismstandard.org/namespaces/1.2/basic/': 'prism',
+ 'http://www.w3.org/1999/02/22-rdf-syntax-ns#': 'rdf',
+@@ -1345,6 +1345,24 @@
+ value = self.pop('itunes_explicit', 0)
+ self._getContext()['itunes_explicit'] = (value == 'yes') and 1 or 0
+
++ def _start_media_content(self, attrsD):
++ context = self._getContext()
++ context.setdefault('media_content', [])
++ context['media_content'].append(attrsD)
++
++ def _start_media_thumbnail(self, attrsD):
++ context = self._getContext()
++ context.setdefault('media_thumbnail', [])
++ self.push('url', 1) # new
++ context['media_thumbnail'].append(attrsD)
++
++ def _end_media_thumbnail(self):
++ url = self.pop('url')
++ context = self._getContext()
++ if url != None and len(url.strip()) != 0:
++ if not context['media_thumbnail'][-1].has_key('url'):
++ context['media_thumbnail'][-1]['url'] = url
++
+ if _XML_AVAILABLE:
+ class _StrictFeedParser(_FeedParserMixin, xml.sax.handler.ContentHandler):
+ def __init__(self, baseuri, baselang, encoding):
CommandManager.__init__(self)
self.commands = [ NewSiteSetup(dcm), # set up the url for the preview (works for new vs. preview)
NewSiteTryURL(sm, dcm), # test the url, download and parse it
- FlickrPreviewThumbnails(dcm), # cache any flickr thumbnails
PreviewSiteDone(dcm) # we're done, update the database
]
error_c = NewSiteError(dcm) # not a typo - turns out the code in there will work for this one, too
def getPreferredFeed(self, feeds):
for i in range(0, len(feeds)):
- if feeds[i][1] == u'application/atom+xml' and re.search('format=atom', feeds[i][0]):
+ if feeds[i][1] == u'application/rss+xml' and re.search('format=rss_200', feeds[i][0]):
print(" Found preferred feed at %s" % feeds[i][0])
return feeds[i]
from services.command.flickr import Flickr
from services.command.identica import Identica
from services.command.delicious import Delicious
+from services.command.youtube import Youtube
from services.command.exceptions import PageNotFoundError, FeedNotFoundError, InvalidFeedError, \
NeedsFeedPickError
from services.command.utils import resolve_relative_url
if t.isIdentica(url):
return "identica"
+ # Check to see if it's a delicious url
t = Delicious()
if t.isDelicious(url):
return "delicious"
+ # Check to see if it's a youtube url
+ t = Youtube()
+ if t.isYoutube(url):
+ return "youtube"
+
return "feed"
le["summary"] = i.summary.text
le["content"] = i.content.text
- le["display_cache"] = i.media.thumbnail[0].url
+ le["display_cache"] = simplejson.dumps({"thumb": i.media.thumbnail[0].url})
data["entries"].append(le)
--- /dev/null
+import urlparse
+import cgi
+import re
+
+class Youtube:
+
+ def isYoutube(self, url):
+ """
+ This is the much shorter version that just looks at a feed and
+ can tell you if it's a youtube url. Use getYoutubeUser for a
+ better version.
+ """
+ u = urlparse.urlparse(url)
+ urlparse.clear_cache()
+ host = u[1]
+ path = u[2]
+ query = u[4]
+
+ try:
+ if not (host == 'youtube.com' or (re.match('[a-zA-Z]+.youtube.com', host) != None)):
+ return False
+
+ # http://www.youtube.com/ut_rss?type=username&arg=christopherblizzard
+ match = re.match('/ut_rss', path)
+ if match:
+ qs = cgi.parse_qs(query)
+ if qs["type"][0] == "username" and qs["arg"][0] != None:
+ return True
+
+ # http://www.youtube.com/rss/user/christopherblizzard/videos.rss
+ match = re.match('/rss/user/([^/]+)/videos.rss', path)
+ if match:
+ return True
+ except:
+ pass
+
+ return False
+
+ def getYoutubeUser(self, url):
+ # youtube user forms
+ # http://www.youtube.com/user/christopherblizzard
+ # http://www.youtube.com/rss/user/christopherblizzard/videos.rss
+ # http://www.youtube.com/ut_rss?type=username&arg=christopherblizzard
+ # http://www.youtube.com/([a-z_]+)?user=christopherblizzard
+ # http://www.youtube.com/christopherblizzard
+ u = urlparse.urlparse(url)
+ urlparse.clear_cache()
+ host = u[1]
+ path = u[2]
+ query = u[4]
+
+ if not (host == 'youtube.com' or (re.match('[a-zA-Z]+.youtube.com', host) != None)):
+ return None
+
+ try:
+ # http://www.youtube.com/user/christopherblizzard
+ match = re.match('^/user/([^/]+)$', path)
+ if match:
+ return match.group(1)
+
+ # http://www.youtube.com/rss/user/christopherblizzard/videos.rss
+ match = re.match('/rss/user/([^/]+)/videos.rss', path)
+ if match:
+ return match.group(1)
+
+ # http://www.youtube.com/ut_rss?type=username&arg=christopherblizzard
+ match = re.match('/ut_rss', path)
+ if match:
+ qs = cgi.parse_qs(query)
+ if qs["type"][0] == "username" and qs["arg"][0] != None:
+ return qs["arg"][0]
+
+ # http://www.youtube.com/([a-z_]+)?user=christopherblizzard
+ match = re.match('^/([a-z_]+)$', path)
+ if match:
+ qs = cgi.parse_qs(query)
+ try:
+ if qs["user"][0] != None:
+ return qs["user"][0]
+ except:
+ pass
+
+ # http://www.youtube.com/christopherblizzard
+ # the crappy fallback
+ print path
+ match = re.match('^/([a-zA-Z0-9]+)$', path)
+ if match:
+ return match.group(1)
+
+ except:
+ pass
+
+ return None
+
+ def feedForUser(self, username):
+ return str('http://www.youtube.com/ut_rss?type=username&arg=' + username)
+
+ def homeForUser(self, username):
+ return str('http://www.youtube.com/user/' + username)
self.refresh_in_progress = False
self.refresh_last_id = 0
# flickr images
- self.flickr_initial_check = False
+ # True == disabled for now - don't need this code to run on startup anymore
+ self.flickr_initial_check = True
# preview sites
self.preview_site_in_progress = False
self.preview_site_last_id = 0
from services.master.worker import Command
from services.command.picasa import Picasa
+from services.command.youtube import Youtube
from urlparse import urlparse, urlunsplit
import re
self.url = "http://picasaweb.google.com/" + picasa_user
return
+ # See services/command/youtube.py for all forms
+ y_user = Youtube().getYoutubeUser(self.url)
+ if y_user:
+ self.url = Youtube().feedForUser(y_user)
+
return
# flickr.com forms
http://randomvandal.wordpress.com/feed/
. Add a twitter feed
http://twitter.com/onehalfamazing
+. Add a youtube feed
+ http://www.youtube.com/ut_rss?type=username&arg=roosterteeth
. Add a linkedin site with a description:
http://www.linkedin.com/pub/8/661/720
. Add a linkedin site without a description:
from services.command.newsite import NewSiteTryURL
from services.command.identica import Identica
from services.command.delicious import Delicious
+from services.command.youtube import Youtube
class TestNewSite(unittest.TestCase):
def setUp(self):
assert(nstu.getPreferredFeed(url, data) == None)
+ def test_youtube(self):
+ """
+ Test to make sure we can detect youtube.com urls
+ """
+ good_urls = ['http://www.youtube.com/user/christopherblizzard',
+ 'http://www.youtube.com/christopherblizzard',
+ 'http://www.youtube.com/rss/user/christopherblizzard/videos.rss',
+ 'http://www.youtube.com/ut_rss?type=username&arg=christopherblizzard',
+ 'http://www.youtube.com/profile?user=christopherblizzard',
+ 'http://youtube.com/ut_rss?type=username&arg=christopherblizzard',
+ 'http://fr.youtube.com/ut_rss?type=username&arg=christopherblizzard',
+ 'http://fr.youtube.com/rss/user/christopherblizzard/videos.rss',
+ 'http://au.youtube.com/christopherblizzard']
+
+ bad_urls = ['http://www.youtube.com',
+ 'http://some.other.site',
+ 'http://www.youtube.com/user/',
+ 'http://fr.youtube.com']
+
+ for i in good_urls:
+ x = Youtube()
+ print("trying %s" % i)
+ assert(x.getYoutubeUser(i) == 'christopherblizzard')
+ print("got %s" % x.getYoutubeUser(i))
+
+ for i in bad_urls:
+ x = Youtube()
+ print("trying not %s" % i)
+ assert(x.getYoutubeUser(i) == None)
+
+ x = Youtube()
+ assert(x.feedForUser('christopherblizzard') == 'http://www.youtube.com/ut_rss?type=username&arg=christopherblizzard')
+ assert(x.homeForUser('christopherblizzard') == 'http://www.youtube.com/user/christopherblizzard')
+
+ good_feeds = [ 'http://www.youtube.com/ut_rss?type=username&arg=christopherblizzard',
+ 'http://www.youtube.com/rss/user/christopherblizzard/videos.rss']
+ bad_feeds = [ 'http://something.else' ]
+
+ for i in good_feeds:
+ x = Youtube()
+ print("trying %s" % i)
+ assert(x.isYoutube(i) == True)
+
+ for i in bad_feeds:
+ x = Youtube()
+ print("trying not %s" % i)
+ assert(x.isYoutube(i) == False)
q = """
SELECT id, display_cache FROM site_history WHERE
site_id IN (SELECT id FROM site WHERE
- type = 'flickr') and display_cache is not null
+ type = 'picasa') and display_cache is not null
"""
c.execute(q)
--- /dev/null
+#!/usr/bin/python
+
+# Copyright (c) 2007-2008 Christopher Blizzard <blizzard@0xdeadbeef.com>
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+import simplejson
+import MySQLdb
+import re
+
+import sys
+sys.path.append("..")
+import services.config as config
+config.read("utils.cfg")
+
+db = MySQLdb.connect(db=config.get("db", "db"),
+ user=config.get("db", "user"),
+ passwd=config.get("db", "passwd"))
+
+c = db.cursor()
+q = """
+ SELECT id, feed from site WHERE type = 'flickr' AND feed LIKE '%format=atom'
+ """
+
+c.execute(q)
+
+r = c.fetchone()
+
+while r:
+ x = r[1]
+ x = re.sub('format=atom$', 'format=rss_200', x)
+ print r[0]
+ print x
+ q2 = """UPDATE site set feed = %s where id = %s"""
+ c2 = db.cursor()
+ c2.execute(q2, [x, r[0]])
+
+ r = c.fetchone()
+
return dict(content=unicode(self.rendersite(s, sh, "full"), "utf-8"))
def getDisplayDepth(self, site_type, render_type):
- depth_matrix = dict(full=dict(flickr=10, picasa=12, twitter=3, identica=3, delicious=3, feed=3),
- edit=dict(flickr=5, picasa=6, twitter=1, identica=1, delicious=1, feed=1),
- search=dict(flickr=5, picasa=6, twitter=1, identica=1, delicious=1, feed=1),
- preview=dict(flickr=5, picasa=6, twitter=3, identica=3, delicious=3, feed=3))
+ depth_matrix = dict(full=dict(flickr=10, picasa=12, twitter=3, identica=3, delicious=3, feed=3, youtube=3),
+ edit=dict(flickr=5, picasa=6, twitter=1, identica=1, delicious=1, feed=1, youtube=3),
+ search=dict(flickr=5, picasa=6, twitter=1, identica=1, delicious=1, feed=1, youtube=3),
+ preview=dict(flickr=5, picasa=6, twitter=3, identica=3, delicious=3, feed=3, youtube=3))
return depth_matrix[render_type][site_type]
def rendersite(self, site, site_history, display):
template = "whoisi.templates.identica-widget"
elif site.type == "delicious":
template = "whoisi.templates.delicious-widget"
+ elif site.type == "youtube":
+ template = "whoisi.templates.youtube-widget"
else:
return "<div>Oh, crap. Wtf?</div>\n"
<%namespace file="flickr-widget.mak" import="flickr_widget"/>
<%namespace file="picasa-widget.mak" import="picasa_widget"/>
<%namespace file="delicious-widget.mak" import="delicious_widget"/>
+<%namespace file="youtube-widget.mak" import="youtube_widget"/>
<%inherit file="master.mak"/>
${picasa_widget(site=site, site_history=cluster, display="time")}
%elif site.type == "delicious":
${delicious_widget(site=site, site_history=cluster, display="time")}
+%elif site.type == "youtube":
+${youtube_widget(site=site, site_history=cluster, display="time")}
%endif
%endfor
<%namespace file="picasa-widget.mak" import="picasa_widget"/>
<%namespace file="linkedin-widget.mak" import="linkedin_widget"/>
<%namespace file="delicious-widget.mak" import="delicious_widget"/>
+<%namespace file="youtube-widget.mak" import="youtube_widget"/>
<%namespace file="aliases-widget.mak" import="aliases_widget"/>
<%namespace file="site-add-status-widget.mak" import="site_add_status_widget"/>
<%namespace file="site-add-pick-widget.mak" import="site_add_pick_widget"/>
${linkedin_widget(site=site, display=display)}
%elif site.type == "picasa":
${picasa_widget(site=site, site_history=site_history[site.id], display=display)}
+ %elif site.type == "youtube":
+ ${youtube_widget(site=site, site_history=site_history[site.id], display=display)}
%endif
%endfor
from whoisi.utils.follow import is_following_person
from whoisi.utils.picasa import picasa_get_summary
from whoisi.utils.display import short_link_ref
+import simplejson
%>
<%def name="picasa_widget(site, site_history, display)">
<div class="link-collection">
%for i in range(0, count):
+<%
+summary = picasa_get_summary(entries_list[i])
+thumb = simplejson.loads(entries_list[i].display_cache)["thumb"]
+%>
%if display != "preview":
<a target="_blank" href="${short_link_ref(entries_list[i].id)}">
- <img width="64" height="64" src="${entries_list[i].display_cache}" title="${picasa_get_summary(entries_list[i]) | h}"/>
+ <img width="64" height="64" src="${thumb}" title="${summary | h}"/>
</a>
%else:
<a target="_blank" href="${entries_list[i].link}">
- <img width="64" height="64" src="${entries_list[i].display_cache}" title="${picasa_get_summary(entries_list[i]) | h}"/>
+ <img width="64" height="64" src="${thumb}" title="${summary | h}"/>
</a>
%endif
%if int(i+1) % 6 == 0:
<%namespace file="flickr-widget.mak" import="flickr_widget"/>
<%namespace file="picasa-widget.mak" import="picasa_widget"/>
<%namespace file="delicious-widget.mak" import="delicious_widget"/>
+<%namespace file="youtube-widget.mak" import="youtube_widget"/>
<%inherit file="master.mak"/>
${picasa_widget(site=site, site_history=cluster, display="time")}
%elif site.type == "delicious":
${delicious_widget(site=site, site_history=cluster, display="time")}
+%elif site.type == "youtube":
+${youtube_widget(site=site, site_history=cluster, display="time")}
%endif
%endfor
--- /dev/null
+# -*- coding: utf-8 -*-
+
+## Copyright (c) 2007-2008 Christopher Blizzard <blizzard@0xdeadbeef.com>
+##
+## Permission is hereby granted, free of charge, to any person
+## obtaining a copy of this software and associated documentation files
+## (the "Software"), to deal in the Software without restriction,
+## including without limitation the rights to use, copy, modify, merge,
+## publish, distribute, sublicense, and/or sell copies of the Software,
+## and to permit persons to whom the Software is furnished to do so,
+## subject to the following conditions:
+##
+## The above copyright notice and this permission notice shall be
+## included in all copies or substantial portions of the Software.
+##
+## THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+## EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+## MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+## NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+## BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+## ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+## CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+## SOFTWARE.
+
+<%!
+from whoisi.utils.follow import is_following_person
+from whoisi.utils.display import short_link_ref
+from whoisi.utils.youtube import youtube_get_user
+import simplejson
+%>
+
+<%def name="youtube_widget(site, site_history, display)">
+<%
+_untitled = "<i>Untitled</i>"
+
+entries_list = site_history
+count = len(site_history)
+needs_refresh = False
+%>
+
+<div class="link-collection-item" site-id="${site.id}" needs-refresh="${needs_refresh}">
+<img src="/static/images/sites/youtube-favicon.png"/>
+%if display == "time" or display == "follow":
+<a href="/p/${site.personID}">${site.person.name | h}</a>:
+%endif
+<%
+site_url = None
+host, user = youtube_get_user(site.url)
+if user == None:
+ site_url = site.url
+else:
+ site_url = 'http://' + host + '/user/' + user
+%>
+<a href="${site_url}">${site.title | h}</a>
+
+<span class="link-action">
+%if display == "edit":
+ <a site-id="${site.id}" class="site-remove" href="">Remove</a>
+%endif
+%if display == "time":
+ %if is_following_person(site.personID):
+ <a class="person-unfollow" person-id="${site.personID}" href="#">Stop Following</a>
+ %else:
+ <a class="person-follow" person-id="${site.personID}" href="#">Follow Person</a>
+ %endif
+%endif
+</span>
+
+<div class="link-collection">
+%for i in range(0, count):
+<%
+thumb = None
+title = None
+
+try:
+ title = entries_list[i].title
+except:
+ title = "Untitled"
+
+try:
+ thumb = simplejson.loads(entries_list[i].display_cache)["thumb"]
+except:
+ thumb = "/static/images/sites/flickr-blank-75x75.png"
+
+%>
+%if display != "preview":
+ <a target="_blank" href="${short_link_ref(entries_list[i].id)}">
+ <img width="128" height="96" src="${thumb}" title="${title| h}"/>
+ </a>
+%else:
+ <a target="_blank" href="${entries_list[i].link}">
+ <img width="128" height="96" src="${thumb}" title="${title | h}"/>
+ </a>
+%endif
+%if int(i+1) % 6 == 0:
+<br/>
+%endif
+%endfor
+</div>
+
+</div>
+</%def>
+
+${youtube_widget(site=site, site_history=site_history, display=display)}
from whoisi.model import *
def site_value(site):
- types = [ "flickr", "picasa", "linkedin", "twitter", "identica", "delicious", "feed" ]
+ types = [ "flickr", "picasa", "youtube", "linkedin", "twitter", "identica", "delicious", "feed" ]
return types.index(site.type)
def reorder_sort(x,y):
--- /dev/null
+import urlparse
+import re
+
+def youtube_get_user(url):
+ u = urlparse.urlparse(url)
+ urlparse.clear_cache()
+ host = u[1]
+ path = u[2]
+
+ # http://www.youtube.com/rss/user/christopherblizzard/videos.rss
+ match = re.match('/rss/user/([^/]+)/videos.rss', path)
+ if match:
+ return host, match.group(1)
+
+ return None, None