Skip to content

Commit

Permalink
feat: 支持给 qx 远程 script 添加 device id
Browse files Browse the repository at this point in the history
  • Loading branch information
geekdada committed Nov 23, 2019
1 parent ae31073 commit e9b9790
Show file tree
Hide file tree
Showing 11 changed files with 203 additions and 93 deletions.
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
**/*.ts
/build/
/test/asset
1 change: 1 addition & 0 deletions lib/gateway/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export const createKoaApp = (surgioServer: SurgioServer): SurgioKoaApplication =
});
router.get('/get-artifact/:name', authMiddleware(), prepareArtifact(), surgioServer.koaGetArtifact.bind(surgioServer));
router.get('/list-artifact', authMiddleware(), surgioServer.koaListArtifact.bind(surgioServer));
router.get('/qx-script', surgioServer.processQuanXScript.bind(surgioServer));
router.get('/robot.txt', ctx => {
ctx.body = 'User-agent: *\n' +
'Disallow: /';
Expand Down
36 changes: 33 additions & 3 deletions lib/gateway/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,17 @@ import fs from 'fs-extra';
import nunjucks, { Environment } from 'nunjucks';
import path from "path";
import { PackageJson } from 'type-fest';
import url from 'url';
import legacyUrl from 'url';
import axios from 'axios';
import TransfromScript from './utils/transform-script';

import getEngine from '../template';
import { ArtifactConfig, CommandConfig, RemoteSnippet } from '../types';
import { getDownloadUrl, loadRemoteSnippetList } from '../utils';
import * as filters from '../utils/filter';
import { generate } from '../generate';
import { FcResponse } from './types';
import ReadableStream = NodeJS.ReadableStream;

export class Server {
public static getEditUrl(repository: PackageJson['repository'], p: string): string {
Expand All @@ -22,7 +25,7 @@ export class Server {
repository :
repository.url;

return url.resolve(base.endsWith('/') ? base : `${base}/`, p);
return legacyUrl.resolve(base.endsWith('/') ? base : `${base}/`, p);
} else {
return '';
}
Expand Down Expand Up @@ -141,6 +144,33 @@ export class Server {
});
}

public async processQuanXScript(ctx: Context): Promise<void> {
const { url, id: idFromUrl } = ctx.query;
const idFromConfig = this.config?.quantumultXConfig?.deviceIds;
const deviceIds = idFromUrl ? idFromUrl.split(',') : (idFromConfig || []);

if (!url) {
ctx.throw(400, 'invalid url');
return;
}

const content = await axios.request<ReadableStream>({
url,
method: 'get',
responseType: 'stream',
})
.catch(err => {
throw createError(400, `请求文件时出错: ${err.message}`);
});

const body = new TransfromScript(deviceIds);

content.data.pipe(body);

ctx.set('cache-control', 'max-age=3600');
ctx.body = body;
}

// istanbul ignore next
public fcErrorHandler(response: FcResponse, err: Error): void {
response.setStatusCode(500);
Expand Down Expand Up @@ -226,7 +256,7 @@ export class Server {
artifactList: this.artifactList,
getPreviewUrl: (name: string) => getDownloadUrl(this.config.urlBase, name),
getDownloadUrl: (name: string) => (
url.format({
legacyUrl.format({
pathname: '/gateway.js',
query: {
name,
Expand Down
25 changes: 25 additions & 0 deletions lib/gateway/utils/transform-script.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import stream from 'stream';

export default class TransformScript extends stream.Transform {
private hasInserted: boolean = false;

constructor(private deviceIds: ReadonlyArray<string>) {
super();
}

public _transform(data, _, cb): void {
const insertion = '' +
'/**\n' +
' * @supported ' + `${this.deviceIds.join(' ')}` + '\n' +
' * THIS COMMENT IS GENERATED BY SURGIO\n' +
' */\n\n';

if (!this.hasInserted) {
this.push(insertion);
this.hasInserted = true;
}

