Last active
December 28, 2023 04:59
-
-
Save Hyseen/729fc4c3ac28c3f2e7c4a832d81780f3 to your computer and use it in GitHub Desktop.
Disney+ 解锁检测
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* @author Helge_0x00 | |
* | |
* [Panel] | |
* disney_check = script-name=disney_check,title=Disney+ 解锁检测,style=alert,content=请刷新面板,update-interval=1800 | |
* | |
* [Script] | |
* disney_check = type=generic,script-path=https://gist.githubusercontent.com/Hyseen/729fc4c3ac28c3f2e7c4a832d81780f3/raw/disney_check.js | |
* | |
* 支持使用脚本使用 argument 参数自定义配置,如:argument=key1=URLEncode(value1)&key2=URLEncode(value2),具体参数如下所示, | |
* title: 面板标题 | |
* availableContent: 解锁时展示的的文本内容,支持以下四个个区域占位符 #REGION_FLAG#、#REGION_CODE#、#REGION_NAME#、#REGION_NAME_EN#,用来展示地区国旗 emoji 、地区编码、地区中文名称、地区英文名称 | |
* availableIcon: 解锁时展示的图标,内容为任意有效的 SF Symbol Name | |
* availableIconColor: 解锁时展示的图标颜色,内容为颜色的 HEX 编码 | |
* availableStyle: 解锁时展示的图标样式,参数可选值有 good, info, alert, error | |
* comingContent: 计划上线展示的的文本内容,支持以下四个个区域占位符 #REGION_FLAG#、#REGION_CODE#、#REGION_NAME#、#REGION_NAME_EN#,用来展示地区国旗 emoji 、地区编码、地区中文名称、地区英文名称 | |
* comingIcon: 计划上线展示的图标,内容为任意有效的 SF Symbol Name | |
* comingIconColor: 计划上线展示的图标颜色,内容为颜色的 HEX 编码 | |
* comingStyle: 计划上线展示的图标样式,参数可选值有 good, info, alert, error | |
* notAvailableContent: 不支持解锁时展示的文本内容 | |
* notAvailableIcon: 不支持解锁时展示的图标 | |
* notAvailableIconColor: 不支持解锁时展示的图标颜色 | |
* notAvailableStyle: 不支持解锁时展示的图标样式 | |
* errorContent: 检测异常时展示的文本内容 | |
* errorIcon: 检测异常时展示的图标 | |
* errorIconColor: 检测异常时展示的图标颜色 | |
* errorStyle: 检测异常时展示的图标样式 | |
* timeout: 超时时间,毫秒,默认为 3000 | |
*/ | |
const UA = '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' | |
// 即将登陆 | |
const STATUS_COMING = 2 | |
// 支持解锁 | |
const STATUS_AVAILABLE = 1 | |
// 不支持解锁 | |
const STATUS_NOT_AVAILABLE = 0 | |
// 检测超时 | |
const STATUS_TIMEOUT = -1 | |
// 检测异常 | |
const STATUS_ERROR = -2 | |
const DEFAULT_OPTIONS = { | |
title: 'Disney+ 解锁检测', | |
availableContent: '支持 Disney+ ➟ #REGION_FLAG# #REGION_NAME#', | |
availableIcon: undefined, | |
availableIconColor: undefined, | |
availableStyle: 'good', | |
comingContent: 'Coming Soon ➟ #REGION_FLAG# #REGION_NAME#', | |
comingIcon: undefined, | |
comingIconColor: undefined, | |
comingStyle: 'info', | |
notAvailableContent: '不支持 Disney+', | |
notAvailableIcon: undefined, | |
notAvailableIconColor: undefined, | |
notAvailableStyle: 'alert', | |
errorContent: '检测失败,请稍后重试', | |
errorIcon: undefined, | |
errorIconColor: undefined, | |
errorStyle: 'error', | |
timeout: 3000, | |
} | |
let options = getOptions() | |
let panel = { | |
title: options.title, | |
} | |
;(async () => { | |
let { region, status } = await testDisneyPlus() | |
console.log(`testDisneyPlus: region=${region}, status=${status}`) | |
switch (status) { | |
case STATUS_AVAILABLE: | |
if (options.availableIcon) { | |
panel['icon'] = options.availableIcon | |
panel['icon-color'] = options.availableIconColor ?? undefined | |
} else { | |
panel['style'] = options.availableStyle | |
} | |
panel['content'] = replaceRegionPlaceholder(options.availableContent, region) | |
return | |
case STATUS_COMING: | |
if (options.comingIcon) { | |
panel['icon'] = options.comingIcon | |
panel['icon-color'] = options.comingIconColor ?? undefined | |
} else { | |
panel['style'] = options.comingStyle | |
} | |
panel['content'] = replaceRegionPlaceholder(options.comingContent, region) | |
return | |
case STATUS_NOT_AVAILABLE: | |
if (options.notAvailableIcon) { | |
panel['icon'] = options.notAvailableIcon | |
panel['icon-color'] = options.notAvailableIconColor ?? undefined | |
} else { | |
panel['style'] = options.notAvailableStyle | |
} | |
panel['content'] = options.notAvailableContent | |
return | |
default: | |
if (options.errorIcon) { | |
panel['icon'] = options.errorIcon | |
panel['icon-color'] = options.errorIconColor ? options.errorIconColor : undefined | |
} else { | |
panel['style'] = options.errorStyle | |
} | |
panel['content'] = options.errorContent | |
} | |
})().finally(() => { | |
$done(panel) | |
}) | |
async function testDisneyPlus() { | |
try { | |
let { region, cnbl } = await Promise.race([testHomePage(), timeout(options.timeout)]) | |
console.log(`homepage: region=${region}, cnbl=${cnbl}`) | |
let { countryCode, inSupportedLocation, accessToken } = await Promise.race([getLocationInfo(), timeout(options.timeout)]) | |
console.log(`getLocationInfo: countryCode=${countryCode}, inSupportedLocation=${inSupportedLocation}`) | |
region = countryCode ?? region | |
// 即将登陆 | |
if (inSupportedLocation === false || inSupportedLocation === 'false') { | |
return { region, status: STATUS_COMING } | |
} | |
let support = await Promise.race([testPublicGraphqlAPI(accessToken), timeout(options.timeout)]) | |
if (!support) { | |
return { status: STATUS_NOT_AVAILABLE } | |
} | |
// 支持解锁 | |
return { region, status: STATUS_AVAILABLE } | |
} catch (error) { | |
console.log(error) | |
// 不支持解锁 | |
if (error === 'Not Available') { | |
return { status: STATUS_NOT_AVAILABLE } | |
} | |
// 检测超时 | |
if (error === 'Timeout') { | |
return { status: STATUS_TIMEOUT } | |
} | |
return { status: STATUS_ERROR } | |
} | |
} | |
function testPublicGraphqlAPI(accessToken) { | |
return new Promise((resolve, reject) => { | |
let opts = { | |
url: 'https://disney.api.edge.bamgrid.com/v1/public/graphql', | |
headers: { | |
'Accept-Language': 'en', | |
Authorization: accessToken, | |
'Content-Type': 'application/json', | |
'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', | |
}, | |
body: JSON.stringify({ | |
query: | |
'query($preferredLanguages: [String!]!, $version: String) {globalization(version: $version) { uiLanguage(preferredLanguages: $preferredLanguages) }}', | |
variables: { version: '1.5.0', preferredLanguages: ['en'] }, | |
}), | |
} | |
$httpClient.post(opts, function (error, response, data) { | |
if (error) { | |
reject('Error') | |
return | |
} | |
resolve(response.status === 200) | |
}) | |
}) | |
} | |
function getLocationInfo() { | |
return new Promise((resolve, reject) => { | |
let opts = { | |
url: 'https://disney.api.edge.bamgrid.com/graph/v1/device/graphql', | |
headers: { | |
'Accept-Language': 'en', | |
Authorization: 'ZGlzbmV5JmJyb3dzZXImMS4wLjA.Cu56AgSfBTDag5NiRA81oLHkDZfu5L3CKadnefEAY84', | |
'Content-Type': 'application/json', | |
'User-Agent': UA, | |
}, | |
body: JSON.stringify({ | |
query: 'mutation registerDevice($input: RegisterDeviceInput!) { registerDevice(registerDevice: $input) { grant { grantType assertion } } }', | |
variables: { | |
input: { | |
applicationRuntime: 'chrome', | |
attributes: { | |
browserName: 'chrome', | |
browserVersion: '94.0.4606', | |
manufacturer: 'apple', | |
model: null, | |
operatingSystem: 'macintosh', | |
operatingSystemVersion: '10.15.7', | |
osDeviceIds: [], | |
}, | |
deviceFamily: 'browser', | |
deviceLanguage: 'en', | |
deviceProfile: 'macosx', | |
}, | |
}, | |
}), | |
} | |
$httpClient.post(opts, function (error, response, data) { | |
if (error) { | |
reject('Error') | |
return | |
} | |
if (response.status !== 200) { | |
console.log('getLocationInfo: ' + data) | |
reject('Not Available') | |
return | |
} | |
data = JSON.parse(data) | |
if(data?.errors){ | |
console.log('getLocationInfo: ' + data) | |
reject('Not Available') | |
return | |
} | |
let { | |
token: { accessToken }, | |
session: { | |
inSupportedLocation, | |
location: { countryCode }, | |
}, | |
} = data?.extensions?.sdk | |
resolve({ inSupportedLocation, countryCode, accessToken }) | |
}) | |
}) | |
} | |
function testHomePage() { | |
return new Promise((resolve, reject) => { | |
let opts = { | |
url: 'https://www.disneyplus.com/', | |
headers: { | |
'Accept-Language': 'en', | |
'User-Agent': UA, | |
}, | |
} | |
$httpClient.get(opts, function (error, response, data) { | |
if (error) { | |
reject('Error') | |
return | |
} | |
if (response.status !== 200 || data.indexOf('Sorry, Disney+ is not available in your region.') !== -1) { | |
reject('Not Available') | |
return | |
} | |
let match = data.match(/Region: ([A-Za-z]{2})[\s\S]*?CNBL: ([12])/) | |
if (!match) { | |
resolve({ region: '', cnbl: '' }) | |
return | |
} | |
let region = match[1] | |
let cnbl = match[2] | |
resolve({ region, cnbl }) | |
}) | |
}) | |
} | |
function timeout(delay = 5000) { | |
return new Promise((resolve, reject) => { | |
setTimeout(() => { | |
reject('Timeout') | |
}, delay) | |
}) | |
} | |
function getOptions() { | |
let options = Object.assign({}, DEFAULT_OPTIONS) | |
if (typeof $argument != 'undefined') { | |
try { | |
let params = Object.fromEntries( | |
$argument | |
.split('&') | |
.map(item => item.split('=')) | |
.map(([k, v]) => [k, decodeURIComponent(v)]) | |
) | |
Object.assign(options, params) | |
} catch (error) { | |
console.error(`$argument 解析失败,$argument: + ${argument}`) | |
} | |
} | |
return options | |
} | |
function replaceRegionPlaceholder(content, region) { | |
let result = content | |
if (result.indexOf('#REGION_CODE#') !== -1) { | |
result = result.replaceAll('#REGION_CODE#', region.toUpperCase()) | |
} | |
if (result.indexOf('#REGION_FLAG#') !== -1) { | |
result = result.replaceAll('#REGION_FLAG#', getCountryFlagEmoji(region.toUpperCase())) | |
} | |
if (result.indexOf('#REGION_NAME#') !== -1) { | |
result = result.replaceAll('#REGION_NAME#', RESION_NAMES?.[region.toUpperCase()]?.chinese ?? '') | |
} | |
if (result.indexOf('#REGION_NAME_EN#') !== -1) { | |
result = result.replaceAll('#REGION_NAME_EN#', RESION_NAMES?.[region.toUpperCase()]?.english ?? '') | |
} | |
return result | |
} | |
function getCountryFlagEmoji(countryCode) { | |
if (countryCode.toUpperCase() == 'TW') { | |
countryCode = 'CN' | |
} | |
const codePoints = countryCode | |
.toUpperCase() | |
.split('') | |
.map(char => 127397 + char.charCodeAt()) | |
return String.fromCodePoint(...codePoints) | |
} | |
// prettier-ignore | |
const RESION_NAMES={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'}} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!name=Disney+ Unlock Check | |
#!desc=Disney+ 解锁检测 | |
#!system=ios | |
[Panel] | |
disney_check = script-name=disney_check,title=Disney+ 解锁检测,style=alert,content=请刷新面板,update-interval=1800 | |
[Script] | |
disney_check = type=generic,script-path=https://gist.githubusercontent.com/Hyseen/729fc4c3ac28c3f2e7c4a832d81780f3/raw/disney_check.js?version=1658320512 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
大佬能整个 Paramount+ 的么