favicon_get 0.1.0

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: d657adc90f58864d09ec522c2ed539192dee5a763343d27020f2f7f66e5d246a
4
+ data.tar.gz: 1da472164378198227a1d334d45d68f5a5067e1cf01ee352f7c75263dfe607ae
5
+ SHA512:
6
+ metadata.gz: 61b4694f5cf3c85c729f29b01fc2830ca31a4d592372e3885dac8e6cfdcc1a6cbbe85db7c3502328e8a5e8439d2628aa0a32d1eb5a64ae02a323bba6322a6c21
7
+ data.tar.gz: c411c9ca0ac415a3b29d8e54894a5fe8ac70944eea3f0a05abe37b4f2fd3e96c0baf5b053eab920611a257e9090c91c9ffb49226b55fa8fd22fb7c57921d6080
data/README.md ADDED
@@ -0,0 +1,83 @@
1
+ # FaviconGet
2
+
3
+ FaviconGet is a Ruby gem for finding and retrieving website favicons (icons). It's a port of the popular [favicon](https://github.com/scottwernervt/favicon) Python library.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'favicon_get'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ ```bash
16
+ $ bundle install
17
+ ```
18
+
19
+ Or install it manually:
20
+
21
+ ```bash
22
+ $ gem install favicon_get
23
+ ```
24
+
25
+ ## Usage
26
+
27
+ ### Get all icons
28
+
29
+ ```ruby
30
+ require 'favicon_get'
31
+
32
+ icons = FaviconGet.get('https://www.ruby-lang.org/')
33
+ # => [Icon, Icon, Icon, ...]
34
+
35
+ # The first icon is usually the largest
36
+ icon = icons.first
37
+ puts "URL: #{icon.url}"
38
+ puts "Size: #{icon.width}x#{icon.height}"
39
+ puts "Format: #{icon.format}"
40
+ ```
41
+
42
+ ### Download an icon
43
+
44
+ ```ruby
45
+ require 'favicon_get'
46
+ require 'open-uri'
47
+
48
+ icons = FaviconGet.get('https://www.ruby-lang.org/')
49
+ icon = icons.first
50
+
51
+ URI.open(icon.url) do |image|
52
+ File.open("/tmp/ruby-favicon.#{icon.format}", "wb") do |file|
53
+ file.write(image.read)
54
+ end
55
+ end
56
+
57
+ # => /tmp/ruby-favicon.png
58
+ ```
59
+
60
+ ### Additional parameters
61
+
62
+ ```ruby
63
+ require 'favicon_get'
64
+
65
+ # Custom headers
66
+ headers = {
67
+ 'User-Agent' => 'My custom User-Agent'
68
+ }
69
+
70
+ # Timeout and other parameters
71
+ FaviconGet.get('https://www.ruby-lang.org/',
72
+ headers: headers,
73
+ timeout: 5)
74
+ ```
75
+
76
+ ## Requirements
77
+
78
+ * [nokogiri](https://github.com/sparklemotion/nokogiri) - for HTML parsing
79
+ * [faraday](https://github.com/lostisland/faraday) - for HTTP requests
80
+
81
+ ## License
82
+
83
+ This gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FaviconGet
4
+ VERSION = '0.1.0'
5
+ end
@@ -0,0 +1,213 @@
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
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FaviconGet
4
+ VERSION = '0.1.0'
5
+ end
@@ -0,0 +1,213 @@
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
metadata ADDED
@@ -0,0 +1,150 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: favicon_get
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Ivan Nemytchenko
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2025-04-01 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: faraday
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.7'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.7'
27
+ - !ruby/object:Gem::Dependency
28
+ name: faraday-follow_redirects
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.3'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.3'
41
+ - !ruby/object:Gem::Dependency
42
+ name: nokogiri
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.14'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.14'
55
+ - !ruby/object:Gem::Dependency
56
+ name: minitest
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '5.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '5.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '13.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '13.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rubocop
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '1.50'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '1.50'
97
+ - !ruby/object:Gem::Dependency
98
+ name: webmock
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '3.18'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '3.18'
111
+ description: favicon_get is a Ruby library to find a website's favicon, ported from
112
+ Python's favicon library
113
+ email:
114
+ - nemytchenko@gmail.com
115
+ executables: []
116
+ extensions: []
117
+ extra_rdoc_files: []
118
+ files:
119
+ - README.md
120
+ - lib/favicon_gem.rb
121
+ - lib/favicon_gem/version.rb
122
+ - lib/favicon_get.rb
123
+ - lib/favicon_get/version.rb
124
+ homepage: https://github.com/inem/favicon
125
+ licenses:
126
+ - MIT
127
+ metadata:
128
+ homepage_uri: https://github.com/inem/favicon
129
+ source_code_uri: https://github.com/inem/favicon
130
+ changelog_uri: https://github.com/inem/favicon/blob/main/CHANGELOG.md
131
+ post_install_message:
132
+ rdoc_options: []
133
+ require_paths:
134
+ - lib
135
+ required_ruby_version: !ruby/object:Gem::Requirement
136
+ requirements:
137
+ - - ">="
138
+ - !ruby/object:Gem::Version
139
+ version: 2.6.0
140
+ required_rubygems_version: !ruby/object:Gem::Requirement
141
+ requirements:
142
+ - - ">="
143
+ - !ruby/object:Gem::Version
144
+ version: '0'
145
+ requirements: []
146
+ rubygems_version: 3.2.32
147
+ signing_key:
148
+ specification_version: 4
149
+ summary: Ruby gem to find a website's favicon
150
+ test_files: []