2008-10-17 Christopher Blizzard <blizzard@0xdeadbeef.com>
authorblizzard <blizzard@ae879524-a8bd-4c4c-a5ea-74d2e5fc5a2c>
Sat, 18 Oct 2008 03:51:30 +0000 (03:51 +0000)
committerblizzard <blizzard@ae879524-a8bd-4c4c-a5ea-74d2e5fc5a2c>
Sat, 18 Oct 2008 03:51:30 +0000 (03:51 +0000)
        * 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.

git-svn-id: svn://trac.whoisi.com/whoisi/trunk@5 ae879524-a8bd-4c4c-a5ea-74d2e5fc5a2c

25 files changed:
ChangeLog
feed-parse-service
lib/feedparser.py
patches/README
patches/feedparser-thumbnail.patch [new file with mode: 0644]
services/command/controller.py
services/command/flickr.py
services/command/newsite.py
services/command/picasa.py
services/command/youtube.py [new file with mode: 0644]
services/master/database.py
services/master/newsite.py
smoketest.txt
tests/nose/test_newsite.py
utils/convert-display-cache.py
utils/convert-flickr-feeds.py [new file with mode: 0755]
whoisi/controllers.py
whoisi/static/images/sites/youtube-favicon.png [new file with mode: 0644]
whoisi/templates/follow.mak
whoisi/templates/person-widget.mak
whoisi/templates/picasa-widget.mak
whoisi/templates/unseen.mak
whoisi/templates/youtube-widget.mak [new file with mode: 0644]
whoisi/utils/sites.py
whoisi/utils/youtube.py [new file with mode: 0644]

index 1e2b8bb..f5457d4 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,79 @@
+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
index 39fe3d5..9daaede 100755 (executable)
@@ -98,7 +98,17 @@ class FeedParseProtocol(ChildListener):
                 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))
index a470df2..33e7f2c 100644 (file)
@@ -323,7 +323,7 @@ class _FeedParserMixin:
                   '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 @@ class _FeedParserMixin:
         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):
index 2df2903..581da11 100644 (file)
@@ -4,3 +4,8 @@ Fix titles for feeds that contain <media:title>
 
 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
diff --git a/patches/feedparser-thumbnail.patch b/patches/feedparser-thumbnail.patch
new file mode 100644 (file)
index 0000000..5cdcff6
--- /dev/null
@@ -0,0 +1,38 @@
+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):
index b3a1a59..872d0e5 100644 (file)
@@ -166,7 +166,6 @@ class PreviewSiteManager(ProtoManager):
         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
index 9789f60..feb27d2 100644 (file)
@@ -302,7 +302,7 @@ class Flickr:
 
     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]
 
index 09b1a05..4a97a0f 100644 (file)
@@ -31,6 +31,7 @@ from services.command.twitter import Twitter
 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
@@ -394,10 +395,16 @@ class NewSiteTryURL(BaseCommand):
         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"
 
 
index 54387f4..d485007 100644 (file)
@@ -93,7 +93,7 @@ class Picasa:
             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)
 
diff --git a/services/command/youtube.py b/services/command/youtube.py
new file mode 100644 (file)
index 0000000..6b371bf
--- /dev/null
@@ -0,0 +1,99 @@
+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)
index 5d2446c..38264d6 100644 (file)
@@ -44,7 +44,8 @@ class DatabaseManager:
         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
index 5847dce..94d7b98 100644 (file)
@@ -22,6 +22,7 @@
 
 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
@@ -139,6 +140,11 @@ class NewSite(Command):
                 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
index 3d9b08e..747f124 100644 (file)
@@ -21,6 +21,8 @@ Sites:
   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:
index d6da11c..c7a7f38 100644 (file)
@@ -5,6 +5,7 @@ from services.master.newsite import NewSite
 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):
@@ -156,3 +157,50 @@ class TestNewSite(unittest.TestCase):
         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)
index 1a46cfc..9069fcc 100755 (executable)
@@ -41,7 +41,7 @@ c = db.cursor()
 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)
diff --git a/utils/convert-flickr-feeds.py b/utils/convert-flickr-feeds.py
new file mode 100755 (executable)
index 0000000..efe36a2
--- /dev/null
@@ -0,0 +1,57 @@
+#!/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()
+
index ee258f1..e6a2feb 100644 (file)
@@ -504,10 +504,10 @@ class Root(controllers.RootController):
         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):
@@ -528,6 +528,8 @@ class Root(controllers.RootController):
             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"
 
diff --git a/whoisi/static/images/sites/youtube-favicon.png b/whoisi/static/images/sites/youtube-favicon.png
new file mode 100644 (file)
index 0000000..2453216
Binary files /dev/null and b/whoisi/static/images/sites/youtube-favicon.png differ
index c8d60dd..153af96 100644 (file)
@@ -34,6 +34,7 @@
 <%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"/>
 
@@ -62,6 +63,8 @@ ${flickr_widget(site=site, site_history=cluster, display="time")}
 ${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
index 64540e3..42450bc 100644 (file)
@@ -29,6 +29,7 @@
 <%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"/>
@@ -95,6 +96,8 @@ ${aliases_widget(person=person, other_names=other_names, display=display)}
     ${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
 
index 62e3460..dc90106 100644 (file)
@@ -26,6 +26,7 @@
 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)">
@@ -59,13 +60,17 @@ needs_refresh = False
 
 <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:
index bac6249..031af2d 100644 (file)
@@ -34,6 +34,7 @@
 <%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"/>
 
@@ -67,6 +68,8 @@ ${flickr_widget(site=site, site_history=cluster, display="time")}
 ${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
diff --git a/whoisi/templates/youtube-widget.mak b/whoisi/templates/youtube-widget.mak
new file mode 100644 (file)
index 0000000..a6a12ba
--- /dev/null
@@ -0,0 +1,104 @@
+# -*- 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":
+&nbsp;&nbsp;<a site-id="${site.id}" class="site-remove" href="">Remove</a>
+%endif
+%if display == "time":
+  %if is_following_person(site.personID):
+  &nbsp;&nbsp;<a class="person-unfollow" person-id="${site.personID}" href="#">Stop Following</a>
+  %else:
+  &nbsp;&nbsp;<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)}
index df65daf..d4d5fdd 100644 (file)
@@ -23,7 +23,7 @@
 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):
diff --git a/whoisi/utils/youtube.py b/whoisi/utils/youtube.py
new file mode 100644 (file)
index 0000000..55c2170
--- /dev/null
@@ -0,0 +1,15 @@
+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