favicon_get 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d657adc90f58864d09ec522c2ed539192dee5a763343d27020f2f7f66e5d246a
4
- data.tar.gz: 1da472164378198227a1d334d45d68f5a5067e1cf01ee352f7c75263dfe607ae
3
+ metadata.gz: 78bdf48e651f3062901d7c6750ebb960caf7dc2086aa950be3d5e456a8f438aa
4
+ data.tar.gz: 981d30c87c1fa9e6ae039ceab2d625551f9512c445f45c417284477cb0d6db79
5
5
  SHA512:
6
- metadata.gz: 61b4694f5cf3c85c729f29b01fc2830ca31a4d592372e3885dac8e6cfdcc1a6cbbe85db7c3502328e8a5e8439d2628aa0a32d1eb5a64ae02a323bba6322a6c21
7
- data.tar.gz: c411c9ca0ac415a3b29d8e54894a5fe8ac70944eea3f0a05abe37b4f2fd3e96c0baf5b053eab920611a257e9090c91c9ffb49226b55fa8fd22fb7c57921d6080
6
+ metadata.gz: a0ad54d1e6657dcb345dc1de8da3036b0ce5a5dcdb0b9fa47a08fa871912684d761a6a88d0cb7bcca4e5b74fc3eb7685e1f08eda8df9d1bbafe9508162058dc2
7
+ data.tar.gz: '09bd2a117bffab266e4a166c9ef19a5e07c71a9c284d1314ff82e8353785c073ac7b52f350dfc2546204c57eec11c1789184aaa6610146380fe676418f28f9a8'
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module FaviconGet
4
- VERSION = '0.1.0'
4
+ VERSION = '0.1.1'
5
5
  end
data/lib/favicon_get.rb CHANGED
@@ -165,7 +165,7 @@ module FaviconGet
165
165
  ext = File.extname(URI.parse(url_parsed).path)[1..]&.downcase || ""
166
166
 
167
167
  icons.add(Icon.new(url_parsed, width, height, ext))
168
- rescue URI::InvalidURIError => e
168
+ rescue URI::InvalidURIError
169
169
  # Skip invalid URIs
170
170
  next
171
171
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: favicon_get
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ivan Nemytchenko
@@ -117,8 +117,6 @@ extensions: []
117
117
  extra_rdoc_files: []
118
118
  files:
119
119
  - README.md
120
- - lib/favicon_gem.rb
121
- - lib/favicon_gem/version.rb
122
120
  - lib/favicon_get.rb
123
121
  - lib/favicon_get/version.rb
124
122
  homepage: https://github.com/inem/favicon
