absfuyu 5.0.0__py3-none-any.whl → 6.1.2__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 (103) hide show
  1. absfuyu/__init__.py +5 -3
  2. absfuyu/__main__.py +3 -3
  3. absfuyu/cli/__init__.py +13 -2
  4. absfuyu/cli/audio_group.py +98 -0
  5. absfuyu/cli/color.py +30 -14
  6. absfuyu/cli/config_group.py +9 -2
  7. absfuyu/cli/do_group.py +23 -6
  8. absfuyu/cli/game_group.py +27 -2
  9. absfuyu/cli/tool_group.py +81 -11
  10. absfuyu/config/__init__.py +3 -3
  11. absfuyu/core/__init__.py +12 -8
  12. absfuyu/core/baseclass.py +929 -96
  13. absfuyu/core/baseclass2.py +44 -3
  14. absfuyu/core/decorator.py +70 -4
  15. absfuyu/core/docstring.py +64 -41
  16. absfuyu/core/dummy_cli.py +3 -3
  17. absfuyu/core/dummy_func.py +19 -6
  18. absfuyu/dxt/__init__.py +2 -2
  19. absfuyu/dxt/base_type.py +93 -0
  20. absfuyu/dxt/dictext.py +204 -16
  21. absfuyu/dxt/dxt_support.py +2 -2
  22. absfuyu/dxt/intext.py +151 -34
  23. absfuyu/dxt/listext.py +969 -127
  24. absfuyu/dxt/strext.py +77 -17
  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 +3 -2
  31. absfuyu/extra/da/__init__.py +72 -0
  32. absfuyu/extra/da/dadf.py +1600 -0
  33. absfuyu/extra/da/dadf_base.py +186 -0
  34. absfuyu/extra/da/df_func.py +181 -0
  35. absfuyu/extra/da/mplt.py +219 -0
  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 +87 -0
  45. absfuyu/extra/rclone.py +253 -0
  46. absfuyu/extra/xml.py +90 -0
  47. absfuyu/fun/__init__.py +7 -20
  48. absfuyu/fun/rubik.py +442 -0
  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 -3
  55. absfuyu/game/wordle.py +6 -4
  56. absfuyu/general/__init__.py +4 -4
  57. absfuyu/general/content.py +4 -4
  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 +28 -2
  72. absfuyu/tools/checksum.py +144 -9
  73. absfuyu/tools/converter.py +120 -34
  74. absfuyu/tools/generator.py +461 -0
  75. absfuyu/tools/inspector.py +752 -0
  76. absfuyu/tools/keygen.py +2 -2
  77. absfuyu/tools/obfuscator.py +47 -9
  78. absfuyu/tools/passwordlib.py +89 -25
  79. absfuyu/tools/shutdownizer.py +3 -8
  80. absfuyu/tools/sw.py +718 -0
  81. absfuyu/tools/web.py +10 -13
  82. absfuyu/typings.py +138 -0
  83. absfuyu/util/__init__.py +114 -6
  84. absfuyu/util/api.py +41 -18
  85. absfuyu/util/cli.py +119 -0
  86. absfuyu/util/gui.py +91 -0
  87. absfuyu/util/json_method.py +43 -14
  88. absfuyu/util/lunar.py +2 -2
  89. absfuyu/util/package.py +124 -0
  90. absfuyu/util/path.py +702 -82
  91. absfuyu/util/performance.py +122 -7
  92. absfuyu/util/shorten_number.py +244 -21
  93. absfuyu/util/text_table.py +481 -0
  94. absfuyu/util/zipped.py +8 -7
  95. absfuyu/version.py +79 -59
  96. {absfuyu-5.0.0.dist-info → absfuyu-6.1.2.dist-info}/METADATA +52 -11
  97. absfuyu-6.1.2.dist-info/RECORD +105 -0
  98. {absfuyu-5.0.0.dist-info → absfuyu-6.1.2.dist-info}/WHEEL +1 -1
  99. absfuyu/extra/data_analysis.py +0 -1078
  100. absfuyu/general/generator.py +0 -303
  101. absfuyu-5.0.0.dist-info/RECORD +0 -68
  102. {absfuyu-5.0.0.dist-info → absfuyu-6.1.2.dist-info}/entry_points.txt +0 -0
  103. {absfuyu-5.0.0.dist-info → absfuyu-6.1.2.dist-info}/licenses/LICENSE +0 -0
