diff --git a/Build/build-apple-cdn.ts b/Build/build-apple-cdn.ts index edb022da..ac6594e7 100644 --- a/Build/build-apple-cdn.ts +++ b/Build/build-apple-cdn.ts @@ -5,13 +5,16 @@ import { parseFelixDnsmasq } from './lib/parse-dnsmasq'; import { task, traceAsync } from './lib/trace-runner'; import { SHARED_DESCRIPTION } from './lib/constants'; import picocolors from 'picocolors'; +import { createMemoizedPromise } from './lib/memo-promise'; + +export const getAppleCdnDomainsPromise = createMemoizedPromise(() => traceAsync( + picocolors.gray('download dnsmasq-china-list apple.china.conf'), + () => parseFelixDnsmasq('https://raw.githubusercontent.com/felixonmars/dnsmasq-china-list/master/apple.china.conf'), + picocolors.gray +)); export const buildAppleCdn = task(import.meta.path, async () => { - const res = await traceAsync( - picocolors.gray('download dnsmasq-china-list apple.china.conf'), - () => parseFelixDnsmasq('https://raw.githubusercontent.com/felixonmars/dnsmasq-china-list/master/apple.china.conf'), - picocolors.gray - ); + const res = await getAppleCdnDomainsPromise(); const description = [ ...SHARED_DESCRIPTION, diff --git a/Build/build-chn-cidr.ts b/Build/build-chn-cidr.ts index 487a2e75..14c1dfe4 100644 --- a/Build/build-chn-cidr.ts +++ b/Build/build-chn-cidr.ts @@ -6,6 +6,7 @@ import { task, traceAsync, traceSync } from './lib/trace-runner'; import { exclude } from 'fast-cidr-tools'; import picocolors from 'picocolors'; +import { createMemoizedPromise } from './lib/memo-promise'; // https://github.com/misakaio/chnroutes2/issues/25 const EXCLUDE_CIDRS = [ @@ -17,17 +18,21 @@ const INCLUDE_CIDRS = [ '211.99.96.0/19' // wy.com.cn ]; -export const buildChnCidr = task(import.meta.path, async () => { +export const getChnCidrPromise = createMemoizedPromise(async () => { const cidr = await traceAsync( picocolors.gray('download chnroutes2'), async () => processLineFromReadline(await fetchRemoteTextAndReadByLine('https://raw.githubusercontent.com/misakaio/chnroutes2/master/chnroutes.txt')), picocolors.gray ); - const filteredCidr = traceSync( + return traceSync( picocolors.gray('processing chnroutes2'), () => exclude([...cidr, ...INCLUDE_CIDRS], EXCLUDE_CIDRS, true), picocolors.gray ); +}); + +export const buildChnCidr = task(import.meta.path, async () => { + const filteredCidr = await getChnCidrPromise(); // Can not use SHARED_DESCRIPTION here as different license const description = [ diff --git a/Build/build-domestic-ruleset.ts b/Build/build-domestic-ruleset.ts index 89d03f7a..7b71b12e 100644 --- a/Build/build-domestic-ruleset.ts +++ b/Build/build-domestic-ruleset.ts @@ -6,8 +6,9 @@ import { processLineFromReadline } from './lib/process-line'; import { compareAndWriteFile, createRuleset } from './lib/create-file'; import { task } from './lib/trace-runner'; import { SHARED_DESCRIPTION } from './lib/constants'; +import { createMemoizedPromise } from './lib/memo-promise'; -export const buildDomesticRuleset = task(import.meta.path, async () => { +export const getDomesticDomainsRulesetPromise = createMemoizedPromise(async () => { const results = await processLineFromReadline(readFileByLine(path.resolve(import.meta.dir, '../Source/non_ip/domestic.conf'))); results.push( @@ -17,6 +18,10 @@ export const buildDomesticRuleset = task(import.meta.path, async () => { }, []).map((domain) => `DOMAIN-SUFFIX,${domain}`) ); + return results; +}); + +export const buildDomesticRuleset = task(import.meta.path, async () => { const rulesetDescription = [ ...SHARED_DESCRIPTION, '', @@ -28,7 +33,7 @@ export const buildDomesticRuleset = task(import.meta.path, async () => { 'Sukka\'s Ruleset - Domestic Domains', rulesetDescription, new Date(), - results, + await getDomesticDomainsRulesetPromise(), 'ruleset', path.resolve(import.meta.dir, '../List/non_ip/domestic.conf'), path.resolve(import.meta.dir, '../Clash/non_ip/domestic.txt') diff --git a/Build/build-sspanel-appprofile.ts b/Build/build-sspanel-appprofile.ts new file mode 100644 index 00000000..5a1009c7 --- /dev/null +++ b/Build/build-sspanel-appprofile.ts @@ -0,0 +1,184 @@ +import fsp from 'fs/promises'; + +import { getAppleCdnDomainsPromise } from './build-apple-cdn'; +import { getDomesticDomainsRulesetPromise } from './build-domestic-ruleset'; +import { surgeRulesetToClashClassicalTextRuleset } from './lib/clash'; +import { readFileByLine } from './lib/fetch-text-by-line'; +import { processLineFromReadline } from './lib/process-line'; +import { task } from './lib/trace-runner'; +import path from 'path'; + +import { ALL as AllStreamServices } from '../Source/stream'; +import { getChnCidrPromise } from './build-chn-cidr'; +import { getTelegramCIDRPromise } from './build-telegram-cidr'; +import { compareAndWriteFile } from './lib/create-file'; + +const POLICY_GROUPS: Array<[name: string, insertProxy: boolean, insertDirect: boolean]> = [ + ['Default Proxy', true, false], + ['Global', true, true], + ['Microsoft & Apple', true, true], + ['Stream', true, false], + ['Domestic', false, true], + ['Final Match', true, true] +]; + +const removeNoResolved = (line: string) => line.replace(',no-resolve', ''); + +/** + * This only generates a simplified version, for under-used users only. + */ +export const buildSSPanelUIMAppProfile = task(import.meta.path, async () => { + const [ + domesticDomains, + appleCdnDomains, + appleCnDomains, + neteaseMusicDomains, + microsoftDomains, + appleDomains, + streamDomains, + globalDomains, + globalPlusDomains, + telegramDomains, + domesticCidrs, + streamCidrs, + { results: rawTelegramCidrs } + ] = await Promise.all([ + // domestic - domains + getDomesticDomainsRulesetPromise().then(surgeRulesetToClashClassicalTextRuleset), + getAppleCdnDomainsPromise().then(domains => domains.map(domain => `DOMAIN-SUFFIX,${domain}`)), + processLineFromReadline(readFileByLine(path.resolve(import.meta.dir, '../Source/non_ip/apple_cn.conf'))), + processLineFromReadline(readFileByLine(path.resolve(import.meta.dir, '../Source/non_ip/neteasemusic.conf'))).then(surgeRulesetToClashClassicalTextRuleset), + // microsoft & apple - domains + processLineFromReadline(readFileByLine(path.resolve(import.meta.dir, '../Source/non_ip/internal_microsoft.conf'))), + (processLineFromReadline(readFileByLine(path.resolve(import.meta.dir, '../Source/non_ip/apple_services.conf')))).then(surgeRulesetToClashClassicalTextRuleset), + // stream - domains + surgeRulesetToClashClassicalTextRuleset(AllStreamServices.flatMap((i) => i.rules)), + // global - domains + processLineFromReadline(readFileByLine(path.resolve(import.meta.dir, '../Source/non_ip/global.conf'))).then(surgeRulesetToClashClassicalTextRuleset), + processLineFromReadline(readFileByLine(path.resolve(import.meta.dir, '../Source/non_ip/global_plus.conf'))).then(surgeRulesetToClashClassicalTextRuleset), + processLineFromReadline(readFileByLine(path.resolve(import.meta.dir, '../Source/non_ip/telegram.conf'))).then(surgeRulesetToClashClassicalTextRuleset), + // domestic - ip cidr + getChnCidrPromise().then(cidrs => cidrs.map(cidr => `IP-CIDR,${cidr}`)), + AllStreamServices.flatMap((i) => ( + i.ip + ? [ + ...i.ip.v4.map((ip) => `IP-CIDR,${ip}`), + ...i.ip.v6.map((ip) => `IP-CIDR6,${ip}`) + ] + : [] + )), + // global - ip cidr + getTelegramCIDRPromise() + ] as const); + + const telegramCidrs = rawTelegramCidrs.map(removeNoResolved); + + const output = generateAppProfile( + [ + ...domesticDomains, + ...appleCdnDomains, + ...appleCnDomains, + ...neteaseMusicDomains + ], + [ + ...microsoftDomains, + ...appleDomains + ], + streamDomains, + [ + ...globalDomains, + ...globalPlusDomains, + ...telegramDomains + ], + domesticCidrs, + streamCidrs, + [ + ...telegramCidrs + ] + ); + + await fsp.mkdir(path.resolve(import.meta.dir, '../List/internal'), { recursive: true }); + + await compareAndWriteFile( + output, + path.resolve(import.meta.dir, '../List/internal/appprofile.php') + ); +}); + +if (import.meta.main) { + buildSSPanelUIMAppProfile(); +} + +const isTruthy = (i: T | 0 | '' | false | null | undefined): i is T => !!i; + +function generateAppProfile( + directDomains: string[], + microsoftAppleDomains: string[], + streamDomains: string[], + globalDomains: string[], + directCidrs: string[], + streamCidrs: string[], + globalCidrs: string[] +) { + const result: string[] = []; + + result.push( + ' 7890,', + ' \'socks-port\' => 7891,', + ' \'allow-lan\' => false,', + ' \'mode\' => \'Rule\',', + ' \'ipv6\' => true,', + ' \'log-level\' => \'error\',', + ' \'external-controller\' => \'0.0.0.0:9090\',', + '];', + '', + `$_ENV['Clash_Group_Indexes'] = [${JSON.stringify(POLICY_GROUPS.reduce((acc, [, insertProxy], index) => { + if (insertProxy) { + acc.push(index); + } + return acc; + }, [])).slice(1, -1)}];`, + '$_ENV[\'Clash_Group_Config\'] = [', + ' \'proxy-groups\' => [', + ...POLICY_GROUPS.flatMap(([name, insertProxy, insertDirect]) => { + return [ + ' [', + ` 'name' => '${name}',`, + ' \'type\' => \'select\',', + ' \'proxies\' => [', + insertProxy && name !== 'Default Proxy' && ' \'Default Proxy\',', + insertDirect && ' \'DIRECT\',', + ' ],', + ' ],' + ].filter(isTruthy); + }), + ' ],', + ' \'rules\' => [', + // domestic - domains + ...directDomains.map(line => ` '${line},Domestic',`), + // microsoft & apple - domains + ...microsoftAppleDomains.map(line => ` '${line},Microsoft & Apple',`), + // stream - domains + ...streamDomains.map(line => ` '${line},Stream',`), + // global - domains + ...globalDomains.map(line => ` '${line},Global',`), + // domestic - ip cidr + ...directCidrs.map(line => ` '${line},Domestic,no-resolve',`), + // microsoft & apple - ip cidr (nope) + // stream - ip cidr + ...streamCidrs.map(line => ` '${line},Stream,no-resolve',`), + // global - ip cidr + ...globalCidrs.map(line => ` '${line},Global,no-resolve',`), + // match + ' \'MATCH,Final Match\',', + ' ],', + '];' + ); + + return result; +} diff --git a/Build/build-stream-service.ts b/Build/build-stream-service.ts index 4810acbd..2cf3e68f 100644 --- a/Build/build-stream-service.ts +++ b/Build/build-stream-service.ts @@ -7,7 +7,7 @@ import { createRuleset } from './lib/create-file'; import { ALL, NORTH_AMERICA, EU, HK, TW, JP, KR } from '../Source/stream'; import { SHARED_DESCRIPTION } from './lib/constants'; -const createRulesetForStreamService = (fileId: string, title: string, streamServices: Array) => { +export const createRulesetForStreamService = (fileId: string, title: string, streamServices: Array) => { return [ // Domains ...createRuleset( diff --git a/Build/build-telegram-cidr.ts b/Build/build-telegram-cidr.ts index 84b795d5..d7a1514e 100644 --- a/Build/build-telegram-cidr.ts +++ b/Build/build-telegram-cidr.ts @@ -7,8 +7,9 @@ import { processLine } from './lib/process-line'; import { createRuleset } from './lib/create-file'; import { task } from './lib/trace-runner'; import { SHARED_DESCRIPTION } from './lib/constants'; +import { createMemoizedPromise } from './lib/memo-promise'; -export const buildTelegramCIDR = task(import.meta.path, async () => { +export const getTelegramCIDRPromise = createMemoizedPromise(async () => { const resp = await fetchWithRetry('https://core.telegram.org/resources/cidr.txt', defaultRequestInit) as Response; const lastModified = resp.headers.get('last-modified'); const date = lastModified ? new Date(lastModified) : new Date(); @@ -28,6 +29,12 @@ export const buildTelegramCIDR = task(import.meta.path, async () => { } } + return { date, results }; +}); + +export const buildTelegramCIDR = task(import.meta.path, async () => { + const { date, results } = await getTelegramCIDRPromise(); + if (results.length === 0) { throw new Error('Failed to fetch data!'); } diff --git a/Build/index.ts b/Build/index.ts index d8597e4b..e8d8540c 100644 --- a/Build/index.ts +++ b/Build/index.ts @@ -14,6 +14,8 @@ import { buildStreamService } from './build-stream-service'; import { buildRedirectModule } from './build-redirect-module'; import { validate } from './validate-domainset'; +import { buildSSPanelUIMAppProfile } from './build-sspanel-appprofile'; + import { buildPublic } from './build-public'; // import type { TaskResult } from './lib/trace-runner'; @@ -64,6 +66,10 @@ import { buildPublic } from './build-public'; const buildRedirectModulePromise = downloadPreviousBuildPromise.then(() => buildRedirectModule()); const buildStreamServicePromise = downloadPreviousBuildPromise.then(() => buildStreamService()); + const buildSSPanelUIMAppProfilePromise = Promise.all([ + downloadPreviousBuildPromise + ]).then(() => buildSSPanelUIMAppProfile()); + const stats = await Promise.all([ downloadPreviousBuildPromise, downloadPublicSuffixListPromise, @@ -80,7 +86,8 @@ import { buildPublic } from './build-public'; // buildInternalChnDomainsPromise, buildDomesticRulesetPromise, buildRedirectModulePromise, - buildStreamServicePromise + buildStreamServicePromise, + buildSSPanelUIMAppProfilePromise ]); await Promise.all([ diff --git a/Build/lib/memo-promise.ts b/Build/lib/memo-promise.ts new file mode 100644 index 00000000..ff26f4c8 --- /dev/null +++ b/Build/lib/memo-promise.ts @@ -0,0 +1,7 @@ +export const createMemoizedPromise = (fn: () => Promise): () => Promise => { + let promise: Promise | null = null; + return () => { + promise ||= fn(); + return promise; + }; +}; diff --git a/Source/non_ip/internal_microsoft.conf b/Source/non_ip/internal_microsoft.conf new file mode 100644 index 00000000..1c1a8eaa --- /dev/null +++ b/Source/non_ip/internal_microsoft.conf @@ -0,0 +1,76 @@ +# $ custom_build_script + +DOMAIN,officecdn-microsoft-com.akamaized.net +DOMAIN-SUFFIX,aadrm.com +DOMAIN-SUFFIX,acompli.com +DOMAIN-SUFFIX,acompli.net +DOMAIN-SUFFIX,aka.ms +DOMAIN-SUFFIX,akadns.net +DOMAIN-SUFFIX,aspnetcdn.com +DOMAIN-SUFFIX,assets-yammer.com +DOMAIN-SUFFIX,azure.com +DOMAIN-SUFFIX,azure.net +DOMAIN-SUFFIX,azureedge.net +DOMAIN-SUFFIX,azurerms.com +DOMAIN-SUFFIX,bing.com +DOMAIN-SUFFIX,cloudapp.net +DOMAIN-SUFFIX,cloudappsecurity.com +DOMAIN-SUFFIX,edgesuite.net +DOMAIN-SUFFIX,gfx.ms +DOMAIN-SUFFIX,hotmail.com +DOMAIN-SUFFIX,live.com +DOMAIN-SUFFIX,live.net +DOMAIN-SUFFIX,lync.com +DOMAIN-SUFFIX,msappproxy.net +DOMAIN-SUFFIX,msauth.net +DOMAIN-SUFFIX,msauthimages.net +DOMAIN-SUFFIX,msecnd.net +DOMAIN-SUFFIX,msedge.net +DOMAIN-SUFFIX,msft.net +DOMAIN-SUFFIX,msftauth.net +DOMAIN-SUFFIX,msftauthimages.net +DOMAIN-SUFFIX,msftidentity.com +DOMAIN-SUFFIX,msidentity.com +DOMAIN-SUFFIX,msn.com +DOMAIN-SUFFIX,msocdn.com +DOMAIN-SUFFIX,msocsp.com +DOMAIN-SUFFIX,mstea.ms +DOMAIN-SUFFIX,o365weve.com +DOMAIN-SUFFIX,oaspapps.com +DOMAIN-SUFFIX,office.com +DOMAIN-SUFFIX,office.net +DOMAIN-SUFFIX,office365.com +DOMAIN-SUFFIX,officeppe.net +DOMAIN-SUFFIX,omniroot.com +DOMAIN-SUFFIX,onedrive.com +DOMAIN-SUFFIX,onenote.com +DOMAIN-SUFFIX,onenote.net +DOMAIN-SUFFIX,onestore.ms +DOMAIN-SUFFIX,outlook.com +DOMAIN-SUFFIX,outlookmobile.com +DOMAIN-SUFFIX,phonefactor.net +DOMAIN-SUFFIX,public-trust.com +DOMAIN-SUFFIX,sfbassets.com +DOMAIN-SUFFIX,sfx.ms +DOMAIN-SUFFIX,sharepoint.com +DOMAIN-SUFFIX,sharepointonline.com +DOMAIN-SUFFIX,skype.com +DOMAIN-SUFFIX,skypeassets.com +DOMAIN-SUFFIX,skypeforbusiness.com +DOMAIN-SUFFIX,staffhub.ms +DOMAIN-SUFFIX,svc.ms +DOMAIN-SUFFIX,sway-cdn.com +DOMAIN-SUFFIX,sway-extensions.com +DOMAIN-SUFFIX,sway.com +DOMAIN-SUFFIX,trafficmanager.net +DOMAIN-SUFFIX,uservoice.com +DOMAIN-SUFFIX,virtualearth.net +DOMAIN-SUFFIX,visualstudio.com +DOMAIN-SUFFIX,windows-ppe.net +DOMAIN-SUFFIX,windows.com +DOMAIN-SUFFIX,windows.net +DOMAIN-SUFFIX,windowsazure.com +DOMAIN-SUFFIX,windowsupdate.com +DOMAIN-SUFFIX,wunderlist.com +DOMAIN-SUFFIX,yammer.com +DOMAIN-SUFFIX,yammerusercontent.com