├── 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 |
--------------------------------------------------------------------------------