@@ -15,10 +15,10 @@ export const fetchRules = async () => {
1515 fetchWithRetry ( 'CN_bilibili' , ( ) => fetchGithubRules ( CN_bilibili ) ) ,
1616 fetchWithRetry ( 'INTL_bilibili' , ( ) => fetchGithubRules ( INTL_bilibili ) ) ,
1717 fetchWithRetry ( 'Blocked_By_GFW' , ( ) => fetchGithubRules ( Blocked_By_GFW ) ) ,
18- fetchWithRetry ( 'Loyalsoldier_GFW' , ( ) => fetchLoyalsoldierRules ( Loyalsoldier_GFW ) ) ,
19- fetchWithRetry ( 'Loyalsoldier_Proxy' , ( ) => fetchLoyalsoldierRules ( Loyalsoldier_Proxy ) ) ,
20- fetchWithRetry ( 'Loyalsoldier_Telegram' , ( ) => fetchLoyalsoldierRules ( Loyalsoldier_Telegram ) ) ,
21- fetchWithRetry ( 'Loyalsoldier_Apple' , ( ) => fetchLoyalsoldierRules ( Loyalsoldier_Apple ) ) ,
18+ fetchWithRetry ( 'Loyalsoldier_GFW' , ( ) => fetchLoyalsoldierDomainRules ( Loyalsoldier_GFW ) ) ,
19+ fetchWithRetry ( 'Loyalsoldier_Proxy' , ( ) => fetchForeignMediaRules ( Loyalsoldier_Proxy ) ) ,
20+ fetchWithRetry ( 'Loyalsoldier_Telegram' , ( ) => fetchTelegramCidrRules ( Loyalsoldier_Telegram ) ) ,
21+ fetchWithRetry ( 'Loyalsoldier_Apple' , ( ) => fetchLoyalsoldierDomainRules ( Loyalsoldier_Apple ) ) ,
2222 fetchWithRetry ( 'Loyalsoldier_Direct' , ( ) => fetchLoyalsoldierDirect ( Loyalsoldier_Direct_Filtered ) ) ,
2323 fetchWithRetry ( 'Microsoft' , ( ) => fetchMicrosoftRules ( Microsoft_Rules ) ) ,
2424 fetchWithRetry ( 'AI' , ( ) => fetchAIRules ( ) ) ,
@@ -59,11 +59,9 @@ then( ( text ) => {
5959} ) ;
6060
6161/**
62- * 获取Loyalsoldier/clash-rules规则
63- * 直接从raw.githubusercontent.com获取YAML文件
64- * 只保留真正的国外媒体服务域名(过滤掉 Microsoft/Apple/Google/开发工具/CDN 等)
62+ * 获取 Loyalsoldier/clash-rules 的通用域名规则
6563 */
66- export const fetchLoyalsoldierRules = ( url : string ) => fetch ( url , {
64+ export const fetchLoyalsoldierDomainRules = ( url : string ) => fetch ( url , {
6765 headers : {
6866 "User-Agent" : "Clash-Parser/1.0"
6967 } ,
@@ -76,30 +74,42 @@ export const fetchLoyalsoldierRules = (url: string) => fetch(url, {
7674 return res . text ( ) ;
7775 } )
7876 . then ( text => {
79- // 解析YAML格式的规则文件
80- const yaml : ResContents = parse ( text ) ;
81- if ( ! yaml . payload || ! Array . isArray ( yaml . payload ) ) return [ ] ;
82-
83- // 过滤并提取域名
84- return yaml . payload
85- . filter ( rule => {
86- // 过滤掉 IP-CIDR 和 IP-CIDR6 规则
87- return ! rule . startsWith ( 'IP-CIDR,' ) && ! rule . startsWith ( 'IP-CIDR6,' ) ;
88- } )
89- . map ( rule => {
90- // 处理 '+.domain.com' 格式(表示 DOMAIN-SUFFIX)
91- if ( rule . startsWith ( '+.' ) ) {
92- return rule . substring ( 2 ) ; // 去掉 '+.' 前缀
93- }
94- // 处理 'DOMAIN,xxx' 或 'DOMAIN-SUFFIX,xxx' 格式
95- if ( rule . includes ( ',' ) ) {
96- const parts = rule . split ( ',' ) ;
97- return parts [ 1 ] ;
98- }
99- // 其他情况直接返回
100- return rule ;
101- } )
102- . filter ( domain => filterForeignMediaDomains ( domain ) ) ;
77+ return parsePayload ( text )
78+ . map ( normalizeDomainPayloadRule )
79+ . filter ( ( domain ) : domain is string => Boolean ( domain ) ) ;
80+ } ) ;
81+
82+ /**
83+ * 获取国外媒体域名规则
84+ * 只保留真正的国外媒体服务域名(过滤掉 Microsoft/Apple/Google/开发工具/CDN 等)
85+ */
86+ export const fetchForeignMediaRules = ( url : string ) => fetchLoyalsoldierDomainRules ( url )
87+ . then ( domains => domains . filter ( domain => filterForeignMediaDomains ( domain ) ) ) ;
88+
89+ /**
90+ * 向后兼容旧函数名。新代码应按用途调用 fetchLoyalsoldierDomainRules / fetchForeignMediaRules。
91+ */
92+ export const fetchLoyalsoldierRules = fetchForeignMediaRules ;
93+
94+ /**
95+ * 获取 Telegram CIDR 规则
96+ */
97+ export const fetchTelegramCidrRules = ( url : string ) => fetch ( url , {
98+ headers : {
99+ "User-Agent" : "Clash-Parser/1.0"
100+ } ,
101+ signal : AbortSignal . timeout ( 30000 )
102+ } )
103+ . then ( res => {
104+ if ( ! res . ok ) {
105+ throw new Error ( `HTTP ${ res . status } : ${ res . statusText } ` ) ;
106+ }
107+ return res . text ( ) ;
108+ } )
109+ . then ( text => {
110+ return parsePayload ( text )
111+ . map ( normalizeCidrPayloadRule )
112+ . filter ( ( rule ) : rule is string => Boolean ( rule ) ) ;
103113 } ) ;
104114
105115/**
@@ -123,28 +133,9 @@ export const fetchLoyalsoldierDirect = (url: string) => fetch(url, {
123133 return res . text ( ) ;
124134 } )
125135 . then ( text => {
126- const yaml : ResContents = parse ( text ) ;
127- if ( ! yaml . payload || ! Array . isArray ( yaml . payload ) ) return [ ] ;
128-
129- // 过滤并提取域名
130- const domains = yaml . payload
131- . filter ( rule => {
132- // 只保留 DOMAIN 相关规则
133- return ! rule . startsWith ( 'IP-CIDR,' ) && ! rule . startsWith ( 'IP-CIDR6,' ) ;
134- } )
135- . map ( rule => {
136- // 处理 '+.domain.com' 格式
137- if ( rule . startsWith ( '+.' ) ) {
138- return rule . substring ( 2 ) ;
139- }
140- // 处理 'DOMAIN,xxx' 或 'DOMAIN-SUFFIX,xxx' 格式
141- if ( rule . includes ( ',' ) ) {
142- const parts = rule . split ( ',' ) ;
143- return parts [ 1 ] ;
144- }
145- return rule ;
146- } )
147- . filter ( domain => domain && domain . length > 0 ) ;
136+ const domains = parsePayload ( text )
137+ . map ( normalizeDomainPayloadRule )
138+ . filter ( ( domain ) : domain is string => Boolean ( domain ) ) ;
148139
149140 // 智能筛选策略
150141 return smartFilterDirectDomains ( domains ) ;
@@ -313,6 +304,60 @@ function filterForeignMediaDomains(domain: string): boolean {
313304 return isMediaDomain ;
314305}
315306
307+ function parsePayload ( text : string ) : string [ ] {
308+ const yaml : ResContents = parse ( text ) ;
309+ if ( ! yaml . payload || ! Array . isArray ( yaml . payload ) ) return [ ] ;
310+ return yaml . payload . map ( rule => String ( rule ) . trim ( ) ) . filter ( Boolean ) ;
311+ }
312+
313+ function normalizeDomainPayloadRule ( rule : string ) : string | null {
314+ const cleanedRule = rule . startsWith ( '- ' ) ? rule . substring ( 2 ) . trim ( ) : rule . trim ( ) ;
315+
316+ if ( ! cleanedRule || cleanedRule . startsWith ( 'IP-CIDR,' ) || cleanedRule . startsWith ( 'IP-CIDR6,' ) ) {
317+ return null ;
318+ }
319+
320+ if ( cleanedRule . includes ( '/' ) ) {
321+ return null ;
322+ }
323+
324+ if ( cleanedRule . includes ( ',' ) ) {
325+ const [ type , value ] = cleanedRule . split ( ',' ) ;
326+ if ( ! [ 'DOMAIN' , 'DOMAIN-SUFFIX' , 'DOMAIN-KEYWORD' , 'DOMAIN-WILDCARD' , 'DOMAIN-REGEX' ] . includes ( type ) ) {
327+ return null ;
328+ }
329+ return normalizeDomainValue ( value ) ;
330+ }
331+
332+ return normalizeDomainValue ( cleanedRule ) ;
333+ }
334+
335+ function normalizeDomainValue ( value : string ) : string {
336+ return value
337+ . trim ( )
338+ . replace ( / ^ \+ \. \* \. / , '' )
339+ . replace ( / ^ \+ \. / , '' )
340+ . replace ( / ^ \* \. / , '' ) ;
341+ }
342+
343+ function normalizeCidrPayloadRule ( rule : string ) : string | null {
344+ const cleanedRule = rule . startsWith ( '- ' ) ? rule . substring ( 2 ) . trim ( ) : rule . trim ( ) ;
345+ if ( ! cleanedRule ) return null ;
346+
347+ if ( cleanedRule . startsWith ( 'IP-CIDR,' ) || cleanedRule . startsWith ( 'IP-CIDR6,' ) ) {
348+ const [ type , value ] = cleanedRule . split ( ',' ) ;
349+ if ( ! value ) return null ;
350+ return `${ type } ,${ value } ,no-resolve` ;
351+ }
352+
353+ if ( ! cleanedRule . includes ( '/' ) ) {
354+ return null ;
355+ }
356+
357+ const type = cleanedRule . includes ( ':' ) ? 'IP-CIDR6' : 'IP-CIDR' ;
358+ return `${ type } ,${ cleanedRule } ,no-resolve` ;
359+ }
360+
316361/**
317362 * 智能筛选直连域名
318363 * 保留高频国内站点,去除长尾域名
0 commit comments