Skip to content

Instantly share code, notes, and snippets.

@CaiJingLong
Last active September 18, 2024 02:37
Show Gist options
  • Save CaiJingLong/20e9cd43e1816dedf71caca8566d1a52 to your computer and use it in GitHub Desktop.
Save CaiJingLong/20e9cd43e1816dedf71caca8566d1a52 to your computer and use it in GitHub Desktop.
一个转化订阅的 dart 服务器,处理 clash 的 json,添加规则/节点
{
"port": 3300,
"meta": "https://example.com/api/v1/client/subscribe?token=REDACTED&flag=clash",
"ssr-meta": "https://example.com?clash=1",
"nodes": [
{
"name": "my-home",
"schema": "hysteria2",
"host": "REDACTED",
"port": 34443,
"password": "REDACTED"
}
],
"rules": [
"GEOIP,CN,DIRECT"
]
}
import 'dart:convert';
import 'dart:io';
import 'package:http/http.dart' as http;
import 'package:yaml/yaml.dart';
import 'package:shelf/shelf.dart' as shelf;
import 'package:shelf/shelf_io.dart' as io;
import 'package:shelf_router/shelf_router.dart';
const String configFile = "config.json"; // 指定配置文件的地址 config.json
Future<Map<String, dynamic>> readConfig() async {
final file = File(configFile);
final jsonString = await file.readAsString();
return json.decode(jsonString);
}
// 节点
class Node {
String name;
String schema;
String host;
int port;
String password;
Node(this.name, this.schema, this.host, this.port, this.password);
static List<Node> listFromConfig(List<dynamic> param) {
return param
.map((node) => Node(node['name'], node['schema'], node['host'],
node['port'], node['password']))
.toList();
}
}
abstract class Injector {
void injectNode(Node node);
String output();
}
class ClashInjector implements Injector {
late Map<String, dynamic> src;
ClashInjector(Map<String, dynamic> map) {
src = Map.from(map);
}
@override
void injectNode(Node node) async {
final nodeDict = {
"name": node.name,
"type": node.schema,
"server": node.host,
"port": node.port,
"password": node.password,
"skip-cert-verify": true,
"sni": node.host,
};
final proxies = src['proxies'].toList();
proxies.add(nodeDict);
src['proxies'] = proxies;
final proxyGroups = src['proxy-groups'].toList();
proxyGroups.add({
"name": "${node.name}_group",
"type": "select",
"proxies": [node.name],
});
src['proxy-groups'] = proxyGroups;
}
void injectRule(List<String> rules) {
final newRules = [...rules, ...src['rules']];
src['rules'] = newRules;
}
@override
String output() {
return json.encode(src);
}
}
// 添加服务器路由处理
class Server {
Router router = Router();
Server() {
router.get('/meta', _handleMeta);
router.get('/ssr-meta', _handleSsrMeta);
}
Future<shelf.Response> _handleMeta(shelf.Request request) async {
return _clashHandle('meta');
}
Future<shelf.Response> _handleSsrMeta(shelf.Request request) async {
return _clashHandle('ssr-meta');
}
Future<shelf.Response> _clashHandle(String nodeName) async {
final config = await readConfig();
final metaUrl = config[nodeName];
final response = await http.get(Uri.parse(metaUrl));
final content = response.body;
final YamlMap d = loadYaml(content);
final injector = ClashInjector(d.cast());
final nodes = Node.listFromConfig(config['nodes']);
for (var node in nodes) {
injector.injectNode(node);
}
injector.injectRule(config['rules'].cast<String>());
final res = injector.output();
return shelf.Response.ok(
res,
headers: {'Content-Type': 'application/json; charset=utf-8'},
);
}
}
void main() async {
final config = await readConfig();
final port = config['port'];
final server = Server();
final handler = const shelf.Pipeline()
.addMiddleware(shelf.logRequests())
.addHandler(server.router);
final httpServer = await io.serve(handler, '0.0.0.0', port);
print('服务器运行在 http://${httpServer.address.host}:${httpServer.port}');
print('订阅链接: http://${httpServer.address.host}:${httpServer.port}/meta');
print('订阅链接: http://${httpServer.address.host}:${httpServer.port}/ssr-meta');
}
name: clash_subscription_injector
description: A proxy tools for clash, to inject nodes and rules into subscription
version: 1.0.0+1
environment:
sdk: ">=2.12.0 <3.0.0"
dependencies:
http: ^0.13.3
shelf_router: ^1.1.4
yaml: ^3.1.0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment