rcarvalho-link_thumbnailer 1.0.9.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. data/.gitignore +19 -0
  2. data/.rspec +2 -0
  3. data/.travis.yml +6 -0
  4. data/CHANGELOG.md +91 -0
  5. data/Gemfile +12 -0
  6. data/LICENSE +22 -0
  7. data/README.md +184 -0
  8. data/Rakefile +7 -0
  9. data/app/controllers/link_thumbnailer/application_controller.rb +4 -0
  10. data/app/controllers/link_thumbnailer/previews_controller.rb +11 -0
  11. data/lib/generators/link_thumbnailer/install_generator.rb +19 -0
  12. data/lib/generators/templates/initializer.rb +41 -0
  13. data/lib/link_thumbnailer.rb +96 -0
  14. data/lib/link_thumbnailer/configuration.rb +6 -0
  15. data/lib/link_thumbnailer/doc.rb +65 -0
  16. data/lib/link_thumbnailer/doc_parser.rb +15 -0
  17. data/lib/link_thumbnailer/engine.rb +9 -0
  18. data/lib/link_thumbnailer/fetcher.rb +34 -0
  19. data/lib/link_thumbnailer/img_comparator.rb +18 -0
  20. data/lib/link_thumbnailer/img_parser.rb +46 -0
  21. data/lib/link_thumbnailer/img_url_filter.rb +13 -0
  22. data/lib/link_thumbnailer/object.rb +41 -0
  23. data/lib/link_thumbnailer/opengraph.rb +20 -0
  24. data/lib/link_thumbnailer/rails/routes.rb +47 -0
  25. data/lib/link_thumbnailer/rails/routes/mapper.rb +30 -0
  26. data/lib/link_thumbnailer/rails/routes/mapping.rb +33 -0
  27. data/lib/link_thumbnailer/version.rb +3 -0
  28. data/lib/link_thumbnailer/web_image.rb +18 -0
  29. data/link_thumbnailer.gemspec +28 -0
  30. data/spec/doc_parser_spec.rb +25 -0
  31. data/spec/doc_spec.rb +23 -0
  32. data/spec/examples/empty_example.html +11 -0
  33. data/spec/examples/example.html +363 -0
  34. data/spec/examples/og_example.html +12 -0
  35. data/spec/fetcher_spec.rb +97 -0
  36. data/spec/img_comparator_spec.rb +16 -0
  37. data/spec/img_url_filter_spec.rb +31 -0
  38. data/spec/link_thumbnailer_spec.rb +205 -0
  39. data/spec/object_spec.rb +130 -0
  40. data/spec/opengraph_spec.rb +7 -0
  41. data/spec/spec_helper.rb +13 -0
  42. data/spec/web_image_spec.rb +57 -0
  43. metadata +245 -0
