梯子是我们日常访问被限制网站的常用工具,但它通常只关注连接速度,而非目标网站的区域访问限制。这意味着即使连接成功,我们仍可能遇到“该地区已被封锁”的提示。直接修改代理配置文件是种方法,但每次更新都会失效。脚本注入虽然能解决部分问题,但在切换不同代理服务时,代理名称的变化会导致无法精准选择地区节点。

经过一番探索,我终于找到了一个有效的解决方案:通过脚本提取各国家国旗和国家名称,然后根据所需的国家进行替换代理节点。 这样一来,无论代理服务如何切换,我们都能稳定地指定目标地区的节点,确保访问的成功率。

我在linuxwin下使用的客户端都是clash verge rev

https://github.com/Clash-Verge-rev/clash-verge-rev

在它的订阅处可以自定义全局扩展脚本。

我需要让gemini的节点使用美国节点(锁区有些厉害),琢磨了下,搞了个鲁棒性不错的脚本

开源在github

https://github.com/LuorixDev/ClashVerge_UsefulScript

这边也留下一份

/**
 * =============================================================================
 * Universal Domain-to-Policy Rule Manager
 * =============================================================================
 *
 * 本脚本旨在为多种网络代理工具(如 Clash 等)的配置文件,
 * 自动添加和管理特定域名与其期望的代理策略组或代理节点之间的映射规则。
 * 作者:洛元 Luorix
 * 作者个人主页:https://blog.dimeta.top
 *
 * 工作流程:
 * 1. 遍历用户在 `USER_CONFIG.ruleMappings` 中定义的所有规则映射。
 * 2. 对每个启用的映射,首先尝试通过通用关键词 (keywords) 找到最匹配的策略组。
 * 3. 如果找不到,则会尝试使用备用域名 (fallbackDomain) 在现有规则中寻找一个合适的策略组。
 * 4. 如果以上两种方法都未能找到策略组,则会尝试直接在 `proxies` 列表中通过通用关键词寻找最匹配的代理节点。
 * 5. 确定目标策略组或代理节点后,将新规则以高优先级插入配置文件的规则列表顶部。
 * 6. 脚本会跳过已存在的规则,并提供详细的日志输出(可控)以便于调试。
 *
 * @param {object} config - 原始配置文件对象。
 * @returns {object} - 修改后的配置文件对象。
 */
function main(config) {
  // --- 核心配置区 (USER_CONFIG) ---
  // 您只需要在此处进行修改,以适配您的需求。
  const USER_CONFIG = {
    // 是否开启详细日志,开启后会在控制台输出脚本的每一步操作,便于调试
    verboseLogging: true,

    // 规则映射表:定义哪个域名应该走哪个策略组或代理节点
    // 您可以按需添加、删除或修改此数组中的对象
    ruleMappings: [
      {
        // 规则说明,仅用于注释,方便您识别
        name: 'Google Gemini',
        // 需要应用规则的域名
        domain: 'gemini.google.com',
        // 规则类型, 'DOMAIN-SUFFIX' 为最优选择
        ruleType: 'DOMAIN-SUFFIX',
        // 寻找策略组或代理的关键词 (不区分大小写),可以是国家、地区、服务商等任何描述性词语
        keywords: ['🇺🇸', 'US', 'America', 'United States', 'Premium US'],
        // 备用查找域名:如果上方关键词找不到策略组,则会查找此域名的现有规则来决定策略组
        fallbackDomain: 'google.com',
        // 是否启用此条规则
        enabled: true,
      },
      {
        name: 'BBC Media',
        domain: 'bbc.co.uk',
        ruleType: 'DOMAIN-SUFFIX',
        // 寻找英国策略组的关键词
        keywords: ['🇬🇧', 'UK', 'GB', 'United Kingdom', '英国', 'London'],
        fallbackDomain: 'google.co.uk',
        enabled: true,
      },
      {
        name: 'Netflix',
        domain: 'netflix.com',
        ruleType: 'DOMAIN-SUFFIX',
        // 寻找日本策略组的关键词
        keywords: ['🇯🇵', 'JP', 'Japan', '日本', 'Tokyo Node'],
        fallbackDomain: 'dmm.com',
        enabled: true,
      },
      {
        name: 'Example Disabled Rule',
        domain: 'example.com',
        ruleType: 'DOMAIN-SUFFIX',
        keywords: ['HK', 'Hong Kong', '香港', 'Azure HK'],
        fallbackDomain: 'google.com.hk',
        // 此规则被禁用,脚本将直接跳过
        enabled: false,
      },
    ],
  };

  // --- 核心逻辑区 ---
  // 一般情况下无需修改以下代码

  const logger = createLogger(USER_CONFIG.verboseLogging);
  const rules = config.rules || [];

  logger.log('脚本开始执行...');

  // 遍历所有定义的规则映射
  for (const mapping of USER_CONFIG.ruleMappings) {
    if (!mapping.enabled) {
      logger.log(`规则 [${mapping.name}] 已被禁用,跳过。`);
      continue;
    }

    logger.log(`正在处理规则 [${mapping.name}]...`);

    // 1. 查找目标策略组或代理节点
    const target = findTarget(config, mapping, logger);

    // 2. 如果找到目标(策略组或代理),则添加规则
    if (target) {
      const newRule = `${mapping.ruleType},${mapping.domain},${target}`;

      if (!rules.includes(newRule)) {
        rules.unshift(newRule); // 添加到规则列表顶部以获得高优先级
        logger.log(`✅ 成功添加规则: "${newRule}"`);
      } else {
        logger.log(`ℹ️ 规则 "${newRule}" 已存在,无需添加。`);
      }
    } else {
      logger.log(`⚠️ 未能为域名 [${mapping.domain}] 找到合适的策略组或代理节点,跳过添加。`);
    }
  }

  logger.log('脚本执行完毕。');
  return config;
}

