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.
- deltona-0.0.1/LICENSE.txt +18 -0
- deltona-0.0.1/PKG-INFO +113 -0
- deltona-0.0.1/README.md +44 -0
- deltona-0.0.1/deltona/__init__.py +4 -0
- deltona-0.0.1/deltona/adp.py +173 -0
- deltona-0.0.1/deltona/chromium.py +133 -0
- deltona-0.0.1/deltona/commands/__init__.py +1 -0
- deltona-0.0.1/deltona/commands/admin.py +229 -0
- deltona-0.0.1/deltona/commands/desktop.py +355 -0
- deltona-0.0.1/deltona/commands/git.py +133 -0
- deltona-0.0.1/deltona/commands/media.py +802 -0
- deltona-0.0.1/deltona/commands/misc.py +131 -0
- deltona-0.0.1/deltona/commands/string.py +180 -0
- deltona-0.0.1/deltona/commands/wine.py +307 -0
- deltona-0.0.1/deltona/commands/www.py +164 -0
- deltona-0.0.1/deltona/constants.py +9 -0
- deltona-0.0.1/deltona/gentoo.py +78 -0
- deltona-0.0.1/deltona/git.py +110 -0
- deltona-0.0.1/deltona/io.py +361 -0
- deltona-0.0.1/deltona/media.py +943 -0
- deltona-0.0.1/deltona/naming.py +214 -0
- deltona-0.0.1/deltona/py.typed +0 -0
- deltona-0.0.1/deltona/string.py +356 -0
- deltona-0.0.1/deltona/system.py +430 -0
- deltona-0.0.1/deltona/typing.py +119 -0
- deltona-0.0.1/deltona/ultraiso.py +327 -0
- deltona-0.0.1/deltona/utils.py +656 -0
- deltona-0.0.1/deltona/windows.py +359 -0
- deltona-0.0.1/deltona/www.py +286 -0
- deltona-0.0.1/man/deltona.1 +2934 -0
- deltona-0.0.1/pyproject.toml +514 -0
- deltona-0.0.1/tests/conftest.py +24 -0
- deltona-0.0.1/tests/pyproject.toml +10 -0
- deltona-0.0.1/tests/test_admin_commands.py +172 -0
- deltona-0.0.1/tests/test_adp.py +69 -0
- deltona-0.0.1/tests/test_chromium.py +199 -0
- deltona-0.0.1/tests/test_desktop_commands.py +595 -0
- deltona-0.0.1/tests/test_gentoo.py +58 -0
- deltona-0.0.1/tests/test_git.py +126 -0
- deltona-0.0.1/tests/test_git_commands.py +106 -0
- deltona-0.0.1/tests/test_io.py +552 -0
- deltona-0.0.1/tests/test_media.py +981 -0
- deltona-0.0.1/tests/test_media_commands.py +352 -0
- deltona-0.0.1/tests/test_misc_commands.py +154 -0
- deltona-0.0.1/tests/test_naming.py +76 -0
- deltona-0.0.1/tests/test_string.py +343 -0
- deltona-0.0.1/tests/test_string_commands.py +204 -0
- deltona-0.0.1/tests/test_system.py +636 -0
- deltona-0.0.1/tests/test_ultraiso.py +366 -0
- deltona-0.0.1/tests/test_utils.py +687 -0
- deltona-0.0.1/tests/test_windows.py +56 -0
- deltona-0.0.1/tests/test_wine_commands.py +213 -0
- deltona-0.0.1/tests/test_www.py +286 -0
- 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
|
+
[](https://www.python.org/)
|
|
72
|
+
[](https://pypi.org/project/deltona/)
|
|
73
|
+
[](https://github.com/Tatsh/deltona/tags)
|
|
74
|
+
[](https://github.com/Tatsh/deltona/blob/master/LICENSE.txt)
|
|
75
|
+
[](https://github.com/Tatsh/deltona/compare/v0.0.1...master)
|
|
76
|
+
[](https://github.com/Tatsh/deltona/actions/workflows/qa.yml)
|
|
77
|
+
[](https://github.com/Tatsh/deltona/actions/workflows/tests.yml)
|
|
78
|
+
[](https://coveralls.io/github/Tatsh/deltona?branch=master)
|
|
79
|
+
[](https://deltona.readthedocs.org/?badge=latest)
|
|
80
|
+
[](http://mypy-lang.org/)
|
|
81
|
+
[](https://github.com/pre-commit/pre-commit)
|
|
82
|
+
[](http://www.pydocstyle.org/en/stable/)
|
|
83
|
+
[](https://docs.pytest.org/en/stable/)
|
|
84
|
+
[](https://github.com/astral-sh/ruff)
|
|
85
|
+
[](https://pepy.tech/project/deltona)
|
|
86
|
+
[](https://github.com/Tatsh/deltona/stargazers)
|
|
87
|
+
|
|
88
|
+
[](https://bsky.app/profile/Tatsh.bsky.social)
|
|
89
|
+
[](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
|
+
|
deltona-0.0.1/README.md
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# deltona
|
|
2
|
+
|
|
3
|
+
[](https://www.python.org/)
|
|
4
|
+
[](https://pypi.org/project/deltona/)
|
|
5
|
+
[](https://github.com/Tatsh/deltona/tags)
|
|
6
|
+
[](https://github.com/Tatsh/deltona/blob/master/LICENSE.txt)
|
|
7
|
+
[](https://github.com/Tatsh/deltona/compare/v0.0.1...master)
|
|
8
|
+
[](https://github.com/Tatsh/deltona/actions/workflows/qa.yml)
|
|
9
|
+
[](https://github.com/Tatsh/deltona/actions/workflows/tests.yml)
|
|
10
|
+
[](https://coveralls.io/github/Tatsh/deltona?branch=master)
|
|
11
|
+
[](https://deltona.readthedocs.org/?badge=latest)
|
|
12
|
+
[](http://mypy-lang.org/)
|
|
13
|
+
[](https://github.com/pre-commit/pre-commit)
|
|
14
|
+
[](http://www.pydocstyle.org/en/stable/)
|
|
15
|
+
[](https://docs.pytest.org/en/stable/)
|
|
16
|
+
[](https://github.com/astral-sh/ruff)
|
|
17
|
+
[](https://pepy.tech/project/deltona)
|
|
18
|
+
[](https://github.com/Tatsh/deltona/stargazers)
|
|
19
|
+
|
|
20
|
+
[](https://bsky.app/profile/Tatsh.bsky.social)
|
|
21
|
+
[](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,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."""
|