refactor#503
Open
ufoptg wants to merge 1 commit into
Open
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Ultroid — Core Refactor & Modernization
Date: 2026-01-28
Branch:
refactor/core-modernizationTarget: Python 3.10+ (preserves
telethonpatch— the project's vendoredTelethon fork — exactly as upstream uses it).
This pass focuses on
pyUltroid/— the core framework — and intentionallyleaves the
plugins/,assistant/,vcbot/,addons/trees untouched. Allpublic symbols those plugins import (
ultroid_cmd,asst_cmd,inline_mention,eor/eod,UltroidClient,udB,LOGS, …) keep theirexisting signatures so existing plugins continue to work without changes.
Files changed
pyUltroid/version.py2026.01.28/2.1.3.pyUltroid/__init__.pysys.exit(1)not bareexit()), cacheOWNER_IDonce.pyUltroid/startup/__init__.pyint(v) < 10→sys.version_info < (3, 10)(the original would have miscompared if Python ever bumped to 4.x).pyUltroid/startup/_extra.py__builtins__["input"]only worked from__main__; switched tobuiltins.input = ….pyUltroid/startup/BaseClient.pypyUltroid/startup/connections.pylogger.exceptionoutside of anexceptblock; localised-string fallback.pyUltroid/startup/_database.pypip install(see §2).pyUltroid/startup/loader.pysubprocess.run(..., shell=True)with list-form invocations.pyUltroid/fns/helper.pypyUltroid/_misc/__init__.py_SudoManager.fullsudoscalled.get()instead of.get_key(). Fixed + handles both string and list shapes.pyUltroid/_misc/_decorators.py1.
pyUltroid/startup/BaseClient.py__dict__@property. Returningself.me.to_dict()from__dict__corrupted instance attribute lookup and could preventTelethon's internals from caching state. Several downstream
"AttributeError on a client that should have the attribute" reports
trace back to this.
fast_uploader/fast_downloader: thewhile not raw_file:loopwould spin forever if
upload_file/download_fileever returned afalsy value. Now raises a clear
RuntimeErrorinstead. Progresscallbacks switched from
self.loop.create_task(...)toasyncio.create_task(...)(the former is brittle inside callbacksscheduled from foreign threads).
start_client: tighter exit semantics — each error class is logged& exits cleanly without falling through to
await self.get_me()after afailed login.
add_handler:any(existing is func ...)instead of building a templist with
[_[0] for _ in self.list_event_handlers()]on every call.from telethonpatch import TelegramClientimport as-is— Ultroid relies on the patched client.
2.
pyUltroid/startup/_database.pySqlDB.get/set/deleteinterpolated user-suppliedcolumn names directly into SQL strings (
f"SELECT {variable} FROM Ultroid").Anything that ended up as a DB key — including DB keys derived from a
Telegram message — could break out and execute arbitrary SQL. The whole
class now uses
psycopg2.sql.Identifier/.Literalcomposition, which isthe documented safe way to parameterise identifiers in psycopg2.
os.system(f"{sys.executable} -m pip install …")at import.Auto-installing dependencies at runtime is brittle (network during boot)
and confusing when it fails. We now raise
SystemExit(1)with a clearpip install …hint instead.Database→LocalDatabaseso it doesn't shadow other namesin the module.
__init__types: added consistent type hints across every backend.3.
pyUltroid/fns/helper.pyinline_mentionno longer shadows the stdlibhtmlmodule — theparameter is now
as_html, but the legacyhtml=Truekeyword is stillaccepted (via
**kwargs) so existing plugins (plugins/pmpermit.py,etc.) keep working unchanged.
progress: rewritten. The bar used"".join("" for i in range(20-…)),which always produced an empty string for the empty half of the bar, so
the progress bar was actually invisible.
run_async: uses one sharedThreadPoolExecutorinstead of creatinga fresh one (with
cpu_count()*5threads!) on every call. Also switchedfrom
asyncio.get_event_loop()toasyncio.get_running_loop().uploader/downloader:asyncio.get_event_loop().create_task→asyncio.create_task(the former emits a DeprecationWarning on 3.10+).gen_chlog: was callingRepo()inside the function while alsoreceiving
repoas an argument — the resolved upstream URL didn't matchthe repo we were diffing. Uses the passed repo throughout.
updater: rewrote control flow. The previous version calledRepo().__del__()on errors (manual__del__calls are basically neverright) and recreated remotes inside an
if … else Noneternary just forside effects.
restart:sys.argv[1..6]wouldIndexErrorif called with fewerthan 6 CLI args. Uses
*sys.argv[1:]instead.bash: regex"\/bin\/sh: …"is now a raw string so\wisn'ttreated as an unknown escape under Python 3.12+ (it currently emits a
DeprecationWarning; in 3.13 it becomes aSyntaxWarning).humanbytes/numerize: simplified formatting; explicitfloat(...)conversion so callers passing strings don't silentlyproduce nonsense.
4.
pyUltroid/_misc/_decorators.pyThe FloodWait handler used to do:
That's wrong on two counts:
client.disconnect()cancels every in-flight request, including onesqueued by other handlers that aren't rate-limited — so a single
FloodWait in one chat would silently fail every other request in
flight.
resume awaiting; you don't need to bounce the connection.
Now it just
await asyncio.sleep(wait)in place and lets Telethon take itfrom there.
5.
pyUltroid/startup/loader.pyReplaced every
subprocess.run("some f-string", shell=True)with list-formsubprocess.run(["git", "clone", …]). Concretely:ADDONS_URLwas concatenated into a shell command. A user-controlledRedis key could therefore execute arbitrary shell on the host.
Repo().active_branchwas interpolated without quoting; if the activebranch name ever contained whitespace, the clone would silently break.
cd vcbot && git pullandcd addons && git pull && cd ..are nowgit -C vcbot pull/git -C addons pull -q.6. What was intentionally not changed
pyUltroid/fns/FastTelethon.py— vendored frommautrix-telegramand works fine against
telethonpatch.plugins/*.py,assistant/*.py,vcbot/*.py,addons/*— out ofscope for "core code". Each is its own surface area and should be
iterated on individually.
pyUltroid/startup/funcs.pyautobot/customize/autopilot—the BotFather conversation flow there is fragile, but reworking it
requires a working bot token to test against. Left as-is.
requirements.txt— Ultroid pulls a custom Telethon fork viahttps://github.com/New-dev0/Telethon-Patch/archive/main.zip. Touchingthat is a separate decision; nothing in this refactor depends on it.
7. How to apply
8. Suggested follow-ups (out of scope)
pyUltroid/configs.Vartopydantic-settingsfor propervalidation (the current
decouple.configdefaults silently hidemisconfigured envs).
autobot()with properwaited conversations (
async with client.conversation(...)alreadyexists in Telethon for exactly this).
telethonpatchto a specific commit instead ofarchive/main.zip— your CI is otherwise non-reproducible.