From 875b2c3ddcd8b2494ef864ac748fe58aae5bc6df Mon Sep 17 00:00:00 2001 From: iamdual Date: Sat, 9 Mar 2024 21:57:36 +0300 Subject: [PATCH 1/5] header generation --- src/ua_generator/headers.py | 47 ++++++++++++++++++++++++++++++++ src/ua_generator/user_agent.py | 9 +++--- tests/test_headers.py | 50 ++++++++++++++++++++++++++++++++++ 3 files changed, 102 insertions(+), 4 deletions(-) create mode 100644 src/ua_generator/headers.py create mode 100644 tests/test_headers.py diff --git a/src/ua_generator/headers.py b/src/ua_generator/headers.py new file mode 100644 index 0000000..0e57460 --- /dev/null +++ b/src/ua_generator/headers.py @@ -0,0 +1,47 @@ +""" +Random User-Agent +Copyright: 2022-2024 Ekin Karadeniz (github.com/iamdual) +License: Apache License 2.0 +""" +from .data.generator import Generator +from .client_hints import ClientHints + + +# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-CH +class Headers: + + def __init__(self, gen: Generator, ch: ClientHints): + self.__generator = gen + self.__client_hints = ch + self.__headers: dict[str, str] = { + 'user-agent': gen.user_agent, + } + + # https://developer.mozilla.org/en-US/docs/Web/HTTP/Client_hints#low_entropy_hints + if self.__generator.browser in ('chrome', 'edge'): + self.add('sec-ch-ua') + self.add('sec-ch-ua-mobile') + self.add('sec-ch-ua-platform') + + def add(self, key: str): + if key == 'sec-ch-ua': + self.__headers[key] = self.__client_hints.brands + elif key == 'sec-ch-ua-full-version-list': + self.__headers[key] = self.__client_hints.brands_full_version_list + if key == 'sec-ch-ua-platform': + self.__headers[key] = self.__client_hints.platform + elif key == 'sec-ch-ua-platform-version': + self.__headers[key] = self.__client_hints.platform_version + elif key == 'sec-ch-ua-mobile': + self.__headers[key] = self.__client_hints.mobile + + def accept_ch(self, val: str): + if self.__generator.browser not in ('chrome', 'edge'): + return + + requested_hints = val.split(',') + for hint in requested_hints: + self.add(hint.strip().lower()) + + def get(self) -> dict[str, str]: + return self.__headers diff --git a/src/ua_generator/user_agent.py b/src/ua_generator/user_agent.py index 344e8e4..e410276 100644 --- a/src/ua_generator/user_agent.py +++ b/src/ua_generator/user_agent.py @@ -5,8 +5,9 @@ """ from . import utils, exceptions from .data import devices, platforms, platforms_desktop, platforms_mobile, browsers -from .data import generator +from .data.generator import Generator from .client_hints import ClientHints +from .headers import Headers class UserAgent: @@ -18,9 +19,8 @@ def __init__(self, device=None, platform=None, browser=None): # Type hinting only self.text: str - self.platform_version: dict - self.browser_version: dict self.ch: ClientHints + self.headers: Headers def __find_device(self): if self.device is not None: @@ -77,9 +77,10 @@ def __complete(self): self.platform = self.__find_platform() self.browser = self.__find_browser() - ua = generator.Generator(device=self.device, platform=self.platform, browser=self.browser) + ua = Generator(device=self.device, platform=self.platform, browser=self.browser) self.text = ua.user_agent self.ch = ClientHints(ua) + self.headers = Headers(ua, self.ch) def __str__(self): return self.text diff --git a/tests/test_headers.py b/tests/test_headers.py new file mode 100644 index 0000000..e595878 --- /dev/null +++ b/tests/test_headers.py @@ -0,0 +1,50 @@ +""" +Random User-Agent +Copyright: 2022 Ekin Karadeniz (github.com/iamdual) +License: Apache License 2.0 +""" +import unittest + +import src.ua_generator as ua_generator + + +class TestHeaders(unittest.TestCase): + def test_default(self): + for i in range(0, 100): + ua = ua_generator.generate() + self.assertIsNotNone(ua.headers) + self.assertTrue('user-agent' in ua.headers.get()) + + def test_client_hints(self): + for i in range(0, 100): + ua = ua_generator.generate(browser=('chrome', 'edge')) + self.assertIsNotNone(ua.headers) + self.assertTrue('sec-ch-ua' in ua.headers.get()) + self.assertTrue('sec-ch-ua-mobile' in ua.headers.get()) + self.assertTrue('sec-ch-ua-platform' in ua.headers.get()) + + def test_client_hints_not_exists(self): + for i in range(0, 100): + ua = ua_generator.generate(browser='firefox') + self.assertIsNotNone(ua.headers) + self.assertFalse('sec-ch-ua' in ua.headers.get()) + self.assertFalse('sec-ch-ua-mobile' in ua.headers.get()) + self.assertFalse('sec-ch-ua-platform' in ua.headers.get()) + + def test_accept_ch(self): + for i in range(0, 100): + ua = ua_generator.generate(browser=('chrome', 'edge')) + ua.headers.accept_ch('Sec-CH-UA-Platform-Version, Sec-CH-UA-Full-Version-List') + self.assertTrue('sec-ch-ua-platform-version' in ua.headers.get()) + self.assertTrue('sec-ch-ua-full-version-list' in ua.headers.get()) + + def test_accept_ch_not_exists(self): + for i in range(0, 100): + ua = ua_generator.generate(browser=('chrome', 'edge')) + ua.headers.accept_ch('Sec-CH-Example') + self.assertFalse('sec-ch-ua-platform-version' in ua.headers.get()) + self.assertFalse('sec-ch-ua-full-version-list' in ua.headers.get()) + + +if __name__ == '__main__': + unittest.main() From c6f636f516e5b0dd73738010700cfd8379aec86a Mon Sep 17 00:00:00 2001 From: iamdual Date: Sat, 9 Mar 2024 21:57:48 +0300 Subject: [PATCH 2/5] update examples --- README.md | 43 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 40777ec..275a198 100644 --- a/README.md +++ b/README.md @@ -23,17 +23,19 @@ print(ua) # Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_3) AppleWebKit/604.1.38 ``` # Customization -There are three different parameters to the generate user-agent by the certain conditions. +There are three different parameters to the generate user-agent by the certain conditions. All of the parameters are optional, and the types can be choose multiple. + ```python device = ('desktop', 'mobile') platform = ('windows', 'macos', 'ios', 'linux', 'android') browser = ('chrome', 'edge', 'firefox', 'safari') ``` -All of the parameters are optional, and the types can be choose multiple. +Customized user-agent generation: ```python import ua_generator +# Sample 1 ua = ua_generator.generate(device='desktop', browser='firefox') print(ua.text) # Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:121.0.1) Gecko/20100101 Firefox/121.0.1 print(ua.platform) # macos @@ -43,6 +45,7 @@ print(ua.ch.mobile) # ?0 print(ua.ch.platform) # "macOS" print(ua.ch.platform_version) # "14.0.1" +# Sample 2 ua = ua_generator.generate(platform=('ios', 'macos'), browser='chrome') print(ua.text) # Mozilla/5.0 (iPhone; CPU iPhone OS 17_0_2 like Mac OS X) AppleWebKit/537.36 (KHTML, like Gecko) CriOS/119.0.6045.176 Mobile/15E148 Safari/537.36 print(ua.platform) # ios @@ -53,5 +56,41 @@ print(ua.ch.platform) # "iOS" print(ua.ch.platform_version) # "17.0.2" ``` +You can also obtain a dictionary of headers: +```python +ua = ua_generator.generate(browser=('chrome', 'edge')) + +# This will return a dictionary containing the generated user-agent: +print(ua.headers.get()) +{ + 'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.43 Safari/537.36', + 'sec-ch-ua': '"Not A(Brand";v="99", "Chromium";v="103", "Google Chrome";v="103"', + 'sec-ch-ua-mobile': '?0', + 'sec-ch-ua-platform': '"macOS"' +} + +# Extending the "Client Hints" by a value of the "Accept-CH" header: +print(ua.headers.accept_ch('Sec-CH-UA-Platform-Version, Sec-CH-UA-Full-Version-List')) +print(ua.headers.get()) +{ + 'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.6261.94 Safari/537.36', + 'sec-ch-ua': '"Not A(Brand";v="99", "Chromium";v="122", "Google Chrome";v="122"', + 'sec-ch-ua-mobile': '?0', + 'sec-ch-ua-platform': '"macOS"', + 'sec-ch-ua-platform-version': '"14.1.0"', + 'sec-ch-ua-full-version-list': '"Not A(Brand";v="99", "Chromium";v="122.0.6261.94", "Google Chrome";v="122.0.6261.94"' +} +``` + +Integrating into the [requests](https://pypi.org/project/requests/): +```python +import requests +import ua_generator + +ua = ua_generator.generate(browser=('chrome', 'edge')) +r = requests.get("https://httpbin.org/get", headers=ua.headers.get()) +print(r.text) +``` + # Author Ekin Karadeniz (iamdual@icloud.com) \ No newline at end of file From 332f6adc478d822ccea110b2df5f49f61da9150a Mon Sep 17 00:00:00 2001 From: iamdual Date: Sat, 9 Mar 2024 22:01:14 +0300 Subject: [PATCH 3/5] make python 3.6 compatible --- src/ua_generator/headers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ua_generator/headers.py b/src/ua_generator/headers.py index 0e57460..118a852 100644 --- a/src/ua_generator/headers.py +++ b/src/ua_generator/headers.py @@ -13,7 +13,7 @@ class Headers: def __init__(self, gen: Generator, ch: ClientHints): self.__generator = gen self.__client_hints = ch - self.__headers: dict[str, str] = { + self.__headers = { 'user-agent': gen.user_agent, } @@ -43,5 +43,5 @@ def accept_ch(self, val: str): for hint in requested_hints: self.add(hint.strip().lower()) - def get(self) -> dict[str, str]: + def get(self): return self.__headers From 07e01a149abeaf42df4dbf1f34b30f1b91a554c3 Mon Sep 17 00:00:00 2001 From: iamdual Date: Sat, 9 Mar 2024 22:03:26 +0300 Subject: [PATCH 4/5] bump version --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index e9c1dcb..df12024 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = ua-generator -version = 0.2.1 +version = 0.3.0 author = Ekin Karadeniz author_email = iamdual@icloud.com description = A random user-agent generator From a67f8df2a6fa332fe49b0f20ef74e762d1eebc02 Mon Sep 17 00:00:00 2001 From: iamdual Date: Sat, 9 Mar 2024 22:24:31 +0300 Subject: [PATCH 5/5] update readme --- README.md | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 275a198..98c5ce3 100644 --- a/README.md +++ b/README.md @@ -23,29 +23,30 @@ print(ua) # Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_3) AppleWebKit/604.1.38 ``` # Customization -There are three different parameters to the generate user-agent by the certain conditions. All of the parameters are optional, and the types can be choose multiple. +**There are three different parameters to the generate user-agent by the certain conditions.** ```python device = ('desktop', 'mobile') platform = ('windows', 'macos', 'ios', 'linux', 'android') browser = ('chrome', 'edge', 'firefox', 'safari') ``` +All of the parameters are optional, and the types can be set multiple times by using a tuple.* -Customized user-agent generation: +## Customized user-agent generation: ```python import ua_generator -# Sample 1 -ua = ua_generator.generate(device='desktop', browser='firefox') -print(ua.text) # Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:121.0.1) Gecko/20100101 Firefox/121.0.1 -print(ua.platform) # macos -print(ua.browser) # firefox -print(ua.ch.brands) # "Not A(Brand";v="99" +# Example 1: +ua = ua_generator.generate(device='desktop', browser=('chrome', 'edge')) +print(ua.text) # Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.5359.145 Safari/537.36 +print(ua.platform) # windows +print(ua.browser) # chrome +print(ua.ch.brands) # "Not A(Brand";v="99", "Chromium";v="108", "Google Chrome";v="108" print(ua.ch.mobile) # ?0 -print(ua.ch.platform) # "macOS" -print(ua.ch.platform_version) # "14.0.1" +print(ua.ch.platform) # "Windows" +print(ua.ch.platform_version) # "10.0" -# Sample 2 +# Example 2: ua = ua_generator.generate(platform=('ios', 'macos'), browser='chrome') print(ua.text) # Mozilla/5.0 (iPhone; CPU iPhone OS 17_0_2 like Mac OS X) AppleWebKit/537.36 (KHTML, like Gecko) CriOS/119.0.6045.176 Mobile/15E148 Safari/537.36 print(ua.platform) # ios @@ -56,7 +57,7 @@ print(ua.ch.platform) # "iOS" print(ua.ch.platform_version) # "17.0.2" ``` -You can also obtain a dictionary of headers: +# Headers ```python ua = ua_generator.generate(browser=('chrome', 'edge')) @@ -82,7 +83,7 @@ print(ua.headers.get()) } ``` -Integrating into the [requests](https://pypi.org/project/requests/): +## Integrating into the [requests](https://pypi.org/project/requests/): ```python import requests import ua_generator @@ -92,5 +93,8 @@ r = requests.get("https://httpbin.org/get", headers=ua.headers.get()) print(r.text) ``` +# Issues +You can create an issue [from here](https://github.com/iamdual/ua-generator/issues) if you are experiencing a problem. + # Author Ekin Karadeniz (iamdual@icloud.com) \ No newline at end of file