diff --git a/.travis.yml b/.travis.yml index 28d1d8e..2932136 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,12 +1,19 @@ sudo: false language: python +dist: xenial +arch: + - amd64 + - ppc64le python: - "2.7" - - "3.3" - "3.4" + - "3.5" + - "3.6" + - "3.7" + - "3.8" install: - "python setup.py install" - "pip install -U pip" - - "pip install pyyaml ua-parser" + - "pip install -r requirements.txt" script: - python -m unittest discover diff --git a/README.md b/README.md new file mode 100644 index 0000000..d71d5a6 --- /dev/null +++ b/README.md @@ -0,0 +1,179 @@ +Python User Agents +================== + +`user_agents` is a Python library that provides an easy way to identify/detect devices like mobile phones, tablets and their capabilities by parsing (browser/HTTP) user agent strings. The goal is to reliably detect whether: + +* User agent is a mobile, tablet or PC based device +* User agent has touch capabilities (has touch screen) + +`user_agents` relies on the excellent [ua-parser](https://github.com/ua-parser/uap-python) to do the actual parsing of the raw user agent string. + +Installation +------------ + +![Build status](https://secure.travis-ci.org/selwin/python-user-agents.png) + +`user-agents` is hosted on [PyPI](http://pypi.python.org/pypi/user-agents/) and can be installed as such: + + pip install pyyaml ua-parser user-agents + +Alternatively, you can also get the latest source code from [Github](https://github.com/selwin/python-user-agents) and install it manually. + +Usage +----- + +Various basic information that can help you identify visitors can be accessed `browser`, `device` and `os` attributes. For example: + +```python +from user_agents import parse + +# iPhone's user agent string +ua_string = 'Mozilla/5.0 (iPhone; CPU iPhone OS 5_1 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 Mobile/9B179 Safari/7534.48.3' +user_agent = parse(ua_string) + +# Accessing user agent's browser attributes +user_agent.browser # returns Browser(family=u'Mobile Safari', version=(5, 1), version_string='5.1') +user_agent.browser.family # returns 'Mobile Safari' +user_agent.browser.version # returns (5, 1) +user_agent.browser.version_string # returns '5.1' + +# Accessing user agent's operating system properties +user_agent.os # returns OperatingSystem(family=u'iOS', version=(5, 1), version_string='5.1') +user_agent.os.family # returns 'iOS' +user_agent.os.version # returns (5, 1) +user_agent.os.version_string # returns '5.1' + +# Accessing user agent's device properties +user_agent.device # returns Device(family=u'iPhone', brand=u'Apple', model=u'iPhone') +user_agent.device.family # returns 'iPhone' +user_agent.device.brand # returns 'Apple' +user_agent.device.model # returns 'iPhone' + +# Viewing a pretty string version +str(user_agent) # returns "iPhone / iOS 5.1 / Mobile Safari 5.1" +``` + +`user_agents` also expose a few other more "sophisticated" attributes that are derived from one or more basic attributes defined above. As for now, these attributes should correctly identify popular platforms/devices, pull requests to support smaller ones are always welcome. + +Currently these attributes are supported: + +* `is_mobile`: whether user agent is identified as a mobile phone (iPhone, Android phones, Blackberry, Windows Phone devices etc) +* `is_tablet`: whether user agent is identified as a tablet device (iPad, Kindle Fire, Nexus 7 etc) +* `is_pc`: whether user agent is identified to be running a traditional "desktop" OS (Windows, OS X, Linux) +* `is_touch_capable`: whether user agent has touch capabilities +* `is_bot`: whether user agent is a search engine crawler/spider + +For example: + +```python +from user_agents import parse + +# Let's start from an old, non touch Blackberry device +ua_string = 'BlackBerry9700/5.0.0.862 Profile/MIDP-2.1 Configuration/CLDC-1.1 VendorID/331 UNTRUSTED/1.0 3gpp-gba' +user_agent = parse(ua_string) +user_agent.is_mobile # returns True +user_agent.is_tablet # returns False +user_agent.is_touch_capable # returns False +user_agent.is_pc # returns False +user_agent.is_bot # returns False +str(user_agent) # returns "BlackBerry 9700 / BlackBerry OS 5 / BlackBerry 9700" + +# Now a Samsung Galaxy S3 +ua_string = 'Mozilla/5.0 (Linux; U; Android 4.0.4; en-gb; GT-I9300 Build/IMM76D) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30' +user_agent = parse(ua_string) +user_agent.is_mobile # returns True +user_agent.is_tablet # returns False +user_agent.is_touch_capable # returns True +user_agent.is_pc # returns False +user_agent.is_bot # returns False +str(user_agent) # returns "Samsung GT-I9300 / Android 4.0.4 / Android 4.0.4" + +# iPad's user agent string +ua_string = 'Mozilla/5.0(iPad; U; CPU iPhone OS 3_2 like Mac OS X; en-us) AppleWebKit/531.21.10 (KHTML, like Gecko) Version/4.0.4 Mobile/7B314 Safari/531.21.10' +user_agent = parse(ua_string) +user_agent.is_mobile # returns False +user_agent.is_tablet # returns True +user_agent.is_touch_capable # returns True +user_agent.is_pc # returns False +user_agent.is_bot # returns False +str(user_agent) # returns "iPad / iOS 3.2 / Mobile Safari 4.0.4" + +# Kindle Fire's user agent string +ua_string = 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_3; en-us; Silk/1.1.0-80) AppleWebKit/533.16 (KHTML, like Gecko) Version/5.0 Safari/533.16 Silk-Accelerated=true' +user_agent = parse(ua_string) +user_agent.is_mobile # returns False +user_agent.is_tablet # returns True +user_agent.is_touch_capable # returns True +user_agent.is_pc # returns False +user_agent.is_bot # returns False +str(user_agent) # returns "Kindle / Android / Amazon Silk 1.1.0-80" + +# Touch capable Windows 8 device +ua_string = 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; Trident/6.0; Touch)' +user_agent = parse(ua_string) +user_agent.is_mobile # returns False +user_agent.is_tablet # returns False +user_agent.is_touch_capable # returns True +user_agent.is_pc # returns True +user_agent.is_bot # returns False +str(user_agent) # returns "PC / Windows 8 / IE 10" +``` + +Running Tests +------------- + + python -m unittest discover + +Changelog +--------- +### Version 2.2.0 (2020-08-23) +* `ua-parser` >= 0.10.0 is required. Thanks @jnozsc! +* Added `get_device()`, `get_os()` and `get_browser()` instance methods +to `UserAgent`. Thanks @rodrigondec! + +### Version 2.1 (2020-02-08) + +* `python-user-agents` now require `ua-parser>=0.9.0`. Thanks @jnozsc! +* Properly detect Chrome Mobile browser families. Thanks @jnozsc! + +### Version 2.0 (2019-04-07) + +* `python-user-agents` now require `ua-parser>=0.8.0`. Thanks @IMDagger! + +### Version 1.1 + +* Fixes packaging issue + +### Version 1.0 + +* Adds compatibility with `ua-parser` 0.4.0 +* Access to more device information in `user_agent.device.brand` and `user_agent.device.model` + +### Version 0.3.2 + +* Better mobile detection +* Better PC detection + +### Version 0.3.1 + +* user\_agent.is\_mobile returns True when mobile spider is detected + +### Version 0.3.0 + +* Added **str**/**unicode** methods for convenience of pretty string + +### Version 0.2.0 + +* Fixed errors when running against newer versions if ua-parser +* Support for Python 3 + +### Version 0.1.1 + +* Added `is_bot` property +* Symbian OS devices are now detected as a mobile device + +### Version 0.1 + +* Initial release + +Developed by the cool guys at [Stamps](http://stamps.co.id). diff --git a/README.rst b/README.rst deleted file mode 100644 index ee4aee2..0000000 --- a/README.rst +++ /dev/null @@ -1,201 +0,0 @@ -Python User Agents -================== - -``user_agents`` is a Python library that provides an easy way to -identify/detect devices like mobile phones, tablets and their -capabilities by parsing (browser/HTTP) user agent strings. The goal is -to reliably detect whether: - -- User agent is a mobile, tablet or PC based device -- User agent has touch capabilities (has touch screen) - -``user_agents`` relies on the excellent -``ua-parser ``\ \_ to do the actual -parsing of the raw user agent string. - -Installation ------------- - -.. figure:: https://secure.travis-ci.org/selwin/python-user-agents.png - :alt: Build status - - Build status -``user-agents`` is hosted on -`PyPI `__ and can be installed -as such: - -:: - - pip install pyyaml ua-parser user-agents - -Alternatively, you can also get the latest source code from -``Github``\ \_ and install it manually. - -Usage ------ - -Various basic information that can help you identify visitors can be -accessed ``browser``, ``device`` and ``os`` attributes. For example: - -.. code:: python - - - from user_agents import parse - - # iPhone's user agent string - ua_string = 'Mozilla/5.0 (iPhone; CPU iPhone OS 5_1 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 Mobile/9B179 Safari/7534.48.3' - user_agent = parse(ua_string) - - # Accessing user agent's browser attributes - user_agent.browser # returns Browser(family=u'Mobile Safari', version=(5, 1), version_string='5.1') - user_agent.browser.family # returns 'Mobile Safari' - user_agent.browser.version # returns (5, 1) - user_agent.browser.version_string # returns '5.1' - - # Accessing user agent's operating system properties - user_agent.os # returns OperatingSystem(family=u'iOS', version=(5, 1), version_string='5.1') - user_agent.os.family # returns 'iOS' - user_agent.os.version # returns (5, 1) - user_agent.os.version_string # returns '5.1' - - # Accessing user agent's device properties - user_agent.device # returns Device(family=u'iPhone', brand=u'Apple', model=u'iPhone') - user_agent.device.family # returns 'iPhone' - user_agent.device.brand # returns 'Apple' - user_agent.device.model # returns 'iPhone' - - # Viewing a pretty string version - str(user_agent) # returns "iPhone / iOS 5.1 / Mobile Safari 5.1" - -``user_agents`` also expose a few other more "sophisticated" attributes -that are derived from one or more basic attributes defined above. As for -now, these attributes should correctly identify popular -platforms/devices, pull requests to support smaller ones are always -welcome. - -Currently these attributes are supported: - -- ``is_mobile``: whether user agent is identified as a mobile phone - (iPhone, Android phones, Blackberry, Windows Phone devices etc) -- ``is_tablet``: whether user agent is identified as a tablet device - (iPad, Kindle Fire, Nexus 7 etc) -- ``is_pc``: whether user agent is identified to be running a - traditional "desktop" OS (Windows, OS X, Linux) -- ``is_touch_capable``: whether user agent has touch capabilities -- ``is_bot``: whether user agent is a search engine crawler/spider - -For example: - -.. code:: python - - - from user_agents import parse - - # Let's start from an old, non touch Blackberry device - ua_string = 'BlackBerry9700/5.0.0.862 Profile/MIDP-2.1 Configuration/CLDC-1.1 VendorID/331 UNTRUSTED/1.0 3gpp-gba' - user_agent = parse(ua_string) - user_agent.is_mobile # returns True - user_agent.is_tablet # returns False - user_agent.is_touch_capable # returns False - user_agent.is_pc # returns False - user_agent.is_bot # returns False - str(user_agent) # returns "BlackBerry 9700 / BlackBerry OS 5 / BlackBerry 9700" - - # Now a Samsung Galaxy S3 - ua_string = 'Mozilla/5.0 (Linux; U; Android 4.0.4; en-gb; GT-I9300 Build/IMM76D) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30' - user_agent = parse(ua_string) - user_agent.is_mobile # returns True - user_agent.is_tablet # returns False - user_agent.is_touch_capable # returns True - user_agent.is_pc # returns False - user_agent.is_bot # returns False - str(user_agent) # returns "Samsung GT-I9300 / Android 4.0.4 / Android 4.0.4" - - # iPad's user agent string - ua_string = 'Mozilla/5.0(iPad; U; CPU iPhone OS 3_2 like Mac OS X; en-us) AppleWebKit/531.21.10 (KHTML, like Gecko) Version/4.0.4 Mobile/7B314 Safari/531.21.10' - user_agent = parse(ua_string) - user_agent.is_mobile # returns False - user_agent.is_tablet # returns True - user_agent.is_touch_capable # returns True - user_agent.is_pc # returns False - user_agent.is_bot # returns False - str(user_agent) # returns "iPad / iOS 3.2 / Mobile Safari 4.0.4" - - # Kindle Fire's user agent string - ua_string = 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_3; en-us; Silk/1.1.0-80) AppleWebKit/533.16 (KHTML, like Gecko) Version/5.0 Safari/533.16 Silk-Accelerated=true' - user_agent = parse(ua_string) - user_agent.is_mobile # returns False - user_agent.is_tablet # returns True - user_agent.is_touch_capable # returns True - user_agent.is_pc # returns False - user_agent.is_bot # returns False - str(user_agent) # returns "Kindle / Android / Amazon Silk 1.1.0-80" - - # Touch capable Windows 8 device - ua_string = 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; Trident/6.0; Touch)' - user_agent = parse(ua_string) - user_agent.is_mobile # returns False - user_agent.is_tablet # returns False - user_agent.is_touch_capable # returns True - user_agent.is_pc # returns True - user_agent.is_bot # returns False - str(user_agent) # returns "PC / Windows 8 / IE 10" - -Running Tests -------------- - -:: - - python -m unittest discover - -Changelog ---------- - -Version 1.0.0 -~~~~~~~~~~~~~ - -- Fixes packaging issue - -Version 1.0 -~~~~~~~~~~~ - -- Adds compatibility with ``ua-parser`` 0.4.0 -- Access to more device information in ``user_agent.device.brand`` and - ``user_agent.device.model`` - -=== - -Version 0.3.2 -~~~~~~~~~~~~~ - -- Better mobile detection -- Better PC detection - -Version 0.3.1 -~~~~~~~~~~~~~ - -- user\_agent.is\_mobile returns True when mobile spider is detected - -Version 0.3.0 -~~~~~~~~~~~~~ - -- Added **str**/**unicode** methods for convenience of pretty string - -Version 0.2.0 -~~~~~~~~~~~~~ - -- Fixed errors when running against newer versions if ua-parser -- Support for Python 3 - -Version 0.1.1 -~~~~~~~~~~~~~ - -- Added ``is_bot`` property -- Symbian OS devices are now detected as a mobile device - -Version 0.1 -~~~~~~~~~~~ - -- Initial release - -Developed by the cool guys at `Stamps `__. diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..013c6b4 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +ua-parser==0.10.0 +PyYAML==5.4; python_version != '3.4' +PyYAML==5.4; python_version == '3.4' # the last version support py34 diff --git a/setup.py b/setup.py index f396edb..95ee319 100644 --- a/setup.py +++ b/setup.py @@ -3,18 +3,19 @@ setup( name='user-agents', - version='1.0.1', + version='2.2.0', author='Selwin Ong', author_email='selwin.ong@gmail.com', packages=['user_agents'], url='https://github.com/selwin/python-user-agents', license='MIT', - description='A library to identify devices (phones, tablets) and their capabilities by parsing (browser/HTTP) user agent strings', - long_description=open('README.rst').read(), + description='A library to identify devices (phones, tablets) and their capabilities by parsing browser user agent strings.', + long_description=open('README.md').read(), + long_description_content_type='text/markdown', zip_safe=False, include_package_data=True, package_data={'': ['README.rst']}, - install_requires=['ua-parser>=0.4.1'], + install_requires=['ua-parser>=0.10.0'], classifiers=[ 'Development Status :: 5 - Production/Stable', 'Environment :: Web Environment', @@ -25,8 +26,11 @@ 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', 'Topic :: Internet :: WWW/HTTP', 'Topic :: Software Development :: Libraries :: Python Modules', ] diff --git a/user_agents/__init__.py b/user_agents/__init__.py index 5d19691..e47740f 100644 --- a/user_agents/__init__.py +++ b/user_agents/__init__.py @@ -1,3 +1,3 @@ -VERSION = (0, 3, 2) +VERSION = (2, 2, 0) from .parsers import parse diff --git a/user_agents/devices.json b/user_agents/devices.json index 80a889f..e83d2b2 100644 --- a/user_agents/devices.json +++ b/user_agents/devices.json @@ -8,6 +8,24 @@ "ua_string": "Mozilla/5.0 (Android; Mobile; rv:27.0) Gecko/27.0 Firefox/27.0", "str": "Other / Android / Firefox Mobile 27" }, + "android_tablet_chrome_mobile": { + "is_bot": false, + "is_mobile": false, + "is_pc": false, + "is_tablet": true, + "is_touch_capable": true, + "ua_string": "Mozilla/5.0 (Linux; Android 9; SM-T820 Build/PPR1.180610.011; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/85.0.4183.101 Safari/537.36 ANDROID_APP", + "str": "Samsung SM-T820 / Android 9 / Chrome Mobile WebView 85.0.4183" + }, + "kindle": { + "is_bot": false, + "is_mobile": false, + "is_pc": false, + "is_tablet": true, + "is_touch_capable": true, + "ua_string": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Silk/85.3.5 like Chrome/85.0.4183.126 Safari/537.36", + "str": "Kindle / Linux / Amazon Silk 85.3.5" + }, "blackberry_bold": { "is_bot": false, "is_mobile": true, @@ -53,6 +71,24 @@ "ua_string": "Mozilla/5.0 (Linux; U; Android 2.2; en-us; SCH-I800 Build/FROYO) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1", "str": "SCH-I800 / Android 2.2 / Android 2.2" }, + "generic_android_tablet": { + "is_bot": false, + "is_mobile": false, + "is_pc": false, + "is_tablet": true, + "is_touch_capable": true, + "ua_string": "Mozilla/5.0 (Linux; Android 10.0; X116L) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36", + "str": "X116L / Android 10.0 / Chrome 86.0.4240" + }, + "generic_smart_phone_puffin": { + "is_bot": false, + "is_mobile": true, + "is_pc": false, + "is_tablet": false, + "is_touch_capable": true, + "ua_string": "Mozilla/5.0 (X11; U; U; Linux x86_64; nl-nl) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36 Puffin/8.4.0.42081AP", + "str": "Generic Smartphone / Linux / Puffin 8.4.0" + }, "google_bot": { "is_bot": true, "is_mobile": false, @@ -89,6 +125,15 @@ "ua_string": "Mozilla/5.0(iPad; U; CPU iPhone OS 3_2 like Mac OS X; en-us) AppleWebKit/531.21.10 (KHTML, like Gecko) Version/4.0.4 Mobile/7B314 Safari/531.21.10", "str": "iPad / iOS 3.2 / Mobile Safari 4.0.4" }, + "ipad_puffin": { + "is_bot": false, + "is_mobile": false, + "is_pc": false, + "is_tablet": true, + "is_touch_capable": true, + "ua_string": "Mozilla/5.0 (X11; U; Linux x86_64; nl-NL) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.114 Safari/537.36 Puffin/5.2.3IT", + "str": "iPad / Linux / Puffin 5.2.3" + }, "iphone": { "is_bot": false, "is_mobile": true, @@ -241,6 +286,15 @@ "ua_string": "Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:15.0) Gecko/20100101 Firefox/15.0.1", "str": "PC / Ubuntu / Firefox 15.0.1" }, + "windows_chrome_mobile": { + "is_bot": false, + "is_mobile": false, + "is_pc": true, + "is_tablet": false, + "is_touch_capable": false, + "ua_string": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/70.0.3538.64 Safari/537.36Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:83.0) Gecko/20100101 Firefox/83.0", + "str": "PC / Windows 10 / Chrome Mobile WebView 70.0.3538" + }, "windows_ie": { "is_bot": false, "is_mobile": false, @@ -273,7 +327,7 @@ "is_mobile": true, "is_pc": false, "is_tablet": false, - "is_touch_capable": false, + "is_touch_capable": true, "ua_string": "Mozilla/5.0 (iPhone; U; CPU iPhone OS) (compatible; Googlebot-Mobile/2.1; http://www.google.com/bot.html)", "str": "Spider / Other / Mobile Safari" }, @@ -317,7 +371,7 @@ "is_bot": false, "is_mobile": true, "is_pc": false, - "is_tablet": true, + "is_tablet": false, "is_touch_capable": true, "ua_string": "Opera/9.80 (Android 2.3.3; Linux; Opera Mobi/ADR-1202011015; U; en) Presto/2.9.201 Version/11.50", "str": "Opera / Android 2.3.3 / Opera Mobile 11.50" diff --git a/user_agents/parsers.py b/user_agents/parsers.py index 0c36b27..65f1ea6 100644 --- a/user_agents/parsers.py +++ b/user_agents/parsers.py @@ -16,7 +16,6 @@ PC_OS_FAMILIES = ( 'Windows 95', 'Windows 98', - 'Windows ME', 'Solaris', ) @@ -31,8 +30,12 @@ ) MOBILE_BROWSER_FAMILIES = ( + 'IE Mobile', 'Opera Mobile', 'Opera Mini', + 'Chrome Mobile', + 'Chrome Mobile WebView', + 'Chrome Mobile iOS', ) TABLET_DEVICE_FAMILIES = ( @@ -47,12 +50,14 @@ 'Dell Streak', ) +TABLET_DEVICE_BRANDS = ( + 'Generic_Android_Tablet', +) + TOUCH_CAPABLE_OS_FAMILIES = ( 'iOS', 'Android', 'Windows Phone', - 'Windows Phone OS', - 'Windows RT', 'Windows CE', 'Windows Mobile', 'Firefox OS', @@ -62,10 +67,13 @@ TOUCH_CAPABLE_DEVICE_FAMILIES = ( 'BlackBerry Playbook', 'Blackberry Playbook', + 'Generic Smartphone', + 'iPad', 'Kindle Fire', + 'Kindle' ) -EMAIL_PROGRAM_FAMILIES = { +EMAIL_PROGRAM_FAMILIES = set(( 'Outlook', 'Windows Live Mail', 'AirMail', @@ -82,7 +90,7 @@ 'MailBar', 'kmail2', 'YahooMobileMail' -} +)) def verify_attribute(attribute): if isinstance(attribute, string_types) and attribute.isdigit(): @@ -139,10 +147,11 @@ def __init__(self, user_agent_string): self.device = parse_device(**ua_dict['device']) def __str__(self): - device = self.is_pc and "PC" or self.device.family - os = ("%s %s" % (self.os.family, self.os.version_string)).strip() - browser = ("%s %s" % (self.browser.family, self.browser.version_string)).strip() - return " / ".join([device, os, browser]) + return "{device} / {os} / {browser}".format( + device=self.get_device(), + os=self.get_os(), + browser=self.get_browser() + ) def __unicode__(self): return unicode(str(self)) @@ -150,10 +159,8 @@ def __unicode__(self): def _is_android_tablet(self): # Newer Android tablets don't have "Mobile" in their user agent string, # older ones like Galaxy Tab still have "Mobile" though they're not - if ('Mobile Safari' not in self.ua_string and - self.browser.family != "Firefox Mobile"): - return True - return False + return ('Mobile Safari' not in self.ua_string and + self.browser.family != "Firefox Mobile") def _is_blackberry_touch_capable_device(self): # A helper to determine whether a BB phone has touch capabilities @@ -162,17 +169,28 @@ def _is_blackberry_touch_capable_device(self): return True if 'Blackberry 95' in self.device.family: # BB Storm devices return True - if 'Blackberry 95' in self.device.family: # BB Torch devices - return True return False + def get_device(self): + return self.is_pc and "PC" or self.device.family + + def get_os(self): + return ("%s %s" % (self.os.family, self.os.version_string)).strip() + + def get_browser(self): + return ("%s %s" % (self.browser.family, self.browser.version_string)).strip() + @property def is_tablet(self): if self.device.family in TABLET_DEVICE_FAMILIES: return True + if self.device.brand in TABLET_DEVICE_BRANDS: + return True + if self.device.family in MOBILE_DEVICE_FAMILIES: + return False if (self.os.family == 'Android' and self._is_android_tablet()): return True - if self.os.family.startswith('Windows RT'): + if self.os.family == 'Windows' and self.os.version_string.startswith('RT'): return True if self.os.family == 'Firefox OS' and 'Mobile' not in self.browser.family: return True @@ -183,14 +201,13 @@ def is_mobile(self): # First check for mobile device and mobile browser families if self.device.family in MOBILE_DEVICE_FAMILIES: return True + if self.is_tablet or self.is_pc: + return False if self.browser.family in MOBILE_BROWSER_FAMILIES: return True # Device is considered Mobile OS is Android and not tablet # This is not fool proof but would have to suffice for now - if ((self.os.family == 'Android' or self.os.family == 'Firefox OS') - and not self.is_tablet): - return True - if self.os.family == 'BlackBerry OS' and self.device.family != 'Blackberry Playbook': + if self.os.family in ['Android', 'Firefox OS', 'BlackBerry OS']: return True if self.os.family in MOBILE_OS_FAMILIES: return True @@ -217,16 +234,24 @@ def is_touch_capable(self): return True if self.device.family in TOUCH_CAPABLE_DEVICE_FAMILIES: return True - if self.os.family.startswith('Windows 8') and 'Touch' in self.ua_string: - return True + if self.os.family == 'Windows': + if self.os.version_string.startswith(('RT', 'CE')): + return True + if self.os.version_string.startswith('8') and 'Touch' in self.ua_string: + return True if 'BlackBerry' in self.os.family and self._is_blackberry_touch_capable_device(): return True return False @property def is_pc(self): + if self.device.family in MOBILE_DEVICE_FAMILIES or \ + self.device.family in TABLET_DEVICE_FAMILIES or \ + self.device.brand in TABLET_DEVICE_BRANDS: + return False # Returns True for "PC" devices (Windows, Mac and Linux) - if 'Windows NT' in self.ua_string or self.os.family in PC_OS_FAMILIES: + if 'Windows NT' in self.ua_string or self.os.family in PC_OS_FAMILIES or \ + self.os.family == 'Windows' and self.os.version_string == 'ME': return True # TODO: remove after https://github.com/tobie/ua-parser/issues/127 is closed if self.os.family == 'Mac OS X' and 'Silk' not in self.ua_string: @@ -236,19 +261,20 @@ def is_pc(self): return False if 'Chrome OS' in self.os.family: return True + if 'Chromecast' in self.os.family: + return False if 'Linux' in self.ua_string and 'X11' in self.ua_string: return True return False @property def is_bot(self): - return True if self.device.family == 'Spider' else False + return self.device.family == 'Spider' @property def is_email_client(self): - if self.browser.family in EMAIL_PROGRAM_FAMILIES: - return True - return False + return self.browser.family in EMAIL_PROGRAM_FAMILIES + def parse(user_agent_string): return UserAgent(user_agent_string) diff --git a/user_agents/tests.py b/user_agents/tests.py index 345cf82..d2633c0 100644 --- a/user_agents/tests.py +++ b/user_agents/tests.py @@ -9,6 +9,7 @@ iphone_ua_string = 'Mozilla/5.0 (iPhone; CPU iPhone OS 5_1 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 Mobile/9B179 Safari/7534.48.3' ipad_ua_string = 'Mozilla/5.0(iPad; U; CPU iPhone OS 3_2 like Mac OS X; en-us) AppleWebKit/531.21.10 (KHTML, like Gecko) Version/4.0.4 Mobile/7B314 Safari/531.21.10' +ipad_mobile_chrome_ua_string = 'Mozilla/5.0 (iPad; CPU OS 13_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/86.0.4240.93 Mobile/15E148 Safari/604.1' galaxy_tab_ua_string = 'Mozilla/5.0 (Linux; U; Android 2.2; en-us; SCH-I800 Build/FROYO) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1' galaxy_s3_ua_string = 'Mozilla/5.0 (Linux; U; Android 4.0.4; en-gb; GT-I9300 Build/IMM76D) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30' kindle_fire_ua_string = 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_3; en-us; Silk/1.1.0-80) AppleWebKit/533.16 (KHTML, like Gecko) Version/5.0 Safari/533.16 Silk-Accelerated=true' @@ -34,6 +35,7 @@ iphone_ua = parse(iphone_ua_string) ipad_ua = parse(ipad_ua_string) +ipad_mobile_chrome_ua = parse(ipad_mobile_chrome_ua_string) galaxy_tab = parse(galaxy_tab_ua_string) galaxy_s3_ua = parse(galaxy_s3_ua_string) kindle_fire_ua = parse(kindle_fire_ua_string) @@ -100,6 +102,7 @@ def test_is_tablet_property(self): self.assertFalse(nokia_n97_ua.is_tablet) self.assertTrue(windows_rt_ua.is_tablet) self.assertTrue(ipad_ua.is_tablet) + self.assertTrue(ipad_mobile_chrome_ua.is_tablet) self.assertTrue(playbook_ua.is_tablet) self.assertTrue(kindle_fire_ua.is_tablet) self.assertTrue(nexus_7_ua.is_tablet) @@ -115,6 +118,7 @@ def test_is_mobile_property(self): self.assertTrue(nokia_n97_ua.is_mobile) self.assertFalse(windows_rt_ua.is_mobile) self.assertFalse(ipad_ua.is_mobile) + self.assertFalse(ipad_mobile_chrome_ua.is_mobile) self.assertFalse(playbook_ua.is_mobile) self.assertFalse(kindle_fire_ua.is_mobile) self.assertFalse(nexus_7_ua.is_mobile) @@ -130,6 +134,7 @@ def test_is_touch_property(self): self.assertTrue(iphone_ua.is_touch_capable) self.assertTrue(galaxy_s3_ua.is_touch_capable) self.assertTrue(ipad_ua.is_touch_capable) + self.assertTrue(ipad_mobile_chrome_ua.is_touch_capable) self.assertTrue(playbook_ua.is_touch_capable) self.assertTrue(kindle_fire_ua.is_touch_capable) self.assertTrue(nexus_7_ua.is_touch_capable) @@ -151,6 +156,7 @@ def test_is_pc(self): self.assertFalse(iphone_ua.is_pc) self.assertFalse(galaxy_s3_ua.is_pc) self.assertFalse(ipad_ua.is_pc) + self.assertFalse(ipad_mobile_chrome_ua.is_pc) self.assertFalse(playbook_ua.is_pc) self.assertFalse(kindle_fire_ua.is_pc) self.assertFalse(nexus_7_ua.is_pc) @@ -174,6 +180,7 @@ def test_is_bot(self): self.assertFalse(iphone_ua.is_bot) self.assertFalse(galaxy_s3_ua.is_bot) self.assertFalse(ipad_ua.is_bot) + self.assertFalse(ipad_mobile_chrome_ua.is_bot) self.assertFalse(playbook_ua.is_bot) self.assertFalse(kindle_fire_ua.is_bot) self.assertFalse(nexus_7_ua.is_bot) @@ -213,6 +220,7 @@ def test_is_email_client(self): def test_strings(self): self.assertEqual(str(iphone_ua), "iPhone / iOS 5.1 / Mobile Safari 5.1") self.assertEqual(str(ipad_ua), "iPad / iOS 3.2 / Mobile Safari 4.0.4") + self.assertEqual(str(ipad_mobile_chrome_ua), "iPad / iOS 13.3 / Chrome Mobile iOS 86.0.4240") self.assertEqual(str(galaxy_tab), "Samsung SCH-I800 / Android 2.2 / Android 2.2") self.assertEqual(str(galaxy_s3_ua), "Samsung GT-I9300 / Android 4.0.4 / Android 4.0.4") self.assertEqual(str(kindle_fire_ua), "Kindle / Android / Amazon Silk 1.1.0-80")