browserx 1.0.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 +7 -0
- data/CHANGELOG.md +29 -0
- data/LICENSE.md +21 -0
- data/README.md +438 -0
- data/bot_exceptions.yml +5 -0
- data/bots.yml +327 -0
- data/languages.yml +113 -0
- data/lib/browser/accept_language.rb +55 -0
- data/lib/browser/action_controller.rb +20 -0
- data/lib/browser/aliases.rb +33 -0
- data/lib/browser/alipay.rb +21 -0
- data/lib/browser/base.rb +292 -0
- data/lib/browser/blackberry.rb +23 -0
- data/lib/browser/bot/empty_user_agent_matcher.rb +11 -0
- data/lib/browser/bot/keyword_matcher.rb +11 -0
- data/lib/browser/bot/known_bots_matcher.rb +11 -0
- data/lib/browser/bot.rb +119 -0
- data/lib/browser/browser.rb +116 -0
- data/lib/browser/chrome.rb +43 -0
- data/lib/browser/detect_version.rb +22 -0
- data/lib/browser/device/android.rb +20 -0
- data/lib/browser/device/base.rb +13 -0
- data/lib/browser/device/blackberry_playbook.rb +19 -0
- data/lib/browser/device/ipad.rb +19 -0
- data/lib/browser/device/iphone.rb +19 -0
- data/lib/browser/device/ipod_touch.rb +19 -0
- data/lib/browser/device/kindle.rb +19 -0
- data/lib/browser/device/kindle_fire.rb +19 -0
- data/lib/browser/device/playstation3.rb +19 -0
- data/lib/browser/device/playstation4.rb +19 -0
- data/lib/browser/device/psp.rb +19 -0
- data/lib/browser/device/psvita.rb +19 -0
- data/lib/browser/device/samsung.rb +33 -0
- data/lib/browser/device/surface.rb +23 -0
- data/lib/browser/device/switch.rb +19 -0
- data/lib/browser/device/tv.rb +19 -0
- data/lib/browser/device/unknown.rb +19 -0
- data/lib/browser/device/wii.rb +19 -0
- data/lib/browser/device/wiiu.rb +19 -0
- data/lib/browser/device/xbox_360.rb +19 -0
- data/lib/browser/device/xbox_one.rb +19 -0
- data/lib/browser/device.rb +222 -0
- data/lib/browser/duck_duck_go.rb +22 -0
- data/lib/browser/edge.rb +25 -0
- data/lib/browser/electron.rb +22 -0
- data/lib/browser/epiphany.rb +21 -0
- data/lib/browser/facebook.rb +23 -0
- data/lib/browser/firefox.rb +21 -0
- data/lib/browser/google_search_app.rb +21 -0
- data/lib/browser/huawei_browser.rb +21 -0
- data/lib/browser/instagram.rb +21 -0
- data/lib/browser/internet_explorer.rb +63 -0
- data/lib/browser/maxthon.rb +21 -0
- data/lib/browser/meta/base.rb +42 -0
- data/lib/browser/meta/device.rb +11 -0
- data/lib/browser/meta/generic_browser.rb +15 -0
- data/lib/browser/meta/id.rb +11 -0
- data/lib/browser/meta/ie.rb +20 -0
- data/lib/browser/meta/ios.rb +11 -0
- data/lib/browser/meta/mobile.rb +11 -0
- data/lib/browser/meta/platform.rb +11 -0
- data/lib/browser/meta/proxy.rb +11 -0
- data/lib/browser/meta/safari.rb +11 -0
- data/lib/browser/meta/tablet.rb +11 -0
- data/lib/browser/meta/webkit.rb +11 -0
- data/lib/browser/meta.rb +14 -0
- data/lib/browser/micro_messenger.rb +21 -0
- data/lib/browser/middleware/context/additions.rb +18 -0
- data/lib/browser/middleware/context/url_methods.rb +15 -0
- data/lib/browser/middleware/context.rb +22 -0
- data/lib/browser/middleware.rb +70 -0
- data/lib/browser/miui_browser.rb +21 -0
- data/lib/browser/nokia.rb +21 -0
- data/lib/browser/opera.rb +21 -0
- data/lib/browser/otter.rb +21 -0
- data/lib/browser/phantom_js.rb +21 -0
- data/lib/browser/platform/adobe_air.rb +23 -0
- data/lib/browser/platform/android.rb +23 -0
- data/lib/browser/platform/base.rb +18 -0
- data/lib/browser/platform/blackberry.rb +23 -0
- data/lib/browser/platform/chrome_os.rb +23 -0
- data/lib/browser/platform/firefox_os.rb +23 -0
- data/lib/browser/platform/ios.rb +36 -0
- data/lib/browser/platform/kai_os.rb +23 -0
- data/lib/browser/platform/linux.rb +23 -0
- data/lib/browser/platform/mac.rb +25 -0
- data/lib/browser/platform/unknown.rb +23 -0
- data/lib/browser/platform/windows.rb +23 -0
- data/lib/browser/platform/windows_mobile.rb +23 -0
- data/lib/browser/platform/windows_phone.rb +23 -0
- data/lib/browser/platform.rb +193 -0
- data/lib/browser/qq.rb +25 -0
- data/lib/browser/rails.rb +23 -0
- data/lib/browser/safari.rb +40 -0
- data/lib/browser/samsung_browser.rb +21 -0
- data/lib/browser/snapchat.rb +21 -0
- data/lib/browser/sougou_browser.rb +24 -0
- data/lib/browser/sputnik.rb +21 -0
- data/lib/browser/testing.rb +27 -0
- data/lib/browser/uc_browser.rb +21 -0
- data/lib/browser/unknown.rb +32 -0
- data/lib/browser/version.rb +5 -0
- data/lib/browser/weibo.rb +21 -0
- data/lib/browser/yandex.rb +21 -0
- data/lib/browser.rb +4 -0
- data/lib/browserx.rb +3 -0
- data/samsung.yml +138 -0
- data/search_engines.yml +7 -0
- data/test/ua.yml +181 -0
- data/test/ua_bots.yml +121 -0
- data/test/ua_search_engines.yml +7 -0
- metadata +267 -0
data/lib/browser/base.rb
ADDED
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Browser
|
|
4
|
+
class Base
|
|
5
|
+
include DetectVersion
|
|
6
|
+
|
|
7
|
+
attr_reader :ua
|
|
8
|
+
|
|
9
|
+
def initialize(ua, accept_language: nil)
|
|
10
|
+
validate_size(:user_agent, ua.to_s)
|
|
11
|
+
|
|
12
|
+
@ua = ua
|
|
13
|
+
@accept_language_raw = accept_language.to_s
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Return a meta info about this browser.
|
|
17
|
+
def meta
|
|
18
|
+
Meta.get(self)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Return an array with all preferred languages that this browser accepts.
|
|
22
|
+
def accept_language
|
|
23
|
+
@accept_language ||= begin
|
|
24
|
+
validate_size(:accept_language, @accept_language_raw)
|
|
25
|
+
AcceptLanguage.parse(@accept_language_raw)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
alias_method :to_a, :meta
|
|
30
|
+
|
|
31
|
+
# Return meta representation as string.
|
|
32
|
+
def to_s
|
|
33
|
+
meta.to_a.join(" ")
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def version
|
|
37
|
+
full_version.split(".").first
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Return the platform.
|
|
41
|
+
def platform
|
|
42
|
+
@platform ||= Platform.new(ua)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Return the bot info.
|
|
46
|
+
def bot
|
|
47
|
+
@bot ||= Bot.new(ua)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Detect if current user agent is from a bot.
|
|
51
|
+
def bot?
|
|
52
|
+
bot.bot?
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Return the device info.
|
|
56
|
+
def device
|
|
57
|
+
@device ||= Device.new(ua, platform:)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Detect if browser is Microsoft Internet Explorer.
|
|
61
|
+
def ie?(expected_version = nil)
|
|
62
|
+
instance_of?(InternetExplorer) &&
|
|
63
|
+
detect_version?(full_version, expected_version)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Detect if browser is Microsoft Edge.
|
|
67
|
+
def edge?(expected_version = nil)
|
|
68
|
+
instance_of?(Edge) && detect_version?(full_version, expected_version)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def compatibility_view?
|
|
72
|
+
false
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def msie_full_version
|
|
76
|
+
"0.0"
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def msie_version
|
|
80
|
+
"0"
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Detect if browser is Instagram.
|
|
84
|
+
def instagram?(expected_version = nil)
|
|
85
|
+
instance_of?(Instagram) &&
|
|
86
|
+
detect_version?(full_version, expected_version)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Detect if browser is Snapchat.
|
|
90
|
+
def snapchat?(expected_version = nil)
|
|
91
|
+
instance_of?(Snapchat) &&
|
|
92
|
+
detect_version?(full_version, expected_version)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Detect if browser if Facebook.
|
|
96
|
+
def facebook?(expected_version = nil)
|
|
97
|
+
instance_of?(Facebook) &&
|
|
98
|
+
detect_version?(full_version, expected_version)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Detect if browser is Otter.
|
|
102
|
+
def otter?(expected_version = nil)
|
|
103
|
+
instance_of?(Otter) &&
|
|
104
|
+
detect_version?(full_version, expected_version)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Detect if browser is WebKit-based.
|
|
108
|
+
def webkit?(expected_version = nil)
|
|
109
|
+
ua.match?(/AppleWebKit/i) &&
|
|
110
|
+
(!edge? || chromium_based?) &&
|
|
111
|
+
detect_version?(webkit_full_version, expected_version)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Detect if browser is QuickTime
|
|
115
|
+
def quicktime?(expected_version = nil)
|
|
116
|
+
ua.match?(/QuickTime/i) && detect_version?(full_version, expected_version)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Detect if browser is Apple CoreMedia.
|
|
120
|
+
def core_media?(expected_version = nil)
|
|
121
|
+
ua.include?("CoreMedia") && detect_version?(full_version,
|
|
122
|
+
expected_version)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Detect if browser is PhantomJS
|
|
126
|
+
def phantom_js?(expected_version = nil)
|
|
127
|
+
instance_of?(PhantomJS) &&
|
|
128
|
+
detect_version?(full_version, expected_version)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Detect if browser is Safari.
|
|
132
|
+
def safari?(expected_version = nil)
|
|
133
|
+
instance_of?(Safari) && detect_version?(full_version, expected_version)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def safari_webapp_mode?
|
|
137
|
+
(device.ipad? || device.iphone?) && ua.include?("AppleWebKit")
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Detect if browser is Firefox.
|
|
141
|
+
def firefox?(expected_version = nil)
|
|
142
|
+
instance_of?(Firefox) && detect_version?(full_version, expected_version)
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Detect if browser is Chrome.
|
|
146
|
+
def chrome?(expected_version = nil)
|
|
147
|
+
instance_of?(Chrome) && detect_version?(full_version, expected_version)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# Detect if browser is Opera.
|
|
151
|
+
def opera?(expected_version = nil)
|
|
152
|
+
instance_of?(Opera) && detect_version?(full_version, expected_version)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Detect if browser is Sputnik.
|
|
156
|
+
def sputnik?(expected_version = nil)
|
|
157
|
+
instance_of?(Sputnik) && detect_version?(full_version, expected_version)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# Detect if browser is Yandex.
|
|
161
|
+
def yandex?(expected_version = nil)
|
|
162
|
+
instance_of?(Yandex) && detect_version?(full_version, expected_version)
|
|
163
|
+
end
|
|
164
|
+
alias_method :yandex_browser?, :yandex?
|
|
165
|
+
|
|
166
|
+
# Detect if browser is UCBrowser.
|
|
167
|
+
def uc_browser?(expected_version = nil)
|
|
168
|
+
instance_of?(UCBrowser) &&
|
|
169
|
+
detect_version?(full_version, expected_version)
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# Detect if browser is Nokia S40 Ovi Browser.
|
|
173
|
+
def nokia?(expected_version = nil)
|
|
174
|
+
instance_of?(Nokia) && detect_version?(full_version, expected_version)
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Detect if browser is MicroMessenger.
|
|
178
|
+
def micro_messenger?(expected_version = nil)
|
|
179
|
+
instance_of?(MicroMessenger) &&
|
|
180
|
+
detect_version?(full_version, expected_version)
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
alias_method :wechat?, :micro_messenger?
|
|
184
|
+
|
|
185
|
+
def weibo?(expected_version = nil)
|
|
186
|
+
instance_of?(Weibo) && detect_version?(full_version, expected_version)
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def alipay?(expected_version = nil)
|
|
190
|
+
instance_of?(Alipay) && detect_version?(full_version, expected_version)
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
# Detect if browser is Opera Mini.
|
|
194
|
+
def opera_mini?(expected_version = nil)
|
|
195
|
+
ua.include?("Opera Mini") && detect_version?(full_version,
|
|
196
|
+
expected_version)
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# Detect if browser is DuckDuckGo.
|
|
200
|
+
def duck_duck_go?(expected_version = nil)
|
|
201
|
+
ua.include?("DuckDuckGo") && detect_version?(full_version,
|
|
202
|
+
expected_version)
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# Detect if browser is Samsung.
|
|
206
|
+
def samsung_browser?(expected_version = nil)
|
|
207
|
+
ua.include?("SamsungBrowser") && detect_version?(full_version,
|
|
208
|
+
expected_version)
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
# Detect if browser is Huawei.
|
|
212
|
+
def huawei_browser?(expected_version = nil)
|
|
213
|
+
instance_of?(HuaweiBrowser) &&
|
|
214
|
+
detect_version?(full_version, expected_version)
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
# Detect if browser is Xiaomi Miui.
|
|
218
|
+
def miui_browser?(expected_version = nil)
|
|
219
|
+
instance_of?(MiuiBrowser) &&
|
|
220
|
+
detect_version?(full_version, expected_version)
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
# Detect if browser is Maxthon.
|
|
224
|
+
def maxthon?(expected_version = nil)
|
|
225
|
+
instance_of?(Maxthon) && detect_version?(full_version, expected_version)
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
# Detect if browser is QQ.
|
|
229
|
+
def qq?(expected_version = nil)
|
|
230
|
+
instance_of?(QQ) && detect_version?(full_version, expected_version)
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
# Detect if browser is Sougou.
|
|
234
|
+
def sougou_browser?(expected_version = nil)
|
|
235
|
+
instance_of?(SougouBrowser) &&
|
|
236
|
+
detect_version?(full_version, expected_version)
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
# Detect if browser is Google Search App
|
|
240
|
+
def google_search_app?(expected_version = nil)
|
|
241
|
+
ua.include?("GSA") && detect_version?(full_version, expected_version)
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
# Detect if browser is Chromium-based.
|
|
245
|
+
def chromium_based?
|
|
246
|
+
false
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
def webkit_full_version
|
|
250
|
+
ua[%r{AppleWebKit/([\d.]+)}, 1] || "0.0"
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def known?
|
|
254
|
+
!unknown?
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
def unknown?
|
|
258
|
+
id == :unknown_browser
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
# Detect if browser is a proxy browser.
|
|
262
|
+
def proxy?
|
|
263
|
+
nokia? || uc_browser? || opera_mini?
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
# Detect if the browser is Electron.
|
|
267
|
+
def electron?(expected_version = nil)
|
|
268
|
+
instance_of?(Electron) && detect_version?(full_version, expected_version)
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
# Detect if the browser is Epiphany.
|
|
272
|
+
def epiphany?(expected_version = nil)
|
|
273
|
+
instance_of?(Epiphany) && detect_version?(full_version, expected_version)
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
private def validate_size(subject, input)
|
|
277
|
+
actual_bytesize = input.bytesize
|
|
278
|
+
size_limit =
|
|
279
|
+
if subject == :user_agent
|
|
280
|
+
Browser.user_agent_size_limit
|
|
281
|
+
else
|
|
282
|
+
Browser.accept_language_size_limit
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
return if actual_bytesize < size_limit
|
|
286
|
+
|
|
287
|
+
raise Error,
|
|
288
|
+
"#{subject} cannot be larger than #{size_limit} bytes; " \
|
|
289
|
+
"actual size is #{actual_bytesize} bytes"
|
|
290
|
+
end
|
|
291
|
+
end
|
|
292
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Browser
|
|
4
|
+
class BlackBerry < Base
|
|
5
|
+
def id
|
|
6
|
+
:blackberry
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def name
|
|
10
|
+
"BlackBerry"
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def full_version
|
|
14
|
+
ua[%r{BlackBerry[\da-z]+/([\d.]+)}, 1] ||
|
|
15
|
+
ua[%r{Version/([\d.]+)}, 1] ||
|
|
16
|
+
"0.0"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def match?
|
|
20
|
+
ua.match?(/BlackBerry|BB10/)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
data/lib/browser/bot.rb
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Browser
|
|
4
|
+
class Bot
|
|
5
|
+
GENERIC_NAME = "Generic Bot"
|
|
6
|
+
|
|
7
|
+
def self.matchers
|
|
8
|
+
@matchers ||= default_matchers
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def self.default_matchers
|
|
12
|
+
DEFAULT_MATCHERS.dup
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def self.load_yaml(path)
|
|
16
|
+
YAML.load_file(Browser.root.join(path))
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def self.bots
|
|
20
|
+
@bots ||= load_yaml("bots.yml")
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def self.bot_exceptions
|
|
24
|
+
@bot_exceptions ||= load_yaml("bot_exceptions.yml")
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def self.search_engines
|
|
28
|
+
@search_engines ||= load_yaml("search_engines.yml")
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# One regexp per keyword list (an Array, or a Hash keyed by keyword),
|
|
32
|
+
# rebuilt when the list grows or is replaced.
|
|
33
|
+
def self.keywords_pattern(keywords)
|
|
34
|
+
@keywords_pattern_by_list ||= {}.compare_by_identity
|
|
35
|
+
size, pattern = @keywords_pattern_by_list[keywords]
|
|
36
|
+
return pattern if size == keywords.size
|
|
37
|
+
|
|
38
|
+
words = (keywords.is_a?(Hash) ? keywords.keys : keywords).map(&:to_s)
|
|
39
|
+
pattern = Regexp.new(trie_source(words))
|
|
40
|
+
@keywords_pattern_by_list[keywords] = [keywords.size, pattern]
|
|
41
|
+
pattern
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# A Regexp.union of hundreds of words tries each of them at every
|
|
45
|
+
# position; nested by common prefix, the regexp follows a single branch
|
|
46
|
+
# per character.
|
|
47
|
+
def self.trie_source(words)
|
|
48
|
+
trie = {}
|
|
49
|
+
words.each do |word|
|
|
50
|
+
leaf = word.each_char.reduce(trie) { |node, char| node[char] ||= {} }
|
|
51
|
+
leaf[:end] = true
|
|
52
|
+
end
|
|
53
|
+
trie_node_source(trie) || "(?!)"
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def self.trie_node_source(node)
|
|
57
|
+
# A shorter keyword matches wherever a longer one would.
|
|
58
|
+
return "" if node.key?(:end)
|
|
59
|
+
|
|
60
|
+
branches = node.map do |char, child|
|
|
61
|
+
Regexp.escape(char) + trie_node_source(child)
|
|
62
|
+
end
|
|
63
|
+
(branches.size == 1) ? branches.first : "(?:#{branches.join("|")})"
|
|
64
|
+
end
|
|
65
|
+
private_class_method :trie_source, :trie_node_source
|
|
66
|
+
|
|
67
|
+
def self.why?(ua)
|
|
68
|
+
ua = ua.downcase.strip
|
|
69
|
+
browser = Browser.new(ua)
|
|
70
|
+
matchers.find { |matcher| matcher.call(ua, browser) }
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
attr_reader :ua
|
|
74
|
+
|
|
75
|
+
def initialize(ua)
|
|
76
|
+
@ua = ua.downcase.strip
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def bot?
|
|
80
|
+
!bot_exception? && detect_bot?
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def why?
|
|
84
|
+
self.class.matchers.find { |matcher| matcher.call(ua, self) }
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def search_engine?
|
|
88
|
+
ua.match?(self.class.keywords_pattern(self.class.search_engines))
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def name
|
|
92
|
+
return unless bot?
|
|
93
|
+
|
|
94
|
+
self.class.bots.find { |key, _| ua.include?(key) }&.last || GENERIC_NAME
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
private def bot_exception?
|
|
98
|
+
ua.match?(self.class.keywords_pattern(self.class.bot_exceptions))
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
private def detect_bot?
|
|
102
|
+
self.class.matchers.any? do |matcher|
|
|
103
|
+
matcher.call(ua, default_matchers.include?(matcher) ? nil : browser)
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# The built-in matchers ignore the browser: building it would detect the
|
|
108
|
+
# user agent twice.
|
|
109
|
+
private def default_matchers
|
|
110
|
+
DEFAULT_MATCHERS
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
private def browser
|
|
114
|
+
@browser ||= Browser.new(ua)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
private :ua
|
|
118
|
+
end
|
|
119
|
+
end
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "yaml"
|
|
4
|
+
require "pathname"
|
|
5
|
+
|
|
6
|
+
require_relative "version"
|
|
7
|
+
require_relative "detect_version"
|
|
8
|
+
require_relative "accept_language"
|
|
9
|
+
require_relative "base"
|
|
10
|
+
require_relative "safari"
|
|
11
|
+
require_relative "chrome"
|
|
12
|
+
require_relative "internet_explorer"
|
|
13
|
+
require_relative "firefox"
|
|
14
|
+
require_relative "edge"
|
|
15
|
+
require_relative "opera"
|
|
16
|
+
require_relative "blackberry"
|
|
17
|
+
require_relative "unknown"
|
|
18
|
+
require_relative "phantom_js"
|
|
19
|
+
require_relative "uc_browser"
|
|
20
|
+
require_relative "nokia"
|
|
21
|
+
require_relative "micro_messenger"
|
|
22
|
+
require_relative "weibo"
|
|
23
|
+
require_relative "qq"
|
|
24
|
+
require_relative "alipay"
|
|
25
|
+
require_relative "electron"
|
|
26
|
+
require_relative "facebook"
|
|
27
|
+
require_relative "otter"
|
|
28
|
+
require_relative "instagram"
|
|
29
|
+
require_relative "yandex"
|
|
30
|
+
require_relative "sputnik"
|
|
31
|
+
require_relative "snapchat"
|
|
32
|
+
require_relative "duck_duck_go"
|
|
33
|
+
require_relative "samsung_browser"
|
|
34
|
+
require_relative "huawei_browser"
|
|
35
|
+
require_relative "miui_browser"
|
|
36
|
+
require_relative "maxthon"
|
|
37
|
+
require_relative "sougou_browser"
|
|
38
|
+
require_relative "google_search_app"
|
|
39
|
+
require_relative "epiphany"
|
|
40
|
+
|
|
41
|
+
require_relative "bot"
|
|
42
|
+
require_relative "bot/empty_user_agent_matcher"
|
|
43
|
+
require_relative "bot/keyword_matcher"
|
|
44
|
+
require_relative "bot/known_bots_matcher"
|
|
45
|
+
|
|
46
|
+
require_relative "middleware"
|
|
47
|
+
require_relative "platform"
|
|
48
|
+
require_relative "device"
|
|
49
|
+
require_relative "meta"
|
|
50
|
+
|
|
51
|
+
module Browser
|
|
52
|
+
EMPTY_STRING = ""
|
|
53
|
+
|
|
54
|
+
Bot::DEFAULT_MATCHERS =
|
|
55
|
+
[Bot::EmptyUserAgentMatcher, Bot::KnownBotsMatcher, Bot::KeywordMatcher]
|
|
56
|
+
.freeze
|
|
57
|
+
|
|
58
|
+
Error = Class.new(StandardError)
|
|
59
|
+
|
|
60
|
+
def self.root
|
|
61
|
+
@root ||= Pathname.new(File.expand_path("../..", __dir__))
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
class << self
|
|
65
|
+
attr_accessor :user_agent_size_limit, :accept_language_size_limit
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
self.user_agent_size_limit = 2048
|
|
69
|
+
self.accept_language_size_limit = 2048
|
|
70
|
+
|
|
71
|
+
# Hold the list of browser matchers.
|
|
72
|
+
# Order is important.
|
|
73
|
+
def self.matchers
|
|
74
|
+
@matchers ||= [
|
|
75
|
+
Nokia,
|
|
76
|
+
UCBrowser,
|
|
77
|
+
PhantomJS,
|
|
78
|
+
BlackBerry,
|
|
79
|
+
Opera,
|
|
80
|
+
Edge,
|
|
81
|
+
InternetExplorer,
|
|
82
|
+
Firefox,
|
|
83
|
+
Otter,
|
|
84
|
+
Epiphany,
|
|
85
|
+
Facebook,
|
|
86
|
+
Instagram,
|
|
87
|
+
Snapchat,
|
|
88
|
+
Weibo,
|
|
89
|
+
MicroMessenger, # must be placed before QQ
|
|
90
|
+
QQ,
|
|
91
|
+
Alipay,
|
|
92
|
+
Electron,
|
|
93
|
+
Yandex,
|
|
94
|
+
Sputnik,
|
|
95
|
+
DuckDuckGo,
|
|
96
|
+
SamsungBrowser,
|
|
97
|
+
HuaweiBrowser,
|
|
98
|
+
MiuiBrowser,
|
|
99
|
+
Maxthon,
|
|
100
|
+
SougouBrowser,
|
|
101
|
+
GoogleSearchApp,
|
|
102
|
+
|
|
103
|
+
# Must be last
|
|
104
|
+
Chrome,
|
|
105
|
+
Safari,
|
|
106
|
+
Unknown
|
|
107
|
+
]
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def self.new(user_agent, **)
|
|
111
|
+
matchers.each do |matcher_class|
|
|
112
|
+
matcher = matcher_class.new(user_agent || EMPTY_STRING, **)
|
|
113
|
+
return matcher if matcher.match?
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Browser
|
|
4
|
+
class Chrome < Base
|
|
5
|
+
def id
|
|
6
|
+
:chrome
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def name
|
|
10
|
+
"Chrome"
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def full_version
|
|
14
|
+
# Each regex on its own line to enforce precedence.
|
|
15
|
+
ua[%r{Chrome/([\d.]+)}, 1] ||
|
|
16
|
+
ua[%r{CriOS/([\d.]+)}, 1] ||
|
|
17
|
+
ua[%r{Safari/([\d.]+)}, 1] ||
|
|
18
|
+
ua[%r{AppleWebKit/([\d.]+)}, 1] ||
|
|
19
|
+
"0.0"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def chromium_based?
|
|
23
|
+
true
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def match?
|
|
27
|
+
ua.match?(/Chrome|CriOS/) &&
|
|
28
|
+
!ua.match?(/PhantomJS|FxiOS|ArchiveBot/) &&
|
|
29
|
+
!opera? &&
|
|
30
|
+
!edge? &&
|
|
31
|
+
!duck_duck_go? &&
|
|
32
|
+
!yandex? &&
|
|
33
|
+
!sputnik? &&
|
|
34
|
+
!samsung_browser? &&
|
|
35
|
+
!huawei_browser? &&
|
|
36
|
+
!miui_browser? &&
|
|
37
|
+
!maxthon? &&
|
|
38
|
+
!qq? &&
|
|
39
|
+
!sougou_browser? &&
|
|
40
|
+
!google_search_app?
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Browser
|
|
4
|
+
module DetectVersion
|
|
5
|
+
private def detect_version?(actual_version, expected_version)
|
|
6
|
+
return true unless expected_version
|
|
7
|
+
return false unless actual_version
|
|
8
|
+
|
|
9
|
+
expected_version = parse_version(expected_version)
|
|
10
|
+
actual_version = parse_version(actual_version)
|
|
11
|
+
|
|
12
|
+
Gem::Requirement.create(expected_version)
|
|
13
|
+
.satisfied_by?(Gem::Version.create(actual_version))
|
|
14
|
+
rescue ArgumentError
|
|
15
|
+
false
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private def parse_version(version)
|
|
19
|
+
version.is_a?(Numeric) ? version.to_s : version
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Browser
|
|
4
|
+
class Device
|
|
5
|
+
class Android < Base
|
|
6
|
+
def id
|
|
7
|
+
:unknown
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def name
|
|
11
|
+
ua[/\(Linux.*?; Android.*?; ([-_a-z0-9 ]+) Build[^)]+\)/i, 1] ||
|
|
12
|
+
"Unknown"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def match?
|
|
16
|
+
ua.include?("Android")
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Browser
|
|
4
|
+
class Device
|
|
5
|
+
class BlackBerryPlaybook < Base
|
|
6
|
+
def id
|
|
7
|
+
:playbook
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def name
|
|
11
|
+
"BlackBerry Playbook"
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def match?
|
|
15
|
+
ua.match?(/PlayBook.*?RIM Tablet/)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|