xdragxt commited on
Commit
3791693
·
verified ·
1 Parent(s): abf8967

Update plugins/devtools.py

Browse files
Files changed (1) hide show
  1. plugins/devtools.py +98 -145
plugins/devtools.py CHANGED
@@ -2,7 +2,7 @@
2
  # Copyright (C) 2021-2025 TeamUltroid
3
  #
4
  # This file is a part of < https://github.com/TeamUltroid/Ultroid/ >
5
- # Please read the GNU Affero General Public License in
6
  # <https://www.github.com/TeamUltroid/Ultroid/blob/main/LICENSE/>.
7
 
8
  from . import get_help
@@ -12,130 +12,40 @@ __doc__ = get_help("help_devtools")
12
  import inspect
13
  import sys
14
  import traceback
15
- import time
16
- import json
17
  from io import BytesIO, StringIO
18
  from os import remove
19
  from pprint import pprint
20
- from random import choice
21
- from html import escape
22
 
23
  from telethon.utils import get_display_name
24
- from telethon.tl import functions
25
 
26
- # project imports (assumed available in Ultroid context)
27
  from pyUltroid import _ignore_eval
28
- from . import * # noqa: F401,F403
29
- from . import upload_file as uf # upload helper used to upload Carbon images
30
 
31
- # Optional imports: keep original fallbacks
 
 
32
  try:
33
  import black
34
- except Exception:
35
  black = None
 
36
 
37
  try:
38
  from yaml import safe_load
39
- except Exception:
40
- from pyUltroid.fns.tools import safe_load # fallback from project
41
-
42
- fn = functions
43
-
44
- pp = pprint # alias kept from original
45
- bot = ultroid = ultroid_bot # ensure these names exist in project
46
 
47
- class u:
48
- _ = ""
49
-
50
- # ---------------------------------------------------------------------
51
- # Helper: parse eval-friendly representation of values
52
- # (kept mostly as original)
53
- def _parse_eval(value=None):
54
- if not value:
55
- return value
56
- if hasattr(value, "stringify"):
57
- try:
58
- return value.stringify()
59
- except TypeError:
60
- pass
61
- elif isinstance(value, dict):
62
- try:
63
- return json.dumps(value, indent=1, default=str)
64
- except BaseException:
65
- pass
66
- elif isinstance(value, list):
67
- newlist = "["
68
- for index, child in enumerate(value):
69
- newlist += "\n " + str(_parse_eval(child))
70
- if index < len(value) - 1:
71
- newlist += ","
72
- newlist += "\n]"
73
- return newlist
74
- return str(value)
75
-
76
- # ---------------------------------------------------------------------
77
- # Exec wrapper: runs user-supplied async code in custom namespace.
78
- # WARNING: This runs arbitrary code via exec() — ensure only trusted users can use it.
79
- async def aexec(code: str, event):
80
- """
81
- Execute `code` (containing Python source for the body of an async function)
82
- as an async function __aexec(e, client) and run it.
83
-
84
- Returns whatever the executed function returns.
85
-
86
- NOTE: This function intentionally exposes a number of objects to the executed
87
- namespace (client, event, reply, etc.). This is powerful and dangerous — keep
88
- access restricted (only owner/devs).
89
- """
90
- # Precompute reply message (do this before creating exec_globals)
91
- try:
92
- reply_msg = await event.get_reply_message()
93
- except Exception:
94
- reply_msg = None
95
-
96
- # Dedicated namespace for execution. Keep builtins as in original, but this is
97
- # a security risk — consider limiting builtins for untrusted usage.
98
- exec_globals = {
99
- "print": _stringify,
100
- "p": _stringify,
101
- "message": event,
102
- "event": event,
103
- "client": event.client,
104
- "reply": reply_msg,
105
- "chat": event.chat_id,
106
- "u": u,
107
- "__builtins__": __builtins__,
108
- "__name__": __name__,
109
- }
110
 
111
- # Wrap the provided code inside an async def so we can await it.
112
- # We indent every line of `code` and place it under the function body.
113
- wrapped_lines = []
114
- for line in code.splitlines():
115
- # Ensure even empty lines are indented properly
116
- if line.strip() == "":
117
- wrapped_lines.append(" pass")
118
- else:
119
- wrapped_lines.append(" " + line)
120
- wrapped_code = "async def __aexec(e, client):\n" + "\n".join(wrapped_lines)
121
 
