absfuyu 5.6.1__py3-none-any.whl → 6.1.3__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.

Files changed (102) hide show
  1. absfuyu/__init__.py +5 -3
  2. absfuyu/__main__.py +2 -2
  3. absfuyu/cli/__init__.py +13 -2
  4. absfuyu/cli/audio_group.py +98 -0
  5. absfuyu/cli/color.py +2 -2
  6. absfuyu/cli/config_group.py +2 -2
  7. absfuyu/cli/do_group.py +2 -2
  8. absfuyu/cli/game_group.py +20 -2
  9. absfuyu/cli/tool_group.py +68 -4
  10. absfuyu/config/__init__.py +3 -3
  11. absfuyu/core/__init__.py +10 -6
  12. absfuyu/core/baseclass.py +104 -34
  13. absfuyu/core/baseclass2.py +43 -2
  14. absfuyu/core/decorator.py +2 -2
  15. absfuyu/core/docstring.py +4 -2
  16. absfuyu/core/dummy_cli.py +3 -3
  17. absfuyu/core/dummy_func.py +2 -2
  18. absfuyu/dxt/__init__.py +2 -2
  19. absfuyu/dxt/base_type.py +93 -0
  20. absfuyu/dxt/dictext.py +188 -6
  21. absfuyu/dxt/dxt_support.py +2 -2
  22. absfuyu/dxt/intext.py +72 -4
  23. absfuyu/dxt/listext.py +495 -23
  24. absfuyu/dxt/strext.py +2 -2
  25. absfuyu/extra/__init__.py +2 -2
  26. absfuyu/extra/audio/__init__.py +8 -0
  27. absfuyu/extra/audio/_util.py +57 -0
  28. absfuyu/extra/audio/convert.py +192 -0
  29. absfuyu/extra/audio/lossless.py +281 -0
  30. absfuyu/extra/beautiful.py +2 -2
  31. absfuyu/extra/da/__init__.py +39 -3
  32. absfuyu/extra/da/dadf.py +458 -29
  33. absfuyu/extra/da/dadf_base.py +2 -2
  34. absfuyu/extra/da/df_func.py +89 -5
  35. absfuyu/extra/da/mplt.py +2 -2
  36. absfuyu/extra/ggapi/__init__.py +8 -0
  37. absfuyu/extra/ggapi/gdrive.py +223 -0
  38. absfuyu/extra/ggapi/glicense.py +148 -0
  39. absfuyu/extra/ggapi/glicense_df.py +186 -0
  40. absfuyu/extra/ggapi/gsheet.py +88 -0
  41. absfuyu/extra/img/__init__.py +30 -0
  42. absfuyu/extra/img/converter.py +402 -0
  43. absfuyu/extra/img/dup_check.py +291 -0
  44. absfuyu/extra/pdf.py +4 -6
  45. absfuyu/extra/rclone.py +253 -0
  46. absfuyu/extra/xml.py +90 -0
  47. absfuyu/fun/__init__.py +2 -20
  48. absfuyu/fun/rubik.py +2 -2
  49. absfuyu/fun/tarot.py +2 -2
  50. absfuyu/game/__init__.py +2 -2
  51. absfuyu/game/game_stat.py +2 -2
  52. absfuyu/game/schulte.py +78 -0
  53. absfuyu/game/sudoku.py +2 -2
  54. absfuyu/game/tictactoe.py +2 -2
  55. absfuyu/game/wordle.py +6 -4
  56. absfuyu/general/__init__.py +2 -2
  57. absfuyu/general/content.py +2 -2
  58. absfuyu/general/human.py +2 -2
  59. absfuyu/general/resrel.py +213 -0
  60. absfuyu/general/shape.py +3 -8
  61. absfuyu/general/tax.py +344 -0
  62. absfuyu/logger.py +806 -59
  63. absfuyu/numbers/__init__.py +13 -0
  64. absfuyu/numbers/number_to_word.py +321 -0
  65. absfuyu/numbers/shorten_number.py +303 -0
  66. absfuyu/numbers/time_duration.py +217 -0
  67. absfuyu/pkg_data/__init__.py +2 -2
  68. absfuyu/pkg_data/deprecated.py +2 -2
  69. absfuyu/pkg_data/logo.py +1462 -0
  70. absfuyu/sort.py +4 -4
  71. absfuyu/tools/__init__.py +2 -2
  72. absfuyu/tools/checksum.py +119 -4
  73. absfuyu/tools/converter.py +2 -2
  74. absfuyu/tools/generator.py +24 -7
  75. absfuyu/tools/inspector.py +2 -2
  76. absfuyu/tools/keygen.py +2 -2
  77. absfuyu/tools/obfuscator.py +2 -2
  78. absfuyu/tools/passwordlib.py +2 -2
  79. absfuyu/tools/shutdownizer.py +3 -8
  80. absfuyu/tools/sw.py +213 -10
  81. absfuyu/tools/web.py +10 -13
  82. absfuyu/typings.py +5 -8
  83. absfuyu/util/__init__.py +31 -2
  84. absfuyu/util/api.py +7 -4
  85. absfuyu/util/cli.py +119 -0
  86. absfuyu/util/gui.py +91 -0
  87. absfuyu/util/json_method.py +2 -2
  88. absfuyu/util/lunar.py +2 -2
  89. absfuyu/util/package.py +124 -0
  90. absfuyu/util/path.py +313 -4
  91. absfuyu/util/performance.py +2 -2
  92. absfuyu/util/shorten_number.py +206 -13
  93. absfuyu/util/text_table.py +2 -2
  94. absfuyu/util/zipped.py +2 -2
  95. absfuyu/version.py +22 -19
  96. {absfuyu-5.6.1.dist-info → absfuyu-6.1.3.dist-info}/METADATA +37 -8
  97. absfuyu-6.1.3.dist-info/RECORD +105 -0
  98. {absfuyu-5.6.1.dist-info → absfuyu-6.1.3.dist-info}/WHEEL +1 -1
  99. absfuyu/extra/data_analysis.py +0 -21
  100. absfuyu-5.6.1.dist-info/RECORD +0 -79
  101. {absfuyu-5.6.1.dist-info → absfuyu-6.1.3.dist-info}/entry_points.txt +0 -0
  102. {absfuyu-5.6.1.dist-info → absfuyu-6.1.3.dist-info}/licenses/LICENSE +0 -0
