absfuyu 4.2.0__py3-none-any.whl → 5.0.1__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 (72) hide show
  1. absfuyu/__init__.py +4 -4
  2. absfuyu/__main__.py +13 -1
  3. absfuyu/cli/__init__.py +2 -2
  4. absfuyu/cli/color.py +9 -2
  5. absfuyu/cli/config_group.py +2 -2
  6. absfuyu/cli/do_group.py +2 -37
  7. absfuyu/cli/game_group.py +2 -2
  8. absfuyu/cli/tool_group.py +7 -7
  9. absfuyu/config/__init__.py +17 -34
  10. absfuyu/core/__init__.py +49 -0
  11. absfuyu/core/baseclass.py +299 -0
  12. absfuyu/core/baseclass2.py +165 -0
  13. absfuyu/core/decorator.py +67 -0
  14. absfuyu/core/docstring.py +166 -0
  15. absfuyu/core/dummy_cli.py +67 -0
  16. absfuyu/core/dummy_func.py +49 -0
  17. absfuyu/dxt/__init__.py +42 -0
  18. absfuyu/dxt/dictext.py +201 -0
  19. absfuyu/dxt/dxt_support.py +79 -0
  20. absfuyu/dxt/intext.py +586 -0
  21. absfuyu/dxt/listext.py +508 -0
  22. absfuyu/dxt/strext.py +530 -0
  23. absfuyu/extra/__init__.py +12 -0
  24. absfuyu/extra/beautiful.py +252 -0
  25. absfuyu/{extensions → extra}/data_analysis.py +51 -82
  26. absfuyu/fun/__init__.py +110 -135
  27. absfuyu/fun/tarot.py +11 -19
  28. absfuyu/game/__init__.py +8 -2
  29. absfuyu/game/game_stat.py +8 -2
  30. absfuyu/game/sudoku.py +9 -3
  31. absfuyu/game/tictactoe.py +14 -7
  32. absfuyu/game/wordle.py +16 -10
  33. absfuyu/general/__init__.py +8 -81
  34. absfuyu/general/content.py +24 -38
  35. absfuyu/general/human.py +108 -228
  36. absfuyu/general/shape.py +1334 -0
  37. absfuyu/logger.py +10 -15
  38. absfuyu/pkg_data/__init__.py +137 -100
  39. absfuyu/pkg_data/deprecated.py +133 -0
  40. absfuyu/sort.py +6 -130
  41. absfuyu/tools/__init__.py +2 -2
  42. absfuyu/tools/checksum.py +33 -22
  43. absfuyu/tools/converter.py +51 -48
  44. absfuyu/{general → tools}/generator.py +17 -42
  45. absfuyu/tools/keygen.py +25 -30
  46. absfuyu/tools/obfuscator.py +246 -112
  47. absfuyu/tools/passwordlib.py +100 -30
  48. absfuyu/tools/shutdownizer.py +68 -47
  49. absfuyu/tools/web.py +4 -11
  50. absfuyu/util/__init__.py +17 -17
  51. absfuyu/util/api.py +10 -15
  52. absfuyu/util/json_method.py +7 -24
  53. absfuyu/util/lunar.py +5 -11
  54. absfuyu/util/path.py +22 -27
  55. absfuyu/util/performance.py +43 -67
  56. absfuyu/util/shorten_number.py +65 -14
  57. absfuyu/util/zipped.py +11 -17
  58. absfuyu/version.py +59 -42
  59. {absfuyu-4.2.0.dist-info → absfuyu-5.0.1.dist-info}/METADATA +41 -14
  60. absfuyu-5.0.1.dist-info/RECORD +68 -0
  61. absfuyu/core.py +0 -57
  62. absfuyu/everything.py +0 -32
  63. absfuyu/extensions/__init__.py +0 -12
  64. absfuyu/extensions/beautiful.py +0 -188
  65. absfuyu/fun/WGS.py +0 -134
  66. absfuyu/general/data_extension.py +0 -1796
  67. absfuyu/tools/stats.py +0 -226
  68. absfuyu/util/pkl.py +0 -67
  69. absfuyu-4.2.0.dist-info/RECORD +0 -59
  70. {absfuyu-4.2.0.dist-info → absfuyu-5.0.1.dist-info}/WHEEL +0 -0
  71. {absfuyu-4.2.0.dist-info → absfuyu-5.0.1.dist-info}/entry_points.txt +0 -0
  72. {absfuyu-4.2.0.dist-info → absfuyu-5.0.1.dist-info}/licenses/LICENSE +0 -0
