diff --git a/app/bot.php b/app/bot.php index 28381fc..6e9c141 100644 --- a/app/bot.php +++ b/app/bot.php @@ -5482,6 +5482,10 @@ DNS-over-HTTPS with IP: 'text' => $this->i18n('sing-box templates'), 'callback_data' => "/templates sing", ], + [ + 'text' => $this->i18n('clash templates'), + 'callback_data' => "/templates clash", + ], ]; $data[] = [ [ @@ -5784,9 +5788,15 @@ DNS-over-HTTPS with IP: 't' => 's', 's' => $c['id'], ])); + $cl = "$scheme://{$domain}/pac/" . base64_encode(serialize([ + 'h' => $hash, + 't' => 'cl', + 's' => $c['id'], + ])); $text[] = "\nxray config:
$xr
"; $text[] = "sing-box config:
$si
"; + $text[] = "clash config:
$cl
"; $text[] = "sing-box windows: windows service"; @@ -5812,6 +5822,7 @@ DNS-over-HTTPS with IP: ]; $singtemplate = $c['singtemplate'] ? base64_decode($c['singtemplate']) : 'default(' . ($pac['defaultsingtemplate'] && !empty($pac['singtemplates'][base64_decode($pac['defaultsingtemplate'])]) ? base64_decode($pac['defaultsingtemplate']) : 'origin') . ')'; $v2raytemplate = $c['v2raytemplate'] ? base64_decode($c['v2raytemplate']) : 'default(' . ($pac['defaultv2raytemplate'] && !empty($pac['v2raytemplates'][base64_decode($pac['defaultv2raytemplate'])]) ? base64_decode($pac['defaultv2raytemplate']) : 'origin') . ')'; + $clashtemplate = $c['clashtemplate'] ? base64_decode($c['clashtemplate']) : 'default(' . ($pac['defaultclashtemplate'] && !empty($pac['clashtemplates'][base64_decode($pac['defaultclashtemplate'])]) ? base64_decode($pac['defaultclashtemplate']) : 'origin') . ')'; $data[] = [ [ 'text' => $this->i18n('v2ray') . ": $v2raytemplate", @@ -5821,6 +5832,10 @@ DNS-over-HTTPS with IP: 'text' => $this->i18n('singbox') . ": $singtemplate", 'callback_data' => "/templateUser sing $i", ], + [ + 'text' => $this->i18n('clash') . ": $clashtemplate", + 'callback_data' => "/templateUser clash $i", + ], ]; $data[] = [ [ @@ -5871,7 +5886,17 @@ DNS-over-HTTPS with IP: public function subscription() { - $type = $_GET['t'] == 's' ? 'v2ray' : 'sing'; + switch ($_GET['t']) { + case 's': + $type = 'v2ray'; + break; + case 'si': + $type = 'sing'; + break; + case 'cl': + $type = 'clash'; + break; + } $pac = $this->getPacConf(); $domain = $_GET['cdn'] ?: ($_SERVER['SERVER_NAME'] ?: $this->getDomain($pac['transport'] == 'Websocket')); $xr = $this->getXray(); @@ -5905,6 +5930,11 @@ DNS-over-HTTPS with IP: 't' => 's', 's' => $uid, ])); + $cl = "$scheme://{$domain}/pac/" . base64_encode(serialize([ + 'h' => $hash, + 't' => 'cl', + 's' => $uid, + ])); switch ($_GET['r']) { case 'si': header("Location: sing-box://import-remote-profile/?url=$si"); @@ -5961,6 +5991,14 @@ DNS-over-HTTPS with IP: break; } } + if (!isset($index)) { + foreach ($c['proxies'] as $k => $v) { + if ($v['name'] == $outbound) { + $index = $k; + break; + } + } + } switch ($_GET['t']) { case 's': @@ -6020,27 +6058,103 @@ DNS-over-HTTPS with IP: $c['outbounds'][$index]['tls']['reality']['short_id'] = '~short_id~'; } + $c['route'] = $this->addRuleSet($c['route']); $c['route'] = $this->createRuleSet($c['route'], $uid, $domain); break; + case 'cl': + $c['proxies'][$index]['server'] = '~domain~'; + $c['proxies'][$index]['uuid'] = '~uid~'; + if ($pac['transport'] == 'Websocket') { + unset($c['proxies'][$index]['flow']); + unset($c['proxies'][$index]['reality-opts']); + $c['proxies'][$index]["network"] = "ws"; + $c['proxies'][$index]["ws-opts"]['path'] = '/ws'; + $c['proxies'][$index]["skip-cert-verify"] = false; + $c['proxies'][$index]['servername'] = '~domain~'; + } else { + unset($c['proxies'][$index]["ws-opts"]); + unset($c['proxies'][$index]["skip-cert-verify"]); + $c['proxies'][$index]["network"] = "tcp"; + $c['proxies'][$index]['flow'] = 'xtls-rprx-vision'; + $c['proxies'][$index]['servername'] = '~server_name~'; + $c['proxies'][$index]['reality-opts'] = [ + 'public-key' => '~public_key~', + 'short-id' => '~short_id~', + ]; + } + break; + } + $c = json_decode($this->replaceTags(json_encode($c), [ + '"~pac~"' => json_encode(array_keys(array_filter($pac['includelist'] ?: []))), + '"~block~"' => json_encode(array_keys(array_filter($pac['blocklist'] ?: []))), + '"~warp~"' => json_encode(array_keys(array_filter($pac['warplist'] ?: []))), + '"~process~"' => json_encode(array_keys(array_filter($pac['processlist'] ?: []))), + '"~package~"' => json_encode(array_keys(array_filter($pac['packagelist'] ?: []))), + '~dns~' => "https://$domain/dns-query/$uid", + '~uid~' => $uid, + '~domain~' => $domain, + '~directdomain~' => $pac['domain'], + '~cdndomain~' => $pac['linkdomain'], + '~short_id~' => $xr['inbounds'][0]['streamSettings']['realitySettings']['shortIds'][0], + '~public_key~' => $pac['xray'], + '~server_name~' => $xr['inbounds'][0]['streamSettings']['realitySettings']['serverNames'][0], + '~ip~' => $this->ip, + ]), true); + + switch ($_GET['t']) { + case 's': + if (!empty($c['routing']['rules'])) { + foreach ($c['routing']['rules'] as $k => $v) { + if (array_key_exists('domain', $v) && empty($v['domain'])) { + unset($c['routing']['rules'][$k]); + } + } + $c['routing']['rules'] = array_values($c['routing']['rules']); + } + break; + case 'si': + $c['route'] = $this->addRuleSet($c['route']); + $c['route'] = $this->createRuleSet($c['route'], $uid, $domain); + if (!empty($c['route']['rules'])) { + foreach ($c['route']['rules'] as $k => $v) { + if (count($v) < 2) { + unset($c['route']['rules'][$k]); + } + } + $c['route']['rules'] = array_values($c['route']['rules']); + } + break; + case 'cl': + if (!empty($c['rules'])) { + $c['rules'] = $this->clashRules($c['rules']); + } + break; } - $json = $this->replaceTags(json_encode($c), [ - '"~pac~"' => json_encode(array_keys(array_filter($pac['includelist'] ?: []))), - '~dns~' => "https://$domain/dns-query/$uid", - '~uid~' => $uid, - '~domain~' => $domain, - '~directdomain~' => $pac['domain'], - '~cdndomain~' => $pac['linkdomain'], - '~short_id~' => $xr['inbounds'][0]['streamSettings']['realitySettings']['shortIds'][0], - '~public_key~' => $pac['xray'], - '~server_name~' => $xr['inbounds'][0]['streamSettings']['realitySettings']['serverNames'][0], - '~ip~' => $this->ip, - ]); - $json = $this->addRuleSet($json); - $json = $this->clearEmptyRules($json); + if ($_GET['t'] == 'cl') { + header('Content-type: text/yaml'); + echo yaml_emit($c); + return; + } header('Content-type: application/json'); - echo $json; + echo json_encode($c); + } + + public function clashRules($rules) + { + foreach ($rules as $v) { + if (isset($v['list'])) { + if (!empty($v['list'])) { + foreach ($v['list'] as $j) { + $tmp[] = "{$v['type']}, $j, {$v['action']}"; + } + } + } else { + $tmp[] = "{$v['type']}, {$v['action']}"; + } + } + return $tmp; } public function replaceTags($subject, $tags) @@ -6048,33 +6162,10 @@ DNS-over-HTTPS with IP: return str_replace(array_keys($tags), array_values($tags), $subject); } - public function clearEmptyRules($json) + public function addRuleSet($route) { - $json = json_decode($json, 1); - if (!empty($json['routing']['rules'])) { - foreach ($json['routing']['rules'] as $k => $v) { - if (array_key_exists('domain', $v) && empty($v['domain'])) { - unset($json['routing']['rules'][$k]); - } - } - $json['routing']['rules'] = array_values($json['routing']['rules']); - } - if (!empty($json['route']['rules'])) { - foreach ($json['route']['rules'] as $k => $v) { - if (count($v) < 2) { - unset($json['route']['rules'][$k]); - } - } - $json['route']['rules'] = array_values($json['route']['rules']); - } - return json_encode($json); - } - - public function addRuleSet($json) - { - $json = json_decode($json, 1); - if (!empty($json['route']['rules'])) { - foreach ($json['route']['rules'] as $k => $v) { + if (!empty($route['rules'])) { + foreach ($route['rules'] as $k => $v) { if (!empty($v['addruleset'])) { $t[$v['outbound']] = $k; } @@ -6084,8 +6175,8 @@ DNS-over-HTTPS with IP: foreach ($p['rulessetlist'] as $k => $v) { if (!empty($v)) { [$type, $time, $url] = explode(':', $k, 3); - if (!empty($json['route']['rules'][$t[$type]])) { - $json['route']['rule_set'][] = [ + if (!empty($route['rules'][$t[$type]])) { + $route['rule_set'][] = [ "tag" => $k, "type" => "remote", "format" => "binary", @@ -6093,16 +6184,16 @@ DNS-over-HTTPS with IP: "download_detour" => "direct", "update_interval" => $time ]; - $json['route']['rules'][$t[$type]]['rule_set'][] = $k; + $route['rules'][$t[$type]]['rule_set'][] = $k; } } } } - foreach ($json['route']['rules'] as $k => $v) { - unset($json['route']['rules'][$k]['addruleset']); + foreach ($route['rules'] as $k => $v) { + unset($route['rules'][$k]['addruleset']); } } - return json_encode($json); + return $route; } public function createRuleSet($route, $uid, $domain) @@ -6114,39 +6205,6 @@ DNS-over-HTTPS with IP: foreach ($route['rules'] as $k => $v) { if (!empty($v['createruleset'])) { foreach ($v['createruleset'] as $r) { - foreach ($r['rules'] as $l => $n) { - switch (true) { - case array_key_exists('domain_suffix', $n): - switch ($n['domain_suffix']) { - case '~pac~': - $t = 'includelist'; - break; - case '~warp~': - $t = 'warplist'; - break; - case '~block~': - $t = 'blocklist'; - break; - } - $r['rules'][$l]['domain_suffix'] = array_keys(array_filter($pac[$t] ?: [])); - if (empty($r['rules'][$l]['domain_suffix'])) { - unset($r['rules'][$l]); - } - break; - case array_key_exists('package_name', $n): - $r['rules'][$l]['package_name'] = array_keys(array_filter($pac['packagelist'] ?: [])); - if (empty($r['rules'][$l]['package_name'])) { - unset($r['rules'][$l]); - } - break; - case array_key_exists('process_name', $n): - $r['rules'][$l]['process_name'] = array_keys(array_filter($pac['processlist'] ?: [])); - if (empty($r['rules'][$l]['process_name'])) { - unset($r['rules'][$l]); - } - break; - } - } if (!empty($_GET['r']) && $r['name'] == $_GET['r']) { header("Content-Disposition: attachment; filename={$r['name']}.srs"); header('Content-Type: application/binary'); diff --git a/app/index.php b/app/index.php index 9a2fd15..7b73dcf 100644 --- a/app/index.php +++ b/app/index.php @@ -37,6 +37,7 @@ if ($hash == substr(md5($c['key']), 0, 8)) { break; case 's': case 'si': + case 'cl': $bot->subscription(); exit; diff --git a/config/clash.json b/config/clash.json new file mode 100644 index 0000000..9425f45 --- /dev/null +++ b/config/clash.json @@ -0,0 +1,119 @@ +{ + "mixed-port": 2080, + "allow-lan": true, + "tcp-concurrent": true, + "enable-process": true, + "find-process-mode": "strict", + "global-client-fingerprint": "chrome", + "mode": "rule", + "log-level": "debug", + "ipv6": false, + "keep-alive-interval": 30, + "unified-delay": false, + "profile": { + "store-selected": true, + "store-fake-ip": true + }, + "sniffer": { + "enable": true, + "sniff": { + "HTTP": { + "ports": [ + 80, + "8080-8880" + ], + "override-destination": true + }, + "TLS": { + "ports": [ + 443, + 8443 + ] + }, + "QUIC": { + "ports": [ + 443, + 8443 + ] + } + } + }, + "tun": { + "enable": true, + "stack": "mixed", + "dns-hijack": [ + "any:53" + ], + "auto-route": true, + "auto-detect-interface": true, + "strict-route": true + }, + "dns": { + "enable": true, + "listen": ":1053", + "prefer-h3": false, + "ipv6": false, + "enhanced-mode": "fake-ip", + "fake-ip-filter": [ + "~domain~", + "+.lan", + "+.local" + ], + "nameserver": [ + "~dns~" + ] + }, + "proxies": [ + { + "name": "~outbound~", + "type": "vless", + "server": "~domain~", + "port": 443, + "uuid": "~uid~", + "network": "tcp", + "flow": "xtls-rprx-vision", + "tls": true, + "reality-opts": { + "public-key": "~public_key~", + "short-id": "~short_id~" + }, + "servername": "~server_name~", + "client-fingerprint": "chrome" + } + ], + "proxy-groups": [ + { + "name": "PROXY", + "type": "select", + "proxies": [ + "~outbound~" + ] + } + ], + "rules": [ + { + "type": "DOMAIN-SUFFIX", + "list": "~block~", + "action": "REJECT" + }, + { + "type": "DOMAIN-SUFFIX", + "list": "~warp~", + "action": "PROXY" + }, + { + "type": "DOMAIN-SUFFIX", + "list": "~pac~", + "action": "PROXY" + }, + { + "type": "PROCESS-NAME", + "list": "~process~", + "action": "REJECT" + }, + { + "type": "MATCH", + "action": "DIRECT" + } + ] +} \ No newline at end of file