absfuyu/sort.py CHANGED
@@ -3,8 +3,8 @@ Absfuyu: Sort
3
3
  -------------
4
4
  Sort Module
5
5
 
6
- Version: 5.6.1
7
- Date updated: 12/09/2025 (dd/mm/yyyy)
6
+ Version: 6.1.2
7
+ Date updated: 30/12/2025 (dd/mm/yyyy)
8
8
  """
9
9
 
10
10
  # Module level
@@ -26,7 +26,7 @@ from typing import Any
26
26
 
27
27
  # Functions
28
28
  # ---------------------------------------------------------------------------
29
- def selection_sort(iterable: list, reverse: bool = False) -> list:
29
+ def selection_sort[T](iterable: list[T], reverse: bool = False) -> list[T]:
30
30
  """
31
31
  Sort the list with selection sort (bubble sort) algorithm
32
32
 
@@ -61,7 +61,7 @@ def selection_sort(iterable: list, reverse: bool = False) -> list:
61
61
  return iterable
62
62
 
63
63
 
64
- def insertion_sort(iterable: list) -> list:
64
+ def insertion_sort[T](iterable: list[T]) -> list[T]:
65
65
  """
66
66
  Sort the list with insertion sort algorithm
67
67
 
absfuyu/tools/__init__.py CHANGED
@@ -3,8 +3,8 @@ Absfuyu: Tools
3
3
  --------------
4
4
  Some useful tools
5
5
 
6
- Version: 5.6.1
7
- Date updated: 12/09/2025 (dd/mm/yyyy)
6
+ Version: 6.1.2
7
+ Date updated: 30/12/2025 (dd/mm/yyyy)
8
8
  """
9
9
 
10
10
  # Module Package
absfuyu/tools/checksum.py CHANGED
@@ -3,13 +3,19 @@ Absufyu: Checksum
3
3
  -----------------