@@ -1,5 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module FaviconGet
4
- VERSION = '0.1.0'
5
- end
data/lib/favicon_gem.rb DELETED
@@ -1,213 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "favicon_get/version"
4
- require "faraday"
5
- require "faraday/follow_redirects"
6
- require "nokogiri"
7
- require "uri"
8
- require "set"
9
-
10
- module FaviconGet
11
- class Error < StandardError; end
12
-
13
- # Website icon representation
14
- Icon = Struct.new(:url, :width, :height, :format)
15
-
16
- # Gem metadata
17
- TITLE = "favicon_get"
18
- AUTHOR = "Ported from Python favicon by Scott Werner"
19
- LICENSE = "MIT"
20
-
21
- HEADERS = {
22
- "User-Agent" => "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) " \
23
- "AppleWebKit/537.36 (KHTML, like Gecko) " \
24
- "Chrome/33.0.1750.152 Safari/537.36"
25
- }
26
-
27
- LINK_RELS = [
28
- "icon",
29
- "shortcut icon",
30
- "apple-touch-icon",
31
- "apple-touch-icon-precomposed"
32
- ]
33
-
34
- META_NAMES = ["msapplication-TileImage"] # Removed og:image from metatags as it's usually not a favicon
35
-
36
- # Format priorities (higher = better)
37
- FORMAT_PRIORITY = {
38
- "ico" => 10,
39
- "png" => 9,
40
- "jpg" => 8,
41
- "jpeg" => 7,
42
- "svg" => 6,
43
- "gif" => 5,
44
- "" => 0 # Unknown format has the lowest priority
45
- }
46
-
47
- SIZE_RE = /(?<width>\d{2,4})x(?<height>\d{2,4})/i
48
-
49
- class << self
50
- # Get all icons for a URL
51
- #
52
- # @param url [String] Page URL
53
- # @param headers [Hash] Request headers
54
- # @return [Array<Icon>] List of found icons, sorted by size
55
- def get(url, headers: HEADERS, **request_options)
56
- request_options[:headers] ||= headers
57
-
58
- conn = Faraday.new(url: url) do |faraday|
59
- faraday.request :url_encoded
60
- faraday.headers = request_options[:headers]
61
- faraday.options.timeout = request_options[:timeout] if request_options[:timeout]
62
- faraday.use Faraday::FollowRedirects::Middleware
63
- faraday.adapter Faraday.default_adapter
64
- end
65
-
66
- response = conn.get
67
- raise Error, "Failed to fetch URL: #{response.status}" unless response.success?
68
-
69
- icons = Set.new
70
-
71
- default_icon = default(response.env.url.to_s, **request_options)
72
- icons.add(default_icon) if default_icon
73
-
74
- link_icons = tags(response.env.url.to_s, response.body)
75
- icons.merge(link_icons) if link_icons.any?
76
-
77
- # Improve sorting:
78
- # 1. By size (larger first)
79
- # 2. If sizes are equal, sort by format (ico/png have higher priority)
80
- # 3. All icons with zero sizes go to the end
81
- icons.to_a.sort_by do |icon|
82
- format_priority = FORMAT_PRIORITY[icon.format] || 0
83
- size = icon.width + icon.height
84
-
85
- if size > 0
86
- [1, size, format_priority] # Icons with non-zero size first
87
- else
88
- [0, format_priority] # Zero sizes - second priority
89
- end
90
- end.reverse
91
- end
92
-
93
- private
94
-
95
- # Get icon using default filename favicon.ico
96
- #
97
- # @param url [String] Site URL
98
- # @param request_options [Hash] Request parameters
99
- # @return [Icon, nil] Icon or nil
100
- def default(url, **request_options)
101
- uri = URI.parse(url)
102
- # Preserve port if explicitly specified
103
- port_part = uri.port == uri.default_port ? "" : ":#{uri.port}"
104
- favicon_url = "#{uri.scheme}://#{uri.host}#{port_part}/favicon.ico"
105
-
106
- conn = Faraday.new(url: favicon_url) do |faraday|
107
- faraday.headers = request_options[:headers] if request_options[:headers]
108
- faraday.options.timeout = request_options[:timeout] if request_options[:timeout]
109
- faraday.use Faraday::FollowRedirects::Middleware
110
- faraday.adapter Faraday.default_adapter
111
- end
112
-
113
- response = conn.head
114
- return Icon.new(response.env.url.to_s, 0, 0, "ico") if response.success?
115
- nil
116
- rescue Faraday::Error
117
- nil
118
- end
119
-
120
- # Get icons from link and meta tags
121
- #
122
- # @param url [String] Site URL
123
- # @param html [String] Page HTML code
124
- # @return [Set<Icon>] Found icons
125
- def tags(url, html)
126
- doc = Nokogiri::HTML(html)
127
- icons = Set.new
128
-
129
- # Search in <link> tags
130
- link_tags = Set.new
131
- LINK_RELS.each do |rel|
132
- doc.css("link[rel='#{rel}'][href]").each do |link_tag|
133
- link_tags.add(link_tag)
134
- end
135
- end
136
-
137
- # Search in <meta> tags
138
- meta_tags = Set.new
139
- META_NAMES.each do |name|
140
- doc.css("meta[name='#{name}'][content], meta[property='#{name}'][content]").each do |meta_tag|
141
- meta_tags.add(meta_tag)
142
- end
143
- end
144
-
145
- (link_tags | meta_tags).each do |tag|
146
- href = tag["href"] || tag["content"] || ""
147
- href = href.strip.gsub(/\s+/, "") # Remove all whitespace, including tabs
148
-
149
- next if href.empty? || href.start_with?("data:image/")
150
-
151
- begin
152
- # Fix URLs like '//cdn.network.com/favicon.png'
153
- if href.start_with?("//")
154
- uri = URI.parse(url)
155
- href = "#{uri.scheme}:#{href}"
156
- end
157
-
158
- url_parsed = if is_absolute(href)
159
- href
160
- else
161
- URI.join(url, href).to_s
162
- end
163
-
164
- width, height = dimensions(tag)
165
- ext = File.extname(URI.parse(url_parsed).path)[1..]&.downcase || ""
166
-
167
- icons.add(Icon.new(url_parsed, width, height, ext))
168
- rescue URI::InvalidURIError => e
169
- # Skip invalid URIs
170
- next
171
- end
172
- end
173
-
174
- icons
175
- end
176
-
177
- # Check if URL is absolute
178
- #
179
- # @param url [String] URL
180
- # @return [Boolean] true if absolute
181
- def is_absolute(url)
182
- uri = URI.parse(url)
183
- !uri.host.nil?
184
- rescue URI::InvalidURIError
185
- false
186
- end
187
-
188
- # Get icon dimensions from size attribute or filename
189
- #
190
- # @param tag [Nokogiri::XML::Element] Link or meta tag
191
- # @return [Array<Integer>] Width and height, or [0,0]
192
- def dimensions(tag)
193
- sizes = tag["sizes"]
194
- if sizes && sizes != "any"
195
- size = sizes.split(" ").max_by { |s| s.scan(/\d+/).map(&:to_i).sum }
196
- width, height = size.split(/[x×]/)
197
- else
198
- filename = tag["href"] || tag["content"] || ""
199
- size = SIZE_RE.match(filename)
200
- if size
201
- width, height = size[:width], size[:height]
202
- else
203
- width, height = "0", "0"
204
- end
205
- end
206
-
207
- # Clean non-digit characters
208
- width = width.to_s.scan(/\d+/).join
209
- height = height.to_s.scan(/\d+/).join
210
- [width.to_i, height.to_i]
211
- end
212
- end
213
- end