@@ -3,38 +3,121 @@ Absfuyu: Obfuscator
3
3
  -------------------
4
4
  Obfuscate code
5
5
 
6
- Version: 2.0.2
7
- Date updated: 05/04/2024 (dd/mm/yyyy)
6
+ Version: 5.0.0
7
+ Date updated: 25/02/2025 (dd/mm/yyyy)
8
8
  """
9
9
 
10
10
  # Module level
11
- ###########################################################################
12
- __all__ = ["Obfuscator"]
11
+ # ---------------------------------------------------------------------------
12
+ __all__ = ["Obfuscator", "StrShifter"]
13
13
 
14
14
 
15
15
  # Library
16
- ###########################################################################
16
+ # ---------------------------------------------------------------------------
17
17
  import base64
18
18
  import codecs
19
19
  import random
20
20
  import zlib
21
+ from collections import deque
22
+ from string import Template
23
+ from typing import ClassVar
21
24
 
22
- from absfuyu.general.data_extension import Text
23
- from absfuyu.general.generator import Charset
24
- from absfuyu.general.generator import Generator as gen
25
+ from absfuyu.core import BaseClass, ShowAllMethodsMixin, versionadded
26
+ from absfuyu.dxt import Text
25
27
  from absfuyu.logger import logger
28
+ from absfuyu.tools.generator import Charset, Generator
26
29
 
27
30
 
28
31
  # Class
29
- ###########################################################################
30
- class ObfuscatorLibraryList:
31
- BASE64_ONLY = ["base64"]
32
- FULL = ["base64", "codecs", "zlib"]
32
+ # ---------------------------------------------------------------------------
33
+ @versionadded("5.0.0")
34
+ class StrShifter(BaseClass):
35
+ """Shift characters in a string by a specified number of positions."""
33
36
 
37
+ __slots__ = ("_str_to_shift", "shift_by")
34
38
 
35
- class Obfuscator:
39
+ def __init__(self, str_to_shift: str, shift_by: int = 5) -> None:
40
+ """
41
+ Initialize the StrShifter with the string to shift and the shift amount.
42
+
43
+ Parameters
44
+ ----------
45
+ str_to_shift : str
46
+ The string whose characters will be shifted.
47
+
48
+ shift_by : int, optional
49
+ The number of positions to shift the characters, by default ``5``.
50
+ """
51
+ if not isinstance(str_to_shift, str):
52
+ raise TypeError("Value must be an instance of str")
53
+ self._str_to_shift = str_to_shift
54
+ self.shift_by = shift_by
55
+
56
+ def _make_convert_table(self) -> dict[str, str]:
57
+ """
58
+ Create a translation table for shifting characters.
59
+
60
+ Returns
61
+ -------
62
+ dict[str, str]
63
+ A dictionary mapping each character to its shifted counterpart.
64
+ """
65
+ data = self._str_to_shift # Make a copy
66
+
67
+ unique_char_sorted = deque(sorted(list(set(data))))
68
+ translate = unique_char_sorted.copy()
69
+ translate.rotate(self.shift_by)
70
+ convert_table = dict(zip(unique_char_sorted, translate))
71
+
72
+ return convert_table
73
+
74
+ def _use_convert_table(self, convert_table: dict[str, str]) -> str:
75
+ """
76
+ Convert the original string using the provided conversion table.
77
+
78
+ Parameters
79
+ ----------
80
+ convert_table : dict[str, str]
81
+ The conversion table mapping original characters to shifted characters.
82
+
83
+ Returns
84
+ -------
85
+ str
86
+ The transformed string after applying the conversion table.
87
+ """
88
+ return "".join([convert_table[char] for char in list(self._str_to_shift)])
89
+
90
+ def shift(self) -> str:
91
+ """
92
+ Shift the characters in the string and return the new string.
93
+
94
+ Returns
95
+ -------
96
+ str
97
+ The resulting string after shifting.
98
+ """
99
+ return self._use_convert_table(self._make_convert_table())
100
+
101
+
102
+ class Obfuscator(ShowAllMethodsMixin):
36
103
  """Obfuscate code"""
37
104
 
105
+ # Var
106
+ LIB_BASE64_ONLY: ClassVar[list[str]] = ["base64"]
107
+ LIB_FULL: ClassVar[list[str]] = ["base64", "codecs", "zlib"]
108
+
109
+ # Template
110
+ SINGLE_LINE_TEMPLATE: ClassVar[Template] = Template(
111
+ "exec(bytes.fromhex('$one_line_code').decode('utf-8'))"
112
+ )
113
+ PRE_HEX_B64_TEMPLATE: ClassVar[Template] = Template(
114
+ "eval(compile(base64.b64decode($encoded_string),$type_var,$execute))"
115
+ )
116
+ PRE_HEX_FULL_TEMPLATE: ClassVar[Template] = Template(
117
+ "eval(compile(base64.b64decode(zlib.decompress(base64.b64decode(codecs."
118
+ "encode($encoded_string,$codec_to_decode).encode()))),$type_var,$execute))"
119
+ )
120
+
38
121
  def __init__(
39
122
  self,
40
123
  code: str,
@@ -45,56 +128,73 @@ class Obfuscator:
45
128
  fake_data: bool = False,
46
129
  ) -> None:
47
130
  """