4
4
  Check MD5, SHA256, ...
5
5
 
6
- Version: 5.6.1
7
- Date updated: 12/09/2025 (dd/mm/yyyy)
6
+ Version: 6.1.2
7
+ Date updated: 30/12/2025 (dd/mm/yyyy)
8
8
  """
9
9
 
10
10
  # Module level
11
11
  # ---------------------------------------------------------------------------
12
- __all__ = ["Checksum", "ChecksumMode"]
12
+ __all__ = [
13
+ # Checksum
14
+ "Checksum",
15
+ "ChecksumMode",
16
+ # Mixin
17
+ "DirectoryRemoveDuplicateMixin",
18
+ ]
13
19
 
14
20
 
15
21
  # Library
@@ -22,6 +28,8 @@ from typing import Literal
22
28
  from absfuyu.core.baseclass import BaseClass
23
29
  from absfuyu.core.docstring import deprecated, versionadded, versionchanged
24
30
  from absfuyu.core.dummy_func import tqdm
31
+ from absfuyu.dxt import DictExt, ListExt
32
+ from absfuyu.util.path import DirectoryBase
25
33
 
26
34
 
27
35
  # Function
@@ -64,6 +72,52 @@ class ChecksumMode(StrEnum):
64
72
  SHA512 = "sha512"
65
73
 
66
74
 
75
+ class DuplicateSummary(DictExt[str, list[Path]]):
76
+ """
77
+ Duplicate file summary
78
+ """
79
+
80
+ def summary(self) -> int:
81
+ """
82
+ Show how many duplicates (include the original)
83
+
84
+ Returns
85
+ -------
86
+ int
87
+ How many duplicates
88
+ """
89
+ temp = self.__class__(self.copy())
90
+ try:
91
+ return sum(temp.apply(lambda x: len(x)).values())
92
+ except Exception as err:
93
+ print(f"Something wrong - {err}")
94
+
95
+ def remove_duplicates(self, dry_run: bool = True, keep_first: bool = True, debug: bool = False) -> None:
96
+ """
97
+ Remove duplicates
98
+
99
+ Parameters
100
+ ----------
101
+ dry_run : bool, optional
102
+ Simulate only (no files deleted), by default ``True``
103
+
104
+ keep_first : bool, optional
105
+ Keep the first duplicate file, will keep the last duplicate file when ``False``, by default ``True``
106
+ """
107
+ temp = self.__class__(self.copy())
108
+ removable_files = ListExt([x[1:] if keep_first else x[:-1] for x in temp.values()]).flatten()
109
+
110
+ for x in removable_files:
111
+ x: Path = x
112
+
113
+ if debug or dry_run:
114
+ print(f"Deleting {x}")
115
+ if dry_run:
116
+ continue
117
+
118
+ x.unlink(missing_ok=True)
119
+
120
+
67
121
  @versionchanged("4.1.1", reason="Checksum for entire folder is possible")
68
122
  @versionadded("4.1.0")
69
123
  class Checksum(BaseClass):
@@ -130,7 +184,8 @@ class Checksum(BaseClass):
130
184
  """This performs checksum"""
131
185
 
132
186
  hash_engine = self._get_hash_engine().copy()
133
- with open(Path(file), "rb") as f:
187
+ # with open(Path(file), "rb") as f:
188
+ with file.open("rb") as f:
134
189
  # Read and hash the file in 4K chunks. Reading the whole
135
190
  # file at once might consume a lot of memory if it is
136
191
  # large.
@@ -183,3 +238,63 @@ class Checksum(BaseClass):
183
238
  f.write(output)
184
239
 
185
240
  return output
241
+
242
+
243
+ # Mixin
244
+ class DirectoryRemoveDuplicateMixin(DirectoryBase):
245
+ """
246
+ Directory - Remove duplicate by SHA256
247
+
248
+ - remove_duplicate
249
+ """
250
+
251
+ def __init__(self, source_path, create_if_not_exist=False) -> None:
252
+ super().__init__(source_path, create_if_not_exist)
253
+
254
+ self._duplicate_cache: DuplicateSummary | None = None
255
+
256
+ def _gather_duplicate_cache(self, recursive: bool = True) -> None:
257
+ engine = Checksum(self.source_path, hash_mode=ChecksumMode.SHA256, save_result_to_file=False)
258
+ valid = [x for x in engine.path.glob("**/*" if recursive else "*") if x.is_file()]
259
+ checksum_cache = {}
260
+
261
+ # Checksum
262
+ for x in tqdm(valid, unit_scale=True, desc="Checking..."):
263
+ try:
264
+ cs_res = engine._checksum_operation(x)
265
+
266
+ if checksum_cache.get(cs_res) is None:
267
+ checksum_cache[cs_res] = [x]
268
+ else:
269
+ checksum_cache[cs_res] += [x]
270
+ except Exception as err:
271
+ print(f"ERROR: {x} - {err}")
272
+ continue
273
+
274
+ # Save to cache
275
+ self._duplicate_cache = DuplicateSummary({k: v for k, v in checksum_cache.items() if len(v) > 1})
276
+
277
+ def remove_duplicate(self, dry_run: bool = True, recursive: bool = True, debug: bool = True) -> None:
278
+ """
279
+ Remove duplicate files by SHA256 checksum
280
+
281
+ Parameters
282
+ ----------
283
+ dry_run : bool, optional
284
+ Simulate only (no files deleted), by default ``True``
285
+
286
+ recursive : bool, optional
287
+ Scan every file in the folder (including child folder), by default ``True``
288
+
289
+ debug : bool, optional
290
+ Print delete messages, by default ``True``
291
+ """
292
+ self._gather_duplicate_cache(recursive=recursive)
293
+
294
+ # Remove
295
+ try:
296
+ summary = self._duplicate_cache
297
+ print(f"Duplicate files: {summary.summary()}")
298
+ summary.remove_duplicates(dry_run=dry_run, keep_first=False, debug=debug)
299
+ except Exception as err:
300
+ pass
@@ -3,8 +3,8 @@ Absufyu: Converter
3
3
  ------------------
4
4
  Convert stuff
5
5
 
6
- Version: 5.6.1
7
- Date updated: 12/09/2025 (dd/mm/yyyy)
6
+ Version: 6.1.2
7
+ Date updated: 30/12/2025 (dd/mm/yyyy)
8
8
 
9
9
  Feature:
10
10
  --------
@@ -3,8 +3,8 @@ Absfuyu: Generator
3
3
  ------------------
4
4
  This generate stuff (Not python's ``generator``)
5
5
 
6
- Version: 5.6.1
7
- Date updated: 12/09/2025 (dd/mm/yyyy)
6
+ Version: 6.1.2
7
+ Date updated: 30/12/2025 (dd/mm/yyyy)
8
8
 
9
9
  Features:
10
10
  ---------
@@ -25,7 +25,7 @@ import string
25
25
  from collections.abc import Collection, Sequence
26
26
  from itertools import chain, combinations
27
27
  from random import choice
28
- from typing import TypeVar, cast, overload
28
+ from typing import Literal, TypeVar, cast, overload
29
29
 
30
30
  from absfuyu.core.baseclass import GetClassMembersMixin
31
31
  from absfuyu.core.docstring import deprecated
@@ -78,6 +78,25 @@ class Generator(GetClassMembersMixin):
78
78
  """
