#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
PyBlosxom plugin that adds $melinks template variable

So called "me" links are a way to connect your different social
network profile pages together, so that services like Google's Social
Graph will identify them as belonging to the same person.  By adding
"me" links from your blog to all your profile pages makes your blog a
well connected hub in your piece of the 'net.

For an example, see the left sidebar section "Elsewhere" at
http://www.linuxbox.fi/~vmj/blog/ and then see how Google sees it at
http://socialgraph-resources.googlecode.com/svn/trunk/samples/findyours.html?q=http%3A%2F%2Flinuxbox.fi%2F%7Evmj%2Fblog%2F
or what plaxo crawler makes of it at
http://www.plaxo.com/opensocialgraph.py?url=http%3A%2F%2Fwww.linuxbox.fi%2F~vmj%2Fblog&output=&verbose=1

INSTALLATION
============

Download the melinks.py file and place it in your plugins directory.

You can find the latest version at
http://www.linuxbox.fi/~vmj/melinks/melinks.py which is documented at
http://www.linuxbox.fi/~vmj/melinks/melinks.html

Previous versions of the plugin can be found at
http://www.linuxbox.fi/~vmj/melinks/archive/

CONFIGURATION
=============

First of all, make sure you add the plugin into plugin load list:

   py['load_plugins'] = [..., 'melinks', ...]

You need to add the account information into your config.py.  For
example,

   py["melinks_accounts"] = [
       ['Identi.ca', 'vmj'],
       ['Twitter', '_vmj_'],
   ]

tells melinks plugin that you've got an account at Identi.ca (where
your user name is 'vmj') and you're also on Twitter (known there as
'_vmj_').  These are later refered to as account descriptors.