122
- try:
123
- # Execute the wrapper to create __aexec in exec_globals
124
- exec(wrapped_code, exec_globals)
125
- func = exec_globals["__aexec"]
126
- # Call the newly created coroutine function
127
- return await func(event, event.client)
128
- except Exception as exc:
129
- # Re-raise with clearer context (caller will handle formatting)
130
- raise Exception(f"Failed to execute code: {str(exc)}")
131
 
132
- # ---------------------------------------------------------------------
133
- # Commands (kept behaviorally similar to original with blockquote HTML wrapping)
134
- @ultroid_cmd(pattern="sysinfo$")
135
  async def _(e):
136
  xx = await e.eor(get_string("com_1"))
137
- # run neofetch and strip ANSI colours; results appended to neo.txt
138
- x, y = await bash("neofetch|sed 's/\\x1B\\[[0-9;\\?]*[a-zA-Z]//g' >> neo.txt")
139
  if y and y.endswith("NOT_FOUND"):
140
  return await xx.edit(f"Error: `{y}`")
141
  with open("neo.txt", "r", encoding="utf-8") as neo:
@@ -148,6 +58,7 @@ async def _(e):
148
  await xx.delete()
149
  remove("neo.txt")
150
 
 
151
  @ultroid_cmd(pattern="bash", fullsudo=True, only_devs=True)
152
  async def _(event):
153
  carb, rayso, yamlf = None, None, False
@@ -161,7 +72,6 @@ async def _(event):
161
  rayso = True
162
  except IndexError:
163
  return await event.eor(get_string("devs_1"), time=10)
164
-
165
  xx = await event.eor(get_string("com_1"))
166
  reply_to_id = event.reply_to_msg_id or event.id
167
  stdout, stderr = await bash(cmd, run_code=1)
@@ -170,7 +80,6 @@ async def _(event):
170
  if stderr:
171
  err = f"**• ERROR:** \n`{stderr}`\n\n"
172
  if stdout:
173
- # When Carbon/Rayso output is requested and allowed by chat permissions:
174
  if (carb or udB.get_key("CARBON_ON_BASH")) and (
175
  event.is_private
176
  or event.chat.admin_rights
@@ -215,7 +124,6 @@ async def _(event):
215
  out = "**• OUTPUT:**"
216
  remove(li)
217
  else:
218
- # treat pip-like keyed output as YAML and pretty-format
219
  if "pip" in cmd and all(":" in line for line in stdout.split("\n")):
220
  try:
221
  load = safe_load(stdout)
@@ -234,9 +142,7 @@ async def _(event):
234
  out = f"**• OUTPUT:**\n{stdout}"
235
  if not stderr and not stdout:
236
  out = "**• OUTPUT:**\n`Success`"
237
-
238
  OUT += err + out
239
-
240
  if len(OUT) > 4096:
241
  ultd = err + out
242
  with BytesIO(str.encode(ultd)) as out_file:
@@ -253,14 +159,40 @@ async def _(event):
253
 
254
  await xx.delete()
255
  else:
256
- # wrap in HTML blockquote and escape unsafe chars
257
- formatted_out = f"<blockquote>{escape(OUT)}</blockquote>"
258
- # Telethon accepts parse_mode='html' in edit calls in recent versions
259
- await xx.edit(formatted_out, parse_mode="html", link_preview=False)
260
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
261
 
262
- # alias kept
263
- pp = pprint
264
 
265
  @ultroid_cmd(pattern="eval", fullsudo=True, only_devs=True)
266
  async def _(event):
@@ -268,7 +200,6 @@ async def _(event):
268
  cmd = event.text.split(maxsplit=1)[1]
269
  except IndexError:
270
  return await event.eor(get_string("devs_2"), time=5)
271
-
272
  xx = None
273
  mode = ""
274
  spli = cmd.split()
@@ -297,18 +228,13 @@ async def _(event):
297
  return
298
  if not mode == "silent" and not xx:
299
  xx = await event.eor(get_string("com_1"))
300
-
301
- # attempt to format with black if available (not critical)
302
  if black:
303
  try:
304
  cmd = black.format_str(cmd, mode=black.Mode())
305
- except Exception:
306
- # ignore formatting failures
307
  pass
308
-
309
- reply_to_id = event.reply_to_msg_id or event.id
310
-
311
- # SECURITY: detect and report potentially dangerous commands
312
  if any(item in cmd for item in KEEP_SAFE().All) and (
313
  not (event.out or event.sender_id == ultroid_bot.uid)
314
  ):
@@ -320,13 +246,11 @@ async def _(event):
320
  return await xx.edit(
321
  "`Malicious Activities suspected⚠️!\nReported to owner. Aborted this request!`"
322
  )
323
-
324
- # Capture stdout/stderr
325
  old_stderr = sys.stderr
326
  old_stdout = sys.stdout
327
  redirected_output = sys.stdout = StringIO()
328
  redirected_error = sys.stderr = StringIO()
329
- value, exc = None, None
330
  tima = time.time()
331
  try:
332
  value = await aexec(cmd, event)
@@ -338,18 +262,18 @@ async def _(event):
338
  stderr = redirected_error.getvalue()
339
  sys.stdout = old_stdout
340
  sys.stderr = old_stderr
341
-
342
  if value:
343
  try:
344
  if mode == "gsource":
345
  exc = inspect.getsource(value)
346
  elif mode == "g-args":
347
  args = inspect.signature(value).parameters.values()
348
- name = getattr(value, "__name__", "")
 
 
349
  exc = f"**{name}**\n\n" + "\n ".join([str(arg) for arg in args])
350
  except Exception:
351
  exc = traceback.format_exc()
352
-
353
  evaluation = exc or stderr or stdout or _parse_eval(value) or get_string("instu_4")
354
  if mode == "silent":
355
  if exc:
@@ -359,10 +283,11 @@ async def _(event):
359
  if len(msg) > 4000:
360
  with BytesIO(msg.encode()) as out_file:
361
  out_file.name = "Eval-Error.txt"
362
- return await event.client.send_message(log_chat, f"`{cmd}`", file=out_file)
 
 
363
  await event.client.send_message(log_chat, msg, parse_mode="html")
364
  return
365
-
366
  tmt = tima * 1000
367
  timef = time_formatter(tmt)
368
  timeform = timef if not timef == "0s" else f"{tmt:.3f}ms"
@@ -385,19 +310,48 @@ async def _(event):
385
  reply_to=reply_to_id,
386
  )
