├── QuantumultX ├── NetflixPolicySwitcher │ ├── README.md │ ├── netflix_checker.js │ └── netflix_switcher.js ├── DisneyPlusPolicySwitcher │ ├── README.md │ ├── disney_checker.js │ └── disney_switcher.js └── task.json ├── .prettierrc.js └── LICENSE /QuantumultX/NetflixPolicySwitcher/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /QuantumultX/DisneyPlusPolicySwitcher/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | printWidth: 160, 3 | trailingComma: 'es5', 4 | tabWidth: 2, 5 | useTabs: false, 6 | semi: false, 7 | singleQuote: true, 8 | bracketSpacing: true, 9 | arrowParens: 'avoid', 10 | endOfLine: 'auto', 11 | proseWrap: 'preserve', 12 | } 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Hyseen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /QuantumultX/task.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "Helge_0x00", 3 | "name": "Helge_0x00 脚本合集", 4 | "description": "", 5 | "author": "@Helge_0x00", 6 | "icon": "https://avatars.githubusercontent.com/u/17402489", 7 | "repo": "https://github.com/Hyseen/Scripts", 8 | "apps": [ 9 | { 10 | "id": "DisneyPlusPolicySwitcher", 11 | "name": "Disney+ 策略组切换", 12 | "keys": [ 13 | "Helge_0x00.Disney_Available_Policies" 14 | ], 15 | "descs_html": [ 16 | "

⚠️ 该脚本仅支持 QuanX 598+ 版本

", 17 | "
1. 配置 Disney+ 分流规则,并关联策略组(默认策略组为:Disney+,可在 BoxJs 中修改),支持策略组嵌套,但是只检测类型为 static 的子策略组
", 18 | "
2. (可选)在 BoxJs 中订阅 脚本集合
", 19 | "
3. 在 task gallery 中订阅 脚本集合
", 20 | "
4. (可选)在 BoxJs 中配置分流策略组名称,以及其他参数
", 21 | "
5. 必须先运行一次「解锁检测」脚本,后续该脚本会定时执行
", 22 | "
6. 在 BoxJs 或 QuanX 中运行「策略切换」即可自动切换策略
" 23 | ], 24 | "settings": [ 25 | { 26 | "id": "Helge_0x00.Disney_Debug", 27 | "name": "Debug 模式", 28 | "val": false, 29 | "type": "boolean", 30 | "desc": "Debug 模式, 用于调试脚本" 31 | }, 32 | { 33 | "id": "Helge_0x00.Disney_Retry", 34 | "name": "失败重试", 35 | "val": false, 36 | "type": "boolean", 37 | "desc": "检测超时或失败时是否重试" 38 | }, 39 | { 40 | "id": "Helge_0x00.Disney_Recheck", 41 | "name": "切换策略前是否检查可用性", 42 | "val": false, 43 | "type": "boolean", 44 | "desc": "切换策略前是否再次检测被选策略的可用性。默认不检查,直接使用上次「解锁检测」脚本的测试结果,但是有可能切换的节点已经不可用" 45 | }, 46 | { 47 | "id": "Helge_0x00.Disney_Sort_By_Time", 48 | "name": "选择耗时最少的策略", 49 | "val": false, 50 | "type": "boolean", 51 | "desc": "选择检测耗时最少的策略,开启后执行「策略切换」脚本时会选择上次检测耗时最少的策略" 52 | }, 53 | { 54 | "id": "Helge_0x00.Disney_Policy", 55 | "name": "策略组名", 56 | "val": "", 57 | "type": "text", 58 | "desc": "Disney+ 分流的策略组名" 59 | }, 60 | { 61 | "id": "Helge_0x00.Disney_Concurrency", 62 | "name": "检测并发度", 63 | "val": "", 64 | "type": "number", 65 | "desc": "执行「解锁检测」脚本时允许同时检测多少个策略,默认为 10" 66 | }, 67 | { 68 | "id": "Helge_0x00.Disney_Timeout", 69 | "name": "超时时间(毫秒)", 70 | "val": "", 71 | "type": "number", 72 | "desc": "检测任务超时时间,默认为 8000 毫秒" 73 | } 74 | ], 75 | "scripts": [ 76 | { 77 | "name": "Disney+ 解锁检测", 78 | "script": "https://raw.githubusercontent.com/Hyseen/Scripts/master/QuantumultX/DisneyPlusPolicySwitcher/disney_checker.js" 79 | }, 80 | { 81 | "name": "Disney+ 策略切换", 82 | "script": "https://raw.githubusercontent.com/Hyseen/Scripts/master/QuantumultX/DisneyPlusPolicySwitcher/disney_switcher.js" 83 | } 84 | ], 85 | "author": "@Helge_0x00", 86 | "repo": "https://github.com/Hyseen/Scripts/tree/master/QuantumultX/DisneyPlusPolicySwitcher", 87 | "icons": [ 88 | "https://raw.githubusercontent.com/Koolson/Qure/master/IconSet/Disney+.png", 89 | "https://raw.githubusercontent.com/Koolson/Qure/master/IconSet/Color/Disney+.png" 90 | ] 91 | }, 92 | { 93 | "id": "NetflixPolicySwitcher", 94 | "name": "Netflix 策略组切换", 95 | "keys": [ 96 | "Helge_0x00.Netflix_Full_Available_Policies", 97 | "Helge_0x00.Netflix_Original_Available_Policies" 98 | ], 99 | "descs_html": [ 100 | "

⚠️ 该脚本仅支持 QuanX 598+ 版本

", 101 | "
1. 配置 Netflix 分流规则,并关联策略组(默认策略组为:Netflix,可在 BoxJs 中修改),支持策略组嵌套,但是只检测类型为 static 的子策略组
", 102 | "
2. (可选)在 BoxJs 中订阅 脚本集合
", 103 | "
3. 在 task gallery 中订阅 脚本集合
", 104 | "
4. (可选)在 BoxJs 中配置分流策略组名称,以及其他参数
", 105 | "
5. 必须先运行一次「解锁检测」脚本,后续该脚本会定时执行
", 106 | "
6. 在 BoxJs 或 QuanX 中运行「策略切换」即可自动切换策略
" 107 | ], 108 | "settings": [ 109 | { 110 | "id": "Helge_0x00.Netflix_Debug", 111 | "name": "Debug 模式", 112 | "val": false, 113 | "type": "boolean", 114 | "desc": "Debug 模式, 用于调试脚本" 115 | }, 116 | { 117 | "id": "Helge_0x00.Netflix_Retry", 118 | "name": "失败重试", 119 | "val": false, 120 | "type": "boolean", 121 | "desc": "检测超时或失败时是否重试" 122 | }, 123 | { 124 | "id": "Helge_0x00.Netflix_Recheck", 125 | "name": "切换策略前是否检查可用性", 126 | "val": false, 127 | "type": "boolean", 128 | "desc": "切换策略前是否再次检测被选策略的可用性。默认不检查,直接使用上次「解锁检测」脚本的测试结果,但是有可能切换的节点已经不可用" 129 | }, 130 | { 131 | "id": "Helge_0x00.Netflix_Sort_By_Time", 132 | "name": "选择耗时最少的策略", 133 | "val": false, 134 | "type": "boolean", 135 | "desc": "选择检测耗时最少的策略,开启后执行「策略切换」脚本时会选择上次检测耗时最少的策略" 136 | }, 137 | { 138 | "id": "Helge_0x00.Netflix_Policy", 139 | "name": "策略组名", 140 | "val": "", 141 | "type": "text", 142 | "desc": "Netflix 分流的策略组名" 143 | }, 144 | { 145 | "id": "Helge_0x00.Netflix_Concurrency", 146 | "name": "检测并发度", 147 | "val": "", 148 | "type": "number", 149 | "desc": "执行「解锁检测」脚本时允许同时检测多少个策略,默认为 10" 150 | }, 151 | { 152 | "id": "Helge_0x00.Netflix_Timeout", 153 | "name": "超时时间(毫秒)", 154 | "val": "", 155 | "type": "number", 156 | "desc": "检测任务超时时间,默认为 8000 毫秒" 157 | } 158 | ], 159 | "scripts": [ 160 | { 161 | "name": "Netflix 解锁检测", 162 | "script": "https://raw.githubusercontent.com/Hyseen/Scripts/master/QuantumultX/NetflixPolicySwitcher/netflix_checker.js" 163 | }, 164 | { 165 | "name": "Netflix 策略切换", 166 | "script": "https://raw.githubusercontent.com/Hyseen/Scripts/master/QuantumultX/NetflixPolicySwitcher/netflix_switcher.js" 167 | } 168 | ], 169 | "author": "@Helge_0x00", 170 | "repo": "https://github.com/Hyseen/Scripts/tree/master/QuantumultX/NetflixPolicySwitcher", 171 | "icons": [ 172 | "https://raw.githubusercontent.com/Koolson/Qure/master/IconSet/Netflix_Letter.png", 173 | "https://raw.githubusercontent.com/Koolson/Qure/master/IconSet/Color/Netflix_Letter.png" 174 | ] 175 | } 176 | ], 177 | "task": [ 178 | { 179 | "config": "30 */2 * * * https://raw.githubusercontent.com/Hyseen/Scripts/master/QuantumultX/DisneyPlusPolicySwitcher/disney_checker.js, tag=Disney+ 解锁检测, img-url=https://raw.githubusercontent.com/Koolson/Qure/master/IconSet/Color/Disney+.png, enabled=true" 180 | }, 181 | { 182 | "config": "0 0 29 2 * https://raw.githubusercontent.com/Hyseen/Scripts/master/QuantumultX/DisneyPlusPolicySwitcher/disney_switcher.js, tag=Disney+ 策略切换, img-url=https://raw.githubusercontent.com/Koolson/Qure/master/IconSet/Color/Disney+.png, enabled=false" 183 | }, 184 | { 185 | "config": "0 */2 * * * https://raw.githubusercontent.com/Hyseen/Scripts/master/QuantumultX/NetflixPolicySwitcher/netflix_checker.js, tag=Netflix 解锁检测, img-url=https://raw.githubusercontent.com/Koolson/Qure/master/IconSet/Color/Netflix_Letter.png, enabled=true" 186 | }, 187 | { 188 | "config": "0 0 29 2 * https://raw.githubusercontent.com/Hyseen/Scripts/master/QuantumultX/NetflixPolicySwitcher/netflix_switcher.js, tag=Neflix 策略切换, img-url=https://raw.githubusercontent.com/Koolson/Qure/master/IconSet/Color/Netflix_Letter.png, enabled=false" 189 | } 190 | ] 191 | } 192 | -------------------------------------------------------------------------------- /QuantumultX/NetflixPolicySwitcher/netflix_checker.js: -------------------------------------------------------------------------------- 1 | const STATUS_FULL_AVAILABLE = 2 // 完整支持 2 | const STATUS_ORIGINAL_AVAILABLE = 1 // 支持自制剧 3 | const STATUS_NOT_AVAILABLE = 0 // 不支持解锁 4 | const STATUS_TIMEOUT = -1 // 检测超时 5 | const STATUS_ERROR = -2 // 检测异常 6 | 7 | const $ = new Env('Netflix 解锁检测') 8 | let policyName = $.getval('Helge_0x00.Netflix_Policy') || 'Netflix' 9 | let debug = $.getval('Helge_0x00.Netflix_Debug') === 'true' 10 | let retry = $.getval('Helge_0x00.Netflix_Retry') === 'true' 11 | let t = parseInt($.getval('Helge_0x00.Netflix_Timeout')) || 8000 12 | let sortByTime = $.getval('Helge_0x00.Netflix_Sort_By_Time') === 'true' 13 | let concurrency = parseInt($.getval('Helge_0x00.Netflix_Concurrency')) || 10 14 | 15 | ;(async () => { 16 | if (!$.isQuanX()) { 17 | throw '该脚本仅支持在 Quantumult X 中运行' 18 | } 19 | 20 | let policies = await sendMessage({ action: 'get_customized_policy' }) 21 | if (!isValidPolicy(policies[policyName])) { 22 | policyName = lookupTargetPolicy(policies) 23 | console.log(`更新策略组名称 ➟ ${policyName}`) 24 | $.setval(policyName, 'Helge_0x00.Netflix_Policy') 25 | } 26 | let candidatePolicies = lookupChildrenNode(policies, policyName) 27 | 28 | let { fullAvailablePolicies, originalAvailablePolicies } = await testPolicies(policyName, candidatePolicies) 29 | if (sortByTime) { 30 | fullAvailablePolicies = fullAvailablePolicies.sort((m, n) => m.time - n.time) 31 | originalAvailablePolicies = originalAvailablePolicies.sort((m, n) => m.time - n.time) 32 | } 33 | $.setval(JSON.stringify(fullAvailablePolicies), 'Helge_0x00.Netflix_Full_Available_Policies') 34 | $.setval(JSON.stringify(originalAvailablePolicies), 'Helge_0x00.Netflix_Original_Available_Policies') 35 | })() 36 | .catch(error => { 37 | console.log(error) 38 | if (typeof error === 'string') { 39 | $.msg($.name, '', `${error} ⚠️`) 40 | } 41 | }) 42 | .finally(() => { 43 | $.done() 44 | }) 45 | 46 | async function testPolicies(policyName, policies = []) { 47 | let failedPolicies = [] 48 | let fullAvailablePolicies = [] 49 | let originalAvailablePolicies = [] 50 | let echo = results => { 51 | console.log(`\n策略组 ${policyName} 检测结果:`) 52 | for (let { policy, status, region, time } of results) { 53 | switch (status) { 54 | case STATUS_FULL_AVAILABLE: { 55 | let flag = getCountryFlagEmoji(region) ?? '' 56 | let regionName = REGIONS?.[region.toUpperCase()]?.chinese ?? '' 57 | console.log(`${policy}: 完整支持 Netflix ➟ ${flag}${regionName}`) 58 | fullAvailablePolicies.push({ policy, region, status, time }) 59 | break 60 | } 61 | case STATUS_ORIGINAL_AVAILABLE: { 62 | let flag = getCountryFlagEmoji(region) ?? '' 63 | let regionName = REGIONS?.[region.toUpperCase()]?.chinese ?? '' 64 | console.log(`${policy}: 仅支持自制剧 ➟ ${flag}${regionName}`) 65 | originalAvailablePolicies.push({ policy, region, status, time }) 66 | break 67 | } 68 | case STATUS_NOT_AVAILABLE: 69 | console.log(`${policy}: 不支持 Netflix`) 70 | break 71 | case STATUS_TIMEOUT: 72 | console.log(`${policy}: 检测超时`) 73 | failedPolicies.push(policy) 74 | break 75 | default: 76 | console.log(`${policy}: 检测异常`) 77 | failedPolicies.push(policy) 78 | } 79 | } 80 | } 81 | 82 | await Promise.map(policies, subPolicy => test(subPolicy), { concurrency }) 83 | .then(echo) 84 | .catch(error => console.log(error)) 85 | 86 | if (retry && failedPolicies.length > 0) { 87 | await Promise.map(failedPolicies, subPolicy => test(subPolicy), { concurrency }) 88 | .then(echo) 89 | .catch(error => console.log(error)) 90 | } 91 | 92 | return { fullAvailablePolicies, originalAvailablePolicies } 93 | } 94 | 95 | function getFilmPage(filmId, policyName) { 96 | return new Promise((resolve, reject) => { 97 | let request = { 98 | url: `https://www.netflix.com/title/${filmId}`, 99 | opts: { 100 | redirection: false, 101 | policy: policyName, 102 | }, 103 | headers: { 104 | 'Accept-Language': 'en', 105 | 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.71 Safari/537.36', 106 | }, 107 | } 108 | $task.fetch(request).then( 109 | response => { 110 | let { 111 | statusCode, 112 | headers: { Location: location, 'X-Originating-URL': originatingUrl }, 113 | } = response 114 | 115 | if (statusCode === 403) { 116 | reject('Not Available') 117 | return 118 | } 119 | 120 | if (statusCode === 404) { 121 | reject('Not Found') 122 | return 123 | } 124 | 125 | if (statusCode === 302 || statusCode === 301 || statusCode === 200) { 126 | if (debug) { 127 | if (statusCode === 200) { 128 | console.log(`${policyName} filmId: ${filmId}, statusCode: ${statusCode}, X-Originating-URL: ${originatingUrl}`) 129 | } else { 130 | console.log(`${policyName} filmId: ${filmId}, statusCode: ${statusCode}, Location: ${location}`) 131 | } 132 | } 133 | 134 | let url = location ?? originatingUrl 135 | let region = url.split('/')[3] 136 | region = region.split('-')[0] 137 | if (region === 'title') { 138 | region = 'US' 139 | } 140 | resolve(region.toUpperCase()) 141 | return 142 | } 143 | 144 | if (debug) { 145 | console.log(`${policyName} filmId: ${filmId}, statusCode: ${statusCode}, response: ${JSON.stringify(response)}`) 146 | } 147 | reject('Not Available') 148 | }, 149 | reason => { 150 | if (debug) { 151 | console.log(`${policyName} getFilmPage Error: ${reason.error}`) 152 | } 153 | reject('Error') 154 | } 155 | ) 156 | }) 157 | } 158 | 159 | async function test(policyName) { 160 | console.log(`开始测试 ${policyName}`) 161 | let startTime = new Date().getTime() 162 | let result = await Promise.race([getFilmPage(81280792, policyName), timeout(t)]) 163 | .then(region => { 164 | return { region, policy: policyName, status: STATUS_FULL_AVAILABLE } 165 | }) 166 | .catch(async error => { 167 | if (error !== 'Not Found') { 168 | return Promise.reject(error) 169 | } 170 | 171 | let region = await Promise.race([getFilmPage(80018499, policyName), timeout(t)]) 172 | return { region, policy: policyName, status: STATUS_ORIGINAL_AVAILABLE } 173 | }) 174 | .catch(error => { 175 | if (error === 'Not Available') { 176 | return { policy: policyName, status: STATUS_NOT_AVAILABLE } 177 | } else if (error === 'Timeout') { 178 | return { policy: policyName, status: STATUS_TIMEOUT } 179 | } 180 | 181 | return { policy: policyName, status: STATUS_ERROR } 182 | }) 183 | return Object.assign(result, { time: new Date().getTime() - startTime }) 184 | } 185 | 186 | function timeout(delay = 5000) { 187 | return new Promise((resolve, reject) => { 188 | setTimeout(() => { 189 | reject('Timeout') 190 | }, delay) 191 | }) 192 | } 193 | 194 | function getCountryFlagEmoji(countryCode) { 195 | if (countryCode.toUpperCase() === 'TW') { 196 | countryCode = 'CN' 197 | } 198 | const codePoints = countryCode 199 | .toUpperCase() 200 | .split('') 201 | .map(char => 127397 + char.charCodeAt()) 202 | return String.fromCodePoint(...codePoints) 203 | } 204 | 205 | function sendMessage(message) { 206 | return new Promise((resolve, reject) => { 207 | $configuration.sendMessage(message).then( 208 | response => { 209 | if (response.error) { 210 | if (debug) { 211 | console.log(`${message?.action} error: ${response.error}`) 212 | } 213 | reject(response.error) 214 | return 215 | } 216 | 217 | resolve(response.ret) 218 | }, 219 | error => { 220 | // Normally will never happen. 221 | reject(error) 222 | } 223 | ) 224 | }) 225 | } 226 | 227 | function lookupChildrenNode(policies = {}, targetPolicyName) { 228 | let targetPolicy = policies[targetPolicyName] 229 | if (!isValidPolicy(targetPolicy)) { 230 | throw '策略组名未填写或填写有误,请在 BoxJS 中填写正确的策略组名称' 231 | } 232 | if (targetPolicy?.type !== 'static') { 233 | throw `${targetPolicyName} 不是 static 类型的策略组` 234 | } 235 | if (targetPolicy.candidates.length <= 0) { 236 | throw `${targetPolicyName} 策略组为空` 237 | } 238 | let candidates = new Set() 239 | 240 | let looked = new Set() 241 | let looking = [targetPolicyName] 242 | 243 | while (looking.length > 0) { 244 | let curPolicyGroupName = looking.shift() 245 | looked.add(curPolicyGroupName) 246 | for (const policy of policies[curPolicyGroupName].candidates) { 247 | // 排除 proxy 和 reject 两个特殊策略 248 | if (policy === 'proxy' || policy === 'reject') { 249 | continue 250 | } 251 | // 如果不是自定义策略,那么就应该是一个节点 252 | if (policies[policy] === undefined) { 253 | candidates.add(policy) 254 | continue 255 | } 256 | 257 | // 没有遍历过的策略,也不是即将遍历的策略,并且是 static 类型的策略 258 | if (!looked.has(policy) && !looking.includes(policy) && policies[policy]?.type === 'static') { 259 | looking.push(policy) 260 | } 261 | } 262 | } 263 | 264 | return [...candidates] 265 | } 266 | 267 | function lookupTargetPolicy(policies = {}) { 268 | let policyNames = Object.entries(policies) 269 | .filter(([key, val]) => key.search(/Netflix|奈飞|网飞/gi) !== -1) 270 | .map(([key, val]) => key) 271 | if (policyNames.length === 1) { 272 | return policyNames[0] 273 | } else if (policyNames.length <= 0) { 274 | throw '没有找到 Netflix 策略组,请在 BoxJS 中填写正确的策略组名称' 275 | } else { 276 | throw `找到多个 Netflix 策略组,请在 BoxJS 中填写正确的策略组名称` 277 | } 278 | } 279 | 280 | function isValidPolicy(policy) { 281 | return policy !== undefined && policy?.type !== undefined && Array.isArray(policy?.candidates) 282 | } 283 | 284 | // prettier-ignore 285 | Array.prototype.remove=function(e){let t=this.indexOf(e);-1!==t&&this.splice(t,1)} 286 | 287 | // prettier-ignore 288 | Promise.map=function(t,e,{concurrency:u}){const i=new class{constructor(t){this.limit=t,this.count=0,this.queue=[]}enqueue(t){return new Promise((e,u)=>{this.queue.push({fn:t,resolve:e,reject:u})})}dequeue(){if(this.counti.build(()=>e(...t))))} 289 | 290 | // prettier-ignore 291 | const REGIONS={AF:{chinese:'阿富汗',english:'Afghanistan'},AL:{chinese:'阿尔巴尼亚',english:'Albania'},DZ:{chinese:'阿尔及利亚',english:'Algeria'},AO:{chinese:'安哥拉',english:'Angola'},AR:{chinese:'阿根廷',english:'Argentina'},AM:{chinese:'亚美尼亚',english:'Armenia'},AU:{chinese:'澳大利亚',english:'Australia'},AT:{chinese:'奥地利',english:'Austria'},AZ:{chinese:'阿塞拜疆',english:'Azerbaijan'},BH:{chinese:'巴林',english:'Bahrain'},BD:{chinese:'孟加拉国',english:'Bangladesh'},BY:{chinese:'白俄罗斯',english:'Belarus'},BE:{chinese:'比利时',english:'Belgium'},BZ:{chinese:'伯利兹',english:'Belize'},BJ:{chinese:'贝宁',english:'Benin'},BT:{chinese:'不丹',english:'Bhutan'},BO:{chinese:'玻利维亚',english:'Bolivia'},BA:{chinese:'波黑',english:'Bosnia and Herzegovina'},BW:{chinese:'博茨瓦纳',english:'Botswana'},BR:{chinese:'巴西',english:'Brazil'},VG:{chinese:'英属维京群岛',english:'British Virgin Islands'},BN:{chinese:'文莱',english:'Brunei'},BG:{chinese:'保加利亚',english:'Bulgaria'},BF:{chinese:'布基纳法索',english:'Burkina-faso'},BI:{chinese:'布隆迪',english:'Burundi'},KH:{chinese:'柬埔寨',english:'Cambodia'},CM:{chinese:'喀麦隆',english:'Cameroon'},CA:{chinese:'加拿大',english:'Canada'},CV:{chinese:'佛得角',english:'Cape Verde'},KY:{chinese:'开曼群岛',english:'Cayman Islands'},CF:{chinese:'中非',english:'Central African Republic'},TD:{chinese:'乍得',english:'Chad'},CL:{chinese:'智利',english:'Chile'},CN:{chinese:'中国',english:'China'},CO:{chinese:'哥伦比亚',english:'Colombia'},KM:{chinese:'科摩罗',english:'Comoros'},CG:{chinese:'刚果(布)',english:'Congo - Brazzaville'},CD:{chinese:'刚果(金)',english:'Congo - Kinshasa'},CR:{chinese:'哥斯达黎加',english:'Costa Rica'},HR:{chinese:'克罗地亚',english:'Croatia'},CY:{chinese:'塞浦路斯',english:'Cyprus'},CZ:{chinese:'捷克',english:'Czech Republic'},DK:{chinese:'丹麦',english:'Denmark'},DJ:{chinese:'吉布提',english:'Djibouti'},DO:{chinese:'多米尼加',english:'Dominican Republic'},EC:{chinese:'厄瓜多尔',english:'Ecuador'},EG:{chinese:'埃及',english:'Egypt'},SV:{chinese:'萨尔瓦多',english:'EI Salvador'},GQ:{chinese:'赤道几内亚',english:'Equatorial Guinea'},ER:{chinese:'厄立特里亚',english:'Eritrea'},EE:{chinese:'爱沙尼亚',english:'Estonia'},ET:{chinese:'埃塞俄比亚',english:'Ethiopia'},FJ:{chinese:'斐济',english:'Fiji'},FI:{chinese:'芬兰',english:'Finland'},FR:{chinese:'法国',english:'France'},GA:{chinese:'加蓬',english:'Gabon'},GM:{chinese:'冈比亚',english:'Gambia'},GE:{chinese:'格鲁吉亚',english:'Georgia'},DE:{chinese:'德国',english:'Germany'},GH:{chinese:'加纳',english:'Ghana'},GR:{chinese:'希腊',english:'Greece'},GL:{chinese:'格陵兰',english:'Greenland'},GT:{chinese:'危地马拉',english:'Guatemala'},GN:{chinese:'几内亚',english:'Guinea'},GY:{chinese:'圭亚那',english:'Guyana'},HT:{chinese:'海地',english:'Haiti'},HN:{chinese:'洪都拉斯',english:'Honduras'},HK:{chinese:'香港',english:'Hong Kong'},HU:{chinese:'匈牙利',english:'Hungary'},IS:{chinese:'冰岛',english:'Iceland'},IN:{chinese:'印度',english:'India'},ID:{chinese:'印度尼西亚',english:'Indonesia'},IR:{chinese:'伊朗',english:'Iran'},IQ:{chinese:'伊拉克',english:'Iraq'},IE:{chinese:'爱尔兰',english:'Ireland'},IM:{chinese:'马恩岛',english:'Isle of Man'},IL:{chinese:'以色列',english:'Israel'},IT:{chinese:'意大利',english:'Italy'},CI:{chinese:'科特迪瓦',english:'Ivory Coast'},JM:{chinese:'牙买加',english:'Jamaica'},JP:{chinese:'日本',english:'Japan'},JO:{chinese:'约旦',english:'Jordan'},KZ:{chinese:'哈萨克斯坦',english:'Kazakstan'},KE:{chinese:'肯尼亚',english:'Kenya'},KR:{chinese:'韩国',english:'Korea'},KW:{chinese:'科威特',english:'Kuwait'},KG:{chinese:'吉尔吉斯斯坦',english:'Kyrgyzstan'},LA:{chinese:'老挝',english:'Laos'},LV:{chinese:'拉脱维亚',english:'Latvia'},LB:{chinese:'黎巴嫩',english:'Lebanon'},LS:{chinese:'莱索托',english:'Lesotho'},LR:{chinese:'利比里亚',english:'Liberia'},LY:{chinese:'利比亚',english:'Libya'},LT:{chinese:'立陶宛',english:'Lithuania'},LU:{chinese:'卢森堡',english:'Luxembourg'},MO:{chinese:'澳门',english:'Macao'},MK:{chinese:'马其顿',english:'Macedonia'},MG:{chinese:'马达加斯加',english:'Madagascar'},MW:{chinese:'马拉维',english:'Malawi'},MY:{chinese:'马来西亚',english:'Malaysia'},MV:{chinese:'马尔代夫',english:'Maldives'},ML:{chinese:'马里',english:'Mali'},MT:{chinese:'马耳他',english:'Malta'},MR:{chinese:'毛利塔尼亚',english:'Mauritania'},MU:{chinese:'毛里求斯',english:'Mauritius'},MX:{chinese:'墨西哥',english:'Mexico'},MD:{chinese:'摩尔多瓦',english:'Moldova'},MC:{chinese:'摩纳哥',english:'Monaco'},MN:{chinese:'蒙古',english:'Mongolia'},ME:{chinese:'黑山',english:'Montenegro'},MA:{chinese:'摩洛哥',english:'Morocco'},MZ:{chinese:'莫桑比克',english:'Mozambique'},MM:{chinese:'缅甸',english:'Myanmar'},NA:{chinese:'纳米比亚',english:'Namibia'},NP:{chinese:'尼泊尔',english:'Nepal'},NL:{chinese:'荷兰',english:'Netherlands'},NZ:{chinese:'新西兰',english:'New Zealand'},NI:{chinese:'尼加拉瓜',english:'Nicaragua'},NE:{chinese:'尼日尔',english:'Niger'},NG:{chinese:'尼日利亚',english:'Nigeria'},KP:{chinese:'朝鲜',english:'North Korea'},NO:{chinese:'挪威',english:'Norway'},OM:{chinese:'阿曼',english:'Oman'},PK:{chinese:'巴基斯坦',english:'Pakistan'},PA:{chinese:'巴拿马',english:'Panama'},PY:{chinese:'巴拉圭',english:'Paraguay'},PE:{chinese:'秘鲁',english:'Peru'},PH:{chinese:'菲律宾',english:'Philippines'},PL:{chinese:'波兰',english:'Poland'},PT:{chinese:'葡萄牙',english:'Portugal'},PR:{chinese:'波多黎各',english:'Puerto Rico'},QA:{chinese:'卡塔尔',english:'Qatar'},RE:{chinese:'留尼旺',english:'Reunion'},RO:{chinese:'罗马尼亚',english:'Romania'},RU:{chinese:'俄罗斯',english:'Russia'},RW:{chinese:'卢旺达',english:'Rwanda'},SM:{chinese:'圣马力诺',english:'San Marino'},SA:{chinese:'沙特阿拉伯',english:'Saudi Arabia'},SN:{chinese:'塞内加尔',english:'Senegal'},RS:{chinese:'塞尔维亚',english:'Serbia'},SL:{chinese:'塞拉利昂',english:'Sierra Leone'},SG:{chinese:'新加坡',english:'Singapore'},SK:{chinese:'斯洛伐克',english:'Slovakia'},SI:{chinese:'斯洛文尼亚',english:'Slovenia'},SO:{chinese:'索马里',english:'Somalia'},ZA:{chinese:'南非',english:'South Africa'},ES:{chinese:'西班牙',english:'Spain'},LK:{chinese:'斯里兰卡',english:'Sri Lanka'},SD:{chinese:'苏丹',english:'Sudan'},SR:{chinese:'苏里南',english:'Suriname'},SZ:{chinese:'斯威士兰',english:'Swaziland'},SE:{chinese:'瑞典',english:'Sweden'},CH:{chinese:'瑞士',english:'Switzerland'},SY:{chinese:'叙利亚',english:'Syria'},TW:{chinese:'台湾',english:'Taiwan'},TJ:{chinese:'塔吉克斯坦',english:'Tajikstan'},TZ:{chinese:'坦桑尼亚',english:'Tanzania'},TH:{chinese:'泰国',english:'Thailand'},TG:{chinese:'多哥',english:'Togo'},TO:{chinese:'汤加',english:'Tonga'},TT:{chinese:'特立尼达和多巴哥',english:'Trinidad and Tobago'},TN:{chinese:'突尼斯',english:'Tunisia'},TR:{chinese:'土耳其',english:'Turkey'},TM:{chinese:'土库曼斯坦',english:'Turkmenistan'},VI:{chinese:'美属维尔京群岛',english:'U.S. Virgin Islands'},UG:{chinese:'乌干达',english:'Uganda'},UA:{chinese:'乌克兰',english:'Ukraine'},AE:{chinese:'阿联酋',english:'United Arab Emirates'},GB:{chinese:'英国',english:'United Kiongdom'},US:{chinese:'美国',english:'USA'},UY:{chinese:'乌拉圭',english:'Uruguay'},UZ:{chinese:'乌兹别克斯坦',english:'Uzbekistan'},VA:{chinese:'梵蒂冈',english:'Vatican City'},VE:{chinese:'委内瑞拉',english:'Venezuela'},VN:{chinese:'越南',english:'Vietnam'},YE:{chinese:'也门',english:'Yemen'},YU:{chinese:'南斯拉夫',english:'Yugoslavia'},ZR:{chinese:'扎伊尔',english:'Zaire'},ZM:{chinese:'赞比亚',english:'Zambia'},ZW:{chinese:'津巴布韦',english:'Zimbabwe'}} 292 | 293 | // prettier-ignore 294 | 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,i)=>{s.call(this,t,(t,s,r)=>{t?i(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("",`\ud83d\udd14${this.name}, \u5f00\u59cb!`)}isNode(){return"undefined"!=typeof module&&!!module.exports}isQuanX(){return"undefined"!=typeof $task}isSurge(){return"undefined"!=typeof $httpClient&&"undefined"==typeof $loon}isLoon(){return"undefined"!=typeof $loon}isShadowrocket(){return"undefined"!=typeof $rocket}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 i=this.getdata(t);if(i)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,i)=>e(i))})}runScript(t,e){return new Promise(s=>{let i=this.getdata("@chavy_boxjs_userCfgs.httpapi");i=i?i.replace(/\n/g,"").trim():i;let r=this.getdata("@chavy_boxjs_userCfgs.httpapi_timeout");r=r?1*r:20,r=e&&e.timeout?e.timeout:r;const[o,h]=i.split("@"),n={url:`http://${h}/v1/scripting/evaluate`,body:{script_text:t,mock_type:"cron",timeout:r},headers:{"X-Key":o,Accept:"*/*"}};this.post(n,(t,e,i)=>s(i))}).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),i=!s&&this.fs.existsSync(e);if(!s&&!i)return{};{const i=s?t:e;try{return JSON.parse(this.fs.readFileSync(i))}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),i=!s&&this.fs.existsSync(e),r=JSON.stringify(this.data);s?this.fs.writeFileSync(t,r):i?this.fs.writeFileSync(e,r):this.fs.writeFileSync(t,r)}}lodash_get(t,e,s){const i=e.replace(/\[(\d+)\]/g,".$1").split(".");let r=t;for(const t of i)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,i)=>Object(t[s])===t[s]?t[s]:t[s]=Math.abs(e[i+1])>>0==+e[i+1]?[]:{},t)[e[e.length-1]]=s,t)}getdata(t){let e=this.getval(t);if(/^@/.test(t)){const[,s,i]=/^@(.*?)\.(.*?)$/.exec(t),r=s?this.getval(s):"";if(r)try{const t=JSON.parse(r);e=t?this.lodash_get(t,i,""):e}catch(t){e=""}}return e}setdata(t,e){let s=!1;if(/^@/.test(e)){const[,i,r]=/^@(.*?)\.(.*?)$/.exec(e),o=this.getval(i),h=i?"null"===o?null:o||"{}":"{}";try{const e=JSON.parse(h);this.lodash_set(e,r,t),s=this.setval(JSON.stringify(e),i)}catch(e){const o={};this.lodash_set(o,r,t),s=this.setval(JSON.stringify(o),i)}}else s=this.setval(t,e);return s}getval(t){return this.isSurge()||this.isLoon()?$persistentStore.read(t):this.isQuanX()?$prefs.valueForKey(t):this.isNode()?(this.data=this.loaddata(),this.data[t]):this.data&&this.data[t]||null}setval(t,e){return this.isSurge()||this.isLoon()?$persistentStore.write(t,e):this.isQuanX()?$prefs.setValueForKey(t,e):this.isNode()?(this.data=this.loaddata(),this.data[e]=t,this.writedata(),!0):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=(()=>{})){if(t.headers&&(delete t.headers["Content-Type"],delete t.headers["Content-Length"]),this.isSurge()||this.isLoon())this.isSurge()&&this.isNeedRewrite&&(t.headers=t.headers||{},Object.assign(t.headers,{"X-Surge-Skip-Scripting":!1})),$httpClient.get(t,(t,s,i)=>{!t&&s&&(s.body=i,s.statusCode=s.status),e(t,s,i)});else if(this.isQuanX())this.isNeedRewrite&&(t.opts=t.opts||{},Object.assign(t.opts,{hints:!1})),$task.fetch(t).then(t=>{const{statusCode:s,statusCode:i,headers:r,body:o}=t;e(null,{status:s,statusCode:i,headers:r,body:o},o)},t=>e(t));else if(this.isNode()){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:i,statusCode:r,headers:o,rawBody:h}=t;e(null,{status:i,statusCode:r,headers:o,rawBody:h},s.decode(h,this.encoding))},t=>{const{message:i,response:r}=t;e(i,r,r&&s.decode(r.rawBody,this.encoding))})}}post(t,e=(()=>{})){const s=t.method?t.method.toLocaleLowerCase():"post";if(t.body&&t.headers&&!t.headers["Content-Type"]&&(t.headers["Content-Type"]="application/x-www-form-urlencoded"),t.headers&&delete t.headers["Content-Length"],this.isSurge()||this.isLoon())this.isSurge()&&this.isNeedRewrite&&(t.headers=t.headers||{},Object.assign(t.headers,{"X-Surge-Skip-Scripting":!1})),$httpClient[s](t,(t,s,i)=>{!t&&s&&(s.body=i,s.statusCode=s.status),e(t,s,i)});else if(this.isQuanX())t.method=s,this.isNeedRewrite&&(t.opts=t.opts||{},Object.assign(t.opts,{hints:!1})),$task.fetch(t).then(t=>{const{statusCode:s,statusCode:i,headers:r,body:o}=t;e(null,{status:s,statusCode:i,headers:r,body:o},o)},t=>e(t));else if(this.isNode()){let i=require("iconv-lite");this.initGotEnv(t);const{url:r,...o}=t;this.got[s](r,o).then(t=>{const{statusCode:s,statusCode:r,headers:o,rawBody:h}=t;e(null,{status:s,statusCode:r,headers:o,rawBody:h},i.decode(h,this.encoding))},t=>{const{message:s,response:r}=t;e(s,r,r&&i.decode(r.rawBody,this.encoding))})}}time(t,e=null){const s=e?new Date(e):new Date;let i={"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 i)new RegExp("("+e+")").test(t)&&(t=t.replace(RegExp.$1,1==RegExp.$1.length?i[e]:("00"+i[e]).substr((""+i[e]).length)));return t}msg(e=t,s="",i="",r){const o=t=>{if(!t)return t;if("string"==typeof t)return this.isLoon()?t:this.isQuanX()?{"open-url":t}:this.isSurge()?{url:t}:void 0;if("object"==typeof t){if(this.isLoon()){let e=t.openUrl||t.url||t["open-url"],s=t.mediaUrl||t["media-url"];return{openUrl:e,mediaUrl:s}}if(this.isQuanX()){let e=t["open-url"]||t.url||t.openUrl,s=t["media-url"]||t.mediaUrl;return{"open-url":e,"media-url":s}}if(this.isSurge()){let e=t.url||t.openUrl||t["open-url"];return{url:e}}}};if(this.isMute||(this.isSurge()||this.isLoon()?$notification.post(e,s,i,o(r)):this.isQuanX()&&$notify(e,s,i,o(r))),!this.isMuteLog){let t=["","==============\ud83d\udce3\u7cfb\u7edf\u901a\u77e5\ud83d\udce3=============="];t.push(e),s&&t.push(s),i&&t.push(i),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){const s=!this.isSurge()&&!this.isQuanX()&&!this.isLoon();s?this.log("",`\u2757\ufe0f${this.name}, \u9519\u8bef!`,t.stack):this.log("",`\u2757\ufe0f${this.name}, \u9519\u8bef!`,t)}wait(t){return new Promise(e=>setTimeout(e,t))}done(t={}){const e=(new Date).getTime(),s=(e-this.startTime)/1e3;this.log("",`\ud83d\udd14${this.name}, \u7ed3\u675f! \ud83d\udd5b ${s} \u79d2`),this.log(),(this.isSurge()||this.isQuanX()||this.isLoon())&&$done(t)}}(t,e)} 295 | -------------------------------------------------------------------------------- /QuantumultX/NetflixPolicySwitcher/netflix_switcher.js: -------------------------------------------------------------------------------- 1 | const STATUS_FULL_AVAILABLE = 2 // 完整支持 2 | const STATUS_ORIGINAL_AVAILABLE = 1 // 支持自制剧 3 | const STATUS_NOT_AVAILABLE = 0 // 不支持解锁 4 | const STATUS_TIMEOUT = -1 // 检测超时 5 | const STATUS_ERROR = -2 // 检测异常 6 | 7 | const $ = new Env('Netflix 策略切换') 8 | let policyName = $.getval('Helge_0x00.Netflix_Policy') || 'Netflix' 9 | let debug = $.getval('Helge_0x00.Netflix_Debug') === 'true' 10 | let recheck = $.getval('Helge_0x00.Netflix_Recheck') === 'true' 11 | let t = parseInt($.getval('Helge_0x00.Netflix_Timeout')) || 8000 12 | let sortByTime = $.getval('Helge_0x00.Netflix_Sort_By_Time') === 'true' 13 | let concurrency = parseInt($.getval('Helge_0x00.Netflix_Concurrency')) || 10 14 | 15 | ;(async () => { 16 | if (!$.isQuanX()) { 17 | throw '该脚本仅支持在 Quantumult X 中运行' 18 | } 19 | 20 | let policies = await sendMessage({ action: 'get_customized_policy' }) 21 | if (!isValidPolicy(policies[policyName])) { 22 | policyName = lookupTargetPolicy(policies) 23 | console.log(`更新策略组名称 ➟ ${policyName}`) 24 | $.setval(policyName, 'Helge_0x00.Netflix_Policy') 25 | } 26 | 27 | let curPolicyPath = await getSelectedPolicy(policyName) 28 | let selected = curPolicyPath[1] 29 | let actualNode = curPolicyPath[curPolicyPath.length - 1] 30 | if (debug) { 31 | console.log(`当前选择的策略:${curPolicyPath.join(' ➤ ')}`) 32 | } 33 | 34 | let { region, status } = await test(actualNode) 35 | if (status === STATUS_FULL_AVAILABLE) { 36 | let flag = getCountryFlagEmoji(region) ?? '' 37 | let regionName = REGIONS?.[region.toUpperCase()]?.chinese ?? '' 38 | $.msg($.name, `${actualNode}`, `该节点完整支持 Netflix ➟ ${flag} ${regionName}`) 39 | return 40 | } 41 | 42 | let cacheFullPolicies = [] 43 | try { 44 | cacheFullPolicies = JSON.parse($.getval('Helge_0x00.Netflix_Full_Available_Policies') ?? '[]') 45 | } catch (error) { 46 | console.log('cacheFullPolicies error: ' + error) 47 | cacheFullPolicies = [] 48 | } 49 | 50 | let cacheOriginalPolicies = [] 51 | try { 52 | cacheOriginalPolicies = JSON.parse($.getval('Helge_0x00.Netflix_Original_Available_Policies') ?? '[]') 53 | } catch (error) { 54 | console.log('cacheOriginalPolicies error: ' + error) 55 | cacheOriginalPolicies = [] 56 | } 57 | 58 | let paths = lookupPath(policies, policyName) 59 | let nodes = new Set(paths.map(path => path[path.length - 1]).filter(item => !['proxy', 'direct', 'reject'].includes(item))) 60 | 61 | // 检测一遍缓存的可用子策略是否还在当前策略中 62 | cacheFullPolicies = cacheFullPolicies.filter(item => nodes.has(item.policy) && item.policy !== selected) 63 | cacheOriginalPolicies = cacheOriginalPolicies.filter(item => nodes.has(item.policy) && item.policy !== selected) 64 | 65 | // 切换前重新检测是否可用 66 | if (recheck) { 67 | let recheckPolicies = [...cacheFullPolicies.map(item => item.policy), ...cacheOriginalPolicies.map(item => item.policy)] 68 | let { fullAvailablePolicies, originalAvailablePolicies } = await testPolicies(recheckPolicies) 69 | cacheFullPolicies = fullAvailablePolicies 70 | cacheOriginalPolicies = originalAvailablePolicies 71 | if (sortByTime) { 72 | cacheFullPolicies = cacheFullPolicies.sort((m, n) => m.time - n.time) 73 | cacheOriginalPolicies = cacheOriginalPolicies.sort((m, n) => m.time - n.time) 74 | } 75 | } 76 | 77 | $.setval(JSON.stringify(cacheFullPolicies), 'Helge_0x00.Netflix_Full_Available_Policies') 78 | $.setval(JSON.stringify(cacheOriginalPolicies), 'Helge_0x00.Netflix_Original_Available_Policies') 79 | 80 | if (cacheFullPolicies.length <= 0 && cacheOriginalPolicies <= 0) { 81 | throw '没有可用策略,请先运行 「Netflix 解锁检测」脚本' 82 | } 83 | 84 | // 当前节点仅支持自制剧,且没有其他支持全部解锁的节点,则不切换 85 | if (cacheFullPolicies.length <= 0 && status === STATUS_ORIGINAL_AVAILABLE) { 86 | let flag = getCountryFlagEmoji(region) ?? '' 87 | let regionName = REGIONS?.[region.toUpperCase()]?.chinese ?? '' 88 | $.msg($.name, `${actualNode}`, `没有支持完整解锁的节点,该节点仅支持 Netflix 自制剧 ➟ ${flag} ${regionName}`) 89 | return 90 | } 91 | 92 | let { policy: newPolicy, region: newRegion, status: newStatus } = cacheFullPolicies.length > 0 ? cacheFullPolicies[0] : cacheOriginalPolicies[0] 93 | 94 | // 找到切换路径,并按照路径长度排序,取路径长度最短的 95 | let switchPath = paths.filter(path => path[path.length - 1] === newPolicy).sort((m, n) => m.length - n.length)[0] 96 | let switchDict = {} 97 | for (let i = 0; i < switchPath.length - 1; i++) { 98 | switchDict[switchPath[i]] = switchPath[i + 1] 99 | } 100 | await setPolicyState(switchDict) 101 | console.log(`\n切换策略:${curPolicyPath.join(' ➤ ')} ➟ ${switchPath.join(' ➤ ')}`) 102 | let flag = getCountryFlagEmoji(newRegion) ?? '' 103 | let regionName = REGIONS?.[newRegion.toUpperCase()]?.chinese ?? '' 104 | let msg = 105 | newStatus === STATUS_FULL_AVAILABLE 106 | ? `完整支持 Netflix ➟ ${flag} ${regionName}` 107 | : `没有支持完整解锁的节点,该节点仅支持 Netflix 自制剧 ➟ ${flag} ${regionName}` 108 | $.msg($.name, `${curPolicyPath[curPolicyPath.length - 1]} ➟ ${switchPath[switchPath.length - 1]}`, msg) 109 | })() 110 | .catch(error => { 111 | console.log(error) 112 | if (typeof error === 'string') { 113 | $.msg($.name, '', `${error} ⚠️`) 114 | } 115 | }) 116 | .finally(() => { 117 | $.done() 118 | }) 119 | 120 | async function getSelectedPolicy(policyName) { 121 | let message = { 122 | action: 'get_policy_state', 123 | content: policyName, 124 | } 125 | 126 | let ret = await sendMessage(message) 127 | return ret?.[policyName] 128 | } 129 | 130 | async function setPolicyState(policyDict) { 131 | let message = { 132 | action: 'set_policy_state', 133 | content: policyDict, 134 | } 135 | try { 136 | await sendMessage(message) 137 | } catch (e) { 138 | if (debug) { 139 | console.log(`策略切换失败:${e}`) 140 | } 141 | throw '策略切换失败,请重试' 142 | } 143 | } 144 | 145 | async function testPolicies(policies = []) { 146 | let fullAvailablePolicies = [] 147 | let originalAvailablePolicies = [] 148 | let echo = results => { 149 | console.log(`\n策略组检测结果:`) 150 | for (let { policy, status, region, time } of results) { 151 | switch (status) { 152 | case STATUS_FULL_AVAILABLE: { 153 | let flag = getCountryFlagEmoji(region) ?? '' 154 | let regionName = REGIONS?.[region.toUpperCase()]?.chinese ?? '' 155 | console.log(`${policy}: 完整支持 Netflix ➟ ${flag}${regionName}`) 156 | fullAvailablePolicies.push({ policy, region, status, time }) 157 | break 158 | } 159 | case STATUS_ORIGINAL_AVAILABLE: { 160 | let flag = getCountryFlagEmoji(region) ?? '' 161 | let regionName = REGIONS?.[region.toUpperCase()]?.chinese ?? '' 162 | console.log(`${policy}: 仅支持 Netflix 自制剧 ➟ ${flag}${regionName}`) 163 | originalAvailablePolicies.push({ policy, region, status, time }) 164 | break 165 | } 166 | case STATUS_NOT_AVAILABLE: 167 | console.log(`${policy}: 不支持 Netflix`) 168 | break 169 | case STATUS_TIMEOUT: 170 | console.log(`${policy}: 检测超时`) 171 | break 172 | default: 173 | console.log(`${policy}: 检测异常`) 174 | } 175 | } 176 | } 177 | 178 | await Promise.map(policies, subPolicy => test(subPolicy), { concurrency }) 179 | .then(echo) 180 | .catch(error => console.log(error)) 181 | 182 | return { fullAvailablePolicies, originalAvailablePolicies } 183 | } 184 | 185 | function getFilmPage(filmId, policyName) { 186 | return new Promise((resolve, reject) => { 187 | let request = { 188 | url: `https://www.netflix.com/title/${filmId}`, 189 | opts: { 190 | redirection: false, 191 | policy: policyName, 192 | }, 193 | headers: { 194 | 'Accept-Language': 'en', 195 | 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.71 Safari/537.36', 196 | }, 197 | } 198 | $task.fetch(request).then( 199 | response => { 200 | let { 201 | statusCode, 202 | headers: { Location: location, 'X-Originating-URL': originatingUrl }, 203 | } = response 204 | 205 | if (statusCode === 403) { 206 | reject('Not Available') 207 | return 208 | } 209 | 210 | if (statusCode === 404) { 211 | reject('Not Found') 212 | return 213 | } 214 | 215 | if (statusCode === 302 || statusCode === 301 || statusCode === 200) { 216 | if (debug) { 217 | if (statusCode === 200) { 218 | console.log(`${policyName} filmId: ${filmId}, statusCode: ${statusCode}, X-Originating-URL: ${originatingUrl}`) 219 | } else { 220 | console.log(`${policyName} filmId: ${filmId}, statusCode: ${statusCode}, Location: ${location}`) 221 | } 222 | } 223 | 224 | let url = location ?? originatingUrl 225 | let region = url.split('/')[3] 226 | region = region.split('-')[0] 227 | if (region === 'title') { 228 | region = 'US' 229 | } 230 | resolve(region.toUpperCase()) 231 | return 232 | } 233 | 234 | if (debug) { 235 | console.log(`${policyName} filmId: ${filmId}, statusCode: ${statusCode}, response: ${JSON.stringify(response)}`) 236 | } 237 | reject('Not Available') 238 | }, 239 | reason => { 240 | if (debug) { 241 | console.log(`${policyName} getFilmPage Error: ${reason.error}`) 242 | } 243 | reject('Error') 244 | } 245 | ) 246 | }) 247 | } 248 | 249 | async function test(policyName) { 250 | console.log(`开始测试 ${policyName}`) 251 | let startTime = new Date().getTime() 252 | let result = await Promise.race([getFilmPage(81215567, policyName), timeout(t)]) 253 | .then(region => { 254 | return { region, policy: policyName, status: STATUS_FULL_AVAILABLE } 255 | }) 256 | .catch(async error => { 257 | if (error !== 'Not Found') { 258 | return Promise.reject(error) 259 | } 260 | 261 | let region = await Promise.race([getFilmPage(80018499, policyName), timeout(t)]) 262 | return { region, policy: policyName, status: STATUS_ORIGINAL_AVAILABLE } 263 | }) 264 | .catch(error => { 265 | if (error === 'Not Available') { 266 | return { policy: policyName, status: STATUS_NOT_AVAILABLE } 267 | } else if (error === 'Timeout') { 268 | return { policy: policyName, status: STATUS_TIMEOUT } 269 | } 270 | 271 | return { policy: policyName, status: STATUS_ERROR } 272 | }) 273 | return Object.assign(result, { time: new Date().getTime() - startTime }) 274 | } 275 | 276 | function timeout(delay = 5000) { 277 | return new Promise((resolve, reject) => { 278 | setTimeout(() => { 279 | reject('Timeout') 280 | }, delay) 281 | }) 282 | } 283 | 284 | function getCountryFlagEmoji(countryCode) { 285 | if (countryCode.toUpperCase() === 'TW') { 286 | countryCode = 'CN' 287 | } 288 | const codePoints = countryCode 289 | .toUpperCase() 290 | .split('') 291 | .map(char => 127397 + char.charCodeAt()) 292 | return String.fromCodePoint(...codePoints) 293 | } 294 | 295 | function sendMessage(message) { 296 | return new Promise((resolve, reject) => { 297 | $configuration.sendMessage(message).then( 298 | response => { 299 | if (response.error) { 300 | if (debug) { 301 | console.log(`${message?.action} error: ${response.error}`) 302 | } 303 | reject(response.error) 304 | return 305 | } 306 | 307 | resolve(response.ret) 308 | }, 309 | error => { 310 | // Normally will never happen. 311 | reject(error) 312 | } 313 | ) 314 | }) 315 | } 316 | 317 | function lookupPath(policies = {}, policyGroupName = '', curPath = [], paths = []) { 318 | let targetPolicy = policies[policyGroupName] 319 | 320 | if (targetPolicy === undefined || targetPolicy?.type === undefined || !Array.isArray(targetPolicy?.candidates)) { 321 | return paths 322 | } 323 | 324 | curPath.push(policyGroupName) 325 | 326 | if (targetPolicy?.type !== 'static') { 327 | paths.push([...curPath]) 328 | return paths 329 | } 330 | 331 | for (const policy of targetPolicy?.candidates) { 332 | if (policies[policy] === undefined) { 333 | paths.push([...curPath, policy]) 334 | continue 335 | } 336 | 337 | // 成环了 338 | if (curPath.includes(policy)) { 339 | paths.push([...curPath, policy, '⚠️', 'direct']) 340 | continue 341 | } 342 | 343 | lookupPath(policies, policy, [...curPath], paths) 344 | } 345 | return paths 346 | } 347 | 348 | function lookupTargetPolicy(policies = {}) { 349 | let policyNames = Object.entries(policies) 350 | .filter(([key, val]) => key.search(/Netflix|奈飞|网飞/gi) !== -1) 351 | .map(([key, val]) => key) 352 | if (policyNames.length === 1) { 353 | return policyNames[0] 354 | } else if (policyNames.length <= 0) { 355 | throw '没有找到 Netflix 策略组,请在 BoxJS 中填写正确的策略组名称' 356 | } else { 357 | throw `找到多个 Netflix 策略组,请在 BoxJS 中填写正确的策略组名称` 358 | } 359 | } 360 | 361 | function isValidPolicy(policy) { 362 | return policy !== undefined && policy?.type !== undefined && Array.isArray(policy?.candidates) 363 | } 364 | 365 | // prettier-ignore 366 | Array.prototype.remove=function(e){let t=this.indexOf(e);-1!==t&&this.splice(t,1)} 367 | 368 | // prettier-ignore 369 | Promise.map=function(t,e,{concurrency:u}){const i=new class{constructor(t){this.limit=t,this.count=0,this.queue=[]}enqueue(t){return new Promise((e,u)=>{this.queue.push({fn:t,resolve:e,reject:u})})}dequeue(){if(this.counti.build(()=>e(...t))))} 370 | 371 | // prettier-ignore 372 | const REGIONS={AF:{chinese:'阿富汗',english:'Afghanistan'},AL:{chinese:'阿尔巴尼亚',english:'Albania'},DZ:{chinese:'阿尔及利亚',english:'Algeria'},AO:{chinese:'安哥拉',english:'Angola'},AR:{chinese:'阿根廷',english:'Argentina'},AM:{chinese:'亚美尼亚',english:'Armenia'},AU:{chinese:'澳大利亚',english:'Australia'},AT:{chinese:'奥地利',english:'Austria'},AZ:{chinese:'阿塞拜疆',english:'Azerbaijan'},BH:{chinese:'巴林',english:'Bahrain'},BD:{chinese:'孟加拉国',english:'Bangladesh'},BY:{chinese:'白俄罗斯',english:'Belarus'},BE:{chinese:'比利时',english:'Belgium'},BZ:{chinese:'伯利兹',english:'Belize'},BJ:{chinese:'贝宁',english:'Benin'},BT:{chinese:'不丹',english:'Bhutan'},BO:{chinese:'玻利维亚',english:'Bolivia'},BA:{chinese:'波黑',english:'Bosnia and Herzegovina'},BW:{chinese:'博茨瓦纳',english:'Botswana'},BR:{chinese:'巴西',english:'Brazil'},VG:{chinese:'英属维京群岛',english:'British Virgin Islands'},BN:{chinese:'文莱',english:'Brunei'},BG:{chinese:'保加利亚',english:'Bulgaria'},BF:{chinese:'布基纳法索',english:'Burkina-faso'},BI:{chinese:'布隆迪',english:'Burundi'},KH:{chinese:'柬埔寨',english:'Cambodia'},CM:{chinese:'喀麦隆',english:'Cameroon'},CA:{chinese:'加拿大',english:'Canada'},CV:{chinese:'佛得角',english:'Cape Verde'},KY:{chinese:'开曼群岛',english:'Cayman Islands'},CF:{chinese:'中非',english:'Central African Republic'},TD:{chinese:'乍得',english:'Chad'},CL:{chinese:'智利',english:'Chile'},CN:{chinese:'中国',english:'China'},CO:{chinese:'哥伦比亚',english:'Colombia'},KM:{chinese:'科摩罗',english:'Comoros'},CG:{chinese:'刚果(布)',english:'Congo - Brazzaville'},CD:{chinese:'刚果(金)',english:'Congo - Kinshasa'},CR:{chinese:'哥斯达黎加',english:'Costa Rica'},HR:{chinese:'克罗地亚',english:'Croatia'},CY:{chinese:'塞浦路斯',english:'Cyprus'},CZ:{chinese:'捷克',english:'Czech Republic'},DK:{chinese:'丹麦',english:'Denmark'},DJ:{chinese:'吉布提',english:'Djibouti'},DO:{chinese:'多米尼加',english:'Dominican Republic'},EC:{chinese:'厄瓜多尔',english:'Ecuador'},EG:{chinese:'埃及',english:'Egypt'},SV:{chinese:'萨尔瓦多',english:'EI Salvador'},GQ:{chinese:'赤道几内亚',english:'Equatorial Guinea'},ER:{chinese:'厄立特里亚',english:'Eritrea'},EE:{chinese:'爱沙尼亚',english:'Estonia'},ET:{chinese:'埃塞俄比亚',english:'Ethiopia'},FJ:{chinese:'斐济',english:'Fiji'},FI:{chinese:'芬兰',english:'Finland'},FR:{chinese:'法国',english:'France'},GA:{chinese:'加蓬',english:'Gabon'},GM:{chinese:'冈比亚',english:'Gambia'},GE:{chinese:'格鲁吉亚',english:'Georgia'},DE:{chinese:'德国',english:'Germany'},GH:{chinese:'加纳',english:'Ghana'},GR:{chinese:'希腊',english:'Greece'},GL:{chinese:'格陵兰',english:'Greenland'},GT:{chinese:'危地马拉',english:'Guatemala'},GN:{chinese:'几内亚',english:'Guinea'},GY:{chinese:'圭亚那',english:'Guyana'},HT:{chinese:'海地',english:'Haiti'},HN:{chinese:'洪都拉斯',english:'Honduras'},HK:{chinese:'香港',english:'Hong Kong'},HU:{chinese:'匈牙利',english:'Hungary'},IS:{chinese:'冰岛',english:'Iceland'},IN:{chinese:'印度',english:'India'},ID:{chinese:'印度尼西亚',english:'Indonesia'},IR:{chinese:'伊朗',english:'Iran'},IQ:{chinese:'伊拉克',english:'Iraq'},IE:{chinese:'爱尔兰',english:'Ireland'},IM:{chinese:'马恩岛',english:'Isle of Man'},IL:{chinese:'以色列',english:'Israel'},IT:{chinese:'意大利',english:'Italy'},CI:{chinese:'科特迪瓦',english:'Ivory Coast'},JM:{chinese:'牙买加',english:'Jamaica'},JP:{chinese:'日本',english:'Japan'},JO:{chinese:'约旦',english:'Jordan'},KZ:{chinese:'哈萨克斯坦',english:'Kazakstan'},KE:{chinese:'肯尼亚',english:'Kenya'},KR:{chinese:'韩国',english:'Korea'},KW:{chinese:'科威特',english:'Kuwait'},KG:{chinese:'吉尔吉斯斯坦',english:'Kyrgyzstan'},LA:{chinese:'老挝',english:'Laos'},LV:{chinese:'拉脱维亚',english:'Latvia'},LB:{chinese:'黎巴嫩',english:'Lebanon'},LS:{chinese:'莱索托',english:'Lesotho'},LR:{chinese:'利比里亚',english:'Liberia'},LY:{chinese:'利比亚',english:'Libya'},LT:{chinese:'立陶宛',english:'Lithuania'},LU:{chinese:'卢森堡',english:'Luxembourg'},MO:{chinese:'澳门',english:'Macao'},MK:{chinese:'马其顿',english:'Macedonia'},MG:{chinese:'马达加斯加',english:'Madagascar'},MW:{chinese:'马拉维',english:'Malawi'},MY:{chinese:'马来西亚',english:'Malaysia'},MV:{chinese:'马尔代夫',english:'Maldives'},ML:{chinese:'马里',english:'Mali'},MT:{chinese:'马耳他',english:'Malta'},MR:{chinese:'毛利塔尼亚',english:'Mauritania'},MU:{chinese:'毛里求斯',english:'Mauritius'},MX:{chinese:'墨西哥',english:'Mexico'},MD:{chinese:'摩尔多瓦',english:'Moldova'},MC:{chinese:'摩纳哥',english:'Monaco'},MN:{chinese:'蒙古',english:'Mongolia'},ME:{chinese:'黑山',english:'Montenegro'},MA:{chinese:'摩洛哥',english:'Morocco'},MZ:{chinese:'莫桑比克',english:'Mozambique'},MM:{chinese:'缅甸',english:'Myanmar'},NA:{chinese:'纳米比亚',english:'Namibia'},NP:{chinese:'尼泊尔',english:'Nepal'},NL:{chinese:'荷兰',english:'Netherlands'},NZ:{chinese:'新西兰',english:'New Zealand'},NI:{chinese:'尼加拉瓜',english:'Nicaragua'},NE:{chinese:'尼日尔',english:'Niger'},NG:{chinese:'尼日利亚',english:'Nigeria'},KP:{chinese:'朝鲜',english:'North Korea'},NO:{chinese:'挪威',english:'Norway'},OM:{chinese:'阿曼',english:'Oman'},PK:{chinese:'巴基斯坦',english:'Pakistan'},PA:{chinese:'巴拿马',english:'Panama'},PY:{chinese:'巴拉圭',english:'Paraguay'},PE:{chinese:'秘鲁',english:'Peru'},PH:{chinese:'菲律宾',english:'Philippines'},PL:{chinese:'波兰',english:'Poland'},PT:{chinese:'葡萄牙',english:'Portugal'},PR:{chinese:'波多黎各',english:'Puerto Rico'},QA:{chinese:'卡塔尔',english:'Qatar'},RE:{chinese:'留尼旺',english:'Reunion'},RO:{chinese:'罗马尼亚',english:'Romania'},RU:{chinese:'俄罗斯',english:'Russia'},RW:{chinese:'卢旺达',english:'Rwanda'},SM:{chinese:'圣马力诺',english:'San Marino'},SA:{chinese:'沙特阿拉伯',english:'Saudi Arabia'},SN:{chinese:'塞内加尔',english:'Senegal'},RS:{chinese:'塞尔维亚',english:'Serbia'},SL:{chinese:'塞拉利昂',english:'Sierra Leone'},SG:{chinese:'新加坡',english:'Singapore'},SK:{chinese:'斯洛伐克',english:'Slovakia'},SI:{chinese:'斯洛文尼亚',english:'Slovenia'},SO:{chinese:'索马里',english:'Somalia'},ZA:{chinese:'南非',english:'South Africa'},ES:{chinese:'西班牙',english:'Spain'},LK:{chinese:'斯里兰卡',english:'Sri Lanka'},SD:{chinese:'苏丹',english:'Sudan'},SR:{chinese:'苏里南',english:'Suriname'},SZ:{chinese:'斯威士兰',english:'Swaziland'},SE:{chinese:'瑞典',english:'Sweden'},CH:{chinese:'瑞士',english:'Switzerland'},SY:{chinese:'叙利亚',english:'Syria'},TW:{chinese:'台湾',english:'Taiwan'},TJ:{chinese:'塔吉克斯坦',english:'Tajikstan'},TZ:{chinese:'坦桑尼亚',english:'Tanzania'},TH:{chinese:'泰国',english:'Thailand'},TG:{chinese:'多哥',english:'Togo'},TO:{chinese:'汤加',english:'Tonga'},TT:{chinese:'特立尼达和多巴哥',english:'Trinidad and Tobago'},TN:{chinese:'突尼斯',english:'Tunisia'},TR:{chinese:'土耳其',english:'Turkey'},TM:{chinese:'土库曼斯坦',english:'Turkmenistan'},VI:{chinese:'美属维尔京群岛',english:'U.S. Virgin Islands'},UG:{chinese:'乌干达',english:'Uganda'},UA:{chinese:'乌克兰',english:'Ukraine'},AE:{chinese:'阿联酋',english:'United Arab Emirates'},GB:{chinese:'英国',english:'United Kiongdom'},US:{chinese:'美国',english:'USA'},UY:{chinese:'乌拉圭',english:'Uruguay'},UZ:{chinese:'乌兹别克斯坦',english:'Uzbekistan'},VA:{chinese:'梵蒂冈',english:'Vatican City'},VE:{chinese:'委内瑞拉',english:'Venezuela'},VN:{chinese:'越南',english:'Vietnam'},YE:{chinese:'也门',english:'Yemen'},YU:{chinese:'南斯拉夫',english:'Yugoslavia'},ZR:{chinese:'扎伊尔',english:'Zaire'},ZM:{chinese:'赞比亚',english:'Zambia'},ZW:{chinese:'津巴布韦',english:'Zimbabwe'}} 373 | 374 | // prettier-ignore 375 | 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,i)=>{s.call(this,t,(t,s,r)=>{t?i(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("",`\ud83d\udd14${this.name}, \u5f00\u59cb!`)}isNode(){return"undefined"!=typeof module&&!!module.exports}isQuanX(){return"undefined"!=typeof $task}isSurge(){return"undefined"!=typeof $httpClient&&"undefined"==typeof $loon}isLoon(){return"undefined"!=typeof $loon}isShadowrocket(){return"undefined"!=typeof $rocket}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 i=this.getdata(t);if(i)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,i)=>e(i))})}runScript(t,e){return new Promise(s=>{let i=this.getdata("@chavy_boxjs_userCfgs.httpapi");i=i?i.replace(/\n/g,"").trim():i;let r=this.getdata("@chavy_boxjs_userCfgs.httpapi_timeout");r=r?1*r:20,r=e&&e.timeout?e.timeout:r;const[o,h]=i.split("@"),n={url:`http://${h}/v1/scripting/evaluate`,body:{script_text:t,mock_type:"cron",timeout:r},headers:{"X-Key":o,Accept:"*/*"}};this.post(n,(t,e,i)=>s(i))}).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),i=!s&&this.fs.existsSync(e);if(!s&&!i)return{};{const i=s?t:e;try{return JSON.parse(this.fs.readFileSync(i))}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),i=!s&&this.fs.existsSync(e),r=JSON.stringify(this.data);s?this.fs.writeFileSync(t,r):i?this.fs.writeFileSync(e,r):this.fs.writeFileSync(t,r)}}lodash_get(t,e,s){const i=e.replace(/\[(\d+)\]/g,".$1").split(".");let r=t;for(const t of i)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,i)=>Object(t[s])===t[s]?t[s]:t[s]=Math.abs(e[i+1])>>0==+e[i+1]?[]:{},t)[e[e.length-1]]=s,t)}getdata(t){let e=this.getval(t);if(/^@/.test(t)){const[,s,i]=/^@(.*?)\.(.*?)$/.exec(t),r=s?this.getval(s):"";if(r)try{const t=JSON.parse(r);e=t?this.lodash_get(t,i,""):e}catch(t){e=""}}return e}setdata(t,e){let s=!1;if(/^@/.test(e)){const[,i,r]=/^@(.*?)\.(.*?)$/.exec(e),o=this.getval(i),h=i?"null"===o?null:o||"{}":"{}";try{const e=JSON.parse(h);this.lodash_set(e,r,t),s=this.setval(JSON.stringify(e),i)}catch(e){const o={};this.lodash_set(o,r,t),s=this.setval(JSON.stringify(o),i)}}else s=this.setval(t,e);return s}getval(t){return this.isSurge()||this.isLoon()?$persistentStore.read(t):this.isQuanX()?$prefs.valueForKey(t):this.isNode()?(this.data=this.loaddata(),this.data[t]):this.data&&this.data[t]||null}setval(t,e){return this.isSurge()||this.isLoon()?$persistentStore.write(t,e):this.isQuanX()?$prefs.setValueForKey(t,e):this.isNode()?(this.data=this.loaddata(),this.data[e]=t,this.writedata(),!0):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=(()=>{})){if(t.headers&&(delete t.headers["Content-Type"],delete t.headers["Content-Length"]),this.isSurge()||this.isLoon())this.isSurge()&&this.isNeedRewrite&&(t.headers=t.headers||{},Object.assign(t.headers,{"X-Surge-Skip-Scripting":!1})),$httpClient.get(t,(t,s,i)=>{!t&&s&&(s.body=i,s.statusCode=s.status),e(t,s,i)});else if(this.isQuanX())this.isNeedRewrite&&(t.opts=t.opts||{},Object.assign(t.opts,{hints:!1})),$task.fetch(t).then(t=>{const{statusCode:s,statusCode:i,headers:r,body:o}=t;e(null,{status:s,statusCode:i,headers:r,body:o},o)},t=>e(t));else if(this.isNode()){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:i,statusCode:r,headers:o,rawBody:h}=t;e(null,{status:i,statusCode:r,headers:o,rawBody:h},s.decode(h,this.encoding))},t=>{const{message:i,response:r}=t;e(i,r,r&&s.decode(r.rawBody,this.encoding))})}}post(t,e=(()=>{})){const s=t.method?t.method.toLocaleLowerCase():"post";if(t.body&&t.headers&&!t.headers["Content-Type"]&&(t.headers["Content-Type"]="application/x-www-form-urlencoded"),t.headers&&delete t.headers["Content-Length"],this.isSurge()||this.isLoon())this.isSurge()&&this.isNeedRewrite&&(t.headers=t.headers||{},Object.assign(t.headers,{"X-Surge-Skip-Scripting":!1})),$httpClient[s](t,(t,s,i)=>{!t&&s&&(s.body=i,s.statusCode=s.status),e(t,s,i)});else if(this.isQuanX())t.method=s,this.isNeedRewrite&&(t.opts=t.opts||{},Object.assign(t.opts,{hints:!1})),$task.fetch(t).then(t=>{const{statusCode:s,statusCode:i,headers:r,body:o}=t;e(null,{status:s,statusCode:i,headers:r,body:o},o)},t=>e(t));else if(this.isNode()){let i=require("iconv-lite");this.initGotEnv(t);const{url:r,...o}=t;this.got[s](r,o).then(t=>{const{statusCode:s,statusCode:r,headers:o,rawBody:h}=t;e(null,{status:s,statusCode:r,headers:o,rawBody:h},i.decode(h,this.encoding))},t=>{const{message:s,response:r}=t;e(s,r,r&&i.decode(r.rawBody,this.encoding))})}}time(t,e=null){const s=e?new Date(e):new Date;let i={"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 i)new RegExp("("+e+")").test(t)&&(t=t.replace(RegExp.$1,1==RegExp.$1.length?i[e]:("00"+i[e]).substr((""+i[e]).length)));return t}msg(e=t,s="",i="",r){const o=t=>{if(!t)return t;if("string"==typeof t)return this.isLoon()?t:this.isQuanX()?{"open-url":t}:this.isSurge()?{url:t}:void 0;if("object"==typeof t){if(this.isLoon()){let e=t.openUrl||t.url||t["open-url"],s=t.mediaUrl||t["media-url"];return{openUrl:e,mediaUrl:s}}if(this.isQuanX()){let e=t["open-url"]||t.url||t.openUrl,s=t["media-url"]||t.mediaUrl;return{"open-url":e,"media-url":s}}if(this.isSurge()){let e=t.url||t.openUrl||t["open-url"];return{url:e}}}};if(this.isMute||(this.isSurge()||this.isLoon()?$notification.post(e,s,i,o(r)):this.isQuanX()&&$notify(e,s,i,o(r))),!this.isMuteLog){let t=["","==============\ud83d\udce3\u7cfb\u7edf\u901a\u77e5\ud83d\udce3=============="];t.push(e),s&&t.push(s),i&&t.push(i),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){const s=!this.isSurge()&&!this.isQuanX()&&!this.isLoon();s?this.log("",`\u2757\ufe0f${this.name}, \u9519\u8bef!`,t.stack):this.log("",`\u2757\ufe0f${this.name}, \u9519\u8bef!`,t)}wait(t){return new Promise(e=>setTimeout(e,t))}done(t={}){const e=(new Date).getTime(),s=(e-this.startTime)/1e3;this.log("",`\ud83d\udd14${this.name}, \u7ed3\u675f! \ud83d\udd5b ${s} \u79d2`),this.log(),(this.isSurge()||this.isQuanX()||this.isLoon())&&$done(t)}}(t,e)} 376 | -------------------------------------------------------------------------------- /QuantumultX/DisneyPlusPolicySwitcher/disney_checker.js: -------------------------------------------------------------------------------- 1 | const STATUS_COMING = 2 // 即将登陆 2 | const STATUS_AVAILABLE = 1 // 支持解锁 3 | const STATUS_NOT_AVAILABLE = 0 // 不支持解锁 4 | const STATUS_TIMEOUT = -1 // 检测超时 5 | const STATUS_ERROR = -2 // 检测异常 6 | 7 | const $ = new Env('Disney+ 解锁检测') 8 | let disneyPolicyName = $.getval('Helge_0x00.Disney_Policy') || 'Disney+' 9 | let debug = $.getval('Helge_0x00.Disney_Debug') === 'true' 10 | let retry = $.getval('Helge_0x00.Disney_Retry') === 'true' 11 | let t = parseInt($.getval('Helge_0x00.Disney_Timeout')) || 8000 12 | let sortByTime = $.getval('Helge_0x00.Disney_Sort_By_Time') === 'true' 13 | let concurrency = parseInt($.getval('Helge_0x00.Disney_Concurrency')) || 10 14 | 15 | ;(async () => { 16 | if (!$.isQuanX()) { 17 | throw '该脚本仅支持在 Quantumult X 中运行' 18 | } 19 | 20 | let policies = await sendMessage({ action: 'get_customized_policy' }) 21 | if (!isValidPolicy(policies[disneyPolicyName])) { 22 | disneyPolicyName = lookupTargetPolicy(policies) 23 | console.log(`更新策略组名称 ➟ ${disneyPolicyName}`) 24 | $.setval(disneyPolicyName, 'Helge_0x00.Disney_Policy') 25 | } 26 | 27 | let candidatePolicies = lookupChildrenNode(policies, disneyPolicyName) 28 | let availablePolicies = await testPolicies(disneyPolicyName, candidatePolicies) 29 | 30 | if (sortByTime) { 31 | availablePolicies = availablePolicies.sort((m, n) => m.time - n.time) 32 | } 33 | 34 | $.setval(JSON.stringify(availablePolicies), 'Helge_0x00.Disney_Available_Policies') 35 | })() 36 | .catch(error => { 37 | console.log(error) 38 | if (typeof error === 'string') { 39 | $.msg($.name, '', `${error} ⚠️`) 40 | } 41 | }) 42 | .finally(() => { 43 | $.done() 44 | }) 45 | 46 | async function testPolicies(policyName, policies = []) { 47 | let failedPolicies = [] 48 | let availablePolicies = [] 49 | let echo = results => { 50 | console.log(`\n策略组 ${policyName} 检测结果:`) 51 | for (let { policy, status, region, time } of results) { 52 | switch (status) { 53 | case STATUS_COMING: { 54 | let flag = getCountryFlagEmoji(region) ?? '' 55 | let regionName = REGIONS?.[region.toUpperCase()]?.chinese ?? '' 56 | console.log(`${policy}: Disney+ 即将登陆 ➟ ${flag}${regionName}`) 57 | break 58 | } 59 | case STATUS_AVAILABLE: { 60 | let flag = getCountryFlagEmoji(region) ?? '' 61 | let regionName = REGIONS?.[region.toUpperCase()]?.chinese ?? '' 62 | console.log(`${policy}: 支持 Disney+ ➟ ${flag}${regionName}`) 63 | availablePolicies.push({ policy, region, time }) 64 | break 65 | } 66 | case STATUS_NOT_AVAILABLE: 67 | console.log(`${policy}: 不支持 Disney+`) 68 | break 69 | case STATUS_TIMEOUT: 70 | console.log(`${policy}: 检测超时`) 71 | failedPolicies.push(policy) 72 | break 73 | default: 74 | console.log(`${policy}: 检测异常`) 75 | failedPolicies.push(policy) 76 | } 77 | } 78 | } 79 | 80 | await Promise.map(policies, subPolicy => test(subPolicy), { concurrency }) 81 | .then(echo) 82 | .catch(error => console.log(error)) 83 | 84 | if (retry && failedPolicies.length > 0) { 85 | await Promise.map(failedPolicies, subPolicy => test(subPolicy), { concurrency }) 86 | .then(echo) 87 | .catch(error => console.log(error)) 88 | } 89 | 90 | return availablePolicies 91 | } 92 | 93 | function getHomePage(policyName) { 94 | return new Promise((resolve, reject) => { 95 | let request = { 96 | url: 'https://www.disneyplus.com/', 97 | opts: { 98 | redirection: false, 99 | policy: policyName, 100 | }, 101 | headers: { 102 | 'Accept-Language': 'en', 103 | 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.71 Safari/537.36', 104 | }, 105 | } 106 | $task.fetch(request).then( 107 | response => { 108 | let { 109 | statusCode, 110 | headers: { Location: location }, 111 | body, 112 | } = response 113 | 114 | if (statusCode === 302) { 115 | if (debug) { 116 | console.log(`${policyName} getHomePage, 302, Location: ${location}`) 117 | } 118 | // 不可用 119 | if (location.indexOf('Sorry, Disney+ is not available in your region.') !== -1) { 120 | if (debug) { 121 | console.log(policyName + ': Not Available') 122 | } 123 | reject('Not Available') 124 | return 125 | } 126 | 127 | // 即将登陆 128 | if (location.indexOf('preview') !== -1) { 129 | if (debug) { 130 | console.log(policyName + ': Preview') 131 | } 132 | resolve({ status: STATUS_COMING }) 133 | return 134 | } 135 | 136 | // 非国际版 Disney+ 137 | reject('Not Available') 138 | return 139 | } 140 | 141 | if (statusCode === 200) { 142 | let match = body.match(/^Region: ([A-Za-z]{2})$/m) 143 | if (!match) { 144 | reject('Not Available') 145 | return 146 | } 147 | 148 | let region = match[1] 149 | resolve({ region, status: STATUS_AVAILABLE }) 150 | return 151 | } 152 | 153 | reject('Not Available') 154 | }, 155 | reason => { 156 | if (debug) { 157 | console.log(`${policyName} getHomePage Error: ${reason.error}`) 158 | } 159 | reject('Error') 160 | } 161 | ) 162 | }) 163 | } 164 | 165 | function testPublicGraphqlAPI(policyName, accessToken) { 166 | return new Promise((resolve, reject) => { 167 | let request = { 168 | url: 'https://disney.api.edge.bamgrid.com/v1/public/graphql', 169 | method: 'POST', 170 | headers: { 171 | 'Accept-Language': 'en', 172 | Authorization: accessToken, 173 | 'Content-Type': 'application/json', 174 | 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.71 Safari/537.36', 175 | }, 176 | opts: { 177 | redirection: false, 178 | policy: policyName, 179 | }, 180 | body: JSON.stringify({ 181 | query: 182 | 'query($preferredLanguages: [String!]!, $version: String) {globalization(version: $version) { uiLanguage(preferredLanguages: $preferredLanguages) }}', 183 | variables: { version: '1.5.0', preferredLanguages: ['en'] }, 184 | }), 185 | } 186 | 187 | $task.fetch(request).then( 188 | response => { 189 | let { statusCode } = response 190 | resolve(statusCode === 200) 191 | }, 192 | reason => { 193 | if (debug) { 194 | console.log(`${policyName} queryLanguage Error: ${reason.error}`) 195 | } 196 | reject('Error') 197 | } 198 | ) 199 | }) 200 | } 201 | 202 | function getLocationInfo(policyName) { 203 | return new Promise((resolve, reject) => { 204 | let request = { 205 | url: 'https://disney.api.edge.bamgrid.com/graph/v1/device/graphql', 206 | method: 'POST', 207 | headers: { 208 | 'Accept-Language': 'en', 209 | Authorization: 'ZGlzbmV5JmJyb3dzZXImMS4wLjA.Cu56AgSfBTDag5NiRA81oLHkDZfu5L3CKadnefEAY84', 210 | 'Content-Type': 'application/json', 211 | 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.71 Safari/537.36', 212 | }, 213 | opts: { 214 | redirection: false, 215 | policy: policyName, 216 | }, 217 | body: JSON.stringify({ 218 | query: 'mutation registerDevice($input: RegisterDeviceInput!) { registerDevice(registerDevice: $input) { grant { grantType assertion } } }', 219 | variables: { 220 | input: { 221 | applicationRuntime: 'chrome', 222 | attributes: { 223 | browserName: 'chrome', 224 | browserVersion: '94.0.4606', 225 | manufacturer: 'apple', 226 | model: null, 227 | operatingSystem: 'macintosh', 228 | operatingSystemVersion: '10.15.7', 229 | osDeviceIds: [], 230 | }, 231 | deviceFamily: 'browser', 232 | deviceLanguage: 'en', 233 | deviceProfile: 'macosx', 234 | }, 235 | }, 236 | }), 237 | } 238 | 239 | $task.fetch(request).then( 240 | response => { 241 | let { statusCode, body } = response 242 | if (statusCode !== 200) { 243 | if (debug) { 244 | console.log(`${policyName} getLocationInfo: ${body}`) 245 | } 246 | reject('Not Available') 247 | return 248 | } 249 | let { 250 | token: { accessToken }, 251 | session: { 252 | inSupportedLocation, 253 | location: { countryCode }, 254 | }, 255 | } = JSON.parse(body)?.extensions?.sdk 256 | resolve({ inSupportedLocation, countryCode, accessToken }) 257 | }, 258 | reason => { 259 | if (debug) { 260 | console.log(`${policyName} getLocationInfo Error: ${reason.error}`) 261 | } 262 | reject('Error') 263 | } 264 | ) 265 | }) 266 | } 267 | 268 | async function test(policyName) { 269 | console.log(`开始检测 ${policyName}`) 270 | let startTime = new Date().getTime() 271 | try { 272 | let { countryCode, inSupportedLocation, accessToken } = await Promise.race([getLocationInfo(policyName), timeout(t)]) 273 | if (debug) { 274 | console.log(`${policyName} getLocationInfo: countryCode=${countryCode}, inSupportedLocation=${inSupportedLocation}`) 275 | } 276 | 277 | // 支持 Disney+ 278 | if (inSupportedLocation === true || inSupportedLocation === 'true') { 279 | let support = await Promise.race([testPublicGraphqlAPI(policyName, accessToken), timeout(t)]) 280 | if (!support) { 281 | return { status: STATUS_NOT_AVAILABLE, policy: policyName, time: new Date().getTime() - startTime } 282 | } 283 | return { 284 | region: countryCode, 285 | status: STATUS_AVAILABLE, 286 | policy: policyName, 287 | time: new Date().getTime() - startTime, 288 | } 289 | } 290 | 291 | let { status } = await Promise.race([getHomePage(policyName), timeout(t)]) 292 | if (debug) { 293 | console.log(`${policyName} getHomePage: status=${status}`) 294 | } 295 | 296 | // 即将登陆 297 | if (status === STATUS_COMING) { 298 | return { region: countryCode, status: STATUS_COMING, policy: policyName, time: new Date().getTime() - startTime } 299 | } 300 | 301 | // 不支持 Disney+ 302 | return { status: STATUS_NOT_AVAILABLE, policy: policyName, time: new Date().getTime() - startTime } 303 | } catch (error) { 304 | if (debug) { 305 | console.log(`${policyName}: ${error}`) 306 | } 307 | 308 | // 不支持 Disney+ 309 | if (error === 'Not Available') { 310 | return { status: STATUS_NOT_AVAILABLE, policy: policyName, time: new Date().getTime() - startTime } 311 | } 312 | 313 | // 检测超时 314 | if (error === 'Timeout') { 315 | return { status: STATUS_TIMEOUT, policy: policyName, time: new Date().getTime() - startTime } 316 | } 317 | 318 | return { status: STATUS_ERROR, policy: policyName, time: new Date().getTime() - startTime } 319 | } 320 | } 321 | 322 | function timeout(delay = 5000) { 323 | return new Promise((resolve, reject) => { 324 | setTimeout(() => { 325 | reject('Timeout') 326 | }, delay) 327 | }) 328 | } 329 | 330 | function getCountryFlagEmoji(countryCode) { 331 | if (countryCode.toUpperCase() === 'TW') { 332 | countryCode = 'CN' 333 | } 334 | const codePoints = countryCode 335 | .toUpperCase() 336 | .split('') 337 | .map(char => 127397 + char.charCodeAt()) 338 | return String.fromCodePoint(...codePoints) 339 | } 340 | 341 | function sendMessage(message) { 342 | return new Promise((resolve, reject) => { 343 | $configuration.sendMessage(message).then( 344 | response => { 345 | if (response.error) { 346 | if (debug) { 347 | console.log(`${message?.action} error: ${response.error}`) 348 | } 349 | reject(response.error) 350 | return 351 | } 352 | 353 | resolve(response.ret) 354 | }, 355 | error => { 356 | // Normally will never happen. 357 | reject(error) 358 | } 359 | ) 360 | }) 361 | } 362 | 363 | function lookupChildrenNode(policies = {}, targetPolicyName) { 364 | let targetPolicy = policies[targetPolicyName] 365 | if (!isValidPolicy(targetPolicy)) { 366 | throw '策略组名未填写或填写有误,请在 BoxJS 中填写正确的策略组名称' 367 | } 368 | if (targetPolicy?.type !== 'static') { 369 | throw `${targetPolicyName} 不是 static 类型的策略组` 370 | } 371 | if (targetPolicy.candidates.length <= 0) { 372 | throw `${targetPolicyName} 策略组为空` 373 | } 374 | let candidates = new Set() 375 | 376 | let looked = new Set() 377 | let looking = [targetPolicyName] 378 | 379 | while (looking.length > 0) { 380 | let curPolicyGroupName = looking.shift() 381 | looked.add(curPolicyGroupName) 382 | for (const policy of policies[curPolicyGroupName].candidates) { 383 | // 排除 proxy 和 reject 两个特殊策略 384 | if (policy === 'proxy' || policy === 'reject') { 385 | continue 386 | } 387 | // 如果不是自定义策略,那么就应该是一个节点 388 | if (policies[policy] === undefined) { 389 | candidates.add(policy) 390 | continue 391 | } 392 | 393 | // 没有遍历过的策略,也不是即将遍历的策略,并且是 static 类型的策略 394 | if (!looked.has(policy) && !looking.includes(policy) && policies[policy]?.type === 'static') { 395 | looking.push(policy) 396 | } 397 | } 398 | } 399 | 400 | return [...candidates] 401 | } 402 | 403 | function lookupTargetPolicy(policies = {}) { 404 | let policyNames = Object.entries(policies) 405 | .filter(([key, val]) => key.search(/Disney\+|Disney Plus|迪士尼/gi) !== -1) 406 | .map(([key, val]) => key) 407 | if (policyNames.length === 1) { 408 | return policyNames[0] 409 | } else if (policyNames.length <= 0) { 410 | throw '没有找到 Disney+ 策略组,请在 BoxJS 中填写正确的策略组名称' 411 | } else { 412 | throw `找到多个 Disney+ 策略组,请在 BoxJS 中填写正确的策略组名称` 413 | } 414 | } 415 | 416 | function isValidPolicy(policy) { 417 | return policy !== undefined && policy?.type !== undefined && Array.isArray(policy?.candidates) 418 | } 419 | 420 | // prettier-ignore 421 | Array.prototype.remove=function(e){let t=this.indexOf(e);-1!==t&&this.splice(t,1)} 422 | 423 | // prettier-ignore 424 | Promise.map=function(t,e,{concurrency:u}){const i=new class{constructor(t){this.limit=t,this.count=0,this.queue=[]}enqueue(t){return new Promise((e,u)=>{this.queue.push({fn:t,resolve:e,reject:u})})}dequeue(){if(this.counti.build(()=>e(...t))))} 425 | 426 | // prettier-ignore 427 | const REGIONS={AF:{chinese:'阿富汗',english:'Afghanistan'},AL:{chinese:'阿尔巴尼亚',english:'Albania'},DZ:{chinese:'阿尔及利亚',english:'Algeria'},AO:{chinese:'安哥拉',english:'Angola'},AR:{chinese:'阿根廷',english:'Argentina'},AM:{chinese:'亚美尼亚',english:'Armenia'},AU:{chinese:'澳大利亚',english:'Australia'},AT:{chinese:'奥地利',english:'Austria'},AZ:{chinese:'阿塞拜疆',english:'Azerbaijan'},BH:{chinese:'巴林',english:'Bahrain'},BD:{chinese:'孟加拉国',english:'Bangladesh'},BY:{chinese:'白俄罗斯',english:'Belarus'},BE:{chinese:'比利时',english:'Belgium'},BZ:{chinese:'伯利兹',english:'Belize'},BJ:{chinese:'贝宁',english:'Benin'},BT:{chinese:'不丹',english:'Bhutan'},BO:{chinese:'玻利维亚',english:'Bolivia'},BA:{chinese:'波黑',english:'Bosnia and Herzegovina'},BW:{chinese:'博茨瓦纳',english:'Botswana'},BR:{chinese:'巴西',english:'Brazil'},VG:{chinese:'英属维京群岛',english:'British Virgin Islands'},BN:{chinese:'文莱',english:'Brunei'},BG:{chinese:'保加利亚',english:'Bulgaria'},BF:{chinese:'布基纳法索',english:'Burkina-faso'},BI:{chinese:'布隆迪',english:'Burundi'},KH:{chinese:'柬埔寨',english:'Cambodia'},CM:{chinese:'喀麦隆',english:'Cameroon'},CA:{chinese:'加拿大',english:'Canada'},CV:{chinese:'佛得角',english:'Cape Verde'},KY:{chinese:'开曼群岛',english:'Cayman Islands'},CF:{chinese:'中非',english:'Central African Republic'},TD:{chinese:'乍得',english:'Chad'},CL:{chinese:'智利',english:'Chile'},CN:{chinese:'中国',english:'China'},CO:{chinese:'哥伦比亚',english:'Colombia'},KM:{chinese:'科摩罗',english:'Comoros'},CG:{chinese:'刚果(布)',english:'Congo - Brazzaville'},CD:{chinese:'刚果(金)',english:'Congo - Kinshasa'},CR:{chinese:'哥斯达黎加',english:'Costa Rica'},HR:{chinese:'克罗地亚',english:'Croatia'},CY:{chinese:'塞浦路斯',english:'Cyprus'},CZ:{chinese:'捷克',english:'Czech Republic'},DK:{chinese:'丹麦',english:'Denmark'},DJ:{chinese:'吉布提',english:'Djibouti'},DO:{chinese:'多米尼加',english:'Dominican Republic'},EC:{chinese:'厄瓜多尔',english:'Ecuador'},EG:{chinese:'埃及',english:'Egypt'},SV:{chinese:'萨尔瓦多',english:'EI Salvador'},GQ:{chinese:'赤道几内亚',english:'Equatorial Guinea'},ER:{chinese:'厄立特里亚',english:'Eritrea'},EE:{chinese:'爱沙尼亚',english:'Estonia'},ET:{chinese:'埃塞俄比亚',english:'Ethiopia'},FJ:{chinese:'斐济',english:'Fiji'},FI:{chinese:'芬兰',english:'Finland'},FR:{chinese:'法国',english:'France'},GA:{chinese:'加蓬',english:'Gabon'},GM:{chinese:'冈比亚',english:'Gambia'},GE:{chinese:'格鲁吉亚',english:'Georgia'},DE:{chinese:'德国',english:'Germany'},GH:{chinese:'加纳',english:'Ghana'},GR:{chinese:'希腊',english:'Greece'},GL:{chinese:'格陵兰',english:'Greenland'},GT:{chinese:'危地马拉',english:'Guatemala'},GN:{chinese:'几内亚',english:'Guinea'},GY:{chinese:'圭亚那',english:'Guyana'},HT:{chinese:'海地',english:'Haiti'},HN:{chinese:'洪都拉斯',english:'Honduras'},HK:{chinese:'香港',english:'Hong Kong'},HU:{chinese:'匈牙利',english:'Hungary'},IS:{chinese:'冰岛',english:'Iceland'},IN:{chinese:'印度',english:'India'},ID:{chinese:'印度尼西亚',english:'Indonesia'},IR:{chinese:'伊朗',english:'Iran'},IQ:{chinese:'伊拉克',english:'Iraq'},IE:{chinese:'爱尔兰',english:'Ireland'},IM:{chinese:'马恩岛',english:'Isle of Man'},IL:{chinese:'以色列',english:'Israel'},IT:{chinese:'意大利',english:'Italy'},CI:{chinese:'科特迪瓦',english:'Ivory Coast'},JM:{chinese:'牙买加',english:'Jamaica'},JP:{chinese:'日本',english:'Japan'},JO:{chinese:'约旦',english:'Jordan'},KZ:{chinese:'哈萨克斯坦',english:'Kazakstan'},KE:{chinese:'肯尼亚',english:'Kenya'},KR:{chinese:'韩国',english:'Korea'},KW:{chinese:'科威特',english:'Kuwait'},KG:{chinese:'吉尔吉斯斯坦',english:'Kyrgyzstan'},LA:{chinese:'老挝',english:'Laos'},LV:{chinese:'拉脱维亚',english:'Latvia'},LB:{chinese:'黎巴嫩',english:'Lebanon'},LS:{chinese:'莱索托',english:'Lesotho'},LR:{chinese:'利比里亚',english:'Liberia'},LY:{chinese:'利比亚',english:'Libya'},LT:{chinese:'立陶宛',english:'Lithuania'},LU:{chinese:'卢森堡',english:'Luxembourg'},MO:{chinese:'澳门',english:'Macao'},MK:{chinese:'马其顿',english:'Macedonia'},MG:{chinese:'马达加斯加',english:'Madagascar'},MW:{chinese:'马拉维',english:'Malawi'},MY:{chinese:'马来西亚',english:'Malaysia'},MV:{chinese:'马尔代夫',english:'Maldives'},ML:{chinese:'马里',english:'Mali'},MT:{chinese:'马耳他',english:'Malta'},MR:{chinese:'毛利塔尼亚',english:'Mauritania'},MU:{chinese:'毛里求斯',english:'Mauritius'},MX:{chinese:'墨西哥',english:'Mexico'},MD:{chinese:'摩尔多瓦',english:'Moldova'},MC:{chinese:'摩纳哥',english:'Monaco'},MN:{chinese:'蒙古',english:'Mongolia'},ME:{chinese:'黑山',english:'Montenegro'},MA:{chinese:'摩洛哥',english:'Morocco'},MZ:{chinese:'莫桑比克',english:'Mozambique'},MM:{chinese:'缅甸',english:'Myanmar'},NA:{chinese:'纳米比亚',english:'Namibia'},NP:{chinese:'尼泊尔',english:'Nepal'},NL:{chinese:'荷兰',english:'Netherlands'},NZ:{chinese:'新西兰',english:'New Zealand'},NI:{chinese:'尼加拉瓜',english:'Nicaragua'},NE:{chinese:'尼日尔',english:'Niger'},NG:{chinese:'尼日利亚',english:'Nigeria'},KP:{chinese:'朝鲜',english:'North Korea'},NO:{chinese:'挪威',english:'Norway'},OM:{chinese:'阿曼',english:'Oman'},PK:{chinese:'巴基斯坦',english:'Pakistan'},PA:{chinese:'巴拿马',english:'Panama'},PY:{chinese:'巴拉圭',english:'Paraguay'},PE:{chinese:'秘鲁',english:'Peru'},PH:{chinese:'菲律宾',english:'Philippines'},PL:{chinese:'波兰',english:'Poland'},PT:{chinese:'葡萄牙',english:'Portugal'},PR:{chinese:'波多黎各',english:'Puerto Rico'},QA:{chinese:'卡塔尔',english:'Qatar'},RE:{chinese:'留尼旺',english:'Reunion'},RO:{chinese:'罗马尼亚',english:'Romania'},RU:{chinese:'俄罗斯',english:'Russia'},RW:{chinese:'卢旺达',english:'Rwanda'},SM:{chinese:'圣马力诺',english:'San Marino'},SA:{chinese:'沙特阿拉伯',english:'Saudi Arabia'},SN:{chinese:'塞内加尔',english:'Senegal'},RS:{chinese:'塞尔维亚',english:'Serbia'},SL:{chinese:'塞拉利昂',english:'Sierra Leone'},SG:{chinese:'新加坡',english:'Singapore'},SK:{chinese:'斯洛伐克',english:'Slovakia'},SI:{chinese:'斯洛文尼亚',english:'Slovenia'},SO:{chinese:'索马里',english:'Somalia'},ZA:{chinese:'南非',english:'South Africa'},ES:{chinese:'西班牙',english:'Spain'},LK:{chinese:'斯里兰卡',english:'Sri Lanka'},SD:{chinese:'苏丹',english:'Sudan'},SR:{chinese:'苏里南',english:'Suriname'},SZ:{chinese:'斯威士兰',english:'Swaziland'},SE:{chinese:'瑞典',english:'Sweden'},CH:{chinese:'瑞士',english:'Switzerland'},SY:{chinese:'叙利亚',english:'Syria'},TW:{chinese:'台湾',english:'Taiwan'},TJ:{chinese:'塔吉克斯坦',english:'Tajikstan'},TZ:{chinese:'坦桑尼亚',english:'Tanzania'},TH:{chinese:'泰国',english:'Thailand'},TG:{chinese:'多哥',english:'Togo'},TO:{chinese:'汤加',english:'Tonga'},TT:{chinese:'特立尼达和多巴哥',english:'Trinidad and Tobago'},TN:{chinese:'突尼斯',english:'Tunisia'},TR:{chinese:'土耳其',english:'Turkey'},TM:{chinese:'土库曼斯坦',english:'Turkmenistan'},VI:{chinese:'美属维尔京群岛',english:'U.S. Virgin Islands'},UG:{chinese:'乌干达',english:'Uganda'},UA:{chinese:'乌克兰',english:'Ukraine'},AE:{chinese:'阿联酋',english:'United Arab Emirates'},GB:{chinese:'英国',english:'United Kiongdom'},US:{chinese:'美国',english:'USA'},UY:{chinese:'乌拉圭',english:'Uruguay'},UZ:{chinese:'乌兹别克斯坦',english:'Uzbekistan'},VA:{chinese:'梵蒂冈',english:'Vatican City'},VE:{chinese:'委内瑞拉',english:'Venezuela'},VN:{chinese:'越南',english:'Vietnam'},YE:{chinese:'也门',english:'Yemen'},YU:{chinese:'南斯拉夫',english:'Yugoslavia'},ZR:{chinese:'扎伊尔',english:'Zaire'},ZM:{chinese:'赞比亚',english:'Zambia'},ZW:{chinese:'津巴布韦',english:'Zimbabwe'}} 428 | 429 | // prettier-ignore 430 | 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,i)=>{s.call(this,t,(t,s,r)=>{t?i(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("",`\ud83d\udd14${this.name}, \u5f00\u59cb!`)}isNode(){return"undefined"!=typeof module&&!!module.exports}isQuanX(){return"undefined"!=typeof $task}isSurge(){return"undefined"!=typeof $httpClient&&"undefined"==typeof $loon}isLoon(){return"undefined"!=typeof $loon}isShadowrocket(){return"undefined"!=typeof $rocket}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 i=this.getdata(t);if(i)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,i)=>e(i))})}runScript(t,e){return new Promise(s=>{let i=this.getdata("@chavy_boxjs_userCfgs.httpapi");i=i?i.replace(/\n/g,"").trim():i;let r=this.getdata("@chavy_boxjs_userCfgs.httpapi_timeout");r=r?1*r:20,r=e&&e.timeout?e.timeout:r;const[o,h]=i.split("@"),n={url:`http://${h}/v1/scripting/evaluate`,body:{script_text:t,mock_type:"cron",timeout:r},headers:{"X-Key":o,Accept:"*/*"}};this.post(n,(t,e,i)=>s(i))}).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),i=!s&&this.fs.existsSync(e);if(!s&&!i)return{};{const i=s?t:e;try{return JSON.parse(this.fs.readFileSync(i))}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),i=!s&&this.fs.existsSync(e),r=JSON.stringify(this.data);s?this.fs.writeFileSync(t,r):i?this.fs.writeFileSync(e,r):this.fs.writeFileSync(t,r)}}lodash_get(t,e,s){const i=e.replace(/\[(\d+)\]/g,".$1").split(".");let r=t;for(const t of i)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,i)=>Object(t[s])===t[s]?t[s]:t[s]=Math.abs(e[i+1])>>0==+e[i+1]?[]:{},t)[e[e.length-1]]=s,t)}getdata(t){let e=this.getval(t);if(/^@/.test(t)){const[,s,i]=/^@(.*?)\.(.*?)$/.exec(t),r=s?this.getval(s):"";if(r)try{const t=JSON.parse(r);e=t?this.lodash_get(t,i,""):e}catch(t){e=""}}return e}setdata(t,e){let s=!1;if(/^@/.test(e)){const[,i,r]=/^@(.*?)\.(.*?)$/.exec(e),o=this.getval(i),h=i?"null"===o?null:o||"{}":"{}";try{const e=JSON.parse(h);this.lodash_set(e,r,t),s=this.setval(JSON.stringify(e),i)}catch(e){const o={};this.lodash_set(o,r,t),s=this.setval(JSON.stringify(o),i)}}else s=this.setval(t,e);return s}getval(t){return this.isSurge()||this.isLoon()?$persistentStore.read(t):this.isQuanX()?$prefs.valueForKey(t):this.isNode()?(this.data=this.loaddata(),this.data[t]):this.data&&this.data[t]||null}setval(t,e){return this.isSurge()||this.isLoon()?$persistentStore.write(t,e):this.isQuanX()?$prefs.setValueForKey(t,e):this.isNode()?(this.data=this.loaddata(),this.data[e]=t,this.writedata(),!0):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=(()=>{})){if(t.headers&&(delete t.headers["Content-Type"],delete t.headers["Content-Length"]),this.isSurge()||this.isLoon())this.isSurge()&&this.isNeedRewrite&&(t.headers=t.headers||{},Object.assign(t.headers,{"X-Surge-Skip-Scripting":!1})),$httpClient.get(t,(t,s,i)=>{!t&&s&&(s.body=i,s.statusCode=s.status),e(t,s,i)});else if(this.isQuanX())this.isNeedRewrite&&(t.opts=t.opts||{},Object.assign(t.opts,{hints:!1})),$task.fetch(t).then(t=>{const{statusCode:s,statusCode:i,headers:r,body:o}=t;e(null,{status:s,statusCode:i,headers:r,body:o},o)},t=>e(t));else if(this.isNode()){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:i,statusCode:r,headers:o,rawBody:h}=t;e(null,{status:i,statusCode:r,headers:o,rawBody:h},s.decode(h,this.encoding))},t=>{const{message:i,response:r}=t;e(i,r,r&&s.decode(r.rawBody,this.encoding))})}}post(t,e=(()=>{})){const s=t.method?t.method.toLocaleLowerCase():"post";if(t.body&&t.headers&&!t.headers["Content-Type"]&&(t.headers["Content-Type"]="application/x-www-form-urlencoded"),t.headers&&delete t.headers["Content-Length"],this.isSurge()||this.isLoon())this.isSurge()&&this.isNeedRewrite&&(t.headers=t.headers||{},Object.assign(t.headers,{"X-Surge-Skip-Scripting":!1})),$httpClient[s](t,(t,s,i)=>{!t&&s&&(s.body=i,s.statusCode=s.status),e(t,s,i)});else if(this.isQuanX())t.method=s,this.isNeedRewrite&&(t.opts=t.opts||{},Object.assign(t.opts,{hints:!1})),$task.fetch(t).then(t=>{const{statusCode:s,statusCode:i,headers:r,body:o}=t;e(null,{status:s,statusCode:i,headers:r,body:o},o)},t=>e(t));else if(this.isNode()){let i=require("iconv-lite");this.initGotEnv(t);const{url:r,...o}=t;this.got[s](r,o).then(t=>{const{statusCode:s,statusCode:r,headers:o,rawBody:h}=t;e(null,{status:s,statusCode:r,headers:o,rawBody:h},i.decode(h,this.encoding))},t=>{const{message:s,response:r}=t;e(s,r,r&&i.decode(r.rawBody,this.encoding))})}}time(t,e=null){const s=e?new Date(e):new Date;let i={"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 i)new RegExp("("+e+")").test(t)&&(t=t.replace(RegExp.$1,1==RegExp.$1.length?i[e]:("00"+i[e]).substr((""+i[e]).length)));return t}msg(e=t,s="",i="",r){const o=t=>{if(!t)return t;if("string"==typeof t)return this.isLoon()?t:this.isQuanX()?{"open-url":t}:this.isSurge()?{url:t}:void 0;if("object"==typeof t){if(this.isLoon()){let e=t.openUrl||t.url||t["open-url"],s=t.mediaUrl||t["media-url"];return{openUrl:e,mediaUrl:s}}if(this.isQuanX()){let e=t["open-url"]||t.url||t.openUrl,s=t["media-url"]||t.mediaUrl;return{"open-url":e,"media-url":s}}if(this.isSurge()){let e=t.url||t.openUrl||t["open-url"];return{url:e}}}};if(this.isMute||(this.isSurge()||this.isLoon()?$notification.post(e,s,i,o(r)):this.isQuanX()&&$notify(e,s,i,o(r))),!this.isMuteLog){let t=["","==============\ud83d\udce3\u7cfb\u7edf\u901a\u77e5\ud83d\udce3=============="];t.push(e),s&&t.push(s),i&&t.push(i),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){const s=!this.isSurge()&&!this.isQuanX()&&!this.isLoon();s?this.log("",`\u2757\ufe0f${this.name}, \u9519\u8bef!`,t.stack):this.log("",`\u2757\ufe0f${this.name}, \u9519\u8bef!`,t)}wait(t){return new Promise(e=>setTimeout(e,t))}done(t={}){const e=(new Date).getTime(),s=(e-this.startTime)/1e3;this.log("",`\ud83d\udd14${this.name}, \u7ed3\u675f! \ud83d\udd5b ${s} \u79d2`),this.log(),(this.isSurge()||this.isQuanX()||this.isLoon())&&$done(t)}}(t,e)} 431 | -------------------------------------------------------------------------------- /QuantumultX/DisneyPlusPolicySwitcher/disney_switcher.js: -------------------------------------------------------------------------------- 1 | const STATUS_COMING = 2 // 即将登陆 2 | const STATUS_AVAILABLE = 1 // 支持解锁 3 | const STATUS_NOT_AVAILABLE = 0 // 不支持解锁 4 | const STATUS_TIMEOUT = -1 // 检测超时 5 | const STATUS_ERROR = -2 // 检测异常 6 | 7 | const $ = new Env('Disney+ 策略切换') 8 | let disneyPolicyName = $.getval('Helge_0x00.Disney_Policy') || 'Disney+' 9 | let debug = $.getval('Helge_0x00.Disney_Debug') === 'true' 10 | let recheck = $.getval('Helge_0x00.Disney_Recheck') === 'true' 11 | let t = parseInt($.getval('Helge_0x00.Disney_Timeout')) || 8000 12 | let sortByTime = $.getval('Helge_0x00.Disney_Sort_By_Time') === 'true' 13 | let concurrency = parseInt($.getval('Helge_0x00.Disney_Concurrency')) || 10 14 | 15 | ;(async () => { 16 | if (!$.isQuanX()) { 17 | throw '该脚本仅支持在 Quantumult X 中运行' 18 | } 19 | let policies = await sendMessage({ action: 'get_customized_policy' }) 20 | if (!isValidPolicy(policies[disneyPolicyName])) { 21 | disneyPolicyName = lookupTargetPolicy(policies) 22 | console.log(`更新策略组名称 ➟ ${disneyPolicyName}`) 23 | $.setval(disneyPolicyName, 'Helge_0x00.Disney_Policy') 24 | } 25 | 26 | let curPolicyPath = await getSelectedPolicy(disneyPolicyName) 27 | let selected = curPolicyPath[1] 28 | let actualNode = curPolicyPath[curPolicyPath.length - 1] 29 | if (debug) { 30 | console.log(`当前选择的策略:${curPolicyPath.join(' ➤ ')}`) 31 | } 32 | 33 | let { region, status } = await test(actualNode) 34 | if (status === STATUS_AVAILABLE) { 35 | let flag = getCountryFlagEmoji(region) ?? '' 36 | let regionName = REGIONS?.[region.toUpperCase()]?.chinese ?? '' 37 | $.msg($.name, `${actualNode}`, `当前节点支持 Disney+ ➟ ${flag} ${regionName}`) 38 | return 39 | } 40 | 41 | let cachePolicies = [] 42 | try { 43 | cachePolicies = JSON.parse($.getval('Helge_0x00.Disney_Available_Policies') ?? '[]') 44 | } catch (error) { 45 | console.log('getCachePolicies error: ' + error) 46 | cachePolicies = [] 47 | } 48 | 49 | let paths = lookupPath(policies, disneyPolicyName) 50 | let nodes = new Set(paths.map(path => path[path.length - 1]).filter(item => !['proxy', 'direct', 'reject'].includes(item))) 51 | 52 | // 检测一遍缓存的可用节点是否还在当前策略中 53 | cachePolicies = cachePolicies.filter(item => nodes.has(item.policy) && item.policy !== selected) 54 | 55 | // 切换前重新检测是否可用 56 | if (recheck) { 57 | cachePolicies = await testPolicies(cachePolicies.map(item => item.policy)) 58 | if (sortByTime) { 59 | cachePolicies = cachePolicies.sort((m, n) => m.time - n.time) 60 | } 61 | } 62 | 63 | $.setval(JSON.stringify(cachePolicies), 'Helge_0x00.Disney_Available_Policies') 64 | if (cachePolicies.length <= 0) { 65 | throw '没有可用策略,请先运行 「Disney+ 解锁检测」脚本' 66 | } 67 | 68 | let { policy: newPolicy, region: newRegion } = cachePolicies[0] 69 | // 找到切换路径,并按照路径长度排序,取路径长度最短的 70 | let switchPath = paths.filter(path => path[path.length - 1] === newPolicy).sort((m, n) => m.length - n.length)[0] 71 | let switchDict = {} 72 | for (let i = 0; i < switchPath.length - 1; i++) { 73 | switchDict[switchPath[i]] = switchPath[i + 1] 74 | } 75 | 76 | await setPolicyState(switchDict) 77 | let flag = getCountryFlagEmoji(newRegion) ?? '' 78 | let regionName = REGIONS?.[newRegion.toUpperCase()]?.chinese ?? '' 79 | console.log(`\n切换策略:${curPolicyPath.join(' ➤ ')} ➟ ${switchPath.join(' ➤ ')}`) 80 | $.msg($.name, `${curPolicyPath[curPolicyPath.length - 1]} ➟ ${switchPath[switchPath.length - 1]}`, `该节点支持 Disney+ ➟ ${flag} ${regionName}`) 81 | })() 82 | .catch(error => { 83 | console.log(error) 84 | if (typeof error === 'string') { 85 | $.msg($.name, '', `${error} ⚠️`) 86 | } 87 | }) 88 | .finally(() => { 89 | $.done() 90 | }) 91 | 92 | async function getSelectedPolicy(policyName) { 93 | let message = { 94 | action: 'get_policy_state', 95 | content: policyName, 96 | } 97 | 98 | let ret = await sendMessage(message) 99 | return ret?.[policyName] 100 | } 101 | 102 | async function setPolicyState(policyDict) { 103 | let message = { 104 | action: 'set_policy_state', 105 | content: policyDict, 106 | } 107 | try { 108 | await sendMessage(message) 109 | } catch (e) { 110 | if (debug) { 111 | console.log(`策略切换失败:${e}`) 112 | } 113 | throw '策略切换失败,请重试' 114 | } 115 | } 116 | 117 | function getHomePage(policyName) { 118 | return new Promise((resolve, reject) => { 119 | let request = { 120 | url: 'https://www.disneyplus.com/', 121 | opts: { 122 | redirection: false, 123 | policy: policyName, 124 | }, 125 | headers: { 126 | 'Accept-Language': 'en', 127 | 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.71 Safari/537.36', 128 | }, 129 | } 130 | $task.fetch(request).then( 131 | response => { 132 | let { 133 | statusCode, 134 | headers: { Location: location }, 135 | body, 136 | } = response 137 | 138 | if (statusCode === 302) { 139 | if (debug) { 140 | console.log(`${policyName} getHomePage, 302, Location: ${location}`) 141 | } 142 | // 不可用 143 | if (location.indexOf('Sorry, Disney+ is not available in your region.') !== -1) { 144 | if (debug) { 145 | console.log(policyName + ': Not Available') 146 | } 147 | reject('Not Available') 148 | return 149 | } 150 | 151 | // 即将登陆 152 | if (location.indexOf('preview') !== -1) { 153 | if (debug) { 154 | console.log(policyName + ': Preview') 155 | } 156 | resolve({ status: STATUS_COMING }) 157 | return 158 | } 159 | 160 | // 非国际版 Disney+ 161 | reject('Not Available') 162 | return 163 | } 164 | 165 | if (statusCode === 200) { 166 | let match = body.match(/^Region: ([A-Za-z]{2})$/m) 167 | if (!match) { 168 | reject('Not Available') 169 | return 170 | } 171 | 172 | let region = match[1] 173 | resolve({ region, status: STATUS_AVAILABLE }) 174 | return 175 | } 176 | 177 | reject('Not Available') 178 | }, 179 | reason => { 180 | if (debug) { 181 | console.log(`${policyName} getHomePage Error: ${reason.error}`) 182 | } 183 | reject('Error') 184 | } 185 | ) 186 | }) 187 | } 188 | 189 | function testPublicGraphqlAPI(policyName, accessToken) { 190 | return new Promise((resolve, reject) => { 191 | let request = { 192 | url: 'https://disney.api.edge.bamgrid.com/v1/public/graphql', 193 | method: 'POST', 194 | headers: { 195 | 'Accept-Language': 'en', 196 | Authorization: accessToken, 197 | 'Content-Type': 'application/json', 198 | 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.71 Safari/537.36', 199 | }, 200 | opts: { 201 | redirection: false, 202 | policy: policyName, 203 | }, 204 | body: JSON.stringify({ 205 | query: 206 | 'query($preferredLanguages: [String!]!, $version: String) {globalization(version: $version) { uiLanguage(preferredLanguages: $preferredLanguages) }}', 207 | variables: { version: '1.5.0', preferredLanguages: ['en'] }, 208 | }), 209 | } 210 | 211 | $task.fetch(request).then( 212 | response => { 213 | let { statusCode } = response 214 | resolve(statusCode === 200) 215 | }, 216 | reason => { 217 | if (debug) { 218 | console.log(`${policyName} queryLanguage Error: ${reason.error}`) 219 | } 220 | reject('Error') 221 | } 222 | ) 223 | }) 224 | } 225 | 226 | function getLocationInfo(policyName) { 227 | return new Promise((resolve, reject) => { 228 | let request = { 229 | url: 'https://disney.api.edge.bamgrid.com/graph/v1/device/graphql', 230 | method: 'POST', 231 | headers: { 232 | 'Accept-Language': 'en', 233 | Authorization: 'ZGlzbmV5JmJyb3dzZXImMS4wLjA.Cu56AgSfBTDag5NiRA81oLHkDZfu5L3CKadnefEAY84', 234 | 'Content-Type': 'application/json', 235 | 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.71 Safari/537.36', 236 | }, 237 | opts: { 238 | redirection: false, 239 | policy: policyName, 240 | }, 241 | body: JSON.stringify({ 242 | query: 'mutation registerDevice($input: RegisterDeviceInput!) { registerDevice(registerDevice: $input) { grant { grantType assertion } } }', 243 | variables: { 244 | input: { 245 | applicationRuntime: 'chrome', 246 | attributes: { 247 | browserName: 'chrome', 248 | browserVersion: '94.0.4606', 249 | manufacturer: 'apple', 250 | model: null, 251 | operatingSystem: 'macintosh', 252 | operatingSystemVersion: '10.15.7', 253 | osDeviceIds: [], 254 | }, 255 | deviceFamily: 'browser', 256 | deviceLanguage: 'en', 257 | deviceProfile: 'macosx', 258 | }, 259 | }, 260 | }), 261 | } 262 | 263 | $task.fetch(request).then( 264 | response => { 265 | let { statusCode, body } = response 266 | if (statusCode !== 200) { 267 | if (debug) { 268 | console.log(`${policyName} getLocationInfo: ${body}`) 269 | } 270 | reject('Not Available') 271 | return 272 | } 273 | 274 | let { 275 | token: { accessToken }, 276 | session: { 277 | inSupportedLocation, 278 | location: { countryCode }, 279 | }, 280 | } = JSON.parse(body)?.extensions?.sdk 281 | resolve({ inSupportedLocation, countryCode, accessToken }) 282 | }, 283 | reason => { 284 | if (debug) { 285 | console.log(`${policyName} getLocationInfo Error: ${reason.error}`) 286 | } 287 | reject('Error') 288 | } 289 | ) 290 | }) 291 | } 292 | 293 | async function test(policyName) { 294 | console.log(`开始检测 ${policyName}`) 295 | let startTime = new Date().getTime() 296 | try { 297 | let { countryCode, inSupportedLocation, accessToken } = await Promise.race([getLocationInfo(policyName), timeout(t)]) 298 | if (debug) { 299 | console.log(`${policyName} getLocationInfo: countryCode=${countryCode}, inSupportedLocation=${inSupportedLocation}`) 300 | } 301 | 302 | // 支持 Disney+ 303 | if (inSupportedLocation === true || inSupportedLocation === 'true') { 304 | let support = await Promise.race([testPublicGraphqlAPI(policyName, accessToken), timeout(t)]) 305 | if (!support) { 306 | return { status: STATUS_NOT_AVAILABLE, policy: policyName, time: new Date().getTime() - startTime } 307 | } 308 | return { 309 | region: countryCode, 310 | status: STATUS_AVAILABLE, 311 | policy: policyName, 312 | time: new Date().getTime() - startTime, 313 | } 314 | } 315 | 316 | let { status } = await Promise.race([getHomePage(policyName), timeout(t)]) 317 | if (debug) { 318 | console.log(`${policyName} getHomePage: status=${status}`) 319 | } 320 | 321 | // 即将登陆 322 | if (status === STATUS_COMING) { 323 | return { region: countryCode, status: STATUS_COMING, policy: policyName, time: new Date().getTime() - startTime } 324 | } 325 | 326 | // 不支持 Disney+ 327 | return { status: STATUS_NOT_AVAILABLE, policy: policyName, time: new Date().getTime() - startTime } 328 | } catch (error) { 329 | if (debug) { 330 | console.log(`${policyName}: ${error}`) 331 | } 332 | 333 | // 不支持 Disney+ 334 | if (error === 'Not Available') { 335 | return { status: STATUS_NOT_AVAILABLE, policy: policyName, time: new Date().getTime() - startTime } 336 | } 337 | 338 | // 检测超时 339 | if (error === 'Timeout') { 340 | return { status: STATUS_TIMEOUT, policy: policyName, time: new Date().getTime() - startTime } 341 | } 342 | 343 | return { status: STATUS_ERROR, policy: policyName, time: new Date().getTime() - startTime } 344 | } 345 | } 346 | 347 | async function testPolicies(policies = []) { 348 | let availablePolicies = [] 349 | let echo = results => { 350 | console.log(`\n策略组检测结果:`) 351 | for (let { policy, status, region, time } of results) { 352 | switch (status) { 353 | case STATUS_COMING: { 354 | let flag = getCountryFlagEmoji(region) ?? '' 355 | let regionName = REGIONS?.[region.toUpperCase()]?.chinese ?? '' 356 | console.log(`${policy}: Disney+ 即将登陆 ➟ ${flag}${regionName}`) 357 | break 358 | } 359 | case STATUS_AVAILABLE: { 360 | let flag = getCountryFlagEmoji(region) ?? '' 361 | let regionName = REGIONS?.[region.toUpperCase()]?.chinese ?? '' 362 | console.log(`${policy}: 支持 Disney+ ➟ ${flag}${regionName}`) 363 | availablePolicies.push({ policy, region, time }) 364 | break 365 | } 366 | case STATUS_NOT_AVAILABLE: 367 | console.log(`${policy}: 不支持 Disney+`) 368 | break 369 | case STATUS_TIMEOUT: 370 | console.log(`${policy}: 检测超时`) 371 | break 372 | default: 373 | console.log(`${policy}: 检测异常`) 374 | } 375 | } 376 | } 377 | 378 | await Promise.map(policies, subPolicy => test(subPolicy), { concurrency }) 379 | .then(echo) 380 | .catch(error => console.log(error)) 381 | 382 | return availablePolicies 383 | } 384 | 385 | function timeout(delay = 5000) { 386 | return new Promise((resolve, reject) => { 387 | setTimeout(() => { 388 | reject('Timeout') 389 | }, delay) 390 | }) 391 | } 392 | 393 | function getCountryFlagEmoji(countryCode) { 394 | if (countryCode.toUpperCase() === 'TW') { 395 | countryCode = 'CN' 396 | } 397 | const codePoints = countryCode 398 | .toUpperCase() 399 | .split('') 400 | .map(char => 127397 + char.charCodeAt()) 401 | return String.fromCodePoint(...codePoints) 402 | } 403 | 404 | function sendMessage(message) { 405 | return new Promise((resolve, reject) => { 406 | $configuration.sendMessage(message).then( 407 | response => { 408 | if (response.error) { 409 | if (debug) { 410 | console.log(`${message?.action} error: ${response.error}`) 411 | } 412 | reject(response.error) 413 | return 414 | } 415 | 416 | resolve(response.ret) 417 | }, 418 | error => { 419 | // Normally will never happen. 420 | reject(error) 421 | } 422 | ) 423 | }) 424 | } 425 | 426 | function lookupPath(policies = {}, policyGroupName = '', curPath = [], paths = []) { 427 | let targetPolicy = policies[policyGroupName] 428 | 429 | if (targetPolicy === undefined || targetPolicy?.type === undefined || !Array.isArray(targetPolicy?.candidates)) { 430 | return paths 431 | } 432 | 433 | curPath.push(policyGroupName) 434 | 435 | if (targetPolicy?.type !== 'static') { 436 | paths.push([...curPath]) 437 | return paths 438 | } 439 | 440 | for (const policy of targetPolicy?.candidates) { 441 | if (policies[policy] === undefined) { 442 | paths.push([...curPath, policy]) 443 | continue 444 | } 445 | 446 | // 成环了 447 | if (curPath.includes(policy)) { 448 | paths.push([...curPath, policy, '⚠️', 'direct']) 449 | continue 450 | } 451 | 452 | lookupPath(policies, policy, [...curPath], paths) 453 | } 454 | return paths 455 | } 456 | 457 | function lookupTargetPolicy(policies = {}) { 458 | let policyNames = Object.entries(policies) 459 | .filter(([key, val]) => key.search(/Disney\+|Disney Plus|迪士尼/gi) !== -1) 460 | .map(([key, val]) => key) 461 | if (policyNames.length === 1) { 462 | return policyNames[0] 463 | } else if (policyNames.length <= 0) { 464 | throw '没有找到 Disney+ 策略组,请在 BoxJS 中填写正确的策略组名称' 465 | } else { 466 | throw `找到多个 Disney+ 策略组,请在 BoxJS 中填写正确的策略组名称` 467 | } 468 | } 469 | 470 | function isValidPolicy(policy) { 471 | return policy !== undefined && policy?.type !== undefined && Array.isArray(policy?.candidates) 472 | } 473 | 474 | // prettier-ignore 475 | Array.prototype.remove=function(e){let t=this.indexOf(e);-1!==t&&this.splice(t,1)} 476 | 477 | // prettier-ignore 478 | Promise.map=function(t,e,{concurrency:u}){const i=new class{constructor(t){this.limit=t,this.count=0,this.queue=[]}enqueue(t){return new Promise((e,u)=>{this.queue.push({fn:t,resolve:e,reject:u})})}dequeue(){if(this.counti.build(()=>e(...t))))} 479 | 480 | // prettier-ignore 481 | const REGIONS={AF:{chinese:'阿富汗',english:'Afghanistan'},AL:{chinese:'阿尔巴尼亚',english:'Albania'},DZ:{chinese:'阿尔及利亚',english:'Algeria'},AO:{chinese:'安哥拉',english:'Angola'},AR:{chinese:'阿根廷',english:'Argentina'},AM:{chinese:'亚美尼亚',english:'Armenia'},AU:{chinese:'澳大利亚',english:'Australia'},AT:{chinese:'奥地利',english:'Austria'},AZ:{chinese:'阿塞拜疆',english:'Azerbaijan'},BH:{chinese:'巴林',english:'Bahrain'},BD:{chinese:'孟加拉国',english:'Bangladesh'},BY:{chinese:'白俄罗斯',english:'Belarus'},BE:{chinese:'比利时',english:'Belgium'},BZ:{chinese:'伯利兹',english:'Belize'},BJ:{chinese:'贝宁',english:'Benin'},BT:{chinese:'不丹',english:'Bhutan'},BO:{chinese:'玻利维亚',english:'Bolivia'},BA:{chinese:'波黑',english:'Bosnia and Herzegovina'},BW:{chinese:'博茨瓦纳',english:'Botswana'},BR:{chinese:'巴西',english:'Brazil'},VG:{chinese:'英属维京群岛',english:'British Virgin Islands'},BN:{chinese:'文莱',english:'Brunei'},BG:{chinese:'保加利亚',english:'Bulgaria'},BF:{chinese:'布基纳法索',english:'Burkina-faso'},BI:{chinese:'布隆迪',english:'Burundi'},KH:{chinese:'柬埔寨',english:'Cambodia'},CM:{chinese:'喀麦隆',english:'Cameroon'},CA:{chinese:'加拿大',english:'Canada'},CV:{chinese:'佛得角',english:'Cape Verde'},KY:{chinese:'开曼群岛',english:'Cayman Islands'},CF:{chinese:'中非',english:'Central African Republic'},TD:{chinese:'乍得',english:'Chad'},CL:{chinese:'智利',english:'Chile'},CN:{chinese:'中国',english:'China'},CO:{chinese:'哥伦比亚',english:'Colombia'},KM:{chinese:'科摩罗',english:'Comoros'},CG:{chinese:'刚果(布)',english:'Congo - Brazzaville'},CD:{chinese:'刚果(金)',english:'Congo - Kinshasa'},CR:{chinese:'哥斯达黎加',english:'Costa Rica'},HR:{chinese:'克罗地亚',english:'Croatia'},CY:{chinese:'塞浦路斯',english:'Cyprus'},CZ:{chinese:'捷克',english:'Czech Republic'},DK:{chinese:'丹麦',english:'Denmark'},DJ:{chinese:'吉布提',english:'Djibouti'},DO:{chinese:'多米尼加',english:'Dominican Republic'},EC:{chinese:'厄瓜多尔',english:'Ecuador'},EG:{chinese:'埃及',english:'Egypt'},SV:{chinese:'萨尔瓦多',english:'EI Salvador'},GQ:{chinese:'赤道几内亚',english:'Equatorial Guinea'},ER:{chinese:'厄立特里亚',english:'Eritrea'},EE:{chinese:'爱沙尼亚',english:'Estonia'},ET:{chinese:'埃塞俄比亚',english:'Ethiopia'},FJ:{chinese:'斐济',english:'Fiji'},FI:{chinese:'芬兰',english:'Finland'},FR:{chinese:'法国',english:'France'},GA:{chinese:'加蓬',english:'Gabon'},GM:{chinese:'冈比亚',english:'Gambia'},GE:{chinese:'格鲁吉亚',english:'Georgia'},DE:{chinese:'德国',english:'Germany'},GH:{chinese:'加纳',english:'Ghana'},GR:{chinese:'希腊',english:'Greece'},GL:{chinese:'格陵兰',english:'Greenland'},GT:{chinese:'危地马拉',english:'Guatemala'},GN:{chinese:'几内亚',english:'Guinea'},GY:{chinese:'圭亚那',english:'Guyana'},HT:{chinese:'海地',english:'Haiti'},HN:{chinese:'洪都拉斯',english:'Honduras'},HK:{chinese:'香港',english:'Hong Kong'},HU:{chinese:'匈牙利',english:'Hungary'},IS:{chinese:'冰岛',english:'Iceland'},IN:{chinese:'印度',english:'India'},ID:{chinese:'印度尼西亚',english:'Indonesia'},IR:{chinese:'伊朗',english:'Iran'},IQ:{chinese:'伊拉克',english:'Iraq'},IE:{chinese:'爱尔兰',english:'Ireland'},IM:{chinese:'马恩岛',english:'Isle of Man'},IL:{chinese:'以色列',english:'Israel'},IT:{chinese:'意大利',english:'Italy'},CI:{chinese:'科特迪瓦',english:'Ivory Coast'},JM:{chinese:'牙买加',english:'Jamaica'},JP:{chinese:'日本',english:'Japan'},JO:{chinese:'约旦',english:'Jordan'},KZ:{chinese:'哈萨克斯坦',english:'Kazakstan'},KE:{chinese:'肯尼亚',english:'Kenya'},KR:{chinese:'韩国',english:'Korea'},KW:{chinese:'科威特',english:'Kuwait'},KG:{chinese:'吉尔吉斯斯坦',english:'Kyrgyzstan'},LA:{chinese:'老挝',english:'Laos'},LV:{chinese:'拉脱维亚',english:'Latvia'},LB:{chinese:'黎巴嫩',english:'Lebanon'},LS:{chinese:'莱索托',english:'Lesotho'},LR:{chinese:'利比里亚',english:'Liberia'},LY:{chinese:'利比亚',english:'Libya'},LT:{chinese:'立陶宛',english:'Lithuania'},LU:{chinese:'卢森堡',english:'Luxembourg'},MO:{chinese:'澳门',english:'Macao'},MK:{chinese:'马其顿',english:'Macedonia'},MG:{chinese:'马达加斯加',english:'Madagascar'},MW:{chinese:'马拉维',english:'Malawi'},MY:{chinese:'马来西亚',english:'Malaysia'},MV:{chinese:'马尔代夫',english:'Maldives'},ML:{chinese:'马里',english:'Mali'},MT:{chinese:'马耳他',english:'Malta'},MR:{chinese:'毛利塔尼亚',english:'Mauritania'},MU:{chinese:'毛里求斯',english:'Mauritius'},MX:{chinese:'墨西哥',english:'Mexico'},MD:{chinese:'摩尔多瓦',english:'Moldova'},MC:{chinese:'摩纳哥',english:'Monaco'},MN:{chinese:'蒙古',english:'Mongolia'},ME:{chinese:'黑山',english:'Montenegro'},MA:{chinese:'摩洛哥',english:'Morocco'},MZ:{chinese:'莫桑比克',english:'Mozambique'},MM:{chinese:'缅甸',english:'Myanmar'},NA:{chinese:'纳米比亚',english:'Namibia'},NP:{chinese:'尼泊尔',english:'Nepal'},NL:{chinese:'荷兰',english:'Netherlands'},NZ:{chinese:'新西兰',english:'New Zealand'},NI:{chinese:'尼加拉瓜',english:'Nicaragua'},NE:{chinese:'尼日尔',english:'Niger'},NG:{chinese:'尼日利亚',english:'Nigeria'},KP:{chinese:'朝鲜',english:'North Korea'},NO:{chinese:'挪威',english:'Norway'},OM:{chinese:'阿曼',english:'Oman'},PK:{chinese:'巴基斯坦',english:'Pakistan'},PA:{chinese:'巴拿马',english:'Panama'},PY:{chinese:'巴拉圭',english:'Paraguay'},PE:{chinese:'秘鲁',english:'Peru'},PH:{chinese:'菲律宾',english:'Philippines'},PL:{chinese:'波兰',english:'Poland'},PT:{chinese:'葡萄牙',english:'Portugal'},PR:{chinese:'波多黎各',english:'Puerto Rico'},QA:{chinese:'卡塔尔',english:'Qatar'},RE:{chinese:'留尼旺',english:'Reunion'},RO:{chinese:'罗马尼亚',english:'Romania'},RU:{chinese:'俄罗斯',english:'Russia'},RW:{chinese:'卢旺达',english:'Rwanda'},SM:{chinese:'圣马力诺',english:'San Marino'},SA:{chinese:'沙特阿拉伯',english:'Saudi Arabia'},SN:{chinese:'塞内加尔',english:'Senegal'},RS:{chinese:'塞尔维亚',english:'Serbia'},SL:{chinese:'塞拉利昂',english:'Sierra Leone'},SG:{chinese:'新加坡',english:'Singapore'},SK:{chinese:'斯洛伐克',english:'Slovakia'},SI:{chinese:'斯洛文尼亚',english:'Slovenia'},SO:{chinese:'索马里',english:'Somalia'},ZA:{chinese:'南非',english:'South Africa'},ES:{chinese:'西班牙',english:'Spain'},LK:{chinese:'斯里兰卡',english:'Sri Lanka'},SD:{chinese:'苏丹',english:'Sudan'},SR:{chinese:'苏里南',english:'Suriname'},SZ:{chinese:'斯威士兰',english:'Swaziland'},SE:{chinese:'瑞典',english:'Sweden'},CH:{chinese:'瑞士',english:'Switzerland'},SY:{chinese:'叙利亚',english:'Syria'},TW:{chinese:'台湾',english:'Taiwan'},TJ:{chinese:'塔吉克斯坦',english:'Tajikstan'},TZ:{chinese:'坦桑尼亚',english:'Tanzania'},TH:{chinese:'泰国',english:'Thailand'},TG:{chinese:'多哥',english:'Togo'},TO:{chinese:'汤加',english:'Tonga'},TT:{chinese:'特立尼达和多巴哥',english:'Trinidad and Tobago'},TN:{chinese:'突尼斯',english:'Tunisia'},TR:{chinese:'土耳其',english:'Turkey'},TM:{chinese:'土库曼斯坦',english:'Turkmenistan'},VI:{chinese:'美属维尔京群岛',english:'U.S. Virgin Islands'},UG:{chinese:'乌干达',english:'Uganda'},UA:{chinese:'乌克兰',english:'Ukraine'},AE:{chinese:'阿联酋',english:'United Arab Emirates'},GB:{chinese:'英国',english:'United Kiongdom'},US:{chinese:'美国',english:'USA'},UY:{chinese:'乌拉圭',english:'Uruguay'},UZ:{chinese:'乌兹别克斯坦',english:'Uzbekistan'},VA:{chinese:'梵蒂冈',english:'Vatican City'},VE:{chinese:'委内瑞拉',english:'Venezuela'},VN:{chinese:'越南',english:'Vietnam'},YE:{chinese:'也门',english:'Yemen'},YU:{chinese:'南斯拉夫',english:'Yugoslavia'},ZR:{chinese:'扎伊尔',english:'Zaire'},ZM:{chinese:'赞比亚',english:'Zambia'},ZW:{chinese:'津巴布韦',english:'Zimbabwe'}} 482 | 483 | // prettier-ignore 484 | 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,i)=>{s.call(this,t,(t,s,r)=>{t?i(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("",`\ud83d\udd14${this.name}, \u5f00\u59cb!`)}isNode(){return"undefined"!=typeof module&&!!module.exports}isQuanX(){return"undefined"!=typeof $task}isSurge(){return"undefined"!=typeof $httpClient&&"undefined"==typeof $loon}isLoon(){return"undefined"!=typeof $loon}isShadowrocket(){return"undefined"!=typeof $rocket}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 i=this.getdata(t);if(i)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,i)=>e(i))})}runScript(t,e){return new Promise(s=>{let i=this.getdata("@chavy_boxjs_userCfgs.httpapi");i=i?i.replace(/\n/g,"").trim():i;let r=this.getdata("@chavy_boxjs_userCfgs.httpapi_timeout");r=r?1*r:20,r=e&&e.timeout?e.timeout:r;const[o,h]=i.split("@"),n={url:`http://${h}/v1/scripting/evaluate`,body:{script_text:t,mock_type:"cron",timeout:r},headers:{"X-Key":o,Accept:"*/*"}};this.post(n,(t,e,i)=>s(i))}).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),i=!s&&this.fs.existsSync(e);if(!s&&!i)return{};{const i=s?t:e;try{return JSON.parse(this.fs.readFileSync(i))}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),i=!s&&this.fs.existsSync(e),r=JSON.stringify(this.data);s?this.fs.writeFileSync(t,r):i?this.fs.writeFileSync(e,r):this.fs.writeFileSync(t,r)}}lodash_get(t,e,s){const i=e.replace(/\[(\d+)\]/g,".$1").split(".");let r=t;for(const t of i)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,i)=>Object(t[s])===t[s]?t[s]:t[s]=Math.abs(e[i+1])>>0==+e[i+1]?[]:{},t)[e[e.length-1]]=s,t)}getdata(t){let e=this.getval(t);if(/^@/.test(t)){const[,s,i]=/^@(.*?)\.(.*?)$/.exec(t),r=s?this.getval(s):"";if(r)try{const t=JSON.parse(r);e=t?this.lodash_get(t,i,""):e}catch(t){e=""}}return e}setdata(t,e){let s=!1;if(/^@/.test(e)){const[,i,r]=/^@(.*?)\.(.*?)$/.exec(e),o=this.getval(i),h=i?"null"===o?null:o||"{}":"{}";try{const e=JSON.parse(h);this.lodash_set(e,r,t),s=this.setval(JSON.stringify(e),i)}catch(e){const o={};this.lodash_set(o,r,t),s=this.setval(JSON.stringify(o),i)}}else s=this.setval(t,e);return s}getval(t){return this.isSurge()||this.isLoon()?$persistentStore.read(t):this.isQuanX()?$prefs.valueForKey(t):this.isNode()?(this.data=this.loaddata(),this.data[t]):this.data&&this.data[t]||null}setval(t,e){return this.isSurge()||this.isLoon()?$persistentStore.write(t,e):this.isQuanX()?$prefs.setValueForKey(t,e):this.isNode()?(this.data=this.loaddata(),this.data[e]=t,this.writedata(),!0):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=(()=>{})){if(t.headers&&(delete t.headers["Content-Type"],delete t.headers["Content-Length"]),this.isSurge()||this.isLoon())this.isSurge()&&this.isNeedRewrite&&(t.headers=t.headers||{},Object.assign(t.headers,{"X-Surge-Skip-Scripting":!1})),$httpClient.get(t,(t,s,i)=>{!t&&s&&(s.body=i,s.statusCode=s.status),e(t,s,i)});else if(this.isQuanX())this.isNeedRewrite&&(t.opts=t.opts||{},Object.assign(t.opts,{hints:!1})),$task.fetch(t).then(t=>{const{statusCode:s,statusCode:i,headers:r,body:o}=t;e(null,{status:s,statusCode:i,headers:r,body:o},o)},t=>e(t));else if(this.isNode()){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:i,statusCode:r,headers:o,rawBody:h}=t;e(null,{status:i,statusCode:r,headers:o,rawBody:h},s.decode(h,this.encoding))},t=>{const{message:i,response:r}=t;e(i,r,r&&s.decode(r.rawBody,this.encoding))})}}post(t,e=(()=>{})){const s=t.method?t.method.toLocaleLowerCase():"post";if(t.body&&t.headers&&!t.headers["Content-Type"]&&(t.headers["Content-Type"]="application/x-www-form-urlencoded"),t.headers&&delete t.headers["Content-Length"],this.isSurge()||this.isLoon())this.isSurge()&&this.isNeedRewrite&&(t.headers=t.headers||{},Object.assign(t.headers,{"X-Surge-Skip-Scripting":!1})),$httpClient[s](t,(t,s,i)=>{!t&&s&&(s.body=i,s.statusCode=s.status),e(t,s,i)});else if(this.isQuanX())t.method=s,this.isNeedRewrite&&(t.opts=t.opts||{},Object.assign(t.opts,{hints:!1})),$task.fetch(t).then(t=>{const{statusCode:s,statusCode:i,headers:r,body:o}=t;e(null,{status:s,statusCode:i,headers:r,body:o},o)},t=>e(t));else if(this.isNode()){let i=require("iconv-lite");this.initGotEnv(t);const{url:r,...o}=t;this.got[s](r,o).then(t=>{const{statusCode:s,statusCode:r,headers:o,rawBody:h}=t;e(null,{status:s,statusCode:r,headers:o,rawBody:h},i.decode(h,this.encoding))},t=>{const{message:s,response:r}=t;e(s,r,r&&i.decode(r.rawBody,this.encoding))})}}time(t,e=null){const s=e?new Date(e):new Date;let i={"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 i)new RegExp("("+e+")").test(t)&&(t=t.replace(RegExp.$1,1==RegExp.$1.length?i[e]:("00"+i[e]).substr((""+i[e]).length)));return t}msg(e=t,s="",i="",r){const o=t=>{if(!t)return t;if("string"==typeof t)return this.isLoon()?t:this.isQuanX()?{"open-url":t}:this.isSurge()?{url:t}:void 0;if("object"==typeof t){if(this.isLoon()){let e=t.openUrl||t.url||t["open-url"],s=t.mediaUrl||t["media-url"];return{openUrl:e,mediaUrl:s}}if(this.isQuanX()){let e=t["open-url"]||t.url||t.openUrl,s=t["media-url"]||t.mediaUrl;return{"open-url":e,"media-url":s}}if(this.isSurge()){let e=t.url||t.openUrl||t["open-url"];return{url:e}}}};if(this.isMute||(this.isSurge()||this.isLoon()?$notification.post(e,s,i,o(r)):this.isQuanX()&&$notify(e,s,i,o(r))),!this.isMuteLog){let t=["","==============\ud83d\udce3\u7cfb\u7edf\u901a\u77e5\ud83d\udce3=============="];t.push(e),s&&t.push(s),i&&t.push(i),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){const s=!this.isSurge()&&!this.isQuanX()&&!this.isLoon();s?this.log("",`\u2757\ufe0f${this.name}, \u9519\u8bef!`,t.stack):this.log("",`\u2757\ufe0f${this.name}, \u9519\u8bef!`,t)}wait(t){return new Promise(e=>setTimeout(e,t))}done(t={}){const e=(new Date).getTime(),s=(e-this.startTime)/1e3;this.log("",`\ud83d\udd14${this.name}, \u7ed3\u675f! \ud83d\udd5b ${s} \u79d2`),this.log(),(this.isSurge()||this.isQuanX()||this.isLoon())&&$done(t)}}(t,e)} 485 | --------------------------------------------------------------------------------