mirror of
https://gitea.redwind.top/Austin/subscription
synced 2024-11-16 04:12:21 +08:00
feat: 规则分类+全局规则组
This commit is contained in:
parent
81a820d4b2
commit
623bcbd29b
|
@ -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
30
src/categories.ts
Normal 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;
|
|
@ -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; // 让带拼音的全排在后面
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
55
src/file.ts
55
src/file.ts
|
@ -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
42
src/globalGroups.ts
Normal 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;
|
300
src/types.ts
300
src/types.ts
|
@ -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只在相邻几个版本可使用
|
* 与这个 key 的 rule 共享 cd
|
||||||
*/
|
|
||||||
appVersionCode?: unknown;
|
|
||||||
screenHeight?: unknown;
|
|
||||||
screenWidth?: unknown;
|
|
||||||
isLandscape?: boolean;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export type AppConfig = {
|
|
||||||
id: string;
|
|
||||||
/**
|
|
||||||
* 如果设备没有安装这个 APP, 则使用这个 name 显示
|
|
||||||
*/
|
|
||||||
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;
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue
Block a user