flagonfly 0.0.1__tar.gz

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.
@@ -0,0 +1,90 @@
1
+ Metadata-Version: 2.3
2
+ Name: flagonfly
3
+ Version: 0.0.1
4
+ Summary: Add your description here
5
+ Author: aidnem
6
+ Author-email: aidnem <aidnem@noreply.codeberg.org>
7
+ Requires-Dist: bitstring>=4.4.0
8
+ Requires-Dist: click>=8.4.1
9
+ Requires-Python: >=3.13
10
+ Description-Content-Type: text/markdown
11
+
12
+ # flagonfly
13
+
14
+ The ultimate CTF tool. Designed to combat the staggering lack of cross-platform CTF tools. Named after the ultimate hunter, the dragonfly.
15
+
16
+ ## Installation
17
+
18
+ For now, flagonfly is not published to any package repositories. To install it, use `pip` or `uv`:
19
+
20
+ 1. First, clone the repository somewhere convenient. Change directory into the project:
21
+
22
+ ```sh
23
+ git clone https://codeberg.org/aidnem/flagonfly.git
24
+ cd flagonfly
25
+ ```
26
+
27
+ 2. Install the package using your package manager of choice.
28
+
29
+ It's preferable to use a package manager that will install the project in its own isolated environment, such as `uv tool install` or `pipx install`.
30
+
31
+ Install the project in editable mode (use the `-e` flag) so that it can be updated by running `git pull` at a later time:
32
+
33
+ ```sh
34
+ uv tool install -e .
35
+ ```
36
+
37
+ Alternatively, use pipx:
38
+
39
+ ```sh
40
+ pipx install -e .
41
+ ```
42
+
43
+ Finally, if you prefer pip, you can use that instead. Keep in mind that this will install the dependencies to your global pip environment:
44
+
45
+ ```sh
46
+ pip install -e .
47
+ ```
48
+
49
+ Installing using any of these 3 methods will add 3 executables to your python script path:
50
+
51
+ - `flagonfly` - The flagonfly CLI. This is how all CLI tools can be used.
52
+ - `flf` - A convenient alias for `flagonfly`. Because the command is intended to be deeply composed (e.g. `cat data | flf encode binary | rev | flf decode binary`), a shorter alias is useful in saving time composing commands.
53
+ - `flagonfly-gui` - Launches the flagonfly GUI, a tkinter app that is intended to provide some of the functionality of the flagonfly library in a graphical form.
54
+
55
+ ## Core Library
56
+
57
+ The flagonfly project is separated into core, UI, and CLI projects so that the central functionality of the can be used from other projects. Please note that, during early development, doc comments will likely be sparse. To use the functionality of flagonfly in other projects, look into importing necessary functions and packages from `flagonfly.core`.
58
+
59
+ ## CLI: Subcommands
60
+
61
+ ### Help
62
+
63
+ Flagonfly contains detailed help messages for commands and their parameters wherever possible. Use the `--help` flag on any command to see detailed descriptions of the arguments.
64
+
65
+ This is important because this README will likely be updated less often and less thoroughly than the help messages themselves.
66
+
67
+ ### Encode/decode
68
+
69
+ Use `flagonfly encode` or `flagonfly decode` followed by a format to convert data to different formats.
70
+
71
+ For example, `echo "Hello, world!" | flagonfly encode binary` will encode the ASCII text into binary and output it as a string of "1"s and "0"s. The `decode` command does the reverse.
72
+
73
+ If an encoding isn't specified when decoding, flagonfly will attempt to guess at what encoding was used. It does this by attempting to decode using various encodings in order until one succeeds. This is not foolproof; if you data was encoded in a larger format but using only certain digits (e.g. data encoded as hex that only uses 1s and 0s), it will decode incorrectly. However, it's good enough as a first try for decoding data quickly.
74
+
75
+ #### Words and separators
76
+
77
+ By default, the output of `encode` is divided into words, separated by a separator string. There are sensible defaults for all data types, and the separator/word length parameters are ignored when encoding to base64/32/16.
78
+
79
+ To change the separator, pass a string to the `-s`/`--separator` option. To remove the separator, pass an empty string: `flagonfly encode -s "" ...`.
80
+
81
+ To change the word size, use the `-w` or `--word-length` option.
82
+
83
+ #### Prefixes
84
+ The `-p`/`--prefix` option can be used to append a prefix (e.g. `0b` for binary data). When using no separator, this prefix is appended to the beginning of the output. When using a separator between words, it will be appended to the beginning of each word.
85
+
86
+ There is no prefix for base64/32/16 data.
87
+
88
+ ## CI Usage
89
+
90
+ The flagonfly project will hopefully leverage Codeberg's hosted woodpecker instance for continuous integration. Its needs are fairly bare-bones: running unit tests on all PRs to main, and running an automatic publish workflow to publish the project to PyPI automatically whenever a new release is tagged. The processing, memory, and storage requirements of these workflows should be minimal, as the project will hopefully remain quite small.
@@ -0,0 +1,79 @@
1
+ # flagonfly
2
+
3
+ The ultimate CTF tool. Designed to combat the staggering lack of cross-platform CTF tools. Named after the ultimate hunter, the dragonfly.
4
+
5
+ ## Installation
6
+
7
+ For now, flagonfly is not published to any package repositories. To install it, use `pip` or `uv`:
8
+
9
+ 1. First, clone the repository somewhere convenient. Change directory into the project:
10
+
11
+ ```sh
12
+ git clone https://codeberg.org/aidnem/flagonfly.git
13
+ cd flagonfly
14
+ ```
15
+
16
+ 2. Install the package using your package manager of choice.
17
+
18
+ It's preferable to use a package manager that will install the project in its own isolated environment, such as `uv tool install` or `pipx install`.
19
+
20
+ Install the project in editable mode (use the `-e` flag) so that it can be updated by running `git pull` at a later time:
21
+
22
+ ```sh
23
+ uv tool install -e .
24
+ ```
25
+
26
+ Alternatively, use pipx:
27
+
28
+ ```sh
29
+ pipx install -e .
30
+ ```
31
+
32
+ Finally, if you prefer pip, you can use that instead. Keep in mind that this will install the dependencies to your global pip environment:
33
+
34
+ ```sh
35
+ pip install -e .
36
+ ```
37
+
38
+ Installing using any of these 3 methods will add 3 executables to your python script path:
39
+
40
+ - `flagonfly` - The flagonfly CLI. This is how all CLI tools can be used.
41
+ - `flf` - A convenient alias for `flagonfly`. Because the command is intended to be deeply composed (e.g. `cat data | flf encode binary | rev | flf decode binary`), a shorter alias is useful in saving time composing commands.
42
+ - `flagonfly-gui` - Launches the flagonfly GUI, a tkinter app that is intended to provide some of the functionality of the flagonfly library in a graphical form.
43
+
44
+ ## Core Library
45
+
46
+ The flagonfly project is separated into core, UI, and CLI projects so that the central functionality of the can be used from other projects. Please note that, during early development, doc comments will likely be sparse. To use the functionality of flagonfly in other projects, look into importing necessary functions and packages from `flagonfly.core`.
47
+
48
+ ## CLI: Subcommands
49
+
50
+ ### Help
51
+
52
+ Flagonfly contains detailed help messages for commands and their parameters wherever possible. Use the `--help` flag on any command to see detailed descriptions of the arguments.
53
+
54
+ This is important because this README will likely be updated less often and less thoroughly than the help messages themselves.
55
+
56
+ ### Encode/decode
57
+
58
+ Use `flagonfly encode` or `flagonfly decode` followed by a format to convert data to different formats.
59
+
60
+ For example, `echo "Hello, world!" | flagonfly encode binary` will encode the ASCII text into binary and output it as a string of "1"s and "0"s. The `decode` command does the reverse.
61
+
62
+ If an encoding isn't specified when decoding, flagonfly will attempt to guess at what encoding was used. It does this by attempting to decode using various encodings in order until one succeeds. This is not foolproof; if you data was encoded in a larger format but using only certain digits (e.g. data encoded as hex that only uses 1s and 0s), it will decode incorrectly. However, it's good enough as a first try for decoding data quickly.
63
+
64
+ #### Words and separators
65
+
66
+ By default, the output of `encode` is divided into words, separated by a separator string. There are sensible defaults for all data types, and the separator/word length parameters are ignored when encoding to base64/32/16.
67
+
68
+ To change the separator, pass a string to the `-s`/`--separator` option. To remove the separator, pass an empty string: `flagonfly encode -s "" ...`.
69
+
70
+ To change the word size, use the `-w` or `--word-length` option.
71
+
72
+ #### Prefixes
73
+ The `-p`/`--prefix` option can be used to append a prefix (e.g. `0b` for binary data). When using no separator, this prefix is appended to the beginning of the output. When using a separator between words, it will be appended to the beginning of each word.
74
+
75
+ There is no prefix for base64/32/16 data.
76
+
77
+ ## CI Usage
78
+
79
+ The flagonfly project will hopefully leverage Codeberg's hosted woodpecker instance for continuous integration. Its needs are fairly bare-bones: running unit tests on all PRs to main, and running an automatic publish workflow to publish the project to PyPI automatically whenever a new release is tagged. The processing, memory, and storage requirements of these workflows should be minimal, as the project will hopefully remain quite small.
@@ -0,0 +1,31 @@
1
+ [project]
2
+ name = "flagonfly"
3
+ version = "0.0.1"
4
+ description = "Add your description here"
5
+ readme = "README.md"
6
+ authors = [{ name = "aidnem", email = "aidnem@noreply.codeberg.org" }]
7
+ requires-python = ">=3.13"
8
+ dependencies = ["bitstring>=4.4.0", "click>=8.4.1"]
9
+
10
+ [project.scripts]
11
+ flagonfly = "flagonfly.cli:main"
12
+ flf = "flagonfly.cli:main"
13
+
14
+ [project.gui-scripts]
15
+ flagonfly-gui = "flagonfly.gui:main"
16
+
17
+ [build-system]
18
+ requires = ["uv_build>=0.11.21,<0.12.0"]
19
+ build-backend = "uv_build"
20
+
21
+ [dependency-groups]
22
+ dev = [
23
+ "mypy>=2.1.0",
24
+ "pytest>=9.1.1",
25
+ ]
26
+
27
+ [[tool.uv.index]]
28
+ name = "testpypi"
29
+ url = "https://test.pypi.org/simple/"
30
+ publish-url = "https://test.pypi.org/legacy/"
31
+ explicit = true
@@ -0,0 +1 @@
1
+ __version__ = "0.1.0"
@@ -0,0 +1,4 @@
1
+ from flagonfly.cli import main
2
+
3
+ if __name__ == "__main__":
4
+ main()
@@ -0,0 +1,11 @@
1
+ import click
2
+ from flagonfly.commands.codec_commands import encode, decode
3
+
4
+
5
+ @click.group()
6
+ def main():
7
+ """flagonfly CLI toolkit"""
8
+
9
+
10
+ main.add_command(encode)
11
+ main.add_command(decode)
@@ -0,0 +1,54 @@
1
+ """flagonfly CLI completion utility, for automatically picking an option based on a partial string"""
2
+
3
+ from typing import Iterable
4
+
5
+ import click
6
+ import sys
7
+
8
+
9
+ def complete_one_strict(inp: str, options: Iterable[str]) -> str:
10
+ """
11
+ Exactly like complete_one, but exits instead of returning None if a match isn't found.
12
+ """
13
+
14
+ result = complete_one(inp, options)
15
+
16
+ if result is None:
17
+ click.echo(
18
+ f"[ERROR] Input '{inp}' did not match any of {options}.",
19
+ file=sys.stderr,
20
+ )
21
+
22
+ sys.exit(1)
23
+
24
+ return result
25
+
26
+
27
+ def complete_one(inp: str, options: Iterable[str]) -> str | None:
28
+ """
29
+ Given an input string and an Iterable of options, pick the first option that starts with the input string.
30
+
31
+ If the input is an exact match, returns that immediately.
32
+
33
+ Prints a warning if multiple options are available.
34
+
35
+ If no matches are found, returns None.
36
+ """
37
+
38
+ if inp in options:
39
+ return inp
40
+
41
+ filtered_options = list(filter(lambda option: option.startswith(inp), options))
42
+
43
+ if len(filtered_options) == 0:
44
+ return None
45
+
46
+ selected_option = filtered_options[0]
47
+
48
+ if len(filtered_options) != 1:
49
+ click.echo(
50
+ f"[WARNING] Ambigious input '{inp}' matches multiple options ({filtered_options}), so first option was selected: '{selected_option}'.",
51
+ file=sys.stderr,
52
+ )
53
+
54
+ return selected_option
@@ -0,0 +1,4 @@
1
+ """flagonfly's reusable help messages for common parameters that will be used between multiple subcommands"""
2
+
3
+ INPUT_FILE_HELP = "File to read for input. Defaults to `-`, meaning stdin."
4
+ OUTPUT_FILE_HELP = "File where output will be written. Defaults to `-`, meaning stdout."
File without changes
@@ -0,0 +1,233 @@
1
+ """
2
+ flagonfly codex tools CLI bindings
3
+ """
4
+
5
+ import base64
6
+ import sys
7
+ from dataclasses import dataclass
8
+ from enum import StrEnum
9
+ from typing import IO, Callable
10
+
11
+ import click
12
+
13
+ from flagonfly.cli_message_constants import INPUT_FILE_HELP, OUTPUT_FILE_HELP
14
+ from flagonfly.core import codec
15
+
16
+
17
+ @dataclass(frozen=True)
18
+ class Codec:
19
+ encode_func: Callable[[bytes, bool, str, int], str]
20
+ decode_func: Callable[[str, str], bytes | None]
21
+ default_word_length: int
22
+
23
+
24
+ class Encoding(StrEnum):
25
+ BINARY = "binary"
26
+ HEX = "hex"
27
+ BASE64 = "b64"
28
+ URLSAFE_BASE64 = "b64url"
29
+ BASE32 = "b32"
30
+ BASE16 = "b16"
31
+ GUESS = "guess"
32
+
33
+
34
+ def guess_encode_wrapper(
35
+ data: bytes, prefix: bool, separator: str, word_length: int
36
+ ) -> str:
37
+ click.echo(
38
+ "[WARN] Best-guess is not supported while encoding; using binary instead",
39
+ file=sys.stderr,
40
+ )
41
+
42
+ return codec.binary_encode(data, prefix, separator, word_length)
43
+
44
+
45
+ def wrap_encode_func_ignoring_separator(
46
+ encode_func: Callable[[bytes], str],
47
+ ) -> Callable[[bytes, bool, str, int], str]:
48
+ """
49
+ Given an encoding function that takes bytes and returns a string, wrap it in a function that accepts but ignores all other encoding arguments.
50
+
51
+ This function exists to provide an easy way to make base64 functions fit the interface required for CLI commands.
52
+ """
53
+ return lambda data, _prefix, _separator, _word_length: encode_func(data)
54
+
55
+
56
+ def wrap_b64encode(
57
+ encode_func: Callable[[bytes], bytes],
58
+ ) -> Callable[[bytes, bool, str, int], str]:
59
+ return wrap_encode_func_ignoring_separator(lambda data: encode_func(data).decode())
60
+
61
+
62
+ def wrap_b64decode(
63
+ decode_func: Callable[[str], bytes],
64
+ ) -> Callable[[str, str], bytes]:
65
+ """
66
+ Given a ecoding function that takes a string and returns bytes, wrap it in a function that accepts but ignores all other decoding arguments.
67
+
68
+ This function exists to provide an easy way to make base64 functions fit the interface required for CLI commands.
69
+ """
70
+ return lambda data, _: decode_func(data)
71
+
72
+
73
+ ENCODINGS: dict[Encoding, Codec] = {
74
+ Encoding.BINARY: Codec(codec.binary_encode, codec.binary_decode, 8),
75
+ Encoding.HEX: Codec(codec.hex_encode, codec.hex_decode, 2),
76
+ Encoding.BASE64: Codec(
77
+ wrap_b64encode(base64.b64encode),
78
+ wrap_b64decode(lambda data: base64.b64decode(data)),
79
+ 1,
80
+ ),
81
+ Encoding.URLSAFE_BASE64: Codec(
82
+ wrap_b64encode(base64.urlsafe_b64encode),
83
+ wrap_b64decode(lambda data: base64.urlsafe_b64decode(data)),
84
+ 1,
85
+ ),
86
+ Encoding.BASE32: Codec(
87
+ wrap_b64encode(base64.b32encode),
88
+ wrap_b64decode(lambda data: base64.b32decode(data)),
89
+ 1,
90
+ ),
91
+ Encoding.BASE16: Codec(
92
+ wrap_b64encode(base64.b16encode),
93
+ wrap_b64decode(lambda data: base64.b16decode(data)),
94
+ 1,
95
+ ),
96
+ Encoding.GUESS: Codec(guess_encode_wrapper, codec.guess_decode, 8),
97
+ }
98
+
99
+ SEPARATOR_HELP = "Separator placed between words (ignored in base64/32/16). Defaults to a single space."
100
+ INVALID_ENCODING_MSG = f"[ERROR] Invalid encoding chosen. Please choose one of {[e.value for e in Encoding]}."
101
+
102
+
103
+ @click.command()
104
+ @click.argument(
105
+ "encoding",
106
+ type=click.Choice(
107
+ [e.value for e in Encoding],
108
+ case_sensitive=False,
109
+ ),
110
+ default=Encoding.BINARY,
111
+ )
112
+ @click.option(
113
+ "-i",
114
+ "--input",
115
+ type=click.File("rb"),
116
+ default="-",
117
+ help=INPUT_FILE_HELP,
118
+ )
119
+ @click.option(
120
+ "-t",
121
+ "--trim",
122
+ is_flag=True,
123
+ help="Trim trailing newlines from input (helpful on Windows where `\\r\\n` is appended to echoed text)",
124
+ )
125
+ @click.option(
126
+ "-p",
127
+ "--prefix",
128
+ is_flag=True,
129
+ help="Include an integer literal prefix on the output (e.g. 0b for binary)",
130
+ )
131
+ @click.option(
132
+ "-s",
133
+ "--separator",
134
+ type=click.STRING,
135
+ default=" ",
136
+ help=SEPARATOR_HELP,
137
+ )
138
+ @click.option(
139
+ "-w",
140
+ "--word-length",
141
+ "word_length",
142
+ type=click.INT,
143
+ help="Defaults: 8 for binary, 2 for hex, 3 for octal",
144
+ )
145
+ @click.option(
146
+ "-o",
147
+ "--output",
148
+ type=click.File("wb"),
149
+ default="-",
150
+ help=OUTPUT_FILE_HELP,
151
+ )
152
+ def encode(
153
+ encoding: Encoding,
154
+ input: IO[bytes],
155
+ trim: bool,
156
+ prefix: bool,
157
+ separator: str,
158
+ word_length: int | None,
159
+ output: IO[bytes],
160
+ ) -> None:
161
+ """
162
+ Encode binary input as a string
163
+
164
+ \b
165
+ Notes:
166
+ -w/--word-length splits output into N-digit words (default 8 for binary, 2 for hex, 3 for octal)
167
+ If -s/--separator is empty, words are concatenated with no delimiter
168
+ -p/--prefix prepends the prefix to each word individually, unless an empty string is passed as the separator, in which case the entire output is treated as one word and the prefix is prepended once at the beginning.
169
+ Last word may be shorter than word-length if data isn't a round multiple.
170
+ Separator and word length are ignored for base64, as base64 is generated as one continuous string.
171
+ Octal data is padded to [word-length]-digit words unless -s/--separator is empty.
172
+ """
173
+ # TODO: Figure out how to use complete_one here...
174
+
175
+ try:
176
+ c = ENCODINGS[encoding]
177
+ except KeyError:
178
+ click.echo(INVALID_ENCODING_MSG, file=sys.stderr)
179
+ sys.exit(1)
180
+ word_length = word_length if word_length is not None else c.default_word_length
181
+ encode_func = c.encode_func
182
+
183
+ output.write(
184
+ encode_func(
185
+ input.read().rstrip(b"\r\n"), prefix, separator, word_length
186
+ ).encode()
187
+ )
188
+
189
+
190
+ @click.command()
191
+ @click.argument(
192
+ "encoding",
193
+ type=click.Choice(
194
+ [e.value for e in Encoding],
195
+ case_sensitive=False,
196
+ ),
197
+ default=Encoding.GUESS,
198
+ )
199
+ @click.option("-i", "--input", type=click.File("rb"), default="-", help=INPUT_FILE_HELP)
200
+ @click.option("-s", "--separator", type=click.STRING, default=" ", help=SEPARATOR_HELP)
201
+ @click.option(
202
+ "-o", "--output", type=click.File("wb"), default="-", help=OUTPUT_FILE_HELP
203
+ )
204
+ def decode(
205
+ encoding: Encoding, input: IO[bytes], separator: str, output: IO[bytes]
206
+ ) -> None:
207
+ """
208
+ Given an encoded string as input, convert to binary data
209
+
210
+ \b
211
+ Notes:
212
+ If input data type is unknown, don't specify an encoding (or use `guess`, the default) to automatically determine what encoding was used.
213
+ Because binary, hex, and base64/32/16 share characters, `guess` will misidentify ambiguous input (e.g. hex containing only 1s and 0s). Always prefer explicitly stating an encoding when possible, especially if the results from `guess` don't make sense.
214
+ If data can't be decoded, the original input will be output instead.
215
+ The separator is automatically removed from the input data, but if it is not found it serves as a noop. However, if words are individually prefixed, the
216
+ separator must be specified in order to detect and remove word prefixes.
217
+ """
218
+ try:
219
+ decode_func = ENCODINGS[encoding].decode_func
220
+ except KeyError:
221
+ click.echo(INVALID_ENCODING_MSG, file=sys.stdout)
222
+ sys.exit(1)
223
+
224
+ input_data = input.read()
225
+ result = decode_func(input_data.decode().strip(), separator)
226
+
227
+ if result is not None:
228
+ output.write(result)
229
+ else:
230
+ click.echo(
231
+ "[ERROR] Failed to decode text. Mirroring input instead.", file=sys.stderr
232
+ )
233
+ output.write(input_data)
File without changes
@@ -0,0 +1,119 @@
1
+ """
2
+ flagonfly tools for changing data codec.
3
+
4
+ Supports encoding and decoding of:
5
+ - binary
6
+ - hex
7
+ - base64/32/16 (in `guess` decoding only)
8
+ """
9
+
10
+ import base64
11
+ import binascii
12
+ from typing import Callable, TypeAlias
13
+
14
+
15
+ def binary_encode(
16
+ data: bytes, prefix: bool = False, separator: str = " ", word_length: int = 8
17
+ ) -> str:
18
+ # Get all bits as a string of 1s and 0s
19
+ bits: str = "".join(f"{byte:08b}" for byte in data)
20
+ # Chunk into words and separate by separator
21
+ # Only prefix every word if prefixing is enabled and there is a separator
22
+ word_prefix = "0b" if prefix and separator != "" else ""
23
+ words = [
24
+ word_prefix + bits[i : i + word_length]
25
+ for i in range(0, len(bits), word_length)
26
+ ]
27
+
28
+ # Only prefix entire string if prefixing is enabled and there is no separator
29
+ prefix_str = "0b" if prefix and separator == "" else ""
30
+ return prefix_str + separator.join(words)
31
+
32
+
33
+ def binary_decode(data: str, separator: str = " ") -> bytes | None:
34
+ """Decode a string of 1s and 0s, optionally prefixed with 0b, optionally separated by `separator`. If the string doesn't
35
+ contain `separator`, it will be ignored."""
36
+ if separator:
37
+ words = [w.removeprefix("0b") for w in data.split(separator)]
38
+ data = "".join(words)
39
+ else:
40
+ data = data.removeprefix("0b")
41
+ # Convert each byte to an int
42
+ try:
43
+ byte_list = [int(data[i : i + 8], 2) for i in range(0, len(data), 8)]
44
+ except ValueError:
45
+ return None
46
+
47
+ # Convert list of ints to a bytes object
48
+ return bytes(byte_list)
49
+
50
+
51
+ def hex_encode(
52
+ data: bytes, prefix: bool = False, separator: str = " ", word_length: int = 2
53
+ ) -> str:
54
+ hex_str = data.hex()
55
+ word_prefix = "0x" if prefix and separator != "" else ""
56
+
57
+ words = [
58
+ word_prefix + hex_str[i : i + word_length]
59
+ for i in range(0, len(hex_str), word_length)
60
+ ]
61
+
62
+ # Only prefix entire string if prefixing is enabled and there is no separator
63
+ prefix_str = "0x" if prefix and separator == "" else ""
64
+ return prefix_str + separator.join(words)
65
+
66
+
67
+ def hex_decode(data: str, separator: str = " ") -> bytes | None:
68
+ """Decode a string of hexadecimal data, optionally prefixed with 0x, optionally separated by `separator`. If the string doesn't
69
+ contain `separator`, it will be ignored."""
70
+ if separator:
71
+ words = [w.removeprefix("0x") for w in data.split(separator)]
72
+ data = "".join(words)
73
+ else:
74
+ data = data.removeprefix("0x")
75
+ # Remove separator
76
+ data = data.replace(separator, "")
77
+ # Convert each byte to an int
78
+ # Convert list of ints to a bytes object
79
+ try:
80
+ return bytes.fromhex(data)
81
+ except ValueError:
82
+ return None
83
+
84
+
85
+ DecodeFunc: TypeAlias = Callable[[str, str], bytes | None]
86
+
87
+
88
+ _B64_DECODE_FUNCS = (
89
+ base64.b16decode,
90
+ base64.b32decode,
91
+ base64.b64decode,
92
+ base64.urlsafe_b64decode,
93
+ )
94
+
95
+
96
+ def get_guess_b64_decoded_data(data_with_separator_removed: str) -> bytes | None:
97
+ for f in _B64_DECODE_FUNCS:
98
+ try:
99
+ data = f(data_with_separator_removed)
100
+ return data
101
+ except binascii.Error:
102
+ pass
103
+
104
+ return None
105
+
106
+
107
+ _GUESS_DECODE_FUNCS = (
108
+ binary_decode,
109
+ hex_decode,
110
+ lambda data, _sep: get_guess_b64_decoded_data(data),
111
+ )
112
+
113
+
114
+ def guess_decode(data: str, separator: str = " ") -> bytes | None:
115
+ for func in _GUESS_DECODE_FUNCS:
116
+ if (d := func(data, separator)) is not None:
117
+ return d
118
+
119
+ return None
@@ -0,0 +1,11 @@
1
+ import tkinter as tk
2
+
3
+
4
+ def main():
5
+ root = tk.Tk()
6
+ root.title("flagonfly")
7
+
8
+ tk.Label(root, text="flagonfly").pack()
9
+ tk.Label(root, text="Functionality coming soon...").pack()
10
+
11
+ root.mainloop()