48
- code: Code text
49
- base64_only: False: base64, compress, rot13 (default) | True: encode in base64 form only
50
- split_every: Split the long line of code every `x` character
51
- variable_length: Length of variable name (when data string split). Minimum is 7
52
- fake_data: Generate additional meaningless data
131
+ Obfuscator
132
+
133
+ Parameters
134
+ ----------
135
+ code : str
136
+ Code text
137
+
138
+ base64_only : bool, optional
139
+ - ``True``: encode in base64 form only
140
+ - ``False``: base64, compress, rot13 (default)
141
+
142
+ split_every : int, optional
143
+ Split the long line of code every ``x`` character.
144
+ Minimum is ``1``, by default ``60``
145
+
146
+ variable_length : int, optional
147
+ Length of variable name (when data string split).
148
+ Minimum is ``7``, by default ``12``
149
+
150
+ fake_data : bool, optional
151
+ Generate additional meaningless data, by default ``False``
53
152
  """
54
153
  self.base_code = code
55
154
  self.base64_only = base64_only
56
- self.split_every_length = split_every
57
- self.variable_length = variable_length if variable_length > 6 else 7
155
+ self.split_every_length = max(1, split_every)
156
+ self.variable_length = max(7, variable_length)
58
157
  self.fake_data = fake_data
59
158
 
60
159
  # Setting
61
160
  self._library_import_variable_length = self.variable_length - 1
62
161
  self._splited_variable_length = self.variable_length
63
162
  self._decode_variable_length = self.variable_length + 3
64
- # logger.debug("Class initiated.")
65
163
 
66
164
  def __str__(self) -> str:
67
- # temp = self.__dict__
68
- temp = {
165
+ return self.__repr__()
166
+
167
+ def __repr__(self) -> str:
168
+ repr_out_dict = {
69
169
  "base_code": "...",
70
170
  "base64_only": self.base64_only,
71
171
  "split_every_length": self.split_every_length,
72
172
  "variable_length": self.variable_length,
73
173
  "fake_data": self.fake_data,
74
174
  }
75
- return f"{self.__class__.__name__}({temp})"
76
-
77
- def __repr__(self) -> str:
78
- return self.__str__()
175
+ repr_out = ", ".join([f"{k}={repr(v)}" for k, v in repr_out_dict.items()])
176
+ return f"{self.__class__.__name__}({repr_out})"
79
177
 
80
178
  def to_single_line(self) -> str:
81
179
  """
82
180
  Convert multiple lines of code into one line
83
181
 
84
- :returns: Converted code
85
- :rtype: str
182
+ Returns
183
+ -------
184
+ str
185
+ Converted code.
86
186
  """
87
187
  newcode = self.base_code.encode("utf-8").hex()
88
- logger.debug(newcode)
89
- output = f"exec(bytes.fromhex('{newcode}').decode('utf-8'))"
188
+ output = self.SINGLE_LINE_TEMPLATE.substitute(one_line_code=newcode)
90
189
  return output
91
190
 
191
+ # Obfuscate original code
92
192
  def _obfuscate(self) -> str:
93
193
  """
