Vortex 漩涡客户端
官网Telegram
  • 关于 Vortex
  • Demo
  • 更新日志
  • 价格
  • 购买
  • 联系我们
  • 🛠️自助打包
    • 后台打包界面
    • 服务端类型
    • 订阅类型(可选)
    • API 地址(一行一个)
    • OSS 地址(一行一个)(可选)
    • 云端更新间隔(小时)(可选)
    • DNS TXT 域名(一行一个)(可选)
    • 内建代理(可选)
    • 禁用直连(可选)
    • 客户端User-Agent(可选)
    • 首页地址(可选)
    • 客服地址(可选)
    • Telegram 链接(可选)
    • 使用条款链接(可选)
    • 主题色
    • 应用图标
    • 登录页图片(可选)for Win/Mac
    • 托盘图标 for Win/Mac
    • 自定义包名(可选)for Android
    • 应用程序内图标(可选)for Android
  • 👥Servo 客服系统
    • 简介
    • 服务端搭建
    • Telegram 客服
    • 服务端升级
  • 🖥️面板相关
    • OSS 所有可配置的字段
    • SSPanel 和 WHMCS 的 guest_config 接口(必要)
    • 使用宝塔缓存guest_config接口
    • 客户端内自动更新
    • V2board 删除默认的订阅地址直连规则(强烈建议)
    • V2board 自定义订阅类型(非高阶不建议)
    • SSPanel-Malio 内购接口
    • SSPanel-Metron/Malio 自动登录接口
    • WHMCS 自动登录接口
  • Chatwoot 自定义属性
  • ⏳其他
    • 客户端日志查看
    • 常见问题
    • 定制服务
    • 使用 AList 搭建直链网盘(可选)
Powered by GitBook
On this page
  1. 面板相关

V2board 自定义订阅类型(非高阶不建议)

使用自定义的订阅类型,让 Vortex 不同的平台获取不一样的 Clash 配置文件,如 Win/Mac 需要策略组分流,安卓只需要选择节点不需要分流。以 V2board 举例

Clash 配置文件自定义

/resources/rules 这个目录下存放着订阅软件相关的配置文件,默认使用的 clash 配置文件为 default.clash.yaml,如果需要自定义配置仅需复制一份default.clash.yaml重命名为custom.clash.yaml并且配置成自己想要的形式即可,保存后将会优先使用custom.clash.yaml

而使用 custom.clash.yaml 由app/Http/Controllers/Client/Protocols/ 目录下的 php 文件决定,因此,只需要在自定义个php文件和一个clash配置文件,我们就可以单独为 Vortex 客户端定制一份配置

示例:为 Vortex 安卓端单独设置一份无需分流的规则

1、在 app/Http/Controllers/Client/Protocols/目录下添加 Vortex.php

<?php

namespace App\Http\Controllers\Client\Protocols;

use App\Utils\Helper;
use Symfony\Component\Yaml\Yaml;

class Vortex
{
    public $flag = 'vortex';
    private $servers;
    private $user;

    public function __construct($user, $servers)
    {
        $this->user = $user;
        $this->servers = $servers;
    }

