~ssweeny/friends/fix-lp1258657

« back to all changes in this revision

Viewing changes to friends/utils/shorteners.py

  • Committer: Robert Bruce Park
  • Date: 2013-05-01 17:05:12 UTC
  • mfrom: (160.11.31 trunk-next)
  • Revision ID: robert.park@canonical.com-20130501170512-d3xrvv4vmv24k3ir
Land trunk-next into lp:friends.

Show diffs side-by-side

added added

removed removed

Lines of Context:
17
17
 
18
18
 
19
19
__all__ = [
20
 
    'PROTOCOLS',
21
 
    'is_shortened',
22
 
    'lookup',
 
20
    'Short',
23
21
    ]
24
22
 
25
23
 
26
 
from friends.shorteners import isgd, ougd, tinyurlcom, linkeecom
27
 
 
28
 
 
29
 
PROTOCOLS = {
30
 
    'is.gd': isgd,
31
 
    'ou.gd': ougd,
32
 
    'linkee.com': linkeecom,
33
 
    'tinyurl.com': tinyurlcom,
 
24
import re
 
25
 
 
26
from urllib.parse import quote
 
27
 
 
28
from friends.utils.base import LINKIFY_REGEX as replace_urls
 
29
from friends.utils.http import Downloader
 
30
 
 
31
 
 
32
# These strings define the shortening services. If you want to add a
 
33
# new shortener to this list, the shortening service must take the URL
 
34
# as a parameter, and return the plaintext URL as the result. No JSON
 
35
# or XML parsing is supported. The strings below must contain exactly
 
36
# one instance of '{}' to represent where the long URL goes in the
 
37
# service. This is typically at the very end, but doesn't have to be.
 
38
URLS = {
 
39
    'is.gd':       'http://is.gd/api.php?longurl={}',
 
40
    'linkee.com':  'http://api.linkee.com/1.0/shorten?format=text&input={}',
 
41
    'ou.gd':       'http://ou.gd/api.php?format=simple&action=shorturl&url={}',
 
42
    'tinyurl.com': 'http://tinyurl.com/api-create.php?url={}',
 
43
    'durl.me':     'http://durl.me/api/Create.do?type=json&longurl={}',
34
44
    }
35
45
 
36
46
 
37
 
class NoShortener:
38
 
    """The default URL 'shortener' which doesn't shorten at all.
39
 
 
40
 
    If the chosen shortener isn't found, or is disabled, then this one is
41
 
    returned.  It supports the standard API but just returns the original URL
42
 
    unchanged.
43
 
    """
44
 
 
45
 
    class URLShortener:
46
 
        def shorten(self, url):
 
47
class Short:
 
48
    """Each instance of this class represents a unique shortening service."""
 
49
 
 
50
    def __init__(self, domain=None):
 
51
        """Determine which shortening service this instance will use."""
 
52
        self.template = URLS.get(domain)
 
53
        self.domain = domain
 
54
 
 
55
        # Disable shortening if no shortener found.
 
56
        if None in (domain, self.template):
 
57
            self.make = lambda url: url
 
58
            return
 
59
 
 
60
        if "json" in self.template:
 
61
            self.make = self.json
 
62
 
 
63
    def make(self, url):
 
64
        """Shorten the URL by querying the shortening service."""
 
65
        if Short.already(url):
 
66
            # Don't re-shorten an already-short URL.
47
67
            return url
48
 
 
49
 
 
50
 
def lookup(name):
51
 
    """Look up a URL shortener by name.
52
 
 
53
 
    :param name: The name of a shortener.
54
 
    :type name: string
55
 
    :return: An object supporting the `shorten(url)` method.
56
 
    """
57
 
    return PROTOCOLS.get(name, NoShortener).URLShortener()
58
 
 
59
 
 
60
 
def is_shortened(url):
61
 
    """True if the URL has been shortened by a known shortener."""
62
 
    # What if we tried to URL shorten http://tinyurl.com/something???
63
 
    for module in PROTOCOLS.values():
64
 
        if url.startswith(module.URLShortener.fqdn):
65
 
            return True
66
 
    return False
 
68
        return Downloader(
 
69
            self.template.format(quote(url, safe=''))).get_string().strip()
 
70
 
 
71
    def sub(self, message):
 
72
        """Find *all* of the URLs in a string and shorten all of them."""
 
73
        return replace_urls(lambda match: self.make(match.group(0)), message)
 
74
 
 
75
    def json(self, url):
 
76
        """Grab URLs swiftly with regex."""
 
77
        # Avoids writing JSON code that is service-specific.
 
78
        find = re.compile('https?://{}[^\"]+'.format(self.domain)).findall
 
79
        for short in find(Short.make(self, url)):
 
80
            return short
 
81
        return url
 
82
 
 
83
    # Used for checking if URLs have already been shortened.
 
84
    already = re.compile(r'https?://({})/'.format('|'.join(URLS))).match