absfuyu/extra/pdf.py ADDED
@@ -0,0 +1,87 @@
1
+ """
2
+ Absfuyu: PDF
3
+ ------------
4
+ PDF Tool [W.I.P]
5
+
6
+ Version: 6.1.1
7
+ Date updated: 30/12/2025 (dd/mm/yyyy)
8
+ """
9
+
10
+ # Module level
11
+ # ---------------------------------------------------------------------------
12
+ __all__ = ["PDFTool"]
13
+
14
+
15
+ # Library
16
+ # ---------------------------------------------------------------------------
17
+ from collections.abc import Sequence
18
+ from pathlib import Path
19
+ from typing import overload
20
+
21
+ from absfuyu.core import BaseClass
22
+ from absfuyu.core.dummy_func import tqdm
23
+
24
+ PDF_MODE = False
25
+
26
+ try:
27
+ from spire.pdf import PdfDocument, PdfTextExtractOptions, PdfTextExtractor
28
+ except ImportError:
29
+ from subprocess import run
30
+
31
+ from absfuyu.config import ABSFUYU_CONFIG
32
+
33
+ if ABSFUYU_CONFIG._get_setting("auto-install-extra").value: # type: ignore
34
+ cmd = "python -m pip install -U absfuyu[pdf]".split()
35
+ run(cmd)
36
+ else:
37
+ raise SystemExit("This feature is in absfuyu[pdf] package") # noqa: B904
38
+ else:
39
+ PDF_MODE = True
40
+
41
+
42
+ # Class
43
+ # ---------------------------------------------------------------------------
44
+ class PDFTool(BaseClass):
45
+ def __init__(self) -> None:
46
+ super().__init__()
47
+ self.engine = PdfDocument() # type: ignore
48
+
49
+ self.files: list[Path] = []
50
+
51
+ @overload
52
+ def add_file(self, file: Path | str) -> None: ...
53
+
54
+ @overload
55
+ def add_file(self, file: Sequence[Path | str]) -> None: ...
56
+
57
+ def add_file(self, file: Path | Sequence[Path | str] | str) -> None:
58
+ if isinstance(file, Sequence) and not isinstance(file, str):
59
+ self.files.extend([Path(x) for x in file])
60
+ else:
61
+ self.files.append(Path(file))
62
+
63
+ def load_file(self) -> list[str]:
64
+ """
65
+ Extract text from all file
66
+
67
+ Returns
68
+ -------
69
+ list[str]
70
+ Extracted text
71
+ """
72
+ engine = PdfDocument() # type: ignore
73
+
74
+ extracted: list[str] = []
75
+ for x in tqdm(self.files, desc="Extracting", unit_scale=True):
76
+ engine.LoadFromFile(x.absolute().__str__())
77
+
78
+ # Get pages
79
+ for page in engine.Pages:
80
+ # Create a PdfTextExtractor for the page
81
+ extractor = PdfTextExtractor(page) # type: ignore
82
+
83
+ # Extract all text from the page
84
+ text = extractor.ExtractText(PdfTextExtractOptions()) # type: ignore
85
+ extracted.append(text)
86
+
87
+ return extracted
@@ -0,0 +1,253 @@
1
+ """
2
+ Absfuyu: Rclone decrypt
3
+ -----------------------
4
+ Rclone decryptor
5
+
6
+ Version: 6.1.1
7
+ Date updated: 30/12/2025 (dd/mm/yyyy)
8
+ """
9
+
10
+ # Module level
11
+ # ---------------------------------------------------------------------------
12
+ __all__ = ["RcloneEncryptDecrypt", "DirectoryRcloneDEMixin"]
13
+
14
+
15
+ # Library
16
+ # ---------------------------------------------------------------------------
17
+ import os
18
+ import shutil
19
+ from pathlib import Path
20
+ from typing import Literal, Self
21
+
22
+ from rclone import Crypt
23
+
24
+ from absfuyu.core.baseclass import AutoREPRMixin
25
+ from absfuyu.logger import LogLevel, logger
26
+ from absfuyu.util.path import DirectoryBase
27
+
28
+
29
+ # Class
30
+ # ---------------------------------------------------------------------------
31
+ class RcloneEncryptDecrypt(AutoREPRMixin):
32
+ """
33
+ Rclone Decrypt/Encrypt Module
34
+ """
35
+
36
+ def __init__(self, crypt: Crypt) -> None:
37
+ """
38
+ This will encrypt/decrypt with rclone style
39
+
40
+ Parameters
41
+ ----------
42
+ crypt : Crypt
43
+ ``rclone.Crypt`` object | Encrypt/Decrypt engine
44
+ """
45
+ self._crypt = crypt
46
+
47
+ @classmethod
48
+ def from_passwd_salt(cls, passwd: str, salt: str | None = None) -> Self:
49
+ """
50
+ Create Rclone Decrypt/Encrypt object from password and salt
51
+
52
+ Parameters
53
+ ----------
54
+ passwd : str
55
+ Custom password
56
+
57
+ salt : str | None, optional
58
+ Custom salt, by default None
59
+
60
+ Returns
61
+ -------
62
+ Self
63
+ Rclone Decrypt/Encrypt object
64
+ """
65
+ if salt is None:
66
+ crypt: Crypt = Crypt(passwd=passwd)
67
+ else:
68
+ crypt: Crypt = Crypt(passwd=passwd, salt=salt)
69
+ return cls(crypt)
70
+
71
+ # Support
72
+ @staticmethod
73
+ def _directory_validator(path: Path) -> Path:
74
+ """Validate if directory exists, then return the path"""
75
+ path = Path(path)
76
+ if not path.exists():
77
+ raise ValueError("Path does not exist")
78
+ return path
79
+
80
+ def _modify_name(self, name_to_modify: str, mode: Literal["encrypt", "decrypt"]) -> str:
81
+ if mode == "decrypt":
82
+ return self._crypt.Name.standard_decrypt(name_to_modify)
83
+ if mode == "encrypt":
84
+ return self._crypt.Name.standard_encrypt(name_to_modify)
85
+
86
+ def _decrypt_encrypt_operation(
87
+ self,
88
+ mode: Literal["encrypt", "decrypt"],
89
+ root_dir: Path | str, # type: ignore
90
+ delete_when_complete: bool = False,
91
+ ) -> None:
92
+ """
93
+ Base operation to decrypt, encrypt
94
+
95
+ Parameters
96
+ ----------
97
+ mode : Literal["encrypt", "decrypt"]
98
+ Encrypt or decrypt
99
+
100
+ root_dir : Path | str
101
+ Directory location
102
+
103
+ delete_when_complete : bool
104
+ Delete directory when completed, defaults to False
105
+ """
106
+
107
+ logger.info(f"Begin operation: {mode.title()}")
108
+ root_dir = Path(root_dir)
109
+ FILE_MODE = False
110
+
111
+ # Make Base folder
112
+ if root_dir.is_file():
113
+ # Get name of parent dir and make name
114
+ temp = self._modify_name(root_dir.parent.name, "encrypt" if mode == "decrypt" else "decrypt")
115
+ # Create sub dir
116
+ base_dir = root_dir.parent.joinpath(temp)
117
+ base_dir.mkdir(exist_ok=True, parents=True)
118
+ # Move file to that dir
119
+ new_path = root_dir.rename(base_dir.joinpath(root_dir.name))
120
+ root_dir = base_dir # Make root dir
121
+ FILE_MODE = True
122
+ else:
123
+ root_dir: Path = self._directory_validator(root_dir)
124
+ base_name: str = self._modify_name(root_dir.name, mode=mode)
125
+ base_dir: Path = root_dir.parent.joinpath(base_name)
126
+ base_dir.mkdir(exist_ok=True, parents=True)
127
+
128
+ _ljust: int = 17 # Logger ljust value
129
+
130
+ # Decrypt / Encrypt
131
+ for path in root_dir.glob("**/*"):
132
+ rel_path: Path = path.relative_to(root_dir)
133
+ rel_path_splited: list[str] = str(rel_path).split(os.sep)
134
+ rel_path_modified: list[str] = [self._modify_name(x, mode=mode) for x in rel_path_splited]
135
+ new_path: Path = base_dir.joinpath(*rel_path_modified)
136
+
137
+ if path.is_dir():
138
+ logger.debug(f"{mode.title()}ing Dir:".ljust(_ljust) + f"{path}")
139
+ new_path.mkdir(exist_ok=True, parents=True)
140
+ logger.debug(f"{mode.title()}ed Dir:".ljust(_ljust) + f"{new_path}")
141
+ else:
142
+ logger.debug(f"{mode.title()}ing File:".ljust(_ljust) + f"{path}")
143
+ if mode == "decrypt":
144
+ self._crypt.File.file_decrypt(path, new_path) # type: ignore
145
+ if mode == "encrypt":
146
+ self._crypt.File.file_encrypt(path, new_path) # type: ignore
147
+ logger.debug(f"{mode.title()}ed File:".ljust(_ljust) + f"{new_path}")
148
+ logger.info(f"Operation: {mode.title()} COMPLETED")
149
+
150
+ # File mode
151
+ if FILE_MODE:
152
+ new_path.rename(root_dir.parent.joinpath(new_path.name)) # type: ignore
153
+ shutil.rmtree(root_dir)
154
+
155
+ # Delete when completed
156
+ if delete_when_complete:
157
+ logger.debug(f"Deleting {root_dir}")
158
+ shutil.rmtree(root_dir)
159
+ logger.debug(f"Deleting {root_dir}...DONE")
160
+
161
+ # Decrypt/Encrypt
162
+ def decrypt(self, root_dir: Path, delete_when_complete: bool = False) -> None:
163
+ """
164
+ Decrypt encrypted directory.
165
+
166
+ This will decrypt the directory itself and
167
+ create a decrypted directory next to the original
168
+
169
+ Parameters
170
+ ----------
171
+ root_dir : Path
172
+ Directory location
173
+
174
+ delete_when_complete : bool, optional
175
+ Delete directory when completed, by default False
176
+ """
177
+ self._decrypt_encrypt_operation(mode="decrypt", root_dir=root_dir, delete_when_complete=delete_when_complete)
178
+
179
+ def encrypt(self, root_dir: Path, delete_when_complete: bool = False) -> None:
180
+ """
181
+ Encrypt entire directory.
182
+
183
+ This will encrypt the directory itself and
184
+ create an encrypted directory next to the original
185
+
186
+ Parameters
187
+ ----------
188
+ root_dir : Path
189
+ Directory location
190
+
191
+ delete_when_complete : bool, optional
192
+ Delete directory when completed, by default False
193
+ """
194
+ self._decrypt_encrypt_operation(mode="encrypt", root_dir=root_dir, delete_when_complete=delete_when_complete)
195
+
196
+
197
+ class DirectoryRcloneDEMixin(DirectoryBase):
198
+ """
199
+ Directory - Rclone encrypt/decrypt
200
+
201
+ Extension for ``absfuyu.util.path.Directory``
202
+
203
+ - Decrypt
204
+ - Encrypt
205
+ """
206
+
207
+ def rclone_encrypt(self, passwd: str, salt: str | None = None, delete_when_complete: bool = False) -> None:
208
+ """
209
+ Encrypt entire directory.
210
+
211
+ This will encrypt the directory itself and
212
+ create an encrypted directory next to the original
213
+
214
+ Parameters
215
+ ----------
216
+ passwd : str
217
+ Custom password
218
+
219
+ salt : str | None, optional
220
+ Custom salt, by default None
221
+
222
+ delete_when_complete : bool, optional
223
+ Delete directory when completed, by default False
224
+ """
225
+ engine = RcloneEncryptDecrypt.from_passwd_salt(passwd=passwd, salt=salt)
226
+ engine.encrypt(self.source_path, delete_when_complete=delete_when_complete)
227
+
228
+ def rclone_decrypt(self, passwd: str, salt: str | None = None, delete_when_complete: bool = False) -> None:
229
+ """
230
+ Decrypt encrypted directory.
231
+
232
+ This will decrypt the directory itself and
233
+ create a decrypted directory next to the original
234
+
235
+ Parameters
236
+ ----------
237
+ passwd : str
238
+ Custom password
239
+
240
+ salt : str | None, optional
241
+ Custom salt, by default None
242
+
243
+ delete_when_complete : bool, optional
244
+ Delete directory when completed, by default False
245
+ """
246
+ engine = RcloneEncryptDecrypt.from_passwd_salt(passwd=passwd, salt=salt)
247
+ engine.decrypt(self.source_path, delete_when_complete=delete_when_complete)
248
+
249
+
250
+ # Run
251
+ # -----------------------------------------------------------------------------------------------
252
+ if __name__ == "__main__":
253
+ logger.setLevel(LogLevel.DEBUG)
absfuyu/extra/xml.py ADDED
@@ -0,0 +1,90 @@
1
+ """
2
+ Absfuyu: XML
3
+ ------------
4
+ XML Tool [W.I.P]
5
+
6
+ Version: 6.1.1
7
+ Date updated: 30/12/2025 (dd/mm/yyyy)
8
+ """
9
+
10
+ # Module level
11
+ # ---------------------------------------------------------------------------
12
+ __all__ = ["XML2Dict"]
13
+
14
+
15
+ # Library
16
+ # ---------------------------------------------------------------------------
17
+ from pathlib import Path
18
+ from typing import Any, Self
19
+
20
+ from absfuyu.core.baseclass import GetClassMembersMixin
21
+
22
+ XML_MODE = False
23
+
24
+ try:
25
+ import xmltodict
26
+ except ImportError:
27
+ from subprocess import run
28
+
29
+ from absfuyu.config import ABSFUYU_CONFIG
30
+
31
+ if ABSFUYU_CONFIG._get_setting("auto-install-extra").value: # type: ignore
32
+ cmd = "python -m pip install -U absfuyu[full]".split()
33
+ run(cmd)
34
+ else:
35
+ raise SystemExit("This feature is in absfuyu[full] package") # noqa: B904
36
+ else:
37
+ XML_MODE = True
38
+
39
+
40
+ # Class
41
+ # ---------------------------------------------------------------------------
42
+ class XML2Dict(GetClassMembersMixin):
43
+ """
44
+ Automatically convert .xml file to dict
45
+
46
+
47
+ Usage
48
+ -----
49
+ >>> XML2Dict(<path>)
50
+ >>> XML2Dict.parsed_data
51
+ """
52
+
53
+ def __init__(self, text: str) -> None:
54
+ """
55
+ Automatically convert .xml file to dict
56
+
57
+ Parameters
58
+ ----------
59
+ text : str
60
+ ``.xml`` format text
61
+ """
62
+ self._text = text
63
+
64
+ self.parsed_data: dict[str, Any] = self._parse_xml()
65
+
66
+ def __repr__(self) -> str:
67
+ return f"{self.__class__.__name__}()"
68
+
69
+ def _parse_xml(self) -> dict[str, Any]:
70
+ """Convert xml to dict"""
71
+ return xmltodict.parse(self._text, encoding="utf-8") # type: ignore
72
+
73
+ @classmethod
74
+ def from_path(cls, xml_path: str | Path) -> Self:
75
+ """
76
+ Convert from .xml file path
77
+
78
+ Parameters
79
+ ----------
80
+ xml_path : str | Path
81
+ Path to .xml file
82
+
83
+ Returns
84
+ -------
85
+ Self
86
+ xml to dict
87
+ """
88
+ with Path(xml_path).open("r", encoding="utf-8") as f:
89
+ dat = "\n".join(f.readlines())
90
+ return cls(dat)
absfuyu/fun/__init__.py CHANGED
@@ -3,10 +3,15 @@ Absfuyu: Fun
3
3
  ------------
