Skip to content

Commit

Permalink
feat: quantumultx plugin
Browse files Browse the repository at this point in the history
Update template.ts
  • Loading branch information
VirgilClyne committed Oct 21, 2024
1 parent 2a39aae commit e13c548
Show file tree
Hide file tree
Showing 7 changed files with 282 additions and 0 deletions.
13 changes: 13 additions & 0 deletions packages/modkit/plugins/quantumultx/modern.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import fs from 'node:fs';
import { dualBuildConfigs } from '@iringo/modkit-config/modern.config.ts';
import { defineConfig, moduleTools } from '@modern-js/module-tools';

export default defineConfig({
plugins: [moduleTools()],
buildConfig: dualBuildConfigs.map((item) => ({
...item,
define: {
'process.env.TEMP': fs.readFileSync('./template.ejs', 'utf-8'),
},
})),
});
27 changes: 27 additions & 0 deletions packages/modkit/plugins/quantumultx/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"name": "@iringo/modkit-plugin-quantumultx",
"version": "0.0.1",
"description": "",
"main": "dist/index.js",
"module": "dist/index.mjs",
"types": "dist/index.d.ts",
"scripts": {
"dev": "modern build -w",
"build": "modern build"
},
"files": ["dist", "types", "CHANGELOG.md"],
"dependencies": {
"@iringo/modkit-shared": "workspace:^"
},
"devDependencies": {
"@iringo/modkit-config": "workspace:^",
"@modern-js/module-tools": "^2.60.2",
"@types/node": "^20.0.0",
"typescript": "^5.6.2"
},
"repository": {
"type": "git",
"url": "https://github.com/NSRingo/engineering-solutions",
"directory": "packages/modkit/apps/quantumultx"
}
}
45 changes: 45 additions & 0 deletions packages/modkit/plugins/quantumultx/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import type { ModkitPlugin } from '@iringo/modkit-shared';
import { QuantumultxTemplate } from './template';

export interface QuantumultxPluginOptions {
objectValuesHandler?: (obj: Record<string, any>) => string;
}

export const pluginQuantumultx = ({
objectValuesHandler = (obj) => {
if (Array.isArray(obj)) {
return `"${obj.join()}"`;
}
return `"${JSON.stringify(obj)}"`;
},
}: QuantumultxPluginOptions = {}): ModkitPlugin => {
return {
name: 'quantumultx',
setup() {
return {
configurePlatform() {
return {
extension: '.snippet',
template: process.env.TEMP || '',
};
},
modifySource({ source }) {
source ??= {};
source.arguments = source.arguments?.filter((item) => {
if (typeof item.type === 'object' && item.type.quantumultx === 'exclude') {
return false;
}
return true;
});
return source;
},
templateParameters(params) {
const quantumultxTemplate = new QuantumultxTemplate(params, objectValuesHandler);
return {
quantumultxTemplate,
};
},
};
},
};
};
144 changes: 144 additions & 0 deletions packages/modkit/plugins/quantumultx/src/template.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import { type RuleType, Template, logger, objectEntries, toKebabCase } from '@iringo/modkit-shared';

const ruleTypeMap: Record<RuleType, string> = {
DOMAIN: 'host',
'DOMAIN-SUFFIX': 'host-suffix',
'DOMAIN-KEYWORD': 'host-keyword',
GEOIP: 'geoip',
'USER-AGENT': 'user-agent',
'IP-CIDR': 'ip-cidr',
'IP-CIDR6': 'ip6-cidr',
'PROCESS-NAME': 'process',
};

