txlyre
synced commits to main at txlyre/3x-ui from mirror
12d51d7195 perf(tests): copy a migrated template DB instead of migrating per test
Most tests opened a throwaway panel DB with database.InitDB, which runs the
full AutoMigrate + seed on an empty file every time: ~230ms, and ~850ms under
-race because GORM's reflection-heavy migration is what the detector slows
most. internal/web/service does this in ~550 of its 830 tests, so the CI race
job spent ~10 of its ~14.6 minutes re-migrating empty databases.
internal/database/dbtest.InitDB migrates once per test process, then hands
each test its own copy of that file (~130ms under -race) and registers the
CloseDB cleanup. The copy then goes through InitDB like a panel restart, so
every test still starts from the state a fresh install has. Tests that reopen
an existing file, migrate a hand-built legacy DB or target Postgres keep
calling database.InitDB.
Locally under -race: internal/web/service 626s (last CI run) -> 114s,
internal/sub 246s -> 35s.
33a469315a feat(clients): preserve traffic counters in portable export/import (#6469)
* feat(clients): preserve traffic counters in portable export/import
ExportAll now attaches client_traffics up/down (plus resetCount and
last-seen fields) on each portable payload, and ImportClients restores
them only for newly created emails so skipped/existing clients keep
their live counters. Fixes #5858.
* fix(clients): restore imported traffic only onto rows the import created
Review of the portable-traffic export/import (#5858) found four defects:
- An orphan's restored row was hand-built, dropping reset_weekday and
forcing enable=true; a row kept by a keepTraffic delete kept the old
client's limits. depletedClientsClause then matched a weekly-renewing
over-quota orphan and DelDepleted deleted it. Orphan rows now go
through AddClientStat, whose upsert refreshes config and keeps counters,
so the unused traffic.total field is dropped from the export.
- Created clients were inferred from Skipped emails, so a duplicate email
in the file left the created copy with zero counters. bulkCreate now
reports which payloads inserted a record, and only those are restored.
- Each client took its own serialized-writer commit: 2000 clients spent
3.66s instead of 0.52s. Counters now apply in batched transactions
(0.51s).
- importClients discarded needRestart when the late restore step failed
after clients were committed; it now flags and notifies first, as
create already does.
The /clients/export and /clients/import API docs now describe traffic.
* fix(groups): keep imported traffic out of group totals
Group totals keep a deleted client's usage (#5675), and the portable
import restores that same usage onto the re-created client. Export,
delete, re-import therefore counted it twice in ListGroups, and a fresh
panel showed the migrated usage as consumption of its groups.
Restored counters are usage from before the import, so the import now
shifts each group's baseline up by what it restored, in the same
transaction. A group total no longer moves at import time; only traffic
consumed afterwards counts. The baseline shift reuses the #5675 helper,
now signed.
---------
Co-authored-by: Sanaei <[email protected]>
ac43b19cfa chore(ci): stop release and CodeQL runs on PRs, drop deploy smoke tests
The release matrix (7 Linux cross-builds + a CGO Windows build) ran on every
PR and on every branch push, so a PR from a repo branch built everything
twice. Release binaries now build only on main (dev channel) and version
tags; any other branch can still be built via workflow_dispatch.
CodeQL keeps its push-to-main and weekly scans but no longer runs per PR.
The deploy smoke workflow fired on every Release completion only to skip
its jobs; deploy/test/smoke-noninteractive.sh stays for manual runs.
0ef94b686e feat(tgbot): add /broadcast to relay an admin message to all clients (#6510)
* feat(tgbot): add /broadcast to relay an admin message to all clients
Admins had no way to reach every client at once: notifications only
cover exhausted quotas, so an operator had to copy a message to each
client chat by hand. Add an admin-only /broadcast flow to the bot:
- /broadcast asks for a message; any message the admin sends — text,
rich text, photo, video, file, sticker or a whole album — becomes the
broadcast by reference (admin chat + message ids), and a preview
self-copy shows the admin exactly what recipients will get while
rejecting content Telegram cannot copy before the run starts.
- The draft references the original instead of parsing its content, so
copyMessage/copyMessages deliver everything 1:1 on behalf of the bot
with no forward header (the admin's identity stays private), no
caption length pitfalls, and future Telegram message types work
without new parsing.
- A media group arrives as separate updates; its ids are buffered with
a short debounce, sorted, and delivered as one copyMessages call so
recipients see the original album.
- Delivery runs in a background goroutine (common.GoRecover): sequential
sends with a small pause, 429 retry_after honored per recipient,
failures counted without stopping the run, progress edited into one
card at most every 25 sends or 3 seconds, a cancel button checked
between sends, and a final delivered/failed/skipped summary. The
summary is edited into the card (only sent separately if the card is
gone), so it is never duplicated.
- Recipients repeat the notifyExhausted walk: clients with a linked
tg_id, deduplicated, admins excluded — they already receive the
reports. The message content is never logged.
New i18n keys are added to all 13 locales.
* fix(tgbot): harden broadcast composition per review
- Key the composition per admin chat instead of one process-wide draft:
two admins can now compose at once without dropping each other's
drafts, and one admin's /broadcast no longer wipes another chat's
half-collected album.
- Bind each preview card to its own draft via a random token carried in
the confirm callback, so a stale Send tap is answered with an error
instead of delivering a newer, unapproved draft.
- Ignore non-admin senders while a chat composes: the awaiting state is
keyed by chat id, and in a group that chat is shared.
- Check the cancel flag inside the flood-control retry loop, so a 429
with a long retry_after no longer holds the single broadcast slot
after the admin cancelled.
- Scale the per-recipient pause by the copied batch size, so an album
keeps the same per-second ceiling as a single message.
- Trim the comment blocks that exceeded the two-line cap.
* fix(tgbot): reset broadcast state on stop and classify 403 as skipped
- Clear compositions and cancel the active run from StopBot, next to the
per-chat draft resets: an album debounce timer, a confirmable token or
a held runner slot must not outlive the receiver that created them.
- Sleep flood-control waits in 5 s slices and re-check cancel and bot
state between them, so a minutes-long retry_after no longer parks the
single-runner slot after the admin cancelled or the bot stopped.
- Count Telegram 403 (the chat never started the bot, or blocked it) as
skipped instead of failed, log it at debug rather than one warning per
recipient, and append one line to the summary naming the reason.
- Trim the remaining comment blocks over the two-line cap.
* fix(tgbot): count unreachable recipients in broadcast progress throttle
The progress card refresh was keyed on sent+failed, which a 403 does not
advance since unreachable chats were split out of the failure count. A
streak of unreachable recipients while that sum sat on a multiple of
broadcastProgressEvery (0 included, so from the very first recipient)
edited the card once per chat, doubling the request rate the send delay
is sized for and defeating the throttle. Count processed recipients.
* fix(tgbot): key broadcast compositions by admin, not chat
After #6604 moved conversation state to the admin (chatUser), the
broadcast draft map stayed keyed by chat. Two admins composing in one
group then shared a slot: the second admin's message dropped the first
admin's draft, whose Send tap answered "went wrong" while only the other
draft could go out - the same class #6604 fixed for the add-client
wizard. Drafts, album buffers and confirm tokens now live under the
admin who ran /broadcast.
The router now hands handleBroadcastInput only the admin whose own
/broadcast is awaiting input, so its sender re-check and the test that
fed it a non-admin message directly (an input no route can deliver)
are removed.
---------
Co-authored-by: MHSanaei <[email protected]>
71e38367c1 feat(sub): add Incy app-management parameters (#6650)
* feat(sub): add Incy app-management parameters
The panel already pushes a set of Happ headers, but INCY documents its own
lowercase header names and its own value domains, so a Happ-shaped payload gets
ignored by the client (per-app mode is bypass|proxy, not on|bypass, and
per-app-proxy-enable has no Happ counterpart at all). Add a sibling Incy path
that emits exactly the documented headers.
Covered, per https://docs.incy.cc/en/app-management/:
- profile-description, sort-order, support-email, announce-url, premium-url
- banner text/button/URL and the two hex colours
- hide-url, hide-check, no-limit-enabled
- per-app split tunnelling (enable/mode/list)
- TCP fragmentation (enable/length/interval/packets)
- UDP noise packets (enable/type/packet/delay)
- DoH pre-resolution (enable/domain/IP)
Each string setting is tri-state: an empty value omits the header, so an
untouched panel never overrides the subscriber's own choice in the app. Values
are validated against the documented domains and dropped when they do not
match, and non-ASCII text is base64-wrapped the way the docs require for
Cyrillic. INCY identifies itself as INCY/<version>/<platform>, which gates the
headers behind the same auto-detect switch the Happ path uses.
Headers the panel already emits for every client (Profile-Title, Support-Url,
Profile-Web-Page-Url, Announce, Profile-Update-Interval, Subscription-Userinfo)
and Incy's routing line are left as they are.
The Premium API (theme, defaultPingProtocol, fallbackHosts, ...) is a separate
encrypted endpoint and stays out of scope here.
* fix(sub): keep Incy per-app list entries separate on the wire
The Incy settings textarea takes one package per line, as Incy documents for
per-app-proxy-list, but the header path ran the value through
sanitizeHeaderValue, which deletes CR/LF. "com.google.chrome\norg.telegram.messenger"
reached the client as the single bogus package
"com.google.chromeorg.telegram.messenger", so per-app split tunnelling silently
matched no app. Join comma- or line-separated entries as CSV instead.
Also drop three tests that could not fail: TestIncyExcludesHappOnlyHeaders
(ApplyIncyHeaders has no path that emits Happ headers, and the non-Happ UA
gate is already pinned by TestApplyHappHeaders_Gating) and two UI tests that
only asserted updateSetting received the key the JSX passes it.
---------
Co-authored-by: DIMFLIX <[email protected]>
Co-authored-by: MHSanaei <[email protected]>
- View comparison for these 30 commits »
2 days ago