4
4
  Some fun or weird stuff
5
5
 
6
- Version: 5.0.0
7
- Date updated: 13/02/2025 (dd/mm/yyyy)
6
+ Version: 6.1.1
7
+ Date updated: 30/12/2025 (dd/mm/yyyy)
8
8
  """
9
9
 
10
+ # Module level
11
+ # ---------------------------------------------------------------------------
12
+ __all__ = ["zodiac_sign", "happy_new_year", "human_year_to_dog_year"]
13
+
14
+
10
15
  # Library
11
16
  # ---------------------------------------------------------------------------
12
17
  from datetime import date
@@ -90,24 +95,6 @@ def zodiac_sign(day: int, month: int, zodiac13: bool = False) -> str:
90
95
  return result
91
96
 
92
97
 
93
- @deprecated("5.0.0", reason="API shutdown, will be removed later")
94
- def im_bored() -> str:
95
- """
96
- Get random activity from ``boredapi`` website
97
-
98
- Returns
99
- -------
100
- str
101
- Random activity
102
- """
103
- raise SystemExit("API shuted down")
104
- # try:
105
- # api = APIRequest("https://www.boredapi.com/api/activity")
106
- # return api.fetch_data_only().json()["activity"] # type: ignore
107
- # except Exception:
108
- # return "FAILED"
109
-
110
-
111
98
  # For new year only
112
99
  def happy_new_year(forced: bool = False, include_lunar: bool = False) -> None:
113
100
  """