deltona 0.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. deltona-0.0.1/LICENSE.txt +18 -0
  2. deltona-0.0.1/PKG-INFO +113 -0
  3. deltona-0.0.1/README.md +44 -0
  4. deltona-0.0.1/deltona/__init__.py +4 -0
  5. deltona-0.0.1/deltona/adp.py +173 -0
  6. deltona-0.0.1/deltona/chromium.py +133 -0
  7. deltona-0.0.1/deltona/commands/__init__.py +1 -0
  8. deltona-0.0.1/deltona/commands/admin.py +229 -0
  9. deltona-0.0.1/deltona/commands/desktop.py +355 -0
  10. deltona-0.0.1/deltona/commands/git.py +133 -0
  11. deltona-0.0.1/deltona/commands/media.py +802 -0
  12. deltona-0.0.1/deltona/commands/misc.py +131 -0
  13. deltona-0.0.1/deltona/commands/string.py +180 -0
  14. deltona-0.0.1/deltona/commands/wine.py +307 -0
  15. deltona-0.0.1/deltona/commands/www.py +164 -0
  16. deltona-0.0.1/deltona/constants.py +9 -0
  17. deltona-0.0.1/deltona/gentoo.py +78 -0
  18. deltona-0.0.1/deltona/git.py +110 -0
  19. deltona-0.0.1/deltona/io.py +361 -0
  20. deltona-0.0.1/deltona/media.py +943 -0
  21. deltona-0.0.1/deltona/naming.py +214 -0
  22. deltona-0.0.1/deltona/py.typed +0 -0
  23. deltona-0.0.1/deltona/string.py +356 -0
  24. deltona-0.0.1/deltona/system.py +430 -0
  25. deltona-0.0.1/deltona/typing.py +119 -0
  26. deltona-0.0.1/deltona/ultraiso.py +327 -0
  27. deltona-0.0.1/deltona/utils.py +656 -0
  28. deltona-0.0.1/deltona/windows.py +359 -0
  29. deltona-0.0.1/deltona/www.py +286 -0
  30. deltona-0.0.1/man/deltona.1 +2934 -0
  31. deltona-0.0.1/pyproject.toml +514 -0
  32. deltona-0.0.1/tests/conftest.py +24 -0
  33. deltona-0.0.1/tests/pyproject.toml +10 -0
  34. deltona-0.0.1/tests/test_admin_commands.py +172 -0
  35. deltona-0.0.1/tests/test_adp.py +69 -0
  36. deltona-0.0.1/tests/test_chromium.py +199 -0
  37. deltona-0.0.1/tests/test_desktop_commands.py +595 -0
  38. deltona-0.0.1/tests/test_gentoo.py +58 -0
  39. deltona-0.0.1/tests/test_git.py +126 -0
  40. deltona-0.0.1/tests/test_git_commands.py +106 -0
  41. deltona-0.0.1/tests/test_io.py +552 -0
  42. deltona-0.0.1/tests/test_media.py +981 -0
  43. deltona-0.0.1/tests/test_media_commands.py +352 -0
  44. deltona-0.0.1/tests/test_misc_commands.py +154 -0
  45. deltona-0.0.1/tests/test_naming.py +76 -0
  46. deltona-0.0.1/tests/test_string.py +343 -0
  47. deltona-0.0.1/tests/test_string_commands.py +204 -0
  48. deltona-0.0.1/tests/test_system.py +636 -0
  49. deltona-0.0.1/tests/test_ultraiso.py +366 -0
  50. deltona-0.0.1/tests/test_utils.py +687 -0
  51. deltona-0.0.1/tests/test_windows.py +56 -0
  52. deltona-0.0.1/tests/test_wine_commands.py +213 -0
  53. deltona-0.0.1/tests/test_www.py +286 -0
  54. deltona-0.0.1/tests/test_www_commands.py +140 -0