For a few sites, the plugin knows how to derive the Profile Page URL
from your user name (as is the case with Identi.ca and Twitter).  For
other sites you need to tell the plugin the URL.  For example,

   py["melinks_accounts"] = [
       ['Some Site', None, {'url': 'http://some.site.com/123/}],
   ]

Note that the user name is not used in this case.  Notably, currently
at least PhotoBucket and Plaxo profile page URLs are a mystery to this
plugin.

As you noticed, the third item in the list is a dictionary.  Other
keys that you can place there are:

   * 'icon': Override the URL of the favicon.  This is useful if you
             want to use a custom favicon for a site.  But if you want
             to use the default icon, don't use this setting.

   * 'label': Label for the site.  By default this is the same as the
              first item in the account descriptor.

Now you're all set.  Rest of the configuration is optional.

By default, the plugin generates a vertical list with icons and
labels.  You can change to a more compact (but IMHO less usable)
horizontal list of icons by adding the following to the config.py:

   py["melinks_style"] = "icons"

You can also define a placeholder favicon for those that don't have
one by adding the following to the config.py:

   py["melinks_icon"] = "http://my.blog.net/favicon.ico"

Note that the placeholder is only used if the icon URL is not
overridden and the plugin doesn't know the default icon.

Finally, add the template variable $melinks somewhere in your
templates, like the foot template.  For example,

   <h2>Elsewhere</h2>
   <div class="sidebox">
   $melinks
   </div>


CACHING THE FAVICONS LOCALLY
============================

To make local copies of the favicons, add following to your config.py:

   py['melinks_icon_dir'] = '/absolute/path/to/favicon/dir'
   py['melinks_icon_url'] = 'http://my.blog.net/images'

Then, on command line, change to the directory where you have the
config.py and execute command:

    PYTHONPATH=. python path/to/plugins/melinks.py

melinks.py will download all the favicons to the directory pointed to
by 'melinks_icon_dir' setting.  The directory need not exist, yet.
Only the default icons are downloaded, not those you have overridden
with the account specific 'icon' setting.

When later adding accounts, just rerun the above command and it will
download the new icons.


COPYRIGHT
=========

Copyright 2009,2010 Mikko Värri (vmj@linuxbox.fi)

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
"""
__author__      = "Mikko Värri - vmj at linuxbox dot fi"
__version__     = "version 0.0.10 2010-08-31"
__url__         = "http://www.linuxbox.fi/~vmj/melinks/melinks.html"
__description__ = "Adds me-links for Social Graph and semantic web"


from Pyblosxom import tools
from os import path


__defaults = {
        'Advogato': [
                lambda user: 'http://www.advogato.org/person/%s' % user,
                'http://www.advogato.org/images/favicon.ico',
                ],
        'bitbucket': [
                lambda user: 'http://bitbucket.org/%s' % user,
                'http://bitbucket-assets.s3.amazonaws.com/img/logo_new.png',
                ],
        'brightkite': [
                lambda user: 'http://brightkite.com/people/%s' % user,
                'http://brightkite.com/favicon.ico',
                ],
        'Cloud27': [
                lambda user: 'http://cloud27.com/profiles/%s/' % user,
                None, # 'http://cloud27.com/favicon.ico',
                ],
        'DjangoPeople': [
                lambda user: 'http://djangopeople.net/%s/' % user,
                None, # 'http://djangopeople.net/favicon.ico',
                ],
        'Dopplr': [
                lambda user: 'http://www.dopplr.com/traveller/%s' % user,
                'http://www.dopplr.com/favicon.ico',
                ],
        'Facebook': [
                lambda user: 'http://facebook.com/%s' % user,
                'http://facebook.com/favicon.ico',
                ],
        'FileCabin': [
                lambda user: 'http://www.filecabin.com/profile/%s/' % user,
                'http://filecabin.com/favicon.ico',
                ],
        'Flickr': [
                lambda user: 'http://www.flickr.com/photos/%s/' % user,
                'http://flickr.com/favicon.ico',
                ],
        'Fluther': [
                lambda user: 'http://www.fluther.com/users/%s/' % user,
                'http://davinci.fluther.com/images/v2/favicon.ico',
                ],
        'foursquare': [
                lambda user: 'http://foursquare.com/user/%s' % user,
                'http://foursquare.com/favicon.ico',
                ],
        'Freshmeat': [
                lambda user: 'http://freshmeat.net/users/%s' % user,
                'http://freshmeat.net/favicon.ico',
                ],
        'FriendFeed': [
                lambda user: 'http://friendfeed.com/%s' % user,
                'http://friendfeed.com/favicon.ico',
                ],
        'github': [
                lambda user: 'http://github.com/%s' % user,
                'http://github.com/favicon.ico',
                ],
        'Google': [
                lambda user: 'http://www.google.com/profiles/%s' % user,
                'http://www.google.com/favicon.ico',
                ],
        'Identi.ca': [
                lambda user: 'http://identi.ca/%s' % user,
                'http://identi.ca/favicon.ico',
                ],
        'Jaiku': [
                lambda user: 'http://%s.jaiku.com/' % user,
                'http://jaiku.com/themes/classic/favicon.ico',
                ],
        'Launchpad': [
                lambda user: 'https://launchpad.net/~%s' % user,
                'https://launchpad.net/favicon.ico',
                ],
        'libre.fm': [
                lambda user: 'http://alpha.libre.fm/user/%s' % user,
                'http://alpha.libre.fm/favicon.ico',
                ],
        'LinkedIn': [
                lambda user: 'http://www.linkedin.com/in/%s' % user,
                'http://www.linkedin.com/favicon.ico',
                ],
        'Linux Counter': [
                lambda user: 'http://counter.li.org/cgi-bin/runscript/display-person.cgi?user=%s' % user,
                'http://counter.li.org/favicon.ico',
                ],
        'LiveJournal': [
                None, # http://www.livejournal.com/userinfo.bml?userid=20241485&t=I,
                'http://www.livejournal.com/favicon.ico',
                ],
        'Moozement': [
                lambda user: 'http://www.moozement.com/users/%s' % user,
                'http://www.moozement.com/favicon.ico',
                ],
        'Ohloh': [
                lambda user: 'http://www.ohloh.net/accounts/%s' % user,
                'http://ohloh.net/favicon.ico',
                ],
        'OpenDesktop.org': [
                lambda user: 'http://opendesktop.org/usermanager/search.php?username=%s' % user,
                'http://static.opendesktop.org/img/favicon-10-1.ico',
                ],
        'OpenHatch.org': [
                lambda user: 'http://openhatch.org/people/%s/' % user,
                'http://openhatch.org/favicon.ico',
                ],
        'Openmoko Wiki': [
                lambda user: 'http://wiki.openmoko.org/wiki/User:%s' % user,
                'http://wiki.openmoko.org/favicon.ico',
                ],
        'OpenStreetMap': [
                lambda user: 'http://www.openstreetmap.org/user/%s' % user,
                'http://www.openstreetmap.org/favicon.ico',
                ],
        'PhotoBucket': [
                None, # 'http://s382.photobucket.com/albums/oo267/_vmj_/'
                'http://photobucket.com/favicon.ico',
                ],
        'Plaxo': [
                None, # http://www.plaxo.com/profile/show/180389975304?pk=be4951159299bfcfc01e97ec6674db523d6d474b
                'http://static.plaxo.net/img/favicon.ico',
                ],
        'Plurk': [
                lambda user: 'http://www.plurk.com/%s' % user,
                'http://statics.plurk.com/b872d9e40dbce69e5cde4787ccb74e60.png',
                ],
        'readernaut': [
                lambda user: 'http://readernaut.com/%s/' % user,
                'http://media.readernaut.com/images/favicon.ico',
                ],
        'ShutEyes': [
                lambda user: 'http://www.shuteyes.org/accounts/profiles/%s/' % user,
                'http://shuteyes.org/shuteyes_static/images/favicon.ico',
                ],
        'StackOverflow': [
                lambda user: 'http://stackoverflow.com/users/%s' % user,
                'http://stackoverflow.com/favicon.ico',
                ],
        'Tagz': [
                lambda user: 'http://%s.tagz.in/' % user,
                'http://tagz.in/favicon.ico',
                ],
        'Twitter': [
                lambda user: 'http://twitter.com/%s' % user,
                'http://twitter.com/favicon.ico',
                ],
        'YouTube': [
                lambda user: 'http://www.youtube.com/user/%s' % user,
                'http://s.ytimg.com/yt/favicon-vfl147246.ico',
                ],
        'Zotero': [
                lambda user: 'http://www.zotero.org/%s' % user,
                'http://www.zotero.org/favicon.ico',
                ],
        }


def __favicon_name(site):
        """Generates the file name for a favicon.

        :param site: Site identifier.
        :returns: Basename of the file (no extension or path).
        """
        return '%s%s' % (site.replace('.', '').replace(' ', ''), '.ico')


def verify_installation(request):
        """Verify plugin installation.

        Always returns 1 (installation OK) but nags if there are no
        account information, which means this plugin has nothing to
        do.

        :param request: Ignored.
        :return: 1 if the plugin installation seems fine, 0 if the plugin can not work.
        """
        config = request.getConfiguration()

        if not config.has_key('melinks_accounts') or len(config['melinks_accounts']) == 0:
                print "There doesn't seem to be any me-links defined."
                print "Either add some accounts to py['melinks_accounts']"
                if config.has_key('load_plugins') and 'melinks' in config['load_plugins']:
                        print "or remove melinks from py['load_plugins']."
                else:
                        print "or remove melinks.py from your plugins directory."

        if config.has_key('melinks_icon_dir') != config.has_key('melinks_icon_url'):
                print "If you want to use the favicon caching, you need to define both"
                print "py['melinks_icon_dir'] and py['melinks_icon_url'].  Now only one"
                print "is defined."

        return 1


def cb_prepare(args):
        """Prepare the request data.

        This callback is called after Pyblosxom default handler has
        figured out what it is rendering and before it is rendered.

        Plugins are expected to modify the data dict in the Request.

        Implemented to add 'melinks' key to request data dictionary.
        The value is the HTML to display XFN "me" links.

        :param args: A dictionary with following keys
          * request - The Request object
        :return: None (ignored)
        """
        request = args['request']
        data = request.getData()

        # This is what we're going to generate
        # Let's ensure it is defined even if we bail out
        data['melinks'] = ''

        config = request.getConfiguration()

        logger = tools.getLogger()

        # Do we have anything to do?
        if not config.has_key('melinks_accounts'):
                return None

        # Style of the UI
        style = config.get('melinks_style', 'list')
        if style not in ('list', 'icons'):
                style = 'list'
                logger.warning("Value of 'melinks_style' is not recognized, defaulting to 'list'")

        # Icon to use for sites that dont have one defined
        # or, most likely, sites that are not defined in __defaults.
        default_icon = config.get('melinks_icon', None)

        # Icon cache: local directory and web URL
        icon_dir = config.get('melinks_icon_dir', None)
        icon_url = config.get('melinks_icon_url', None)
        if icon_dir is None or not path.isdir(icon_dir) or icon_url is None:
                icon_dir = None
                icon_url = None

        html = ''
        for account in config['melinks_accounts']:
                site = None
                label = None
                user = None
                url = None
                icon = None

                # First two array entries are mandatory
                try:
                        site = account[0]
                        user = account[1]
                except:
                        logger.error("Invalid entry in 'melinks_accounts' array, skipped")
                        continue

                # Overriding dict is optional
                overrides = {}
                if len(account) > 2:
                        overrides = account[2]

                # Profile URL
                try:
                        url = overrides['url']
                except:
                        if __defaults.has_key(site) and __defaults[site][0]:
                                url = __defaults[site][0](user)
                        else:
                                logger.error("Don't know how to generate Profile URL for site '%s'" % site)

                # Favicon URL
                try:
                        # Use the overridden icon unconditionally
                        icon = overrides['icon']
                except:
                        # First see if the icon is cached
                        if icon_dir:
                                icon = path.join(icon_dir, __favicon_name(site))
                                if path.isfile(icon):
                                        # Found it, use the URL
                                        icon = '%s/%s' % (icon_url,
                                                          __favicon_name(site))
                                else:
                                        icon = None
                        # If it is not, use the defaults
                        if icon is None:
                                if __defaults.has_key(site) and __defaults[site][1]:
                                        # Use the remote icon
                                        icon = __defaults[site][1]
                                elif default_icon:
                                        # Use the default icon
                                        icon = default_icon
                                else:
                                        icon = None
                                        logger.error("Don't know the favicon URL for site '%s' and no default defined" % site)

                # Site label
                try:
                        label = overrides['label']
                except:
                        label = site

                # Generate the entry
                snippet = ""

                if url:
                        snippet += '<a rel="me" href="%s">' % url
                if icon:
                        snippet += "<img src='%s' height='16' width='16'" % icon
                        if style == "icons":
                                snippet += " alt='%s' title='%s'" % (label, label)
                        snippet += " />"
                if not icon or style == "list":
                        snippet += " %s" % label
                if url:
                        snippet += "</a>"

                # Only add line breaks if theres something on the line
                if snippet != "":
                        if style == "list":
                                snippet += "<br />"
                        snippet += "\n"

                        html += snippet

        data['melinks'] = html

        return None


if __name__ == "__main__":
        from sys import exit
        from os import makedirs
        from urllib import urlretrieve

        try:
                from config import py as config
        except ImportError:
                print "Unable to import the config.py.  Try defining PYTHONPATH."
                exit()

        if not config.has_key('melinks_accounts'):
                print "The configuration doesn't seem to contain any accounts."
                exit()

        # Where to save the downloaded icons
        if not config.has_key('melinks_icon_dir'):
                print "The configuration doesn't seem to define the icon dir."
                exit()

        favicons = config['melinks_icon_dir']

        # Ensure the directory exists
        try:
                makedirs(favicons)
        except OSError:
                pass

        default_icon = config.get('melinks_icon', None)

        for account in config['melinks_accounts']:
                site = None
                icon = None

                # First two array entries are mandatory
                try:
                        site = account[0]
                except:
                        print "Invalid entry in 'melinks_accounts' array, skipped"
                        continue

                # Overriding dict is optional
                overrides = {}
                if len(account) > 2:
                        overrides = account[2]

                # Overridden icons are assumed to be local
                if overrides.has_key('icon'):
                        continue

                # Don't download already downloaded icons.
                # Using "exists" test instead of "isfile" test
                # to err on the safe side (i.e. not to overwrite files)
                filename = path.join(favicons, __favicon_name(site))
                if path.exists(filename):
                        continue

                if __defaults.has_key(site) and __defaults[site][1]:
                        icon = __defaults[site][1]
                else:
                        if not default_icon:
                                print "Don't know the favicon URL for site '%s' and no default defined" % site
                        continue

                # Download icon
                print "%s " % icon,
                filename, foo = urlretrieve(icon, filename)
                print "=> %s" % filename