79
79
 
80
80
  # Generate string
81
+ @overload
82
+ @staticmethod
83
+ def generate_string( # type: ignore
84
+ charset: str = Charset.DEFAULT,
85
+ size: int = 8,
86
+ times: int = 1,
87
+ unique: bool = False,
88
+ ) -> list[str]: ...
89
+
90
+ @overload
91
+ @staticmethod
92
+ def generate_string(
93
+ charset: str = Charset.DEFAULT,
94
+ size: int = 8,
95
+ times: int = 1,
96
+ unique: bool = False,
97
+ string_type_if_1: Literal[True] = ...,
98
+ ) -> str: ...
99
+
81
100
  @staticmethod
82
101
  @deprecated("5.2.0", reason="Use generate_string2() instead.")
83
102
  def generate_string(
@@ -86,7 +105,7 @@ class Generator(GetClassMembersMixin):
86
105
  times: int = 1,
87
106
  unique: bool = False,
88
107
  string_type_if_1: bool = False,
89
- ):
108
+ ) -> str | list[str]:
90
109
  """
91
110
  Generate a list of random string from character set (Random string generator).
92
111
  Deprecated
@@ -394,9 +413,7 @@ class Generator(GetClassMembersMixin):
394
413
 
395
414
  @overload
396
415
  @staticmethod
397
- def combinations_range(
398
- collection: Collection[T], *, min_len: int = 1, max_len: int = 0
399
- ) -> list[tuple[T, ...]]: ...
416
+ def combinations_range(collection: Collection[T], *, min_len: int = 1, max_len: int = 0) -> list[tuple[T, ...]]: ...
400
417
 
401
418
  @staticmethod
402
419
  def combinations_range(
@@ -3,8 +3,8 @@ Absfuyu: Inspector
3
3
  ------------------
4
4
  Inspector
5
5
 
6
- Version: 5.6.1
7
- Date updated: 12/09/2025 (dd/mm/yyyy)
6
+ Version: 6.1.2
7
+ Date updated: 30/12/2025 (dd/mm/yyyy)
8
8
  """