94
194
  Convert multiple lines of code through multiple transformation
95
195
  (base64 -> compress -> base64 -> caesar (13))
96
196
  """
97
- code = self.base_code
197
+ code = self.base_code # Make a copy
98
198
  logger.debug("Encoding...")
99
199
 
100
200
  b64_encode = base64.b64encode(code.encode())
@@ -102,12 +202,12 @@ class Obfuscator:
102
202
  output = b64_encode.decode()
103
203
  else:
104
204
  compressed_data = zlib.compress(b64_encode)
105
- # log_debug(compressed_data)
106
- logger.debug(f"Compressed data: {compressed_data}") # type: ignore
107
- b64_encode_2 = base64.b64encode(compressed_data)
108
- # log_debug(b64_encode_2)
109
- logger.debug(f"Base64 encode 2: {b64_encode_2}") # type: ignore
110
- caesar_data = codecs.encode(b64_encode_2.decode(), "rot_13")
205
+ logger.debug(
206
+ f"Compressed data: {str(compressed_data)} | Len: {len(str(compressed_data))}"
207
+ )
208
+ b64_encode_2 = base64.b64encode(compressed_data).decode()
209
+ logger.debug(f"Base64 encode 2: {b64_encode_2} | Len: {len(b64_encode_2)}")
210
+ caesar_data = codecs.encode(b64_encode_2, "rot_13")
111
211
  output = caesar_data
112
212
 
113
213
  logger.debug(f"Output: {output}")
@@ -122,16 +222,100 @@ class Obfuscator:
122
222
  text: Code that need to convert
123
223
  raw: Return hex form only
124
224
  """
125
- encode_codec = "rot_13"
126
- b64_encode_codec = base64.b64encode(encode_codec.encode()).decode()
127
- b64_decode_codec = f"base64.b64decode('{b64_encode_codec}'.encode())"
225
+ b64_encode_codec = base64.b64encode(text.encode()).decode()
226
+ b64_decode_codec = f"base64.b64decode('{b64_encode_codec}'.encode()).decode()"
128
227
  hex = Text(b64_decode_codec).to_hex()
129
- out = f"eval('{hex}').decode()"
228
+ out = f"eval('{hex}')"
130
229
  if raw:
131
230
  return hex
132
231
  return out
133
232
 