387
  return await xx.delete()
 
388
 
389
- # wrap eval output in HTML blockquote and escape unsafe chars
390
- formatted_eval = f"<blockquote>{escape(final_output)}</blockquote>"
391
- await xx.edit(formatted_eval, parse_mode="html")
392
 
393
- # Small helper used by exec namespace
394
  def _stringify(text=None, *args, **kwargs):
395
  if text:
396
  u._ = text
397
  text = _parse_eval(text)
398
  return print(text, *args, **kwargs)
399
 
400
- # ---------------------------------------------------------------------
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
401
  DUMMY_CPP = """#include <iostream>
402
  using namespace std;
403
 
@@ -406,6 +360,7 @@ int main(){
406
  }
407
  """
408
 
 
409
  @ultroid_cmd(pattern="cpp", only_devs=True)
410
  async def doie(e):
411
  match = e.text.split(" ", maxsplit=1)
@@ -423,9 +378,9 @@ async def doie(e):
423
  if m[1]:
424
  o_cpp += f"\n\n**• Error :**\n`{m[1]}`"
425
  if len(o_cpp) > 3000:
426
- remove("cpp-ultroid.cpp")
427
  if os.path.exists("CppUltroid"):
428
- remove("CppUltroid")
429
  with BytesIO(str.encode(o_cpp)) as out_file:
430
  out_file.name = "error.txt"
431
  return await msg.reply(f"`{match}`", file=out_file)
@@ -440,8 +395,6 @@ async def doie(e):
440
  out_file.name = "eval.txt"
441
  await msg.reply(f"`{match}`", file=out_file)
442
  else:
443
- # wrap cpp output in HTML blockquote and escape unsafe chars
444
- formatted_cpp = f"<blockquote>{escape(o_cpp)}</blockquote>"
445
- await eor(msg, formatted_cpp, parse_mode="html")
446
- remove("CppUltroid")
447
- remove("cpp-ultroid.cpp")
 
2
  # Copyright (C) 2021-2025 TeamUltroid
3
  #
4
  # This file is a part of < https://github.com/TeamUltroid/Ultroid/ >
5
+ # PLease read the GNU Affero General Public License in
6
  # <https://www.github.com/TeamUltroid/Ultroid/blob/main/LICENSE/>.
7
 
8
  from . import get_help
 
12
  import inspect
13
  import sys
14
  import traceback
 
 
15
  from io import BytesIO, StringIO
16
  from os import remove
17
  from pprint import pprint
 
 
18
 
