├── .github └── workflows │ └── ci.yaml ├── .gitignore ├── .gitmodules ├── LICENSE-Apache2 ├── Makefile ├── README.md ├── bower.json ├── lib ├── appliance.js ├── browser.js ├── crawler.js ├── dataset.js ├── misc.js ├── mobilephone.js ├── os.js ├── util.js └── woothee.js ├── package.json ├── release └── woothee.js ├── scripts ├── dataset_footer.js ├── dataset_header.js ├── dataset_yaml2js.js ├── jsbuilder.js ├── release_footer.js └── release_header.js └── test ├── 00_require.js ├── 01_dataset.js ├── 02_run_testsets.js └── mocha.opts /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: 4 | pull_request: 5 | branches: '*' 6 | push: 7 | branches: 8 | - master 9 | - main 10 | - 'release-*' 11 | 12 | jobs: 13 | test: 14 | strategy: 15 | matrix: 16 | node: ['12.x', '14.x'] 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: actions/checkout@v2 20 | - uses: actions/setup-node@v2.4.0 21 | with: 22 | node-version: ${{ matrix.node }} 23 | - run: git submodule update --init 24 | - run: npm install 25 | - run: npm test 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | package-lock.json 3 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "woothee"] 2 | path = woothee 3 | url = https://github.com/woothee/woothee.git 4 | -------------------------------------------------------------------------------- /LICENSE-Apache2: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | TESTS=test/*.js 2 | 3 | all: test 4 | 5 | datasets: 6 | git submodule update --init 7 | 8 | lib/dataset.js: datasets 9 | node scripts/dataset_yaml2js.js 10 | sync; sync; sync 11 | 12 | release/woothee.js: lib/*.js 13 | mkdir release || ls -d release 14 | node scripts/jsbuilder.js 15 | 16 | test: lib/dataset.js build 17 | @NODE_ENV=test ./node_modules/.bin/mocha \ 18 | --require should \ 19 | --slow 20 \ 20 | $(TESTS) 21 | 22 | build: release/woothee.js 23 | 24 | .PHONY: datasets test 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Woothee javascript 2 | 3 | The Javascript implementation of Project Woothee, which is multi-language user-agent strings parsers. 4 | 5 | https://github.com/woothee/woothee 6 | 7 | ## Installation 8 | 9 | $ npm install woothee 10 | 11 | Or, download `release/woothee.js` and put it on your own directory for js files. 12 | 13 | ## Usage 14 | 15 | ### Parsing user-agent 16 | 17 | In html, load `woothee.js` and call `woothee.parse()` directly. 18 | 19 | ```html 20 | 21 | 25 | ``` 26 | 27 | In node.js code, require 'woothee' and call its `parse()`. 28 | ```javascript 29 | var woothee = require('woothee'); 30 | woothee.parse('Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0)') 31 | // => {name: 'Internet Explorer', category: 'pc', os: 'Windows 7', version: '8.0', vendor: 'Microsoft', os_version: 'NT 6.1'} 32 | ``` 33 | 34 | Parse user-agent string and returns a object with keys `name`, `category`, `os`, `version` and `vendor`. 35 | 36 | For unknown user-agent (or partially failed to parse), result objects may have value 'UNKNOWN'. 37 | 38 | * `category` 39 | * labels of user terminal type, one of 'pc', 'smartphone', 'mobilephone', 'appliance', 'crawler' or 'misc' (or 'UNKNOWN') 40 | * `name` 41 | * the name of browser, like 'Internet Explorer', 'Firefox', 'GoogleBot' 42 | * `version` 43 | * version string, like '8.0' for IE, '9.0.1' for Firefix, '0.2.149.27' for Chrome, and so on 44 | * `os` 45 | * ex: 'Windows 7', 'Mac OSX', 'iPhone', 'iPad', 'Android' 46 | * This field used to indicate cellar phone carrier for category 'mobilephone' 47 | * `vendor` 48 | * optional field, shows browser vendor 49 | * `os_version` 50 | * optional field, shows version of operating systems 51 | 52 | ### Finding crawlers (almost all, not all) in fast 53 | 54 | ```javascript 55 | woothee.isCrawler('Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0)') 56 | // => false 57 | ``` 58 | 59 | Try to see useragent's category is 'crawler' or not, by casual(fast) method. Minor case of crawlers is not tested in this method. To check crawler strictly, use `woothee.parse(str).category === 'crawler'`. 60 | 61 | * * * * * 62 | 63 | ## Authors 64 | 65 | * TAGOMORI Satoshi 66 | 67 | ## License 68 | 69 | Copyright 2012- TAGOMORI Satoshi (tagomoris) 70 | 71 | Licensed under the Apache License, Version 2.0 (the "License"); 72 | you may not use this file except in compliance with the License. 73 | You may obtain a copy of the License at 74 | 75 | http://www.apache.org/licenses/LICENSE-2.0 76 | 77 | Unless required by applicable law or agreed to in writing, software 78 | distributed under the License is distributed on an "AS IS" BASIS, 79 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 80 | See the License for the specific language governing permissions and 81 | limitations under the License. 82 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "whoothee", 3 | "version": "1.4.0", 4 | "homepage": "https://github.com/woothee/woothee-js", 5 | "authors": [ 6 | "null" 7 | ], 8 | "description": "Woothee javascript(and node.js) implementation", 9 | "main": "release/whoothee.js", 10 | "moduleType": [ 11 | "amd", 12 | "globals", 13 | "node" 14 | ], 15 | "keywords": [ 16 | "whoothee" 17 | ], 18 | "license": "MIT", 19 | "ignore": [ 20 | "**/.*", 21 | "node_modules", 22 | "bower_components", 23 | "test", 24 | "tests" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /lib/appliance.js: -------------------------------------------------------------------------------- 1 | /* HEADER BEGIN */ 2 | var util = require('./util'), 3 | updateMap = util.updateMap, 4 | updateVersion = util.updateVersion, 5 | updateCategory = util.updateCategory, 6 | updateOs = util.updateOs, 7 | updateOsVersion = util.updateOsVersion, 8 | dataset = require('./dataset'); 9 | /* HEADER END */ 10 | 11 | var challengePlaystation = exports.challengePlaystation = function(ua, result) { 12 | var data = null; 13 | var os_version = null; 14 | var match; 15 | if (ua.indexOf('PSP (PlayStation Portable);') >= 0) { 16 | data = dataset.get('PSP'); 17 | if ((match = /PSP \(PlayStation Portable\); ([.0-9]+)\)/.exec(ua))) 18 | os_version = match[1]; 19 | } else if (ua.indexOf('PlayStation Vita') >= 0) { 20 | data = dataset.get('PSVita'); 21 | if ((match = /PlayStation Vita ([.0-9]+)\)/.exec(ua))) 22 | os_version = match[1]; 23 | } else if (ua.indexOf('PLAYSTATION 3 ') >= 0 || ua.indexOf('PLAYSTATION 3;') >= 0) { 24 | data = dataset.get('PS3'); 25 | if ((match = /PLAYSTATION 3;? ([.0-9]+)\)/.exec(ua))) 26 | os_version = match[1]; 27 | } else if (ua.indexOf('PlayStation 4 ') >= 0) { 28 | data = dataset.get('PS4'); 29 | if ((match = /PlayStation 4 ([.0-9]+)\)/.exec(ua))) 30 | os_version = match[1]; 31 | } 32 | 33 | if (! data) 34 | return false; 35 | 36 | updateMap(result, data); 37 | if (os_version) 38 | updateOsVersion(result, os_version); 39 | return true; 40 | }; 41 | 42 | var challengeNintendo = exports.challengeNintendo = function(ua, result) { 43 | var data = null; 44 | if (ua.indexOf('Nintendo 3DS;') >= 0) data = dataset.get('Nintendo3DS'); 45 | else if (ua.indexOf('Nintendo DSi;') >= 0) data = dataset.get('NintendoDSi'); 46 | else if (ua.indexOf('Nintendo Wii;') >= 0) data = dataset.get('NintendoWii'); 47 | else if (ua.indexOf('(Nintendo WiiU)') >= 0) data = dataset.get('NintendoWiiU'); 48 | 49 | if (! data) 50 | return false; 51 | 52 | updateMap(result, data); 53 | return true; 54 | }; 55 | 56 | var challengeDigitalTV = exports.challengeDigitalTV = function(ua, result) { 57 | var data = null; 58 | if (ua.indexOf('InettvBrowser/') >= 0) data = dataset.get('DigitalTV'); 59 | 60 | if (! data) 61 | return false; 62 | 63 | updateMap(result, data); 64 | return true; 65 | }; -------------------------------------------------------------------------------- /lib/browser.js: -------------------------------------------------------------------------------- 1 | /* HEADER BEGIN */ 2 | var util = require('./util'), 3 | updateMap = util.updateMap, 4 | updateVersion = util.updateVersion, 5 | updateCategory = util.updateCategory, 6 | updateOs = util.updateOs, 7 | dataset = require('./dataset'); 8 | /* HEADER END */ 9 | 10 | var msiePattern = /MSIE ([.0-9]+);/; 11 | var tridentPattern = /Trident\/[.0-9]+;/; 12 | var tridentVersionPattern = / rv:([.0-9]+)/; 13 | var iemobilePattern = /IEMobile\/([.0-9]+);/; 14 | var challengeMSIE = exports.challengeMSIE = function(ua, result) { 15 | if (ua.indexOf('compatible; MSIE') < 0 && ua.indexOf('Trident/') < 0 && ua.indexOf('IEMobile/')) 16 | return false; 17 | 18 | var version; 19 | var match = msiePattern.exec(ua); 20 | if (!match) 21 | if (tridentPattern.exec(ua)) 22 | match = tridentVersionPattern.exec(ua); 23 | if (!match) 24 | match = iemobilePattern.exec(ua); 25 | 26 | if (match) 27 | version = match[1]; 28 | else 29 | version = dataset.VALUE_UNKNOWN; 30 | updateMap(result, dataset.get('MSIE')); 31 | updateVersion(result, version); 32 | return true; 33 | }; 34 | 35 | var yandexBrowserPattern = /YaBrowser\/([.0-9]+)/; 36 | var challengeYandexBrowser = exports.challengeYandexBrowser = function(ua, result) { 37 | if (ua.indexOf('YaBrowser/') < 0) 38 | return false; 39 | 40 | var version; 41 | var match = yandexBrowserPattern.exec(ua); 42 | if (match) { 43 | version = match[1]; 44 | } else { 45 | version = dataset.VALUE_UNKNOWN; 46 | } 47 | updateMap(result, dataset.get('YaBrowser')); 48 | updateVersion(result, version); 49 | return true; 50 | }; 51 | 52 | var edgePattern = /(?:Edge|Edg|EdgiOS|EdgA)\/([.0-9]+)/; 53 | var firefoxiOSPattern = /FxiOS\/([.0-9]+)/; 54 | var chromePattern = /(?:Chrome|CrMo|CriOS)\/([.0-9]+)/; 55 | var operaBlinkPattern = /OPR\/([.0-9]+)/; 56 | var gsaPattern = /GSA\/([.0-9]+)/; 57 | var safariPattern = /Version\/([.0-9]+)/; 58 | var challengeSafariChrome = exports.challengeSafariChrome = function(ua, result) { 59 | if (ua.indexOf('Safari/') < 0) 60 | return false; 61 | if (ua.indexOf('Chrome') >= 0 && ua.indexOf('wv') >= 0) 62 | return false; 63 | 64 | var version = dataset.VALUE_UNKNOWN; 65 | 66 | var match; 67 | if ((match = edgePattern.exec(ua))) { 68 | version = match[1]; 69 | updateMap(result, dataset.get('Edge')); 70 | updateVersion(result, version); 71 | return true; 72 | } 73 | 74 | if ((match = firefoxiOSPattern.exec(ua))) { 75 | version = match[1]; 76 | updateMap(result, dataset.get('Firefox')); 77 | updateVersion(result, version); 78 | return true; 79 | } 80 | 81 | if ((match = chromePattern.exec(ua))) { 82 | var matchOpera; 83 | if ((matchOpera = operaBlinkPattern.exec(ua))) { 84 | // Opera w/ blink 85 | version = matchOpera[1]; 86 | updateMap(result, dataset.get('Opera')); 87 | updateVersion(result, version); 88 | return true; 89 | } 90 | 91 | // Chrome 92 | version = match[1]; 93 | updateMap(result, dataset.get('Chrome')); 94 | updateVersion(result, version); 95 | return true; 96 | } 97 | 98 | if ((match = gsaPattern.exec(ua))) { 99 | version = match[1]; 100 | updateMap(result, dataset.get('GSA')); 101 | updateVersion(result, version); 102 | return true; 103 | } 104 | 105 | if ((match = safariPattern.exec(ua))) { 106 | version = match[1]; 107 | } 108 | 109 | updateMap(result, dataset.get('Safari')); 110 | updateVersion(result, version); 111 | return true; 112 | }; 113 | 114 | var firefoxPattern = /Firefox\/([.0-9]+)/; 115 | var challengeFirefox = exports.challengeFirefox = function(ua, result) { 116 | if (ua.indexOf('Firefox/') < 0) 117 | return false; 118 | 119 | var version = dataset.VALUE_UNKNOWN; 120 | var match = firefoxPattern.exec(ua); 121 | if (match) 122 | version = match[1]; 123 | updateMap(result, dataset.get('Firefox')); 124 | updateVersion(result, version); 125 | return true; 126 | }; 127 | 128 | var operaPattern1 = /Version\/([.0-9]+)/; 129 | var operaPattern2 = /Opera[\/ ]([.0-9]+)/; 130 | var challengeOpera = exports.challengeOpera = function(ua, result) { 131 | if (ua.indexOf('Opera') < 0) 132 | return false; 133 | 134 | var version = dataset.VALUE_UNKNOWN; 135 | var match = operaPattern1.exec(ua); 136 | if (match) { 137 | version = match[1]; 138 | } else { 139 | match = operaPattern2.exec(ua); 140 | if (match) 141 | version = match[1]; 142 | } 143 | updateMap(result, dataset.get('Opera')); 144 | updateVersion(result, version); 145 | return true; 146 | }; 147 | 148 | var webviewPattern = /iP(hone;|ad;|od) .*like Mac OS X/; 149 | var webviewVersionPattern = /Version\/([.0-9]+)/; 150 | var challengeWebview = exports.challengeWebview = function(ua, result) { 151 | var version = dataset.VALUE_UNKNOWN; 152 | 153 | // Android(Lollipop and Above) 154 | if (ua.indexOf('Chrome') >= 0 && ua.indexOf('wv') >= 0) { 155 | var vmatch = webviewVersionPattern.exec(ua); 156 | if (vmatch) 157 | version = vmatch[1]; 158 | 159 | updateMap(result, dataset.get('Webview')); 160 | updateVersion(result, version); 161 | return true; 162 | } 163 | 164 | // iOS 165 | var match = webviewPattern.exec(ua); 166 | if (match) { 167 | if (ua.indexOf('Safari/') > -1) 168 | return false; 169 | 170 | var vmatch = webviewVersionPattern.exec(ua); 171 | if (vmatch) 172 | version = vmatch[1]; 173 | 174 | updateMap(result, dataset.get('Webview')); 175 | updateVersion(result, version); 176 | return true; 177 | } 178 | 179 | return false; 180 | }; 181 | 182 | var sleipnirPattern = /Sleipnir\/([.0-9]+)/; 183 | var challengeSleipnir = exports.challengeSleipnir = function(ua, result) { 184 | if (ua.indexOf('Sleipnir/') < 0) 185 | return false; 186 | 187 | var version = dataset.VALUE_UNKNOWN; 188 | var match = sleipnirPattern.exec(ua); 189 | if (match) 190 | version = match[1]; 191 | updateMap(result, dataset.get('Sleipnir')); 192 | updateVersion(result, version); 193 | 194 | /* 195 | Sleipnir's user-agent doesn't contain Windows version, so put 'Windows UNKNOWN Ver'. 196 | Sleipnir is IE component browser, so for Windows only. 197 | */ 198 | var win = dataset.get('Win'); 199 | updateCategory(result, win[dataset.KEY_CATEGORY]); 200 | updateOs(result, win[dataset.KEY_NAME]); 201 | 202 | return true; 203 | }; 204 | 205 | var vivaldiPattern = /Vivaldi\/([.0-9]+)/; 206 | var challengeVivaldi = exports.challengeVivaldi = function(ua, result) { 207 | if (ua.indexOf('Vivaldi/') < 0) 208 | return false; 209 | 210 | var version = dataset.VALUE_UNKNOWN; 211 | var match = vivaldiPattern.exec(ua); 212 | if (match) 213 | version = match[1]; 214 | updateMap(result, dataset.get('Vivaldi')); 215 | updateVersion(result, version); 216 | 217 | return true; 218 | }; 219 | -------------------------------------------------------------------------------- /lib/crawler.js: -------------------------------------------------------------------------------- 1 | /* HEADER BEGIN */ 2 | var util = require('./util'), 3 | updateMap = util.updateMap, 4 | updateVersion = util.updateVersion, 5 | updateCategory = util.updateCategory, 6 | updateOs = util.updateOs, 7 | dataset = require('./dataset'); 8 | /* HEADER END */ 9 | 10 | var challengeGoogle = exports.challengeGoogle = function(ua, result) { 11 | if (ua.indexOf('Google') < 0) 12 | return false; 13 | 14 | if (ua.indexOf('compatible; Googlebot') >= 0) { 15 | if (ua.indexOf('compatible; Googlebot-Mobile') >= 0) { 16 | updateMap(result, dataset.get('GoogleBotMobile')); 17 | return true; 18 | } else { 19 | updateMap(result, dataset.get('GoogleBot')); 20 | return true; 21 | } 22 | } 23 | if (ua.indexOf('Googlebot-Image/') >= 0) { 24 | updateMap(result, dataset.get('GoogleBot')); 25 | return true; 26 | } 27 | if (ua.indexOf('Mediapartners-Google') >= 0) { 28 | if (ua.indexOf('compatible; Mediapartners-Google') >= 0 || ua === 'Mediapartners-Google') { 29 | updateMap(result, dataset.get('GoogleMediaPartners')); 30 | return true; 31 | } 32 | } 33 | if (ua.indexOf('Feedfetcher-Google;') >= 0) { 34 | updateMap(result, dataset.get('GoogleFeedFetcher')); 35 | return true; 36 | } 37 | if (ua.indexOf('AppEngine-Google') >= 0) { 38 | updateMap(result, dataset.get('GoogleAppEngine')); 39 | return true; 40 | } 41 | if (ua.indexOf('Google Web Preview') >= 0) { 42 | updateMap(result, dataset.get('GoogleWebPreview')); 43 | return true; 44 | } 45 | 46 | return false; 47 | }; 48 | 49 | var challengeCrawlers = exports.challengeCrawlers = function(ua, result) { 50 | if (ua.indexOf('Yahoo') >= 0 51 | || ua.indexOf('help.yahoo.co.jp/help/jp/') >= 0 52 | || ua.indexOf('listing.yahoo.co.jp/support/faq/') >= 0) { 53 | if (ua.indexOf('compatible; Yahoo! Slurp') >= 0) { 54 | updateMap(result, dataset.get('YahooSlurp')); 55 | return true; 56 | } 57 | if (ua.indexOf('YahooFeedSeekerJp') >= 0 || ua.indexOf('YahooFeedSeekerBetaJp') >= 0) { 58 | updateMap(result, dataset.get('YahooJP')); 59 | return true; 60 | } 61 | if (ua.indexOf('crawler (http://listing.yahoo.co.jp/support/faq/') >= 0 62 | || ua.indexOf('crawler (http://help.yahoo.co.jp/help/jp/') >= 0) { 63 | updateMap(result, dataset.get('YahooJP')); 64 | return true; 65 | } 66 | if (ua.indexOf('Y!J-BRZ/YATSHA crawler') >= 0 67 | || ua.indexOf('Y!J-BRY/YATSH crawler') >= 0) { 68 | updateMap(result, dataset.get('YahooJP')); 69 | return true; 70 | } 71 | if (ua.indexOf('Yahoo Pipes') >= 0) { 72 | updateMap(result, dataset.get('YahooPipes')); 73 | return true; 74 | } 75 | } 76 | if (ua.indexOf('msnbot') >= 0) { 77 | updateMap(result, dataset.get('msnbot')); 78 | return true; 79 | } 80 | if (ua.indexOf('bingbot') >= 0) { 81 | if (ua.indexOf('compatible; bingbot') >= 0) { 82 | updateMap(result, dataset.get('bingbot')); 83 | return true; 84 | } 85 | } 86 | if (ua.indexOf('BingPreview') >= 0) { 87 | updateMap(result, dataset.get('BingPreview')); 88 | return true; 89 | } 90 | if (ua.indexOf('Baidu') >= 0) { 91 | if (ua.indexOf('compatible; Baiduspider') >= 0 || 92 | ua.indexOf('Baiduspider+') >= 0 || 93 | ua.indexOf('Baiduspider-image+') >= 0) { 94 | updateMap(result, dataset.get('Baiduspider')); 95 | return true; 96 | } 97 | } 98 | if (ua.indexOf('Yeti') >= 0) { 99 | if (ua.indexOf('http://help.naver.com/robots') >= 0 || 100 | ua.indexOf('http://help.naver.com/support/robots.html') >= 0 || 101 | ua.indexOf('http://naver.me/bot') >= 0) { 102 | 103 | updateMap(result, dataset.get('Yeti')); 104 | return true; 105 | } 106 | } 107 | if (ua.indexOf('FeedBurner/') >= 0) { 108 | updateMap(result, dataset.get('FeedBurner')); 109 | return true; 110 | } 111 | if (ua.indexOf('facebookexternalhit') >= 0) { 112 | updateMap(result, dataset.get('facebook')); 113 | return true; 114 | } 115 | if (ua.indexOf('Twitterbot/') >= 0) { 116 | updateMap(result, dataset.get('twitter')); 117 | return true; 118 | } 119 | if (ua.indexOf('ichiro') >= 0) { 120 | if (ua.indexOf('http://help.goo.ne.jp/door/crawler.html') >= 0 || 121 | ua.indexOf('compatible; ichiro/mobile goo;') >= 0) { 122 | updateMap(result, dataset.get('goo')); 123 | return true; 124 | } 125 | } 126 | if (ua.indexOf('gooblogsearch/') >= 0) { 127 | updateMap(result, dataset.get('goo')); 128 | return true; 129 | } 130 | if (ua.indexOf('Apple-PubSub') >= 0) { 131 | updateMap(result, dataset.get('ApplePubSub')); 132 | return true; 133 | } 134 | if (ua.indexOf('(www.radian6.com/crawler)') >= 0) { 135 | updateMap(result, dataset.get('radian6')); 136 | return true; 137 | } 138 | if (ua.indexOf('Genieo/') >= 0) { 139 | updateMap(result, dataset.get('Genieo')); 140 | return true; 141 | } 142 | if (ua.indexOf('labs.topsy.com/butterfly/') >= 0) { 143 | updateMap(result, dataset.get('topsyButterfly')); 144 | return true; 145 | } 146 | if (ua.indexOf('rogerbot/1.0 (http://www.seomoz.org/dp/rogerbot') >= 0) { 147 | updateMap(result, dataset.get('rogerbot')); 148 | return true; 149 | } 150 | if (ua.indexOf('compatible; AhrefsBot/') >= 0) { 151 | updateMap(result, dataset.get('AhrefsBot')); 152 | return true; 153 | } 154 | if (ua.indexOf('livedoor FeedFetcher') >= 0 || ua.indexOf('Fastladder FeedFetcher') >= 0) { 155 | updateMap(result, dataset.get('livedoorFeedFetcher')); 156 | return true; 157 | } 158 | if (ua.indexOf('Hatena ') >= 0) { 159 | if (ua.indexOf('Hatena Antenna') >= 0 || 160 | ua.indexOf('Hatena Pagetitle Agent') >= 0 || 161 | ua.indexOf('Hatena Diary RSS') >= 0) { 162 | updateMap(result, dataset.get('Hatena')); 163 | return true; 164 | } 165 | } 166 | if (ua.indexOf('mixi-check') >= 0 || 167 | ua.indexOf('mixi-crawler') >= 0 || 168 | ua.indexOf('mixi-news-crawler') >= 0) { 169 | updateMap(result, dataset.get('mixi')); 170 | return true; 171 | } 172 | if (ua.indexOf('Indy Library') >= 0) { 173 | if (ua.indexOf('compatible; Indy Library') >= 0) { 174 | updateMap(result, dataset.get('IndyLibrary')); 175 | return true; 176 | } 177 | } 178 | if (ua.indexOf('trendictionbot') >= 0) { 179 | updateMap(result, dataset.get('trendictionbot')); 180 | return true; 181 | } 182 | 183 | return false; 184 | }; 185 | 186 | var challengeMaybeCrawler = exports.challengeMaybeCrawler = function(ua, result) { 187 | if (/(bot|crawler|spider)(?:[-_ .\/;@()]|$)/i.exec(ua)) { 188 | updateMap(result, dataset.get('VariousCrawler')); 189 | return true; 190 | } 191 | if (/(?:Rome Client |UnwindFetchor\/|ia_archiver |Summify |PostRank\/)/.exec(ua) || 192 | ua.indexOf('ASP-Ranker Feed Crawler') >= 0) { 193 | updateMap(result, dataset.get('VariousCrawler')); 194 | return true; 195 | } 196 | if (/(feed|web) ?parser/i.exec(ua)) { 197 | updateMap(result, dataset.get('VariousCrawler')); 198 | return true; 199 | } 200 | if (/watch ?dog/i.exec(ua)) { 201 | updateMap(result, dataset.get('VariousCrawler')); 202 | return true; 203 | } 204 | return false; 205 | }; 206 | -------------------------------------------------------------------------------- /lib/dataset.js: -------------------------------------------------------------------------------- 1 | var KEY_LABEL = exports.KEY_LABEL = 'label', 2 | KEY_NAME = exports.KEY_NAME = 'name', 3 | KEY_TYPE = exports.KEY_TYPE = 'type', 4 | KEY_CATEGORY = exports.KEY_CATEGORY = 'category', 5 | KEY_OS = exports.KEY_OS = 'os', 6 | KEY_OS_VERSION = exports.KEY_OS_VERSION = 'os_version', 7 | KEY_VENDOR = exports.KEY_VENDOR = 'vendor', 8 | KEY_VERSION = exports.KEY_VERSION = 'version'; 9 | 10 | var TYPE_BROWSER = 'browser', 11 | TYPE_OS = 'os', 12 | TYPE_FULL = 'full'; 13 | 14 | var CATEGORY_PC = exports.CATEGORY_PC = 'pc', 15 | CATEGORY_SMARTPHONE = exports.CATEGORY_SMARTPHONE = 'smartphone', 16 | CATEGORY_MOBILEPHONE = exports.CATEGORY_MOBILEPHONE = 'mobilephone', 17 | CATEGORY_CRAWLER = exports.CATEGORY_CRAWLER = 'crawler', 18 | CATEGORY_APPLIANCE = exports.CATEGORY_APPLIANCE = 'appliance', 19 | CATEGORY_MISC = exports.CATEGORY_MISC = 'misc'; 20 | 21 | var ATTRIBUTE_NAME = exports.ATTRIBUTE_NAME = 'name', 22 | ATTRIBUTE_CATEGORY = exports.ATTRIBUTE_CATEGORY = 'category', 23 | ATTRIBUTE_OS = exports.ATTRIBUTE_OS = 'os', 24 | ATTRIBUTE_OS_VERSION = exports.ATTRIBUTE_OS_VERSION = 'os_version', 25 | ATTRIBUTE_VENDOR = exports.ATTRIBUTE_VENDOR = 'vendor', 26 | ATTRIBUTE_VERSION = exports.ATTRIBUTE_VERSION = 'version'; 27 | 28 | var VALUE_UNKNOWN = exports.VALUE_UNKNOWN = 'UNKNOWN'; 29 | 30 | var CATEGORY_LIST = exports.CATEGORY_LIST = [ 31 | CATEGORY_PC, CATEGORY_SMARTPHONE, CATEGORY_MOBILEPHONE, 32 | CATEGORY_CRAWLER, CATEGORY_APPLIANCE, CATEGORY_MISC, VALUE_UNKNOWN 33 | ]; 34 | var ATTRIBUTE_LIST = exports.ATTRIBUTE_LIST = [ATTRIBUTE_NAME, ATTRIBUTE_CATEGORY, ATTRIBUTE_OS, ATTRIBUTE_VENDOR, ATTRIBUTE_VERSION, ATTRIBUTE_OS_VERSION]; 35 | // GENERATED from dataset.yaml at Tue Sep 7 11:33:54 JST 2021 by tagomoris 36 | var DATASET = {MSIE: {label: "MSIE", name: "Internet Explorer", type: "browser", vendor: "Microsoft"}, Edge: {label: "Edge", name: "Edge", type: "browser", vendor: "Microsoft"}, Chrome: {label: "Chrome", name: "Chrome", type: "browser", vendor: "Google"}, Safari: {label: "Safari", name: "Safari", type: "browser", vendor: "Apple"}, Firefox: {label: "Firefox", name: "Firefox", type: "browser", vendor: "Mozilla"}, Opera: {label: "Opera", name: "Opera", type: "browser", vendor: "Opera"}, Vivaldi: {label: "Vivaldi", name: "Vivaldi", type: "browser", vendor: "Vivaldi Technologies"}, Sleipnir: {label: "Sleipnir", name: "Sleipnir", type: "browser", vendor: "Fenrir Inc."}, GSA: {label: "GSA", name: "Google Search App", type: "browser", vendor: "Google"}, Webview: {label: "Webview", name: "Webview", type: "browser", vendor: "OS vendor"}, YaBrowser: {label: "YaBrowser", name: "Yandex Browser", type: "browser", vendor: "Yandex"}, Win: {label: "Win", name: "Windows UNKNOWN Ver", type: "os", category: "pc"}, Win10: {label: "Win10", name: "Windows 10", type: "os", category: "pc"}, "Win8.1": {label: "Win8.1", name: "Windows 8.1", type: "os", category: "pc"}, Win8: {label: "Win8", name: "Windows 8", type: "os", category: "pc"}, Win7: {label: "Win7", name: "Windows 7", type: "os", category: "pc"}, WinVista: {label: "WinVista", name: "Windows Vista", type: "os", category: "pc"}, WinXP: {label: "WinXP", name: "Windows XP", type: "os", category: "pc"}, Win2000: {label: "Win2000", name: "Windows 2000", type: "os", category: "pc"}, WinNT4: {label: "WinNT4", name: "Windows NT 4.0", type: "os", category: "pc"}, WinMe: {label: "WinMe", name: "Windows Me", type: "os", category: "pc"}, Win98: {label: "Win98", name: "Windows 98", type: "os", category: "pc"}, Win95: {label: "Win95", name: "Windows 95", type: "os", category: "pc"}, WinPhone: {label: "WinPhone", name: "Windows Phone OS", type: "os", category: "smartphone"}, WinCE: {label: "WinCE", name: "Windows CE", type: "os", category: "smartphone"}, OSX: {label: "OSX", name: "Mac OSX", type: "os", category: "pc"}, MacOS: {label: "MacOS", name: "Mac OS Classic", type: "os", category: "pc"}, Linux: {label: "Linux", name: "Linux", type: "os", category: "pc"}, BSD: {label: "BSD", name: "BSD", type: "os", category: "pc"}, ChromeOS: {label: "ChromeOS", name: "ChromeOS", type: "os", category: "pc"}, Android: {label: "Android", name: "Android", type: "os", category: "smartphone"}, iPhone: {label: "iPhone", name: "iPhone", type: "os", category: "smartphone"}, iPad: {label: "iPad", name: "iPad", type: "os", category: "smartphone"}, iPod: {label: "iPod", name: "iPod", type: "os", category: "smartphone"}, iOS: {label: "iOS", name: "iOS", type: "os", category: "smartphone"}, FirefoxOS: {label: "FirefoxOS", name: "Firefox OS", type: "os", category: "smartphone"}, BlackBerry: {label: "BlackBerry", name: "BlackBerry", type: "os", category: "smartphone"}, BlackBerry10: {label: "BlackBerry10", name: "BlackBerry 10", type: "os", category: "smartphone"}, docomo: {label: "docomo", name: "docomo", type: "full", vendor: "docomo", category: "mobilephone", os: "docomo"}, au: {label: "au", name: "au by KDDI", type: "full", vendor: "au", category: "mobilephone", os: "au"}, SoftBank: {label: "SoftBank", name: "SoftBank Mobile", type: "full", vendor: "SoftBank", category: "mobilephone", os: "SoftBank"}, willcom: {label: "willcom", name: "WILLCOM", type: "full", vendor: "WILLCOM", category: "mobilephone", os: "WILLCOM"}, jig: {label: "jig", name: "jig browser", type: "full", vendor: "", category: "mobilephone", os: "jig"}, emobile: {label: "emobile", name: "emobile", type: "full", vendor: "", category: "mobilephone", os: "emobile"}, SymbianOS: {label: "SymbianOS", name: "SymbianOS", type: "full", vendor: "", category: "mobilephone", os: "SymbianOS"}, MobileTranscoder: {label: "MobileTranscoder", name: "Mobile Transcoder", type: "full", vendor: "", category: "mobilephone", os: "Mobile Transcoder"}, Nintendo3DS: {label: "Nintendo3DS", name: "Nintendo 3DS", type: "full", vendor: "Nintendo", category: "appliance", os: "Nintendo 3DS"}, NintendoDSi: {label: "NintendoDSi", name: "Nintendo DSi", type: "full", vendor: "Nintendo", category: "appliance", os: "Nintendo DSi"}, NintendoWii: {label: "NintendoWii", name: "Nintendo Wii", type: "full", vendor: "Nintendo", category: "appliance", os: "Nintendo Wii"}, NintendoWiiU: {label: "NintendoWiiU", name: "Nintendo Wii U", type: "full", vendor: "Nintendo", category: "appliance", os: "Nintendo Wii U"}, PSP: {label: "PSP", name: "PlayStation Portable", type: "full", vendor: "Sony", category: "appliance", os: "PlayStation Portable"}, PSVita: {label: "PSVita", name: "PlayStation Vita", type: "full", vendor: "Sony", category: "appliance", os: "PlayStation Vita"}, PS3: {label: "PS3", name: "PlayStation 3", type: "full", vendor: "Sony", category: "appliance", os: "PlayStation 3"}, PS4: {label: "PS4", name: "PlayStation 4", type: "full", vendor: "Sony", category: "appliance", os: "PlayStation 4"}, Xbox360: {label: "Xbox360", name: "Xbox 360", type: "full", vendor: "Microsoft", category: "appliance", os: "Xbox 360"}, XboxOne: {label: "XboxOne", name: "Xbox One", type: "full", vendor: "Microsoft", category: "appliance", os: "Xbox One"}, DigitalTV: {label: "DigitalTV", name: "InternetTVBrowser", type: "full", vendor: "", category: "appliance", os: "DigitalTV"}, SafariRSSReader: {label: "SafariRSSReader", name: "Safari RSSReader", type: "full", vendor: "Apple", category: "misc"}, GoogleDesktop: {label: "GoogleDesktop", name: "Google Desktop", type: "full", vendor: "Google", category: "misc"}, WindowsRSSReader: {label: "WindowsRSSReader", name: "Windows RSSReader", type: "full", vendor: "Microsoft", category: "misc"}, VariousRSSReader: {label: "VariousRSSReader", name: "RSSReader", type: "full", vendor: "", category: "misc"}, HTTPLibrary: {label: "HTTPLibrary", name: "HTTP Library", type: "full", vendor: "", category: "misc"}, GoogleBot: {label: "GoogleBot", name: "Googlebot", type: "full", vendor: "", category: "crawler"}, GoogleBotMobile: {label: "GoogleBotMobile", name: "Googlebot Mobile", type: "full", vendor: "", category: "crawler"}, GoogleMediaPartners: {label: "GoogleMediaPartners", name: "Google Mediapartners", type: "full", vendor: "", category: "crawler"}, GoogleFeedFetcher: {label: "GoogleFeedFetcher", name: "Google Feedfetcher", type: "full", vendor: "", category: "crawler"}, GoogleAppEngine: {label: "GoogleAppEngine", name: "Google AppEngine", type: "full", vendor: "", category: "crawler"}, GoogleWebPreview: {label: "GoogleWebPreview", name: "Google Web Preview", type: "full", vendor: "", category: "crawler"}, YahooSlurp: {label: "YahooSlurp", name: "Yahoo! Slurp", type: "full", vendor: "", category: "crawler"}, YahooJP: {label: "YahooJP", name: "Yahoo! Japan", type: "full", vendor: "", category: "crawler"}, YahooPipes: {label: "YahooPipes", name: "Yahoo! Pipes", type: "full", vendor: "", category: "crawler"}, Baiduspider: {label: "Baiduspider", name: "Baiduspider", type: "full", vendor: "", category: "crawler"}, msnbot: {label: "msnbot", name: "msnbot", type: "full", vendor: "", category: "crawler"}, bingbot: {label: "bingbot", name: "bingbot", type: "full", vendor: "", category: "crawler"}, BingPreview: {label: "BingPreview", name: "BingPreview", type: "full", vendor: "", category: "crawler"}, Yeti: {label: "Yeti", name: "Naver Yeti", type: "full", vendor: "", category: "crawler"}, FeedBurner: {label: "FeedBurner", name: "Google FeedBurner", type: "full", vendor: "", category: "crawler"}, facebook: {label: "facebook", name: "facebook", type: "full", vendor: "", category: "crawler"}, twitter: {label: "twitter", name: "twitter", type: "full", vendor: "", category: "crawler"}, trendictionbot: {label: "trendictionbot", name: "trendiction", type: "full", vendor: "", category: "crawler"}, mixi: {label: "mixi", name: "mixi", type: "full", vendor: "", category: "crawler"}, IndyLibrary: {label: "IndyLibrary", name: "Indy Library", type: "full", vendor: "", category: "crawler"}, ApplePubSub: {label: "ApplePubSub", name: "Apple iCloud", type: "full", vendor: "", category: "crawler"}, Genieo: {label: "Genieo", name: "Genieo Web Filter", type: "full", vendor: "", category: "crawler"}, topsyButterfly: {label: "topsyButterfly", name: "topsy Butterfly", type: "full", vendor: "", category: "crawler"}, rogerbot: {label: "rogerbot", name: "SeoMoz rogerbot", type: "full", vendor: "", category: "crawler"}, AhrefsBot: {label: "AhrefsBot", name: "ahref AhrefsBot", type: "full", vendor: "", category: "crawler"}, radian6: {label: "radian6", name: "salesforce radian6", type: "full", vendor: "", category: "crawler"}, Hatena: {label: "Hatena", name: "Hatena", type: "full", vendor: "", category: "crawler"}, goo: {label: "goo", name: "goo", type: "full", vendor: "", category: "crawler"}, livedoorFeedFetcher: {label: "livedoorFeedFetcher", name: "livedoor FeedFetcher", type: "full", vendor: "", category: "crawler"}, VariousCrawler: {label: "VariousCrawler", name: "misc crawler", type: "full", vendor: "", category: "crawler"}}; 37 | var get = exports.get = function(label) { 38 | return DATASET[label]; 39 | }; 40 | -------------------------------------------------------------------------------- /lib/misc.js: -------------------------------------------------------------------------------- 1 | /* HEADER BEGIN */ 2 | var util = require('./util'), 3 | updateMap = util.updateMap, 4 | updateVersion = util.updateVersion, 5 | updateCategory = util.updateCategory, 6 | updateOs = util.updateOs, 7 | dataset = require('./dataset'); 8 | /* HEADER END */ 9 | 10 | var challengeDesktopTools = exports.challengeDesktopTools = function(ua, result){ 11 | var data = null; 12 | if (ua.indexOf('AppleSyndication/') >= 0) data = dataset.get('SafariRSSReader'); 13 | else if (ua.indexOf('compatible; Google Desktop/') >= 0) data = dataset.get('GoogleDesktop'); 14 | else if (ua.indexOf('Windows-RSS-Platform') >= 0) data = dataset.get('WindowsRSSReader'); 15 | 16 | if (!data) 17 | return false; 18 | 19 | updateMap(result, data); 20 | return true; 21 | }; 22 | 23 | var challengeSmartPhonePatterns = exports.challengeSmartPhonePatterns = function(ua, result){ 24 | var data; 25 | if (ua.indexOf('CFNetwork/') >= 0) { 26 | data = dataset.get('iOS'); 27 | updateCategory(result, data[dataset.KEY_CATEGORY]); 28 | updateOs(result, data[dataset.KEY_NAME]); 29 | return true; 30 | } 31 | 32 | return false; 33 | }; 34 | 35 | var challengeHTTPLibrary = exports.challengeHTTPLibrary = function(ua, result){ 36 | var data,version; 37 | if (/^(?:Apache-HttpClient\/|Jakarta Commons-HttpClient\/|Java\/)/.exec(ua) || /[- ]HttpClient(\/|$)/.exec(ua)) { 38 | data = dataset.get('HTTPLibrary'); version = 'Java'; 39 | } else if (ua.indexOf('Java(TM) 2 Runtime Environment,') >= 0) { 40 | data = dataset.get('HTTPLibrary'); version = 'Java'; 41 | } else if (/^Wget/.exec(ua)) { 42 | data = dataset.get('HTTPLibrary'); version = 'wget'; 43 | } else if (/^(?:libwww-perl|WWW-Mechanize|LWP::Simple|LWP |lwp-trivial)/.exec(ua)) { 44 | data = dataset.get('HTTPLibrary'); version = 'perl'; 45 | } else if (/^(?:Ruby|feedzirra|Typhoeus)/.exec(ua)) { 46 | data = dataset.get('HTTPLibrary'); version = 'ruby'; 47 | } else if (/^(?:Python-urllib\/|Twisted )/.exec(ua)) { 48 | data = dataset.get('HTTPLibrary'); version = 'python'; 49 | } else if (/^(:?PHP|WordPress|CakePHP|PukiWiki|PECL::HTTP)(?:\/| |$)/.exec(ua) || /(?:PEAR |)HTTP_Request(?: class|2)/.exec(ua)) { 50 | data = dataset.get('HTTPLibrary'); version = 'php'; 51 | } else if (ua.indexOf('PEAR HTTP_Request class;') >= 0) { 52 | data = dataset.get('HTTPLibrary'); version = 'php'; 53 | } else if (ua.indexOf('curl/') >= 0) { 54 | data = dataset.get('HTTPLibrary'); version = 'curl'; 55 | } 56 | 57 | if (! data) 58 | return false; 59 | 60 | updateMap(result, data); 61 | updateVersion(result, version); 62 | return true; 63 | }; 64 | 65 | var challengeMaybeRSSReader = exports.challengeMaybeRSSReader = function(ua, result){ 66 | var data = null; 67 | if (/rss(?:reader|bar|[-_ \/;()]|[ +]*\/)/i.exec(ua) || /headline-reader/i.exec(ua)) { 68 | data = dataset.get('VariousRSSReader'); 69 | } 70 | else { 71 | if (ua.indexOf('cococ/') >= 0) 72 | data = dataset.get('VariousRSSReader'); 73 | } 74 | 75 | if (! data) 76 | return false; 77 | 78 | updateMap(result, data); 79 | return true; 80 | }; 81 | -------------------------------------------------------------------------------- /lib/mobilephone.js: -------------------------------------------------------------------------------- 1 | /* HEADER BEGIN */ 2 | var util = require('./util'), 3 | updateMap = util.updateMap, 4 | updateVersion = util.updateVersion, 5 | updateCategory = util.updateCategory, 6 | updateOs = util.updateOs, 7 | dataset = require('./dataset'); 8 | /* HEADER END */ 9 | 10 | var docomoPattern = /DoCoMo\/[.0-9]+[ \/]([^- \/;()"']+)/; 11 | var fomaPattern = /\(([^;)]+);FOMA;/; 12 | var challengeDocomo = exports.challengeDocomo = function(ua, result) { 13 | if (ua.indexOf('DoCoMo') < 0 && ua.indexOf(';FOMA;') < 0) 14 | return false; 15 | 16 | var version = dataset.VALUE_UNKNOWN; 17 | var match; 18 | if ((match = docomoPattern.exec(ua))) { 19 | version = match[1]; 20 | } else if ((match = fomaPattern.exec(ua))) { 21 | version = match[1]; 22 | } 23 | updateMap(result, dataset.get('docomo')); 24 | updateVersion(result, version); 25 | return true; 26 | }; 27 | 28 | var kddiPattern = /KDDI-([^- \/;()"']+)/; 29 | var challengeAu = exports.challengeAu = function(ua, result) { 30 | if (ua.indexOf('KDDI-') < 0) 31 | return false; 32 | 33 | var version = dataset.VALUE_UNKNOWN; 34 | var match = kddiPattern.exec(ua); 35 | if (match) 36 | version = match[1]; 37 | updateMap(result, dataset.get('au')); 38 | updateVersion(result, version); 39 | return true; 40 | }; 41 | 42 | var softbankPattern = /(?:SoftBank|Vodafone|J-PHONE)\/[.0-9]+\/([^ \/;()]+)/; 43 | var challengeSoftbank = exports.challengeSoftbank = function(ua, result) { 44 | if (ua.indexOf('SoftBank') < 0 && ua.indexOf('Vodafone') < 0 && ua.indexOf('J-PHONE') < 0) 45 | return false; 46 | 47 | var version = dataset.VALUE_UNKNOWN; 48 | var match = softbankPattern.exec(ua); 49 | if (match) 50 | version = match[1]; 51 | updateMap(result, dataset.get('SoftBank')); 52 | updateVersion(result, version); 53 | return true; 54 | }; 55 | 56 | var willcomPattern = /(?:WILLCOM|DDIPOCKET);[^\/]+\/([^ \/;()]+)/; 57 | var challengeWillcom = exports.challengeWillcom = function(ua, result) { 58 | if (ua.indexOf('WILLCOM') < 0 && ua.indexOf('DDIPOCKET') < 0) 59 | return false; 60 | 61 | var version = dataset.VALUE_UNKNOWN; 62 | var match = willcomPattern.exec(ua); 63 | if (match) 64 | version = match[1]; 65 | updateMap(result, dataset.get('willcom')); 66 | updateVersion(result, version); 67 | return true; 68 | }; 69 | 70 | var challengeMisc = exports.challengeMisc = function(ua, result) { 71 | var match; 72 | if (ua.indexOf('jig browser') >= 0) { 73 | updateMap(result, dataset.get('jig')); 74 | if ((match = /jig browser[^;]+; ([^);]+)/.exec(ua))) { 75 | updateVersion(result, match[1]); 76 | } 77 | return true; 78 | } 79 | if (ua.indexOf('emobile/') >= 0 || ua.indexOf('OpenBrowser') >= 0 || ua.indexOf('Browser/Obigo-Browser') >= 0) { 80 | updateMap(result, dataset.get('emobile')); 81 | return true; 82 | } 83 | if (ua.indexOf('SymbianOS') >= 0) { 84 | updateMap(result, dataset.get('SymbianOS')); 85 | return true; 86 | } 87 | if (ua.indexOf('Hatena-Mobile-Gateway/') >= 0) { 88 | updateMap(result, dataset.get('MobileTranscoder')); 89 | updateVersion(result, 'Hatena'); 90 | return true; 91 | } 92 | if (ua.indexOf('livedoor-Mobile-Gateway/') >= 0) { 93 | updateMap(result, dataset.get('MobileTranscoder')); 94 | updateVersion(result, 'livedoor'); 95 | return true; 96 | } 97 | return false; 98 | }; 99 | -------------------------------------------------------------------------------- /lib/os.js: -------------------------------------------------------------------------------- 1 | /* HEADER BEGIN */ 2 | var util = require('./util'), 3 | updateMap = util.updateMap, 4 | updateVersion = util.updateVersion, 5 | updateCategory = util.updateCategory, 6 | updateOs = util.updateOs, 7 | updateOsVersion = util.updateOsVersion, 8 | dataset = require('./dataset'); 9 | /* HEADER END */ 10 | 11 | var windowsPattern = /Windows ([ .a-zA-Z0-9]+)[;\\)]/; 12 | var windowsPhonePattern = /^Phone(?: OS)? ([.0-9]+)/; 13 | var challengeWindows = exports.challengeWindows = function(ua, result) { 14 | if (ua.indexOf('Windows') < 0) 15 | return false; 16 | 17 | // Xbox Series 18 | if (ua.indexOf('Xbox') > -1) { 19 | var d; 20 | if (ua.indexOf('Xbox; Xbox One)') > -1) 21 | d = dataset.get("XboxOne"); 22 | else 23 | d = dataset.get("Xbox360"); 24 | // overwrite browser detections as appliance 25 | updateMap(result, d); 26 | return true; 27 | } 28 | 29 | 30 | var data = dataset.get('Win'); 31 | var match = windowsPattern.exec(ua); 32 | if (!match) { 33 | // Windows, but version unknown 34 | updateCategory(result, data[dataset.KEY_CATEGORY]); 35 | updateOs(result, data[dataset.KEY_NAME]); 36 | return true; 37 | } 38 | var version = match[1]; 39 | if (version === 'NT 10.0') data = dataset.get('Win10'); 40 | else if (version === 'NT 6.3') data = dataset.get('Win8.1'); 41 | else if (version === 'NT 6.2') data = dataset.get('Win8'); // "NT 6.2; ARM;" means Windows RT, oh.... 42 | else if (version === 'NT 6.1') data = dataset.get('Win7'); 43 | else if (version === 'NT 6.0') data = dataset.get('WinVista'); 44 | else if (version === 'NT 5.1') data = dataset.get('WinXP'); 45 | else if ((match = windowsPhonePattern.exec(version))) { 46 | data = dataset.get('WinPhone'); 47 | version = match[1]; 48 | } 49 | else if (version === 'NT 5.0') data = dataset.get('Win2000'); 50 | else if (version === 'NT 4.0') data = dataset.get('WinNT4'); 51 | else if (version === '98') data = dataset.get('Win98'); // wow, WinMe is shown as 'Windows 98; Win9x 4.90', fxxxk 52 | else if (version === '95') data = dataset.get('Win95'); 53 | else if (version === 'CE') data = dataset.get('WinCE'); 54 | /* 55 | else 56 | // windows unknown 57 | */ 58 | updateCategory(result, data[dataset.KEY_CATEGORY]); 59 | updateOs(result, data[dataset.KEY_NAME]); 60 | updateOsVersion(result, version); 61 | return true; 62 | }; 63 | 64 | var challengeOSX = exports.challengeOSX = function(ua, result) { 65 | if (ua.indexOf('Mac OS X') < 0) 66 | return false; 67 | 68 | var data = dataset.get('OSX'); 69 | var version; 70 | var match; 71 | if (ua.indexOf('like Mac OS X') >= 0) { 72 | if (ua.indexOf('iPhone;') >= 0) data = dataset.get('iPhone'); 73 | else if (ua.indexOf('iPad;') >= 0) data = dataset.get('iPad'); 74 | else if (ua.indexOf('iPod') >= 0) data = dataset.get('iPod'); 75 | 76 | if ((match = /; CPU(?: iPhone)? OS (\d+_\d+(?:_\d+)?) like Mac OS X/.exec(ua))) 77 | version = match[1].replace(/_/g, '.'); 78 | } 79 | else { 80 | if ((match = /Mac OS X (10[._]\d+(?:[._]\d+)?)(?:\)|;)/.exec(ua))) { 81 | version = match[1].replace(/_/g, '.'); 82 | } 83 | } 84 | updateCategory(result, data[dataset.KEY_CATEGORY]); 85 | updateOs(result, data[dataset.KEY_NAME]); 86 | if (version) 87 | updateOsVersion(result, version); 88 | return true; 89 | }; 90 | 91 | var challengeLinux = exports.challengeLinux = function(ua, result) { 92 | if (ua.indexOf('Linux') < 0) 93 | return false; 94 | 95 | var data = dataset.get('Linux'); 96 | var os_version; 97 | var match; 98 | if (ua.indexOf('Android') >= 0) { 99 | data = dataset.get('Android'); 100 | if ((match = /Android[- ](\d+(?:\.\d+(?:\.\d+)?)?)/.exec(ua))) 101 | os_version = match[1]; 102 | } 103 | updateCategory(result, data[dataset.KEY_CATEGORY]); 104 | updateOs(result, data[dataset.KEY_NAME]); 105 | if (os_version) 106 | updateOsVersion(result, os_version); 107 | return true; 108 | }; 109 | 110 | var challengeSmartPhone = exports.challengeSmartPhone = function(ua, result) { 111 | var data = null; 112 | var os_version = null; 113 | var match; 114 | if (ua.indexOf('iPhone') >= 0) data = dataset.get('iPhone'); 115 | else if (ua.indexOf('iPad') >= 0) data = dataset.get('iPad'); 116 | else if (ua.indexOf('iPod') >= 0) data = dataset.get('iPod'); 117 | else if (ua.indexOf('Android') >= 0) data = dataset.get('Android'); 118 | else if (ua.indexOf('CFNetwork') >= 0) data = dataset.get('iOS'); 119 | else if (ua.indexOf('BB10') >= 0) { 120 | data = dataset.get('BlackBerry10'); 121 | if ((match = /BB10(?:.+)Version\/([.0-9]+)/.exec(ua))) 122 | os_version = match[1]; 123 | } 124 | else if (ua.indexOf('BlackBerry') >= 0) { 125 | data = dataset.get('BlackBerry'); 126 | if ((match = /BlackBerry(?:\d+)\/([.0-9]+) /.exec(ua))) 127 | os_version = match[1]; 128 | } 129 | 130 | if (result[dataset.KEY_NAME] && result[dataset.KEY_NAME] === dataset.get('Firefox')[dataset.KEY_NAME]) { 131 | // Firefox OS specific pattern 132 | // http://lawrencemandel.com/2012/07/27/decision-made-firefox-os-user-agent-string/ 133 | // https://github.com/woothee/woothee/issues/2 134 | if ((match = /^Mozilla\/[.0-9]+ \((?:Mobile|Tablet);(?:.*;)? rv:([.0-9]+)\) Gecko\/[.0-9]+ Firefox\/[.0-9]+$/.exec(ua))) { 135 | data = dataset.get('FirefoxOS'); 136 | os_version = match[1]; 137 | } 138 | } 139 | 140 | if (!data) 141 | return false; 142 | 143 | updateCategory(result, data[dataset.KEY_CATEGORY]); 144 | updateOs(result, data[dataset.KEY_NAME]); 145 | if (os_version) 146 | updateOsVersion(result, os_version); 147 | return true; 148 | }; 149 | 150 | var challengeMobilePhone = exports.challengeMobilePhone = function(ua, result) { 151 | var data,match; 152 | if (ua.indexOf('KDDI-') >= 0) { 153 | if ((match = /KDDI-([^- \/;()"']+)/.exec(ua))) { 154 | var term = match[1]; 155 | data = dataset.get('au'); 156 | updateCategory(result, data[dataset.KEY_CATEGORY]); 157 | updateOs(result, data[dataset.KEY_OS]); 158 | updateVersion(result, term); 159 | return true; 160 | } 161 | } 162 | if (ua.indexOf('WILLCOM') >= 0 || ua.indexOf('DDIPOCKET') >= 0) { 163 | if ((match = /(?:WILLCOM|DDIPOCKET);[^\/]+\/([^ \/;()]+)/.exec(ua))) { 164 | term = match[1]; 165 | data = dataset.get('willcom'); 166 | updateCategory(result, data[dataset.KEY_CATEGORY]); 167 | updateOs(result, data[dataset.KEY_OS]); 168 | updateVersion(result, term); 169 | return true; 170 | } 171 | } 172 | if (ua.indexOf('SymbianOS') >= 0) { 173 | data = dataset.get('SymbianOS'); 174 | updateCategory(result, data[dataset.KEY_CATEGORY]); 175 | updateOs(result, data[dataset.KEY_OS]); 176 | return true; 177 | } 178 | if (ua.indexOf('Google Wireless Transcoder') >= 0) { 179 | updateMap(result, dataset.get('MobileTranscoder')); 180 | updateVersion(result, 'Google'); 181 | return true; 182 | } 183 | if (ua.indexOf('Naver Transcoder') >= 0) { 184 | updateMap(result, dataset.get('MobileTranscoder')); 185 | updateVersion(result, 'Naver'); 186 | return true; 187 | } 188 | 189 | return false; 190 | }; 191 | 192 | var challengeAppliance = exports.challengeAppliance = function(ua, result) { 193 | var data; 194 | if (ua.indexOf('Nintendo DSi;') >= 0) { 195 | data = dataset.get('NintendoDSi'); 196 | updateCategory(result, data[dataset.KEY_CATEGORY]); 197 | updateOs(result, data[dataset.KEY_OS]); 198 | return true; 199 | } 200 | if (ua.indexOf('Nintendo Wii;') >= 0) { 201 | data = dataset.get('NintendoWii'); 202 | updateCategory(result, data[dataset.KEY_CATEGORY]); 203 | updateOs(result, data[dataset.KEY_OS]); 204 | return true; 205 | } 206 | return false; 207 | }; 208 | 209 | var challengeMisc = exports.challengeMisc = function(ua, result) { 210 | var data; 211 | var osVersion; 212 | var match; 213 | if (ua.indexOf('(Win98;') >= 0) { 214 | data = dataset.get('Win98'); 215 | osVersion = "98"; 216 | } else if (ua.indexOf('Macintosh; U; PPC;') >= 0) { 217 | data = dataset.get('MacOS'); 218 | if ((match = /rv:(\d+\.\d+\.\d+)/.exec(ua))) 219 | osVersion = match[1]; 220 | } 221 | else if (ua.indexOf('Mac_PowerPC') >= 0) data = dataset.get('MacOS'); 222 | else if (ua.indexOf('X11; FreeBSD ') >= 0) { 223 | data = dataset.get('BSD'); 224 | if ((match = /FreeBSD ([^;\)]+);/.exec(ua))) 225 | osVersion = match[1]; 226 | } 227 | else if (ua.indexOf('X11; CrOS ') >= 0) { 228 | data = dataset.get('ChromeOS'); 229 | if ((match = /CrOS ([^\)]+)\)/.exec(ua))) 230 | osVersion = match[1]; 231 | } 232 | 233 | if (data) { 234 | updateCategory(result, data[dataset.KEY_CATEGORY]); 235 | updateOs(result, data[dataset.KEY_NAME]); 236 | if (osVersion) 237 | updateOsVersion(result, osVersion); 238 | return true; 239 | } 240 | 241 | return false; 242 | }; 243 | -------------------------------------------------------------------------------- /lib/util.js: -------------------------------------------------------------------------------- 1 | /* HEADER BEGIN */ 2 | var dataset = require('./dataset'); 3 | /* HEADER END */ 4 | 5 | var updateMap = exports.updateMap = function(target, source) { 6 | for (var key in source) { 7 | if (key === dataset.KEY_LABEL || key === dataset.KEY_TYPE) 8 | continue; 9 | if (source[key] && source[key].length > 0) 10 | target[key] = source[key]; 11 | } 12 | }; 13 | 14 | var updateCategory = exports.updateCategory = function(target, category) { 15 | target[dataset.ATTRIBUTE_CATEGORY] = category; 16 | }; 17 | 18 | var updateVersion = exports.updateVersion = function(target, version) { 19 | target[dataset.ATTRIBUTE_VERSION] = version; 20 | }; 21 | 22 | var updateOs = exports.updateOs = function(target, os) { 23 | target[dataset.ATTRIBUTE_OS] = os; 24 | }; 25 | 26 | var updateOsVersion = exports.updateOsVersion = function(target, version) { 27 | target[dataset.ATTRIBUTE_OS_VERSION] = version; 28 | }; -------------------------------------------------------------------------------- /lib/woothee.js: -------------------------------------------------------------------------------- 1 | /* HEADER BEGIN */ 2 | var dataset = require('./dataset') 3 | , browser = require('./browser') 4 | , os = require('./os') 5 | , mobilephone = require('./mobilephone') 6 | , crawler = require('./crawler') 7 | , appliance = require('./appliance') 8 | , misc = require('./misc') 9 | , package_info = require('./../package.json'); 10 | /* HEADER END */ 11 | 12 | var version = exports.VERSION = package_info.version; 13 | 14 | var parse = exports.parse = function(userAgent) { 15 | return fillResult(execParse(userAgent)); 16 | }; 17 | 18 | var isCrawler = exports.isCrawler = function(userAgent) { 19 | return !!userAgent && userAgent !== '-' && tryCrawler(userAgent, {}); 20 | }; 21 | 22 | function execParse(userAgent) { 23 | var result = {}; 24 | 25 | if (! userAgent || userAgent === '-') 26 | return result; 27 | 28 | if (tryCrawler(userAgent, result)) 29 | return result; 30 | 31 | if (tryBrowser(userAgent, result)) { 32 | if (tryOS(userAgent, result)) 33 | return result; 34 | else 35 | return result; 36 | } 37 | 38 | if (tryMobilePhone(userAgent, result)) 39 | return result; 40 | 41 | if (tryAppliance(userAgent, result)) 42 | return result; 43 | 44 | if (tryMisc(userAgent, result)) 45 | return result; 46 | 47 | // browser unknown, check os only 48 | if (tryOS(userAgent, result)) 49 | return result; 50 | 51 | if (tryRareCases(userAgent, result)) 52 | return result; 53 | 54 | return result; 55 | }; 56 | 57 | function tryCrawler(userAgent, result) { 58 | if (crawler.challengeGoogle(userAgent, result)) 59 | return true; 60 | 61 | if (crawler.challengeCrawlers(userAgent, result)) 62 | return true; 63 | 64 | return false; 65 | } 66 | 67 | function tryBrowser(userAgent, result) { 68 | if (browser.challengeMSIE(userAgent, result)) 69 | return true; 70 | 71 | if (browser.challengeVivaldi(userAgent, result)) 72 | return true; 73 | 74 | if (browser.challengeYandexBrowser(userAgent, result)) 75 | return true; 76 | 77 | if (browser.challengeSafariChrome(userAgent, result)) 78 | return true; 79 | 80 | if (browser.challengeFirefox(userAgent, result)) 81 | return true; 82 | 83 | if (browser.challengeOpera(userAgent, result)) 84 | return true; 85 | 86 | if (browser.challengeWebview(userAgent, result)) 87 | return true; 88 | 89 | return false; 90 | } 91 | 92 | function tryOS(userAgent, result) { 93 | if (os.challengeWindows(userAgent, result)) 94 | return true; 95 | 96 | // OSX PC and iOS devices (strict check) 97 | if (os.challengeOSX(userAgent, result)) 98 | return true; 99 | 100 | // Linux PC and Android 101 | if (os.challengeLinux(userAgent, result)) 102 | return true; 103 | 104 | // all useragents matches /(iPhone|iPad|iPod|Android|BlackBerry)/ 105 | if (os.challengeSmartPhone(userAgent, result)) 106 | return true; 107 | 108 | // mobile phones like KDDI-.* 109 | if (os.challengeMobilePhone(userAgent, result)) 110 | return true; 111 | 112 | // Nintendo DSi/Wii with Opera 113 | if (os.challengeAppliance(userAgent, result)) 114 | return true; 115 | 116 | if (os.challengeMisc(userAgent, result)) 117 | return true; 118 | 119 | return false; 120 | } 121 | 122 | function tryMobilePhone(userAgent, result) { 123 | if (mobilephone.challengeDocomo(userAgent, result)) 124 | return true; 125 | 126 | if (mobilephone.challengeAu(userAgent, result)) 127 | return true; 128 | 129 | if (mobilephone.challengeSoftbank(userAgent, result)) 130 | return true; 131 | 132 | if (mobilephone.challengeWillcom(userAgent, result)) 133 | return true; 134 | 135 | if (mobilephone.challengeMisc(userAgent, result)) 136 | return true; 137 | 138 | return false; 139 | } 140 | 141 | function tryAppliance(userAgent, result) { 142 | if (appliance.challengePlaystation(userAgent, result)) 143 | return true; 144 | 145 | if (appliance.challengeNintendo(userAgent, result)) 146 | return true; 147 | 148 | if (appliance.challengeDigitalTV(userAgent, result)) 149 | return true; 150 | 151 | return false; 152 | } 153 | 154 | function tryMisc(userAgent, result) { 155 | if (misc.challengeDesktopTools(userAgent, result)) 156 | return true; 157 | 158 | return false; 159 | } 160 | 161 | function tryRareCases(userAgent, result) { 162 | if (misc.challengeSmartPhonePatterns(userAgent, result)) 163 | return true; 164 | 165 | if (browser.challengeSleipnir(userAgent, result)) 166 | return true; 167 | 168 | if (misc.challengeHTTPLibrary(userAgent, result)) 169 | return true; 170 | 171 | if (misc.challengeMaybeRSSReader(userAgent, result)) 172 | return true; 173 | 174 | if (crawler.challengeMaybeCrawler(userAgent, result)) 175 | return true; 176 | 177 | return false; 178 | } 179 | 180 | var filled = {}; 181 | filled[dataset.ATTRIBUTE_NAME] = dataset.VALUE_UNKNOWN; 182 | filled[dataset.ATTRIBUTE_CATEGORY] = dataset.VALUE_UNKNOWN; 183 | filled[dataset.ATTRIBUTE_OS] = dataset.VALUE_UNKNOWN; 184 | filled[dataset.ATTRIBUTE_OS_VERSION] = dataset.VALUE_UNKNOWN; 185 | filled[dataset.ATTRIBUTE_VERSION] = dataset.VALUE_UNKNOWN; 186 | filled[dataset.ATTRIBUTE_VENDOR] = dataset.VALUE_UNKNOWN; 187 | 188 | function fillResult(result) { 189 | if (! result[dataset.ATTRIBUTE_NAME]) 190 | result[dataset.ATTRIBUTE_NAME] = dataset.VALUE_UNKNOWN; 191 | if (! result[dataset.ATTRIBUTE_CATEGORY]) 192 | result[dataset.ATTRIBUTE_CATEGORY] = dataset.VALUE_UNKNOWN; 193 | if (! result[dataset.ATTRIBUTE_OS]) 194 | result[dataset.ATTRIBUTE_OS] = dataset.VALUE_UNKNOWN; 195 | if (! result[dataset.ATTRIBUTE_OS_VERSION]) 196 | result[dataset.ATTRIBUTE_OS_VERSION] = dataset.VALUE_UNKNOWN; 197 | if (! result[dataset.ATTRIBUTE_VERSION]) 198 | result[dataset.ATTRIBUTE_VERSION] = dataset.VALUE_UNKNOWN; 199 | if (! result[dataset.ATTRIBUTE_VENDOR]) 200 | result[dataset.ATTRIBUTE_VENDOR] = dataset.VALUE_UNKNOWN; 201 | return result; 202 | }; 203 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "woothee", 3 | "version": "1.11.0", 4 | "description": "User-Agent string parser (js implementation)", 5 | "main": "./release/woothee", 6 | "devDependencies": { 7 | "chai": ">= 1.3.0", 8 | "js-yaml": ">= 1.0.3", 9 | "mocha": "^6.0.0", 10 | "serialize-to-js": "^3.0.0", 11 | "should": "~1.2.2" 12 | }, 13 | "scripts": { 14 | "test": "make test" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "https://github.com/woothee/woothee-js" 19 | }, 20 | "author": "tagomoris", 21 | "license": "Apache-2.0" 22 | } 23 | -------------------------------------------------------------------------------- /release/woothee.js: -------------------------------------------------------------------------------- 1 | (function (){ 2 | var root = this; 3 | // embed: dataset, util, browser, mobilephone, crawler, appliance, misc, woothee 4 | 5 | // GENERATED from dataset.yaml at Tue Sep 7 11:33:54 JST 2021 by tagomoris 6 | 7 | // Snapshot from package.json 8 | var package_info = {"name":"woothee","version":"1.11.0","description":"User-Agent string parser (js implementation)","main":"./release/woothee","devDependencies":{"chai":">= 1.3.0","js-yaml":">= 1.0.3","mocha":"^6.0.0","serialize-to-js":"^3.0.0","should":"~1.2.2"},"scripts":{"test":"make test"},"repository":{"type":"git","url":"https://github.com/woothee/woothee-js"},"author":"tagomoris","license":"Apache-2.0"}; 9 | 10 | var dataset = {}; 11 | (function(){ 12 | var exports = dataset; 13 | /* CODE: dataset.js */ 14 | var KEY_LABEL = exports.KEY_LABEL = 'label', 15 | KEY_NAME = exports.KEY_NAME = 'name', 16 | KEY_TYPE = exports.KEY_TYPE = 'type', 17 | KEY_CATEGORY = exports.KEY_CATEGORY = 'category', 18 | KEY_OS = exports.KEY_OS = 'os', 19 | KEY_OS_VERSION = exports.KEY_OS_VERSION = 'os_version', 20 | KEY_VENDOR = exports.KEY_VENDOR = 'vendor', 21 | KEY_VERSION = exports.KEY_VERSION = 'version'; 22 | var TYPE_BROWSER = 'browser', 23 | TYPE_OS = 'os', 24 | TYPE_FULL = 'full'; 25 | var CATEGORY_PC = exports.CATEGORY_PC = 'pc', 26 | CATEGORY_SMARTPHONE = exports.CATEGORY_SMARTPHONE = 'smartphone', 27 | CATEGORY_MOBILEPHONE = exports.CATEGORY_MOBILEPHONE = 'mobilephone', 28 | CATEGORY_CRAWLER = exports.CATEGORY_CRAWLER = 'crawler', 29 | CATEGORY_APPLIANCE = exports.CATEGORY_APPLIANCE = 'appliance', 30 | CATEGORY_MISC = exports.CATEGORY_MISC = 'misc'; 31 | var ATTRIBUTE_NAME = exports.ATTRIBUTE_NAME = 'name', 32 | ATTRIBUTE_CATEGORY = exports.ATTRIBUTE_CATEGORY = 'category', 33 | ATTRIBUTE_OS = exports.ATTRIBUTE_OS = 'os', 34 | ATTRIBUTE_OS_VERSION = exports.ATTRIBUTE_OS_VERSION = 'os_version', 35 | ATTRIBUTE_VENDOR = exports.ATTRIBUTE_VENDOR = 'vendor', 36 | ATTRIBUTE_VERSION = exports.ATTRIBUTE_VERSION = 'version'; 37 | var VALUE_UNKNOWN = exports.VALUE_UNKNOWN = 'UNKNOWN'; 38 | var CATEGORY_LIST = exports.CATEGORY_LIST = [ 39 | CATEGORY_PC, CATEGORY_SMARTPHONE, CATEGORY_MOBILEPHONE, 40 | CATEGORY_CRAWLER, CATEGORY_APPLIANCE, CATEGORY_MISC, VALUE_UNKNOWN 41 | ]; 42 | var ATTRIBUTE_LIST = exports.ATTRIBUTE_LIST = [ATTRIBUTE_NAME, ATTRIBUTE_CATEGORY, ATTRIBUTE_OS, ATTRIBUTE_VENDOR, ATTRIBUTE_VERSION, ATTRIBUTE_OS_VERSION]; 43 | // GENERATED from dataset.yaml at Tue Sep 7 11:33:54 JST 2021 by tagomoris 44 | var DATASET = {MSIE: {label: "MSIE", name: "Internet Explorer", type: "browser", vendor: "Microsoft"}, Edge: {label: "Edge", name: "Edge", type: "browser", vendor: "Microsoft"}, Chrome: {label: "Chrome", name: "Chrome", type: "browser", vendor: "Google"}, Safari: {label: "Safari", name: "Safari", type: "browser", vendor: "Apple"}, Firefox: {label: "Firefox", name: "Firefox", type: "browser", vendor: "Mozilla"}, Opera: {label: "Opera", name: "Opera", type: "browser", vendor: "Opera"}, Vivaldi: {label: "Vivaldi", name: "Vivaldi", type: "browser", vendor: "Vivaldi Technologies"}, Sleipnir: {label: "Sleipnir", name: "Sleipnir", type: "browser", vendor: "Fenrir Inc."}, GSA: {label: "GSA", name: "Google Search App", type: "browser", vendor: "Google"}, Webview: {label: "Webview", name: "Webview", type: "browser", vendor: "OS vendor"}, YaBrowser: {label: "YaBrowser", name: "Yandex Browser", type: "browser", vendor: "Yandex"}, Win: {label: "Win", name: "Windows UNKNOWN Ver", type: "os", category: "pc"}, Win10: {label: "Win10", name: "Windows 10", type: "os", category: "pc"}, "Win8.1": {label: "Win8.1", name: "Windows 8.1", type: "os", category: "pc"}, Win8: {label: "Win8", name: "Windows 8", type: "os", category: "pc"}, Win7: {label: "Win7", name: "Windows 7", type: "os", category: "pc"}, WinVista: {label: "WinVista", name: "Windows Vista", type: "os", category: "pc"}, WinXP: {label: "WinXP", name: "Windows XP", type: "os", category: "pc"}, Win2000: {label: "Win2000", name: "Windows 2000", type: "os", category: "pc"}, WinNT4: {label: "WinNT4", name: "Windows NT 4.0", type: "os", category: "pc"}, WinMe: {label: "WinMe", name: "Windows Me", type: "os", category: "pc"}, Win98: {label: "Win98", name: "Windows 98", type: "os", category: "pc"}, Win95: {label: "Win95", name: "Windows 95", type: "os", category: "pc"}, WinPhone: {label: "WinPhone", name: "Windows Phone OS", type: "os", category: "smartphone"}, WinCE: {label: "WinCE", name: "Windows CE", type: "os", category: "smartphone"}, OSX: {label: "OSX", name: "Mac OSX", type: "os", category: "pc"}, MacOS: {label: "MacOS", name: "Mac OS Classic", type: "os", category: "pc"}, Linux: {label: "Linux", name: "Linux", type: "os", category: "pc"}, BSD: {label: "BSD", name: "BSD", type: "os", category: "pc"}, ChromeOS: {label: "ChromeOS", name: "ChromeOS", type: "os", category: "pc"}, Android: {label: "Android", name: "Android", type: "os", category: "smartphone"}, iPhone: {label: "iPhone", name: "iPhone", type: "os", category: "smartphone"}, iPad: {label: "iPad", name: "iPad", type: "os", category: "smartphone"}, iPod: {label: "iPod", name: "iPod", type: "os", category: "smartphone"}, iOS: {label: "iOS", name: "iOS", type: "os", category: "smartphone"}, FirefoxOS: {label: "FirefoxOS", name: "Firefox OS", type: "os", category: "smartphone"}, BlackBerry: {label: "BlackBerry", name: "BlackBerry", type: "os", category: "smartphone"}, BlackBerry10: {label: "BlackBerry10", name: "BlackBerry 10", type: "os", category: "smartphone"}, docomo: {label: "docomo", name: "docomo", type: "full", vendor: "docomo", category: "mobilephone", os: "docomo"}, au: {label: "au", name: "au by KDDI", type: "full", vendor: "au", category: "mobilephone", os: "au"}, SoftBank: {label: "SoftBank", name: "SoftBank Mobile", type: "full", vendor: "SoftBank", category: "mobilephone", os: "SoftBank"}, willcom: {label: "willcom", name: "WILLCOM", type: "full", vendor: "WILLCOM", category: "mobilephone", os: "WILLCOM"}, jig: {label: "jig", name: "jig browser", type: "full", vendor: "", category: "mobilephone", os: "jig"}, emobile: {label: "emobile", name: "emobile", type: "full", vendor: "", category: "mobilephone", os: "emobile"}, SymbianOS: {label: "SymbianOS", name: "SymbianOS", type: "full", vendor: "", category: "mobilephone", os: "SymbianOS"}, MobileTranscoder: {label: "MobileTranscoder", name: "Mobile Transcoder", type: "full", vendor: "", category: "mobilephone", os: "Mobile Transcoder"}, Nintendo3DS: {label: "Nintendo3DS", name: "Nintendo 3DS", type: "full", vendor: "Nintendo", category: "appliance", os: "Nintendo 3DS"}, NintendoDSi: {label: "NintendoDSi", name: "Nintendo DSi", type: "full", vendor: "Nintendo", category: "appliance", os: "Nintendo DSi"}, NintendoWii: {label: "NintendoWii", name: "Nintendo Wii", type: "full", vendor: "Nintendo", category: "appliance", os: "Nintendo Wii"}, NintendoWiiU: {label: "NintendoWiiU", name: "Nintendo Wii U", type: "full", vendor: "Nintendo", category: "appliance", os: "Nintendo Wii U"}, PSP: {label: "PSP", name: "PlayStation Portable", type: "full", vendor: "Sony", category: "appliance", os: "PlayStation Portable"}, PSVita: {label: "PSVita", name: "PlayStation Vita", type: "full", vendor: "Sony", category: "appliance", os: "PlayStation Vita"}, PS3: {label: "PS3", name: "PlayStation 3", type: "full", vendor: "Sony", category: "appliance", os: "PlayStation 3"}, PS4: {label: "PS4", name: "PlayStation 4", type: "full", vendor: "Sony", category: "appliance", os: "PlayStation 4"}, Xbox360: {label: "Xbox360", name: "Xbox 360", type: "full", vendor: "Microsoft", category: "appliance", os: "Xbox 360"}, XboxOne: {label: "XboxOne", name: "Xbox One", type: "full", vendor: "Microsoft", category: "appliance", os: "Xbox One"}, DigitalTV: {label: "DigitalTV", name: "InternetTVBrowser", type: "full", vendor: "", category: "appliance", os: "DigitalTV"}, SafariRSSReader: {label: "SafariRSSReader", name: "Safari RSSReader", type: "full", vendor: "Apple", category: "misc"}, GoogleDesktop: {label: "GoogleDesktop", name: "Google Desktop", type: "full", vendor: "Google", category: "misc"}, WindowsRSSReader: {label: "WindowsRSSReader", name: "Windows RSSReader", type: "full", vendor: "Microsoft", category: "misc"}, VariousRSSReader: {label: "VariousRSSReader", name: "RSSReader", type: "full", vendor: "", category: "misc"}, HTTPLibrary: {label: "HTTPLibrary", name: "HTTP Library", type: "full", vendor: "", category: "misc"}, GoogleBot: {label: "GoogleBot", name: "Googlebot", type: "full", vendor: "", category: "crawler"}, GoogleBotMobile: {label: "GoogleBotMobile", name: "Googlebot Mobile", type: "full", vendor: "", category: "crawler"}, GoogleMediaPartners: {label: "GoogleMediaPartners", name: "Google Mediapartners", type: "full", vendor: "", category: "crawler"}, GoogleFeedFetcher: {label: "GoogleFeedFetcher", name: "Google Feedfetcher", type: "full", vendor: "", category: "crawler"}, GoogleAppEngine: {label: "GoogleAppEngine", name: "Google AppEngine", type: "full", vendor: "", category: "crawler"}, GoogleWebPreview: {label: "GoogleWebPreview", name: "Google Web Preview", type: "full", vendor: "", category: "crawler"}, YahooSlurp: {label: "YahooSlurp", name: "Yahoo! Slurp", type: "full", vendor: "", category: "crawler"}, YahooJP: {label: "YahooJP", name: "Yahoo! Japan", type: "full", vendor: "", category: "crawler"}, YahooPipes: {label: "YahooPipes", name: "Yahoo! Pipes", type: "full", vendor: "", category: "crawler"}, Baiduspider: {label: "Baiduspider", name: "Baiduspider", type: "full", vendor: "", category: "crawler"}, msnbot: {label: "msnbot", name: "msnbot", type: "full", vendor: "", category: "crawler"}, bingbot: {label: "bingbot", name: "bingbot", type: "full", vendor: "", category: "crawler"}, BingPreview: {label: "BingPreview", name: "BingPreview", type: "full", vendor: "", category: "crawler"}, Yeti: {label: "Yeti", name: "Naver Yeti", type: "full", vendor: "", category: "crawler"}, FeedBurner: {label: "FeedBurner", name: "Google FeedBurner", type: "full", vendor: "", category: "crawler"}, facebook: {label: "facebook", name: "facebook", type: "full", vendor: "", category: "crawler"}, twitter: {label: "twitter", name: "twitter", type: "full", vendor: "", category: "crawler"}, trendictionbot: {label: "trendictionbot", name: "trendiction", type: "full", vendor: "", category: "crawler"}, mixi: {label: "mixi", name: "mixi", type: "full", vendor: "", category: "crawler"}, IndyLibrary: {label: "IndyLibrary", name: "Indy Library", type: "full", vendor: "", category: "crawler"}, ApplePubSub: {label: "ApplePubSub", name: "Apple iCloud", type: "full", vendor: "", category: "crawler"}, Genieo: {label: "Genieo", name: "Genieo Web Filter", type: "full", vendor: "", category: "crawler"}, topsyButterfly: {label: "topsyButterfly", name: "topsy Butterfly", type: "full", vendor: "", category: "crawler"}, rogerbot: {label: "rogerbot", name: "SeoMoz rogerbot", type: "full", vendor: "", category: "crawler"}, AhrefsBot: {label: "AhrefsBot", name: "ahref AhrefsBot", type: "full", vendor: "", category: "crawler"}, radian6: {label: "radian6", name: "salesforce radian6", type: "full", vendor: "", category: "crawler"}, Hatena: {label: "Hatena", name: "Hatena", type: "full", vendor: "", category: "crawler"}, goo: {label: "goo", name: "goo", type: "full", vendor: "", category: "crawler"}, livedoorFeedFetcher: {label: "livedoorFeedFetcher", name: "livedoor FeedFetcher", type: "full", vendor: "", category: "crawler"}, VariousCrawler: {label: "VariousCrawler", name: "misc crawler", type: "full", vendor: "", category: "crawler"}}; 45 | var get = exports.get = function(label) { 46 | return DATASET[label]; 47 | }; 48 | 49 | })(); 50 | var util = {}; 51 | (function(){ 52 | var exports = util; 53 | /* CODE: util.js */ 54 | var updateMap = exports.updateMap = function(target, source) { 55 | for (var key in source) { 56 | if (key === dataset.KEY_LABEL || key === dataset.KEY_TYPE) 57 | continue; 58 | if (source[key] && source[key].length > 0) 59 | target[key] = source[key]; 60 | } 61 | }; 62 | var updateCategory = exports.updateCategory = function(target, category) { 63 | target[dataset.ATTRIBUTE_CATEGORY] = category; 64 | }; 65 | var updateVersion = exports.updateVersion = function(target, version) { 66 | target[dataset.ATTRIBUTE_VERSION] = version; 67 | }; 68 | var updateOs = exports.updateOs = function(target, os) { 69 | target[dataset.ATTRIBUTE_OS] = os; 70 | }; 71 | var updateOsVersion = exports.updateOsVersion = function(target, version) { 72 | target[dataset.ATTRIBUTE_OS_VERSION] = version; 73 | }; 74 | 75 | })(); 76 | var browser = {}; 77 | (function(){ 78 | var exports = browser; 79 | /* CODE: browser.js */ 80 | var msiePattern = /MSIE ([.0-9]+);/; 81 | var tridentPattern = /Trident\/[.0-9]+;/; 82 | var tridentVersionPattern = / rv:([.0-9]+)/; 83 | var iemobilePattern = /IEMobile\/([.0-9]+);/; 84 | var challengeMSIE = exports.challengeMSIE = function(ua, result) { 85 | if (ua.indexOf('compatible; MSIE') < 0 && ua.indexOf('Trident/') < 0 && ua.indexOf('IEMobile/')) 86 | return false; 87 | var version; 88 | var match = msiePattern.exec(ua); 89 | if (!match) 90 | if (tridentPattern.exec(ua)) 91 | match = tridentVersionPattern.exec(ua); 92 | if (!match) 93 | match = iemobilePattern.exec(ua); 94 | if (match) 95 | version = match[1]; 96 | else 97 | version = dataset.VALUE_UNKNOWN; 98 | updateMap(result, dataset.get('MSIE')); 99 | updateVersion(result, version); 100 | return true; 101 | }; 102 | var yandexBrowserPattern = /YaBrowser\/([.0-9]+)/; 103 | var challengeYandexBrowser = exports.challengeYandexBrowser = function(ua, result) { 104 | if (ua.indexOf('YaBrowser/') < 0) 105 | return false; 106 | var version; 107 | var match = yandexBrowserPattern.exec(ua); 108 | if (match) { 109 | version = match[1]; 110 | } else { 111 | version = dataset.VALUE_UNKNOWN; 112 | } 113 | updateMap(result, dataset.get('YaBrowser')); 114 | updateVersion(result, version); 115 | return true; 116 | }; 117 | var edgePattern = /(?:Edge|Edg|EdgiOS|EdgA)\/([.0-9]+)/; 118 | var firefoxiOSPattern = /FxiOS\/([.0-9]+)/; 119 | var chromePattern = /(?:Chrome|CrMo|CriOS)\/([.0-9]+)/; 120 | var operaBlinkPattern = /OPR\/([.0-9]+)/; 121 | var gsaPattern = /GSA\/([.0-9]+)/; 122 | var safariPattern = /Version\/([.0-9]+)/; 123 | var challengeSafariChrome = exports.challengeSafariChrome = function(ua, result) { 124 | if (ua.indexOf('Safari/') < 0) 125 | return false; 126 | if (ua.indexOf('Chrome') >= 0 && ua.indexOf('wv') >= 0) 127 | return false; 128 | var version = dataset.VALUE_UNKNOWN; 129 | var match; 130 | if ((match = edgePattern.exec(ua))) { 131 | version = match[1]; 132 | updateMap(result, dataset.get('Edge')); 133 | updateVersion(result, version); 134 | return true; 135 | } 136 | if ((match = firefoxiOSPattern.exec(ua))) { 137 | version = match[1]; 138 | updateMap(result, dataset.get('Firefox')); 139 | updateVersion(result, version); 140 | return true; 141 | } 142 | if ((match = chromePattern.exec(ua))) { 143 | var matchOpera; 144 | if ((matchOpera = operaBlinkPattern.exec(ua))) { 145 | // Opera w/ blink 146 | version = matchOpera[1]; 147 | updateMap(result, dataset.get('Opera')); 148 | updateVersion(result, version); 149 | return true; 150 | } 151 | // Chrome 152 | version = match[1]; 153 | updateMap(result, dataset.get('Chrome')); 154 | updateVersion(result, version); 155 | return true; 156 | } 157 | if ((match = gsaPattern.exec(ua))) { 158 | version = match[1]; 159 | updateMap(result, dataset.get('GSA')); 160 | updateVersion(result, version); 161 | return true; 162 | } 163 | if ((match = safariPattern.exec(ua))) { 164 | version = match[1]; 165 | } 166 | updateMap(result, dataset.get('Safari')); 167 | updateVersion(result, version); 168 | return true; 169 | }; 170 | var firefoxPattern = /Firefox\/([.0-9]+)/; 171 | var challengeFirefox = exports.challengeFirefox = function(ua, result) { 172 | if (ua.indexOf('Firefox/') < 0) 173 | return false; 174 | var version = dataset.VALUE_UNKNOWN; 175 | var match = firefoxPattern.exec(ua); 176 | if (match) 177 | version = match[1]; 178 | updateMap(result, dataset.get('Firefox')); 179 | updateVersion(result, version); 180 | return true; 181 | }; 182 | var operaPattern1 = /Version\/([.0-9]+)/; 183 | var operaPattern2 = /Opera[\/ ]([.0-9]+)/; 184 | var challengeOpera = exports.challengeOpera = function(ua, result) { 185 | if (ua.indexOf('Opera') < 0) 186 | return false; 187 | var version = dataset.VALUE_UNKNOWN; 188 | var match = operaPattern1.exec(ua); 189 | if (match) { 190 | version = match[1]; 191 | } else { 192 | match = operaPattern2.exec(ua); 193 | if (match) 194 | version = match[1]; 195 | } 196 | updateMap(result, dataset.get('Opera')); 197 | updateVersion(result, version); 198 | return true; 199 | }; 200 | var webviewPattern = /iP(hone;|ad;|od) .*like Mac OS X/; 201 | var webviewVersionPattern = /Version\/([.0-9]+)/; 202 | var challengeWebview = exports.challengeWebview = function(ua, result) { 203 | var version = dataset.VALUE_UNKNOWN; 204 | // Android(Lollipop and Above) 205 | if (ua.indexOf('Chrome') >= 0 && ua.indexOf('wv') >= 0) { 206 | var vmatch = webviewVersionPattern.exec(ua); 207 | if (vmatch) 208 | version = vmatch[1]; 209 | updateMap(result, dataset.get('Webview')); 210 | updateVersion(result, version); 211 | return true; 212 | } 213 | // iOS 214 | var match = webviewPattern.exec(ua); 215 | if (match) { 216 | if (ua.indexOf('Safari/') > -1) 217 | return false; 218 | var vmatch = webviewVersionPattern.exec(ua); 219 | if (vmatch) 220 | version = vmatch[1]; 221 | updateMap(result, dataset.get('Webview')); 222 | updateVersion(result, version); 223 | return true; 224 | } 225 | return false; 226 | }; 227 | var sleipnirPattern = /Sleipnir\/([.0-9]+)/; 228 | var challengeSleipnir = exports.challengeSleipnir = function(ua, result) { 229 | if (ua.indexOf('Sleipnir/') < 0) 230 | return false; 231 | var version = dataset.VALUE_UNKNOWN; 232 | var match = sleipnirPattern.exec(ua); 233 | if (match) 234 | version = match[1]; 235 | updateMap(result, dataset.get('Sleipnir')); 236 | updateVersion(result, version); 237 | /* 238 | Sleipnir's user-agent doesn't contain Windows version, so put 'Windows UNKNOWN Ver'. 239 | Sleipnir is IE component browser, so for Windows only. 240 | */ 241 | var win = dataset.get('Win'); 242 | updateCategory(result, win[dataset.KEY_CATEGORY]); 243 | updateOs(result, win[dataset.KEY_NAME]); 244 | return true; 245 | }; 246 | var vivaldiPattern = /Vivaldi\/([.0-9]+)/; 247 | var challengeVivaldi = exports.challengeVivaldi = function(ua, result) { 248 | if (ua.indexOf('Vivaldi/') < 0) 249 | return false; 250 | var version = dataset.VALUE_UNKNOWN; 251 | var match = vivaldiPattern.exec(ua); 252 | if (match) 253 | version = match[1]; 254 | updateMap(result, dataset.get('Vivaldi')); 255 | updateVersion(result, version); 256 | return true; 257 | }; 258 | 259 | })(); 260 | var os = {}; 261 | (function(){ 262 | var exports = os; 263 | /* CODE: os.js */ 264 | var windowsPattern = /Windows ([ .a-zA-Z0-9]+)[;\\)]/; 265 | var windowsPhonePattern = /^Phone(?: OS)? ([.0-9]+)/; 266 | var challengeWindows = exports.challengeWindows = function(ua, result) { 267 | if (ua.indexOf('Windows') < 0) 268 | return false; 269 | // Xbox Series 270 | if (ua.indexOf('Xbox') > -1) { 271 | var d; 272 | if (ua.indexOf('Xbox; Xbox One)') > -1) 273 | d = dataset.get("XboxOne"); 274 | else 275 | d = dataset.get("Xbox360"); 276 | // overwrite browser detections as appliance 277 | updateMap(result, d); 278 | return true; 279 | } 280 | var data = dataset.get('Win'); 281 | var match = windowsPattern.exec(ua); 282 | if (!match) { 283 | // Windows, but version unknown 284 | updateCategory(result, data[dataset.KEY_CATEGORY]); 285 | updateOs(result, data[dataset.KEY_NAME]); 286 | return true; 287 | } 288 | var version = match[1]; 289 | if (version === 'NT 10.0') data = dataset.get('Win10'); 290 | else if (version === 'NT 6.3') data = dataset.get('Win8.1'); 291 | else if (version === 'NT 6.2') data = dataset.get('Win8'); // "NT 6.2; ARM;" means Windows RT, oh.... 292 | else if (version === 'NT 6.1') data = dataset.get('Win7'); 293 | else if (version === 'NT 6.0') data = dataset.get('WinVista'); 294 | else if (version === 'NT 5.1') data = dataset.get('WinXP'); 295 | else if ((match = windowsPhonePattern.exec(version))) { 296 | data = dataset.get('WinPhone'); 297 | version = match[1]; 298 | } 299 | else if (version === 'NT 5.0') data = dataset.get('Win2000'); 300 | else if (version === 'NT 4.0') data = dataset.get('WinNT4'); 301 | else if (version === '98') data = dataset.get('Win98'); // wow, WinMe is shown as 'Windows 98; Win9x 4.90', fxxxk 302 | else if (version === '95') data = dataset.get('Win95'); 303 | else if (version === 'CE') data = dataset.get('WinCE'); 304 | /* 305 | else 306 | // windows unknown 307 | */ 308 | updateCategory(result, data[dataset.KEY_CATEGORY]); 309 | updateOs(result, data[dataset.KEY_NAME]); 310 | updateOsVersion(result, version); 311 | return true; 312 | }; 313 | var challengeOSX = exports.challengeOSX = function(ua, result) { 314 | if (ua.indexOf('Mac OS X') < 0) 315 | return false; 316 | var data = dataset.get('OSX'); 317 | var version; 318 | var match; 319 | if (ua.indexOf('like Mac OS X') >= 0) { 320 | if (ua.indexOf('iPhone;') >= 0) data = dataset.get('iPhone'); 321 | else if (ua.indexOf('iPad;') >= 0) data = dataset.get('iPad'); 322 | else if (ua.indexOf('iPod') >= 0) data = dataset.get('iPod'); 323 | if ((match = /; CPU(?: iPhone)? OS (\d+_\d+(?:_\d+)?) like Mac OS X/.exec(ua))) 324 | version = match[1].replace(/_/g, '.'); 325 | } 326 | else { 327 | if ((match = /Mac OS X (10[._]\d+(?:[._]\d+)?)(?:\)|;)/.exec(ua))) { 328 | version = match[1].replace(/_/g, '.'); 329 | } 330 | } 331 | updateCategory(result, data[dataset.KEY_CATEGORY]); 332 | updateOs(result, data[dataset.KEY_NAME]); 333 | if (version) 334 | updateOsVersion(result, version); 335 | return true; 336 | }; 337 | var challengeLinux = exports.challengeLinux = function(ua, result) { 338 | if (ua.indexOf('Linux') < 0) 339 | return false; 340 | var data = dataset.get('Linux'); 341 | var os_version; 342 | var match; 343 | if (ua.indexOf('Android') >= 0) { 344 | data = dataset.get('Android'); 345 | if ((match = /Android[- ](\d+(?:\.\d+(?:\.\d+)?)?)/.exec(ua))) 346 | os_version = match[1]; 347 | } 348 | updateCategory(result, data[dataset.KEY_CATEGORY]); 349 | updateOs(result, data[dataset.KEY_NAME]); 350 | if (os_version) 351 | updateOsVersion(result, os_version); 352 | return true; 353 | }; 354 | var challengeSmartPhone = exports.challengeSmartPhone = function(ua, result) { 355 | var data = null; 356 | var os_version = null; 357 | var match; 358 | if (ua.indexOf('iPhone') >= 0) data = dataset.get('iPhone'); 359 | else if (ua.indexOf('iPad') >= 0) data = dataset.get('iPad'); 360 | else if (ua.indexOf('iPod') >= 0) data = dataset.get('iPod'); 361 | else if (ua.indexOf('Android') >= 0) data = dataset.get('Android'); 362 | else if (ua.indexOf('CFNetwork') >= 0) data = dataset.get('iOS'); 363 | else if (ua.indexOf('BB10') >= 0) { 364 | data = dataset.get('BlackBerry10'); 365 | if ((match = /BB10(?:.+)Version\/([.0-9]+)/.exec(ua))) 366 | os_version = match[1]; 367 | } 368 | else if (ua.indexOf('BlackBerry') >= 0) { 369 | data = dataset.get('BlackBerry'); 370 | if ((match = /BlackBerry(?:\d+)\/([.0-9]+) /.exec(ua))) 371 | os_version = match[1]; 372 | } 373 | if (result[dataset.KEY_NAME] && result[dataset.KEY_NAME] === dataset.get('Firefox')[dataset.KEY_NAME]) { 374 | // Firefox OS specific pattern 375 | // http://lawrencemandel.com/2012/07/27/decision-made-firefox-os-user-agent-string/ 376 | // https://github.com/woothee/woothee/issues/2 377 | if ((match = /^Mozilla\/[.0-9]+ \((?:Mobile|Tablet);(?:.*;)? rv:([.0-9]+)\) Gecko\/[.0-9]+ Firefox\/[.0-9]+$/.exec(ua))) { 378 | data = dataset.get('FirefoxOS'); 379 | os_version = match[1]; 380 | } 381 | } 382 | if (!data) 383 | return false; 384 | updateCategory(result, data[dataset.KEY_CATEGORY]); 385 | updateOs(result, data[dataset.KEY_NAME]); 386 | if (os_version) 387 | updateOsVersion(result, os_version); 388 | return true; 389 | }; 390 | var challengeMobilePhone = exports.challengeMobilePhone = function(ua, result) { 391 | var data,match; 392 | if (ua.indexOf('KDDI-') >= 0) { 393 | if ((match = /KDDI-([^- \/;()"']+)/.exec(ua))) { 394 | var term = match[1]; 395 | data = dataset.get('au'); 396 | updateCategory(result, data[dataset.KEY_CATEGORY]); 397 | updateOs(result, data[dataset.KEY_OS]); 398 | updateVersion(result, term); 399 | return true; 400 | } 401 | } 402 | if (ua.indexOf('WILLCOM') >= 0 || ua.indexOf('DDIPOCKET') >= 0) { 403 | if ((match = /(?:WILLCOM|DDIPOCKET);[^\/]+\/([^ \/;()]+)/.exec(ua))) { 404 | term = match[1]; 405 | data = dataset.get('willcom'); 406 | updateCategory(result, data[dataset.KEY_CATEGORY]); 407 | updateOs(result, data[dataset.KEY_OS]); 408 | updateVersion(result, term); 409 | return true; 410 | } 411 | } 412 | if (ua.indexOf('SymbianOS') >= 0) { 413 | data = dataset.get('SymbianOS'); 414 | updateCategory(result, data[dataset.KEY_CATEGORY]); 415 | updateOs(result, data[dataset.KEY_OS]); 416 | return true; 417 | } 418 | if (ua.indexOf('Google Wireless Transcoder') >= 0) { 419 | updateMap(result, dataset.get('MobileTranscoder')); 420 | updateVersion(result, 'Google'); 421 | return true; 422 | } 423 | if (ua.indexOf('Naver Transcoder') >= 0) { 424 | updateMap(result, dataset.get('MobileTranscoder')); 425 | updateVersion(result, 'Naver'); 426 | return true; 427 | } 428 | return false; 429 | }; 430 | var challengeAppliance = exports.challengeAppliance = function(ua, result) { 431 | var data; 432 | if (ua.indexOf('Nintendo DSi;') >= 0) { 433 | data = dataset.get('NintendoDSi'); 434 | updateCategory(result, data[dataset.KEY_CATEGORY]); 435 | updateOs(result, data[dataset.KEY_OS]); 436 | return true; 437 | } 438 | if (ua.indexOf('Nintendo Wii;') >= 0) { 439 | data = dataset.get('NintendoWii'); 440 | updateCategory(result, data[dataset.KEY_CATEGORY]); 441 | updateOs(result, data[dataset.KEY_OS]); 442 | return true; 443 | } 444 | return false; 445 | }; 446 | var challengeMisc = exports.challengeMisc = function(ua, result) { 447 | var data; 448 | var osVersion; 449 | var match; 450 | if (ua.indexOf('(Win98;') >= 0) { 451 | data = dataset.get('Win98'); 452 | osVersion = "98"; 453 | } else if (ua.indexOf('Macintosh; U; PPC;') >= 0) { 454 | data = dataset.get('MacOS'); 455 | if ((match = /rv:(\d+\.\d+\.\d+)/.exec(ua))) 456 | osVersion = match[1]; 457 | } 458 | else if (ua.indexOf('Mac_PowerPC') >= 0) data = dataset.get('MacOS'); 459 | else if (ua.indexOf('X11; FreeBSD ') >= 0) { 460 | data = dataset.get('BSD'); 461 | if ((match = /FreeBSD ([^;\)]+);/.exec(ua))) 462 | osVersion = match[1]; 463 | } 464 | else if (ua.indexOf('X11; CrOS ') >= 0) { 465 | data = dataset.get('ChromeOS'); 466 | if ((match = /CrOS ([^\)]+)\)/.exec(ua))) 467 | osVersion = match[1]; 468 | } 469 | if (data) { 470 | updateCategory(result, data[dataset.KEY_CATEGORY]); 471 | updateOs(result, data[dataset.KEY_NAME]); 472 | if (osVersion) 473 | updateOsVersion(result, osVersion); 474 | return true; 475 | } 476 | return false; 477 | }; 478 | 479 | })(); 480 | var mobilephone = {}; 481 | (function(){ 482 | var exports = mobilephone; 483 | /* CODE: mobilephone.js */ 484 | var docomoPattern = /DoCoMo\/[.0-9]+[ \/]([^- \/;()"']+)/; 485 | var fomaPattern = /\(([^;)]+);FOMA;/; 486 | var challengeDocomo = exports.challengeDocomo = function(ua, result) { 487 | if (ua.indexOf('DoCoMo') < 0 && ua.indexOf(';FOMA;') < 0) 488 | return false; 489 | var version = dataset.VALUE_UNKNOWN; 490 | var match; 491 | if ((match = docomoPattern.exec(ua))) { 492 | version = match[1]; 493 | } else if ((match = fomaPattern.exec(ua))) { 494 | version = match[1]; 495 | } 496 | updateMap(result, dataset.get('docomo')); 497 | updateVersion(result, version); 498 | return true; 499 | }; 500 | var kddiPattern = /KDDI-([^- \/;()"']+)/; 501 | var challengeAu = exports.challengeAu = function(ua, result) { 502 | if (ua.indexOf('KDDI-') < 0) 503 | return false; 504 | var version = dataset.VALUE_UNKNOWN; 505 | var match = kddiPattern.exec(ua); 506 | if (match) 507 | version = match[1]; 508 | updateMap(result, dataset.get('au')); 509 | updateVersion(result, version); 510 | return true; 511 | }; 512 | var softbankPattern = /(?:SoftBank|Vodafone|J-PHONE)\/[.0-9]+\/([^ \/;()]+)/; 513 | var challengeSoftbank = exports.challengeSoftbank = function(ua, result) { 514 | if (ua.indexOf('SoftBank') < 0 && ua.indexOf('Vodafone') < 0 && ua.indexOf('J-PHONE') < 0) 515 | return false; 516 | var version = dataset.VALUE_UNKNOWN; 517 | var match = softbankPattern.exec(ua); 518 | if (match) 519 | version = match[1]; 520 | updateMap(result, dataset.get('SoftBank')); 521 | updateVersion(result, version); 522 | return true; 523 | }; 524 | var willcomPattern = /(?:WILLCOM|DDIPOCKET);[^\/]+\/([^ \/;()]+)/; 525 | var challengeWillcom = exports.challengeWillcom = function(ua, result) { 526 | if (ua.indexOf('WILLCOM') < 0 && ua.indexOf('DDIPOCKET') < 0) 527 | return false; 528 | var version = dataset.VALUE_UNKNOWN; 529 | var match = willcomPattern.exec(ua); 530 | if (match) 531 | version = match[1]; 532 | updateMap(result, dataset.get('willcom')); 533 | updateVersion(result, version); 534 | return true; 535 | }; 536 | var challengeMisc = exports.challengeMisc = function(ua, result) { 537 | var match; 538 | if (ua.indexOf('jig browser') >= 0) { 539 | updateMap(result, dataset.get('jig')); 540 | if ((match = /jig browser[^;]+; ([^);]+)/.exec(ua))) { 541 | updateVersion(result, match[1]); 542 | } 543 | return true; 544 | } 545 | if (ua.indexOf('emobile/') >= 0 || ua.indexOf('OpenBrowser') >= 0 || ua.indexOf('Browser/Obigo-Browser') >= 0) { 546 | updateMap(result, dataset.get('emobile')); 547 | return true; 548 | } 549 | if (ua.indexOf('SymbianOS') >= 0) { 550 | updateMap(result, dataset.get('SymbianOS')); 551 | return true; 552 | } 553 | if (ua.indexOf('Hatena-Mobile-Gateway/') >= 0) { 554 | updateMap(result, dataset.get('MobileTranscoder')); 555 | updateVersion(result, 'Hatena'); 556 | return true; 557 | } 558 | if (ua.indexOf('livedoor-Mobile-Gateway/') >= 0) { 559 | updateMap(result, dataset.get('MobileTranscoder')); 560 | updateVersion(result, 'livedoor'); 561 | return true; 562 | } 563 | return false; 564 | }; 565 | 566 | })(); 567 | var crawler = {}; 568 | (function(){ 569 | var exports = crawler; 570 | /* CODE: crawler.js */ 571 | var challengeGoogle = exports.challengeGoogle = function(ua, result) { 572 | if (ua.indexOf('Google') < 0) 573 | return false; 574 | if (ua.indexOf('compatible; Googlebot') >= 0) { 575 | if (ua.indexOf('compatible; Googlebot-Mobile') >= 0) { 576 | updateMap(result, dataset.get('GoogleBotMobile')); 577 | return true; 578 | } else { 579 | updateMap(result, dataset.get('GoogleBot')); 580 | return true; 581 | } 582 | } 583 | if (ua.indexOf('Googlebot-Image/') >= 0) { 584 | updateMap(result, dataset.get('GoogleBot')); 585 | return true; 586 | } 587 | if (ua.indexOf('Mediapartners-Google') >= 0) { 588 | if (ua.indexOf('compatible; Mediapartners-Google') >= 0 || ua === 'Mediapartners-Google') { 589 | updateMap(result, dataset.get('GoogleMediaPartners')); 590 | return true; 591 | } 592 | } 593 | if (ua.indexOf('Feedfetcher-Google;') >= 0) { 594 | updateMap(result, dataset.get('GoogleFeedFetcher')); 595 | return true; 596 | } 597 | if (ua.indexOf('AppEngine-Google') >= 0) { 598 | updateMap(result, dataset.get('GoogleAppEngine')); 599 | return true; 600 | } 601 | if (ua.indexOf('Google Web Preview') >= 0) { 602 | updateMap(result, dataset.get('GoogleWebPreview')); 603 | return true; 604 | } 605 | return false; 606 | }; 607 | var challengeCrawlers = exports.challengeCrawlers = function(ua, result) { 608 | if (ua.indexOf('Yahoo') >= 0 609 | || ua.indexOf('help.yahoo.co.jp/help/jp/') >= 0 610 | || ua.indexOf('listing.yahoo.co.jp/support/faq/') >= 0) { 611 | if (ua.indexOf('compatible; Yahoo! Slurp') >= 0) { 612 | updateMap(result, dataset.get('YahooSlurp')); 613 | return true; 614 | } 615 | if (ua.indexOf('YahooFeedSeekerJp') >= 0 || ua.indexOf('YahooFeedSeekerBetaJp') >= 0) { 616 | updateMap(result, dataset.get('YahooJP')); 617 | return true; 618 | } 619 | if (ua.indexOf('crawler (http://listing.yahoo.co.jp/support/faq/') >= 0 620 | || ua.indexOf('crawler (http://help.yahoo.co.jp/help/jp/') >= 0) { 621 | updateMap(result, dataset.get('YahooJP')); 622 | return true; 623 | } 624 | if (ua.indexOf('Y!J-BRZ/YATSHA crawler') >= 0 625 | || ua.indexOf('Y!J-BRY/YATSH crawler') >= 0) { 626 | updateMap(result, dataset.get('YahooJP')); 627 | return true; 628 | } 629 | if (ua.indexOf('Yahoo Pipes') >= 0) { 630 | updateMap(result, dataset.get('YahooPipes')); 631 | return true; 632 | } 633 | } 634 | if (ua.indexOf('msnbot') >= 0) { 635 | updateMap(result, dataset.get('msnbot')); 636 | return true; 637 | } 638 | if (ua.indexOf('bingbot') >= 0) { 639 | if (ua.indexOf('compatible; bingbot') >= 0) { 640 | updateMap(result, dataset.get('bingbot')); 641 | return true; 642 | } 643 | } 644 | if (ua.indexOf('BingPreview') >= 0) { 645 | updateMap(result, dataset.get('BingPreview')); 646 | return true; 647 | } 648 | if (ua.indexOf('Baidu') >= 0) { 649 | if (ua.indexOf('compatible; Baiduspider') >= 0 || 650 | ua.indexOf('Baiduspider+') >= 0 || 651 | ua.indexOf('Baiduspider-image+') >= 0) { 652 | updateMap(result, dataset.get('Baiduspider')); 653 | return true; 654 | } 655 | } 656 | if (ua.indexOf('Yeti') >= 0) { 657 | if (ua.indexOf('http://help.naver.com/robots') >= 0 || 658 | ua.indexOf('http://help.naver.com/support/robots.html') >= 0 || 659 | ua.indexOf('http://naver.me/bot') >= 0) { 660 | updateMap(result, dataset.get('Yeti')); 661 | return true; 662 | } 663 | } 664 | if (ua.indexOf('FeedBurner/') >= 0) { 665 | updateMap(result, dataset.get('FeedBurner')); 666 | return true; 667 | } 668 | if (ua.indexOf('facebookexternalhit') >= 0) { 669 | updateMap(result, dataset.get('facebook')); 670 | return true; 671 | } 672 | if (ua.indexOf('Twitterbot/') >= 0) { 673 | updateMap(result, dataset.get('twitter')); 674 | return true; 675 | } 676 | if (ua.indexOf('ichiro') >= 0) { 677 | if (ua.indexOf('http://help.goo.ne.jp/door/crawler.html') >= 0 || 678 | ua.indexOf('compatible; ichiro/mobile goo;') >= 0) { 679 | updateMap(result, dataset.get('goo')); 680 | return true; 681 | } 682 | } 683 | if (ua.indexOf('gooblogsearch/') >= 0) { 684 | updateMap(result, dataset.get('goo')); 685 | return true; 686 | } 687 | if (ua.indexOf('Apple-PubSub') >= 0) { 688 | updateMap(result, dataset.get('ApplePubSub')); 689 | return true; 690 | } 691 | if (ua.indexOf('(www.radian6.com/crawler)') >= 0) { 692 | updateMap(result, dataset.get('radian6')); 693 | return true; 694 | } 695 | if (ua.indexOf('Genieo/') >= 0) { 696 | updateMap(result, dataset.get('Genieo')); 697 | return true; 698 | } 699 | if (ua.indexOf('labs.topsy.com/butterfly/') >= 0) { 700 | updateMap(result, dataset.get('topsyButterfly')); 701 | return true; 702 | } 703 | if (ua.indexOf('rogerbot/1.0 (http://www.seomoz.org/dp/rogerbot') >= 0) { 704 | updateMap(result, dataset.get('rogerbot')); 705 | return true; 706 | } 707 | if (ua.indexOf('compatible; AhrefsBot/') >= 0) { 708 | updateMap(result, dataset.get('AhrefsBot')); 709 | return true; 710 | } 711 | if (ua.indexOf('livedoor FeedFetcher') >= 0 || ua.indexOf('Fastladder FeedFetcher') >= 0) { 712 | updateMap(result, dataset.get('livedoorFeedFetcher')); 713 | return true; 714 | } 715 | if (ua.indexOf('Hatena ') >= 0) { 716 | if (ua.indexOf('Hatena Antenna') >= 0 || 717 | ua.indexOf('Hatena Pagetitle Agent') >= 0 || 718 | ua.indexOf('Hatena Diary RSS') >= 0) { 719 | updateMap(result, dataset.get('Hatena')); 720 | return true; 721 | } 722 | } 723 | if (ua.indexOf('mixi-check') >= 0 || 724 | ua.indexOf('mixi-crawler') >= 0 || 725 | ua.indexOf('mixi-news-crawler') >= 0) { 726 | updateMap(result, dataset.get('mixi')); 727 | return true; 728 | } 729 | if (ua.indexOf('Indy Library') >= 0) { 730 | if (ua.indexOf('compatible; Indy Library') >= 0) { 731 | updateMap(result, dataset.get('IndyLibrary')); 732 | return true; 733 | } 734 | } 735 | if (ua.indexOf('trendictionbot') >= 0) { 736 | updateMap(result, dataset.get('trendictionbot')); 737 | return true; 738 | } 739 | return false; 740 | }; 741 | var challengeMaybeCrawler = exports.challengeMaybeCrawler = function(ua, result) { 742 | if (/(bot|crawler|spider)(?:[-_ .\/;@()]|$)/i.exec(ua)) { 743 | updateMap(result, dataset.get('VariousCrawler')); 744 | return true; 745 | } 746 | if (/(?:Rome Client |UnwindFetchor\/|ia_archiver |Summify |PostRank\/)/.exec(ua) || 747 | ua.indexOf('ASP-Ranker Feed Crawler') >= 0) { 748 | updateMap(result, dataset.get('VariousCrawler')); 749 | return true; 750 | } 751 | if (/(feed|web) ?parser/i.exec(ua)) { 752 | updateMap(result, dataset.get('VariousCrawler')); 753 | return true; 754 | } 755 | if (/watch ?dog/i.exec(ua)) { 756 | updateMap(result, dataset.get('VariousCrawler')); 757 | return true; 758 | } 759 | return false; 760 | }; 761 | 762 | })(); 763 | var appliance = {}; 764 | (function(){ 765 | var exports = appliance; 766 | /* CODE: appliance.js */ 767 | var challengePlaystation = exports.challengePlaystation = function(ua, result) { 768 | var data = null; 769 | var os_version = null; 770 | var match; 771 | if (ua.indexOf('PSP (PlayStation Portable);') >= 0) { 772 | data = dataset.get('PSP'); 773 | if ((match = /PSP \(PlayStation Portable\); ([.0-9]+)\)/.exec(ua))) 774 | os_version = match[1]; 775 | } else if (ua.indexOf('PlayStation Vita') >= 0) { 776 | data = dataset.get('PSVita'); 777 | if ((match = /PlayStation Vita ([.0-9]+)\)/.exec(ua))) 778 | os_version = match[1]; 779 | } else if (ua.indexOf('PLAYSTATION 3 ') >= 0 || ua.indexOf('PLAYSTATION 3;') >= 0) { 780 | data = dataset.get('PS3'); 781 | if ((match = /PLAYSTATION 3;? ([.0-9]+)\)/.exec(ua))) 782 | os_version = match[1]; 783 | } else if (ua.indexOf('PlayStation 4 ') >= 0) { 784 | data = dataset.get('PS4'); 785 | if ((match = /PlayStation 4 ([.0-9]+)\)/.exec(ua))) 786 | os_version = match[1]; 787 | } 788 | if (! data) 789 | return false; 790 | updateMap(result, data); 791 | if (os_version) 792 | updateOsVersion(result, os_version); 793 | return true; 794 | }; 795 | var challengeNintendo = exports.challengeNintendo = function(ua, result) { 796 | var data = null; 797 | if (ua.indexOf('Nintendo 3DS;') >= 0) data = dataset.get('Nintendo3DS'); 798 | else if (ua.indexOf('Nintendo DSi;') >= 0) data = dataset.get('NintendoDSi'); 799 | else if (ua.indexOf('Nintendo Wii;') >= 0) data = dataset.get('NintendoWii'); 800 | else if (ua.indexOf('(Nintendo WiiU)') >= 0) data = dataset.get('NintendoWiiU'); 801 | if (! data) 802 | return false; 803 | updateMap(result, data); 804 | return true; 805 | }; 806 | var challengeDigitalTV = exports.challengeDigitalTV = function(ua, result) { 807 | var data = null; 808 | if (ua.indexOf('InettvBrowser/') >= 0) data = dataset.get('DigitalTV'); 809 | if (! data) 810 | return false; 811 | updateMap(result, data); 812 | return true; 813 | }; 814 | 815 | })(); 816 | var misc = {}; 817 | (function(){ 818 | var exports = misc; 819 | /* CODE: misc.js */ 820 | var challengeDesktopTools = exports.challengeDesktopTools = function(ua, result){ 821 | var data = null; 822 | if (ua.indexOf('AppleSyndication/') >= 0) data = dataset.get('SafariRSSReader'); 823 | else if (ua.indexOf('compatible; Google Desktop/') >= 0) data = dataset.get('GoogleDesktop'); 824 | else if (ua.indexOf('Windows-RSS-Platform') >= 0) data = dataset.get('WindowsRSSReader'); 825 | if (!data) 826 | return false; 827 | updateMap(result, data); 828 | return true; 829 | }; 830 | var challengeSmartPhonePatterns = exports.challengeSmartPhonePatterns = function(ua, result){ 831 | var data; 832 | if (ua.indexOf('CFNetwork/') >= 0) { 833 | data = dataset.get('iOS'); 834 | updateCategory(result, data[dataset.KEY_CATEGORY]); 835 | updateOs(result, data[dataset.KEY_NAME]); 836 | return true; 837 | } 838 | return false; 839 | }; 840 | var challengeHTTPLibrary = exports.challengeHTTPLibrary = function(ua, result){ 841 | var data,version; 842 | if (/^(?:Apache-HttpClient\/|Jakarta Commons-HttpClient\/|Java\/)/.exec(ua) || /[- ]HttpClient(\/|$)/.exec(ua)) { 843 | data = dataset.get('HTTPLibrary'); version = 'Java'; 844 | } else if (ua.indexOf('Java(TM) 2 Runtime Environment,') >= 0) { 845 | data = dataset.get('HTTPLibrary'); version = 'Java'; 846 | } else if (/^Wget/.exec(ua)) { 847 | data = dataset.get('HTTPLibrary'); version = 'wget'; 848 | } else if (/^(?:libwww-perl|WWW-Mechanize|LWP::Simple|LWP |lwp-trivial)/.exec(ua)) { 849 | data = dataset.get('HTTPLibrary'); version = 'perl'; 850 | } else if (/^(?:Ruby|feedzirra|Typhoeus)/.exec(ua)) { 851 | data = dataset.get('HTTPLibrary'); version = 'ruby'; 852 | } else if (/^(?:Python-urllib\/|Twisted )/.exec(ua)) { 853 | data = dataset.get('HTTPLibrary'); version = 'python'; 854 | } else if (/^(:?PHP|WordPress|CakePHP|PukiWiki|PECL::HTTP)(?:\/| |$)/.exec(ua) || /(?:PEAR |)HTTP_Request(?: class|2)/.exec(ua)) { 855 | data = dataset.get('HTTPLibrary'); version = 'php'; 856 | } else if (ua.indexOf('PEAR HTTP_Request class;') >= 0) { 857 | data = dataset.get('HTTPLibrary'); version = 'php'; 858 | } else if (ua.indexOf('curl/') >= 0) { 859 | data = dataset.get('HTTPLibrary'); version = 'curl'; 860 | } 861 | if (! data) 862 | return false; 863 | updateMap(result, data); 864 | updateVersion(result, version); 865 | return true; 866 | }; 867 | var challengeMaybeRSSReader = exports.challengeMaybeRSSReader = function(ua, result){ 868 | var data = null; 869 | if (/rss(?:reader|bar|[-_ \/;()]|[ +]*\/)/i.exec(ua) || /headline-reader/i.exec(ua)) { 870 | data = dataset.get('VariousRSSReader'); 871 | } 872 | else { 873 | if (ua.indexOf('cococ/') >= 0) 874 | data = dataset.get('VariousRSSReader'); 875 | } 876 | if (! data) 877 | return false; 878 | updateMap(result, data); 879 | return true; 880 | }; 881 | 882 | })(); 883 | var woothee = {}; 884 | (function(){ 885 | var exports = woothee; 886 | /* CODE: woothee.js */ 887 | var version = exports.VERSION = package_info.version; 888 | var parse = exports.parse = function(userAgent) { 889 | return fillResult(execParse(userAgent)); 890 | }; 891 | var isCrawler = exports.isCrawler = function(userAgent) { 892 | return !!userAgent && userAgent !== '-' && tryCrawler(userAgent, {}); 893 | }; 894 | function execParse(userAgent) { 895 | var result = {}; 896 | if (! userAgent || userAgent === '-') 897 | return result; 898 | if (tryCrawler(userAgent, result)) 899 | return result; 900 | if (tryBrowser(userAgent, result)) { 901 | if (tryOS(userAgent, result)) 902 | return result; 903 | else 904 | return result; 905 | } 906 | if (tryMobilePhone(userAgent, result)) 907 | return result; 908 | if (tryAppliance(userAgent, result)) 909 | return result; 910 | if (tryMisc(userAgent, result)) 911 | return result; 912 | // browser unknown, check os only 913 | if (tryOS(userAgent, result)) 914 | return result; 915 | if (tryRareCases(userAgent, result)) 916 | return result; 917 | return result; 918 | }; 919 | function tryCrawler(userAgent, result) { 920 | if (crawler.challengeGoogle(userAgent, result)) 921 | return true; 922 | if (crawler.challengeCrawlers(userAgent, result)) 923 | return true; 924 | return false; 925 | } 926 | function tryBrowser(userAgent, result) { 927 | if (browser.challengeMSIE(userAgent, result)) 928 | return true; 929 | if (browser.challengeVivaldi(userAgent, result)) 930 | return true; 931 | if (browser.challengeYandexBrowser(userAgent, result)) 932 | return true; 933 | if (browser.challengeSafariChrome(userAgent, result)) 934 | return true; 935 | if (browser.challengeFirefox(userAgent, result)) 936 | return true; 937 | if (browser.challengeOpera(userAgent, result)) 938 | return true; 939 | if (browser.challengeWebview(userAgent, result)) 940 | return true; 941 | return false; 942 | } 943 | function tryOS(userAgent, result) { 944 | if (os.challengeWindows(userAgent, result)) 945 | return true; 946 | // OSX PC and iOS devices (strict check) 947 | if (os.challengeOSX(userAgent, result)) 948 | return true; 949 | // Linux PC and Android 950 | if (os.challengeLinux(userAgent, result)) 951 | return true; 952 | // all useragents matches /(iPhone|iPad|iPod|Android|BlackBerry)/ 953 | if (os.challengeSmartPhone(userAgent, result)) 954 | return true; 955 | // mobile phones like KDDI-.* 956 | if (os.challengeMobilePhone(userAgent, result)) 957 | return true; 958 | // Nintendo DSi/Wii with Opera 959 | if (os.challengeAppliance(userAgent, result)) 960 | return true; 961 | if (os.challengeMisc(userAgent, result)) 962 | return true; 963 | return false; 964 | } 965 | function tryMobilePhone(userAgent, result) { 966 | if (mobilephone.challengeDocomo(userAgent, result)) 967 | return true; 968 | if (mobilephone.challengeAu(userAgent, result)) 969 | return true; 970 | if (mobilephone.challengeSoftbank(userAgent, result)) 971 | return true; 972 | if (mobilephone.challengeWillcom(userAgent, result)) 973 | return true; 974 | if (mobilephone.challengeMisc(userAgent, result)) 975 | return true; 976 | return false; 977 | } 978 | function tryAppliance(userAgent, result) { 979 | if (appliance.challengePlaystation(userAgent, result)) 980 | return true; 981 | if (appliance.challengeNintendo(userAgent, result)) 982 | return true; 983 | if (appliance.challengeDigitalTV(userAgent, result)) 984 | return true; 985 | return false; 986 | } 987 | function tryMisc(userAgent, result) { 988 | if (misc.challengeDesktopTools(userAgent, result)) 989 | return true; 990 | return false; 991 | } 992 | function tryRareCases(userAgent, result) { 993 | if (misc.challengeSmartPhonePatterns(userAgent, result)) 994 | return true; 995 | if (browser.challengeSleipnir(userAgent, result)) 996 | return true; 997 | if (misc.challengeHTTPLibrary(userAgent, result)) 998 | return true; 999 | if (misc.challengeMaybeRSSReader(userAgent, result)) 1000 | return true; 1001 | if (crawler.challengeMaybeCrawler(userAgent, result)) 1002 | return true; 1003 | return false; 1004 | } 1005 | var filled = {}; 1006 | filled[dataset.ATTRIBUTE_NAME] = dataset.VALUE_UNKNOWN; 1007 | filled[dataset.ATTRIBUTE_CATEGORY] = dataset.VALUE_UNKNOWN; 1008 | filled[dataset.ATTRIBUTE_OS] = dataset.VALUE_UNKNOWN; 1009 | filled[dataset.ATTRIBUTE_OS_VERSION] = dataset.VALUE_UNKNOWN; 1010 | filled[dataset.ATTRIBUTE_VERSION] = dataset.VALUE_UNKNOWN; 1011 | filled[dataset.ATTRIBUTE_VENDOR] = dataset.VALUE_UNKNOWN; 1012 | function fillResult(result) { 1013 | if (! result[dataset.ATTRIBUTE_NAME]) 1014 | result[dataset.ATTRIBUTE_NAME] = dataset.VALUE_UNKNOWN; 1015 | if (! result[dataset.ATTRIBUTE_CATEGORY]) 1016 | result[dataset.ATTRIBUTE_CATEGORY] = dataset.VALUE_UNKNOWN; 1017 | if (! result[dataset.ATTRIBUTE_OS]) 1018 | result[dataset.ATTRIBUTE_OS] = dataset.VALUE_UNKNOWN; 1019 | if (! result[dataset.ATTRIBUTE_OS_VERSION]) 1020 | result[dataset.ATTRIBUTE_OS_VERSION] = dataset.VALUE_UNKNOWN; 1021 | if (! result[dataset.ATTRIBUTE_VERSION]) 1022 | result[dataset.ATTRIBUTE_VERSION] = dataset.VALUE_UNKNOWN; 1023 | if (! result[dataset.ATTRIBUTE_VENDOR]) 1024 | result[dataset.ATTRIBUTE_VENDOR] = dataset.VALUE_UNKNOWN; 1025 | return result; 1026 | }; 1027 | 1028 | })(); 1029 | var updateMap = util.updateMap, 1030 | updateCategory = util.updateCategory, 1031 | updateOs = util.updateOs, 1032 | updateOsVersion = util.updateOsVersion, 1033 | updateVersion = util.updateVersion; 1034 | 1035 | // AMD / RequireJS 1036 | if (typeof define !== 'undefined' && define.amd) { 1037 | define('woothee', [], function () { 1038 | return woothee; 1039 | }); 1040 | } 1041 | // Node.js 1042 | else if (typeof module !== 'undefined' && module.exports) { 1043 | module.exports = woothee; 1044 | } 1045 | // included directly via