absfuyu 5.4.0__py3-none-any.whl → 5.6.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 +1 -1
- absfuyu/__main__.py +2 -2
- absfuyu/cli/__init__.py +2 -2
- absfuyu/cli/color.py +2 -2
- absfuyu/cli/config_group.py +2 -2
- absfuyu/cli/do_group.py +2 -2
- absfuyu/cli/game_group.py +2 -2
- absfuyu/cli/tool_group.py +2 -2
- absfuyu/config/__init__.py +2 -2
- absfuyu/core/__init__.py +4 -4
- absfuyu/core/baseclass.py +518 -154
- absfuyu/core/baseclass2.py +3 -3
- absfuyu/core/decorator.py +2 -2
- absfuyu/core/docstring.py +2 -2
- absfuyu/core/dummy_cli.py +2 -2
- absfuyu/core/dummy_func.py +2 -2
- absfuyu/dxt/__init__.py +2 -2
- absfuyu/dxt/dictext.py +5 -11
- absfuyu/dxt/dxt_support.py +2 -2
- absfuyu/dxt/intext.py +4 -4
- absfuyu/dxt/listext.py +69 -14
- absfuyu/dxt/strext.py +5 -5
- absfuyu/extra/__init__.py +2 -2
- absfuyu/extra/beautiful.py +2 -2
- absfuyu/extra/da/__init__.py +3 -3
- absfuyu/extra/da/dadf.py +4 -4
- absfuyu/extra/da/dadf_base.py +2 -2
- absfuyu/extra/da/df_func.py +2 -2
- absfuyu/extra/da/mplt.py +2 -2
- absfuyu/extra/data_analysis.py +2 -2
- absfuyu/extra/pdf.py +89 -0
- absfuyu/fun/__init__.py +2 -2
- absfuyu/fun/rubik.py +2 -2
- absfuyu/fun/tarot.py +2 -2
- absfuyu/game/__init__.py +2 -2
- absfuyu/game/game_stat.py +2 -2
- absfuyu/game/sudoku.py +2 -2
- absfuyu/game/tictactoe.py +2 -2
- absfuyu/game/wordle.py +2 -2
- absfuyu/general/__init__.py +2 -2
- absfuyu/general/content.py +4 -4
- absfuyu/general/human.py +2 -2
- absfuyu/general/shape.py +2 -2
- absfuyu/logger.py +2 -2
- absfuyu/pkg_data/__init__.py +2 -2
- absfuyu/pkg_data/deprecated.py +2 -2
- absfuyu/sort.py +2 -2
- absfuyu/tools/__init__.py +2 -2
- absfuyu/tools/checksum.py +2 -2
- absfuyu/tools/converter.py +2 -2
- absfuyu/tools/generator.py +4 -4
- absfuyu/tools/inspector.py +325 -71
- absfuyu/tools/keygen.py +2 -2
- absfuyu/tools/obfuscator.py +4 -4
- absfuyu/tools/passwordlib.py +2 -2
- absfuyu/tools/shutdownizer.py +2 -2
- absfuyu/tools/sw.py +514 -0
- absfuyu/tools/web.py +2 -2
- absfuyu/typings.py +2 -2
- absfuyu/util/__init__.py +14 -3
- absfuyu/util/api.py +2 -2
- absfuyu/util/json_method.py +2 -2
- absfuyu/util/lunar.py +2 -2
- absfuyu/util/path.py +53 -4
- absfuyu/util/performance.py +2 -2
- absfuyu/util/shorten_number.py +2 -2
- absfuyu/util/text_table.py +33 -14
- absfuyu/util/zipped.py +2 -2
- absfuyu/version.py +3 -3
- {absfuyu-5.4.0.dist-info → absfuyu-5.6.0.dist-info}/METADATA +7 -2
- absfuyu-5.6.0.dist-info/RECORD +79 -0
- absfuyu-5.4.0.dist-info/RECORD +0 -77
- {absfuyu-5.4.0.dist-info → absfuyu-5.6.0.dist-info}/WHEEL +0 -0
- {absfuyu-5.4.0.dist-info → absfuyu-5.6.0.dist-info}/entry_points.txt +0 -0
- {absfuyu-5.4.0.dist-info → absfuyu-5.6.0.dist-info}/licenses/LICENSE +0 -0
absfuyu/tools/sw.py
ADDED
|
@@ -0,0 +1,514 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Absufyu: Software
|
|
3
|
+
-----------------
|
|
4
|
+
Software, pyinstaller related stuff
|
|
5
|
+
|
|
6
|
+
Version: 5.6.0
|
|
7
|
+
Date updated: 12/09/2025 (dd/mm/yyyy)
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
# Module level
|
|
11
|
+
# ---------------------------------------------------------------------------
|
|
12
|
+
__all__ = [
|
|
13
|
+
# Function
|
|
14
|
+
"get_system_info",
|
|
15
|
+
"get_pyinstaller_exe_dir",
|
|
16
|
+
"get_pyinstaller_resource_path",
|
|
17
|
+
"HWIDgen",
|
|
18
|
+
"LicenseKeySystem",
|
|
19
|
+
"BasicSoftwareProtection",
|
|
20
|
+
# Support
|
|
21
|
+
"SystemInfo",
|
|
22
|
+
"LicenseKey",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# Library
|
|
27
|
+
# ---------------------------------------------------------------------------
|
|
28
|
+
import base64
|
|
29
|
+
import hashlib
|
|
30
|
+
import hmac
|
|
31
|
+
import json
|
|
32
|
+
import os
|
|
33
|
+
import platform
|
|
34
|
+
import socket
|
|
35
|
+
import subprocess
|
|
36
|
+
import sys
|
|
37
|
+
import uuid
|
|
38
|
+
from datetime import datetime
|
|
39
|
+
from pathlib import Path
|
|
40
|
+
from typing import Literal, NamedTuple, TypedDict
|
|
41
|
+
|
|
42
|
+
from absfuyu.core.baseclass import BaseClass
|
|
43
|
+
from absfuyu.dxt import Text
|
|
44
|
+
from absfuyu.tools.converter import Base64EncodeDecode
|
|
45
|
+
from absfuyu.util import stop_after_day
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
# System Info
|
|
49
|
+
# ---------------------------------------------------------------------------
|
|
50
|
+
class SystemInfo(NamedTuple):
|
|
51
|
+
"""System info"""
|
|
52
|
+
|
|
53
|
+
os_type: str | Literal["Windows", "Linux", "Darwin"]
|
|
54
|
+
os_kernel: str
|
|
55
|
+
os_arch: str
|
|
56
|
+
system_name: str
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def get_system_info() -> SystemInfo:
|
|
60
|
+
"""
|
|
61
|
+
Returns the current operating system info.
|
|
62
|
+
|
|
63
|
+
The function attempts to retrieve the computer name using `platform.node()`.
|
|
64
|
+
If that fails or returns an empty string, it falls back to environment variables
|
|
65
|
+
or `socket.gethostname()` as a last resort, depending on the OS.
|
|
66
|
+
|
|
67
|
+
Returns
|
|
68
|
+
-------
|
|
69
|
+
SystemInfo
|
|
70
|
+
A tuple containing:
|
|
71
|
+
- OS name (e.g., 'Windows', 'Linux', 'Darwin')
|
|
72
|
+
- OS kernel (version)
|
|
73
|
+
- OS arch
|
|
74
|
+
- Computer name (hostname)
|
|
75
|
+
"""
|
|
76
|
+
os_name = platform.system()
|
|
77
|
+
|
|
78
|
+
# Get computer name
|
|
79
|
+
try:
|
|
80
|
+
computer_name = platform.node()
|
|
81
|
+
if not computer_name:
|
|
82
|
+
raise ValueError("Empty name")
|
|
83
|
+
except ValueError:
|
|
84
|
+
if os_name == "Windows":
|
|
85
|
+
computer_name = os.environ.get("COMPUTERNAME", "Unknown")
|
|
86
|
+
else:
|
|
87
|
+
computer_name = os.environ.get("HOSTNAME", socket.gethostname())
|
|
88
|
+
|
|
89
|
+
# Return
|
|
90
|
+
return SystemInfo(
|
|
91
|
+
os_name, platform.version(), platform.machine().lower(), computer_name
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
# Pyinstaller
|
|
96
|
+
# ---------------------------------------------------------------------------
|
|
97
|
+
def get_pyinstaller_exe_dir() -> Path:
|
|
98
|
+
"""
|
|
99
|
+
Returns the directory where the current script or executable resides.
|
|
100
|
+
|
|
101
|
+
This function is useful for locating resources relative to the running script or
|
|
102
|
+
bundled executable (e.g., when using PyInstaller). It checks if the script is
|
|
103
|
+
running in a "frozen" state (as an executable), and returns the appropriate
|
|
104
|
+
directory accordingly.
|
|
105
|
+
|
|
106
|
+
Returns
|
|
107
|
+
-------
|
|
108
|
+
Path
|
|
109
|
+
A `pathlib.Path` object representing the directory containing the
|
|
110
|
+
executable or the current script file.
|
|
111
|
+
"""
|
|
112
|
+
if getattr(sys, "frozen", False):
|
|
113
|
+
return Path(sys.executable).parent
|
|
114
|
+
else:
|
|
115
|
+
return Path(__file__).resolve().parent
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def get_pyinstaller_resource_path(relative_path: str) -> Path:
|
|
119
|
+
r"""
|
|
120
|
+
Get the absolute path to a resource file, compatible with both development
|
|
121
|
+
environments and PyInstaller-packaged executables.
|
|
122
|
+
|
|
123
|
+
When running from a PyInstaller bundle, this function resolves the path relative
|
|
124
|
+
to the temporary `_MEIPASS` folder. During normal execution, it resolves the path
|
|
125
|
+
relative to the current script's directory.
|
|
126
|
+
|
|
127
|
+
Parameters
|
|
128
|
+
----------
|
|
129
|
+
relative_path : str
|
|
130
|
+
Relative path to the resource file or directory.
|
|
131
|
+
|
|
132
|
+
Returns
|
|
133
|
+
-------
|
|
134
|
+
Path
|
|
135
|
+
A `pathlib.Path` object pointing to the absolute location of the resource.
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
Example:
|
|
139
|
+
--------
|
|
140
|
+
>>> get_pyinstaller_resource_path("assets/logo.png")
|
|
141
|
+
<path>\assets\logo.png
|
|
142
|
+
"""
|
|
143
|
+
if hasattr(sys, "_MEIPASS"):
|
|
144
|
+
# PyInstaller temp folder
|
|
145
|
+
base_path = Path(getattr(sys, "_MEIPASS")) # type: ignore[attr-defined]
|
|
146
|
+
else:
|
|
147
|
+
base_path = Path(__file__).resolve().parent
|
|
148
|
+
|
|
149
|
+
return base_path / relative_path
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
# Key System
|
|
153
|
+
# ---------------------------------------------------------------------------
|
|
154
|
+
class HWIDgen(BaseClass):
|
|
155
|
+
"""
|
|
156
|
+
Generate Hardware ID (HWID)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
Example:
|
|
160
|
+
--------
|
|
161
|
+
>>> HWIDgen.generate()
|
|
162
|
+
"""
|
|
163
|
+
|
|
164
|
+
def __init__(self) -> None:
|
|
165
|
+
pass
|
|
166
|
+
|
|
167
|
+
@classmethod
|
|
168
|
+
def generate(cls) -> str:
|
|
169
|
+
"""Generate HWID for current system"""
|
|
170
|
+
os_type = platform.system().lower()
|
|
171
|
+
if os_type == "windows":
|
|
172
|
+
return cls._get_windows_hwid()
|
|
173
|
+
elif os_type == "linux":
|
|
174
|
+
return cls._get_linux_hwid()
|
|
175
|
+
else:
|
|
176
|
+
return cls._get_hwid_mac()
|
|
177
|
+
|
|
178
|
+
@staticmethod
|
|
179
|
+
def _get_hwid_mac() -> str:
|
|
180
|
+
"""HWID: MAC address"""
|
|
181
|
+
mac = uuid.getnode() # 48-bit MAC address
|
|
182
|
+
mac_str = ":".join(("%012X" % mac)[i : i + 2] for i in range(0, 12, 2))
|
|
183
|
+
hwid = hashlib.sha256(mac_str.encode()).hexdigest()
|
|
184
|
+
return hwid
|
|
185
|
+
|
|
186
|
+
@staticmethod
|
|
187
|
+
def _get_windows_hwid() -> str:
|
|
188
|
+
try:
|
|
189
|
+
# Get BIOS serial number
|
|
190
|
+
bios = (
|
|
191
|
+
subprocess.check_output("wmic bios get serialnumber", shell=True)
|
|
192
|
+
.decode()
|
|
193
|
+
.split("\n")[1]
|
|
194
|
+
.strip()
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
# Get Motherboard serial
|
|
198
|
+
board = (
|
|
199
|
+
subprocess.check_output("wmic baseboard get serialnumber", shell=True)
|
|
200
|
+
.decode()
|
|
201
|
+
.split("\n")[1]
|
|
202
|
+
.strip()
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
# Get Disk serial
|
|
206
|
+
disk = (
|
|
207
|
+
subprocess.check_output("wmic diskdrive get serialnumber", shell=True)
|
|
208
|
+
.decode()
|
|
209
|
+
.split("\n")[1]
|
|
210
|
+
.strip()
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
raw = bios + board + disk
|
|
214
|
+
hwid = hashlib.sha256(raw.encode()).hexdigest()
|
|
215
|
+
return hwid
|
|
216
|
+
except Exception as e:
|
|
217
|
+
return f"Error getting HWID: {e}"
|
|
218
|
+
|
|
219
|
+
@staticmethod
|
|
220
|
+
def _get_linux_hwid() -> str:
|
|
221
|
+
try:
|
|
222
|
+
disk_info = subprocess.check_output(
|
|
223
|
+
"udevadm info --query=all --name=/dev/sda", shell=True
|
|
224
|
+
).decode()
|
|
225
|
+
serial = ""
|
|
226
|
+
for line in disk_info.splitlines():
|
|
227
|
+
if "ID_SERIAL_SHORT" in line:
|
|
228
|
+
serial = line.split("=")[1]
|
|
229
|
+
break
|
|
230
|
+
|
|
231
|
+
mac = uuid.getnode()
|
|
232
|
+
mac_str = ":".join(("%012X" % mac)[i : i + 2] for i in range(0, 12, 2))
|
|
233
|
+
|
|
234
|
+
raw = serial + mac_str
|
|
235
|
+
hwid = hashlib.sha256(raw.encode()).hexdigest()
|
|
236
|
+
return hwid
|
|
237
|
+
except Exception as e:
|
|
238
|
+
return f"Error getting HWID: {e}"
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
class LicenseKey(TypedDict):
|
|
242
|
+
name: str
|
|
243
|
+
expiry: str
|
|
244
|
+
signature: str
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
class LicenseKeySystem(BaseClass):
|
|
248
|
+
|
|
249
|
+
def __init__(self, name: str, expiry: str, secret_key: str) -> None:
|
|
250
|
+
"""
|
|
251
|
+
License Key implementation
|
|
252
|
+
|
|
253
|
+
Parameters
|
|
254
|
+
----------
|
|
255
|
+
name : str
|
|
256
|
+
Name of license holder
|
|
257
|
+
|
|
258
|
+
expiry : str
|
|
259
|
+
Expiry date (in yyyy-mm-dd format)
|
|
260
|
+
|
|
261
|
+
secret_key : str
|
|
262
|
+
Secret key to make license key
|
|
263
|
+
"""
|
|
264
|
+
self._name = name
|
|
265
|
+
self._expiry = expiry
|
|
266
|
+
self._secret = secret_key.encode("utf-8")
|
|
267
|
+
|
|
268
|
+
# Make key
|
|
269
|
+
def make_key(self, *, hwid_overwrite: str | None = None) -> str:
|
|
270
|
+
"""
|
|
271
|
+
Make a license key in these following steps:
|
|
272
|
+
1. Generate HWID
|
|
273
|
+
2. Combine name, expiry, HWID -> sign it
|
|
274
|
+
3. Base64-encode the signature
|
|
275
|
+
4. Store name, expiry, signature in JSON -> Base64 -> Hex
|
|
276
|
+
5. Divide by 30 chars per line
|
|
277
|
+
6. Wrap in BEGIN/END KEY
|
|
278
|
+
|
|
279
|
+
Parameters
|
|
280
|
+
----------
|
|
281
|
+
hwid_overwrite : str | None, optional
|
|
282
|
+
Overwrite the HWID, by default None
|
|
283
|
+
|
|
284
|
+
Returns
|
|
285
|
+
-------
|
|
286
|
+
str
|
|
287
|
+
License key
|
|
288
|
+
"""
|
|
289
|
+
# Prepare
|
|
290
|
+
hwid = (
|
|
291
|
+
HWIDgen.generate() if hwid_overwrite is None else hwid_overwrite
|
|
292
|
+
) # Get HWID
|
|
293
|
+
msg_data = f"{self._name}|{self._expiry}|{hwid}" # Make msg
|
|
294
|
+
signature = hmac.new(self._secret, msg_data.encode(), hashlib.sha256).digest()
|
|
295
|
+
encoded_sig = base64.urlsafe_b64encode(signature).decode()
|
|
296
|
+
|
|
297
|
+
# Key format
|
|
298
|
+
key: LicenseKey = {
|
|
299
|
+
"name": self._name,
|
|
300
|
+
"expiry": self._expiry,
|
|
301
|
+
"signature": encoded_sig,
|
|
302
|
+
}
|
|
303
|
+
# This convert to .json -> Base64 -> Hex
|
|
304
|
+
encoded_key = Text(Base64EncodeDecode.encode(json.dumps(key))).to_hex(raw=True)
|
|
305
|
+
output_key = (
|
|
306
|
+
"BEGIN KEY".center(30, "=")
|
|
307
|
+
+ "\n"
|
|
308
|
+
+ "\n".join(Text(encoded_key).divide(30))
|
|
309
|
+
+ "\n"
|
|
310
|
+
+ "END KEY".center(30, "=")
|
|
311
|
+
)
|
|
312
|
+
return output_key
|
|
313
|
+
|
|
314
|
+
# Check key
|
|
315
|
+
@staticmethod
|
|
316
|
+
def _parse_license_key(license_key: str) -> LicenseKey:
|
|
317
|
+
"""
|
|
318
|
+
Parse a formatted license key
|
|
319
|
+
|
|
320
|
+
Parameters
|
|
321
|
+
----------
|
|
322
|
+
license_key : str
|
|
323
|
+
License key
|
|
324
|
+
|
|
325
|
+
Returns
|
|
326
|
+
-------
|
|
327
|
+
LicenseKey
|
|
328
|
+
Parsed license key
|
|
329
|
+
"""
|
|
330
|
+
try:
|
|
331
|
+
lines = license_key.strip().split("\n")[1:-1] # Remove BEGIN/END KEY lines
|
|
332
|
+
raw_hex = "".join(lines)
|
|
333
|
+
decoded_json = Base64EncodeDecode.decode(
|
|
334
|
+
bytes.fromhex(raw_hex).decode("utf-8")
|
|
335
|
+
)
|
|
336
|
+
return json.loads(decoded_json)
|
|
337
|
+
except Exception as e:
|
|
338
|
+
raise ValueError("Invalid license key format") from e
|
|
339
|
+
|
|
340
|
+
@classmethod
|
|
341
|
+
def verify_license(
|
|
342
|
+
cls,
|
|
343
|
+
license_key: str,
|
|
344
|
+
secret_key: str | None = None,
|
|
345
|
+
hwid_overwrite: str | None = None,
|
|
346
|
+
) -> bool:
|
|
347
|
+
"""
|
|
348
|
+
Verify a license key
|
|
349
|
+
|
|
350
|
+
Parameters
|
|
351
|
+
----------
|
|
352
|
+
license_key : str
|
|
353
|
+
License key
|
|
354
|
+
|
|
355
|
+
secret_key : str | None, optional
|
|
356
|
+
Secret key, by default None
|
|
357
|
+
|
|
358
|
+
hwid_overwrite : str | None, optional
|
|
359
|
+
HWID, by default None
|
|
360
|
+
|
|
361
|
+
Returns
|
|
362
|
+
-------
|
|
363
|
+
bool
|
|
364
|
+
_description_
|
|
365
|
+
"""
|
|
366
|
+
|
|
367
|
+
# Prep
|
|
368
|
+
secret = "" if secret_key is None else secret_key
|
|
369
|
+
hwid = HWIDgen.generate() if hwid_overwrite is None else hwid_overwrite
|
|
370
|
+
parsed_license_key = cls._parse_license_key(license_key)
|
|
371
|
+
|
|
372
|
+
try:
|
|
373
|
+
msg_data = (
|
|
374
|
+
f"{parsed_license_key['name']}|{parsed_license_key['expiry']}|{hwid}"
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
expected_sig = hmac.new(
|
|
378
|
+
secret.encode("utf-8"), msg_data.encode(), hashlib.sha256
|
|
379
|
+
).digest()
|
|
380
|
+
expected_encoded_sig = base64.urlsafe_b64encode(expected_sig).decode()
|
|
381
|
+
|
|
382
|
+
return parsed_license_key["signature"] == expected_encoded_sig
|
|
383
|
+
except Exception:
|
|
384
|
+
return False
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
# Software
|
|
388
|
+
# ---------------------------------------------------------------------------
|
|
389
|
+
class BasicSoftwareProtection(BaseClass):
|
|
390
|
+
"""
|
|
391
|
+
Basic software protection
|
|
392
|
+
|
|
393
|
+
This check valid license before run any app. Recommended to put at start of the code
|
|
394
|
+
|
|
395
|
+
Usage:
|
|
396
|
+
------
|
|
397
|
+
>>> t = BasicSoftwareProtection(get_pyinstaller_exe_dir())
|
|
398
|
+
>>> t.add_secret("Test Key")
|
|
399
|
+
>>> t.check_valid_license()
|
|
400
|
+
"""
|
|
401
|
+
|
|
402
|
+
def __init__(
|
|
403
|
+
self,
|
|
404
|
+
cwd: str | Path,
|
|
405
|
+
name: str | None = None,
|
|
406
|
+
version: str | None = None,
|
|
407
|
+
author: str | None = None,
|
|
408
|
+
author_email: str | None = None,
|
|
409
|
+
) -> None:
|
|
410
|
+
"""
|
|
411
|
+
Basic software protection.
|
|
412
|
+
|
|
413
|
+
Parameters
|
|
414
|
+
----------
|
|
415
|
+
cwd : str | Path
|
|
416
|
+
Current working directory
|
|
417
|
+
|
|
418
|
+
name : str | None, optional
|
|
419
|
+
Name of the software, by default None
|
|
420
|
+
|
|
421
|
+
version : str | None, optional
|
|
422
|
+
Version of the software, by default None
|
|
423
|
+
|
|
424
|
+
author : str | None, optional
|
|
425
|
+
Author of the software, by default None
|
|
426
|
+
|
|
427
|
+
author_email : str | None, optional
|
|
428
|
+
Author's email of the software, by default None
|
|
429
|
+
"""
|
|
430
|
+
self._cwd = Path(cwd)
|
|
431
|
+
self._author = "" if author is None else author
|
|
432
|
+
self._author_email = "" if author_email is None else author_email
|
|
433
|
+
self._software_name = "" if name is None else name
|
|
434
|
+
self._software_version = "" if version is None else version
|
|
435
|
+
self._secret = ""
|
|
436
|
+
|
|
437
|
+
# Metadata
|
|
438
|
+
@property
|
|
439
|
+
def cwd(self) -> Path:
|
|
440
|
+
"""Current working directory"""
|
|
441
|
+
return self._cwd
|
|
442
|
+
|
|
443
|
+
@property
|
|
444
|
+
def software_name(self) -> str:
|
|
445
|
+
"""Name of the software"""
|
|
446
|
+
return self._software_name
|
|
447
|
+
|
|
448
|
+
@software_name.setter
|
|
449
|
+
def software_name(self, value: str) -> None:
|
|
450
|
+
# Logic to validate name
|
|
451
|
+
self._software_name = value
|
|
452
|
+
|
|
453
|
+
@property
|
|
454
|
+
def author(self) -> str:
|
|
455
|
+
"""Author of the software"""
|
|
456
|
+
return self._author
|
|
457
|
+
|
|
458
|
+
@property
|
|
459
|
+
def version(self) -> str:
|
|
460
|
+
"""Version of the software"""
|
|
461
|
+
return self._software_version
|
|
462
|
+
|
|
463
|
+
# Protection
|
|
464
|
+
def add_secret(self, secret: str) -> None:
|
|
465
|
+
"""
|
|
466
|
+
Add secret
|
|
467
|
+
|
|
468
|
+
Parameters
|
|
469
|
+
----------
|
|
470
|
+
secret : str
|
|
471
|
+
secret
|
|
472
|
+
"""
|
|
473
|
+
self._secret = secret
|
|
474
|
+
|
|
475
|
+
def check_valid_license(self, generate_helper: bool = True) -> None:
|
|
476
|
+
try:
|
|
477
|
+
# Get license file
|
|
478
|
+
license_file = list(self._cwd.glob("*.zlic"))[0]
|
|
479
|
+
with license_file.open() as f:
|
|
480
|
+
# Load data
|
|
481
|
+
data = "".join(f.readlines())
|
|
482
|
+
except IndexError:
|
|
483
|
+
if generate_helper:
|
|
484
|
+
self.generate_license_helper()
|
|
485
|
+
raise SystemExit("License file not found!")
|
|
486
|
+
except ValueError:
|
|
487
|
+
raise SystemExit("Invalid license key format!")
|
|
488
|
+
else:
|
|
489
|
+
# Verify license
|
|
490
|
+
if LicenseKeySystem.verify_license(data, secret_key=self._secret):
|
|
491
|
+
parsed_date = datetime.strptime(
|
|
492
|
+
LicenseKeySystem._parse_license_key(data)["expiry"], "%Y-%m-%d"
|
|
493
|
+
)
|
|
494
|
+
stop_after_day(
|
|
495
|
+
parsed_date.year,
|
|
496
|
+
parsed_date.month,
|
|
497
|
+
parsed_date.day,
|
|
498
|
+
custom_msg="License expired!",
|
|
499
|
+
)
|
|
500
|
+
else: # Invalid license
|
|
501
|
+
raise SystemExit("Invalid license!")
|
|
502
|
+
|
|
503
|
+
# Make key
|
|
504
|
+
def _make_key(self, name: str, expiry: str, secret: str):
|
|
505
|
+
path = self._cwd.joinpath("license.zlic")
|
|
506
|
+
engine = LicenseKeySystem(name, expiry, secret)
|
|
507
|
+
with path.open("w", encoding="utf-8") as f:
|
|
508
|
+
f.write(engine.make_key())
|
|
509
|
+
|
|
510
|
+
def generate_license_helper(self):
|
|
511
|
+
"""Gather HWID and make it into a file"""
|
|
512
|
+
path = self._cwd.joinpath("license.helper")
|
|
513
|
+
with path.open("w", encoding="utf-8") as f:
|
|
514
|
+
f.write(HWIDgen.generate())
|
absfuyu/tools/web.py
CHANGED
absfuyu/typings.py
CHANGED
absfuyu/util/__init__.py
CHANGED
|
@@ -3,8 +3,8 @@ Absufyu: Utilities
|
|
|
3
3
|
------------------
|
|
4
4
|
Some random utilities
|
|
5
5
|
|
|
6
|
-
Version: 5.
|
|
7
|
-
Date updated:
|
|
6
|
+
Version: 5.6.0
|
|
7
|
+
Date updated: 12/09/2025 (dd/mm/yyyy)
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
10
|
# Module Package
|
|
@@ -163,9 +163,14 @@ def set_min_max(
|
|
|
163
163
|
return current_value
|
|
164
164
|
|
|
165
165
|
|
|
166
|
+
@versionchanged("5.6.0", reason="New `custom_msg` parameter")
|
|
166
167
|
@versionadded("3.2.0")
|
|
167
168
|
def stop_after_day(
|
|
168
|
-
year: int | None = None,
|
|
169
|
+
year: int | None = None,
|
|
170
|
+
month: int | None = None,
|
|
171
|
+
day: int | None = None,
|
|
172
|
+
*,
|
|
173
|
+
custom_msg: str | None = None,
|
|
169
174
|
) -> None:
|
|
170
175
|
"""
|
|
171
176
|
Stop working after specified day.
|
|
@@ -184,6 +189,10 @@ def stop_after_day(
|
|
|
184
189
|
day : int
|
|
185
190
|
Desired day
|
|
186
191
|
(Default: ``None`` - 1 day trial)
|
|
192
|
+
|
|
193
|
+
custom_msg : str
|
|
194
|
+
Custom exit message
|
|
195
|
+
(Default: ``None``)
|
|
187
196
|
"""
|
|
188
197
|
# None checking - By default: 1 day trial
|
|
189
198
|
now = datetime.now()
|
|
@@ -198,6 +207,8 @@ def stop_after_day(
|
|
|
198
207
|
end_date = datetime(year, month, day)
|
|
199
208
|
result = end_date - now
|
|
200
209
|
if result.days < 0:
|
|
210
|
+
if custom_msg:
|
|
211
|
+
raise SystemExit(custom_msg)
|
|
201
212
|
raise SystemExit("End of time")
|
|
202
213
|
|
|
203
214
|
|
absfuyu/util/api.py
CHANGED
absfuyu/util/json_method.py
CHANGED