134
- def _obfuscate_out(self) -> list: # type: ignore
233
+ # Generate output (decode obfuscated code)
234
+ def _make_output_lib(self) -> list[str]:
235
+ """Obfuscate the `import <lib>`"""
236
+ output = []
237
+
238
+ # Make import lib
239
+ library_list = self.LIB_BASE64_ONLY if self.base64_only else self.LIB_FULL
240
+ imports = [f"import {lib}" for lib in library_list]
241
+ logger.debug(f"Lib: {imports}")
242
+
243
+ # Convert to hex
244
+ lib_hex = Text("\n".join(imports)).to_hex()
245
+ output.append(f"exec('{lib_hex}')")
246
+ logger.debug(f"Current output (import library): {output}")
247
+
248
+ return output
249
+
250
+ def _make_prep_for_decode_var(self) -> tuple[list[str], list[str]]:
251
+ """
252
+ ``<var> = "rot_13"``
253
+ ``<var> = "<string>"``
254
+ ``<var> = "exec"``
255
+
256
+ Returns
257
+ -------
258
+ tuple[list[str], list[str]]
259
+ - tuple[0]: output
260
+ - tuple[1]: decode var name
261
+ """
262
+ output = []
263
+
264
+ # Make variables for "rot_13", "<string>", "exec"
265
+ dc_name_lst: list[str] = Generator.generate_string(
266
+ charset=Charset.ALPHABET,
267
+ size=self._decode_variable_length,
268
+ times=3,
269
+ unique=True,
270
+ )
271
+
272
+ # Assign and convert to hex
273
+ encode_codec = "rot_13" # full
274
+ if not self.base64_only: # full
275
+ hex_0 = self._convert_to_base64_decode(encode_codec)
276
+ output.append(f"{dc_name_lst[0]}={hex_0}")
277
+
278
+ for i, x in enumerate(["<string>", "exec"], start=1):
279
+ # hex_str = Text(x).to_hex()
280
+ hex_str = self._convert_to_base64_decode(x)
281
+ output.append(f"{dc_name_lst[i]}={hex_str}")
282
+ logger.debug(f"Current output (decode variables): {output}")
283
+
284
+ return output, dc_name_lst
285
+
286
+ def _make_fake_output(self, input_size: int) -> list[str]:
287
+ """Fake data"""
288
+ output = []
289
+
290
+ f1 = Generator.generate_string(
291
+ charset=Charset.DEFAULT,
292
+ size=input_size,
293
+ times=1,
294
+ string_type_if_1=True,
295
+ ) # Generate fake data with len of original data
296
+ f2 = Text(f1).divide_with_variable(
297
+ self.split_every_length, self._splited_variable_length
298
+ )
299
+ output.extend(f2[:-1])
300
+
301
+ # Random data
302
+ bait_lst = Generator.generate_string(
303
+ charset=Charset.ALPHABET, size=self._splited_variable_length, times=25
304
+ )
305
+ for x in bait_lst:
306
+ output.append(
307
+ f"{x}='{Generator.generate_string(charset=Charset.DEFAULT, size=self.split_every_length, times=1, string_type_if_1=True)}'"
308
+ )
309
+
310
+ random_eval_text = str(random.randint(1, 100))
311
+ for _ in range(random.randint(10, 50)):
312
+ random_eval_text += f"+{random.randint(1, 100)}"
313
+ random_eval_text_final = Text(random_eval_text).to_hex()
314
+ output.append(f"eval('{random_eval_text_final}')")
315
+
316
+ return output
317
+
318
+ def _make_obfuscate_output(self) -> list[str]:
135
319
  """
136
320
  Convert multiple lines of code through multiple transformation
137
321
  (base64 -> compress -> base64 -> caesar (13))
@@ -146,16 +330,7 @@ class Obfuscator:
146
330
  output = []
147
331
 
148
332
  # Import library
149
- library_list = (
150
- ObfuscatorLibraryList.BASE64_ONLY
151
- if self.base64_only
152
- else ObfuscatorLibraryList.FULL
153
- )
154
- temp = [f"import {lib}" for lib in library_list]
155
- logger.debug(f"Lib: {temp}")
156
- lib_hex = Text("\n".join(temp)).to_hex() # Convert to hex
157
- output.append(f"exec('{lib_hex}')")
158
- logger.debug(f"Current output (import library): {output}")
333
+ output.extend(self._make_output_lib())
159
334
 
160
335
  # Append divided long text list
161
336
  input_list = input_str.divide_with_variable(
@@ -167,64 +342,30 @@ class Obfuscator:
167
342
  logger.debug(f"Current output (encoded code): {output}")
168
343
 
169
344
  # Decode: encoded_str
170
- dc_name_lst = gen.generate_string(
171
- charset=Charset.ALPHABET, size=self._decode_variable_length, times=3
172
- )
173
- encode_codec = "rot_13" # full
174
- if not self.base64_only: # full
175
- hex_0 = self._convert_to_base64_decode(encode_codec)
176
- output.append(f"{dc_name_lst[0]}={hex_0}")
177
- hex_1 = Text("<string>").to_hex()
178
- output.append(f"{dc_name_lst[1]}='{hex_1}'")
179
- hex_2 = Text("exec").to_hex()
180
- output.append(f"{dc_name_lst[2]}='{hex_2}'")
181
- logger.debug(f"Current output (decode variables): {output}")
345
+ dc_out, dc_name_lst = self._make_prep_for_decode_var()
346
+ output.extend(dc_out)
182
347
 
183
348
  if self.base64_only: # b64
184
- pre_hex = (
185
- f"eval(compile(base64."
186
- f"b64decode({encoded_str}),{dc_name_lst[1]},"
187
- f"{dc_name_lst[2]}))"
349
+ pre_hex = self.PRE_HEX_B64_TEMPLATE.substitute(
350
+ encoded_string=encoded_str,
351
+ type_var=dc_name_lst[1],
352
+ execute=dc_name_lst[2],
188
353
  )
189
354
  else: # full
190
- pre_hex = (
191
- f"eval(compile(base64."
192
- f"b64decode(zlib.decompress(base64."
193
- f"b64decode(codecs."
194
- f"encode({encoded_str},{dc_name_lst[0]})."
195
- f"encode()))),{dc_name_lst[1]},{dc_name_lst[2]}))"
355
+ pre_hex = self.PRE_HEX_FULL_TEMPLATE.substitute(
356
+ encoded_string=encoded_str,
357
+ codec_to_decode=dc_name_lst[0],
358
+ type_var=dc_name_lst[1],
359
+ execute=dc_name_lst[2],
196
360
  )
361
+
197
362
  t_hex = Text(pre_hex).to_hex()
198
363
  output.append(f"exec('{t_hex}')")
199
364
  logger.debug(f"Current output (decode code): {output}")
200
365
 
201
366
  # Fake data
202
367
  if self.fake_data:
203
- f1 = gen.generate_string(
204
- charset=Charset.DEFAULT,
205
- size=len(input_str),
206
- times=1,
207
- string_type_if_1=True,
208
- ) # Generate fake data with len of original data
209
- f2 = Text(f1).divide_with_variable(
210
- self.split_every_length, self._splited_variable_length
211
- )
212
- output.extend(f2[:-1])
213
-
214
- # Random data
215
- bait_lst = gen.generate_string(
216
- charset=Charset.ALPHABET, size=self._splited_variable_length, times=25
217
- )
218
- for x in bait_lst:
219
- output.append(
220
- f"{x}='{gen.generate_string(charset=Charset.DEFAULT, size=self.split_every_length, times=1, string_type_if_1=True)}'"
221
- )
222
-
223
- random_eval_text = str(random.randint(1, 100))
224
- for _ in range(random.randint(10, 50)):
225
- random_eval_text += f"+{random.randint(1, 100)}"
226
- random_eval_text_final = Text(random_eval_text).to_hex()
227
- output.append(f"eval('{random_eval_text_final}')")
368
+ output.extend(self._make_fake_output(len(input_str)))
228
369
 
229
370
  logger.debug("Code obfuscated.")
230
371
  return output
@@ -233,16 +374,9 @@ class Obfuscator:
233
374
  """
