feat: 规则分类+全局规则组

This commit is contained in:
lisonge 2023-12-25 16:46:26 +08:00
parent 81a820d4b2
commit 623bcbd29b
6 changed files with 281 additions and 171 deletions

View File

@ -1,6 +1,6 @@
import fs from 'node:fs/promises'; import fs from 'node:fs/promises';
import url from 'node:url'; import url from 'node:url';
import type { AppConfigMudule } from '../src/types'; import type { RawApp } from '../src/types';
import { tryRun } from '../src/utils'; import { tryRun } from '../src/utils';
// 使用命令更新内存订阅 // 使用命令更新内存订阅
@ -36,7 +36,7 @@ if (!(await fs.stat(tsFp).catch(() => false))) {
} }
const getAppConfig = async () => { const getAppConfig = async () => {
const mod: AppConfigMudule = await import(url.pathToFileURL(tsFp).href); const mod: { default: RawApp } = await import(url.pathToFileURL(tsFp).href);
return mod.default; return mod.default;
}; };

30
src/categories.ts Normal file
View File

@ -0,0 +1,30 @@
import type { RawCategory } from './types';
const categories: RawCategory[] = [
{
key: 0,
name: '开屏广告',
},
{
key: 1,
name: '青少年模式',
},
{
key: 2,
name: '更新提示',
},
{
key: 3,
name: '评价提示',
},
{
key: 4,
name: '通知提示',
},
{
key: 5,
name: '定位提示',
},
];
export default categories;

View File