    public function handle()
    {
        $servers = $this->servers;
        $user = $this->user;
        $appName = config('v2board.app_name', 'V2Board');
        header("subscription-userinfo: upload={$user['u']}; download={$user['d']}; total={$user['transfer_enable']}; expire={$user['expired_at']}");
        header('profile-update-interval: 24');
        header("content-disposition:attachment;filename*=UTF-8''".rawurlencode($appName));
        $defaultConfig = base_path() . '/resources/rules/default.clash.yaml';
        $customConfig = base_path() . '/resources/rules/vortex.clash.yaml';
        if (\File::exists($customConfig)) {
            $config = Yaml::parseFile($customConfig);
        } else {
            $config = Yaml::parseFile($defaultConfig);
        }
        $proxy = [];
        $proxies = [];

        foreach ($servers as $item) {
            if ($item['type'] === 'shadowsocks') {
                array_push($proxy, self::buildShadowsocks($user['uuid'], $item));
                array_push($proxies, $item['name']);
            }
            if ($item['type'] === 'vmess') {
                array_push($proxy, self::buildVmess($user['uuid'], $item));
                array_push($proxies, $item['name']);
            }
            if ($item['type'] === 'trojan') {
                array_push($proxy, self::buildTrojan($user['uuid'], $item));
                array_push($proxies, $item['name']);
            }
        }

        $config['proxies'] = array_merge($config['proxies'] ? $config['proxies'] : [], $proxy);
        foreach ($config['proxy-groups'] as $k => $v) {
            if (!is_array($config['proxy-groups'][$k]['proxies'])) $config['proxy-groups'][$k]['proxies'] = [];
            $isFilter = false;
            foreach ($config['proxy-groups'][$k]['proxies'] as $src) {
                foreach ($proxies as $dst) {
                    if (!$this->isRegex($src)) continue;
                    $isFilter = true;
                    $config['proxy-groups'][$k]['proxies'] = array_values(array_diff($config['proxy-groups'][$k]['proxies'], [$src]));
                    if ($this->isMatch($src, $dst)) {
                        array_push($config['proxy-groups'][$k]['proxies'], $dst);
                    }
                }
                if ($isFilter) continue;
            }
            if ($isFilter) continue;
            $config['proxy-groups'][$k]['proxies'] = array_merge($config['proxy-groups'][$k]['proxies'], $proxies);
        }
        $config['proxy-groups'] = array_filter($config['proxy-groups'], function($group) {
            return $group['proxies'];
        });
        $config['proxy-groups'] = array_values($config['proxy-groups']);
        // Force the current subscription domain to be a direct rule
        $subsDomain = $_SERVER['HTTP_HOST'];
        if ($subsDomain) {
            array_unshift($config['rules'], "DOMAIN,{$subsDomain},DIRECT");
        }

        $yaml = Yaml::dump($config, 2, 4, Yaml::DUMP_EMPTY_ARRAY_AS_SEQUENCE);
        $yaml = str_replace('$app_name', config('v2board.app_name', 'V2Board'), $yaml);
        return $yaml;
    }

    public static function buildShadowsocks($password, $server)
    {
        if ($server['cipher'] === '2022-blake3-aes-128-gcm') {
            $serverKey = Helper::getServerKey($server['created_at'], 16);
            $userKey = Helper::uuidToBase64($password, 16);
            $password = "{$serverKey}:{$userKey}";
        }
        if ($server['cipher'] === '2022-blake3-aes-256-gcm') {
            $serverKey = Helper::getServerKey($server['created_at'], 32);
            $userKey = Helper::uuidToBase64($password, 32);
            $password = "{$serverKey}:{$userKey}";
        }
        $array = [];
        $array['name'] = $server['name'];
        $array['type'] = 'ss';
        $array['server'] = $server['host'];
        $array['port'] = $server['port'];
        $array['cipher'] = $server['cipher'];
        $array['password'] = $password;
        $array['udp'] = true;
        return $array;
    }