19
  from telethon.utils import get_display_name
 
20
 
 
21
  from pyUltroid import _ignore_eval
 
 
22
 
23
+ from . import *
24
+
25
+ # Used for Formatting Eval Code, if installed
26
  try:
27
  import black
28
+ except ImportError:
29
  black = None
30
+ from random import choice
31
 
32
  try:
33
  from yaml import safe_load
34
+ except ImportError:
35
+ from pyUltroid.fns.tools import safe_load
 
 
 
 
 
36
 
37
+ from . import upload_file as uf
38
+ from telethon.tl import functions
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
 
40
+ fn = functions
 
 
 
 
 
 
 
 
 
41
 
 
 
 
 
 
 
 
 
 
42
 
43
+ @ultroid_cmd(
44
+ pattern="sysinfo$",
45
+ )
46
  async def _(e):
47
  xx = await e.eor(get_string("com_1"))
48
+ x, y = await bash("neofetch|sed 's/\x1B\\[[0-9;\\?]*[a-zA-Z]//g' >> neo.txt")
 
49
  if y and y.endswith("NOT_FOUND"):
50
  return await xx.edit(f"Error: `{y}`")
51
  with open("neo.txt", "r", encoding="utf-8") as neo:
 
58
  await xx.delete()
59
  remove("neo.txt")
60
 
61
+
62
  @ultroid_cmd(pattern="bash", fullsudo=True, only_devs=True)
63
  async def _(event):
64
  carb, rayso, yamlf = None, None, False
 
72
  rayso = True
73
  except IndexError:
74
  return await event.eor(get_string("devs_1"), time=10)
 
75
  xx = await event.eor(get_string("com_1"))
76
  reply_to_id = event.reply_to_msg_id or event.id
77
  stdout, stderr = await bash(cmd, run_code=1)
 
80
  if stderr:
81
  err = f"**• ERROR:** \n`{stderr}`\n\n"
82
  if stdout:
 
83
  if (carb or udB.get_key("CARBON_ON_BASH")) and (
84
  event.is_private
85
  or event.chat.admin_rights
 
124
  out = "**• OUTPUT:**"
125
  remove(li)
126
  else:
 
127
  if "pip" in cmd and all(":" in line for line in stdout.split("\n")):
128
  try:
129
  load = safe_load(stdout)
 
142
  out = f"**• OUTPUT:**\n{stdout}"
143
  if not stderr and not stdout:
144
  out = "**• OUTPUT:**\n`Success`"
 
145
  OUT += err + out
 
146
  if len(OUT) > 4096:
147
  ultd = err + out
148
  with BytesIO(str.encode(ultd)) as out_file:
 
159
 
160
  await xx.delete()
161
  else:
162
+ await xx.edit(OUT, link_preview=not yamlf)
163
+
 
 
164
 
165
+ pp = pprint # ignore: pylint
166
+ bot = ultroid = ultroid_bot
167
+
168
+
169
+ class u:
170
+ _ = ""
171
+
172
+
173
+ def _parse_eval(value=None):
174
+ if not value:
175
+ return value
176
+ if hasattr(value, "stringify"):
177
+ try:
178
+ return value.stringify()
179
+ except TypeError:
180
+ pass
181
+ elif isinstance(value, dict):
182
+ try:
183
+ return json_parser(value, indent=1)
184
+ except BaseException:
185
+ pass
186
+ elif isinstance(value, list):
187
+ newlist = "["
188
+ for index, child in enumerate(value):
189
+ newlist += "\n " + str(_parse_eval(child))
190
+ if index < len(value) - 1:
191
+ newlist += ","
192
+ newlist += "\n]"
193
+ return newlist
194
+ return str(value)
195
 
 
 
196
 
197
  @ultroid_cmd(pattern="eval", fullsudo=True, only_devs=True)
198
  async def _(event):
 
200
  cmd = event.text.split(maxsplit=1)[1]
201
  except IndexError:
202
  return await event.eor(get_string("devs_2"), time=5)
 
203
  xx = None
204
  mode = ""
205
  spli = cmd.split()
 
228
  return
229
  if not mode == "silent" and not xx:
230
  xx = await event.eor(get_string("com_1"))
 
 
231
  if black:
232
  try:
233
  cmd = black.format_str(cmd, mode=black.Mode())
234
+ except BaseException:
235
+ # Consider it as Code Error, and move on to be shown ahead.
236
  pass
237
+ reply_to_id = event.reply_to_msg_id or event
 
 
 
238
  if any(item in cmd for item in KEEP_SAFE().All) and (
239
  not (event.out or event.sender_id == ultroid_bot.uid)
240
  ):
 
246
  return await xx.edit(
247
  "`Malicious Activities suspected⚠️!\nReported to owner. Aborted this request!`"
248
  )
 
 
249
  old_stderr = sys.stderr
250
  old_stdout = sys.stdout
251
  redirected_output = sys.stdout = StringIO()
252
  redirected_error = sys.stderr = StringIO()
253
+ stdout, stderr, exc, timeg = None, None, None, None
254
  tima = time.time()
255
  try:
256
  value = await aexec(cmd, event)
 
262
  stderr = redirected_error.getvalue()
263
  sys.stdout = old_stdout
264
  sys.stderr = old_stderr
 
265
  if value:
266
  try:
267
  if mode == "gsource":
268
  exc = inspect.getsource(value)
269
  elif mode == "g-args":
270
  args = inspect.signature(value).parameters.values()
271
+ name = ""
272
+ if hasattr(value, "__name__"):
273
+ name = value.__name__
274
  exc = f"**{name}**\n\n" + "\n ".join([str(arg) for arg in args])
275
  except Exception:
276
  exc = traceback.format_exc()
 
277
  evaluation = exc or stderr or stdout or _parse_eval(value) or get_string("instu_4")
278
  if mode == "silent":
279
  if exc:
 
283
  if len(msg) > 4000:
284
  with BytesIO(msg.encode()) as out_file:
285
  out_file.name = "Eval-Error.txt"
286
+ return await event.client.send_message(
287
+ log_chat, f"`{cmd}`", file=out_file
288
+ )
289
  await event.client.send_message(log_chat, msg, parse_mode="html")
290
  return
 
291
  tmt = tima * 1000
292
  timef = time_formatter(tmt)
293
  timeform = timef if not timef == "0s" else f"{tmt:.3f}ms"
 
310
  reply_to=reply_to_id,
311
  )
