absfuyu 4.1.1__py3-none-any.whl → 5.0.0__py3-none-any.whl
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.
Potentially problematic release.
This version of absfuyu might be problematic. Click here for more details.
- absfuyu/__init__.py +4 -4
- absfuyu/__main__.py +13 -1
- absfuyu/cli/__init__.py +4 -2
- absfuyu/cli/color.py +7 -0
- absfuyu/cli/do_group.py +9 -91
- absfuyu/cli/tool_group.py +136 -0
- absfuyu/config/__init__.py +17 -34
- absfuyu/core/__init__.py +49 -0
- absfuyu/core/baseclass.py +299 -0
- absfuyu/core/baseclass2.py +165 -0
- absfuyu/core/decorator.py +67 -0
- absfuyu/core/docstring.py +163 -0
- absfuyu/core/dummy_cli.py +67 -0
- absfuyu/core/dummy_func.py +47 -0
- absfuyu/dxt/__init__.py +42 -0
- absfuyu/dxt/dictext.py +201 -0
- absfuyu/dxt/dxt_support.py +79 -0
- absfuyu/dxt/intext.py +586 -0
- absfuyu/dxt/listext.py +508 -0
- absfuyu/dxt/strext.py +530 -0
- absfuyu/{extensions → extra}/__init__.py +3 -2
- absfuyu/extra/beautiful.py +251 -0
- absfuyu/{extensions/extra → extra}/data_analysis.py +51 -82
- absfuyu/fun/__init__.py +110 -135
- absfuyu/fun/tarot.py +9 -17
- absfuyu/game/__init__.py +6 -0
- absfuyu/game/game_stat.py +6 -0
- absfuyu/game/sudoku.py +7 -1
- absfuyu/game/tictactoe.py +12 -5
- absfuyu/game/wordle.py +14 -8
- absfuyu/general/__init__.py +6 -79
- absfuyu/general/content.py +22 -36
- absfuyu/general/generator.py +17 -42
- absfuyu/general/human.py +108 -228
- absfuyu/general/shape.py +1334 -0
- absfuyu/logger.py +8 -13
- absfuyu/pkg_data/__init__.py +137 -99
- absfuyu/pkg_data/deprecated.py +133 -0
- absfuyu/pkg_data/passwordlib_lzma.pkl +0 -0
- absfuyu/sort.py +6 -130
- absfuyu/tools/__init__.py +2 -2
- absfuyu/tools/checksum.py +44 -22
- absfuyu/tools/converter.py +82 -50
- absfuyu/tools/keygen.py +25 -30
- absfuyu/tools/obfuscator.py +246 -112
- absfuyu/tools/passwordlib.py +330 -0
- absfuyu/tools/shutdownizer.py +287 -0
- absfuyu/tools/web.py +2 -9
- absfuyu/util/__init__.py +15 -15
- absfuyu/util/api.py +10 -15
- absfuyu/util/json_method.py +7 -24
- absfuyu/util/lunar.py +3 -9
- absfuyu/util/path.py +22 -27
- absfuyu/util/performance.py +43 -67
- absfuyu/util/shorten_number.py +65 -14
- absfuyu/util/zipped.py +9 -15
- absfuyu-5.0.0.dist-info/METADATA +143 -0
- absfuyu-5.0.0.dist-info/RECORD +68 -0
- absfuyu/core.py +0 -57
- absfuyu/everything.py +0 -32
- absfuyu/extensions/beautiful.py +0 -188
- absfuyu/extensions/dev/__init__.py +0 -244
- absfuyu/extensions/dev/password_hash.py +0 -80
- absfuyu/extensions/dev/passwordlib.py +0 -258
- absfuyu/extensions/dev/project_starter.py +0 -60
- absfuyu/extensions/dev/shutdownizer.py +0 -156
- absfuyu/extensions/extra/__init__.py +0 -24
- absfuyu/fun/WGS.py +0 -134
- absfuyu/general/data_extension.py +0 -1796
- absfuyu/tools/stats.py +0 -226
- absfuyu/util/pkl.py +0 -67
- absfuyu-4.1.1.dist-info/METADATA +0 -121
- absfuyu-4.1.1.dist-info/RECORD +0 -61
- {absfuyu-4.1.1.dist-info → absfuyu-5.0.0.dist-info}/WHEEL +0 -0
- {absfuyu-4.1.1.dist-info → absfuyu-5.0.0.dist-info}/entry_points.txt +0 -0
- {absfuyu-4.1.1.dist-info → absfuyu-5.0.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Absfuyu: Passwordlib
|
|
3
|
+
--------------------
|
|
4
|
+
Password library
|
|
5
|
+
|
|
6
|
+
Version: 5.0.0
|
|
7
|
+
Date updated: 19/02/2025 (dd/mm/yyyy)
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
# Module level
|
|
11
|
+
# ---------------------------------------------------------------------------
|
|
12
|
+
__all__ = ["PasswordGenerator", "TOTP"]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# Library
|
|
16
|
+
# ---------------------------------------------------------------------------
|
|
17
|
+
import hashlib
|
|
18
|
+
import os
|
|
19
|
+
import random
|
|
20
|
+
import re
|
|
21
|
+
from typing import ClassVar, Literal, NamedTuple
|
|
22
|
+
from urllib.parse import quote, urlencode
|
|
23
|
+
|
|
24
|
+
from absfuyu.core import BaseClass, deprecated, versionadded
|
|
25
|
+
from absfuyu.dxt import DictExt, Text
|
|
26
|
+
from absfuyu.general.generator import Charset, Generator
|
|
27
|
+
from absfuyu.logger import logger
|
|
28
|
+
from absfuyu.pkg_data import DataList, DataLoader
|
|
29
|
+
from absfuyu.util import set_min
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# Function
|
|
33
|
+
# ---------------------------------------------------------------------------
|
|
34
|
+
@deprecated("5.0.0")
|
|
35
|
+
@versionadded("4.2.0")
|
|
36
|
+
def _password_check(password: str) -> bool:
|
|
37
|
+
"""
|
|
38
|
+
Verify the strength of ``password``.
|
|
39
|
+
A password is considered strong if:
|
|
40
|
+
|
|
41
|
+
- 8 characters length or more
|
|
42
|
+
- 1 digit or more
|
|
43
|
+
- 1 symbol or more
|
|
44
|
+
- 1 uppercase letter or more
|
|
45
|
+
- 1 lowercase letter or more
|
|
46
|
+
|
|
47
|
+
:param password: Password want to be checked
|
|
48
|
+
:type password: str
|
|
49
|
+
:rtype: bool
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
# calculating the length
|
|
53
|
+
length_error = len(password) < 8
|
|
54
|
+
|
|
55
|
+
# searching for digits
|
|
56
|
+
digit_error = re.search(r"\d", password) is None
|
|
57
|
+
|
|
58
|
+
# searching for uppercase
|
|
59
|
+
uppercase_error = re.search(r"[A-Z]", password) is None
|
|
60
|
+
|
|
61
|
+
# searching for lowercase
|
|
62
|
+
lowercase_error = re.search(r"[a-z]", password) is None
|
|
63
|
+
|
|
64
|
+
# searching for symbols
|
|
65
|
+
symbols = re.compile(r"[ !#$%&'()*+,-./[\\\]^_`{|}~" + r'"]')
|
|
66
|
+
symbol_error = symbols.search(password) is None
|
|
67
|
+
|
|
68
|
+
detail = {
|
|
69
|
+
"length_error": length_error,
|
|
70
|
+
"digit_error": digit_error,
|
|
71
|
+
"uppercase_error": uppercase_error,
|
|
72
|
+
"lowercase_error": lowercase_error,
|
|
73
|
+
"symbol_error": symbol_error,
|
|
74
|
+
}
|
|
75
|
+
logger.debug(f"Password error summary: {detail}")
|
|
76
|
+
|
|
77
|
+
return not any(
|
|
78
|
+
[
|
|
79
|
+
length_error,
|
|
80
|
+
digit_error,
|
|
81
|
+
uppercase_error,
|
|
82
|
+
lowercase_error,
|
|
83
|
+
symbol_error,
|
|
84
|
+
]
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
# Class
|
|
89
|
+
# ---------------------------------------------------------------------------
|
|
90
|
+
class PasswordHash(NamedTuple):
|
|
91
|
+
salt: bytes
|
|
92
|
+
key: bytes
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@versionadded("4.2.0")
|
|
96
|
+
class PasswordGenerator(BaseClass):
|
|
97
|
+
"""Password Generator"""
|
|
98
|
+
|
|
99
|
+
def __str__(self) -> str:
|
|
100
|
+
return f"{self.__class__.__name__}()"
|
|
101
|
+
|
|
102
|
+
@staticmethod
|
|
103
|
+
def password_hash(password: str) -> PasswordHash:
|
|
104
|
+
"""
|
|
105
|
+
Generate hash for password
|
|
106
|
+
"""
|
|
107
|
+
salt = os.urandom(32)
|
|
108
|
+
key = hashlib.pbkdf2_hmac(
|
|
109
|
+
hash_name="sha256",
|
|
110
|
+
password=password.encode("utf-8"),
|
|
111
|
+
salt=salt,
|
|
112
|
+
iterations=100000,
|
|
113
|
+
)
|
|
114
|
+
out = PasswordHash(salt, key)
|
|
115
|
+
return out
|
|
116
|
+
|
|
117
|
+
@staticmethod
|
|
118
|
+
def password_check(password: str) -> dict:
|
|
119
|
+
"""Check password's characteristic"""
|
|
120
|
+
data = Text(password).analyze()
|
|
121
|
+
data = DictExt(data).apply(lambda x: True if x > 0 else False) # type: ignore
|
|
122
|
+
data.__setitem__("length", len(password))
|
|
123
|
+
return dict(data)
|
|
124
|
+
|
|
125
|
+
# Password generator
|
|
126
|
+
@staticmethod
|
|
127
|
+
def generate_password(
|
|
128
|
+
length: int = 8,
|
|
129
|
+
include_uppercase: bool = True,
|
|
130
|
+
include_number: bool = True,
|
|
131
|
+
include_special: bool = True,
|
|
132
|
+
) -> str:
|
|
133
|
+
r"""
|
|
134
|
+
Generate a random password
|
|
135
|
+
|
|
136
|
+
Parameters
|
|
137
|
+
----------
|
|
138
|
+
length : int
|
|
139
|
+
| Length of the password.
|
|
140
|
+
| Minimum value: ``8``
|
|
141
|
+
| (Default: ``8``)
|
|
142
|
+
|
|
143
|
+
include_uppercase : bool
|
|
144
|
+
Include uppercase character in the password (Default: ``True``)
|
|
145
|
+
|
|
146
|
+
include_number : bool
|
|
147
|
+
Include digit character in the password (Default: ``True``)
|
|
148
|
+
|
|
149
|
+
include_special : bool
|
|
150
|
+
Include special character in the password (Default: ``True``)
|
|
151
|
+
|
|
152
|
+
Returns
|
|
153
|
+
-------
|
|
154
|
+
str
|
|
155
|
+
Generated password
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
Example:
|
|
159
|
+
--------
|
|
160
|
+
>>> Password.generate_password()
|
|
161
|
+
[T&b@mq2
|
|
162
|
+
"""
|
|
163
|
+
charset = Charset.LOWERCASE
|
|
164
|
+
check = 0
|
|
165
|
+
|
|
166
|
+
if include_uppercase:
|
|
167
|
+
charset += Charset.UPPERCASE
|
|
168
|
+
check += 1
|
|
169
|
+
|
|
170
|
+
if include_number:
|
|
171
|
+
charset += Charset.DIGIT
|
|
172
|
+
check += 1
|
|
173
|
+
|
|
174
|
+
if include_special:
|
|
175
|
+
charset += r"[!#$%&'()*+,-./]^_`{|}~\""
|
|
176
|
+
check += 1
|
|
177
|
+
|
|
178
|
+
while True:
|
|
179
|
+
pwd = Generator.generate_string(
|
|
180
|
+
charset=charset,
|
|
181
|
+
size=set_min(length, min_value=8), # type: ignore
|
|
182
|
+
times=1,
|
|
183
|
+
string_type_if_1=True,
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
analyze = Text(pwd).analyze() # Count each type of char
|
|
187
|
+
|
|
188
|
+
s = sum([1 for x in analyze.values() if x > 0]) # type: ignore
|
|
189
|
+
if s > check: # Break loop if each type of char has atleast 1
|
|
190
|
+
break
|
|
191
|
+
return pwd # type: ignore
|
|
192
|
+
|
|
193
|
+
@staticmethod
|
|
194
|
+
def generate_passphrase(
|
|
195
|
+
num_of_blocks: int = 5,
|
|
196
|
+
block_divider: str | None = None,
|
|
197
|
+
first_letter_cap: bool = True,
|
|
198
|
+
include_number: bool = True,
|
|
199
|
+
*,
|
|
200
|
+
custom_word_list: list[str] | None = None,
|
|
201
|
+
) -> str:
|
|
202
|
+
"""
|
|
203
|
+
Generate a random passphrase
|
|
204
|
+
|
|
205
|
+
Parameters
|
|
206
|
+
----------
|
|
207
|
+
num_of_blocks : int
|
|
208
|
+
Number of word used (Default: ``5``)
|
|
209
|
+
|
|
210
|
+
block_divider : str
|
|
211
|
+
Character symbol that between each word (Default: ``"-"``)
|
|
212
|
+
|
|
213
|
+
first_letter_cap : bool
|
|
214
|
+
Capitalize first character of each word (Default: ``True``)
|
|
215
|
+
|
|
216
|
+
include_number : bool
|
|
217
|
+
Add number to the end of each word (Default: ``True``)
|
|
218
|
+
|
|
219
|
+
custom_word_list : list[str] | None
|
|
220
|
+
Custom word list for passphrase generation, by default uses a list of 360K+ words
|
|
221
|
+
|
|
222
|
+
Returns
|
|
223
|
+
-------
|
|
224
|
+
str
|
|
225
|
+
Generated passphrase
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
Example:
|
|
229
|
+
--------
|
|
230
|
+
>>> print(Password().generate_passphrase())
|
|
231
|
+
Myomectomies7-Sully4-Torpedomen7-Netful2-Begaud8
|
|
232
|
+
"""
|
|
233
|
+
words: list[str] = (
|
|
234
|
+
DataLoader(DataList.PASSWORDLIB).load().decode().split(",")
|
|
235
|
+
if not custom_word_list
|
|
236
|
+
else custom_word_list
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
if block_divider is None:
|
|
240
|
+
block_divider = "-"
|
|
241
|
+
|
|
242
|
+
dat = [random.choice(words) for _ in range(num_of_blocks)]
|
|
243
|
+
|
|
244
|
+
if first_letter_cap:
|
|
245
|
+
dat = list(map(lambda x: x.title(), dat))
|
|
246
|
+
|
|
247
|
+
if include_number:
|
|
248
|
+
idx = random.choice(range(num_of_blocks))
|
|
249
|
+
dat[idx] += str(random.choice(range(10)))
|
|
250
|
+
|
|
251
|
+
return block_divider.join(dat)
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
@versionadded("5.0.0")
|
|
255
|
+
class TOTP(BaseClass):
|
|
256
|
+
"""
|
|
257
|
+
A class to represent a Time-based One-Time Password (TOTP) generator.
|
|
258
|
+
"""
|
|
259
|
+
|
|
260
|
+
URL_SCHEME: ClassVar[str] = "otpauth://totp/"
|
|
261
|
+
|
|
262
|
+
def __init__(
|
|
263
|
+
self,
|
|
264
|
+
secret: str,
|
|
265
|
+
name: str | None = None,
|
|
266
|
+
issuer: str | None = None,
|
|
267
|
+
algorithm: Literal["SHA1", "SHA256", "SHA512"] = "SHA1",
|
|
268
|
+
digit: int = 6,
|
|
269
|
+
period: int = 30,
|
|
270
|
+
) -> None:
|
|
271
|
+
"""
|
|
272
|
+
Initializes a TOTP instance.
|
|
273
|
+
|
|
274
|
+
Parameters
|
|
275
|
+
----------
|
|
276
|
+
secret : str
|
|
277
|
+
The shared secret key used to generate the TOTP.
|
|
278
|
+
|
|
279
|
+
name : str, optional
|
|
280
|
+
The name associated with the TOTP. If not provided, defaults to ``"None"``.
|
|
281
|
+
|
|
282
|
+
issuer : str, optional
|
|
283
|
+
The issuer of the TOTP.
|
|
284
|
+
|
|
285
|
+
algorithm : Literal["SHA1", "SHA256", "SHA512"], optional
|
|
286
|
+
The hashing algorithm used to generate the TOTP.
|
|
287
|
+
Must be one of ``"SHA1"``, ``"SHA256"``, or ``"SHA512"``.
|
|
288
|
+
Defaults to ``"SHA1"``.
|
|
289
|
+
|
|
290
|
+
digit : int, optional
|
|
291
|
+
The number of digits in the generated TOTP. Must be greater than 0.
|
|
292
|
+
Defaults to ``6``.
|
|
293
|
+
|
|
294
|
+
period : int, optional
|
|
295
|
+
The time step in seconds for TOTP generation. Must be greater than 0.
|
|
296
|
+
Defaults to ``30``.
|
|
297
|
+
"""
|
|
298
|
+
self.secret = secret.upper()
|
|
299
|
+
self.name = name if name else "None"
|
|
300
|
+
self.issuer = issuer
|
|
301
|
+
self.algorithm = algorithm.upper()
|
|
302
|
+
self.digit = max(digit, 1) # digit must be larger than 0
|
|
303
|
+
self.period = max(period, 1) # period must be larger than 0
|
|
304
|
+
|
|
305
|
+
def to_url(self) -> str:
|
|
306
|
+
"""
|
|
307
|
+
Generates a URL for the TOTP in the otpauth format.
|
|
308
|
+
|
|
309
|
+
The URL format is as follows:
|
|
310
|
+
``otpauth://totp/<name>?secret=<secret>&issuer=<issuer>&algorithm=<algorithm>&digit=<digit>&period=<period>``
|
|
311
|
+
|
|
312
|
+
Returns
|
|
313
|
+
-------
|
|
314
|
+
str
|
|
315
|
+
A URL representing the TOTP in otpauth format.
|
|
316
|
+
"""
|
|
317
|
+
params = {
|
|
318
|
+
"secret": self.secret,
|
|
319
|
+
"issuer": self.issuer,
|
|
320
|
+
"algorithm": self.algorithm,
|
|
321
|
+
"digit": self.digit,
|
|
322
|
+
"period": self.period,
|
|
323
|
+
}
|
|
324
|
+
# Filter out None values from the params dictionary
|
|
325
|
+
filtered_params = {k: v for k, v in params.items() if v is not None}
|
|
326
|
+
# filtered_params = {k: v for k, v in self.__dict__.items() if v is not None}
|
|
327
|
+
|
|
328
|
+
name = quote(self.name)
|
|
329
|
+
tail = urlencode(filtered_params, quote_via=quote)
|
|
330
|
+
return f"{self.URL_SCHEME}{name}?{tail}"
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Absfuyu: Shutdownizer
|
|
3
|
+
---------------------
|
|
4
|
+
This shutdowns
|
|
5
|
+
|
|
6
|
+
Version: 5.0.0
|
|
7
|
+
Date updated: 23/02/2025 (dd/mm/yyyy)
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
# Module level
|
|
11
|
+
# ---------------------------------------------------------------------------
|
|
12
|
+
__all__ = ["ShutDownizer", "ShutdownEngine"]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# Library
|
|
16
|
+
# ---------------------------------------------------------------------------
|
|
17
|
+
import os
|
|
18
|
+
import subprocess
|
|
19
|
+
import sys
|
|
20
|
+
from abc import ABC, abstractmethod
|
|
21
|
+
from datetime import datetime, time, timedelta
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
from typing import Annotated
|
|
24
|
+
|
|
25
|
+
try:
|
|
26
|
+
from typing import override # type: ignore
|
|
27
|
+
except ImportError:
|
|
28
|
+
from absfuyu.core.decorator import dummy_decorator as override
|
|
29
|
+
|
|
30
|
+
from absfuyu.core import BaseClass, versionadded, versionchanged
|
|
31
|
+
from absfuyu.logger import logger
|
|
32
|
+
|
|
33
|
+
# TODO: Schedule shutdown, random time shutdown, test
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
# Class
|
|
37
|
+
# ---------------------------------------------------------------------------
|
|
38
|
+
@versionadded("4.2.0")
|
|
39
|
+
class ShutDownizer(BaseClass):
|
|
40
|
+
"""
|
|
41
|
+
ShutDownizer
|
|
42
|
+
|
|
43
|
+
Shutdown tool because why not
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
__slots__ = ("os", "engine")
|
|
47
|
+
|
|
48
|
+
def __init__(self) -> None:
|
|
49
|
+
self.os: str = sys.platform
|
|
50
|
+
logger.debug(f"Current OS: {self.os}")
|
|
51
|
+
|
|
52
|
+
if self.os in ["win32", "cygwin"]: # Windows
|
|
53
|
+
self.engine = ShutdownEngineWin() # type: ignore
|
|
54
|
+
elif self.os == "darwin": # MacOS
|
|
55
|
+
self.engine = ShutdownEngineMac() # type: ignore
|
|
56
|
+
elif self.os == "linux": # Linux
|
|
57
|
+
self.engine = ShutdownEngineLinux() # type: ignore
|
|
58
|
+
else:
|
|
59
|
+
raise SystemError("OS not supported")
|
|
60
|
+
|
|
61
|
+
def __str__(self) -> str:
|
|
62
|
+
return f"{self.__class__.__name__}({self.os})"
|
|
63
|
+
|
|
64
|
+
def shutdown(self, *args, **kwargs) -> None:
|
|
65
|
+
"""Shutdown"""
|
|
66
|
+
self.engine.shutdown(*args, **kwargs)
|
|
67
|
+
|
|
68
|
+
def restart(self, *args, **kwargs) -> None:
|
|
69
|
+
"""Restart"""
|
|
70
|
+
self.engine.restart(*args, **kwargs)
|
|
71
|
+
|
|
72
|
+
def cancel(self) -> None:
|
|
73
|
+
"""Cancel"""
|
|
74
|
+
self.engine.cancel()
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class ShutdownEngine(ABC, BaseClass):
|
|
78
|
+
"""
|
|
79
|
+
Abstract shutdown class for different type of OS
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
def __str__(self) -> str:
|
|
83
|
+
return f"{self.__class__.__name__}()"
|
|
84
|
+
|
|
85
|
+
def _execute_cmd(self, cmd: str | list) -> None:
|
|
86
|
+
"""Execute the cmd"""
|
|
87
|
+
try:
|
|
88
|
+
if isinstance(cmd, str):
|
|
89
|
+
subprocess.run(cmd.split())
|
|
90
|
+
elif isinstance(cmd, list):
|
|
91
|
+
subprocess.run(cmd)
|
|
92
|
+
except (FileNotFoundError, Exception) as e:
|
|
93
|
+
logger.error(f'"{cmd}" failed to run: {e}')
|
|
94
|
+
raise ValueError(f'"{cmd}" failed to run') # noqa
|
|
95
|
+
|
|
96
|
+
def _execute_multiple_cmds(self, cmds: list) -> None:
|
|
97
|
+
if not isinstance(cmds, list):
|
|
98
|
+
raise ValueError("cmds must be a <list>")
|
|
99
|
+
for cmd in cmds:
|
|
100
|
+
try:
|
|
101
|
+
logger.debug(f"Executing: {cmd}")
|
|
102
|
+
self._execute_cmd(cmd)
|
|
103
|
+
break
|
|
104
|
+
except Exception as e:
|
|
105
|
+
logger.error(f'"{cmd}" failed to run: {e}')
|
|
106
|
+
|
|
107
|
+
@abstractmethod
|
|
108
|
+
def shutdown(self, *args, **kwargs) -> None:
|
|
109
|
+
"""Shutdown"""
|
|
110
|
+
pass
|
|
111
|
+
|
|
112
|
+
@abstractmethod
|
|
113
|
+
def restart(self, *args, **kwargs) -> None:
|
|
114
|
+
"""Restart"""
|
|
115
|
+
pass
|
|
116
|
+
|
|
117
|
+
@abstractmethod
|
|
118
|
+
def sleep(self, *args, **kwargs) -> None:
|
|
119
|
+
"""Sleep"""
|
|
120
|
+
pass
|
|
121
|
+
|
|
122
|
+
@abstractmethod
|
|
123
|
+
def abort(self) -> None:
|
|
124
|
+
"""Abort/Cancel"""
|
|
125
|
+
pass
|
|
126
|
+
|
|
127
|
+
def cancel(self) -> None:
|
|
128
|
+
"""Abort/Cancel"""
|
|
129
|
+
self.abort()
|
|
130
|
+
|
|
131
|
+
def _calculate_time(
|
|
132
|
+
self,
|
|
133
|
+
h: Annotated[int, "positive"] = 0,
|
|
134
|
+
m: Annotated[int, "positive"] = 0,
|
|
135
|
+
aggregate: bool = True,
|
|
136
|
+
) -> int:
|
|
137
|
+
"""
|
|
138
|
+
Calculate time for scheduled shutdown.
|
|
139
|
+
|
|
140
|
+
Parameters
|
|
141
|
+
----------
|
|
142
|
+
h : int, optional
|
|
143
|
+
Hours to add (24h format), by default ``0``
|
|
144
|
+
|
|
145
|
+
m : int, optional
|
|
146
|
+
Minutes to add (24h format), by default ``0``
|
|
147
|
+
|
|
148
|
+
aggregate : bool, optional
|
|
149
|
+
This add hours and and minutes to `time.now()`, by default ``True``
|
|
150
|
+
- ``True`` : Add hours and minutes to current time
|
|
151
|
+
- ``False``: Use ``h`` and ``m`` as fixed time point to shutdown
|
|
152
|
+
|
|
153
|
+
Returns
|
|
154
|
+
-------
|
|
155
|
+
int
|
|
156
|
+
Seconds left until shutdown.
|
|
157
|
+
"""
|
|
158
|
+
h = max(0, h) # Force >= 0
|
|
159
|
+
m = max(0, m)
|
|
160
|
+
now = datetime.now()
|
|
161
|
+
if aggregate:
|
|
162
|
+
delta = timedelta(hours=h, minutes=m)
|
|
163
|
+
out = delta.seconds
|
|
164
|
+
else:
|
|
165
|
+
new_time = datetime.combine(now.date(), time(hour=h, minute=m))
|
|
166
|
+
diff = new_time - now
|
|
167
|
+
out = diff.seconds
|
|
168
|
+
return out
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
class ShutdownEngineWin(ShutdownEngine):
|
|
172
|
+
"""ShutDownizer - Windows"""
|
|
173
|
+
|
|
174
|
+
@override
|
|
175
|
+
@versionchanged("5.0.0", "Scheduled shutdown")
|
|
176
|
+
def shutdown(
|
|
177
|
+
self,
|
|
178
|
+
h: Annotated[int, "positive"] = 0,
|
|
179
|
+
m: Annotated[int, "positive"] = 0,
|
|
180
|
+
aggregate: bool = True,
|
|
181
|
+
) -> None:
|
|
182
|
+
time_until_sd = self._calculate_time(h=h, m=m, aggregate=aggregate)
|
|
183
|
+
cmds = [f"shutdown -f -s -t {time_until_sd}"]
|
|
184
|
+
self._execute_multiple_cmds(cmds)
|
|
185
|
+
|
|
186
|
+
@override
|
|
187
|
+
def restart(self, *args, **kwargs) -> None:
|
|
188
|
+
cmds = ["shutdown -r"]
|
|
189
|
+
self._execute_multiple_cmds(cmds)
|
|
190
|
+
|
|
191
|
+
@override
|
|
192
|
+
def sleep(self, *args, **kwargs) -> None:
|
|
193
|
+
cmds = ["rundll32.exe powrprof.dll,SetSuspendState 0,1,0"]
|
|
194
|
+
self._execute_multiple_cmds(cmds)
|
|
195
|
+
|
|
196
|
+
@override
|
|
197
|
+
def abort(self) -> None:
|
|
198
|
+
cmds = ["shutdown -a"]
|
|
199
|
+
self._execute_multiple_cmds(cmds)
|
|
200
|
+
|
|
201
|
+
def _punish(self, *, are_you_sure_about_this: bool = False) -> None:
|
|
202
|
+
"""Create a `batch` script that shut down computer when boot up"""
|
|
203
|
+
if not are_you_sure_about_this:
|
|
204
|
+
return None
|
|
205
|
+
try:
|
|
206
|
+
startup_folder_win = Path(os.getenv("appdata")).joinpath( # type: ignore
|
|
207
|
+
"Microsoft", "Windows", "Start Menu", "Programs", "Startup"
|
|
208
|
+
)
|
|
209
|
+
with open(startup_folder_win.joinpath("system.bat"), "w") as f:
|
|
210
|
+
f.write("shutdown -f -s -t 0")
|
|
211
|
+
except Exception:
|
|
212
|
+
logger.error("Cannot write file to startup folder")
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
class ShutdownEngineMac(ShutdownEngine):
|
|
216
|
+
"""ShutDownizer - MacOS"""
|
|
217
|
+
|
|
218
|
+
@override
|
|
219
|
+
def shutdown(self, *args, **kwargs) -> None:
|
|
220
|
+
cmds = [
|
|
221
|
+
["osascript", "-e", 'tell application "System Events" to shut down'],
|
|
222
|
+
"pmset sleepnow",
|
|
223
|
+
"shutdown -h now",
|
|
224
|
+
"sudo shutdown -h now",
|
|
225
|
+
]
|
|
226
|
+
self._execute_multiple_cmds(cmds)
|
|
227
|
+
|
|
228
|
+
@override
|
|
229
|
+
def restart(self, *args, **kwargs) -> None:
|
|
230
|
+
cmds = [
|
|
231
|
+
["osascript", "-e", 'tell application "System Events" to restart'],
|
|
232
|
+
"shutdown -r now",
|
|
233
|
+
"sudo shutdown -r now",
|
|
234
|
+
]
|
|
235
|
+
self._execute_multiple_cmds(cmds)
|
|
236
|
+
|
|
237
|
+
@override
|
|
238
|
+
def sleep(self, *args, **kwargs) -> None:
|
|
239
|
+
cmds = [
|
|
240
|
+
["osascript", "-e", 'tell application "System Events" to sleep'],
|
|
241
|
+
"pmset sleepnow",
|
|
242
|
+
"shutdown -s now",
|
|
243
|
+
"sudo shutdown -s now",
|
|
244
|
+
]
|
|
245
|
+
self._execute_multiple_cmds(cmds)
|
|
246
|
+
|
|
247
|
+
@override
|
|
248
|
+
def abort(self) -> None:
|
|
249
|
+
cmds = [
|
|
250
|
+
["osascript", "-e", 'tell application "System Events" to cancel shutdown'],
|
|
251
|
+
"killall shutdown",
|
|
252
|
+
"shutdown -c",
|
|
253
|
+
"sudo shutdown -c",
|
|
254
|
+
]
|
|
255
|
+
self._execute_multiple_cmds(cmds)
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
class ShutdownEngineLinux(ShutdownEngine):
|
|
259
|
+
"""ShutDownizer - Linux"""
|
|
260
|
+
|
|
261
|
+
@override
|
|
262
|
+
def shutdown(self, *args, **kwargs) -> None:
|
|
263
|
+
cmds = [
|
|
264
|
+
"gnome-session-quit --power-off",
|
|
265
|
+
"systemctl --user poweroff",
|
|
266
|
+
"sudo shutdown -h now",
|
|
267
|
+
]
|
|
268
|
+
self._execute_multiple_cmds(cmds)
|
|
269
|
+
|
|
270
|
+
@override
|
|
271
|
+
def restart(self, *args, **kwargs) -> None:
|
|
272
|
+
cmds = [
|
|
273
|
+
"gnome-session-quit --reboot",
|
|
274
|
+
"systemctl reboot",
|
|
275
|
+
"sudo shutdown -r now",
|
|
276
|
+
]
|
|
277
|
+
self._execute_multiple_cmds(cmds)
|
|
278
|
+
|
|
279
|
+
@override
|
|
280
|
+
def sleep(self, *args, **kwargs) -> None:
|
|
281
|
+
cmds = ["systemctl suspend", "sudo shutdown -s now"]
|
|
282
|
+
self._execute_multiple_cmds(cmds)
|
|
283
|
+
|
|
284
|
+
@override
|
|
285
|
+
def abort(self) -> None:
|
|
286
|
+
cmds = ["sudo shutdown -c"]
|
|
287
|
+
self._execute_multiple_cmds(cmds)
|
absfuyu/tools/web.py
CHANGED
|
@@ -8,7 +8,7 @@ Date updated: 05/04/2024 (dd/mm/yyyy)
|
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
10
|
# Library
|
|
11
|
-
|
|
11
|
+
# ---------------------------------------------------------------------------
|
|
12
12
|
import requests
|
|
13
13
|
from bs4 import BeautifulSoup
|
|
14
14
|
|
|
@@ -16,7 +16,7 @@ from absfuyu.logger import logger
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
# Function
|
|
19
|
-
|
|
19
|
+
# ---------------------------------------------------------------------------
|
|
20
20
|
def soup_link(link: str) -> BeautifulSoup:
|
|
21
21
|
"""
|
|
22
22
|
``BeautifulSoup`` the link
|
|
@@ -53,10 +53,3 @@ def gen_random_commit_msg() -> str:
|
|
|
53
53
|
out = soup_link("https://whatthecommit.com/").get_text()[34:-20]
|
|
54
54
|
logger.debug(out)
|
|
55
55
|
return out # type: ignore
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
# Run
|
|
59
|
-
###########################################################################
|
|
60
|
-
if __name__ == "__main__":
|
|
61
|
-
logger.setLevel(10)
|
|
62
|
-
gen_random_commit_msg()
|