    public static function buildVmess($uuid, $server)
    {
        $array = [];
        $array['name'] = $server['name'];
        $array['type'] = 'vmess';
        $array['server'] = $server['host'];
        $array['port'] = $server['port'];
        $array['uuid'] = $uuid;
        $array['alterId'] = 0;
        $array['cipher'] = 'auto';
        $array['udp'] = true;

        if ($server['tls']) {
            $array['tls'] = true;
            if ($server['tlsSettings']) {
                $tlsSettings = $server['tlsSettings'];
                if (isset($tlsSettings['allowInsecure']) && !empty($tlsSettings['allowInsecure']))
                    $array['skip-cert-verify'] = ($tlsSettings['allowInsecure'] ? true : false);
                if (isset($tlsSettings['serverName']) && !empty($tlsSettings['serverName']))
                    $array['servername'] = $tlsSettings['serverName'];
            }
        }
        if ($server['network'] === 'tcp') {
            $tcpSettings = $server['networkSettings'];
            if (isset($tcpSettings['header']['type'])) $array['network'] = $tcpSettings['header']['type'];
            if (isset($tcpSettings['header']['request']['path'][0])) $array['http-opts']['path'] = $tcpSettings['header']['request']['path'][0];
        }
        if ($server['network'] === 'ws') {
            $array['network'] = 'ws';
            if ($server['networkSettings']) {
                $wsSettings = $server['networkSettings'];
                $array['ws-opts'] = [];
                if (isset($wsSettings['path']) && !empty($wsSettings['path']))
                    $array['ws-opts']['path'] = $wsSettings['path'];
                if (isset($wsSettings['headers']['Host']) && !empty($wsSettings['headers']['Host']))
                    $array['ws-opts']['headers'] = ['Host' => $wsSettings['headers']['Host']];
                if (isset($wsSettings['path']) && !empty($wsSettings['path']))
                    $array['ws-path'] = $wsSettings['path'];
                if (isset($wsSettings['headers']['Host']) && !empty($wsSettings['headers']['Host']))
                    $array['ws-headers'] = ['Host' => $wsSettings['headers']['Host']];
            }
        }
        if ($server['network'] === 'grpc') {
            $array['network'] = 'grpc';
            if ($server['networkSettings']) {
                $grpcSettings = $server['networkSettings'];
                $array['grpc-opts'] = [];
                if (isset($grpcSettings['serviceName'])) $array['grpc-opts']['grpc-service-name'] = $grpcSettings['serviceName'];
            }
        }

        return $array;
    }

    public static function buildTrojan($password, $server)
    {
        $array = [];
        $array['name'] = $server['name'];
        $array['type'] = 'trojan';
        $array['server'] = $server['host'];
        $array['port'] = $server['port'];
        $array['password'] = $password;
        $array['udp'] = true;
        if (!empty($server['server_name'])) $array['sni'] = $server['server_name'];
        if (!empty($server['allow_insecure'])) $array['skip-cert-verify'] = ($server['allow_insecure'] ? true : false);
        return $array;
    }

    private function isMatch($exp, $str)
    {
        return @preg_match($exp, $str);
    }

    private function isRegex($exp)
    {
        return @preg_match($exp, null) !== false;
    }
}

如需要 Hysteria2 等协议的下发,请自行修改上面的 Vortex.php

2、在 /resources/rules 目录下添加 vortex.clash.yaml

mixed-port: 7890
allow-lan: true
bind-address: "*"
mode: rule
log-level: info
external-controller: 127.0.0.1:9090

dns:
  enable: true
  # listen: 0.0.0.0:53
  ipv6: false

  default-nameserver:
    - 223.5.5.5
    - 119.29.29.29
  enhanced-mode: fake-ip
  fake-ip-range: 198.18.0.1/16
  use-hosts: true
  nameserver:
    - https://doh.pub/dns-query
    - https://dns.alidns.com/dns-query
  fallback:
    - https://doh.dns.sb/dns-query
    - https://dns.cloudflare.com/dns-query
    - https://dns.twnic.tw/dns-query
    - tls://8.8.4.4:853
  fallback-filter:
    geoip: true
    ipcidr:
      - 240.0.0.0/4
      - 0.0.0.0/32

proxies:

proxy-groups:
  - { name: "$app_name", type: select, proxies: ["自动选择", "故障转移"] }
  - { name: "自动选择", type: url-test, proxies: [], url: "http://www.gstatic.com/generate_204", interval: 86400 }
  - { name: "故障转移", type: fallback, proxies: [], url: "http://www.gstatic.com/generate_204", interval: 7200 }

rules:
  # 自定义规则

上面配置文件中 rules: 部分千万勿留空!需要写完整规则!

3、打包时订阅类型填写为 vortex


V2board 使用 rule-provider(托管规则集)的自定义规则配置示例:

PreviousV2board 删除默认的订阅地址直连规则(强烈建议)NextSSPanel-Malio 内购接口

Last updated 1 year ago

规则集采用 的托管规则,且已替换为CDN

🖥️
https://github.com/dler-io/Rules
20KB
vortex-ruleprovider.yaml