From 2338430b39e48ad7f2c8af9c141c0a08d7bde45a Mon Sep 17 00:00:00 2001 From: kgala Date: Thu, 22 Aug 2024 21:40:18 -0400 Subject: [PATCH 1/8] Currently the implementation advocates for caching to speed up version-range queries, I believe this is not neccesary, as you have to store more overhead. Currently we have O(|versions|) per query regardless of if a recent generate with the same browser/platform version_ranges provided. This is evident in the VersionRanges class filter method. I have modified the initialization module (where the generate method is) to dynamically initialize index maps at most once per type of browser/platform range specified. This initialization direcly modifies a index map field in the browsers/platforms modules which remains throughout generates. This reduces the time complexity from O(|versions|) per query to O(|versions|) on the first, and O(1) for the rest. This is to be expected for a ua-generator, as it is commonly used as a small piece of many webscraping algorithms. Changes: Initialize the relevant index map when the version_ranges feature is used. Use index-maps for faster version filtering. Provide error checking for version correctness instead of ignoring the error. This means creating a new exception for invalid version. A unit test of the functionality. (I also did some print statement tests) --- src/ua_generator/__init__.py | 25 ++++++++++++++++++- src/ua_generator/data/browsers/chrome.py | 15 +++++++++--- src/ua_generator/data/browsers/edge.py | 16 ++++++++++--- src/ua_generator/data/browsers/firefox.py | 17 +++++++++---- src/ua_generator/data/browsers/safari.py | 15 ++++++++++-- src/ua_generator/data/platforms/ios.py | 15 +++++++++--- src/ua_generator/data/platforms/linux.py | 19 +++++++++++++-- src/ua_generator/data/platforms/macos.py | 15 +++++++++--- src/ua_generator/data/platforms/windows.py | 16 +++++++++---- src/ua_generator/data/version.py | 1 - src/ua_generator/exceptions.py | 3 +++ tests/test_idx_map.py | 28 ++++++++++++++++++++++ tests/test_version_range.py | 9 +++---- 13 files changed, 162 insertions(+), 32 deletions(-) create mode 100644 tests/test_idx_map.py diff --git a/src/ua_generator/__init__.py b/src/ua_generator/__init__.py index dd5cc7f..a855ad0 100644 --- a/src/ua_generator/__init__.py +++ b/src/ua_generator/__init__.py @@ -3,13 +3,36 @@ Copyright: 2022-2024 Ekin Karadeniz (github.com/iamdual) License: Apache License 2.0 """ +#ua-generator/src/__init__.py import typing - from . import user_agent, options as _options +from src.ua_generator.data.browsers import chrome,firefox,edge,safari +from src.ua_generator.data.platforms import ios, macos, windows,linux +versions_idx_map = {"chrome":{}, 'edge':{},'safari':{},'firefox':{},'ios':{},'windows':{},'macos':{},'linux':{}} +""" +Initialize an index map containing a mapping of version to index in the original +respective versions array. Only occurs once upon the first generate using +options as a parameter. Verified with print statements. +""" +def initialize_idx_map(option): + global versions_idx_map + module = globals()[option] + for idx,val in enumerate(module.versions): + if(val.major not in versions_idx_map[option]): + versions_idx_map[option][val.major] = idx + module.versions_idx_map = versions_idx_map[option] def generate(device: typing.Union[tuple, str, None] = None, platform: typing.Union[tuple, str, None] = None, browser: typing.Union[tuple, str, None] = None, options: typing.Union[_options.Options, None] = None) -> user_agent.UserAgent: + global versions_idx_map + if options is not None and options.version_ranges is not None: + for option in options.version_ranges.keys(): + """initialize each index map for each browser/platform dynamically + and do this AT MOST across generates + for the same browser/platform where a version range is specified""" + if option in versions_idx_map and len(versions_idx_map[option]) == 0: + initialize_idx_map(option) return user_agent.UserAgent(device, platform, browser, options) diff --git a/src/ua_generator/data/browsers/chrome.py b/src/ua_generator/data/browsers/chrome.py index 59ae76e..b3df22b 100644 --- a/src/ua_generator/data/browsers/chrome.py +++ b/src/ua_generator/data/browsers/chrome.py @@ -8,7 +8,8 @@ from ..version import Version, ChromiumVersion, VersionRange from ...options import Options - +from ...exceptions import InvalidVersionError +#ua-generator/src/data/browsers/chrome.py # https://chromereleases.googleblog.com/search/label/Stable%20updates versions: List[ChromiumVersion] = [ ChromiumVersion(Version(major=100, minor=0, build=4896, patch=(0, 255))), @@ -40,12 +41,20 @@ ChromiumVersion(Version(major=127, minor=0, build=6533, patch=(0, 255))), ] +versions_idx_map = {} def get_version(options: Options) -> ChromiumVersion: if options.version_ranges is not None and 'chrome' in options.version_ranges: if type(options.version_ranges['chrome']) == VersionRange: - filtered = options.version_ranges['chrome'].filter(versions) - if type(filtered) == list and len(filtered) > 0: + version_range = options.version_ranges['chrome'] + if(version_range.min_version.major not in versions_idx_map): + raise InvalidVersionError("Invalid {} version {} specified, valid versions are {}-{}\n".format("chrome", version_range.min_version.major, versions[0].major, versions[-1].major)) + if(version_range.max_version.major not in versions_idx_map): + raise InvalidVersionError("Invalid {} version {} specified, valid versions are {}-{}\n".format("chrome", version_range.min_version.major, versions[0].major, versions[-1].major)) + min_idx = versions_idx_map[version_range.min_version.major] + max_idx = versions_idx_map[version_range.max_version.major]+1 + filtered = versions[min_idx:max_idx] + if len(filtered) > 0: return random.choice(filtered) weights = None diff --git a/src/ua_generator/data/browsers/edge.py b/src/ua_generator/data/browsers/edge.py index 44c1531..ea7b20c 100644 --- a/src/ua_generator/data/browsers/edge.py +++ b/src/ua_generator/data/browsers/edge.py @@ -8,7 +8,9 @@ from ..version import Version, ChromiumVersion, VersionRange from ...options import Options +from ...exceptions import InvalidVersionError +#ua-generator/src/data/browsers/edge.py # https://docs.microsoft.com/en-us/deployedge/microsoft-edge-release-schedule versions: List[ChromiumVersion] = [ ChromiumVersion(Version(major=100, minor=0, build=1185, patch=(0, 99))), @@ -42,13 +44,21 @@ ChromiumVersion(Version(major=128, minor=0, build=2739, patch=(0, 99))), ] +versions_idx_map = {} def get_version(options: Options) -> ChromiumVersion: if options.version_ranges is not None and 'edge' in options.version_ranges: if type(options.version_ranges['edge']) == VersionRange: - filtered = options.version_ranges['edge'].filter(versions) - if type(filtered) == list and len(filtered) > 0: - return random.choice(filtered) + version_range = options.version_ranges['edge'] + if(version_range.min_version.major not in versions_idx_map): + raise InvalidVersionError("Invalid {} version {} specified, valid versions are {}-{}\n".format("edge", version_range.min_version.major, versions[0].major, versions[-1].major)) + if(version_range.max_version.major not in versions_idx_map): + raise InvalidVersionError("Invalid {} version {} specified, valid versions are {}-{}\n".format("edge", version_range.min_version.major, versions[0].major, versions[-1].major)) + min_idx = versions_idx_map[version_range.min_version.major] + max_idx = versions_idx_map[version_range.max_version.major]+1 + filtered = versions[min_idx:max_idx] + if len(filtered) > 0: + return random.choice(filtered) weights = None if options.weighted_versions: diff --git a/src/ua_generator/data/browsers/firefox.py b/src/ua_generator/data/browsers/firefox.py index 798e67e..f24949e 100644 --- a/src/ua_generator/data/browsers/firefox.py +++ b/src/ua_generator/data/browsers/firefox.py @@ -8,7 +8,8 @@ from ..version import Version, VersionRange from ...options import Options - +from ...exceptions import InvalidVersionError +#ua-generator/src/data/browsers/firefox.py # https://www.mozilla.org/en-US/firefox/releases/ versions: List[Version] = [ Version(major=103, minor=0, build=(0, 2)), @@ -49,13 +50,21 @@ Version(major=129, minor=0, build=0), ] +versions_idx_map = {} def get_version(options: Options) -> Version: if options.version_ranges is not None and 'firefox' in options.version_ranges: if type(options.version_ranges['firefox']) == VersionRange: - filtered = options.version_ranges['firefox'].filter(versions) - if type(filtered) == list and len(filtered) > 0: - return random.choice(filtered) + version_range = options.version_ranges['firefox'] + if(version_range.min_version.major not in versions_idx_map): + raise InvalidVersionError("Invalid {} version {} specified, valid versions are {}-{}\n".format("firefox", version_range.min_version.major, versions[0].major, versions[-1].major)) + if(version_range.max_version.major not in versions_idx_map): + raise InvalidVersionError("Invalid {} version {} specified, valid versions are {}-{}\n".format("firefox", version_range.min_version.major, versions[0].major, versions[-1].major)) + min_idx = versions_idx_map[version_range.min_version.major] + max_idx = versions_idx_map[version_range.max_version.major]+1 + filtered = versions[min_idx:max_idx] + if len(filtered) > 0: + return random.choice(filtered) weights = None if options.weighted_versions: diff --git a/src/ua_generator/data/browsers/safari.py b/src/ua_generator/data/browsers/safari.py index aade8ad..abfa77c 100644 --- a/src/ua_generator/data/browsers/safari.py +++ b/src/ua_generator/data/browsers/safari.py @@ -1,3 +1,4 @@ +#ua-generator/src/data/browsers/safari.py """ Random User-Agent Copyright: 2022-2024 Ekin Karadeniz (github.com/iamdual) @@ -8,6 +9,7 @@ from ..version import Version, ChromiumVersion, VersionRange from ...options import Options +from ...exceptions import InvalidVersionError # https://developer.apple.com/documentation/safari-release-notes versions: List[ChromiumVersion] = [ @@ -21,13 +23,22 @@ ChromiumVersion(Version(major=17, minor=(0, 6)), webkit=Version(major=605, minor=1, build=15)), ] +versions_idx_map = {} def get_version(options: Options) -> ChromiumVersion: if options.version_ranges is not None and 'safari' in options.version_ranges: if type(options.version_ranges['safari']) == VersionRange: - filtered = options.version_ranges['safari'].filter(versions) - if type(filtered) == list and len(filtered) > 0: + version_range = options.version_ranges['safari'] + if(version_range.min_version.major not in versions_idx_map): + raise InvalidVersionError("Invalid {} version {} specified, valid versions are {}-{}\n".format("safari", version_range.min_version.major, versions[0].major, versions[-1].major)) + if(version_range.max_version.major not in versions_idx_map): + raise InvalidVersionError("Invalid {} version {} specified, valid versions are {}-{}\n".format("safari", version_range.min_version.major, versions[0].major, versions[-1].major)) + min_idx = versions_idx_map[version_range.min_version.major] + max_idx = versions_idx_map[version_range.max_version.major]+1 + filtered = versions[min_idx:max_idx] + if len(filtered) > 0: return random.choice(filtered) + weights = None if options.weighted_versions: diff --git a/src/ua_generator/data/platforms/ios.py b/src/ua_generator/data/platforms/ios.py index 803247c..a53cadd 100644 --- a/src/ua_generator/data/platforms/ios.py +++ b/src/ua_generator/data/platforms/ios.py @@ -8,7 +8,8 @@ from ..version import Version, VersionRange from ...options import Options - +from ...exceptions import InvalidVersionError +#ua-generator/src/data/platforms/ios.py # https://developer.apple.com/documentation/ios-ipados-release-notes # https://support.apple.com/en-us/HT201222 versions: List[Version] = [ @@ -38,12 +39,20 @@ Version(major=17, minor=5, build=(0, 1)), ] +versions_idx_map = {} def get_version(options: Options) -> Version: if options.version_ranges is not None and 'ios' in options.version_ranges: if type(options.version_ranges['ios']) == VersionRange: - filtered = options.version_ranges['ios'].filter(versions) - if type(filtered) == list and len(filtered) > 0: + version_range = options.version_ranges['ios'] + if(version_range.min_version.major not in versions_idx_map): + raise InvalidVersionError("Invalid {} version {} specified, valid versions are {}-{}\n".format("ios", version_range.min_version.major, versions[0].major, versions[-1].major)) + if(version_range.max_version.major not in versions_idx_map): + raise InvalidVersionError("Invalid {} version {} specified, valid versions are {}-{}\n".format("ios", version_range.min_version.major, versions[0].major, versions[-1].major)) + min_idx = versions_idx_map[version_range.min_version.major] + max_idx = versions_idx_map[version_range.max_version.major]+1 + filtered = versions[min_idx:max_idx] + if len(filtered) > 0: return random.choice(filtered) weights = None diff --git a/src/ua_generator/data/platforms/linux.py b/src/ua_generator/data/platforms/linux.py index 0a2f716..8543e81 100644 --- a/src/ua_generator/data/platforms/linux.py +++ b/src/ua_generator/data/platforms/linux.py @@ -6,9 +6,10 @@ import random from typing import List -from ..version import Version +from ..version import Version, VersionRange from ...options import Options - +from ...exceptions import InvalidVersionError +#ua-generator/src/data/browsers/linux.py # https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/refs/ versions: List[Version] = [ Version(major=5, minor=0, build=(0, 21)), @@ -41,8 +42,22 @@ Version(major=6, minor=7, build=(0, 5)), ] +versions_idx_map = {} def get_version(options: Options) -> Version: + if options.version_ranges is not None and 'linux' in options.version_ranges: + if type(options.version_ranges['linux']) == VersionRange: + version_range = options.version_ranges['linux'] + if(version_range.min_version.major not in versions_idx_map): + raise InvalidVersionError("Invalid {} version {} specified, valid versions are {}-{}\n".format("linux", version_range.min_version.major, versions[0].major, versions[-1].major)) + if(version_range.max_version.major not in versions_idx_map): + raise InvalidVersionError("Invalid {} version {} specified, valid versions are {}-{}\n".format("linux", version_range.min_version.major, versions[0].major, versions[-1].major)) + min_idx = versions_idx_map[version_range.min_version.major] + max_idx = versions_idx_map[version_range.max_version.major]+1 + filtered = versions[min_idx:max_idx] + if len(filtered) > 0: + return random.choice(filtered) + weights = None if options.weighted_versions: weights = [1.0] * len(versions) diff --git a/src/ua_generator/data/platforms/macos.py b/src/ua_generator/data/platforms/macos.py index e96c55a..0babd85 100644 --- a/src/ua_generator/data/platforms/macos.py +++ b/src/ua_generator/data/platforms/macos.py @@ -8,7 +8,8 @@ from ..version import Version, VersionRange from ...options import Options - +from ...exceptions import InvalidVersionError +#ua-generator/src/data/browsers/macos.py # User agent cap on macOS # https://groups.google.com/a/chromium.org/g/blink-dev/c/hAI4QoX6rEo/m/qQNPThr0AAAJ @@ -45,12 +46,20 @@ Version(major=14, minor=5, build=0), ] +versions_idx_map = {} def get_version(options: Options) -> Version: if options.version_ranges is not None and 'macos' in options.version_ranges: if type(options.version_ranges['macos']) == VersionRange: - filtered = options.version_ranges['macos'].filter(versions) - if type(filtered) == list and len(filtered) > 0: + version_range = options.version_ranges['macos'] + if(version_range.min_version.major not in versions_idx_map): + raise InvalidVersionError("Invalid {} version {} specified, valid versions are {}-{}\n".format("macos", version_range.min_version.major, versions[0].major, versions[-1].major)) + if(version_range.max_version.major not in versions_idx_map): + raise InvalidVersionError("Invalid {} version {} specified, valid versions are {}-{}\n".format("macos", version_range.min_version.major, versions[0].major, versions[-1].major)) + min_idx = versions_idx_map[version_range.min_version.major] + max_idx = versions_idx_map[version_range.max_version.major]+1 + filtered = versions[min_idx:max_idx] + if len(filtered) > 0: return random.choice(filtered) weights = None diff --git a/src/ua_generator/data/platforms/windows.py b/src/ua_generator/data/platforms/windows.py index 69873e9..c0f56d6 100644 --- a/src/ua_generator/data/platforms/windows.py +++ b/src/ua_generator/data/platforms/windows.py @@ -5,10 +5,10 @@ """ import random from typing import List - +from ...exceptions import InvalidVersionError from ..version import Version, WindowsVersion, VersionRange from ...options import Options - +#ua-generator/src/data/browsers/windows.py # https://learn.microsoft.com/en-us/windows/win32/sysinfo/operating-system-version # https://learn.microsoft.com/en-us/microsoft-edge/web-platform/how-to-detect-win11 versions: List[WindowsVersion] = [ @@ -19,12 +19,20 @@ WindowsVersion(Version(major=10, minor=0), ch_platform=Version(major=(13, 15))), ] +versions_idx_map = {} def get_version(options: Options) -> WindowsVersion: if options.version_ranges is not None and 'windows' in options.version_ranges: if type(options.version_ranges['windows']) == VersionRange: - filtered = options.version_ranges['windows'].filter(versions) - if type(filtered) == list and len(filtered) > 0: + version_range = options.version_ranges['windows'] + if(version_range.min_version.major not in versions_idx_map): + raise InvalidVersionError("Invalid {} version {} specified, valid versions are {}-{}\n".format("windows", version_range.min_version.major, versions[0].major, versions[-1].major)) + if(version_range.max_version.major not in versions_idx_map): + raise InvalidVersionError("Invalid {} version {} specified, valid versions are {}-{}\n".format("windows", version_range.min_version.major, versions[0].major, versions[-1].major)) + min_idx = versions_idx_map[version_range.min_version.major] + max_idx = versions_idx_map[version_range.max_version.major]+1 + filtered = versions[min_idx:max_idx] + if len(filtered) > 0: return random.choice(filtered) weights = None diff --git a/src/ua_generator/data/version.py b/src/ua_generator/data/version.py index f348560..d351aad 100644 --- a/src/ua_generator/data/version.py +++ b/src/ua_generator/data/version.py @@ -5,7 +5,6 @@ """ import random from typing import Union, List - from .. import exceptions diff --git a/src/ua_generator/exceptions.py b/src/ua_generator/exceptions.py index f07a12d..23a9ad1 100644 --- a/src/ua_generator/exceptions.py +++ b/src/ua_generator/exceptions.py @@ -11,3 +11,6 @@ class CannotGenerateError(Exception): class InvalidArgumentError(Exception): pass + +class InvalidVersionError(Exception): + pass diff --git a/tests/test_idx_map.py b/tests/test_idx_map.py new file mode 100644 index 0000000..42c629c --- /dev/null +++ b/tests/test_idx_map.py @@ -0,0 +1,28 @@ +import unittest + +import src.ua_generator as ua_generator +from src.ua_generator.options import Options +from src.ua_generator.data.version import VersionRange, Version +from src.ua_generator.data.browsers import chrome,safari,edge,firefox +from src.ua_generator.data.platforms import linux,ios,macos,windows +class TestIdxMap(unittest.TestCase): + def test_idx_maps_persist_on_browser_and_platform_modules_across_generates(self): + options = Options(version_ranges={ + "chrome": VersionRange(chrome.versions[0], chrome.versions[-1]), + "safari": VersionRange(safari.versions[0], safari.versions[-1]), + "edge": VersionRange(edge.versions[0], edge.versions[-1]), + "firefox": VersionRange(firefox.versions[0], firefox.versions[-1]), + "linux": VersionRange(linux.versions[0], linux.versions[-1]), + "ios": VersionRange(ios.versions[0], ios.versions[-1]), + "macos": VersionRange(macos.versions[0], macos.versions[-1]), + "windows": VersionRange(windows.versions[0], windows.versions[-1]), + }) + + for i in range(10000): + #if no indexing error is thrown, then pass + ua_generator.generate(options=options) + for i in ua_generator.versions_idx_map.keys(): + self.assertGreater(len(ua_generator.versions_idx_map[i]),0) + #ensures the index map persists + + diff --git a/tests/test_version_range.py b/tests/test_version_range.py index ac4c2c2..dc79f37 100644 --- a/tests/test_version_range.py +++ b/tests/test_version_range.py @@ -8,7 +8,7 @@ import src.ua_generator as ua_generator from src.ua_generator.data.version import Version, VersionRange from src.ua_generator.options import Options - +from src.ua_generator.exceptions import InvalidVersionError class TestVersionRange(unittest.TestCase): def test_version_range(self): @@ -115,11 +115,8 @@ def test_version_range_invalid(self): options = Options(version_ranges={ 'edge': VersionRange(min_version=edge_min, max_version=edge_max), }) - ua = ua_generator.generate(browser='edge', options=options) - self.assertTrue(ua.browser == 'edge') - self.assertIsNotNone(ua.generator.browser_version) - self.assertFalse(edge_min <= ua.generator.browser_version.major <= edge_max) - + with self.assertRaises(InvalidVersionError): + ua = ua_generator.generate(browser='edge', options=options) if __name__ == '__main__': unittest.main() From 0dc7c0f07e3074bb2ede323ad4d8b936a36e3485 Mon Sep 17 00:00:00 2001 From: kgala Date: Mon, 26 Aug 2024 22:03:16 -0400 Subject: [PATCH 2/8] Add version_range support for android, add a get_versions feature, add appropriate unit tests --- src/ua_generator/__init__.py | 12 ++++++++-- .../data/platforms/android/android_nexus.py | 21 ++++++++++++++++-- .../data/platforms/android/android_pixel.py | 22 ++++++++++++++++--- .../data/platforms/android/android_samsung.py | 20 ++++++++++++++++- tests/test_get_versions.py | 18 +++++++++++++++ tests/test_idx_map.py | 6 +++-- 6 files changed, 89 insertions(+), 10 deletions(-) create mode 100644 tests/test_get_versions.py diff --git a/src/ua_generator/__init__.py b/src/ua_generator/__init__.py index a855ad0..ba540f5 100644 --- a/src/ua_generator/__init__.py +++ b/src/ua_generator/__init__.py @@ -8,8 +8,9 @@ from . import user_agent, options as _options from src.ua_generator.data.browsers import chrome,firefox,edge,safari from src.ua_generator.data.platforms import ios, macos, windows,linux - -versions_idx_map = {"chrome":{}, 'edge':{},'safari':{},'firefox':{},'ios':{},'windows':{},'macos':{},'linux':{}} +from src.ua_generator.data.platforms.android import android_nexus,android_samsung,android_pixel +from src.ua_generator.exceptions import InvalidArgumentError +versions_idx_map = {"chrome":{}, 'edge':{},'safari':{},'firefox':{},'ios':{},'windows':{},'macos':{},'linux':{},'android_samsung':{},'android_nexus':{},'android_pixel':{}} """ Initialize an index map containing a mapping of version to index in the original respective versions array. Only occurs once upon the first generate using @@ -23,6 +24,13 @@ def initialize_idx_map(option): versions_idx_map[option][val.major] = idx module.versions_idx_map = versions_idx_map[option] +def get_versions(option:str): + if option in versions_idx_map: + module = globals()[option] + return module.versions + else: + raise InvalidArgumentError("{} is not a valid browser/platform") + def generate(device: typing.Union[tuple, str, None] = None, platform: typing.Union[tuple, str, None] = None, browser: typing.Union[tuple, str, None] = None, diff --git a/src/ua_generator/data/platforms/android/android_nexus.py b/src/ua_generator/data/platforms/android/android_nexus.py index d0bfd52..f060d01 100644 --- a/src/ua_generator/data/platforms/android/android_nexus.py +++ b/src/ua_generator/data/platforms/android/android_nexus.py @@ -7,8 +7,9 @@ import string from typing import List -from ...version import Version, AndroidVersion +from ...version import Version, VersionRange, AndroidVersion from ....options import Options +from ....exceptions import InvalidVersionError # https://en.wikipedia.org/wiki/Android_version_history # https://source.android.com/setup/start/build-numbers @@ -33,10 +34,26 @@ ] platform_models = ('Nexus 5', 'Nexus 5X', 'Nexus 6', 'Nexus 6P', 'Nexus 9') - +versions_idx_map = {} def get_version(options: Options) -> AndroidVersion: weights = None + if options.version_ranges is not None and 'android_nexus' in options.version_ranges: + if type(options.version_ranges['android_nexus']) == VersionRange: + version_range = options.version_ranges['android_nexus'] + min_idx = 0 + max_idx = len(versions) + if(version_range.min_version is not None): + if(version_range.min_version.major not in versions_idx_map): + raise InvalidVersionError("Invalid {} version {} specified, valid versions are {}-{}\n".format("firefox", version_range.min_version.major, versions[0].major, versions[-1].major)) + min_idx = versions_idx_map[version_range.min_version.major] + if(version_range.max_version is not None): + if(version_range.max_version.major not in versions_idx_map): + raise InvalidVersionError("Invalid {} version {} specified, valid versions are {}-{}\n".format("firefox", version_range.min_version.major, versions[0].major, versions[-1].major)) + max_idx = versions_idx_map[version_range.max_version.major]+1 + filtered = versions[min_idx:max_idx] + if len(filtered) > 0: + return random.choice(filtered) if options.weighted_versions: weights = [1.0] * len(versions) weights[-1] = 10.0 diff --git a/src/ua_generator/data/platforms/android/android_pixel.py b/src/ua_generator/data/platforms/android/android_pixel.py index 6d7c02b..66d41d7 100644 --- a/src/ua_generator/data/platforms/android/android_pixel.py +++ b/src/ua_generator/data/platforms/android/android_pixel.py @@ -6,9 +6,9 @@ import random from typing import List -from ...version import Version, AndroidVersion +from ...version import Version,VersionRange, AndroidVersion from ....options import Options - +from ....exceptions import InvalidVersionError # https://en.wikipedia.org/wiki/Android_version_history # https://source.android.com/setup/start/build-numbers versions: List[AndroidVersion] = [ @@ -39,9 +39,25 @@ 'Pixel 4 XL', 'Pixel 4a (5G)', 'Pixel 5', 'Pixel 5a (5G)', 'Pixel 6', 'Pixel 6 Pro', 'Pixel 6a', 'Pixel 7', 'Pixel 7 Pro', 'Pixel 8', 'Pixel 8 Pro') - +versions_idx_map = {} def get_version(options: Options) -> AndroidVersion: weights = None + if options.version_ranges is not None and 'android_pixel' in options.version_ranges: + if type(options.version_ranges['android_pixel']) == VersionRange: + version_range = options.version_ranges['android_pixel'] + min_idx = 0 + max_idx = len(versions) + if(version_range.min_version is not None): + if(version_range.min_version.major not in versions_idx_map): + raise InvalidVersionError("Invalid {} version {} specified, valid versions are {}-{}\n".format("firefox", version_range.min_version.major, versions[0].major, versions[-1].major)) + min_idx = versions_idx_map[version_range.min_version.major] + if(version_range.max_version is not None): + if(version_range.max_version.major not in versions_idx_map): + raise InvalidVersionError("Invalid {} version {} specified, valid versions are {}-{}\n".format("firefox", version_range.min_version.major, versions[0].major, versions[-1].major)) + max_idx = versions_idx_map[version_range.max_version.major]+1 + filtered = versions[min_idx:max_idx] + if len(filtered) > 0: + return random.choice(filtered) if options.weighted_versions: weights = [1.0] * len(versions) weights[-1] = 10.0 diff --git a/src/ua_generator/data/platforms/android/android_samsung.py b/src/ua_generator/data/platforms/android/android_samsung.py index 8151a36..eb87f9d 100644 --- a/src/ua_generator/data/platforms/android/android_samsung.py +++ b/src/ua_generator/data/platforms/android/android_samsung.py @@ -6,8 +6,9 @@ import random from typing import List -from ...version import Version, AndroidVersion +from ...version import Version, VersionRange,AndroidVersion from ....options import Options +from ....exceptions import InvalidVersionError # https://en.wikipedia.org/wiki/Android_version_history # https://source.android.com/setup/start/build-numbers @@ -135,9 +136,26 @@ 'SM-G9980', 'SM-G9988', 'SM-G998B', 'SM-G998N', 'SM-G998X', 'SM-G998XU', 'SM-J730F', 'SM-M017F',) +versions_idx_map = {} def get_version(options: Options) -> AndroidVersion: weights = None + if options.version_ranges is not None and 'android_samsung' in options.version_ranges: + if type(options.version_ranges['android_samsung']) == VersionRange: + version_range = options.version_ranges['android_samsung'] + min_idx = 0 + max_idx = len(versions) + if(version_range.min_version is not None): + if(version_range.min_version.major not in versions_idx_map): + raise InvalidVersionError("Invalid {} version {} specified, valid versions are {}-{}\n".format("firefox", version_range.min_version.major, versions[0].major, versions[-1].major)) + min_idx = versions_idx_map[version_range.min_version.major] + if(version_range.max_version is not None): + if(version_range.max_version.major not in versions_idx_map): + raise InvalidVersionError("Invalid {} version {} specified, valid versions are {}-{}\n".format("firefox", version_range.min_version.major, versions[0].major, versions[-1].major)) + max_idx = versions_idx_map[version_range.max_version.major]+1 + filtered = versions[min_idx:max_idx] + if len(filtered) > 0: + return random.choice(filtered) if options.weighted_versions: weights = [1.0] * len(versions) weights[-1] = 10.0 diff --git a/tests/test_get_versions.py b/tests/test_get_versions.py new file mode 100644 index 0000000..10ab0ce --- /dev/null +++ b/tests/test_get_versions.py @@ -0,0 +1,18 @@ +import unittest + +import src.ua_generator as ua_generator +from src.ua_generator.options import Options +from src.ua_generator.data.version import VersionRange, Version +from src.ua_generator.exceptions import InvalidArgumentError +from src.ua_generator.data.browsers import chrome,safari,edge,firefox +from src.ua_generator.data.platforms import linux,ios,macos,windows +from src.ua_generator.data.platforms.android import android_nexus,android_samsung,android_pixel +class TestIdxMap(unittest.TestCase): + def test_get_versions(self): + options = ['chrome','safari','edge','firefox','macos','windows','ios','linux','android_nexus','android_samsung','android_pixel'] + for option in options: + module = globals()[option] + versions = ua_generator.get_versions(option) + self.assertEqual(module.versions,versions) + with self.assertRaises(InvalidArgumentError): + ua_generator.get_versions("invalid") \ No newline at end of file diff --git a/tests/test_idx_map.py b/tests/test_idx_map.py index 42c629c..4560e86 100644 --- a/tests/test_idx_map.py +++ b/tests/test_idx_map.py @@ -1,10 +1,10 @@ import unittest - import src.ua_generator as ua_generator from src.ua_generator.options import Options from src.ua_generator.data.version import VersionRange, Version from src.ua_generator.data.browsers import chrome,safari,edge,firefox from src.ua_generator.data.platforms import linux,ios,macos,windows +from src.ua_generator.data.platforms.android import android_nexus,android_samsung,android_pixel class TestIdxMap(unittest.TestCase): def test_idx_maps_persist_on_browser_and_platform_modules_across_generates(self): options = Options(version_ranges={ @@ -16,8 +16,10 @@ def test_idx_maps_persist_on_browser_and_platform_modules_across_generates(self) "ios": VersionRange(ios.versions[0], ios.versions[-1]), "macos": VersionRange(macos.versions[0], macos.versions[-1]), "windows": VersionRange(windows.versions[0], windows.versions[-1]), + "android_nexus": VersionRange(android_nexus.versions[0], android_nexus.versions[-1]), + "android_pixel": VersionRange(android_pixel.versions[0], android_pixel.versions[-1]), + "android_samsung": VersionRange(android_samsung.versions[0], android_samsung.versions[-1]), }) - for i in range(10000): #if no indexing error is thrown, then pass ua_generator.generate(options=options) From 3b315526fba42f06134357e2f5537306c724d5d4 Mon Sep 17 00:00:00 2001 From: kgala Date: Mon, 26 Aug 2024 22:04:55 -0400 Subject: [PATCH 3/8] Small bug fix --- src/ua_generator/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ua_generator/__init__.py b/src/ua_generator/__init__.py index ba540f5..7dc124b 100644 --- a/src/ua_generator/__init__.py +++ b/src/ua_generator/__init__.py @@ -29,7 +29,7 @@ def get_versions(option:str): module = globals()[option] return module.versions else: - raise InvalidArgumentError("{} is not a valid browser/platform") + raise InvalidArgumentError("{} is not a valid browser/platform".format(option)) def generate(device: typing.Union[tuple, str, None] = None, platform: typing.Union[tuple, str, None] = None, From e730906d99fee86ca63ae0f4a3ff2dda869abde5 Mon Sep 17 00:00:00 2001 From: kgala Date: Tue, 27 Aug 2024 11:36:02 -0400 Subject: [PATCH 4/8] Add support for android version ranges --- README.md | 2 +- src/ua_generator/data/__init__.py | 2 +- src/ua_generator/data/generator.py | 7 +++++++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2cf0685..11d5d1d 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ print(ua) # Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_3) AppleWebKit/604.1.38 ```python device = ('desktop', 'mobile') -platform = ('windows', 'macos', 'ios', 'linux', 'android') +platform = ('windows', 'macos', 'ios', 'linux', 'android','android_nexus','android_pixel','android_samsung') browser = ('chrome', 'edge', 'firefox', 'safari') ``` _All parameters are optional and multiple types can be specified using a tuple._ diff --git a/src/ua_generator/data/__init__.py b/src/ua_generator/data/__init__.py index afca546..dc9c604 100644 --- a/src/ua_generator/data/__init__.py +++ b/src/ua_generator/data/__init__.py @@ -6,7 +6,7 @@ DEVICES = ('desktop', 'mobile') -PLATFORMS = ('windows', 'macos', 'ios', 'linux', 'android') +PLATFORMS = ('windows', 'macos', 'ios', 'linux', 'android', 'android_nexus','android_pixel','android_samsung') PLATFORMS_DESKTOP = ('windows', 'macos', 'linux') # Platforms on desktop devices PLATFORMS_MOBILE = ('ios', 'android') # Platforms on mobile devices diff --git a/src/ua_generator/data/generator.py b/src/ua_generator/data/generator.py index fd75a83..509347c 100644 --- a/src/ua_generator/data/generator.py +++ b/src/ua_generator/data/generator.py @@ -5,6 +5,7 @@ """ from .browsers import chrome, safari, firefox, edge from .platforms import ios, android, linux, windows, macos +from .platforms.android import android_nexus,android_pixel,android_samsung from .. import utils, exceptions from ..options import Options @@ -31,6 +32,12 @@ def __platform_version(self): return linux.get_version(options=self.options) elif self.platform == 'android': return android.get_version(options=self.options) + elif self.platform == 'android_nexus': + return android_nexus.get_version(options=self.options) + elif self.platform == 'android_pixel': + return android_pixel.get_version(options=self.options) + elif self.platform == 'android_samsung': + return android_samsung.get_version(options=self.options) def __browser_version(self): if self.browser == 'chrome': From a6fd53de1ad376592948e57cae9a99065d0865d8 Mon Sep 17 00:00:00 2001 From: kgala Date: Fri, 30 Aug 2024 18:40:34 -0400 Subject: [PATCH 5/8] Fix bugs that when specific android platform were specified (e.g. android_pixel), wouldn't be recognized by generator and throw an error, this is an extension to allowing the user to specify a specific android platform or version. At the same time produce more verbose generator errors (this means adding to_string methods in Version, VersionRange, and Options). Finally add a constant ANDROIDS containing all the possible android platforms that can be specified, 'android' is still supported, and a specific platform will be chosen for the user --- src/ua_generator/data/__init__.py | 4 ++-- src/ua_generator/data/generator.py | 5 +++-- src/ua_generator/data/version.py | 8 +++++++- src/ua_generator/options.py | 8 ++++++++ tests/test_platform.py | 3 ++- 5 files changed, 22 insertions(+), 6 deletions(-) diff --git a/src/ua_generator/data/__init__.py b/src/ua_generator/data/__init__.py index dc9c604..fda3ce4 100644 --- a/src/ua_generator/data/__init__.py +++ b/src/ua_generator/data/__init__.py @@ -8,7 +8,7 @@ PLATFORMS = ('windows', 'macos', 'ios', 'linux', 'android', 'android_nexus','android_pixel','android_samsung') PLATFORMS_DESKTOP = ('windows', 'macos', 'linux') # Platforms on desktop devices -PLATFORMS_MOBILE = ('ios', 'android') # Platforms on mobile devices - +PLATFORMS_MOBILE = ('ios', 'android','android_nexus','android_pixel','android_samsung') # Platforms on mobile devices +ANDROIDS = ('android','android_nexus','android_pixel','android_samsung') BROWSERS = ('chrome', 'edge', 'firefox', 'safari') BROWSERS_SUPPORT_CH = ('chrome', 'edge') # Browsers that support Client Hints diff --git a/src/ua_generator/data/generator.py b/src/ua_generator/data/generator.py index 509347c..7e02777 100644 --- a/src/ua_generator/data/generator.py +++ b/src/ua_generator/data/generator.py @@ -7,6 +7,7 @@ from .platforms import ios, android, linux, windows, macos from .platforms.android import android_nexus,android_pixel,android_samsung from .. import utils, exceptions +from . import ANDROIDS from ..options import Options @@ -101,7 +102,7 @@ def __user_agent(self): template = template.replace('{firefox}', str(self.browser_version)) return template - elif self.platform == 'android': + elif self.platform in ANDROIDS: if self.browser == 'chrome': template = 'Mozilla/5.0 (Linux; Android {android}{model}{build}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/{chrome} Mobile Safari/{webkit}' template = template.replace('{android}', str(self.platform_version.major)) @@ -180,4 +181,4 @@ def __user_agent(self): template = template.replace('{firefox}', self.browser_version.format(partitions=2)) return template - raise exceptions.CannotGenerateError(self) + raise exceptions.CannotGenerateError("\n\nCould not generate UA with the following inputs:\nBrowser:{}\nPlatform:{}\nDevice:{}\nOptions:{}".format(self.browser,self.platform,self.device,self.options.to_string())) diff --git a/src/ua_generator/data/version.py b/src/ua_generator/data/version.py index 39147f4..5ba949f 100644 --- a/src/ua_generator/data/version.py +++ b/src/ua_generator/data/version.py @@ -26,7 +26,9 @@ def __init__(self, (major, minor, build, patch) ) self.__tuple = None - + def to_string(self) -> str: + return f"Version(major={self.major}, minor={self.minor}, build={self.build}, patch={self.patch})" + def format(self, partitions=None, separator='.', trim_zero=False) -> str: versions = [self.major, self.minor, self.build, self.patch] @@ -117,6 +119,10 @@ class VersionRange: def __init__(self, min_version: Union[Version, int] = None, max_version: Union[Version, int] = None): self.min_version = Version(major=min_version) if type(min_version) is int else min_version self.max_version = Version(major=max_version) if type(max_version) is int else max_version + def to_string(self) -> str: + min_version_str = self.min_version.to_string() if self.min_version else "None" + max_version_str = self.max_version.to_string() if self.max_version else "None" + return f"VersionRange(min_version={min_version_str}, max_version={max_version_str})" def filter(self, versions: List[Version]) -> List[Version]: tmp_versions: List[Version] = [] diff --git a/src/ua_generator/options.py b/src/ua_generator/options.py index 5b0512d..8bf77a5 100644 --- a/src/ua_generator/options.py +++ b/src/ua_generator/options.py @@ -16,3 +16,11 @@ def __init__(self, weighted_versions: bool = False, version_ranges: typing.Dict[ self.weighted_versions = weighted_versions if version_ranges is not None: self.version_ranges = version_ranges + def to_string(self) -> str: + # Stringify version_ranges if it exists + version_ranges_str = ( + {k: v.to_string() for k, v in self.version_ranges.items()} + if self.version_ranges is not None + else None + ) + return f"Options(weighted_versions={self.weighted_versions}, version_ranges={version_ranges_str})" diff --git a/tests/test_platform.py b/tests/test_platform.py index 70446ee..ce78f16 100644 --- a/tests/test_platform.py +++ b/tests/test_platform.py @@ -6,6 +6,7 @@ import unittest import src.ua_generator as ua_generator +from src.ua_generator.data import ANDROIDS class TestPlatform(unittest.TestCase): @@ -27,7 +28,7 @@ def test_platform_3(self): def test_platform_4(self): for i in range(0, 100): ua = ua_generator.generate(device='mobile') - self.assertTrue(ua.platform == 'ios' or ua.platform == 'android') + self.assertTrue(ua.platform == 'ios' or ua.platform in ANDROIDS) def test_platform_5(self): for i in range(0, 100): From eb4493cfe06b5ce862eb6e8585736104b7f720f7 Mon Sep 17 00:00:00 2001 From: kgala Date: Fri, 30 Aug 2024 19:05:14 -0400 Subject: [PATCH 6/8] Fix to_string methods, use __str__ already implemented --- src/ua_generator/data/generator.py | 2 +- src/ua_generator/data/version.py | 9 +++------ src/ua_generator/options.py | 4 ++-- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/ua_generator/data/generator.py b/src/ua_generator/data/generator.py index 7e02777..e201276 100644 --- a/src/ua_generator/data/generator.py +++ b/src/ua_generator/data/generator.py @@ -181,4 +181,4 @@ def __user_agent(self): template = template.replace('{firefox}', self.browser_version.format(partitions=2)) return template - raise exceptions.CannotGenerateError("\n\nCould not generate UA with the following inputs:\nBrowser:{}\nPlatform:{}\nDevice:{}\nOptions:{}".format(self.browser,self.platform,self.device,self.options.to_string())) + raise exceptions.CannotGenerateError("\n\nCould not generate UA with the following inputs:\nBrowser:{}\nPlatform:{}\nDevice:{}\nOptions:{}".format(self.browser,self.platform,self.device,str(self.options))) diff --git a/src/ua_generator/data/version.py b/src/ua_generator/data/version.py index 5ba949f..965a223 100644 --- a/src/ua_generator/data/version.py +++ b/src/ua_generator/data/version.py @@ -26,9 +26,6 @@ def __init__(self, (major, minor, build, patch) ) self.__tuple = None - def to_string(self) -> str: - return f"Version(major={self.major}, minor={self.minor}, build={self.build}, patch={self.patch})" - def format(self, partitions=None, separator='.', trim_zero=False) -> str: versions = [self.major, self.minor, self.build, self.patch] @@ -119,9 +116,9 @@ class VersionRange: def __init__(self, min_version: Union[Version, int] = None, max_version: Union[Version, int] = None): self.min_version = Version(major=min_version) if type(min_version) is int else min_version self.max_version = Version(major=max_version) if type(max_version) is int else max_version - def to_string(self) -> str: - min_version_str = self.min_version.to_string() if self.min_version else "None" - max_version_str = self.max_version.to_string() if self.max_version else "None" + def __str__(self) -> str: + min_version_str = str(self.min_version) if self.min_version else "None" + max_version_str = str(self.max_version) if self.max_version else "None" return f"VersionRange(min_version={min_version_str}, max_version={max_version_str})" def filter(self, versions: List[Version]) -> List[Version]: diff --git a/src/ua_generator/options.py b/src/ua_generator/options.py index 8bf77a5..5633166 100644 --- a/src/ua_generator/options.py +++ b/src/ua_generator/options.py @@ -16,10 +16,10 @@ def __init__(self, weighted_versions: bool = False, version_ranges: typing.Dict[ self.weighted_versions = weighted_versions if version_ranges is not None: self.version_ranges = version_ranges - def to_string(self) -> str: + def __str__(self) -> str: # Stringify version_ranges if it exists version_ranges_str = ( - {k: v.to_string() for k, v in self.version_ranges.items()} + {k: str(v) for k, v in self.version_ranges.items()} if self.version_ranges is not None else None ) From 10ac5757b20c089a6f7c2a771e3d304ce7e03d1c Mon Sep 17 00:00:00 2001 From: kgala Date: Sat, 31 Aug 2024 10:21:01 -0400 Subject: [PATCH 7/8] Fix major bug where versions per platform/browser are only randomized once at import, instead of across generates. Also include better type checking for version_ranges --- src/ua_generator/data/__init__.py | 4 +- src/ua_generator/data/browsers/chrome.py | 42 ++++++++-------- src/ua_generator/data/browsers/edge.py | 42 ++++++++-------- src/ua_generator/data/browsers/firefox.py | 44 ++++++++--------- src/ua_generator/data/browsers/safari.py | 42 ++++++++-------- .../data/platforms/android/android_nexus.py | 47 +++++++++--------- .../data/platforms/android/android_pixel.py | 47 +++++++++--------- .../data/platforms/android/android_samsung.py | 48 +++++++++---------- src/ua_generator/data/platforms/ios.py | 40 ++++++++-------- src/ua_generator/data/platforms/linux.py | 41 ++++++++-------- src/ua_generator/data/platforms/macos.py | 42 ++++++++-------- src/ua_generator/data/platforms/windows.py | 46 +++++++++--------- src/ua_generator/data/version.py | 34 ++++++++++--- src/ua_generator/user_agent.py | 14 ++++-- tests/test_idx_map.py | 30 ------------ 15 files changed, 285 insertions(+), 278 deletions(-) delete mode 100644 tests/test_idx_map.py diff --git a/src/ua_generator/data/__init__.py b/src/ua_generator/data/__init__.py index fda3ce4..d519602 100644 --- a/src/ua_generator/data/__init__.py +++ b/src/ua_generator/data/__init__.py @@ -5,10 +5,12 @@ """ DEVICES = ('desktop', 'mobile') - PLATFORMS = ('windows', 'macos', 'ios', 'linux', 'android', 'android_nexus','android_pixel','android_samsung') PLATFORMS_DESKTOP = ('windows', 'macos', 'linux') # Platforms on desktop devices PLATFORMS_MOBILE = ('ios', 'android','android_nexus','android_pixel','android_samsung') # Platforms on mobile devices +PLATFORMS_ANDROID = ('android_nexus','android_pixel','android_samsung') ANDROIDS = ('android','android_nexus','android_pixel','android_samsung') BROWSERS = ('chrome', 'edge', 'firefox', 'safari') +VERSION_SUPPORTED_MODULES = ('windows', 'macos', 'ios', 'linux', 'android_nexus','android_pixel','android_samsung','chrome', 'edge', 'firefox', 'safari') BROWSERS_SUPPORT_CH = ('chrome', 'edge') # Browsers that support Client Hints + diff --git a/src/ua_generator/data/browsers/chrome.py b/src/ua_generator/data/browsers/chrome.py index 551a6ac..afd4424 100644 --- a/src/ua_generator/data/browsers/chrome.py +++ b/src/ua_generator/data/browsers/chrome.py @@ -44,29 +44,29 @@ versions_idx_map = {} def get_version(options: Options) -> ChromiumVersion: + selected_version : ChromiumVersion if options.version_ranges is not None and 'chrome' in options.version_ranges: - if type(options.version_ranges['chrome']) == VersionRange: - version_range = options.version_ranges['chrome'] - min_idx = 0 - max_idx = len(versions) - if(version_range.min_version is not None): - if(version_range.min_version.major not in versions_idx_map): - raise InvalidVersionError("Invalid {} version {} specified, valid versions are {}-{}\n".format("firefox", version_range.min_version.major, versions[0].major, versions[-1].major)) - min_idx = versions_idx_map[version_range.min_version.major] - if(version_range.max_version is not None): - if(version_range.max_version.major not in versions_idx_map): - raise InvalidVersionError("Invalid {} version {} specified, valid versions are {}-{}\n".format("firefox", version_range.min_version.major, versions[0].major, versions[-1].major)) - max_idx = versions_idx_map[version_range.max_version.major]+1 - filtered = versions[min_idx:max_idx] - if len(filtered) > 0: - return random.choice(filtered) - - weights = None - if options.weighted_versions: + version_range = options.version_ranges['chrome'] + min_idx = 0 + max_idx = len(versions) + if(version_range.min_version is not None): + if(version_range.min_version.major not in versions_idx_map): + raise InvalidVersionError("Invalid {} version {} specified, valid versions are {}-{}\n".format("firefox", version_range.min_version.major, versions[0].major, versions[-1].major)) + min_idx = versions_idx_map[version_range.min_version.major] + if(version_range.max_version is not None): + if(version_range.max_version.major not in versions_idx_map): + raise InvalidVersionError("Invalid {} version {} specified, valid versions are {}-{}\n".format("firefox", version_range.min_version.major, versions[0].major, versions[-1].major)) + max_idx = versions_idx_map[version_range.max_version.major]+1 + filtered = versions[min_idx:max_idx] + if len(filtered) > 0: + selected_version = random.choice(filtered) + elif options.weighted_versions: weights = [1.0] * len(versions) weights[-1] = 10.0 weights[-2] = 9.0 weights[-3] = 8.0 - - choice: List[ChromiumVersion] = random.choices(versions, weights=weights, k=1) - return choice[0] + selected_version = random.choices(versions, weights=weights, k=1)[0] + else: + selected_version = random.choice(versions) + selected_version.get_version() + return selected_version diff --git a/src/ua_generator/data/browsers/edge.py b/src/ua_generator/data/browsers/edge.py index 6bc23e7..d9d39bb 100644 --- a/src/ua_generator/data/browsers/edge.py +++ b/src/ua_generator/data/browsers/edge.py @@ -47,29 +47,29 @@ versions_idx_map = {} def get_version(options: Options) -> ChromiumVersion: + selected_version : ChromiumVersion if options.version_ranges is not None and 'edge' in options.version_ranges: - if type(options.version_ranges['edge']) == VersionRange: - version_range = options.version_ranges['edge'] - min_idx = 0 - max_idx = len(versions) - if(version_range.min_version is not None): - if(version_range.min_version.major not in versions_idx_map): - raise InvalidVersionError("Invalid {} version {} specified, valid versions are {}-{}\n".format("firefox", version_range.min_version.major, versions[0].major, versions[-1].major)) - min_idx = versions_idx_map[version_range.min_version.major] - if(version_range.max_version is not None): - if(version_range.max_version.major not in versions_idx_map): - raise InvalidVersionError("Invalid {} version {} specified, valid versions are {}-{}\n".format("firefox", version_range.min_version.major, versions[0].major, versions[-1].major)) - max_idx = versions_idx_map[version_range.max_version.major]+1 - filtered = versions[min_idx:max_idx] - if len(filtered) > 0: - return random.choice(filtered) - - weights = None - if options.weighted_versions: + version_range = options.version_ranges['edge'] + min_idx = 0 + max_idx = len(versions) + if(version_range.min_version is not None): + if(version_range.min_version.major not in versions_idx_map): + raise InvalidVersionError("Invalid {} version {} specified, valid versions are {}-{}\n".format("firefox", version_range.min_version.major, versions[0].major, versions[-1].major)) + min_idx = versions_idx_map[version_range.min_version.major] + if(version_range.max_version is not None): + if(version_range.max_version.major not in versions_idx_map): + raise InvalidVersionError("Invalid {} version {} specified, valid versions are {}-{}\n".format("firefox", version_range.min_version.major, versions[0].major, versions[-1].major)) + max_idx = versions_idx_map[version_range.max_version.major]+1 + filtered = versions[min_idx:max_idx] + if len(filtered) > 0: + selected_version = random.choice(filtered) + elif options.weighted_versions: weights = [1.0] * len(versions) weights[-1] = 10.0 weights[-2] = 9.0 weights[-3] = 8.0 - - choice: List[ChromiumVersion] = random.choices(versions, weights=weights, k=1) - return choice[0] + selected_version = random.choices(versions, weights=weights, k=1)[0] + else: + selected_version = random.choice(versions) + selected_version.get_version() + return selected_version diff --git a/src/ua_generator/data/browsers/firefox.py b/src/ua_generator/data/browsers/firefox.py index 9ceed75..74bb7df 100644 --- a/src/ua_generator/data/browsers/firefox.py +++ b/src/ua_generator/data/browsers/firefox.py @@ -53,30 +53,30 @@ versions_idx_map = {} def get_version(options: Options) -> Version: + selected_version : Version if options.version_ranges is not None and 'firefox' in options.version_ranges: - if type(options.version_ranges['firefox']) == VersionRange: - version_range = options.version_ranges['firefox'] - min_idx = 0 - max_idx = len(versions) - if(version_range.min_version is not None): - if(version_range.min_version.major not in versions_idx_map): - raise InvalidVersionError("Invalid {} version {} specified, valid versions are {}-{}\n".format("firefox", version_range.min_version.major, versions[0].major, versions[-1].major)) - min_idx = versions_idx_map[version_range.min_version.major] - if(version_range.max_version is not None): - if(version_range.max_version.major not in versions_idx_map): - raise InvalidVersionError("Invalid {} version {} specified, valid versions are {}-{}\n".format("firefox", version_range.min_version.major, versions[0].major, versions[-1].major)) - max_idx = versions_idx_map[version_range.max_version.major]+1 - - filtered = versions[min_idx:max_idx] - if len(filtered) > 0: - return random.choice(filtered) - - weights = None - if options.weighted_versions: + version_range = options.version_ranges['firefox'] + min_idx = 0 + max_idx = len(versions) + if(version_range.min_version is not None): + if(version_range.min_version.major not in versions_idx_map): + raise InvalidVersionError("Invalid {} version {} specified, valid versions are {}-{}\n".format("firefox", version_range.min_version.major, versions[0].major, versions[-1].major)) + min_idx = versions_idx_map[version_range.min_version.major] + if(version_range.max_version is not None): + if(version_range.max_version.major not in versions_idx_map): + raise InvalidVersionError("Invalid {} version {} specified, valid versions are {}-{}\n".format("firefox", version_range.min_version.major, versions[0].major, versions[-1].major)) + max_idx = versions_idx_map[version_range.max_version.major]+1 + + filtered = versions[min_idx:max_idx] + if len(filtered) > 0: + selected_version = random.choice(filtered) + elif options.weighted_versions: weights = [1.0] * len(versions) weights[-1] = 10.0 weights[-2] = 9.0 weights[-3] = 8.0 - - choice: List[Version] = random.choices(versions, weights=weights, k=1) - return choice[0] + selected_version = random.choices(versions, weights=weights, k=1)[0] + else: + selected_version = random.choice(versions) + selected_version.get_version() + return selected_version diff --git a/src/ua_generator/data/browsers/safari.py b/src/ua_generator/data/browsers/safari.py index 707f920..3b13177 100644 --- a/src/ua_generator/data/browsers/safari.py +++ b/src/ua_generator/data/browsers/safari.py @@ -26,29 +26,29 @@ versions_idx_map = {} def get_version(options: Options) -> ChromiumVersion: + selected_version : ChromiumVersion if options.version_ranges is not None and 'safari' in options.version_ranges: - if type(options.version_ranges['safari']) == VersionRange: - version_range = options.version_ranges['safari'] - min_idx = 0 - max_idx = len(versions) - if(version_range.min_version is not None): - if(version_range.min_version.major not in versions_idx_map): - raise InvalidVersionError("Invalid {} version {} specified, valid versions are {}-{}\n".format("firefox", version_range.min_version.major, versions[0].major, versions[-1].major)) - min_idx = versions_idx_map[version_range.min_version.major] - if(version_range.max_version is not None): - if(version_range.max_version.major not in versions_idx_map): - raise InvalidVersionError("Invalid {} version {} specified, valid versions are {}-{}\n".format("firefox", version_range.min_version.major, versions[0].major, versions[-1].major)) - max_idx = versions_idx_map[version_range.max_version.major]+1 - filtered = versions[min_idx:max_idx] - if len(filtered) > 0: - return random.choice(filtered) + version_range = options.version_ranges['safari'] + min_idx = 0 + max_idx = len(versions) + if(version_range.min_version is not None): + if(version_range.min_version.major not in versions_idx_map): + raise InvalidVersionError("Invalid {} version {} specified, valid versions are {}-{}\n".format("firefox", version_range.min_version.major, versions[0].major, versions[-1].major)) + min_idx = versions_idx_map[version_range.min_version.major] + if(version_range.max_version is not None): + if(version_range.max_version.major not in versions_idx_map): + raise InvalidVersionError("Invalid {} version {} specified, valid versions are {}-{}\n".format("firefox", version_range.min_version.major, versions[0].major, versions[-1].major)) + max_idx = versions_idx_map[version_range.max_version.major]+1 + filtered = versions[min_idx:max_idx] + if len(filtered) > 0: + selected_version = random.choice(filtered) - - weights = None - if options.weighted_versions: + elif options.weighted_versions: weights = [1.0] * len(versions) weights[-1] = 10.0 weights[-2] = 9.0 - - choice: List[ChromiumVersion] = random.choices(versions, weights=weights, k=1) - return choice[0] + selected_version = random.choices(versions, weights=weights, k=1)[0] + else: + selected_version = random.choice(versions) + selected_version.get_version() + return selected_version diff --git a/src/ua_generator/data/platforms/android/android_nexus.py b/src/ua_generator/data/platforms/android/android_nexus.py index f060d01..e4db151 100644 --- a/src/ua_generator/data/platforms/android/android_nexus.py +++ b/src/ua_generator/data/platforms/android/android_nexus.py @@ -37,37 +37,38 @@ versions_idx_map = {} def get_version(options: Options) -> AndroidVersion: - weights = None + selected_version : AndroidVersion if options.version_ranges is not None and 'android_nexus' in options.version_ranges: - if type(options.version_ranges['android_nexus']) == VersionRange: - version_range = options.version_ranges['android_nexus'] - min_idx = 0 - max_idx = len(versions) - if(version_range.min_version is not None): - if(version_range.min_version.major not in versions_idx_map): - raise InvalidVersionError("Invalid {} version {} specified, valid versions are {}-{}\n".format("firefox", version_range.min_version.major, versions[0].major, versions[-1].major)) - min_idx = versions_idx_map[version_range.min_version.major] - if(version_range.max_version is not None): - if(version_range.max_version.major not in versions_idx_map): - raise InvalidVersionError("Invalid {} version {} specified, valid versions are {}-{}\n".format("firefox", version_range.min_version.major, versions[0].major, versions[-1].major)) - max_idx = versions_idx_map[version_range.max_version.major]+1 - filtered = versions[min_idx:max_idx] - if len(filtered) > 0: - return random.choice(filtered) - if options.weighted_versions: + version_range = options.version_ranges['android_nexus'] + min_idx = 0 + max_idx = len(versions) + if(version_range.min_version is not None): + if(version_range.min_version.major not in versions_idx_map): + raise InvalidVersionError("Invalid {} version {} specified, valid versions are {}-{}\n".format("firefox", version_range.min_version.major, versions[0].major, versions[-1].major)) + min_idx = versions_idx_map[version_range.min_version.major] + if(version_range.max_version is not None): + if(version_range.max_version.major not in versions_idx_map): + raise InvalidVersionError("Invalid {} version {} specified, valid versions are {}-{}\n".format("firefox", version_range.min_version.major, versions[0].major, versions[-1].major)) + max_idx = versions_idx_map[version_range.max_version.major]+1 + filtered = versions[min_idx:max_idx] + if len(filtered) > 0: + selected_version = random.choice(filtered) + elif options.weighted_versions: weights = [1.0] * len(versions) weights[-1] = 10.0 weights[-2] = 9.0 weights[-3] = 8.0 weights[-4] = 8.0 - choice: List[AndroidVersion] = random.choices(versions, weights=weights, k=1) - - build_number = choice[0].build_number + selected_version = random.choices(versions, weights=weights, k=1)[0] + else: + selected_version = random.choice(versions) + selected_version.get_version() + build_number = selected_version.build_number build_number = build_number.replace('{s}', '{}'.format(random.choice(string.ascii_uppercase))) build_number = build_number.replace('{d}', '{:02d}{:02d}{:02d}'.format(random.randint(17, 22), random.randint(0, 12), random.randint(0, 29))) build_number = build_number.replace('{v}', '{}'.format(random.randint(1, 255))) - choice[0].build_number = build_number - choice[0].platform_model = random.choice(platform_models) - return choice[0] + selected_version.build_number = build_number + selected_version.platform_model = random.choice(platform_models) + return selected_version diff --git a/src/ua_generator/data/platforms/android/android_pixel.py b/src/ua_generator/data/platforms/android/android_pixel.py index 66d41d7..ecfbbd6 100644 --- a/src/ua_generator/data/platforms/android/android_pixel.py +++ b/src/ua_generator/data/platforms/android/android_pixel.py @@ -41,35 +41,36 @@ versions_idx_map = {} def get_version(options: Options) -> AndroidVersion: - weights = None + selected_version : AndroidVersion if options.version_ranges is not None and 'android_pixel' in options.version_ranges: - if type(options.version_ranges['android_pixel']) == VersionRange: - version_range = options.version_ranges['android_pixel'] - min_idx = 0 - max_idx = len(versions) - if(version_range.min_version is not None): - if(version_range.min_version.major not in versions_idx_map): - raise InvalidVersionError("Invalid {} version {} specified, valid versions are {}-{}\n".format("firefox", version_range.min_version.major, versions[0].major, versions[-1].major)) - min_idx = versions_idx_map[version_range.min_version.major] - if(version_range.max_version is not None): - if(version_range.max_version.major not in versions_idx_map): - raise InvalidVersionError("Invalid {} version {} specified, valid versions are {}-{}\n".format("firefox", version_range.min_version.major, versions[0].major, versions[-1].major)) - max_idx = versions_idx_map[version_range.max_version.major]+1 - filtered = versions[min_idx:max_idx] - if len(filtered) > 0: - return random.choice(filtered) - if options.weighted_versions: + version_range = options.version_ranges['android_pixel'] + min_idx = 0 + max_idx = len(versions) + if(version_range.min_version is not None): + if(version_range.min_version.major not in versions_idx_map): + raise InvalidVersionError("Invalid {} version {} specified, valid versions are {}-{}\n".format("firefox", version_range.min_version.major, versions[0].major, versions[-1].major)) + min_idx = versions_idx_map[version_range.min_version.major] + if(version_range.max_version is not None): + if(version_range.max_version.major not in versions_idx_map): + raise InvalidVersionError("Invalid {} version {} specified, valid versions are {}-{}\n".format("firefox", version_range.min_version.major, versions[0].major, versions[-1].major)) + max_idx = versions_idx_map[version_range.max_version.major]+1 + filtered = versions[min_idx:max_idx] + if len(filtered) > 0: + selected_version = random.choice(filtered) + elif options.weighted_versions: weights = [1.0] * len(versions) weights[-1] = 10.0 weights[-2] = 9.0 weights[-3] = 8.0 - choice: List[AndroidVersion] = random.choices(versions, weights=weights, k=1) - - build_number = choice[0].build_number + selected_version = random.choices(versions, weights=weights, k=1)[0] + else: + selected_version = random.choice(versions) + selected_version.get_version() + build_number = selected_version.build_number build_number = build_number.replace('{d}', '{:02d}{:02d}{:02d}'.format(random.randint(17, 22), random.randint(0, 12), random.randint(0, 29))) build_number = build_number.replace('{v}', '{}'.format(random.randint(1, 255))) - choice[0].build_number = build_number - choice[0].platform_model = random.choice(platform_models) - return choice[0] + selected_version.build_number = build_number + selected_version.platform_model = random.choice(platform_models) + return selected_version diff --git a/src/ua_generator/data/platforms/android/android_samsung.py b/src/ua_generator/data/platforms/android/android_samsung.py index eb87f9d..805af2f 100644 --- a/src/ua_generator/data/platforms/android/android_samsung.py +++ b/src/ua_generator/data/platforms/android/android_samsung.py @@ -139,37 +139,37 @@ versions_idx_map = {} def get_version(options: Options) -> AndroidVersion: - weights = None + selected_version : AndroidVersion if options.version_ranges is not None and 'android_samsung' in options.version_ranges: - if type(options.version_ranges['android_samsung']) == VersionRange: - version_range = options.version_ranges['android_samsung'] - min_idx = 0 - max_idx = len(versions) - if(version_range.min_version is not None): - if(version_range.min_version.major not in versions_idx_map): - raise InvalidVersionError("Invalid {} version {} specified, valid versions are {}-{}\n".format("firefox", version_range.min_version.major, versions[0].major, versions[-1].major)) - min_idx = versions_idx_map[version_range.min_version.major] - if(version_range.max_version is not None): - if(version_range.max_version.major not in versions_idx_map): - raise InvalidVersionError("Invalid {} version {} specified, valid versions are {}-{}\n".format("firefox", version_range.min_version.major, versions[0].major, versions[-1].major)) - max_idx = versions_idx_map[version_range.max_version.major]+1 - filtered = versions[min_idx:max_idx] - if len(filtered) > 0: - return random.choice(filtered) - if options.weighted_versions: + version_range = options.version_ranges['android_samsung'] + min_idx = 0 + max_idx = len(versions) + if(version_range.min_version is not None): + if(version_range.min_version.major not in versions_idx_map): + raise InvalidVersionError("Invalid {} version {} specified, valid versions are {}-{}\n".format("firefox", version_range.min_version.major, versions[0].major, versions[-1].major)) + min_idx = versions_idx_map[version_range.min_version.major] + if(version_range.max_version is not None): + if(version_range.max_version.major not in versions_idx_map): + raise InvalidVersionError("Invalid {} version {} specified, valid versions are {}-{}\n".format("firefox", version_range.min_version.major, versions[0].major, versions[-1].major)) + max_idx = versions_idx_map[version_range.max_version.major]+1 + filtered = versions[min_idx:max_idx] + if len(filtered) > 0: + selected_version = random.choice(filtered) + elif options.weighted_versions: weights = [1.0] * len(versions) weights[-1] = 10.0 weights[-2] = 9.0 weights[-3] = 8.0 weights[-4] = 8.0 weights[-5] = 8.0 - - choice: List[AndroidVersion] = random.choices(versions, weights=weights, k=1) - - build_number = choice[0].build_number + selected_version = random.choices(versions, weights=weights, k=1)[0] + else: + selected_version = random.choice(versions) + selected_version.get_version() + build_number = selected_version.build_number build_number = build_number.replace('{d}', '{:02d}{:02d}{:02d}'.format(random.randint(17, 22), random.randint(0, 12), random.randint(0, 29))) build_number = build_number.replace('{v}', '{}'.format(random.randint(1, 255))) - choice[0].build_number = build_number - choice[0].platform_model = random.choice(platform_models) - return choice[0] + selected_version.build_number = build_number + selected_version.platform_model = random.choice(platform_models) + return selected_version diff --git a/src/ua_generator/data/platforms/ios.py b/src/ua_generator/data/platforms/ios.py index c416456..9e3101f 100644 --- a/src/ua_generator/data/platforms/ios.py +++ b/src/ua_generator/data/platforms/ios.py @@ -42,30 +42,32 @@ versions_idx_map = {} def get_version(options: Options) -> Version: + selected_version : Version if options.version_ranges is not None and 'ios' in options.version_ranges: - if type(options.version_ranges['ios']) == VersionRange: - version_range = options.version_ranges['ios'] - min_idx = 0 - max_idx = len(versions) - if(version_range.min_version is not None): - if(version_range.min_version.major not in versions_idx_map): - raise InvalidVersionError("Invalid {} version {} specified, valid versions are {}-{}\n".format("firefox", version_range.min_version.major, versions[0].major, versions[-1].major)) - min_idx = versions_idx_map[version_range.min_version.major] - if(version_range.max_version is not None): - if(version_range.max_version.major not in versions_idx_map): - raise InvalidVersionError("Invalid {} version {} specified, valid versions are {}-{}\n".format("firefox", version_range.min_version.major, versions[0].major, versions[-1].major)) - max_idx = versions_idx_map[version_range.max_version.major]+1 - filtered = versions[min_idx:max_idx] - if len(filtered) > 0: - return random.choice(filtered) + version_range = options.version_ranges['ios'] + min_idx = 0 + max_idx = len(versions) + if(version_range.min_version is not None): + if(version_range.min_version.major not in versions_idx_map): + raise InvalidVersionError("Invalid {} version {} specified, valid versions are {}-{}\n".format("firefox", version_range.min_version.major, versions[0].major, versions[-1].major)) + min_idx = versions_idx_map[version_range.min_version.major] + if(version_range.max_version is not None): + if(version_range.max_version.major not in versions_idx_map): + raise InvalidVersionError("Invalid {} version {} specified, valid versions are {}-{}\n".format("firefox", version_range.min_version.major, versions[0].major, versions[-1].major)) + max_idx = versions_idx_map[version_range.max_version.major]+1 + filtered = versions[min_idx:max_idx] + if len(filtered) > 0: + selected_version = random.choice(filtered) - weights = None - if options.weighted_versions: + elif options.weighted_versions: weights = [1.0] * len(versions) weights[-1] = 10.0 weights[-2] = 9.0 weights[-3] = 8.0 weights[-4] = 7.0 - choice: List[Version] = random.choices(versions, weights=weights, k=1) - return choice[0] + selected_version = random.choices(versions, weights=weights, k=1)[0] + else: + selected_version = random.choice(versions) + selected_version.get_version() + return selected_version diff --git a/src/ua_generator/data/platforms/linux.py b/src/ua_generator/data/platforms/linux.py index c19a0d1..4b5480a 100644 --- a/src/ua_generator/data/platforms/linux.py +++ b/src/ua_generator/data/platforms/linux.py @@ -45,26 +45,27 @@ versions_idx_map = {} def get_version(options: Options) -> Version: + selected_version : Version if options.version_ranges is not None and 'linux' in options.version_ranges: - if type(options.version_ranges['linux']) == VersionRange: - version_range = options.version_ranges['linux'] - min_idx = 0 - max_idx = len(versions) - if(version_range.min_version is not None): - if(version_range.min_version.major not in versions_idx_map): - raise InvalidVersionError("Invalid {} version {} specified, valid versions are {}-{}\n".format("firefox", version_range.min_version.major, versions[0].major, versions[-1].major)) - min_idx = versions_idx_map[version_range.min_version.major] - if(version_range.max_version is not None): - if(version_range.max_version.major not in versions_idx_map): - raise InvalidVersionError("Invalid {} version {} specified, valid versions are {}-{}\n".format("firefox", version_range.min_version.major, versions[0].major, versions[-1].major)) - max_idx = versions_idx_map[version_range.max_version.major]+1 - filtered = versions[min_idx:max_idx] - if len(filtered) > 0: - return random.choice(filtered) + version_range = options.version_ranges['linux'] + min_idx = 0 + max_idx = len(versions) + if(version_range.min_version is not None): + if(version_range.min_version.major not in versions_idx_map): + raise InvalidVersionError("Invalid {} version {} specified, valid versions are {}-{}\n".format("firefox", version_range.min_version.major, versions[0].major, versions[-1].major)) + min_idx = versions_idx_map[version_range.min_version.major] + if(version_range.max_version is not None): + if(version_range.max_version.major not in versions_idx_map): + raise InvalidVersionError("Invalid {} version {} specified, valid versions are {}-{}\n".format("firefox", version_range.min_version.major, versions[0].major, versions[-1].major)) + max_idx = versions_idx_map[version_range.max_version.major]+1 + filtered = versions[min_idx:max_idx] + if len(filtered) > 0: + selected_version = random.choice(filtered) - weights = None - if options.weighted_versions: + elif options.weighted_versions: weights = [1.0] * len(versions) - - choice: List[Version] = random.choices(versions, weights=weights, k=1) - return choice[0] + selected_version = random.choices(versions, weights=weights, k=1)[0] + else: + selected_version = random.choice(versions) + selected_version.get_version() + return selected_version diff --git a/src/ua_generator/data/platforms/macos.py b/src/ua_generator/data/platforms/macos.py index 1bcef27..5b76fac 100644 --- a/src/ua_generator/data/platforms/macos.py +++ b/src/ua_generator/data/platforms/macos.py @@ -49,29 +49,29 @@ versions_idx_map = {} def get_version(options: Options) -> Version: + selected_version : Version if options.version_ranges is not None and 'macos' in options.version_ranges: - if type(options.version_ranges['macos']) == VersionRange: - version_range = options.version_ranges['macos'] - min_idx = 0 - max_idx = len(versions) - if(version_range.min_version is not None): - if(version_range.min_version.major not in versions_idx_map): - raise InvalidVersionError("Invalid {} version {} specified, valid versions are {}-{}\n".format("firefox", version_range.min_version.major, versions[0].major, versions[-1].major)) - min_idx = versions_idx_map[version_range.min_version.major] - if(version_range.max_version is not None): - if(version_range.max_version.major not in versions_idx_map): - raise InvalidVersionError("Invalid {} version {} specified, valid versions are {}-{}\n".format("firefox", version_range.min_version.major, versions[0].major, versions[-1].major)) - max_idx = versions_idx_map[version_range.max_version.major]+1 - filtered = versions[min_idx:max_idx] - if len(filtered) > 0: - return random.choice(filtered) - - weights = None - if options.weighted_versions: + version_range = options.version_ranges['macos'] + min_idx = 0 + max_idx = len(versions) + if(version_range.min_version is not None): + if(version_range.min_version.major not in versions_idx_map): + raise InvalidVersionError("Invalid {} version {} specified, valid versions are {}-{}\n".format("firefox", version_range.min_version.major, versions[0].major, versions[-1].major)) + min_idx = versions_idx_map[version_range.min_version.major] + if(version_range.max_version is not None): + if(version_range.max_version.major not in versions_idx_map): + raise InvalidVersionError("Invalid {} version {} specified, valid versions are {}-{}\n".format("firefox", version_range.min_version.major, versions[0].major, versions[-1].major)) + max_idx = versions_idx_map[version_range.max_version.major]+1 + filtered = versions[min_idx:max_idx] + if len(filtered) > 0: + selected_version = random.choice(filtered) + elif options.weighted_versions: weights = [1.0] * len(versions) weights[-1] = 10.0 weights[-2] = 9.0 weights[-3] = 8.0 - - choice: List[Version] = random.choices(versions, weights=weights, k=1) - return choice[0] + selected_version = random.choices(versions, weights=weights, k=1)[0] + else: + selected_version = random.choice(versions) + selected_version.get_version() + return selected_version diff --git a/src/ua_generator/data/platforms/windows.py b/src/ua_generator/data/platforms/windows.py index 3db0d2a..9c8488a 100644 --- a/src/ua_generator/data/platforms/windows.py +++ b/src/ua_generator/data/platforms/windows.py @@ -5,7 +5,7 @@ """ import random from typing import List -from ...exceptions import InvalidVersionError +from ...exceptions import InvalidVersionError,InvalidArgumentError from ..version import Version, WindowsVersion, VersionRange from ...options import Options #ua-generator/src/data/browsers/windows.py @@ -22,29 +22,29 @@ versions_idx_map = {} def get_version(options: Options) -> WindowsVersion: - if options.version_ranges is not None and 'windows' in options.version_ranges: - if type(options.version_ranges['windows']) == VersionRange: - version_range = options.version_ranges['windows'] - min_idx = 0 - max_idx = len(versions) - if(version_range.min_version is not None): - if(version_range.min_version.major not in versions_idx_map): - raise InvalidVersionError("Invalid {} version {} specified, valid versions are {}-{}\n".format("firefox", version_range.min_version.major, versions[0].major, versions[-1].major)) - min_idx = versions_idx_map[version_range.min_version.major] - if(version_range.max_version is not None): - if(version_range.max_version.major not in versions_idx_map): - raise InvalidVersionError("Invalid {} version {} specified, valid versions are {}-{}\n".format("firefox", version_range.min_version.major, versions[0].major, versions[-1].major)) - max_idx = versions_idx_map[version_range.max_version.major]+1 - filtered = versions[min_idx:max_idx] - if len(filtered) > 0: - return random.choice(filtered) - - weights = None - if options.weighted_versions: + selected_version : WindowsVersion + if options.version_ranges is not None and 'windows' in options.version_ranges: + version_range = options.version_ranges['windows'] + min_idx = 0 + max_idx = len(versions) + if(version_range.min_version is not None): + if(version_range.min_version.major not in versions_idx_map): + raise InvalidVersionError("Invalid {} version {} specified, valid versions are {}-{}\n".format("firefox", version_range.min_version.major, versions[0].major, versions[-1].major)) + min_idx = versions_idx_map[version_range.min_version.major] + if(version_range.max_version is not None): + if(version_range.max_version.major not in versions_idx_map): + raise InvalidVersionError("Invalid {} version {} specified, valid versions are {}-{}\n".format("firefox", version_range.min_version.major, versions[0].major, versions[-1].major)) + max_idx = versions_idx_map[version_range.max_version.major]+1 + filtered = versions[min_idx:max_idx] + if len(filtered) > 0: + selected_version = random.choice(filtered) + elif options.weighted_versions: weights = [1.0] * len(versions) # https://gs.statcounter.com/os-version-market-share/windows/desktop/worldwide weights[-1] = 7.0 weights[-2] = 10.0 - - choice: List[WindowsVersion] = random.choices(versions, weights=weights, k=1) - return choice[0] + selected_version = random.choices(versions, weights=weights, k=1)[0] + else: + selected_version = random.choice(versions) + selected_version.get_version() + return selected_version diff --git a/src/ua_generator/data/version.py b/src/ua_generator/data/version.py index 965a223..342bcc4 100644 --- a/src/ua_generator/data/version.py +++ b/src/ua_generator/data/version.py @@ -7,23 +7,33 @@ from typing import Union, List from .. import exceptions - class Version: + #Variable ranges + majors : Union[int, tuple] = None + minors: Union[int, tuple] = None, + builds: Union[int, tuple] = None, + patches: Union[int, tuple] = None + #Selected at random from the above ranges major: int = None minor: int = None build: int = None patch: int = None - def __init__(self, major: Union[int, tuple] = None, minor: Union[int, tuple] = None, build: Union[int, tuple] = None, patch: Union[int, tuple] = None): - self.major, self.minor, self.build, self.patch = map( + self.majors = major + self.minors = minor + self.builds = build + self.patches = patch + self.get_version() + def get_version(self): + self.major,self.minor,self.build,self.patch = map( lambda x: # https://docs.python.org/3/tutorial/controlflow.html#tut-unpacking-arguments random.randrange(*x) if isinstance(x, tuple) else x, - (major, minor, build, patch) + (self.majors,self.minors, self.builds, self.patches) ) self.__tuple = None def format(self, partitions=None, separator='.', trim_zero=False) -> str: @@ -114,8 +124,20 @@ class VersionRange: max_version: Version = None def __init__(self, min_version: Union[Version, int] = None, max_version: Union[Version, int] = None): - self.min_version = Version(major=min_version) if type(min_version) is int else min_version - self.max_version = Version(major=max_version) if type(max_version) is int else max_version + if min_version is not None: + if type(min_version) is int: + self.min_version = Version(major=min_version) + elif issubclass(type(min_version),Version): + self.min_version = min_version + else: + raise TypeError("Improper version specified, min_version must be of type int or Version") + if max_version is not None: + if type(max_version) is int: + self.max_version = Version(major=max_version) + elif issubclass(type(max_version),Version): + self.max_version = max_version + else: + raise TypeError("Improper version specified, max_version must be of type int or Version") def __str__(self) -> str: min_version_str = str(self.min_version) if self.min_version else "None" max_version_str = str(self.max_version) if self.max_version else "None" diff --git a/src/ua_generator/user_agent.py b/src/ua_generator/user_agent.py index 3a68cd8..491bce9 100644 --- a/src/ua_generator/user_agent.py +++ b/src/ua_generator/user_agent.py @@ -7,10 +7,12 @@ from . import utils, exceptions from .client_hints import ClientHints -from .data import DEVICES, BROWSERS, PLATFORMS, PLATFORMS_DESKTOP, PLATFORMS_MOBILE +from .data import DEVICES, BROWSERS, PLATFORMS, PLATFORMS_DESKTOP, PLATFORMS_MOBILE,VERSION_SUPPORTED_MODULES from .data.generator import Generator from .headers import Headers from .options import Options +from .data.version import VersionRange + class UserAgent: @@ -74,12 +76,18 @@ def __find_browser(self) -> str: self.browser = 'chrome' return self.browser - + def __validate_version_ranges(self): + if self.options is not None and self.options.version_ranges is not None: + for option,version_range in self.options.version_ranges.items(): + if option not in VERSION_SUPPORTED_MODULES: + raise exceptions.InvalidArgumentError("{} not supported with version ranges".format(option)) + if type(version_range) is not VersionRange: + raise exceptions.InvalidArgumentError("{} specified version range must encapsulated in a VersionRange object, refer to readme for info.".format(option)) def __complete(self): self.device = self.__find_device() self.platform = self.__find_platform() self.browser = self.__find_browser() - + self.__validate_version_ranges() ua = Generator(device=self.device, platform=self.platform, browser=self.browser, options=self.options) self.text = ua.user_agent self.ch = ClientHints(ua) diff --git a/tests/test_idx_map.py b/tests/test_idx_map.py deleted file mode 100644 index 4560e86..0000000 --- a/tests/test_idx_map.py +++ /dev/null @@ -1,30 +0,0 @@ -import unittest -import src.ua_generator as ua_generator -from src.ua_generator.options import Options -from src.ua_generator.data.version import VersionRange, Version -from src.ua_generator.data.browsers import chrome,safari,edge,firefox -from src.ua_generator.data.platforms import linux,ios,macos,windows -from src.ua_generator.data.platforms.android import android_nexus,android_samsung,android_pixel -class TestIdxMap(unittest.TestCase): - def test_idx_maps_persist_on_browser_and_platform_modules_across_generates(self): - options = Options(version_ranges={ - "chrome": VersionRange(chrome.versions[0], chrome.versions[-1]), - "safari": VersionRange(safari.versions[0], safari.versions[-1]), - "edge": VersionRange(edge.versions[0], edge.versions[-1]), - "firefox": VersionRange(firefox.versions[0], firefox.versions[-1]), - "linux": VersionRange(linux.versions[0], linux.versions[-1]), - "ios": VersionRange(ios.versions[0], ios.versions[-1]), - "macos": VersionRange(macos.versions[0], macos.versions[-1]), - "windows": VersionRange(windows.versions[0], windows.versions[-1]), - "android_nexus": VersionRange(android_nexus.versions[0], android_nexus.versions[-1]), - "android_pixel": VersionRange(android_pixel.versions[0], android_pixel.versions[-1]), - "android_samsung": VersionRange(android_samsung.versions[0], android_samsung.versions[-1]), - }) - for i in range(10000): - #if no indexing error is thrown, then pass - ua_generator.generate(options=options) - for i in ua_generator.versions_idx_map.keys(): - self.assertGreater(len(ua_generator.versions_idx_map[i]),0) - #ensures the index map persists - - From 37bf43363de6bdd551786cd7fb67038289c0657e Mon Sep 17 00:00:00 2001 From: kgala Date: Fri, 6 Sep 2024 19:52:41 -0400 Subject: [PATCH 8/8] Bug fix on the previous bug fix --- src/ua_generator/__init__.py | 5 ++-- src/ua_generator/data/__init__.py | 4 +-- src/ua_generator/data/version.py | 22 ++++++++------- src/ua_generator/options.py | 4 +-- src/ua_generator/user_agent.py | 18 +++++++------ tests/test_src_ua_generator_init_.py | 40 ++++++++++++++++++++++++++++ 6 files changed, 68 insertions(+), 25 deletions(-) create mode 100644 tests/test_src_ua_generator_init_.py diff --git a/src/ua_generator/__init__.py b/src/ua_generator/__init__.py index 7dc124b..4f13f77 100644 --- a/src/ua_generator/__init__.py +++ b/src/ua_generator/__init__.py @@ -6,6 +6,7 @@ #ua-generator/src/__init__.py import typing from . import user_agent, options as _options +from src.ua_generator.data import VERSION_SUPPORTED_MODULES from src.ua_generator.data.browsers import chrome,firefox,edge,safari from src.ua_generator.data.platforms import ios, macos, windows,linux from src.ua_generator.data.platforms.android import android_nexus,android_samsung,android_pixel @@ -25,11 +26,11 @@ def initialize_idx_map(option): module.versions_idx_map = versions_idx_map[option] def get_versions(option:str): - if option in versions_idx_map: + if option in VERSION_SUPPORTED_MODULES: module = globals()[option] return module.versions else: - raise InvalidArgumentError("{} is not a valid browser/platform".format(option)) + raise InvalidArgumentError("{} is not a valid browser/platform with versions.\tValid options include : {}\n".format(option,VERSION_SUPPORTED_MODULES)) def generate(device: typing.Union[tuple, str, None] = None, platform: typing.Union[tuple, str, None] = None, diff --git a/src/ua_generator/data/__init__.py b/src/ua_generator/data/__init__.py index d519602..15c75e7 100644 --- a/src/ua_generator/data/__init__.py +++ b/src/ua_generator/data/__init__.py @@ -7,10 +7,10 @@ DEVICES = ('desktop', 'mobile') PLATFORMS = ('windows', 'macos', 'ios', 'linux', 'android', 'android_nexus','android_pixel','android_samsung') PLATFORMS_DESKTOP = ('windows', 'macos', 'linux') # Platforms on desktop devices -PLATFORMS_MOBILE = ('ios', 'android','android_nexus','android_pixel','android_samsung') # Platforms on mobile devices +PLATFORMS_MOBILE = ('ios','android','android_nexus','android_pixel','android_samsung') # Platforms on mobile devices +VERSION_SUPPORTED_PLATFORMS_MOBILE = ('ios','android_nexus','android_pixel','android_samsung') #Platforms that support version filter on mobile PLATFORMS_ANDROID = ('android_nexus','android_pixel','android_samsung') ANDROIDS = ('android','android_nexus','android_pixel','android_samsung') BROWSERS = ('chrome', 'edge', 'firefox', 'safari') VERSION_SUPPORTED_MODULES = ('windows', 'macos', 'ios', 'linux', 'android_nexus','android_pixel','android_samsung','chrome', 'edge', 'firefox', 'safari') BROWSERS_SUPPORT_CH = ('chrome', 'edge') # Browsers that support Client Hints - diff --git a/src/ua_generator/data/version.py b/src/ua_generator/data/version.py index 342bcc4..cc0f142 100644 --- a/src/ua_generator/data/version.py +++ b/src/ua_generator/data/version.py @@ -28,6 +28,7 @@ def __init__(self, self.builds = build self.patches = patch self.get_version() + def get_version(self): self.major,self.minor,self.build,self.patch = map( lambda x: @@ -36,6 +37,7 @@ def get_version(self): (self.majors,self.minors, self.builds, self.patches) ) self.__tuple = None + def format(self, partitions=None, separator='.', trim_zero=False) -> str: versions = [self.major, self.minor, self.build, self.patch] @@ -87,30 +89,31 @@ def __ge__(self, other): class ChromiumVersion(Version): webkit: Version = None - def __init__(self, version: Version, webkit: Version = Version(major=537, minor=36)): - super().__init__(version.major, version.minor, version.build, version.patch) + super().__init__(version.majors, version.minors, version.builds, version.patches) self.webkit = webkit - - + class AndroidVersion(Version): + build_numbers: tuple = None build_number: str = None platform_model: str = None def __init__(self, version: Version, build_numbers: tuple = None): - super().__init__(version.major, version.minor, version.build, version.patch) + super().__init__(version.majors, version.minors, version.builds, version.patches) if build_numbers is not None: - self.build_number = random.choice(build_numbers) - + if(type(build_numbers) is tuple): + self.build_numbers = build_numbers + self.build_number = random.choice(build_numbers) + else: + self.build_number = build_numbers class WindowsVersion(Version): ch_platform: Version = None def __init__(self, version: Version, ch_platform: Version): - super().__init__(version.major, version.minor, version.build, version.patch) + super().__init__(version.majors, version.minors, version.builds, version.patches) self.ch_platform = ch_platform - VERSION_TYPES = ( Version, ChromiumVersion, @@ -118,7 +121,6 @@ def __init__(self, version: Version, ch_platform: Version): WindowsVersion, ) - class VersionRange: min_version: Version = None max_version: Version = None diff --git a/src/ua_generator/options.py b/src/ua_generator/options.py index 5633166..7448446 100644 --- a/src/ua_generator/options.py +++ b/src/ua_generator/options.py @@ -11,11 +11,9 @@ class Options: weighted_versions: bool = False version_ranges: typing.Dict[str, VersionRange] = None - def __init__(self, weighted_versions: bool = False, version_ranges: typing.Dict[str, VersionRange] = None): self.weighted_versions = weighted_versions - if version_ranges is not None: - self.version_ranges = version_ranges + self.version_ranges = version_ranges def __str__(self) -> str: # Stringify version_ranges if it exists version_ranges_str = ( diff --git a/src/ua_generator/user_agent.py b/src/ua_generator/user_agent.py index 491bce9..f69902b 100644 --- a/src/ua_generator/user_agent.py +++ b/src/ua_generator/user_agent.py @@ -76,18 +76,20 @@ def __find_browser(self) -> str: self.browser = 'chrome' return self.browser - def __validate_version_ranges(self): - if self.options is not None and self.options.version_ranges is not None: - for option,version_range in self.options.version_ranges.items(): - if option not in VERSION_SUPPORTED_MODULES: - raise exceptions.InvalidArgumentError("{} not supported with version ranges".format(option)) - if type(version_range) is not VersionRange: - raise exceptions.InvalidArgumentError("{} specified version range must encapsulated in a VersionRange object, refer to readme for info.".format(option)) + def __validate_options(self): + if self.options is not None: + if self.options.version_ranges is not None: + for option,version_range in self.options.version_ranges.items(): + if option not in VERSION_SUPPORTED_MODULES: + raise exceptions.InvalidArgumentError("{} not supported with version ranges".format(option)) + if type(version_range) is not VersionRange: + raise exceptions.InvalidArgumentError("{} specified version range must encapsulated in a VersionRange object, refer to readme for info.".format(option)) + def __complete(self): self.device = self.__find_device() self.platform = self.__find_platform() self.browser = self.__find_browser() - self.__validate_version_ranges() + self.__validate_options() ua = Generator(device=self.device, platform=self.platform, browser=self.browser, options=self.options) self.text = ua.user_agent self.ch = ClientHints(ua) diff --git a/tests/test_src_ua_generator_init_.py b/tests/test_src_ua_generator_init_.py new file mode 100644 index 0000000..e94e4c5 --- /dev/null +++ b/tests/test_src_ua_generator_init_.py @@ -0,0 +1,40 @@ +import unittest +import src.ua_generator as ua_generator +from src.ua_generator.options import Options +from typing import List +from src.ua_generator.data.version import VersionRange, Version +from src.ua_generator.data.browsers import chrome,safari,edge,firefox +from src.ua_generator.data.platforms import linux,ios,macos,windows +from src.ua_generator.data.platforms.android import android_nexus,android_samsung,android_pixel +from src.ua_generator.data import VERSION_SUPPORTED_MODULES +class TestUaGeneratorSrcInit(unittest.TestCase): + def test_idx_maps_persist_on_browser_and_platform_modules_across_generates(self): + options = Options(version_ranges={ + "chrome": VersionRange(chrome.versions[0], chrome.versions[-1]), + "safari": VersionRange(safari.versions[0], safari.versions[-1]), + "edge": VersionRange(edge.versions[0], edge.versions[-1]), + "firefox": VersionRange(firefox.versions[0], firefox.versions[-1]), + "linux": VersionRange(linux.versions[0], linux.versions[-1]), + "ios": VersionRange(ios.versions[0], ios.versions[-1]), + "macos": VersionRange(macos.versions[0], macos.versions[-1]), + "windows": VersionRange(windows.versions[0], windows.versions[-1]), + "android_nexus": VersionRange(android_nexus.versions[0], android_nexus.versions[-1]), + "android_pixel": VersionRange(android_pixel.versions[0], android_pixel.versions[-1]), + "android_samsung": VersionRange(android_samsung.versions[0], android_samsung.versions[-1]), + }) + for i in range(10000): + #if no indexing error is thrown, then pass + ua_generator.generate(options=options) + for i in ua_generator.versions_idx_map.keys(): + self.assertGreater(len(ua_generator.versions_idx_map[i]),0) + #ensures the index map persists + def test_get_versions(self): + for option in VERSION_SUPPORTED_MODULES: + versions = ua_generator.get_versions(option) + # Check if versions is a list + self.assertIsInstance(versions, list, f"Expected 'versions' to be a list for option {option}") + # Check if the list is not empty + self.assertGreater(len(versions), 0, f"'versions' list is empty for option {option}") + # Check if each element in the list is an instance of Version + for version in versions: + self.assertIsInstance(version, Version, f"Expected each element to be a 'Version', got {type(version)}")