export class QuantumultxTemplate extends Template {
get Metadata() {
const result: Record<string, string | undefined> = {};
result.name = this.metadata.name;
result.desc = this.metadata.description;
result.system = this.metadata.system?.join();
result.version = this.metadata.version;
Object.entries(this.metadata.extra || {}).forEach(([key, value]) => {
result[key] = Array.isArray(value) ? value.join(',') : value;
});
return this.renderKeyValuePairs(result, { prefix: '#!' });
}

get Rule() {
const filters: string[] = [];
this.content.rule?.forEach((rule) => {
if (typeof rule === 'string') {
return rule;
}
const { type, content, policyName, ...rest } = rule;
const options = [];
if (ruleTypeMap[type]) {
options.push(ruleTypeMap[type]);
} else {
logger.warn(`[Quantumult X] Unsupported rule type: ${type}`);
}
options.push(content);
options.push(policyName);
filters.push(options.join(', '));
});
return filters.join('\n').trim();
}

get Rewrite() {
const rewrites: string[] = [];
this.content.rewrite?.forEach((rewrite) => {
let { type, pattern, mode, content, ...rest } = rewrite;
switch (rewrite.mode) {
case 'header':
logger.warn('[Quantumult X] Unsupported rewrite mode: header');
break;
case 'header-add':
mode = 'request-header';
break;
case 'header-del':
mode = 'request-header';
content += " ''";
break;
case undefined:
switch (type) {
case 'http-request':
mode = 'request-body';
break;
case 'http-response':
mode = 'response-body';
break;
}
break;
}
const options = [];
options.push(pattern);
options.push('url');
options.push(mode);
options.push(content || '');
rewrites.push(options.join(' '));
});
return `${rewrites.join('\n').trim()}\n${this.#script.rewrite}`;
}

get #script() {
const rewrites: string[] = [];
const tasks: string[] = [];
this.content.script?.forEach((script, index) => {
let { type, pattern, cronexp, scriptKey, argument, injectArgument, name, ...rest } = script;
switch (type) {
case 'http-request':
type = 'script-request';
break;
case 'http-response':
type = 'script-response';
break;
case 'generic':
// 没找到示例
break;
case 'event':
// 没找到示例
break;
case 'dns':
logger.warn('[Quantumult X] Unsupported script type: dns');
break;
}
const parameters: Record<string, any> = {};
parameters['script-path'] = this.utils.getScriptPath(scriptKey);
parameters.tag = name || `Script${index}`;
const options = [];
switch (type) {
case 'script-request':
case 'script-response':
options.push(pattern);
options.push('url');
options.push(type);
options.push(parameters['script-path']);
rewrites.push(`# ${parameters.tag}\n${options.join(' ')}`);
break;
case 'cron': {
options.push(cronexp);
options.push(parameters['script-path']);
const option = [options.join(' ')];
if (name) {
option.push(`tag = ${name}`);
}
if (imgUrl) {
option.push(`img-url = ${imgUrl}`);
}
if (enabled) {
option.push(`enabled = ${enabled}`);
}
tasks.push(option.join(', '));
break;
}
}
});
const rewrite = rewrites.join('\n').trim();
const task = tasks.join('\n').trim();
return { rewrite, task };
}

get MITM() {
return this.content.mitm?.hostname?.join(', ');
}
}
26 changes: 26 additions & 0 deletions packages/modkit/plugins/quantumultx/template.ejs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<%= quantumultxTemplate.Metadata %>

<% if (quantumultxTemplate.Host) { %>
#[dns]
<%= quantumultxTemplate.Host %>
<% } %>

<% if (quantumultxTemplate.Rule) { %>
#[filter_local]
<%= quantumultxTemplate.Rule %>
<% } %>

<% if (quantumultxTemplate.Rewrite) { %>
#[rewrite_local]
<%= quantumultxTemplate.Rewrite %>
<% } %>

<% if (quantumultxTemplate.Script) { %>
#[rewrite_local]
<%= quantumultxTemplate.Script %>
<% } %>

<% if (quantumultxTemplate.MITM) { %>
#[mitm]
<%= quantumultxTemplate.MITM %>
<% } %>
8 changes: 8 additions & 0 deletions packages/modkit/plugins/quantumultx/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"extends": "@iringo/modkit-config/tsconfig",
"compilerOptions": {
"outDir": "./dist",
"baseUrl": "./"
},
"include": ["src", "types"]
}
19 changes: 19 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit e13c548

Please sign in to comment.