234
375
  Obfuscate code
235
376
 
236
- :returns: Obfuscated code
237
- :rtype: str
377
+ Returns
378
+ -------
379
+ str
380
+ Obfuscated code
238
381
  """
239
- return "\n".join(self._obfuscate_out())
240
-
241
-
242
- # Run
243
- ###########################################################################
244
- if __name__ == "__main__":
245
- logger.setLevel(10)
246
- code = "print('Hello World')"
247
- test = Obfuscator(code, fake_data=True)
248
- print(test.obfuscate())
382
+ return "\n".join(self._make_obfuscate_output())
@@ -3,34 +3,36 @@ Absfuyu: Passwordlib
3
3
  --------------------
4
4
  Password library
5
5
 
6
- Version: 1.0.0
7
- Date updated: 11/02/2025 (dd/mm/yyyy)
6
+ Version: 5.0.0
7
+ Date updated: 22/02/2025 (dd/mm/yyyy)
8
8
  """
9
9
 
10
10
  # Module level
11
- ###########################################################################
12
- __all__ = ["PasswordGenerator"]
11
+ # ---------------------------------------------------------------------------
12
+ __all__ = ["PasswordGenerator", "TOTP"]
13
13
 
14
14
 
15
15
  # Library
16
- ###########################################################################
16
+ # ---------------------------------------------------------------------------
17
17
  import hashlib
18
- import lzma
19
18
  import os
20
19
  import random
21
20
  import re
22
- from typing import NamedTuple, Optional
21
+ from typing import ClassVar, Literal, NamedTuple
22
+ from urllib.parse import quote, urlencode
23
23
 
24
- from absfuyu.general.data_extension import DictExt, Text
25
- from absfuyu.general.generator import Charset, Generator
24
+ from absfuyu.core import BaseClass, deprecated, versionadded
25
+ from absfuyu.dxt import DictExt, Text
26
26
  from absfuyu.logger import logger
