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.
- absfuyu/__init__.py +5 -3
- absfuyu/__main__.py +3 -3
- absfuyu/cli/__init__.py +13 -2
- absfuyu/cli/audio_group.py +98 -0
- absfuyu/cli/color.py +30 -14
- absfuyu/cli/config_group.py +9 -2
- absfuyu/cli/do_group.py +23 -6
- absfuyu/cli/game_group.py +27 -2
- absfuyu/cli/tool_group.py +81 -11
- absfuyu/config/__init__.py +3 -3
- absfuyu/core/__init__.py +12 -8
- absfuyu/core/baseclass.py +929 -96
- absfuyu/core/baseclass2.py +44 -3
- absfuyu/core/decorator.py +70 -4
- absfuyu/core/docstring.py +64 -41
- absfuyu/core/dummy_cli.py +3 -3
- absfuyu/core/dummy_func.py +19 -6
- absfuyu/dxt/__init__.py +2 -2
- absfuyu/dxt/base_type.py +93 -0
- absfuyu/dxt/dictext.py +204 -16
- absfuyu/dxt/dxt_support.py +2 -2
- absfuyu/dxt/intext.py +151 -34
- absfuyu/dxt/listext.py +969 -127
- absfuyu/dxt/strext.py +77 -17
- absfuyu/extra/__init__.py +2 -2
- absfuyu/extra/audio/__init__.py +8 -0
- absfuyu/extra/audio/_util.py +57 -0
- absfuyu/extra/audio/convert.py +192 -0
- absfuyu/extra/audio/lossless.py +281 -0
- absfuyu/extra/beautiful.py +3 -2
- absfuyu/extra/da/__init__.py +72 -0
- absfuyu/extra/da/dadf.py +1600 -0
- absfuyu/extra/da/dadf_base.py +186 -0
- absfuyu/extra/da/df_func.py +181 -0
- absfuyu/extra/da/mplt.py +219 -0
- absfuyu/extra/ggapi/__init__.py +8 -0
- absfuyu/extra/ggapi/gdrive.py +223 -0
- absfuyu/extra/ggapi/glicense.py +148 -0
- absfuyu/extra/ggapi/glicense_df.py +186 -0
- absfuyu/extra/ggapi/gsheet.py +88 -0
- absfuyu/extra/img/__init__.py +30 -0
- absfuyu/extra/img/converter.py +402 -0
- absfuyu/extra/img/dup_check.py +291 -0
- absfuyu/extra/pdf.py +87 -0
- absfuyu/extra/rclone.py +253 -0
- absfuyu/extra/xml.py +90 -0
- absfuyu/fun/__init__.py +7 -20
- absfuyu/fun/rubik.py +442 -0
- absfuyu/fun/tarot.py +2 -2
- absfuyu/game/__init__.py +2 -2
- absfuyu/game/game_stat.py +2 -2
- absfuyu/game/schulte.py +78 -0
- absfuyu/game/sudoku.py +2 -2
- absfuyu/game/tictactoe.py +2 -3
- absfuyu/game/wordle.py +6 -4
- absfuyu/general/__init__.py +4 -4
- absfuyu/general/content.py +4 -4
- absfuyu/general/human.py +2 -2
- absfuyu/general/resrel.py +213 -0
- absfuyu/general/shape.py +3 -8
- absfuyu/general/tax.py +344 -0
- absfuyu/logger.py +806 -59
- absfuyu/numbers/__init__.py +13 -0
- absfuyu/numbers/number_to_word.py +321 -0
- absfuyu/numbers/shorten_number.py +303 -0
- absfuyu/numbers/time_duration.py +217 -0
- absfuyu/pkg_data/__init__.py +2 -2
- absfuyu/pkg_data/deprecated.py +2 -2
- absfuyu/pkg_data/logo.py +1462 -0
- absfuyu/sort.py +4 -4
- absfuyu/tools/__init__.py +28 -2
- absfuyu/tools/checksum.py +144 -9
- absfuyu/tools/converter.py +120 -34
- absfuyu/tools/generator.py +461 -0
- absfuyu/tools/inspector.py +752 -0
- absfuyu/tools/keygen.py +2 -2
- absfuyu/tools/obfuscator.py +47 -9
- absfuyu/tools/passwordlib.py +89 -25
- absfuyu/tools/shutdownizer.py +3 -8
- absfuyu/tools/sw.py +718 -0
- absfuyu/tools/web.py +10 -13
- absfuyu/typings.py +138 -0
- absfuyu/util/__init__.py +114 -6
- absfuyu/util/api.py +41 -18
- absfuyu/util/cli.py +119 -0
- absfuyu/util/gui.py +91 -0
- absfuyu/util/json_method.py +43 -14
- absfuyu/util/lunar.py +2 -2
- absfuyu/util/package.py +124 -0
- absfuyu/util/path.py +702 -82
- absfuyu/util/performance.py +122 -7
- absfuyu/util/shorten_number.py +244 -21
- absfuyu/util/text_table.py +481 -0
- absfuyu/util/zipped.py +8 -7
- absfuyu/version.py +79 -59
- {absfuyu-5.0.0.dist-info → absfuyu-6.1.2.dist-info}/METADATA +52 -11
- absfuyu-6.1.2.dist-info/RECORD +105 -0
- {absfuyu-5.0.0.dist-info → absfuyu-6.1.2.dist-info}/WHEEL +1 -1
- absfuyu/extra/data_analysis.py +0 -1078
- absfuyu/general/generator.py +0 -303
- absfuyu-5.0.0.dist-info/RECORD +0 -68
- {absfuyu-5.0.0.dist-info → absfuyu-6.1.2.dist-info}/entry_points.txt +0 -0
- {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
|
absfuyu/extra/rclone.py
ADDED
|
@@ -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:
|
|
7
|
-
Date updated:
|
|
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
|
"""
|