// 脚本引用 https://raw.githubusercontent.com/xream/scripts/main/surge/modules/network-info/net-lsp-x.js const NAME = 'network-info' const $ = new Env(NAME) let arg if (typeof $argument != 'undefined') { arg = Object.fromEntries($argument.split('&').map(item => item.split('='))) } else { arg = {} } $.log(`传入的 $argument: ${$.toStr(arg)}`) // if (typeof $loon === 'string') { // // const build = $loon.match(/\(\s*?(\d+)\s*?\)\s*?$/)?.[1] // // $.log(`当前 Loon Build: ${build}`) // $.log(`当前版本: ${$loon}`) // } arg = { ...arg, ...$.getjson(NAME, {}) } $.log(`从持久化存储读取参数后: ${$.toStr(arg)}`) if (typeof $environment !== 'undefined' && $.lodash_get($environment, 'executor') === 'event-network') { $.log(`QX 事件脚本不能带参 修正运行环境`) $.lodash_set(arg, 'TYPE', 'EVENT') } if (!isInteraction() && !isRequest() && !isTile() && !isPanel()) { $.log(`参数为空 非可交互操作, 非请求, 非面板的情况下, 修正运行环境`) $.lodash_set(arg, 'TYPE', 'EVENT') } if (isRequest()) { // $.log($.toStr($request)) arg = { ...arg, ...parseQueryString($request.url) } $.log(`从请求后读取参数后: ${$.toStr(arg)}`) } const keya = 'spe' const keyb = 'ge' const keyc = 'pin' const keyd = 'gan' const keye = 'pi' const keyf = 'ob' const bay = 'edtest' let result = {} let proxy_policy = '' let title = '' let content = '' !(async () => { if ($.lodash_get(arg, 'TYPE') === 'EVENT') { const eventDelay = parseFloat($.lodash_get(arg, 'EVENT_DELAY') || 3) $.log(`网络变化, 等待 ${eventDelay} 秒后开始查询`) if (eventDelay) { await $.wait(1000 * eventDelay) } } if (isTile()) { await notify('入口落地查询', '面板', '开始查询') } let SSID = '' let LAN = '' let LAN_IPv4 = '' let LAN_IPv6 = '' if (typeof $network !== 'undefined') { $.log($.toStr($network)) const v4 = $.lodash_get($network, 'v4.primaryAddress') const v6 = $.lodash_get($network, 'v6.primaryAddress') if ($.lodash_get(arg, 'SSID') == 1) { SSID = $.lodash_get($network, 'wifi.ssid') } if (v4 && $.lodash_get(arg, 'LAN') == 1) { LAN_IPv4 = v4 } if (v6 && $.lodash_get(arg, 'LAN') == 1 && $.lodash_get(arg, 'IPv6') == 1) { LAN_IPv6 = v6 } } else if (typeof $config !== 'undefined') { try { let conf = $config.getConfig() $.log(conf) conf = JSON.parse(conf) if ($.lodash_get(arg, 'SSID') == 1) { SSID = $.lodash_get(conf, 'ssid') } } catch (e) {} } else if (typeof $environment !== 'undefined') { try { $.log($.toStr($environment)) const version = $.lodash_get($environment, 'version') const os = version?.split(' ')?.[0] // QX 上 macOS/iOS 不一致 if (os !== 'macOS' && $.lodash_get(arg, 'SSID') == 1) { SSID = $.lodash_get($environment, 'ssid') } else if (os === 'macOS' && $.lodash_get(arg, 'LAN') == 1) { LAN_IPv4 = $.lodash_get($environment, 'ssid') } } catch (e) {} } if (LAN_IPv4 || LAN_IPv6) { LAN = ['LAN:', LAN_IPv4, maskIP(LAN_IPv6)].filter(i => i).join(' ') } if (LAN) { LAN = `${LAN}\n\n` } if (SSID) { SSID = `SSID: ${SSID}\n\n` } else { SSID = '' } let { PROXIES = [] } = await getProxies() let [ { CN_IP = '', CN_INFO = '', CN_POLICY = '' } = {}, { PROXY_IP = '', PROXY_INFO = '', PROXY_PRIVACY = '', PROXY_POLICY = '', ENTRANCE_IP = '' } = {}, { CN_IPv6 = '' } = {}, { PROXY_IPv6 = '' } = {}, ] = await Promise.all( $.lodash_get(arg, 'IPv6') == 1 ? [getDirectRequestInfo({ PROXIES }), getProxyRequestInfo({ PROXIES }), getDirectInfoIPv6(), getProxyInfoIPv6()] : [getDirectRequestInfo({ PROXIES }), getProxyRequestInfo({ PROXIES })] ) let continueFlag = true if ($.lodash_get(arg, 'TYPE') === 'EVENT') { const lastNetworkInfoEvent = $.getjson('lastNetworkInfoEvent') if ( CN_IP !== $.lodash_get(lastNetworkInfoEvent, 'CN_IP') || CN_IPv6 !== $.lodash_get(lastNetworkInfoEvent, 'CN_IPv6') || PROXY_IP !== $.lodash_get(lastNetworkInfoEvent, 'PROXY_IP') || PROXY_IPv6 !== $.lodash_get(lastNetworkInfoEvent, 'PROXY_IPv6') ) { // 有任何一项不同 都继续 $.setjson({ CN_IP, PROXY_IP, CN_IPv6, PROXY_IPv6 }, 'lastNetworkInfoEvent') } else { // 否则 直接结束 $.log('入口落地查询未发生变化, 不继续') continueFlag = false } } if (continueFlag) { if ($.lodash_get(arg, 'PRIVACY') == '1' && PROXY_PRIVACY) { PROXY_PRIVACY = `\n${PROXY_PRIVACY}` } let ENTRANCE = '' if (ENTRANCE_IP) { const { IP: resolvedIP } = await resolveDomain(ENTRANCE_IP) if (resolvedIP) { $.log(`入口域名解析: ${ENTRANCE_IP} ➟ ${resolvedIP}`) ENTRANCE_IP = resolvedIP } } if (ENTRANCE_IP && ENTRANCE_IP !== PROXY_IP) { const entranceDelay = parseFloat($.lodash_get(arg, 'ENTRANCE_DELAY') || 0) $.log(`入口: ${ENTRANCE_IP} 与落地 IP: ${PROXY_IP} 不一致, 等待 ${entranceDelay} 秒后查询入口`) if (entranceDelay) { await $.wait(1000 * entranceDelay) } let [{ CN_INFO: ENTRANCE_INFO1 = '', isCN = false } = {}, { PROXY_INFO: ENTRANCE_INFO2 = '' } = {}] = await Promise.all([ getDirectInfo(ENTRANCE_IP, $.lodash_get(arg, 'DOMESTIC_IPv4')), getProxyInfo(ENTRANCE_IP, $.lodash_get(arg, 'LANDING_IPv4')), ]) // 国内接口的国外 IP 解析过于离谱 排除掉 if (ENTRANCE_INFO1 && isCN) { ENTRANCE = `入口: ${maskIP(ENTRANCE_IP) || '-'}\n${maskAddr(ENTRANCE_INFO1)}` } if (ENTRANCE_INFO2) { if (ENTRANCE) { ENTRANCE = `${ENTRANCE.replace(/^(.*?):/gim, '$1¹:')}\n${maskAddr( ENTRANCE_INFO2.replace(/^(.*?):/gim, '$1²:') )}` } else { ENTRANCE = `入口: ${maskIP(ENTRANCE_IP) || '-'}\n${maskAddr(ENTRANCE_INFO2)}` } } } if (ENTRANCE) { ENTRANCE = `${ENTRANCE}\n\n` } if (CN_IPv6 && isIPv6(CN_IPv6) && $.lodash_get(arg, 'IPv6') == 1) { CN_IPv6 = `\n${maskIP(CN_IPv6)}` } else { CN_IPv6 = '' } if (PROXY_IPv6 && isIPv6(PROXY_IPv6) && $.lodash_get(arg, 'IPv6') == 1) { PROXY_IPv6 = `\n${maskIP(PROXY_IPv6)}` } else { PROXY_IPv6 = '' } if ($.isSurge() || $.isStash()) { if (CN_POLICY === 'DIRECT') { CN_POLICY = `` } else { CN_POLICY = `策略: ${maskAddr(CN_POLICY) || '-'}\n` } } if (CN_INFO) { CN_INFO = `\n${CN_INFO}` } const policy_prefix = $.isQuanX() || $.isLoon() ? '节点: ' : '代理策略: ' if (PROXY_POLICY === 'DIRECT') { PROXY_POLICY = `${policy_prefix}直连` } else if (PROXY_POLICY) { PROXY_POLICY = `${policy_prefix}${maskAddr(PROXY_POLICY) || '-'}` } else { PROXY_POLICY = '' } if (PROXY_POLICY) { proxy_policy = PROXY_POLICY } else { proxy_policy = '' } if (PROXY_INFO) { PROXY_INFO = `\n${PROXY_INFO}` } title = `${PROXY_POLICY}` content = `${SSID}${LAN}${CN_POLICY}IP: ${maskIP(CN_IP) || '-'}${CN_IPv6}${maskAddr( CN_INFO )}\n\n${ENTRANCE}落地 IP: ${maskIP(PROXY_IP) || '-'}${PROXY_IPv6}${maskAddr(PROXY_INFO)}${PROXY_PRIVACY}` if (!isInteraction()) { content = `${content}\n执行时间: ${new Date().toTimeString().split(' ')[0]}` } title = title || '入口落地查询' if (isTile()) { await notify('入口落地查询', '面板', '查询完成') } else if (!isPanel()) { if ($.lodash_get(arg, 'TYPE') === 'EVENT') { await notify( `🄳 ${maskIP(CN_IP) || '-'} 🅿 ${maskIP(PROXY_IP) || '-'}`.replace(/\n+/g, '\n').replace(/\ +/g, ' ').trim(), `${maskAddr(CN_INFO.replace(/(位置|运营商).*?:/g, '').replace(/\n/g, ' '))}` .replace(/\n+/g, '\n') .replace(/\ +/g, ' ') .trim(), `${maskAddr(PROXY_INFO.replace(/(位置|运营商).*?:/g, '').replace(/\n/g, ' '))}${ CN_IPv6 ? `\n🄳 ${CN_IPv6.replace(/\n+/g, '')}` : '' }${PROXY_IPv6 ? `\n🅿 ${PROXY_IPv6.replace(/\n+/g, '')}` : ''}${SSID ? `\n${SSID}` : '\n'}${LAN}` .replace(/\n+/g, '\n') .replace(/\ +/g, ' ') .trim() ) } else { await notify('入口落地查询', title, content) } } } })() .catch(async e => { $.logErr(e) $.logErr($.toStr(e)) const msg = `${$.lodash_get(e, 'message') || $.lodash_get(e, 'error') || e}` title = `❌` content = msg await notify('入口落地查询', title, content) }) .finally(async () => { if (isRequest()) { result = { response: { status: 200, body: JSON.stringify( { title, content, }, null, 2 ), headers: { 'Content-Type': 'application/json; charset=UTF-8', 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'POST,GET,OPTIONS,PUT,DELETE', 'Access-Control-Allow-Headers': 'Origin, X-Requested-With, Content-Type, Accept', }, }, } } else { result = { title, content, ...arg } } $.log($.toStr(result)) if (isInteraction()) { const html = `
${`\n${content}${ proxy_policy ? `\n\n${proxy_policy.replace(/^(.*?:\s*)(.*)$/, '$1$2')}` : '' }` .replace(/^(.*?):/gim, '$1:') .replace(/\n/g, '
')}
` // $.log(html) $.done({ title: '入口落地查询', htmlMessage: html, }) } else { $.done(result) } }) async function getEntranceInfo() { let IP = '' let POLICY = '' if (isInteraction()) { try { if ($.isQuanX()) { const nodeName = $environment.params const { ret, error } = await $configuration.sendMessage({ action: 'get_server_description', content: nodeName }) if (error) throw new Error(error) // $.log(JSON.stringify(ret, null, 2)) const proxy = Object.values(ret)[0] // $.log(proxy) IP = proxy.match(/.+?\s*?=\s*?(.+?):\d+\s*?,.+/)[1] POLICY = nodeName } else if ($.isLoon()) { IP = $.lodash_get($environment, 'params.nodeInfo.address') POLICY = $.lodash_get($environment, 'params.node') } } catch (e) { $.logErr(`获取入口信息 发生错误: ${e.message || e}`) $.logErr(e) $.logErr($.toStr(e)) } } return { IP, POLICY } } async function getDirectRequestInfo({ PROXIES = [] } = {}) { const { CN_IP, CN_INFO } = await getDirectInfo(undefined, $.lodash_get(arg, 'DOMESTIC_IPv4')) const { POLICY } = await getRequestInfo( new RegExp( `cip\\.cc|for${keyb}\\.${keya}${bay}\\.cn|rmb\\.${keyc}${keyd}\\.com\\.cn|api-v3\\.${keya}${bay}\\.cn|ipservice\\.ws\\.126\\.net|api\\.bilibili\\.com|api\\.live\\.bilibili\\.com|myip\\.ipip\\.net|ip\\.ip233\\.cn|ua${keye}\\.wo${keyf}x\\.cn|ip\\.im|ips\\.market\\.alicloudapi\\.com|api\\.ip\\.plus|ip\\.qtfm\\.cn|dashi\\.163\\.com|api\\.zhuishushenqi\\.com|admin-app\\.edifier\\.com` ), PROXIES ) return { CN_IP, CN_INFO, CN_POLICY: POLICY } } async function getProxyRequestInfo({ PROXIES = [] } = {}) { const { PROXY_IP, PROXY_INFO, PROXY_PRIVACY } = await getProxyInfo(undefined, $.lodash_get(arg, 'LANDING_IPv4')) let result if ($.isSurge() || $.isStash()) { result = await getRequestInfo(/ipinfo\.io|ip-score\.com|ipwhois\.app|ip-api\.com|api-ipv4\.ip\.sb/, PROXIES) } else if ($.isQuanX() || $.isLoon()) { result = await getEntranceInfo() } return { PROXY_IP, PROXY_INFO, PROXY_PRIVACY, PROXY_POLICY: $.lodash_get(result, 'POLICY'), ENTRANCE_IP: $.lodash_get(result, 'IP'), } } async function getRequestInfo(regexp, PROXIES = []) { let POLICY = '' let IP = '' try { if ($.isSurge()) { const { requests } = await httpAPI('/v1/requests/recent', 'GET') const request = requests.slice(0, 10).find(i => regexp.test(i.URL)) // $.log('recent request', $.toStr(request)) POLICY = request.policyName if (/\(Proxy\)/.test(request.remoteAddress)) { IP = request.remoteAddress.replace(/\s*\(Proxy\)\s*/, '') } } else if ($.isStash()) { const res = await $.http.get({ url: `http://127.0.0.1:9090/connections`, }) let body = String($.lodash_get(res, 'body') || $.lodash_get(res, 'rawBody')) try { body = JSON.parse(body) } catch (e) {} const connections = $.lodash_get(body, 'connections') || [] const connection = connections.slice(0, 10).find(i => { const dest = $.lodash_get(i, 'metadata.host') || $.lodash_get(i, 'metadata.destinationIP') return regexp.test(dest) }) || {} const chain = $.lodash_get(connection, 'metadata.chain') || [] const proxy = chain[0] POLICY = proxy // chain.reverse().join(' ➟ ') IP = PROXIES?.[proxy]?.match(/^(.*?):\d+$/)?.[1] } } catch (e) { $.logErr(`从最近请求中获取 ${regexp} 发生错误: ${e.message || e}`) $.logErr(e) $.logErr($.toStr(e)) } return { POLICY, IP, } } async function getDirectInfo(ip, provider) { let CN_IP let CN_INFO let isCN const msg = `使用 ${provider || 'pingan'} 查询 ${ip ? ip : '分流'} 信息` if (provider == 'cip') { try { const res = await http({ url: `https://cip.cc/${ip ? encodeURIComponent(ip) : ''}`, headers: { 'User-Agent': 'curl/7.16.3 (powerpc-apple-darwin9.0) libcurl/7.16.3' }, }) let body = String($.lodash_get(res, 'body')) const addr = body.match(/地址\s*(:|:)\s*(.*)/)[2] isCN = addr.includes('中国') CN_IP = ip || body.match(/IP\s*(:|:)\s*(.*?)\s/)[2] CN_INFO = [ ['位置:', isCN ? getflag('CN') : undefined, addr.replace(/中国\s*/, '') || ''].filter(i => i).join(' '), ['运营商:', body.match(/运营商\s*(:|:)\s*(.*)/)[2].replace(/中国\s*/, '') || ''].filter(i => i).join(' '), ] .filter(i => i) .join('\n') } catch (e) { $.logErr(`${msg} 发生错误: ${e.message || e}`) } } else if (!ip && provider == 'qtfm') { try { const res = await http({ url: `https://ip.qtfm.cn/ip`, headers: { 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36 Edg/109.0.1518.14', }, }) let body = String($.lodash_get(res, 'body')) try { body = JSON.parse(body) } catch (e) {} const data = body?.data const ip = data?.ip isCN = data?.country === '中国' CN_IP = ip CN_INFO = [ ['位置:', isCN ? getflag('CN') : '', data?.region, data?.city].filter(i => i).join(' '), ['运营商:', data?.isp].filter(i => i).join(' '), ] .filter(i => i) .join('\n') } catch (e) { $.logErr(`${msg} 发生错误: ${e.message || e}`) } } else if (!ip && provider == '163') { try { const res = await http({ url: `https://dashi.163.com/fgw/mailsrv-ipdetail/detail`, headers: { 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36 Edg/109.0.1518.14', }, }) let body = String($.lodash_get(res, 'body')) try { body = JSON.parse(body) } catch (e) {} let data = $.lodash_get(body, 'result') const ip = data?.ip const countryCode = data?.countryCode isCN = countryCode === 'CN' CN_IP = ip CN_INFO = [ ['位置:', getflag(countryCode), data?.province, data?.city].filter(i => i).join(' '), ['运营商:', data?.isp || data?.org].filter(i => i).join(' '), ] .filter(i => i) .join('\n') } catch (e) { $.logErr(`${msg} 发生错误: ${e.message || e}`) } } else if (!ip && provider == 'ipip') { try { const res = await http({ url: `https://myip.ipip.net/json`, headers: { 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36 Edg/109.0.1518.14', }, }) let body = String($.lodash_get(res, 'body')) try { body = JSON.parse(body) } catch (e) {} isCN = $.lodash_get(body, 'data.location.0') === '中国' CN_IP = $.lodash_get(body, 'data.ip') CN_INFO = [ [ '位置:', isCN ? getflag('CN') : undefined, $.lodash_get(body, 'data.location.0'), $.lodash_get(body, 'data.location.1'), $.lodash_get(body, 'data.location.2'), ] .filter(i => i) .join(' '), ['运营商:', $.lodash_get(body, 'data.location.4')].filter(i => i).join(' '), ] .filter(i => i) .join('\n') } catch (e) { $.logErr(`${msg} 发生错误: ${e.message || e}`) } } else if (!ip && provider == 'bilibili') { try { const res = await http({ url: `https://api.bilibili.com/x/web-interface/zone`, // url: `https://api.live.bilibili.com/ip_service/v1/ip_service/get_ip_addr`, headers: { 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36 Edg/109.0.1518.14', }, }) let body = String($.lodash_get(res, 'body')) try { body = JSON.parse(body) } catch (e) {} isCN = $.lodash_get(body, 'data.country') === '中国' CN_IP = $.lodash_get(body, 'data.addr') CN_INFO = [ [ '位置:', isCN ? getflag('CN') : undefined, $.lodash_get(body, 'data.country'), $.lodash_get(body, 'data.province'), $.lodash_get(body, 'data.city'), ] .filter(i => i) .join(' '), ['运营商:', $.lodash_get(body, 'data.isp')].filter(i => i).join(' '), ] .filter(i => i) .join('\n') } catch (e) { $.logErr(`${msg} 发生错误: ${e.message || e}`) } } else if (!ip && provider == 'zssq') { try { const res = await http({ url: `https://api.zhuishushenqi.com/user/getCity`, headers: { 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36 Edg/109.0.1518.14', }, }) let body = String($.lodash_get(res, 'body')) try { body = JSON.parse(body) } catch (e) {} isCN = $.lodash_get(body, 'data.country') === '中国' CN_IP = $.lodash_get(body, 'data.ip') CN_INFO = [ [ '位置:', isCN ? getflag('CN') : undefined, $.lodash_get(body, 'data.country'), $.lodash_get(body, 'data.province'), $.lodash_get(body, 'data.city'), ] .filter(i => i) .join(' '), ] .filter(i => i) .join('\n') } catch (e) { $.logErr(`${msg} 发生错误: ${e.message || e}`) } } else if (!ip && provider == 'edifier') { try { const res = await http({ url: `https://admin-app.edifier.com/edifier_provider/public/user-ip`, headers: { 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36 Edg/109.0.1518.14', }, }) let body = String($.lodash_get(res, 'body')) try { body = JSON.parse(body) } catch (e) {} isCN = $.lodash_get(body, 'data.country') === '中国' CN_IP = $.lodash_get(body, 'data.ip') CN_INFO = [ [ '位置:', isCN ? getflag('CN') : undefined, $.lodash_get(body, 'data.country'), $.lodash_get(body, 'data.province'), $.lodash_get(body, 'data.city'), ] .filter(i => i) .join(' '), ] .filter(i => i) .join('\n') } catch (e) { $.logErr(`${msg} 发生错误: ${e.message || e}`) } } else if (!ip && provider == '126') { try { const res = await http({ url: `https://ipservice.ws.126.net/locate/api/getLocByIp`, headers: { 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36 Edg/109.0.1518.14', }, }) let body = String($.lodash_get(res, 'body')) try { body = JSON.parse(body) } catch (e) {} const countryCode = $.lodash_get(body, 'result.countrySymbol') isCN = countryCode === 'CN' CN_IP = $.lodash_get(body, 'result.ip') CN_INFO = [ [ '位置:', getflag(countryCode), $.lodash_get(body, 'result.country'), $.lodash_get(body, 'result.province'), $.lodash_get(body, 'result.city'), ] .filter(i => i) .join(' '), ['运营商:', $.lodash_get(body, 'result.operator') || $.lodash_get(body, 'result.company')] .filter(i => i) .join(' '), ] .filter(i => i) .join('\n') } catch (e) { $.logErr(`${msg} 发生错误: ${e.message || e}`) } } else if (!ip && provider == 'ip233') { try { const res = await http({ url: `https://ip.ip233.cn/ip`, headers: { Referer: 'https://ip233.cn/', 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36 Edg/121.0.0.0', }, }) let body = String($.lodash_get(res, 'body')) try { body = JSON.parse(body) } catch (e) {} const countryCode = $.lodash_get(body, 'country') isCN = countryCode === 'CN' CN_IP = $.lodash_get(body, 'ip') CN_INFO = CN_INFO = [ ['位置:', getflag(countryCode), $.lodash_get(body, 'desc').replace(/中国\s*/, '')].filter(i => i).join(' '), $.lodash_get(arg, 'ORG') == 1 ? ['组织:', $.lodash_get(body, 'org') || '-'].filter(i => i).join(' ') : undefined, ] .filter(i => i) .join('\n') } catch (e) { $.logErr(`${msg} 发生错误: ${e.message || e}`) } } else if (provider == 'muhan') { try { const res = await http({ url: `https://ua${keye}.wo${keyf}x.cn/app/ip-location`, params: { ip }, headers: { 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36 Edg/121.0.0.0', }, }) let body = String($.lodash_get(res, 'body')) try { body = JSON.parse(body) } catch (e) {} const countryCode = $.lodash_get(body, 'data.showapi_res_body.en_name_short') isCN = countryCode === 'CN' CN_IP = ip || $.lodash_get(body, 'data.showapi_res_body.ip') CN_INFO = [ [ '位置:', getflag(countryCode), $.lodash_get(body, 'data.showapi_res_body.country').replace(/\s*中国\s*/, ''), $.lodash_get(body, 'data.showapi_res_body.region'), $.lodash_get(body, 'data.showapi_res_body.city'), $.lodash_get(body, 'data.showapi_res_body.county'), ] .filter(i => i) .join(' '), ['运营商:', $.lodash_get(body, 'data.showapi_res_body.isp') || '-'].filter(i => i).join(' '), ] .filter(i => i) .join('\n') } catch (e) { $.logErr(`${msg} 发生错误: ${e.message || e}`) } } else if (provider == 'ipim') { try { const res = await ipim(ip) isCN = $.lodash_get(res, 'isCN') CN_IP = $.lodash_get(res, 'IP') CN_INFO = $.lodash_get(res, 'INFO') } catch (e) { $.logErr(`${msg} 发生错误: ${e.message || e}`) } } else if (provider == 'ali') { try { let APPCODE = $.lodash_get(arg, 'DOMESTIC_IPv4_KEY') if (!APPCODE) throw new Error('请在 DOMESTIC_IPv4_KEY 填写阿里云 IP 接口的 APPCODE') APPCODE = APPCODE.split(/,|,/) .map(i => i.trim()) .filter(i => i) APPCODE = APPCODE[Math.floor(Math.random() * APPCODE.length)] if (APPCODE.length > 1) { $.log(`随机使用阿里云 IP 接口的 APPCODE: ${APPCODE}`) } let ali_ip = ip if (!ali_ip) { $.log('阿里云接口需要 IP. 未提供 IP, 先使用默认 IP 查询') const res = await getDirectInfo() ali_ip = $.lodash_get(res, 'CN_IP') if (!ali_ip) throw new Error('阿里云接口需要 IP. 未提供 IP, 使用默认 IP 查询失败') } const res = await ali(ali_ip, APPCODE) isCN = $.lodash_get(res, 'isCN') CN_IP = $.lodash_get(res, 'IP') CN_INFO = $.lodash_get(res, 'INFO') } catch (e) { $.logErr(`${msg} 发生错误: ${e.message || e}`) } } else if (ip && provider == 'spcn') { try { const res = await http({ url: `https://api-v3.${keya}${bay}.cn/ip`, params: { ip }, headers: { 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36 Edg/109.0.1518.14', }, }) let body = String($.lodash_get(res, 'body')) try { body = JSON.parse(body) } catch (e) {} const countryCode = $.lodash_get(body, 'data.countryCode') isCN = countryCode === 'CN' CN_IP = ip || $.lodash_get(body, 'data.ip') CN_INFO = [ [ '位置:', getflag(countryCode), $.lodash_get(body, 'data.country').replace(/\s*中国\s*/, ''), $.lodash_get(body, 'data.province'), $.lodash_get(body, 'data.city'), $.lodash_get(body, 'data.district'), ] .filter(i => i) .join(' '), ['运营商:', $.lodash_get(body, 'data.operator') || $.lodash_get(body, 'data.isp') || '-'] .filter(i => i) .join(' '), ] .filter(i => i) .join('\n') } catch (e) { $.logErr(`${msg} 发生错误: ${e.message || e}`) } } else { try { const res = await http({ url: `https://rmb.${keyc}${keyd}.com.cn/itam/mas/linden/ip/request`, params: { ip }, headers: { 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36 Edg/121.0.0.0', }, }) let body = String($.lodash_get(res, 'body')) try { body = JSON.parse(body) } catch (e) {} const countryCode = $.lodash_get(body, 'data.countryIsoCode') isCN = countryCode === 'CN' CN_IP = ip || $.lodash_get(body, 'data.ip') CN_INFO = [ [ '位置:', getflag(countryCode), $.lodash_get(body, 'data.country').replace(/\s*中国\s*/, ''), $.lodash_get(body, 'data.region'), $.lodash_get(body, 'data.city'), ] .filter(i => i) .join(' '), ['运营商:', $.lodash_get(body, 'data.isp') || '-'].filter(i => i).join(' '), $.lodash_get(arg, 'ORG') == 1 ? ['组织:', $.lodash_get(body, 'org') || '-'].filter(i => i).join(' ') : undefined, ] .filter(i => i) .join('\n') } catch (e) { $.logErr(`${msg} 发生错误: ${e.message || e}`) } } return { CN_IP, CN_INFO: simplifyAddr(CN_INFO), isCN } } async function getDirectInfoIPv6() { let CN_IPv6 const msg = `使用 ${$.lodash_get(arg, 'DOMESTIC_IPv6') || 'ddnspod'} 查询 IPv6 分流信息` if ($.lodash_get(arg, 'DOMESTIC_IPv6') == 'neu6') { try { const res = await http({ url: `https://speed.neu6.edu.cn/getIP.php`, headers: { 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36 Edg/109.0.1518.14', }, }) let body = String($.lodash_get(res, 'body')) CN_IPv6 = body.trim() } catch (e) { $.logErr(`${msg} 发生错误: ${e.message || e}`) } } else { try { const res = await http({ url: `https://ipv6.ddnspod.com`, headers: { 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36 Edg/109.0.1518.14', }, }) let body = String($.lodash_get(res, 'body')) CN_IPv6 = body.trim() } catch (e) { $.logErr(`${msg} 发生错误: ${e.message || e}`) } } return { CN_IPv6 } } async function getProxyInfo(ip, provider) { let PROXY_IP let PROXY_INFO let PROXY_PRIVACY const msg = `使用 ${provider || 'ipapi'} 查询 ${ip ? ip : '分流'} 信息` if (provider == 'ipinfo') { try { let token = $.lodash_get(arg, 'LANDING_IPv4_KEY') if (!token) throw new Error('请在 LANDING_IPv4_KEY 填写 ipinfo 的 token') token = token .split(/,|,/) .map(i => i.trim()) .filter(i => i) token = token[Math.floor(Math.random() * token.length)] if (token.length > 1) { $.log(`随机使用 ipinfo 的 token: ${token}`) } const res = await http({ ...(ip ? {} : getNodeOpt()), url: ip ? `https://ipinfo.io/${encodeURIComponent(ip)}/json?token=${encodeURIComponent(token)}` : `https://ipinfo.io/json?token=${encodeURIComponent(token)}`, headers: { 'User-Agent': 'Mozilla/5.0 (iPhone CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1 Edg/109.0.0.0', }, }) let body = String($.lodash_get(res, 'body')) try { body = JSON.parse(body) } catch (e) {} PROXY_IP = ip || $.lodash_get(body, 'ip') PROXY_INFO = [ ['位置:', getflag(body.country), body.country.replace(/\s*中国\s*/, ''), body.region, body.city] .filter(i => i) .join(' '), ['运营商:', $.lodash_get(body, 'org') || '-'].filter(i => i).join(' '), ] .filter(i => i) .join('\n') } catch (e) { $.logErr(`${msg} 发生错误: ${e.message || e}`) } } else if (provider == 'ipscore') { try { const res = await http({ ...(ip ? {} : getNodeOpt()), url: `https://ip-score.com/json`, params: { ip }, headers: { 'User-Agent': 'Mozilla/5.0 (iPhone CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1 Edg/109.0.0.0', }, }) let body = String($.lodash_get(res, 'body')) try { body = JSON.parse(body) } catch (e) {} PROXY_IP = ip || $.lodash_get(body, 'ip') PROXY_INFO = [ [ '位置¹:', getflag($.lodash_get(body, 'geoip1.countrycode')), $.lodash_get(body, 'geoip1.country'), $.lodash_get(body, 'geoip1.region'), $.lodash_get(body, 'geoip1.city'), ] .filter(i => i) .join(' '), [ '位置²:', getflag($.lodash_get(body, 'geoip2.countrycode')), $.lodash_get(body, 'geoip2.country'), $.lodash_get(body, 'geoip2.region'), $.lodash_get(body, 'geoip2.city'), ] .filter(i => i) .join(' '), ['运营商:', body.isp || body.org || body.asn].filter(i => i).join(' '), ] .filter(i => i) .join('\n') } catch (e) { $.logErr(`${msg} 发生错误: ${e.message || e}`) } } else if (provider == 'ipsb') { try { const res = await http({ ...(ip ? {} : getNodeOpt()), url: `https://api-ipv4.ip.sb/geoip${ip ? `/${encodeURIComponent(ip)}` : ''}`, headers: { 'User-Agent': 'Mozilla/5.0 (iPhone CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1 Edg/109.0.0.0', }, }) let body = String($.lodash_get(res, 'body')) try { body = JSON.parse(body) } catch (e) {} PROXY_IP = ip || $.lodash_get(body, 'ip') PROXY_INFO = [ [ '位置:', getflag($.lodash_get(body, 'country_code')), $.lodash_get(body, 'country'), $.lodash_get(body, 'region'), $.lodash_get(body, 'city'), ] .filter(i => i) .join(' '), ['运营商:', body.isp || body.organization].filter(i => i).join(' '), $.lodash_get(arg, 'ORG') == 1 ? ['组织:', $.lodash_get(body, 'asn_organization') || '-'].filter(i => i).join(' ') : undefined, $.lodash_get(arg, 'ASN') == 1 ? ['ASN:', $.lodash_get(body, 'asn') || '-'].filter(i => i).join(' ') : undefined, ] .filter(i => i) .join('\n') } catch (e) { $.logErr(`${msg} 发生错误: ${e.message || e}`) } } else if (provider == 'ipwhois') { try { const res = await http({ ...(ip ? {} : getNodeOpt()), url: `https://ipwhois.app/widget.php`, params: { lang: 'zh-CN', ip, }, headers: { Host: 'ipwhois.app', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/116.0', Accept: '*/*', 'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2', 'Accept-Encoding': 'gzip, deflate, br', Origin: 'https://ipwhois.io', Connection: 'keep-alive', Referer: 'https://ipwhois.io/', 'Sec-Fetch-Dest': 'empty', 'Sec-Fetch-Mode': 'cors', 'Sec-Fetch-Site': 'cross-site', }, }) let body = String($.lodash_get(res, 'body')) try { body = JSON.parse(body) } catch (e) {} PROXY_IP = ip || $.lodash_get(body, 'ip') PROXY_INFO = [ ['位置:', getflag(body.country_code), body.country.replace(/\s*中国\s*/, ''), body.region, body.city] .filter(i => i) .join(' '), ['运营商:', $.lodash_get(body, 'connection.isp') || '-'].filter(i => i).join(' '), $.lodash_get(arg, 'ORG') == 1 ? ['组织:', $.lodash_get(body, 'connection.org') || '-'].filter(i => i).join(' ') : undefined, $.lodash_get(arg, 'ASN') == 1 ? ['ASN:', $.lodash_get(body, 'connection.asn') || '-'].filter(i => i).join(' ') : undefined, ] .filter(i => i) .join('\n') if (!ip && $.lodash_get(arg, 'PRIVACY') == 1) { const securityMap = { true: '✓', false: '✗', '': '-', } const securityObj = $.lodash_get(body, 'security') || {} let security = [] Object.keys(securityObj).forEach(key => { security.push(`${key.toUpperCase()}: ${securityMap[securityObj[key]]}`) }) if (security.length > 0) { PROXY_PRIVACY = `隐私安全:\n${security.join('\n')}` } else { PROXY_PRIVACY = `隐私安全: -` } } } catch (e) { $.logErr(`${msg} 发生错误: ${e.message || e}`) } } else { try { const p = ip ? `/${encodeURIComponent(ip)}` : '' const res = await http({ ...(ip ? {} : getNodeOpt()), url: `http://ip-api.com/json${p}?lang=zh-CN`, headers: { 'User-Agent': 'Mozilla/5.0 (iPhone CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1 Edg/109.0.0.0', }, }) let body = String($.lodash_get(res, 'body')) try { body = JSON.parse(body) } catch (e) {} PROXY_IP = ip || $.lodash_get(body, 'query') PROXY_INFO = [ [ '位置:', getflag(body.countryCode), body.country.replace(/\s*中国\s*/, ''), body.regionName ? body.regionName.split(/\s+or\s+/)[0] : body.regionName, body.city, ] .filter(i => i) .join(' '), ['运营商:', body.isp || body.org || body.as].filter(i => i).join(' '), $.lodash_get(arg, 'ORG') == 1 ? ['组织:', $.lodash_get(body, 'org') || '-'].filter(i => i).join(' ') : undefined, $.lodash_get(arg, 'ASN') == 1 ? ['ASN:', $.lodash_get(body, 'as') || '-'].filter(i => i).join(' ') : undefined, ] .filter(i => i) .join('\n') } catch (e) { $.logErr(`${msg} 发生错误: ${e.message || e}`) } } return { PROXY_IP, PROXY_INFO: simplifyAddr(PROXY_INFO), PROXY_PRIVACY } } async function getProxyInfoIPv6(ip) { let PROXY_IPv6 const msg = `使用 ${$.lodash_get(arg, 'LANDING_IPv6') || 'ipsb'} 查询 IPv6 分流信息` if ($.lodash_get(arg, 'LANDING_IPv6') == 'ident') { try { const res = await http({ ...(ip ? {} : getNodeOpt()), url: `https://v6.ident.me`, headers: { 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36 Edg/109.0.1518.14', }, }) let body = String($.lodash_get(res, 'body')) PROXY_IPv6 = body.trim() } catch (e) { $.logErr(`${msg} 发生错误: ${e.message || e}`) } } else if ($.lodash_get(arg, 'LANDING_IPv6') == 'ipify') { try { const res = await http({ ...(ip ? {} : getNodeOpt()), url: `https://api6.ipify.org`, headers: { 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36 Edg/109.0.1518.14', }, }) let body = String($.lodash_get(res, 'body')) PROXY_IPv6 = body.trim() } catch (e) { $.logErr(`${msg} 发生错误: ${e.message || e}`) } } else { try { const res = await http({ ...(ip ? {} : getNodeOpt()), url: `https://api-ipv6.ip.sb/ip`, headers: { 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36 Edg/109.0.1518.14', }, }) let body = String($.lodash_get(res, 'body')) PROXY_IPv6 = body.trim() } catch (e) { $.logErr(`${msg} 发生错误: ${e.message || e}`) } } return { PROXY_IPv6 } } async function ipim(ip) { let isCN let IP let INFO const res = await http({ url: `https://ip.im/${ip ? encodeURIComponent(ip) : 'info'}`, headers: { 'User-Agent': 'curl/7.16.3 (powerpc-apple-darwin9.0) libcurl/7.16.3' }, }) let body = String($.lodash_get(res, 'body')) IP = body.match(/(^|\s+)Ip\s*(:|:)\s*(.*)/m)?.[3] const country = body.match(/(^|\s+)Country\s*(:|:)\s*(.*)/m)?.[3] const province = body.match(/(^|\s+)Province\s*(:|:)\s*(.*)/m)?.[3] || body.match(/(^|\s+)Region\s*(:|:)\s*(.*)/m)?.[3] const city = body.match(/(^|\s+)City\s*(:|:)\s*(.*)/m)?.[3] const district = body.match(/(^|\s+)Districts\s*(:|:)\s*(.*)/m)?.[3] const isp = body.match(/(^|\s+)Isp\s*(:|:)\s*(.*)/m)?.[3] const org = body.match(/(^|\s+)Org\s*(:|:)\s*(.*)/m)?.[3] isCN = country.includes('中国') INFO = [ ['位置:', isCN ? getflag('CN') : getflag(country), country, province, city, district].filter(i => i).join(' '), ['运营商:', isp || '-'].filter(i => i).join(' '), $.lodash_get(arg, 'ORG') == 1 ? ['组织:', org || '-'].filter(i => i).join(' ') : undefined, ] .filter(i => i) .join('\n') return { IP, INFO, isCN } } async function ali(ip, key) { let isCN let IP let INFO const res = await http({ url: `https://ips.market.alicloudapi.com/iplocaltion`, params: { ip }, headers: { authorization: `APPCODE ${key}` }, }) let body = String($.lodash_get(res, 'body')) try { body = JSON.parse(body) } catch (e) {} IP = $.lodash_get(body, 'ip') const countryCode = $.lodash_get(body, 'result.en_short') isCN = countryCode === 'CN' INFO = [ [ '位置:', getflag(countryCode), $.lodash_get(body, 'result.nation').replace(/中国\s*/, ''), $.lodash_get(body, 'result.province'), $.lodash_get(body, 'result.city'), $.lodash_get(body, 'result.district'), ] .filter(i => i) .join(' '), ] .filter(i => i) .join('\n') return { IP, INFO, isCN } } function simplifyAddr(addr) { if (!addr) return '' return addr .split(/\n/) .map(i => Array.from(new Set(i.split(/\ +/))).join(' ')) .join('\n') } function maskAddr(addr) { if (!addr) return '' if ($.lodash_get(arg, 'MASK') == 1) { let result = '' const parts = addr.split(' ') if (parts.length >= 3) { // 如果有两个或更多的空格,按空格分组后将中间的部分替换为"*" result = [parts[0], '*', parts[parts.length - 1]].join(' ') } else { // 如果空格少于2个,将字符串三等分,将中间的部分替换为"*" const third = Math.floor(addr.length / 3) result = addr.substring(0, third) + '*'.repeat(third) + addr.substring(2 * third) } return result } else { return addr } } function maskIP(ip) { if (!ip) return '' if ($.lodash_get(arg, 'MASK') == 1) { let result = '' if (ip.includes('.')) { // IPv4 let parts = ip.split('.') result = [...parts.slice(0, 2), '*', '*'].join('.') } else { // IPv6 let parts = ip.split(':') result = [...parts.slice(0, 4), '*', '*', '*', '*'].join(':') } return result } else { return ip } } function getflag(e) { if ($.lodash_get(arg, 'FLAG', 1) == 1) { try { const t = e .toUpperCase() .split('') .map(e => 127397 + e.charCodeAt()) // return String.fromCodePoint(...t).replace(/🇹🇼/g, '🇨🇳'); return String.fromCodePoint(...t).replace(/🇹🇼/g, '🇼🇸') } catch (e) { return '' } } else { return '' } } // 参数 与其他脚本逻辑一致 function parseQueryString(url) { const queryString = url.split('?')[1] // 获取查询字符串部分 const regex = /([^=&]+)=([^&]*)/g // 匹配键值对的正则表达式 const params = {} let match while ((match = regex.exec(queryString))) { const key = decodeURIComponent(match[1]) // 解码键 const value = decodeURIComponent(match[2]) // 解码值 params[key] = value // 将键值对添加到对象中 } return params } const DOMAIN_RESOLVERS = { google: async function (domain, type) { const resp = await http({ url: `https://8.8.4.4/resolve`, params: { name: domain, type: type === 'IPv6' ? 'AAAA' : 'A', }, headers: { accept: 'application/dns-json', }, }) const body = JSON.parse(resp.body) if (body['Status'] !== 0) { throw new Error(`Status is ${body['Status']}`) } const answers = body['Answer'] if (answers.length === 0) { throw new Error('域名解析无结果') } return answers[answers.length - 1].data }, cf: async function (domain, type) { const resp = await http({ url: `https://1.0.0.1/dns-query`, params: { name: domain, type: type === 'IPv6' ? 'AAAA' : 'A', }, headers: { accept: 'application/dns-json', }, }) const body = JSON.parse(resp.body) if (body['Status'] !== 0) { throw new Error(`Status is ${body['Status']}`) } const answers = body['Answer'] if (answers.length === 0) { throw new Error('域名解析无结果') } return answers[answers.length - 1].data }, ali: async function (domain, type) { const resp = await http({ url: `https://223.6.6.6/resolve`, params: { edns_client_subnet: '223.6.6.6/24', name: domain, short: 1, type: type === 'IPv6' ? 'AAAA' : 'A', }, headers: { accept: 'application/dns-json', }, }) const answers = JSON.parse(resp.body) if (answers.length === 0) { throw new Error('域名解析无结果') } return answers[answers.length - 1] }, tencent: async function (domain, type) { const resp = await http({ url: `https://119.28.28.28/d`, params: { ip: '119.28.28.28', dn: domain, type: type === 'IPv6' ? 'AAAA' : 'A', }, headers: { accept: 'application/dns-json', }, }) const answers = resp.body.split(';').map(i => i.split(',')[0]) if (answers.length === 0 || String(answers) === '0') { throw new Error('域名解析无结果') } return answers[answers.length - 1] }, } async function resolveDomain(domain) { let IPv4 let IPv6 if (isIPv4(domain)) { IPv4 = domain } else if (isIPv6(domain)) { IPv6 = domain } else { let resolverName = $.lodash_get(arg, 'DNS') || 'ali' let resolver = DOMAIN_RESOLVERS[resolverName] if (!resolver) { resolverName = 'ali' resolver = DOMAIN_RESOLVERS[resolverName] } const msg = `使用 ${resolverName} 解析域名 ${domain}` const res = await Promise.all([ (async () => { try { return await resolver(domain, 'IPv4') } catch (e) { // $.logErr(`${msg} 发生错误: ${e.message || e}`) } })(), (async () => { try { return await resolver(domain, 'IPv6') } catch (e) { // $.logErr(`${msg} 发生错误: ${e.message || e}`) } })(), ]) const [v4, v6] = res if (isIPv4(v4)) { IPv4 = v4 } else { $.logErr(`${msg} 解析 IPv4 失败`) } if (isIPv6(v6)) { IPv6 = v6 } else { $.logErr(`${msg} 解析 IPv6 失败`) } } return { IP: IPv4 || IPv6, IPv4, IPv6 } } // source: https://stackoverflow.com/a/36760050 const IPV4_REGEX = /^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)(\.(?!$)|$)){4}$/ // source: https://ihateregex.io/expr/ipv6/ const IPV6_REGEX = /^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$/ function isIPv4(ip) { return IPV4_REGEX.test(ip) } function isIPv6(ip) { return IPV6_REGEX.test(ip) } function isIP(ip) { return isIPv4(ip) || isIPv6(ip) } async function getProxies() { let PROXIES = [] if ($.isStash()) { try { const res = await $.http.get({ url: `http://127.0.0.1:9090/providers/proxies`, }) let body = String($.lodash_get(res, 'body') || $.lodash_get(res, 'rawBody')) try { body = JSON.parse(body) } catch (e) {} // $.log(JSON.stringify(body, null, 2)) PROXIES = Object.values(body.providers) .map(i => i.proxies) .flat() .reduce((obj, i) => { obj[i.name] = i.address return obj }, {}) } catch (e) { $.logErr(e) $.logErr($.toStr(e)) } } return { PROXIES } } async function httpAPI(path = '/v1/requests/recent', method = 'GET', body = null) { return new Promise((resolve, reject) => { $httpAPI(method, path, body, result => { resolve(result) }) }) } function isRequest() { return typeof $request !== 'undefined' } function isPanel() { return $.isSurge() && typeof $input != 'undefined' && $.lodash_get($input, 'purpose') === 'panel' } function isTile() { return ( $.isStash() && ((typeof $script != 'undefined' && $.lodash_get($script, 'type') === 'tile') || $.lodash_get(arg, 'TYPE') === 'TILE') ) } function isInteraction() { return ( ($.isQuanX() && typeof $environment != 'undefined' && $.lodash_get($environment, 'executor') === 'event-interaction') || ($.isLoon() && typeof $environment != 'undefined' && $.lodash_get($environment, 'params.node')) ) } function getNodeOpt() { let opt = {} if (isInteraction()) { if ($.isQuanX()) { opt = { opts: { policy: $environment.params, }, } } else if ($.isLoon()) { opt = { node: $environment.params.node, } } } return opt } // 请求 async function http(opt = {}) { const TIMEOUT = parseFloat(opt.timeout || $.lodash_get(arg, 'TIMEOUT') || 5) const RETRIES = parseFloat(opt.retries || $.lodash_get(arg, 'RETRIES') || 1) const RETRY_DELAY = parseFloat(opt.retry_delay || $.lodash_get(arg, 'RETRY_DELAY') || 1) let timeout = TIMEOUT + 1 timeout = $.isSurge() ? timeout : timeout * 1000 let count = 0 const fn = async () => { try { if (TIMEOUT) { // Surge, Loon, Stash 默认为 5 秒 return await Promise.race([ $.http.get({ ...opt, timeout }), new Promise((_, reject) => setTimeout(() => reject(new Error('HTTP TIMEOUT')), TIMEOUT * 1000)), ]) } return await $.http.get(opt) } catch (e) { if (count < RETRIES) { count++ $.log(`第 ${count} 次请求失败: ${e.message || e}, 等待 ${RETRY_DELAY}s 后重试`) await $.wait(RETRY_DELAY * 1000) return await fn() } } } return await fn() } // 通知 async function notify(title, subt, desc, opts) { if ($.lodash_get(arg, 'TYPE') === 'EVENT' || $.lodash_get(arg, 'notify') == 1) { $.msg(title, subt, desc, opts) } else { $.log('🔕', title, subt, desc, opts) } } // prettier-ignore function Env(t,e){class s{constructor(t){this.env=t}send(t,e="GET"){t="string"==typeof t?{url:t}:t;let s=this.get;return"POST"===e&&(s=this.post),new Promise((e,a)=>{s.call(this,t,(t,s,r)=>{t?a(t):e(s)})})}get(t){return this.send.call(this.env,t)}post(t){return this.send.call(this.env,t,"POST")}}return new class{constructor(t,e){this.name=t,this.http=new s(this),this.data=null,this.dataFile="box.dat",this.logs=[],this.isMute=!1,this.isNeedRewrite=!1,this.logSeparator="\n",this.encoding="utf-8",this.startTime=(new Date).getTime(),Object.assign(this,e),this.log("",`🔔${this.name}, 开始!`)}getEnv(){return"undefined"!=typeof $environment&&$environment["surge-version"]?"Surge":"undefined"!=typeof $environment&&$environment["stash-version"]?"Stash":"undefined"!=typeof module&&module.exports?"Node.js":"undefined"!=typeof $task?"Quantumult X":"undefined"!=typeof $loon?"Loon":"undefined"!=typeof $rocket?"Shadowrocket":void 0}isNode(){return"Node.js"===this.getEnv()}isQuanX(){return"Quantumult X"===this.getEnv()}isSurge(){return"Surge"===this.getEnv()}isLoon(){return"Loon"===this.getEnv()}isShadowrocket(){return"Shadowrocket"===this.getEnv()}isStash(){return"Stash"===this.getEnv()}toObj(t,e=null){try{return JSON.parse(t)}catch{return e}}toStr(t,e=null){try{return JSON.stringify(t)}catch{return e}}getjson(t,e){let s=e;const a=this.getdata(t);if(a)try{s=JSON.parse(this.getdata(t))}catch{}return s}setjson(t,e){try{return this.setdata(JSON.stringify(t),e)}catch{return!1}}getScript(t){return new Promise(e=>{this.get({url:t},(t,s,a)=>e(a))})}runScript(t,e){return new Promise(s=>{let a=this.getdata("@chavy_boxjs_userCfgs.httpapi");a=a?a.replace(/\n/g,"").trim():a;let r=this.getdata("@chavy_boxjs_userCfgs.httpapi_timeout");r=r?1*r:20,r=e&&e.timeout?e.timeout:r;const[i,o]=a.split("@"),n={url:`http://${o}/v1/scripting/evaluate`,body:{script_text:t,mock_type:"cron",timeout:r},headers:{"X-Key":i,Accept:"*/*"},timeout:r};this.post(n,(t,e,a)=>s(a))}).catch(t=>this.logErr(t))}loaddata(){if(!this.isNode())return{};{this.fs=this.fs?this.fs:require("fs"),this.path=this.path?this.path:require("path");const t=this.path.resolve(this.dataFile),e=this.path.resolve(process.cwd(),this.dataFile),s=this.fs.existsSync(t),a=!s&&this.fs.existsSync(e);if(!s&&!a)return{};{const a=s?t:e;try{return JSON.parse(this.fs.readFileSync(a))}catch(t){return{}}}}}writedata(){if(this.isNode()){this.fs=this.fs?this.fs:require("fs"),this.path=this.path?this.path:require("path");const t=this.path.resolve(this.dataFile),e=this.path.resolve(process.cwd(),this.dataFile),s=this.fs.existsSync(t),a=!s&&this.fs.existsSync(e),r=JSON.stringify(this.data);s?this.fs.writeFileSync(t,r):a?this.fs.writeFileSync(e,r):this.fs.writeFileSync(t,r)}}lodash_get(t,e,s){const a=e.replace(/\[(\d+)\]/g,".$1").split(".");let r=t;for(const t of a)if(r=Object(r)[t],void 0===r)return s;return r}lodash_set(t,e,s){return Object(t)!==t?t:(Array.isArray(e)||(e=e.toString().match(/[^.[\]]+/g)||[]),e.slice(0,-1).reduce((t,s,a)=>Object(t[s])===t[s]?t[s]:t[s]=Math.abs(e[a+1])>>0==+e[a+1]?[]:{},t)[e[e.length-1]]=s,t)}getdata(t){let e=this.getval(t);if(/^@/.test(t)){const[,s,a]=/^@(.*?)\.(.*?)$/.exec(t),r=s?this.getval(s):"";if(r)try{const t=JSON.parse(r);e=t?this.lodash_get(t,a,""):e}catch(t){e=""}}return e}setdata(t,e){let s=!1;if(/^@/.test(e)){const[,a,r]=/^@(.*?)\.(.*?)$/.exec(e),i=this.getval(a),o=a?"null"===i?null:i||"{}":"{}";try{const e=JSON.parse(o);this.lodash_set(e,r,t),s=this.setval(JSON.stringify(e),a)}catch(e){const i={};this.lodash_set(i,r,t),s=this.setval(JSON.stringify(i),a)}}else s=this.setval(t,e);return s}getval(t){switch(this.getEnv()){case"Surge":case"Loon":case"Stash":case"Shadowrocket":return $persistentStore.read(t);case"Quantumult X":return $prefs.valueForKey(t);case"Node.js":return this.data=this.loaddata(),this.data[t];default:return this.data&&this.data[t]||null}}setval(t,e){switch(this.getEnv()){case"Surge":case"Loon":case"Stash":case"Shadowrocket":return $persistentStore.write(t,e);case"Quantumult X":return $prefs.setValueForKey(t,e);case"Node.js":return this.data=this.loaddata(),this.data[e]=t,this.writedata(),!0;default:return this.data&&this.data[e]||null}}initGotEnv(t){this.got=this.got?this.got:require("got"),this.cktough=this.cktough?this.cktough:require("tough-cookie"),this.ckjar=this.ckjar?this.ckjar:new this.cktough.CookieJar,t&&(t.headers=t.headers?t.headers:{},void 0===t.headers.Cookie&&void 0===t.cookieJar&&(t.cookieJar=this.ckjar))}get(t,e=(()=>{})){switch(t.headers&&(delete t.headers["Content-Type"],delete t.headers["Content-Length"],delete t.headers["content-type"],delete t.headers["content-length"]),t.params&&(t.url+="?"+this.queryStr(t.params)),this.getEnv()){case"Surge":case"Loon":case"Stash":case"Shadowrocket":default:this.isSurge()&&this.isNeedRewrite&&(t.headers=t.headers||{},Object.assign(t.headers,{"X-Surge-Skip-Scripting":!1})),$httpClient.get(t,(t,s,a)=>{!t&&s&&(s.body=a,s.statusCode=s.status?s.status:s.statusCode,s.status=s.statusCode),e(t,s,a)});break;case"Quantumult X":this.isNeedRewrite&&(t.opts=t.opts||{},Object.assign(t.opts,{hints:!1})),$task.fetch(t).then(t=>{const{statusCode:s,statusCode:a,headers:r,body:i,bodyBytes:o}=t;e(null,{status:s,statusCode:a,headers:r,body:i,bodyBytes:o},i,o)},t=>e(t&&t.error||"UndefinedError"));break;case"Node.js":let s=require("iconv-lite");this.initGotEnv(t),this.got(t).on("redirect",(t,e)=>{try{if(t.headers["set-cookie"]){const s=t.headers["set-cookie"].map(this.cktough.Cookie.parse).toString();s&&this.ckjar.setCookieSync(s,null),e.cookieJar=this.ckjar}}catch(t){this.logErr(t)}}).then(t=>{const{statusCode:a,statusCode:r,headers:i,rawBody:o}=t,n=s.decode(o,this.encoding);e(null,{status:a,statusCode:r,headers:i,rawBody:o,body:n},n)},t=>{const{message:a,response:r}=t;e(a,r,r&&s.decode(r.rawBody,this.encoding))})}}post(t,e=(()=>{})){const s=t.method?t.method.toLocaleLowerCase():"post";switch(t.body&&t.headers&&!t.headers["Content-Type"]&&!t.headers["content-type"]&&(t.headers["content-type"]="application/x-www-form-urlencoded"),t.headers&&(delete t.headers["Content-Length"],delete t.headers["content-length"]),this.getEnv()){case"Surge":case"Loon":case"Stash":case"Shadowrocket":default:this.isSurge()&&this.isNeedRewrite&&(t.headers=t.headers||{},Object.assign(t.headers,{"X-Surge-Skip-Scripting":!1})),$httpClient[s](t,(t,s,a)=>{!t&&s&&(s.body=a,s.statusCode=s.status?s.status:s.statusCode,s.status=s.statusCode),e(t,s,a)});break;case"Quantumult X":t.method=s,this.isNeedRewrite&&(t.opts=t.opts||{},Object.assign(t.opts,{hints:!1})),$task.fetch(t).then(t=>{const{statusCode:s,statusCode:a,headers:r,body:i,bodyBytes:o}=t;e(null,{status:s,statusCode:a,headers:r,body:i,bodyBytes:o},i,o)},t=>e(t&&t.error||"UndefinedError"));break;case"Node.js":let a=require("iconv-lite");this.initGotEnv(t);const{url:r,...i}=t;this.got[s](r,i).then(t=>{const{statusCode:s,statusCode:r,headers:i,rawBody:o}=t,n=a.decode(o,this.encoding);e(null,{status:s,statusCode:r,headers:i,rawBody:o,body:n},n)},t=>{const{message:s,response:r}=t;e(s,r,r&&a.decode(r.rawBody,this.encoding))})}}time(t,e=null){const s=e?new Date(e):new Date;let a={"M+":s.getMonth()+1,"d+":s.getDate(),"H+":s.getHours(),"m+":s.getMinutes(),"s+":s.getSeconds(),"q+":Math.floor((s.getMonth()+3)/3),S:s.getMilliseconds()};/(y+)/.test(t)&&(t=t.replace(RegExp.$1,(s.getFullYear()+"").substr(4-RegExp.$1.length)));for(let e in a)new RegExp("("+e+")").test(t)&&(t=t.replace(RegExp.$1,1==RegExp.$1.length?a[e]:("00"+a[e]).substr((""+a[e]).length)));return t}queryStr(t){let e="";for(const s in t){let a=t[s];null!=a&&""!==a&&("object"==typeof a&&(a=JSON.stringify(a)),e+=`${s}=${a}&`)}return e=e.substring(0,e.length-1),e}msg(e=t,s="",a="",r){const i=t=>{switch(typeof t){case void 0:return t;case"string":switch(this.getEnv()){case"Surge":case"Stash":default:return{url:t};case"Loon":case"Shadowrocket":return t;case"Quantumult X":return{"open-url":t};case"Node.js":return}case"object":switch(this.getEnv()){case"Surge":case"Stash":case"Shadowrocket":default:{let e=t.url||t.openUrl||t["open-url"];return{url:e}}case"Loon":{let e=t.openUrl||t.url||t["open-url"],s=t.mediaUrl||t["media-url"];return{openUrl:e,mediaUrl:s}}case"Quantumult X":{let e=t["open-url"]||t.url||t.openUrl,s=t["media-url"]||t.mediaUrl,a=t["update-pasteboard"]||t.updatePasteboard;return{"open-url":e,"media-url":s,"update-pasteboard":a}}case"Node.js":return}default:return}};if(!this.isMute)switch(this.getEnv()){case"Surge":case"Loon":case"Stash":case"Shadowrocket":default:$notification.post(e,s,a,i(r));break;case"Quantumult X":$notify(e,s,a,i(r));break;case"Node.js":}if(!this.isMuteLog){let t=["","==============📣系统通知📣=============="];t.push(e),s&&t.push(s),a&&t.push(a),console.log(t.join("\n")),this.logs=this.logs.concat(t)}}log(...t){t.length>0&&(this.logs=[...this.logs,...t]),console.log(t.join(this.logSeparator))}logErr(t,e){switch(this.getEnv()){case"Surge":case"Loon":case"Stash":case"Shadowrocket":case"Quantumult X":default:this.log("",`❗️${this.name}, 错误!`,t);break;case"Node.js":this.log("",`❗️${this.name}, 错误!`,t.stack)}}wait(t){return new Promise(e=>setTimeout(e,t))}done(t={}){const e=(new Date).getTime(),s=(e-this.startTime)/1e3;switch(this.log("",`🔔${this.name}, 结束! 🕛 ${s} 秒`),this.log(),this.getEnv()){case"Surge":case"Loon":case"Stash":case"Shadowrocket":case"Quantumult X":default:$done(t);break;case"Node.js":process.exit(1)}}}(t,e)}