27
- from absfuyu.pkg_data import DataList
27
+ from absfuyu.pkg_data import DataList, DataLoader
28
+ from absfuyu.tools.generator import Charset, Generator
28
29
  from absfuyu.util import set_min
29
- from absfuyu.util.pkl import Pickler
30
30
 
31
31
 
32
32
  # Function
33
- ###########################################################################
33
+ # ---------------------------------------------------------------------------
34
+ @deprecated("5.0.0")
35
+ @versionadded("4.2.0")
34
36
  def _password_check(password: str) -> bool:
35
37
  """
36
38
  Verify the strength of ``password``.
@@ -84,21 +86,19 @@ def _password_check(password: str) -> bool:
84
86
 
85
87
 
86
88
  # Class
87
- ###########################################################################
89
+ # ---------------------------------------------------------------------------
88
90
  class PasswordHash(NamedTuple):
89
91
  salt: bytes
90
92
  key: bytes
91
93
 
92
94
 
93
- class PasswordGenerator:
95
+ @versionadded("4.2.0")
96
+ class PasswordGenerator(BaseClass):
94
97
  """Password Generator"""
95
98
 
96
99
  def __str__(self) -> str:
97
100
  return f"{self.__class__.__name__}()"
98
101
 
99
- def __repr__(self) -> str:
100
- return self.__str__()
101
-
102
102
  @staticmethod
103
103
  def password_hash(password: str) -> PasswordHash:
104
104
  """
@@ -111,15 +111,12 @@ class PasswordGenerator:
111
111
  salt=salt,
112
112
  iterations=100000,
113
113
  )
114
- # out = {
115
- # "salt": salt,
116
- # "key": key,
117
- # }
118
114
  out = PasswordHash(salt, key)
119
115
  return out
120
116
 
121
117
  @staticmethod
122
118
  def password_check(password: str) -> dict:
119
+ """Check password's characteristic"""
123
120
  data = Text(password).analyze()
124
121
  data = DictExt(data).apply(lambda x: True if x > 0 else False) # type: ignore
125
122
  data.__setitem__("length", len(password))
@@ -175,7 +172,7 @@ class PasswordGenerator:
175
172
  check += 1
176
173
 
177
174
  if include_special:
178
- charset += r"[ !#$%&'()*+,-./[\\\]^_`{|}~" + r'"]'
175
+ charset += r"[!#$%&'()*+,-./]^_`{|}~\""
179
176
  check += 1
180
177
 
181
178
  while True:
@@ -196,7 +193,7 @@ class PasswordGenerator:
196
193
  @staticmethod
197
194
  def generate_passphrase(
198
195
  num_of_blocks: int = 5,
199
- block_divider: Optional[str] = None,
196
+ block_divider: str | None = None,
200
197
  first_letter_cap: bool = True,
201
198
  include_number: bool = True,
202
199
  *,
@@ -234,12 +231,12 @@ class PasswordGenerator:
234
231
  Myomectomies7-Sully4-Torpedomen7-Netful2-Begaud8
235
232
  """
236
233
  words: list[str] = (
237
- (lzma.decompress(Pickler.load(DataList.PASSWORDLIB)).decode().split(",")) # type: ignore
234
+ DataLoader(DataList.PASSWORDLIB).load().decode().split(",")
238
235
  if not custom_word_list
239
236
  else custom_word_list
240
237
  )
241
238
 
242
- if not block_divider:
239
+ if block_divider is None:
243
240
  block_divider = "-"
244
241
 
245
242
  dat = [random.choice(words) for _ in range(num_of_blocks)]
@@ -248,13 +245,86 @@ class PasswordGenerator:
248
245
  dat = list(map(lambda x: x.title(), dat))
249
246
 
250
247
  if include_number:
251
- idx = random.choice(range(5))
248
+ idx = random.choice(range(num_of_blocks))
252
249
  dat[idx] += str(random.choice(range(10)))
253
250
 
254
251
  return block_divider.join(dat)
255
252
 
256
253
 
257
- # Run
258
- ###########################################################################
259
- if __name__ == "__main__":
260
- logger.setLevel(10)
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}"