feat(sub): add dummy info node and status configs for subscriptions (#6412)

* feat(settings): add subInfoNodeEnable and status template settings

* feat(sub): add dummy info node and status configs for raw links

* feat(sub): support dummy info node in clash and json subscriptions

* feat(ui): add subscription info node switch and status templates to settings

* style: apply gofumpt formatting

* fix(sub): address review feedback on subscription info node

- Restore GetSubs contract to avoid unintended remark expansions on non-subscription-body calls.
- Exclude dummy info node from Clash PROXY select group when active server nodes exist.
- Track hasEnabledClient and set traffic.Enable in JSON and Clash paths so status tokens evaluate correctly.
- Deterministically sort client emails across subscriptions before selecting primaryEmail.
- Consolidate duplicated info-node evaluation logic into resolveInfoNodeRemark helper.
- Remove redundant pure-getter test from setting_sub_info_node_test.go.

Co-Authored-By: Claude Code <noreply@anthropic.com>

---------

Co-authored-by: Claude Code <noreply@anthropic.com>
This commit is contained in:
Rouzbeh†
2026-09-08 18:44:01 +03:30
committed by GitHub
parent b8597314f8
commit 9f76a66dcf
33 changed files with 1374 additions and 35 deletions
@@ -0,0 +1,27 @@
import { describe, it, expect } from 'vitest';
import { AllSettingSchema } from '@/schemas/setting';
import { AllSetting } from '@/models/setting';
describe('subInfoNode settings', () => {
it('defaults on AllSetting', () => {
const s = new AllSetting();
expect(s.subInfoNodeEnable).toBe(false);
expect(s.subExpiredTemplate).toBe('⛔ {{EMAIL}} | Expired: {{EXPIRE_DATE}}');
expect(s.subTrafficDepletedTemplate).toBe(
'🚫 {{EMAIL}} | Traffic Depleted | {{TRAFFIC_USED}}/{{TRAFFIC_TOTAL}}',
);
});
it('accepts valid values in the settings schema', () => {
const r = AllSettingSchema.safeParse({
subInfoNodeEnable: true,
subExpiredTemplate: 'custom expired',
subTrafficDepletedTemplate: 'custom depleted',
});
expect(r.success).toBe(true);
});
it('rejects invalid types', () => {
expect(AllSettingSchema.safeParse({ subInfoNodeEnable: 'true' }).success).toBe(false);
});
});