312
  return await xx.delete()
313
+ await xx.edit(final_output)
314
 
 
 
 
315
 
 
316
  def _stringify(text=None, *args, **kwargs):
317
  if text:
318
  u._ = text
319
  text = _parse_eval(text)
320
  return print(text, *args, **kwargs)
321
 
322
+
323
+ async def aexec(code, event):
324
+ # Create a dedicated namespace for execution
325
+ exec_globals = {
326
+ 'print': _stringify,
327
+ 'p': _stringify,
328
+ 'message': event,
329
+ 'event': event,
330
+ 'client': event.client,
331
+ 'reply': await event.get_reply_message(),
332
+ 'chat': event.chat_id,
333
+ 'u': u,
334
+ '__builtins__': __builtins__,
335
+ '__name__': __name__
336
+ }
337
+
338
+ # Format the async function definition
339
+ wrapped_code = (
340
+ 'async def __aexec(e, client):\n' +
341
+ '\n'.join(f' {line}' for line in code.split('\n'))
342
+ )
343
+
344
+ try:
345
+ # Execute the wrapped code in our custom namespace
346
+ exec(wrapped_code, exec_globals)
347
+ # Get the defined async function
348
+ func = exec_globals['__aexec']
349
+ # Execute it with proper parameters
350
+ return await func(event, event.client)
351
+ except Exception as e:
352
+ raise Exception(f"Failed to execute code: {str(e)}")
353
+
354
+
355
  DUMMY_CPP = """#include <iostream>
356
  using namespace std;
357
 
 
360
  }
361
  """
362
 
363
+
364
  @ultroid_cmd(pattern="cpp", only_devs=True)
365
  async def doie(e):
366
  match = e.text.split(" ", maxsplit=1)
 
378
  if m[1]:
379
  o_cpp += f"\n\n**• Error :**\n`{m[1]}`"
380
  if len(o_cpp) > 3000:
381
+ os.remove("cpp-ultroid.cpp")
382
  if os.path.exists("CppUltroid"):
383
+ os.remove("CppUltroid")
384
  with BytesIO(str.encode(o_cpp)) as out_file:
385
  out_file.name = "error.txt"
386
  return await msg.reply(f"`{match}`", file=out_file)
 
395
  out_file.name = "eval.txt"
396
  await msg.reply(f"`{match}`", file=out_file)
397
  else:
398
+ await eor(msg, o_cpp)
399
+ os.remove("CppUltroid")
400
+ os.remove("cpp-ultroid.cpp")