@@ -0,0 +1,18 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2025 deltona authors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
6
+ associated documentation files (the "Software"), to deal in the Software without restriction,
7
+ including without limitation the rights to use, copy, modify, merge, publish, distribute,
8
+ sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
9
+ furnished to do so, subject to the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in all copies or
12
+ substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
15
+ NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
16
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
17
+ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
18
+ OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
deltona-0.0.1/PKG-INFO ADDED
@@ -0,0 +1,113 @@
1
+ Metadata-Version: 2.3
2
+ Name: deltona
3
+ Version: 0.0.1
4
+ Summary: A lot of uncategorised utilities.
5
+ License: MIT
6
+ Keywords: bluetooth,command line,file management,git,multimedia
7
+ Author: Andrew Udvare
8
+ Author-email: audvare@gmail.com
9
+ Requires-Python: >=3.10,<3.14
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Environment :: Console
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Intended Audience :: End Users/Desktop
14
+ Classifier: Intended Audience :: Information Technology
15
+ Classifier: Intended Audience :: System Administrators
16
+ Classifier: Operating System :: MacOS
17
+ Classifier: Operating System :: Microsoft :: Windows
18
+ Classifier: Operating System :: POSIX :: Linux
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Programming Language :: Python
22
+ Classifier: Topic :: Desktop Environment :: K Desktop Environment (KDE)
23
+ Classifier: Topic :: Desktop Environment
24
+ Classifier: Topic :: File Formats
25
+ Classifier: Topic :: Games/Entertainment
26
+ Classifier: Topic :: Internet :: WWW/HTTP :: Browsers
27
+ Classifier: Topic :: Multimedia
28
+ Classifier: Topic :: System :: Hardware
29
+ Classifier: Topic :: System :: Systems Administration
30
+ Classifier: Topic :: Utilities
31
+ Classifier: Typing :: Typed
32
+ Provides-Extra: admin
33
+ Provides-Extra: desktop
34
+ Provides-Extra: git
35
+ Provides-Extra: media
36
+ Provides-Extra: string
37
+ Provides-Extra: wine
38
+ Provides-Extra: www
39
+ Requires-Dist: beautifulsoup4 (>=4.13.4,<5.0.0) ; extra == "www"
40
+ Requires-Dist: binaryornot (>=0.4.4,<0.5.0)
41
+ Requires-Dist: click (>=8.2.1,<9.0.0)
42
+ Requires-Dist: colorlog (>=6.9.0,<7.0.0)
43
+ Requires-Dist: gitpython (>=3.1.44,<4.0.0) ; extra == "git"
44
+ Requires-Dist: html5lib (>=1.1,<2.0) ; extra == "www"
45
+ Requires-Dist: keyring (>=25.6.0,<26.0.0)
46
+ Requires-Dist: mutagen (>=1.47.0,<2.0.0) ; extra == "media"
47
+ Requires-Dist: paramiko (>=3.5.1,<4.0.0) ; extra == "admin"
48
+ Requires-Dist: pexpect (>=4.9.0,<5.0.0) ; extra == "wine"
49
+ Requires-Dist: pillow (>=11.2.1,<12.0.0) ; extra == "www"
50
+ Requires-Dist: platformdirs (>=4.3.8,<5.0.0) ; extra == "media" or extra == "wine"
51
+ Requires-Dist: psutil (>=7.0.0,<8.0.0) ; extra == "wine"
52
+ Requires-Dist: pydbus (>=0.6.0,<0.7.0) ; (sys_platform == "linux") and (extra == "desktop")
53
+ Requires-Dist: pygithub (>=2.6.1,<3.0.0) ; extra == "git"
54
+ Requires-Dist: pygobject (>=3.52.3,<4.0.0) ; (sys_platform == "linux") and (extra == "desktop")
55
+ Requires-Dist: pyperclip (>=1.9.0,<2.0.0) ; extra == "desktop"
56
+ Requires-Dist: python-xz (>=0.5.0,<0.6.0) ; extra == "wine"
57
+ Requires-Dist: pyyaml (>=6.0.2,<7.0.0) ; extra == "string"
58
+ Requires-Dist: requests (>=2.32.3,<3.0.0)
59
+ Requires-Dist: send2trash (>=1.8.3,<2.0.0)
60
+ Requires-Dist: typing-extensions (>=4.13.2,<5.0.0)
61
+ Requires-Dist: unidecode (>=1.4.0,<2.0.0) ; extra == "string"
62
+ Requires-Dist: yt-dlp (>=2025.5.22,<2026.0.0) ; extra == "string"
63
+ Project-URL: Documentation, https://deltona.readthedocs.org
64
+ Project-URL: Homepage, https://tatsh.github.io/deltona/
65
+ Project-URL: Issues, https://github.com/Tatsh/deltona/issues
66
+ Project-URL: Repository, https://github.com/Tatsh/deltona
67
+ Description-Content-Type: text/markdown
68
+
69
+ # deltona
70
+
71
+ [![Python versions](https://img.shields.io/pypi/pyversions/deltona.svg?color=blue&logo=python&logoColor=white)](https://www.python.org/)
72
+ [![PyPI - Version](https://img.shields.io/pypi/v/deltona)](https://pypi.org/project/deltona/)
73
+ [![GitHub tag (with filter)](https://img.shields.io/github/v/tag/Tatsh/deltona)](https://github.com/Tatsh/deltona/tags)
74
+ [![License](https://img.shields.io/github/license/Tatsh/deltona)](https://github.com/Tatsh/deltona/blob/master/LICENSE.txt)
75
+ [![GitHub commits since latest release (by SemVer including pre-releases)](https://img.shields.io/github/commits-since/Tatsh/deltona/v0.0.1/master)](https://github.com/Tatsh/deltona/compare/v0.0.1...master)
76
+ [![QA](https://github.com/Tatsh/deltona/actions/workflows/qa.yml/badge.svg)](https://github.com/Tatsh/deltona/actions/workflows/qa.yml)
77
+ [![Tests](https://github.com/Tatsh/deltona/actions/workflows/tests.yml/badge.svg)](https://github.com/Tatsh/deltona/actions/workflows/tests.yml)
78
+ [![Coverage Status](https://coveralls.io/repos/github/Tatsh/deltona/badge.svg?branch=master)](https://coveralls.io/github/Tatsh/deltona?branch=master)
79
+ [![Documentation Status](https://readthedocs.org/projects/deltona/badge/?version=latest)](https://deltona.readthedocs.org/?badge=latest)
80
+ [![mypy](https://www.mypy-lang.org/static/mypy_badge.svg)](http://mypy-lang.org/)
81
+ [![pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white)](https://github.com/pre-commit/pre-commit)
82
+ [![pydocstyle](https://img.shields.io/badge/pydocstyle-enabled-AD4CD3)](http://www.pydocstyle.org/en/stable/)
83
+ [![pytest](https://img.shields.io/badge/pytest-zz?logo=Pytest&labelColor=black&color=black)](https://docs.pytest.org/en/stable/)
84
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
85
+ [![Downloads](https://static.pepy.tech/badge/deltona/month)](https://pepy.tech/project/deltona)
86
+ [![Stargazers](https://img.shields.io/github/stars/Tatsh/deltona?logo=github&style=flat)](https://github.com/Tatsh/deltona/stargazers)
87
+
88
+ [![@Tatsh](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fpublic.api.bsky.app%2Fxrpc%2Fapp.bsky.actor.getProfile%2F%3Factor%3Ddid%3Aplc%3Auq42idtvuccnmtl57nsucz72%26query%3D%24.followersCount%26style%3Dsocial%26logo%3Dbluesky%26label%3DFollow%2520%40Tatsh&query=%24.followersCount&style=social&logo=bluesky&label=Follow%20%40Tatsh)](https://bsky.app/profile/Tatsh.bsky.social)
89
+ [![Mastodon Follow](https://img.shields.io/mastodon/follow/109370961877277568?domain=hostux.social&style=social)](https://hostux.social/@Tatsh)
90
+
91
+ A lot of uncategorised utilities.
92
+
93
+ This project will eventually be split into different projects by category.
94
+
95
+ ## Installation
96
+
97
+ ### Poetry
98
+
99
+ ```shell
100
+ poetry add deltona
101
+ ```
102
+
103
+ ### Pip
104
+
105
+ ```shell
106
+ pip install deltona
107
+ ```
108
+
109
+ ## Usage
110
+
111
+ See [the documentation](https://deltona.readthedocs.org/?badge=latest) for commands
112
+ and the library.
113
+
@@ -0,0 +1,44 @@
1
+ # deltona
2
+
3
+ [![Python versions](https://img.shields.io/pypi/pyversions/deltona.svg?color=blue&logo=python&logoColor=white)](https://www.python.org/)
4
+ [![PyPI - Version](https://img.shields.io/pypi/v/deltona)](https://pypi.org/project/deltona/)
5
+ [![GitHub tag (with filter)](https://img.shields.io/github/v/tag/Tatsh/deltona)](https://github.com/Tatsh/deltona/tags)
6
+ [![License](https://img.shields.io/github/license/Tatsh/deltona)](https://github.com/Tatsh/deltona/blob/master/LICENSE.txt)
7
+ [![GitHub commits since latest release (by SemVer including pre-releases)](https://img.shields.io/github/commits-since/Tatsh/deltona/v0.0.1/master)](https://github.com/Tatsh/deltona/compare/v0.0.1...master)
8
+ [![QA](https://github.com/Tatsh/deltona/actions/workflows/qa.yml/badge.svg)](https://github.com/Tatsh/deltona/actions/workflows/qa.yml)
9
+ [![Tests](https://github.com/Tatsh/deltona/actions/workflows/tests.yml/badge.svg)](https://github.com/Tatsh/deltona/actions/workflows/tests.yml)
10
+ [![Coverage Status](https://coveralls.io/repos/github/Tatsh/deltona/badge.svg?branch=master)](https://coveralls.io/github/Tatsh/deltona?branch=master)
11
+ [![Documentation Status](https://readthedocs.org/projects/deltona/badge/?version=latest)](https://deltona.readthedocs.org/?badge=latest)
12
+ [![mypy](https://www.mypy-lang.org/static/mypy_badge.svg)](http://mypy-lang.org/)
13
+ [![pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white)](https://github.com/pre-commit/pre-commit)
14
+ [![pydocstyle](https://img.shields.io/badge/pydocstyle-enabled-AD4CD3)](http://www.pydocstyle.org/en/stable/)
15
+ [![pytest](https://img.shields.io/badge/pytest-zz?logo=Pytest&labelColor=black&color=black)](https://docs.pytest.org/en/stable/)
16
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
17
+ [![Downloads](https://static.pepy.tech/badge/deltona/month)](https://pepy.tech/project/deltona)
18
+ [![Stargazers](https://img.shields.io/github/stars/Tatsh/deltona?logo=github&style=flat)](https://github.com/Tatsh/deltona/stargazers)
19
+
20
+ [![@Tatsh](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fpublic.api.bsky.app%2Fxrpc%2Fapp.bsky.actor.getProfile%2F%3Factor%3Ddid%3Aplc%3Auq42idtvuccnmtl57nsucz72%26query%3D%24.followersCount%26style%3Dsocial%26logo%3Dbluesky%26label%3DFollow%2520%40Tatsh&query=%24.followersCount&style=social&logo=bluesky&label=Follow%20%40Tatsh)](https://bsky.app/profile/Tatsh.bsky.social)
21
+ [![Mastodon Follow](https://img.shields.io/mastodon/follow/109370961877277568?domain=hostux.social&style=social)](https://hostux.social/@Tatsh)
22
+
23
+ A lot of uncategorised utilities.
24
+
25
+ This project will eventually be split into different projects by category.
26
+
27
+ ## Installation
28
+
29
+ ### Poetry
30
+
31
+ ```shell
32
+ poetry add deltona
33
+ ```
34
+
35
+ ### Pip
36
+
37
+ ```shell
38
+ pip install deltona
39
+ ```
40
+
41
+ ## Usage
42
+
43
+ See [the documentation](https://deltona.readthedocs.org/?badge=latest) for commands
44
+ and the library.
@@ -0,0 +1,4 @@
1
+ """Many uncategorised utilities for CLI and Python."""
2
+ from __future__ import annotations
3
+
4
+ __version__ = '0.0.1'
@@ -0,0 +1,173 @@
1
+ """Salary calculator."""
2
+ from __future__ import annotations
3
+
4
+ from dataclasses import dataclass
5
+ from datetime import UTC, datetime
6
+ from typing import TYPE_CHECKING, TypedDict, cast, override
7
+
8
+ import requests
9
+
10
+ from .string import strip_ansi_if_no_colors
11
+
12
+ if TYPE_CHECKING:
13
+ from .typing import INCITS38Code
14
+
15
+ __all__ = ('SalaryResponse', 'calculate_salary')
16
+
17
+ # Find the API key by looking at requests on this page
18
+ # http://www.symmetry.com/try-it-for-free/calculators
19
+ API_KEY = 'RnFqNFA0NVlRTExEenRwWjNiRnJrTXY4WkZHZEpkcENEeFFzQ3F0Nnh5VT0='
20
+ POST_URI = ('https://calculators.symmetry.com/api/calculators/'
21
+ 'hourly?report=none')
22
+ REFERER = 'https://www.symmetry.com/'
23
+
24
+
25
+ class ContentDict(TypedDict):
26
+ federal: float
27
+ fica: float
28
+ medicare: float
29
+ netPay: float
30
+ state: float
31
+
32
+
33
+ class ResponseDict(TypedDict):
34
+ content: ContentDict
35
+
36
+
37
+ @dataclass
38
+ class SalaryResponse:
39
+ """Response from the Symmetry API."""
40
+ federal: float
41
+ """Federal tax amount."""
42
+ fica: float
43
+ """FICA tax amount."""
44
+ fuckery: float
45
+ """Difference between gross and net pay."""
46
+ gross: float
47
+ """Gross pay amount."""
48
+ medicare: float
49
+ """Medicare tax amount."""
50
+ net_pay: float
51
+ """Net pay amount."""
52
+ state: float
53
+ """State tax amount."""
54
+ @override
55
+ def __str__(self) -> str:
56
+ return strip_ansi_if_no_colors(f"""Gross \033[1;32m{self.gross:8.2f}\033[0m
57
+ Federal \033[1;32m{self.federal:8.2f}\033[0m
58
+ FICA \033[1;32m{self.fica:8.2f}\033[0m
59
+ Medicare \033[1;32m{self.medicare:8.2f}\033[0m
60
+ State \033[1;32m{self.state:8.2f}\033[0m
61
+ ------------------
62
+ Net \033[1;32m{self.net_pay:8.2f}\033[0m
63
+
64
+ ------------------
65
+ Fuckery \033[1;31m{self.fuckery:8.2f}\033[0m""")
66
+
67
+
68
+ def calculate_salary(*,
69
+ hours: int = 70,
70
+ pay_rate: float = 70.0,
71
+ state: INCITS38Code = 'FL') -> SalaryResponse:
72
+ """
73
+ Calculate a US salary using the Symmetry API.
74
+
75
+ Parameters
76
+ ----------
77
+ hours : int
78
+ The number of hours worked. Default is 70.
79
+ pay_rate : float
80
+ The pay rate per hour. Default is 70.0.
81
+ state : INCITS38Code
82
+ The state code. Default is 'FL'.
83
+
84
+ Returns
85
+ -------
86
+ SalaryResponse
87
+ The response from the Symmetry API.
88
+ """
89
+ check_date = int(datetime.now(tz=UTC).timestamp() * 1000)
90
+ gross_pay = hours * pay_rate
91
+ req = requests.post(POST_URI,
92
+ headers={
93
+ 'accept': 'application/json, text/javascript, */*; q=0.01',
94
+ 'cache-control': 'no-cache',
95
+ 'dnt': '1',
96
+ 'origin': 'https://www.symmetry.com',
97
+ 'pcc-api-key': API_KEY,
98
+ 'referer': REFERER
99
+ },
100
+ json={
101
+ 'checkDate': check_date,
102
+ 'state': state.upper(),
103
+ 'rates': [{
104
+ 'payRate': str(pay_rate),
105
+ 'hours': str(hours),
106
+ }],
107
+ 'grossPay': str(gross_pay),
108
+ 'grossPayType': 'PAY_PER_PERIOD',
109
+ 'grossPayYTD': '0',
110
+ 'payFrequency': 'MONTHLY',
111
+ 'exemptFederal': 'false',
112
+ 'exemptFica': 'false',
113
+ 'exemptMedicare': 'false',
114
+ 'federalFilingStatusType': 'SINGLE',
115
+ 'federalAllowances': '0',
116
+ 'additionalFederalWithholding': '0',
117
+ 'roundFederalWithholding': 'false',
118
+ 'print': {
119
+ 'checkDate': check_date,
120
+ 'checkNumber': '',
121
+ 'checkNumberOnCheck': 'false',
122
+ 'companyAddressLine1': '',
123
+ 'companyAddressLine2': '',
124
+ 'companyAddressLine3': '',
125
+ 'companyName': '',
126
+ 'companyNameOnCheck': 'false',
127
+ 'employeeAddressLine1': '',
128
+ 'employeeAddressLine2': '',
129
+ 'employeeAddressLine3': '',
130
+ 'employeeName': '',
131
+ 'id': '',
132
+ 'remarks': ''
133
+ },
134
+ 'otherIncome': [],
135
+ 'payCodes': [],
136
+ 'stockOptions': [],
137
+ 'stateInfo': {
138
+ 'parms': [{
139
+ 'name': 'TOTALALLOWANCES',
140
+ 'value': '0'
141
+ }, {
142
+ 'name': 'additionalStateWithholding',
143
+ 'value': '0'
144
+ }, {
145
+ 'name': 'SPOUSEBLINDNESS',
146
+ 'value': 'false'
147
+ }, {
148
+ 'name': 'stateExemption',
149
+ 'value': 'false'
150
+ }, {
151
+ 'name': 'PERSONALBLINDNESS',
152
+ 'value': 'false'
153
+ }, {
154
+ 'name': 'HEADOFHOUSEHOLD',
155
+ 'value': 'false'
156
+ }, {
157
+ 'name': 'FULLTIMESTUDENT',
158
+ 'value': 'false'
159
+ }]
160
+ },
161
+ 'voluntaryDeductions': [],
162
+ 'presetDeductions': []
163
+ },
164
+ timeout=30)
165
+ req.raise_for_status()
166
+ data = cast('ResponseDict', req.json())['content']
167
+ return SalaryResponse(federal=data['federal'],
168
+ fica=data['fica'],
169
+ fuckery=gross_pay - data['netPay'],
170
+ gross=gross_pay,
171
+ medicare=data['medicare'],
172
+ net_pay=data['netPay'],
173
+ state=data['state'])
@@ -0,0 +1,133 @@
1
+ """Chromium-related functions."""
2
+ from __future__ import annotations
3
+
4
+ from functools import cache
5
+ from io import BytesIO
6
+ from pathlib import Path
7
+ from typing import TYPE_CHECKING, cast
8
+
9
+ import requests
10
+
11
+ if TYPE_CHECKING:
12
+ from types import ModuleType
13
+
14
+ from .typing import StrPath
15
+
16
+
17
+ def _get_pil_image_module() -> ModuleType: # pragma: no cover
18
+ from PIL import Image # noqa: PLC0415
19
+ return Image
20
+
21
+
22
+ def fix_chromium_pwa_icon(config_path: StrPath,
23
+ app_id: str,
24
+ icon_src_uri: str,
25
+ profile: str = 'Default',
26
+ *,
27
+ masked: bool = False,
28
+ monochrome: bool = False) -> None:
29
+ """
30
+ Fix a Chromium PWA icon that failed to sync.
31
+
32
+ Parameters
33
+ ----------
34
+ config_path : StrPath
35
+ Path to the Chromium configuration directory.
36
+ app_id : str
37
+ App ID of the PWA.
38
+ icon_src_uri : str
39
+ URI of the icon source.
40
+ profile : str
41
+ Profile name. Default is ``'Default'``.
42
+ masked : bool
43
+ If ``True``, save the icon as a maskable icon. Default is ``False``.
44
+ monochrome : bool
45
+ If ``True``, save the icon as a monochrome icon. Default is ``False``.
46
+
47
+
48
+ See Also
49
+ --------
50
+ https://issues.chromium.org/issues/40595456
51
+
52
+ Raises
53
+ ------
54
+ ValueError
55
+ If the icon is not square.
56
+ """
57
+ image_mod = _get_pil_image_module()
58
+ config_path = Path(config_path) / profile / 'Web Applications' / app_id
59
+ r = requests.get(icon_src_uri, timeout=15)
60
+ r.raise_for_status()
61
+ img = image_mod.open(BytesIO(r.content))
62
+ width, height = img.size
63
+ if width != height:
64
+ msg = 'Icon is not square.'
65
+ raise ValueError(msg)
66
+ sizes = list(reversed([1 << x for x in range(4, min(10, width.bit_length()))]))
67
+ for size in sizes:
68
+ img.resize((size, size), image_mod.LANCZOS).save(config_path / 'Icons' / f'{size}.png')
69
+ if masked:
70
+ for size in sizes:
71
+ img.resize((size, size),
72
+ image_mod.LANCZOS).save(config_path / 'Icons Maskable' / f'{size}.png')
73
+ if monochrome:
74
+ for size in sizes:
75
+ img.resize((size, size),
76
+ image_mod.LANCZOS).save(config_path / 'Icons Monochrome' / f'{size}.png')
77
+
78
+
79
+ @cache
80
+ def get_last_chrome_major_version() -> str:
81
+ """
82
+ Get last major Chrome version from the profile directory.
83
+
84
+ Tries the following, in order:
85
+
86
+ * Chrome Beta
87
+ * Chrome
88
+ * Chrome Canary
89
+ * Chromium
90
+
91
+ Returns
92
+ -------
93
+ str
94
+ If no ``Last Version`` file is found, returns empty string.
95
+ """
96
+ for location in ('~/.config/google-chrome-beta', '~/AppData/Local/Google/Chrome Beta/User Data'
97
+ '~/Library/Application Support/Google/Chrome Beta', '~/.config/google-chrome',
98
+ '~/AppData/Local/Google/Chrome/User Data',
99
+ '~/Library/Application Support/Google/Chrome',
100
+ '~/.config/google-chrome-unstable',
101
+ '~/AppData/Local/Google/Chrome Canary/User Data',
102
+ '~/Library/Application Support/Google/Chrome Canary', '~/.config/chromium',
103
+ '~/AppData/Local/Google/Chromium/User Data',
104
+ '~/Library/Application Support/Google/Chromium'):
105
+ if (p := (Path(location).expanduser() / 'Last Version')).exists():
106
+ return p.read_text().split('.', 1)[0]
107
+ return ''
108
+
109
+
110
+ @cache
111
+ def get_latest_chrome_major_version() -> str:
112
+ """Get the latest Chrome major version."""
113
+ return cast(
114
+ 'str',
115
+ requests.get(
116
+ ('https://versionhistory.googleapis.com/v1/chrome/platforms/win/channels/stable/'
117
+ 'versions'),
118
+ timeout=5).json()['versions'][0]['version'].split('.')[0])
119
+
120
+
121
+ @cache
122
+ def generate_chrome_user_agent(os: str = 'Windows NT 10.0; Win64; x64') -> str:
123
+ """
124
+ Get a Chrome user agent.
125
+
126
+ Parameters
127
+ ----------
128
+ os : str
129
+ The operating system. Default is ``'Windows NT 10.0; Win64; x64'``.
130
+ """
131
+ last_major = get_last_chrome_major_version() or get_latest_chrome_major_version()
132
+ return (f'Mozilla/5.0 ({os}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/{last_major}.0.0.0'
133
+ ' Safari/537.36')
@@ -0,0 +1 @@
1
+ """Commands."""