@ -2,13 +2,15 @@ import path from 'node:path';
import url from 'node:url'; import url from 'node:url';
import picocolors from 'picocolors'; import picocolors from 'picocolors';
import { walk } from './file'; import { walk } from './file';
import type { AppConfig, AppConfigMudule, SubscriptionConfig } from './types'; import type { RawApp, RawSubscription } from './types';
import _ from 'lodash'; import _ from 'lodash';
import { pinyin } from 'pinyin-pro'; import { pinyin } from 'pinyin-pro';
import globalGroups from './globalGroups';
import categories from './categories';
const apps: AppConfig[] = []; const apps: RawApp[] = [];
for await (const tsFp of walk(process.cwd() + '/src/apps')) { for await (const tsFp of walk(process.cwd() + '/src/apps')) {
const mod: AppConfigMudule = await import(url.pathToFileURL(tsFp).href); const mod: { default: RawApp } = await import(url.pathToFileURL(tsFp).href);
const appConfig = mod.default; const appConfig = mod.default;
if (path.basename(tsFp, '.ts') != appConfig.id) { if (path.basename(tsFp, '.ts') != appConfig.id) {
throw new Error( throw new Error(
@ -27,16 +29,23 @@ for await (const tsFp of walk(process.cwd() + '/src/apps')) {
apps.push(appConfig); apps.push(appConfig);
} }
const subsConfig: SubscriptionConfig = { const subsConfig: RawSubscription = {
id: 0, id: 0,
version: 0,
name: '默认订阅', name: '默认订阅',
author: 'lisonge', author: 'lisonge',
supportUri: 'https://github.com/gkd-kit/subscription', supportUri: 'https://github.com/gkd-kit/subscription',
checkUpdateUrl: checkUpdateUrl:
'https://registry.npmmirror.com/@gkd-kit/subscription/latest/files/dist/gkd.version.json', 'https://registry.npmmirror.com/@gkd-kit/subscription/latest/files/dist/gkd.version.json',
globalGroups,
categories,
apps: _.sortBy(apps, (a) => { apps: _.sortBy(apps, (a) => {
const pyName = pinyin(a.name, { separator: '', toneType: 'none' }); const showName = a.name || a.id;
if (pyName === a.name) return a.name; const pyName = pinyin(showName, {
separator: '',
toneType: 'none',
});
if (pyName === showName) return showName;
return '\uFFFF' + pyName; // 让带拼音的全排在后面 return '\uFFFF' + pyName; // 让带拼音的全排在后面
}), }),
}; };

View File

@ -3,19 +3,14 @@ import fs from 'node:fs/promises';
import path from 'node:path'; import path from 'node:path';
import type PkgT from '../package.json'; import type PkgT from '../package.json';
import { parseSelector } from './selector'; import { parseSelector } from './selector';
import type { import type { RawApp, RawAppGroup, IArray, RawSubscription } from './types';
AppConfig,
GroupConfig,
IArray,
SubscriptionConfig,
} from './types';
import JSON5 from 'json5'; import JSON5 from 'json5';
const iArrayToArray = <T>(array: IArray<T> = []): T[] => { const iArrayToArray = <T>(array: IArray<T> = []): T[] => {
return Array<T>().concat(array); return Array<T>().concat(array);
}; };
const sortKeys: (keyof SubscriptionConfig)[] = [ const sortKeys: (keyof RawSubscription)[] = [
'id', 'id',
'name', 'name',
'version', 'version',
@ -61,14 +56,14 @@ const pkg: typeof PkgT = JSON.parse(
); );
const pkgKeys = Object.keys(pkg); const pkgKeys = Object.keys(pkg);
export const writeConfig = async (config: SubscriptionConfig) => { export const writeConfig = async (config: RawSubscription) => {
const gkdFp = process.cwd() + '/dist/gkd.json5'; const gkdFp = process.cwd() + '/dist/gkd.json5';
const versionFp = process.cwd() + '/dist/gkd.version.json'; const versionFp = process.cwd() + '/dist/gkd.version.json';
const oldConfig: SubscriptionConfig = JSON5.parse( const oldConfig: RawSubscription = JSON5.parse(
await fs.readFile(gkdFp, 'utf-8').catch(() => '{}'), await fs.readFile(gkdFp, 'utf-8').catch(() => '{}'),
); );
const newConfig: SubscriptionConfig = { const newConfig: RawSubscription = {
...config, ...config,
version: oldConfig.version || 0, version: oldConfig.version || 0,
}; };
@ -137,7 +132,7 @@ export const validSnapshotUrl = (s: string) => {
return u.pathname.startsWith('/import/'); return u.pathname.startsWith('/import/');
}; };
export const checkConfig = (newConfig: SubscriptionConfig) => { export const checkConfig = (newConfig: RawSubscription) => {
// check duplicated group key // check duplicated group key
newConfig.apps?.forEach((app) => { newConfig.apps?.forEach((app) => {
const deprecatedKeys = app.deprecatedKeys || []; const deprecatedKeys = app.deprecatedKeys || [];
@ -246,7 +241,7 @@ export const checkConfig = (newConfig: SubscriptionConfig) => {
}); });
}); });
}); });
const newKeys = Object.keys(newConfig) as (keyof SubscriptionConfig)[]; const newKeys = Object.keys(newConfig) as (keyof RawSubscription)[];
if (newKeys.some((s) => !sortKeys.includes(s))) { if (newKeys.some((s) => !sortKeys.includes(s))) {
console.log({ console.log({
sortKeys, sortKeys,
@ -256,7 +251,7 @@ export const checkConfig = (newConfig: SubscriptionConfig) => {
} }
}; };
export const updateAppMd = async (app: AppConfig) => { export const updateAppMd = async (app: RawApp) => {
const appHeadMdText = [ const appHeadMdText = [
`# ${app.name}`, `# ${app.name}`,
`存在 ${app.groups?.length || 0} 规则组 - [${app.id}](/src/apps/${ `存在 ${app.groups?.length || 0} 规则组 - [${app.id}](/src/apps/${
@ -322,14 +317,14 @@ export const updateAppMd = async (app: AppConfig) => {
}; };
const getAppDiffLog = ( const getAppDiffLog = (
oldGroups: GroupConfig[] = [], oldGroups: RawAppGroup[] = [],
newGroups: GroupConfig[] = [], newGroups: RawAppGroup[] = [],
) => { ) => {
const removeGroups = oldGroups.filter( const removeGroups = oldGroups.filter(
(og) => !newGroups.find((ng) => ng.key == og.key), (og) => !newGroups.find((ng) => ng.key == og.key),
); );
const addGroups: GroupConfig[] = []; const addGroups: RawAppGroup[] = [];
const changeGroups: GroupConfig[] = []; const changeGroups: RawAppGroup[] = [];
newGroups.forEach((ng) => { newGroups.forEach((ng) => {
const oldGroup = oldGroups.find((og) => og.key == ng.key); const oldGroup = oldGroups.find((og) => og.key == ng.key);
if (oldGroup) { if (oldGroup) {
@ -348,21 +343,21 @@ const getAppDiffLog = (
}; };
type AppDiff = { type AppDiff = {
app: AppConfig; app: RawApp;
addGroups: GroupConfig[]; addGroups: RawAppGroup[];
changeGroups: GroupConfig[]; changeGroups: RawAppGroup[];
removeGroups: GroupConfig[]; removeGroups: RawAppGroup[];
}; };
export const updateReadMeMd = async ( export const updateReadMeMd = async (
newConfig: SubscriptionConfig, newConfig: RawSubscription,
oldConfig: SubscriptionConfig, oldConfig: RawSubscription,
) => { ) => {
let changeCount = 0; let changeCount = 0;
const appDiffs: AppDiff[] = []; const appDiffs: AppDiff[] = [];
await Promise.all( await Promise.all(
newConfig.apps.map(async (app) => { newConfig.apps!.map(async (app) => {
const oldApp = oldConfig.apps.find((a) => a.id == app.id); const oldApp = oldConfig.apps!.find((a) => a.id == app.id);
if (!_.isEqual(oldApp, app)) { if (!_.isEqual(oldApp, app)) {
changeCount++; changeCount++;
await updateAppMd(app); await updateAppMd(app);
@ -423,19 +418,19 @@ export const updateReadMeMd = async (
const appListText = const appListText =
'| 名称 | ID | 规则组 |\n| - | - | - |\n' + '| 名称 | ID | 规则组 |\n| - | - | - |\n' +
newConfig.apps newConfig
.map((app) => { .apps!.map((app) => {
const groups = app.groups || []; const groups = app.groups || [];
return `| ${app.name} | [${app.id}](/docs/${app.id}.md) | ${groups.length} |`; return `| ${app.name} | [${app.id}](/docs/${app.id}.md) | ${groups.length} |`;
}) })
.join('\n'); .join('\n');
const mdTemplate = await fs.readFile(process.cwd() + '/Template.md', 'utf-8'); const mdTemplate = await fs.readFile(process.cwd() + '/Template.md', 'utf-8');
const readMeMdText = mdTemplate const readMeMdText = mdTemplate
.replaceAll('--APP_SIZE--', newConfig.apps.length.toString()) .replaceAll('--APP_SIZE--', newConfig.apps!.length.toString())
.replaceAll( .replaceAll(
'--GROUP_SIZE--', '--GROUP_SIZE--',
newConfig.apps newConfig
.reduce((p, c) => p + (c.groups?.length || 0), 0) .apps!.reduce((p, c) => p + (c.groups?.length || 0), 0)
.toString(), .toString(),
) )
.replaceAll('--VERSION--', (newConfig.version || 0).toString()); .replaceAll('--VERSION--', (newConfig.version || 0).toString());

42
src/globalGroups.ts Normal file
View File

@ -0,0 +1,42 @@
import type { RawGlobalGroup } from './types';
const globalGroups: RawGlobalGroup[] = [
{
key: 0,
name: '开屏广告',
actionMaximum: 1,
matchTime: 10000,
resetMatch: 'app',
actionCdKey: 0,
actionMaximumKey: 0,
rules: [
{
key: 0,
quickFind: true,
matches: '[text*="跳过"][text.length<10]',
action: 'clickCenter',
},
{
key: 1,
matches:
'[id*="skip"||((text*="跳过"||text*="skip")&&text.length<10)||desc*="skip"||desc*="跳过"][editable=false]',
action: 'clickCenter',
},
],
apps: [
{
id: 'com.android.systemui',
enable: false,
},
{
id: 'com.miui.aod',
enable: false,
},
{
id: 'com.miui.home',
enable: false,
},
],
},
];
export default globalGroups;

View File

@ -1,45 +1,13 @@
export type IArray<T> = T | T[]; export type IArray<T> = T | T[];
/** type RawCommonProps = {
* undefined 使, rule.cd undefined, rule.cd 使 group.cd
*/
type CommonProps = {
/**
* Id startWith activityIds ,
*
* : `undefined` () `[]` (使)
*/
activityIds?: IArray<string>;
/**
* @deprecated v1.5.0
*
* activityId, activityId /
*
* 广 , activityId
*
*/
matchLauncher?: boolean;
/**
* Id startWith excludeActivityIds , ,
*/
excludeActivityIds?: IArray<string>;
/** /**
* 单位: 毫秒 * 单位: 毫秒
* *
* , action * , action
*
*/ */
actionCd?: number; actionCd?: number;
/**
* @deprecated
* 使 actionCd
*/
cd?: number;
/** /**
* 单位: 毫秒 * 单位: 毫秒
* *
@ -48,17 +16,12 @@ type CommonProps = {
*/ */
actionDelay?: number; actionDelay?: number;
/**
* @deprecated
* 使 actionDelay
*/
delay?: number;
/** /**
* *
* , `末尾属性选择器``第一个属性选择表达式` 使 * , `末尾属性选择器``第一个属性选择表达式` 使
* *
* - [id='abc'] * - [id='abc']
* - [vid='abc']
* - [text='abc'] * - [text='abc']
* - [text^='abc'] * - [text^='abc']
* - [text*='abc'] * - [text*='abc']
@ -134,74 +97,38 @@ type CommonProps = {
*/ */
resetMatch?: 'activity' | 'app'; resetMatch?: 'activity' | 'app';
// 暂未支持
filter?: {
/**
* 使id, QQ/, id只在相邻几个版本可使用
*/
appVersionCode?: unknown;
screenHeight?: unknown;
screenWidth?: unknown;
isLandscape?: boolean;
};
};
export type AppConfig = {
id: string;
/** /**
* APP, 使 name * key rule cd
*/
name: string;
groups?: GroupConfig[];
/**
* 使, key , key 使
*/
deprecatedKeys?: number[];
} & CommonProps;
export type AppConfigMudule = {
default: AppConfig;
};
export type GroupConfig = {
/**
* \
* /
*/
key: number;
name: string;
desc?: string;
/**
* ,
* *
* , 广, * 广 rule , rule , rule
*/
enable?: boolean;
/**
* string => { matches: string }
* *
* string[] => { matches: string }[] * key rule actionCd=3000, rule rule , 3000 rule cd
*/ */
rules?: IArray<RuleConfig | string>; actionCdKey?: number;
/** /**
* / , , * key rule
*
* 广 rule , rule , rule
*
* key rule actionMaximum=0, rule rule , rule
*/
actionMaximumKey?: number;
/**
* / ,
*/ */
snapshotUrls?: IArray<string>; snapshotUrls?: IArray<string>;
/** /**
* / , gif/mp4 * / , jpg/png/webp/gif
* *
* , , -- 广 gif * , , -- 广 gif
*/ */
exampleUrls?: IArray<string>; exampleUrls?: IArray<string>;
} & CommonProps; };
type RuleConfig = { type RawRuleProps = RawCommonProps & {
/** /**
* *
*/ */
@ -209,16 +136,6 @@ type RuleConfig = {
name?: string; name?: string;
/**
* GKD , ,
*/
matches?: IArray<string>;
/**
* GKD , ,
*/
excludeMatches?: IArray<string>;
/** /**
* key * key
* *
@ -228,8 +145,6 @@ type RuleConfig = {
* *
* , , 广 * , , 广
* *
*
*
*/ */
preKeys?: IArray<number>; preKeys?: IArray<number>;
@ -276,60 +191,182 @@ type RuleConfig = {
| 'longClickCenter'; | 'longClickCenter';
/** /**
* key rule * GKD , ,
*
* 广 rule , rule , rule
*
* key rule actionMaximum=1, rule rule , rule
*/ */
actionMaximumKey?: number; matches?: IArray<string>;
/** /**
* key rule cd * GKD , ,
*
* 广 rule , rule , rule
*
* key rule actionCd=3000, rule rule , 3000 rule cd
*/ */
actionCdKey?: number; excludeMatches?: IArray<string>;
};
snapshotUrls?: IArray<string>; type RawGroupProps = RawCommonProps & {
exampleUrls?: IArray<string>; /**
} & CommonProps; *
*
* /
*/
key: number;
export type SubscriptionConfig = { name: string;
desc?: string;
/**
* ,
*
* , 广,
*/
enable?: boolean;
// rules: RawRuleProps[];
};
type RawAppRuleProps = {
/**
* Id startWith activityIds ,
*
* : `undefined` () `[]` (使)
*/
activityIds?: IArray<string>;
/**
* Id startWith excludeActivityIds ,
*
* activityIds
*/
excludeActivityIds?: IArray<string>;
};
// <--全局规则相关--
type RawGlobalApp = RawAppRuleProps & {
id: string;
/**
* : `true`
*
* true => APP
*
* false => APP
*/
enable?: boolean;
};
type RawGlobalRuleProps = {
/**
* : `true`
*
* true => APP
*
* false => apps app
*/
matchAnyApp?: boolean;
apps?: RawGlobalApp[];
};
type RawGlobalRule = RawRuleProps & RawGlobalRuleProps;
export type RawGlobalGroup = RawGroupProps &
RawGlobalRuleProps & {
apps: RawGlobalApp[];
rules: RawGlobalRule[];
};
// --全局规则相关-->
// <--APP规则相关--
export type RawCategory = {
/**
*
*
* /
*/
key: number;
/**
*
*
* , name APP ,
*
* : `开屏广告` `开屏广告-1` `开屏广告-2` `开屏广告-233` APP
*/
name: string;
/**
* null => enable
*
* true =>
*
* false =>
*/
enable?: boolean;
};
type RawAppRule = RawRuleProps & RawAppRuleProps;
export type RawAppGroup = RawGroupProps &
RawAppRuleProps & {
/**
* string => { matches: string }
*
* string[] => { matches: string }[]
*/
rules: IArray<RawAppRule | string>;
};
export type RawApp = {
id: string;
/**
* APP, 使 name
*/
name?: string;
groups: RawAppGroup[];
/**
* 使, key , key 使
*/
deprecatedKeys?: number[];
};
// --APP规则相关-->
export type RawSubscription = {
/** /**
* , id不一致则更新失败\ * , id不一致则更新失败\
* : `[0, Number.MAX_SAFE_INTEGER]`\ * : `[0, Number.MAX_SAFE_INTEGER]`\
* : `new Date().getTime()` * : `new Date().getTime()`
* *
* 0, id APP 使, APP id * GKD默认订阅0, id APP 使, APP id
* *
* APP 使, -2, -1 * APP 使, -2, -1
*/ */
id: number; id: number;
/** /**
* *
*/ */
name: string; name: string;
/** /**
* , ? version ts * ,
* *
* version version * version version
*/ */
version?: number; version: number;
author?: string; author?: string;
/** /**
* APP , version APP version , * GKD , version APP version ,
* *
* , 使 * , 使
*/ */
updateUrl?: string; updateUrl?: string;
/**
* uri , [],
*
* , APP uri
*/
supportUri?: string;
/** /**
* id version json , , , id version , updateUrl * id version json , , , id version , updateUrl
* *
@ -337,18 +374,15 @@ export type SubscriptionConfig = {
*/ */
checkUpdateUrl?: string; checkUpdateUrl?: string;
/** apps?: RawApp[];
* https url, custom android schema url categories?: RawCategory[];
*/ globalGroups?: RawGlobalGroup[];
supportUri?: string;
apps: AppConfig[];
}; };
export const defineSubsConfig = (config: SubscriptionConfig) => { export const defineSubsConfig = (config: RawSubscription) => {
return JSON.stringify(config, undefined, 2); return JSON.stringify(config, undefined, 2);
}; };
export const defineAppConfig = (config: AppConfig) => { export const defineAppConfig = (config: RawApp) => {
return config; return config;
}; };