@@ -0,0 +1,6 @@
1
+ require 'hashie'
2
+
3
+ module LinkThumbnailer
4
+ class Configuration < Hashie::Mash
5
+ end
6
+ end
@@ -0,0 +1,65 @@
1
+ require 'uri'
2
+
3
+ module LinkThumbnailer
4
+
5
+ module Doc
6
+
7
+ def doc_base_href
8
+ base = at('//head/base')
9
+ base['href'] if base
10
+ end
11
+
12
+ def img_srcs
13
+ search('//img').map { |i| i['src'] }.compact
14
+ end
15
+
16
+ def img_abs_urls(base_url = nil)
17
+ result = []
18
+
19
+ img_srcs.each do |i|
20
+ begin
21
+ u = URI(i)
22
+ rescue URI::InvalidURIError
23
+ next
24
+ end
25
+
26
+ result << if u.is_a?(URI::HTTP)
27
+ u
28
+ else
29
+ URI.join(base_url || doc_base_href || source_url, i)
30
+ end
31
+ end
32
+
33
+ result
34
+ end
35
+
36
+ def title
37
+ css('title').text.strip
38
+ end
39
+
40
+ def description
41
+ if element = xpath("//meta[translate(@name,'ABCDEFGHIJKLMNOPQRSTUVWXYZ','abcdefghijklmnopqrstuvwxyz') = 'description' and @content]").first
42
+ return element.attributes['content'].value.strip
43
+ end
44
+
45
+ css('body p').each do |node|
46
+ if !node.has_attribute?('style') && node.first_element_child.nil?
47
+ return node.text.strip
48
+ end
49
+ end
50
+
51
+ nil
52
+ end
53
+
54
+ def canonical_url
55
+ if element = xpath("//link[translate(@rel, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ','abcdefghijklmnopqrstuvwxyz') = 'canonical' and @href]").first
56
+ return element.attributes['href'].value.strip
57
+ end
58
+ nil
59
+ end
60
+
61
+ attr_accessor :source_url
62
+
63
+ end
64
+
65
+ end
@@ -0,0 +1,15 @@
1
+ require 'nokogiri'
2
+
3
+ module LinkThumbnailer
4
+
5
+ class DocParser
6
+
7
+ def parse(doc_string, source_url = nil)
8
+ doc = Nokogiri::HTML(doc_string).extend(LinkThumbnailer::Doc)
9
+ doc.source_url = source_url
10
+ doc
11
+ end
12
+
13
+ end
14
+
15
+ end
@@ -0,0 +1,9 @@
1
+ module LinkThumbnailer
2
+ class Engine < Rails::Engine
3
+
4
+ initializer 'link_thumbnailer.routes' do
5
+ LinkThumbnailer::Rails::Routes.install!
6
+ end
7
+
8
+ end
9
+ end
@@ -0,0 +1,34 @@
1
+ require 'net/http/persistent'
2
+
3
+ module LinkThumbnailer
4
+
5
+ class Fetcher
6
+
7
+ attr_accessor :url
8
+
9
+ def fetch(url, redirect_count = 0)
10
+ if redirect_count > LinkThumbnailer.configuration.redirect_limit
11
+ raise ArgumentError, "too many redirects (#{redirect_count})"
12
+ end
13
+
14
+ self.url = url.is_a?(URI) ? url : URI(url)
15
+
16
+ if self.url.is_a?(URI::HTTP)
17
+ http = Net::HTTP::Persistent.new('linkthumbnailer')
18
+ http.headers['User-Agent'] = LinkThumbnailer.configuration.user_agent
19
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE unless LinkThumbnailer.configuration.verify_ssl
20
+ http.open_timeout = LinkThumbnailer.configuration.http_timeout
21
+ resp = http.request(self.url)
22
+ case resp
23
+ when Net::HTTPSuccess then resp.body
24
+ when Net::HTTPRedirection
25
+ location = resp['location'].start_with?('http') ? resp['location'] : "#{self.url.scheme}://#{self.url.host}#{resp['location']}"
26
+ fetch(location, redirect_count + 1)
27
+ else resp.error!
28
+ end
29
+ end
30
+ end
31
+
32
+ end
33
+
34
+ end
@@ -0,0 +1,18 @@
1
+ module LinkThumbnailer
2
+
3
+ module ImgComparator
4
+
5
+ def <=> other
6
+ result = ([other.rows, other.columns].min ** 2) <=>
7
+ ([rows, columns].min ** 2)
8
+
9
+ if result == 0
10
+ result = other.number_colors <=> number_colors
11
+ end
12
+
13
+ result
14
+ end
15
+
16
+ end
17
+
18
+ end
@@ -0,0 +1,46 @@
1
+ require 'RMagick'
2
+
3
+ module LinkThumbnailer
4
+
5
+ class ImgParser
6
+
7
+ def initialize(fetcher, img_url_filter)
8
+ @fetcher = fetcher
9
+ @img_url_filters = [*img_url_filter]
10
+ end
11
+
12
+ def parse(img_urls)
13
+ @img_url_filters.each do |filter|
14
+ img_urls.delete_if { |i| filter.reject?(i) }
15
+ end
16
+
17
+ imgs = []
18
+ count = 0
19
+ img_urls.each { |i|
20
+ break if count >= LinkThumbnailer.configuration.limit
21
+ img = parse_one(i)
22
+ next unless img
23
+ img.extend LinkThumbnailer::ImgComparator
24
+ imgs << img
25
+ count += 1
26
+ }
27
+
28
+ imgs.sort! unless imgs.count <= 1
29
+
30
+ imgs.first(LinkThumbnailer.configuration.top)
31
+ end
32
+
33
+ def parse_one(img_url)
34
+ img_data = @fetcher.fetch(img_url)
35
+ img = Magick::ImageList.new.from_blob(img_data).extend(
36
+ LinkThumbnailer::WebImage
37
+ )
38
+ img.source_url = img_url
39
+ img
40
+ rescue StandardError
41
+ nil
42
+ end
43
+
44
+ end
45
+
46
+ end
@@ -0,0 +1,13 @@
1
+ module LinkThumbnailer
2
+ class ImgUrlFilter
3
+
4
+ def reject?(img_url)
5
+ LinkThumbnailer.configuration.blacklist_urls.each do |url|
6
+ return true if img_url && img_url.to_s[url]
7
+ end
8
+
9
+ false
10
+ end
11
+
12
+ end
13
+ end
@@ -0,0 +1,41 @@
1
+ require 'hashie'
2
+ require 'json'
3
+
4
+ module LinkThumbnailer
5
+ class Object < Hashie::Mash
6
+
7
+ def method_missing(method_name, *args, &block)
8
+ method_name = method_name.to_s
9
+
10
+ if method_name.end_with?('?')
11
+ method_name.chop!
12
+ !self[method_name].nil?
13
+ else
14
+ self[method_name]
15
+ end
16
+ end
17
+
18
+ def valid?
19
+ return false if self.keys.empty?
20
+ LinkThumbnailer.configuration.mandatory_attributes.each {|a| return false if self[a].nil? || self[a].empty? } if LinkThumbnailer.configuration.strict
21
+ true
22
+ end
23
+
24
+ def to_hash
25
+ if self.images.none? {|i| i.is_a?(String)}
26
+ super.merge('images' => self.images.map(&:to_hash))
27
+ else
28
+ super
29
+ end
30
+ end
31
+
32
+ def to_json
33
+ if self.images.none? {|i| i.is_a?(String)}
34
+ JSON.generate(self.to_hash.merge('images' => self.images.map(&:to_hash)))
35
+ else
36
+ JSON.generate(self.to_hash)
37
+ end
38
+ end
39
+
40
+ end
41
+ end
@@ -0,0 +1,20 @@
1
+ module LinkThumbnailer
2
+ class Opengraph
3
+
4
+ def self.parse(object, doc)
5
+ doc.css('meta').each do |m|
6
+ if m.attribute('property') && m.attribute('property').to_s.match(/^og:(.+)$/i)
7
+ object[$1.gsub('-', '_')] = m.attribute('content').to_s
8
+ end
9
+ end
10
+
11
+ object[:images] = []
12
+ if object[:image]
13
+ object[:images] << { source_url: object[:image] }
14
+ end
15
+
16
+ object
17
+ end
18
+
19
+ end
20
+ end
@@ -0,0 +1,47 @@
1
+ require 'link_thumbnailer/rails/routes/mapping'
2
+ require 'link_thumbnailer/rails/routes/mapper'
3
+
4
+ module LinkThumbnailer
5
+ module Rails
6
+ class Routes
7
+
8
+ module Helper
9
+ def use_link_thumbnailer(options = {}, &block)
10
+ LinkThumbnailer::Rails::Routes.new(self, &block).generate_routes!(options)
11
+ end
12
+ end
13
+
14
+ def self.install!
15
+ ActionDispatch::Routing::Mapper.send(:include, LinkThumbnailer::Rails::Routes::Helper)
16
+ end
17
+
18
+ attr_accessor :routes
19
+
20
+ def initialize(routes, &options)
21
+ @routes, @options = routes, options
22
+ end
23
+
24
+ def generate_routes!(options)
25
+ @mapping = Mapper.new.map(&@options)
26
+ routes.scope 'link', as: 'link' do
27
+ map_route(:previews, :preview_routes)
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ def map_route(name, method)
34
+ unless @mapping.skipped?(name)
35
+ send method, @mapping[name]
36
+ end
37
+ end
38
+
39
+ def preview_routes(mapping)
40
+ routes.scope controller: mapping[:controllers] do
41
+ routes.match 'preview', via: :post, action: :create, as: mapping[:as], defaults: { format: 'json' }
42
+ end
43
+ end
44
+
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,30 @@
1
+ module LinkThumbnailer
2
+ module Rails
3
+ class Routes
4
+ class Mapper
5
+
6
+ def initialize(mapping = Mapping.new)
7
+ @mapping = mapping
8
+ end
9
+
10
+ def map(&block)
11
+ self.instance_eval(&block) if block
12
+ @mapping
13
+ end
14
+
15
+ def controllers(controller_names = {})
16
+ @mapping.controllers.merge!(controller_names)
17
+ end
18
+
19
+ def skip_controllers(*controller_names)
20
+ @mapping.skips = controller_names
21
+ end
22
+
23
+ def as(alias_names = {})
24
+ @mapping.as.merge!(alias_names)
25
+ end
26
+
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,33 @@
1
+ module LinkThumbnailer
2
+ module Rails
3
+ class Routes
4
+ class Mapping
5
+
6
+ attr_accessor :controllers, :as, :skips
7
+
8
+ def initialize
9
+ @controllers = {
10
+ previews: 'link_thumbnailer/previews'
11
+ }
12
+
13
+ @as = {
14
+ previews: :preview
15
+ }
16
+
17
+ @skips = []
18
+ end
19
+
20
+ def [](routes)
21
+ {
22
+ controllers: @controllers[routes],
23
+ as: @as[routes]
24
+ }
25
+ end
26
+
27
+ def skipped?(controller)
28
+ @skips.include?(controller)
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,3 @@
1
+ module LinkThumbnailer
2
+ VERSION = "1.0.9.1"
3
+ end
@@ -0,0 +1,18 @@
1
+ module LinkThumbnailer
2
+ module WebImage
3
+
4
+ attr_accessor :source_url, :doc
5
+
6
+ def to_hash
7
+ result = {}
8
+ LinkThumbnailer.configuration.rmagick_attributes.each {|m|
9
+ k = m.to_sym
10
+ result[k] = self.send(m) if self.respond_to?(m)
11
+ result[k] = result[k].to_s if result[k].is_a?(URI)
12
+ }
13
+
14
+ result
15
+ end
16
+
17
+ end
18
+ end
@@ -0,0 +1,28 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/link_thumbnailer/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Pierre-Louis Gottfrois"]
6
+ gem.email = ["pierrelouis.gottfrois@gmail.com"]
7
+ gem.description = %q{Ruby gem generating thumbnail images from a given URL.}
8
+ gem.summary = %q{Ruby gem ranking images from a given URL returning an object containing images and website informations.}
9
+ gem.homepage = "https://github.com/gottfrois/link_thumbnailer"
10
+
11
+ gem.files = `git ls-files`.split($\)
12
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
13
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
14
+ gem.name = "rcarvalho-link_thumbnailer"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = LinkThumbnailer::VERSION
17
+
18
+ gem.add_dependency 'rake', '>= 0.9'
19
+ gem.add_dependency 'nokogiri', '>= 1.5.5'
20
+ gem.add_dependency 'hashie', '>= 1.2.0'
21
+ gem.add_dependency 'net-http-persistent', '>= 2.7'
22
+ gem.add_dependency 'rmagick', '>= 2.13.1'
23
+ gem.add_dependency 'json', '>= 1.7.6'
24
+
25
+ gem.add_development_dependency 'bundler', '>= 1.3'
26
+ gem.add_development_dependency 'rspec', '>= 2.14'
27
+ gem.add_development_dependency 'pry', '>= 0.9'
28
+ end