Skip to content

Commit

Permalink
change http library to typhoeus to better follow https links. move su…
Browse files Browse the repository at this point in the history
…bmethods to their own classes and use the url to pick which class to use. There are still better abstractions that can be used here, but this solves #12 for now.
  • Loading branch information
stim371 committed Sep 13, 2013
1 parent 9601d29 commit d4888d4
Show file tree
Hide file tree
Showing 27 changed files with 289 additions and 4,152 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Expand Up @@ -3,4 +3,5 @@
Gemfile.lock
pkg/*
coverage/
credentials.yml
credentials.yml
tmp/*
3 changes: 0 additions & 3 deletions .yardoc/checksums

This file was deleted.

Binary file removed .yardoc/objects/root.dat
Binary file not shown.
2 changes: 0 additions & 2 deletions .yardoc/proxy_types

This file was deleted.

121 changes: 23 additions & 98 deletions lib/uncoil.rb
@@ -1,118 +1,43 @@
require 'uncoil/domain_finder'
require 'uncoil/expander'
require 'uncoil/response'

require 'bitly'
require_relative 'uncoil_submethods'

# @author Joel Stimson
class Uncoil
ISGD_ROOT_URL = "http://is.gd/forward.php?format=json&shorturl="
BITLY_DOM_ARRAY = %w[bit.ly, j.mp, bitlypro.com, cs.pn, nyti.ms]

# Creates a new Uncoil object and will log into Bit.ly if you provide credentials
#
# @option options [String] :bitlyuser A key for your Bit.ly API username
# @option options [String] :bitlykey A key for your Bit.ly API key
#
# @return [Class] the new instance of the Uncoil class
#
# @example Set up a new instance
# Uncoil.new(:bitlyuser => CREDENTIALS['bitlyuser'], :bitlykey => CREDENTIALS['bitlykey']) => "#<Uncoil:0x00000102560d30 @bitly_access=true>"
#
def initialize options = {}
def initialize(options = {})
Bitly.use_api_version_3
@bitly_access = false

# create bitly instance if the auth criteria are all entered by user

if options.has_key?(:bitlyuser) && options.has_key?(:bitlykey)
@bitly_instance = Bitly.new("#{options[:bitlyuser]}", "#{options[:bitlykey]}")
@bitly_access = true
end
end

# A class method version of the main expand method. This will not have access to the bit.ly API, but it's faster than having to create an instance and then use it for a one-off request.
#
# @param [String, Array] short_url The single url or array of urls you would like to expand
#
# @example Use the class method for a one-off request
# Uncoil.expand("http://tinyurl.com/736swvl") # => #<Uncoil::Response:0x00000101ed9250 @long_url="http://www.chinadaily.com.cn/usa/business/2011-11/08/content_14057648.htm", @short_url="http://tinyurl.com/736swvl", @error=nil>
#
def self.expand short_url
Uncoil.new.expand(short_url)
def self.expand(urls)
Uncoil.new.expand(urls)
end

# The main method used for all requests. This method will delegate to submethods based on the domain of the link given.
#
# @param [String, Array] url_arr This can be a single url as a String or an array of Strings that the method will expand in order
#
# @return [Uncoil::Response] Returns a response object with getters for the long and short url
#
def expand url_arr
output_array = Array(url_arr).flatten.map do |short_url|
short_url = clean_url(short_url)
domain = identify_domain(short_url)

begin
long_url =
if @bitly_access && ( BITLY_DOM_ARRAY.include?(domain) || check_bitly_pro(domain) )
uncoil_bitly(short_url)
elsif domain == "is.gd"
uncoil_isgd(short_url)
else
uncoil_other(short_url)
end
rescue => exception
long_url = nil
error = exception.message
end
# return a response object for each time through the loop
Response.new(long_url, short_url, error)
end
# here's the return
output_array.length == 1 ? output_array[0] : output_array
def expand(urls)
format_output(expand_all(urls))
end

private

# Contacts the bit.ly API to see if the domain is a bitlypro domain, which are custom domains purchased by 3rd parties but managed by bit.ly
#
# @param [String] url_domain The domain to check against the bit.ly API.
#
def check_bitly_pro url_domain
@bitly_instance.bitly_pro_domain(url_domain)
end


# Extracts the domain from the link to help match it with the right sub-method to expand the url
#
# @param [String] short_url A single url to extract a domain from.
#
def identify_domain short_url
clean_url(short_url).split("/")[2].to_s
def expand_all(urls)
Array(urls).flatten.map { |short_url| response_for(short_url) }
end


# Standardizes the url by adding a protocol if there isn't one and removing trailing slashes
#
# @param [String] short_url A single url to be cleaned up.
#
def clean_url short_url
short_url = "http://" << short_url unless short_url =~ /^https?:\/\//
short_url.chop! if short_url[-1] == "/"
short_url
def response_for(url)
response = Response.new({:short_url => url})
begin
response.long_url = Expander.expand(url, @bitly_instance)
rescue => exception
response.long_url, response.error = nil, exception.message
end
response
end

end


class Uncoil::Response
attr_reader :long_url, :short_url, :error

# Creates a new Response object with attributes for the original and short url, as well as any errors that occured. It is called at the end of 'expand' method.
#
# @param [String] long_url The expanded url that we were looking for.
# @param [String] short_url The original, short url that we used to look up the long url.
# @param [String] error The error output if anything went wrong during the request. And I mean ANYTHING from a code error to an HTTP issue. It catches it all.
#
def initialize(long_url, short_url, error)
@long_url = long_url
@short_url = short_url
@error = error
def format_output(output_array)
output_array.length == 1 ? output_array[0] : output_array
end
end
33 changes: 33 additions & 0 deletions lib/uncoil/domain_finder.rb
@@ -0,0 +1,33 @@
require 'open-uri'

class DomainFinder
def domain_for(short_url)
#TODO: new object here?
match_host_to_provider(host_for(short_url))
end

def host_for(url)
URI.parse(url).host
end

def match_host_to_provider(host)
case
when bitly_domains.include?(host)
:bitly
when isgd_domains.include?(host)
:isgd
else
host
end
end

private

def bitly_domains
%w[bit.ly j.mp bitlypro.com cs.pn nyti.ms]
end

def isgd_domains
'is.gd'
end
end
24 changes: 24 additions & 0 deletions lib/uncoil/expander.rb
@@ -0,0 +1,24 @@
require_relative './expanders/default_expander'
require_relative './expanders/bitly_expander'
require_relative './expanders/isgd_expander'

module Expander
class << self
def expand(short_url, bitly_instance = nil)
case
when domain_for(short_url) == :bitly && bitly_instance
BitlyExpander.expand(short_url, bitly_instance)
when domain_for(short_url) == :isgd
IsgdExpander.expand(short_url)
else
DefaultExpander.expand(short_url)
end
end

private

def domain_for(short_url)
DomainFinder.new.domain_for(short_url)
end
end
end
5 changes: 5 additions & 0 deletions lib/uncoil/expanders/bitly_expander.rb
@@ -0,0 +1,5 @@
class BitlyExpander
def self.expand(short_url, bitly_instance)
bitly_instance.expand(short_url).long_url
end
end
17 changes: 17 additions & 0 deletions lib/uncoil/expanders/default_expander.rb
@@ -0,0 +1,17 @@
require 'open-uri'
require 'net/http'

class DefaultExpander
class << self
def expand(short_url)
response = Typhoeus.head(short_url, :followlocation => true)
location_from_response(response)
end

private

def location_from_response(response)
response.options[:effective_url]
end
end
end
19 changes: 19 additions & 0 deletions lib/uncoil/expanders/isgd_expander.rb
@@ -0,0 +1,19 @@
require 'json'
require 'typhoeus'

class IsgdExpander
ISGD_ROOT_URL = "http://is.gd/forward.php?format=json&shorturl="

class << self
def expand(short_url)
response = Typhoeus.get("#{ISGD_ROOT_URL}#{short_url}")
isgd_location_from_response(response.response_body)
end

private

def isgd_location_from_response(body)
JSON.parse(body)["url"]
end
end
end
11 changes: 11 additions & 0 deletions lib/uncoil/response.rb
@@ -0,0 +1,11 @@
class Uncoil
class Response
attr_accessor :long_url, :short_url, :error

def initialize(attributes = {})
attributes.each do |k, v|
self.send("#{k.to_s}=", v)
end
end
end
end
38 changes: 0 additions & 38 deletions lib/uncoil_submethods.rb

This file was deleted.

0 comments on commit d4888d4

Please sign in to comment.