/**
 * 为给定的规则映射查找最佳的目标(策略组或代理节点)。
 * 它会先尝试通过关键词查找策略组,如果失败,再尝试后备域名查找策略组,
 * 最后如果仍未找到,则尝试直接通过关键词查找代理节点。
 * @param {object} config - 配置文件对象。
 * @param {object} mapping - 单个规则映射对象。
 * @param {object} logger - 日志记录器实例。
 * @returns {string|null} 找到的策略组或代理节点名称,否则为 null。
 */
function findTarget(config, mapping, logger) {
  // 优先通过关键词在策略组中查找
  let targetName = findGroupByKeywords(config, mapping.keywords);
  if (targetName) {
    logger.log(`   ↳ 通过关键词 [${mapping.keywords.join(', ')}] 在策略组中找到: "${targetName}"`);
    return targetName;
  }
  logger.log(`   ↳ 未通过关键词在策略组中找到 [${mapping.name}]。`);

  // 如果关键词查找策略组失败,并且配置了 fallbackDomain,则尝试后备方案
  if (mapping.fallbackDomain) {
    logger.log(`   ↳ 正在尝试通过备用域名 [${mapping.fallbackDomain}] 在现有规则中寻找策略组...`);
    targetName = findFallbackGroup(config, mapping.fallbackDomain);
    if (targetName) {
      logger.log(`   ↳ 通过备用域名找到策略组: "${targetName}"`);
      return targetName;
    }
    logger.log(`   ↳ 未通过备用域名找到 [${mapping.name}] 的策略组。`);
  }

  // 如果以上都失败,则尝试直接在代理节点中查找
  logger.log(`   ↳ 未找到合适的策略组,正在尝试直接在代理节点中查找 [${mapping.keywords.join(', ')}]...`);
  targetName = findProxyByKeywords(config, mapping.keywords);
  if (targetName) {
    logger.log(`   ↳ 通过关键词在代理节点中找到: "${targetName}"`);
    return targetName;
  }
  logger.log(`   ↳ 未在代理节点中找到 [${mapping.name}] 的目标。`);

  return null;
}

/**
 * 在 `proxy-groups` 中通过关键词数组查找策略组名称。
 * @param {object} config - 配置文件对象。
 * @param {string[]} keywords - 关键词数组。
 * @returns {string|null} 策略组名称或 null。
 */
function findGroupByKeywords(config, keywords) {
  const groups = config['proxy-groups'] || [];
  for (const group of groups) {
    const groupName = group.name || '';
    if (keywords.some(k => groupName.toLowerCase().includes(k.toLowerCase()))) {
      return groupName;
    }
  }
  return null;
}

/**
 * 在现有 `rules` 中查找基于特定域名的规则,并返回其使用的策略组。
 * @param {object} config - 配置文件对象。
 * @param {string} fallbackDomain - 要在规则中查找的域名。
 * @returns {string|null} 策略组名称或 null。
 */
function findFallbackGroup(config, fallbackDomain) {
  const rules = config.rules || [];
  for (const rule of rules) {
    // 确保规则是字符串并且包含 'POLICY,VALUE,GROUP' 这种格式
    if (typeof rule === 'string' && rule.includes(fallbackDomain)) {
      const parts = rule.split(',');
      // 必须是 'TYPE,VALUE,POLICY' 格式, 且 VALUE 部分包含目标域名
      if (parts.length >= 3 && parts[1].includes(fallbackDomain)) {
        return parts[2]; // parts[2] 是策略组名称
      }
    }
  }
  return null;
}

/**
 * 在 `proxies` 中通过关键词数组查找代理节点名称。
 * @param {object} config - 配置文件对象。
 * @param {string[]} keywords - 关键词数组。
 * @returns {string|null} 代理节点名称或 null。
 */
function findProxyByKeywords(config, keywords) {
  const proxies = config.proxies || [];
  for (const proxy of proxies) {
    const proxyName = proxy.name || '';
    if (keywords.some(k => proxyName.toLowerCase().includes(k.toLowerCase()))) {
      return proxyName;
    }
  }
  return null;
}

/**
 * 创建一个简单的日志记录器。
 * @param {boolean} isEnabled - 是否启用日志输出。
 * @returns {{log: function}} 日志记录器对象。
 */
function createLogger(isEnabled) {
  return {
    log: (message) => {
      if (isEnabled) {
        console.log(message);
      }
    },
  };
}