this.push(data);
cb();
}
}
3 changes: 3 additions & 0 deletions lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ export interface CommandConfig {
readonly v2ray?: 'native'|'external';
readonly resolveHostname?: boolean;
};
readonly quantumultXConfig?: {
readonly deviceIds?: ReadonlyArray<string>;
};
readonly gateway?: {
readonly accessToken?: string;
readonly auth?: boolean;
Expand Down
3 changes: 3 additions & 0 deletions lib/utils/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,9 @@ export const validateConfig = (userConfig: Partial<CommandConfig>): void => {
v2ray: Joi.string().valid('native', 'external'),
resolveHostname: Joi.boolean(),
}),
quantumultXConfig: Joi.object({
deviceIds: Joi.array().items(Joi.string()),
}),
analytics: Joi.boolean(),
gateway: Joi.object({
accessToken: Joi.string(),
Expand Down
5 changes: 5 additions & 0 deletions test/fixture/gateway/surgio.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ module.exports = {
surgeConfig: {
v2ray: 'native',
},
quantumultXConfig: {
deviceIds: [
'55BE3B10F8A1',
],
},
customFilters: {
globalFilter: node => node.nodeName === '🇺🇲 US',
},
Expand Down
20 changes: 20 additions & 0 deletions test/gateway/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,3 +164,23 @@ test('auth', async t => {
.get('/list-artifact?access_token=abcd')
.expect(200);
});

test.only('qx-script', async t => {
const fixture = path.join(__dirname, '../fixture/gateway');
const surgioServer = gateway.createSurgioServer(fixture);
const app = gateway.createKoaApp(surgioServer);

const res1 = await request(app.callback())
.get('/qx-script?url=https%3A%2F%2Fraw.githubusercontent.com%2Fcrossutility%2FQuantumult-X%2Fmaster%2Fsample-rewrite-with-script.js');
const res2 = await request(app.callback())
.get('/qx-script?id=abcdef&url=https%3A%2F%2Fraw.githubusercontent.com%2Fcrossutility%2FQuantumult-X%2Fmaster%2Fsample-rewrite-with-script.js');
const res3 = await request(app.callback())
.get('/qx-script?id=abcdef,bcdefg&url=https%3A%2F%2Fraw.githubusercontent.com%2Fcrossutility%2FQuantumult-X%2Fmaster%2Fsample-rewrite-with-script.js');
const res4 = await request(app.callback())
.get('/qx-script?id=abcdef&url=https%3A%2F%2Fraw.githubusercontent.com%2Fcrossutility%2FQuantumult-X%2Fmaster%2Fsample-rewrite-with-script.js');

t.snapshot(res1.body.toString());
t.snapshot(res2.body.toString());
t.snapshot(res3.body.toString());
t.snapshot(res4.body.toString());
});
200 changes: 110 additions & 90 deletions test/gateway/snapshots/index.test.ts.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,104 +4,124 @@ The actual snapshot is saved in `index.test.ts.snap`.

Generated by [AVA](https://ava.li).

## get artifact
## qx-script

> Snapshot 1
`🇺🇲 US = custom, us.example.com, 443, chacha20-ietf-poly1305, password, https://raw.githubusercontent.com/ConnersHua/SSEncrypt/master/SSEncrypt.module, udp-relay=true, obfs=tls, obfs-host=gateway-carry.icloud.com, tfo=true␊
Snell = snell, us.example.com, 443, psk=password, obfs=tls␊
HTTPS = https, us.example.com, 443, username, password␊
🇺🇸US 1 = custom, us.example.com, 443, chacha20-ietf-poly1305, password, https://raw.githubusercontent.com/ConnersHua/SSEncrypt/master/SSEncrypt.module, obfs=tls, obfs-host=gateway-carry.icloud.com␊
🇺🇸US 2 = custom, us.example.com, 443, chacha20-ietf-poly1305, password, https://raw.githubusercontent.com/ConnersHua/SSEncrypt/master/SSEncrypt.module␊
ss1 = custom, server, 443, chacha20-ietf-poly1305, password, https://raw.githubusercontent.com/ConnersHua/SSEncrypt/master/SSEncrypt.module, udp-relay=true␊
ss2 = custom, server, 443, chacha20-ietf-poly1305, password, https://raw.githubusercontent.com/ConnersHua/SSEncrypt/master/SSEncrypt.module, udp-relay=false, obfs=tls, obfs-host=www.bing.com␊
vmess = vmess, server, 443, username=uuid␊
http = https, server, 443, username, password␊
snell = snell, server, 44046, psk=yourpsk, obfs=http␊
ss4 = custom, server, 443, chacha20-ietf-poly1305, password, https://raw.githubusercontent.com/ConnersHua/SSEncrypt/master/SSEncrypt.module, udp-relay=false, obfs=tls, obfs-host=example.com␊
----␊
🇺🇲 US, Snell, HTTPS, 🇺🇸US 1, 🇺🇸US 2, ss1, ss2, vmess, http, snell, ss4␊
`/**␊
* @supported 55BE3B10F8A1␊
* THIS COMMENT IS GENERATED BY SURGIO␊
*/␊
/**␊
* @supported 23AD6B11CD4B 55BE3B10F8A1␊
* The above random generated device ID can be found at the bottom of Quantumult X additional menu, and may be changed when system restored.␊
* Indicate what device are supported by the file. This is necessary when the file is not loaded from local("On My iPhone - Quantumult X - Scripts").␊
*/␊
// $request, $response, $notify(title, subtitle, message), console.log(message)␊
// $request.scheme, $request.method, $request.url, $request.path, $request.headers␊
// $response.statusCode, $response.headers, $response.body␊
// You can optional change the response headers at the same time by using $done({body: modifiedBody, headers: modifiedHeaders}); only change the response headers is not allowed for script-response-body. The modifiedHeaders can be copied and modified from $response.headers, please do not change the content length, type and encoding field.␊
// Response status can also be optional changed by using $done({body: modifiedBody, headers: modifiedHeaders, status: modifiedStatus}), the modifiedStatus should be like "HTTP/1.1 200 OK"␊
var body = $response.body;␊
var obj = JSON.parse(body);␊
obj['result'] = 0;␊
body = JSON.stringify(obj);␊
console.log(body);␊
$done(body);␊
`

## list artifact

> Snapshot 1
`␊
<li class="artifact">␊
<div class="inner-wrapper">␊
<div class="name">test.conf</div>␊
<pre class="preview">/get-artifact/test.conf?access_token=abcd</pre>␊
<div class="tag-list clearfix">␊
<div class="tag">Provider: ss</div>␊
<div class="tag">Provider: custom</div>␊
<div class="tag">Provider: clash</div>␊
</div>␊
<div class="link-group">␊
<a rel="nofollow" class="link pure-button pure-button-primary" target="_blank" href="/get-artifact/test.conf?access_token=abcd&amp;dl=1">下载</a>␊
<a rel="nofollow" class="link pure-button pure-button-primary" target="_blank" href="/get-artifact/test.conf?access_token=abcd">预览</a>␊
<button class="ctc link pure-button pure-button-primary" data-clipboard-text="/get-artifact/test.conf?access_token=abcd">复制地址</button>␊
</div>␊
</div>␊
</li>␊
`

## transform artifact qx

> Snapshot 1
`shadowsocks=us.example.com:443, method=chacha20-ietf-poly1305, password=password, obfs=tls, obfs-host=gateway-carry.icloud.com, udp-relay=true, fast-open=true, tag=🇺🇲 US␊
http=us.example.com:443, username=username, password=password, over-tls=true, tag=HTTPS␊
shadowsocks=us.example.com:443, method=chacha20-ietf-poly1305, password=password, obfs=tls, obfs-host=gateway-carry.icloud.com, tag=🇺🇸US 1␊
shadowsocks=us.example.com:443, method=chacha20-ietf-poly1305, password=password, tag=🇺🇸US 2␊
shadowsocks=server:443, method=chacha20-ietf-poly1305, password=password, udp-relay=true, tag=ss1␊
shadowsocks=server:443, method=chacha20-ietf-poly1305, password=password, obfs=tls, obfs-host=www.bing.com, tag=ss2␊
vmess=server:443, method=chacha20-ietf-poly1305, password=uuid, udp-relay=true, tag=vmess␊
http=server:443, username=username, password=password, over-tls=true, tag=http␊
shadowsocks=server:443, method=chacha20-ietf-poly1305, password=password, obfs=tls, obfs-host=example.com, tag=ss4`

> Snapshot 2
'shadowsocks=us.example.com:443, method=chacha20-ietf-poly1305, password=password, obfs=tls, obfs-host=gateway-carry.icloud.com, udp-relay=true, fast-open=true, tag=🇺🇲 US'

## transform artifact surge
`/**␊
* @supported abcdef␊
* THIS COMMENT IS GENERATED BY SURGIO␊
*/␊
/**␊
* @supported 23AD6B11CD4B 55BE3B10F8A1␊
* The above random generated device ID can be found at the bottom of Quantumult X additional menu, and may be changed when system restored.␊
* Indicate what device are supported by the file. This is necessary when the file is not loaded from local("On My iPhone - Quantumult X - Scripts").␊
*/␊
// $request, $response, $notify(title, subtitle, message), console.log(message)␊
// $request.scheme, $request.method, $request.url, $request.path, $request.headers␊
// $response.statusCode, $response.headers, $response.body␊
// You can optional change the response headers at the same time by using $done({body: modifiedBody, headers: modifiedHeaders}); only change the response headers is not allowed for script-response-body. The modifiedHeaders can be copied and modified from $response.headers, please do not change the content length, type and encoding field.␊
// Response status can also be optional changed by using $done({body: modifiedBody, headers: modifiedHeaders, status: modifiedStatus}), the modifiedStatus should be like "HTTP/1.1 200 OK"␊
var body = $response.body;␊
var obj = JSON.parse(body);␊
obj['result'] = 0;␊
body = JSON.stringify(obj);␊
console.log(body);␊
$done(body);␊
`

> Snapshot 1
> Snapshot 3
`🇺🇲 US = custom, us.example.com, 443, chacha20-ietf-poly1305, password, https://raw.githubusercontent.com/ConnersHua/SSEncrypt/master/SSEncrypt.module, udp-relay=true, obfs=tls, obfs-host=gateway-carry.icloud.com, tfo=true␊
Snell = snell, us.example.com, 443, psk=password, obfs=tls␊
HTTPS = https, us.example.com, 443, username, password␊
🇺🇸US 1 = custom, us.example.com, 443, chacha20-ietf-poly1305, password, https://raw.githubusercontent.com/ConnersHua/SSEncrypt/master/SSEncrypt.module, obfs=tls, obfs-host=gateway-carry.icloud.com␊
🇺🇸US 2 = custom, us.example.com, 443, chacha20-ietf-poly1305, password, https://raw.githubusercontent.com/ConnersHua/SSEncrypt/master/SSEncrypt.module␊
ss1 = custom, server, 443, chacha20-ietf-poly1305, password, https://raw.githubusercontent.com/ConnersHua/SSEncrypt/master/SSEncrypt.module, udp-relay=true␊
ss2 = custom, server, 443, chacha20-ietf-poly1305, password, https://raw.githubusercontent.com/ConnersHua/SSEncrypt/master/SSEncrypt.module, udp-relay=false, obfs=tls, obfs-host=www.bing.com␊
vmess = vmess, server, 443, username=uuid␊
http = https, server, 443, username, password␊
snell = snell, server, 44046, psk=yourpsk, obfs=http␊
ss4 = custom, server, 443, chacha20-ietf-poly1305, password, https://raw.githubusercontent.com/ConnersHua/SSEncrypt/master/SSEncrypt.module, udp-relay=false, obfs=tls, obfs-host=example.com`
`/**␊
* @supported abcdef bcdefg␊
* THIS COMMENT IS GENERATED BY SURGIO␊
*/␊
/**␊
* @supported 23AD6B11CD4B 55BE3B10F8A1␊
* The above random generated device ID can be found at the bottom of Quantumult X additional menu, and may be changed when system restored.␊
* Indicate what device are supported by the file. This is necessary when the file is not loaded from local("On My iPhone - Quantumult X - Scripts").␊
*/␊
// $request, $response, $notify(title, subtitle, message), console.log(message)␊
// $request.scheme, $request.method, $request.url, $request.path, $request.headers␊
// $response.statusCode, $response.headers, $response.body␊
// You can optional change the response headers at the same time by using $done({body: modifiedBody, headers: modifiedHeaders}); only change the response headers is not allowed for script-response-body. The modifiedHeaders can be copied and modified from $response.headers, please do not change the content length, type and encoding field.␊
// Response status can also be optional changed by using $done({body: modifiedBody, headers: modifiedHeaders, status: modifiedStatus}), the modifiedStatus should be like "HTTP/1.1 200 OK"␊
var body = $response.body;␊
var obj = JSON.parse(body);␊
obj['result'] = 0;␊
body = JSON.stringify(obj);␊
console.log(body);␊
$done(body);␊
`

> Snapshot 2
> Snapshot 4
`🇺🇸US 1 = custom, us.example.com, 443, chacha20-ietf-poly1305, password, https://raw.githubusercontent.com/ConnersHua/SSEncrypt/master/SSEncrypt.module, obfs=tls, obfs-host=gateway-carry.icloud.com␊
🇺🇸US 2 = custom, us.example.com, 443, chacha20-ietf-poly1305, password, https://raw.githubusercontent.com/ConnersHua/SSEncrypt/master/SSEncrypt.module␊
🇺🇲 US = custom, us.example.com, 443, chacha20-ietf-poly1305, password, https://raw.githubusercontent.com/ConnersHua/SSEncrypt/master/SSEncrypt.module, udp-relay=true, obfs=tls, obfs-host=gateway-carry.icloud.com, tfo=true`
`/**␊
* @supported abcdef␊
* THIS COMMENT IS GENERATED BY SURGIO␊
*/␊
/**␊
* @supported 23AD6B11CD4B 55BE3B10F8A1␊
* The above random generated device ID can be found at the bottom of Quantumult X additional menu, and may be changed when system restored.␊
* Indicate what device are supported by the file. This is necessary when the file is not loaded from local("On My iPhone - Quantumult X - Scripts").␊
*/␊
// $request, $response, $notify(title, subtitle, message), console.log(message)␊
// $request.scheme, $request.method, $request.url, $request.path, $request.headers␊
// $response.statusCode, $response.headers, $response.body␊
// You can optional change the response headers at the same time by using $done({body: modifiedBody, headers: modifiedHeaders}); only change the response headers is not allowed for script-response-body. The modifiedHeaders can be copied and modified from $response.headers, please do not change the content length, type and encoding field.␊
// Response status can also be optional changed by using $done({body: modifiedBody, headers: modifiedHeaders, status: modifiedStatus}), the modifiedStatus should be like "HTTP/1.1 200 OK"␊
var body = $response.body;␊
var obj = JSON.parse(body);␊
obj['result'] = 0;␊
body = JSON.stringify(obj);␊
console.log(body);␊
$done(body);␊
`
Binary file modified test/gateway/snapshots/index.test.ts.snap
Binary file not shown.
2 changes: 2 additions & 0 deletions test/stub-axios.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,5 @@ mock.onGet(/\/error/).reply(
500,
''
);

mock.onAny().passThrough();

0 comments on commit e9b9790

Please sign in to comment.