9
9
 
10
10
  # Module level
absfuyu/tools/keygen.py CHANGED
@@ -5,8 +5,8 @@ Mod7 product key generator (90's)
5
5
 
6
6
  This is for educational and informative purposes only.
7
7
 
8
- Version: 5.6.1
9
- Date updated: 12/09/2025 (dd/mm/yyyy)
8
+ Version: 6.1.2
9
+ Date updated: 30/12/2025 (dd/mm/yyyy)
10
10
  """
11
11
 
12
12
  # Module level
@@ -3,8 +3,8 @@ Absfuyu: Obfuscator
3
3
  -------------------
4
4
  Obfuscate code
5
5
 
6
- Version: 5.6.1
7
- Date updated: 12/09/2025 (dd/mm/yyyy)
6
+ Version: 6.1.2
7
+ Date updated: 30/12/2025 (dd/mm/yyyy)
8
8
  """
9
9
 
10
10
  # Module level
@@ -3,8 +3,8 @@ Absfuyu: Passwordlib
3
3
  --------------------
4
4
  Password library
5
5
 
6
- Version: 5.6.1
7
- Date updated: 12/09/2025 (dd/mm/yyyy)
6
+ Version: 6.1.2
7
+ Date updated: 30/12/2025 (dd/mm/yyyy)
8
8
  """
9
9
 
10
10
  # Module level
@@ -3,8 +3,8 @@ Absfuyu: Shutdownizer
3
3
  ---------------------
4
4
  This shutdowns
5
5
 
6
- Version: 5.6.1
7
- Date updated: 12/09/2025 (dd/mm/yyyy)
6
+ Version: 6.1.2
7
+ Date updated: 30/12/2025 (dd/mm/yyyy)
8
8
  """
9
9
 
10
10
  # Module level
@@ -20,12 +20,7 @@ import sys
20
20
  from abc import ABC, abstractmethod
21
21
  from datetime import datetime, time, timedelta
22
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
23
+ from typing import Annotated, override
29
24
 
30
25
  from absfuyu.core import BaseClass, versionadded, versionchanged
31
26
  from absfuyu.logger import logger
absfuyu/tools/sw.py CHANGED
@@ -3,8 +3,8 @@ Absufyu: Software
3
3
  -----------------
4
4
  Software, pyinstaller related stuff
5
5
 
6
- Version: 5.6.1
7
- Date updated: 12/09/2025 (dd/mm/yyyy)
6
+ Version: 6.1.2
7
+ Date updated: 30/12/2025 (dd/mm/yyyy)
8
8
  """
9
9
 
10
10
  # Module level
@@ -14,12 +14,14 @@ __all__ = [
14
14
  "get_system_info",
15
15
  "get_pyinstaller_exe_dir",
16
16
  "get_pyinstaller_resource_path",
17
+ "PyinstallerHelper",
17
18
  "HWIDgen",
18
19
  "LicenseKeySystem",
19
20
  "BasicSoftwareProtection",
20
21
  # Support
21
22
  "SystemInfo",
22
23
  "LicenseKey",
24
+ "PyinstallerHiddenImportPreset",
23
25
  ]
24
26
 
25
27
 
@@ -40,13 +42,13 @@ from pathlib import Path
40
42
  from typing import Literal, NamedTuple, TypedDict
41
43
 
42
44
  from absfuyu.core.baseclass import BaseClass
43
- from absfuyu.core.docstring import versionchanged
45
+ from absfuyu.core.docstring import versionadded, versionchanged
44
46
  from absfuyu.dxt import Text
45
47
  from absfuyu.tools.converter import Base64EncodeDecode
46
48
  from absfuyu.util import stop_after_day
47
49
 
48
50
 
49
- # System Info
51
+ # MARK: System Info
50
52
  # ---------------------------------------------------------------------------
51
53
  class SystemInfo(NamedTuple):
52
54
  """System info"""
@@ -93,7 +95,7 @@ def get_system_info() -> SystemInfo:
93
95
  )
94
96
 
95
97
 
96
- # Pyinstaller
98
+ # MARK: Pyinstaller
97
99
  # ---------------------------------------------------------------------------
98
100
  @versionchanged("5.6.1", "Fixed behavior")
99
101
  def get_pyinstaller_exe_dir() -> Path:
@@ -123,7 +125,7 @@ def get_pyinstaller_resource_path(relative_path: str) -> Path:
123
125
  environments and PyInstaller-packaged executables.
124
126
 
125
127
  When running from a PyInstaller bundle, this function resolves the path relative
126
- to the temporary `_MEIPASS` folder. During normal execution, it resolves the path
128
+ to the temporary ``_MEIPASS`` folder. During normal execution, it resolves the path
127
129
  relative to the current script's directory.
128
130
 
129
131
  Parameters
@@ -151,7 +153,137 @@ def get_pyinstaller_resource_path(relative_path: str) -> Path:
151
153
  return base_path / relative_path
152
154
 
153
155
 
154
- # Key System
156
+ class PyinstallerHiddenImportPreset:
157
+ """
158
+ pyinstaller hidden import preset (library preset)
159
+
160
+ Example:
161
+ --------
162
+ >>> PyinstallerHelper(...).add_hidden_import(*PyinstallerHiddenImportPreset.ABSFUYU)
163
+ """
164
+
165
+ ABSFUYU = ["absfuyu"]
166
+ DF = ["pandas", "numpy", "openpyxl", "xlsxwriter"] # DataFrame
167
+ VISUAL = ["rich", "tqdm"]
168
+
169
+
170
+ @versionadded("5.11.0")
171
+ class PyinstallerHelper(BaseClass):
172
+ """pyinstaller helper"""
173
+
174
+ def __init__(
175
+ self,
176
+ path_to_file: str | Path,
177
+ relative_to_cwd: bool = True,
178
+ console: bool = True,
179
+ onefile: bool = False,
180
+ noconfirm: bool = False,
181
+ ) -> None:
182
+ """
183
+ pyinstaller cmd helper
184
+
185
+ Parameters
186
+ ----------
187
+ path_to_file : str | Path
188
+ Path to .py file to make .exe
189
+
190
+ relative_to_cwd : bool, optional
191
+ Is the file relative to cwd, by default True
192
+
193
+ console : bool, optional
194
+ Include console, by default True
195
+
196
+ onefile : bool, optional
197
+ Convert into one file, by default False
198
+
199
+ noconfirm : bool, optional
200
+ No confirmation, by default False
201
+ """
202
+ self.source_path = Path(path_to_file)
203
+ self.relative_to_cwd = relative_to_cwd
204
+ self.console = console
205
+ self.onefile = onefile
206
+ self.noconfirm = noconfirm
207
+
208
+ if self.relative_to_cwd:
209
+ # rel = self.source_path.relative_to(Path.cwd())
210
+ # self._base_cmd = ["pyinstaller", f"'.\\{Path('.').joinpath(rel)}'"]
211
+ self._base_cmd = ["pyinstaller", f"'.\\{self.source_path.relative_to(Path.cwd())}'"]
212
+ else:
213
+ self._base_cmd = ["pyinstaller", f"'{self.source_path.resolve()}'"]
214
+
215
+ self._hidden_import = []
216
+ self._icon = ""
217
+
218
+ def add_hidden_import(self, *library: str) -> None:
219
+ """
220
+ Add hidden import (library)
221
+ """
222
+ if len(self._hidden_import) < 1:
223
+ self._hidden_import = list(library)
224
+ else:
225
+ self._hidden_import.extend(list(library))
226
+
227
+ def add_icon(self, path_to_icon: str | Path, *, relative_to_cwd: bool | None = None) -> None:
228
+ """
229
+ Add icon to .exe
230
+
231
+ Parameters
232
+ ----------
233
+ path_to_icon : str | Path
234
+ Path to icon file
235
+
236
+ relative_to_cwd : bool | None, optional
237
+ Use boolean value to overwrite relative_to_cwd option of the main engine, by default None
238
+ """
239
+ p = Path(path_to_icon)
240
+ use_relative = self.relative_to_cwd if relative_to_cwd is None else relative_to_cwd
241
+
242
+ # --icon=favicon.ico
243
+ if use_relative:
244
+ rel = p.relative_to(Path.cwd())
245
+ # ensure it always shows with ./ prefix
246
+ # self._icon = f"'{Path('.').joinpath(rel)}'"
247
+ self._icon = f"--icon='.\\{rel}'"
248
+ else:
249
+ self._icon = f"--icon='{p.resolve()}'"
250
+
251
+ def export_cmd(self) -> str:
252
+ """
253
+ Export pyinstaller cmd
254
+
255
+ Returns
256
+ -------
257
+ str
258
+ pyinstaller command
259
+ """
260
+ cmd = self._base_cmd
261
+
262
+ # Hidden import
263
+ if len(self._hidden_import) > 0:
264
+ dat = (f"--hidden-import={x}" for x in list(set(self._hidden_import)))
265
+ cmd.append(" ".join(dat))
266
+
267
+ # Console
268
+ if not self.console:
269
+ cmd.append("--noconsole")
270
+
271
+ # One file
272
+ if self.onefile:
273
+ cmd.append("--onefile")
274
+
275
+ # No confirm
276
+ if self.noconfirm:
277
+ cmd.append("--noconfirm")
278
+
279
+ # Icon
280
+ if len(self._icon) > 0:
281
+ cmd.append(self._icon)
282
+
283
+ return " ".join(cmd)
284
+
285
+
286
+ # MARK: Key System
155
287
  # ---------------------------------------------------------------------------
156
288
  class HWIDgen(BaseClass):
157
289
  """
@@ -385,7 +517,7 @@ class LicenseKeySystem(BaseClass):
385
517
  return False
386
518
 
387
519
 
388
- # Software
520
+ # MARK: Software
389
521
  # ---------------------------------------------------------------------------
390
522
  class BasicSoftwareProtection(BaseClass):
391
523
  """
@@ -474,6 +606,28 @@ class BasicSoftwareProtection(BaseClass):
474
606
  self._secret = secret
475
607
 
476
608
  def check_valid_license(self, generate_helper: bool = True) -> None:
609
+ """
610
+ Check for valid license
611
+ (``.zlic`` file in the same directory as the script that runs this code)
612
+
613
+ Parameters
614
+ ----------
615
+ generate_helper : bool, optional
616
+ Generate a helper file (``license.helper``),
617
+ which can be used to make license key,
618
+ by default ``True``
619
+
620
+ Raises
621
+ ------
622
+ SystemExit
623
+ License file not found!
624
+
625
+ SystemExit
626
+ Invalid license key format!
627
+
628
+ SystemExit
629
+ Invalid license!
630
+ """
477
631
  try:
478
632
  # Get license file
479
633
  license_file = list(self._cwd.glob("*.zlic"))[0]
@@ -501,14 +655,63 @@ class BasicSoftwareProtection(BaseClass):
501
655
  else: # Invalid license
502
656
  raise SystemExit("Invalid license!")
503
657
 
658
+ @versionadded("5.9.0")
659
+ def check_valid_license_gui(
660
+ self, title: str = "WARNING", message: str = "INVALID LICENSE!", generate_helper: bool = True
661
+ ) -> None:
662
+ """
663
+ Check for valid license
664
+ (``.zlic`` file in the same directory as the script that runs this code)
665
+ but will pop up a GUI when invalid license.
666
+
667
+ Parameters
668
+ ----------
669
+ title : str, optional
670
+ Title of the GUI, by default "WARNING"
671
+
672
+ message : str, optional
673
+ Message in the GUI, by default "INVALID LICENSE!"
674
+
675
+ generate_helper : bool, optional
676
+ Generate a helper file (``license.helper``),
677
+ which can be used to make license key,
678
+ by default ``True``
679
+
680
+ Raises
681
+ ------
682
+ SystemExit
683
+ Invalid license
684
+ """
685
+ try:
686
+ self.check_valid_license(generate_helper=generate_helper)
687
+ except SystemExit:
688
+ from tkinter import messagebox
689
+
690
+ messagebox.showwarning(title, message, icon="error")
691
+ raise SystemExit("Invalid license")
692
+
504
693
  # Make key
505
- def _make_key(self, name: str, expiry: str, secret: str):
694
+ def _make_key(self, name: str, expiry: str, secret: str) -> None:
695
+ """
696
+ Generate license key in the same directory as the script that runs this code.
697
+
698
+ Parameters
699
+ ----------
700
+ name : str
701
+ Name of license's holder
702
+
703
+ expiry : str
704
+ Expiry date in format "yyyy-mm-dd"
705
+
706
+ secret : str
707
+ Secret string
708
+ """
506
709
  path = self._cwd.joinpath("license.zlic")
507
710
  engine = LicenseKeySystem(name, expiry, secret)
508
711
  with path.open("w", encoding="utf-8") as f:
509
712
  f.write(engine.make_key())
510
713
 
511
- def generate_license_helper(self):
714
+ def generate_license_helper(self) -> None:
512
715
  """Gather HWID and make it into a file"""
513
716
  path = self._cwd.joinpath("license.helper")
514
717
  with path.open("w", encoding="utf-8") as f:
absfuyu/tools/web.py CHANGED
@@ -3,21 +3,14 @@ Absfuyu: Web
3
3
  ------------
4
4
  Web, ``request``, ``BeautifulSoup`` stuff
5
5
 
6
- Version: 5.6.1
7
- Date updated: 12/09/2025 (dd/mm/yyyy)
6
+ Version: 6.1.2
7
+ Date updated: 30/12/2025 (dd/mm/yyyy)
8
8
  """
9
9
 
10
- # Library
11
- # ---------------------------------------------------------------------------
12
- import requests
13
- from bs4 import BeautifulSoup
14
-
15
- from absfuyu.logger import logger
16
-
17
10
 
18
11
  # Function
19
12
  # ---------------------------------------------------------------------------
20
- def soup_link(link: str) -> BeautifulSoup:
13
+ def soup_link(link: str):
21
14
  """
22
15
  ``BeautifulSoup`` the link
23
16
 
@@ -32,12 +25,17 @@ def soup_link(link: str) -> BeautifulSoup:
32
25
  ``BeautifulSoup`` instance
33
26
  """
34
27
  try:
28
+ import requests
29
+ from bs4 import BeautifulSoup
30
+
35
31
  page = requests.get(link)
36
32
  soup = BeautifulSoup(page.content, "html.parser")
37
- logger.debug("Soup completed!")
38
33
  return soup
34
+
35
+ except ImportError:
36
+ raise ImportError("Please install bs4, requests package")
37
+
39
38
  except Exception:
40
- logger.error("Can't soup")
41
39
  raise SystemExit("Something wrong") # noqa: B904
42
40
 
43
41
 
@@ -51,5 +49,4 @@ def gen_random_commit_msg() -> str:
51
49
  Random commit message
52
50
  """
53
51
  out = soup_link("https://whatthecommit.com/").get_text()[34:-20]
54
- logger.debug(out)
55
52
  return out # type: ignore