special_agent 0.1.3

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.
@@ -0,0 +1,3 @@
1
+ .bundle
2
+ Gemfile.lock
3
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in agent_orange.gemspec
4
+ gemspec
@@ -0,0 +1,193 @@
1
+ Special Agent
2
+ =============
3
+
4
+ User Agent detection for Ruby. Based on [agent_orange](https://github.com/kevinelliott/agent_orange)
5
+ but we didn't like the name so we forked it.
6
+
7
+ About
8
+ -----
9
+
10
+ There are quite a few User Agent parsing libraries already, but none of them make adequate
11
+ detection of mobile browsers and clients. With such a significant number of requests
12
+ coming from mobile users a library with the ability to detect what type of device a user
13
+ is coming from is necessary.
14
+
15
+ This library for Ruby will allow you to detect whether a user is on a computer, a mobile
16
+ device, or is a bot (such as Google). It was composed by using techniques gathered from
17
+ several other libraries/gems in an effort to be consistent and cover as many types
18
+ of agents as possible.
19
+
20
+ Installation
21
+ ------------
22
+
23
+ ```bash
24
+ gem install special_agent
25
+ ```
26
+
27
+ If you're going to use it with Rails then add the gem to your Gemfile.
28
+
29
+ Example Usage
30
+ -------------
31
+
32
+ Create new user agent parser
33
+
34
+ ```ruby
35
+ ua = SpecialAgent::UserAgent.new(user_agent_string)
36
+ ```
37
+
38
+ Looking at the device
39
+
40
+ ```ruby
41
+ >> device = ua.device
42
+ => "Mobile"
43
+ >> device.type
44
+ => "mobile"
45
+ >> device.name
46
+ => "Mobile"
47
+ >> device.version
48
+ => nil
49
+ ```
50
+
51
+ Check to see if the device is mobile, a desktop/laptop/server computer, or a bot
52
+
53
+ ```ruby
54
+ >> device.is_mobile?
55
+ => true
56
+ >> device.is_computer?
57
+ => false
58
+ >> device.is_bot?
59
+ => false
60
+ ```
61
+
62
+ Use the proxies to check if the user is on a mobile device
63
+
64
+ ```ruby
65
+ >> ua.is_mobile?
66
+ => true
67
+ ```
68
+
69
+ Looking at the platform
70
+
71
+ ```ruby
72
+ >> platform = ua.device.platform
73
+ => "iPhone"
74
+ >> platform.type
75
+ => "iphone"
76
+ >> platform.name
77
+ => "iPhone"
78
+ >> platform.version
79
+ => "3GS"
80
+ ```
81
+
82
+ Looking at the operating system
83
+
84
+ ```ruby
85
+ >> os = ua.device.os
86
+ => "iPhone OS 4.3"
87
+ >> os.type
88
+ => "iphone_os"
89
+ >> os.name
90
+ => "iPhone OS"
91
+ >> os.version
92
+ => "4.3"
93
+ ```
94
+
95
+ Looking at the web engine
96
+
97
+ ```ruby
98
+ >> engine = ua.device.engine
99
+ => "WebKit 5.3"
100
+ >> engine.type
101
+ => "webkit"
102
+ >> engine.name
103
+ => "WebKit"
104
+ >> engine.version
105
+ => "5.3.1.2321"
106
+ ```
107
+
108
+ Looking at the browser
109
+
110
+ ```ruby
111
+ >> browser = ua.device.engine.browser
112
+ => "Internet Explorer 10"
113
+ >> browser.type
114
+ => "ie"
115
+ >> browser.name
116
+ => "Internet Explorer"
117
+ >> browser.version
118
+ => "10.0.112"
119
+ ```
120
+
121
+ Quickly get to the browser version
122
+
123
+ ```ruby
124
+ >> SpecialAgent::UserAgent.new(user_agent_string).device.engine.browser.version
125
+ => "10.0.112"
126
+ ```
127
+
128
+
129
+ General Class Definition
130
+ ------------------------
131
+
132
+ SpecialAgent::UserAgent
133
+ device
134
+ parse
135
+ is_computer? # proxy
136
+ is_mobile? # proxy
137
+ is_bot? # proxy
138
+ to_s
139
+
140
+ SpecialAgent::Device
141
+ parse
142
+ type
143
+ name
144
+ version
145
+ is_computer?(name=nil) #
146
+ is_mobile?(name=nil) # accepts a :symbol or "String"
147
+ is_bot?(name=nil) #
148
+ to_s
149
+
150
+ SpecialAgent::Engine
151
+ parse
152
+ type
153
+ name
154
+ version
155
+ to_s
156
+
157
+ SpecialAgent::Version
158
+ parse
159
+ major
160
+ minor
161
+ patch_level
162
+ build_number
163
+ to_s
164
+
165
+ Credit
166
+ ------
167
+
168
+ As noted above, special_agent is just a fork of Kevin Elliot's [agent_orange](https://github.com/kevinelliott/agent_orange) gem.
169
+ He deserves all the credit for writing them gem. We just didn't want to perpetuate the use of such a culturally sensitive name.
170
+
171
+ License
172
+ -------
173
+
174
+ (The MIT License)
175
+
176
+ Permission is hereby granted, free of charge, to any person obtaining
177
+ a copy of this software and associated documentation files (the
178
+ 'Software'), to deal in the Software without restriction, including
179
+ without limitation the rights to use, copy, modify, merge, publish,
180
+ distribute, sublicense, and/or sell copies of the Software, and to
181
+ permit persons to whom the Software is furnished to do so, subject to
182
+ the following conditions:
183
+
184
+ The above copyright notice and this permission notice shall be
185
+ included in all copies or substantial portions of the Software.
186
+
187
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
188
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
189
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
190
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
191
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
192
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
193
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,14 @@
1
+ require 'bundler/gem_tasks'
2
+ Dir.glob('./lib/tasks/*.rake').each { |r| import r }
3
+
4
+ begin
5
+ require 'rspec/core/rake_task'
6
+
7
+ desc "Run all specs"
8
+ RSpec::Core::RakeTask.new do |t|
9
+ # t.pattern = "./spec/**/*_spec.rb" # don't need this, it's default.
10
+ # Put spec opts in a file named .rspec in root
11
+ end
12
+
13
+ task :default => :spec
14
+ end
@@ -0,0 +1,8 @@
1
+ require 'special_agent/version'
2
+ require 'special_agent/user_agent'
3
+ require 'special_agent/device'
4
+ require 'special_agent/engine'
5
+ require 'special_agent/browser'
6
+
7
+ module SpecialAgent
8
+ end
@@ -0,0 +1,58 @@
1
+ module SpecialAgent
2
+ class Base
3
+
4
+ def initialize(user_agent)
5
+ self.parse(user_agent)
6
+ end
7
+
8
+ def parse_user_agent_string_into_groups(user_agent)
9
+ results = user_agent.scan(/([^\/[:space:]]*)(\/([^[:space:]]*))?([[:space:]]*\[[a-zA-Z][a-zA-Z]\])?[[:space:]]*(\((([^()]|(\([^()]*\)))*)\))?[[:space:]]*/i)
10
+ groups = []
11
+ results.each do |result|
12
+ if result[0] != "" # Add the group of content if name isn't blank
13
+ groups << { :name => result[0], :version => result[2], :comment => result[5] }
14
+ end
15
+ end
16
+ groups
17
+ end
18
+
19
+ def parse_comment(comment)
20
+ groups = []
21
+ comment.split('; ').each do |piece|
22
+ content = { :name => nil, :version => nil }
23
+
24
+ # Remove 'like Mac OS X' or similar since it distracts from real results
25
+ piece = piece.scan(/(.+) like .+$/i)[0][0] if piece =~ /(.+) like (.+)$/i
26
+
27
+ if piece =~ /(.+)[ \/]([\w.]+)$/i
28
+ chopped = piece.scan(/(.+)[ \/]([\w.]+)$/i)[0]
29
+ groups << { :name => chopped[0], :version => chopped[1] }
30
+ end
31
+ end
32
+ groups
33
+ end
34
+
35
+ def determine_type(types={}, content="")
36
+ # Determine type
37
+ type = nil
38
+ types.each do |key, value|
39
+ type = key if content =~ /(#{value})/i
40
+ end
41
+ type = "other" if type.nil?
42
+ type
43
+ end
44
+
45
+ def debug_raw_content(content)
46
+ SpecialAgent.debug " Raw Name : #{content[:name]}", 2
47
+ SpecialAgent.debug " Raw Version: #{content[:version]}", 2
48
+ SpecialAgent.debug " Raw Comment: #{content[:comment]}", 2
49
+ end
50
+
51
+ def debug_content(content)
52
+ SpecialAgent.debug " Type: #{self.type}", 2
53
+ SpecialAgent.debug " Name: #{self.name}", 2
54
+ SpecialAgent.debug " Version: #{self.version}", 2
55
+ end
56
+
57
+ end
58
+ end
@@ -0,0 +1,61 @@
1
+ require 'special_agent/base'
2
+ require 'special_agent/version'
3
+
4
+ module SpecialAgent
5
+ class Browser < Base
6
+ attr_accessor :type, :name, :version
7
+ attr_accessor :security
8
+
9
+ BROWSERS = {
10
+ :ie => 'MSIE|Internet Explorer|IE',
11
+ :firefox => 'Firefox',
12
+ :opera => 'Opera',
13
+ :safari => 'Safari'
14
+ }
15
+
16
+ def parse(user_agent)
17
+ SpecialAgent.debug "BROWSER PARSING", 2
18
+
19
+ groups = parse_user_agent_string_into_groups(user_agent)
20
+ groups.each_with_index do |content,i|
21
+ if content[:name] =~ /(#{BROWSERS.collect{|cat,regex| regex}.join(')|(')})/i
22
+ # Matched group against name
23
+ self.populate(content)
24
+ elsif content[:comment] =~ /(#{BROWSERS.collect{|cat,regex| regex}.join(')|(')})/i
25
+ # Matched group against comment
26
+ chosen_content = { :name => nil, :version => nil }
27
+ additional_groups = parse_comment(content[:comment])
28
+ additional_groups.each do |additional_content|
29
+ if additional_content[:name] =~ /(#{BROWSERS.collect{|cat,regex| regex}.join(')|(')})/i
30
+ chosen_content = additional_content
31
+ end
32
+ end
33
+
34
+ self.populate(chosen_content)
35
+ end
36
+ end
37
+
38
+ self.analysis
39
+ end
40
+
41
+ def populate(content={})
42
+ self.debug_raw_content(content)
43
+ SpecialAgent.debug "", 2
44
+
45
+ self.type = self.determine_type(BROWSERS, content[:name])
46
+ self.name = content[:name]
47
+ self.version = SpecialAgent::Version.new(content[:version])
48
+ self
49
+ end
50
+
51
+ def analysis
52
+ SpecialAgent.debug "BROWSER ANALYSIS", 2
53
+ self.debug_content(:type => self.type, :name => self.name, :version => self.version)
54
+ SpecialAgent.debug "", 2
55
+ end
56
+
57
+ def to_s
58
+ [self.name, self.version].compact.join(' ')
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,97 @@
1
+ require 'special_agent/base'
2
+ require 'special_agent/platform'
3
+ require 'special_agent/operating_system'
4
+ require 'special_agent/engine'
5
+ require 'special_agent/version'
6
+
7
+ module SpecialAgent
8
+ class Device < Base
9
+ attr_accessor :type, :name, :version
10
+ attr_accessor :platform
11
+ attr_accessor :operating_system
12
+ attr_accessor :engine
13
+
14
+ DEVICES = {
15
+ :computer => 'windows|macintosh|x11|linux',
16
+ :mobile => 'ipod|ipad|iphone|palm|android|opera mini|hiptop|windows ce|smartphone|mobile|treo|psp',
17
+ :bot => 'bot|googlebot|crawler|spider|robot|crawling'
18
+ }
19
+
20
+ def parse(user_agent)
21
+ SpecialAgent.debug "DEVICE PARSING", 2
22
+
23
+ groups = parse_user_agent_string_into_groups(user_agent)
24
+ groups.each_with_index do |content,i|
25
+ if content[:comment] =~ /(#{DEVICES.collect{|cat,regex| regex}.join(')|(')})/i
26
+ # Matched group against name
27
+ self.populate(content)
28
+ end
29
+ end
30
+
31
+ self.analysis
32
+
33
+ self.platform = SpecialAgent::Platform.new(user_agent)
34
+ self.operating_system = SpecialAgent::OperatingSystem.new(user_agent)
35
+ self.engine = SpecialAgent::Engine.new(user_agent)
36
+ end
37
+
38
+ def populate(content={})
39
+ self.debug_raw_content(content)
40
+ SpecialAgent.debug "", 2
41
+
42
+ self.type = self.determine_type(DEVICES, content[:comment])
43
+ self.name = self.type.to_s.capitalize
44
+ self.version = nil
45
+ self
46
+ end
47
+
48
+ def analysis
49
+ SpecialAgent.debug "DEVICE ANALYSIS", 2
50
+ self.debug_content(:type => self.type, :name => self.name, :version => self.version)
51
+ SpecialAgent.debug "", 2
52
+ end
53
+
54
+ def is_computer?(name=nil)
55
+ if name
56
+ case name
57
+ when String
58
+ return self.platform.name.downcase.include?(name.downcase)
59
+ when Symbol
60
+ return self.platform.name.downcase.include?(name.to_s.downcase)
61
+ end
62
+ else
63
+ (self.type == :computer)
64
+ end
65
+ end
66
+
67
+ def is_mobile?(name=nil)
68
+ if !name.nil? && !self.platform.name.nil?
69
+ case name
70
+ when String
71
+ return self.platform.name.downcase.include?(name.downcase) || self.platform.version.downcase.include?(name.downcase)
72
+ when Symbol
73
+ return self.platform.name.downcase.include?(name.to_s.downcase) || self.platform.version.to_s.downcase.include?(name.to_s.downcase)
74
+ end
75
+ else
76
+ (self.type == :mobile)
77
+ end
78
+ end
79
+
80
+ def is_bot?(name=nil)
81
+ if name
82
+ case name
83
+ when String
84
+ return self.name.downcase.include?(name.downcase)
85
+ when Symbol
86
+ return self.name.downcase.include?(name.to_s.downcase)
87
+ end
88
+ else
89
+ (self.type == :bot)
90
+ end
91
+ end
92
+
93
+ def to_s
94
+ [self.name, self.version].compact.join(' ')
95
+ end
96
+ end
97
+ end