├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .rubocop.yml ├── .ruby-version ├── CHANGELOG.md ├── Gemfile ├── LICENSE.txt ├── README.md ├── Rakefile ├── device_detector.gemspec ├── lib ├── device_detector.rb └── device_detector │ ├── bot.rb │ ├── browser.rb │ ├── client.rb │ ├── client_hint.rb │ ├── device.rb │ ├── memory_cache.rb │ ├── metadata_extractor.rb │ ├── model_extractor.rb │ ├── name_extractor.rb │ ├── os.rb │ ├── parser.rb │ ├── vendor_fragment.rb │ ├── version.rb │ └── version_extractor.rb ├── regexes ├── bots.yml ├── client │ ├── browser_engine.yml │ ├── browsers.yml │ ├── feed_readers.yml │ ├── hints │ │ ├── apps.yml │ │ └── browsers.yml │ ├── libraries.yml │ ├── mediaplayers.yml │ ├── mobile_apps.yml │ └── pim.yml ├── device │ ├── cameras.yml │ ├── car_browsers.yml │ ├── consoles.yml │ ├── mobiles.yml │ ├── notebooks.yml │ ├── portable_media_player.yml │ ├── shell_tv.yml │ └── televisions.yml ├── oss.yml └── vendorfragments.yml └── spec ├── device_detector ├── bot_fixtures_spec.rb ├── client_fixtures_spec.rb ├── concrete_user_agent_spec.rb ├── detector_fixtures_spec.rb ├── device_fixtures_spec.rb ├── device_spec.rb ├── memory_cache_spec.rb ├── model_extractor_spec.rb ├── os_fixtures_spec.rb └── version_extractor_spec.rb ├── device_detector_spec.rb ├── fixtures ├── client │ ├── browser.yml │ ├── feed_reader.yml │ ├── library.yml │ ├── mediaplayer.yml │ ├── mobile_app.yml │ └── pim.yml ├── detector │ ├── bots.yml │ ├── camera.yml │ ├── car_browser.yml │ ├── clienthints-app.yml │ ├── clienthints.yml │ ├── console.yml │ ├── desktop.yml │ ├── feature_phone.yml │ ├── feed_reader.yml │ ├── mediaplayer.yml │ ├── mobile_apps.yml │ ├── peripheral.yml │ ├── phablet-1.yml │ ├── phablet.yml │ ├── podcasting.yml │ ├── portable_media_player.yml │ ├── smart_display.yml │ ├── smart_speaker.yml │ ├── smartphone-1.yml │ ├── smartphone-10.yml │ ├── smartphone-11.yml │ ├── smartphone-12.yml │ ├── smartphone-13.yml │ ├── smartphone-14.yml │ ├── smartphone-15.yml │ ├── smartphone-16.yml │ ├── smartphone-17.yml │ ├── smartphone-18.yml │ ├── smartphone-19.yml │ ├── smartphone-2.yml │ ├── smartphone-20.yml │ ├── smartphone-21.yml │ ├── smartphone-22.yml │ ├── smartphone-23.yml │ ├── smartphone-24.yml │ ├── smartphone-25.yml │ ├── smartphone-26.yml │ ├── smartphone-27.yml │ ├── smartphone-28.yml │ ├── smartphone-29.yml │ ├── smartphone-3.yml │ ├── smartphone-30.yml │ ├── smartphone-31.yml │ ├── smartphone-32.yml │ ├── smartphone-33.yml │ ├── smartphone-34.yml │ ├── smartphone-35.yml │ ├── smartphone-36.yml │ ├── smartphone-37.yml │ ├── smartphone-38.yml │ ├── smartphone-39.yml │ ├── smartphone-4.yml │ ├── smartphone-5.yml │ ├── smartphone-6.yml │ ├── smartphone-7.yml │ ├── smartphone-8.yml │ ├── smartphone-9.yml │ ├── smartphone.yml │ ├── tablet-1.yml │ ├── tablet-10.yml │ ├── tablet-11.yml │ ├── tablet-2.yml │ ├── tablet-3.yml │ ├── tablet-4.yml │ ├── tablet-5.yml │ ├── tablet-6.yml │ ├── tablet-7.yml │ ├── tablet-8.yml │ ├── tablet-9.yml │ ├── tablet.yml │ ├── tv-1.yml │ ├── tv-2.yml │ ├── tv-3.yml │ ├── tv.yml │ ├── unknown.yml │ └── wearable.yml ├── device │ ├── camera.yml │ ├── car_browser.yml │ ├── console.yml │ └── notebook.yml └── parser │ ├── oss.yml │ ├── type-methods.yml │ └── vendorfragments.yml └── spec_helper.rb /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: CI 3 | 4 | on: 5 | push: 6 | branches: [ master, develop, ci ] 7 | pull_request: 8 | 9 | jobs: 10 | rspec: 11 | runs-on: ubuntu-20.04 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | ruby: 16 | - '3.2' 17 | - '3.1' 18 | - '3.0' 19 | - '2.7' 20 | steps: 21 | - name: Checkout 22 | uses: actions/checkout@v2 23 | 24 | - name: Setup Ruby 25 | uses: ruby/setup-ruby@v1 26 | with: 27 | ruby-version: ${{ matrix.ruby }} 28 | 29 | - name: Setup Ruby cache 30 | uses: actions/cache@v2 31 | with: 32 | path: vendor/bundle 33 | key: ${{ runner.os }}-gems-${{ matrix.ruby }}-${{ hashFiles('**/Gemfile.lock') }} 34 | restore-keys: | 35 | ${{ runner.os }}-gems-${{ matrix.ruby }}- 36 | 37 | - name: Bundle 38 | run: | 39 | gem update --system 40 | gem --version 41 | bundle --version 42 | bundle config path vendor/bundle 43 | bundle install --jobs 4 --retry 3 44 | 45 | - name: Test 46 | run: | 47 | bundle exec rake test 48 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /Gemfile.lock 4 | /_yardoc/ 5 | /coverage/ 6 | /doc/ 7 | /pkg/ 8 | /spec/reports/ 9 | /tmp/ 10 | *.bundle 11 | *.so 12 | *.o 13 | *.a 14 | mkmf.log 15 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | AllCops: 2 | TargetRubyVersion: 2.7 3 | Exclude: 4 | - 'db/schema.rb' 5 | - 'Gemfile' 6 | - 'bin/' 7 | Style/Documentation: 8 | Enabled: false 9 | Style/EmptyMethod: 10 | EnforcedStyle: expanded 11 | Style/HashEachMethods: 12 | Enabled: true 13 | Style/HashTransformKeys: 14 | Enabled: true 15 | Style/HashTransformValues: 16 | Enabled: true 17 | Style/RedundantRegexpCharacterClass: 18 | Enabled: true 19 | Style/RedundantRegexpEscape: 20 | Enabled: true 21 | Style/StructInheritance: 22 | Enabled: false 23 | Style/ExponentialNotation: 24 | Enabled: true 25 | Style/SlicingWithRange: 26 | Enabled: true 27 | Layout/EmptyLinesAroundAttributeAccessor: 28 | Enabled: true 29 | Layout/LineLength: 30 | Max: 100 31 | Layout/SpaceAroundMethodCallOperator: 32 | Enabled: true 33 | Metrics/MethodLength: 34 | Max: 15 35 | Metrics/BlockLength: 36 | Exclude: 37 | - 'Rakefile' 38 | - '**/*.rake' 39 | - 'spec/**/*.rb' 40 | Lint/DeprecatedOpenSSLConstant: 41 | Enabled: true 42 | Lint/MixedRegexpCaptureTypes: 43 | Enabled: true 44 | Lint/RedundantCopDisableDirective: 45 | Enabled: false 46 | Lint/RaiseException: 47 | Enabled: true 48 | Lint/StructNewOverride: 49 | Enabled: true 50 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | 3.3.2 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## [1.1.3] 4 | 5 | - Updated detection rules from upstream on 2024-06-25 ([#124](https://github.com/podigee/device_detector/pull/124)) 6 | 7 | ## [1.1.2] 8 | 9 | - Updated detection rules from upstream on 2023-11-27 10 | 11 | ## [1.1.1] 12 | 13 | - Updated detection rules from upstream on 2023-07-01 14 | - Fix issue when user agent is nil ([#104](https://github.com/podigee/device_detector/issues/104)) 15 | - Fix issue when user agent is not UTF-8 encoded ([#105](https://github.com/podigee/device_detector/issues/105), [#106](https://github.com/podigee/device_detector/issues/106)) 16 | - Improve device brand name detection 17 | 18 | ## [1.1.0] 19 | 20 | - Updated detection rules from upstream on 2022-12-09 21 | - Add support for client hints in header 22 | - Changed the minimum required Ruby version (>= 2.7.5) 23 | 24 | ## [1.0.7] 25 | 26 | - Updated detection rules from upstream on 2022-02-17 27 | - Fixes Ruby warnings when compiling RegExes ([#89](https://github.com/podigee/device_detector/issues/89), [#91](https://github.com/podigee/device_detector/issues/91)) 28 | 29 | ## [1.0.6] 30 | 31 | - Updated detection rules from upstream on 2021-10-28 32 | 33 | ## [1.0.5] 34 | 35 | - Updated detection rules from upstream on 2020-10-06 36 | 37 | ## [1.0.4] 38 | 39 | - Updated detection rules from upstream on 2020-06-23 40 | - [Issue #69](https://github.com/podigee/device_detector/issues/69): Performance: RegExp definitions are only loaded once. 41 | - [Issue #74](https://github.com/podigee/device_detector/issues/74): Development: Added Rubocop 42 | 43 | ## [1.0.3] 44 | 45 | - Updated detection rules from upstream on 2019-12-09 46 | 47 | ## [1.0.2] 48 | 49 | - Updated detection rules from upstream on 2019-08-05 50 | 51 | ## [1.0.1] 52 | 53 | - Updated detection rules from upstream on 2018-04-27 54 | 55 | ## [1.0.0] 56 | 57 | - Boom! The 1.0.0 has landed :) 58 | 59 | ## [0.9.0] 60 | 61 | - Preparing for the 1.0.0 release. This version (with minor bumps) will be promoted to 1.0.0 once the release has been proven stable 62 | - Updated regex files from upstream 63 | - Updated test fixtures from upstream 64 | 65 | ## [0.8.2] 66 | 67 | - Added device brand support. Thanks to [dnswus](https://github.com/dnswus) 68 | 69 | ## [0.8.1] 70 | 71 | - Added Instacast detection rules 72 | - Updated test fixtures 73 | 74 | ## [0.8.0] 75 | 76 | - Added a better and more robust device detection. Thanks to [skaes](https://github.com/skaes) 77 | - Added test fixture from the piwik project 78 | 79 | ## [0.7.0] 80 | 81 | - [Issue #8](https://github.com/podigee/device_detector/issues/8) Fixed Mac OS X full version format. Thanks to [aaronchi](https://github.com/aaronchi) for reporting 82 | 83 | ## [0.6.0] 84 | 85 | - [Issue #7](https://github.com/podigee/device_detector/issues/7) Fixed missing name extraction from regexp. Thanks to [janxious](https://github.com/janxious) for reporting 86 | - Optimized performance of name and version extraction, by using the built-in memory cache 87 | - Move specs from RSpec to the more lightweight Minitest 88 | 89 | ## [0.5.1] 90 | 91 | - Added the minimum required Ruby version (>= 1.9.3) 92 | 93 | ## [0.5.0] 94 | 95 | - Added rake task for automatic generation of supported and detectable clients and devices 96 | - Updated detection rules 97 | - Fixed device type detection, when type is specified on top level of a nested regex 98 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in device_detector.gemspec 4 | gemspec 5 | 6 | if RUBY_VERSION >= "2.0" && defined?(RUBY_ENGINE) && RUBY_ENGINE == "ruby" 7 | gem 'byebug' 8 | end 9 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Podigee 2 | 3 | GNU LESSER GENERAL PUBLIC LICENSE 4 | 5 | Version 3, 29 June 2007 6 | 7 | Copyright © 2007 Free Software Foundation, Inc. 8 | 9 | Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. 10 | 11 | This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | As used herein, “this License” refers to version 3 of the GNU Lesser General Public License, and the “GNU GPL” refers to version 3 of the GNU General Public License. 15 | 16 | “The Library” refers to a covered work governed by this License, other than an Application or a Combined Work as defined below. 17 | 18 | An “Application” is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library. 19 | 20 | A “Combined Work” is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the “Linked Version”. 21 | 22 | The “Minimal Corresponding Source” for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version. 23 | 24 | The “Corresponding Application Code” for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work. 25 | 26 | 1. Exception to Section 3 of the GNU GPL. 27 | You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL. 28 | 29 | 2. Conveying Modified Versions. 30 | If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version: 31 | 32 | a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or 33 | b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy. 34 | 3. Object Code Incorporating Material from Library Header Files. 35 | The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following: 36 | 37 | a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License. 38 | b) Accompany the object code with a copy of the GNU GPL and this license document. 39 | 4. Combined Works. 40 | You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following: 41 | 42 | a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License. 43 | b) Accompany the Combined Work with a copy of the GNU GPL and this license document. 44 | c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document. 45 | d) Do one of the following: 46 | 0) Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source. 47 | 1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version. 48 | e) Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.) 49 | 5. Combined Libraries. 50 | You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following: 51 | 52 | a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License. 53 | b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 54 | 6. Revised Versions of the GNU Lesser General Public License. 55 | The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. 56 | 57 | Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License “or any later version” applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation. 58 | 59 | If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library. 60 | 61 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rake' 4 | require 'rake/testtask' 5 | 6 | $LOAD_PATH.unshift 'lib' 7 | require 'device_detector' 8 | 9 | Rake::TestTask.new do |t| 10 | t.pattern = 'spec/**/*_spec.rb' 11 | t.libs.push 'spec' 12 | end 13 | 14 | task default: :test 15 | 16 | desc 'generate detectable names output for README' 17 | task :detectable_names do 18 | require 'date' 19 | 20 | bot_names = DeviceDetector::Bot.new('').send(:regexes) 21 | .map { |r| r[:name] }.uniq.sort_by(&:downcase) 22 | bot_names.delete('$1') 23 | client_names = DeviceDetector::Client.new('').send(:regexes) 24 | .map { |r| r[:name] }.uniq.sort_by(&:downcase) 25 | client_names.delete('$1') 26 | device = DeviceDetector::Device.new('') 27 | device_paths = device.send(:filepaths) 28 | device_regexes = device.send(:load_regexes, device_paths) 29 | device_names = device_regexes.flat_map { |dn| dn[1].keys }.uniq.sort_by(&:downcase) 30 | 31 | today = Date.today.strftime 32 | 33 | puts '## Detectable clients, bots and devices' 34 | puts 35 | puts "Updated on #{today}" 36 | puts 37 | puts '### Bots' 38 | puts 39 | puts bot_names.join(', ') 40 | puts 41 | puts '### Clients' 42 | puts 43 | puts client_names.join(', ') 44 | puts 45 | puts '### Devices' 46 | puts 47 | puts device_names.join(', ') 48 | puts 49 | end 50 | 51 | PIWIK_REPO_URL = 'https://github.com/matomo-org/device-detector' 52 | PIWIK_CHECKOUT_LOCATION = '/tmp/matomo_device_detector' 53 | 54 | def get_latest_piwik_checkout 55 | if File.exist?(PIWIK_CHECKOUT_LOCATION) 56 | system "cd #{PIWIK_CHECKOUT_LOCATION}; git reset --hard HEAD; git pull origin master" 57 | else 58 | system "git clone --depth 1 #{PIWIK_REPO_URL} #{PIWIK_CHECKOUT_LOCATION}" 59 | end 60 | end 61 | 62 | desc 'update regex database from piwik project' 63 | task :update_regexes do 64 | top = File.expand_path(__dir__) 65 | get_latest_piwik_checkout 66 | system "cp -R #{PIWIK_CHECKOUT_LOCATION}/regexes/* #{top}/regexes" 67 | end 68 | 69 | desc 'update fixtures from piwik project' 70 | task :update_fixtures do 71 | top = File.expand_path(__dir__) 72 | get_latest_piwik_checkout 73 | 74 | fixture_mappings = [ 75 | { target_path: "#{top}/spec/fixtures/detector", source_path: 'Tests/fixtures/*.yml' }, 76 | { target_path: "#{top}/spec/fixtures/client", 77 | source_path: 'Tests/Parser/Client/fixtures/*.yml' }, 78 | { target_path: "#{top}/spec/fixtures/parser", source_path: 'Tests/Parser/fixtures/*.yml' }, 79 | { target_path: "#{top}/spec/fixtures/device", 80 | source_path: 'Tests/Parser/Device/fixtures/*.yml' } 81 | ] 82 | 83 | fixture_mappings.each do |mapping| 84 | source_path = mapping.fetch(:source_path) 85 | target_path = mapping.fetch(:target_path) 86 | system "cp -R #{PIWIK_CHECKOUT_LOCATION}/#{source_path} #{target_path}" 87 | end 88 | end 89 | -------------------------------------------------------------------------------- /device_detector.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'device_detector/version' 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = 'device_detector' 8 | spec.version = DeviceDetector::VERSION 9 | spec.authors = ['Mati Sójka', 'Ben Zimmer'] 10 | spec.email = ['yagooar@gmail.com'] 11 | spec.summary = %q{Precise and fast user agent parser and device detector} 12 | spec.description = %q{Precise and fast user agent parser and device detector, backed by the largest and most up-to-date agent and device database} 13 | spec.homepage = 'http://podigee.github.io/device_detector' 14 | spec.license = 'LGPL-3.0' 15 | spec.metadata = { 'changelog_uri' => 'https://github.com/podigee/device_detector/blob/develop/CHANGELOG.md' } 16 | 17 | spec.files = Dir["{lib,regexes}/**/*", "README.md", "LICENSE.txt", "CHANGELOG.md"] 18 | spec.executables = [] 19 | spec.require_paths = ['lib'] 20 | 21 | spec.required_ruby_version = '>= 2.7.5' 22 | 23 | spec.add_development_dependency 'minitest' 24 | spec.add_development_dependency 'rake' 25 | spec.add_development_dependency 'pry', '>= 0.10' 26 | spec.add_development_dependency 'rubocop', '0.85.1' 27 | end 28 | -------------------------------------------------------------------------------- /lib/device_detector.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'yaml' 4 | 5 | require 'device_detector/version' 6 | require 'device_detector/metadata_extractor' 7 | require 'device_detector/version_extractor' 8 | require 'device_detector/model_extractor' 9 | require 'device_detector/name_extractor' 10 | require 'device_detector/memory_cache' 11 | require 'device_detector/parser' 12 | require 'device_detector/bot' 13 | require 'device_detector/client' 14 | require 'device_detector/device' 15 | require 'device_detector/os' 16 | require 'device_detector/browser' 17 | require 'device_detector/client_hint' 18 | require 'device_detector/vendor_fragment' 19 | 20 | class DeviceDetector 21 | attr_reader :client_hint, :user_agent 22 | 23 | def initialize(user_agent, headers = nil) 24 | @client_hint = ClientHint.new(headers) 25 | utf8_user_agent = encode_user_agent_if_needed(user_agent) 26 | @user_agent = build_user_agent(utf8_user_agent) 27 | end 28 | 29 | # https://github.com/matomo-org/device-detector/blob/a2535ff3b63e4187f1d3440aed24ff43d74fb7f1/Parser/Device/AbstractDeviceParser.php#L2065-L2073 30 | def build_user_agent(user_agent) 31 | return user_agent if client_hint.model.nil? 32 | 33 | regex = build_regex('Android 10[.\d]*; K(?: Build/|[;)])') 34 | return user_agent unless user_agent =~ regex 35 | 36 | version = client_hint.os_version || '10' 37 | 38 | user_agent.gsub(/(Android 10[.\d]*; K)/, "Android #{version}; #{client_hint.model}") 39 | end 40 | 41 | def encode_user_agent_if_needed(user_agent) 42 | return if user_agent.nil? 43 | return user_agent if user_agent.encoding.name == 'UTF-8' 44 | 45 | user_agent.encode('utf-8', 'binary', undef: :replace) 46 | end 47 | 48 | def name 49 | return client.name if mobile_fix? 50 | 51 | client_hint.browser_name || client.name 52 | end 53 | 54 | def full_version 55 | client_hint.full_version || client.full_version 56 | end 57 | 58 | def os_family 59 | return 'GNU/Linux' if linux_fix? 60 | 61 | client_hint.os_family || os.family || client_hint.platform 62 | end 63 | 64 | def os_name 65 | return 'GNU/Linux' if linux_fix? 66 | 67 | client_hint.os_name || os.name || client_hint.platform 68 | end 69 | 70 | def os_full_version 71 | return if skip_os_version? 72 | return os.full_version if pico_os_fix? 73 | return fire_os_version if fire_os_fix? 74 | 75 | client_hint.os_version || os.full_version 76 | end 77 | 78 | def device_name 79 | return if fake_ua? 80 | 81 | device.name || client_hint.model || fix_for_x_music 82 | end 83 | 84 | def device_brand 85 | return if fake_ua? 86 | 87 | # Assume all devices running iOS / Mac OS are from Apple 88 | brand = device.brand 89 | brand = 'Apple' if brand.nil? && DeviceDetector::OS::APPLE_OS_NAMES.include?(os_name) 90 | 91 | brand 92 | end 93 | 94 | def device_type 95 | t = device.type 96 | 97 | t = nil if fake_ua? 98 | 99 | # Chrome on Android passes the device type based on the keyword 'Mobile' 100 | # If it is present the device should be a smartphone, otherwise it's a tablet 101 | # See https://developer.chrome.com/multidevice/user-agent#chrome_for_android_user_agent 102 | # Note: We do not check for browser (family) here, as there might be mobile apps using Chrome, 103 | # that won't have a detected browser, but can still be detected. So we check the useragent for 104 | # Chrome instead. 105 | if t.nil? && os_family == 'Android' && user_agent =~ build_regex('Chrome\/[\.0-9]*') 106 | t = user_agent =~ build_regex('(?:Mobile|eliboM)') ? 'smartphone' : 'tablet' 107 | end 108 | 109 | # Some UA contain the fragment 'Pad/APad', so we assume those devices as tablets 110 | t = 'tablet' if t == 'smartphone' && user_agent =~ build_regex('Pad\/APad') 111 | 112 | # Some UA contain the fragment 'Android; Tablet;' or 'Opera Tablet', so we assume those devices 113 | # as tablets 114 | t = 'tablet' if t.nil? && (android_tablet_fragment? || opera_tablet?) 115 | 116 | # Some user agents simply contain the fragment 'Android; Mobile;', so we assume those devices 117 | # as smartphones 118 | t = 'smartphone' if t.nil? && android_mobile_fragment? 119 | 120 | # Some UA contains the 'Android; Mobile VR;' fragment 121 | t = 'wearable' if t.nil? && android_vr_fragment? 122 | 123 | # Android up to 3.0 was designed for smartphones only. But as 3.0, 124 | # which was tablet only, was published too late, there were a 125 | # bunch of tablets running with 2.x With 4.0 the two trees were 126 | # merged and it is for smartphones and tablets 127 | # 128 | # So were are expecting that all devices running Android < 2 are 129 | # smartphones Devices running Android 3.X are tablets. Device type 130 | # of Android 2.X and 4.X+ are unknown 131 | if t.nil? && os_name == 'Android' && os.full_version && !os.full_version.empty? 132 | full_version = Gem::Version.new(os.full_version) 133 | if full_version < VersionExtractor::MAJOR_VERSION_2 134 | t = 'smartphone' 135 | elsif full_version >= VersionExtractor::MAJOR_VERSION_3 && \ 136 | full_version < VersionExtractor::MAJOR_VERSION_4 137 | t = 'tablet' 138 | end 139 | end 140 | 141 | # All detected feature phones running android are more likely a smartphone 142 | t = 'smartphone' if t == 'feature phone' && os_family == 'Android' 143 | 144 | # All unknown devices under running Java ME are more likely a features phones 145 | t = 'feature phone' if t.nil? && os_name == 'Java ME' 146 | 147 | # According to http://msdn.microsoft.com/en-us/library/ie/hh920767(v=vs.85).aspx 148 | # Internet Explorer 10 introduces the "Touch" UA string token. If this token is present at the 149 | # end of the UA string, the computer has touch capability, and is running Windows 8 (or later). 150 | # This UA string will be transmitted on a touch-enabled system running Windows 8 (RT) 151 | # 152 | # As most touch enabled devices are tablets and only a smaller part are desktops/notebooks we 153 | # assume that all Windows 8 touch devices are tablets. 154 | if t.nil? && touch_enabled? && 155 | (os_name == 'Windows RT' || 156 | (os_name == 'Windows' && os_full_version && 157 | Gem::Version.new(os_full_version) >= VersionExtractor::MAJOR_VERSION_8)) 158 | t = 'tablet' 159 | end 160 | 161 | # All devices running Opera TV Store are assumed to be a tv 162 | t = 'tv' if opera_tv_store? 163 | 164 | # All devices that contain Andr0id in string are assumed to be a tv 165 | if user_agent =~ build_regex('Andr0id|(?:Android(?: UHD)?|Google) TV|\(lite\) TV|BRAVIA') 166 | t = 'tv' 167 | end 168 | 169 | # All devices running Tizen TV or SmartTV are assumed to be a tv 170 | t = 'tv' if t.nil? && tizen_samsung_tv? 171 | 172 | # Devices running those clients are assumed to be a TV 173 | t = 'tv' if ['Kylo', 'Espial TV Browser', 'LUJO TV Browser', 'LogicUI TV Browser', 174 | 'Open TV Browser', 'Seraphic Sraf', 'Opera Devices', 'Crow Browser', 175 | 'Vewd Browser', 'TiviMate', 'Quick Search TV', 'QJY TV Browser', 176 | 'TV Bro'].include?(name) 177 | 178 | # All devices containing TV fragment are assumed to be a tv 179 | t = 'tv' if t.nil? && user_agent =~ build_regex('\(TV;') 180 | 181 | has_desktop = t != 'desktop' && desktop_string? && desktop_fragment? 182 | t = 'desktop' if has_desktop 183 | 184 | # set device type to desktop for all devices running a desktop os that were not detected as 185 | # another device type 186 | return t if t || !desktop? 187 | 188 | 'desktop' 189 | end 190 | 191 | def known? 192 | client.known? 193 | end 194 | 195 | def bot? 196 | bot.bot? 197 | end 198 | 199 | def bot_name 200 | bot.name 201 | end 202 | 203 | class << self 204 | class Configuration 205 | attr_accessor :max_cache_keys 206 | 207 | def to_hash 208 | { 209 | max_cache_keys: max_cache_keys 210 | } 211 | end 212 | end 213 | 214 | def config 215 | @config ||= Configuration.new 216 | end 217 | 218 | def cache 219 | @cache ||= MemoryCache.new(config.to_hash) 220 | end 221 | 222 | def configure 223 | @config = Configuration.new 224 | yield(config) 225 | end 226 | end 227 | 228 | private 229 | 230 | def bot 231 | @bot ||= Bot.new(user_agent) 232 | end 233 | 234 | def client 235 | @client ||= Client.new(user_agent) 236 | end 237 | 238 | def device 239 | @device ||= Device.new(user_agent) 240 | end 241 | 242 | def os 243 | @os ||= OS.new(user_agent) 244 | end 245 | 246 | # https://github.com/matomo-org/device-detector/blob/67ae11199a5129b42fa8b985d372ea834104fe3a/DeviceDetector.php#L931-L938 247 | def fake_ua? 248 | device.brand == 'Apple' && !DeviceDetector::OS::APPLE_OS_NAMES.include?(os_name) 249 | end 250 | 251 | # https://github.com/matomo-org/device-detector/blob/be1c9ef486c247dc4886668da5ed0b1c49d90ba8/Parser/Client/Browser.php#L772 252 | # Fix mobile browser names e.g. Chrome => Chrome Mobile 253 | def mobile_fix? 254 | client.name == "#{client_hint.browser_name} Mobile" 255 | end 256 | 257 | def linux_fix? 258 | client_hint.platform == 'Linux' && 259 | %w[iOS Android].include?(os.name) && 260 | %w[?0 0].include?(client_hint.mobile) 261 | end 262 | 263 | # Related to issue mentionned in device.rb#1562 264 | def fix_for_x_music 265 | user_agent&.include?('X-music Ⅲ') ? 'X-Music III' : nil 266 | end 267 | 268 | def pico_os_fix? 269 | client_hint.os_name == 'Pico OS' 270 | end 271 | 272 | # https://github.com/matomo-org/device-detector/blob/323629cb679c8572a9745cba9c3803fee13f3cf6/Parser/OperatingSystem.php#L398-L403 273 | def fire_os_fix? 274 | !client_hint.platform.nil? && os.name == 'Fire OS' 275 | end 276 | 277 | def fire_os_version 278 | DeviceDetector::OS 279 | .mapped_os_version(client_hint.os_version, DeviceDetector::OS::FIRE_OS_VERSION_MAPPING) 280 | end 281 | 282 | # https://github.com/matomo-org/device-detector/blob/323629cb679c8572a9745cba9c3803fee13f3cf6/Parser/OperatingSystem.php#L378-L383 283 | def skip_os_version? 284 | !client_hint.os_family.nil? && 285 | client_hint.os_version.nil? && 286 | client_hint.os_family != os.family 287 | end 288 | 289 | def android_tablet_fragment? 290 | user_agent =~ build_regex('Android( [\.0-9]+)?; Tablet;|Tablet(?! PC)|.*\-tablet$') 291 | end 292 | 293 | def android_mobile_fragment? 294 | user_agent =~ build_regex('Android( [\.0-9]+)?; Mobile;|.*\-mobile$') 295 | end 296 | 297 | def android_vr_fragment? 298 | user_agent =~ build_regex('Android( [\.0-9]+)?; Mobile VR;| VR ') 299 | end 300 | 301 | def desktop_fragment? 302 | user_agent =~ build_regex('Desktop(?: (x(?:32|64)|WOW64))?;') 303 | end 304 | 305 | def touch_enabled? 306 | user_agent =~ build_regex('Touch') 307 | end 308 | 309 | def opera_tv_store? 310 | user_agent =~ build_regex('Opera TV Store|OMI/') 311 | end 312 | 313 | def opera_tablet? 314 | user_agent =~ build_regex('Opera Tablet') 315 | end 316 | 317 | def tizen_samsung_tv? 318 | user_agent =~ build_regex('SmartTV|Tizen.+ TV .+$') 319 | end 320 | 321 | def uses_mobile_browser? 322 | client.browser? && client.mobile_only_browser? 323 | end 324 | 325 | # This is a workaround until we support detecting mobile only browsers 326 | def desktop_string? 327 | user_agent =~ /Desktop/ 328 | end 329 | 330 | def desktop? 331 | return false if os_name.nil? || os_name == '' || os_name == 'UNK' 332 | 333 | # Check for browsers available for mobile devices only 334 | return false if uses_mobile_browser? 335 | 336 | DeviceDetector::OS::DESKTOP_OSS.include?(os_family) 337 | end 338 | 339 | def build_regex(src) 340 | Regexp.new('(?:^|[^A-Z0-9\_\-])(?:' + src + ')', Regexp::IGNORECASE) 341 | end 342 | end 343 | -------------------------------------------------------------------------------- /lib/device_detector/bot.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class DeviceDetector 4 | class Bot < Parser 5 | def bot? 6 | regex_meta.any? 7 | end 8 | 9 | private 10 | 11 | def filenames 12 | ['bots.yml'] 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/device_detector/client.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class DeviceDetector 4 | class Client < Parser 5 | def known? 6 | regex_meta.any? 7 | end 8 | 9 | def browser? 10 | regex_meta[:path] == :"client/browsers.yml" 11 | end 12 | 13 | def mobile_only_browser? 14 | DeviceDetector::Browser.mobile_only_browser?(name) 15 | end 16 | 17 | private 18 | 19 | def filenames 20 | [ 21 | 'client/feed_readers.yml', 22 | 'client/mobile_apps.yml', 23 | 'client/mediaplayers.yml', 24 | 'client/pim.yml', 25 | 'client/browsers.yml', 26 | 'client/libraries.yml' 27 | ] 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/device_detector/client_hint.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class DeviceDetector 4 | class ClientHint 5 | ROOT = File.expand_path('../..', __dir__) 6 | 7 | REGEX_CACHE = ::DeviceDetector::MemoryCache.new({}) 8 | private_constant :REGEX_CACHE 9 | 10 | class HintBrowser < Struct.new(:name, :version) 11 | end 12 | 13 | def initialize(headers) 14 | return if headers.nil? 15 | 16 | @headers = headers 17 | @full_version = extract_full_version 18 | @browser_list = extract_browser_list 19 | @app_name = extract_app_name 20 | @platform = extract_platform 21 | @platform_version = extract_platform_version 22 | @mobile = extract_mobile 23 | @model = extract_model 24 | end 25 | 26 | attr_reader :app_name, :browser_list, :full_version, :headers, :mobile, :model, :platform, 27 | :platform_version 28 | 29 | def browser_name 30 | return 'Iridium' if iridium? 31 | return '360 Secure Browser' if secure_browser? 32 | 33 | browser_name_from_list || app_name 34 | end 35 | 36 | def os_version 37 | return windows_version if platform == 'Windows' 38 | return lineage_version if lineage_os_app? 39 | return fire_os_version if fire_os_app? 40 | 41 | platform_version 42 | end 43 | 44 | def os_name 45 | return 'Android' if android_app? 46 | return 'Lineage OS' if lineage_os_app? 47 | return 'Fire OS' if fire_os_app? 48 | return unless ['Windows', 'Chromium OS'].include?(platform) 49 | 50 | platform 51 | end 52 | 53 | def os_short_name 54 | return if os_name.nil? 55 | 56 | DeviceDetector::OS::DOWNCASED_OPERATING_SYSTEMS[os_name.downcase] 57 | end 58 | 59 | def os_family 60 | return if os_short_name.nil? 61 | 62 | DeviceDetector::OS::FAMILY_TO_OS[os_short_name] 63 | end 64 | 65 | private 66 | 67 | # https://github.com/matomo-org/device-detector/blob/28211c6f411528abf41304e07b886fdf322a49b7/Parser/OperatingSystem.php#L330 68 | def android_app? 69 | %w[com.hisense.odinbrowser com.seraphic.openinet.pre 70 | com.appssppa.idesktoppcbrowser every.browser.inc].include?(app_name_from_headers) 71 | end 72 | 73 | # https://github.com/matomo-org/device-detector/blob/67ae11199a5129b42fa8b985d372ea834104fe3a/Parser/OperatingSystem.php#L449-L456 74 | def fire_os_app? 75 | app_name_from_headers == 'org.mozilla.tv.firefox' 76 | end 77 | 78 | # https://github.com/matomo-org/device-detector/blob/67ae11199a5129b42fa8b985d372ea834104fe3a/Parser/OperatingSystem.php#L439-L447 79 | def lineage_os_app? 80 | app_name_from_headers == 'org.lineageos.jelly' 81 | end 82 | 83 | # https://github.com/matomo-org/device-detector/blob/75d88bbefb0182f9207c9f48dc39b1bc8c7cc43f/Parser/Client/Browser.php#L1076-L1079 84 | def browser_name_from_list 85 | @browser_name_from_list ||= browser_list&.reject do |b| 86 | ['Chromium', 'Microsoft Edge'].include?(b.name) 87 | end&.last&.name 88 | end 89 | 90 | def available_browsers 91 | DeviceDetector::Browser::AVAILABLE_BROWSERS.values 92 | end 93 | 94 | def available_osses 95 | DeviceDetector::OS::OPERATING_SYSTEMS.values 96 | end 97 | 98 | # https://github.com/matomo-org/device-detector/blob/28211c6f411528abf41304e07b886fdf322a49b7/Parser/OperatingSystem.php#L434 99 | def windows_version 100 | return if platform_version.nil? 101 | 102 | major_version = platform_version.split('.').first.to_i 103 | return if major_version < 1 104 | 105 | major_version < 11 ? '10' : '11' 106 | end 107 | 108 | def lineage_version 109 | DeviceDetector::OS 110 | .mapped_os_version(platform_version, DeviceDetector::OS::LINEAGE_OS_VERSION_MAPPING) 111 | end 112 | 113 | def fire_os_version 114 | DeviceDetector::OS 115 | .mapped_os_version(platform_version, DeviceDetector::OS::FIRE_OS_VERSION_MAPPING) 116 | end 117 | 118 | # https://github.com/matomo-org/device-detector/blob/67ae11199a5129b42fa8b985d372ea834104fe3a/Parser/Client/Browser.php#L923-L929 119 | # If the version reported from the client hints is YYYY or YYYY.MM (e.g., 2022 or 2022.04), 120 | # then it is the Iridium browser 121 | # https://iridiumbrowser.de/news/ 122 | def iridium? 123 | return if browser_list.nil? 124 | 125 | !browser_list.find do |browser| 126 | browser.name == 'Chromium' && browser.version =~ /^202[0-4]/ 127 | end.nil? 128 | end 129 | 130 | # https://github.com/matomo-org/device-detector/blob/67ae11199a5129b42fa8b985d372ea834104fe3a/Parser/Client/Browser.php#L931-L937 131 | # https://bbs.360.cn/thread-16096544-1-1.html 132 | def secure_browser? 133 | return if browser_list.nil? 134 | 135 | !browser_list.find do |browser| 136 | browser.name == 'Chromium' && browser.version =~ /^15/ 137 | end.nil? 138 | end 139 | 140 | def app_name_from_headers 141 | return if headers.nil? 142 | 143 | headers['http-x-requested-with'] || 144 | headers['X-Requested-With'] || 145 | headers['x-requested-with'] 146 | end 147 | 148 | def extract_app_name 149 | requested_with = app_name_from_headers 150 | return if requested_with.nil? 151 | 152 | hint_app_names[requested_with] 153 | end 154 | 155 | def hint_app_names 156 | DeviceDetector.cache.get_or_set('hint_app_names') do 157 | load_hint_app_names.flatten.reduce({}, :merge) 158 | end 159 | end 160 | 161 | def hint_filenames 162 | %w[client/hints/browsers.yml client/hints/apps.yml] 163 | end 164 | 165 | def hint_filepaths 166 | hint_filenames.map do |filename| 167 | [filename.to_sym, File.join(ROOT, 'regexes', filename)] 168 | end 169 | end 170 | 171 | def load_hint_app_names 172 | hint_filepaths.map { |_, full_path| YAML.load_file(full_path) } 173 | end 174 | 175 | def extract_browser_list 176 | extract_browser_list_from_full_version_list || 177 | extract_browser_list_from_header('Sec-CH-UA') || 178 | extract_browser_list_from_header('Sec-CH-UA-Full-Version-List') 179 | end 180 | 181 | def extract_browser_list_from_header(header) 182 | return if headers[header].nil? 183 | 184 | headers[header].split(', ').map do |component| 185 | name_and_version = extract_browser_name_and_version(component) 186 | next if name_and_version[:name].nil? 187 | 188 | HintBrowser.new(name_and_version[:name], name_and_version[:version]) 189 | end.compact 190 | end 191 | 192 | def extract_browser_name_and_version(component) 193 | component_and_version = component.gsub('"', '').split("\;v=") 194 | name = name_from_known_browsers(component_and_version.first) 195 | browser_version = full_version&.gsub('"', '') || component_and_version.last 196 | { name: name, version: browser_version } 197 | end 198 | 199 | def extract_browser_list_from_full_version_list 200 | return if headers['fullVersionList'].nil? && headers['brands'].nil? 201 | 202 | (headers['brands'] || headers['fullVersionList']).map do |item| 203 | name = name_from_known_browsers(item['brand']) 204 | next if name.nil? 205 | 206 | HintBrowser.new(name, full_version || item['version']) 207 | end.compact 208 | end 209 | 210 | # https://github.com/matomo-org/device-detector/blob/be1c9ef486c247dc4886668da5ed0b1c49d90ba8/Parser/Client/Browser.php#L865 211 | def name_from_known_browsers(name) 212 | DeviceDetector::Browser::KNOWN_BROWSER_TO_NAME.fetch(name) do 213 | available_browsers.find do |i| 214 | i == name || 215 | i.gsub(' ', '') == name.gsub(' ', '') || 216 | i == name.gsub('Browser', '') || 217 | i == name.gsub(' Browser', '') || 218 | i == "#{name} Browser" 219 | end 220 | end 221 | end 222 | 223 | def extract_from_header(header) 224 | return if headers[header].nil? || headers[header] == '' 225 | 226 | headers[header] 227 | end 228 | 229 | def extract_full_version 230 | extract_from_header('Sec-CH-UA-Full-Version') || extract_from_header('uaFullVersion') 231 | end 232 | 233 | def extract_platform 234 | extract_from_header('Sec-CH-UA-Platform') || extract_from_header('platform') 235 | end 236 | 237 | def extract_platform_version 238 | extract_from_header('Sec-CH-UA-Platform-Version') || extract_from_header('platformVersion') 239 | end 240 | 241 | def extract_mobile 242 | extract_from_header('Sec-CH-UA-Mobile') || extract_from_header('mobile') 243 | end 244 | 245 | def extract_model 246 | extract_from_header('Sec-CH-UA-Model') || extract_from_header('model') 247 | end 248 | end 249 | end 250 | -------------------------------------------------------------------------------- /lib/device_detector/memory_cache.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class DeviceDetector 4 | class MemoryCache 5 | DEFAULT_MAX_KEYS = 5000 6 | STORES_NIL_VALUE = :__is_nil__ 7 | 8 | attr_reader :data, :max_keys, :lock 9 | private :lock 10 | 11 | def initialize(config) 12 | @data = {} 13 | @max_keys = config[:max_cache_keys] || DEFAULT_MAX_KEYS 14 | @lock = Mutex.new 15 | end 16 | 17 | def set(key, value) 18 | lock.synchronize do 19 | purge_cache 20 | # convert nil values into symbol so we know a value is present 21 | cache_value = value.nil? ? STORES_NIL_VALUE : value 22 | data[String(key)] = cache_value 23 | value 24 | end 25 | end 26 | 27 | def get(key) 28 | value, _hit = get_hit(key) 29 | value 30 | end 31 | 32 | def get_or_set(key, value = nil) 33 | string_key = String(key) 34 | 35 | result, hit = get_hit(string_key) 36 | return result if hit 37 | 38 | value = yield if block_given? 39 | set(string_key, value) 40 | end 41 | 42 | private 43 | 44 | def get_hit(key) 45 | value = data[String(key)] 46 | is_hit = !value.nil? || value == STORES_NIL_VALUE 47 | value = nil if value == STORES_NIL_VALUE 48 | [value, is_hit] 49 | end 50 | 51 | def purge_cache 52 | key_size = data.size 53 | 54 | return if key_size < max_keys 55 | 56 | # always remove about 1/3 of keys to reduce garbage collecting 57 | amount_of_keys = key_size / 3 58 | 59 | data.keys.first(amount_of_keys).each { |key| data.delete(key) } 60 | end 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /lib/device_detector/metadata_extractor.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class DeviceDetector 4 | class MetadataExtractor < Struct.new(:user_agent, :regex_meta) 5 | def call 6 | regex_meta.any? ? extract_metadata : nil 7 | end 8 | 9 | private 10 | 11 | def metadata_string 12 | message = "#{name} (a child of MetadataExtractor) must implement the '#{__method__}' method." 13 | raise NotImplementedError, message 14 | end 15 | 16 | def extract_metadata 17 | user_agent.match(regex) do |match_data| 18 | metadata_string.gsub(/\$(\d)/) do 19 | match_data[Regexp.last_match(1).to_i].to_s 20 | end.strip 21 | end 22 | end 23 | 24 | def regex 25 | @regex ||= regex_meta[:regex] 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/device_detector/model_extractor.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class DeviceDetector 4 | class ModelExtractor < MetadataExtractor 5 | def call 6 | s = super.to_s.gsub('_', ' ').strip 7 | s = s.gsub(/ TD$/i, '') 8 | 9 | return nil if s == 'Build' 10 | 11 | s.empty? ? nil : s 12 | end 13 | 14 | private 15 | 16 | def metadata_string 17 | String(regex_meta[:model]) 18 | end 19 | 20 | def regex 21 | @regex ||= regex_meta[:regex_model] || regex_meta[:regex] 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/device_detector/name_extractor.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class DeviceDetector 4 | class NameExtractor < MetadataExtractor 5 | def call 6 | if /\$[0-9]/ =~ metadata_string 7 | extract_metadata 8 | else 9 | metadata_string 10 | end 11 | end 12 | 13 | private 14 | 15 | def metadata_string 16 | regex_meta[:name] 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/device_detector/os.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'set' 4 | 5 | class DeviceDetector 6 | class OS < Parser 7 | class << self 8 | def mapped_os_version(version, mapping) 9 | return if version.nil? 10 | 11 | major_version = version.split('.').first 12 | 13 | mapping[version] || mapping[major_version] 14 | end 15 | end 16 | 17 | def name 18 | os_info[:name] 19 | end 20 | 21 | def short_name 22 | os_info[:short] 23 | end 24 | 25 | def family 26 | os_info[:family] 27 | end 28 | 29 | def desktop? 30 | DESKTOP_OSS.include?(family) 31 | end 32 | 33 | def full_version 34 | raw_version = super.to_s.split('_').join('.') 35 | raw_version == '' ? nil : raw_version 36 | end 37 | 38 | private 39 | 40 | def os_info 41 | from_cache(['os_info', self.class.name, user_agent]) do 42 | os_name = NameExtractor.new(user_agent, regex_meta).call 43 | if os_name && (short = DOWNCASED_OPERATING_SYSTEMS[os_name.downcase]) 44 | os_name = OPERATING_SYSTEMS[short] 45 | else 46 | short = 'UNK' 47 | end 48 | { name: os_name, short: short, family: FAMILY_TO_OS[short] } 49 | end 50 | end 51 | 52 | # https://github.com/matomo-org/device-detector/blob/75d88bbefb0182f9207c9f48dc39b1bc8c7cc43f/Parser/OperatingSystem.php#L286-L288 53 | DESKTOP_OSS = Set.new( 54 | [ 55 | 'AmigaOS', 'IBM', 'GNU/Linux', 'Mac', 'Unix', 'Windows', 'BeOS', 'Chrome OS', 'Chromium OS' 56 | ] 57 | ) 58 | 59 | # OS short codes mapped to long names 60 | # https://github.com/matomo-org/device-detector/blob/75d88bbefb0182f9207c9f48dc39b1bc8c7cc43f/Parser/OperatingSystem.php#L42-L220 61 | OPERATING_SYSTEMS = { 62 | 'AIX' => 'AIX', 63 | 'AND' => 'Android', 64 | 'ADR' => 'Android TV', 65 | 'ALP' => 'Alpine Linux', 66 | 'AMZ' => 'Amazon Linux', 67 | 'AMG' => 'AmigaOS', 68 | 'ARM' => 'Armadillo OS', 69 | 'ARO' => 'AROS', 70 | 'ATV' => 'tvOS', 71 | 'ARL' => 'Arch Linux', 72 | 'AOS' => 'AOSC OS', 73 | 'ASP' => 'ASPLinux', 74 | 'BTR' => 'BackTrack', 75 | 'SBA' => 'Bada', 76 | 'BYI' => 'Baidu Yi', 77 | 'BEO' => 'BeOS', 78 | 'BLB' => 'BlackBerry OS', 79 | 'QNX' => 'BlackBerry Tablet OS', 80 | 'BOS' => 'Bliss OS', 81 | 'BMP' => 'Brew', 82 | 'BSN' => 'BrightSignOS', 83 | 'CAI' => 'Caixa Mágica', 84 | 'CES' => 'CentOS', 85 | 'CST' => 'CentOS Stream', 86 | 'CLO' => 'Clear Linux OS', 87 | 'CLR' => 'ClearOS Mobile', 88 | 'COS' => 'Chrome OS', 89 | 'CRS' => 'Chromium OS', 90 | 'CHN' => 'China OS', 91 | 'CYN' => 'CyanogenMod', 92 | 'DEB' => 'Debian', 93 | 'DEE' => 'Deepin', 94 | 'DFB' => 'DragonFly', 95 | 'DVK' => 'DVKBuntu', 96 | 'ELE' => 'ElectroBSD', 97 | 'EUL' => 'EulerOS', 98 | 'FED' => 'Fedora', 99 | 'FEN' => 'Fenix', 100 | 'FOS' => 'Firefox OS', 101 | 'FIR' => 'Fire OS', 102 | 'FOR' => 'Foresight Linux', 103 | 'FRE' => 'Freebox', 104 | 'BSD' => 'FreeBSD', 105 | 'FRI' => 'FRITZ!OS', 106 | 'FYD' => 'FydeOS', 107 | 'FUC' => 'Fuchsia', 108 | 'GNT' => 'Gentoo', 109 | 'GNX' => 'GENIX', 110 | 'GEO' => 'GEOS', 111 | 'GNS' => 'gNewSense', 112 | 'GRI' => 'GridOS', 113 | 'GTV' => 'Google TV', 114 | 'HPX' => 'HP-UX', 115 | 'HAI' => 'Haiku OS', 116 | 'IPA' => 'iPadOS', 117 | 'HAR' => 'HarmonyOS', 118 | 'HAS' => 'HasCodingOS', 119 | 'HEL' => 'HELIX OS', 120 | 'IRI' => 'IRIX', 121 | 'INF' => 'Inferno', 122 | 'JME' => 'Java ME', 123 | 'JOL' => 'Joli OS', 124 | 'KOS' => 'KaiOS', 125 | 'KAL' => 'Kali', 126 | 'KAN' => 'Kanotix', 127 | 'KIN' => 'KIN OS', 128 | 'KNO' => 'Knoppix', 129 | 'KTV' => 'KreaTV', 130 | 'KBT' => 'Kubuntu', 131 | 'LIN' => 'GNU/Linux', 132 | 'LND' => 'LindowsOS', 133 | 'LNS' => 'Linspire', 134 | 'LEN' => 'Lineage OS', 135 | 'LIR' => 'Liri OS', 136 | 'LOO' => 'Loongnix', 137 | 'LBT' => 'Lubuntu', 138 | 'LOS' => 'Lumin OS', 139 | 'LUN' => 'LuneOS', 140 | 'VLN' => 'VectorLinux', 141 | 'MAC' => 'Mac', 142 | 'MAE' => 'Maemo', 143 | 'MAG' => 'Mageia', 144 | 'MDR' => 'Mandriva', 145 | 'SMG' => 'MeeGo', 146 | 'MCD' => 'MocorDroid', 147 | 'MON' => 'moonOS', 148 | 'EZX' => 'Motorola EZX', 149 | 'MIN' => 'Mint', 150 | 'MLD' => 'MildWild', 151 | 'MOR' => 'MorphOS', 152 | 'NBS' => 'NetBSD', 153 | 'MTK' => 'MTK / Nucleus', 154 | 'MRE' => 'MRE', 155 | 'NXT' => 'NeXTSTEP', 156 | 'NWS' => 'NEWS-OS', 157 | 'WII' => 'Nintendo', 158 | 'NDS' => 'Nintendo Mobile', 159 | 'NOV' => 'Nova', 160 | 'OS2' => 'OS/2', 161 | 'T64' => 'OSF1', 162 | 'OBS' => 'OpenBSD', 163 | 'OVS' => 'OpenVMS', 164 | 'OVZ' => 'OpenVZ', 165 | 'OWR' => 'OpenWrt', 166 | 'OTV' => 'Opera TV', 167 | 'ORA' => 'Oracle Linux', 168 | 'ORD' => 'Ordissimo', 169 | 'PAR' => 'Pardus', 170 | 'PCL' => 'PCLinuxOS', 171 | 'PIC' => 'PICO OS', 172 | 'PLA' => 'Plasma Mobile', 173 | 'PSP' => 'PlayStation Portable', 174 | 'PS3' => 'PlayStation', 175 | 'PVE' => 'Proxmox VE', 176 | 'PUR' => 'PureOS', 177 | 'QTP' => 'Qtopia', 178 | 'PIO' => 'Raspberry Pi OS', 179 | 'RAS' => 'Raspbian', 180 | 'RHT' => 'Red Hat', 181 | 'RST' => 'Red Star', 182 | 'RED' => 'RedOS', 183 | 'REV' => 'Revenge OS', 184 | 'ROS' => 'RISC OS', 185 | 'ROC' => 'Rocky Linux', 186 | 'ROK' => 'Roku OS', 187 | 'RSO' => 'Rosa', 188 | 'ROU' => 'RouterOS', 189 | 'REM' => 'Remix OS', 190 | 'RRS' => 'Resurrection Remix OS', 191 | 'REX' => 'REX', 192 | 'RZD' => 'RazoDroiD', 193 | 'SAB' => 'Sabayon', 194 | 'SSE' => 'SUSE', 195 | 'SAF' => 'Sailfish OS', 196 | 'SCI' => 'Scientific Linux', 197 | 'SEE' => 'SeewoOS', 198 | 'SER' => 'SerenityOS', 199 | 'SIR' => 'Sirin OS', 200 | 'SLW' => 'Slackware', 201 | 'SOS' => 'Solaris', 202 | 'SBL' => 'Star-Blade OS', 203 | 'SYL' => 'Syllable', 204 | 'SYM' => 'Symbian', 205 | 'SYS' => 'Symbian OS', 206 | 'S40' => 'Symbian OS Series 40', 207 | 'S60' => 'Symbian OS Series 60', 208 | 'SY3' => 'Symbian^3', 209 | 'TEN' => 'TencentOS', 210 | 'TDX' => 'ThreadX', 211 | 'TIZ' => 'Tizen', 212 | 'TIV' => 'TiVo OS', 213 | 'TOS' => 'TmaxOS', 214 | 'TUR' => 'Turbolinux', 215 | 'UBT' => 'Ubuntu', 216 | 'ULT' => 'ULTRIX', 217 | 'UOS' => 'UOS', 218 | 'VID' => 'VIDAA', 219 | 'WAS' => 'watchOS', 220 | 'WER' => 'Wear OS', 221 | 'WTV' => 'WebTV', 222 | 'WHS' => 'Whale OS', 223 | 'WIN' => 'Windows', 224 | 'WCE' => 'Windows CE', 225 | 'WIO' => 'Windows IoT', 226 | 'WMO' => 'Windows Mobile', 227 | 'WPH' => 'Windows Phone', 228 | 'WRT' => 'Windows RT', 229 | 'WPO' => 'WoPhone', 230 | 'XBX' => 'Xbox', 231 | 'XBT' => 'Xubuntu', 232 | 'YNS' => 'YunOS', 233 | 'ZEN' => 'Zenwalk', 234 | 'ZOR' => 'ZorinOS', 235 | 'IOS' => 'iOS', 236 | 'POS' => 'palmOS', 237 | 'WEB' => 'Webian', 238 | 'WOS' => 'webOS' 239 | }.freeze 240 | 241 | DOWNCASED_OPERATING_SYSTEMS = OPERATING_SYSTEMS.each_with_object({}) do |(short, long), h| 242 | h[long.downcase] = short 243 | end.freeze 244 | 245 | APPLE_OS_NAMES = Set.new(%w[iPadOS tvOS watchOS iOS Mac]).freeze 246 | 247 | # https://github.com/matomo-org/device-detector/blob/75d88bbefb0182f9207c9f48dc39b1bc8c7cc43f/Parser/OperatingSystem.php#L227-L269 248 | OS_FAMILIES = { 249 | 'Android' => %w[ AND CYN FIR REM RZD MLD MCD YNS GRI HAR 250 | ADR CLR BOS REV LEN SIR RRS WER PIC ARM 251 | HEL BYI], 252 | 'AmigaOS' => %w[AMG MOR ARO], 253 | 'BlackBerry' => %w[BLB QNX], 254 | 'Brew' => ['BMP'], 255 | 'BeOS' => %w[BEO HAI], 256 | 'Chrome OS' => %w[COS CRS FYD SEE], 257 | 'Firefox OS' => %w[FOS KOS], 258 | 'Gaming Console' => %w[WII PS3], 259 | 'Google TV' => ['GTV'], 260 | 'IBM' => ['OS2'], 261 | 'iOS' => %w[IOS ATV WAS IPA], 262 | 'RISC OS' => ['ROS'], 263 | 'GNU/Linux' => %w[ 264 | LIN ARL DEB KNO MIN UBT KBT XBT LBT FED 265 | RHT VLN MDR GNT SAB SLW SSE CES BTR SAF 266 | ORD TOS RSO DEE FRE MAG FEN CAI PCL HAS 267 | LOS DVK ROK OWR OTV KTV PUR PLA FUC PAR 268 | FOR MON KAN ZEN LND LNS CHN AMZ TEN CST 269 | NOV ROU ZOR RED KAL ORA VID TIV BSN RAS 270 | UOS PIO FRI LIR WEB SER ASP AOS LOO EUL 271 | SCI ALP CLO ROC OVZ PVE RST EZX GNS JOL 272 | TUR QTP WPO 273 | ], 274 | 'Mac' => ['MAC'], 275 | 'Mobile Gaming Console' => %w[PSP NDS XBX], 276 | 'OpenVMS' => ['OVS'], 277 | 'Real-time OS' => %w[MTK TDX MRE JME REX], 278 | 'Other Mobile' => %w[WOS POS SBA TIZ SMG MAE LUN GEO], 279 | 'Symbian' => %w[SYM SYS SY3 S60 S40], 280 | 'Unix' => %w[ 281 | SOS AIX HPX BSD NBS OBS DFB SYL IRI T64 282 | INF ELE GNX ULT NWS NXT SBL 283 | ], 284 | 'WebTV' => ['WTV'], 285 | 'Windows' => ['WIN'], 286 | 'Windows Mobile' => %w[WPH WMO WCE WRT WIO KIN], 287 | 'Other Smart TV' => ['WHS'] 288 | }.freeze 289 | 290 | FAMILY_TO_OS = OS_FAMILIES.each_with_object({}) do |(family, oss), h| 291 | oss.each { |os| h[os] = family } 292 | end.freeze 293 | 294 | # https://github.com/matomo-org/device-detector/blob/75d88bbefb0182f9207c9f48dc39b1bc8c7cc43f/Parser/OperatingSystem.php#L295-L308 295 | FIRE_OS_VERSION_MAPPING = { 296 | '11' => '8', 297 | '10' => '8', 298 | '9' => '7', 299 | '7' => '6', 300 | '5' => '5', 301 | '4.4.3' => '4.5.1', 302 | '4.4.2' => '4', 303 | '4.2.2' => '3', 304 | '4.0.3' => '3', 305 | '4.0.2' => '3', 306 | '4' => '2', 307 | '2' => '1', 308 | }.freeze 309 | 310 | # https://github.com/matomo-org/device-detector/blob/75d88bbefb0182f9207c9f48dc39b1bc8c7cc43f/Parser/OperatingSystem.php#L315-L337 311 | LINEAGE_OS_VERSION_MAPPING = { 312 | '14' => '21', 313 | '13' => '20.0', 314 | '12.1' => '19.1', 315 | '12' => '19.0', 316 | '11' => '18.0', 317 | '10' => '17.0', 318 | '9' => '16.0', 319 | '8.1.0' => '15.1', 320 | '8.0.0' => '15.0', 321 | '7.1.2' => '14.1', 322 | '7.1.1' => '14.1', 323 | '7.0' => '14.0', 324 | '6.0.1' => '13.0', 325 | '6.0' => '13.0', 326 | '5.1.1' => '12.1', 327 | '5.0.2' => '12.0', 328 | '5.0' => '12.0', 329 | '4.4.4' => '11.0', 330 | '4.3' => '10.2', 331 | '4.2.2' => '10.1', 332 | '4.0.4' => '9.1.0' 333 | }.freeze 334 | 335 | def filenames 336 | ['oss.yml'] 337 | end 338 | end 339 | end 340 | -------------------------------------------------------------------------------- /lib/device_detector/parser.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class DeviceDetector 4 | class Parser 5 | ROOT = File.expand_path('../..', __dir__) 6 | 7 | REGEX_CACHE = ::DeviceDetector::MemoryCache.new({}) 8 | private_constant :REGEX_CACHE 9 | 10 | def initialize(user_agent) 11 | @user_agent = user_agent 12 | end 13 | 14 | attr_reader :user_agent 15 | 16 | def name 17 | from_cache(['name', self.class.name, user_agent]) do 18 | NameExtractor.new(user_agent, regex_meta).call 19 | end 20 | end 21 | 22 | def full_version 23 | from_cache(['full_version', self.class.name, user_agent]) do 24 | VersionExtractor.new(user_agent, regex_meta).call 25 | end 26 | end 27 | 28 | private 29 | 30 | def regex_meta 31 | @regex_meta ||= matching_regex || {} 32 | end 33 | 34 | def matching_regex 35 | from_cache([self.class.name, user_agent]) do 36 | regexes.find { |r| user_agent =~ r[:regex] } 37 | end 38 | end 39 | 40 | def regexes 41 | @regexes ||= regexes_for(filepaths) 42 | end 43 | 44 | def filenames 45 | raise NotImplementedError 46 | end 47 | 48 | def filepaths 49 | filenames.map do |filename| 50 | [filename.to_sym, File.join(ROOT, 'regexes', filename)] 51 | end 52 | end 53 | 54 | def regexes_for(file_paths) 55 | REGEX_CACHE.get_or_set(file_paths) do 56 | load_regexes(file_paths).flat_map { |path, regex| parse_regexes(path, regex) } 57 | end 58 | end 59 | 60 | def load_regexes(file_paths) 61 | file_paths.map do |path, full_path| 62 | object = YAML.load_file(full_path) 63 | object = rewrite_device_object!(object) if device_yml_file?(full_path) 64 | object = rewrite_vendor_object!(object) if vendor_yml_file?(full_path) 65 | 66 | [path, symbolize_keys!(object)] 67 | end 68 | end 69 | 70 | def device_yml_file?(file_path) 71 | file_path.include?('/regexes/device/') 72 | end 73 | 74 | def vendor_yml_file?(file_path) 75 | file_path.include?('/regexes/vendorfragments') 76 | end 77 | 78 | def rewrite_vendor_object!(object) 79 | object.map { |key, values| values.map { |v| { 'regex_name' => key, 'regex' => v } } }.flatten 80 | end 81 | 82 | def rewrite_device_object!(object) 83 | object.map { |key, value| [key, { 'regex_name' => key }.merge!(value)] }.to_h 84 | end 85 | 86 | def symbolize_keys!(object) 87 | case object 88 | when Array 89 | object.map! { |v| symbolize_keys!(v) } 90 | when Hash 91 | keys = object.keys 92 | keys.each do |k| 93 | object[k.to_sym] = symbolize_keys!(object.delete(k)) if k.is_a?(String) 94 | end 95 | end 96 | object 97 | end 98 | 99 | def parse_regexes(path, raw_regexes) 100 | raw_regexes.map do |meta| 101 | raise "invalid device spec: #{meta.inspect}" unless meta[:regex].is_a? String 102 | 103 | meta[:regex] = build_regex(meta[:regex]) 104 | meta[:versions].each { |v| v[:regex] = build_regex(v[:regex]) } if meta.key?(:versions) 105 | meta[:path] = path 106 | meta 107 | end 108 | end 109 | 110 | def build_regex(src) 111 | Regexp.new("(?:^|[^A-Z0-9_-]|[^A-Z0-9-]_|sprd-|MZ-)(?:#{src})", Regexp::IGNORECASE) 112 | end 113 | 114 | def from_cache(key, &block) 115 | DeviceDetector.cache.get_or_set(key, &block) 116 | end 117 | end 118 | end 119 | -------------------------------------------------------------------------------- /lib/device_detector/vendor_fragment.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'set' 4 | 5 | class DeviceDetector 6 | class VendorFragment < Parser 7 | def name 8 | vendor_fragment_info 9 | end 10 | 11 | private 12 | 13 | def vendor_fragment_info 14 | from_cache(['vendor_fragment', self.class.name, user_agent]) do 15 | return if regex_meta.nil? || regex_meta.empty? 16 | 17 | regex_meta[:regex_name] 18 | end 19 | end 20 | 21 | def filenames 22 | ['vendorfragments.yml'] 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/device_detector/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class DeviceDetector 4 | VERSION = '1.1.3' 5 | end 6 | -------------------------------------------------------------------------------- /lib/device_detector/version_extractor.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class DeviceDetector 4 | class VersionExtractor < MetadataExtractor 5 | MAJOR_VERSION_2 = Gem::Version.new('2.0') 6 | MAJOR_VERSION_3 = Gem::Version.new('3.0') 7 | MAJOR_VERSION_4 = Gem::Version.new('4.0') 8 | MAJOR_VERSION_8 = Gem::Version.new('8.0') 9 | 10 | def call 11 | simple_version = super&.chomp('.') 12 | 13 | return simple_version unless simple_version&.empty? 14 | 15 | os_version_by_regexes 16 | end 17 | 18 | private 19 | 20 | def os_version_by_regexes 21 | version_matches = regex_meta[:versions] 22 | return '' unless version_matches 23 | 24 | version_matches.detect do |matcher| 25 | user_agent.match(matcher[:regex]) do |match_data| 26 | return matcher[:version].gsub(/\$(\d)/) do 27 | match_data[Regexp.last_match(1).to_i].to_s 28 | end.strip 29 | end 30 | end 31 | 32 | '' 33 | end 34 | 35 | def metadata_string 36 | String(regex_meta[:version]) 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /regexes/client/browser_engine.yml: -------------------------------------------------------------------------------- 1 | ############### 2 | # Device Detector - The Universal Device Detection library for parsing User Agents 3 | # 4 | # @link https://matomo.org 5 | # @license http://www.gnu.org/licenses/lgpl.html LGPL v3 or later 6 | ############### 7 | 8 | - regex: 'NetFront' 9 | name: 'NetFront' 10 | 11 | - regex: 'Edge/' 12 | name: 'Edge' 13 | 14 | - regex: 'Trident' 15 | name: 'Trident' 16 | 17 | - regex: 'Chrome/(?!1?\d\.|2[0-7]\.)' 18 | name: 'Blink' 19 | 20 | - regex: '(?:Apple)?WebKit' 21 | name: 'WebKit' 22 | 23 | - regex: 'Presto' 24 | name: 'Presto' 25 | 26 | - regex: 'Goanna' 27 | name: 'Goanna' 28 | 29 | - regex: '(? e 12 | raise "Failed to parse #{fixture_file}, reason: #{e}" 13 | end 14 | 15 | def str_or_nil(string) 16 | return nil if string.nil? 17 | return nil if string == '' 18 | 19 | string.to_s 20 | end 21 | 22 | fixtures.each do |f| 23 | user_agent = f['user_agent'] 24 | headers = f['headers'] 25 | detector = DeviceDetector.new(user_agent, headers) 26 | os = detector.send(:os) 27 | 28 | describe user_agent do 29 | it 'should be detected' do 30 | if detector.bot? 31 | assert_equal str_or_nil(f['bot']['name']), detector.bot_name, 32 | 'failed bot name detection' 33 | else 34 | if f['client'] 35 | assert_equal str_or_nil(f['client']['name']), detector.name, 36 | 'failed client name detection' 37 | end 38 | 39 | os_family = str_or_nil(f['os_family']) 40 | if os_family != 'Unknown' 41 | if os_family.nil? 42 | assert_nil detector.os_family, 'failed os family detection' 43 | else 44 | assert_equal os_family, detector.os_family, 'failed os family detection' 45 | end 46 | 47 | name = str_or_nil(f['os']['name']) 48 | if name.nil? 49 | assert_nil detector.os_name, 'failed os name detection' 50 | else 51 | assert_equal name, detector.os_name, 'failed os name detection' 52 | end 53 | 54 | short_name = str_or_nil(f['os']['short_name']) 55 | if short_name.nil? && f['os']['name'] 56 | short_name = DeviceDetector::OS::OPERATING_SYSTEMS[f['os']['name']] 57 | end 58 | detector_short_name = detector.client_hint&.os_short_name || os.short_name 59 | 60 | unless short_name.nil? 61 | assert_equal short_name, detector_short_name, 'failed os short name detection' 62 | end 63 | 64 | os_version = str_or_nil(f['os']['version']) 65 | if os_version.nil? 66 | assert_nil detector.os_full_version, 'failed os version detection' 67 | else 68 | assert_equal os_version, detector.os_full_version, 'failed os version detection' 69 | end 70 | end 71 | if f['device'] 72 | expected_type = str_or_nil(f['device']['type']) 73 | actual_type = detector.device_type 74 | 75 | if expected_type != actual_type 76 | # puts "\n", f.inspect, expected_type, actual_type, detector.device_name, regex_meta.inspect 77 | # debugger 78 | # detector.device_type 79 | end 80 | if expected_type.nil? 81 | assert_nil actual_type, 'failed device type detection' 82 | else 83 | assert_equal expected_type, actual_type, 'failed device type detection' 84 | end 85 | 86 | model = str_or_nil(f['device']['model']) 87 | model = model.to_s unless model.nil? 88 | 89 | if model.nil? 90 | assert_nil detector.device_name, 'failed device name detection' 91 | else 92 | assert_equal model, detector.device_name, 'failed device name detection' 93 | end 94 | 95 | brand = str_or_nil(f['device']['brand']) 96 | brand = brand.to_s unless brand.nil? 97 | if brand.nil? 98 | assert_nil detector.device_brand, 'failed brand name detection' 99 | else 100 | assert_equal brand, detector.device_brand, 'failed brand name detection' 101 | end 102 | end 103 | end 104 | end 105 | end 106 | end 107 | end 108 | end 109 | end 110 | -------------------------------------------------------------------------------- /spec/device_detector/device_fixtures_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative '../spec_helper' 2 | 3 | describe DeviceDetector::Device do 4 | fixture_dir = File.expand_path('../fixtures/devices', __dir__) 5 | fixture_files = Dir["#{fixture_dir}/*.yml"] 6 | fixture_files.each do |fixture_file| 7 | describe File.basename(fixture_file) do 8 | fixtures = YAML.load_file(fixture_file) 9 | fixtures.each do |f| 10 | user_agent = f['user_agent'] 11 | headers = f['headers'] 12 | 13 | device = DeviceDetector::Device.new(user_agent, headers) 14 | 15 | describe user_agent do 16 | it 'should be known' do 17 | assert device.known?, "isn't known as a device" 18 | end 19 | 20 | it 'should have the expected model' do 21 | assert_equal f['device']['model'], device.name, 'failed model detection' 22 | end 23 | 24 | it 'should have the expected brand' do 25 | assert_equal f['device']['brand'], device.brand, 'failed brand detection' 26 | end 27 | 28 | it 'should have the expected type' do 29 | expected_device_type = DeviceDetector::Device::DEVICE_NAMES[f['device']['type']] 30 | assert_equal expected_device_type, device.type, 'failed device name detection' 31 | end 32 | end 33 | end 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /spec/device_detector/device_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative '../spec_helper' 4 | 5 | describe DeviceDetector::Device do 6 | subject { DeviceDetector::Device.new(user_agent) } 7 | 8 | alias_method :device, :subject 9 | 10 | describe '#name' do 11 | describe 'when models are nested' do 12 | let(:user_agent) do 13 | 'Mozilla/5.0 (iPhone; CPU iPhone OS 8_1_3 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) Mobile/12B466 [FBDV/iPhone7,2]' 14 | end 15 | 16 | it 'finds an Apple iPhone 6' do 17 | value(device.name).must_equal 'iPhone 6' 18 | end 19 | end 20 | 21 | describe 'when models are NOT nested' do 22 | let(:user_agent) { 'AIRNESS-AIR99/REV 2.2.1/Teleca Q03B1' } 23 | 24 | it 'finds an Airness AIR99' do 25 | value(device.name).must_equal 'AIR99' 26 | end 27 | end 28 | 29 | describe 'when it cannot find a device name' do 30 | let(:user_agent) { 'UNKNOWN MODEL NAME' } 31 | 32 | it 'returns nil' do 33 | value(device.name).must_be_nil 34 | end 35 | end 36 | end 37 | 38 | describe '#type' do 39 | describe 'when models are nested' do 40 | let(:user_agent) do 41 | 'Mozilla/5.0 (iPhone; CPU iPhone OS 8_1_3 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) Mobile/12B466 [FBDV/iPhone7,2]' 42 | end 43 | 44 | it 'finds device of Apple iPhone 6' do 45 | value(device.type).must_equal 'smartphone' 46 | end 47 | end 48 | 49 | describe 'when models are NOT nested' do 50 | let(:user_agent) { 'AIRNESS-AIR99/REV 2.2.1/Teleca Q03B1' } 51 | 52 | it 'finds the device of Airness AIR99' do 53 | value(device.type).must_equal 'feature phone' 54 | end 55 | end 56 | 57 | describe 'when it cannot find a device type' do 58 | let(:user_agent) { 'UNKNOWN MODEL TYPE' } 59 | 60 | it 'returns nil' do 61 | value(device.type).must_be_nil 62 | end 63 | end 64 | 65 | describe 'device not specified in nested block' do 66 | let(:user_agent) do 67 | 'Mozilla/5.0 (Linux; Android 4.4.2; es-us; SAMSUNG SM-G900F Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko)' 68 | end 69 | 70 | it 'falls back to top-level device' do 71 | value(device.type).must_equal 'smartphone' 72 | end 73 | end 74 | end 75 | 76 | describe 'concrete device types' do 77 | describe 'mobiles' do 78 | let(:user_agent) do 79 | 'Mozilla/5.0 (Linux; Android 4.4.2; es-us; SAMSUNG SM-G900F Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko)' 80 | end 81 | 82 | it 'identifies the device' do 83 | value(device.name).must_equal 'Galaxy S5' 84 | value(device.type).must_equal 'smartphone' 85 | value(device.brand).must_equal 'Samsung' 86 | end 87 | end 88 | 89 | describe 'cameras' do 90 | let(:user_agent) do 91 | 'Mozilla/5.0 (Linux; U; Android 4.0; xx-xx; EK-GC100 Build/IMM76D) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30' 92 | end 93 | 94 | it 'identifies the device' do 95 | value(device.name).must_equal 'Galaxy Camera' 96 | value(device.type).must_equal 'camera' 97 | value(device.brand).must_equal 'Samsung' 98 | end 99 | end 100 | 101 | describe 'car browsers' do 102 | let(:user_agent) do 103 | 'Mozilla/5.0 (X11; Linux) AppleWebKit/534.34 (KHTML, like Gecko) QtCarBrowser Safari/534.34' 104 | end 105 | 106 | it 'identifies the device' do 107 | value(device.name).must_equal 'Model S' 108 | value(device.type).must_equal 'car browser' 109 | value(device.brand).must_equal 'Tesla' 110 | end 111 | end 112 | 113 | describe '(gaming) consoles' do 114 | let(:user_agent) { 'Opera/9.30 (Nintendo Wii; U; ; 2047-7;en)' } 115 | 116 | it 'identifies the device' do 117 | value(device.name).must_equal 'Wii' 118 | value(device.type).must_equal 'console' 119 | value(device.brand).must_equal 'Nintendo' 120 | end 121 | end 122 | 123 | describe 'portable media players' do 124 | let(:user_agent) do 125 | 'Mozilla/5.0 (iPod touch; CPU iPhone OS 7_0_6 like Mac OS X) AppleWebKit/537.51.1 (KHTML, like Gecko) Version/7.0 Mobile/11B651 Safari/9537.53' 126 | end 127 | 128 | it 'identifies the device' do 129 | value(device.name).must_equal 'iPod Touch' 130 | value(device.type).must_equal 'portable media player' 131 | value(device.brand).must_equal 'Apple' 132 | end 133 | end 134 | 135 | describe 'televisions' do 136 | let(:user_agent) do 137 | 'Mozilla/5.0 (Unknown; Linux armv7l) AppleWebKit/537.1+ (KHTML, like Gecko) Safari/537.1+ HbbTV/1.1.1 ( ;LGE ;NetCast 4.0 ;03.10.81 ;1.0M ;)' 138 | end 139 | 140 | it 'identifies the device' do 141 | value(device.name).must_equal 'NetCast 4.0' 142 | value(device.type).must_equal 'tv' 143 | value(device.brand).must_equal 'LG' 144 | end 145 | end 146 | end 147 | end 148 | -------------------------------------------------------------------------------- /spec/device_detector/memory_cache_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative '../spec_helper' 4 | 5 | describe DeviceDetector::MemoryCache do 6 | let(:subject) { DeviceDetector::MemoryCache.new(config) } 7 | 8 | let(:config) { {} } 9 | 10 | describe '#set' do 11 | describe 'string key' do 12 | let(:key) { 'string' } 13 | 14 | it 'sets the value under the key' do 15 | subject.set(key, 'value') 16 | 17 | value(subject.data[key]).must_equal 'value' 18 | end 19 | 20 | it 'returns the value' do 21 | value(subject.set(key, 'value')).must_equal 'value' 22 | value(subject.set(key, false)).must_equal false 23 | assert_nil subject.set(key, nil) 24 | end 25 | end 26 | 27 | describe 'array key' do 28 | let(:key) { %w[string1 string2] } 29 | 30 | it 'sets the value under the key' do 31 | subject.set(key, 'value') 32 | 33 | value(subject.data[String(key)]).must_equal 'value' 34 | end 35 | end 36 | 37 | describe 'nil value' do 38 | let(:key) { 'string' } 39 | let(:internal_value) { DeviceDetector::MemoryCache::STORES_NIL_VALUE } 40 | 41 | it 'sets the value under the key' do 42 | subject.set(key, nil) 43 | 44 | value(subject.data[String(key)]).must_equal internal_value 45 | assert_nil subject.get(key) 46 | end 47 | 48 | it 'sets the value under the key' do 49 | subject.get_or_set(key, nil) 50 | 51 | value(subject.data[String(key)]).must_equal internal_value 52 | assert_nil subject.get(key) 53 | end 54 | end 55 | 56 | describe 'false value' do 57 | let(:key) { 'string' } 58 | 59 | it 'sets the value under the key' do 60 | subject.set(key, false) 61 | 62 | value(subject.data[String(key)]).must_equal false 63 | value(subject.get(key)).must_equal false 64 | end 65 | 66 | it 'sets the value under the key' do 67 | subject.get_or_set(key, false) 68 | 69 | value(subject.data[String(key)]).must_equal false 70 | value(subject.get(key)).must_equal false 71 | end 72 | end 73 | end 74 | 75 | describe '#get' do 76 | describe 'string key' do 77 | let(:key) { 'string' } 78 | 79 | it 'gets the value for the key' do 80 | subject.data[key] = 'value' 81 | 82 | value(subject.get(key)).must_equal 'value' 83 | end 84 | end 85 | 86 | describe 'array key' do 87 | let(:key) { %w[string1 string2] } 88 | 89 | it 'gets the value for the key' do 90 | subject.data[String(key)] = 'value' 91 | 92 | value(subject.get(key)).must_equal 'value' 93 | end 94 | end 95 | end 96 | 97 | describe '#get_or_set' do 98 | let(:key) { 'string' } 99 | 100 | describe 'value already present' do 101 | it 'gets the value for the key from cache' do 102 | subject.data[key] = 'value' 103 | 104 | block_called = false 105 | value = subject.get_or_set(key) do 106 | block_called = true 107 | end 108 | 109 | value(value).must_equal 'value' 110 | value(block_called).must_equal false 111 | end 112 | 113 | it 'returns the value' do 114 | subject.data[key] = 'value2' 115 | value(subject.get_or_set(key, 'value')).must_equal 'value2' 116 | end 117 | end 118 | 119 | describe 'value not yet present' do 120 | it 'evaluates the block and sets the result' do 121 | block_called = false 122 | subject.get_or_set(key) do 123 | block_called = true 124 | end 125 | 126 | value(block_called).must_equal true 127 | value(subject.data[key]).must_equal true 128 | end 129 | 130 | it 'returns the value' do 131 | value(subject.get_or_set(key, 'value')).must_equal 'value' 132 | end 133 | end 134 | end 135 | 136 | describe 'cache purging' do 137 | let(:config) { { max_cache_keys: 3 } } 138 | 139 | it 'purges the cache when key size arrives at max' do 140 | subject.set('1', 'foo') 141 | subject.set('2', 'bar') 142 | subject.set('3', 'baz') 143 | subject.set('4', 'boz') 144 | 145 | value(subject.data.keys.size).must_equal 3 146 | end 147 | end 148 | end 149 | -------------------------------------------------------------------------------- /spec/device_detector/model_extractor_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative '../spec_helper' 2 | 3 | describe DeviceDetector::ModelExtractor do 4 | subject { DeviceDetector::ModelExtractor.new(user_agent, regex_meta) } 5 | 6 | alias_method :extractor, :subject 7 | 8 | describe '#call' do 9 | describe 'when matching against dynamic model' do 10 | let(:regex_meta) do 11 | { 12 | regex: '(?:Apple-)?iPhone ?(3GS?|4S?|5[CS]?|6(:? Plus)?)?', 13 | model: 'iPhone $1', 14 | device: 'smartphone' 15 | } 16 | end 17 | 18 | describe 'when no dynamic match is found' do 19 | let(:user_agent) do 20 | 'Mozilla/5.0 (iPhone; CPU iPhone OS 8_1_3 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) Version/8.0 Mobile/12B466 Safari/600.1.4' 21 | end 22 | let(:device_name) { 'iPhone' } 23 | 24 | it 'returns the textual portion without trailing whitespace' do 25 | value(extractor.call).must_equal device_name 26 | end 27 | end 28 | 29 | describe 'when a dynamic match is found' do 30 | let(:user_agent) do 31 | 'Mozilla/5.0 (iPhone 5S; CPU iPhone OS 8_1_3 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) Version/8.0 Mobile/12B466 Safari/600.1.4' 32 | end 33 | let(:device_name) { 'iPhone 5S' } 34 | 35 | it 'returns the full device name' do 36 | value(extractor.call).must_equal device_name 37 | end 38 | end 39 | end 40 | 41 | describe 'when matching against static model' do 42 | let(:user_agent) do 43 | 'Mozilla/5.0 (iPhone; CPU iPhone OS 8_0 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) Mobile/12A365 Weibo (iPhone7,2)' 44 | end 45 | let(:device_name) { 'iPhone 6' } 46 | let(:regex_meta) do 47 | { 48 | regex: '(?:Apple-)?iPhone7[C,]2', 49 | model: 'iPhone 6', 50 | device: 'smartphone' 51 | } 52 | end 53 | 54 | it 'returns the model name' do 55 | value(extractor.call).must_equal device_name 56 | end 57 | end 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /spec/device_detector/os_fixtures_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative '../spec_helper' 2 | 3 | describe DeviceDetector::OS do 4 | fixture_dir = File.expand_path('../fixtures/parser', __dir__) 5 | fixture_files = Dir["#{fixture_dir}/oss.yml"] 6 | fixture_files.each do |fixture_file| 7 | describe File.basename(fixture_file) do 8 | fixtures = YAML.load(File.read(fixture_file)) 9 | fixtures.each do |f| 10 | user_agent = f['user_agent'] 11 | headers = f['headers'] 12 | 13 | describe user_agent do 14 | it 'should have the expected name' do 15 | device = DeviceDetector.new(user_agent, headers) 16 | assert_equal f['os']['name'], device.os_name, 'failed OS name detection' 17 | end 18 | end 19 | end 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /spec/device_detector/version_extractor_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative '../spec_helper' 2 | 3 | describe DeviceDetector::VersionExtractor do 4 | subject { DeviceDetector::VersionExtractor.new(user_agent, regex_meta) } 5 | 6 | alias_method :extractor, :subject 7 | 8 | describe '#call' do 9 | describe 'extractor without version' do 10 | let(:user_agent) do 11 | 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0; Avant Browser; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0)' 12 | end 13 | 14 | let(:regex_meta) do 15 | { 16 | regex: 'Avant Browser', 17 | name: 'Avant Browser', 18 | version: '' 19 | } 20 | end 21 | 22 | it 'returns nil' do 23 | value(extractor.call).must_equal '' 24 | end 25 | end 26 | 27 | describe 'regex with dynamic matching' do 28 | let(:user_agent) do 29 | 'Mozilla/5.0 (X11; U; Linux i686; nl; rv:1.8.1b2) Gecko/20060821 BonEcho/2.0b2 (Debian-1.99+2.0b2+dfsg-1)' 30 | end 31 | let(:version) { 'BonEcho (2.0)' } 32 | let(:regex_meta) do 33 | { 34 | regex: '(BonEcho|GranParadiso|Lorentz|Minefield|Namoroka|Shiretoko)/(\d+[\.\d]+)', 35 | name: 'Firefox', 36 | version: '$1 ($2)' 37 | } 38 | end 39 | 40 | it 'returns the correct version' do 41 | value(extractor.call).must_equal version 42 | end 43 | 44 | it 'removes trailing white spaces' do 45 | regex_meta[:version] = regex_meta[:version] + ' ' 46 | value(extractor.call).must_equal version 47 | end 48 | end 49 | 50 | describe 'extractor with fixed version' do 51 | let(:user_agent) { 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0)' } 52 | let(:regex_meta) do 53 | { 54 | regex: 'MSIE.*Trident/4.0', 55 | version: '8.0' 56 | } 57 | end 58 | 59 | it 'returns the correct version' do 60 | value(extractor.call).must_equal '8.0' 61 | end 62 | end 63 | 64 | describe 'unknown user agent' do 65 | let(:user_agent) { 'garbage' } 66 | let(:regex_meta) { {} } 67 | 68 | it 'returns nil' do 69 | value(extractor.call).must_be_nil 70 | end 71 | end 72 | end 73 | end 74 | -------------------------------------------------------------------------------- /spec/device_detector_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'spec_helper' 4 | 5 | describe DeviceDetector do 6 | subject { DeviceDetector.new(user_agent) } 7 | 8 | alias_method :client, :subject 9 | 10 | describe 'known user agent' do 11 | describe 'desktop chrome browser' do 12 | let(:user_agent) do 13 | 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.69' 14 | end 15 | 16 | describe '#name' do 17 | it 'returns the name' do 18 | value(client.name).must_equal 'Chrome' 19 | end 20 | end 21 | 22 | describe '#full_version' do 23 | it 'returns the full version' do 24 | value(client.full_version).must_equal '30.0.1599.69' 25 | end 26 | end 27 | 28 | describe '#os_family' do 29 | it 'returns the operating system name' do 30 | value(client.os_family).must_equal 'Mac' 31 | end 32 | end 33 | 34 | describe '#os_name' do 35 | it 'returns the operating system name' do 36 | value(client.os_name).must_equal 'Mac' 37 | end 38 | end 39 | 40 | describe '#os_full_version' do 41 | it 'returns the operating system full version' do 42 | value(client.os_full_version).must_equal '10.8.5' 43 | end 44 | end 45 | 46 | describe '#known?' do 47 | it 'returns true' do 48 | value(client.known?).must_equal true 49 | end 50 | end 51 | 52 | describe '#bot?' do 53 | it 'returns false' do 54 | value(client.bot?).must_equal false 55 | end 56 | end 57 | 58 | describe '#bot_name' do 59 | it 'returns nil' do 60 | value(client.bot_name).must_be_nil 61 | end 62 | end 63 | end 64 | 65 | describe 'ubuntu linux' do 66 | let(:user_agent) do 67 | 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.101 Safari/537.36' 68 | end 69 | 70 | describe '#os_family' do 71 | it 'returns the operating system name' do 72 | value(client.os_family).must_equal 'GNU/Linux' 73 | end 74 | end 75 | 76 | describe '#os_name' do 77 | it 'returns the operating system name' do 78 | value(client.os_name).must_equal 'Ubuntu' 79 | end 80 | end 81 | end 82 | 83 | describe 'firefox mobile phone' do 84 | let(:user_agent) { 'Mozilla/5.0 (Android 7.0; Mobile; rv:53.0) Gecko/53.0 Firefox/53.0' } 85 | 86 | it 'detects smartphone' do 87 | value(client.device_type).must_equal 'smartphone' 88 | end 89 | end 90 | 91 | describe 'firefox mobile tablet' do 92 | let(:user_agent) { 'Mozilla/5.0 (Android 6.0.1; Tablet; rv:47.0) Gecko/47.0 Firefox/47.0' } 93 | 94 | it 'detects tablet' do 95 | value(client.device_type).must_equal 'tablet' 96 | end 97 | end 98 | end 99 | 100 | describe 'unknown user agent' do 101 | let(:user_agent) { 'garbage123' } 102 | 103 | describe '#name' do 104 | it 'returns nil' do 105 | value(client.name).must_be_nil 106 | end 107 | end 108 | 109 | describe '#full_version' do 110 | it 'returns nil' do 111 | value(client.full_version).must_be_nil 112 | end 113 | end 114 | 115 | describe '#os_name' do 116 | it 'returns nil' do 117 | value(client.os_name).must_be_nil 118 | end 119 | end 120 | 121 | describe '#os_full_version' do 122 | it 'returns nil' do 123 | value(client.os_full_version).must_be_nil 124 | end 125 | end 126 | 127 | describe '#known?' do 128 | it 'returns false' do 129 | value(client.known?).must_equal false 130 | end 131 | end 132 | 133 | describe '#bot?' do 134 | it 'returns false' do 135 | value(client.bot?).must_equal false 136 | end 137 | end 138 | 139 | describe '#bot_name' do 140 | it 'returns nil' do 141 | value(client.bot_name).must_be_nil 142 | end 143 | end 144 | end 145 | 146 | describe 'user agent is nil' do 147 | let(:user_agent) { nil } 148 | 149 | describe '#name' do 150 | it 'returns nil' do 151 | value(client.name).must_be_nil 152 | end 153 | end 154 | 155 | describe '#full_version' do 156 | it 'returns nil' do 157 | value(client.full_version).must_be_nil 158 | end 159 | end 160 | 161 | describe '#os_name' do 162 | it 'returns nil' do 163 | value(client.os_name).must_be_nil 164 | end 165 | end 166 | 167 | describe '#os_full_version' do 168 | it 'returns nil' do 169 | value(client.os_full_version).must_be_nil 170 | end 171 | end 172 | 173 | describe '#known?' do 174 | it 'returns false' do 175 | value(client.known?).must_equal false 176 | end 177 | end 178 | 179 | describe '#bot?' do 180 | it 'returns false' do 181 | value(client.bot?).must_equal false 182 | end 183 | end 184 | 185 | describe '#bot_name' do 186 | it 'returns nil' do 187 | value(client.bot_name).must_be_nil 188 | end 189 | end 190 | end 191 | 192 | describe 'wrongly encoded user agent' do 193 | let(:user_agent) { 'Mon User-Agent personnalisé'.dup.force_encoding('ASCII-8BIT') } 194 | 195 | describe '#name' do 196 | it 'returns nil' do 197 | value(client.name).must_be_nil 198 | end 199 | end 200 | 201 | describe '#full_version' do 202 | it 'returns nil' do 203 | value(client.full_version).must_be_nil 204 | end 205 | end 206 | 207 | describe '#os_name' do 208 | it 'returns nil' do 209 | value(client.os_name).must_be_nil 210 | end 211 | end 212 | 213 | describe '#os_full_version' do 214 | it 'returns nil' do 215 | value(client.os_full_version).must_be_nil 216 | end 217 | end 218 | 219 | describe '#known?' do 220 | it 'returns false' do 221 | value(client.known?).must_equal false 222 | end 223 | end 224 | 225 | describe '#bot?' do 226 | it 'returns false' do 227 | value(client.bot?).must_equal false 228 | end 229 | end 230 | 231 | describe '#bot_name' do 232 | it 'returns nil' do 233 | value(client.bot_name).must_be_nil 234 | end 235 | end 236 | end 237 | 238 | describe 'bot' do 239 | let(:user_agent) { 'Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)' } 240 | 241 | describe '#name' do 242 | it 'returns nil' do 243 | value(client.name).must_be_nil 244 | end 245 | end 246 | 247 | describe '#full_version' do 248 | it 'returns nil' do 249 | value(client.full_version).must_be_nil 250 | end 251 | end 252 | 253 | describe '#os_name' do 254 | it 'returns nil' do 255 | value(client.os_name).must_be_nil 256 | end 257 | end 258 | 259 | describe '#os_full_version' do 260 | it 'returns nil' do 261 | value(client.os_full_version).must_be_nil 262 | end 263 | end 264 | 265 | describe '#known?' do 266 | it 'returns false' do 267 | value(client.known?).must_equal false 268 | end 269 | end 270 | 271 | describe '#bot?' do 272 | it 'returns true' do 273 | value(client.bot?).must_equal true 274 | end 275 | end 276 | 277 | describe '#bot_name' do 278 | it 'returns the name of the bot' do 279 | value(client.bot_name).must_equal 'Googlebot' 280 | end 281 | end 282 | end 283 | end 284 | -------------------------------------------------------------------------------- /spec/fixtures/client/feed_reader.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - 3 | user_agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.21 (KHTML, like Gecko) akregator/4.11.5 Safari/537.21 4 | client: 5 | type: feed reader 6 | name: Akregator 7 | version: 4.11.5 8 | - 9 | user_agent: Akregator/4.12.3; syndication SUSE 10 | client: 11 | type: feed reader 12 | name: Akregator 13 | version: 4.12.3 14 | - 15 | user_agent: Akregator/1.2.9; librss/remnants 16 | client: 17 | type: feed reader 18 | name: Akregator 19 | version: 1.2.9 20 | - 21 | user_agent: Apple-PubSub/65.28 22 | client: 23 | type: feed reader 24 | name: Apple PubSub 25 | version: "65.28" 26 | - 27 | user_agent: FeedDemon/4.5 (http://www.feeddemon.com/; Microsoft Windows) 28 | client: 29 | type: feed reader 30 | name: FeedDemon 31 | version: "4.5" 32 | - 33 | user_agent: FeedDemon/4.5 (http://www.feeddemon.com/; Microsoft Windows XP) 34 | client: 35 | type: feed reader 36 | name: FeedDemon 37 | version: "4.5" 38 | - 39 | user_agent: FeeddlerPro/2.4 CFNetwork/672.0.8 Darwin/14.0.0 40 | client: 41 | type: feed reader 42 | name: Feeddler RSS Reader 43 | version: "2.4" 44 | - 45 | user_agent: FeeddlerRSS/2.4 CFNetwork/548.1.4 Darwin/11.0.0 46 | client: 47 | type: feed reader 48 | name: Feeddler RSS Reader 49 | version: "2.4" 50 | - 51 | user_agent: FeeddlerRSS 2.4 (iPad; iPhone OS 5.1.1; en_US) 52 | client: 53 | type: feed reader 54 | name: Feeddler RSS Reader 55 | version: "2.4" 56 | - 57 | user_agent: JetBrains Omea Reader 2.2 (http://www.jetbrains.com/omea/reader/) 58 | client: 59 | type: feed reader 60 | name: JetBrains Omea Reader 61 | version: "2.2" 62 | - 63 | user_agent: Liferea/1.6.4 (Linux; en_US.UTF-8; http://liferea.sf.net/) 64 | client: 65 | type: feed reader 66 | name: Liferea 67 | version: 1.6.4 68 | - 69 | user_agent: Liferea/1.10-RC1 (Linux; en_GB.UTF-8; http://liferea.sf.net/) 70 | client: 71 | type: feed reader 72 | name: Liferea 73 | version: "1.10" 74 | - 75 | user_agent: Liferea/1.10.6 (Linux; en_US.UTF8; http://liferea.sf.net/) AppleWebKit (KHTML, like Gecko) 76 | client: 77 | type: feed reader 78 | name: Liferea 79 | version: 1.10.6 80 | - 81 | user_agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.74.9 (KHTML, like Gecko) Version/6.0 NetNewsWire/4.0.0 82 | client: 83 | type: feed reader 84 | name: NetNewsWire 85 | version: 4.0.0 86 | - 87 | user_agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.74.9 (KHTML, like Gecko) NetNewsWire/3.3.2 88 | client: 89 | type: feed reader 90 | name: NetNewsWire 91 | version: 3.3.2 92 | - 93 | user_agent: NetNewsWire/4.0.0 (Mac OS X; http://netnewswireapp.com/mac/; gzip-happy) 94 | client: 95 | type: feed reader 96 | name: NetNewsWire 97 | version: 4.0.0 98 | - 99 | user_agent: newsbeuter/2.7 (Linux x86_64) 100 | client: 101 | type: feed reader 102 | name: Newsbeuter 103 | version: "2.7" 104 | - 105 | user_agent: NewsBlur iPhone App v3.6 106 | client: 107 | type: feed reader 108 | name: NewsBlur Mobile App 109 | version: "3.6" 110 | - 111 | user_agent: NewsBlur iPad App v3.6 112 | client: 113 | type: feed reader 114 | name: NewsBlur Mobile App 115 | version: "3.6" 116 | - 117 | user_agent: NewsBlur/4.0.1 CFNetwork/672.1.13 Darwin/14.0.0 118 | client: 119 | type: feed reader 120 | name: NewsBlur 121 | version: 4.0.1 122 | - 123 | user_agent: newsbeuter/2.4 (Linux 3.2.0-23-generic; i686; http://www.newsbeuter.org/) libcurl/7.22.0 GnuTLS/2.12.14 zlib/1.2.3.4 libidn/1.23 librtmp/2.3 124 | client: 125 | type: feed reader 126 | name: Newsbeuter 127 | version: "2.4" 128 | - 129 | user_agent: Pulp/1.5.2 (iPad; http://www.acrylicapps.com/pulp/) 130 | client: 131 | type: feed reader 132 | name: Pulp 133 | version: 1.5.2 134 | - 135 | user_agent: ReadKit/2.4.0 (Mac OS X Version 10.9.2 (Build 13C64)) 136 | client: 137 | type: feed reader 138 | name: ReadKit 139 | version: 2.4.0 140 | - 141 | user_agent: 'ReadKit/7017 CFNetwork/673.2.1 Darwin/13.1.0 (x86_64) (MacBookPro10%2C1)' 142 | client: 143 | type: feed reader 144 | name: ReadKit 145 | version: "7017" 146 | - 147 | user_agent: Reeder/3.2 CFNetwork/672.1.12 Darwin/14.0.0 148 | client: 149 | type: feed reader 150 | name: Reeder 151 | version: "3.2" 152 | - 153 | user_agent: RssBandit/1.9.0.1002 154 | client: 155 | type: feed reader 156 | name: RSS Bandit 157 | version: 1.9.0.1002 158 | - 159 | user_agent: RssBandit/1.9.0.1002 (.NET CLR 2.0.50727.7512; WinNT 6.2.9200.0; http://www.rssbandit.org) 160 | client: 161 | type: feed reader 162 | name: RSS Bandit 163 | version: 1.9.0.1002 164 | - 165 | user_agent: RSS Junkie Daemon 166 | client: 167 | type: feed reader 168 | name: RSS Junkie 169 | version: "" 170 | - 171 | user_agent: RSSOwl/2.2.1.201312301314 (Windows; U; en) 172 | client: 173 | type: feed reader 174 | name: RSSOwl 175 | version: 2.2.1.201312301314 176 | - 177 | user_agent: Stringer (https://github.com/swanson/stringer) 178 | client: 179 | type: feed reader 180 | name: Stringer 181 | version: "" 182 | -------------------------------------------------------------------------------- /spec/fixtures/client/mediaplayer.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - 3 | user_agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.28) Gecko/20130316 Songbird/1.12.1 (20140112193149) 4 | client: 5 | type: mediaplayer 6 | name: Songbird 7 | version: 1.12.1 8 | - 9 | user_agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.28) Gecko/20130316 Nightingale/1.12.2 (20140112193149) 10 | client: 11 | type: mediaplayer 12 | name: Nightingale 13 | version: 1.12.2 14 | - 15 | user_agent: iTunes/10.2.1 (Macintosh; Intel Mac OS X 10.7) AppleWebKit/534.20.8 16 | client: 17 | type: mediaplayer 18 | name: iTunes 19 | version: 10.2.1 20 | - 21 | user_agent: iTunes/10.2.1 (Windows; Microsoft Windows 7 Enterprise Edition Service Pack 1 (Build 7601)) AppleWebKit/533.20.25 22 | client: 23 | type: mediaplayer 24 | name: iTunes 25 | version: 10.2.1 26 | - 27 | user_agent: VLC/2.1.0 LibVLC/2.1.0 28 | client: 29 | type: mediaplayer 30 | name: VLC 31 | version: 2.1.0 32 | - 33 | user_agent: LibVLC/2.2.3 (LIVE555 Streaming Media v2015.10.12) 34 | client: 35 | type: mediaplayer 36 | name: VLC 37 | version: 2.2.3 38 | - 39 | user_agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; FunWebProducts; SLCC1; .NET CLR 2.0.50727; Media Center PC 5.0; .NET CLR 3.0.04506; Windows-Media-Player/10.00.00.3990) 40 | client: 41 | type: mediaplayer 42 | name: Windows Media Player 43 | version: 10.00.00.3990 44 | - 45 | user_agent: Windows-Media-Player/11.0.6001.7000 46 | client: 47 | type: mediaplayer 48 | name: Windows Media Player 49 | version: 11.0.6001.7000 50 | - 51 | user_agent: SAMSUNG-GT-S3850/S3850CXKD1 SHP/VPP/R5 Dolfin/2.0 NexPlayer/3.0 SMM-MMS/1.2.0 profile/MIDP-2.1 configuration/CLDC-1.1 OPN-B 52 | client: 53 | type: mediaplayer 54 | name: NexPlayer 55 | version: "3.0" 56 | - 57 | user_agent: Banshee 1.5.1 (http://banshee-project.org/) 58 | client: 59 | type: mediaplayer 60 | name: Banshee 61 | version: 1.5.1 62 | - 63 | user_agent: Banshee/2.6.2 (http://banshee-project.org/) 64 | client: 65 | type: mediaplayer 66 | name: Banshee 67 | version: 2.6.2 68 | - 69 | user_agent: QuickTime/7.6.6 (qtver=7.6.6;cpu=IA32;os=Mac 10.6.8) 70 | client: 71 | type: mediaplayer 72 | name: QuickTime 73 | version: 7.6.6 74 | - 75 | user_agent: QuickTime.7.7.4 (qtver=7.7.4;os=Windows NT 6.0Service Pack 2) 76 | client: 77 | type: mediaplayer 78 | name: QuickTime 79 | version: 7.7.4 80 | - 81 | user_agent: QuickTime (qtver=7.0.2a26;os=Windows NT 6.0) 82 | client: 83 | type: mediaplayer 84 | name: QuickTime 85 | version: 7.0.2 86 | - 87 | user_agent: QuickTime E-/7.7.5 (qtver=7.7.5;os=Windows NT 6.1) 88 | client: 89 | type: mediaplayer 90 | name: QuickTime 91 | version: 7.7.5 92 | - 93 | user_agent: FlyCast/1.34 (BlackBerry; 8330/4.5.0.131 Profile/MIDP-2.0 Configuration/CLDC-1.1 VendorID/-1) 94 | client: 95 | type: mediaplayer 96 | name: FlyCast 97 | version: "1.34" 98 | - 99 | user_agent: XBMC/9.04 r19840 (Mac OS X; Darwin 9.6.0; http://www.xbmc.org) 100 | client: 101 | type: mediaplayer 102 | name: XBMC 103 | version: "9.04" 104 | - 105 | user_agent: XBMC/9.04-beta1 r19639 (Windows; Windows XP Professional Service Pack 2 build 2600; http://www.xbmc.org) 106 | client: 107 | type: mediaplayer 108 | name: XBMC 109 | version: "9.04" 110 | - 111 | user_agent: SubStream/0.7 CFNetwork/485.12.30 Darwin/10.4.0 112 | client: 113 | type: mediaplayer 114 | name: SubStream 115 | version: "0.7" 116 | - 117 | user_agent: MediaMonkey 4.1.1.1703 118 | client: 119 | type: mediaplayer 120 | name: MediaMonkey 121 | version: 4.1.1.1703 122 | - 123 | user_agent: Clementine 1.2.2 124 | client: 125 | type: mediaplayer 126 | name: Clementine 127 | version: 1.2.2 128 | - 129 | user_agent: WAFA/1.2.10 (Linux; Android 4.1; Winamp) Replicant/1.0 130 | client: 131 | type: mediaplayer 132 | name: Winamp 133 | version: "" 134 | - 135 | user_agent: WinampMPEG/5.66, Ultravox/2.1 136 | client: 137 | type: mediaplayer 138 | name: Winamp 139 | version: "5.66" 140 | - 141 | user_agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9) Gecko Miro/2.0.4 (http://www.getmiro.com/) 142 | client: 143 | type: mediaplayer 144 | name: Miro 145 | version: 2.0.4 146 | - 147 | user_agent: Miro/3.0.1 (http://www.getmiro.com/; Darwin 8.11.1 i386) 148 | client: 149 | type: mediaplayer 150 | name: Miro 151 | version: 3.0.1 152 | - 153 | user_agent: Kodi/14.0 (Macintosh; Intel Mac OS X 10_10_3) App_Bitness/64 Version/14.0-Git:2014-12-23-ad747d9-dirty 154 | client: 155 | type: mediaplayer 156 | name: Kodi 157 | version: "14.0" 158 | - 159 | user_agent: foobar2000/1.3.10 160 | client: 161 | type: mediaplayer 162 | name: Foobar2000 163 | version: 1.3.10 164 | - 165 | user_agent: Linux UPnP/1.0 Sonos/61.1-83220 (ZPS1) 166 | client: 167 | type: mediaplayer 168 | name: SONOS 169 | version: 61.1 170 | - 171 | user_agent: Linux UPnP/1.0 Sonos/57.4-83220 (ZP120) 172 | client: 173 | type: mediaplayer 174 | name: SONOS 175 | version: 57.4 176 | - 177 | user_agent: Linux UPnP/1.0 Sonos/61.1-83220 (ZPS3) 178 | client: 179 | type: mediaplayer 180 | name: SONOS 181 | version: 61.1 182 | - 183 | user_agent: HTC Streaming Player htc / 1.0 / o2_de / 4.2.2 184 | client: 185 | type: mediaplayer 186 | name: HTC Streaming Player 187 | version: "" 188 | - 189 | user_agent: MPlayer 1.1-4.6 190 | client: 191 | type: mediaplayer 192 | name: MPlayer 193 | version: "1.1" 194 | - 195 | user_agent: StudioDisplay/0.5 (Linux; 5.15.0-79-generic; x86_64; 64bit; latin1) 196 | client: 197 | type: mediaplayer 198 | name: StudioDisplay 199 | version: "0.5" 200 | - 201 | user_agent: JHV/SWHV-4.4.2.10777 (x86_64 Mac OS X 13.2.1) Eclipse Adoptium JRE 19.0.2 202 | client: 203 | type: mediaplayer 204 | name: JHelioviewer 205 | version: 4.4.2.10777 206 | - 207 | user_agent: com.devcoder.iptvxtreamplayer/111 (Linux; U; Android 12; en; Philips Google TV TA1; Build/STT2.220929.001; Cronet/114.0.5735.33) 208 | client: 209 | type: mediaplayer 210 | name: Xtream Player 211 | version: "" 212 | - 213 | user_agent: Mozilla/5.0(compatible; U; InfiNet 0.1; Diga) AppleWebKit/420+ (KHTML, like Gecko)(avdn/Panasonic.bd.pxs2p.2017) 214 | client: 215 | type: mediaplayer 216 | name: DIGA 217 | version: "" 218 | - 219 | user_agent: Mozilla/5.0 (FreeBSD; U; Viera; like Android 4.0.2; xx; ja-jp) AppleWebKit/534.30 (KHTML, like Gecko) Viera/2.0.0 DIGAPlus/1.0.0 Mobile/534.30 220 | client: 221 | type: mediaplayer 222 | name: DIGA 223 | version: 1.0.0 224 | - 225 | user_agent: Mozilla/5.0 (Linux armv7l) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 Safari/537.36 OPR/22.0.1481.0 OMI/4.2.12.48.ALSAN3.97 HbbTV/1.1.1 (; Sony; N/A; v1.000; 2015;) sony.hbbtv.tv.2015HE YouView 226 | client: 227 | type: mediaplayer 228 | name: YouView 229 | version: "" 230 | - 231 | user_agent: YouView (Humax; DTRT2120; 84B08520; CDS/22.18.0; API/2.10.4; PS/2.10.32) (+DVR+FLASH+HTML+MHEG+IPCMC) 232 | client: 233 | type: mediaplayer 234 | name: YouView 235 | version: "" 236 | - 237 | user_agent: YouViewHTML/1.0 AppleWebKit/538.1 (Humax; DTRT2120; 84B08520; CDS/31.8.0; API/3.1.5; PS/3.5.20) (+DVR+FLASH+HTML+MHEG+IPCMC+DASH) 238 | client: 239 | type: mediaplayer 240 | name: YouView 241 | version: "1.0" 242 | -------------------------------------------------------------------------------- /spec/fixtures/client/pim.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - 3 | user_agent: Outlook-Express/7.0 (MSIE 7.0; Windows NT 6.1; WOW64; Trident/6.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; AskTbORJ/5.15.9.29495; .NET4.0E; TmstmpExt) 4 | client: 5 | type: pim 6 | name: Outlook Express 7 | version: "7.0" 8 | - 9 | user_agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; Win64; x64; Trident/7.0; .NET CLR 2.0.50727; SLCC2; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; InfoPath.3; .NET CLR 1.1.4322; FDM; Tablet PC 2.0; .NET4.0E; Microsoft Outlook 14.0.7113; ms-office; MSOffice 14) 10 | client: 11 | type: pim 12 | name: Microsoft Outlook 13 | version: 14.0.7113 14 | - 15 | user_agent: Mozilla/5.0 (X11; Linux i686; rv:17.0) Gecko/20130330 Thunderbird/17.0.5 16 | client: 17 | type: pim 18 | name: Thunderbird 19 | version: 17.0.5 20 | - 21 | user_agent: Mozilla/5.0 (X11; Linux x86_64; rv:24.0) Gecko/20100101 Thunderbird/24.4.0 Lightning/2.6.4 22 | client: 23 | type: pim 24 | name: Thunderbird 25 | version: 24.4.0 26 | - 27 | user_agent: Airmail 1.4 rv:238 (Macintosh; Mac OS X 10.9.2; hr_HR) 28 | client: 29 | type: pim 30 | name: Airmail 31 | version: "1.4" 32 | - 33 | user_agent: Airmail 1.3.3 rv:237 (Macintosh; Mac OS X 10.9.2; en_US) 34 | client: 35 | type: pim 36 | name: Airmail 37 | version: 1.3.3 38 | - 39 | user_agent: Mozilla/5.0 (X11; Linux x86_64; rv:24.0) Gecko/20100101 Icedove/24.4.0 40 | client: 41 | type: pim 42 | name: Thunderbird 43 | version: 24.4.0 44 | - 45 | user_agent: Mozilla/5.0 (X11; Linux x86_64; rv:24.0) Gecko/20100101 Icedove/24.4.0 Lightning/2.6.5 46 | client: 47 | type: pim 48 | name: Thunderbird 49 | version: 24.4.0 50 | - 51 | user_agent: Mozilla/4.0 (compatible; Lotus-Notes/6.0; Windows-NT) 52 | client: 53 | type: pim 54 | name: Lotus Notes 55 | version: "6.0" 56 | - 57 | user_agent: Barca/2.8.4400 58 | client: 59 | type: pim 60 | name: Barca 61 | version: 2.8.4400 62 | - 63 | user_agent: BarcaPro/1.4 L.1001 64 | client: 65 | type: pim 66 | name: Barca 67 | version: "1.4" 68 | - 69 | user_agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; en-US; rv:1.9.1.8) Gecko/20100317 Postbox/1.1.3 70 | client: 71 | type: pim 72 | name: Postbox 73 | version: 1.1.3 74 | - 75 | user_agent: Postbox 1.0b14 (Windows/2009072715) 76 | client: 77 | type: pim 78 | name: Postbox 79 | version: "1.0" 80 | - 81 | user_agent: MailBar/1.3.2 (Mac OS X Version 10.11.1 (Build 15B42)) 82 | client: 83 | type: pim 84 | name: MailBar 85 | version: 1.3.2 86 | - 87 | user_agent: The Bat! 4.0.0.22 88 | client: 89 | type: pim 90 | name: The Bat! 91 | version: 4.0.0.22 92 | - 93 | user_agent: The Bat! Voyager 4.0.18.4 94 | client: 95 | type: pim 96 | name: The Bat! 97 | version: 4.0.18.4 98 | - 99 | user_agent: DAVdroid/1.6.2-ose (2017/06/23; dav4android; okhttp3) Android/7.0 100 | client: 101 | type: pim 102 | name: DAVdroid 103 | version: 1.6.2 104 | - 105 | user_agent: Mozilla/5.0 (X11; Linux x86_64; rv:10.0.12) Gecko/20130823 Firefox/10.0.11esrpre Iceape/2.7.12 106 | client: 107 | type: pim 108 | name: SeaMonkey 109 | version: 2.7.12 110 | - 111 | user_agent: Mozilla/5.0 (X11; Linux x86_64; rv:29.0) Gecko/20100101 Firefox/29.0 SeaMonkey/2.26a1 Lightning/3.1a1 112 | client: 113 | type: pim 114 | name: SeaMonkey 115 | version: "2.26" 116 | - 117 | user_agent: Monazilla/1.00 Live5ch/1.52 Windows/10.0.17134 118 | client: 119 | type: pim 120 | name: Live5ch 121 | version: "1.52" 122 | - 123 | user_agent: Monazilla/1.00 (JaneView/1501150412) 124 | client: 125 | type: pim 126 | name: JaneView 127 | version: "" 128 | - 129 | user_agent: Monazilla/1.00 BathyScaphe/1057 Mac OS X/10.11.6 130 | client: 131 | type: pim 132 | name: BathyScaphe 133 | version: "" 134 | - 135 | user_agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/537.36 (KHTML, like Gecko) Raindrop.io/5.2.0 Chrome/82.0.4048.0 Electron/9.0.0-beta.3 Safari/537.36 136 | client: 137 | type: pim 138 | name: Raindrop.io 139 | version: 5.2.0 140 | - 141 | user_agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_2) AppleWebKit/537.36 (KHTML, like Gecko) Franz/5.4.1 Chrome/76.0.3809.146 Electron/6.0.10 Safari/537.36 142 | client: 143 | type: pim 144 | name: Franz 145 | version: 5.4.1 146 | - 147 | user_agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Mailspring/1.7.4 Chrome/69.0.3497.128 Electron/4.2.2 Safari/537.36 148 | client: 149 | type: pim 150 | name: Mailspring 151 | version: 1.7.4 152 | - 153 | user_agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Notion/2.0.8 Chrome/76.0.3809.146 Electron/6.1.5 Safari/537.36 154 | client: 155 | type: pim 156 | name: Notion 157 | version: 2.0.8 158 | - 159 | user_agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Basecamp3/2.1.0 Chrome/78.0.3904.130 Electron/7.1.5 Safari/537.36 160 | client: 161 | type: pim 162 | name: Basecamp 163 | version: 2.1.0 164 | - 165 | user_agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Evernote/10.8.5 Chrome/87.0.4280.88 Electron/11.1.1 Safari/537.36 166 | client: 167 | type: pim 168 | name: Evernote 169 | version: 10.8.5 170 | - 171 | user_agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) ramboxpro/1.5.2 Chrome/83.0.4103.122 Electron/9.4.4 Safari/537.36 172 | client: 173 | type: pim 174 | name: Rambox Pro 175 | version: 1.5.2 176 | - 177 | user_agent: Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36 Mailbird/2.4.30.0/ 178 | client: 179 | type: pim 180 | name: Mailbird 181 | version: 2.4.30.0 182 | - 183 | user_agent: MacOutlook/16.46.21021202 (Intelx64 Mac OS X 11.2.2 (Build 20D80)) 184 | client: 185 | type: pim 186 | name: Microsoft Outlook 187 | version: 16.46.21021202 188 | - 189 | user_agent: WindowsMail/17.5.9600.22013 190 | client: 191 | type: pim 192 | name: Windows Mail 193 | version: 17.5.9600.22013 194 | - 195 | user_agent: Outlook-iOS/709.2042344.prod.iphone (3.21.0) 196 | client: 197 | type: pim 198 | name: Microsoft Outlook 199 | version: 3.21.0 200 | - 201 | user_agent: Outlook-iOS/711.3061168.prod.iphone 202 | client: 203 | type: pim 204 | name: Microsoft Outlook 205 | version: "" 206 | - 207 | user_agent: Outlook/16.16.18071205 CFNetwork/902.1 Darwin/17.7.0 (x86_64) 208 | client: 209 | type: pim 210 | name: Microsoft Outlook 211 | version: 16.16.18071205 212 | - 213 | user_agent: 'Yahoo%20Mail/49268 CFNetwork/1121.2.2 Darwin/19.3.0' 214 | client: 215 | type: pim 216 | name: Yahoo Mail 217 | version: "" 218 | - 219 | user_agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36 eM Client/8.2.1659.0 220 | client: 221 | type: pim 222 | name: eM Client 223 | version: 8.2.1659.0 224 | - 225 | user_agent: NaverMailApp/2.2.2 (Android 10; SM-G986N) 226 | client: 227 | type: pim 228 | name: NAVER Mail 229 | version: 2.2.2 230 | - 231 | user_agent: Microsoft Office/16.0 (Microsoft Outlook 16.0.12329; Pro), Mozilla/4.0 (compatible; ms-office; MSOffice 16) 232 | client: 233 | type: pim 234 | name: Microsoft Outlook 235 | version: 16.0.12329 236 | - 237 | user_agent: Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.67 Safari/537.36 Foxmail/7.2.25.213 238 | client: 239 | type: pim 240 | name: Foxmail 241 | version: 7.2.25.213 242 | - 243 | user_agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.110 MailMasterPC/4.15.6.1016 Safari/537.36 244 | client: 245 | type: pim 246 | name: Mail Master 247 | version: 4.15.6.1016 248 | - 249 | user_agent: Mozilla/5.0 (iPhone; CPU iPhone OS 11_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15F79 MailMaster/6.5.3.1210 250 | client: 251 | type: pim 252 | name: Mail Master 253 | version: 6.5.3.1210 254 | - 255 | user_agent: Mozilla/5.0 (iPhone; CPU iPhone OS 15_3_1 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Mobile/9B176 YJApp-IOS jp.co.yahoo.ymail/7.4.1 256 | client: 257 | type: pim 258 | name: Yahoo! Mail 259 | version: 7.4.1 260 | - 261 | user_agent: eMClient/9.3.6041.0 262 | client: 263 | type: pim 264 | name: eM Client 265 | version: 9.3.6041.0 266 | - 267 | user_agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) BlueMail/0.10.31 Chrome/61.0.3163.100 Electron/2.0.18 Safari/537.36 268 | client: 269 | type: pim 270 | name: BlueMail 271 | version: 0.10.31 272 | - 273 | user_agent: Mozilla/5.0 (iPhone; CPU iPhone OS 17_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 mailapp/6.5.0 274 | client: 275 | type: pim 276 | name: mailapp 277 | version: 6.5.0 278 | - 279 | user_agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.2pre) Gecko/2009031304 Spicebird/0.7.1 280 | client: 281 | type: pim 282 | name: Spicebird 283 | version: 0.7.1 284 | - 285 | user_agent: Android-Gmail/64562652 (sw411dp; 420dpi) (r7 TP1A.220624.014) 286 | client: 287 | type: pim 288 | name: Gmail 289 | version: "" 290 | -------------------------------------------------------------------------------- /spec/fixtures/detector/camera.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - 3 | user_agent: Mozilla/5.0 (Linux; U; Android 2.3.3; ja-jp; COOLPIX S800c Build/CP01_WW) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1 4 | os: 5 | name: Android 6 | version: 2.3.3 7 | platform: "" 8 | client: 9 | type: browser 10 | name: Android Browser 11 | version: "" 12 | engine: WebKit 13 | engine_version: "533.1" 14 | device: 15 | type: camera 16 | brand: Nikon 17 | model: Coolpix S800c 18 | os_family: Android 19 | browser_family: Android Browser 20 | - 21 | user_agent: Mozilla/5.0 (Linux; Android 5.0.2; DMC-CM1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36 22 | os: 23 | name: Android 24 | version: 5.0.2 25 | platform: "" 26 | client: 27 | type: browser 28 | name: Chrome Mobile 29 | version: 77.0.3865.92 30 | engine: Blink 31 | engine_version: 77.0.3865.92 32 | device: 33 | type: camera 34 | brand: Panasonic 35 | model: Lumix DMC-CM1 36 | os_family: Android 37 | browser_family: Chrome 38 | - 39 | user_agent: Mozilla/5.0 (Linux; U; Android 4.0; de-DE; EK-GC100 Build/IMM76D) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30 40 | os: 41 | name: Android 42 | version: "4.0" 43 | platform: "" 44 | client: 45 | type: browser 46 | name: Android Browser 47 | version: "" 48 | engine: WebKit 49 | engine_version: "534.30" 50 | device: 51 | type: camera 52 | brand: Samsung 53 | model: Galaxy Camera 54 | os_family: Android 55 | browser_family: Android Browser 56 | - 57 | user_agent: Mozilla/5.0 (Linux; Android 4.1.2; EK-GC100 Build/JZO54K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.63 Mobile Safari/537.36 OPR/15.0.1162.60140 58 | os: 59 | name: Android 60 | version: 4.1.2 61 | platform: "" 62 | client: 63 | type: browser 64 | name: Opera Mobile 65 | version: 15.0.1162.60140 66 | engine: Blink 67 | engine_version: 28.0.1500.63 68 | device: 69 | type: camera 70 | brand: Samsung 71 | model: Galaxy Camera 72 | os_family: Android 73 | browser_family: Opera 74 | - 75 | user_agent: Mozilla/5.0 (Linux; Android 4.3; EK-GC200 Build/JSS15J) AppleWebKit/537.36 (KHTML like Gecko) Chrome/35.0.1916.141 Mobile Safari/537.36 76 | os: 77 | name: Android 78 | version: "4.3" 79 | platform: "" 80 | client: 81 | type: browser 82 | name: Chrome Mobile 83 | version: 35.0.1916.141 84 | engine: Blink 85 | engine_version: 35.0.1916.141 86 | device: 87 | type: camera 88 | brand: Samsung 89 | model: Galaxy Camera 2 90 | os_family: Android 91 | browser_family: Chrome 92 | - 93 | user_agent: Mozilla/5.0 (Linux; Android 4.1.2; EK-GC110 Build/JZO54K) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.166 Mobile Safari/535.19 94 | os: 95 | name: Android 96 | version: 4.1.2 97 | platform: "" 98 | client: 99 | type: browser 100 | name: Chrome Mobile 101 | version: 18.0.1025.166 102 | engine: WebKit 103 | engine_version: "535.19" 104 | device: 105 | type: camera 106 | brand: Samsung 107 | model: Galaxy Camera WiFi only 108 | os_family: Android 109 | browser_family: Chrome 110 | - 111 | user_agent: Mozilla/5.0 (Linux; Android 4.2.2; en-nz; SAMSUNG EK-GN120 Build/JDQ39) AppleWebKit/535.19 (KHTML, like Gecko) Version/1.0 Chrome/18.0.1025.308 Mobile Safari/535.19 112 | os: 113 | name: Android 114 | version: 4.2.2 115 | platform: "" 116 | client: 117 | type: browser 118 | name: Chrome Webview 119 | version: 18.0.1025.308 120 | engine: WebKit 121 | engine_version: "535.19" 122 | device: 123 | type: camera 124 | brand: Samsung 125 | model: Galaxy NX 126 | os_family: Android 127 | browser_family: Chrome 128 | -------------------------------------------------------------------------------- /spec/fixtures/detector/feed_reader.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - 3 | user_agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.21 (KHTML, like Gecko) akregator/4.11.5 Safari/537.21 4 | os: 5 | name: GNU/Linux 6 | version: "" 7 | platform: x64 8 | client: 9 | type: feed reader 10 | name: Akregator 11 | version: 4.11.5 12 | device: 13 | type: desktop 14 | brand: "" 15 | model: "" 16 | os_family: GNU/Linux 17 | browser_family: Unknown 18 | - 19 | user_agent: Akregator/4.12.3; syndication SUSE 20 | os: 21 | name: SUSE 22 | version: "" 23 | platform: "" 24 | client: 25 | type: feed reader 26 | name: Akregator 27 | version: 4.12.3 28 | device: 29 | type: desktop 30 | brand: "" 31 | model: "" 32 | os_family: GNU/Linux 33 | browser_family: Unknown 34 | - 35 | user_agent: Akregator/1.2.9; librss/remnants 36 | os: [ ] 37 | client: 38 | type: feed reader 39 | name: Akregator 40 | version: 1.2.9 41 | device: 42 | type: "" 43 | brand: "" 44 | model: "" 45 | os_family: Unknown 46 | browser_family: Unknown 47 | - 48 | user_agent: Apple-PubSub/65.28 49 | os: [ ] 50 | client: 51 | type: feed reader 52 | name: Apple PubSub 53 | version: "65.28" 54 | device: 55 | type: "" 56 | brand: "" 57 | model: "" 58 | os_family: Unknown 59 | browser_family: Unknown 60 | - 61 | user_agent: FeedDemon/4.5 (http://www.feeddemon.com/; Microsoft Windows) 62 | os: 63 | name: Windows 64 | version: "" 65 | platform: "" 66 | client: 67 | type: feed reader 68 | name: FeedDemon 69 | version: "4.5" 70 | device: 71 | type: desktop 72 | brand: "" 73 | model: "" 74 | os_family: Windows 75 | browser_family: Unknown 76 | - 77 | user_agent: FeedDemon/4.5 (http://www.feeddemon.com/; Microsoft Windows XP) 78 | os: 79 | name: Windows 80 | version: XP 81 | platform: "" 82 | client: 83 | type: feed reader 84 | name: FeedDemon 85 | version: "4.5" 86 | device: 87 | type: desktop 88 | brand: "" 89 | model: "" 90 | os_family: Windows 91 | browser_family: Unknown 92 | - 93 | user_agent: FeeddlerPro/2.4 CFNetwork/672.0.8 Darwin/14.0.0 94 | os: 95 | name: iOS 96 | version: "7.0" 97 | platform: "" 98 | client: 99 | type: feed reader 100 | name: Feeddler RSS Reader 101 | version: "2.4" 102 | device: 103 | type: "" 104 | brand: Apple 105 | model: "" 106 | os_family: iOS 107 | browser_family: Unknown 108 | - 109 | user_agent: FeeddlerRSS/2.4 CFNetwork/548.1.4 Darwin/11.0.0 110 | os: 111 | name: iOS 112 | version: "5.1" 113 | platform: "" 114 | client: 115 | type: feed reader 116 | name: Feeddler RSS Reader 117 | version: "2.4" 118 | device: 119 | type: "" 120 | brand: Apple 121 | model: "" 122 | os_family: iOS 123 | browser_family: Unknown 124 | - 125 | user_agent: FeeddlerRSS 2.4 (iPad; iPhone OS 5.1.1; en_US) 126 | os: 127 | name: iOS 128 | version: 5.1.1 129 | platform: "" 130 | client: 131 | type: feed reader 132 | name: Feeddler RSS Reader 133 | version: "2.4" 134 | device: 135 | type: tablet 136 | brand: Apple 137 | model: iPad 138 | os_family: iOS 139 | browser_family: Unknown 140 | - 141 | user_agent: Mozilla/5.0 (Windows NT 6.1) AppleWebKit/602.1 (KHTML, like Gecko) QuiteRSS/0.18.12 Safari/602.1 142 | os: 143 | name: Windows 144 | version: "7" 145 | platform: "" 146 | client: 147 | type: feed reader 148 | name: QuiteRSS 149 | version: 0.18.12 150 | device: 151 | type: desktop 152 | brand: "" 153 | model: "" 154 | os_family: Windows 155 | browser_family: Unknown 156 | - 157 | user_agent: JetBrains Omea Reader 2.2 (http://www.jetbrains.com/omea/reader/) 158 | os: [ ] 159 | client: 160 | type: feed reader 161 | name: JetBrains Omea Reader 162 | version: "2.2" 163 | device: 164 | type: "" 165 | brand: "" 166 | model: "" 167 | os_family: Unknown 168 | browser_family: Unknown 169 | - 170 | user_agent: Liferea/1.6.4 (Linux; en_US.UTF-8; http://liferea.sf.net/) 171 | os: 172 | name: GNU/Linux 173 | version: "" 174 | platform: "" 175 | client: 176 | type: feed reader 177 | name: Liferea 178 | version: 1.6.4 179 | device: 180 | type: desktop 181 | brand: "" 182 | model: "" 183 | os_family: GNU/Linux 184 | browser_family: Unknown 185 | - 186 | user_agent: Liferea/1.10-RC1 (Linux; en_GB.UTF-8; http://liferea.sf.net/) 187 | os: 188 | name: GNU/Linux 189 | version: "" 190 | platform: "" 191 | client: 192 | type: feed reader 193 | name: Liferea 194 | version: "1.10" 195 | device: 196 | type: desktop 197 | brand: "" 198 | model: "" 199 | os_family: GNU/Linux 200 | browser_family: Unknown 201 | - 202 | user_agent: Liferea/1.10.6 (Linux; en_US.UTF8; http://liferea.sf.net/) AppleWebKit (KHTML, like Gecko) 203 | os: 204 | name: GNU/Linux 205 | version: "" 206 | platform: "" 207 | client: 208 | type: feed reader 209 | name: Liferea 210 | version: 1.10.6 211 | device: 212 | type: desktop 213 | brand: "" 214 | model: "" 215 | os_family: GNU/Linux 216 | browser_family: Unknown 217 | - 218 | user_agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.74.9 (KHTML, like Gecko) Version/6.0 NetNewsWire/4.0.0 219 | os: 220 | name: Mac 221 | version: 10.9.2 222 | platform: "" 223 | client: 224 | type: feed reader 225 | name: NetNewsWire 226 | version: 4.0.0 227 | device: 228 | type: desktop 229 | brand: Apple 230 | model: "" 231 | os_family: Mac 232 | browser_family: Unknown 233 | - 234 | user_agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.74.9 (KHTML, like Gecko) NetNewsWire/3.3.2 235 | os: 236 | name: Mac 237 | version: 10.9.2 238 | platform: "" 239 | client: 240 | type: feed reader 241 | name: NetNewsWire 242 | version: 3.3.2 243 | device: 244 | type: desktop 245 | brand: Apple 246 | model: "" 247 | os_family: Mac 248 | browser_family: Unknown 249 | - 250 | user_agent: Breaker/v315 (subscribers=9999; feed-id=123456; url=https://www.breaker.audio/url-slug-to-podcast) 251 | os: [ ] 252 | client: 253 | type: feed reader 254 | name: Breaker 255 | version: "315" 256 | device: 257 | type: "" 258 | brand: "" 259 | model: "" 260 | os_family: Unknown 261 | browser_family: Unknown 262 | - 263 | user_agent: NetNewsWire/4.0.0 (Mac OS X; http://netnewswireapp.com/mac/; gzip-happy) 264 | os: 265 | name: Mac 266 | version: "" 267 | platform: "" 268 | client: 269 | type: feed reader 270 | name: NetNewsWire 271 | version: 4.0.0 272 | device: 273 | type: desktop 274 | brand: Apple 275 | model: "" 276 | os_family: Mac 277 | browser_family: Unknown 278 | - 279 | user_agent: newsbeuter/2.7 (Linux x86_64) 280 | os: 281 | name: GNU/Linux 282 | version: "" 283 | platform: x64 284 | client: 285 | type: feed reader 286 | name: Newsbeuter 287 | version: "2.7" 288 | device: 289 | type: desktop 290 | brand: "" 291 | model: "" 292 | os_family: GNU/Linux 293 | browser_family: Unknown 294 | - 295 | user_agent: NewsBlur iPhone App v3.6 296 | os: 297 | name: iOS 298 | version: "" 299 | platform: "" 300 | client: 301 | type: feed reader 302 | name: NewsBlur Mobile App 303 | version: "3.6" 304 | device: 305 | type: smartphone 306 | brand: Apple 307 | model: iPhone 308 | os_family: iOS 309 | browser_family: Unknown 310 | - 311 | user_agent: NewsBlur iPad App v3.6 312 | os: 313 | name: iOS 314 | version: "" 315 | platform: "" 316 | client: 317 | type: feed reader 318 | name: NewsBlur Mobile App 319 | version: "3.6" 320 | device: 321 | type: tablet 322 | brand: Apple 323 | model: iPad 324 | os_family: iOS 325 | browser_family: Unknown 326 | - 327 | user_agent: NewsBlur/4.0.1 CFNetwork/672.1.13 Darwin/14.0.0 328 | os: 329 | name: iOS 330 | version: "7.1" 331 | platform: "" 332 | client: 333 | type: feed reader 334 | name: NewsBlur 335 | version: 4.0.1 336 | device: 337 | type: "" 338 | brand: Apple 339 | model: "" 340 | os_family: iOS 341 | browser_family: Unknown 342 | - 343 | user_agent: newsbeuter/2.4 (Linux 3.2.0-23-generic; i686; http://www.newsbeuter.org/) libcurl/7.22.0 GnuTLS/2.12.14 zlib/1.2.3.4 libidn/1.23 librtmp/2.3 344 | os: 345 | name: GNU/Linux 346 | version: "" 347 | platform: x86 348 | client: 349 | type: feed reader 350 | name: Newsbeuter 351 | version: "2.4" 352 | device: 353 | type: desktop 354 | brand: "" 355 | model: "" 356 | os_family: GNU/Linux 357 | browser_family: Unknown 358 | - 359 | user_agent: Pulp/1.5.2 (iPad; http://www.acrylicapps.com/pulp/) 360 | os: 361 | name: iOS 362 | version: "" 363 | platform: "" 364 | client: 365 | type: feed reader 366 | name: Pulp 367 | version: 1.5.2 368 | device: 369 | type: tablet 370 | brand: Apple 371 | model: iPad 372 | os_family: iOS 373 | browser_family: Unknown 374 | - 375 | user_agent: ReadKit/2.4.0 (Mac OS X Version 10.9.2 (Build 13C64)) 376 | os: 377 | name: Mac 378 | version: 10.9.2 379 | platform: "" 380 | client: 381 | type: feed reader 382 | name: ReadKit 383 | version: 2.4.0 384 | device: 385 | type: desktop 386 | brand: Apple 387 | model: "" 388 | os_family: Mac 389 | browser_family: Unknown 390 | - 391 | user_agent: 'ReadKit/7017 CFNetwork/673.2.1 Darwin/13.1.0 (x86_64) (MacBookPro10%2C1)' 392 | os: 393 | name: Mac 394 | version: 10.9.2 395 | platform: x64 396 | client: 397 | type: feed reader 398 | name: ReadKit 399 | version: "7017" 400 | device: 401 | type: desktop 402 | brand: Apple 403 | model: MacBook Pro 15" (2012-2013) 404 | os_family: Mac 405 | browser_family: Unknown 406 | - 407 | user_agent: Reeder/3.2 CFNetwork/672.1.12 Darwin/14.0.0 408 | os: 409 | name: iOS 410 | version: "7.1" 411 | platform: "" 412 | client: 413 | type: feed reader 414 | name: Reeder 415 | version: "3.2" 416 | device: 417 | type: "" 418 | brand: Apple 419 | model: "" 420 | os_family: iOS 421 | browser_family: Unknown 422 | - 423 | user_agent: RssBandit/1.9.0.1002 424 | os: [ ] 425 | client: 426 | type: feed reader 427 | name: RSS Bandit 428 | version: 1.9.0.1002 429 | device: 430 | type: "" 431 | brand: "" 432 | model: "" 433 | os_family: Unknown 434 | browser_family: Unknown 435 | - 436 | user_agent: RssBandit/1.9.0.1002 (.NET CLR 2.0.50727.7512; WinNT 6.2.9200.0; http://www.rssbandit.org) 437 | os: 438 | name: Windows 439 | version: NT 440 | platform: "" 441 | client: 442 | type: feed reader 443 | name: RSS Bandit 444 | version: 1.9.0.1002 445 | device: 446 | type: desktop 447 | brand: "" 448 | model: "" 449 | os_family: Windows 450 | browser_family: Unknown 451 | - 452 | user_agent: RSS Junkie Daemon 453 | os: [ ] 454 | client: 455 | type: feed reader 456 | name: RSS Junkie 457 | version: "" 458 | device: 459 | type: "" 460 | brand: "" 461 | model: "" 462 | os_family: Unknown 463 | browser_family: Unknown 464 | - 465 | user_agent: RSSOwl/2.2.1.201312301314 (Windows; U; en) 466 | os: 467 | name: Windows 468 | version: "" 469 | platform: "" 470 | client: 471 | type: feed reader 472 | name: RSSOwl 473 | version: 2.2.1.201312301314 474 | device: 475 | type: desktop 476 | brand: "" 477 | model: "" 478 | os_family: Windows 479 | browser_family: Unknown 480 | - 481 | user_agent: RSSOwl/2.2.1.201312301316 (X11; U; en) 482 | os: 483 | name: GNU/Linux 484 | version: "" 485 | platform: "" 486 | client: 487 | type: feed reader 488 | name: RSSOwl 489 | version: 2.2.1.201312301316 490 | device: 491 | type: desktop 492 | brand: "" 493 | model: "" 494 | os_family: GNU/Linux 495 | browser_family: Unknown 496 | - 497 | user_agent: Evergreen (macOS; RSS Reader; https://ranchero.com/evergreen/) 498 | os: 499 | name: Mac 500 | version: "" 501 | platform: "" 502 | client: 503 | type: feed reader 504 | name: NetNewsWire 505 | version: "" 506 | device: 507 | type: desktop 508 | brand: Apple 509 | model: "" 510 | os_family: Mac 511 | browser_family: Unknown 512 | - 513 | user_agent: NetNewsWire (macOS; RSS Reader; https://ranchero.com/netnewswire/) 514 | os: 515 | name: Mac 516 | version: "" 517 | platform: "" 518 | client: 519 | type: feed reader 520 | name: NetNewsWire 521 | version: "" 522 | device: 523 | type: desktop 524 | brand: Apple 525 | model: "" 526 | os_family: Mac 527 | browser_family: Unknown 528 | -------------------------------------------------------------------------------- /spec/fixtures/detector/mediaplayer.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - 3 | user_agent: Audacious/3.6.2 neon/0.30.1 4 | os: [ ] 5 | client: 6 | type: mediaplayer 7 | name: Audacious 8 | version: 3.6.2 9 | device: 10 | type: "" 11 | brand: "" 12 | model: "" 13 | os_family: Unknown 14 | browser_family: Unknown 15 | - 16 | user_agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.28) Gecko/20130316 Songbird/1.12.1 (20140112193149) 17 | os: 18 | name: GNU/Linux 19 | version: "" 20 | platform: x86 21 | client: 22 | type: mediaplayer 23 | name: Songbird 24 | version: 1.12.1 25 | device: 26 | type: desktop 27 | brand: "" 28 | model: "" 29 | os_family: GNU/Linux 30 | browser_family: Unknown 31 | - 32 | user_agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.28) Gecko/20130316 Nightingale/1.12.2 (20140112193149) 33 | os: 34 | name: GNU/Linux 35 | version: "" 36 | platform: x86 37 | client: 38 | type: mediaplayer 39 | name: Nightingale 40 | version: 1.12.2 41 | device: 42 | type: desktop 43 | brand: "" 44 | model: "" 45 | os_family: GNU/Linux 46 | browser_family: Unknown 47 | - 48 | user_agent: iTunes/10.2.1 (Macintosh; Intel Mac OS X 10.7) AppleWebKit/534.20.8 49 | os: 50 | name: Mac 51 | version: "10.7" 52 | platform: "" 53 | client: 54 | type: mediaplayer 55 | name: iTunes 56 | version: 10.2.1 57 | device: 58 | type: desktop 59 | brand: Apple 60 | model: "" 61 | os_family: Mac 62 | browser_family: Unknown 63 | - 64 | user_agent: iTunes/10.2.1 (Windows; Microsoft Windows 7 Enterprise Edition Service Pack 1 (Build 7601)) AppleWebKit/533.20.25 65 | os: 66 | name: Windows 67 | version: "7" 68 | platform: "" 69 | client: 70 | type: mediaplayer 71 | name: iTunes 72 | version: 10.2.1 73 | device: 74 | type: desktop 75 | brand: "" 76 | model: "" 77 | os_family: Windows 78 | browser_family: Unknown 79 | - 80 | user_agent: SAMSUNG-GT-S3850/S3850CXKD1 SHP/VPP/R5 Dolfin/2.0 NexPlayer/3.0 SMM-MMS/1.2.0 profile/MIDP-2.1 configuration/CLDC-1.1 OPN-B 81 | os: 82 | name: Java ME 83 | version: "" 84 | platform: "" 85 | client: 86 | type: mediaplayer 87 | name: NexPlayer 88 | version: "3.0" 89 | device: 90 | type: smartphone 91 | brand: Samsung 92 | model: GT-S3850 93 | os_family: Real-time OS 94 | browser_family: Unknown 95 | - 96 | user_agent: Deezer/5.4.21.97 (Android; 6.0; Mobile; fr) WIKO U FEEL 97 | os: 98 | name: Android 99 | version: "6.0" 100 | platform: "" 101 | client: 102 | type: mediaplayer 103 | name: Deezer 104 | version: 5.4.21.97 105 | device: 106 | type: smartphone 107 | brand: Wiko 108 | model: U Feel 109 | os_family: Android 110 | browser_family: Unknown 111 | - 112 | user_agent: FlyCast/1.34 (BlackBerry; 8330/4.5.0.131 Profile/MIDP-2.0 Configuration/CLDC-1.1 VendorID/-1) 113 | os: 114 | name: BlackBerry OS 115 | version: "" 116 | platform: "" 117 | client: 118 | type: mediaplayer 119 | name: FlyCast 120 | version: "1.34" 121 | device: 122 | type: smartphone 123 | brand: RIM 124 | model: BlackBerry 125 | os_family: BlackBerry 126 | browser_family: Unknown 127 | - 128 | user_agent: NSPlayer/10.0.0.4072 WMFSDK/10.0 129 | os: [ ] 130 | client: 131 | type: mediaplayer 132 | name: Windows Media Player 133 | version: 10.0.0.4072 134 | device: 135 | type: "" 136 | brand: "" 137 | model: "" 138 | os_family: Unknown 139 | browser_family: Unknown 140 | - 141 | user_agent: XBMC/9.04 r19840 (Mac OS X; Darwin 9.6.0; http://www.xbmc.org) 142 | os: 143 | name: Mac 144 | version: "" 145 | platform: "" 146 | client: 147 | type: mediaplayer 148 | name: XBMC 149 | version: "9.04" 150 | device: 151 | type: desktop 152 | brand: Apple 153 | model: "" 154 | os_family: Mac 155 | browser_family: Unknown 156 | - 157 | user_agent: SubStream/0.7 CFNetwork/485.12.30 Darwin/10.4.0 158 | os: 159 | name: iOS 160 | version: "4.2" 161 | platform: "" 162 | client: 163 | type: mediaplayer 164 | name: SubStream 165 | version: "0.7" 166 | device: 167 | type: "" 168 | brand: Apple 169 | model: "" 170 | os_family: iOS 171 | browser_family: Unknown 172 | - 173 | user_agent: Samsung GT-I9505 stagefright/1.2 (Linux;Android 4.4.2) 174 | os: 175 | name: Android 176 | version: 4.4.2 177 | platform: "" 178 | client: 179 | type: mediaplayer 180 | name: Stagefright 181 | version: "1.2" 182 | device: 183 | type: smartphone 184 | brand: Samsung 185 | model: Galaxy S4 186 | os_family: Android 187 | browser_family: Unknown 188 | - 189 | user_agent: Kodi/14.0 (Macintosh; Intel Mac OS X 10_10_3) App_Bitness/64 Version/14.0-Git:2014-12-23-ad747d9-dirty 190 | os: 191 | name: Mac 192 | version: 10.10.3 193 | platform: "" 194 | client: 195 | type: mediaplayer 196 | name: Kodi 197 | version: "14.0" 198 | device: 199 | type: desktop 200 | brand: Apple 201 | model: "" 202 | os_family: Mac 203 | browser_family: Unknown 204 | - 205 | user_agent: GoogleChirp/1.0.1 (Linux; Android/5.0) GOOG/7 AppleWebKit/534.30 (KHTML, like Gecko) 206 | os: 207 | name: Android 208 | version: "5.0" 209 | platform: "" 210 | client: 211 | type: mediaplayer 212 | name: Google Podcasts 213 | version: 1.0.1 214 | device: 215 | type: "" 216 | brand: "" 217 | model: "" 218 | os_family: Android 219 | browser_family: Unknown 220 | - 221 | user_agent: Music Player Daemon 0.19.21 222 | os: [ ] 223 | client: 224 | type: mediaplayer 225 | name: Music Player Daemon 226 | version: 0.19.21 227 | device: 228 | type: "" 229 | brand: "" 230 | model: "" 231 | os_family: Unknown 232 | browser_family: Unknown 233 | - 234 | user_agent: mpv 0.29.1 235 | os: [ ] 236 | client: 237 | type: mediaplayer 238 | name: mpv 239 | version: 0.29.1 240 | device: 241 | type: "" 242 | brand: "" 243 | model: "" 244 | os_family: Unknown 245 | browser_family: Unknown 246 | - 247 | user_agent: foobar2000/1.6.1 248 | os: [ ] 249 | client: 250 | type: mediaplayer 251 | name: Foobar2000 252 | version: 1.6.1 253 | device: 254 | type: "" 255 | brand: "" 256 | model: "" 257 | os_family: Unknown 258 | browser_family: Unknown 259 | - 260 | user_agent: Downcast/2.9.11 (Mac OS X Version 10.11.3 (Build 15D21)) 261 | os: 262 | name: Mac 263 | version: 10.11.3 264 | platform: "" 265 | client: 266 | type: mediaplayer 267 | name: Downcast 268 | version: 2.9.11 269 | device: 270 | type: desktop 271 | brand: Apple 272 | model: "" 273 | os_family: Mac 274 | browser_family: Unknown 275 | - 276 | user_agent: 'Downcast/1241 CFNetwork/673.4 Darwin/13.3.0 (x86_64) (MacBookAir4%2C2)' 277 | os: 278 | name: Mac 279 | version: 10.9.4 280 | platform: x64 281 | client: 282 | type: mediaplayer 283 | name: Downcast 284 | version: "" 285 | device: 286 | type: desktop 287 | brand: Apple 288 | model: MacBook Air 13" (2011) 289 | os_family: Mac 290 | browser_family: Unknown 291 | - 292 | user_agent: MediaGo/3.2.0.191 (Windows NT 10.0; WOW64) PlaybackEngine/2.20.103.05220 293 | os: 294 | name: Windows 295 | version: "10" 296 | platform: x64 297 | client: 298 | type: mediaplayer 299 | name: Sony Media Go 300 | version: 3.2.0.191 301 | device: 302 | type: desktop 303 | brand: "" 304 | model: "" 305 | os_family: Windows 306 | browser_family: Unknown 307 | -------------------------------------------------------------------------------- /spec/fixtures/detector/smart_display.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - 3 | user_agent: Mozilla/5.0 (Linux; U; Android 4.0.4; fr-be; DA220HQL Build/IMM76D) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Safari/534.30 4 | os: 5 | name: Android 6 | version: 4.0.4 7 | platform: "" 8 | client: 9 | type: browser 10 | name: Android Browser 11 | version: "" 12 | engine: WebKit 13 | engine_version: "534.30" 14 | device: 15 | type: smart display 16 | brand: Acer 17 | model: DA220HQL 18 | os_family: Android 19 | browser_family: Android Browser 20 | - 21 | user_agent: Mozilla/5.0 (Linux; Android 4.2.1; DA241HL Build/JOP40D) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.114 Safari/537.36 22 | os: 23 | name: Android 24 | version: 4.2.1 25 | platform: "" 26 | client: 27 | type: browser 28 | name: Chrome 29 | version: 34.0.1847.114 30 | engine: Blink 31 | engine_version: 34.0.1847.114 32 | device: 33 | type: smart display 34 | brand: Acer 35 | model: DA241HL 36 | os_family: Android 37 | browser_family: Chrome 38 | - 39 | user_agent: Mozilla/5.0 (Linux; U; Android 4.0.4; de-de; VSD220 Build/IMM76D.UI23ED12_VSC) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Safari/534.30 40 | os: 41 | name: Android 42 | version: 4.0.4 43 | platform: "" 44 | client: 45 | type: browser 46 | name: Android Browser 47 | version: "" 48 | engine: WebKit 49 | engine_version: "534.30" 50 | device: 51 | type: smart display 52 | brand: ViewSonic 53 | model: VSD220 54 | os_family: Android 55 | browser_family: Android Browser 56 | - 57 | user_agent: Mozilla/5.0 (Linux; Android 8.0.0; IFP7550-3 Build/OPR5.170623.014) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/61.0.3163.98 Safari/537.36 58 | os: 59 | name: Android 60 | version: 8.0.0 61 | platform: "" 62 | client: 63 | type: browser 64 | name: Chrome Webview 65 | version: 61.0.3163.98 66 | engine: Blink 67 | engine_version: 61.0.3163.98 68 | device: 69 | type: smart display 70 | brand: ViewSonic 71 | model: ViewBoard IFP7550-3 75" 4K 72 | os_family: Android 73 | browser_family: Chrome 74 | - 75 | user_agent: Mozilla/5.0 (Linux; Android 8.0.0; IFP9850-3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3739.0 Safari/537.36 76 | os: 77 | name: Android 78 | version: 8.0.0 79 | platform: "" 80 | client: 81 | type: browser 82 | name: Chrome 83 | version: 75.0.3739.0 84 | engine: Blink 85 | engine_version: 75.0.3739.0 86 | device: 87 | type: smart display 88 | brand: ViewSonic 89 | model: ViewBoard IFP9850-3 98" 4K 90 | os_family: Android 91 | browser_family: Chrome 92 | - 93 | user_agent: Mozilla/5.0 (Linux; Android 8.0.0; IFP8650-3 Build/OPR5.170623.014; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/61.0.3163.98 Safari/537.36 94 | os: 95 | name: Android 96 | version: 8.0.0 97 | platform: "" 98 | client: 99 | type: browser 100 | name: Chrome Webview 101 | version: 61.0.3163.98 102 | engine: Blink 103 | engine_version: 61.0.3163.98 104 | device: 105 | type: smart display 106 | brand: ViewSonic 107 | model: ViewBoard IFP8650-3 86" 4K 108 | os_family: Android 109 | browser_family: Chrome 110 | - 111 | user_agent: Mozilla/5.0 (Linux; Android 8.0.0; IFP6550-3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.85 Safari/537.36 112 | os: 113 | name: Android 114 | version: 8.0.0 115 | platform: "" 116 | client: 117 | type: browser 118 | name: Chrome 119 | version: 76.0.3809.85 120 | engine: Blink 121 | engine_version: 76.0.3809.85 122 | device: 123 | type: smart display 124 | brand: ViewSonic 125 | model: ViewBoard IFP6550-3 65" 4K 126 | os_family: Android 127 | browser_family: Chrome 128 | - 129 | user_agent: Mozilla/5.0 (Linux; Android 8.0.0; IFP7550-3 Build/OPR5.170623.014) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/61.0.3163.98 Safari/537.36 130 | os: 131 | name: Android 132 | version: 8.0.0 133 | platform: "" 134 | client: 135 | type: browser 136 | name: Chrome Webview 137 | version: 61.0.3163.98 138 | engine: Blink 139 | engine_version: 61.0.3163.98 140 | device: 141 | type: smart display 142 | brand: ViewSonic 143 | model: ViewBoard IFP7550-3 75" 4K 144 | os_family: Android 145 | browser_family: Chrome 146 | - 147 | user_agent: Mozilla/5.0 (Linux; Android 8.0.0; IFP9850-3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3739.0 Safari/537.36 148 | os: 149 | name: Android 150 | version: 8.0.0 151 | platform: "" 152 | client: 153 | type: browser 154 | name: Chrome 155 | version: 75.0.3739.0 156 | engine: Blink 157 | engine_version: 75.0.3739.0 158 | device: 159 | type: smart display 160 | brand: ViewSonic 161 | model: ViewBoard IFP9850-3 98" 4K 162 | os_family: Android 163 | browser_family: Chrome 164 | - 165 | user_agent: Mozilla/5.0 (Linux; Android 8.0.0; IFP8650-3 Build/OPR5.170623.014; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/61.0.3163.98 Safari/537.36 166 | os: 167 | name: Android 168 | version: 8.0.0 169 | platform: "" 170 | client: 171 | type: browser 172 | name: Chrome Webview 173 | version: 61.0.3163.98 174 | engine: Blink 175 | engine_version: 61.0.3163.98 176 | device: 177 | type: smart display 178 | brand: ViewSonic 179 | model: ViewBoard IFP8650-3 86" 4K 180 | os_family: Android 181 | browser_family: Chrome 182 | - 183 | user_agent: Mozilla/5.0 (Linux; Android 8.0.0; IFP6550-3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.85 Safari/537.36 184 | os: 185 | name: Android 186 | version: 8.0.0 187 | platform: "" 188 | client: 189 | type: browser 190 | name: Chrome 191 | version: 76.0.3809.85 192 | engine: Blink 193 | engine_version: 76.0.3809.85 194 | device: 195 | type: smart display 196 | brand: ViewSonic 197 | model: ViewBoard IFP6550-3 65" 4K 198 | os_family: Android 199 | browser_family: Chrome 200 | - 201 | user_agent: Dalvik/1.6.0 (Linux; U; Android 4.4.4; WT22M-FI Build/KTU84Q) 202 | os: 203 | name: Android 204 | version: 4.4.4 205 | platform: "" 206 | client: 207 | type: browser 208 | name: Android Browser 209 | version: "" 210 | engine: WebKit 211 | engine_version: "" 212 | device: 213 | type: smart display 214 | brand: AOpen 215 | model: eTILE WT22M-FI 216 | os_family: Android 217 | browser_family: Android Browser 218 | - 219 | user_agent: Dalvik/1.6.0 (Linux; U; Android 4.3.1; WT19M-FI Build/JLS36I) 220 | os: 221 | name: Android 222 | version: 4.3.1 223 | platform: "" 224 | client: 225 | type: browser 226 | name: Android Browser 227 | version: "" 228 | engine: WebKit 229 | engine_version: "" 230 | device: 231 | type: smart display 232 | brand: AOpen 233 | model: eTILE WT19M-FI 234 | os_family: Android 235 | browser_family: Android Browser 236 | - 237 | user_agent: Mozilla/5.0 (Linux; Android 9; T2s_LITE) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.74 Safari/537.36 238 | os: 239 | name: Android 240 | version: "9" 241 | platform: "" 242 | client: 243 | type: browser 244 | name: Chrome 245 | version: 95.0.4638.74 246 | engine: Blink 247 | engine_version: 95.0.4638.74 248 | device: 249 | type: smart display 250 | brand: Sunmi 251 | model: T2s Lite 252 | os_family: Android 253 | browser_family: Chrome 254 | - 255 | user_agent: Mozilla/5.0 (Linux; Android 8.1.0; XDH-0F-A1 Build/O11019; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/85.0.4183.99 Mobile Safari/537.36 256 | os: 257 | name: Android 258 | version: 8.1.0 259 | platform: "" 260 | client: 261 | type: browser 262 | name: Chrome Webview 263 | version: 85.0.4183.99 264 | engine: Blink 265 | engine_version: 85.0.4183.99 266 | device: 267 | type: smart display 268 | brand: Xiaodu 269 | model: XDH-0F-A1 270 | os_family: Android 271 | browser_family: Chrome 272 | - 273 | user_agent: Mozilla/5.0 (Linux; Android 8.1.0; XDH-17-A1 Build/O11019; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/85.0.4183.99 Mobile Safari/537.36 274 | os: 275 | name: Android 276 | version: 8.1.0 277 | platform: "" 278 | client: 279 | type: browser 280 | name: Chrome Webview 281 | version: 85.0.4183.99 282 | engine: Blink 283 | engine_version: 85.0.4183.99 284 | device: 285 | type: smart display 286 | brand: Xiaodu 287 | model: XDH-17-A1 288 | os_family: Android 289 | browser_family: Chrome 290 | - 291 | user_agent: Mozilla/5.0 (Linux; Android 8.1.0; XDH-18-A1 Build/O11019; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/85.0.4183.99 Mobile Safari/537.36 292 | os: 293 | name: Android 294 | version: 8.1.0 295 | platform: "" 296 | client: 297 | type: browser 298 | name: Chrome Webview 299 | version: 85.0.4183.99 300 | engine: Blink 301 | engine_version: 85.0.4183.99 302 | device: 303 | type: smart display 304 | brand: Xiaodu 305 | model: XDH-18-A1 306 | os_family: Android 307 | browser_family: Chrome 308 | - 309 | user_agent: Mozilla/5.0 (Linux; Android 8.1.0; XDH-21-A1 Build/O11019; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/85.0.4183.99 Mobile Safari/537.36 310 | os: 311 | name: Android 312 | version: 8.1.0 313 | platform: "" 314 | client: 315 | type: browser 316 | name: Chrome Webview 317 | version: 85.0.4183.99 318 | engine: Blink 319 | engine_version: 85.0.4183.99 320 | device: 321 | type: smart display 322 | brand: Xiaodu 323 | model: XDH-21-A1 324 | os_family: Android 325 | browser_family: Chrome 326 | - 327 | user_agent: Mozilla/5.0 (Linux; Android 8.1.0; XDH-29-B1 Build/O11019; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/85.0.4183.99 Mobile Safari/537.36 328 | os: 329 | name: Android 330 | version: 8.1.0 331 | platform: "" 332 | client: 333 | type: browser 334 | name: Chrome Webview 335 | version: 85.0.4183.99 336 | engine: Blink 337 | engine_version: 85.0.4183.99 338 | device: 339 | type: smart display 340 | brand: Xiaodu 341 | model: XDH-29-B1 342 | os_family: Android 343 | browser_family: Chrome 344 | - 345 | user_agent: Mozilla/5.0 (Linux; Android 8.1.0; XDH-2A-B1 Build/O11019; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/85.0.4183.99 Mobile Safari/537.36 346 | os: 347 | name: Android 348 | version: 8.1.0 349 | platform: "" 350 | client: 351 | type: browser 352 | name: Chrome Webview 353 | version: 85.0.4183.99 354 | engine: Blink 355 | engine_version: 85.0.4183.99 356 | device: 357 | type: smart display 358 | brand: Xiaodu 359 | model: XDH-2A-B1 360 | os_family: Android 361 | browser_family: Chrome 362 | - 363 | user_agent: Mozilla/5.0 (Linux; Android 7.1.2; 10BDL4151T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.157 Safari/537.36 364 | os: 365 | name: Android 366 | version: 7.1.2 367 | platform: "" 368 | client: 369 | type: browser 370 | name: Chrome 371 | version: 74.0.3729.157 372 | engine: Blink 373 | engine_version: 74.0.3729.157 374 | device: 375 | type: smart display 376 | brand: Philips 377 | model: 10BDL4151T 378 | os_family: Android 379 | browser_family: Chrome 380 | - 381 | user_agent: Mozilla/5.0 (Linux; Android 4.2.2; DA222HQL Build/JDQ39) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.136 Safari/537.36 382 | os: 383 | name: Android 384 | version: 4.2.2 385 | platform: "" 386 | client: 387 | type: browser 388 | name: Chrome 389 | version: 33.0.1750.136 390 | engine: Blink 391 | engine_version: 33.0.1750.136 392 | device: 393 | type: smart display 394 | brand: Acer 395 | model: DA222HQL 396 | os_family: Android 397 | browser_family: Chrome 398 | - 399 | user_agent: Mozilla/5.0 (Linux; U; Android 10; zh-CN; BLM-00 Build/HUAWEIBLM-00) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/69.0.3497.100 UWS/3.22.1.228 Mobile Safari/537.36 AliApp(DingTalk/6.5.30) com.alibaba.android.rimet/25293052 Channel/227200 language/zh-CN abi/64 Pad/APad Hmos/1 UT4Aplus/0.2.25 colorScheme/light 400 | os: 401 | name: HarmonyOS 402 | version: 1.0.0 403 | platform: "" 404 | client: 405 | type: mobile app 406 | name: DingTalk 407 | version: 6.5.30 408 | device: 409 | type: smart display 410 | brand: Huawei 411 | model: BLM-00 SmartScreen 412 | os_family: Android 413 | browser_family: Unknown 414 | - 415 | user_agent: Mozilla/5.0 (Linux; Android 10; XD-SDD05-2101 Build/QP1A.191105.004; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/74.0.3729.186 Mobile Safari/537.36 416 | os: 417 | name: Android 418 | version: "10" 419 | platform: "" 420 | client: 421 | type: browser 422 | name: Chrome Webview 423 | version: 74.0.3729.186 424 | engine: Blink 425 | engine_version: 74.0.3729.186 426 | device: 427 | type: smart display 428 | brand: Xiaodu 429 | model: XD-SDD05-2101 430 | os_family: Android 431 | browser_family: Chrome 432 | - 433 | user_agent: Mozilla/5.0 (Linux; Android 10; PortalGo) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.9999.0 Safari/537.36 434 | os: 435 | name: Android 436 | version: "10" 437 | platform: "" 438 | client: 439 | type: browser 440 | name: Chrome 441 | version: 102.0.9999.0 442 | engine: Blink 443 | engine_version: 102.0.9999.0 444 | device: 445 | type: smart display 446 | brand: Meta 447 | model: Portal Go 448 | os_family: Android 449 | browser_family: Chrome 450 | -------------------------------------------------------------------------------- /spec/fixtures/detector/smart_speaker.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - 3 | user_agent: AppleCoreMedia/1.0.0.15F80 (HomePod; U; CPU OS 11_4 like Mac OS X; fr_fr) 4 | os: 5 | name: iOS 6 | version: "11.4" 7 | platform: "" 8 | client: 9 | type: library 10 | name: iOS Application 11 | version: "" 12 | device: 13 | type: smart speaker 14 | brand: Apple 15 | model: HomePod 16 | os_family: iOS 17 | browser_family: Unknown 18 | - 19 | user_agent: Dalvik/2.1.0 (Linux; U; Android 5.1.1; AEOBC Build/LVY48F) 20 | os: 21 | name: Fire OS 22 | version: "5" 23 | platform: "" 24 | client: 25 | type: browser 26 | name: Android Browser 27 | version: "" 28 | engine: WebKit 29 | engine_version: "" 30 | device: 31 | type: smart speaker 32 | brand: Amazon 33 | model: Echo 34 | os_family: Android 35 | browser_family: Android Browser 36 | - 37 | user_agent: Mozilla/5.0 (Linux; Android 5.1.1; AEOKN Build/LVY48F; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/59.0.3071.125 Safari/537.36 38 | os: 39 | name: Fire OS 40 | version: "5" 41 | platform: "" 42 | client: 43 | type: browser 44 | name: Chrome Webview 45 | version: 59.0.3071.125 46 | engine: Blink 47 | engine_version: 59.0.3071.125 48 | device: 49 | type: smart speaker 50 | brand: Amazon 51 | model: Echo 52 | os_family: Android 53 | browser_family: Chrome 54 | - 55 | user_agent: Echo/1.0(APNG) 56 | os: [ ] 57 | client: 58 | type: mediaplayer 59 | name: Alexa 60 | version: "1.0" 61 | device: 62 | type: smart speaker 63 | brand: Amazon 64 | model: Echo 65 | os_family: Unknown 66 | browser_family: Unknown 67 | - 68 | user_agent: Mozilla/5.0 (Linux; Android 7.1.2; AEOCH Build/NS6548; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/92.0.4515.115 Mobile Safari/537.36 69 | os: 70 | name: Fire OS 71 | version: "6" 72 | platform: "" 73 | client: 74 | type: browser 75 | name: Chrome Webview 76 | version: 92.0.4515.115 77 | engine: Blink 78 | engine_version: 92.0.4515.115 79 | device: 80 | type: smart speaker 81 | brand: Amazon 82 | model: Echo Show 8 83 | os_family: Android 84 | browser_family: Chrome 85 | - 86 | user_agent: Mozilla/5.0 (Linux; Android 7.1.2; AEOCW Build/NS6543; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/88.0.4324.152 Safari/537.36 87 | os: 88 | name: Fire OS 89 | version: "6" 90 | platform: "" 91 | client: 92 | type: browser 93 | name: Chrome Webview 94 | version: 88.0.4324.152 95 | engine: Blink 96 | engine_version: 88.0.4324.152 97 | device: 98 | type: smart speaker 99 | brand: Amazon 100 | model: Echo Show 8 101 | os_family: Android 102 | browser_family: Chrome 103 | - 104 | user_agent: AirPlay/540.31 105 | os: 106 | name: iOS 107 | version: "" 108 | platform: "" 109 | client: null 110 | device: 111 | type: smart speaker 112 | brand: Apple 113 | model: "" 114 | os_family: iOS 115 | browser_family: Unknown 116 | - 117 | user_agent: Mozilla/5.0 (Linux; Android 9; AEOHY) AppleWebKit/537.36 (KHTML, like Gecko) Silk/94.3.10 like Chrome/94.0.4606.126 Safari/537.36 118 | os: 119 | name: Fire OS 120 | version: "7" 121 | platform: "" 122 | client: 123 | type: browser 124 | name: Mobile Silk 125 | version: 94.3.10 126 | engine: Blink 127 | engine_version: 94.0.4606.126 128 | device: 129 | type: smart speaker 130 | brand: Amazon 131 | model: Echo Show 15 (2021) 132 | os_family: Android 133 | browser_family: Chrome 134 | - 135 | user_agent: Mozilla/5.0 (Linux; Android 7.1.2; AEOCN) AppleWebKit/537.36 (KHTML, like Gecko) Silk/110.6.7 like Chrome/110.0.5481.212 Safari/537.36 136 | os: 137 | name: Fire OS 138 | version: "6" 139 | platform: "" 140 | client: 141 | type: browser 142 | name: Mobile Silk 143 | version: 110.6.7 144 | engine: Blink 145 | engine_version: 110.0.5481.212 146 | device: 147 | type: smart speaker 148 | brand: Amazon 149 | model: Echo 150 | os_family: Android 151 | browser_family: Chrome 152 | - 153 | user_agent: Mozilla/5.0 (Linux; Android 9; AEOAT) AppleWebKit/537.36 (KHTML, like Gecko) Silk/112.6.3 like Chrome/112.0.5615.213 Safari/537.36 154 | os: 155 | name: Fire OS 156 | version: "7" 157 | platform: "" 158 | client: 159 | type: browser 160 | name: Mobile Silk 161 | version: 112.6.3 162 | engine: Blink 163 | engine_version: 112.0.5615.213 164 | device: 165 | type: smart speaker 166 | brand: Amazon 167 | model: Echo 168 | os_family: Android 169 | browser_family: Chrome 170 | - 171 | user_agent: Mozilla/5.0 (Linux; Android 9; AEOTA) AppleWebKit/537.36 (KHTML, like Gecko) Silk/106.4.3 like Chrome/106.0.5249.190 Safari/537.36 172 | os: 173 | name: Fire OS 174 | version: "7" 175 | platform: "" 176 | client: 177 | type: browser 178 | name: Mobile Silk 179 | version: 106.4.3 180 | engine: Blink 181 | engine_version: 106.0.5249.190 182 | device: 183 | type: smart speaker 184 | brand: Amazon 185 | model: Echo 186 | os_family: Android 187 | browser_family: Chrome 188 | - 189 | user_agent: Mozilla/5.0 (Linux; Android 5.1.1; AEOBP) AppleWebKit/537.36 (KHTML, like Gecko) Silk/100.1.158 like Chrome/100.0.4896.127 Safari/537.36 190 | os: 191 | name: Fire OS 192 | version: "5" 193 | platform: "" 194 | client: 195 | type: browser 196 | name: Mobile Silk 197 | version: 100.1.158 198 | engine: Blink 199 | engine_version: 100.0.4896.127 200 | device: 201 | type: smart speaker 202 | brand: Amazon 203 | model: Echo Show (Gen 2) 204 | os_family: Android 205 | browser_family: Chrome 206 | - 207 | user_agent: Mozilla/5.0 (Linux; Android 11; YNDX-00055 Build/RQ3A.210805.001.A1; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/113.0.5672.77 Safari/537.36 centaur/1100 208 | os: 209 | name: Android 210 | version: "11" 211 | platform: "" 212 | client: 213 | type: browser 214 | name: Chrome Webview 215 | version: 113.0.5672.77 216 | engine: Blink 217 | engine_version: 113.0.5672.77 218 | device: 219 | type: smart speaker 220 | brand: Yandex 221 | model: SmartSpeak Duo Max 222 | os_family: Android 223 | browser_family: Chrome 224 | - 225 | user_agent: Mozilla/5.0 (X11; Linux armv7l) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.225 Safari/537.36 CrKey/1.56.500000 DeviceType/SmartSpeaker 226 | os: 227 | name: GNU/Linux 228 | version: "" 229 | platform: "ARM" 230 | client: 231 | type: browser 232 | name: Chrome 233 | version: 90.0.4430.225 234 | engine: Blink 235 | engine_version: 90.0.4430.225 236 | device: 237 | type: smart speaker 238 | brand: Google 239 | model: Chromecast 240 | os_family: GNU/Linux 241 | browser_family: Chrome 242 | -------------------------------------------------------------------------------- /spec/fixtures/device/camera.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - 3 | user_agent: Mozilla/5.0 (Linux; U; Android 4.0; de-DE; EK-GC100 Build/IMM76D) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30 4 | device: 5 | type: camera 6 | brand: Samsung 7 | model: Galaxy Camera 8 | - 9 | user_agent: Mozilla/5.0 (Linux; Android 4.1.2; EK-GC100 Build/JZO54K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.63 Mobile Safari/537.36 OPR/15.0.1162.60140 10 | device: 11 | type: camera 12 | brand: Samsung 13 | model: Galaxy Camera 14 | - 15 | user_agent: Mozilla/5.0 (Linux; U; Android 2.3.3; ja-jp; COOLPIX S800c Build/CP01_WW) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1 16 | device: 17 | type: camera 18 | brand: Nikon 19 | model: Coolpix S800c 20 | -------------------------------------------------------------------------------- /spec/fixtures/device/car_browser.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - 3 | user_agent: Mozilla/5.0 (X11; u; Linux; C) AppleWebKit /533.3 (Khtml, like Gheko) QtCarBrowser Safari/533.3 4 | device: 5 | type: car browser 6 | brand: Tesla 7 | model: Model S 8 | - 9 | user_agent: Mozilla/5.0 (X11; GNU/Linux) AppleWebKit/537.36 (KHTML, like Gecko) Chromium/75.0.3770.100 Chrome/75.0.3770.100 Safari/537.36 Tesla/2019.40.50.7-ad132c7b057e 10 | device: 11 | type: car browser 12 | brand: Tesla 13 | model: "" 14 | - 15 | user_agent: Mozilla/5.0 (Linux; Android 9; AFTLBT962E2) AppleWebKit/537.36 (KHTML, like Gecko) Silk/118.3.1 like Chrome/118.0.5993.155 Safari/537.36 16 | device: 17 | type: car browser 18 | brand: BMW 19 | model: Car (2022) 20 | - 21 | user_agent: Dalvik/2.1.0 (Linux; U; Android 9; AFTLFT962X3 Build/PMAIN1) 22 | device: 23 | type: car browser 24 | brand: Jeep 25 | model: Wagoneer 26 | -------------------------------------------------------------------------------- /spec/fixtures/device/console.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - 3 | user_agent: Mozilla/5.0 (Linux; Android 4.1.1; ARCHOS GAMEPAD Build/JRO03H) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.166 Safari/535.19 4 | device: 5 | type: console 6 | brand: Archos 7 | model: Gamepad 8 | - 9 | user_agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0; Xbox) 10 | device: 11 | type: console 12 | brand: Microsoft 13 | model: Xbox 360 14 | - 15 | user_agent: Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; Trident/6.0; Xbox; Xbox One) 16 | device: 17 | type: console 18 | brand: Microsoft 19 | model: Xbox One 20 | - 21 | user_agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; Xbox; Xbox One) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2486.0 Safari/537.36 Edge/13.10586 22 | device: 23 | type: console 24 | brand: Microsoft 25 | model: Xbox One 26 | - 27 | user_agent: Mozilla/5.0 (Nintendo 3DS; U; ; en) Version/1.7498.EU 28 | device: 29 | type: console 30 | brand: Nintendo 31 | model: 3DS 32 | - 33 | user_agent: Bunjalloo/0.7.6(Nintendo DS;U;en) 34 | device: 35 | type: console 36 | brand: Nintendo 37 | model: DS 38 | - 39 | user_agent: Opera/9.30 (Nintendo Wii; U; ; 3642; en) 40 | device: 41 | type: console 42 | brand: Nintendo 43 | model: Wii 44 | - 45 | user_agent: Mozilla/5.0 (Nintendo WiiU) AppleWebKit/534.52 (KHTML, like Gecko) NX/2.1.0.8.21 NintendoBrowser/1.0.0.7494.US 46 | device: 47 | type: console 48 | brand: Nintendo 49 | model: WiiU 50 | - 51 | user_agent: Mozilla/5.0 (Linux; U; Android OUYA 4.1.2; en-us; OUYA Build/JZO54L-OUYA) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Safari/534.30 52 | device: 53 | type: console 54 | brand: OUYA 55 | model: OUYA 56 | - 57 | user_agent: Mozilla/5.0 (PLAYSTATION 3 4.46) AppleWebKit/531.22.8 (KHTML, like Gecko) 58 | device: 59 | type: console 60 | brand: Sony 61 | model: PlayStation 3 62 | - 63 | user_agent: Mozilla/5.0 (PlayStation 4 1.52) AppleWebKit/536.26 (KHTML, like Gecko) 64 | device: 65 | type: console 66 | brand: Sony 67 | model: PlayStation 4 68 | - 69 | user_agent: Mozilla/4.0 (PlayStation Portable); 2.00) 70 | device: 71 | type: console 72 | brand: Sony 73 | model: PlayStation Portable 74 | - 75 | user_agent: Mozilla/5.0 (PlayStation Vita 3.01) AppleWebKit/536.26 (KHTML, like Gecko) Silk/3.2 76 | device: 77 | type: console 78 | brand: Sony 79 | model: PlayStation Vita 80 | - 81 | user_agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; XBOX_ONE_ED) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.79 Safari/537.36 Edge/14.14393 82 | device: 83 | type: console 84 | brand: Microsoft 85 | model: Xbox One S 86 | - 87 | user_agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; Xbox; Xbox One X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.82 Safari/537.36 Edge/20.02 88 | device: 89 | type: console 90 | brand: Microsoft 91 | model: Xbox One X 92 | - 93 | user_agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; Xbox; Xbox Series X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.82 Safari/537.36 Edge/20.02 94 | device: 95 | type: console 96 | brand: Microsoft 97 | model: Xbox Series X 98 | -------------------------------------------------------------------------------- /spec/fixtures/device/notebook.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - 3 | user_agent: 'Mozilla/5.0 (Windows NT 10.0.16299.125; osmeta 10.3.3308) AppleWebKit/602.1.1 (KHTML, like Gecko) Version/9.0 Safari/602.1.1 osmeta/10.3.3308 Build/3308 [FBAN/FBW;FBAV/140.0.0.232.179;FBBV/83145113;FBDV/WindowsDevice;FBMD/80VR;FBSN/Windows;FBSV/10.0.16299.371;FBSS/1;FBCR/;FBID/desktop;FBLC/ru_RU;FBOP/45;FBRV/0]' 4 | device: 5 | type: desktop 6 | brand: Lenovo 7 | model: Legion Y720 8 | -------------------------------------------------------------------------------- /spec/fixtures/parser/type-methods.yml: -------------------------------------------------------------------------------- 1 | ### check format: 2 | ### [isBot(), isMobile(), isDesktop(), isTablet(), isTV(), isWearable()] 3 | --- 4 | - 5 | user_agent: Mozilla/5.0 (Linux; Android 9; TEST-XXXX Build/PPR2.180905.006.A1; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/83.0.4103.120 YaBrowser/22.8.0.12 (lite) TV Safari/537.36 6 | check: [false, false, false, false, true, false] 7 | - 8 | user_agent: Mozilla/5.0 (Linux; U; Android 5.1.1; zh-CN; TEST-XXXXX Build/LMY47V) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/78.0.3904.108 Quark/5.3.3.191 Mobile Safari/537.36 9 | check: [false, true, false, false, false, false] 10 | - 11 | user_agent: Mozilla/5.0 (Linux; Android 10; HarmonyOS; TEST-XXXXX ; HMSCore 6.1.0.314) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.93 HuaweiBrowser/11.1.5.310 Mobile Safari/537.36 12 | check: [false, true, false, false, false, false] 13 | - 14 | user_agent: Googlebot/2.1 (http://www.googlebot.com/bot.html) 15 | check: [true, false, false, false, false, false] 16 | - 17 | user_agent: Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.136 Mobile Safari/537.36 18 | check: [false, true, false, false, false, false] 19 | - 20 | user_agent: Mozilla/5.0 (Linux; Android 4.4.3; Build/KTU84L) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.117 Mobile Safari/537.36 21 | check: [false, true, false, false, false, false] 22 | - 23 | user_agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0) 24 | check: [false, false, true, false, false, false] 25 | - 26 | user_agent: Mozilla/3.01 (compatible;) 27 | check: [false, false, false, false, false, false] 28 | - 29 | user_agent: 'Mozilla/5.0 (Linux; U; Android 10; zh-CN; TEST-XXXX Build/HUAWEI) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/69.0.3497.100 UWS/3.22.1.228 Mobile Safari/537.36 AliApp(DingTalk/6.5.30) com.alibaba.android.rimet/25293052 Channel/227200 language/zh-CN abi/64 Pad/APad Hmos/1 UT4Aplus/0.2.25 colorScheme/light' 30 | check: [false, true, false, true, false, false] 31 | ### Mobile only browsers 32 | - 33 | user_agent: Opera/9.80 (J2ME/MIDP; Opera Mini/9.5/37.8069; U; en) Presto/2.12.423 Version/12.16 34 | check: [false, true, false, false, false, false] 35 | - 36 | user_agent: 'Mozilla/5.0 (X11; U; Linux i686; th-TH@calendar=gregorian) AppleWebKit/534.12 (KHTML, like Gecko) Puffin/1.3.2665MS Safari/534.12' 37 | check: [false, true, false, false, false, false] 38 | - 39 | user_agent: Mozilla/5.0 (Linux; Android 4.4.4; MX4 Pro Build/KTU84P) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/33.0.0.0 Mobile Safari/537.36; 360 Aphone Browser (6.9.7) 40 | check: [false, true, false, false, false, false] 41 | - 42 | user_agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_5_7; xx) AppleWebKit/530.17 (KHTML, like Gecko) Version/4.0 Safari/530.17 Skyfire/6DE 43 | check: [false, true, false, false, false, false] 44 | - 45 | user_agent: Mozilla/5.0 (Linux; U; Android 4.1.2; ru-ru; PMP7380D3G Build/JZO54K) AppleWebKit/534.30 (KHTML, Ã�ºÃ�°Ã�º Gecko) Version/4.0 Safari/534.30 46 | check: [false, true, false, true, false, false] 47 | -------------------------------------------------------------------------------- /spec/fixtures/parser/vendorfragments.yml: -------------------------------------------------------------------------------- 1 | - 2 | useragent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; Trident/7.0; SLCC2; .NET CLR 2.0.50727; Media Center PC 6.0; MAAR; Tablet PC 2.0; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET4.0C; .NET4.0E) 3 | vendor: Acer 4 | - 5 | useragent: Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; WOW64; Trident/6.0; Touch; MAARJS) 6 | vendor: Acer 7 | - 8 | useragent: Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; MAAU; rv:11.0) like Gecko 9 | vendor: Asus 10 | - 11 | useragent: Mozilla/5.0 (Windows NT 6.3; Win64; x64; Trident/7.0; ASU2JS; rv:11.0) like Gecko 12 | vendor: Asus 13 | - 14 | useragent: Mozilla/5.0 (Windows NT 6.3; Win64; x64; Trident/7.0; NP06; rv:11.0) like Gecko 15 | vendor: Asus 16 | - 17 | useragent: Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; ASJB; rv:11.0) like Gecko 18 | vendor: Asus 19 | - 20 | useragent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; WOW64; Trident/7.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; CPDTDF; .NET4.0C; InfoPath.3; .NET4.0E; Microsoft Outlook 14.0.7113; ms-office; MSOffice 14) 21 | vendor: Compaq 22 | - 23 | useragent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.2; WOW64; Trident/6.0; .NET4.0E; .NET4.0C; .NET CLR 3.5.30729; .NET CLR 2.0.50727; .NET CLR 3.0.30729; CPNTDFJS) 24 | vendor: Compaq 25 | - 26 | useragent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; CMNTDF; InfoPath.2; .NET4.0C; .NET4.0E) 27 | vendor: Compaq 28 | - 29 | useragent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.2; WOW64; Trident/6.0; .NET4.0E; .NET4.0C; .NET CLR 3.5.30729; .NET CLR 2.0.50727; .NET CLR 3.0.30729; CMDTDFJS; Tablet PC 2.0) 30 | vendor: Compaq 31 | - 32 | useragent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E; CMDTDF; InfoPath.3) 33 | vendor: Compaq 34 | - 35 | useragent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; Win64; x64; Trident/4.0; .NET CLR 2.0.50727; SLCC2; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; MDDC; .NET4.0C; .NET4.0E) 36 | vendor: Dell 37 | - 38 | useragent: Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; Win64; x64; Trident/6.0; Touch; MDDCJS) 39 | vendor: Dell 40 | - 41 | useragent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; WOW64; Trident/4.0; GTB7.5; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; MDDR) 42 | vendor: Dell 43 | - 44 | useragent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; MDDRJS) 45 | vendor: Dell 46 | - 47 | useragent: Mozilla/5.0 (Windows NT 6.1; Trident/7.0; MDDSJS; rv:11.0) like Gecko 48 | vendor: Dell 49 | - 50 | useragent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.2; WOW64; Trident/6.0; .NET4.0E; .NET4.0C; .NET CLR 3.5.30729; .NET CLR 2.0.50727; .NET CLR 3.0.30729; McAfee; MDDSJS) 51 | vendor: Dell 52 | - 53 | useragent: Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; FSJB; rv:11.0) like Gecko 54 | vendor: Fujitsu 55 | - 56 | useragent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; MAFSJS) 57 | vendor: Fujitsu 58 | - 59 | useragent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; WOW64; Trident/7.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E; MAFS; InfoPath.2; OfficeLiveConnector.1.5; OfficeLivePatch.1.3; MSOffice 12) 60 | vendor: Fujitsu 61 | - 62 | useragent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; WOW64; Trident/7.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; MAGW; .NET4.0C; .NET4.0E; Microsoft Outlook 14.0.7113; ms-office; MSOffice 14) 63 | vendor: Gateway 64 | - 65 | useragent: Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; WOW64; Trident/6.0; MAGWJS) 66 | vendor: Gateway 67 | - 68 | useragent: Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; WOW64; Trident/6.0; HPCMHP) 69 | vendor: HP 70 | - 71 | useragent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.2; WOW64; Trident/6.0; .NET4.0E; .NET4.0C; .NET CLR 3.5.30729; .NET CLR 2.0.50727; .NET CLR 3.0.30729; HPDTDFJS) 72 | vendor: HP 73 | - 74 | useragent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.2; WOW64; Trident/6.0; .NET4.0E; .NET4.0C; .NET CLR 3.5.30729; .NET CLR 2.0.50727; .NET CLR 3.0.30729; HPNTDFJS) 75 | vendor: HP 76 | - 77 | useragent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; Trident/6.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; MANM; .NET4.0C; InfoPath.3; .NET4.0E) 78 | vendor: Hyrican 79 | - 80 | useragent: Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; MANMJS; rv:11.0) like Gecko 81 | vendor: Hyrican 82 | - 83 | useragent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; Trident/4.0; QQDownload 691; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; MALC) 84 | vendor: Lenovo 85 | - 86 | useragent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; LEN2) 87 | vendor: Lenovo 88 | - 89 | useragent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.2; WOW64; Trident/6.0; .NET4.0E; .NET4.0C; .NET CLR 3.5.30729; .NET CLR 2.0.50727; .NET CLR 3.0.30729; MALEJS) 90 | vendor: Lenovo 91 | - 92 | useragent: Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; LCJB; rv:11.0) like Gecko 93 | vendor: Lenovo 94 | - 95 | useragent: Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; Trident/6.0; Touch; MALCJS; WebView/1.0) 96 | vendor: Lenovo 97 | - 98 | useragent: Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; ARM; Trident/6.0; Touch; MALNJS) 99 | vendor: Lenovo 100 | - 101 | useragent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30; MALN) 102 | vendor: Lenovo 103 | - 104 | useragent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0; MAMD) 105 | vendor: Medion 106 | - 107 | useragent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; MAM3; InfoPath.3; .NET4.0E; MAM3) 108 | vendor: MSI 109 | - 110 | useragent: Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; MAMI; rv:11.0) like Gecko 111 | vendor: MSI 112 | - 113 | useragent: Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; MAMIJS; rv:11.0) like Gecko 114 | vendor: MSI 115 | - 116 | useragent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; WOW64; Trident/7.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; MASM; .NET4.0C; .NET4.0E) 117 | vendor: Samsung 118 | - 119 | useragent: Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; WOW64; Trident/6.0; MASMJS) 120 | vendor: Samsung 121 | - 122 | useragent: Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; WOW64; Trident/6.0; SMJB) 123 | vendor: Samsung 124 | - 125 | useragent: Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; WOW64; Trident/6.0; MASP) 126 | vendor: Sony 127 | - 128 | useragent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; WOW64; Trident/7.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET4.0C; .NET4.0E; Media Center PC 6.0; MASA) 129 | vendor: Sony 130 | - 131 | useragent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; WOW64; Trident/7.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET4.0C; .NET4.0E; Media Center PC 6.0; MASE) 132 | vendor: Sony 133 | - 134 | useragent: Mozilla/5.0 (Windows NT 6.1; Win64; x64; Trident/7.0; MASAJS; rv:11.0) like Gecko 135 | vendor: Sony 136 | - 137 | useragent: Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; Win64; x64; Trident/6.0; Touch; MASPJS) 138 | vendor: Sony 139 | - 140 | useragent: Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; Win64; x64; Trident/6.0; MASEJS) 141 | vendor: Sony 142 | - 143 | useragent: Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; WOW64; Trident/6.0; MATMJS) 144 | vendor: Toshiba 145 | - 146 | useragent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/7.0; MATM) 147 | vendor: Toshiba 148 | - 149 | useragent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/7.0; MATP) 150 | vendor: Toshiba 151 | - 152 | useragent: Mozilla/5.0 (MSIE 9.0; Windows NT 6.3; WOW64; Trident/7.0; MATBJS; rv:11.0) like Gecko 153 | vendor: Toshiba 154 | - 155 | useragent: Mozilla/5.0 (Windows NT 6.3; Win64; x64; Trident/7.0; MATPJS; rv:11.0) like Gecko 156 | vendor: Toshiba 157 | - 158 | useragent: Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; TNJB; rv:11.0) like Gecko 159 | vendor: Toshiba 160 | - 161 | useragent: Mozilla/5.0 (Windows NT 6.3; Win64; x64; Trident/7.0; Touch; TAJB; rv:11.0) like Gecko 162 | vendor: Toshiba 163 | - 164 | useragent: Mozilla/5.0 (X11; Linux i686; rv:45.0) Gecko/20100101 Firefox/45.0 Ordissimo/3.8.6.6+svn37147 165 | vendor: Ordissimo 166 | - 167 | useragent: Mozilla/5.0 (X11; Linux i686; rv:45.0) Gecko/20100101 Firefox/45.0 webissimo3/3.7.30+svn32090 168 | vendor: Ordissimo 169 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'minitest/autorun' 2 | require 'minitest/spec' 3 | 4 | $:.unshift(File.expand_path('../../lib', __FILE__)) 5 | require 'device_detector' 6 | begin 7 | require "byebug" 8 | rescue LoadError 9 | end unless RUBY_VERSION < "2.0.0" || RUBY_ENGINE != "ruby" 10 | --------------------------------------------------------------------------------