diff --git a/Archive/box/iRingo.v3.boxjs.json b/Archive/box/iRingo.v3.boxjs.json deleted file mode 100644 index d7e30921c..000000000 --- a/Archive/box/iRingo.v3.boxjs.json +++ /dev/null @@ -1,1546 +0,0 @@ -{ - "id": "iRingo.app.sub", - "name": "iRingo", - "author": "@VirgilClyne", - "description": "解锁完整的 Apple功能和集成服务", - "icon": "https://avatars.githubusercontent.com/u/2111377?s=100&v=4", - "repo": "https://github.com/VirgilClyne/iRingo", - "apps": [ - { - "id": "iRingo.Location", - "name": "定位服务", - "descs_html": [ - "请参照iRingo#定位服务的使用说明进行配置", - "影响功能范围包括但不限于“地图”、“Apple News”、“指南针”等" - ], - "keys": [ - "@iRingo.Location.Settings", - "@iRingo.Location.Caches" - ], - "settings": [ - { - "id": "@iRingo.Location.Settings.Switch", - "name": "总功能开关", - "val": true, - "type": "boolean", - "desc": "是否启用此APP修改" - }, - { - "id": "@iRingo.Location.Settings.PEP.GCC", - "name": "[地区检测]地理国家或地区代码", - "val": "US", - "type": "selects", - "desc": "要更改为的地区或国家,此选项影响Wi-Fi或有线网络下国家和地区检测的结果", - "items": [ - { - "key": "CN", - "label": "中国大陆" - }, - { - "key": "HK", - "label": "香港" - }, - { - "key": "TW", - "label": "台湾" - }, - { - "key": "SG", - "label": "新加坡" - }, - { - "key": "US", - "label": "美国" - }, - { - "key": "JP", - "label": "日本" - }, - { - "key": "AU", - "label": "澳大利亚" - }, - { - "key": "GB", - "label": "英国" - }, - { - "key": "KR", - "label": "韩国" - }, - { - "key": "CA", - "label": "加拿大" - } - ] - }, - { - "id": "@iRingo.Location.Settings.Geo_manifest.Dynamic.Config.Country_code", - "name": "[动态配置]地理服务清单的国家或地区代码", - "val": "CN", - "type": "selects", - "desc": "要更改为的地区或国家,此选项影响“地图”整体配置内容,包括以下的地图功能与服务", - "items": [ - { - "key": "CN", - "label": "中国大陆" - }, - { - "key": "HK", - "label": "香港" - }, - { - "key": "TW", - "label": "台湾" - }, - { - "key": "SG", - "label": "新加坡" - }, - { - "key": "US", - "label": "美国" - }, - { - "key": "JP", - "label": "日本" - }, - { - "key": "AU", - "label": "澳大利亚" - }, - { - "key": "GB", - "label": "英国" - }, - { - "key": "KR", - "label": "韩国" - }, - { - "key": "CA", - "label": "加拿大" - } - ] - }, - { - "id": "@iRingo.Location.Settings.Services.Dispatcher", - "name": "[调度程序]数据源", - "val": "AUTO", - "type": "selects", - "desc": "调度程序的数据源,此选项影响公共指南,兴趣点(POI)与位置信息等功能", - "items": [ - { - "key": "AUTO", - "label": "自动(随[动态配置]版本自动选择)" - }, - { - "key": "CN", - "label": "中国大陆版(兴趣点:大众点评,位置信息:高德)" - }, - { - "key": "XX", - "label": "国际版(兴趣点:Yelp,位置信息:TomTom)" - } - ] - }, - { - "id": "@iRingo.Location.Settings.Services.Directions", - "name": "[导航]数据源", - "val": "AUTO", - "type": "selects", - "desc": "导航服务的数据源,此选项影响导航服务", - "items": [ - { - "key": "AUTO", - "label": "自动(随[动态配置]版本自动选择)" - }, - { - "key": "CN", - "label": "中国大陆版(高德)" - }, - { - "key": "XX", - "label": "国际版" - } - ] - }, - { - "id": "@iRingo.Location.Settings.Services.Traffic", - "name": "[交通状况]数据源", - "val": "AUTO", - "type": "selects", - "desc": "交通状况服务的数据源,此选项影响交通状况与道路信息服务", - "items": [ - { - "key": "AUTO", - "label": "自动(随[动态配置]版本自动选择)" - } - ] - }, - { - "id": "@iRingo.Location.Settings.Services.Tiles", - "name": "[瓦片地图]服务版本", - "val": "AUTO", - "type": "selects", - "desc": "瓦片地图的数据源,此选项影响使用的地图与图层", - "items": [ - { - "key": "AUTO", - "label": "自动(随[动态配置]版本自动选择)" - } - ] - }, - { - "id": "@iRingo.Location.Settings.Config.Announcements.Environment", - "name": "[通告配置]环境代码", - "val": "prod-cn", - "type": "selects", - "desc": "通告的配置文件的环境代码,此选项影响功能未知", - "items": [ - { - "key": "prod-cn", - "label": "中国大陆版" - }, - { - "key": "prod", - "label": "国际版" - } - ] - }, - { - "id": "@iRingo.Location.Settings.Config.Defaults.LagunaBeach", - "name": "[默认配置]为“地图”提供助力", - "val": true, - "type": "boolean", - "desc": "是否启用“评分与照片”,可让您将图像、评分或其他内容发布到所选的兴趣点。(“设置” >“地图”>“评分与照片”)" - }, - { - "id": "@iRingo.Location.Settings.Config.Defaults.DrivingMultiWaypointRoutesEnabled", - "name": "[默认配置]多途经点路线规划", - "val": true, - "type": "boolean", - "desc": "是否启用“多途经点路线规划”,可在一条路线中添加多个途经点。(iOS 16)" - }, - { - "id": "@iRingo.Location.Settings.Config.Defaults.GEOAddressCorrection", - "name": "[默认配置]地址校正", - "val": true, - "type": "boolean", - "desc": "是否启用定位地址矫正" - }, - { - "id": "@iRingo.Location.Settings.Config.Defaults.LookupMaxParametersCount", - "name": "[默认配置]Spatial批量查询上限", - "val": true, - "type": "boolean", - "desc": "是否移除Spatial批量查询上限" - }, - { - "id": "@iRingo.Location.Settings.Config.Defaults.LocalitiesAndLandmarks", - "name": "[默认配置]景点与地标支持", - "val": true, - "type": "boolean", - "desc": "是否启用景点与地标支持" - }, - { - "id": "@iRingo.Location.Settings.Config.Defaults.PedestrianAR", - "name": "[默认配置]现实世界中的路线", - "val": true, - "type": "boolean", - "desc": "是否启用在增强现实中查看导航(“设置” >“地图”>“步行”>“举起以查看”)" - }, - { - "id": "@iRingo.Location.Settings.Config.Defaults.6694982d2b14e95815e44e970235e230", - "name": "[默认配置]6694982d2b14e95815e44e970235e230", - "val": true, - "type": "boolean", - "desc": "6694982d2b14e95815e44e970235e230" - }, - { - "id": "@iRingo.Location.Settings.Config.Defaults.OpticalHeading", - "name": "[默认配置]导航准确性", - "val": true, - "type": "boolean", - "desc": "是否使用相机和运动传感器提高您位置和步行朝向的准确度(“设置” >“地图”>“步行”>“增强”)" - }, - { - "id": "@iRingo.Location.Settings.Config.Defaults.UseCLPedestrianMapMatchedLocations", - "name": "[默认配置]使用AR地图匹配地点", - "val": true, - "type": "boolean", - "desc": "是否使用AR地图匹配地点" - } - ], - "author": "@VirgilClyne", - "repo": "https://github.com/VirgilClyne/iRingo/", - "icons": [ - "https://support.apple.com/library/content/dam/edam/applecare/images/en_US/iOS/ios15-location-arrow-status-icon.png", - "https://support.apple.com/library/content/dam/edam/applecare/images/en_US/iOS/ios15-location-arrow-status-icon.png" - ] - }, - { - "id": "iRingo.Weather", - "name": "天气", - "descs_html": [ - "请参照iRingo#天气的使用说明进行配置", - "填写完成后别忘点击此页面底端右下角的\"保存\"。", - "查询速度:\"私有API+城市\" > \"私有API+观测站\" > \"公共API+观测站\"", - "定位精度:\"观测站\" > \"城市\"" - ], - "keys": [ - "@iRingo.Weather.Settings", - "@iRingo.Weather.Caches", - "@iRingo.Weather" - ], - "settings": [ - { - "id": "@iRingo.Weather.Switch", - "name": "总功能开关", - "val": true, - "type": "boolean", - "desc": "是否启用此APP修改" - }, - { - "id": "@iRingo.Weather.NextHour.Switch", - "name": "[未来一小时降水强度]修改开关", - "val": true, - "type": "boolean", - "desc": "是否启用此功能修改" - }, - { - "id": "@iRingo.Weather.NextHour.Mode", - "name": "[未来一小时降水强度]切换API", - "val": "www.weatherol.cn", - "type": "radios", - "items": [ - { - "key": "www.weatherol.cn", - "label": "气象在线" - }, - { - "key": "api.caiyunapp.com", - "label": "彩云天气" - } - ], - "desc": "气象在线使用的是彩云天气的数据,优点是无需付费,缺点是仅支持简体中文。彩云天气需要付费才能获取获取分钟级降水数据。" - }, - { - "id": "@iRingo.Weather.NextHour.HTTPHeaders", - "name": "[未来一小时降水强度]HTTP请求头", - "val": "{ \"Content-Type\": \"application/x-www-form-urlencoded\", \"User-Agent\": \"Mozilla/5.0 (iPhone; CPU iPhone OS 15_1_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.1 Mobile/15E148 Safari/604.1\" }", - "type": "text", - "placeholder": "{ \"User-Agent\": \"Mozilla/5.0 (iPhone; CPU iPhone OS 15_1_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.1 Mobile/15E148 Safari/604.1\" }", - "desc": "请求API时使用的HTTP请求头" - }, - { - "id": "@iRingo.Weather.NextHour.ColorfulClouds.Auth", - "name": "[未来一小时降水强度]彩云天气令牌", - "val": "", - "type": "text", - "placeholder": "ABcdef1g2hiJklmN", - "desc": "彩云天气API令牌" - }, - { - "id": "@iRingo.Weather.AQI.Switch", - "name": "[空气质量]修改开关", - "val": true, - "type": "boolean", - "desc": "是否启用此功能修改,数据来源:WAQI" - }, - { - "id": "@iRingo.Weather.AQI.Mode", - "name": "[空气质量]工作模式", - "val": "WAQI Public", - "type": "radios", - "items": [ - { - "key": "WAQI Public", - "label": "waqi.info 公共API" - }, - { - "key": "WAQI Private", - "label": "waqi.info 私有API" - } - ], - "desc": "没有申请自己Token的只能使用\"公共API\"" - }, - { - "id": "@iRingo.Weather.AQI.Location", - "name": "[空气质量]定位精度", - "val": "Station", - "type": "radios", - "items": [ - { - "key": "City", - "label": "城市" - }, - { - "key": "Station", - "label": "观测站" - } - ], - "desc": "获取天气数据的精确度,\"观测站\"会获取距离你最近的观测站数据,\"城市\"只会获取所在地城市指定观测站的数据" - }, - { - "id": "@iRingo.Weather.AQI.Auth", - "name": "[空气质量]WAQI令牌", - "val": "", - "type": "text", - "placeholder": "1234567893feefc5f0q5000bfo0c38d90bbeb", - "desc": "私有API令牌" - }, - { - "id": "@iRingo.Weather.AQI.Scale", - "name": "[空气质量]测量标准代码", - "val": "EPA_NowCast.2204", - "type": "text", - "placeholder": "EPA_NowCast.2204", - "desc": "空气质量标准代码,会随系统更新而更新" - }, - { - "id": "@iRingo.Weather.Map.AQI", - "name": "[空气质量地图]修改开关", - "val": false, - "type": "boolean", - "desc": "是否启用此功能修改,数据来源:WAQI" - } - ], - "author": "@VirgilClyne", - "repo": "https://github.com/VirgilClyne/iRingo/", - "icons": [ - "https://is4-ssl.mzstatic.com/image/thumb/Purple125/v4/c8/76/87/c8768792-b7ab-7de4-9c70-1888096b7ae9/AppIcon-0-0-1x_U007emarketing-0-0-0-10-0-0-sRGB-0-0-0-GLES2_U002c0-512MB-85-220-0-0.png/108x0w.webp", - "https://is4-ssl.mzstatic.com/image/thumb/Purple125/v4/c8/76/87/c8768792-b7ab-7de4-9c70-1888096b7ae9/AppIcon-0-0-1x_U007emarketing-0-0-0-10-0-0-sRGB-0-0-0-GLES2_U002c0-512MB-85-220-0-0.png/108x0w.webp" - ] - }, - { - "id": "iRingo.Siri", - "name": "Siri与搜索", - "descs_html": [ - "请参照iRingo#siri与搜索的使用说明进行配置", - "影响功能范围包括但不限于“Siri建议”、“来自APPLE的内容”、“来自APPLE的建议”等" - ], - "keys": [ - "@iRingo.Siri.Settings", - "@iRingo.Siri.Caches" - ], - "settings": [ - { - "id": "@iRingo.Siri.Settings.Switch", - "name": "总功能开关", - "val": true, - "type": "boolean", - "desc": "是否启用此APP修改" - }, - { - "id": "@iRingo.Siri.Settings.CountryCode", - "name": "国家或地区代码", - "val": "SG", - "type": "selects", - "desc": "不同国家或地区提供的内容或有差别,此选项同时会影响分配给您不同地区的Siri服务器", - "items": [ - { - "key": "AUTO", - "label": "自动(跟随系统地区设置)" - }, - { - "key": "CN", - "label": "中国大陆" - }, - { - "key": "HK", - "label": "香港" - }, - { - "key": "TW", - "label": "台湾" - }, - { - "key": "SG", - "label": "新加坡" - }, - { - "key": "US", - "label": "美国" - }, - { - "key": "JP", - "label": "日本" - }, - { - "key": "AU", - "label": "澳大利亚" - }, - { - "key": "GB", - "label": "英国" - }, - { - "key": "KR", - "label": "韩国" - }, - { - "key": "CA", - "label": "加拿大" - } - ] - }, - { - "id": "@iRingo.Siri.Settings.Domains", - "name": "启用领域", - "val": [ - "web", - "itunes", - "app_store", - "movies", - "restaurants", - "maps" - ], - "type": "checkboxes", - "desc": "启用搜索的领域,未选项领域将被关闭,启用领域数据由国家和地区决定,此选项仅代表功能上的开启,不代表对应地区一定有数据和服务", - "items": [ - { - "key": "web", - "label": "网页" - }, - { - "key": "itunes", - "label": "iTunes" - }, - { - "key": "app_store", - "label": "App Store" - }, - { - "key": "movies", - "label": "电影" - }, - { - "key": "restaurants", - "label": "餐厅" - }, - { - "key": "maps", - "label": "地图" - } - ] - }, - { - "id": "@iRingo.Siri.Settings.Functions", - "name": "强制启用的功能", - "val": [ - "flightutilities", - "lookup", - "mail", - "messages", - "news", - "safari", - "siri", - "spotlight", - "visualintelligence" - ], - "type": "checkboxes", - "desc": "强制启用Siri建议的APP,被选项的功能会被强制开启,但是未选不代表关闭对应的功能,仅代表还原到该地区默认设置状态", - "items": [ - { - "key": "flightutilities", - "label": "航班工具" - }, - { - "key": "lookup", - "label": "查询" - }, - { - "key": "mail", - "label": "邮件" - }, - { - "key": "messages", - "label": "信息" - }, - { - "key": "news", - "label": "新闻" - }, - { - "key": "safari", - "label": "Safari浏览器" - }, - { - "key": "siri", - "label": "Siri" - }, - { - "key": "spotlight", - "label": "聚焦搜索" - }, - { - "key": "visualintelligence", - "label": "视觉智能" - } - ] - }, - { - "id": "@iRingo.Siri.Settings.Safari_Smart_History", - "name": "Safari智能历史记录", - "val": true, - "type": "boolean", - "desc": "是否在Safari浏览器中启用基于历史记录的Siri建议功能,启用后将在Safari浏览器起始页推荐基于时间地点跨设备等的相关浏览记录" - } - ], - "author": "@VirgilClyne", - "repo": "https://github.com/VirgilClyne/iRingo/", - "icons": [ - "https://raw.githubusercontent.com/Koolson/Qure/master/IconSet/Siri.png", - "https://raw.githubusercontent.com/Koolson/Qure/master/IconSet/Color/Siri.png" - ] - }, - { - "id": "iRingo.TV", - "name": "TV", - "descs_html": [ - "请参照iRingo#TV的使用说明进行配置", - "自定义TV app的配置文件及各个栏目" - ], - "keys": [ - "@iRingo.TV.Settings", - "@iRingo.TV.Caches" - ], - "settings": [ - { - "id": "@iRingo.TV.Settings.Switch", - "name": "总功能开关", - "val": true, - "type": "boolean", - "desc": "是否启用此APP修改" - }, - { - "id": "@iRingo.TV.Settings.Third-Party", - "name": "为不兼容平台启用第三方影片库", - "val": true, - "type": "boolean", - "desc": "是否为桌面版/macOS版/androidTV版等不兼容平台的TV app启用第三方影片库(如: Disney+,Prime Video等)" - }, - { - "id": "@iRingo.TV.Settings.Configs.CountryCode", - "name": "[配置文件]国家或地区代码", - "val": "AUTO", - "type": "selects", - "desc": "“配置文件”要更改为的地区或国家版本", - "items": [ - { - "key": "AUTO", - "label": "自动(与当前登陆账号保持一致)" - }, - { - "key": "CN", - "label": "中国大陆" - }, - { - "key": "HK", - "label": "香港" - }, - { - "key": "TW", - "label": "台湾" - }, - { - "key": "SG", - "label": "新加坡" - }, - { - "key": "US", - "label": "美国" - }, - { - "key": "JP", - "label": "日本" - }, - { - "key": "AU", - "label": "澳大利亚" - }, - { - "key": "GB", - "label": "英国" - }, - { - "key": "KR", - "label": "韩国" - }, - { - "key": "CA", - "label": "加拿大" - } - ] - }, - { - "id": "@iRingo.TV.Settings.Configs.Tabs", - "name": "启用的标签与栏目", - "val": [ - "WatchNow", - "Originals", - "Movies", - "TV", - "Sports", - "Kids", - "Library", - "Search" - ], - "type": "checkboxes", - "desc": "启用的标签与栏目,未选择的标签与栏目入口将被隐藏,启用的入口由国家和地区决定,此选项仅代表功能入口上的开启,不代表对应地区一定有数据和服务", - "items": [ - { - "key": "WatchNow", - "label": "立即观看" - }, - { - "key": "Originals", - "label": "原创内容(TV+)" - }, - { - "key": "Movies", - "label": "电影" - }, - { - "key": "TV", - "label": "电视节目" - }, - { - "key": "Sports", - "label": "体育节目" - }, - { - "key": "Kids", - "label": "儿童" - }, - { - "key": "Library", - "label": "资料库" - }, - { - "key": "Search", - "label": "搜索" - } - ] - }, - { - "id": "@iRingo.TV.Settings.View.CountryCode[0]", - "name": "[内容详情]首选语言", - "val": "SG", - "type": "selects", - "desc": "“内容详情”(电影、电视节目、人物等详情页面)要更改为的首选语言", - "items": [ - { - "key": "AUTO", - "label": "自动(与当前登陆账号保持一致)" - }, - { - "key": "CN", - "label": "简体中文(中国)" - }, - { - "key": "HK", - "label": "繁体粤语(香港)" - }, - { - "key": "TW", - "label": "繁体中文(台湾)" - }, - { - "key": "SG", - "label": "简体中文(新加坡)" - }, - { - "key": "US", - "label": "英语(美国)" - }, - { - "key": "JP", - "label": "日语(日本)" - }, - { - "key": "AU", - "label": "英语(澳大利亚)" - }, - { - "key": "GB", - "label": "英语(英国)" - }, - { - "key": "KR", - "label": "韩语(韩国)" - }, - { - "key": "CA", - "label": "英语(加拿大)" - } - ] - }, - { - "id": "@iRingo.TV.Settings.View.CountryCode[1]", - "name": "[内容详情]第二语言", - "val": "TW", - "type": "selects", - "desc": "当首选语言不可用时,“内容详情”(电影、电视节目、人物等详情页面)要更改为的第二语言", - "items": [ - { - "key": "AUTO", - "label": "自动(与当前登陆账号保持一致)" - }, - { - "key": "CN", - "label": "简体中文(中国)" - }, - { - "key": "HK", - "label": "繁体粤语(香港)" - }, - { - "key": "TW", - "label": "繁体中文(台湾)" - }, - { - "key": "SG", - "label": "简体中文(新加坡)" - }, - { - "key": "US", - "label": "英语(美国)" - }, - { - "key": "JP", - "label": "日语(日本)" - }, - { - "key": "AU", - "label": "英语(澳大利亚)" - }, - { - "key": "GB", - "label": "英语(英国)" - }, - { - "key": "KR", - "label": "韩语(韩国)" - }, - { - "key": "CA", - "label": "英语(加拿大)" - } - ] - }, - { - "id": "@iRingo.TV.Settings.WatchNow.CountryCode", - "name": "[立即观看]国家或地区代码", - "val": "AUTO", - "type": "selects", - "desc": "“立即观看”栏目要更改为的地区或国家版本", - "items": [ - { - "key": "AUTO", - "label": "自动(与当前登陆账号保持一致)" - }, - { - "key": "CN", - "label": "中国大陆" - }, - { - "key": "HK", - "label": "香港" - }, - { - "key": "TW", - "label": "台湾" - }, - { - "key": "SG", - "label": "新加坡" - }, - { - "key": "US", - "label": "美国" - }, - { - "key": "JP", - "label": "日本" - }, - { - "key": "AU", - "label": "澳大利亚" - }, - { - "key": "GB", - "label": "英国" - }, - { - "key": "KR", - "label": "韩国" - }, - { - "key": "CA", - "label": "加拿大" - } - ] - }, - { - "id": "@iRingo.TV.Settings.Originals.CountryCode", - "name": "[原创内容](TV+)国家或地区代码", - "val": "TW", - "type": "selects", - "desc": "“原创内容”(TV+)栏目要更改为的地区或国家版本", - "items": [ - { - "key": "AUTO", - "label": "自动(与当前登陆账号保持一致)" - }, - { - "key": "CN", - "label": "中国大陆" - }, - { - "key": "HK", - "label": "香港" - }, - { - "key": "TW", - "label": "台湾" - }, - { - "key": "SG", - "label": "新加坡" - }, - { - "key": "US", - "label": "美国" - }, - { - "key": "JP", - "label": "日本" - }, - { - "key": "AU", - "label": "澳大利亚" - }, - { - "key": "GB", - "label": "英国" - }, - { - "key": "KR", - "label": "韩国" - }, - { - "key": "CA", - "label": "加拿大" - } - ] - }, - { - "id": "@iRingo.TV.Settings.Channels.CountryCode", - "name": "[TV频道]国家或地区代码", - "val": "AUTO", - "type": "selects", - "desc": "“TV频道”栏目要更改为的地区或国家版本", - "items": [ - { - "key": "AUTO", - "label": "自动(与当前登陆账号保持一致)" - }, - { - "key": "CN", - "label": "中国大陆" - }, - { - "key": "HK", - "label": "香港" - }, - { - "key": "TW", - "label": "台湾" - }, - { - "key": "SG", - "label": "新加坡" - }, - { - "key": "US", - "label": "美国" - }, - { - "key": "JP", - "label": "日本" - }, - { - "key": "AU", - "label": "澳大利亚" - }, - { - "key": "GB", - "label": "英国" - }, - { - "key": "KR", - "label": "韩国" - }, - { - "key": "CA", - "label": "加拿大" - } - ] - }, - { - "id": "@iRingo.TV.Settings.Movies.CountryCode", - "name": "[电影]国家或地区代码", - "val": "AUTO", - "type": "selects", - "desc": "“电影”栏目要更改为的地区或国家版本", - "items": [ - { - "key": "AUTO", - "label": "自动(与当前登陆账号保持一致)" - }, - { - "key": "CN", - "label": "中国大陆" - }, - { - "key": "HK", - "label": "香港" - }, - { - "key": "TW", - "label": "台湾" - }, - { - "key": "SG", - "label": "新加坡" - }, - { - "key": "US", - "label": "美国" - }, - { - "key": "JP", - "label": "日本" - }, - { - "key": "AU", - "label": "澳大利亚" - }, - { - "key": "GB", - "label": "英国" - }, - { - "key": "KR", - "label": "韩国" - }, - { - "key": "CA", - "label": "加拿大" - } - ] - }, - { - "id": "@iRingo.TV.Settings.TV.CountryCode", - "name": "[电视节目]国家或地区代码", - "val": "AUTO", - "type": "selects", - "desc": "“电视节目”栏目要更改为的地区或国家版本", - "items": [ - { - "key": "AUTO", - "label": "自动(与当前登陆账号保持一致)" - }, - { - "key": "CN", - "label": "中国大陆" - }, - { - "key": "HK", - "label": "香港" - }, - { - "key": "TW", - "label": "台湾" - }, - { - "key": "SG", - "label": "新加坡" - }, - { - "key": "US", - "label": "美国" - }, - { - "key": "JP", - "label": "日本" - }, - { - "key": "AU", - "label": "澳大利亚" - }, - { - "key": "GB", - "label": "英国" - }, - { - "key": "KR", - "label": "韩国" - }, - { - "key": "CA", - "label": "加拿大" - } - ] - }, - { - "id": "@iRingo.TV.Settings.Sports.CountryCode", - "name": "[体育节目]国家或地区代码", - "val": "US", - "type": "selects", - "desc": "“体育节目”栏目要更改为的地区或国家版本", - "items": [ - { - "key": "AUTO", - "label": "自动(与当前登陆账号保持一致)" - }, - { - "key": "CN", - "label": "中国大陆" - }, - { - "key": "HK", - "label": "香港" - }, - { - "key": "TW", - "label": "台湾" - }, - { - "key": "SG", - "label": "新加坡" - }, - { - "key": "US", - "label": "美国" - }, - { - "key": "JP", - "label": "日本" - }, - { - "key": "AU", - "label": "澳大利亚" - }, - { - "key": "GB", - "label": "英国" - }, - { - "key": "KR", - "label": "韩国" - }, - { - "key": "CA", - "label": "加拿大" - } - ] - }, - { - "id": "@iRingo.TV.Settings.Kids.CountryCode", - "name": "[儿童]国家或地区代码", - "val": "US", - "type": "selects", - "desc": "“儿童”栏目要更改为的地区或国家版本", - "items": [ - { - "key": "AUTO", - "label": "自动(与当前登陆账号保持一致)" - }, - { - "key": "CN", - "label": "中国大陆" - }, - { - "key": "HK", - "label": "香港" - }, - { - "key": "TW", - "label": "台湾" - }, - { - "key": "SG", - "label": "新加坡" - }, - { - "key": "US", - "label": "美国" - }, - { - "key": "JP", - "label": "日本" - }, - { - "key": "AU", - "label": "澳大利亚" - }, - { - "key": "GB", - "label": "英国" - }, - { - "key": "KR", - "label": "韩国" - }, - { - "key": "CA", - "label": "加拿大" - } - ] - }, - { - "id": "@iRingo.TV.Settings.Persons.CountryCode", - "name": "[人物]国家或地区代码", - "val": "SG", - "type": "selects", - "desc": "“人物”栏目(导演、演员等)要更改为的地区或国家版本", - "items": [ - { - "key": "AUTO", - "label": "自动(与当前登陆账号保持一致)" - }, - { - "key": "CN", - "label": "中国大陆" - }, - { - "key": "HK", - "label": "香港" - }, - { - "key": "TW", - "label": "台湾" - }, - { - "key": "SG", - "label": "新加坡" - }, - { - "key": "US", - "label": "美国" - }, - { - "key": "JP", - "label": "日本" - }, - { - "key": "AU", - "label": "澳大利亚" - }, - { - "key": "GB", - "label": "英国" - }, - { - "key": "KR", - "label": "韩国" - }, - { - "key": "CA", - "label": "加拿大" - } - ] - }, - { - "id": "@iRingo.TV.Settings.Search.CountryCode", - "name": "[搜索]国家或地区代码", - "val": "TW", - "type": "selects", - "desc": "“搜索”栏目要更改为的地区或国家版本", - "items": [ - { - "key": "AUTO", - "label": "自动(与当前登陆账号保持一致)" - }, - { - "key": "CN", - "label": "中国大陆" - }, - { - "key": "HK", - "label": "香港" - }, - { - "key": "TW", - "label": "台湾" - }, - { - "key": "SG", - "label": "新加坡" - }, - { - "key": "US", - "label": "美国" - }, - { - "key": "JP", - "label": "日本" - }, - { - "key": "AU", - "label": "澳大利亚" - }, - { - "key": "GB", - "label": "英国" - }, - { - "key": "KR", - "label": "韩国" - }, - { - "key": "CA", - "label": "加拿大" - } - ] - }, - { - "id": "@iRingo.TV.Settings.Others.CountryCode", - "name": "[其他]国家或地区代码", - "val": "AUTO", - "type": "selects", - "desc": "其他未指定的栏目要更改为的地区或国家版本", - "items": [ - { - "key": "AUTO", - "label": "自动(与当前登陆账号保持一致)" - }, - { - "key": "CN", - "label": "中国大陆" - }, - { - "key": "HK", - "label": "香港" - }, - { - "key": "TW", - "label": "台湾" - }, - { - "key": "SG", - "label": "新加坡" - }, - { - "key": "US", - "label": "美国" - }, - { - "key": "JP", - "label": "日本" - }, - { - "key": "AU", - "label": "澳大利亚" - }, - { - "key": "GB", - "label": "英国" - }, - { - "key": "KR", - "label": "韩国" - }, - { - "key": "CA", - "label": "加拿大" - } - ] - } - ], - "author": "@VirgilClyne", - "repo": "https://github.com/VirgilClyne/iRingo/", - "icons": [ - "https://is1-ssl.mzstatic.com/image/thumb/Purple122/v4/9c/f7/8a/9cf78ad4-5443-acc0-3b36-f13d2ad7d64c/AppIcon-0-0-1x_U007emarketing-0-0-0-7-0-0-sRGB-0-0-0-GLES2_U002c0-512MB-85-220-0-0.png/108x0w.webp", - "https://is1-ssl.mzstatic.com/image/thumb/Purple122/v4/9c/f7/8a/9cf78ad4-5443-acc0-3b36-f13d2ad7d64c/AppIcon-0-0-1x_U007emarketing-0-0-0-7-0-0-sRGB-0-0-0-GLES2_U002c0-512MB-85-220-0-0.png/108x0w.webp" - ] - }, - { - "id": "iRingo.News", - "name": "News", - "descs_html": [ - "请参照📰News的使用说明进行配置", - "影响功能范围……等" - ], - "keys": [ - "@iRingo.News.Settings", - "@iRingo.News.Caches" - ], - "settings": [ - { - "id": "@iRingo.News.Settings.Switch", - "name": "总功能开关", - "val": true, - "type": "boolean", - "desc": "是否启用此APP修改" - }, - { - "id": "@iRingo.News.Settings.CountryCode", - "name": "国家或地区代码", - "val": "US", - "type": "selects", - "desc": "不同国家或地区提供的内容或有差别", - "items": [ - { - "key": "AUTO", - "label": "自动(跟随地区检测结果)" - }, - { - "key": "CN", - "label": "中国大陆" - }, - { - "key": "HK", - "label": "香港" - }, - { - "key": "TW", - "label": "台湾" - }, - { - "key": "SG", - "label": "新加坡" - }, - { - "key": "US", - "label": "美国" - }, - { - "key": "JP", - "label": "日本" - }, - { - "key": "AU", - "label": "澳大利亚" - }, - { - "key": "GB", - "label": "英国" - }, - { - "key": "KR", - "label": "韩国" - }, - { - "key": "CA", - "label": "加拿大" - } - ] - } - ], - "author": "@VirgilClyne", - "repo": "https://github.com/VirgilClyne/iRingo/", - "icons": [ - "https://raw.githubusercontent.com/Koolson/Qure/master/IconSet/Apple_News.png", - "https://raw.githubusercontent.com/Koolson/Qure/master/IconSet/Color/Apple_News.png" - ] - }, - { - "id": "iRingo.TestFlight", - "name": "TestFlight", - "descs_html": [ - "请参照✈️TestFlight的使用说明进行配置", - "影响功能范围……等" - ], - "keys": [ - "@iRingo.TestFlight.Settings", - "@iRingo.TestFlight.Caches" - ], - "settings": [ - { - "id": "@iRingo.TestFlight.Settings.Switch", - "name": "总功能开关", - "val": true, - "type": "boolean", - "desc": "是否启用此APP修改" - }, - { - "id": "@iRingo.TestFlight.Settings.CountryCode", - "name": "国家或地区代码", - "val": "US", - "type": "selects", - "desc": "不同国家或地区提供的内容或有差别", - "items": [ - { - "key": "AUTO", - "label": "自动(跟随地区检测结果)" - }, - { - "key": "CN", - "label": "中国大陆" - }, - { - "key": "HK", - "label": "香港" - }, - { - "key": "TW", - "label": "台湾" - }, - { - "key": "SG", - "label": "新加坡" - }, - { - "key": "US", - "label": "美国" - }, - { - "key": "JP", - "label": "日本" - }, - { - "key": "AU", - "label": "澳大利亚" - }, - { - "key": "GB", - "label": "英国" - }, - { - "key": "KR", - "label": "韩国" - }, - { - "key": "CA", - "label": "加拿大" - } - ] - }, - { - "id": "@iRingo.TestFlight.Settings.MultiAccount", - "name": "启用多账号支持", - "val": false, - "type": "boolean", - "desc": "是否启用多账号支持,会自动保存保存更新当前账号信息" - }, - { - "id": "@iRingo.TestFlight.Settings.Universal", - "name": "启用通用应用支持(测试)", - "val": false, - "type": "boolean", - "desc": "是否启用通用应用支持,解除TF中app的iOS/iPadOS/macOS(AppleSilicon)平台限制" - } - ], - "author": "@VirgilClyne", - "repo": "https://github.com/VirgilClyne/iRingo/", - "icons": [ - "https://raw.githubusercontent.com/Koolson/Qure/master/IconSet/TestFlight_2.png", - "https://raw.githubusercontent.com/Koolson/Qure/master/IconSet/Color/TestFlight_2.png" - ] - } - ] -} diff --git a/Archive/js/Apple_Weather.beta.js b/Archive/js/Apple_Weather.beta.js deleted file mode 100644 index 25ecbc7e9..000000000 --- a/Archive/js/Apple_Weather.beta.js +++ /dev/null @@ -1,388 +0,0 @@ -/* -README:https://github.com/VirgilClyne/iRingo -*/ - -const $ = new Env("Apple Weather v2.3.1-beta"); -const DataBase = { - "Weather":{"Switch":true,"Mode":"WAQI Public","Location":"Station","Verify":{"Mode":"Token","Content":null},"Scale":"EPA_NowCast.2201"}, - "Siri":{"Switch":true,"CountryCode":"TW","Domains":["web","itunes","app_store","movies","restaurants","maps"],"Functions":["flightutilities","lookup","mail","messages","news","safari","siri","spotlight","visualintelligence"],"Safari_Smart_History":true} -}; -const { url } = $request; -let { body } = $response; - -/***************** Processing *****************/ -!(async () => { - const { Settings } = await setENV("iRingo", url, DataBase); - let data = JSON.parse(body); - if (/\/(v1|v2)\/weather\//.test(url)) { - const Status = await getStatus(data); - if (Status == true) { - $.log(`🎉 ${$.name}, 需要替换AQI`, ""); - const Parameter = await getParameter(url); - if (Settings.Mode == "WAQI Public") { - $.log(`🚧 ${$.name}, 工作模式: waqi.info 公共API`, "") - var { Station, idx } = await WAQI("Nearest", { api: Parameter.ver, lat: Parameter.lat, lng: Parameter.lng }); - const Token = await WAQI("Token", { idx: idx }); - //var NOW = await WAQI("NOW", { token:Token, idx: idx }); - var AQI = await WAQI("AQI", { token: Token, idx: idx }); - } else if (Settings.Mode == "WAQI Private") { - $.log(`🚧 ${$.name}, 工作模式: waqi.info 私有API`, "") - const Token = Settings?.Verify?.Content; - if (Settings.Location == "Station") { - $.log(`🚧 ${$.name}, 定位精度: 观测站`, "") - var { Station, idx } = await WAQI("Nearest", { api: Parameter.ver, lat: Parameter.lat, lng: Parameter.lng }); - var AQI = await WAQI("StationFeed", { token: Token, idx: idx }); - } else if (Settings.Location == "City") { - $.log(`🚧 ${$.name}, 定位精度: 城市`, "") - var AQI = await WAQI("CityFeed", { token: Token, lat: Parameter.lat, lng: Parameter.lng }); - } - }; - data = await outputData(Parameter.ver, Station, AQI, data, Settings); - } else $.log(`🎉 ${$.name}, 无须替换, 跳过`, ""); - } else if (/\/(v1|v2)\/availability\//.test(url)) { - $.log(`🎉 ${$.name}, 可用性检查`, ""); - const availability = ["currentWeather", "forecastDaily", "forecastHourly", "history", "weatherChange", "forecastNextHour", "severeWeather", "airQuality"]; - data = Array.from(new Set([...data, ...availability])); - $.log(`🎉 ${$.name}, 功能列表`, JSON.stringify(data), ""); - }; - body = JSON.stringify(data); -})() - .catch((e) => $.logErr(e)) - .finally(() => $.done({ body })) - -/***************** Async Function *****************/ -/** - * Set Environment Variables - * @author VirgilClyne - * @param {String} name - Persistent Store Key - * @param {String} url - Request URL - * @param {Object} database - Default DataBase - * @return {Promise<*>} - */ -async function setENV(name, url, database) { - $.log(`⚠ ${$.name}, Set Environment Variables`, ""); - /***************** Platform *****************/ - const Platform = /weather-(.*)\.apple\.com/i.test(url) ? "Weather" - : /smoot\.apple\.(com|cn)/i.test(url) ? "Siri" - : /\.apple\.com/i.test(url) ? "Apple" - : "Apple" - $.log(`🚧 ${$.name}, Set Environment Variables`, `Platform: ${Platform}`, ""); - /***************** BoxJs *****************/ - // 包装为局部变量,用完释放内存 - // BoxJs的清空操作返回假值空字符串, 逻辑或操作符会在左侧操作数为假值时返回右侧操作数。 - let BoxJs = $.getjson(name, database) - $.log(`🚧 ${$.name}, Set Environment Variables`, `BoxJs类型: ${typeof BoxJs}`, `BoxJs内容: ${JSON.stringify(BoxJs)}`, ""); - /***************** Settings *****************/ - let Settings = BoxJs?.[Platform] || BoxJs?.Settings?.[Platform] || BoxJs?.Apple?.[Platform] || database[Platform]; - $.log(`🎉 ${$.name}, Set Environment Variables`, `Settings: ${typeof Settings}`, `Settings内容: ${JSON.stringify(Settings)}`, ""); - /***************** Argument *****************/ - if (typeof $argument != "undefined") { - $.log(`🎉 ${$.name}, $Argument`); - let arg = Object.fromEntries($argument.split("&").map((item) => item.split("="))); - $.log(JSON.stringify(arg)); - Object.assign(Settings, arg); - }; - /***************** Prase *****************/ - Settings.Switch = JSON.parse(Settings.Switch) // BoxJs字符串转Boolean - if (typeof Settings?.Domains == "string") Settings.Domains = Settings.Domains.split(",") // BoxJs字符串转数组 - if (typeof Settings?.Functions == "string") Settings.Functions = Settings.Functions.split(",") // BoxJs字符串转数组 - if (Settings?.Safari_Smart_History) Settings.Safari_Smart_History = JSON.parse(Settings.Safari_Smart_History) // BoxJs字符串转Boolean - $.log(`🎉 ${$.name}, Set Environment Variables`, `Settings: ${typeof Settings}`, `Settings内容: ${JSON.stringify(Settings)}`, ""); - return { Platform, Settings }; -}; - -/** - * Get Origin Parameter - * @author VirgilClyne - * @param {String} url - Request URL - * @return {Promise<*>} - */ -async function getParameter(url) { - const Regular = /^https?:\/\/(?weather-data|weather-data-origin)\.apple\.com\/(?v1|v2)\/weather\/(?[\w-_]+)\/(?-?\d+\.\d+)\/(?-?\d+\.\d+).*(?country=[A-Z]{2})?.*/i; - const Parameter = url.match(Regular).groups; - $.log(`🚧 ${$.name}`, `Parameter: ${JSON.stringify(Parameter)}`, ""); - return Parameter -}; - -/** - * Get AQI Source Status - * @author VirgilClyne - * @param {Object} data - Parsed response body JSON - * @return {Promise<*>} - */ -async function getStatus(data) { - const result = ["和风天气", "QWeather"].includes(data.air_quality?.metadata?.provider_name ?? data.airQuality?.metadata?.providerName ?? "QWeather"); - $.log(`🚧 ${$.name}, ${data.air_quality?.metadata?.provider_name ?? data.airQuality?.metadata?.providerName}`, ''); - return (result || false) -}; - -/** - * WAQI - * @author VirgilClyne - * @param {String} type - type - * @param {Object} input - verify - * @return {Promise<*>} - */ -async function WAQI(type = "", input = {}) { - $.log(`⚠ ${$.name}, WAQI`, `input: ${JSON.stringify(input)}`, ""); - // 构造请求 - let request = await GetRequest(type, input); - // 发送请求 - let output = await GetData(type, request); - $.log(`🚧 ${$.name}, WAQI`, `output: ${JSON.stringify(output)}`, ""); - return output - /***************** Fuctions *****************/ - async function GetRequest(type = "", input = { api: "v2", lat: 0, lng: 0, idx: 0, token: "na" }) { - $.log(`⚠ ${$.name}, Get WAQI Request, type: ${type}`, ""); - let request = { - "url": "https://api.waqi.info", - "headers": { - "Content-Type": "application/x-www-form-urlencoded", - "Origin": "https://waqi.info", - "User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 15_1_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.1 Mobile/15E148 Safari/604.1", - "Referer": "https://waqi.info/" - } - }; - if (type == "Nearest") { - $.log('获取最近站点'); - if (input.api == "v1") mapq = "mapq"; - else if (input.api == "v2") mapq = "mapq2"; - request.url = `${request.url}/${mapq}/nearest?n=1&geo=1/${input.lat}/${input.lng}`; - } else if (type == "Token") { - $.log('获取令牌'); - request.url = `${request.url}/api/token/${input.idx}` - } else if (type == "NOW") { - $.log('获取即时信息'); - request.url = `${request.url}/api/feed/@${input.idx}/now.json` - request.body = `token=${input.token}&id=${input.idx}` - } else if (type == "AQI") { - $.log('获取空气质量信息'); - request.url = `${request.url}/api/feed/@${input.idx}/aqi.json` - request.body = `token=${input.token}&id=${input.idx}` - } else if (type == "CityFeed") { - $.log('获取城市信息'); - request.url = `${request.url}/feed/geo:${input.lat};${input.lng}/?token=${input.token}` - } else if (type == "StationFeed") { - $.log('获取站点信息'); - request.url = `${request.url}/feed/@${input.idx}/?token=${input.token}` - } - //$.log(`🎉 ${$.name}, Get WAQI Request`, `request: ${JSON.stringify(request)}`, ""); - return request - }; - - function GetData(type, request) { - $.log(`⚠ ${$.name}, Get WAQI Data, type: ${type}`, ""); - return new Promise(resolve => { - if (type == "NOW" || type == "AQI") { - $.post(request, (error, response, data) => { - try { - if (error) throw new Error(error) - else if (data) { - const _data = JSON.parse(data) - // Get Nearest Observation Station AQI Data - // https://api.waqi.info/api/feed/@station.uid/now.json - // https://api.waqi.info/api/feed/@station.uid/aqi.json - if (type == "NOW" || type == "AQI") { - if (_data.rxs.status == "ok") { - if (_data.rxs.obs.some(o => o.status == 'ok')) { - let i = _data.rxs.obs.findIndex(o => o.status == 'ok') - let m = _data.rxs.obs.findIndex(o => o.msg) - //$.obs = _data.rxs.obs[i].msg; - if (i >= 0 && m >= 0) { - $.log(`🎉 ${$.name}, GetData:${type}完成`, `i = ${i}, m = ${m}`, '') - resolve(_data.rxs.obs[i].msg) - } else if (i < 0 || m < 0) { - $.log(`❗️ ${$.name}, GetData:${type}失败`, `OBS Get Error`, `i = ${i}, m = ${m}`, `空数据,浏览器访问 https://api.waqi.info/api/feed/@${idx}/aqi.json 查看获取结果`, '') - resolve(_data.rxs.obs[i].msg) - } - } else $.log(`❗️ ${$.name}, GetData:${type}失败`, `OBS Status Error`, `obs.status: ${_data.rxs.obs[0].status}`, `data = ${data}`, '') - } else $.log(`❗️ ${$.name}, GetData:${type}失败`, `RXS Status Error`, `status: ${_data.rxs.status}`, `data = ${data}`, '') - } - } else throw new Error(response); - } catch (e) { - $.logErr(`❗️${$.name}, GetData:${type}执行失败`, ` request = ${JSON.stringify(request)}`, ` error = ${error || e}`, `response = ${JSON.stringify(response)}`, `data = ${data}`, '') - } finally { - //$.log(`🚧 ${$.name}, GetData:${type}调试信息`, ` request = ${JSON.stringify(request)}`, `data = ${data}`, '') - resolve() - } - }) - } else { - $.get(request, (error, response, data) => { - try { - if (error) throw new Error(error) - else if (data) { - const _data = JSON.parse(data) - // Search Nearest Observation Station - // https://api.waqi.info/mapq/nearest/?n=1&geo=1/lat/lng - // https://api.waqi.info/mapq2/nearest?n=1&geo=1/lat/lng - if (type == "Nearest") { - // 空值合并运算符 - var station = _data?.data?.stations?.[0] ?? _data?.d?.[0] ?? null; - var idx = station?.idx ?? station?.x ?? null; - var name = station?.name ?? station?.u ?? station?.nna ?? station?.nlo ?? null; - var aqi = station?.aqi ?? station?.v ?? null; - var distance = station?.distance ?? station?.d ?? null; - //var country = station?.cca2 ?? station?.country ?? null; - $.log(`🎉 ${$.name}, GetData:${type}完成`, `idx: ${idx}`, `观测站: ${name}`, `AQI: ${aqi}`, `距离: ${distance}`, '') - resolve({ station, idx }) - } - // Get Nearest Observation Station Token - // https://api.waqi.info/api/token/station.uid - else if (type == "Token") { - var token = _data.rxs?.obs[0]?.msg?.token ?? "na" - $.log(`🎉 ${$.name}, GetData:${type}完成`, `token = ${token}`, '') - resolve(token) - } - // Geolocalized Feed - // https://aqicn.org/json-api/doc/#api-Geolocalized_Feed-GetGeolocFeed - // https://api.waqi.info/feed/geo::lat;:lng/?token=:token - else if (type == "CityFeed") { - var city = (_data.status == 'ok') ? _data?.data : null; - $.log(`🎉 ${$.name}, GetData:${type}完成`, `idx: ${city?.idx}`, `观测站: ${city?.city?.name}`, `AQI: ${city?.aqi}`, '') - resolve(city) - } - // Station Feed - // https://api.waqi.info/feed/@station.uid/?token=:token - else if (type == "StationFeed") { - var station = (_data.status == 'ok') ? _data?.data : null; - $.log(`🎉 ${$.name}, GetData:${type}完成`, `idx: ${station?.idx}`, `观测站: ${station?.city?.name}`, `AQI: ${station?.aqi}`, '') - resolve(station) - } - } else throw new Error(response); - } catch (e) { - $.logErr(`❗️${$.name}, GetData:${type}执行失败`, ` request = ${JSON.stringify(request)}`, ` error = ${error || e}`, `response = ${JSON.stringify(response)}`, `data = ${data}`, '') - } finally { - //$.log(`🚧 ${$.name}, GetData:${type}调试信息`, ` request = ${JSON.stringify(request)}`, `data = ${data}`, '') - resolve() - } - }) - }; - }); - }; -}; - -// Output Data -async function outputData(api, now, obs, data, Settings) { - // Input Data - let weather = data; - $.log(`⚠️ ${$.name}, ${outputData.name}检测`, `AQ data ${api}`, ''); - const AQIname = (api == "v1") ? "air_quality" - : (api == "v2") ? "airQuality" - : "airQuality"; - const unit = (api == "v1") ? "μg\/m3" - : (api == "v2") ? "microgramsPerM3" - : "microgramsPerM3"; - //创建对象 - if (!weather[`${AQIname}`]) { - $.log(`⚠️ ${$.name}, 没有空气质量数据, 创建`, ''); - weather[`${AQIname}`] = { - "isSignificant": true, // 重要/置顶 - "pollutants": {}, - "metadata": {}, - "name": "AirQuality", - }; - if (api == "v1") { - weather[`${AQIname}`].metadata.version = 1; - weather[`${AQIname}`].metadata.data_source = 0; //来自XX读数 0:监测站 1:模型 - } - else if (api == "v2") { - weather[`${AQIname}`].metadata.units = "m"; - weather[`${AQIname}`].metadata.version = 2; - weather[`${AQIname}`].sourceType = "station"; //station:监测站 modeled:模型 - } - }; - // 注入数据 - //条件运算符 & 可选链操作符 - weather[`${AQIname}`].source = obs?.city?.name ?? now?.name ?? now?.u ?? now?.nna ?? now?.nlo; - weather[`${AQIname}`].learnMoreURL = obs?.city?.url + `/${now?.country ?? now?.cca2}/m`.toLowerCase(); - weather[`${AQIname}`].primaryPollutant = switchPollutantsType(obs?.dominentpol ?? now?.pol); - weather[`${AQIname}`].pollutants.CO = { "name": "CO", "amount": obs.iaqi.co?.v || -1, "unit": unit }; - weather[`${AQIname}`].pollutants.NO = { "name": "NO", "amount": obs.iaqi.no?.v || -1, "unit": unit }; - weather[`${AQIname}`].pollutants.NO2 = { "name": "NO2", "amount": obs.iaqi.no2?.v || -1, "unit": unit }; - weather[`${AQIname}`].pollutants.SO2 = { "name": "SO2", "amount": obs.iaqi.so2?.v || -1, "unit": unit }; - weather[`${AQIname}`].pollutants.OZONE = { "name": "OZONE", "amount": obs.iaqi.o3?.v || -1, "unit": unit }; - weather[`${AQIname}`].pollutants.NOX = { "name": "NOX", "amount": obs.iaqi.nox?.v || -1, "unit": unit }; - weather[`${AQIname}`].pollutants["PM2.5"] = { "name": "PM2.5", "amount": obs.iaqi.pm25?.v || -1, "unit": unit }; - weather[`${AQIname}`].pollutants.PM10 = { "name": "PM10", "amount": obs.iaqi.pm10?.v || -1, "unit": unit }; - weather[`${AQIname}`].metadata.longitude = obs?.city?.geo?.[0] ?? now?.geo?.[0]; - weather[`${AQIname}`].metadata.latitude = obs?.city?.geo?.[1] ?? now?.geo?.[1]; - weather[`${AQIname}`].metadata.language = weather?.[`${AQIname}`]?.metadata?.language ?? weather?.currentWeather?.metadata?.language ?? weather?.current_observations?.metadata?.language; - if (api == "v1") { - weather.air_quality.airQualityIndex = obs?.aqi ?? now?.aqi ?? now?.v; - weather.air_quality.airQualityScale = Settings?.Scale || "EPA_NowCast.2201"; - weather.air_quality.airQualityCategoryIndex = classifyAirQualityLevel(obs?.aqi ?? now?.aqi ?? now?.v); - weather.air_quality.metadata.reported_time = convertTime(new Date(obs?.time?.v ?? now?.t), 'remain', api); - //weather.air_quality.metadata.provider_name = obs?.attributions?.[obs.attributions.length - 1]?.name; - weather.air_quality.metadata.provider_name = obs?.attributions?.[0]?.name; - weather.air_quality.metadata.expire_time = convertTime(new Date(obs?.time?.v ?? now?.t), 'add-1h-floor', api); - weather.air_quality.metadata.provider_logo = "https:\/\/waqi.info\/images\/logo.png"; - weather.air_quality.metadata.read_time = convertTime(new Date(), 'remain', api); - } else if (api == "v2") { - weather.airQuality.index = obs?.aqi ?? now?.aqi ?? now?.v; - weather.airQuality.scale = Settings?.Scale || "EPA_NowCast.2201"; - weather.airQuality.categoryIndex = classifyAirQualityLevel(obs?.aqi ?? now?.aqi ?? now?.v); - weather.airQuality.metadata.providerLogo = "https:\/\/waqi.info\/images\/logo.png"; - //weather.airQuality.metadata.providerName = obs?.attributions?.[obs.attributions.length - 1]?.name; - weather.airQuality.metadata.providerName = obs?.attributions?.[0]?.name; - weather.airQuality.metadata.expireTime = convertTime(new Date(obs?.time?.iso ?? now?.utime), 'add-1h-floor', api); - weather.airQuality.metadata.reportedTime = convertTime(new Date(obs?.time?.iso ?? now?.utime), 'remain', api); - weather.airQuality.metadata.readTime = convertTime(new Date(), 'remain', api); - } - $.log(`🎉 ${$.name}, ${outputData.name}完成`, ''); - return weather -}; - -/***************** Fuctions *****************/ -// Function 1 -// Switch Pollutants Type -// https://github.com/Hackl0us/SS-Rule-Snippet/blob/master/Scripts/Surge/weather_aqi_us/iOS15_Weather_AQI_US.js -function switchPollutantsType(pollutant) { - const pollutant_map = { "co": "CO", "no": "NO", "no2": "NO2", "so2": "SO2", "o3": "OZONE", "nox": "NOX", "pm25": "PM2.5", "pm10": "PM10" }; - return pollutant_map?.[pollutant] ?? "OTHER"; -}; - -// Function 2 -// Convert Time Format -// https://github.com/Hackl0us/SS-Rule-Snippet/blob/master/Scripts/Surge/weather_aqi_us/iOS15_Weather_AQI_US.js -function convertTime(time, action, api) { - switch (action) { - case 'remain': - time.setMilliseconds(0); - break; - case 'add-1h-floor': - time.setHours(time.getHours() + 1); - time.setMinutes(0, 0, 0); - break; - default: - $.log(`⚠️ ${$.name}, Time Converter, Error`, `time: ${time}`, ''); - } - if (api == "v1") { - let timeString = time.getTime() / 1000; - return timeString; - } - if (api == "v2") { - let timeString = time.toISOString().split('.')[0] + 'Z'; - return timeString; - } -}; - -// Function 3 -// Calculate Air Quality Level -// https://github.com/Hackl0us/SS-Rule-Snippet/blob/master/Scripts/Surge/weather_aqi_us/iOS15_Weather_AQI_US.js -function classifyAirQualityLevel(aqiIndex) { - if (aqiIndex >= 0 && aqiIndex <= 50) return 1; - else if (aqiIndex >= 51 && aqiIndex <= 100) return 2; - else if (aqiIndex >= 101 && aqiIndex <= 150) return 3; - else if (aqiIndex >= 151 && aqiIndex <= 200) return 4; - else if (aqiIndex >= 201 && aqiIndex <= 300) return 5; - else if (aqiIndex >= 301 && aqiIndex <= 500) return 6; - else { - $.log(`⚠️ ${$.name}, classifyAirQualityLevel, Error`, `aqiIndex: ${aqiIndex}`, ''); - return 0; - } -}; - -/***************** Env *****************/ -// prettier-ignore -// https://github.com/chavyleung/scripts/blob/master/Env.min.js -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}isStash(){return"undefined"!=typeof $environment&&$environment["stash-version"]}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,i=t["update-pasteboard"]||t.updatePasteboard;return{"open-url":e,"media-url":s,"update-pasteboard":i}}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)} diff --git a/Archive/js/Apple_Weather.js b/Archive/js/Apple_Weather.js deleted file mode 100644 index 71abc4952..000000000 --- a/Archive/js/Apple_Weather.js +++ /dev/null @@ -1,358 +0,0 @@ -/* -README:https://github.com/VirgilClyne/iRingo -*/ - -const $ = new Env("Apple Weather v2.3.1"); -const DataBase = { - "Weather":{"Switch":true,"Mode":"WAQI Public","Location":"Station","Verify":{"Mode":"Token","Content":null},"Scale":"EPA_NowCast.2201"}, - "Siri":{"Switch":true,"CountryCode":"TW","Domains":["web","itunes","app_store","movies","restaurants","maps"],"Functions":["flightutilities","lookup","mail","messages","news","safari","siri","spotlight","visualintelligence"],"Safari_Smart_History":true} -}; -const { url } = $request; -let { body } = $response; - -/***************** Processing *****************/ -!(async () => { - const { Settings } = await setENV("iRingo", url, DataBase); - let data = JSON.parse(body); - if (/\/(v1|v2)\/weather\//.test(url)) { - const Status = await getStatus(data); - if (Status == true) { - $.log(`🎉 ${$.name}, 需要替换AQI`, ""); - const Parameter = await getParameter(url); - if (Settings.Mode == "WAQI Public") { - $.log(`🚧 ${$.name}, 工作模式: waqi.info 公共API`, "") - var { Station, idx } = await WAQI("Nearest", { api: Parameter.ver, lat: Parameter.lat, lng: Parameter.lng }); - const Token = await WAQI("Token", { idx: idx }); - //var NOW = await WAQI("NOW", { token:Token, idx: idx }); - var AQI = await WAQI("AQI", { token: Token, idx: idx }); - } else if (Settings.Mode == "WAQI Private") { - $.log(`🚧 ${$.name}, 工作模式: waqi.info 私有API`, "") - const Token = Settings?.Verify?.Content; - if (Settings.Location == "Station") { - $.log(`🚧 ${$.name}, 定位精度: 观测站`, "") - var { Station, idx } = await WAQI("Nearest", { api: Parameter.ver, lat: Parameter.lat, lng: Parameter.lng }); - var AQI = await WAQI("StationFeed", { token: Token, idx: idx }); - } else if (Settings.Location == "City") { - $.log(`🚧 ${$.name}, 定位精度: 城市`, "") - var AQI = await WAQI("CityFeed", { token: Token, lat: Parameter.lat, lng: Parameter.lng }); - } - }; - data = await outputData(Parameter.ver, Station, AQI, data, Settings); - } else $.log(`🎉 ${$.name}, 无须替换, 跳过`, ""); - } else if (/\/(v1|v2)\/availability\//.test(url)) { - $.log(`🎉 ${$.name}, 可用性检查`, ""); - const availability = ["currentWeather", "forecastDaily", "forecastHourly", "history", "weatherChange", "forecastNextHour", "severeWeather", "airQuality"]; - data = Array.from(new Set([...data, ...availability])); - $.log(`🎉 ${$.name}, 功能列表`, JSON.stringify(data), ""); - }; - body = JSON.stringify(data); -})() - .catch((e) => $.logErr(e)) - .finally(() => $.done({ body })) - -/***************** Async Function *****************/ -/** - * Set Environment Variables - * @author VirgilClyne - * @param {String} t - Persistent Store Key - * @param {String} i - Request URL - * @param {Object} e - Default DataBase - * @return {Promise<*>} - */ -async function setENV(t,i,e){const s=/weather-(.*)\.apple\.com/i.test(i)?"Weather":/smoot\.apple\.(com|cn)/i.test(i)?"Siri":(/\.apple\.com/i.test(i),"Apple");let n=$.getjson(t,e),a=n?.[s]||n?.Settings?.[s]||n?.Apple?.[s]||e[s];if("undefined"!=typeof $argument){let t=Object.fromEntries($argument.split("&").map((t=>t.split("="))));Object.assign(a,t)}return a.Switch=JSON.parse(a.Switch),"string"==typeof a?.Domains&&(a.Domains=a.Domains.split(",")),"string"==typeof a?.Functions&&(a.Functions=a.Functions.split(",")),a?.Safari_Smart_History&&(a.Safari_Smart_History=JSON.parse(a.Safari_Smart_History)),{Platform:s,Settings:a}} - -/** - * Get Origin Parameter - * @author VirgilClyne - * @param {String} url - Request URL - * @return {Promise<*>} - */ -async function getParameter(url) { - const Regular = /^https?:\/\/(?weather-data|weather-data-origin)\.apple\.com\/(?v1|v2)\/weather\/(?[\w-_]+)\/(?-?\d+\.\d+)\/(?-?\d+\.\d+).*(?country=[A-Z]{2})?.*/i; - const Parameter = url.match(Regular).groups; - $.log(`🚧 ${$.name}`, `Parameter: ${JSON.stringify(Parameter)}`, ""); - return Parameter -}; - -/** - * Get AQI Source Status - * @author VirgilClyne - * @param {Object} data - Parsed response body JSON - * @return {Promise<*>} - */ -async function getStatus(data) { - const result = ["和风天气", "QWeather"].includes(data.air_quality?.metadata?.provider_name ?? data.airQuality?.metadata?.providerName ?? "QWeather"); - $.log(`🚧 ${$.name}, ${data.air_quality?.metadata?.provider_name ?? data.airQuality?.metadata?.providerName}`, ''); - return (result || false) -}; - -/** - * WAQI - * @author VirgilClyne - * @param {String} type - type - * @param {Object} input - verify - * @return {Promise<*>} - */ -async function WAQI(type = "", input = {}) { - //$.log(`⚠ ${$.name}, WAQI`, `input: ${JSON.stringify(input)}`, ""); - // 构造请求 - let request = await GetRequest(type, input); - // 发送请求 - let output = await GetData(type, request); - //$.log(`🚧 ${$.name}, WAQI`, `output: ${JSON.stringify(output)}`, ""); - return output - /***************** Fuctions *****************/ - async function GetRequest(type = "", input = { api: "v2", lat: 0, lng: 0, idx: 0, token: "na" }) { - $.log(`⚠ ${$.name}, Get WAQI Request, type: ${type}`, ""); - let request = { - "url": "https://api.waqi.info", - "headers": { - "Content-Type": "application/x-www-form-urlencoded", - "Origin": "https://waqi.info", - "User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 15_1_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.1 Mobile/15E148 Safari/604.1", - "Referer": "https://waqi.info/" - } - }; - if (type == "Nearest") { - $.log('获取最近站点'); - if (input.api == "v1") mapq = "mapq"; - else if (input.api == "v2") mapq = "mapq2"; - request.url = `${request.url}/${mapq}/nearest?n=1&geo=1/${input.lat}/${input.lng}`; - } else if (type == "Token") { - $.log('获取令牌'); - request.url = `${request.url}/api/token/${input.idx}` - } else if (type == "NOW") { - $.log('获取即时信息'); - request.url = `${request.url}/api/feed/@${input.idx}/now.json` - request.body = `token=${input.token}&id=${input.idx}` - } else if (type == "AQI") { - $.log('获取空气质量信息'); - request.url = `${request.url}/api/feed/@${input.idx}/aqi.json` - request.body = `token=${input.token}&id=${input.idx}` - } else if (type == "CityFeed") { - $.log('获取城市信息'); - request.url = `${request.url}/feed/geo:${input.lat};${input.lng}/?token=${input.token}` - } else if (type == "StationFeed") { - $.log('获取站点信息'); - request.url = `${request.url}/feed/@${input.idx}/?token=${input.token}` - } - //$.log(`🎉 ${$.name}, Get WAQI Request`, `request: ${JSON.stringify(request)}`, ""); - return request - }; - - function GetData(type, request) { - $.log(`⚠ ${$.name}, Get WAQI Data, type: ${type}`, ""); - return new Promise(resolve => { - if (type == "NOW" || type == "AQI") { - $.post(request, (error, response, data) => { - try { - if (error) throw new Error(error) - else if (data) { - const _data = JSON.parse(data) - // Get Nearest Observation Station AQI Data - // https://api.waqi.info/api/feed/@station.uid/now.json - // https://api.waqi.info/api/feed/@station.uid/aqi.json - if (type == "NOW" || type == "AQI") { - if (_data.rxs.status == "ok") { - if (_data.rxs.obs.some(o => o.status == 'ok')) { - let i = _data.rxs.obs.findIndex(o => o.status == 'ok') - let m = _data.rxs.obs.findIndex(o => o.msg) - //$.obs = _data.rxs.obs[i].msg; - if (i >= 0 && m >= 0) { - $.log(`🎉 ${$.name}, GetData:${type}完成`, `i = ${i}, m = ${m}`, '') - resolve(_data.rxs.obs[i].msg) - } else if (i < 0 || m < 0) { - $.log(`❗️ ${$.name}, GetData:${type}失败`, `OBS Get Error`, `i = ${i}, m = ${m}`, `空数据,浏览器访问 https://api.waqi.info/api/feed/@${idx}/aqi.json 查看获取结果`, '') - resolve(_data.rxs.obs[i].msg) - } - } else $.log(`❗️ ${$.name}, GetData:${type}失败`, `OBS Status Error`, `obs.status: ${_data.rxs.obs[0].status}`, `data = ${data}`, '') - } else $.log(`❗️ ${$.name}, GetData:${type}失败`, `RXS Status Error`, `status: ${_data.rxs.status}`, `data = ${data}`, '') - } - } else throw new Error(response); - } catch (e) { - $.logErr(`❗️${$.name}, GetData:${type}执行失败`, ` request = ${JSON.stringify(request)}`, ` error = ${error || e}`, `response = ${JSON.stringify(response)}`, `data = ${data}`, '') - } finally { - //$.log(`🚧 ${$.name}, GetData:${type}调试信息`, ` request = ${JSON.stringify(request)}`, `data = ${data}`, '') - resolve() - } - }) - } else { - $.get(request, (error, response, data) => { - try { - if (error) throw new Error(error) - else if (data) { - const _data = JSON.parse(data) - // Search Nearest Observation Station - // https://api.waqi.info/mapq/nearest/?n=1&geo=1/lat/lng - // https://api.waqi.info/mapq2/nearest?n=1&geo=1/lat/lng - if (type == "Nearest") { - // 空值合并运算符 - var station = _data?.data?.stations?.[0] ?? _data?.d?.[0] ?? null; - var idx = station?.idx ?? station?.x ?? null; - var name = station?.name ?? station?.u ?? station?.nna ?? station?.nlo ?? null; - var aqi = station?.aqi ?? station?.v ?? null; - var distance = station?.distance ?? station?.d ?? null; - //var country = station?.cca2 ?? station?.country ?? null; - $.log(`🎉 ${$.name}, GetData:${type}完成`, `idx: ${idx}`, `观测站: ${name}`, `AQI: ${aqi}`, `距离: ${distance}`, '') - resolve({ station, idx }) - } - // Get Nearest Observation Station Token - // https://api.waqi.info/api/token/station.uid - else if (type == "Token") { - var token = _data.rxs?.obs[0]?.msg?.token ?? "na" - $.log(`🎉 ${$.name}, GetData:${type}完成`, `token = ${token}`, '') - resolve(token) - } - // Geolocalized Feed - // https://aqicn.org/json-api/doc/#api-Geolocalized_Feed-GetGeolocFeed - // https://api.waqi.info/feed/geo::lat;:lng/?token=:token - else if (type == "CityFeed") { - var city = (_data.status == 'ok') ? _data?.data : null; - $.log(`🎉 ${$.name}, GetData:${type}完成`, `idx: ${city?.idx}`, `观测站: ${city?.city?.name}`, `AQI: ${city?.aqi}`, '') - resolve(city) - } - // Station Feed - // https://api.waqi.info/feed/@station.uid/?token=:token - else if (type == "StationFeed") { - var station = (_data.status == 'ok') ? _data?.data : null; - $.log(`🎉 ${$.name}, GetData:${type}完成`, `idx: ${station?.idx}`, `观测站: ${station?.city?.name}`, `AQI: ${station?.aqi}`, '') - resolve(station) - } - } else throw new Error(response); - } catch (e) { - $.logErr(`❗️${$.name}, GetData:${type}执行失败`, ` request = ${JSON.stringify(request)}`, ` error = ${error || e}`, `response = ${JSON.stringify(response)}`, `data = ${data}`, '') - } finally { - //$.log(`🚧 ${$.name}, GetData:${type}调试信息`, ` request = ${JSON.stringify(request)}`, `data = ${data}`, '') - resolve() - } - }) - }; - }); - }; -}; - -// Output Data -async function outputData(api, now, obs, data, Settings) { - // Input Data - let weather = data; - $.log(`⚠️ ${$.name}, ${outputData.name}检测`, `AQ data ${api}`, ''); - const AQIname = (api == "v1") ? "air_quality" - : (api == "v2") ? "airQuality" - : "airQuality"; - const unit = (api == "v1") ? "μg\/m3" - : (api == "v2") ? "microgramsPerM3" - : "microgramsPerM3"; - //创建对象 - if (!weather[`${AQIname}`]) { - $.log(`⚠️ ${$.name}, 没有空气质量数据, 创建`, ''); - weather[`${AQIname}`] = { - "isSignificant": true, // 重要/置顶 - "pollutants": {}, - "metadata": {}, - "name": "AirQuality", - }; - if (api == "v1") { - weather[`${AQIname}`].metadata.version = 1; - weather[`${AQIname}`].metadata.data_source = 0; //来自XX读数 0:监测站 1:模型 - } - else if (api == "v2") { - weather[`${AQIname}`].metadata.units = "m"; - weather[`${AQIname}`].metadata.version = 2; - weather[`${AQIname}`].sourceType = "station"; //station:监测站 modeled:模型 - } - }; - // 注入数据 - //条件运算符 & 可选链操作符 - weather[`${AQIname}`].source = obs?.city?.name ?? now?.name ?? now?.u ?? now?.nna ?? now?.nlo; - weather[`${AQIname}`].learnMoreURL = obs?.city?.url + `/${now?.country ?? now?.cca2}/m`.toLowerCase(); - weather[`${AQIname}`].primaryPollutant = switchPollutantsType(obs?.dominentpol ?? now?.pol); - weather[`${AQIname}`].pollutants.CO = { "name": "CO", "amount": obs.iaqi.co?.v || -1, "unit": unit }; - weather[`${AQIname}`].pollutants.NO = { "name": "NO", "amount": obs.iaqi.no?.v || -1, "unit": unit }; - weather[`${AQIname}`].pollutants.NO2 = { "name": "NO2", "amount": obs.iaqi.no2?.v || -1, "unit": unit }; - weather[`${AQIname}`].pollutants.SO2 = { "name": "SO2", "amount": obs.iaqi.so2?.v || -1, "unit": unit }; - weather[`${AQIname}`].pollutants.OZONE = { "name": "OZONE", "amount": obs.iaqi.o3?.v || -1, "unit": unit }; - weather[`${AQIname}`].pollutants.NOX = { "name": "NOX", "amount": obs.iaqi.nox?.v || -1, "unit": unit }; - weather[`${AQIname}`].pollutants["PM2.5"] = { "name": "PM2.5", "amount": obs.iaqi.pm25?.v || -1, "unit": unit }; - weather[`${AQIname}`].pollutants.PM10 = { "name": "PM10", "amount": obs.iaqi.pm10?.v || -1, "unit": unit }; - weather[`${AQIname}`].metadata.longitude = obs?.city?.geo?.[0] ?? now?.geo?.[0]; - weather[`${AQIname}`].metadata.latitude = obs?.city?.geo?.[1] ?? now?.geo?.[1]; - weather[`${AQIname}`].metadata.language = weather?.[`${AQIname}`]?.metadata?.language ?? weather?.currentWeather?.metadata?.language ?? weather?.current_observations?.metadata?.language; - if (api == "v1") { - weather.air_quality.airQualityIndex = obs?.aqi ?? now?.aqi ?? now?.v; - weather.air_quality.airQualityScale = Settings?.Scale || "EPA_NowCast.2201"; - weather.air_quality.airQualityCategoryIndex = classifyAirQualityLevel(obs?.aqi ?? now?.aqi ?? now?.v); - weather.air_quality.metadata.reported_time = convertTime(new Date(obs?.time?.v ?? now?.t), 'remain', api); - //weather.air_quality.metadata.provider_name = obs?.attributions?.[obs.attributions.length - 1]?.name; - weather.air_quality.metadata.provider_name = obs?.attributions?.[0]?.name; - weather.air_quality.metadata.expire_time = convertTime(new Date(obs?.time?.v ?? now?.t), 'add-1h-floor', api); - weather.air_quality.metadata.provider_logo = "https:\/\/waqi.info\/images\/logo.png"; - weather.air_quality.metadata.read_time = convertTime(new Date(), 'remain', api); - } else if (api == "v2") { - weather.airQuality.index = obs?.aqi ?? now?.aqi ?? now?.v; - weather.airQuality.scale = Settings?.Scale || "EPA_NowCast.2201"; - weather.airQuality.categoryIndex = classifyAirQualityLevel(obs?.aqi ?? now?.aqi ?? now?.v); - weather.airQuality.metadata.providerLogo = "https:\/\/waqi.info\/images\/logo.png"; - //weather.airQuality.metadata.providerName = obs?.attributions?.[obs.attributions.length - 1]?.name; - weather.airQuality.metadata.providerName = obs?.attributions?.[0]?.name; - weather.airQuality.metadata.expireTime = convertTime(new Date(obs?.time?.iso ?? now?.utime), 'add-1h-floor', api); - weather.airQuality.metadata.reportedTime = convertTime(new Date(obs?.time?.iso ?? now?.utime), 'remain', api); - weather.airQuality.metadata.readTime = convertTime(new Date(), 'remain', api); - } - $.log(`🎉 ${$.name}, ${outputData.name}完成`, ''); - return weather -}; - -/***************** Fuctions *****************/ -// Function 1 -// Switch Pollutants Type -// https://github.com/Hackl0us/SS-Rule-Snippet/blob/master/Scripts/Surge/weather_aqi_us/iOS15_Weather_AQI_US.js -function switchPollutantsType(pollutant) { - const pollutant_map = { "co": "CO", "no": "NO", "no2": "NO2", "so2": "SO2", "o3": "OZONE", "nox": "NOX", "pm25": "PM2.5", "pm10": "PM10" }; - return pollutant_map?.[pollutant] ?? "OTHER"; -}; - -// Function 2 -// Convert Time Format -// https://github.com/Hackl0us/SS-Rule-Snippet/blob/master/Scripts/Surge/weather_aqi_us/iOS15_Weather_AQI_US.js -function convertTime(time, action, api) { - switch (action) { - case 'remain': - time.setMilliseconds(0); - break; - case 'add-1h-floor': - time.setHours(time.getHours() + 1); - time.setMinutes(0, 0, 0); - break; - default: - $.log(`⚠️ ${$.name}, Time Converter, Error`, `time: ${time}`, ''); - } - if (api == "v1") { - let timeString = time.getTime() / 1000; - return timeString; - } - if (api == "v2") { - let timeString = time.toISOString().split('.')[0] + 'Z'; - return timeString; - } -}; - -// Function 3 -// Calculate Air Quality Level -// https://github.com/Hackl0us/SS-Rule-Snippet/blob/master/Scripts/Surge/weather_aqi_us/iOS15_Weather_AQI_US.js -function classifyAirQualityLevel(aqiIndex) { - if (aqiIndex >= 0 && aqiIndex <= 50) return 1; - else if (aqiIndex >= 51 && aqiIndex <= 100) return 2; - else if (aqiIndex >= 101 && aqiIndex <= 150) return 3; - else if (aqiIndex >= 151 && aqiIndex <= 200) return 4; - else if (aqiIndex >= 201 && aqiIndex <= 300) return 5; - else if (aqiIndex >= 301 && aqiIndex <= 500) return 6; - else { - $.log(`⚠️ ${$.name}, classifyAirQualityLevel, Error`, `aqiIndex: ${aqiIndex}`, ''); - return 0; - } -}; - -/***************** Env *****************/ -// prettier-ignore -// https://github.com/chavyleung/scripts/blob/master/Env.min.js -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}isStash(){return"undefined"!=typeof $environment&&$environment["stash-version"]}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,i=t["update-pasteboard"]||t.updatePasteboard;return{"open-url":e,"media-url":s,"update-pasteboard":i}}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)} diff --git a/Archive/js/Apple_Weather.v1.0.0.js b/Archive/js/Apple_Weather.v1.0.0.js deleted file mode 100644 index 99060e6f2..000000000 --- a/Archive/js/Apple_Weather.v1.0.0.js +++ /dev/null @@ -1,373 +0,0 @@ -/* -README:https://github.com/VirgilClyne/iRingo -*/ - -const $ = new Env('Apple_Weather'); -var url = $request.url; -$.VAL_headers = { - 'Content-Type': `application/x-www-form-urlencoded`, - 'Origin': `https://waqi.info`, - 'User-Agent': `Mozilla/5.0 (iPhone; CPU iPhone OS 15_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.1 Mobile/15E148 Safari/605.1.15`, - 'Referer': `https://waqi.info/`, -} - -!(async () => { - await getOrigin(url) - await getAQIstatus($.apiVer, $response.body) - await getNearest($.apiVer, $.lat, $.lng) - await getToken($.idx) - await getStation($.token, $.idx) - await outputData($.apiVer, $.stations, $.obs) -})() - .catch((e) => $.logErr(e)) - .finally(() => $.done()) - -// Step 1 -// Get Origin Parameter -function getOrigin(url) { - return new Promise((resove) => { - const Regular = /^https?:\/\/(weather-data|weather-data-origin)\.apple\.com\/(v1|v2)\/weather\/([\w-_]+)\/(-?\d+\.\d+)\/(-?\d+\.\d+).*(country=[A-Z]{2})?.*/; - try { - [$.url, $.dataServer, $.apiVer, $.language, $.lat, $.lng, $.countryCode] = url.match(Regular); - $.log(`🎉 ${$.name}, getOrigin, Finish`, $.url, `${$.dataServer}, ${$.apiVer}, ${$.language}, ${$.lat}, ${$.lng}, ${$.countryCode}`, '') - } catch (e) { - $.log(`❗️ ${$.name}, getAQIstatus, Failure`, ` error = ${e}`, '') - } finally { - $.log(`🎉 ${$.name}, getOrigin, Finish`, '') - resove() - } - }) -}; - -// Step 2 -// Get AQI Source Status -function getAQIstatus(api, body) { - return new Promise((resove) => { - const weather = JSON.parse(body); - const provider = ['和风天气', 'QWeather'] - try { - if (api == 'v1' && weather.air_quality) { - $.log(`⚠️ ${$.name}, getAQIstatus, AQ data ${api}`, ''); - if (provider.includes(weather.air_quality.metadata.provider_name)) { - $.log(`🎉 ${$.name}, getAQIstatus, Continue`, `${weather.air_quality.metadata.provider_name}`, '') - resove() - } else { - $.log(`⚠️ ${$.name}, getAQIstatus, Abort`, `${weather.air_quality.metadata.provider_name}`, ''); - $.done() - } - } else if (api == 'v2' && weather.airQuality) { - $.log(`⚠️ ${$.name}, getAQIstatus, AQ data ${api}`, ''); - if (provider.includes(weather.airQuality.metadata.providerName)) { - $.log(`🎉 ${$.name}, getAQIstatus, Continue`, `${weather.airQuality.metadata.providerName}`, '') - resove() - } else { - $.log(`⚠️ ${$.name}, getAQIstatus, Abort`, `${weather.airQuality.metadata.providerName}`, ''); - $.done() - } - } else { - $.log(`🎉 ${$.name}, getAQIstatus, non-existent AQI data, Continue`, '') - resove() - } - } catch (e) { - $.log(`❗️ ${$.name}, getAQIstatus, Failure`, ` error = ${e}`, '') - } finally { - $.log(`🎉 ${$.name}, getAQIstatus, Finish`, '') - resove() - } - }) -}; - - - -// Step 3 -// Search Nearest Observation Station -// https://api.waqi.info/mapq/nearest/?n=1&geo=1/lat/lng -// https://api.waqi.info/mapq2/nearest?n=1&geo=1/lat/lng -function getNearest(api, lat, lng) { - return new Promise((resove) => { - if (api == "v1") mapq = 'mapq' - else if (api == "v2") mapq = 'mapq2' - const url = { url: `https://api.waqi.info/${mapq}/nearest?n=1&geo=1/${lat}/${lng}`, headers: $.VAL_headers } - $.get(url, (error, response, data) => { - try { - const _data = JSON.parse(data) - if (error) throw new Error(error) - else if (api == "v1" && _data.d[0]) { - $.stations = _data.d[0]; - $.idx = $.stations.x; - $.country = $.stations.cca2 - resove() - } else if (api == "v2" && _data.status == "ok") { - $.stations = _data.data.stations[0]; - $.idx = $.stations.idx; - $.country = $.stations.country - resove() - } else { - $.log(`❗️ ${$.name}, getNearest, Error, api: ${api}`, `station: ${_data.d[0]}`, `data = ${data}`, '') - $.done() - } - } catch (e) { - $.log(`❗️ ${$.name}, getNearest, Failure`, ` error = ${error || e}`, `response = ${JSON.stringify(response)}`, `data = ${data}`, '') - } finally { - $.log(`🎉 ${$.name}, getNearest, Finish`, `data = ${data}`, '') - resove() - } - }) - }) -}; - -// Step 4 -// Get Nearest Observation Station Token -// https://api.waqi.info/api/token/station.uid -function getToken(idx) { - return new Promise((resove) => { - const url = { url: `https://api.waqi.info/api/token/${idx}`, headers: $.VAL_headers } - $.get(url, (error, response, data) => { - try { - const _data = JSON.parse(data) - if (error) throw new Error(error) - else if (_data.rxs.status == "ok") { - $.token = _data.rxs.obs[0].msg.token; - resove() - } else { - $.log(`⚠️ ${$.name}, getToken, Error, status: ${_data.rxs.status}`, `data = ${data}`, '') - $.token = "na"; - resove() - } - } catch (e) { - $.log(`❗️ ${$.name}, getToken, Failure`, ` error = ${error || e}`, `response = ${JSON.stringify(response)}`, `data = ${data}`, '') - } finally { - $.log(`🎉 ${$.name}, getToken, Finish`, '') - resove() - } - }); - }) -}; - -// Step 5 -// Get Nearest Observation Station AQI Data -// https://api.waqi.info/api/feed/@station.uid/now.json -// https://api.waqi.info/api/feed/@station.uid/aqi.json -function getStation(token = "na", idx) { - return new Promise((resove) => { - const url = { url: `https://api.waqi.info/api/feed/@${idx}/aqi.json`, body: `token=${token}&id=${idx}`, headers: $.VAL_headers } - $.post(url, (error, response, data) => { - try { - const _data = JSON.parse(data) - if (error) throw new Error(error) - else if (_data.rxs.status == "ok") { - if (_data.rxs.obs.some(o => o.status == 'ok')) { - let i = _data.rxs.obs.findIndex(o => o.status == 'ok') - let m = _data.rxs.obs.findIndex(o => o.msg) - $.obs = _data.rxs.obs[i].msg; - if (i >= 0 && m >= 0) $.log(`🎉 ${$.name}, getStation, i = ${i}, m = ${m}`, '') - else if (i < 0 || m < 0) $.log(`❗️ ${$.name}, getStation`, `OBS Get Error`, `i = ${i}, m = ${m}`, `空数据,浏览器访问 https://api.waqi.info/api/feed/@${idx}/aqi.json 查看获取结果`, '') - resove() - } else { - $.log(`❗️ ${$.name}, getStation`, `OBS Status Error`, `obs.status: ${_data.rxs.obs[0].status}`, `data = ${data}`, '') - resove() - } - } else { - $.log(`❗️ ${$.name}, getStation`, `RXS Status Error`, `status: ${_data.rxs.status}`, `data = ${data}`, '') - resove() - } - } catch (e) { - $.log(`❗️ ${$.name}, getStation执行失败!`, `浏览器访问 https://api.waqi.info/api/feed/@${idx}/aqi.json 看看是不是空数据`, `原因:网络不畅或者获取太频繁导致被封`, `error = ${error || e}`, `response = ${JSON.stringify(response)}`, `data = ${data}`, '') - } finally { - $.log(`🎉 ${$.name}, getStation, Finish`, '') - resove() - } - }) - }) -}; - -// Step 6 -// Output Data -function outputData(api, stations, obs) { - return new Promise((resove) => { - // Input Data - let body = $response.body - let weather = JSON.parse(body); - try { - if (api == "v1") { - $.log(`⚠️ ${$.name}, outputData, Detect`, `data ${api}`, ''); - if (!weather.air_quality) { - $.log(`⚠️ ${$.name}, non-existent Air Quality data, creating`, ''); - weather.air_quality = { - "isSignificant": true, //重要/置顶 - "pollutants": {}, - "metadata": { - "version": 1, - "data_source": 0, //来自XX读数 0:监测站 1:模型 - }, - "name": "AirQuality", - }; - }; - if (obs?.aqi) { // From Observation Station - weather.air_quality.source = obs.city.name; - weather.air_quality.learnMoreURL = obs.city.url + `/${$.country}/m`.toLowerCase(); - weather.air_quality.airQualityIndex = obs.aqi; - weather.air_quality.airQualityScale = "EPA_NowCast.2115"; - weather.air_quality.primaryPollutant = switchPollutantsType(obs.dominentpol); - weather.air_quality.airQualityCategoryIndex = classifyAirQualityLevel(obs.aqi); - weather.air_quality.pollutants.CO = { "name": "CO", "amount": obs.iaqi.co?.v || -1, "unit": "μg\/m3" }; - weather.air_quality.pollutants.NO = { "name": "NO", "amount": obs.iaqi.no?.v || -1, "unit": "μg\/m3" }; - weather.air_quality.pollutants.NO2 = { "name": "NO2", "amount": obs.iaqi.no2?.v || -1, "unit": "μg\/m3" }; - weather.air_quality.pollutants.SO2 = { "name": "SO2", "amount": obs.iaqi.so2?.v || -1, "unit": "μg\/m3" }; - weather.air_quality.pollutants.OZONE = { "name": "OZONE", "amount": obs.iaqi.o3?.v || -1, "unit": "μg\/m3" }; - weather.air_quality.pollutants.NOX = { "name": "NOX", "amount": obs.iaqi.nox?.v || -1, "unit": "μg\/m3" }; - weather.air_quality.pollutants["PM2.5"] = { "name": "PM2.5", "amount": obs.iaqi.pm25?.v || -1, "unit": "μg\/m3" }; - weather.air_quality.pollutants.PM10 = { "name": "PM10", "amount": obs.iaqi.pm10?.v || -1, "unit": "μg\/m3" }; - weather.air_quality.metadata.reported_time = convertTime(new Date(obs.time.v), 'remain', api); - weather.air_quality.metadata.longitude = obs.city.geo[0]; - weather.air_quality.metadata.provider_name = obs.attributions[obs.attributions.length - 1].name; - weather.air_quality.metadata.expire_time = convertTime(new Date(obs.time.v), 'add-1h-floor', api); - weather.air_quality.metadata.provider_logo = "https:\/\/waqi.info\/images\/logo.png"; - weather.air_quality.metadata.read_time = convertTime(new Date(), 'remain', api); - weather.air_quality.metadata.latitude = obs.city.geo[1]; - //weather.air_quality.metadata.version = ""; - weather.air_quality.metadata.language ? weather.air_quality.metadata.language : weather.current_observations.metadata.language - //weather.air_quality.metadata.language = $.language; - //weather.air_quality.metadata.data_source = 0; - } else if (stations) { // From Nearest List - weather.air_quality.source = stations.nna; - weather.air_quality.airQualityIndex = stations.v; - weather.air_quality.airQualityScale = "EPA_NowCast.2115"; - weather.air_quality.primaryPollutant = switchPollutantsType(stations.pol); //mapq1 - weather.air_quality.airQualityCategoryIndex = classifyAirQualityLevel(stations.v); - weather.air_quality.metadata.reported_time = convertTime(new Date(stations.t), 'remain', api); - weather.air_quality.metadata.expire_time = convertTime(new Date(stations.t), 'add-1h-floor', api); - weather.air_quality.metadata.read_time = convertTime(new Date(), 'remain', api); - weather.air_quality.metadata.longitude = stations.geo[0]; - weather.air_quality.metadata.latitude = stations.geo[1]; - weather.air_quality.metadata.language ? weather.air_quality.metadata.language : weather.current_observations.metadata.language - } - } - else if (api == "v2") { - $.log(`⚠️ ${$.name}, outputData, Detect`, `data ${api}`, ''); - if (!weather.airQuality) { - $.log(`⚠️ ${$.name}, non-existent Air Quality data, creating`, ''); - weather.airQuality = { - "pollutants": {}, - "metadata": { - "units": "m", - "version": 2, - }, - "sourceType": "station", //station:监测站 modeled:模型 - "isSignificant": true, //重要/置顶 - "name": "AirQuality", - } - }; - if (obs?.aqi) { // From Observation Station - weather.airQuality.source = obs.city.name; - weather.airQuality.learnMoreURL = obs.city.url + `/${$.country}/m`.toLowerCase(); - weather.airQuality.index = obs.aqi; - weather.airQuality.scale = "EPA_NowCast.2115"; - weather.airQuality.primaryPollutant = switchPollutantsType(obs.dominentpol); - weather.airQuality.categoryIndex = classifyAirQualityLevel(obs.aqi); - weather.airQuality.pollutants.CO = { "name": "CO", "amount": obs.iaqi.co?.v || -1, "unit": "microgramsPerM3" }; - weather.airQuality.pollutants.NO = { "name": "NO", "amount": obs.iaqi.no?.v || -1, "unit": "microgramsPerM3" }; - weather.airQuality.pollutants.NO2 = { "name": "NO2", "amount": obs.iaqi.no2?.v || -1, "unit": "microgramsPerM3" }; - weather.airQuality.pollutants.SO2 = { "name": "SO2", "amount": obs.iaqi.so2?.v || -1, "unit": "microgramsPerM3" }; - weather.airQuality.pollutants.OZONE = { "name": "OZONE", "amount": obs.iaqi.o3?.v || -1, "unit": "microgramsPerM3" }; - weather.airQuality.pollutants.NOX = { "name": "NOX", "amount": obs.iaqi.nox?.v || -1, "unit": "microgramsPerM3" }; - weather.airQuality.pollutants["PM2.5"] = { "name": "PM2.5", "amount": obs.iaqi.pm25?.v || -1, "unit": "microgramsPerM3" }; - weather.airQuality.pollutants.PM10 = { "name": "PM10", "amount": obs.iaqi.pm10?.v || -1, "unit": "microgramsPerM3" }; - weather.airQuality.metadata.longitude = obs.city.geo[0]; - weather.airQuality.metadata.providerLogo = "https:\/\/waqi.info\/images\/logo.png"; - weather.airQuality.metadata.providerName = obs.attributions[obs.attributions.length - 1].name; - weather.airQuality.metadata.expireTime = convertTime(new Date(obs.time.iso), 'add-1h-floor', api); - weather.airQuality.metadata.language ? weather.airQuality.metadata.language : weather.currentWeather.metadata.language; - //weather.airQuality.metadata.language = $.language; - weather.airQuality.metadata.latitude = obs.city.geo[1]; - weather.airQuality.metadata.reportedTime = convertTime(new Date(obs.time.iso), 'remain', api); - weather.airQuality.metadata.readTime = convertTime(new Date(), 'remain', api); - //weather.airQuality.metadata.units = "m"; - } else if (stations) { // From Nearest List - weather.airQuality.source = stations.name; - weather.airQuality.index = stations.aqi; - weather.airQuality.scale = "EPA_NowCast.2115"; - //weather.airQuality.primaryPollutant = switchPollutantsType(stations.pol); //mapq1 - weather.airQuality.categoryIndex = classifyAirQualityLevel(stations.aqi); - weather.airQuality.metadata.longitude = stations.geo[0]; - weather.airQuality.metadata.latitude = stations.geo[1]; - weather.airQuality.metadata.language ? weather.airQuality.metadata.language : weather.currentWeather.metadata.language; - weather.airQuality.metadata.expireTime = convertTime(new Date(stations.utime), 'add-1h-floor', api); - weather.airQuality.metadata.reportedTime = convertTime(new Date(stations.utime), 'remain', api); - weather.airQuality.metadata.readTime = convertTime(new Date(), 'remain', api); - }; - } else $.done() - } catch (e) { - $.log(`❗️ ${$.name}, outputData执行失败!`, `浏览器访问 https://api.waqi.info/api/feed/@${idx}/aqi.json 看看是不是空数据`, `原因:网络不畅或者获取太频繁导致被封`, `error = ${error || e}`, `response = ${JSON.stringify(response)}`, `data = ${data}`, '') - } finally { - // Output Data - body = JSON.stringify(weather); - $done({ body }); - $.log(`🎉 ${$.name}, outputData, Finish`, '') - resove() - } - }) -} - -// Step 6.1 -// Switch Pollutants Type -// https://github.com/Hackl0us/SS-Rule-Snippet/blob/master/Scripts/Surge/weather_aqi_us/iOS15_Weather_AQI_US.js -function switchPollutantsType(pollutant) { - switch (pollutant) { - case 'co': return 'CO'; - case 'no': return 'NO'; - case 'no2': return 'NO2'; - case 'so2': return 'SO2'; - case 'o3': return 'OZONE'; - case 'nox': return 'NOX'; - case 'pm25': return 'PM2.5'; - case 'pm10': return 'PM10'; - default: return "OTHER"; - } -}; - -// Step 6.2 -// Convert Time Format -// https://github.com/Hackl0us/SS-Rule-Snippet/blob/master/Scripts/Surge/weather_aqi_us/iOS15_Weather_AQI_US.js -function convertTime(time, action, api) { - switch (action) { - case 'remain': - time.setMilliseconds(0); - break; - case 'add-1h-floor': - time.setHours(time.getHours() + 1); - time.setMinutes(0, 0, 0); - break; - default: - $.log(`⚠️ ${$.name}, Time Converter, Error`, `time: ${time}`, ''); - } - if (api == "v1") { - let timeString = time.getTime() / 1000; - return timeString; - } - if (api == "v2") { - let timeString = time.toISOString().split('.')[0] + 'Z'; - return timeString; - } -}; - -// Step 6.3 -// Calculate Air Quality Level -// https://github.com/Hackl0us/SS-Rule-Snippet/blob/master/Scripts/Surge/weather_aqi_us/iOS15_Weather_AQI_US.js -function classifyAirQualityLevel(aqiIndex) { - if (aqiIndex >= 0 && aqiIndex <= 50) return 1; - else if (aqiIndex >= 51 && aqiIndex <= 100) return 2; - else if (aqiIndex >= 101 && aqiIndex <= 150) return 3; - else if (aqiIndex >= 151 && aqiIndex <= 200) return 4; - else if (aqiIndex >= 201 && aqiIndex <= 300) return 5; - else if (aqiIndex >= 301 && aqiIndex <= 500) return 6; - else { - $.log(`⚠️ ${$.name}, classifyAirQualityLevel, Error`, `aqiIndex: ${aqiIndex}`, ''); - return 0; - } -}; - -/***************** Env *****************/ -// prettier-ignore -// https://github.com/chavyleung/scripts/blob/master/Env.min.js -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("",`🔔${this.name}, 开始!`)}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("@"),a={url:`http://${h}/v1/scripting/evaluate`,body:{script_text:t,mock_type:"cron",timeout:r},headers:{"X-Key":o,Accept:"*/*"}};this.post(a,(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,i=rawOpts["update-pasteboard"]||rawOpts.updatePasteboard;return{"open-url":e,"media-url":s,"update-pasteboard":i}}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=["","==============📣系统通知📣=============="];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("",`❗️${this.name}, 错误!`,t.stack):this.log("",`❗️${this.name}, 错误!`,t)}wait(t){return new Promise(e=>setTimeout(e,t))}done(t={}){const e=(new Date).getTime(),s=(e-this.startTime)/1e3;this.log("",`🔔${this.name}, 结束! 🕛 ${s} 秒`),this.log(),(this.isSurge()||this.isQuanX()||this.isLoon())&&$done(t)}}(t,e)} diff --git a/Archive/js/Apple_Weather.v2.1.0.js b/Archive/js/Apple_Weather.v2.1.0.js deleted file mode 100644 index 5c1a117b7..000000000 --- a/Archive/js/Apple_Weather.v2.1.0.js +++ /dev/null @@ -1,383 +0,0 @@ -/* -README:https://github.com/VirgilClyne/iRingo -*/ - -const $ = new Env("Apple Weather v2.1.0"); -$.VAL = { - "url": "https://api.waqi.info", - "headers": { - "Content-Type": "application/x-www-form-urlencoded", - "Origin": "https://waqi.info", - "User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 15_1_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.1 Mobile/15E148 Safari/604.1", - "Referer": "https://waqi.info/" - } -}; - -/***************** Async *****************/ -!(async () => { - $.Apple = await setENV("iRingo") - const Mode = $.Apple.Weather.Mode - const Location = $.Apple.Weather.Location - const Parameter = await getOrigin($request.url) - const Status = await getAQIstatus(Parameter.Version, $response.body) - if (Status == true) { - if (Mode == "WAQI Public") { - $.log("工作模式: waqi.info 公共API") - var [NOW, idx] = await getNearestNOW(Parameter.Version, Parameter.lat, Parameter.lng) - let Token = await getToken(idx) - //var NOW = await getStationNOW(Token, idx) - var AQI = await getStationAQI(Token, idx) - } else if (Mode == "WAQI Private") { - $.log("工作模式: waqi.info 私有API") - let Token = $.Apple.Weather.Verify.Content - if (Location == "Station") { - $.log("定位精度: 观测站") - var [NOW, idx] = await getNearestNOW(Parameter.Version, Parameter.lat, Parameter.lng) - var AQI = await getStationFeed(Token, idx) - } else if (Location == "City") { - $.log("定位精度: 城市") - var AQI = await getCityFeed(Token, Parameter.lat, Parameter.lng) - } - } - let body = await outputData(Parameter.Version, NOW, AQI, $response.body); - await $.done({ body }); - } else $.log(`⚠️ ${$.name}, 无须替换, 跳过`, ''); -})() - .catch((e) => $.logErr(e)) - .finally(() => $.done()) - -/***************** Async Function *****************/ -// Function 0 -// Set Environment Variables -async function setENV(name) { - $.log(`⚠ ${$.name}, Set Environment Variables`, ""); - /***************** Settings *****************/ - // Default Settings - const database = { "Apple": { "Weather": { "Mode": "WAQI Public", "Location": "Station", "Verify": { "Mode": "Token", "Content": null }, "Scale": "EPA_NowCast.2201" } } }; - // BoxJs - let iRingo = $.getjson(name, database) - /***************** $.Apple *****************/ - let Apple = iRingo?.Apple || database.Apple; - if (typeof Apple == "string") Apple = JSON.parse(Apple) - $.log(`🎉 ${$.name}, Set Environment Variables`, `Apple: ${typeof Apple}`, `Apple内容: ${JSON.stringify(Apple)}`, ""); - // Argument Function Supported - if (typeof $argument != "undefined") { - $.log(`🎉 ${$.name}, $Argument`); - let arg = Object.fromEntries($argument.split("&").map((item) => item.split("="))); - $.log(JSON.stringify(arg)); - Apple.Weather.Mode = arg.Mode; - Apple.Weather.Location = arg.Location; - Apple.Weather.Verify.Mode = arg.VerifyMode; - Apple.Weather.Verify.Content = arg.Token; - } - //$.log(`🚧 ${$.name}, 调试信息, Apple.Weather类型: ${typeof Apple.Weather}`, `Apple.Weather内容: ${JSON.stringify(Apple.Weather)}`, ""); - return Apple; -}; - -// Step 1 -// Get Origin Parameter -function getOrigin(url) { - return new Promise((resolve) => { - const Regular = /^https?:\/\/(?weather-data|weather-data-origin)\.apple\.com\/(?v1|v2)\/weather\/(?[\w-_]+)\/(?-?\d+\.\d+)\/(?-?\d+\.\d+).*(?country=[A-Z]{2})?.*/i; - try { - var Parameter = url.match(Regular).groups; - } catch (e) { - $.log(`❗️${$.name}, ${getOrigin.name}执行失败`, `error = ${e}`, ''); - } finally { - $.log(`🎉 ${$.name}, ${getOrigin.name}完成`, JSON.stringify(Parameter), ''); - resolve(Parameter); - } - }) -}; - -// Step 2 -// Get AQI Source Status -function getAQIstatus(api, body) { - return new Promise((resolve) => { - const weather = JSON.parse(body); - const provider = ['和风天气', 'QWeather'] - try { - var result = (api == "v1" && weather.air_quality) ? provider.includes(weather.air_quality?.metadata?.provider_name) - : (api == "v2" && weather.airQuality) ? provider.includes(weather.airQuality?.metadata?.providerName) - : true - } catch (e) { - $.log(`❗️${$.name}, ${getAQIstatus.name}执行失败`, `error = ${e}`, ''); - } finally { - $.log(`🎉 ${$.name}, ${getAQIstatus.name}完成`, `AQ data ${api ?? "None"}, ${weather.air_quality?.metadata?.provider_name ?? weather.airQuality?.metadata?.providerName}`, ''); - resolve(result || false) - } - }) -}; - -// Step 3 -// Search Nearest Observation Station -// https://api.waqi.info/mapq/nearest/?n=1&geo=1/lat/lng -// https://api.waqi.info/mapq2/nearest?n=1&geo=1/lat/lng -async function getNearestNOW(api, lat, lng) { - $.log('获取最近站点'); - if (api == "v1") mapq = "mapq"; - else if (api == "v2") mapq = "mapq2"; - const url = { url: `${$.VAL.url}/${mapq}/nearest?n=1&geo=1/${lat}/${lng}`, headers: $.VAL.headers }; - return await getWAQIjson(url); -} - -// Step 4 -// Get Nearest Observation Station Token -// https://api.waqi.info/api/token/station.uid -async function getToken(idx) { - $.log('获取令牌'); - const url = { url: `${$.VAL.url}/api/token/${idx}`, headers: $.VAL.headers }; - return await getWAQIjson(url); -} - -// Step 5A -// Get Observation Station NOW JSON -// https://api.waqi.info/api/feed/@station.uid/now.json -async function getStationNOW(token = "na", idx) { - $.log('获取站点信息'); - const url = { method: 'post', url: `${$.VAL.url}/api/feed/@${idx}/now.json`, headers: $.VAL.headers, body: `token=${token}&id=${idx}` }; - return await fatchWAQIjson(url); -} - -// Step 5B -// Get Observation Station AQI JSON -// https://api.waqi.info/api/feed/@station.uid/aqi.json -async function getStationAQI(token = "na", idx) { - $.log('获取站点信息'); - const url = { method: 'post', url: `${$.VAL.url}/api/feed/@${idx}/aqi.json`, headers: $.VAL.headers, body: `token=${token}&id=${idx}` }; - return await fatchWAQIjson(url); -} - - -// Step 5C -// Get Geolocalized Feed -// https://aqicn.org/json-api/doc/#api-Geolocalized_Feed-GetGeolocFeed -// https://api.waqi.info/feed/geo::lat;:lng/?token=:token -async function getCityFeed(token, lat, lng) { - $.log('获取最近源信息'); - const url = { url: `${$.VAL.url}/feed/geo:${lat};${lng}/?token=${token}`, headers: $.VAL.headers }; - return await getWAQIjson(url); -} - -// Step 5D -// Get Observation Station Feed -// https://api.waqi.info/feed/@station.uid/?token=:token -async function getStationFeed(token, idx) { - $.log('获取最近源信息'); - const url = { url: `${$.VAL.url}/feed/@${idx}/?token=${token}`, headers: $.VAL.headers }; - return await getWAQIjson(url); -} - -// Step 6 -// Output Data -function outputData(api, now, obs, body) { - return new Promise((resolve) => { - // Input Data - let weather = JSON.parse(body); - try { - //检测版本 - $.log(`⚠️ ${$.name}, ${outputData.name}检测`, `AQ data ${api}`, ''); - var AQIname = (api == "v1") ? "air_quality" - : (api == "v2") ? "airQuality" - : "airQuality"; - var unit = (api == "v1") ? "μg\/m3" - : (api == "v2") ? "microgramsPerM3" - : "microgramsPerM3"; - //创建对象 - if (!weather[`${AQIname}`]) { - $.log(`⚠️ ${$.name}, 没有空气质量数据, 创建`, ''); - weather[`${AQIname}`] = { - "isSignificant": true, // 重要/置顶 - "pollutants": {}, - "metadata": {}, - "name": "AirQuality", - }; - if (api == "v1") { - weather[`${AQIname}`].metadata.version = 1; - weather[`${AQIname}`].metadata.data_source = 0; //来自XX读数 0:监测站 1:模型 - } - else if (api == "v2") { - weather[`${AQIname}`].metadata.units = "m"; - weather[`${AQIname}`].metadata.version = 2; - weather[`${AQIname}`].sourceType = "station"; //station:监测站 modeled:模型 - } - }; - // 注入数据 - //条件运算符 & 可选链操作符 - weather[`${AQIname}`].source = obs?.city?.name ?? now?.name ?? now?.u ?? now?.nna ?? now?.nlo; - weather[`${AQIname}`].learnMoreURL = obs?.city?.url + `/${now?.country ?? now?.cca2}/m`.toLowerCase(); - weather[`${AQIname}`].primaryPollutant = switchPollutantsType(obs?.dominentpol ?? now?.pol); - weather[`${AQIname}`].pollutants.CO = { "name": "CO", "amount": obs.iaqi.co?.v || -1, "unit": unit }; - weather[`${AQIname}`].pollutants.NO = { "name": "NO", "amount": obs.iaqi.no?.v || -1, "unit": unit }; - weather[`${AQIname}`].pollutants.NO2 = { "name": "NO2", "amount": obs.iaqi.no2?.v || -1, "unit": unit }; - weather[`${AQIname}`].pollutants.SO2 = { "name": "SO2", "amount": obs.iaqi.so2?.v || -1, "unit": unit }; - weather[`${AQIname}`].pollutants.OZONE = { "name": "OZONE", "amount": obs.iaqi.o3?.v || -1, "unit": unit }; - weather[`${AQIname}`].pollutants.NOX = { "name": "NOX", "amount": obs.iaqi.nox?.v || -1, "unit": unit }; - weather[`${AQIname}`].pollutants["PM2.5"] = { "name": "PM2.5", "amount": obs.iaqi.pm25?.v || -1, "unit": unit }; - weather[`${AQIname}`].pollutants.PM10 = { "name": "PM10", "amount": obs.iaqi.pm10?.v || -1, "unit": unit }; - weather[`${AQIname}`].metadata.longitude = obs?.city?.geo?.[0] ?? now?.geo?.[0]; - weather[`${AQIname}`].metadata.latitude = obs?.city?.geo?.[1] ?? now?.geo?.[1]; - weather[`${AQIname}`].metadata.language = weather?.[`${AQIname}`]?.metadata?.language ?? weather?.currentWeather?.metadata?.language ?? weather?.current_observations?.metadata?.language; - if (api == "v1") { - weather.air_quality.airQualityIndex = obs?.aqi ?? now?.aqi ?? now?.v; - weather.air_quality.airQualityScale = $.Apple.Weather.Scale ?? "EPA_NowCast.2201"; - weather.air_quality.airQualityCategoryIndex = classifyAirQualityLevel(obs?.aqi ?? now?.aqi ?? now?.v); - weather.air_quality.metadata.reported_time = convertTime(new Date(obs?.time?.v ?? now?.t), 'remain', api); - weather.air_quality.metadata.provider_name = obs?.attributions?.[0]?.name; - weather.air_quality.metadata.expire_time = convertTime(new Date(obs?.time?.v ?? now?.t), 'add-1h-floor', api); - weather.air_quality.metadata.provider_logo = "https:\/\/waqi.info\/images\/logo.png"; - weather.air_quality.metadata.read_time = convertTime(new Date(), 'remain', api); - } else if (api == "v2") { - weather.airQuality.index = obs?.aqi ?? now?.aqi ?? now?.v; - weather.airQuality.scale = $.Apple.Weather.Scale ?? "EPA_NowCast.2201"; - weather.airQuality.categoryIndex = classifyAirQualityLevel(obs?.aqi ?? now?.aqi ?? now?.v); - weather.airQuality.metadata.providerLogo = "https:\/\/waqi.info\/images\/logo.png"; - weather.airQuality.metadata.providerName = obs?.attributions?.[0]?.name; - weather.airQuality.metadata.expireTime = convertTime(new Date(obs?.time?.iso ?? now?.utime), 'add-1h-floor', api); - weather.airQuality.metadata.reportedTime = convertTime(new Date(obs?.time?.iso ?? now?.utime), 'remain', api); - weather.airQuality.metadata.readTime = convertTime(new Date(), 'remain', api); - } - } catch (e) { - $.log(`❗️${$.name}, ${outputData.name}执行失败`, `浏览器访问 https://api.waqi.info/api/feed/@${idx}/aqi.json 看看是不是空数据`, `原因:网络不畅或者获取太频繁导致被封`, `error = ${error || e}`, '') - } finally { - // Output Data - body = JSON.stringify(weather); - $.log(`🎉 ${$.name}, ${outputData.name}完成`, ''); - resolve(body) - } - }) -} - -/***************** Fuctions *****************/ -// Function 0A -// Get WAQI JSON -function getWAQIjson(url) { - return new Promise((resolve) => { - $.get(url, (error, response, data) => { - try { - if (error) throw new Error(error) - else if (data) { - const _data = JSON.parse(data) - if (url.url.search("/nearest") != -1) { - var station = _data?.data?.stations?.[0] ?? _data?.d?.[0] ?? null; - var idx = station?.idx ?? station?.x ?? null; - var name = station?.name ?? station?.u ?? station?.nna ?? station?.nlo ?? null; - var aqi = station?.aqi ?? station?.v ?? null; - var distance = station?.distance ?? station?.d ?? null; - $.log(`🎉 ${$.name}, ${getNearestNOW.name}完成`, `idx: ${idx}`, `观测站: ${name}`, `AQI: ${aqi}`, `距离: ${distance}`, '') - resolve([station, idx]) - } - else if (url.url.search("/api/token/") != -1) { - var token = _data.rxs?.obs[0]?.msg?.token ?? "na" - $.log(`🎉 ${$.name}, ${getToken.name}完成`, `token = ${token}`, '') - resolve(token) - } - else if (url.url.search("/feed/geo:") != -1) { - var city = (_data.status == 'ok') ? _data?.data : null; - $.log(`🎉 ${$.name}, ${getCityFeed.name}完成`, `idx: ${city?.idx}`, `观测站: ${city?.city?.name}`, `AQI: ${city?.aqi}`, '') - resolve(city) - } - else if (url.url.search("/feed/@") != -1) { - var station = (_data.status == 'ok') ? _data?.data : null; - $.log(`🎉 ${$.name}, ${getStationFeed.name}完成`, `idx: ${station?.idx}`, `观测站: ${station?.city?.name}`, `AQI: ${station?.aqi}`, '') - resolve(station) - } - } else throw new Error(response); - } catch (e) { - $.logErr(`❗️${$.name}, ${getWAQIjson.name}执行失败`, ` url = ${JSON.stringify(url)}`, ` error = ${error || e}`, `response = ${JSON.stringify(response)}`, `data = ${data}`, '') - } finally { - //$.log(`🚧 ${$.name}, ${getWAQIjson.name}调试信息`, ` url = ${JSON.stringify(url)}`, `data = ${data}`, '') - resolve() - } - }) - }) -}; - -// Function 0B -// Fatch WAQI JSON -function fatchWAQIjson(url) { - return new Promise((resolve) => { - $.post(url, (error, response, data) => { - try { - if (error) throw new Error(error) - else if (data) { - const _data = JSON.parse(data) - if (url.url.search("/api/feed/") != -1) { - if (_data.rxs.status == "ok") { - if (_data.rxs.obs.some(o => o.status == 'ok')) { - let i = _data.rxs.obs.findIndex(o => o.status == 'ok') - let m = _data.rxs.obs.findIndex(o => o.msg) - if (i >= 0 && m >= 0) { - $.log(`🎉 ${$.name}, ${getStationAQI.name}`, `i = ${i}, m = ${m}`, '') - resolve(_data.rxs.obs[i].msg) - } else if (i < 0 || m < 0) { - $.log(`❗️ ${$.name}, ${getStationAQI.name}`, `OBS Get Error`, `i = ${i}, m = ${m}`, `空数据,浏览器访问 https://api.waqi.info/api/feed/@${idx}/aqi.json 查看获取结果`, '') - resolve(_data.rxs.obs[i].msg) - } - } else $.log(`❗️ ${$.name}, ${getStationAQI.name}`, `OBS Status Error`, `obs.status: ${_data.rxs.obs[0].status}`, `data = ${data}`, '') - } else $.log(`❗️ ${$.name}, ${getStationAQI.name}`, `RXS Status Error`, `status: ${_data.rxs.status}`, `data = ${data}`, '') - } - } else throw new Error(response); - } catch (e) { - $.logErr(`❗️${$.name}, ${fatchWAQIjson.name}执行失败`, ` url = ${JSON.stringify(url)}`, ` error = ${error || e}`, `response = ${JSON.stringify(response)}`, `data = ${data}`, '') - } finally { - //$.log(`🚧 ${$.name}, ${fatchWAQIjson.name}调试信息`, ` url = ${JSON.stringify(url)}`, `data = ${data}`, '') - resolve() - } - }) - }) -}; - -// Function 1 -// Switch Pollutants Type -// https://github.com/Hackl0us/SS-Rule-Snippet/blob/master/Scripts/Surge/weather_aqi_us/iOS15_Weather_AQI_US.js -function switchPollutantsType(pollutant) { - const pollutant_map = { "co": "CO", "no": "NO", "no2": "NO2", "so2": "SO2", "o3": "OZONE", "nox": "NOX", "pm25": "PM2.5", "pm10": "PM10" }; - return pollutant_map?.[pollutant] ?? "OTHER"; -}; - -// Function 2 -// Convert Time Format -// https://github.com/Hackl0us/SS-Rule-Snippet/blob/master/Scripts/Surge/weather_aqi_us/iOS15_Weather_AQI_US.js -function convertTime(time, action, api) { - switch (action) { - case 'remain': - time.setMilliseconds(0); - break; - case 'add-1h-floor': - time.setHours(time.getHours() + 1); - time.setMinutes(0, 0, 0); - break; - default: - $.log(`⚠️ ${$.name}, Time Converter, Error`, `time: ${time}`, ''); - } - if (api == "v1") { - let timeString = time.getTime() / 1000; - return timeString; - } - if (api == "v2") { - let timeString = time.toISOString().split('.')[0] + 'Z'; - return timeString; - } -}; - -// Function 3 -// Calculate Air Quality Level -// https://github.com/Hackl0us/SS-Rule-Snippet/blob/master/Scripts/Surge/weather_aqi_us/iOS15_Weather_AQI_US.js -function classifyAirQualityLevel(aqiIndex) { - if (aqiIndex >= 0 && aqiIndex <= 50) return 1; - else if (aqiIndex >= 51 && aqiIndex <= 100) return 2; - else if (aqiIndex >= 101 && aqiIndex <= 150) return 3; - else if (aqiIndex >= 151 && aqiIndex <= 200) return 4; - else if (aqiIndex >= 201 && aqiIndex <= 300) return 5; - else if (aqiIndex >= 301 && aqiIndex <= 500) return 6; - else { - $.log(`⚠️ ${$.name}, classifyAirQualityLevel, Error`, `aqiIndex: ${aqiIndex}`, ''); - return 0; - } -}; - -/***************** Env *****************/ -// prettier-ignore -// https://github.com/chavyleung/scripts/blob/master/Env.min.js -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("",`🔔${this.name}, 开始!`)}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("@"),a={url:`http://${h}/v1/scripting/evaluate`,body:{script_text:t,mock_type:"cron",timeout:r},headers:{"X-Key":o,Accept:"*/*"}};this.post(a,(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,i=rawOpts["update-pasteboard"]||rawOpts.updatePasteboard;return{"open-url":e,"media-url":s,"update-pasteboard":i}}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=["","==============📣系统通知📣=============="];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("",`❗️${this.name}, 错误!`,t.stack):this.log("",`❗️${this.name}, 错误!`,t)}wait(t){return new Promise(e=>setTimeout(e,t))}done(t={}){const e=(new Date).getTime(),s=(e-this.startTime)/1e3;this.log("",`🔔${this.name}, 结束! 🕛 ${s} 秒`),this.log(),(this.isSurge()||this.isQuanX()||this.isLoon())&&$done(t)}}(t,e)} diff --git a/Archive/js/Apple_Weather.v2.2.0.js b/Archive/js/Apple_Weather.v2.2.0.js deleted file mode 100644 index a94f7a137..000000000 --- a/Archive/js/Apple_Weather.v2.2.0.js +++ /dev/null @@ -1,777 +0,0 @@ -/* -README:https://github.com/VirgilClyne/iRingo -*/ - -const $ = new Env("Apple Weather v2.2.0-beta"); -const DataBase = {"Apple":{"Weather":{"Mode":"WAQI Public","Location":"Station","Verify":{"Mode":"Token","Content":null},"Scale":"EPA_NowCast.2201"}}}; -const { url } = $request; - -/***************** Async *****************/ -!(async () => { - const { Parameter, Settings } = await setENV("iRingo", url, DataBase) - const Mode = Settings.Weather.Mode - const Location = Settings.Weather.Location - //const Parameter = await getOrigin(url) - const Status = await getAQIstatus(Parameter.Version, $response.body) - if (Status == true) { - if (Mode == "WAQI Public") { - $.log("工作模式: waqi.info 公共API") - //var [NOW, idx] = await getNearestNOW(Parameter.Version, Parameter.lat, Parameter.lng) - var { NOW, idx } = await WAQI("Nearest", { api: Parameter.Version, lat: Parameter.lat, lng: Parameter.lng }); - //let Token = await getToken(idx) - let Token = await WAQI("Token", { idx: idx }); - //var NOW = await getStationNOW(Token, idx) - //var AQI = await getStationAQI(Token, idx) - var AQI = await WAQI("AQI", { token:Token, idx: idx }); - } else if (Mode == "WAQI Private") { - $.log("工作模式: waqi.info 私有API") - let Token = Settings.Weather.Verify.Content - if (Location == "Station") { - $.log("定位精度: 观测站") - //var [NOW, idx] = await getNearestNOW(Parameter.Version, Parameter.lat, Parameter.lng) - var { NOW, idx } = await WAQI("Nearest", { api: Parameter.Version, lat: Parameter.lat, lng: Parameter.lng }); - //var AQI = await getStationFeed(Token, idx) - var AQI = await WAQI("StationFeed", { token:Token, idx: idx }); - } else if (Location == "City") { - $.log("定位精度: 城市") - //var AQI = await getCityFeed(Token, Parameter.lat, Parameter.lng) - var AQI = await WAQI("CityFeed", { token:Token, lat: Parameter.lat, lng: Parameter.lng }); - } - } - /* - let [now, idx] = await getNearestNOW(Parameter.Version, Parameter.lat, Parameter.lng) - let token = (Mode == "WAQI Public API") ? await getToken(idx) - : (Mode == "WAQI Private API") ? Settings.Weather.Verify.Content - : $.log(`⚠️ ${$.name}, 无可用令牌`, `令牌: ${Mode}`, ''); - let obs = (Mode == "WAQI Public API") ? await getStation(token, idx) - : (Mode == "WAQI Private API" && Location == "Station") ? getStationFeed(token, idx) - : (Mode == "WAQI Private API" && Location == "City") ? getCityFeed(token, lat, lng) - : $.log(`⚠️ ${$.name}, 无可用获取模式`, `获取模式: ${Mode}`, ''); - */ - let body = await outputData(Parameter.Version, NOW, AQI, $response.body, Settings); - await $.done({ body }); - } else $.log(`⚠️ ${$.name}, 无须替换, 跳过`, ''); -})() - .catch((e) => $.logErr(e)) - .finally(() => $.done()) - -/***************** Async Function *****************/ -/** - * Set Environment Variables - * @author VirgilClyne - * @param {String} url - url - * @param {Object} database - database - * @return {Promise<*>} - */ -async function setENV(name, url, database) { - $.log(`⚠ ${$.name}, Set Environment Variables`, ""); - /***************** Parameter *****************/ - const Regular = /^https?:\/\/(?weather-data|weather-data-origin)\.apple\.com\/(?v1|v2)\/weather\/(?[\w-_]+)\/(?-?\d+\.\d+)\/(?-?\d+\.\d+).*(?country=[A-Z]{2})?.*/i; - const Parameter = url.match(Regular).groups; - /***************** BoxJs *****************/ - // 包装为局部变量,用完释放内存 - // BoxJs的清空操作返回假值空字符串, 逻辑或操作符会在左侧操作数为假值时返回右侧操作数。 - let BoxJs = $.getjson(name, database) - //$.log(`🚧 ${$.name}, Set Environment Variables`, `$.BoxJs类型: ${typeof $.BoxJs}`, `$.BoxJs内容: ${JSON.stringify($.BoxJs)}`, ""); - /***************** Settings *****************/ - let Settings = BoxJs?.Apple || database.Apple; - //if (typeof Apple == "string") Apple = JSON.parse(Apple) - $.log(`🎉 ${$.name}, Set Environment Variables`, `Settings: ${typeof Settings}`, `Settings内容: ${JSON.stringify(Settings)}`, ""); - /***************** Argument *****************/ - if (typeof $argument != "undefined") { - $.log(`🎉 ${$.name}, $Argument`); - let arg = Object.fromEntries($argument.split("&").map((item) => item.split("="))); - $.log(JSON.stringify(arg)); - Settings.Weather.Mode = arg.Mode; - Settings.Weather.Location = arg.Location; - Settings.Weather.Verify.Mode = arg.VerifyMode; - Settings.Weather.Verify.Content = arg.Token; - } - //$.log(`🚧 ${$.name}, 调试信息, Settings.Weather类型: ${typeof Settings.Weather}`, `Settings.Weather内容: ${JSON.stringify(Settings.Weather)}`, ""); - return { Parameter, Settings }; -}; - -// Step 1 -// Get Origin Parameter -function getOrigin(url) { - return new Promise((resolve) => { - //const Regular = /^https?:\/\/(weather-data|weather-data-origin)\.apple\.com\/(v1|v2)\/weather\/([\w-_]+)\/(-?\d+\.\d+)\/(-?\d+\.\d+).*(country=[A-Z]{2})?.*/i; - const Regular = /^https?:\/\/(?weather-data|weather-data-origin)\.apple\.com\/(?v1|v2)\/weather\/(?[\w-_]+)\/(?-?\d+\.\d+)\/(?-?\d+\.\d+).*(?country=[A-Z]{2})?.*/i; - try { - //$.log(`🎉 ${$.name}, getOrigin, Finish`, $.url, `${$.dataServer}, ${$.Version}, ${$.language}, ${$.lat}, ${$.lng}, ${$.countryCode}`, ''); - //[$.url, $.dataServer, $.Version, $.language, $.lat, $.lng, $.countryCode] = url.match(Regular); - //$.log(`🚧 ${$.name}, ${getOrigin.name}`, url.match(Regular), ''); - var Parameter = url.match(Regular).groups; - } catch (e) { - $.log(`❗️${$.name}, ${getOrigin.name}执行失败`, `error = ${e}`, ''); - } finally { - $.log(`🎉 ${$.name}, ${getOrigin.name}完成`, JSON.stringify(Parameter), ''); - resolve(Parameter); - } - }) -}; - -// Step 2 -// Get AQI Source Status -function getAQIstatus(api, body) { - return new Promise((resolve) => { - const weather = JSON.parse(body); - const provider = ['和风天气', 'QWeather'] - try { - var result = (api == "v1" && weather.air_quality) ? provider.includes(weather.air_quality?.metadata?.provider_name) - : (api == "v2" && weather.airQuality) ? provider.includes(weather.airQuality?.metadata?.providerName) - : true - /* - if (api == 'v1' && weather.air_quality) { - $.log(`⚠️ ${$.name}, ${getAQIstatus.name}检测`, `AQ data ${api}, ${weather.air_quality?.metadata?.provider_name}`, ''); - var result = provider.includes(weather.air_quality.metadata.provider_name); - } else if (api == 'v2' && weather.airQuality) { - $.log(`⚠️ ${$.name}, ${getAQIstatus.name}检测`, `AQ data ${api}, ${weather.airQuality?.metadata?.providerName}`, ''); - var result = provider.includes(weather.airQuality.metadata.providerName); - } else { - $.log(`🎉 ${$.name}, ${getAQIstatus.name}检测`, "不存在 AQI data", ''); - var result = true; - } - */ - } catch (e) { - $.log(`❗️${$.name}, ${getAQIstatus.name}执行失败`, `error = ${e}`, ''); - } finally { - $.log(`🎉 ${$.name}, ${getAQIstatus.name}完成`, `AQ data ${api ?? "None"}, ${weather.air_quality?.metadata?.provider_name ?? weather.airQuality?.metadata?.providerName}`, ''); - resolve(result || false) - } - }) -}; - - - -/** - * WAQI - * @author VirgilClyne - * @param {String} type - type - * @param {Object} input - verify - * @return {Promise<*>} - */ -async function WAQI(type = "", input = {}) { - $.log(`⚠ ${$.name}, WAQI`, `input: ${JSON.stringify(input)}`, ""); - // 构造请求 - let request = await GetRequest(type, input); - // 发送请求 - let output = await GetData(type, request); - $.log(`🚧 ${$.name}, WAQI`, `output: ${output}`, ""); - return output - /***************** Fuctions *****************/ - async function GetRequest(type = "", input = { api: "v2", lat: 0, lng: 0, idx: 0, token: "na" }) { - $.log(`⚠ ${$.name}, Get Weather Request`, ""); - let request = { - "url": "https://api.waqi.info", - "headers": { - "Content-Type": "application/x-www-form-urlencoded", - "Origin": "https://waqi.info", - "User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 15_1_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.1 Mobile/15E148 Safari/604.1", - "Referer": "https://waqi.info/" - } - }; - if (type == "Nearest") { - $.log('获取最近站点'); - if (input.api == "v1") mapq = "mapq"; - else if (input.api == "v2") mapq = "mapq2"; - request.url = `${request.url}/${mapq}/nearest?n=1&geo=1/${input.lat}/${input.lng}`; - } else if (type == "Token") { - $.log('获取令牌'); - request.url = `${request.url}/api/token/${input.idx}` - } else if (type == "Now") { - $.log('获取即时信息'); - request.url = `${request.url}/api/feed/@${input.idx}/now.json` - request.body = `token=${input.token}&id=${input.idx}` - } else if (type == "AQI") { - $.log('获取空气质量信息'); - request.url = `${request.url}/api/feed/@${input.idx}/aqi.json` - request.body = `token=${input.token}&id=${input.idx}` - } else if (type == "CityFeed") { - $.log('获取城市信息'); - request.url = `${request.url}/feed/geo:${input.lat};${input.lng}/?token=${input.token}` - } else if (type == "StationFeed") { - $.log('获取站点信息'); - request.url = `${request.url}/feed/@${input.idx}/?token=${input.token}` - } - }; - - function GetData(type, request) { - $.log(`⚠ ${$.name}, Get Weather Data`, ""); - return new Promise(resolve => { - if (type == "Now" || type == "AQI") { - $.post(request, (error, response, data) => { - try { - if (error) throw new Error(error) - else if (data) { - const _data = JSON.parse(data) - // Step 5A - // Get Nearest Observation Station AQI Data - // https://api.waqi.info/api/feed/@station.uid/now.json - // https://api.waqi.info/api/feed/@station.uid/aqi.json - if (type == "Now" || type == "AQI") { - if (_data.rxs.status == "ok") { - if (_data.rxs.obs.some(o => o.status == 'ok')) { - let i = _data.rxs.obs.findIndex(o => o.status == 'ok') - let m = _data.rxs.obs.findIndex(o => o.msg) - //$.obs = _data.rxs.obs[i].msg; - if (i >= 0 && m >= 0) { - $.log(`🎉 ${$.name}, ${getStationAQI.name}`, `i = ${i}, m = ${m}`, '') - resolve(_data.rxs.obs[i].msg) - } else if (i < 0 || m < 0) { - $.log(`❗️ ${$.name}, ${getStationAQI.name}`, `OBS Get Error`, `i = ${i}, m = ${m}`, `空数据,浏览器访问 https://api.waqi.info/api/feed/@${idx}/aqi.json 查看获取结果`, '') - resolve(_data.rxs.obs[i].msg) - } - } else $.log(`❗️ ${$.name}, ${getStationAQI.name}`, `OBS Status Error`, `obs.status: ${_data.rxs.obs[0].status}`, `data = ${data}`, '') - } else $.log(`❗️ ${$.name}, ${getStationAQI.name}`, `RXS Status Error`, `status: ${_data.rxs.status}`, `data = ${data}`, '') - } - } else throw new Error(response); - } catch (e) { - $.logErr(`❗️${$.name}, ${fatchWAQIjson.name}执行失败`, ` url = ${JSON.stringify(url)}`, ` error = ${error || e}`, `response = ${JSON.stringify(response)}`, `data = ${data}`, '') - } finally { - //$.log(`🚧 ${$.name}, ${fatchWAQIjson.name}调试信息`, ` url = ${JSON.stringify(url)}`, `data = ${data}`, '') - resolve() - } - }) - } else { - $.get(request, (error, response, data) => { - try { - if (error) throw new Error(error) - else if (data) { - const _data = JSON.parse(data) - // Step 3 - // Search Nearest Observation Station - // https://api.waqi.info/mapq/nearest/?n=1&geo=1/lat/lng - // https://api.waqi.info/mapq2/nearest?n=1&geo=1/lat/lng - if (type == "Nearest") { - // 空值合并运算符 - var station = _data?.data?.stations?.[0] ?? _data?.d?.[0] ?? null; - var idx = station?.idx ?? station?.x ?? null; - var name = station?.name ?? station?.u ?? station?.nna ?? station?.nlo ?? null; - var aqi = station?.aqi ?? station?.v ?? null; - var distance = station?.distance ?? station?.d ?? null; - //var country = station?.cca2 ?? station?.country ?? null; - /* - if (url.url.search("/mapq/") != -1 && _data.d[0]) { - var station = _data.d[0]; - var idx = station.x; - var name = station.nna; - var distance = station.d; - //var country = station.cca2; - } else if (url.url.search("/mapq2/") != -1 && _data.status == "ok") { - var station = _data.data.stations[0]; - var idx = station.idx; - var name = station.name; - var distance = station.distance; - //var country = station.country; - } else { - $.log(`❗️ ${$.name}, ${getNearestNOW.name}执行失败`, `api: ${api}`, `data = ${data}`, ''); - $.done(); - } - */ - $.log(`🎉 ${$.name}, ${getNearestNOW.name}完成`, `idx: ${idx}`, `观测站: ${name}`, `AQI: ${aqi}`, `距离: ${distance}`, '') - resolve({ station, idx }) - } - // Step 4 - // Get Nearest Observation Station Token - // https://api.waqi.info/api/token/station.uid - else if (type == "Token") { - var token = _data.rxs?.obs[0]?.msg?.token ?? "na" - $.log(`🎉 ${$.name}, ${getToken.name}完成`, `token = ${token}`, '') - resolve(token) - /* - if (_data.rxs.status == "ok") { - var token = _data.rxs.obs[0].msg.token; - $.log(`🎉 ${$.name}, ${getToken.name}完成`, `token = ${token}`, '') - } else { - var token = "na"; - $.log(`⚠️ ${$.name}, ${getToken.name}执行失败`, `status: ${_data.rxs.status}`, `data = ${data}`, '') - } resolve(token) - */ - } - // Step 5B - // Geolocalized Feed - // https://aqicn.org/json-api/doc/#api-Geolocalized_Feed-GetGeolocFeed - // https://api.waqi.info/feed/geo::lat;:lng/?token=:token - else if (type == "CityFeed") { - var city = (_data.status == 'ok') ? _data?.data : null; - $.log(`🎉 ${$.name}, ${getCityFeed.name}完成`, `idx: ${city?.idx}`, `观测站: ${city?.city?.name}`, `AQI: ${city?.aqi}`, '') - resolve(city) - } - // Step 5C - // Station Feed - // https://api.waqi.info/feed/@station.uid/?token=:token - else if (type == "StationFeed") { - var station = (_data.status == 'ok') ? _data?.data : null; - $.log(`🎉 ${$.name}, ${getStationFeed.name}完成`, `idx: ${station?.idx}`, `观测站: ${station?.city?.name}`, `AQI: ${station?.aqi}`, '') - resolve(station) - } - } else throw new Error(response); - } catch (e) { - $.logErr(`❗️${$.name}, ${getWAQIjson.name}执行失败`, ` url = ${JSON.stringify(url)}`, ` error = ${error || e}`, `response = ${JSON.stringify(response)}`, `data = ${data}`, '') - } finally { - //$.log(`🚧 ${$.name}, ${getWAQIjson.name}调试信息`, ` url = ${JSON.stringify(url)}`, `data = ${data}`, '') - resolve() - } - }) - }; - }); - }; -}; -// Step 3 -// Search Nearest Observation Station -// https://api.waqi.info/mapq/nearest/?n=1&geo=1/lat/lng -// https://api.waqi.info/mapq2/nearest?n=1&geo=1/lat/lng -async function getNearestNOW(api, lat, lng) { - $.log('获取最近站点'); - if (api == "v1") mapq = "mapq"; - else if (api == "v2") mapq = "mapq2"; - const url = { url: `${$.VAL.url}/${mapq}/nearest?n=1&geo=1/${lat}/${lng}`, headers: $.VAL.headers }; - return await getWAQIjson(url); -} - -// Step 4 -// Get Nearest Observation Station Token -// https://api.waqi.info/api/token/station.uid -async function getToken(idx) { - $.log('获取令牌'); - const url = { url: `${$.VAL.url}/api/token/${idx}`, headers: $.VAL.headers }; - return await getWAQIjson(url); -} - -// Step 5A -// Get Observation Station NOW JSON -// https://api.waqi.info/api/feed/@station.uid/now.json -async function getStationNOW(token = "na", idx) { - $.log('获取站点信息'); - const url = { method: 'post', url: `${$.VAL.url}/api/feed/@${idx}/now.json`, headers: $.VAL.headers, body: `token=${token}&id=${idx}` }; - return await fatchWAQIjson(url); -} - -// Step 5B -// Get Observation Station AQI JSON -// https://api.waqi.info/api/feed/@station.uid/aqi.json -async function getStationAQI(token = "na", idx) { - $.log('获取站点信息'); - const url = { method: 'post', url: `${$.VAL.url}/api/feed/@${idx}/aqi.json`, headers: $.VAL.headers, body: `token=${token}&id=${idx}` }; - return await fatchWAQIjson(url); -} - - -// Step 5C -// Get Geolocalized Feed -// https://aqicn.org/json-api/doc/#api-Geolocalized_Feed-GetGeolocFeed -// https://api.waqi.info/feed/geo::lat;:lng/?token=:token -async function getCityFeed(token, lat, lng) { - $.log('获取最近源信息'); - const url = { url: `${$.VAL.url}/feed/geo:${lat};${lng}/?token=${token}`, headers: $.VAL.headers }; - return await getWAQIjson(url); -} - -// Step 5D -// Get Observation Station Feed -// https://api.waqi.info/feed/@station.uid/?token=:token -async function getStationFeed(token, idx) { - $.log('获取最近源信息'); - const url = { url: `${$.VAL.url}/feed/@${idx}/?token=${token}`, headers: $.VAL.headers }; - return await getWAQIjson(url); -} - -// Step 6 -// Output Data -function outputData(api, now, obs, body, Settings) { - return new Promise((resolve) => { - // Input Data - let weather = JSON.parse(body); - try { - //检测版本 - $.log(`⚠️ ${$.name}, ${outputData.name}检测`, `AQ data ${api}`, ''); - var AQIname = (api == "v1") ? "air_quality" - : (api == "v2") ? "airQuality" - : "airQuality"; - var unit = (api == "v1") ? "μg\/m3" - : (api == "v2") ? "microgramsPerM3" - : "microgramsPerM3"; - //创建对象 - if (!weather[`${AQIname}`]) { - $.log(`⚠️ ${$.name}, 没有空气质量数据, 创建`, ''); - weather[`${AQIname}`] = { - "isSignificant": true, // 重要/置顶 - "pollutants": {}, - "metadata": {}, - "name": "AirQuality", - }; - if (api == "v1") { - weather[`${AQIname}`].metadata.version = 1; - weather[`${AQIname}`].metadata.data_source = 0; //来自XX读数 0:监测站 1:模型 - } - else if (api == "v2") { - weather[`${AQIname}`].metadata.units = "m"; - weather[`${AQIname}`].metadata.version = 2; - weather[`${AQIname}`].sourceType = "station"; //station:监测站 modeled:模型 - } - }; - // 注入数据 - //条件运算符 & 可选链操作符 - weather[`${AQIname}`].source = obs?.city?.name ?? now?.name ?? now?.u ?? now?.nna ?? now?.nlo; - weather[`${AQIname}`].learnMoreURL = obs?.city?.url + `/${now?.country ?? now?.cca2}/m`.toLowerCase(); - weather[`${AQIname}`].primaryPollutant = switchPollutantsType(obs?.dominentpol ?? now?.pol); - weather[`${AQIname}`].pollutants.CO = { "name": "CO", "amount": obs.iaqi.co?.v || -1, "unit": unit }; - weather[`${AQIname}`].pollutants.NO = { "name": "NO", "amount": obs.iaqi.no?.v || -1, "unit": unit }; - weather[`${AQIname}`].pollutants.NO2 = { "name": "NO2", "amount": obs.iaqi.no2?.v || -1, "unit": unit }; - weather[`${AQIname}`].pollutants.SO2 = { "name": "SO2", "amount": obs.iaqi.so2?.v || -1, "unit": unit }; - weather[`${AQIname}`].pollutants.OZONE = { "name": "OZONE", "amount": obs.iaqi.o3?.v || -1, "unit": unit }; - weather[`${AQIname}`].pollutants.NOX = { "name": "NOX", "amount": obs.iaqi.nox?.v || -1, "unit": unit }; - weather[`${AQIname}`].pollutants["PM2.5"] = { "name": "PM2.5", "amount": obs.iaqi.pm25?.v || -1, "unit": unit }; - weather[`${AQIname}`].pollutants.PM10 = { "name": "PM10", "amount": obs.iaqi.pm10?.v || -1, "unit": unit }; - weather[`${AQIname}`].metadata.longitude = obs?.city?.geo?.[0] ?? now?.geo?.[0]; - weather[`${AQIname}`].metadata.latitude = obs?.city?.geo?.[1] ?? now?.geo?.[1]; - weather[`${AQIname}`].metadata.language = weather?.[`${AQIname}`]?.metadata?.language ?? weather?.currentWeather?.metadata?.language ?? weather?.current_observations?.metadata?.language; - if (api == "v1") { - weather.air_quality.airQualityIndex = obs?.aqi ?? now?.aqi ?? now?.v; - weather.air_quality.airQualityScale = Settings.Weather.Scale ?? "EPA_NowCast.2201"; - weather.air_quality.airQualityCategoryIndex = classifyAirQualityLevel(obs?.aqi ?? now?.aqi ?? now?.v); - weather.air_quality.metadata.reported_time = convertTime(new Date(obs?.time?.v ?? now?.t), 'remain', api); - //weather.air_quality.metadata.provider_name = obs?.attributions?.[obs.attributions.length - 1]?.name; - weather.air_quality.metadata.provider_name = obs?.attributions?.[0]?.name; - weather.air_quality.metadata.expire_time = convertTime(new Date(obs?.time?.v ?? now?.t), 'add-1h-floor', api); - weather.air_quality.metadata.provider_logo = "https:\/\/waqi.info\/images\/logo.png"; - weather.air_quality.metadata.read_time = convertTime(new Date(), 'remain', api); - } else if (api == "v2") { - weather.airQuality.index = obs?.aqi ?? now?.aqi ?? now?.v; - weather.airQuality.scale = Settings.Weather.Scale ?? "EPA_NowCast.2201"; - weather.airQuality.categoryIndex = classifyAirQualityLevel(obs?.aqi ?? now?.aqi ?? now?.v); - weather.airQuality.metadata.providerLogo = "https:\/\/waqi.info\/images\/logo.png"; - //weather.airQuality.metadata.providerName = obs?.attributions?.[obs.attributions.length - 1]?.name; - weather.airQuality.metadata.providerName = obs?.attributions?.[0]?.name; - weather.airQuality.metadata.expireTime = convertTime(new Date(obs?.time?.iso ?? now?.utime), 'add-1h-floor', api); - weather.airQuality.metadata.reportedTime = convertTime(new Date(obs?.time?.iso ?? now?.utime), 'remain', api); - weather.airQuality.metadata.readTime = convertTime(new Date(), 'remain', api); - } - - /* - if (api == "v1") { - $.log(`⚠️ ${$.name}, ${outputData.name}检测`, `AQ data ${api}`, ''); - if (!weather.air_quality) { - $.log(`⚠️ ${$.name}, non-existent Air Quality data, creating`, ''); - weather.air_quality = { - "isSignificant": true, //重要/置顶 - "pollutants": {}, - "metadata": { - "version": 1, - "data_source": 0, //来自XX读数 0:监测站 1:模型 - }, - "name": "AirQuality", - }; - }; - if (obs?.aqi) { // From Observation Station - weather.air_quality.source = obs.city.name; - weather.air_quality.learnMoreURL = obs.city.url + `/${now.cca2}/m`.toLowerCase(); - weather.air_quality.airQualityIndex = obs.aqi; - weather.air_quality.airQualityScale = "EPA_NowCast.2115"; - weather.air_quality.primaryPollutant = switchPollutantsType(obs.dominentpol); - weather.air_quality.airQualityCategoryIndex = classifyAirQualityLevel(obs.aqi); - weather.air_quality.pollutants.CO = { "name": "CO", "amount": obs.iaqi.co?.v || -1, "unit": "μg\/m3" }; - weather.air_quality.pollutants.NO = { "name": "NO", "amount": obs.iaqi.no?.v || -1, "unit": "μg\/m3" }; - weather.air_quality.pollutants.NO2 = { "name": "NO2", "amount": obs.iaqi.no2?.v || -1, "unit": "μg\/m3" }; - weather.air_quality.pollutants.SO2 = { "name": "SO2", "amount": obs.iaqi.so2?.v || -1, "unit": "μg\/m3" }; - weather.air_quality.pollutants.OZONE = { "name": "OZONE", "amount": obs.iaqi.o3?.v || -1, "unit": "μg\/m3" }; - weather.air_quality.pollutants.NOX = { "name": "NOX", "amount": obs.iaqi.nox?.v || -1, "unit": "μg\/m3" }; - weather.air_quality.pollutants["PM2.5"] = { "name": "PM2.5", "amount": obs.iaqi.pm25?.v || -1, "unit": "μg\/m3" }; - weather.air_quality.pollutants.PM10 = { "name": "PM10", "amount": obs.iaqi.pm10?.v || -1, "unit": "μg\/m3" }; - weather.air_quality.metadata.reported_time = convertTime(new Date(obs.time.v), 'remain', api); - weather.air_quality.metadata.longitude = obs.city.geo[0]; - weather.air_quality.metadata.provider_name = obs.attributions[obs.attributions.length - 1].name; - weather.air_quality.metadata.expire_time = convertTime(new Date(obs.time.v), 'add-1h-floor', api); - weather.air_quality.metadata.provider_logo = "https:\/\/waqi.info\/images\/logo.png"; - weather.air_quality.metadata.read_time = convertTime(new Date(), 'remain', api); - weather.air_quality.metadata.latitude = obs.city.geo[1]; - //weather.air_quality.metadata.version = ""; - weather.air_quality.metadata.language ? weather.air_quality.metadata.language : weather.current_observations.metadata.language - //weather.air_quality.metadata.language = $.language; - //weather.air_quality.metadata.data_source = 0; - } else if (now) { // From Nearest List - weather.air_quality.source = now.nna; - weather.air_quality.airQualityIndex = now.v; - weather.air_quality.airQualityScale = "EPA_NowCast.2115"; - weather.air_quality.primaryPollutant = switchPollutantsType(now.pol); //mapq1 - weather.air_quality.airQualityCategoryIndex = classifyAirQualityLevel(now.v); - weather.air_quality.metadata.reported_time = convertTime(new Date(now.t), 'remain', api); - weather.air_quality.metadata.expire_time = convertTime(new Date(now.t), 'add-1h-floor', api); - weather.air_quality.metadata.read_time = convertTime(new Date(), 'remain', api); - weather.air_quality.metadata.longitude = now.geo[0]; - weather.air_quality.metadata.latitude = now.geo[1]; - weather.air_quality.metadata.language ? weather.air_quality.metadata.language : weather.current_observations.metadata.language - } - } else if (api == "v2") { - $.log(`⚠️ ${$.name}, ${outputData.name}检测`, `AQ data ${api}`, ''); - if (!weather.airQuality) { - $.log(`⚠️ ${$.name}, non-existent Air Quality data, creating`, ''); - weather.airQuality = { - "pollutants": {}, - "metadata": { - "units": "m", - "version": 2, - }, - "sourceType": "station", //station:监测站 modeled:模型 - "isSignificant": true, //重要/置顶 - "name": "AirQuality", - } - }; - if (obs?.aqi) { // From Observation Station - weather.airQuality.source = obs.city.name; - weather.airQuality.learnMoreURL = obs.city.url + `/${now.country}/m`.toLowerCase(); - weather.airQuality.index = obs.aqi; - weather.airQuality.scale = "EPA_NowCast.2115"; - weather.airQuality.primaryPollutant = switchPollutantsType(obs.dominentpol); - weather.airQuality.categoryIndex = classifyAirQualityLevel(obs.aqi); - weather.airQuality.pollutants.CO = { "name": "CO", "amount": obs.iaqi.co?.v || -1, "unit": "microgramsPerM3" }; - weather.airQuality.pollutants.NO = { "name": "NO", "amount": obs.iaqi.no?.v || -1, "unit": "microgramsPerM3" }; - weather.airQuality.pollutants.NO2 = { "name": "NO2", "amount": obs.iaqi.no2?.v || -1, "unit": "microgramsPerM3" }; - weather.airQuality.pollutants.SO2 = { "name": "SO2", "amount": obs.iaqi.so2?.v || -1, "unit": "microgramsPerM3" }; - weather.airQuality.pollutants.OZONE = { "name": "OZONE", "amount": obs.iaqi.o3?.v || -1, "unit": "microgramsPerM3" }; - weather.airQuality.pollutants.NOX = { "name": "NOX", "amount": obs.iaqi.nox?.v || -1, "unit": "microgramsPerM3" }; - weather.airQuality.pollutants["PM2.5"] = { "name": "PM2.5", "amount": obs.iaqi.pm25?.v || -1, "unit": "microgramsPerM3" }; - weather.airQuality.pollutants.PM10 = { "name": "PM10", "amount": obs.iaqi.pm10?.v || -1, "unit": "microgramsPerM3" }; - weather.airQuality.metadata.longitude = obs.city.geo[0]; - weather.airQuality.metadata.providerLogo = "https:\/\/waqi.info\/images\/logo.png"; - weather.airQuality.metadata.providerName = obs.attributions[obs.attributions.length - 1].name; - weather.airQuality.metadata.expireTime = convertTime(new Date(obs.time.iso), 'add-1h-floor', api); - weather.airQuality.metadata.language ? weather.airQuality.metadata.language : weather.currentWeather.metadata.language; - //weather.airQuality.metadata.language = $.language; - weather.airQuality.metadata.latitude = obs.city.geo[1]; - weather.airQuality.metadata.reportedTime = convertTime(new Date(obs.time.iso), 'remain', api); - weather.airQuality.metadata.readTime = convertTime(new Date(), 'remain', api); - //weather.airQuality.metadata.units = "m"; - } else if (now) { // From Nearest List - weather.airQuality.source = now.name; - weather.airQuality.index = now.aqi; - weather.airQuality.scale = "EPA_NowCast.2115"; - //weather.airQuality.primaryPollutant = switchPollutantsType(now.pol); //mapq1 - weather.airQuality.categoryIndex = classifyAirQualityLevel(now.aqi); - weather.airQuality.metadata.longitude = now.geo[0]; - weather.airQuality.metadata.latitude = now.geo[1]; - weather.airQuality.metadata.language ? weather.airQuality.metadata.language : weather.currentWeather.metadata.language; - weather.airQuality.metadata.expireTime = convertTime(new Date(now.utime), 'add-1h-floor', api); - weather.airQuality.metadata.reportedTime = convertTime(new Date(now.utime), 'remain', api); - weather.airQuality.metadata.readTime = convertTime(new Date(), 'remain', api); - }; - } - */ - } catch (e) { - $.log(`❗️${$.name}, ${outputData.name}执行失败`, `浏览器访问 https://api.waqi.info/api/feed/@${idx}/aqi.json 看看是不是空数据`, `原因:网络不畅或者获取太频繁导致被封`, `error = ${error || e}`, '') - } finally { - // Output Data - body = JSON.stringify(weather); - $.log(`🎉 ${$.name}, ${outputData.name}完成`, ''); - resolve(body) - } - }) -} - -/***************** Fuctions *****************/ -// Function 0A -// Get WAQI JSON -function getWAQIjson(url) { - return new Promise((resolve) => { - $.get(url, (error, response, data) => { - try { - if (error) throw new Error(error) - else if (data) { - const _data = JSON.parse(data) - // Step 3 - // Search Nearest Observation Station - // https://api.waqi.info/mapq/nearest/?n=1&geo=1/lat/lng - // https://api.waqi.info/mapq2/nearest?n=1&geo=1/lat/lng - if (url.url.search("/nearest") != -1) { - // 空值合并运算符 - var station = _data?.data?.stations?.[0] ?? _data?.d?.[0] ?? null; - var idx = station?.idx ?? station?.x ?? null; - var name = station?.name ?? station?.u ?? station?.nna ?? station?.nlo ?? null; - var aqi = station?.aqi ?? station?.v ?? null; - var distance = station?.distance ?? station?.d ?? null; - //var country = station?.cca2 ?? station?.country ?? null; - /* - if (url.url.search("/mapq/") != -1 && _data.d[0]) { - var station = _data.d[0]; - var idx = station.x; - var name = station.nna; - var distance = station.d; - //var country = station.cca2; - } else if (url.url.search("/mapq2/") != -1 && _data.status == "ok") { - var station = _data.data.stations[0]; - var idx = station.idx; - var name = station.name; - var distance = station.distance; - //var country = station.country; - } else { - $.log(`❗️ ${$.name}, ${getNearestNOW.name}执行失败`, `api: ${api}`, `data = ${data}`, ''); - $.done(); - } - */ - $.log(`🎉 ${$.name}, ${getNearestNOW.name}完成`, `idx: ${idx}`, `观测站: ${name}`, `AQI: ${aqi}`, `距离: ${distance}`, '') - resolve([station, idx]) - } - // Step 4 - // Get Nearest Observation Station Token - // https://api.waqi.info/api/token/station.uid - else if (url.url.search("/api/token/") != -1) { - var token = _data.rxs?.obs[0]?.msg?.token ?? "na" - $.log(`🎉 ${$.name}, ${getToken.name}完成`, `token = ${token}`, '') - resolve(token) - /* - if (_data.rxs.status == "ok") { - var token = _data.rxs.obs[0].msg.token; - $.log(`🎉 ${$.name}, ${getToken.name}完成`, `token = ${token}`, '') - } else { - var token = "na"; - $.log(`⚠️ ${$.name}, ${getToken.name}执行失败`, `status: ${_data.rxs.status}`, `data = ${data}`, '') - } resolve(token) - */ - } - // Step 5B - // Geolocalized Feed - // https://aqicn.org/json-api/doc/#api-Geolocalized_Feed-GetGeolocFeed - // https://api.waqi.info/feed/geo::lat;:lng/?token=:token - else if (url.url.search("/feed/geo:") != -1) { - var city = (_data.status == 'ok') ? _data?.data : null; - $.log(`🎉 ${$.name}, ${getCityFeed.name}完成`, `idx: ${city?.idx}`, `观测站: ${city?.city?.name}`, `AQI: ${city?.aqi}`, '') - resolve(city) - } - // Step 5C - // Station Feed - // https://api.waqi.info/feed/@station.uid/?token=:token - else if (url.url.search("/feed/@") != -1) { - var station = (_data.status == 'ok') ? _data?.data : null; - $.log(`🎉 ${$.name}, ${getStationFeed.name}完成`, `idx: ${station?.idx}`, `观测站: ${station?.city?.name}`, `AQI: ${station?.aqi}`, '') - resolve(station) - } - } else throw new Error(response); - } catch (e) { - $.logErr(`❗️${$.name}, ${getWAQIjson.name}执行失败`, ` url = ${JSON.stringify(url)}`, ` error = ${error || e}`, `response = ${JSON.stringify(response)}`, `data = ${data}`, '') - } finally { - //$.log(`🚧 ${$.name}, ${getWAQIjson.name}调试信息`, ` url = ${JSON.stringify(url)}`, `data = ${data}`, '') - resolve() - } - }) - }) -}; - -// Function 0B -// Fatch WAQI JSON -function fatchWAQIjson(url) { - return new Promise((resolve) => { - $.post(url, (error, response, data) => { - try { - if (error) throw new Error(error) - else if (data) { - const _data = JSON.parse(data) - // Step 5A - // Get Nearest Observation Station AQI Data - // https://api.waqi.info/api/feed/@station.uid/now.json - // https://api.waqi.info/api/feed/@station.uid/aqi.json - if (url.url.search("/api/feed/") != -1) { - if (_data.rxs.status == "ok") { - if (_data.rxs.obs.some(o => o.status == 'ok')) { - let i = _data.rxs.obs.findIndex(o => o.status == 'ok') - let m = _data.rxs.obs.findIndex(o => o.msg) - //$.obs = _data.rxs.obs[i].msg; - if (i >= 0 && m >= 0) { - $.log(`🎉 ${$.name}, ${getStationAQI.name}`, `i = ${i}, m = ${m}`, '') - resolve(_data.rxs.obs[i].msg) - } else if (i < 0 || m < 0) { - $.log(`❗️ ${$.name}, ${getStationAQI.name}`, `OBS Get Error`, `i = ${i}, m = ${m}`, `空数据,浏览器访问 https://api.waqi.info/api/feed/@${idx}/aqi.json 查看获取结果`, '') - resolve(_data.rxs.obs[i].msg) - } - } else $.log(`❗️ ${$.name}, ${getStationAQI.name}`, `OBS Status Error`, `obs.status: ${_data.rxs.obs[0].status}`, `data = ${data}`, '') - } else $.log(`❗️ ${$.name}, ${getStationAQI.name}`, `RXS Status Error`, `status: ${_data.rxs.status}`, `data = ${data}`, '') - } - } else throw new Error(response); - } catch (e) { - $.logErr(`❗️${$.name}, ${fatchWAQIjson.name}执行失败`, ` url = ${JSON.stringify(url)}`, ` error = ${error || e}`, `response = ${JSON.stringify(response)}`, `data = ${data}`, '') - } finally { - //$.log(`🚧 ${$.name}, ${fatchWAQIjson.name}调试信息`, ` url = ${JSON.stringify(url)}`, `data = ${data}`, '') - resolve() - } - }) - }) -}; - -// Function 1 -// Switch Pollutants Type -// https://github.com/Hackl0us/SS-Rule-Snippet/blob/master/Scripts/Surge/weather_aqi_us/iOS15_Weather_AQI_US.js -function switchPollutantsType(pollutant) { - // Way 3 - // Array Dictionary - const pollutant_map = { "co": "CO", "no": "NO", "no2": "NO2", "so2": "SO2", "o3": "OZONE", "nox": "NOX", "pm25": "PM2.5", "pm10": "PM10" }; - return pollutant_map?.[pollutant] ?? "OTHER"; - /* - // Way 2 - // Array Map - const pollutant_map = {'co':'CO','no':'NO','no2':'NO2','so2':'SO2','o3':'OZONE','nox':'NOX','pm25':'PM2.5','pm10':'PM10'} - const pollutant_group = [pollutant] - var [pollutant] = pollutant_group.map(x => pollutant_map[x]); - return pollutant; - */ - /* - // Way 1 - // Switch Case - switch (pollutant) { - case 'co': return 'CO'; - case 'no': return 'NO'; - case 'no2': return 'NO2'; - case 'so2': return 'SO2'; - case 'o3': return 'OZONE'; - case 'nox': return 'NOX'; - case 'pm25': return 'PM2.5'; - case 'pm10': return 'PM10'; - default: return "OTHER"; - } - */ -}; - -// Function 2 -// Convert Time Format -// https://github.com/Hackl0us/SS-Rule-Snippet/blob/master/Scripts/Surge/weather_aqi_us/iOS15_Weather_AQI_US.js -function convertTime(time, action, api) { - switch (action) { - case 'remain': - time.setMilliseconds(0); - break; - case 'add-1h-floor': - time.setHours(time.getHours() + 1); - time.setMinutes(0, 0, 0); - break; - default: - $.log(`⚠️ ${$.name}, Time Converter, Error`, `time: ${time}`, ''); - } - if (api == "v1") { - let timeString = time.getTime() / 1000; - return timeString; - } - if (api == "v2") { - let timeString = time.toISOString().split('.')[0] + 'Z'; - return timeString; - } -}; - -// Function 3 -// Calculate Air Quality Level -// https://github.com/Hackl0us/SS-Rule-Snippet/blob/master/Scripts/Surge/weather_aqi_us/iOS15_Weather_AQI_US.js -function classifyAirQualityLevel(aqiIndex) { - if (aqiIndex >= 0 && aqiIndex <= 50) return 1; - else if (aqiIndex >= 51 && aqiIndex <= 100) return 2; - else if (aqiIndex >= 101 && aqiIndex <= 150) return 3; - else if (aqiIndex >= 151 && aqiIndex <= 200) return 4; - else if (aqiIndex >= 201 && aqiIndex <= 300) return 5; - else if (aqiIndex >= 301 && aqiIndex <= 500) return 6; - else { - $.log(`⚠️ ${$.name}, classifyAirQualityLevel, Error`, `aqiIndex: ${aqiIndex}`, ''); - return 0; - } -}; - -/***************** Env *****************/ -// prettier-ignore -// https://github.com/chavyleung/scripts/blob/master/Env.min.js -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("",`🔔${this.name}, 开始!`)}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("@"),a={url:`http://${h}/v1/scripting/evaluate`,body:{script_text:t,mock_type:"cron",timeout:r},headers:{"X-Key":o,Accept:"*/*"}};this.post(a,(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,i=rawOpts["update-pasteboard"]||rawOpts.updatePasteboard;return{"open-url":e,"media-url":s,"update-pasteboard":i}}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=["","==============📣系统通知📣=============="];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("",`❗️${this.name}, 错误!`,t.stack):this.log("",`❗️${this.name}, 错误!`,t)}wait(t){return new Promise(e=>setTimeout(e,t))}done(t={}){const e=(new Date).getTime(),s=(e-this.startTime)/1e3;this.log("",`🔔${this.name}, 结束! 🕛 ${s} 秒`),this.log(),(this.isSurge()||this.isQuanX()||this.isLoon())&&$done(t)}}(t,e)} diff --git a/Archive/js/Apple_Weather_Map.js b/Archive/js/Apple_Weather_Map.js deleted file mode 100644 index 013b0d014..000000000 --- a/Archive/js/Apple_Weather_Map.js +++ /dev/null @@ -1,379 +0,0 @@ -/* -README:https://github.com/VirgilClyne/iRingo -*/ - -const $ = new Env('Apple_Weather_Map'); - -var url = $request.url; -var headers = $request.headers; -$.VAL_headers = { - 'Host': `tiles.waqi.info`, - //'Content-Type': `application/x-www-form-urlencoded`, - 'Origin': `https://waqi.info`, - 'User-Agent': `Mozilla/5.0 (iPhone; CPU iPhone OS 15_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.1 Mobile/15E148 Safari/605.1.15`, - 'Referer': `https://waqi.info/`, -} - -!(async () => { - await getOrigin(url) - await convertTilesCoord($.lat, $.lng, 'gps84_To_gcj02') - await outputUrl('usepa-aqi', $.newLat, $.newLng, $.alt) - //await getTiles('usepa-aqi', $.wgs84togcj02[0], $.wgs84togcj02[1], $.alt) - //await ConvertTiles(png) - //await outputData($.Tiles) -})() - .catch((e) => $.logErr(e)) - .finally(() => $.done()) - -// Step 1 -// Get Origin Parameter -function getOrigin(url) { - return new Promise((resove) => { - const Regular = /^https?:\/\/(weather-map)\.apple\.com\/(v1|v2)\/mapOverlay\/airQuality.*/; - //const Regular = /^https?:\/\/(weather-map)\.apple\.com\/(v1|v2)\/mapOverlay\/airQuality.*x=(-?\d+)&y=(-?\d+)&z=(-?\d+).*(country=[A-Z]{2})?.*/; - try { - [$.url, $.dataServer, $.apiVer] = url.match(Regular); - $.lat = processQuery(url, 'x') - $.lng = processQuery(url, 'y') - $.alt = processQuery(url, 'z') - $.countryCode = processQuery(url, 'country') - $.log(`🎉 ${$.name}, getOrigin`, `Finish`, $.url, $.dataServer, $.apiVer, $.lat, $.lng, $.alt, $.countryCode, '') - } catch (e) { - $.log(`❗️ ${$.name}, getOrigin, Failure`, ` error = ${e}`, '') - } finally { - $.log(`🎉 ${$.name}, getOrigin, Finish`, '') - resove() - } - }) -}; - -// Step 2 -// Convert Tiles Coordinates -// https://github.com/CntChen/tile-lnglat-transform -function convertTilesCoord(lat, lng, type) { - return new Promise((resove) => { - center = L.coordConver().gps84_To_gcj02(lng, lat); - try { - const _data = JSON.parse(center) - if (error) throw new Error(error) - $.newLng = _data.lng; - $.newLat = _data.lat; - resove() - } catch (e) { - $.log(`❗️ ${$.name}, convertGeo`, `Failure`, ` error = ${error || e}`, '') - } finally { - $.log(`🎉 ${$.name}, convertGeo`, `Finish`, `${type}: ${lat},${lng} => ${$.newLat},${$.newLng}`, center, '') - resove() - } - }); -}; - -// Step 3 -// Get WAQI Air Quality Map Tiles -// https://tiles.waqi.info/tiles/{aqi}/{z}/{x}/{y}.png -// https://tiles.aqicn.org/tiles/{aqi}/{z}/{x}/{y}.png -function getTiles(aqi, lat, lng, alt) { - //if ($.country = 'CN') - return new Promise((resove) => { - const url = { url: `https://tiles.waqi.info/tiles/${aqi}/${alt}/${lat}/${lng}.png`, headers: $.VAL_headers } - $.get(url, (error, response, data) => { - try { - const _data = data - if (error) throw new Error(error) - $.Tiles = _data - } catch (e) { - $.log(`❗️ ${$.name}, getTiles`, `Failure`, ` error = ${error || e}`, `response = ${JSON.stringify(response)}`, `data = ${data}`, '') - } finally { - $.log(`🎉 ${$.name}, getTiles`, `Finish`, '') - resove() - } - }); - }) -}; - -/* -// Step 4 -// Convert WAQI Air Quality Map Tiles Color Format -function ConvertTiles(png) { - //if ($.country = 'CN') - return new Promise((resove) => { - - $.post(url, (error, response, data) => { - try { - if (error) throw new Error(error) - } - else { - $.log(`⚠️ ${$.name}, ConvertTiles`, `Error`, `data = ${data}`, '') - } - } catch (e) { - $.log(`❗️ ${$.name}, ConvertTiles执行失败!`, ` error = ${error || e}`, `response = ${JSON.stringify(response)}`, `data = ${data}`, '') - } finally { - $.log(`🎉 ${$.name}, ConvertTiles`, `Finish`, '') - resove() - } - }) - }) -}; -*/ - - -// Step 5 -// Output URL -function outputUrl(aqi, lat, lng, alt) { - return new Promise((resove) => { - try { - url = `https://tiles.waqi.info/tiles/${aqi}/${alt}/${lat}/${lng}.png`; - headers = $.VAL_headers; - $.done({ url, headers }); - } catch (e) { - $.log(`❗️ ${$.name}, outputUrl`, `Failure`, ` error = ${error || e}`, `response = ${JSON.stringify(response)}`, `data = ${data}`, '') - } finally { - $.log(`🎉 ${$.name}, outputUrl`, `Finish`, '') - resove() - } - }) -}; - -// Step 5 -// Output Data -function outputData(png) { - return new Promise((resove) => { - let body = $response.rawBody - try { - if (png) body = png; - $.log(`🎉 ${$.name}, outputData`, `Finish`, '') - $done({ body }); - } catch (e) { - $.log(`❗️ ${$.name}, getAQIstatus, Failure`, ` error = ${e}`, '') - } finally { - $.log(`🎉 ${$.name}, getOrigin, Finish`, '') - resove() - } - }) -}; - -// Funtion 1 -// 查询并替换自身,url为链接,variable为参数,parameter为新值(如果有就替换) -// https://github.com/VirgilClyne/iRingo/blob/main/js/QueryURL.js -function processQuery(url, variable, parameter) { - console.log(`processQuery, INPUT: variable: ${variable}, parameter: ${parameter}`, ``); - if (url.indexOf("?") != -1) { - if (parameter == undefined) { - console.log(`getQueryVariable, INPUT: variable: ${variable}`, ``); - var query = url.split("?")[1]; - var vars = query.split("&"); - for (var i = 0; i < vars.length; i++) { - var pair = vars[i].split("="); - if (pair[0] == variable) { - console.log(`getQueryVariable, OUTPUT: ${variable}=${pair[1]}`, ``); - return pair[1]; - } - } - console.log(`getQueryVariable, ERROR: No such variable: ${variable}, Skip`, ``); - return false; - } else if (parameter != undefined) { - console.log(`replaceQueryParamter, INPUT: ${variable}=${parameter}, Start`, ``); - var re = new RegExp('(' + variable + '=)([^&]*)', 'gi') - var newUrl = url.replace(re, variable + '=' + parameter) - console.log(`replaceQueryParamter, OUTPUT: ${variable}=${parameter}`, newUrl, ``); - return newUrl - } else { - console.log(`processQuery, ERROR: No such variable: ${variable}, Skip`, ``); - return url; - } - } else { - console.log(`processQuery, ERROR: No such URL ,Skip`, url, ``); - return url; - } -}; - - -//坐标转换 -L.CoordConver = function () { - - /**百度转84*/ - this.bd09_To_gps84 = function(lng, lat) { - var gcj02 = this.bd09_To_gcj02(lng, lat); - var map84 = this.gcj02_To_gps84(gcj02.lng, gcj02.lat); - return map84; - } - /**84转百度*/ - this.gps84_To_bd09 = function (lng, lat) { - var gcj02 = this.gps84_To_gcj02(lng, lat); - var bd09 = this.gcj02_To_bd09(gcj02.lng, gcj02.lat); - return bd09; - } - /**84转火星*/ - this.gps84_To_gcj02 = function (lng, lat) { - var dLat = transformLat(lng - 105.0, lat - 35.0); - var dLng = transformLng(lng - 105.0, lat - 35.0); - var radLat = lat / 180.0 * pi; - var magic = Math.sin(radLat); - magic = 1 - ee * magic * magic; - var sqrtMagic = Math.sqrt(magic); - dLat = (dLat * 180.0) / ((a * (1 - ee)) / (magic * sqrtMagic) * pi); - dLng = (dLng * 180.0) / (a / sqrtMagic * Math.cos(radLat) * pi); - var mgLat = lat + dLat; - var mgLng = lng + dLng; - var newCoord = { - lng: mgLng, - lat: mgLat - }; - return newCoord; - } - /**火星转84*/ - this.gcj02_To_gps84 = function (lng, lat) { - var coord = transform(lng, lat); - var lontitude = lng * 2 - coord.lng; - var latitude = lat * 2 - coord.lat; - var newCoord = { - lng: lontitude, - lat: latitude - }; - return newCoord; - } - /**火星转百度*/ - this.gcj02_To_bd09 = function (x, y) { - var z = Math.sqrt(x * x + y * y) + 0.00002 * Math.sin(y * x_pi); - var theta = Math.atan2(y, x) + 0.000003 * Math.cos(x * x_pi); - var bd_lng = z * Math.cos(theta) + 0.0065; - var bd_lat = z * Math.sin(theta) + 0.006; - var newCoord = { - lng: bd_lng, - lat: bd_lat - }; - return newCoord; - } - /**百度转火星*/ - this.bd09_To_gcj02 = function (bd_lng, bd_lat) { - var x = bd_lng - 0.0065; - var y = bd_lat - 0.006; - var z = Math.sqrt(x * x + y * y) - 0.00002 * Math.sin(y * x_pi); - var theta = Math.atan2(y, x) - 0.000003 * Math.cos(x * x_pi); - var gg_lng = z * Math.cos(theta); - var gg_lat = z * Math.sin(theta); - var newCoord = { - lng: gg_lng, - lat: gg_lat - }; - return newCoord; - } - - var pi = 3.1415926535897932384626; - var a = 6378245.0; - var ee = 0.00669342162296594323; - var x_pi = pi * 3000.0 / 180.0; - var R = 6378137; - - function transform(lng, lat) { - var dLat = transformLat(lng - 105.0, lat - 35.0); - var dLng = transformLng(lng - 105.0, lat - 35.0); - var radLat = lat / 180.0 * pi; - var magic = Math.sin(radLat); - magic = 1 - ee * magic * magic; - var sqrtMagic = Math.sqrt(magic); - dLat = (dLat * 180.0) / ((a * (1 - ee)) / (magic * sqrtMagic) * pi); - dLng = (dLng * 180.0) / (a / sqrtMagic * Math.cos(radLat) * pi); - var mgLat = lat + dLat; - var mgLng = lng + dLng; - var newCoord = { - lng: mgLng, - lat: mgLat - }; - return newCoord; - } - - function transformLat(x, y) { - var ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * Math.sqrt(Math.abs(x)); - ret += (20.0 * Math.sin(6.0 * x * pi) + 20.0 * Math.sin(2.0 * x * pi)) * 2.0 / 3.0; - ret += (20.0 * Math.sin(y * pi) + 40.0 * Math.sin(y / 3.0 * pi)) * 2.0 / 3.0; - ret += (160.0 * Math.sin(y / 12.0 * pi) + 320 * Math.sin(y * pi / 30.0)) * 2.0 / 3.0; - return ret; - } - - function transformLng(x, y) { - var ret = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * Math.sqrt(Math.abs(x)); - ret += (20.0 * Math.sin(6.0 * x * pi) + 20.0 * Math.sin(2.0 * x * pi)) * 2.0 / 3.0; - ret += (20.0 * Math.sin(x * pi) + 40.0 * Math.sin(x / 3.0 * pi)) * 2.0 / 3.0; - ret += (150.0 * Math.sin(x / 12.0 * pi) + 300.0 * Math.sin(x / 30.0 * pi)) * 2.0 / 3.0; - return ret; - } -} - -L.coordConver = function () { - return new L.CoordConver() -} - -L.tileLayer.chinaProvider = function (type, options) { - options = options || {} - options.corrdType = getCorrdType(type); - return new L.TileLayer.ChinaProvider(type, options); - - //获取坐标类型 - function getCorrdType(type) { - var parts = type.split('.'); - var providerName = parts[0]; - var zbName = "wgs84" - switch (providerName) { - case "Geoq": - case "GaoDe": - case "Google": - zbName = "gcj02"; - break; - case "Baidu": - zbName = "bd09"; - break; - case "OSM": - case "TianDiTu": - zbName = "wgs84"; - break; - } - return zbName; - } -}; - -L.GridLayer.include({ - _setZoomTransform: function (level, _center, zoom) { - var center = _center; - if (center != undefined && this.options) { - if (this.options.corrdType == 'gcj02') { - center = L.coordConver().gps84_To_gcj02(_center.lng, _center.lat); - } else if (this.options.corrdType == 'bd09') { - center = L.coordConver().gps84_To_bd09(_center.lng, _center.lat); - } - } - var scale = this._map.getZoomScale(zoom, level.zoom), - translate = level.origin.multiplyBy(scale) - .subtract(this._map._getNewPixelOrigin(center, zoom)).round(); - - if (L.Browser.any3d) { - L.DomUtil.setTransform(level.el, translate, scale); - } else { - L.DomUtil.setPosition(level.el, translate); - } - }, - _getTiledPixelBounds: function (_center) { - var center = _center; - if (center != undefined && this.options) { - if (this.options.corrdType == 'gcj02') { - center = L.coordConver().gps84_To_gcj02(_center.lng, _center.lat); - } else if (this.options.corrdType == 'bd09') { - center = L.coordConver().gps84_To_bd09(_center.lng, _center.lat); - } - } - var map = this._map, - mapZoom = map._animatingZoom ? Math.max(map._animateToZoom, map.getZoom()) : map.getZoom(), - scale = map.getZoomScale(mapZoom, this._tileZoom), - pixelCenter = map.project(center, this._tileZoom).floor(), - halfSize = map.getSize().divideBy(scale * 2); - - return new L.Bounds(pixelCenter.subtract(halfSize), pixelCenter.add(halfSize)); - } -}) - -/***************** Env *****************/ -// prettier-ignore -// https://github.com/chavyleung/scripts/blob/master/Env.min.js -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("",`🔔${this.name}, 开始!`)}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("@"),a={url:`http://${h}/v1/scripting/evaluate`,body:{script_text:t,mock_type:"cron",timeout:r},headers:{"X-Key":o,Accept:"*/*"}};this.post(a,(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,i=rawOpts["update-pasteboard"]||rawOpts.updatePasteboard;return{"open-url":e,"media-url":s,"update-pasteboard":i}}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=["","==============📣系统通知📣=============="];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("",`❗️${this.name}, 错误!`,t.stack):this.log("",`❗️${this.name}, 错误!`,t)}wait(t){return new Promise(e=>setTimeout(e,t))}done(t={}){const e=(new Date).getTime(),s=(e-this.startTime)/1e3;this.log("",`🔔${this.name}, 结束! 🕛 ${s} 秒`),this.log(),(this.isSurge()||this.isQuanX()||this.isLoon())&&$done(t)}}(t,e)} -// https://github.com/gisarmory/Leaflet.InternetMapCorrection/blob/master/dist/leaflet.mapCorrection.min.js -// L.CoordConver=function(){function a(b,c){var d=-100+2*b+3*c+.2*c*c+.1*b*c+.2*Math.sqrt(Math.abs(b)),d=d+2*(20*Math.sin(6*b*e)+20*Math.sin(2*b*e))/3,d=d+2*(20*Math.sin(c*e)+40*Math.sin(c/3*e))/3;return d+=2*(160*Math.sin(c/12*e)+320*Math.sin(c*e/30))/3}function f(b,c){var d=300+b+2*c+.1*b*b+.1*b*c+.1*Math.sqrt(Math.abs(b)),d=d+2*(20*Math.sin(6*b*e)+20*Math.sin(2*b*e))/3,d=d+2*(20*Math.sin(b*e)+40*Math.sin(b/3*e))/3;return d+=2*(150*Math.sin(b/12*e)+300*Math.sin(b/30*e))/3}this.getCorrdType=function(b){var c="wgs84";switch(b.split(".")[0]){case "Geoq":case "GaoDe":case "Google":c="gcj02";break;case "Baidu":c="bd09";break;case "OSM":case "TianDiTu":c="wgs84"}return c};this.bd09_To_gps84=function(b,c){var d=this.bd09_To_gcj02(b,c);return this.gcj02_To_gps84(d.lng,d.lat)};this.gps84_To_bd09=function(b,c){var d=this.gps84_To_gcj02(b,c);return this.gcj02_To_bd09(d.lng,d.lat)};this.gps84_To_gcj02=function(b,c){var d=a(b-105,c-35),k=f(b-105,c-35),l=c/180*e,g=Math.sin(l),g=1-n*g*g,m=Math.sqrt(g),d=180*d/(h*(1-n)/(g*m)*e),k=180*k/(h/m*Math.cos(l)*e);return{lng:b+k,lat:c+d}};this.gcj02_To_gps84=function(b,c){var d=a(b-105,c-35),k=f(b-105,c-35),l=c/180*e,g=Math.sin(l),g=1-n*g*g,m=Math.sqrt(g),d=180*d/(h*(1-n)/(g*m)*e),k=180*k/(h/m*Math.cos(l)*e);return{lng:2*b-(b+k),lat:2*c-(c+d)}};this.gcj02_To_bd09=function(b,c){var d=Math.sqrt(b*b+c*c)+2E-5*Math.sin(c*p),a=Math.atan2(c,b)+3E-6*Math.cos(b*p);return{lng:d*Math.cos(a)+.0065,lat:d*Math.sin(a)+.006}};this.bd09_To_gcj02=function(b,c){var d=b-.0065,a=c-.006,e=Math.sqrt(d*d+a*a)-2E-5*Math.sin(a*p),d=Math.atan2(a,d)-3E-6*Math.cos(d*p);return{lng:e*Math.cos(d),lat:e*Math.sin(d)}};var e=3.141592653589793,h=6378245,n=.006693421622965943,p=3E3*e/180};L.coordConver=function(){return new L.CoordConver};L.TileLayer.ChinaProvider.include({addTo:function(a){a.options.corrdType||(a.options.corrdType=this.options.corrdType);a.addLayer(this);return this}});L.tileLayer.chinaProvider=function(a,f){f=f||{};f.corrdType=L.coordConver().getCorrdType(a);return new L.TileLayer.ChinaProvider(a,f)};L.GridLayer.include({_setZoomTransform:function(a,f,e){var h=f;void 0!=h&&this.options&&("gcj02"==this.options.corrdType?h=L.coordConver().gps84_To_gcj02(f.lng,f.lat):"bd09"==this.options.corrdType&&(h=L.coordConver().gps84_To_bd09(f.lng,f.lat)));f=this._map.getZoomScale(e,a.zoom);e=a.origin.multiplyBy(f).subtract(this._map._getNewPixelOrigin(h,e)).round();L.Browser.any3d?L.DomUtil.setTransform(a.el,e,f):L.DomUtil.setPosition(a.el,e)},_getTiledPixelBounds:function(a){var f=a;void 0!=f&&this.options&&("gcj02"==this.options.corrdType?f=L.coordConver().gps84_To_gcj02(a.lng,a.lat):"bd09"==this.options.corrdType&&(f=L.coordConver().gps84_To_bd09(a.lng,a.lat)));a=this._map;var e=a._animatingZoom?Math.max(a._animateToZoom,a.getZoom()):a.getZoom(),e=a.getZoomScale(e,this._tileZoom),f=a.project(f,this._tileZoom).floor();a=a.getSize().divideBy(2*e);return new L.Bounds(f.subtract(a),f.add(a))}}); diff --git a/Archive/js/Apple_Weather_NextHour.beta.js b/Archive/js/Apple_Weather_NextHour.beta.js deleted file mode 100644 index e67864c6f..000000000 --- a/Archive/js/Apple_Weather_NextHour.beta.js +++ /dev/null @@ -1,231 +0,0 @@ -/* -README:https://github.com/VirgilClyne/iRingo -*/ - -//你的KEY,参见https://dev.qweather.com/docs/resource/get-key/ -const key = 123456789 - -const $ = new Env('Apple_Weather'); -!(async () => { - await getOrigin($request.url) - await getNextHourStatus($response.body) - await getGridWeatherMinutely($.lat, $.lng, key) - await outputData($.lat, $.lng, $.getPrecipitation, $.Minutely) -})() - .catch((e) => $.logErr(e)) - .finally(() => $.done()) - -// Step 1 -// Get Origin Parameter -function getOrigin(url) { - const Regular = /^https?:\/\/(weather-data)\.apple\.com\/(v1|v2)\/weather\/([\w-_]+)\/(-?\d+\.\d+)\/(-?\d+\.\d+).*(next_hour_forecast|forecastNextHour).*(country=[A-Z]{2})?.*/; - [$.url, $.dataServer, $.apiVer, $.language, $.lat, $.lng, $.NextHour, $.countryCode] = url.match(Regular); - //return parameter = $request.url.match(url); - $.log(`🎉 ${$.name}, getOrigin`, `Finish`, $.url, $.dataServer, $.apiVer, $.language, $.lat, $.lng, $.NextHour, $.countryCode, '') -} - -// Step 2 -// forecastNextHour Source Status -function getNextHourStatus(body) { - return new Promise((resove) => { - const weather = JSON.parse(body); - try { - if ($.apiVer == 'v1') { - $.log(`⚠️ ${$.name}, getNextHourStatus`, `AQ data ${$.apiVer}`, ''); - if (weather.next_hour_forecast.metadata) { - $.log(`⚠️ ${$.name}, getNextHourStatus, Abort`, `${weather.next_hour_forecast.metadata.provider_name}`, ''); - $.done() - } else { - $.log(`🎉 ${$.name}, getNextHourStatus, Continue`, `目前没有v1版数据,Abort`, '') - $.done() - } - } else if ($.apiVer == 'v2') { - $.log(`⚠️ ${$.name}, getNextHourStatus`, `AQ data ${$.apiVer}`, ''); - if (weather.forecastNextHour.metadata) { - $.log(`⚠️ ${$.name}, getNextHourStatus, Abort`, `${weather.forecastNextHour.metadata.providerName}`, ''); - $.done() - } else { - $.log(`🎉 ${$.name}, getNextHourStatus, Continue`, '') - resove() - } - } else { - $.log(`🎉 ${$.name}, getAQIstatus, non-existent Next Hour Forecast data, Continue`, '') - resove() - } - } catch (e) { - $.log(`❗️ ${$.name}, getAQIstatus`, `Failure`, ` error = ${e}`, '') - } - }) -}; - -// Step 3 -// Get Nearest forecast Next Hour Station AQI Data -// https://www.weatherol.cn/api/minute/getPrecipitation?type=forecast&ll=${lng},${lat} -function getGridWeatherMinutely(lat, lng, timeout = 0) { - return new Promise((resove) => { - if ($.countryCode = 'CN') { - lang = switchLanguage($.language) - setTimeout( ()=>{ - const url = { - url: `https://www.weatherol.cn/api/minute/getPrecipitation?type=forecast&ll=${lng},${lat}`, - } - $.get(url, (error, response, data) => { - try { - const _data = JSON.parse(data) - if (error) throw new Error(error) - if (_data.status == "ok") { - $.getPrecipitation = _data - $.Minutely = _data.result.minutely - /* - if (_data.minutely[0]) { - $.Minutely = _data; - resove() - } else { - $.log(`❗️ ${$.name}, getGridWeatherMinutely`, `minutely Empty`, `code: ${_data.code}`, `data = ${data}`, `连接正常,数据为空`, '') - resove() - } - */ - } else { - $.log(`❗️ ${$.name}, getGridWeatherMinutely`, `Code Error`, `code: ${_data.code}`, `data = ${data}`, '') - resove() - } - } catch (e) { - $.log(`❗️ ${$.name}, getGridWeatherMinutely执行失败!`, ` error = ${error || e}`, `response = ${JSON.stringify(response)}`, `data = ${data}`, '') - } finally { - //$.log(`⚠️ ${$.name}, getStation`, `Finish`, `data = ${data}`, '') - $.log(`🎉 ${$.name}, getGridWeatherMinutely`, `Finish`, '') - resove() - } - }) - },timeout) - } - }) -}; - -// Step 6 -// Output Data -function outputData(lat, lng, obs, minutely) { - let body = $response.body - let weather = JSON.parse(body); - - // Input Data - if ($.apiVer == "v1") { - $.log(`⚠️ ${$.name}, Detect`, `AQ next_hour_forecast ${$.apiVer}`, `没有V1版数据`, ''); - $.done() - }; - if ($.apiVer == "v2") { - $.log(`⚠️ ${$.name}, Detect`, `forecastNextHour data ${$.apiVer}`, ''); - if (!weather.forecastNextHour) { - $.log(`⚠️ ${$.name}, non-existent forecastNextHour data`, `creating`, ''); - weather.forecastNextHour = { - "metadata": { - "units": "m", - "version": 2, - }, - "startTime": "", - "condition": [], - "minutes": [], - "name": "NextHourForecast", - "summary": [] - } - } - if (obs) { // From Observation Station - weather.forecastNextHour.source = obs.refer.sources[0]; - weather.forecastNextHour.learnMoreURL = obs.fxLink; - weather.forecastNextHour.metadata.longitude = obs.location[0]; - weather.forecastNextHour.metadata.providerLogo = ""; - weather.forecastNextHour.metadata.providerName = obs.refer.sources[0]; - weather.forecastNextHour.metadata.expireTime = convertTime(new Date(obs.updateTime), 'add-1h-floor'); - weather.forecastNextHour.metadata.language ? weather.forecastNextHour.metadata.language : weather.currentWeather.metadata.language; - //weather.forecastNextHour.metadata.language = $.language; - weather.forecastNextHour.metadata.latitude = obs.location[1]; - weather.forecastNextHour.metadata.reportedTime = convertTime(new Date(obs.updateTime), 'remain'); - weather.forecastNextHour.metadata.readTime = convertTime(new Date(), 'remain'); - //weather.forecastNextHour.metadata.units = "m"; - } - if (minutely) { // From Observation Station - var maps= new Map([['fxTime','startTime'],['precip','precipIntensity']]); - obs.minutely = obs.minutely.map(element =>{ - element.placeCode = maps.get(element.placeCode); - return element; - }); - weather.forecastNextHour.minutes = obs.minutely - } - }; - body = JSON.stringify(weather); - $.log(`🎉 ${$.name}, outputData`, `Finish`, '') - $done({ body }); -}; - -// Step 6.1 -// Switch Language -function switchLanguage(lang) { - const languageCode = ['zh','zh-Hans', 'zh-Hant'] - if (lang.includes(languageCode)) return 'zh'; - else return 'en' - /* - switch (lang) { - case lang.includes(language): - return 'zh'; - case 'zh-Hans-CN': - return 'zh'; - case 'zh-Hans-HK': - return 'zh'; - case 'zh-Hans-TW': - return 'zh'; - case 'zh-Hans-US': - return 'zh' - default: - return 'en'; - } - */ -}; - -// Step 6.2 -// Convert Time Format -// https://github.com/Hackl0us/SS-Rule-Snippet/blob/master/Scripts/Surge/weather_aqi_us/iOS15_Weather_AQI_US.js -function convertTime(time, action) { - switch (action) { - case 'remain': - time.setMilliseconds(0); - break; - case 'add-1h-floor': - time.setHours(time.getHours() + 1); - time.setMinutes(0, 0, 0); - break; - default: - $.log(`⚠️ ${$.name}, Time Converter`, `Error`, ''); - } - if ($.apiVer == "v1") { - let timeString = time.getTime() / 1000; - return timeString; - } - if ($.apiVer == "v2") { - let timeString = time.toISOString().split('.')[0] + 'Z'; - return timeString; - } -}; - -// Step 6.3 -// Calculate Air Quality Level -// https://github.com/Hackl0us/SS-Rule-Snippet/blob/master/Scripts/Surge/weather_aqi_us/iOS15_Weather_AQI_US.js -function classifyAirQualityLevel(aqiIndex) { - if (aqiIndex >= 0 && aqiIndex <= 50) { - return 1; - } else if (aqiIndex >= 51 && aqiIndex <= 100) { - return 2; - } else if (aqiIndex >= 101 && aqiIndex <= 150) { - return 3; - } else if (aqiIndex >= 151 && aqiIndex <= 200) { - return 4; - } else if (aqiIndex >= 201 && aqiIndex <= 300) { - return 5; - } else if (aqiIndex >= 301) { - return 6; - } -}; - -/***************** Env *****************/ -// prettier-ignore -// https://github.com/chavyleung/scripts/blob/master/Env.min.js -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("",`🔔${this.name}, 开始!`)}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("@"),a={url:`http://${h}/v1/scripting/evaluate`,body:{script_text:t,mock_type:"cron",timeout:r},headers:{"X-Key":o,Accept:"*/*"}};this.post(a,(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,i=rawOpts["update-pasteboard"]||rawOpts.updatePasteboard;return{"open-url":e,"media-url":s,"update-pasteboard":i}}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=["","==============📣系统通知📣=============="];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("",`❗️${this.name}, 错误!`,t.stack):this.log("",`❗️${this.name}, 错误!`,t)}wait(t){return new Promise(e=>setTimeout(e,t))}done(t={}){const e=(new Date).getTime(),s=(e-this.startTime)/1e3;this.log("",`🔔${this.name}, 结束! 🕛 ${s} 秒`),this.log(),(this.isSurge()||this.isQuanX()||this.isLoon())&&$done(t)}}(t,e)} \ No newline at end of file diff --git a/Archive/js/Apple_Weather_NextHour.js b/Archive/js/Apple_Weather_NextHour.js deleted file mode 100644 index 096d9e0e2..000000000 --- a/Archive/js/Apple_Weather_NextHour.js +++ /dev/null @@ -1,241 +0,0 @@ -/* -README:https://github.com/VirgilClyne/iRingo -*/ - -//你的KEY,参见https://dev.qweather.com/docs/resource/get-key/ -const key = '123456789ABC' - -const $ = new Env('Apple_Weather'); -!(async () => { - await getOrigin($request.url) - await getNextHourStatus($response.body) - await getGridWeatherMinutely($.lat, $.lng, key, switchLanguage($.language)) - await outputData($.lat, $.lng, $.GridWeather) -})() - .catch((e) => $.logErr(e)) - .finally(() => $.done()) - -// Step 1 -// Get Origin Parameter -function getOrigin(url) { - const Regular = /^https?:\/\/(weather-data)\.apple\.com\/(v1|v2)\/weather\/([\w-_]+)\/(-?\d+\.\d+)\/(-?\d+\.\d+).*(next_hour_forecast|forecastNextHour).*(country=[A-Z]{2})?.*/; - [$.url, $.dataServer, $.apiVer, $.language, $.lat, $.lng, $.NextHour, $.countryCode] = url.match(Regular); - //return parameter = $request.url.match(url); - $.log(`🎉 ${$.name}, getOrigin`, `Finish`, $.url, $.dataServer, $.apiVer, $.language, $.lat, $.lng, $.NextHour, $.countryCode, '') -} - -// Step 2 -// forecastNextHour Source Status -function getNextHourStatus(body) { - return new Promise((resove) => { - const weather = JSON.parse(body); - try { - if ($.apiVer == 'v1') { - $.log(`⚠️ ${$.name}, getNextHourStatus`, `AQ data ${$.apiVer}`, ''); - if (weather.next_hour_forecast.metadata) { - $.log(`⚠️ ${$.name}, getNextHourStatus, Abort`, `${weather.next_hour_forecast.metadata.provider_name}`, ''); - $.done() - } else { - $.log(`🎉 ${$.name}, getNextHourStatus, Continue`, `目前没有v1版数据,Abort`, '') - $.done() - } - } else if ($.apiVer == 'v2') { - $.log(`⚠️ ${$.name}, getNextHourStatus`, `AQ data ${$.apiVer}`, ''); - if (weather.forecastNextHour.metadata) { - $.log(`⚠️ ${$.name}, getNextHourStatus, Abort`, `${weather.forecastNextHour.metadata.providerName}`, ''); - $.done() - } else { - $.log(`🎉 ${$.name}, getNextHourStatus, Continue`, '') - resove() - } - } else { - $.log(`🎉 ${$.name}, getAQIstatus, non-existent Next Hour Forecast data, Continue`, '') - resove() - } - } catch (e) { - $.log(`❗️ ${$.name}, getAQIstatus`, `Failure`, ` error = ${e}`, '') - } - }) -}; - -// Step 3 -// Get Nearest forecast Next Hour Station AQI Data -// https://dev.qweather.com/docs/api/grid-weather/minutely/ -// https://api.qweather.com/v7/minutely/5m?location=${lng},${lat}&key=${key}&lang=${lang} -function getGridWeatherMinutely(lat, lng, key, lang, timeout = 0) { - return new Promise((resove) => { - if ($.countryCode = 'CN') { - setTimeout( ()=>{ - const url = { - url: `https://api.qweather.com/v7/minutely/5m?location=${lng},${lat}&key=${key}&lang=${lang}`, - } - $.get(url, (error, response, data) => { - try { - const _data = JSON.parse(data) - if (error) throw new Error(error) - if (_data.code == "200") { - $.GridWeather = _data - $.Minutely = _data.minutely - /* - if (_data.minutely[0]) { - $.Minutely = _data; - resove() - } else { - $.log(`❗️ ${$.name}, getGridWeatherMinutely`, `minutely Empty`, `code: ${_data.code}`, `data = ${data}`, `连接正常,数据为空`, '') - resove() - } - */ - } else { - $.log(`❗️ ${$.name}, getGridWeatherMinutely`, `Code Error`, `code: ${_data.code}`, `data = ${data}`, '') - resove() - } - } catch (e) { - $.log(`❗️ ${$.name}, getGridWeatherMinutely执行失败!`, ` error = ${error || e}`, `response = ${JSON.stringify(response)}`, `data = ${data}`, '') - } finally { - //$.log(`⚠️ ${$.name}, getStation`, `Finish`, `data = ${data}`, '') - $.log(`🎉 ${$.name}, getGridWeatherMinutely`, `Finish`, '') - resove() - } - }) - },timeout) - } - }) -}; - -// Step 6 -// Output Data -function outputData(lat, lng, obs) { - let body = $response.body - let weather = JSON.parse(body); - - // Input Data - if ($.apiVer == "v1") { - $.log(`⚠️ ${$.name}, Detect`, `AQ next_hour_forecast ${$.apiVer}`, `没有V1版数据`, ''); - $.done() - }; - if ($.apiVer == "v2") { - $.log(`⚠️ ${$.name}, Detect`, `forecastNextHour data ${$.apiVer}`, ''); - if (!weather.forecastNextHour) { - $.log(`⚠️ ${$.name}, non-existent forecastNextHour data`, `creating`, ''); - weather.forecastNextHour = { - "metadata": { - "units": "m", - "version": 2, - }, - "startTime": "", - "condition": [], - "minutes": [], - "name": "NextHourForecast", - "summary": [] - } - } - if (obs) { // From Observation Station - weather.forecastNextHour.source = obs.refer.sources[0]; - weather.forecastNextHour.learnMoreURL = obs.fxLink; - weather.forecastNextHour.metadata.longitude = lng; - weather.forecastNextHour.metadata.providerLogo = ""; - weather.forecastNextHour.metadata.providerName = obs.refer.sources[0]; - weather.forecastNextHour.metadata.expireTime = convertTime(new Date(obs.updateTime), 'add-1h-floor'); - weather.forecastNextHour.metadata.language ? weather.forecastNextHour.metadata.language : weather.currentWeather.metadata.language; - //weather.forecastNextHour.metadata.language = $.language; - weather.forecastNextHour.metadata.latitude = lat; - weather.forecastNextHour.metadata.reportedTime = convertTime(new Date(obs.updateTime), 'remain'); - weather.forecastNextHour.metadata.readTime = convertTime(new Date(), 'remain'); - //weather.forecastNextHour.metadata.units = "m"; - } - if (obs.minutely) { // From Observation Station - /* - var maps= new Map([['fxTime','startTime'],['precip','precipIntensity']]); - obs.minutely = obs.minutely.map(element =>{ - element.placeCode = maps.get(element.placeCode); - return element; - }); - weather.forecastNextHour.minutes = obs.minutely - */ - obs.minutely = obs.minutely.map(element =>{ - return { - startTime: convertTime(new Date(element.fxTime), 'remain'), //五分钟 - precipChance: element.precipChance, //没有概率 - precipIntensityPerceived: element.precipIntensityPerceived, //没有体感 - precipIntensity: element.precip, //只有降水量 - } - }) - } - }; - body = JSON.stringify(weather); - $.log(`🎉 ${$.name}, outputData`, `Finish`, '') - $done({ body }); -}; - -// Step 6.1 -// Switch Language -function switchLanguage(lang) { - const languageCode = ['zh','zh-Hans', 'zh-Hant'] - if (lang.includes(languageCode)) return 'zh'; - else return 'en' - /* - switch (lang) { - case lang.includes(language): - return 'zh'; - case 'zh-Hans-CN': - return 'zh'; - case 'zh-Hans-HK': - return 'zh'; - case 'zh-Hans-TW': - return 'zh'; - case 'zh-Hans-US': - return 'zh' - default: - return 'en'; - } - */ -}; - -// Step 6.2 -// Convert Time Format -// https://github.com/Hackl0us/SS-Rule-Snippet/blob/master/Scripts/Surge/weather_aqi_us/iOS15_Weather_AQI_US.js -function convertTime(time, action) { - switch (action) { - case 'remain': - time.setMilliseconds(0); - break; - case 'add-1h-floor': - time.setHours(time.getHours() + 1); - time.setMinutes(0, 0, 0); - break; - default: - $.log(`⚠️ ${$.name}, Time Converter`, `Error`, ''); - } - if ($.apiVer == "v1") { - let timeString = time.getTime() / 1000; - return timeString; - } - if ($.apiVer == "v2") { - let timeString = time.toISOString().split('.')[0] + 'Z'; - return timeString; - } -}; - -// Step 6.3 -// Calculate Air Quality Level -// https://github.com/Hackl0us/SS-Rule-Snippet/blob/master/Scripts/Surge/weather_aqi_us/iOS15_Weather_AQI_US.js -function classifyAirQualityLevel(aqiIndex) { - if (aqiIndex >= 0 && aqiIndex <= 50) { - return 1; - } else if (aqiIndex >= 51 && aqiIndex <= 100) { - return 2; - } else if (aqiIndex >= 101 && aqiIndex <= 150) { - return 3; - } else if (aqiIndex >= 151 && aqiIndex <= 200) { - return 4; - } else if (aqiIndex >= 201 && aqiIndex <= 300) { - return 5; - } else if (aqiIndex >= 301) { - return 6; - } -}; - -/***************** Env *****************/ -// prettier-ignore -// https://github.com/chavyleung/scripts/blob/master/Env.min.js -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("",`🔔${this.name}, 开始!`)}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("@"),a={url:`http://${h}/v1/scripting/evaluate`,body:{script_text:t,mock_type:"cron",timeout:r},headers:{"X-Key":o,Accept:"*/*"}};this.post(a,(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,i=rawOpts["update-pasteboard"]||rawOpts.updatePasteboard;return{"open-url":e,"media-url":s,"update-pasteboard":i}}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=["","==============📣系统通知📣=============="];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("",`❗️${this.name}, 错误!`,t.stack):this.log("",`❗️${this.name}, 错误!`,t)}wait(t){return new Promise(e=>setTimeout(e,t))}done(t={}){const e=(new Date).getTime(),s=(e-this.startTime)/1e3;this.log("",`🔔${this.name}, 结束! 🕛 ${s} 秒`),this.log(),(this.isSurge()||this.isQuanX()||this.isLoon())&&$done(t)}}(t,e)} diff --git a/Archive/js/Siri_Suggestions.js b/Archive/js/Siri_Suggestions.js deleted file mode 100644 index 67a573985..000000000 --- a/Archive/js/Siri_Suggestions.js +++ /dev/null @@ -1,114 +0,0 @@ -/* -README:https://github.com/VirgilClyne/iRingo -*/ - -var url = $request.url; -const locale = processQuery(url, 'locale'); //Region Setting -if (locale) var cc = locale.match(/[A-Z]{2}/g) //CountryCode, Redirect to Region Setting -const esl = processQuery(url, 'esl'); //Environment System Language? :Display Language Setting -const qtype = processQuery(url, 'qtype'); //Search Type -const card_locale = locale //Infomation Card Locale, Redirect to Region Setting -const siri_locale = processQuery(url, 'siri_locale'); //Siri Locale Setting -const storefront = processQuery(url, 'storefront') //StoreFront Setting, from App Store Region -if (storefront) var sf = storefront.match(/[\d]{6}/g) //StoreFront ID, from App Store Region - -console.log(`Start, locale=${locale}, cc=${cc}, esl=${esl}, card_locale=${card_locale}, storefront=${storefront}`, ``); - -const url0 = "smoot.apple.cn"; -const path1 = "/bag?"; -const path2 = "/search?"; -const path3 = "/card?"; - -// URL -if (url.indexOf(url0) != -1) url.replace(/smoot\.apple\.cn/g, 'smoot.apple.com'); //Redirect .cn to .com - -// PATH -if (url.indexOf(path1) != -1) { //Bag - url = (cc == 'CN') ? processQuery(url, 'cc', 'TW') : processQuery(url, 'cc', cc); - console.log(path1, `locale=${locale}, cc=${cc}`, ``); - $done({ url }); -} -else if (url.indexOf(path2) != -1) { //Search - url = (cc == 'CN') ? processQuery(url, 'cc', 'TW') : processQuery(url, 'cc', cc); - if (qtype == 'zkw') { // 处理'新闻'小组件 - ['CN', 'HK', 'MO', 'TW', 'SG'].includes(`${cc}`) ? url = processQuery(url, 'locale', `${esl}_SG`) - : ['US', 'CA', 'UK', 'AU'].includes(`${cc}`) ? url = processQuery(url, 'locale') - : url = processQuery(url, 'locale', `${esl}_US`); - } else { // 其他搜索 - let q = processQuery(url, 'q') - if (q.match(/^%E5%A4%A9%E6%B0%94%20/)) { // 处理'天气'搜索,搜索词'天气 '开头 - console.log('Type A', ``); - q = q.replace(/%E5%A4%A9%E6%B0%94/, 'weather') // '天气'替换为'weather' - if (q.match(/^weather%20.*%E5%B8%82$/) == null) q = q.replace(/$/, '%E5%B8%82') - url = processQuery(url, 'q', q) - } else if (q.match(/%20%E5%A4%A9%E6%B0%94$/)) {// 处理'天气'搜索,搜索词' 天气'结尾 - console.log('Type B', ``); - q = q.replace(/%E5%A4%A9%E6%B0%94/, 'weather') // '天气'替换为'weather' - if (q.match(/.*%E5%B8%82%20weather$/) == null) q = q.replace(/%20weather$/, '%E5%B8%82%20weather') - url = processQuery(url, 'q', q) - } - url = processQuery(url, 'card_locale', card_locale); - //url = processQuery(url, 'storefront', '143464-19%2C29'); //SG - //url = processQuery(url, 'storefront', '143441-19%2C29'); //US - }; - console.log(path2, `locale=${locale}, cc=${cc}, qtype=${qtype}, card_locale=${card_locale}`, ``); - $done({ url }); -} -else if (url.indexOf(path3) != -1) { //Card - url = (cc == 'CN') ? processQuery(url, 'cc', 'TW') : processQuery(url, 'cc', cc); - url = processQuery(url, 'card_locale', card_locale); - //url = processQuery(url, 'storefront', '143464-19%2C29'); //SG - //url = processQuery(url, 'storefront', '143441-19%2C29'); //US - if (processQuery(url, 'include') == 'movies') { - let A = processQuery(url, 'q') - if (sf == '143463') newA = A.replace(/%2F[a-z]{2}-[A-Z]{2}/, '%2Fzh-HK') - else if (sf == '143470') newA = A.replace(/%2F[a-z]{2}-[A-Z]{2}/, '%2Fzh-TW') - else if (sf == '143464') newA = A.replace(/%2F[a-z]{2}-[A-Z]{2}/, '%2Fzh-SG') - else newA = A - url = processQuery(url, 'q', newA) - } - else if (processQuery(url, 'include') == 'tv') { - let A = processQuery(url, 'q') - if (sf == '143463') newA = A.replace(/%2F[a-z]{2}-[A-Z]{2}/, '%2Fzh-HK') - else if (sf == '143470') newA = A.replace(/%2F[a-z]{2}-[A-Z]{2}/, '%2Fzh-TW') - else if (sf == '143464') newA = A.replace(/%2F[a-z]{2}-[A-Z]{2}/, '%2Fzh-SG') - else newA = A - url = processQuery(url, 'q', newA) - }; - console.log(path3, `locale=${locale}, cc=${cc}, card_locale=${card_locale}, storefront=${storefront}`, ``); - $done({ url }); -} -else $done({}); - -// Function 1 -// process Query URL -// 查询并替换自身,url为链接,variable为参数,parameter为新值(如果有就替换) -// https://github.com/VirgilClyne/iRingo/blob/main/js/QueryURL.js -function processQuery(url, variable, parameter) { - //console.log(`processQuery, INPUT: variable: ${variable}, parameter: ${parameter}`, url, ``); - if (url.indexOf("?") != -1) { - if (parameter == undefined) { - //console.log(`getQueryVariable, INPUT: variable: ${variable}`, ``); - var query = url.split("?")[1]; - var vars = query.split("&"); - for (var i = 0; i < vars.length; i++) { - var pair = vars[i].split("="); - if (pair[0] == variable) { - //console.log(`getQueryVariable, OUTPUT: ${variable}=${pair[1]}`, ``); - return pair[1]; - } - } - console.log(`getQueryVariable, ERROR: No such variable: ${variable}, Skip`, ``); - return false; - } else { - //console.log(`replaceQueryParamter, INPUT: ${variable}=${parameter}, Start`, ``); - var re = new RegExp('(' + variable + '=)([^&]*)', 'gi') - var newUrl = url.replace(re, variable + '=' + parameter) - //console.log(`replaceQueryParamter, OUTPUT: ${variable}=${parameter}`, newUrl, ``); - return newUrl - }; - } else { - console.log(`processQuery, ERROR: No such URL ,Skip`, url, ``); - return url; - } -}; diff --git a/Archive/js/Weather.Availability.js b/Archive/js/Weather.Availability.js deleted file mode 100644 index 84e882154..000000000 --- a/Archive/js/Weather.Availability.js +++ /dev/null @@ -1,90 +0,0 @@ -/* -README:https://github.com/VirgilClyne/iRingo -*/ - -const $ = new Env("Apple Weather Availability v1.0.4"); -const DataBase = { - "Location":{ - "Settings":{"Switch":true,"PEP":{"GCC":"US"},"Services":{"Dispatcher":"AUTO","Directions":"AUTO","Traffic":"AUTO","Tiles":"AUTO"},"Geo_manifest":{"Dynamic":{"Config":{"Country_code":"CN"}}},"Config":{"Announcements":{"Environment:":"prod-cn"},"Defaults":{"LagunaBeach":true,"DrivingMultiWaypointRoutesEnabled":true,"GEOAddressCorrection":true,"LookupMaxParametersCount":true,"LocalitiesAndLandmarks":true,"PedestrianAR":true,"6694982d2b14e95815e44e970235e230":true,"OpticalHeading":true,"UseCLPedestrianMapMatchedLocations":true,"WiFiQualityNetworkDisabled":false,"WiFiQualityTileDisabled":false}}} - }, - "Weather":{ - "Settings":{"Switch":true,"NextHour":{"Switch":true},"AQI":{"Switch":true,"Mode":"WAQI Public","Location":"Station","Auth":null,"Scale":"EPA_NowCast.2204"},"Map":{"AQI":false}}, - "Configs":{"Availability":["currentWeather","forecastDaily","forecastHourly","history","weatherChange","forecastNextHour","severeWeather","airQuality"],"Pollutants":{"co":"CO","no":"NO","no2":"NO2","so2":"SO2","o3":"OZONE","nox":"NOX","pm25":"PM2.5","pm10":"PM10","other":"OTHER"}} - }, - "Siri":{ - "Settings":{"Switch":true,"CountryCode":"SG","Domains":["web","itunes","app_store","movies","restaurants","maps"],"Functions":["flightutilities","lookup","mail","messages","news","safari","siri","spotlight","visualintelligence"],"Safari_Smart_History":true} - }, - "TV":{ - "Settings":{"Switch":true,"Third-Party":true,"Configs":{"CountryCode":"AUTO","Tabs":["WatchNow","Originals","Movies","TV","Sports","Kids","Library","Search"]},"View":{"CountryCode":["SG","TW"]},"WatchNow":{"CountryCode":"AUTO"},"Channels":{"CountryCode":"AUTO"},"Originals":{"CountryCode":"TW"},"Movies":{"CountryCode":"AUTO"},"TV":{"CountryCode":"AUTO"},"Sports":{"CountryCode":"US"},"Kids":{"CountryCode":"US"},"Persons":{"CountryCode":"SG"},"Search":{"CountryCode":"TW"},"Others":{"CountryCode":"AUTO"}}, - "Configs":{ - "Locale":{"AU":"en-AU","CA":"en-CA","GB":"en-GB","KR":"ko-KR","HK":"yue-Hant","JP":"ja-JP","MO":"zh-Hant","TW":"zh-Hant","US":"en-US","SG":"zh-Hans"}, - "Tabs":{"zh":{"WatchNow":"立即观看","Originals":"原创内容","Movies":"电影","TV":"电视节目","Store":"商店","Sports":"体育节目","Kids":"儿童","Library":"资料库","Search":"搜索"},"zh-Hans":{"WatchNow":"立即观看","Originals":"原创内容","Movies":"电影","TV":"电视节目","Store":"商店","Sports":"体育节目","Kids":"儿童","Library":"资料库","Search":"搜索"},"zh-Hant":{"WatchNow":"立即觀看","Originals":"原創內容","Movies":"電影","TV":"電視節目","Store":"商店","Sports":"體育節目","Kids":"兒童","Library":"資料庫","Search":"蒐索"},"en":{"WatchNow":"Watch Now","Originals":"Originals","Movies":"Movies","TV":"TV Shows","Store":"Store","Sports":"Sports","Kids":"Kids","Library":"Library","Search":"Search"}} - } - }, - "News":{ - "Settings":{"Switch":true,"CountryCode":"US","newsPlusUser":"AUTO"} - }, - "Default": { - "Settings":{"Switch":true}, - "Configs":{ - "Storefront":{"AF":"143610","AL":"143575","AO":"143564","AI":"143538","AG":"143540","AR":"143505","AM":"143524","AU":"143460","AT":"143445","AZ":"143568","BA":"143612","BS":"143539","BH":"143559","BB":"143541","BD":"143490","BY":"143565","BE":"143446","BZ":"143555","BJ":"143576","BM":"143542","BT":"143577","BO":"143556","BW":"143525","BR":"143503","VG":"143543","BN":"143560","BG":"143526","BF":"143578","CA":"143455","CI":"143527","CM":"143574","CV":"143580","KY":"143544","TD":"143581","CL":"143483","CN":"143465","CO":"143501","CG":"143582","CR":"143495","HR":"143494","CY":"143557","CZ":"143489","DK":"143458","DM":"143545","DO":"143508","DZ":"143563","EC":"143509","EG":"143516","SV":"143506","EE":"143518","FJ":"143583","FI":"143447","FR":"143442","GM":"143584","DE":"143443","GH":"143573","GR":"143448","GD":"143546","GT":"143504","GW":"143585","GY":"143553","HN":"143510","HK":"143463","HU":"143482","IS":"143558","IN":"143467","ID":"143476","IE":"143449","IL":"143491","IT":"143450","JM":"143511","JP":"143462","JO":"143528","KH":"143579","KR":"143466","KZ":"143517","KE":"143529","KW":"143493","KG":"143586","LA":"143587","LV":"143519","LB":"143497","LR":"143588","LT":"143520","LI":"143522","LU":"143451","MO":"143515","MK":"143530","MG":"143531","MW":"143589","MY":"143473","MV":"143488","ML":"143532","MT":"143521","MR":"143590","MU":"143533","MX":"143468","FM":"143591","MD":"143523","MN":"143592","MS":"143547","MZ":"143593","NA":"143594","NP":"143484","NL":"143452","NZ":"143461","NI":"143512","NE":"143534","NG":"143561","NO":"143457","OM":"143562","PK":"143477","PW":"143595","PA":"143485","PG":"143597","PY":"143513","PE":"143507","PH":"143474","PL":"143478","PT":"143453","QA":"143498","RO":"143487","RU":"143469","ST":"143598","SA":"143479","SN":"143535","SC":"143599","SL":"143600","SG":"143464","SK":"143496","SI":"143499","SB":"143601","ZA":"143472","KP":"143466","ES":"143454","LK":"143486","KN":"143548","LC":"143549","VC":"143550","SR":"143554","SZ":"143602","SE":"143456","CH":"143459","TW":"143470","TJ":"143603","TZ":"143572","TH":"143475","TT":"143551","TN":"143536","TR":"143480","TM":"143604","TC":"143552","AE":"143481","UG":"143537","UA":"143492","GB":"143444","US":"143441","UY":"143514","UZ":"143566","VE":"143502","VN":"143471","YE":"143571","ZW":"143605","CD":"143613","GA":"143614","GF":"143615","IQ":"143617","XK":"143624","LY":"143567","ME":"143619","MA":"143620","MM":"143570","NR":"143606","RW":"143621","RS":"143500","TO":"143608","VU":"143609","ZM":"143622"} - } - } -}; - -/***************** Processing *****************/ -!(async () => { - const { Settings, Caches, Configs } = await setENV("iRingo", "Weather", DataBase); - if (Settings.Switch) { - $.log(`🎉 ${$.name}, 可用性检查`, ""); - if ($response.statusCode === 200 || $response.status === 200) { - $.log($response.statusCode || $response.status); - let data = JSON.parse($response.body); - data = Array.from(new Set([...data, ...Configs.Availability])); - $.log(`🎉 ${$.name}, 功能列表`, JSON.stringify(data), ""); - $response.body = JSON.stringify(data); - } - } -})() - .catch((e) => $.logErr(e)) - .finally(() => { - if ($.isQuanX()) $.done({ body: $response.body }) - else $.done($response) - }) - -/***************** Async Function *****************/ -/** - * Get Environment Variables - * @link https://github.com/VirgilClyne/VirgilClyne/blob/main/function/getENV/getENV.min.js - * @author VirgilClyne - * @param {String} t - Persistent Store Key - * @param {String} e - Platform Name - * @param {Object} n - Default Database - * @return {Promise<*>} - */ -async function getENV(t,e,n){let i=$.getjson(t,n),s={};if("undefined"!=typeof $argument&&Boolean($argument)){let t=Object.fromEntries($argument.split("&").map((t=>t.split("="))));for(let e in t)f(s,e,t[e])}let g={...n?.Default?.Settings,...n?.[e]?.Settings,...i?.[e]?.Settings,...s},o={...n?.Default?.Configs,...n?.[e]?.Configs,...i?.[e]?.Configs},a=i?.[e]?.Caches||void 0;return"string"==typeof a&&(a=JSON.parse(a)),{Settings:g,Caches:a,Configs:o};function f(t,e,n){e.split(".").reduce(((t,i,s)=>t[i]=e.split(".").length===++s?n:t[i]||{}),t)}} - -/** - * Set Environment Variables - * @author VirgilClyne - * @param {String} name - Persistent Store Key - * @param {String} platform - Platform Name - * @param {Object} database - Default DataBase - * @return {Promise<*>} - */ -async function setENV(name, platform, database) { - $.log(`⚠ ${$.name}, Set Environment Variables`, ""); - let { Settings, Caches = {}, Configs } = await getENV(name, platform, database); - /***************** Prase *****************/ - Settings.Switch = JSON.parse(Settings.Switch) // BoxJs字符串转Boolean - Settings.NextHour.Switch = JSON.parse(Settings.NextHour.Switch) // BoxJs字符串转Boolean - Settings.AQI.Switch = JSON.parse(Settings.AQI.Switch) // BoxJs字符串转Boolean - Settings.Map.AQI = JSON.parse(Settings.Map.AQI) // BoxJs字符串转Boolean - $.log(`🎉 ${$.name}, Set Environment Variables`, `Settings: ${typeof Settings}`, `Settings内容: ${JSON.stringify(Settings)}`, ""); - return { Settings, Caches, Configs } -}; - -/***************** Env *****************/ -// prettier-ignore -// https://github.com/chavyleung/scripts/blob/master/Env.min.js -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}isStash(){return"undefined"!=typeof $environment&&$environment["stash-version"]}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,a]=i.split("@"),n={url:`http://${a}/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),a=i?"null"===o?null:o||"{}":"{}";try{const e=JSON.parse(a);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?s.status:s.statusCode,s.status=s.statusCode),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&&t.error||"UndefinedError"));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:a}=t,n=s.decode(a,this.encoding);e(null,{status:i,statusCode:r,headers:o,rawBody:a,body:n},n)},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?s.status:s.statusCode,s.status=s.statusCode),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&&t.error||"UndefinedError"));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:a}=t,n=i.decode(a,this.encoding);e(null,{status:s,statusCode:r,headers:o,rawBody:a,body:n},n)},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,i=t["update-pasteboard"]||t.updatePasteboard;return{"open-url":e,"media-url":s,"update-pasteboard":i}}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):this.isNode()&&process.exit(1)}}(t,e)} diff --git a/Archive/js/Weather.Map.js b/Archive/js/Weather.Map.js deleted file mode 100644 index d4fe0c463..000000000 --- a/Archive/js/Weather.Map.js +++ /dev/null @@ -1,119 +0,0 @@ -/* -README:https://github.com/VirgilClyne/iRingo -*/ - -const $ = new Env("Apple Weather Map v1.0.0"); -const URL = new URLSearch(); -const DataBase = { - "Weather":{"Switch":true,"NextHour":{"Switch":true},"AQI":{"Switch":true,"Mode":"WAQI Public","Location":"Station","Auth":null,"Scale":"EPA_NowCast.2201"},"Map":{"AQI":false}}, - "Siri":{"Switch":true,"CountryCode":"TW","Domains":["web","itunes","app_store","movies","restaurants","maps"],"Functions":["flightutilities","lookup","mail","messages","news","safari","siri","spotlight","visualintelligence"],"Safari_Smart_History":true} -}; -var { url, headers } = $request; - -/***************** Processing *****************/ -!(async () => { - const Settings = await setENV("iRingo", "Weather", DataBase); - if (Settings.Switch) { - if (Settings.Map.AQI) { - url = URL.parse(url); - if (url.path?.includes("airQuality") || url?.params?.country == "CN") { - let request = await WAQI("tiles", { aqi: "usepa-aqi", lat: url.params?.x, lng: url.params?.y, alt: url.params?.z }); - url = request.url; - headers = request.headers; - } else url = URL.stringify(url); - } - } -})() - .catch((e) => $.logErr(e)) - .finally(() => $.done({ url, headers })) - -/***************** Async Function *****************/ -/** - * Set Environment Variables - * @author VirgilClyne - * @param {String} name - Persistent Store Key - * @param {String} platform - Platform Name - * @param {Object} database - Default DataBase - * @return {Promise<*>} - */ -async function setENV(name, platform, database) { - $.log(`⚠ ${$.name}, Set Environment Variables`, ""); - let Settings = await getENV(name, platform, database); - /***************** Prase *****************/ - Settings.Switch = JSON.parse(Settings.Switch) // BoxJs字符串转Boolean - Settings.NextHour.Switch = JSON.parse(Settings.NextHour.Switch) // BoxJs字符串转Boolean - Settings.AQI.Switch = JSON.parse(Settings.AQI.Switch) // BoxJs字符串转Boolean - Settings.Map.AQI = JSON.parse(Settings.Map.AQI) // BoxJs字符串转Boolean - $.log(`🎉 ${$.name}, Set Environment Variables`, `Settings: ${typeof Settings}`, `Settings内容: ${JSON.stringify(Settings)}`, ""); - return Settings - async function getENV(t,e,n){let i=$.getjson(t,n),r=i?.[e]||i?.Settings?.[e]||n[e];if("undefined"!=typeof $argument){if($argument){let t=Object.fromEntries($argument.split("&").map((t=>t.split("=")))),e={};for(var s in t)f(e,s,t[s]);Object.assign(r,e)}function f(t,e,n){e.split(".").reduce(((t,i,r)=>t[i]=e.split(".").length===++r?n:t[i]||{}),t)}}return r} -}; - -/** - * Get WAQI Air Quality Map Tiles - * https://tiles.waqi.info/tiles/{aqi}/{z}/{x}/{y}.png - * https://tiles.aqicn.org/tiles/{aqi}/{z}/{x}/{y}.png - * @author VirgilClyne - * @param {String} name - Persistent Store Key - * @param {String} url - Request URL - * @param {Object} database - Default DataBase - * @return {Promise<*>} - */ -async function WAQI(type = "", input = {}) { - $.log(`⚠ ${$.name}, WAQI`, `input: ${JSON.stringify(input)}`, ""); - // 构造请求 - let request = await GetRequest(type, input); - $.log(`🚧 ${$.name}, WAQI`, `request: ${JSON.stringify(request)}`, ""); - return request - // 发送请求 - //let output = await GetData(type, request); - // TODO: add debug switch (geo) - //$.log(`🚧 ${$.name}, WAQI`, `output: ${JSON.stringify(output)}`, ""); - //return output - /***************** Fuctions *****************/ - async function GetRequest(type = "", input = { aqi: "usepa-aqi", lat: 0, lng: 0, alt: 0 }) { - $.log(`⚠ ${$.name}, Get WAQI Request, type: ${type}`, ""); - let request = { - "url": "https://tiles.waqi.info", - "headers": { - "Host": "tiles.waqi.info", - "Content-Type": "application/x-www-form-urlencoded", - "Origin": "https://waqi.info", - "User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 15_1_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.1 Mobile/15E148 Safari/604.1", - "Referer": "https://waqi.info/" - } - }; - if (type == "tiles") { - $.log('tiles'); - request.url = `${request.url}/tiles/${input.aqi}/${input.alt}/${input.lat}/${input.lng}.png`; - } - //$.log(`🎉 ${$.name}, Get WAQI Request`, `request: ${JSON.stringify(request)}`, ""); - return request - }; - - function GetData(type, request) { - $.log(`⚠ ${$.name}, Get WAQI Data, type: ${type}`, ""); - return new Promise(resolve => { - $.get(request, (error, response, data) => { - try { - if (error) throw new Error(error) - else if (data) resolve(response) - else throw new Error(response); - } catch (e) { - $.log(`❗️ ${$.name}, getTiles`, `Failure`, ` error = ${error || e}`, `response = ${JSON.stringify(response)}`, `data = ${data}`, '') - } finally { - $.log(`🎉 ${$.name}, getTiles`, `Finish`, '') - resolve() - } - }); - }) - }; -}; - -/***************** Env *****************/ -// prettier-ignore -// https://github.com/chavyleung/scripts/blob/master/Env.min.js -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}isStash(){return"undefined"!=typeof $environment&&$environment["stash-version"]}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,a]=i.split("@"),n={url:`http://${a}/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),a=i?"null"===o?null:o||"{}":"{}";try{const e=JSON.parse(a);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?s.status:s.statusCode,s.status=s.statusCode),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&&t.error||"UndefinedError"));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:a}=t,n=s.decode(a,this.encoding);e(null,{status:i,statusCode:r,headers:o,rawBody:a,body:n},n)},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?s.status:s.statusCode,s.status=s.statusCode),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&&t.error||"UndefinedError"));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:a}=t,n=i.decode(a,this.encoding);e(null,{status:s,statusCode:r,headers:o,rawBody:a,body:n},n)},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,i=t["update-pasteboard"]||t.updatePasteboard;return{"open-url":e,"media-url":s,"update-pasteboard":i}}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):this.isNode()&&process.exit(1)}}(t,e)} - -// https://github.com/VirgilClyne/iRingo/blob/main/function/URLSearch.min.js -function URLSearch(s){return new class{constructor(s=[]){this.name="urlParams v1.0.0",this.opts=s,this.json={url:{scheme:"",host:"",path:""},params:{}}}parse(s){let t=s.match(/(?.+):\/\/(?[^/]+)\/?(?[^?]+)?\??(?.*)?/)?.groups??null;return t?.params&&(t.params=Object.fromEntries(t.params.split("&").map((s=>s.split("="))))),t}stringify(s=this.json){return s?.params?s.scheme+"://"+s.host+"/"+s.path+"?"+Object.entries(s.params).map((s=>s.join("="))).join("&"):s.scheme+"://"+s.host+"/"+s.path}}(s)} diff --git a/Archive/js/Weather.js b/Archive/js/Weather.js deleted file mode 100644 index f5350ad6e..000000000 --- a/Archive/js/Weather.js +++ /dev/null @@ -1,1564 +0,0 @@ -/* -README:https://github.com/VirgilClyne/iRingo -*/ - -const $ = new Env("Apple Weather v3.2.9"); -const URL = new URLs(); -const DataBase = { - "Weather":{"Switch":true,"NextHour":{"Switch":true,"Mode":"www.weatherol.cn","HTTPHeaders":{"Content-Type":"application/x-www-form-urlencoded","User-Agent":"Mozilla/5.0 (iPhone; CPU iPhone OS 15_1_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.1 Mobile/15E148 Safari/604.1"},"ColorfulClouds":{"Auth":null},},"AQI":{"Switch":true,"Mode":"WAQI Public","Location":"Station","Auth":null,"Scale":"EPA_NowCast.2204"},"Map":{"AQI":false}}, - "Siri":{"Switch":true,"CountryCode":"TW","Domains":["web","itunes","app_store","movies","restaurants","maps"],"Functions":["flightutilities","lookup","mail","messages","news","safari","siri","spotlight","visualintelligence"],"Safari_Smart_History":true}, - "Pollutants":{"co":"CO","no":"NO","no2":"NO2","so2":"SO2","o3":"OZONE","nox":"NOX","pm25":"PM2.5","pm10":"PM10","other":"OTHER"} -}; -var { url } = $request; -var { body } = $response; - -const WEATHER_TYPES = { CLEAR: "clear", RAIN: "rain", SNOW: "snow", SLEET: "sleet" }; -const PRECIPITATION_LEVEL = { INVALID: -1, NO: 0, LIGHT: 1, MODERATE: 2, HEAVY: 3, STORM: 4 }; - -// https://docs.caiyunapp.com/docs/tables/precip -const RADAR_PRECIPITATION_RANGE = { - NO: { LOWER: 0, UPPER: 0.031 }, - LIGHT: { LOWER: 0.031, UPPER: 0.25 }, - MODERATE: { LOWER: 0.25, UPPER: 0.35 }, - HEAVY: { LOWER: 0.35, UPPER: 0.48 }, - STORM: { LOWER: 0.48, UPPER: Number.MAX_VALUE }, -}; -const MMPERHR_PRECIPITATION_RANGE = { - NO: { LOWER: 0, UPPER: 0.08 }, - LIGHT: { LOWER: 0.08, UPPER: 3.44 }, - MODERATE: { LOWER: 3.44, UPPER: 11.33 }, - HEAVY: { LOWER: 11.33, UPPER: 51.30 }, - STORM: { LOWER: 51.30, UPPER: Number.MAX_VALUE }, -}; - -const WEATHER_STATUS = { - // precipIntensityPerceived <= 0 - CLEAR: "clear", - - // precipIntensityPerceived < 1 - DRIZZLE: "drizzle", - FLURRIES: "flurries", - SLEET: "sleet", - - // between - RAIN: "rain", - SNOW: "snow", - - // precipIntensityPerceived > 2 - HEAVY_RAIN: "heavy-rain", - // TODO: untested, check if it is `heavy-snow` - HEAVY_SNOW: "heavy-snow", -}; - -/***************** Processing *****************/ -!(async () => { - const Settings = await setENV("iRingo", "Weather", DataBase); - if (Settings.Switch) { - url = URL.parse(url); - const Params = await getParams(url.path); - let data = JSON.parse(body); - const Status = await getStatus(data); - // AQI - if (Settings.AQI.Switch) { - if (url.params?.include?.includes("air_quality") || url.params?.dataSets?.includes("airQuality")) { - if (Status == true) { - $.log(`🎉 ${$.name}, 需要替换AQI`, ""); - if (Settings.AQI.Mode == "WAQI Public") { - $.log(`🚧 ${$.name}, 工作模式: waqi.info 公共API`, "") - var { Station, idx } = await WAQI("Nearest", { api: "v1", lat: Params.lat, lng: Params.lng }); - const Token = await WAQI("Token", { idx: idx }); - //var NOW = await WAQI("NOW", { token:Token, idx: idx }); - var AQI = await WAQI("AQI", { token: Token, idx: idx }); - } else if (Settings.AQI.Mode == "WAQI Private") { - $.log(`🚧 ${$.name}, 工作模式: waqi.info 私有API`, "") - const Token = Settings.AQI.Auth; - if (Settings.AQI.Location == "Station") { - $.log(`🚧 ${$.name}, 定位精度: 观测站`, "") - var { Station, idx } = await WAQI("Nearest", { api: "v1", lat: Params.lat, lng: Params.lng }); - var AQI = await WAQI("StationFeed", { token: Token, idx: idx }); - } else if (Settings.AQI.Location == "City") { - $.log(`🚧 ${$.name}, 定位精度: 城市`, "") - var AQI = await WAQI("CityFeed", { token: Token, lat: Params.lat, lng: Params.lng }); - } - }; - data = await outputAQI(Params.ver, Station, AQI, data, Settings); - } else $.log(`🎉 ${$.name}, 无须替换, 跳过`, ""); - } - }; - // NextHour - if (Settings.NextHour.Switch) { - if ( - url.params?.dataSets?.includes("forecastNextHour") || - url.params?.include?.includes("next_hour_forecast") - ) { - $.log( - `🚧 ${$.name}, 下小时降水强度, ` + - `providerName = ${ - data?.forecastNextHour?.providerName ?? data?.next_hour?.provider_name - }`, "" - ); - - if (!(data?.forecastNextHour?.metadata?.providerName || data?.next_hour?.provider_name)) { - const NEXT_HOUR = (Params.ver === "v1") ? "next_hour" : "forecastNextHour"; - if (Settings.NextHour?.Mode === "api.caiyunapp.com") { - const CC_API_VERSION = "v2.6"; - const token = Settings.NextHour?.ColorfulClouds?.Auth; - const languageWithReigon = Params.language; - - if (token) { - // No official name for Japanese - let providerName = "ColorfulClouds"; - if (/zh-(Hans|CN)/.test(languageWithReigon)) { - providerName = "彩云天气"; - } else if (/zh-(Hant|HK|TW)/.test(languageWithReigon)) { - providerName = "彩雲天氣"; - } - - const weatherData = await colorfulClouds( - Settings.NextHour?.HTTPHeaders, - CC_API_VERSION, - token, - { latitude: Params.lat, longitude: Params.lng }, - // get hourly.skycon data to detect the weather type - "weather", - // unit for calculate precipitations - // https://docs.caiyunapp.com/docs/tables/precip - { "unit": "metric:v2", "lang": toColorfulCloudsLang(languageWithReigon) }, - ); - - // no data for current location, skip - if ( - weatherData && - weatherData?.result?.minutely?.datasource && - weatherData.result.minutely.datasource !== "gfs" - ) { - data[NEXT_HOUR] = await outputNextHour( - Params.ver, - colorfulCloudsToNextHour( - providerName, - weatherData.result?.hourly?.skycon, - weatherData, - ), - null, - ); - } - } - } else { - const providerName = "气象在线"; - const weatherData = await weatherOl( - Settings.NextHour?.HTTPHeaders, - "forecast", - { latitude: Params.lat, longitude: Params.lng }, - ); - - // no data for current location, skip - if ( - weatherData && - weatherData?.result?.minutely?.datasource && - weatherData.result.minutely.datasource !== "gfs" - ) { - data[NEXT_HOUR] = await outputNextHour( - Params.ver, - colorfulCloudsToNextHour( - providerName, - weatherData.result?.hourly?.skycon, - weatherData, - ), - null, - ); - } - } - - if (!( - data?.forecastNextHour?.metadata?.providerName || - data?.next_hour?.provider_name - )) { - $.log(`🚧 ${$.name}, 没有找到合适的API, 跳过`, ""); - } - } else { - //$.log(`🚧 ${$.name}, data = ${JSON.stringify(data?.forecastNextHour ?? data?.next_hour)}`, ""); - $.log(`🎉 ${$.name}, 已有下一小时降水强度信息, 跳过`, ""); - } - } - }; - body = JSON.stringify(data); - } -})() - .catch((e) => $.logErr(e)) - .finally(() => $.done({ body })) - -/***************** Async Function *****************/ -/** - * Set Environment Variables - * @author VirgilClyne - * @param {String} name - Persistent Store Key - * @param {String} platform - Platform Name - * @param {Object} database - Default DataBase - * @return {Promise<*>} - */ - async function setENV(name, platform, database) { - $.log(`⚠ ${$.name}, Set Environment Variables`, ""); - let Settings = await getENV(name, platform, database); - /***************** Prase *****************/ - Settings.Switch = JSON.parse(Settings.Switch) // BoxJs字符串转Boolean - Settings.NextHour.Switch = JSON.parse(Settings.NextHour.Switch) // BoxJs字符串转Boolean - Settings.NextHour.HTTPHeaders = typeof Settings.NextHour?.HTTPHeaders === "string" || - Settings.NextHour?.HTTPHeaders instanceof String ? - JSON.parse(Settings.NextHour.HTTPHeaders) : database.Weather.NextHour.HTTPHeaders // BoxJs字符串转Object - Settings.AQI.Switch = JSON.parse(Settings.AQI.Switch) // BoxJs字符串转Boolean - Settings.Map.AQI = JSON.parse(Settings.Map.AQI) // BoxJs字符串转Boolean - $.log(`🎉 ${$.name}, Set Environment Variables`, `Settings: ${typeof Settings}`, `Settings内容: ${JSON.stringify(Settings)}`, ""); - return Settings - async function getENV(t,e,n){let i=$.getjson(t,n),r=i?.[e]||i?.Settings?.[e]||n[e];if("undefined"!=typeof $argument){if($argument){let t=Object.fromEntries($argument.split("&").map((t=>t.split("=")))),e={};for(var s in t)f(e,s,t[s]);Object.assign(r,e)}function f(t,e,n){e.split(".").reduce(((t,i,r)=>t[i]=e.split(".").length===++r?n:t[i]||{}),t)}}return r} -}; - -/** - * Get Origin Parameters - * @author VirgilClyne - * @param {String} url - Request URL - * @return {Promise<*>} - */ -async function getParams(path) { - const Regular = /^(?v1|v2)\/weather\/(?[\w-_]+)\/(?-?\d+\.\d+)\/(?-?\d+\.\d+).*(?country=[A-Z]{2})?.*/i; - const Params = path.match(Regular).groups; - // TODO: add debug switch (lat, lng) - $.log(`🚧 ${$.name}`, `Params: ${JSON.stringify(Params)}`, ""); - return Params -}; - -/** - * Get AQI Source Status - * @author VirgilClyne - * @param {Object} data - Parsed response body JSON - * @return {Promise<*>} - */ -async function getStatus(data) { - const result = ["和风天气", "QWeather"].includes(data.air_quality?.metadata?.provider_name ?? data.airQuality?.metadata?.providerName ?? "QWeather"); - $.log(`🚧 ${$.name}, providerName = ${data.air_quality?.metadata?.provider_name ?? data.airQuality?.metadata?.providerName}`, ''); - return (result || false) -}; - -/** - * WAQI - * @author VirgilClyne - * @param {String} type - type - * @param {Object} input - verify - * @return {Promise<*>} - */ -async function WAQI(type = "", input = {}) { - // TODO: add debug switch (lat, lng) - $.log(`⚠ ${$.name}, WAQI`, `input: ${JSON.stringify(input)}`, ""); - // 构造请求 - let request = await GetRequest(type, input); - // 发送请求 - let output = await GetData(type, request); - //$.log(`🚧 ${$.name}, WAQI`, `output: ${JSON.stringify(output)}`, ""); - return output - /***************** Fuctions *****************/ - async function GetRequest(type = "", input = { api: "v2", lat: 0, lng: 0, idx: 0, token: "na" }) { - $.log(`⚠ ${$.name}, Get WAQI Request, type: ${type}`, ""); - let request = { - "url": "https://api.waqi.info", - "headers": { - "Content-Type": "application/x-www-form-urlencoded", - "Origin": "https://waqi.info", - "User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 15_1_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.1 Mobile/15E148 Safari/604.1", - "Referer": "https://waqi.info/" - } - }; - if (type == "Nearest") { - $.log('获取最近站点'); - if (input.api == "v1") mapq = "mapq"; - else if (input.api == "v2") mapq = "mapq2"; - request.url = `${request.url}/${mapq}/nearest?n=1&geo=1/${input.lat}/${input.lng}`; - } else if (type == "Token") { - $.log('获取令牌'); - request.url = `${request.url}/api/token/${input.idx}` - } else if (type == "NOW") { - $.log('获取即时信息'); - request.url = `${request.url}/api/feed/@${input.idx}/now.json` - request.body = `token=${input.token}&id=${input.idx}` - } else if (type == "AQI") { - $.log('获取空气质量信息'); - request.url = `${request.url}/api/feed/@${input.idx}/aqi.json` - request.body = `token=${input.token}&id=${input.idx}` - } else if (type == "CityFeed") { - $.log('获取城市信息'); - request.url = `${request.url}/feed/geo:${input.lat};${input.lng}/?token=${input.token}` - } else if (type == "StationFeed") { - $.log('获取站点信息'); - request.url = `${request.url}/feed/@${input.idx}/?token=${input.token}` - } - //$.log(`🎉 ${$.name}, Get WAQI Request`, `request: ${JSON.stringify(request)}`, ""); - return request - }; - - function GetData(type, request) { - $.log(`⚠ ${$.name}, Get WAQI Data, type: ${type}`, ""); - return new Promise(resolve => { - if (type == "NOW" || type == "AQI") { - $.post(request, (error, response, data) => { - try { - if (error) throw new Error(error) - else if (data) { - const _data = JSON.parse(data) - // Get Nearest Observation Station AQI Data - // https://api.waqi.info/api/feed/@station.uid/now.json - // https://api.waqi.info/api/feed/@station.uid/aqi.json - if (type == "NOW" || type == "AQI") { - if (_data.rxs.status == "ok") { - if (_data.rxs.obs.some(o => o.status == 'ok')) { - let i = _data.rxs.obs.findIndex(o => o.status == 'ok') - let m = _data.rxs.obs.findIndex(o => o.msg) - //$.obs = _data.rxs.obs[i].msg; - if (i >= 0 && m >= 0) { - $.log(`🎉 ${$.name}, GetData:${type}完成`, `i = ${i}, m = ${m}`, '') - resolve(_data.rxs.obs[i].msg) - } else if (i < 0 || m < 0) { - $.log(`❗️ ${$.name}, GetData:${type}失败`, `OBS Get Error`, `i = ${i}, m = ${m}`, `空数据,浏览器访问 https://api.waqi.info/api/feed/@${idx}/aqi.json 查看获取结果`, '') - resolve(_data.rxs.obs[i].msg) - } - } else $.log(`❗️ ${$.name}, GetData:${type}失败`, `OBS Status Error`, `obs.status: ${_data.rxs.obs[0].status}`, `data = ${data}`, '') - } else $.log(`❗️ ${$.name}, GetData:${type}失败`, `RXS Status Error`, `status: ${_data.rxs.status}`, `data = ${data}`, '') - } - } else throw new Error(response); - } catch (e) { - $.logErr(`❗️${$.name}, GetData:${type}执行失败`, ` request = ${JSON.stringify(request)}`, ` error = ${error || e}`, `response = ${JSON.stringify(response)}`, `data = ${data}`, '') - } finally { - //$.log(`🚧 ${$.name}, GetData:${type}调试信息`, ` request = ${JSON.stringify(request)}`, `data = ${data}`, '') - resolve() - } - }) - } else { - $.get(request, (error, response, data) => { - try { - if (error) throw new Error(error) - else if (data) { - const _data = JSON.parse(data) - // Search Nearest Observation Station - // https://api.waqi.info/mapq/nearest/?n=1&geo=1/lat/lng - // https://api.waqi.info/mapq2/nearest?n=1&geo=1/lat/lng - if (type == "Nearest") { - // 空值合并运算符 - var station = _data?.data?.stations?.[0] ?? _data?.d?.[0] ?? null; - var idx = station?.idx ?? station?.x ?? null; - var name = station?.name ?? station?.u ?? station?.nna ?? station?.nlo ?? null; - var aqi = station?.aqi ?? station?.v ?? null; - var distance = station?.distance ?? station?.d ?? null; - // var country = station?.cca2 ?? station?.country ?? null; - $.log(`🎉 ${$.name}, GetData:${type}完成`, `idx: ${idx}`, `观测站: ${name}`, `AQI: ${aqi}`, '') - resolve({ station, idx }) - } - // Get Nearest Observation Station Token - // https://api.waqi.info/api/token/station.uid - else if (type == "Token") { - var token = _data.rxs?.obs[0]?.msg?.token ?? "na" - $.log(`🎉 ${$.name}, GetData:${type}完成`, `token = ${token}`, '') - resolve(token) - } - // Geolocalized Feed - // https://aqicn.org/json-api/doc/#api-Geolocalized_Feed-GetGeolocFeed - // https://api.waqi.info/feed/geo::lat;:lng/?token=:token - else if (type == "CityFeed") { - var city = (_data.status == 'ok') ? _data?.data : null; - $.log(`🎉 ${$.name}, GetData:${type}完成`, `idx: ${city?.idx}`, `观测站: ${city?.city?.name}`, `AQI: ${city?.aqi}`, '') - resolve(city) - } - // Station Feed - // https://api.waqi.info/feed/@station.uid/?token=:token - else if (type == "StationFeed") { - var station = (_data.status == 'ok') ? _data?.data : null; - $.log(`🎉 ${$.name}, GetData:${type}完成`, `idx: ${station?.idx}`, `观测站: ${station?.city?.name}`, `AQI: ${station?.aqi}`, '') - resolve(station) - } - } else throw new Error(response); - } catch (e) { - $.logErr(`❗️${$.name}, GetData:${type}执行失败`, ` request = ${JSON.stringify(request)}`, ` error = ${error || e}`, `response = ${JSON.stringify(response)}`, `data = ${data}`, '') - } finally { - //$.log(`🚧 ${$.name}, GetData:${type}调试信息`, ` request = ${JSON.stringify(request)}`, `data = ${data}`, '') - resolve() - } - }) - }; - }); - }; -}; - -/** - * Get data from "气象在线" - * https://docs.caiyunapp.com/docs/v2.2/intro - * https://open.caiyunapp.com/%E9%80%9A%E7%94%A8%E9%A2%84%E6%8A%A5%E6%8E%A5%E5%8F%A3/v2.2 - * @author VirgilClyne - * @author WordlessEcho - * @param {Object} headers - HTTP headers - * @param {string} type - `forecast` or `realtime` - * @param {Object} location - { latitude, longitude } - * @return {Promise<*>} data from "气象在线" - */ - function weatherOl( - headers = { - "Content-Type": "application/x-www-form-urlencoded", - "User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 15_1_1 like Mac OS X) " + - "AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.1 Mobile/15E148 Safari/604.1", - }, - type, - location, -) { - // this API could be considered as unconfigurable ColorfulClouds API - const request = { - "headers": headers, - "url": "https://www.weatherol.cn/api/minute/getPrecipitation" + - `?type=${type}` + - `&ll=${location.longitude},${location.latitude}`, - }; - - return new Promise((resolve) => { - $.get(request, (error, response, data) => { - try { - const _data = JSON.parse(data) - - if (error) { - throw new Error(error); - } - - if (_data?.status === "ok") { - $.log(`🎉 ${$.name}, ${weatherOl.name}: 获取完成`, ''); - resolve(_data); - } else { - $.logErr( - `❗️ ${$.name}, ${weatherOl.name}: API返回失败, `, - `status = ${_data?.status}, `, '' - ); - - throw new Error( - _data?.error ?? - `API returned status: ${_data?.status}` ?? - "Failed to request www.weatherol.cn" - ); - } - } catch (e) { - $.logErr( - `❗️ ${$.name}, ${weatherOl.name}执行失败!`, - `error = ${error || e}, `, - `response = ${JSON.stringify(response)}, `, - `data = ${JSON.stringify(data)}`, '' - ); - } finally { - // $.log( - // `🚧 ${$.name}, ${weatherOl.name}: 调试信息 `, - // `request = ${JSON.stringify(request)}, `, - // `data = ${data}`, '' - // ); - resolve(); - } - }); - }); -}; - -/** - * get data from ColorfulClouds - * https://docs.caiyunapp.com/docs/intro/ - * @author WordlessEcho - * @author shindgewongxj - * @param {Object} headers - HTTP headers - * @param {string} apiVersion - ColorfulClouds API version - * @param {string} token - token for ColorfulClouds API - * @param {Object} location - { latitude, longitude } - * @param {Object} parameters - parameters pass to URL - * @return {Promise<*>} data from ColorfulClouds - */ -async function colorfulClouds( - headers = { - "Content-Type": "application/x-www-form-urlencoded", - "User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 15_1_1 like Mac OS X) " + - "AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.1 Mobile/15E148 Safari/604.1", - }, - apiVersion, - token, - location, - path = "weather", - parameters = { "alert": true, "dailysteps": 1, "hourlysteps": 24 }, -) { - $.log(`🚧 ${$.name}, 正在使用彩云天气 API`, ""); - - const parametersArray = []; - for (const [key, value] of Object.entries(parameters)) { - parametersArray.push(key + '=' + value); - } - - // Build request - const request = { - "headers": headers, - "url": `https://api.caiyunapp.com/${apiVersion}/${token}/` + - `${location.longitude},${location.latitude}/` + - // https://docs.caiyunapp.com/docs/weather/ - `${path}` + - `${parametersArray.length > 0 ? '?' + parametersArray.join('&') : ''}`, - }; - - // $.log(`🚧 ${$.name}, request = ${JSON.stringify(request)}`, ""); - - // API Document - // https://docs.caiyunapp.com/docs/introreturn - return new Promise(resolve => { - $.get(request, (error, response, data) => { - try { - const _data = JSON.parse(data); - - if (error) { - throw new Error(error); - } - - if (_data?.status === "ok") { - $.log(`🎉 ${$.name}, ${colorfulClouds.name}: 获取完成`, ''); - resolve(_data); - } else { - $.logErr( - `❗️ ${$.name}, ${colorfulClouds.name}: API返回失败, `, - `status = ${_data?.status}, `, '' - ); - - throw new Error( - _data?.error ?? - `API returned status: ${_data?.status}` ?? - "Failed to request api.caiyunapp.com" - ); - } - } catch (e) { - $.logErr( - `❗️${$.name}, ${colorfulClouds.name}: 无法获取数据 `, - `request = ${JSON.stringify(request)}, `, - `error = ${error || e}, `, - `response = ${JSON.stringify(response)}, `, - `data = ${JSON.stringify(data)}`, '' - ); - } finally { - // $.log( - // `🚧 ${$.name}, ${colorfulClouds.name}: 调试信息 `, - // `request = ${JSON.stringify(request)}, `, - // `data = ${data}`, '' - // ); - resolve(); - } - }); - }); -} - -/** - * differ rain or snow from ColorfulClouds hourly skycons - * https://docs.caiyunapp.com/docs/tables/skycon/ - * @author WordlessEcho - * @param {Array} skycons - skycon array from ColorfulClouds - * @return {string} one of WEATHER_TYPES - */ - function getCcWeatherType(skycons) { - // enough for us - const SKY_CONDITION_KEYWORDS = { CLEAR: "CLEAR", RAIN: "RAIN", SNOW: "SNOW" }; - const skyCondition = skycons?.map(skycon => skycon.value)?.find(condition => - condition.includes(SKY_CONDITION_KEYWORDS.RAIN) || - condition.includes(SKY_CONDITION_KEYWORDS.SNOW) - ); - - if (!skyCondition) { - // although this function is designed for find out rain or snow - return WEATHER_TYPES.CLEAR; - } else { - if (skyCondition.includes(SKY_CONDITION_KEYWORDS.SNOW)) { - return WEATHER_TYPES.SNOW; - } else { - return WEATHER_TYPES.RAIN; - } - } -}; - -/** - * Covert data from ColorfulClouds to NextHour object - * @author WordlessEcho - * @param {Object} dataWithMinutely - data with minutely - * @param {Array} hourlySkycons - skycon array in hourly - * @return {Object} object for `outputNextHour()` - */ - function colorfulCloudsToNextHour(providerName, hourlySkycons, dataWithMinutely) { - const SUPPORTED_APIS = [ 2 ]; - // words that used to insert into description - const AFTER = { - "zh_CN": "再过", - "zh_TW": "再過", - "ja": "その後", - "en_US": "after that", - // ColorfulClouds seems not prefer to display multiple times in en_GB - "en_GB": "after that", - }; - // splitors for description - const SPLITORS = { - "en_US": ["but ", "and "], - "en_GB": ["but ", "and "], - "zh_CN": [","], - "zh_TW": [","], - "ja": ["、"], - }; - - // version from API is beginning with `v` - function getMajorVersion(apiVersion) { return parseInt(apiVersion.slice(1)) }; - - const apiVersion = dataWithMinutely?.api_version; - const majorVersion = getMajorVersion(apiVersion); - if (!SUPPORTED_APIS.includes(majorVersion)) { - $.logErr( - `❗️${$.name}, ${colorfulCloudsToNextHour.name}: 不支持此版本的API, `, - `api_version = ${apiVersion}`, '' - ); - throw new Error(`Unsupported API version ${apiVersion}`); - } - - // the unit of server_time is second - const serverTime = parseInt(dataWithMinutely?.server_time); - const serverTimestamp = !isNaN(serverTime) ? serverTime * 1000 : (+ new Date()); - const ccLanguage = dataWithMinutely?.lang; - // example: replace `zh_CN` to `zh-CN` - const language = ccLanguage?.replace('_', '-') ?? "en-US"; - const location = { - latitude: Array.isArray(dataWithMinutely?.location) ? dataWithMinutely.location[0] : -1, - longitude: Array.isArray(dataWithMinutely?.location) && dataWithMinutely.location.length > 1 - ? dataWithMinutely.location[1] : -1, - } - const minutely = dataWithMinutely?.result?.minutely; - const minutelyDescription = minutely?.description; - const precipitationTwoHr = minutely?.precipitation_2h; - const probability = minutely?.probability; - const forecastKeypoint = dataWithMinutely?.result?.forecast_keypoint; - - let unit = "radar"; - let precipStandard = RADAR_PRECIPITATION_RANGE; - // https://docs.caiyunapp.com/docs/tables/unit/ - switch (dataWithMinutely?.unit) { - case "SI": - unit = "metersPerSecond"; - // TODO: find out the standard of this unit - precipStandard = RADAR_PRECIPITATION_RANGE; - break; - case "imperial": - unit = "inchesPerHour"; - // TODO: find out the standard of this unit - precipStandard = RADAR_PRECIPITATION_RANGE; - break; - case "metric:v2": - unit = "millimetersPerHour"; - precipStandard = MMPERHR_PRECIPITATION_RANGE; - break; - case "metric:v1": - case "metric": - default: - unit = "radar"; - precipStandard = RADAR_PRECIPITATION_RANGE; - break; - } - - function toMinutes(standard, weatherType, minutelyDescription, precipitations, probability) { - if (!Array.isArray(precipitations)) return []; - - // initialze 0 as first bound - const bounds = [0]; - for (const lastBound of bounds) { - const precipitationLevel = calculatePL(standard, precipitations[lastBound]); - // find different precipitation level as next bound - // this will ignore differences between the light and rain to avoid too many light weather - const relativeBound = precipitations.slice(lastBound).findIndex(value => { - if (precipitationLevel < PRECIPITATION_LEVEL.LIGHT) { - return calculatePL(standard, value) > PRECIPITATION_LEVEL.NO; - } else if (precipitationLevel > PRECIPITATION_LEVEL.MODERATE) { - return calculatePL(standard, value) < PRECIPITATION_LEVEL.HEAVY; - } else { - return calculatePL(standard, value) < PRECIPITATION_LEVEL.LIGHT || - calculatePL(standard, value) > PRECIPITATION_LEVEL.MODERATE; - } - }); - - if (relativeBound !== -1) { - bounds.push(lastBound + relativeBound); - } - } - - // detect weather change by description - // ignore clear - if (Math.max(...precipitations) >= standard.NO.UPPER) { - const times = minutelyDescription?.match(/\d+/g); - times?.forEach(timeInString => { - const time = parseInt(timeInString); - - if (!isNaN(time) && !(bounds.includes(time))) { - // array start from 0 - bounds.push(time - 1); - } - }); - - bounds.sort((a, b) => a - b); - } - - // initialize minutes - const minutes = []; - bounds.forEach((bound, index, bounds) => { - const sameStatusMinutes = precipitations.slice( - bound, - // use last index of precipitations if is last bound - index + 1 < bounds.length ? bounds[index + 1] : precipitations.length - 1, - ); - const precipitationLevel = calculatePL(standard, Math.max(...sameStatusMinutes)); - - sameStatusMinutes.forEach((minute, index) => minutes.push({ - weatherStatus: precipLevelToStatus(weatherType, precipitationLevel), - precipitation: minute, - // set chance to zero if clear - chance: precipitationLevel > PRECIPITATION_LEVEL.NO - // calculate order, 1 as first index - // index here is relative to bound, plus bound for real index in precipitations - // we have only 4 chances per half hour from API - ? parseInt(probability[parseInt((1 + bound + index) / 30)] * 100) : 0 - })); - }); - - return minutes; - }; - - // extract minute times that helpful for Apple to use cache data - function toDescriptions(isClear, forecastKeypoint, minutelyDescription, language) { - let longDescription = minutelyDescription ?? forecastKeypoint; - // match all numbers in descriptions to array - const parameters = {}; - - function getSentenceSplitors(language) { - switch (language) { - case "en_GB": - return SPLITORS.en_GB; - case "zh_CN": - return SPLITORS.zh_CN; - case "zh_TW": - return SPLITORS.zh_TW; - case "ja": - return SPLITORS.ja; - case "en_US": - default: - return SPLITORS.en_US; - } - }; - - function insertAfterToDescription(language, description) { - const FIRST_AT = "{firstAt}"; - // split into two part at `{firstAt}` - const splitedDescriptions = description?.split(FIRST_AT); - - switch (language) { - case "en_GB": - // take second part to skip firstAt - // append `after that` to description - splitedDescriptions[splitedDescriptions.length - 1] = - splitedDescriptions[splitedDescriptions.length - 1] - // remove stopping & later - // (.*?) will match `*At` - .replaceAll("} min later", `} min later ${AFTER.en_GB}`); - break; - case "zh_CN": - splitedDescriptions[splitedDescriptions.length - 1] = - splitedDescriptions[splitedDescriptions.length - 1] - .replaceAll("直到{", '{'); - - splitedDescriptions[splitedDescriptions.length - 1] = - splitedDescriptions[splitedDescriptions.length - 1] - .replaceAll("{", `${AFTER.zh_CN}{`); - break; - case "zh_TW": - splitedDescriptions[splitedDescriptions.length - 1] = - splitedDescriptions[splitedDescriptions.length - 1] - .replaceAll("直到{", '{'); - - splitedDescriptions[splitedDescriptions.length - 1] = - splitedDescriptions[splitedDescriptions.length - 1] - .replaceAll("{", `${AFTER.zh_TW}{`); - break; - case "ja": - // Japanese support from ColorfulClouds is broken for sometime - // https://lolic.at/notice/AJNH316TTSy1fRlOka - - // TODO: I am not familiar for Japanese, contributions welcome - splitedDescriptions[splitedDescriptions.length - 1] = - splitedDescriptions[splitedDescriptions.length - 1] - .replaceAll("{", `${AFTER.ja}{`); - break; - case "en_US": - default: - splitedDescriptions[splitedDescriptions.length - 1] = - splitedDescriptions[splitedDescriptions.length - 1] - .replaceAll("} min later", `} min later ${AFTER.en_US}`); - break; - } - - return splitedDescriptions.join(FIRST_AT); - }; - - // https://stackoverflow.com/a/20426113 - // transfer numbers into ordinal numerals - function stringifyNumber(n) { - const special = [ - 'zeroth', 'first', 'second', 'third', 'fourth', 'fifth', 'sixth', 'seventh', 'eighth', - 'ninth', 'tenth', 'eleventh', 'twelfth', 'thirteenth', 'fourteenth', 'fifteenth', - 'sixteenth', 'seventeenth', 'eighteenth', 'nineteenth', - ]; - const deca = ['twent', 'thirt', 'fort', 'fift', 'sixt', 'sevent', 'eight', 'ninet']; - - if (n < 20) return special[n]; - if (n % 10 === 0) return deca[Math.floor(n / 10) - 2] + 'ieth'; - return deca[Math.floor(n / 10) - 2] + 'y-' + special[n % 10]; - }; - - const descriptions = []; - descriptions.push({ - long: longDescription, - short: forecastKeypoint ?? minutelyDescription, - parameters, - }); - - if (!isClear) { - // split sentence by time - const allTimes = longDescription?.match(/\d+/g); - allTimes?.forEach(timeInString => { - const startIndex = longDescription.indexOf(timeInString) + timeInString.length; - const splitors = getSentenceSplitors(language); - - let splitIndex = 0; - for (const splitor of splitors) { - const index = longDescription.indexOf(splitor, startIndex) + splitor.length; - - if (index !== -1 && (splitIndex === 0 || index < splitIndex)) { - splitIndex = index; - } - } - - descriptions.push({ - long: longDescription.slice(splitIndex), - short: forecastKeypoint ?? minutelyDescription, - parameters, - }); - }); - - // format description.long and add parameters - for (const description of descriptions) { - const times = description.long?.match(/\d+/g); - times?.forEach((timeInString, index) => { - const time = parseInt(timeInString); - - if (!isNaN(time)) { - const key = `${stringifyNumber(index + 1)}At`; - - description.long = description.long.replace(timeInString, '{' + key + '}'); - // times after {firstAt} is lasting time in Apple Weather - // and will be displayed as `lasting for {secondAt} - {firstAt} min` - description.long = insertAfterToDescription(language, description.long); - description.parameters[key] = time; - } - }); - } - } - - return descriptions; - }; - - return toNextHourObject( - serverTimestamp, - language, - location, - providerName, - unit, - precipStandard, - toMinutes( - precipStandard, - getCcWeatherType(hourlySkycons), - minutelyDescription, - precipitationTwoHr, - probability, - ), - toDescriptions( - // display description only rain in one hour - !(Math.max(...precipitationTwoHr.slice(0, 59) ?? [0]) >= precipStandard.NO.UPPER), - forecastKeypoint, - minutelyDescription, - ccLanguage, - ), - ); -}; - -/** - * Produce a object for `outputNextHour()` - * @author WordlessEcho - * @param {Number} timestamp - UNIX timestamp when you get data - * @param {string} language - ISO 3166-1 language tag - * @param {Object} location - `{ latitude, longitude }` - * @param {string} providerName - provider name - * @param {string} unit - example: "mmPerHour" - * @param {Object} precipStandard - `*_PRECIPITATION_RANGE` - * @param {Array} minutes - array of `{ weatherStatus: one of WEATHER_STATUS, precipitation, - * chance: percentage (0 to 100) }` - * @param {Array} descriptions - array of `{ long: "Rain starting in {firstAt} min", - * short: "Rain for the next hour", parameters: (can be empty) { "firstAt": minutesNumber } }` - * @return {Object} object for `outputNextHour()` - */ -function toNextHourObject( - timestamp = (+ new Date()), - language, - location, - providerName, - unit, - precipStandard, - minutes, - descriptions, -) { - // it looks like Apple doesn't care unit - - // description can be more than one and relative to summary - // but there are too much works to collect different language of templates of Apple Weather - // I wish Apple could provide description from app but not API - const nextHourObject = { - timestamp, - language, - location, - providerName, - unit, - precipStandard, - minutes, - descriptions, - }; - - // $.log( - // `⚠️ ${$.name}, ${toNextHourObject.name}: `, - // `nextHourObject = ${JSON.stringify(nextHourObject)}`, '' - // ); - - return nextHourObject; -}; - -/** - * Output Air Quality Data - * @author VirgilClyne - * @param {String} apiVersion - Apple Weather API Version - * @param {Object} now - now weather data from Third-Party - * @param {Object} obs - observation station data from Third-Party - * @param {Object} weather - weather data from Apple - * @param {Object} Settings - Settings config in Box.js - * @return {Promise<*>} - */ -async function outputAQI(apiVersion, now, obs, weather, Settings) { - $.log(`⚠️ ${$.name}, ${outputAQI.name}检测`, `AQI data ${apiVersion}`, ''); - const NAME = (apiVersion == "v1") ? "air_quality" : "airQuality"; - const UNIT = (apiVersion == "v1") ? "μg\/m3" : "microgramsPerM3"; - // 创建对象 - if (!weather[NAME]) { - $.log(`⚠️ ${$.name}, 没有空气质量数据, 创建`, ''); - weather[NAME] = { - "name": "AirQuality", - "isSignificant": true, // 重要/置顶 - "pollutants": {}, - "previousDayComparison": "unknown", // 昨日同期对比 - "metadata": {}, - }; - }; - // 创建metadata - let metadata = { - "Version": (apiVersion == "v1") ? 1 : 2, - "Time": (apiVersion == "v1") ? obs?.time?.v ?? now?.t : obs?.time?.iso ?? now?.utime ?? Date(), - "Expire": 60, - "Longitude": obs?.city?.geo?.[0] ?? now?.geo?.[0] ?? weather?.currentWeather?.metadata?.longitude ?? weather?.current_observations?.metadata?.longitude, - "Latitude": obs?.city?.geo?.[1] ?? now?.geo?.[1] ?? weather?.currentWeather?.metadata?.latitude ?? weather?.current_observations?.metadata?.latitude, - "Language": weather?.[NAME]?.metadata?.language ?? weather?.currentWeather?.metadata?.language ?? weather?.current_observations?.metadata?.language, - "Name": obs?.attributions?.[0]?.name ?? "WAQI.info", - //"Name": obs?.attributions?.[obs.attributions.length - 1]?.name, - "Logo": (apiVersion == "v1") ? "https://waqi.info/images/logo.png" : "https://raw.githubusercontent.com/VirgilClyne/iRingo/main/image/waqi.info.logo.png", - "Unit": "m", - "Source": 0, //来自XX读数 0:监测站 1:模型 - }; - weather[NAME].metadata = Metadata(metadata); - // 固定数据 - weather[NAME].primaryPollutant = DataBase.Pollutants[obs?.dominentpol ?? now?.pol ?? "other"]; - weather[NAME].source = obs?.city?.name ?? now?.name ?? now?.u ?? now?.nna ?? now?.nlo ?? "WAQI"; - weather[NAME].learnMoreURL = obs?.city?.url ? `${obs?.city?.url}/${now?.country ?? now?.cca2 ?? weather[NAME].metadata.language}/m`.toLowerCase() : "https://aqicn.org/"; - // 注入数据 - if (now || obs) { - //条件运算符 & 可选链操作符 - if (apiVersion == "v1") { - weather[NAME].airQualityIndex = obs?.aqi ?? now?.aqi ?? now?.v; - weather[NAME].airQualityScale = Settings?.AQI?.Scale || "EPA_NowCast.2201"; - weather[NAME].airQualityCategoryIndex = calculateAQI(obs?.aqi ?? now?.aqi ?? now?.v); - } else if (apiVersion == "v2") { - weather[NAME].index = obs?.aqi ?? now?.aqi ?? now?.v; - weather[NAME].scale = Settings?.AQI?.Scale || "EPA_NowCast.2201"; - weather[NAME].categoryIndex = calculateAQI(obs?.aqi ?? now?.aqi ?? now?.v); - weather[NAME].sourceType = "station"; //station:监测站 modeled:模型 - } - //weather[NAME].pollutants.CO = { "name": "CO", "amount": obs?.iaqi?.co?.v || -1, "unit": UNIT }; - //weather[NAME].pollutants.NO = { "name": "NO", "amount": obs?.iaqi?.no?.v || -1, "unit": UNIT }; - //weather[NAME].pollutants.NO2 = { "name": "NO2", "amount": obs?.iaqi?.no2?.v || -1, "unit": UNIT }; - //weather[NAME].pollutants.SO2 = { "name": "SO2", "amount": obs?.iaqi?.so2?.v || -1, "unit": UNIT }; - //weather[NAME].pollutants.OZONE = { "name": "OZONE", "amount": obs?.iaqi?.o3?.v || -1, "unit": UNIT }; - //weather[NAME].pollutants.NOX = { "name": "NOX", "amount": obs?.iaqi?.nox?.v || -1, "unit": UNIT }; - //weather[NAME].pollutants["PM2.5"] = { "name": "PM2.5", "amount": obs?.iaqi?.pm25?.v || -1, "UNIT": UNIT }; - //weather[NAME].pollutants.PM10 = { "name": "PM10", "amount": obs?.iaqi?.pm10?.v || -1, "unit": UNIT }; - } else weather[NAME].metadata.temporarilyUnavailable = true; - $.log(`🎉 ${$.name}, ${outputAQI.name}完成`, ''); - return weather -}; - -/** - * output forecast NextHour Data - * @author WordlessEcho - * @author VirgilClyne - * @param {String} apiVersion - Apple Weather API Version - * @param {Object} nextHourObject - generated by `toNextHourObject()` - * @param {Object} debugOptions - nullable, debug settings configs in Box.js - * @return {Promise<*>} a `Promise` that returned edited Apple data - */ -async function outputNextHour(apiVersion, nextHourObject, debugOptions) { - $.log(`⚠️ ${$.name}, ${outputNextHour.name}检测`, `API: ${apiVersion}`, ''); - // 3 demical places in `precipIntensityPerceived` - const PERCEIVED_DECIMAL_PLACES = 1000; - // 2 demical places in `precipIntensity` - const _INTENSITY_DECIMAL_PLACES = 100; - // the graph of Apple weather is divided into three parts - const PERCEIVED_DIVIDERS = { INVALID: -1, BEGINNING: 0, BOTTOM: 1, MIDDLE: 2, TOP: 3, }; - - // 创建对象 - const nextHour = { - "name": "NextHourForecast", - //"isSignificant": true, // 重要/置顶 - "metadata": {}, - "startTime": "", - "summary": [], - "condition": [], - "minutes": [], - }; - - // 创建metadata - let metadata = { - "Version": (apiVersion == "v1") ? 1 : 2, - "Time": nextHourObject.timestamp, - "Expire": 15, - "Longitude": nextHourObject.location.longitude, - "Latitude": nextHourObject.location.latitude, - "Language": nextHourObject.language, - "Name": nextHourObject.providerName, - // should be no Logo as same as the Apple Weather in nextHour - "Logo": "https://www.weatherol.cn/images/logo.png", - "Unit": nextHourObject.units, - // untested: I guess this is as same as the data_source in AQI - "Source": 0, //来自XX读数 0:监测站 1:模型 - }; - // 注入数据 - nextHour.metadata = Metadata(metadata); - - // use next minute and set second to zero as start time in next hour forecast - const startTimestamp = nextHourObject.timestamp + 1000 * 60; - nextHour.startTime = convertTime(apiVersion, new Date(startTimestamp)); - nextHour.minutes = getMinutes(apiVersion, nextHourObject.minutes, startTimestamp); - nextHour.condition = getConditions( - apiVersion, - nextHourObject.minutes, - startTimestamp, - nextHourObject.descriptions, - ); - nextHour.summary = getSummaries(apiVersion, nextHourObject.minutes, startTimestamp); - - $.log(`🎉 ${$.name}, 下一小时降水强度替换完成`, ""); - return nextHour; - - /***************** Fuctions *****************/ - // mapping the standard preciptation level to 3 level standard of Apple - function toApplePrecipitation(standard, precipitation) { - const { - NO, - LIGHT, - MODERATE, - HEAVY, - } = standard; - - switch (calculatePL(standard, precipitation)) { - case PRECIPITATION_LEVEL.INVALID: - return PERCEIVED_DIVIDERS.INVALID; - case PRECIPITATION_LEVEL.NO: - return PERCEIVED_DIVIDERS.BEGINNING; - case PRECIPITATION_LEVEL.LIGHT: - return ( - // multiple 1000 (PERCEIVED_DECIMAL_PLACES) for precision of calculation - // base of previous levels and plus the percentage of value at its level - PERCEIVED_DIVIDERS.BEGINNING + - // from the lower of range to the value - (((precipitation - NO.UPPER) * PERCEIVED_DECIMAL_PLACES) / - // sum of the range - ((LIGHT.UPPER - LIGHT.LOWER) * PERCEIVED_DECIMAL_PLACES)) - // divided them to get percentage - // then calculate Apple standard value by percentage - // because Apple divided graph into 3 parts, value limitation is also 3 - // we omit the "multiple one" - ); - case PRECIPITATION_LEVEL.MODERATE: - return ( - PERCEIVED_DIVIDERS.BOTTOM + - (((precipitation - LIGHT.UPPER) * PERCEIVED_DECIMAL_PLACES) / - ((MODERATE.UPPER - MODERATE.LOWER) * PERCEIVED_DECIMAL_PLACES)) - ); - case PRECIPITATION_LEVEL.HEAVY: - return ( - PERCEIVED_DIVIDERS.MIDDLE + - (((precipitation - MODERATE.UPPER) * PERCEIVED_DECIMAL_PLACES) / - ((HEAVY.UPPER - HEAVY.LOWER) * PERCEIVED_DECIMAL_PLACES)) - ); - case PRECIPITATION_LEVEL.STORM: - default: - return PERCEIVED_DIVIDERS.TOP; - } - }; - - function getMinutes(apiVersion, minutesData, startTimestamp) { - // $.log(`🚧 ${$.name}, 开始设置Minutes`, ''); - const minutes = minutesData.map(({ precipitation, chance }, index) => { - const minute = { - "precipIntensity": precipitation, - "precipChance": chance, - }; - - if (apiVersion == "v1") { - minute.startAt = convertTime(apiVersion, new Date(startTimestamp), index); - minute.perceivedIntensity = toApplePrecipitation( - nextHourObject.precipStandard, precipitation, - ); - } else { - minute.startTime = convertTime(apiVersion, new Date(startTimestamp), index); - minute.precipIntensityPerceived = toApplePrecipitation( - nextHourObject.precipStandard, precipitation, - ); - } - - return minute; - }); - - // $.log(`🚧 ${$.name}, minutes = ${JSON.stringify(minutes)}`, ''); - return minutes; - }; - - function getConditions(apiVersion, minutesData, startTimestamp, descriptions) { - $.log(`🚧 ${$.name}, 开始设置conditions`, ""); - // TODO: when to add possible - const ADD_POSSIBLE_UPPER = 0; - const POSSIBILITY = { POSSIBLE: "possible" }; - const TIME_STATUS = { - CONSTANT: "constant", - START: "start", - STOP: "stop" - }; - - function toToken(possibleClear, weatherStatus, timeStatus) { - const tokenLeft = - `${possibleClear ? POSSIBILITY.POSSIBLE + '-' : ''}${weatherStatus.join('-to-')}`; - - if (timeStatus.length > 0 && weatherStatus[0] !== WEATHER_STATUS.CLEAR) { - return `${tokenLeft}.${timeStatus.join('-')}`; - } else { - // weatherStatus is clear, no timeStatus needed - return tokenLeft; - } - }; - - function needPossible(precipChance) { return precipChance < ADD_POSSIBLE_UPPER }; - - // initialize data - const slicedMinutes = minutesData.slice(0, 59); - // empty object for loop - const conditions = [{}]; - - let lastBoundIndex = 0; - let weatherStatus = [slicedMinutes[lastBoundIndex].weatherStatus]; - - for (const _condition of conditions) { - // initialize data - const index = conditions.length - 1; - const lastWeather = weatherStatus[weatherStatus.length - 1]; - const minutesForConditions = slicedMinutes.slice(lastBoundIndex); - const boundIndex = minutesForConditions - .findIndex(minute => minute.weatherStatus !== lastWeather); - - let timeStatus = [TIME_STATUS.START]; - // set descriptions as more as possible - const descriptionsIndex = index < descriptions.length ? index : descriptions.length - 1; - const condition = { - longTemplate: descriptions[descriptionsIndex].long, - shortTemplate: descriptions[descriptionsIndex].short, - parameters: {}, - }; - if (apiVersion !== "v1") { - condition.startTime = convertTime(apiVersion, new Date(startTimestamp), lastBoundIndex); - } - // time provided by nextHourObject is relative of startTimestamp - for (const [key, value] of Object.entries(descriptions[descriptionsIndex].parameters)) { - // $.log( - // `🚧 ${$.name}, `, - // `descriptions[${descriptionsIndex}].parameters.${key} = ${value}, `, - // `startTimestamp = ${startTimestamp}, `, - // `new Date(startTimestamp) = ${new Date(startTimestamp)}`, "" - // ); - - condition.parameters[key] = convertTime(apiVersion, new Date(startTimestamp), value); - }; - - if (boundIndex === -1) { - // cannot find the next bound - const chance = Math.max(...minutesForConditions.map(minute => minute.chance)); - // $.log(`🚧 ${$.name}, max chance = ${chance}`, ''); - const possibleClear = needPossible(chance); - timeStatus = [TIME_STATUS.CONSTANT]; - - condition.token = toToken(possibleClear, weatherStatus, timeStatus); - - conditions.push(condition); - - // avoid endless loop - lastBoundIndex = slicedMinutes.length - 1; - break; - } else { - const chance = Math.max( - ...minutesForConditions.slice(0, boundIndex).map(minute => minute.chance) - ); - // $.log(`🚧 ${$.name}, max chance = ${chance}`, ''); - const possibleClear = needPossible(chance); - const currentWeather = minutesForConditions[boundIndex].weatherStatus; - const endTime = - convertTime(apiVersion, new Date(startTimestamp), lastBoundIndex + boundIndex); - - switch (apiVersion) { - case "v1": - condition.validUntil = endTime; - break; - case "v2": - default: - condition.endTime = endTime; - break; - } - - switch (currentWeather) { - case WEATHER_STATUS.CLEAR: - timeStatus.push(TIME_STATUS.STOP); - break; - // TODO: drizzle & flurries - case WEATHER_STATUS.DRIZZLE: - case WEATHER_STATUS.FLURRIES: - case WEATHER_STATUS.SLEET: - case WEATHER_STATUS.RAIN: - case WEATHER_STATUS.SNOW: - case WEATHER_STATUS.HEAVY_RAIN: - case WEATHER_STATUS.HEAVY_SNOW: - default: - if (lastWeather !== WEATHER_STATUS.CLEAR) { - timeStatus = [TIME_STATUS.CONSTANT]; - } - break; - } - - switch (lastWeather) { - case WEATHER_STATUS.CLEAR: - condition.token = toToken(possibleClear, [currentWeather], timeStatus); - break; - case WEATHER_STATUS.HEAVY_RAIN: - case WEATHER_STATUS.HEAVY_SNOW: - weatherStatus.push(currentWeather); - // no break as intend - // TODO: drizzle & flurries - case WEATHER_STATUS.DRIZZLE: - case WEATHER_STATUS.FLURRIES: - case WEATHER_STATUS.SLEET: - case WEATHER_STATUS.RAIN: - case WEATHER_STATUS.SNOW: - default: - condition.token = toToken(possibleClear, weatherStatus, timeStatus); - break; - } - - conditions.push(condition); - - lastBoundIndex += boundIndex; - weatherStatus = [minutesForConditions[boundIndex].weatherStatus]; - } - } - - // shift first empty object - conditions.shift(); - $.log(`🚧 ${$.name}, conditions = ${JSON.stringify(conditions)}`, ''); - return conditions; - }; - - function getSummaries(apiVersion, minutesData, startTimestamp) { - $.log(`🚧 ${$.name}, 开始设置summary`, ""); - const slicedMinutes = minutesData.slice(0, 59); - - // initialize data - // empty object for loop - let summaries = [{}]; - let lastBoundIndex = 0; - - for (const _summary of summaries) { - // initialize data - const isClear = slicedMinutes[lastBoundIndex].weatherStatus === WEATHER_STATUS.CLEAR; - const minutesForSummary = slicedMinutes.slice(lastBoundIndex); - const boundIndex = minutesForSummary.findIndex(minute => - isClear ? minute.weatherStatus !== WEATHER_STATUS.CLEAR - : minute.weatherStatus === WEATHER_STATUS.CLEAR - ); - - const summary = { - condition: weatherStatusToType(slicedMinutes[lastBoundIndex].weatherStatus), - }; - if (apiVersion !== "v1") { - summary.startTime = convertTime(apiVersion, new Date(startTimestamp), lastBoundIndex); - } - - if (!isClear) { - const minutesForNotClear = minutesForSummary.slice( - 0, - boundIndex === -1 ? slicedMinutes.length - 1 : boundIndex, - ); - const chance = Math.max(...minutesForNotClear.map(minute => minute.chance)); - const precipitations = minutesForNotClear.map(minute => minute.precipitation); - - switch (apiVersion) { - case "v1": - summary.probability = chance; - summary.maxIntensity = Math.max(...precipitations); - summary.minIntensity = Math.min(...precipitations); - break; - case "v2": - default: - summary.precipChance = chance; - summary.precipIntensity = Math.max(...precipitations); - break; - } - } - - if (boundIndex === -1) { - summaries.push(summary); - - // avoid endless loop - lastBoundIndex = slicedMinutes.length - 1; - break; - } else { - const endTime = - convertTime(apiVersion, new Date(startTimestamp), lastBoundIndex + boundIndex); - switch (apiVersion) { - case "v1": - summary.validUntil = endTime; - case "v2": - summary.endTime = endTime; - } - - summaries.push(summary); - - lastBoundIndex += boundIndex; - } - }; - - summaries.shift(); - $.log(`🚧 ${$.name}, summaries = ${JSON.stringify(summaries)}`, ""); - return summaries; - }; -}; - -/***************** Fuctions *****************/ -/** - * Convert Time - * @author VirgilClyne - * @param {String} apiVersion - Apple Weather API Version - * @param {Time} time - Time - * @param {Number} addMinutes - add Minutes Number - * @param {Number} addSeconds - add Seconds Number - * @returns {String} - */ -function convertTime(apiVersion, time, addMinutes = 0, addSeconds = "") { - time.setMinutes(time.getMinutes() + addMinutes, (addSeconds) ? time.getSeconds() + addSeconds : 0, 0); - let timeString = (apiVersion == "v1") ? time.getTime() / 1000 : time.toISOString().split(".")[0] + "Z" - return timeString; -}; - -/** - * Calculate Air Quality Level - * @author VirgilClyne - * @param {Number} AQI - Air Quality index - * @returns {Number} - */ -function calculateAQI(AQI) { - if (!AQI) return -1 - else if (AQI <= 200) return Math.ceil(AQI / 50); - else if (AQI <= 300) return 5; - else return 6; -}; - -/** - * Calculate Precipitation Level - * https://docs.caiyunapp.com/docs/tables/precip - * @author VirgilClyne - * @author WordlessEcho - * @param {object} standard - `*_PRECIPITATION_RANGE` - * @param {Number} pptn - precipitation - * @returns {Number} one of `PRECIPITATION_LEVEL` - */ -function calculatePL(standard, pptn) { - const { - NO, - LIGHT, - MODERATE, - HEAVY, - } = standard; - - if (typeof pptn !== "number") return PRECIPITATION_LEVEL.INVALID; - else if (pptn < NO.UPPER) return PRECIPITATION_LEVEL.NO; - else if (pptn < LIGHT.UPPER) return PRECIPITATION_LEVEL.LIGHT; - else if (pptn < MODERATE.UPPER) return PRECIPITATION_LEVEL.MODERATE; - else if (pptn < HEAVY.UPPER) return PRECIPITATION_LEVEL.HEAVY; - else return PRECIPITATION_LEVEL.STORM; -}; - -/** - * Convert PRECIPITATION_LEVEL to WEATHER_TYPES - * @author WordlessEcho - * @param {string} weatherType - one of `WEATHER_TYPES` - * @param {Number} precipitationLevel - one of `PRECIPITATION_LEVEL` - * @returns {string} one of `WEATHER_STATUS` - */ -function precipLevelToStatus(weatherType, precipitationLevel) { - const { - INVALID, - NO, - LIGHT, - MODERATE, - HEAVY, - STORM, - } = PRECIPITATION_LEVEL; - - if ( - weatherType === WEATHER_TYPES.CLEAR || - precipitationLevel === INVALID || - precipitationLevel === NO - ) { - return WEATHER_STATUS.CLEAR; - } - - switch (precipitationLevel) { - case LIGHT: - return weatherType === WEATHER_TYPES.RAIN ? WEATHER_STATUS.DRIZZLE : WEATHER_STATUS.FLURRIES; - case MODERATE: - return weatherType === WEATHER_TYPES.RAIN ? WEATHER_STATUS.RAIN : WEATHER_STATUS.SNOW; - case HEAVY: - case STORM: - return weatherType === WEATHER_TYPES.RAIN - ? WEATHER_STATUS.HEAVY_RAIN : WEATHER_STATUS.HEAVY_SNOW; - default: - $.logErr( - `❗️${$.name}, unexpeted precipitation level, `, - `precipitationLevel = ${precipitationLevel}` - ); - return WEATHER_STATUS.CLEAR; - } -}; - -/** - * Convert WEATHER_STATUS to WEATHER_TYPES - * @author WordlessEcho - * @param {string} weatherStatus - one of `WEATHER_STATUS` - * @returns {string} one of `WEATHER_TYPES` - */ -function weatherStatusToType(weatherStatus) { - const { - CLEAR, - DRIZZLE, - FLURRIES, - SLEET, - RAIN, - SNOW, - HEAVY_RAIN, - HEAVY_SNOW, - } = WEATHER_STATUS; - - switch (weatherStatus) { - case CLEAR: - return WEATHER_TYPES.CLEAR; - case SLEET: - return WEATHER_TYPES.SLEET; - case FLURRIES: - case SNOW: - case HEAVY_SNOW: - return WEATHER_TYPES.SNOW; - case DRIZZLE: - case RAIN: - case HEAVY_RAIN: - default: - return WEATHER_TYPES.RAIN; - } -}; - -/** - * create Metadata - * @author VirgilClyne - * @param {Object} input - input - * @returns {Object} - */ -function Metadata(input = { "Version": new Number, "Time": new Date, "Expire": new Number, "Report": true, "Latitude": new Number, "Longitude": new Number, "Language": "", "Name": "", "Logo": "", "Unit": "", "Source": new Number }) { - let metadata = { - "version": input.Version, - "language": input.Language, - "longitude": input.Longitude, - "latitude": input.Latitude, - } - if (input.Version == 1) { - metadata.read_time = convertTime("v"+input.Version, new Date(), 0, 0); - metadata.expire_time = convertTime("v"+input.Version, new Date(input?.Time), input.Expire, 0); - if (input.Report) metadata.reported_time = convertTime("v"+input.Version, new Date(input?.Time), 0, 0); - metadata.provider_name = input.Name; - if (input.Logo) metadata.provider_logo = input.Logo; - metadata.data_source = input.Source; - } else { - metadata.readTime = convertTime("v"+input.Version, new Date(), 0, 0); - metadata.expireTime = convertTime("v"+input.Version, new Date(input?.Time), input.Expire, 0); - if (input.Report) metadata.reportedTime = convertTime("v"+input.Version, new Date(input?.Time), 0, 0); - metadata.providerName = input.Name; - if (input.Logo) metadata.providerLogo = input.Logo; - metadata.units = input.Unit; - } - return metadata -}; - -/** - * convert iOS language into ColorfulClouds style - * @author shindgewongxj - * @author WordlessEcho - * @param {string} languageWithReigon - "zh-Hans-CA", "en-US", "ja-CA" from Apple URL - * @returns {string} https://docs.caiyunapp.com/docs/tables/lang - */ - function toColorfulCloudsLang(languageWithReigon) { - if (languageWithReigon.includes("en-US")) { - return "en_US"; - } else if (/zh-(Hans|CN)/.test(languageWithReigon)) { - return "zh_CN"; - } else if (/zh-(Hant|HK|TW)/.test(languageWithReigon)) { - return "zh_TW"; - } else if (languageWithReigon.includes("en-GB")) { - return "en_GB"; - } else if (languageWithReigon.includes("ja")) { - return "ja"; - } else { - $.log( - `⚠ ${$.name}, ColorfulClouds: unsupported language detected, fallback to en_US. `, - `languageWithReigon = ${languageWithReigon}`, "" - ); - return "en_US"; - } -}; - -/***************** Env *****************/ -// prettier-ignore -// https://github.com/chavyleung/scripts/blob/master/Env.min.js -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}isStash(){return"undefined"!=typeof $environment&&$environment["stash-version"]}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,a]=i.split("@"),n={url:`http://${a}/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),a=i?"null"===o?null:o||"{}":"{}";try{const e=JSON.parse(a);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?s.status:s.statusCode,s.status=s.statusCode),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&&t.error||"UndefinedError"));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:a}=t,n=s.decode(a,this.encoding);e(null,{status:i,statusCode:r,headers:o,rawBody:a,body:n},n)},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?s.status:s.statusCode,s.status=s.statusCode),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&&t.error||"UndefinedError"));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:a}=t,n=i.decode(a,this.encoding);e(null,{status:s,statusCode:r,headers:o,rawBody:a,body:n},n)},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,i=t["update-pasteboard"]||t.updatePasteboard;return{"open-url":e,"media-url":s,"update-pasteboard":i}}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):this.isNode()&&process.exit(1)}}(t,e)} - -// https://github.com/VirgilClyne/VirgilClyne/blob/main/function/URL/URLs.embedded.min.js -function URLs(s){return new class{constructor(s=[]){this.name="URL v1.0.0",this.opts=s,this.json={url:{scheme:"",host:"",path:""},params:{}}}parse(s){let t=s.match(/(?.+):\/\/(?[^/]+)\/?(?[^?]+)?\??(?.*)?/)?.groups??null;return t?.params&&(t.params=Object.fromEntries(t.params.split("&").map((s=>s.split("="))))),t}stringify(s=this.json){return s?.params?s.scheme+"://"+s.host+"/"+s.path+"?"+Object.entries(s.params).map((s=>s.join("="))).join("&"):s.scheme+"://"+s.host+"/"+s.path}}(s)} diff --git a/Archive/plugin/Apple_Weather.plugin b/Archive/plugin/Apple_Weather.plugin deleted file mode 100644 index 34d70926c..000000000 --- a/Archive/plugin/Apple_Weather.plugin +++ /dev/null @@ -1,14 +0,0 @@ -#!name= iRingo for Apple Weather AQI data with waqi.info -#!desc=(V2) 解锁全部类型天气数据可用性,切换空气质量数据源为waqi.info,并更改标准为AQI(US) -#!openUrl=http://boxjs.com/#/app/iRingo.Weather -#!author=VirgilClyne -#!homepage=https://github.com/VirgilClyne -#!manual=https://github.com/VirgilClyne/iRingo#天气 -#!icon=https://is4-ssl.mzstatic.com/image/thumb/Purple125/v4/c8/76/87/c8768792-b7ab-7de4-9c70-1888096b7ae9/AppIcon-0-0-1x_U007emarketing-0-0-0-10-0-0-sRGB-0-0-0-GLES2_U002c0-512MB-85-220-0-0.png/108x0w.webp - -[Script] -http-response ^https?:\/\/weather-data\.apple\.com\/(v1|v2)\/weather.*(?!dataSets=forecastNextHour)(include=.*air_quality.*|dataSets=.*airQuality.*).*(country=[A-Z]{2})?.* script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/main/archive/js/Apple_Weather.js, requires-body=1, timeout=10, tag=Apple Weather -http-response ^https?:\/\/weather-data\.apple\.com\/(v1|v2)\/availability\/.* script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/main/archive/js/Apple_Weather.js, requires-body=1, timeout=10, tag=Apple Weather Availability - -[MITM] -hostname = weather-data.apple.com diff --git a/Archive/plugin/Siri_Suggestions.plugin b/Archive/plugin/Siri_Suggestions.plugin deleted file mode 100644 index d95957cf2..000000000 --- a/Archive/plugin/Siri_Suggestions.plugin +++ /dev/null @@ -1,19 +0,0 @@ -#!name= Enable Siri Suggestions -#!desc=(V1.5) 在聚焦搜索(Spotlight)和查询(Look Up)中启用Siri建议(Siri Suggestions)功能 -#!openUrl=http://boxjs.com/#/app/iRingo.Siri -#!author=VirgilClyne -#!homepage=https://github.com/VirgilClyne -#!manual=https://github.com/VirgilClyne/iRingo#siri与搜索 -#!icon=https://raw.githubusercontent.com/Koolson/Qure/master/IconSet/Color/Siri.png - -[Script] -# Redirect Siri Suggestions Service -# Bag (iOS/macOS) -http-request ^https?:\/\/api.*\.smoot\.apple\.(com|cn)\/bag\?(.*) script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/main/archive/js/Siri_Suggestions.js, timeout=3, tag=Siri_Suggestions -# Spotlight & Look Up Search (iOS/macOS) -http-request ^https?:\/\/api.*\.smoot\.apple\.(com|cn)\/search\?(.*) script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/main/archive/js/Siri_Suggestions.js, timeout=3, tag=Siri_Suggestions -# Siri Infomation Card (macOS) -http-request ^https?:\/\/api.*\.smoot\.apple\.(com|cn)\/card\?(.*) script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/main/archive/js/Siri_Suggestions.js, timeout=3, tag=Siri_Suggestions - -[MITM] -hostname = api*.smoot.apple.com, api*.smoot.apple.cn diff --git a/Archive/plugin/Siri_Suggestions_JP.plugin b/Archive/plugin/Siri_Suggestions_JP.plugin deleted file mode 100644 index 58eabbf4e..000000000 --- a/Archive/plugin/Siri_Suggestions_JP.plugin +++ /dev/null @@ -1,25 +0,0 @@ -#!name= Enable Siri Suggestions 🇯🇵JP -#!desc=(V1) 在聚焦搜索(Spotlight)和查询(Look Up)中启用Siri建议(Siri Suggestions)功能 -#!openUrl=http://boxjs.com/#/app/iRingo.Siri -#!author=VirgilClyne -#!homepage=https://github.com/VirgilClyne -#!manual=https://github.com/VirgilClyne/iRingo#siri与搜索 -#!icon=https://raw.githubusercontent.com/Koolson/Qure/master/IconSet/Color/Siri.png - -[URL Rewrite] -# Redirect Siri Suggestions Service -# Bag (iOS/macOS) -(^https?:\/\/api.*\.smoot\.apple\.)(com|cn)(\/bag.*)(cc=[A-Z]{2})(.*) $1com$3cc=JP$5 header -# General Spotlight & Look Up Search (iOS/macOS) -(^https?:\/\/api.*\.smoot\.apple\.)(com|cn)(\/search.*)(cc=[A-Z]{2})(.*) $1com$3cc=JP$5 header -# General Info Card (macOS) -(^https?:\/\/api.*\.smoot\.apple\.)(com|cn)(\/card.*)(card_locale=[a-zA-z\-]{2,7}_[A-Z]{2})(.*)(cc=[A-Z]{2})(.*)(include=[^kg][^tv][^movies])(.*) $1com$3$4$5cc=JP$7$8$9 header -# Siri Knowledge(Siri资料) Card (Spotlight@macOS & Siri@iOS) -(^https?:\/\/api.*\.smoot\.apple\.)(com|cn)(\/card.*)(card_locale=[a-zA-z\-]{2,7}_[A-Z]{2})(.*)(cc=[A-Z]{2})(.*)(include=(?:kgcn|kg))(.*) $1com$3$4$5cc=JP$7$8$9 header -# Siri Knowledge(Siri资料) Card (Spotlight@iOS) -(^https?:\/\/api.*\.smoot\.apple\.)(com|cn)(\/search.*)(cardDomain=kg)(.*)(card_locale=[a-zA-z\-]{2,7}_[A-Z]{2})(.*)(cc=[A-Z]{2})(.*) $1com$3$4$5$6$7cc=JP$9 header -# tv Movies and TV Show Card (macOS) -(^https?:\/\/api.*\.smoot\.apple\.)(com|cn)(\/card.*)(card_locale=[a-zA-z\-]{2,7}_[A-Z]{2})(.*)(cc=[A-Z]{2})(.*)(include=(?:tv|movies))(.*)(q=(?:tv|movies)%3A)(.*)(%2F)([a-z]{2}-[A-Z]{2})(.*) $1com$3$4$5cc=JP$7$8$9$10$11$12$13$14 header - -[MITM] -hostname = api*.smoot.apple.com, api*.smoot.apple.cn diff --git a/Archive/plugin/Siri_Suggestions_TW.plugin b/Archive/plugin/Siri_Suggestions_TW.plugin deleted file mode 100644 index d284a9728..000000000 --- a/Archive/plugin/Siri_Suggestions_TW.plugin +++ /dev/null @@ -1,25 +0,0 @@ -#!name= Enable Siri Suggestions 🇹🇼TW -#!desc=(V1) 在聚焦搜索(Spotlight)和查询(Look Up)中启用Siri建议(Siri Suggestions)功能 -#!openUrl=http://boxjs.com/#/app/iRingo.Siri -#!author=VirgilClyne -#!homepage=https://github.com/VirgilClyne -#!manual=https://github.com/VirgilClyne/iRingo#siri与搜索 -#!icon=https://raw.githubusercontent.com/Koolson/Qure/master/IconSet/Color/Siri.png - -[URL Rewrite] -# Redirect Siri Suggestions Service -# Bag (iOS/macOS) -(^https?:\/\/api.*\.smoot\.apple\.)(com|cn)(\/bag.*)(cc=[A-Z]{2})(.*) $1com$3cc=TW$5 header -# General Spotlight & Look Up Search (iOS/macOS) -(^https?:\/\/api.*\.smoot\.apple\.)(com|cn)(\/search.*)(cc=[A-Z]{2})(.*) $1com$3cc=TW$5 header -# General Info Card (macOS) -(^https?:\/\/api.*\.smoot\.apple\.)(com|cn)(\/card.*)(card_locale=[a-zA-z\-]{2,7}_[A-Z]{2})(.*)(cc=[A-Z]{2})(.*)(include=[^kg][^tv][^movies])(.*) $1com$3$4$5cc=TW$7$8$9 header -# Siri Knowledge(Siri资料) Card (Spotlight@macOS & Siri@iOS) -(^https?:\/\/api.*\.smoot\.apple\.)(com|cn)(\/card.*)(card_locale=[a-zA-z\-]{2,7}_[A-Z]{2})(.*)(cc=[A-Z]{2})(.*)(include=(?:kgcn|kg))(.*) $1com$3$4$5cc=TW$7$8$9 header -# Siri Knowledge(Siri资料) Card (Spotlight@iOS) -(^https?:\/\/api.*\.smoot\.apple\.)(com|cn)(\/search.*)(cardDomain=kg)(.*)(card_locale=[a-zA-z\-]{2,7}_[A-Z]{2})(.*)(cc=[A-Z]{2})(.*) $1com$3$4$5$6$7cc=TW$9 header -# tv Movies and TV Show Card (macOS) -(^https?:\/\/api.*\.smoot\.apple\.)(com|cn)(\/card.*)(card_locale=[a-zA-z\-]{2,7}_[A-Z]{2})(.*)(cc=[A-Z]{2})(.*)(include=(?:tv|movies))(.*)(q=(?:tv|movies)%3A)(.*)(%2F)([a-z]{2}-[A-Z]{2})(.*) $1com$3$4$5cc=TW$7$8$9$10$11$12$13$14 header - -[MITM] -hostname = api*.smoot.apple.com, api*.smoot.apple.cn diff --git a/Archive/plugin/Siri_Suggestions_US.plugin b/Archive/plugin/Siri_Suggestions_US.plugin deleted file mode 100644 index 5fc696f26..000000000 --- a/Archive/plugin/Siri_Suggestions_US.plugin +++ /dev/null @@ -1,25 +0,0 @@ -#!name= Enable Siri Suggestions 🇺🇸US -#!desc=(V1) 在聚焦搜索(Spotlight)和查询(Look Up)中启用Siri建议(Siri Suggestions)功能 -#!openUrl=http://boxjs.com/#/app/iRingo.Siri -#!author=VirgilClyne -#!homepage=https://github.com/VirgilClyne -#!manual=https://github.com/VirgilClyne/iRingo#siri与搜索 -#!icon=https://raw.githubusercontent.com/Koolson/Qure/master/IconSet/Color/Siri.png - -[URL Rewrite] -# Redirect Siri Suggestions Service -# Bag (iOS/macOS) -(^https?:\/\/api.*\.smoot\.apple\.)(com|cn)(\/bag.*)(cc=[A-Z]{2})(.*) $1com$3cc=US$5 header -# General Spotlight & Look Up Search (iOS/macOS) -(^https?:\/\/api.*\.smoot\.apple\.)(com|cn)(\/search.*)(cc=[A-Z]{2})(.*) $1com$3cc=US$5 header -# General Info Card (macOS) -(^https?:\/\/api.*\.smoot\.apple\.)(com|cn)(\/card.*)(card_locale=[a-zA-z\-]{2,7}_[A-Z]{2})(.*)(cc=[A-Z]{2})(.*)(include=[^kg][^tv][^movies])(.*) $1com$3$4$5cc=US$7$8$9 header -# Siri Knowledge(Siri资料) Card (Spotlight@macOS & Siri@iOS) -(^https?:\/\/api.*\.smoot\.apple\.)(com|cn)(\/card.*)(card_locale=[a-zA-z\-]{2,7}_[A-Z]{2})(.*)(cc=[A-Z]{2})(.*)(include=(?:kgcn|kg))(.*) $1com$3$4$5cc=US$7$8$9 header -# Siri Knowledge(Siri资料) Card (Spotlight@iOS) -(^https?:\/\/api.*\.smoot\.apple\.)(com|cn)(\/search.*)(cardDomain=kg)(.*)(card_locale=[a-zA-z\-]{2,7}_[A-Z]{2})(.*)(cc=[A-Z]{2})(.*) $1com$3$4$5$6$7cc=US$9 header -# tv Movies and TV Show Card (macOS) -(^https?:\/\/api.*\.smoot\.apple\.)(com|cn)(\/card.*)(card_locale=[a-zA-z\-]{2,7}_[A-Z]{2})(.*)(cc=[A-Z]{2})(.*)(include=(?:tv|movies))(.*)(q=(?:tv|movies)%3A)(.*)(%2F)([a-z]{2}-[A-Z]{2})(.*) $1com$3$4$5cc=US$7$8$9$10$11$12$13$14 header - -[MITM] -hostname = api*.smoot.apple.com, api*.smoot.apple.cn diff --git a/Archive/plugin/Weather.v3.plugin b/Archive/plugin/Weather.v3.plugin deleted file mode 100644 index 233ddd8a6..000000000 --- a/Archive/plugin/Weather.v3.plugin +++ /dev/null @@ -1,21 +0,0 @@ -#!name= iRingo for Apple Weather -#!desc=(V3) 1.解锁全部天气数据类型 2.替换空气质量数据:WAQI 3.添加下一小时降水:气象在线 4.替换空气质量地图数据:WAQI -#!openUrl=http://boxjs.com/#/app/iRingo.Weather -#!author=VirgilClyne -#!homepage=https://github.com/VirgilClyne -#!manual=https://github.com/VirgilClyne/iRingo/wiki/🌤天气 -#!icon=https://is4-ssl.mzstatic.com/image/thumb/Purple125/v4/c8/76/87/c8768792-b7ab-7de4-9c70-1888096b7ae9/AppIcon-0-0-1x_U007emarketing-0-0-0-10-0-0-sRGB-0-0-0-GLES2_U002c0-512MB-85-220-0-0.png/108x0w.webp - -[Rule] -DOMAIN-SUFFIX,waqi.info - -[Rewrite] -^https?:\/\/weather-data\.apple\.com\/(v1|v2)\/weather\/.* header-del If-None-Match - -[Script] -http-response ^https?:\/\/weather-data\.apple\.com\/(v1|v2)\/availability\/.* script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/main/Archive/js/Weather.Availability.js, requires-body=1, tag=Apple Weather Availability -http-response ^https?:\/\/weather-data\.apple\.com\/(v1|v2)\/weather\/.* script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/main/Archive/js/Weather.js, requires-body=1, timeout=20, tag=Apple Weather -http-request ^https?:\/\/weather-map\.apple\.com\/(v1|v2)\/mapOverlay\/.*(\?.*country=CN.*) script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/main/Archive/js/Weather.Map.js, requires-body=0, tag=Apple Weather Map - -[MITM] -hostname = weather-data.apple.com, weather-map.apple.com diff --git a/Archive/qxrewrite/Apple_Weather.beta.qxrewrite b/Archive/qxrewrite/Apple_Weather.beta.qxrewrite deleted file mode 100644 index 5a4eabe98..000000000 --- a/Archive/qxrewrite/Apple_Weather.beta.qxrewrite +++ /dev/null @@ -1,7 +0,0 @@ -#!name= iRingo for Apple Weather AQI data with waqi.info -#!version=BETA -#!desc=解锁全部类型天气数据可用性,切换空气质量数据源为waqi.info,并更改标准为AQI(US) - -^https?:\/\/weather-data\.apple\.com\/(v1|v2)\/weather.*(?!dataSets=forecastNextHour)(include=.*air_quality.*|dataSets=.*airQuality.*).*(country=[A-Z]{2})?.* url script-response-body https://raw.githubusercontent.com/VirgilClyne/iRingo/beta/archive/js/Apple_Weather.beta.js - -hostname = weather-data.apple.com diff --git a/Archive/qxrewrite/Apple_Weather.qxrewrite b/Archive/qxrewrite/Apple_Weather.qxrewrite deleted file mode 100644 index 6f5261008..000000000 --- a/Archive/qxrewrite/Apple_Weather.qxrewrite +++ /dev/null @@ -1,8 +0,0 @@ -#!name= iRingo for Apple Weather AQI data with waqi.info -#!version=V2 -#!desc=解锁全部类型天气数据可用性,切换空气质量数据源为waqi.info,并更改标准为AQI(US) - -^https?:\/\/weather-data\.apple\.com\/(v1|v2)\/weather.*(?!dataSets=forecastNextHour)(include=.*air_quality.*|dataSets=.*airQuality.*).*(country=[A-Z]{2})?.* url script-response-body https://raw.githubusercontent.com/VirgilClyne/iRingo/main/archive/js/Apple_Weather.js -^https?:\/\/weather-data\.apple\.com\/(v1|v2)\/availability\/.* url script-response-body https://raw.githubusercontent.com/VirgilClyne/iRingo/main/archive/js/Apple_Weather.js - -hostname = weather-data.apple.com diff --git a/Archive/qxrewrite/Siri_Suggestions.qxrewrite b/Archive/qxrewrite/Siri_Suggestions.qxrewrite deleted file mode 100644 index 3bbe6b44d..000000000 --- a/Archive/qxrewrite/Siri_Suggestions.qxrewrite +++ /dev/null @@ -1,13 +0,0 @@ -#!name= Enable Siri Suggestions -#!version=V1.5 -#!desc=在聚焦搜索(Spotlight)和查询(Look Up)中启用Siri建议(Siri Suggestions)功能。 - -# Redirect Siri Suggestions Service -# Bag (iOS/macOS) -^https?:\/\/api.*\.smoot\.apple\.(com|cn)\/bag\?(.*) url script-request-header https://raw.githubusercontent.com/VirgilClyne/iRingo/main/archive/js/Siri_Suggestions.js -# Spotlight & Look Up Search (iOS/macOS) -^https?:\/\/api.*\.smoot\.apple\.(com|cn)\/search\?(.*) url script-request-header https://raw.githubusercontent.com/VirgilClyne/iRingo/main/archive/js/Siri_Suggestions.js -# Siri Infomation Card (macOS) -^https?:\/\/api.*\.smoot\.apple\.(com|cn)\/card\?(.*) url script-request-header https://raw.githubusercontent.com/VirgilClyne/iRingo/main/archive/js/Siri_Suggestions.js - -hostname = api*.smoot.apple.com, api*.smoot.apple.cn diff --git a/Archive/qxrewrite/Siri_Suggestions_JP.qxrewrite b/Archive/qxrewrite/Siri_Suggestions_JP.qxrewrite deleted file mode 100644 index 473c360c3..000000000 --- a/Archive/qxrewrite/Siri_Suggestions_JP.qxrewrite +++ /dev/null @@ -1,19 +0,0 @@ -#!name= Enable Siri Suggestions 🇯🇵JP -#!version=V1 -#!desc=在聚焦搜索(Spotlight)和查询(Look Up)中启用Siri建议(Siri Suggestions)功能。 - -# Redirect Siri Suggestions Service -# Bag (iOS/macOS) -(^https?:\/\/api.*\.smoot\.apple\.)(com|cn)(\/bag.*)(cc=[A-Z]{2})(.*) url 302 $1com$3cc=JP$5 -# General Spotlight & Look Up Search (iOS/macOS) -(^https?:\/\/api.*\.smoot\.apple\.)(com|cn)(\/search.*)(cc=[A-Z]{2})(.*) url 302 $1com$3cc=JP$5 -# General Info Card (macOS) -(^https?:\/\/api.*\.smoot\.apple\.)(com|cn)(\/card.*)(card_locale=[a-zA-z\-]{2,7}_[A-Z]{2})(.*)(cc=[A-Z]{2})(.*)(include=[^kg][^tv][^movies])(.*) url 302 $1com$3$4$5cc=JP$7$8$9 -# Siri Knowledge(Siri资料) Card (Spotlight@macOS & Siri@iOS) -(^https?:\/\/api.*\.smoot\.apple\.)(com|cn)(\/card.*)(card_locale=[a-zA-z\-]{2,7}_[A-Z]{2})(.*)(cc=[A-Z]{2})(.*)(include=(?:kgcn|kg))(.*) url 302 $1com$3$4$5cc=JP$7$8$9 -# Siri Knowledge(Siri资料) Card (Spotlight@iOS) -(^https?:\/\/api.*\.smoot\.apple\.)(com|cn)(\/search.*)(cardDomain=kg)(.*)(card_locale=[a-zA-z\-]{2,7}_[A-Z]{2})(.*)(cc=[A-Z]{2})(.*) url 302 $1com$3$4$5$6$7cc=JP$9 -# tv Movies and TV Show Card (macOS) -(^https?:\/\/api.*\.smoot\.apple\.)(com|cn)(\/card.*)(card_locale=[a-zA-z\-]{2,7}_[A-Z]{2})(.*)(cc=[A-Z]{2})(.*)(include=(?:tv|movies))(.*)(q=(?:tv|movies)%3A)(.*)(%2F)([a-z]{2}-[A-Z]{2})(.*) url 302 $1com$3$4$5cc=JP$7$8$9$10$11$12$13$14 - -hostname = api*.smoot.apple.com, api*.smoot.apple.cn diff --git a/Archive/qxrewrite/Siri_Suggestions_TW.qxrewrite b/Archive/qxrewrite/Siri_Suggestions_TW.qxrewrite deleted file mode 100644 index d51fd984d..000000000 --- a/Archive/qxrewrite/Siri_Suggestions_TW.qxrewrite +++ /dev/null @@ -1,19 +0,0 @@ -#!name= Enable Siri Suggestions 🇹🇼TW -#!version=V1 -#!desc=在聚焦搜索(Spotlight)和查询(Look Up)中启用Siri建议(Siri Suggestions)功能。 - -# Redirect Siri Suggestions Service -# Bag (iOS/macOS) -(^https?:\/\/api.*\.smoot\.apple\.)(com|cn)(\/bag.*)(cc=[A-Z]{2})(.*) url 302 $1com$3cc=TW$5 -# General Spotlight & Look Up Search (iOS/macOS) -(^https?:\/\/api.*\.smoot\.apple\.)(com|cn)(\/search.*)(cc=[A-Z]{2})(.*) url 302 $1com$3cc=TW$5 -# General Info Card (macOS) -(^https?:\/\/api.*\.smoot\.apple\.)(com|cn)(\/card.*)(card_locale=[a-zA-z\-]{2,7}_[A-Z]{2})(.*)(cc=[A-Z]{2})(.*)(include=[^kg][^tv][^movies])(.*) url 302 $1com$3$4$5cc=TW$7$8$9 -# Siri Knowledge(Siri资料) Card (Spotlight@macOS & Siri@iOS) -(^https?:\/\/api.*\.smoot\.apple\.)(com|cn)(\/card.*)(card_locale=[a-zA-z\-]{2,7}_[A-Z]{2})(.*)(cc=[A-Z]{2})(.*)(include=(?:kgcn|kg))(.*) url 302 $1com$3$4$5cc=TW$7$8$9 -# Siri Knowledge(Siri资料) Card (Spotlight@iOS) -(^https?:\/\/api.*\.smoot\.apple\.)(com|cn)(\/search.*)(cardDomain=kg)(.*)(card_locale=[a-zA-z\-]{2,7}_[A-Z]{2})(.*)(cc=[A-Z]{2})(.*) url 302 $1com$3$4$5$6$7cc=TW$9 -# tv Movies and TV Show Card (macOS) -(^https?:\/\/api.*\.smoot\.apple\.)(com|cn)(\/card.*)(card_locale=[a-zA-z\-]{2,7}_[A-Z]{2})(.*)(cc=[A-Z]{2})(.*)(include=(?:tv|movies))(.*)(q=(?:tv|movies)%3A)(.*)(%2F)([a-z]{2}-[A-Z]{2})(.*) url 302 $1com$3$4$5cc=TW$7$8$9$10$11$12zh-TW$14 - -hostname = api*.smoot.apple.com, api*.smoot.apple.cn diff --git a/Archive/qxrewrite/Siri_Suggestions_US.qxrewrite b/Archive/qxrewrite/Siri_Suggestions_US.qxrewrite deleted file mode 100644 index 21756e5a1..000000000 --- a/Archive/qxrewrite/Siri_Suggestions_US.qxrewrite +++ /dev/null @@ -1,19 +0,0 @@ -#!name= Enable Siri Suggestions 🇺🇸US -#!version=V1 -#!desc=在聚焦搜索(Spotlight)和查询(Look Up)中启用Siri建议(Siri Suggestions)功能。 - -# Redirect Siri Suggestions Service -# Bag (iOS/macOS) -(^https?:\/\/api.*\.smoot\.apple\.)(com|cn)(\/bag.*)(cc=[A-Z]{2})(.*) url 302 $1com$3cc=US$5 -# General Spotlight & Look Up Search (iOS/macOS) -(^https?:\/\/api.*\.smoot\.apple\.)(com|cn)(\/search.*)(cc=[A-Z]{2})(.*) url 302 $1com$3cc=US$5 -# General Info Card (macOS) -(^https?:\/\/api.*\.smoot\.apple\.)(com|cn)(\/card.*)(card_locale=[a-zA-z\-]{2,7}_[A-Z]{2})(.*)(cc=[A-Z]{2})(.*)(include=[^kg][^tv][^movies])(.*) url 302 $1com$3$4$5cc=US$7$8$9 -# Siri Knowledge(Siri资料) Card (Spotlight@macOS & Siri@iOS) -(^https?:\/\/api.*\.smoot\.apple\.)(com|cn)(\/card.*)(card_locale=[a-zA-z\-]{2,7}_[A-Z]{2})(.*)(cc=[A-Z]{2})(.*)(include=(?:kgcn|kg))(.*) url 302 $1com$3$4$5cc=US$7$8$9 -# Siri Knowledge(Siri资料) Card (Spotlight@iOS) -(^https?:\/\/api.*\.smoot\.apple\.)(com|cn)(\/search.*)(cardDomain=kg)(.*)(card_locale=[a-zA-z\-]{2,7}_[A-Z]{2})(.*)(cc=[A-Z]{2})(.*) url 302 $1com$3$4$5$6$7cc=US$9 -# tv Movies and TV Show Card (macOS) -(^https?:\/\/api.*\.smoot\.apple\.)(com|cn)(\/card.*)(card_locale=[a-zA-z\-]{2,7}_[A-Z]{2})(.*)(cc=[A-Z]{2})(.*)(include=(?:tv|movies))(.*)(q=(?:tv|movies)%3A)(.*)(%2F)([a-z]{2}-[A-Z]{2})(.*) url 302 $1com$3$4$5cc=US$7$8$9$10$11$12$13$14 - -hostname = api*.smoot.apple.com, api*.smoot.apple.cn diff --git a/Archive/qxrewrite/Weather.v3.qxrewrite b/Archive/qxrewrite/Weather.v3.qxrewrite deleted file mode 100644 index ed1a2c489..000000000 --- a/Archive/qxrewrite/Weather.v3.qxrewrite +++ /dev/null @@ -1,15 +0,0 @@ -#!name= iRingo for Apple Weather -#!desc=(V3) 1.解锁全部天气数据类型 2.替换空气质量数据:WAQI 3.添加下一小时降水:气象在线 4.替换空气质量地图数据:WAQI -#!openUrl=http://boxjs.com/#/app/iRingo.Weather -#!author=VirgilClyne -#!homepage=https://github.com/VirgilClyne -#!manual=https://github.com/VirgilClyne/iRingo/wiki/🌤天气 -#!icon=https://is4-ssl.mzstatic.com/image/thumb/Purple125/v4/c8/76/87/c8768792-b7ab-7de4-9c70-1888096b7ae9/AppIcon-0-0-1x_U007emarketing-0-0-0-10-0-0-sRGB-0-0-0-GLES2_U002c0-512MB-85-220-0-0.png/108x0w.webp - -^https?:\/\/weather-data\.apple\.com\/(v1|v2)\/weather\/.* url request-header If-None-Match:.+\r\n request-header \r\n - -^https?:\/\/weather-data\.apple\.com\/(v1|v2)\/availability\/.* url script-response-body https://raw.githubusercontent.com/VirgilClyne/iRingo/main/Archive/js/Weather.Availability.js -^https?:\/\/weather-data\.apple\.com\/(v1|v2)\/weather\/.* url script-response-body https://raw.githubusercontent.com/VirgilClyne/iRingo/main/Archive/js/Weather.js -^https?:\/\/weather-map\.apple\.com\/(v1|v2)\/mapOverlay\/.*(\?.*country=CN.*) url script-request-header https://raw.githubusercontent.com/VirgilClyne/iRingo/main/Archive/js/Weather.Map.js - -hostname = weather-data.apple.com, weather-map.apple.com diff --git a/Archive/ruleset/Wikipedia_for_Look_Up.list b/Archive/ruleset/Wikipedia_for_Look_Up.list deleted file mode 100644 index ae0beedfc..000000000 --- a/Archive/ruleset/Wikipedia_for_Look_Up.list +++ /dev/null @@ -1,6 +0,0 @@ -# > Look Up (Only for Wikipedia@macOS, Surge Enhance Mode enabled required) -PROCESS-NAME,LookupViewService -PROCESS-NAME,/System/Library/PrivateFrameworks/Lookup.framework/Versions/A/XPCServices/LookupViewService.xpc/Contents/MacOS/LookupViewService -DOMAIN,lookup-api.apple.com -DOMAIN,lookup-api.apple.com.edgekey.net -DOMAIN,e16991.b.akamaiedge.net \ No newline at end of file diff --git a/Archive/ruleset/Wikipedia_for_Look_Up.yaml b/Archive/ruleset/Wikipedia_for_Look_Up.yaml deleted file mode 100644 index ad1ae6d9b..000000000 --- a/Archive/ruleset/Wikipedia_for_Look_Up.yaml +++ /dev/null @@ -1,7 +0,0 @@ -payload: -# > Look Up (Only for Wikipedia@macOS, Surge Enhance Mode enabled required) - - PROCESS-NAME,LookupViewService - - PROCESS-NAME,/System/Library/PrivateFrameworks/Lookup.framework/Versions/A/XPCServices/LookupViewService.xpc/Contents/MacOS/LookupViewService - - DOMAIN,lookup-api.apple.com - - DOMAIN,lookup-api.apple.com.edgekey.net - - DOMAIN,e16991.b.akamaiedge.net \ No newline at end of file diff --git a/Archive/sgmodule/Apple_Weather.beta.sgmodule b/Archive/sgmodule/Apple_Weather.beta.sgmodule deleted file mode 100644 index 2fd949ade..000000000 --- a/Archive/sgmodule/Apple_Weather.beta.sgmodule +++ /dev/null @@ -1,9 +0,0 @@ -#!name= iRingo for Apple Weather AQI data with waqi.info -#!desc=(BETA) 解锁全部类型天气数据可用性,切换空气质量数据源为waqi.info,并更改标准为AQI(US) - -[Script] -Apple Weather = type=http-response, pattern=^https?:\/\/weather-data\.apple\.com\/(v1|v2)\/weather.*(?!dataSets=forecastNextHour)(include=.*air_quality.*|dataSets=.*airQuality.*).*(country=[A-Z]{2})?.*, requires-body=1, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/beta/archive/js/Apple_Weather.beta.js -Apple Weather Availability = type=http-response, pattern=^https?:\/\/weather-data\.apple\.com\/(v1|v2)\/availability\/.*, requires-body=1, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/beta/archive/js/Apple_Weather.beta.js - -[MITM] -hostname = %APPEND% weather-data.apple.com, api.waqi.info diff --git a/Archive/sgmodule/Apple_Weather.sgmodule b/Archive/sgmodule/Apple_Weather.sgmodule deleted file mode 100644 index 314321c64..000000000 --- a/Archive/sgmodule/Apple_Weather.sgmodule +++ /dev/null @@ -1,9 +0,0 @@ -#!name= iRingo for Apple Weather AQI data with waqi.info -#!desc=(V2) 解锁全部类型天气数据可用性,切换空气质量数据源为waqi.info,并更改标准为AQI(US) - -[Script] -Apple Weather = type=http-response, pattern=^https?:\/\/weather-data\.apple\.com\/(v1|v2)\/weather.*(?!dataSets=forecastNextHour)(include=.*air_quality.*|dataSets=.*airQuality.*).*(country=[A-Z]{2})?.*, requires-body=1, timeout=15, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/main/archive/js/Apple_Weather.js -Apple Weather Availability = type=http-response, pattern=^https?:\/\/weather-data\.apple\.com\/(v1|v2)\/availability\/.*, requires-body=1, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/main/archive/js/Apple_Weather.js - -[MITM] -hostname = %APPEND% weather-data.apple.com diff --git a/Archive/sgmodule/Apple_Weather_Map.beta.sgmodule b/Archive/sgmodule/Apple_Weather_Map.beta.sgmodule deleted file mode 100644 index 4389315cf..000000000 --- a/Archive/sgmodule/Apple_Weather_Map.beta.sgmodule +++ /dev/null @@ -1,10 +0,0 @@ -#!name= Replace Apple Weather Map with 🇺🇸US @waqi.info -#!desc=(BETA) 切换空气质量地图数据源为waqi.info,并更改标准为AQI(US) - -[Script] -# Convert Apple Weather Air Quality Map -http-request ^https?:\/\/weather-map\.apple\.com\/(v1|v2)\/mapOverlay\/airQuality\?.*(country=CN)?.* script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/beta/archive/js/Apple_Weather_Map.js, tag=Apple_Weather_Map -# http-response ^https?:\/\/weather-map\.apple\.com\/(v1|v2)\/mapOverlay\/airQuality\?.*(country=CN)?.* script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/beta/archive/js/Apple_Weather_Map.js, requires-body=true, tag=Apple_Weather_Map - -[MITM] -hostname = %APPEND% weather-map.apple.com, tiles.waqi.info diff --git a/Archive/sgmodule/Apple_Weather_Map.sgmodule b/Archive/sgmodule/Apple_Weather_Map.sgmodule deleted file mode 100644 index b66eff0fc..000000000 --- a/Archive/sgmodule/Apple_Weather_Map.sgmodule +++ /dev/null @@ -1,9 +0,0 @@ -#!name= Replace Apple Weather Map with 🇺🇸US @waqi.info -#!desc=(V2) 切换空气质量地图数据源为waqi.info,并更改标准为AQI(US) - -[URL Rewrite] -# Rewrite Apple Weather Air Quality Map -^https?:\/\/weather-map\.apple\.com\/(v1|v2)\/mapOverlay\/airQuality\?x=(-?\d+)&y=(-?\d+)&z=(-?\d+).*(country=CN)?.* https://tiles.waqi.info/tiles/usepa-aqi/$4/$2/$3.png?&scale=2&country=US&colorFormat=agr header - -[MITM] -hostname = %APPEND% weather-map.apple.com, tiles.waqi.info diff --git a/Archive/sgmodule/Siri_Suggestions.beta.sgmodule b/Archive/sgmodule/Siri_Suggestions.beta.sgmodule deleted file mode 100644 index 6f7281d0c..000000000 --- a/Archive/sgmodule/Siri_Suggestions.beta.sgmodule +++ /dev/null @@ -1,14 +0,0 @@ -#!name= Enable Siri Suggestions -#!desc=(BETA) 在聚焦搜索(Spotlight)和查询(Look Up)中启用Siri建议(Siri Suggestions)功能。 - -[Script] -# Redirect Siri Suggestions Service -# Bag (iOS/macOS) -Redirect Siri Suggestions Service = type=http-request, pattern=^https?:\/\/api.*\.smoot\.apple\.(com|cn)\/bag\?(.*), requires-body=0, timeout=5, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/beta/archive/js/Siri_Suggestions.js -# Spotlight & Look Up Search (iOS/macOS) -Spotlight & Look Up Search = type=http-request, pattern=^https?:\/\/api.*\.smoot\.apple\.(com|cn)\/search\?(.*), requires-body=0, timeout=5, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/beta/archive/js/Siri_Suggestions.js -# Siri Infomation Card (macOS) -Siri Infomation Card = type=http-request, pattern=^https?:\/\/api.*\.smoot\.apple\.(com|cn)\/card\?(.*), requires-body=0, timeout=5, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/beta/archive/js/Siri_Suggestions.js - -[MITM] -hostname = %APPEND% api*.smoot.apple.com, api*.smoot.apple.cn diff --git a/Archive/sgmodule/Siri_Suggestions.sgmodule b/Archive/sgmodule/Siri_Suggestions.sgmodule deleted file mode 100644 index 5c3f01386..000000000 --- a/Archive/sgmodule/Siri_Suggestions.sgmodule +++ /dev/null @@ -1,14 +0,0 @@ -#!name= Enable Siri Suggestions -#!desc=(V1.5) 在聚焦搜索(Spotlight)和查询(Look Up)中启用Siri建议(Siri Suggestions)功能。 - -[Script] -# Redirect Siri Suggestions Service -# Bag (iOS/macOS) -Redirect Siri Suggestions Service = type=http-request, pattern=^https?:\/\/api.*\.smoot\.apple\.(com|cn)\/bag\?(.*), requires-body=0, timeout=5, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/main/archive/js/Siri_Suggestions.js -# Spotlight & Look Up Search (iOS/macOS) -Spotlight & Look Up Search = type=http-request, pattern=^https?:\/\/api.*\.smoot\.apple\.(com|cn)\/search\?(.*), requires-body=0, timeout=5, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/main/archive/js/Siri_Suggestions.js -# Siri Infomation Card (macOS) -Siri Infomation Card = type=http-request, pattern=^https?:\/\/api.*\.smoot\.apple\.(com|cn)\/card\?(.*), requires-body=0, timeout=5, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/main/archive/js/Siri_Suggestions.js - -[MITM] -hostname = %APPEND% api*.smoot.apple.com, api*.smoot.apple.cn diff --git a/Archive/sgmodule/Siri_Suggestions_JP.sgmodule b/Archive/sgmodule/Siri_Suggestions_JP.sgmodule deleted file mode 100644 index 767af099c..000000000 --- a/Archive/sgmodule/Siri_Suggestions_JP.sgmodule +++ /dev/null @@ -1,20 +0,0 @@ -#!name= Enable Siri Suggestions 🇯🇵JP -#!desc=(V1) 在聚焦搜索(Spotlight)和查询(Look Up)中启用Siri建议(Siri Suggestions)功能。 - -[URL Rewrite] -# Redirect Siri Suggestions Service -# Bag (iOS/macOS) -(^https?:\/\/api.*\.smoot\.apple\.)(com|cn)(\/bag.*)(cc=[A-Z]{2})(.*) $1com$3cc=JP$5 header -# General Spotlight & Look Up Search (iOS/macOS) -(^https?:\/\/api.*\.smoot\.apple\.)(com|cn)(\/search.*)(cc=[A-Z]{2})(.*) $1com$3cc=JP$5 header -# General Info Card (macOS) -(^https?:\/\/api.*\.smoot\.apple\.)(com|cn)(\/card.*)(card_locale=[a-zA-z\-]{2,7}_[A-Z]{2})(.*)(cc=[A-Z]{2})(.*)(include=[^kg][^tv][^movies])(.*) $1com$3$4$5cc=JP$7$8$9 header -# Siri Knowledge(Siri资料) Card (Spotlight@macOS & Siri@iOS) -(^https?:\/\/api.*\.smoot\.apple\.)(com|cn)(\/card.*)(card_locale=[a-zA-z\-]{2,7}_[A-Z]{2})(.*)(cc=[A-Z]{2})(.*)(include=(?:kgcn|kg))(.*) $1com$3$4$5cc=JP$7$8$9 header -# Siri Knowledge(Siri资料) Card (Spotlight@iOS) -(^https?:\/\/api.*\.smoot\.apple\.)(com|cn)(\/search.*)(cardDomain=kg)(.*)(card_locale=[a-zA-z\-]{2,7}_[A-Z]{2})(.*)(cc=[A-Z]{2})(.*) $1com$3$4$5$6$7cc=JP$9 header -# tv Movies and TV Show Card (macOS) -(^https?:\/\/api.*\.smoot\.apple\.)(com|cn)(\/card.*)(card_locale=[a-zA-z\-]{2,7}_[A-Z]{2})(.*)(cc=[A-Z]{2})(.*)(include=(?:tv|movies))(.*)(q=(?:tv|movies)%3A)(.*)(%2F)([a-z]{2}-[A-Z]{2})(.*) $1com$3$4$5cc=JP$7$8$9$10$11$12$13$14 header - -[MITM] -hostname = %APPEND% api*.smoot.apple.com, api*.smoot.apple.cn diff --git a/Archive/sgmodule/Siri_Suggestions_TW.sgmodule b/Archive/sgmodule/Siri_Suggestions_TW.sgmodule deleted file mode 100644 index 605d773ac..000000000 --- a/Archive/sgmodule/Siri_Suggestions_TW.sgmodule +++ /dev/null @@ -1,20 +0,0 @@ -#!name= Enable Siri Suggestions 🇹🇼TW -#!desc=(V1) 在聚焦搜索(Spotlight)和查询(Look Up)中启用Siri建议(Siri Suggestions)功能。 - -[URL Rewrite] -# Redirect Siri Suggestions Service -# Bag (iOS/macOS) -(^https?:\/\/api.*\.smoot\.apple\.)(com|cn)(\/bag.*)(cc=[A-Z]{2})(.*) $1com$3cc=TW$5 header -# General Spotlight & Look Up Search (iOS/macOS) -(^https?:\/\/api.*\.smoot\.apple\.)(com|cn)(\/search.*)(cc=[A-Z]{2})(.*) $1com$3cc=TW$5 header -# General Info Card (macOS) -(^https?:\/\/api.*\.smoot\.apple\.)(com|cn)(\/card.*)(card_locale=[a-zA-z\-]{2,7}_[A-Z]{2})(.*)(cc=[A-Z]{2})(.*)(include=[^kg][^tv][^movies])(.*) $1com$3$4$5cc=TW$7$8$9 header -# Siri Knowledge(Siri资料) Card (Spotlight@macOS & Siri@iOS) -(^https?:\/\/api.*\.smoot\.apple\.)(com|cn)(\/card.*)(card_locale=[a-zA-z\-]{2,7}_[A-Z]{2})(.*)(cc=[A-Z]{2})(.*)(include=(?:kgcn|kg))(.*) $1com$3$4$5cc=TW$7$8$9 header -# Siri Knowledge(Siri资料) Card (Spotlight@iOS) -(^https?:\/\/api.*\.smoot\.apple\.)(com|cn)(\/search.*)(cardDomain=kg)(.*)(card_locale=[a-zA-z\-]{2,7}_[A-Z]{2})(.*)(cc=[A-Z]{2})(.*) $1com$3$4$5$6$7cc=TW$9 header -# tv Movies and TV Show Card (macOS) -(^https?:\/\/api.*\.smoot\.apple\.)(com|cn)(\/card.*)(card_locale=[a-zA-z\-]{2,7}_[A-Z]{2})(.*)(cc=[A-Z]{2})(.*)(include=(?:tv|movies))(.*)(q=(?:tv|movies)%3A)(.*)(%2F)([a-z]{2}-[A-Z]{2})(.*) $1com$3$4$5cc=TW$7$8$9$10$11$12$13$14 header - -[MITM] -hostname = %APPEND% api*.smoot.apple.com, api*.smoot.apple.cn diff --git a/Archive/sgmodule/Siri_Suggestions_US.sgmodule b/Archive/sgmodule/Siri_Suggestions_US.sgmodule deleted file mode 100644 index d8bc8c1fe..000000000 --- a/Archive/sgmodule/Siri_Suggestions_US.sgmodule +++ /dev/null @@ -1,20 +0,0 @@ -#!name= Enable Siri Suggestions 🇺🇸US -#!desc=(V1) 在聚焦搜索(Spotlight)和查询(Look Up)中启用Siri建议(Siri Suggestions)功能。 - -[URL Rewrite] -# Redirect Siri Suggestions Service -# Bag (iOS/macOS) -(^https?:\/\/api.*\.smoot\.apple\.)(com|cn)(\/bag.*)(cc=[A-Z]{2})(.*) $1com$3cc=US$5 header -# General Spotlight & Look Up Search (iOS/macOS) -(^https?:\/\/api.*\.smoot\.apple\.)(com|cn)(\/search.*)(cc=[A-Z]{2})(.*) $1com$3cc=US$5 header -# General Info Card (macOS) -(^https?:\/\/api.*\.smoot\.apple\.)(com|cn)(\/card.*)(card_locale=[a-zA-z\-]{2,7}_[A-Z]{2})(.*)(cc=[A-Z]{2})(.*)(include=[^kg][^tv][^movies])(.*) $1com$3$4$5cc=US$7$8$9 header -# Siri Knowledge(Siri资料) Card (Spotlight@macOS & Siri@iOS) -(^https?:\/\/api.*\.smoot\.apple\.)(com|cn)(\/card.*)(card_locale=[a-zA-z\-]{2,7}_[A-Z]{2})(.*)(cc=[A-Z]{2})(.*)(include=(?:kgcn|kg))(.*) $1com$3$4$5cc=US$7$8$9 header -# Siri Knowledge(Siri资料) Card (Spotlight@iOS) -(^https?:\/\/api.*\.smoot\.apple\.)(com|cn)(\/search.*)(cardDomain=kg)(.*)(card_locale=[a-zA-z\-]{2,7}_[A-Z]{2})(.*)(cc=[A-Z]{2})(.*) $1com$3$4$5$6$7cc=US$9 header -# tv Movies and TV Show Card (macOS) -(^https?:\/\/api.*\.smoot\.apple\.)(com|cn)(\/card.*)(card_locale=[a-zA-z\-]{2,7}_[A-Z]{2})(.*)(cc=[A-Z]{2})(.*)(include=(?:tv|movies))(.*)(q=(?:tv|movies)%3A)(.*)(%2F)([a-z]{2}-[A-Z]{2})(.*) $1com$3$4$5cc=US$7$8$9$10$11$12$13$14 header - -[MITM] -hostname = %APPEND% api*.smoot.apple.com, api*.smoot.apple.cn diff --git a/Archive/sgmodule/Siri_Suggestions_for_ACL4SSR.sgmodule b/Archive/sgmodule/Siri_Suggestions_for_ACL4SSR.sgmodule deleted file mode 100644 index c66deffc1..000000000 --- a/Archive/sgmodule/Siri_Suggestions_for_ACL4SSR.sgmodule +++ /dev/null @@ -1,18 +0,0 @@ -#!name= Enable Siri Suggestions -#!desc=(V1.5) 在聚焦搜索(Spotlight)和查询(Look Up)中启用Siri建议(Siri Suggestions)功能。 - -[Rule] -# > Look Up (Only for Wikipedia@macOS, Surge Enhance Mode enabled required) -RULE-SET,https://raw.githubusercontent.com/VirgilClyne/iRingo/main/archive/RuleSet/Wikipedia_for_Look_Up.list,🍎 苹果服务 - -[Script] -# Redirect Siri Suggestions Service -# Bag (iOS/macOS) -Redirect Siri Suggestions Service = type=http-request, pattern=^https?:\/\/api.*\.smoot\.apple\.(com|cn)\/bag\?(.*), requires-body=0, timeout=5, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/main/archive/js/Siri_Suggestions.js -# Spotlight & Look Up Search (iOS/macOS) -Spotlight & Look Up Search = type=http-request, pattern=^https?:\/\/api.*\.smoot\.apple\.(com|cn)\/search\?(.*), requires-body=0, timeout=5, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/main/archive/js/Siri_Suggestions.js -# Siri Infomation Card (macOS) -Siri Infomation Card = type=http-request, pattern=^https?:\/\/api.*\.smoot\.apple\.(com|cn)\/card\?(.*), requires-body=0, timeout=5, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/main/archive/js/Siri_Suggestions.js - -[MITM] -hostname = %APPEND% api*.smoot.apple.com, api*.smoot.apple.cn diff --git a/Archive/sgmodule/Siri_Suggestions_for_Apple.sgmodule b/Archive/sgmodule/Siri_Suggestions_for_Apple.sgmodule deleted file mode 100644 index b4c02ac04..000000000 --- a/Archive/sgmodule/Siri_Suggestions_for_Apple.sgmodule +++ /dev/null @@ -1,18 +0,0 @@ -#!name= Enable Siri Suggestions -#!desc=(V1.5) 在聚焦搜索(Spotlight)和查询(Look Up)中启用Siri建议(Siri Suggestions)功能。 - -[Rule] -# > Look Up (Only for Wikipedia@macOS, Surge Enhance Mode enabled required) -RULE-SET,https://raw.githubusercontent.com/VirgilClyne/iRingo/main/archive/RuleSet/Wikipedia_for_Look_Up.list,Apple - -[Script] -# Redirect Siri Suggestions Service -# Bag (iOS/macOS) -Redirect Siri Suggestions Service = type=http-request, pattern=^https?:\/\/api.*\.smoot\.apple\.(com|cn)\/bag\?(.*), requires-body=0, timeout=5, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/main/archive/js/Siri_Suggestions.js -# Spotlight & Look Up Search (iOS/macOS) -Spotlight & Look Up Search = type=http-request, pattern=^https?:\/\/api.*\.smoot\.apple\.(com|cn)\/search\?(.*), requires-body=0, timeout=5, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/main/archive/js/Siri_Suggestions.js -# Siri Infomation Card (macOS) -Siri Infomation Card = type=http-request, pattern=^https?:\/\/api.*\.smoot\.apple\.(com|cn)\/card\?(.*), requires-body=0, timeout=5, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/main/archive/js/Siri_Suggestions.js - -[MITM] -hostname = %APPEND% api*.smoot.apple.com, api*.smoot.apple.cn diff --git a/Archive/sgmodule/Siri_Suggestions_for_Apple_icon.sgmodule b/Archive/sgmodule/Siri_Suggestions_for_Apple_icon.sgmodule deleted file mode 100644 index f9beee918..000000000 --- a/Archive/sgmodule/Siri_Suggestions_for_Apple_icon.sgmodule +++ /dev/null @@ -1,18 +0,0 @@ -#!name= Enable Siri Suggestions -#!desc=(V1.5) 在聚焦搜索(Spotlight)和查询(Look Up)中启用Siri建议(Siri Suggestions)功能。 - -[Rule] -# > Look Up (Only for Wikipedia@macOS, Surge Enhance Mode enabled required) -RULE-SET,https://raw.githubusercontent.com/VirgilClyne/iRingo/main/archive/RuleSet/Wikipedia_for_Look_Up.list,🍎Apple - -[Script] -# Redirect Siri Suggestions Service -# Bag (iOS/macOS) -Redirect Siri Suggestions Service = type=http-request, pattern=^https?:\/\/api.*\.smoot\.apple\.(com|cn)\/bag\?(.*), requires-body=0, timeout=5, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/main/archive/js/Siri_Suggestions.js -# Spotlight & Look Up Search (iOS/macOS) -Spotlight & Look Up Search = type=http-request, pattern=^https?:\/\/api.*\.smoot\.apple\.(com|cn)\/search\?(.*), requires-body=0, timeout=5, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/main/archive/js/Siri_Suggestions.js -# Siri Infomation Card (macOS) -Siri Infomation Card = type=http-request, pattern=^https?:\/\/api.*\.smoot\.apple\.(com|cn)\/card\?(.*), requires-body=0, timeout=5, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/main/archive/js/Siri_Suggestions.js - -[MITM] -hostname = %APPEND% api*.smoot.apple.com, api*.smoot.apple.cn diff --git a/Archive/sgmodule/Siri_Suggestions_for_DivineEngine.sgmodule b/Archive/sgmodule/Siri_Suggestions_for_DivineEngine.sgmodule deleted file mode 100644 index 2ead7f25c..000000000 --- a/Archive/sgmodule/Siri_Suggestions_for_DivineEngine.sgmodule +++ /dev/null @@ -1,18 +0,0 @@ -#!name= Enable Siri Suggestions -#!desc=(V1.5) 在聚焦搜索(Spotlight)和查询(Look Up)中启用Siri建议(Siri Suggestions)功能。 - -[Rule] -# > Look Up (Only for Wikipedia@macOS, Surge Enhance Mode enabled required) -RULE-SET,https://raw.githubusercontent.com/VirgilClyne/iRingo/main/archive/RuleSet/Wikipedia_for_Look_Up.list,🌑Proxy - -[Script] -# Redirect Siri Suggestions Service -# Bag (iOS/macOS) -Redirect Siri Suggestions Service = type=http-request, pattern=^https?:\/\/api.*\.smoot\.apple\.(com|cn)\/bag\?(.*), requires-body=0, timeout=5, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/main/archive/js/Siri_Suggestions.js -# Spotlight & Look Up Search (iOS/macOS) -Spotlight & Look Up Search = type=http-request, pattern=^https?:\/\/api.*\.smoot\.apple\.(com|cn)\/search\?(.*), requires-body=0, timeout=5, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/main/archive/js/Siri_Suggestions.js -# Siri Infomation Card (macOS) -Siri Infomation Card = type=http-request, pattern=^https?:\/\/api.*\.smoot\.apple\.(com|cn)\/card\?(.*), requires-body=0, timeout=5, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/main/archive/js/Siri_Suggestions.js - -[MITM] -hostname = %APPEND% api*.smoot.apple.com, api*.smoot.apple.cn diff --git a/Archive/sgmodule/Siri_Suggestions_for_Proxy.sgmodule b/Archive/sgmodule/Siri_Suggestions_for_Proxy.sgmodule deleted file mode 100644 index 50b99da36..000000000 --- a/Archive/sgmodule/Siri_Suggestions_for_Proxy.sgmodule +++ /dev/null @@ -1,18 +0,0 @@ -#!name= Enable Siri Suggestions -#!desc=(V1.5) 在聚焦搜索(Spotlight)和查询(Look Up)中启用Siri建议(Siri Suggestions)功能。 - -[Rule] -# > Look Up (Only for Wikipedia@macOS, Surge Enhance Mode enabled required) -RULE-SET,https://raw.githubusercontent.com/VirgilClyne/iRingo/main/archive/RuleSet/Wikipedia_for_Look_Up.list,Proxy - -[Script] -# Redirect Siri Suggestions Service -# Bag (iOS/macOS) -Redirect Siri Suggestions Service = type=http-request, pattern=^https?:\/\/api.*\.smoot\.apple\.(com|cn)\/bag\?(.*), requires-body=0, timeout=5, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/main/archive/js/Siri_Suggestions.js -# Spotlight & Look Up Search (iOS/macOS) -Spotlight & Look Up Search = type=http-request, pattern=^https?:\/\/api.*\.smoot\.apple\.(com|cn)\/search\?(.*), requires-body=0, timeout=5, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/main/archive/js/Siri_Suggestions.js -# Siri Infomation Card (macOS) -Siri Infomation Card = type=http-request, pattern=^https?:\/\/api.*\.smoot\.apple\.(com|cn)\/card\?(.*), requires-body=0, timeout=5, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/main/archive/js/Siri_Suggestions.js - -[MITM] -hostname = %APPEND% api*.smoot.apple.com, api*.smoot.apple.cn diff --git a/Archive/sgmodule/Siri_Suggestions_for_Surgio.sgmodule b/Archive/sgmodule/Siri_Suggestions_for_Surgio.sgmodule deleted file mode 100644 index 848a9548a..000000000 --- a/Archive/sgmodule/Siri_Suggestions_for_Surgio.sgmodule +++ /dev/null @@ -1,18 +0,0 @@ -#!name= Enable Siri Suggestions -#!desc=(V1.5) 在聚焦搜索(Spotlight)和查询(Look Up)中启用Siri建议(Siri Suggestions)功能。 - -[Rule] -# > Look Up (Only for Wikipedia@macOS, Surge Enhance Mode enabled required) -RULE-SET,https://raw.githubusercontent.com/VirgilClyne/iRingo/main/archive/RuleSet/Wikipedia_for_Look_Up.list,🍎 Apple - -[Script] -# Redirect Siri Suggestions Service -# Bag (iOS/macOS) -Redirect Siri Suggestions Service = type=http-request, pattern=^https?:\/\/api.*\.smoot\.apple\.(com|cn)\/bag\?(.*), requires-body=0, timeout=5, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/main/archive/js/Siri_Suggestions.js -# Spotlight & Look Up Search (iOS/macOS) -Spotlight & Look Up Search = type=http-request, pattern=^https?:\/\/api.*\.smoot\.apple\.(com|cn)\/search\?(.*), requires-body=0, timeout=5, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/main/archive/js/Siri_Suggestions.js -# Siri Infomation Card (macOS) -Siri Infomation Card = type=http-request, pattern=^https?:\/\/api.*\.smoot\.apple\.(com|cn)\/card\?(.*), requires-body=0, timeout=5, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/main/archive/js/Siri_Suggestions.js - -[MITM] -hostname = %APPEND% api*.smoot.apple.com, api*.smoot.apple.cn diff --git a/Archive/sgmodule/Siri_Suggestions_for_Uppercase_PROXY.sgmodule b/Archive/sgmodule/Siri_Suggestions_for_Uppercase_PROXY.sgmodule deleted file mode 100644 index dd34dc451..000000000 --- a/Archive/sgmodule/Siri_Suggestions_for_Uppercase_PROXY.sgmodule +++ /dev/null @@ -1,18 +0,0 @@ -#!name= Enable Siri Suggestions -#!desc=(V1.5) 在聚焦搜索(Spotlight)和查询(Look Up)中启用Siri建议(Siri Suggestions)功能。 - -[Rule] -# > Look Up (Only for Wikipedia@macOS, Surge Enhance Mode enabled required) -RULE-SET,https://raw.githubusercontent.com/VirgilClyne/iRingo/main/archive/RuleSet/Wikipedia_for_Look_Up.list,PROXY - -[Script] -# Redirect Siri Suggestions Service -# Bag (iOS/macOS) -Redirect Siri Suggestions Service = type=http-request, pattern=^https?:\/\/api.*\.smoot\.apple\.(com|cn)\/bag\?(.*), requires-body=0, timeout=5, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/main/archive/js/Siri_Suggestions.js -# Spotlight & Look Up Search (iOS/macOS) -Spotlight & Look Up Search = type=http-request, pattern=^https?:\/\/api.*\.smoot\.apple\.(com|cn)\/search\?(.*), requires-body=0, timeout=5, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/main/archive/js/Siri_Suggestions.js -# Siri Infomation Card (macOS) -Siri Infomation Card = type=http-request, pattern=^https?:\/\/api.*\.smoot\.apple\.(com|cn)\/card\?(.*), requires-body=0, timeout=5, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/main/archive/js/Siri_Suggestions.js - -[MITM] -hostname = %APPEND% api*.smoot.apple.com, api*.smoot.apple.cn diff --git a/Archive/sgmodule/Weather.v3.sgmodule b/Archive/sgmodule/Weather.v3.sgmodule deleted file mode 100644 index 7a1147566..000000000 --- a/Archive/sgmodule/Weather.v3.sgmodule +++ /dev/null @@ -1,18 +0,0 @@ -#!name= iRingo for Apple Weather -#!desc=(V3) 1.解锁全部天气数据类型 2.替换空气质量数据:WAQI 3.添加下一小时降水:气象在线 4.替换空气质量地图数据:WAQI -#!openUrl=http://boxjs.com/#/app/iRingo.Weather -#!author=VirgilClyne -#!homepage=https://github.com/VirgilClyne -#!manual=https://github.com/VirgilClyne/iRingo/wiki/🌤天气 -#!icon=https://is4-ssl.mzstatic.com/image/thumb/Purple125/v4/c8/76/87/c8768792-b7ab-7de4-9c70-1888096b7ae9/AppIcon-0-0-1x_U007emarketing-0-0-0-10-0-0-sRGB-0-0-0-GLES2_U002c0-512MB-85-220-0-0.png/108x0w.webp - -[Header Rewrite] -http-request ^https?:\/\/weather-data\.apple\.com\/(v1|v2)\/weather\/.* header-del If-None-Match - -[Script] -Apple Weather Availability = type=http-response, pattern=^https?:\/\/weather-data\.apple\.com\/(v1|v2)\/availability\/.*, requires-body=1, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/main/Archive/js/Weather.Availability.js, argument= -Apple Weather = type=http-response, pattern=^https?:\/\/weather-data\.apple\.com\/(v1|v2)\/weather\/.*, requires-body=1, timeout=20, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/main/Archive/js/Weather.js, argument= -Apple Weather Map = type=http-request, pattern=^https?:\/\/weather-map\.apple\.com\/(v1|v2)\/mapOverlay\/.*(\?.*country=CN.*), requires-body=0, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/main/Archive/js/Weather.Map.js, argument= - -[MITM] -hostname = %APPEND% weather-data.apple.com, weather-map.apple.com diff --git a/Archive/stoverride/Apple_Weather.stoverride b/Archive/stoverride/Apple_Weather.stoverride deleted file mode 100644 index 36f7ccb8f..000000000 --- a/Archive/stoverride/Apple_Weather.stoverride +++ /dev/null @@ -1,22 +0,0 @@ -name:  iRingo for Apple Weather AQI data with waqi.info -desc: (V2) 解锁全部类型天气数据可用性,切换空气质量数据源为waqi.info,并更改标准为AQI(US)。 - -http: - mitm: - - "weather-data.apple.com" - script: - - match: ^https?:\/\/weather-data\.apple\.com\/(v1|v2)\/weather.*(?!dataSets=forecastNextHour)(include=.*air_quality.*|dataSets=.*airQuality.*).*(country=[A-Z]{2})?.* - name: Apple_Weather - type: response - require-body: true - timeout: 10 - - match: ^https?:\/\/weather-data\.apple\.com\/(v1|v2)\/availability\/.* - name: Apple_Weather - type: response - require-body: true - timeout: 10 - -script-providers: - Apple_Weather: - url: https://raw.githubusercontent.com/VirgilClyne/iRingo/main/archive/js/Apple_Weather.js - interval: 86400 diff --git a/Archive/stoverride/Siri_Suggestions.stoverride b/Archive/stoverride/Siri_Suggestions.stoverride deleted file mode 100644 index 6fac8541e..000000000 --- a/Archive/stoverride/Siri_Suggestions.stoverride +++ /dev/null @@ -1,28 +0,0 @@ -name:  Enable Siri Suggestions -desc: (V1.5) 在聚焦搜索(Spotlight)和查询(Look Up)中启用Siri建议(Siri Suggestions)功能。 - -http: - mitm: - - "api*.smoot.apple.com" - - "api*.smoot.apple.cn" - script: - - match: ^https?:\/\/api.*\.smoot\.apple\.(com|cn)\/bag\?(.*) - name: Siri_Suggestions - type: request - require-body: false - timeout: 5 - - match: ^https?:\/\/api.*\.smoot\.apple\.(com|cn)\/search\?(.*) - name: Siri_Suggestions - type: request - require-body: false - timeout: 5 - - match: ^https?:\/\/api.*\.smoot\.apple\.(com|cn)\/card\?(.*) - name: Siri_Suggestions - type: request - require-body: false - timeout: 5 - -script-providers: - Siri_Suggestions: - url: https://raw.githubusercontent.com/VirgilClyne/iRingo/main/archive/js/Siri_Suggestions.js - interval: 86400 diff --git a/Archive/stoverride/Weather.v3.stoverride b/Archive/stoverride/Weather.v3.stoverride deleted file mode 100644 index 193201853..000000000 --- a/Archive/stoverride/Weather.v3.stoverride +++ /dev/null @@ -1,38 +0,0 @@ -name:  iRingo for Apple Weather -desc: (V3) 1.解锁全部天气数据类型 2.替换空气质量数据:WAQI 3.添加下一小时降水:气象在线 4.替换空气质量地图数据:WAQI - -http: - header-rewrite: - - ^https?:\/\/weather-data\.apple\.com\/(v1|v2)\/weather\/.* request-del If-None-Match - mitm: - - "weather-data.apple.com" - - "weather-map.apple.com" - script: - - match: ^https?:\/\/weather-data\.apple\.com\/(v1|v2)\/availability\/.* - name: Apple_Weather_Availability - type: response - require-body: true - timeout: 10 - - match: ^https?:\/\/weather-data\.apple\.com\/(v1|v2)\/weather\/.* - name: Apple_Weather - type: response - require-body: true - timeout: 20 - argument: - - match: ^https?:\/\/weather-map\.apple\.com\/(v1|v2)\/mapOverlay\/.*(\?.*country=CN.*) - name: Apple_Weather_Map - type: request - require-body: false - timeout: 10 - argument: - -script-providers: - Apple_Weather_Availability: - url: https://raw.githubusercontent.com/VirgilClyne/iRingo/main/Archive/js/Weather.Availability.js - interval: 86400 - Apple_Weather: - url: https://raw.githubusercontent.com/VirgilClyne/iRingo/main/Archive/js/Weather.js - interval: 86400 - Apple_Weather_Map: - url: https://raw.githubusercontent.com/VirgilClyne/iRingo/main/Archive/js/Weather.Map.js - interval: 86400 diff --git a/BoxJs/iRingo.17.BoxJs.beta.json b/BoxJs/iRingo.17.BoxJs.beta.json index 59da9335e..b6eddcbdc 100644 --- a/BoxJs/iRingo.17.BoxJs.beta.json +++ b/BoxJs/iRingo.17.BoxJs.beta.json @@ -4,13 +4,13 @@ "author": "@VirgilClyne", "description": "解锁完整的 Apple功能和集成服务", "icon": "https://avatars.githubusercontent.com/u/2111377?s=100&v=4", - "repo": "https://github.com/VirgilClyne/iRingo", + "repo": "https://github.com/NSRingo", "apps": [ { "id": "iRingo.Weather.beta", "name": "🌤 天气 β", "descs_html": [ - "请参照🌤 天气的使用说明进行配置", + "请参照🌤 天气的使用说明进行配置", "填写完成后别忘点击此页面底端右下角的\"保存\"。", "查询速度:\"私有API+城市\" > \"私有API+观测站\" > \"公共API+观测站\"", "定位精度:\"观测站\" > \"城市\"" @@ -702,7 +702,7 @@ "id": "iRingo.Siri.beta", "name": "⭕ Siri与搜索 β", "descs_html": [ - "请参照⭕ Siri与搜索的使用说明进行配置", + "请参照⭕ Siri与搜索的使用说明进行配置", "影响功能范围包括但不限于「Siri建议」「来自APPLE的内容」「来自APPLE的建议」等" ], "keys": [ diff --git a/BoxJs/iRingo.17.BoxJs.json b/BoxJs/iRingo.17.BoxJs.json index 08ca2619a..2676d5234 100644 --- a/BoxJs/iRingo.17.BoxJs.json +++ b/BoxJs/iRingo.17.BoxJs.json @@ -4,13 +4,13 @@ "author": "@VirgilClyne", "description": "解锁完整的 Apple功能和集成服务", "icon": "https://avatars.githubusercontent.com/u/2111377?s=100&v=4", - "repo": "https://github.com/VirgilClyne/iRingo", + "repo": "https://github.com/NSRingo", "apps": [ { "id": "iRingo.Weather", "name": "🌤 天气", "descs_html": [ - "请参照🌤 天气的使用说明进行配置", + "请参照🌤 天气的使用说明进行配置", "填写完成后别忘点击此页面底端右下角的\"保存\"。", "查询速度:\"私有API+城市\" > \"私有API+观测站\" > \"公共API+观测站\"", "定位精度:\"观测站\" > \"城市\"" @@ -586,7 +586,7 @@ "id": "iRingo.Siri", "name": "⭕ Siri与搜索", "descs_html": [ - "请参照⭕ Siri与搜索的使用说明进行配置", + "请参照⭕ Siri与搜索的使用说明进行配置", "影响功能范围包括但不限于「Siri建议」「来自APPLE的内容」「来自APPLE的建议」等" ], "keys": [ diff --git a/BoxJs/iRingo.BoxJs.beta.json b/BoxJs/iRingo.BoxJs.beta.json index 142306dcb..4518dcf09 100644 --- a/BoxJs/iRingo.BoxJs.beta.json +++ b/BoxJs/iRingo.BoxJs.beta.json @@ -4,13 +4,13 @@ "author": "@VirgilClyne", "description": "解锁完整的 Apple功能和集成服务", "icon": "https://avatars.githubusercontent.com/u/2111377?s=100&v=4", - "repo": "https://github.com/VirgilClyne/iRingo", + "repo": "https://github.com/NSRingo", "apps": [ { "id": "iRingo.WeatherKit.beta", "name": "🌤 天气服务 β", "descs_html": [ - "请参照🌤 天气服务的使用说明进行配置", + "请参照🌤 天气服务的使用说明进行配置", "填写完成后别忘点击此页面底端右下角的\"保存\"。" ], "keys": [ @@ -532,7 +532,7 @@ "id": "iRingo.AppleIntelligence.beta", "name": "🟥 Apple智能 β", "descs_html": [ - "请参照🟥 Apple智能与Siri的使用说明进行配置" + "请参照🟥 Apple智能与Siri的使用说明进行配置" ], "keys": [ "@iRingo.AppleIntelligence.Settings", @@ -558,7 +558,7 @@ "id": "iRingo.Siri.beta", "name": "⭕ Siri β", "descs_html": [ - "请参照🟥 Apple智能与Siri的使用说明进行配置", + "请参照🟥 Apple智能与Siri的使用说明进行配置", "影响功能范围不包括「Siri 请求」和「Siri 建议」等" ], "keys": [ @@ -631,11 +631,11 @@ ] }, { - "id": "@iRingo.Siri.Settings.Region", - "name": "Siri 区域", - "val": "AUTO", + "id": "@iRingo.Siri.Settings.SiriResponseLanguageVariant", + "name": "Siri 响应语言", + "val": "zh_CN", "type": "selects", - "desc": "忽略系统中“Siri”的“语言”设置,强制采用这里的设置。", + "desc": "自定义 Siri 回复使用的语言。", "items": [ { "key": "AUTO", @@ -1568,6 +1568,23 @@ "label": "加拿大" } ] + }, + { + "id": "@iRingo.TV.Settings.CountryCode.restoreLowPriceRegion", + "name": "[修复低价区]国家或地区代码", + "val": "DISABLED", + "type": "selects", + "desc": "修复低价区,解决低价区换区后无法播放的问题,仅限低价区用户使用", + "items": [ + { + "key": "DISABLED", + "label": "默认不使能" + }, + { + "key": "IN", + "label": "印度" + } + ] } ], "author": "@VirgilClyne", diff --git a/BoxJs/iRingo.BoxJs.json b/BoxJs/iRingo.BoxJs.json index aefb7505f..49771fcdb 100644 --- a/BoxJs/iRingo.BoxJs.json +++ b/BoxJs/iRingo.BoxJs.json @@ -4,13 +4,13 @@ "author": "@VirgilClyne", "description": "解锁完整的 Apple功能和集成服务", "icon": "https://avatars.githubusercontent.com/u/2111377?s=100&v=4", - "repo": "https://github.com/VirgilClyne/iRingo", + "repo": "https://github.com/NSRingo", "apps": [ { "id": "iRingo.WeatherKit", "name": "🌤 天气服务", "descs_html": [ - "请参照🌤 天气服务的使用说明进行配置", + "请参照🌤 天气服务的使用说明进行配置", "填写完成后别忘点击此页面底端右下角的\"保存\"。" ], "keys": [ @@ -502,7 +502,7 @@ "id": "iRingo.Siri", "name": "⭕ Siri", "descs_html": [ - "请参照🟥 Apple智能与Siri的使用说明进行配置", + "请参照🟥 Apple智能与Siri的使用说明进行配置", "影响功能范围包括「Siri 请求」和「Siri 建议」等" ], "keys": [ diff --git a/RuleSet/LookUp.Wikipedia.list b/RuleSet/LookUp.Wikipedia.list deleted file mode 100644 index ee27e2e7a..000000000 --- a/RuleSet/LookUp.Wikipedia.list +++ /dev/null @@ -1,6 +0,0 @@ -# > Look Up (Only for Wikipedia@macOS, Surge Enhance Mode enabled required) -PROCESS-NAME,LookupViewService -PROCESS-NAME,/System/Library/PrivateFrameworks/Lookup.framework/Versions/A/XPCServices/LookupViewService.xpc/Contents/MacOS/LookupViewService -DOMAIN,lookup-api.apple.com -DOMAIN,lookup-api.apple.com.edgekey.net -DOMAIN,e16991.b.akamaiedge.net diff --git a/RuleSet/LookUp.Wikipedia.yaml b/RuleSet/LookUp.Wikipedia.yaml deleted file mode 100644 index 0d3813e06..000000000 --- a/RuleSet/LookUp.Wikipedia.yaml +++ /dev/null @@ -1,7 +0,0 @@ -payload: -# > Look Up (Only for Wikipedia@macOS, Surge Enhance Mode enabled required) - - PROCESS-NAME,LookupViewService - - PROCESS-NAME,/System/Library/PrivateFrameworks/Lookup.framework/Versions/A/XPCServices/LookupViewService.xpc/Contents/MacOS/LookupViewService - - DOMAIN,lookup-api.apple.com - - DOMAIN,lookup-api.apple.com.edgekey.net - - DOMAIN,e16991.b.akamaiedge.net diff --git a/RuleSet/Look_Up.Wikipedia.list b/RuleSet/Look_Up.Wikipedia.list deleted file mode 100644 index ee27e2e7a..000000000 --- a/RuleSet/Look_Up.Wikipedia.list +++ /dev/null @@ -1,6 +0,0 @@ -# > Look Up (Only for Wikipedia@macOS, Surge Enhance Mode enabled required) -PROCESS-NAME,LookupViewService -PROCESS-NAME,/System/Library/PrivateFrameworks/Lookup.framework/Versions/A/XPCServices/LookupViewService.xpc/Contents/MacOS/LookupViewService -DOMAIN,lookup-api.apple.com -DOMAIN,lookup-api.apple.com.edgekey.net -DOMAIN,e16991.b.akamaiedge.net diff --git a/RuleSet/Look_Up.Wikipedia.yaml b/RuleSet/Look_Up.Wikipedia.yaml deleted file mode 100644 index 0d3813e06..000000000 --- a/RuleSet/Look_Up.Wikipedia.yaml +++ /dev/null @@ -1,7 +0,0 @@ -payload: -# > Look Up (Only for Wikipedia@macOS, Surge Enhance Mode enabled required) - - PROCESS-NAME,LookupViewService - - PROCESS-NAME,/System/Library/PrivateFrameworks/Lookup.framework/Versions/A/XPCServices/LookupViewService.xpc/Contents/MacOS/LookupViewService - - DOMAIN,lookup-api.apple.com - - DOMAIN,lookup-api.apple.com.edgekey.net - - DOMAIN,e16991.b.akamaiedge.net diff --git a/ScreenShots/App Store - Spotlight - iOS.jpeg b/ScreenShots/App Store - Spotlight - iOS.jpeg deleted file mode 100644 index 46dc92fd5..000000000 Binary files a/ScreenShots/App Store - Spotlight - iOS.jpeg and /dev/null differ diff --git a/ScreenShots/Apple Music - Spotlight - iOS.jpeg b/ScreenShots/Apple Music - Spotlight - iOS.jpeg deleted file mode 100644 index ab187d338..000000000 Binary files a/ScreenShots/Apple Music - Spotlight - iOS.jpeg and /dev/null differ diff --git a/ScreenShots/Apple Music - Spotlight - macOS.png b/ScreenShots/Apple Music - Spotlight - macOS.png deleted file mode 100644 index e55414cf1..000000000 Binary files a/ScreenShots/Apple Music - Spotlight - macOS.png and /dev/null differ diff --git a/ScreenShots/Apple News - Locked - iOS.jpeg b/ScreenShots/Apple News - Locked - iOS.jpeg deleted file mode 100644 index ca38d6cd1..000000000 Binary files a/ScreenShots/Apple News - Locked - iOS.jpeg and /dev/null differ diff --git a/ScreenShots/Apple News - Unlock Invalid - iOS.jpeg b/ScreenShots/Apple News - Unlock Invalid - iOS.jpeg deleted file mode 100644 index 020c2d68f..000000000 Binary files a/ScreenShots/Apple News - Unlock Invalid - iOS.jpeg and /dev/null differ diff --git a/ScreenShots/Apple News - Unlock Success - iOS.jpeg b/ScreenShots/Apple News - Unlock Success - iOS.jpeg deleted file mode 100644 index d2f352bd0..000000000 Binary files a/ScreenShots/Apple News - Unlock Success - iOS.jpeg and /dev/null differ diff --git a/ScreenShots/Flights - Spotlight - macOS.png b/ScreenShots/Flights - Spotlight - macOS.png deleted file mode 100644 index 358bed347..000000000 Binary files a/ScreenShots/Flights - Spotlight - macOS.png and /dev/null differ diff --git a/ScreenShots/Mac App Store - Spotlight - macOS.png b/ScreenShots/Mac App Store - Spotlight - macOS.png deleted file mode 100644 index 2926e478c..000000000 Binary files a/ScreenShots/Mac App Store - Spotlight - macOS.png and /dev/null differ diff --git a/ScreenShots/Siri Knowledge - Spotlight - macOS.png b/ScreenShots/Siri Knowledge - Spotlight - macOS.png deleted file mode 100644 index 35207d559..000000000 Binary files a/ScreenShots/Siri Knowledge - Spotlight - macOS.png and /dev/null differ diff --git a/ScreenShots/Sports - Spotlight - iOS.jpeg b/ScreenShots/Sports - Spotlight - iOS.jpeg deleted file mode 100644 index 139d7df1d..000000000 Binary files a/ScreenShots/Sports - Spotlight - iOS.jpeg and /dev/null differ diff --git a/ScreenShots/Sports - Spotlight - macOS.png b/ScreenShots/Sports - Spotlight - macOS.png deleted file mode 100644 index f177e3297..000000000 Binary files a/ScreenShots/Sports - Spotlight - macOS.png and /dev/null differ diff --git a/ScreenShots/Stock - Spotlight - macOS.png b/ScreenShots/Stock - Spotlight - macOS.png deleted file mode 100644 index 45206c712..000000000 Binary files a/ScreenShots/Stock - Spotlight - macOS.png and /dev/null differ diff --git a/ScreenShots/iCloud Private Relay - Avaliable - iOS.jpeg b/ScreenShots/iCloud Private Relay - Avaliable - iOS.jpeg deleted file mode 100644 index 46f022d08..000000000 Binary files a/ScreenShots/iCloud Private Relay - Avaliable - iOS.jpeg and /dev/null differ diff --git a/ScreenShots/iCloud Private Relay - Network Traffic Audits - iOS.jpeg b/ScreenShots/iCloud Private Relay - Network Traffic Audits - iOS.jpeg deleted file mode 100644 index ac63d4a22..000000000 Binary files a/ScreenShots/iCloud Private Relay - Network Traffic Audits - iOS.jpeg and /dev/null differ diff --git a/ScreenShots/iCloud Private Relay - Not Supported - iOS.jpeg b/ScreenShots/iCloud Private Relay - Not Supported - iOS.jpeg deleted file mode 100644 index cf47a3f08..000000000 Binary files a/ScreenShots/iCloud Private Relay - Not Supported - iOS.jpeg and /dev/null differ diff --git a/ScreenShots/iCloud Private Relay - Temporarily Unavaliable - iOS.jpeg b/ScreenShots/iCloud Private Relay - Temporarily Unavaliable - iOS.jpeg deleted file mode 100644 index 1a3d8c483..000000000 Binary files a/ScreenShots/iCloud Private Relay - Temporarily Unavaliable - iOS.jpeg and /dev/null differ diff --git a/ScreenShots/iCloud Private Relay - Turned Off - iOS.jpeg b/ScreenShots/iCloud Private Relay - Turned Off - iOS.jpeg deleted file mode 100644 index 6074b2eeb..000000000 Binary files a/ScreenShots/iCloud Private Relay - Turned Off - iOS.jpeg and /dev/null differ diff --git "a/ScreenShots/\357\243\277tv - Spotlight - macOS.png" "b/ScreenShots/\357\243\277tv - Spotlight - macOS.png" deleted file mode 100644 index 19495ece6..000000000 Binary files "a/ScreenShots/\357\243\277tv - Spotlight - macOS.png" and /dev/null differ diff --git a/js/Siri.request.beta.js b/js/Siri.request.beta.js deleted file mode 100644 index 6636375e1..000000000 --- a/js/Siri.request.beta.js +++ /dev/null @@ -1,12607 +0,0 @@ -/* README: https://github.com/VirgilClyne/iRingo */ -console.log(' iRingo: ⭕ Siri β Request') -const $platform = platform(); -function platform() { - if ("undefined" !== typeof $environment && $environment["surge-version"]) - return "Surge" - if ("undefined" !== typeof $environment && $environment["stash-version"]) - return "Stash" - if ("undefined" !== typeof module && !!module.exports) return "Node.js" - if ("undefined" !== typeof $task) return "Quantumult X" - if ("undefined" !== typeof $loon) return "Loon" - if ("undefined" !== typeof $rocket) return "Shadowrocket" - if ("undefined" !== typeof Egern) return "Egern" -} - -class URL { - constructor(url, base = undefined) { - const name = "URL"; - const version = "2.1.2"; - console.log(`\n🟧 ${name} v${version}\n`); - url = this.#parse(url, base); - return this; - }; - - #parse(url, base = undefined) { - const URLRegex = /(?:(?\w+:)\/\/(?:(?[^\s:"]+)(?::(?[^\s:"]+))?@)?(?[^\s@/]+))?(?\/?[^\s@?]+)?(?\?[^\s?]+)?/; - const PortRegex = /(?.+):(?\d+)$/; - url = url.match(URLRegex)?.groups || {}; - if (base) { - base = base?.match(URLRegex)?.groups || {}; - if (!base.protocol || !base.hostname) throw new Error(`🚨 ${name}, ${base} is not a valid URL`); - } if (url.protocol || base?.protocol) this.protocol = url.protocol || base.protocol; - if (url.username || base?.username) this.username = url.username || base.username; - if (url.password || base?.password) this.password = url.password || base.password; - if (url.host || base?.host) { - this.host = url.host || base.host; - Object.freeze(this.host); - this.hostname = this.host.match(PortRegex)?.groups.hostname ?? this.host; - this.port = this.host.match(PortRegex)?.groups.port ?? ""; - } if (url.pathname || base?.pathname) { - this.pathname = url.pathname || base?.pathname; - if (!this.pathname.startsWith("/")) this.pathname = "/" + this.pathname; - this.paths = this.pathname.split("/").filter(Boolean); - Object.freeze(this.paths); - if (this.paths) { - const fileName = this.paths[this.paths.length - 1]; - if (fileName?.includes(".")) { - const list = fileName.split("."); - this.format = list[list.length - 1]; - Object.freeze(this.format); - } - } } else this.pathname = ""; - if (url.search || base?.search) { - this.search = url.search || base.search; - Object.freeze(this.search); - if (this.search) this.searchParams = this.search.slice(1).split("&").map((param) => param.split("=")); - } this.searchParams = new Map(this.searchParams || []); - this.harf = this.toString(); - Object.freeze(this.harf); - return this; - }; - - toString() { - let string = ""; - if (this.protocol) string += this.protocol + "//"; - if (this.username) string += this.username + (this.password ? ":" + this.password : "") + "@"; - if (this.hostname) string += this.hostname; - if (this.port) string += ":" + this.port; - if (this.pathname) string += this.pathname; - if (this.searchParams.size !== 0) string += "?" + Array.from(this.searchParams).map(param => param.join("=")).join("&"); - return string; - }; - - toJSON() { return JSON.stringify({ ...this }) }; -} - -/* https://www.lodashjs.com */ -class Lodash { - static name = "Lodash"; - static version = "1.2.2"; - static about() { return console.log(`\n🟧 ${this.name} v${this.version}\n`) }; - - static get(object = {}, path = "", defaultValue = undefined) { - // translate array case to dot case, then split with . - // a[0].b -> a.0.b -> ['a', '0', 'b'] - if (!Array.isArray(path)) path = this.toPath(path); - - const result = path.reduce((previousValue, currentValue) => { - return Object(previousValue)[currentValue]; // null undefined get attribute will throwError, Object() can return a object - }, object); - return (result === undefined) ? defaultValue : result; - } - - static set(object = {}, path = "", value) { - if (!Array.isArray(path)) path = this.toPath(path); - path - .slice(0, -1) - .reduce( - (previousValue, currentValue, currentIndex) => - (Object(previousValue[currentValue]) === previousValue[currentValue]) - ? previousValue[currentValue] - : previousValue[currentValue] = (/^\d+$/.test(path[currentIndex + 1]) ? [] : {}), - object - )[path[path.length - 1]] = value; - return object - } - - static unset(object = {}, path = "") { - if (!Array.isArray(path)) path = this.toPath(path); - let result = path.reduce((previousValue, currentValue, currentIndex) => { - if (currentIndex === path.length - 1) { - delete previousValue[currentValue]; - return true - } - return Object(previousValue)[currentValue] - }, object); - return result - } - - static toPath(value) { - return value.replace(/\[(\d+)\]/g, '.$1').split('.').filter(Boolean); - } - - static escape(string) { - const map = { - '&': '&', - '<': '<', - '>': '>', - '"': '"', - "'": ''', - }; - return string.replace(/[&<>"']/g, m => map[m]) - }; - - static unescape(string) { - const map = { - '&': '&', - '<': '<', - '>': '>', - '"': '"', - ''': "'", - }; - return string.replace(/&|<|>|"|'/g, m => map[m]) - } - -} - -/* https://developer.mozilla.org/zh-CN/docs/Web/API/Storage/setItem */ -class Storage { - static name = "Storage"; - static version = "1.1.0"; - static about () { return log("", `🟧 ${this.name} v${this.version}`, "") }; - static data = null; - static dataFile = 'box.dat'; - static #nameRegex = /^@(?[^.]+)(?:\.(?.*))?$/; - - static getItem(keyName = new String, defaultValue = null) { - let keyValue = defaultValue; - // 如果以 @ - switch (keyName.startsWith('@')) { - case true: - const { key, path } = keyName.match(this.#nameRegex)?.groups; - //log(`1: ${key}, ${path}`); - keyName = key; - let value = this.getItem(keyName, {}); - //log(`2: ${JSON.stringify(value)}`) - if (typeof value !== "object") value = {}; - //log(`3: ${JSON.stringify(value)}`) - keyValue = Lodash.get(value, path); - //log(`4: ${JSON.stringify(keyValue)}`) - try { - keyValue = JSON.parse(keyValue); - } catch (e) { - // do nothing - } //log(`5: ${JSON.stringify(keyValue)}`) - break; - default: - switch ($platform) { - case 'Surge': - case 'Loon': - case 'Stash': - case 'Egern': - case 'Shadowrocket': - keyValue = $persistentStore.read(keyName); - break; - case 'Quantumult X': - keyValue = $prefs.valueForKey(keyName); - break; - case 'Node.js': - this.data = this.#loaddata(this.dataFile); - keyValue = this.data?.[keyName]; - break; - default: - keyValue = this.data?.[keyName] || null; - break; - } try { - keyValue = JSON.parse(keyValue); - } catch (e) { - // do nothing - } break; - } return keyValue ?? defaultValue; - }; - - static setItem(keyName = new String, keyValue = new String) { - let result = false; - //log(`0: ${typeof keyValue}`); - switch (typeof keyValue) { - case "object": - keyValue = JSON.stringify(keyValue); - break; - default: - keyValue = String(keyValue); - break; - } switch (keyName.startsWith('@')) { - case true: - const { key, path } = keyName.match(this.#nameRegex)?.groups; - //log(`1: ${key}, ${path}`); - keyName = key; - let value = this.getItem(keyName, {}); - //log(`2: ${JSON.stringify(value)}`) - if (typeof value !== "object") value = {}; - //log(`3: ${JSON.stringify(value)}`) - Lodash.set(value, path, keyValue); - //log(`4: ${JSON.stringify(value)}`) - result = this.setItem(keyName, value); - //log(`5: ${result}`) - break; - default: - switch ($platform) { - case 'Surge': - case 'Loon': - case 'Stash': - case 'Egern': - case 'Shadowrocket': - result = $persistentStore.write(keyValue, keyName); - break; - case 'Quantumult X': - result =$prefs.setValueForKey(keyValue, keyName); - break; - case 'Node.js': - this.data = this.#loaddata(this.dataFile); - this.data[keyName] = keyValue; - this.#writedata(this.dataFile); - result = true; - break; - default: - result = this.data?.[keyName] || null; - break; - } break; - } return result; - }; - - static removeItem(keyName){ - let result = false; - switch (keyName.startsWith('@')) { - case true: - const { key, path } = keyName.match(this.#nameRegex)?.groups; - keyName = key; - let value = this.getItem(keyName); - if (typeof value !== "object") value = {}; - keyValue = Lodash.unset(value, path); - result = this.setItem(keyName, value); - break; - default: - switch ($platform) { - case 'Surge': - case 'Loon': - case 'Stash': - case 'Egern': - case 'Shadowrocket': - result = false; - break; - case 'Quantumult X': - result = $prefs.removeValueForKey(keyName); - break; - case 'Node.js': - result = false; - break; - default: - result = false; - break; - } break; - } return result; - } - - static clear() { - let result = false; - switch ($platform) { - case 'Surge': - case 'Loon': - case 'Stash': - case 'Egern': - case 'Shadowrocket': - result = false; - break; - case 'Quantumult X': - result = $prefs.removeAllValues(); - break; - case 'Node.js': - result = false; - break; - default: - result = false; - break; - } return result; - } - - static #loaddata(dataFile) { - if (this.isNode()) { - this.fs = this.fs ? this.fs : require('fs'); - this.path = this.path ? this.path : require('path'); - const curDirDataFilePath = this.path.resolve(dataFile); - const rootDirDataFilePath = this.path.resolve( - process.cwd(), - dataFile - ); - const isCurDirDataFile = this.fs.existsSync(curDirDataFilePath); - const isRootDirDataFile = - !isCurDirDataFile && this.fs.existsSync(rootDirDataFilePath); - if (isCurDirDataFile || isRootDirDataFile) { - const datPath = isCurDirDataFile - ? curDirDataFilePath - : rootDirDataFilePath; - try { - return JSON.parse(this.fs.readFileSync(datPath)) - } catch (e) { - return {} - } - } else return {} - } else return {} - } - - static #writedata(dataFile = this.dataFile) { - if (this.isNode()) { - this.fs = this.fs ? this.fs : require('fs'); - this.path = this.path ? this.path : require('path'); - const curDirDataFilePath = this.path.resolve(dataFile); - const rootDirDataFilePath = this.path.resolve( - process.cwd(), - dataFile - ); - const isCurDirDataFile = this.fs.existsSync(curDirDataFilePath); - const isRootDirDataFile = - !isCurDirDataFile && this.fs.existsSync(rootDirDataFilePath); - const jsondata = JSON.stringify(this.data); - if (isCurDirDataFile) { - this.fs.writeFileSync(curDirDataFilePath, jsondata); - } else if (isRootDirDataFile) { - this.fs.writeFileSync(rootDirDataFilePath, jsondata); - } else { - this.fs.writeFileSync(curDirDataFilePath, jsondata); - } - } - }; - -} - -function logError(error) { - switch ($platform) { - case "Surge": - case "Loon": - case "Stash": - case "Egern": - case "Shadowrocket": - case "Quantumult X": - default: - log("", `❗️执行错误!`, error, ""); - break - case "Node.js": - log("", `❗️执行错误!`, error.stack, ""); - break - }} - -function done(object = {}) { - switch ($platform) { - case "Surge": - if (object.policy) Lodash.set(object, "headers.X-Surge-Policy", object.policy); - log("", `🚩 执行结束! 🕛 ${(new Date().getTime() / 1000 - $script.startTime)} 秒`, ""); - $done(object); - break; - case "Loon": - if (object.policy) object.node = object.policy; - log("", `🚩 执行结束! 🕛 ${(new Date() - $script.startTime) / 1000} 秒`, ""); - $done(object); - break; - case "Stash": - if (object.policy) Lodash.set(object, "headers.X-Stash-Selected-Proxy", encodeURI(object.policy)); - log("", `🚩 执行结束! 🕛 ${(new Date() - $script.startTime) / 1000} 秒`, ""); - $done(object); - break; - case "Egern": - log("", `🚩 执行结束!`, ""); - $done(object); - break; - case "Shadowrocket": - default: - log("", `🚩 执行结束!`, ""); - $done(object); - break; - case "Quantumult X": - if (object.policy) Lodash.set(object, "opts.policy", object.policy); - // 移除不可写字段 - delete object["auto-redirect"]; - delete object["auto-cookie"]; - delete object["binary-mode"]; - delete object.charset; - delete object.host; - delete object.insecure; - delete object.method; // 1.4.x 不可写 - delete object.opt; // $task.fetch() 参数, 不可写 - delete object.path; // 可写, 但会与 url 冲突 - delete object.policy; - delete object["policy-descriptor"]; - delete object.scheme; - delete object.sessionIndex; - delete object.statusCode; - delete object.timeout; - if (object.body instanceof ArrayBuffer) { - object.bodyBytes = object.body; - delete object.body; - } else if (ArrayBuffer.isView(object.body)) { - object.bodyBytes = object.body.buffer.slice(object.body.byteOffset, object.body.byteLength + object.body.byteOffset); - delete object.body; - } else if (object.body) delete object.bodyBytes; - log("", `🚩 执行结束!`, ""); - $done(object); - break; - case "Node.js": - log("", `🚩 执行结束!`, ""); - process.exit(1); - break; - } -} - -const log = (...logs) => console.log(logs.join("\n")); - -/*! pako 2.1.0 https://github.com/nodeca/pako @license (MIT AND Zlib) */ -// (C) 1995-2013 Jean-loup Gailly and Mark Adler -// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin -// -// This software is provided 'as-is', without any express or implied -// warranty. In no event will the authors be held liable for any damages -// arising from the use of this software. -// -// Permission is granted to anyone to use this software for any purpose, -// including commercial applications, and to alter it and redistribute it -// freely, subject to the following restrictions: -// -// 1. The origin of this software must not be misrepresented; you must not -// claim that you wrote the original software. If you use this software -// in a product, an acknowledgment in the product documentation would be -// appreciated but is not required. -// 2. Altered source versions must be plainly marked as such, and must not be -// misrepresented as being the original software. -// 3. This notice may not be removed or altered from any source distribution. - -/* eslint-disable space-unary-ops */ - -/* Public constants ==========================================================*/ -/* ===========================================================================*/ - - -//const Z_FILTERED = 1; -//const Z_HUFFMAN_ONLY = 2; -//const Z_RLE = 3; -const Z_FIXED$1 = 4; -//const Z_DEFAULT_STRATEGY = 0; - -/* Possible values of the data_type field (though see inflate()) */ -const Z_BINARY = 0; -const Z_TEXT = 1; -//const Z_ASCII = 1; // = Z_TEXT -const Z_UNKNOWN$1 = 2; - -/*============================================================================*/ - - -function zero$1(buf) { let len = buf.length; while (--len >= 0) { buf[len] = 0; } } - -// From zutil.h - -const STORED_BLOCK = 0; -const STATIC_TREES = 1; -const DYN_TREES = 2; -/* The three kinds of block type */ - -const MIN_MATCH$1 = 3; -const MAX_MATCH$1 = 258; -/* The minimum and maximum match lengths */ - -// From deflate.h -/* =========================================================================== - * Internal compression state. - */ - -const LENGTH_CODES$1 = 29; -/* number of length codes, not counting the special END_BLOCK code */ - -const LITERALS$1 = 256; -/* number of literal bytes 0..255 */ - -const L_CODES$1 = LITERALS$1 + 1 + LENGTH_CODES$1; -/* number of Literal or Length codes, including the END_BLOCK code */ - -const D_CODES$1 = 30; -/* number of distance codes */ - -const BL_CODES$1 = 19; -/* number of codes used to transfer the bit lengths */ - -const HEAP_SIZE$1 = 2 * L_CODES$1 + 1; -/* maximum heap size */ - -const MAX_BITS$1 = 15; -/* All codes must not exceed MAX_BITS bits */ - -const Buf_size = 16; -/* size of bit buffer in bi_buf */ - - -/* =========================================================================== - * Constants - */ - -const MAX_BL_BITS = 7; -/* Bit length codes must not exceed MAX_BL_BITS bits */ - -const END_BLOCK = 256; -/* end of block literal code */ - -const REP_3_6 = 16; -/* repeat previous bit length 3-6 times (2 bits of repeat count) */ - -const REPZ_3_10 = 17; -/* repeat a zero length 3-10 times (3 bits of repeat count) */ - -const REPZ_11_138 = 18; -/* repeat a zero length 11-138 times (7 bits of repeat count) */ - -/* eslint-disable comma-spacing,array-bracket-spacing */ -const extra_lbits = /* extra bits for each length code */ - new Uint8Array([0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0]); - -const extra_dbits = /* extra bits for each distance code */ - new Uint8Array([0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13]); - -const extra_blbits = /* extra bits for each bit length code */ - new Uint8Array([0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,3,7]); - -const bl_order = - new Uint8Array([16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15]); -/* eslint-enable comma-spacing,array-bracket-spacing */ - -/* The lengths of the bit length codes are sent in order of decreasing - * probability, to avoid transmitting the lengths for unused bit length codes. - */ - -/* =========================================================================== - * Local data. These are initialized only once. - */ - -// We pre-fill arrays with 0 to avoid uninitialized gaps - -const DIST_CODE_LEN = 512; /* see definition of array dist_code below */ - -// !!!! Use flat array instead of structure, Freq = i*2, Len = i*2+1 -const static_ltree = new Array((L_CODES$1 + 2) * 2); -zero$1(static_ltree); -/* The static literal tree. Since the bit lengths are imposed, there is no - * need for the L_CODES extra codes used during heap construction. However - * The codes 286 and 287 are needed to build a canonical tree (see _tr_init - * below). - */ - -const static_dtree = new Array(D_CODES$1 * 2); -zero$1(static_dtree); -/* The static distance tree. (Actually a trivial tree since all codes use - * 5 bits.) - */ - -const _dist_code = new Array(DIST_CODE_LEN); -zero$1(_dist_code); -/* Distance codes. The first 256 values correspond to the distances - * 3 .. 258, the last 256 values correspond to the top 8 bits of - * the 15 bit distances. - */ - -const _length_code = new Array(MAX_MATCH$1 - MIN_MATCH$1 + 1); -zero$1(_length_code); -/* length code for each normalized match length (0 == MIN_MATCH) */ - -const base_length = new Array(LENGTH_CODES$1); -zero$1(base_length); -/* First normalized length for each code (0 = MIN_MATCH) */ - -const base_dist = new Array(D_CODES$1); -zero$1(base_dist); -/* First normalized distance for each code (0 = distance of 1) */ - - -function StaticTreeDesc(static_tree, extra_bits, extra_base, elems, max_length) { - - this.static_tree = static_tree; /* static tree or NULL */ - this.extra_bits = extra_bits; /* extra bits for each code or NULL */ - this.extra_base = extra_base; /* base index for extra_bits */ - this.elems = elems; /* max number of elements in the tree */ - this.max_length = max_length; /* max bit length for the codes */ - - // show if `static_tree` has data or dummy - needed for monomorphic objects - this.has_stree = static_tree && static_tree.length; -} - - -let static_l_desc; -let static_d_desc; -let static_bl_desc; - - -function TreeDesc(dyn_tree, stat_desc) { - this.dyn_tree = dyn_tree; /* the dynamic tree */ - this.max_code = 0; /* largest code with non zero frequency */ - this.stat_desc = stat_desc; /* the corresponding static tree */ -} - - - -const d_code = (dist) => { - - return dist < 256 ? _dist_code[dist] : _dist_code[256 + (dist >>> 7)]; -}; - - -/* =========================================================================== - * Output a short LSB first on the stream. - * IN assertion: there is enough room in pendingBuf. - */ -const put_short = (s, w) => { -// put_byte(s, (uch)((w) & 0xff)); -// put_byte(s, (uch)((ush)(w) >> 8)); - s.pending_buf[s.pending++] = (w) & 0xff; - s.pending_buf[s.pending++] = (w >>> 8) & 0xff; -}; - - -/* =========================================================================== - * Send a value on a given number of bits. - * IN assertion: length <= 16 and value fits in length bits. - */ -const send_bits = (s, value, length) => { - - if (s.bi_valid > (Buf_size - length)) { - s.bi_buf |= (value << s.bi_valid) & 0xffff; - put_short(s, s.bi_buf); - s.bi_buf = value >> (Buf_size - s.bi_valid); - s.bi_valid += length - Buf_size; - } else { - s.bi_buf |= (value << s.bi_valid) & 0xffff; - s.bi_valid += length; - } -}; - - -const send_code = (s, c, tree) => { - - send_bits(s, tree[c * 2]/*.Code*/, tree[c * 2 + 1]/*.Len*/); -}; - - -/* =========================================================================== - * Reverse the first len bits of a code, using straightforward code (a faster - * method would use a table) - * IN assertion: 1 <= len <= 15 - */ -const bi_reverse = (code, len) => { - - let res = 0; - do { - res |= code & 1; - code >>>= 1; - res <<= 1; - } while (--len > 0); - return res >>> 1; -}; - - -/* =========================================================================== - * Flush the bit buffer, keeping at most 7 bits in it. - */ -const bi_flush = (s) => { - - if (s.bi_valid === 16) { - put_short(s, s.bi_buf); - s.bi_buf = 0; - s.bi_valid = 0; - - } else if (s.bi_valid >= 8) { - s.pending_buf[s.pending++] = s.bi_buf & 0xff; - s.bi_buf >>= 8; - s.bi_valid -= 8; - } -}; - - -/* =========================================================================== - * Compute the optimal bit lengths for a tree and update the total bit length - * for the current block. - * IN assertion: the fields freq and dad are set, heap[heap_max] and - * above are the tree nodes sorted by increasing frequency. - * OUT assertions: the field len is set to the optimal bit length, the - * array bl_count contains the frequencies for each bit length. - * The length opt_len is updated; static_len is also updated if stree is - * not null. - */ -const gen_bitlen = (s, desc) => { -// deflate_state *s; -// tree_desc *desc; /* the tree descriptor */ - - const tree = desc.dyn_tree; - const max_code = desc.max_code; - const stree = desc.stat_desc.static_tree; - const has_stree = desc.stat_desc.has_stree; - const extra = desc.stat_desc.extra_bits; - const base = desc.stat_desc.extra_base; - const max_length = desc.stat_desc.max_length; - let h; /* heap index */ - let n, m; /* iterate over the tree elements */ - let bits; /* bit length */ - let xbits; /* extra bits */ - let f; /* frequency */ - let overflow = 0; /* number of elements with bit length too large */ - - for (bits = 0; bits <= MAX_BITS$1; bits++) { - s.bl_count[bits] = 0; - } - - /* In a first pass, compute the optimal bit lengths (which may - * overflow in the case of the bit length tree). - */ - tree[s.heap[s.heap_max] * 2 + 1]/*.Len*/ = 0; /* root of the heap */ - - for (h = s.heap_max + 1; h < HEAP_SIZE$1; h++) { - n = s.heap[h]; - bits = tree[tree[n * 2 + 1]/*.Dad*/ * 2 + 1]/*.Len*/ + 1; - if (bits > max_length) { - bits = max_length; - overflow++; - } - tree[n * 2 + 1]/*.Len*/ = bits; - /* We overwrite tree[n].Dad which is no longer needed */ - - if (n > max_code) { continue; } /* not a leaf node */ - - s.bl_count[bits]++; - xbits = 0; - if (n >= base) { - xbits = extra[n - base]; - } - f = tree[n * 2]/*.Freq*/; - s.opt_len += f * (bits + xbits); - if (has_stree) { - s.static_len += f * (stree[n * 2 + 1]/*.Len*/ + xbits); - } - } - if (overflow === 0) { return; } - - // Tracev((stderr,"\nbit length overflow\n")); - /* This happens for example on obj2 and pic of the Calgary corpus */ - - /* Find the first bit length which could increase: */ - do { - bits = max_length - 1; - while (s.bl_count[bits] === 0) { bits--; } - s.bl_count[bits]--; /* move one leaf down the tree */ - s.bl_count[bits + 1] += 2; /* move one overflow item as its brother */ - s.bl_count[max_length]--; - /* The brother of the overflow item also moves one step up, - * but this does not affect bl_count[max_length] - */ - overflow -= 2; - } while (overflow > 0); - - /* Now recompute all bit lengths, scanning in increasing frequency. - * h is still equal to HEAP_SIZE. (It is simpler to reconstruct all - * lengths instead of fixing only the wrong ones. This idea is taken - * from 'ar' written by Haruhiko Okumura.) - */ - for (bits = max_length; bits !== 0; bits--) { - n = s.bl_count[bits]; - while (n !== 0) { - m = s.heap[--h]; - if (m > max_code) { continue; } - if (tree[m * 2 + 1]/*.Len*/ !== bits) { - // Tracev((stderr,"code %d bits %d->%d\n", m, tree[m].Len, bits)); - s.opt_len += (bits - tree[m * 2 + 1]/*.Len*/) * tree[m * 2]/*.Freq*/; - tree[m * 2 + 1]/*.Len*/ = bits; - } - n--; - } - } -}; - - -/* =========================================================================== - * Generate the codes for a given tree and bit counts (which need not be - * optimal). - * IN assertion: the array bl_count contains the bit length statistics for - * the given tree and the field len is set for all tree elements. - * OUT assertion: the field code is set for all tree elements of non - * zero code length. - */ -const gen_codes = (tree, max_code, bl_count) => { -// ct_data *tree; /* the tree to decorate */ -// int max_code; /* largest code with non zero frequency */ -// ushf *bl_count; /* number of codes at each bit length */ - - const next_code = new Array(MAX_BITS$1 + 1); /* next code value for each bit length */ - let code = 0; /* running code value */ - let bits; /* bit index */ - let n; /* code index */ - - /* The distribution counts are first used to generate the code values - * without bit reversal. - */ - for (bits = 1; bits <= MAX_BITS$1; bits++) { - code = (code + bl_count[bits - 1]) << 1; - next_code[bits] = code; - } - /* Check that the bit counts in bl_count are consistent. The last code - * must be all ones. - */ - //Assert (code + bl_count[MAX_BITS]-1 == (1< { - - let n; /* iterates over tree elements */ - let bits; /* bit counter */ - let length; /* length value */ - let code; /* code value */ - let dist; /* distance index */ - const bl_count = new Array(MAX_BITS$1 + 1); - /* number of codes at each bit length for an optimal tree */ - - // do check in _tr_init() - //if (static_init_done) return; - - /* For some embedded targets, global variables are not initialized: */ -/*#ifdef NO_INIT_GLOBAL_POINTERS - static_l_desc.static_tree = static_ltree; - static_l_desc.extra_bits = extra_lbits; - static_d_desc.static_tree = static_dtree; - static_d_desc.extra_bits = extra_dbits; - static_bl_desc.extra_bits = extra_blbits; -#endif*/ - - /* Initialize the mapping length (0..255) -> length code (0..28) */ - length = 0; - for (code = 0; code < LENGTH_CODES$1 - 1; code++) { - base_length[code] = length; - for (n = 0; n < (1 << extra_lbits[code]); n++) { - _length_code[length++] = code; - } - } - //Assert (length == 256, "tr_static_init: length != 256"); - /* Note that the length 255 (match length 258) can be represented - * in two different ways: code 284 + 5 bits or code 285, so we - * overwrite length_code[255] to use the best encoding: - */ - _length_code[length - 1] = code; - - /* Initialize the mapping dist (0..32K) -> dist code (0..29) */ - dist = 0; - for (code = 0; code < 16; code++) { - base_dist[code] = dist; - for (n = 0; n < (1 << extra_dbits[code]); n++) { - _dist_code[dist++] = code; - } - } - //Assert (dist == 256, "tr_static_init: dist != 256"); - dist >>= 7; /* from now on, all distances are divided by 128 */ - for (; code < D_CODES$1; code++) { - base_dist[code] = dist << 7; - for (n = 0; n < (1 << (extra_dbits[code] - 7)); n++) { - _dist_code[256 + dist++] = code; - } - } - //Assert (dist == 256, "tr_static_init: 256+dist != 512"); - - /* Construct the codes of the static literal tree */ - for (bits = 0; bits <= MAX_BITS$1; bits++) { - bl_count[bits] = 0; - } - - n = 0; - while (n <= 143) { - static_ltree[n * 2 + 1]/*.Len*/ = 8; - n++; - bl_count[8]++; - } - while (n <= 255) { - static_ltree[n * 2 + 1]/*.Len*/ = 9; - n++; - bl_count[9]++; - } - while (n <= 279) { - static_ltree[n * 2 + 1]/*.Len*/ = 7; - n++; - bl_count[7]++; - } - while (n <= 287) { - static_ltree[n * 2 + 1]/*.Len*/ = 8; - n++; - bl_count[8]++; - } - /* Codes 286 and 287 do not exist, but we must include them in the - * tree construction to get a canonical Huffman tree (longest code - * all ones) - */ - gen_codes(static_ltree, L_CODES$1 + 1, bl_count); - - /* The static distance tree is trivial: */ - for (n = 0; n < D_CODES$1; n++) { - static_dtree[n * 2 + 1]/*.Len*/ = 5; - static_dtree[n * 2]/*.Code*/ = bi_reverse(n, 5); - } - - // Now data ready and we can init static trees - static_l_desc = new StaticTreeDesc(static_ltree, extra_lbits, LITERALS$1 + 1, L_CODES$1, MAX_BITS$1); - static_d_desc = new StaticTreeDesc(static_dtree, extra_dbits, 0, D_CODES$1, MAX_BITS$1); - static_bl_desc = new StaticTreeDesc(new Array(0), extra_blbits, 0, BL_CODES$1, MAX_BL_BITS); - - //static_init_done = true; -}; - - -/* =========================================================================== - * Initialize a new block. - */ -const init_block = (s) => { - - let n; /* iterates over tree elements */ - - /* Initialize the trees. */ - for (n = 0; n < L_CODES$1; n++) { s.dyn_ltree[n * 2]/*.Freq*/ = 0; } - for (n = 0; n < D_CODES$1; n++) { s.dyn_dtree[n * 2]/*.Freq*/ = 0; } - for (n = 0; n < BL_CODES$1; n++) { s.bl_tree[n * 2]/*.Freq*/ = 0; } - - s.dyn_ltree[END_BLOCK * 2]/*.Freq*/ = 1; - s.opt_len = s.static_len = 0; - s.sym_next = s.matches = 0; -}; - - -/* =========================================================================== - * Flush the bit buffer and align the output on a byte boundary - */ -const bi_windup = (s) => -{ - if (s.bi_valid > 8) { - put_short(s, s.bi_buf); - } else if (s.bi_valid > 0) { - //put_byte(s, (Byte)s->bi_buf); - s.pending_buf[s.pending++] = s.bi_buf; - } - s.bi_buf = 0; - s.bi_valid = 0; -}; - -/* =========================================================================== - * Compares to subtrees, using the tree depth as tie breaker when - * the subtrees have equal frequency. This minimizes the worst case length. - */ -const smaller = (tree, n, m, depth) => { - - const _n2 = n * 2; - const _m2 = m * 2; - return (tree[_n2]/*.Freq*/ < tree[_m2]/*.Freq*/ || - (tree[_n2]/*.Freq*/ === tree[_m2]/*.Freq*/ && depth[n] <= depth[m])); -}; - -/* =========================================================================== - * Restore the heap property by moving down the tree starting at node k, - * exchanging a node with the smallest of its two sons if necessary, stopping - * when the heap property is re-established (each father smaller than its - * two sons). - */ -const pqdownheap = (s, tree, k) => { -// deflate_state *s; -// ct_data *tree; /* the tree to restore */ -// int k; /* node to move down */ - - const v = s.heap[k]; - let j = k << 1; /* left son of k */ - while (j <= s.heap_len) { - /* Set j to the smallest of the two sons: */ - if (j < s.heap_len && - smaller(tree, s.heap[j + 1], s.heap[j], s.depth)) { - j++; - } - /* Exit if v is smaller than both sons */ - if (smaller(tree, v, s.heap[j], s.depth)) { break; } - - /* Exchange v with the smallest son */ - s.heap[k] = s.heap[j]; - k = j; - - /* And continue down the tree, setting j to the left son of k */ - j <<= 1; - } - s.heap[k] = v; -}; - - -// inlined manually -// const SMALLEST = 1; - -/* =========================================================================== - * Send the block data compressed using the given Huffman trees - */ -const compress_block = (s, ltree, dtree) => { -// deflate_state *s; -// const ct_data *ltree; /* literal tree */ -// const ct_data *dtree; /* distance tree */ - - let dist; /* distance of matched string */ - let lc; /* match length or unmatched char (if dist == 0) */ - let sx = 0; /* running index in sym_buf */ - let code; /* the code to send */ - let extra; /* number of extra bits to send */ - - if (s.sym_next !== 0) { - do { - dist = s.pending_buf[s.sym_buf + sx++] & 0xff; - dist += (s.pending_buf[s.sym_buf + sx++] & 0xff) << 8; - lc = s.pending_buf[s.sym_buf + sx++]; - if (dist === 0) { - send_code(s, lc, ltree); /* send a literal byte */ - //Tracecv(isgraph(lc), (stderr," '%c' ", lc)); - } else { - /* Here, lc is the match length - MIN_MATCH */ - code = _length_code[lc]; - send_code(s, code + LITERALS$1 + 1, ltree); /* send the length code */ - extra = extra_lbits[code]; - if (extra !== 0) { - lc -= base_length[code]; - send_bits(s, lc, extra); /* send the extra length bits */ - } - dist--; /* dist is now the match distance - 1 */ - code = d_code(dist); - //Assert (code < D_CODES, "bad d_code"); - - send_code(s, code, dtree); /* send the distance code */ - extra = extra_dbits[code]; - if (extra !== 0) { - dist -= base_dist[code]; - send_bits(s, dist, extra); /* send the extra distance bits */ - } - } /* literal or match pair ? */ - - /* Check that the overlay between pending_buf and sym_buf is ok: */ - //Assert(s->pending < s->lit_bufsize + sx, "pendingBuf overflow"); - - } while (sx < s.sym_next); - } - - send_code(s, END_BLOCK, ltree); -}; - - -/* =========================================================================== - * Construct one Huffman tree and assigns the code bit strings and lengths. - * Update the total bit length for the current block. - * IN assertion: the field freq is set for all tree elements. - * OUT assertions: the fields len and code are set to the optimal bit length - * and corresponding code. The length opt_len is updated; static_len is - * also updated if stree is not null. The field max_code is set. - */ -const build_tree = (s, desc) => { -// deflate_state *s; -// tree_desc *desc; /* the tree descriptor */ - - const tree = desc.dyn_tree; - const stree = desc.stat_desc.static_tree; - const has_stree = desc.stat_desc.has_stree; - const elems = desc.stat_desc.elems; - let n, m; /* iterate over heap elements */ - let max_code = -1; /* largest code with non zero frequency */ - let node; /* new node being created */ - - /* Construct the initial heap, with least frequent element in - * heap[SMALLEST]. The sons of heap[n] are heap[2*n] and heap[2*n+1]. - * heap[0] is not used. - */ - s.heap_len = 0; - s.heap_max = HEAP_SIZE$1; - - for (n = 0; n < elems; n++) { - if (tree[n * 2]/*.Freq*/ !== 0) { - s.heap[++s.heap_len] = max_code = n; - s.depth[n] = 0; - - } else { - tree[n * 2 + 1]/*.Len*/ = 0; - } - } - - /* The pkzip format requires that at least one distance code exists, - * and that at least one bit should be sent even if there is only one - * possible code. So to avoid special checks later on we force at least - * two codes of non zero frequency. - */ - while (s.heap_len < 2) { - node = s.heap[++s.heap_len] = (max_code < 2 ? ++max_code : 0); - tree[node * 2]/*.Freq*/ = 1; - s.depth[node] = 0; - s.opt_len--; - - if (has_stree) { - s.static_len -= stree[node * 2 + 1]/*.Len*/; - } - /* node is 0 or 1 so it does not have extra bits */ - } - desc.max_code = max_code; - - /* The elements heap[heap_len/2+1 .. heap_len] are leaves of the tree, - * establish sub-heaps of increasing lengths: - */ - for (n = (s.heap_len >> 1/*int /2*/); n >= 1; n--) { pqdownheap(s, tree, n); } - - /* Construct the Huffman tree by repeatedly combining the least two - * frequent nodes. - */ - node = elems; /* next internal node of the tree */ - do { - //pqremove(s, tree, n); /* n = node of least frequency */ - /*** pqremove ***/ - n = s.heap[1/*SMALLEST*/]; - s.heap[1/*SMALLEST*/] = s.heap[s.heap_len--]; - pqdownheap(s, tree, 1/*SMALLEST*/); - /***/ - - m = s.heap[1/*SMALLEST*/]; /* m = node of next least frequency */ - - s.heap[--s.heap_max] = n; /* keep the nodes sorted by frequency */ - s.heap[--s.heap_max] = m; - - /* Create a new node father of n and m */ - tree[node * 2]/*.Freq*/ = tree[n * 2]/*.Freq*/ + tree[m * 2]/*.Freq*/; - s.depth[node] = (s.depth[n] >= s.depth[m] ? s.depth[n] : s.depth[m]) + 1; - tree[n * 2 + 1]/*.Dad*/ = tree[m * 2 + 1]/*.Dad*/ = node; - - /* and insert the new node in the heap */ - s.heap[1/*SMALLEST*/] = node++; - pqdownheap(s, tree, 1/*SMALLEST*/); - - } while (s.heap_len >= 2); - - s.heap[--s.heap_max] = s.heap[1/*SMALLEST*/]; - - /* At this point, the fields freq and dad are set. We can now - * generate the bit lengths. - */ - gen_bitlen(s, desc); - - /* The field len is now set, we can generate the bit codes */ - gen_codes(tree, max_code, s.bl_count); -}; - - -/* =========================================================================== - * Scan a literal or distance tree to determine the frequencies of the codes - * in the bit length tree. - */ -const scan_tree = (s, tree, max_code) => { -// deflate_state *s; -// ct_data *tree; /* the tree to be scanned */ -// int max_code; /* and its largest code of non zero frequency */ - - let n; /* iterates over all tree elements */ - let prevlen = -1; /* last emitted length */ - let curlen; /* length of current code */ - - let nextlen = tree[0 * 2 + 1]/*.Len*/; /* length of next code */ - - let count = 0; /* repeat count of the current code */ - let max_count = 7; /* max repeat count */ - let min_count = 4; /* min repeat count */ - - if (nextlen === 0) { - max_count = 138; - min_count = 3; - } - tree[(max_code + 1) * 2 + 1]/*.Len*/ = 0xffff; /* guard */ - - for (n = 0; n <= max_code; n++) { - curlen = nextlen; - nextlen = tree[(n + 1) * 2 + 1]/*.Len*/; - - if (++count < max_count && curlen === nextlen) { - continue; - - } else if (count < min_count) { - s.bl_tree[curlen * 2]/*.Freq*/ += count; - - } else if (curlen !== 0) { - - if (curlen !== prevlen) { s.bl_tree[curlen * 2]/*.Freq*/++; } - s.bl_tree[REP_3_6 * 2]/*.Freq*/++; - - } else if (count <= 10) { - s.bl_tree[REPZ_3_10 * 2]/*.Freq*/++; - - } else { - s.bl_tree[REPZ_11_138 * 2]/*.Freq*/++; - } - - count = 0; - prevlen = curlen; - - if (nextlen === 0) { - max_count = 138; - min_count = 3; - - } else if (curlen === nextlen) { - max_count = 6; - min_count = 3; - - } else { - max_count = 7; - min_count = 4; - } - } -}; - - -/* =========================================================================== - * Send a literal or distance tree in compressed form, using the codes in - * bl_tree. - */ -const send_tree = (s, tree, max_code) => { -// deflate_state *s; -// ct_data *tree; /* the tree to be scanned */ -// int max_code; /* and its largest code of non zero frequency */ - - let n; /* iterates over all tree elements */ - let prevlen = -1; /* last emitted length */ - let curlen; /* length of current code */ - - let nextlen = tree[0 * 2 + 1]/*.Len*/; /* length of next code */ - - let count = 0; /* repeat count of the current code */ - let max_count = 7; /* max repeat count */ - let min_count = 4; /* min repeat count */ - - /* tree[max_code+1].Len = -1; */ /* guard already set */ - if (nextlen === 0) { - max_count = 138; - min_count = 3; - } - - for (n = 0; n <= max_code; n++) { - curlen = nextlen; - nextlen = tree[(n + 1) * 2 + 1]/*.Len*/; - - if (++count < max_count && curlen === nextlen) { - continue; - - } else if (count < min_count) { - do { send_code(s, curlen, s.bl_tree); } while (--count !== 0); - - } else if (curlen !== 0) { - if (curlen !== prevlen) { - send_code(s, curlen, s.bl_tree); - count--; - } - //Assert(count >= 3 && count <= 6, " 3_6?"); - send_code(s, REP_3_6, s.bl_tree); - send_bits(s, count - 3, 2); - - } else if (count <= 10) { - send_code(s, REPZ_3_10, s.bl_tree); - send_bits(s, count - 3, 3); - - } else { - send_code(s, REPZ_11_138, s.bl_tree); - send_bits(s, count - 11, 7); - } - - count = 0; - prevlen = curlen; - if (nextlen === 0) { - max_count = 138; - min_count = 3; - - } else if (curlen === nextlen) { - max_count = 6; - min_count = 3; - - } else { - max_count = 7; - min_count = 4; - } - } -}; - - -/* =========================================================================== - * Construct the Huffman tree for the bit lengths and return the index in - * bl_order of the last bit length code to send. - */ -const build_bl_tree = (s) => { - - let max_blindex; /* index of last bit length code of non zero freq */ - - /* Determine the bit length frequencies for literal and distance trees */ - scan_tree(s, s.dyn_ltree, s.l_desc.max_code); - scan_tree(s, s.dyn_dtree, s.d_desc.max_code); - - /* Build the bit length tree: */ - build_tree(s, s.bl_desc); - /* opt_len now includes the length of the tree representations, except - * the lengths of the bit lengths codes and the 5+5+4 bits for the counts. - */ - - /* Determine the number of bit length codes to send. The pkzip format - * requires that at least 4 bit length codes be sent. (appnote.txt says - * 3 but the actual value used is 4.) - */ - for (max_blindex = BL_CODES$1 - 1; max_blindex >= 3; max_blindex--) { - if (s.bl_tree[bl_order[max_blindex] * 2 + 1]/*.Len*/ !== 0) { - break; - } - } - /* Update opt_len to include the bit length tree and counts */ - s.opt_len += 3 * (max_blindex + 1) + 5 + 5 + 4; - //Tracev((stderr, "\ndyn trees: dyn %ld, stat %ld", - // s->opt_len, s->static_len)); - - return max_blindex; -}; - - -/* =========================================================================== - * Send the header for a block using dynamic Huffman trees: the counts, the - * lengths of the bit length codes, the literal tree and the distance tree. - * IN assertion: lcodes >= 257, dcodes >= 1, blcodes >= 4. - */ -const send_all_trees = (s, lcodes, dcodes, blcodes) => { -// deflate_state *s; -// int lcodes, dcodes, blcodes; /* number of codes for each tree */ - - let rank; /* index in bl_order */ - - //Assert (lcodes >= 257 && dcodes >= 1 && blcodes >= 4, "not enough codes"); - //Assert (lcodes <= L_CODES && dcodes <= D_CODES && blcodes <= BL_CODES, - // "too many codes"); - //Tracev((stderr, "\nbl counts: ")); - send_bits(s, lcodes - 257, 5); /* not +255 as stated in appnote.txt */ - send_bits(s, dcodes - 1, 5); - send_bits(s, blcodes - 4, 4); /* not -3 as stated in appnote.txt */ - for (rank = 0; rank < blcodes; rank++) { - //Tracev((stderr, "\nbl code %2d ", bl_order[rank])); - send_bits(s, s.bl_tree[bl_order[rank] * 2 + 1]/*.Len*/, 3); - } - //Tracev((stderr, "\nbl tree: sent %ld", s->bits_sent)); - - send_tree(s, s.dyn_ltree, lcodes - 1); /* literal tree */ - //Tracev((stderr, "\nlit tree: sent %ld", s->bits_sent)); - - send_tree(s, s.dyn_dtree, dcodes - 1); /* distance tree */ - //Tracev((stderr, "\ndist tree: sent %ld", s->bits_sent)); -}; - - -/* =========================================================================== - * Check if the data type is TEXT or BINARY, using the following algorithm: - * - TEXT if the two conditions below are satisfied: - * a) There are no non-portable control characters belonging to the - * "block list" (0..6, 14..25, 28..31). - * b) There is at least one printable character belonging to the - * "allow list" (9 {TAB}, 10 {LF}, 13 {CR}, 32..255). - * - BINARY otherwise. - * - The following partially-portable control characters form a - * "gray list" that is ignored in this detection algorithm: - * (7 {BEL}, 8 {BS}, 11 {VT}, 12 {FF}, 26 {SUB}, 27 {ESC}). - * IN assertion: the fields Freq of dyn_ltree are set. - */ -const detect_data_type = (s) => { - /* block_mask is the bit mask of block-listed bytes - * set bits 0..6, 14..25, and 28..31 - * 0xf3ffc07f = binary 11110011111111111100000001111111 - */ - let block_mask = 0xf3ffc07f; - let n; - - /* Check for non-textual ("block-listed") bytes. */ - for (n = 0; n <= 31; n++, block_mask >>>= 1) { - if ((block_mask & 1) && (s.dyn_ltree[n * 2]/*.Freq*/ !== 0)) { - return Z_BINARY; - } - } - - /* Check for textual ("allow-listed") bytes. */ - if (s.dyn_ltree[9 * 2]/*.Freq*/ !== 0 || s.dyn_ltree[10 * 2]/*.Freq*/ !== 0 || - s.dyn_ltree[13 * 2]/*.Freq*/ !== 0) { - return Z_TEXT; - } - for (n = 32; n < LITERALS$1; n++) { - if (s.dyn_ltree[n * 2]/*.Freq*/ !== 0) { - return Z_TEXT; - } - } - - /* There are no "block-listed" or "allow-listed" bytes: - * this stream either is empty or has tolerated ("gray-listed") bytes only. - */ - return Z_BINARY; -}; - - -let static_init_done = false; - -/* =========================================================================== - * Initialize the tree data structures for a new zlib stream. - */ -const _tr_init$1 = (s) => -{ - - if (!static_init_done) { - tr_static_init(); - static_init_done = true; - } - - s.l_desc = new TreeDesc(s.dyn_ltree, static_l_desc); - s.d_desc = new TreeDesc(s.dyn_dtree, static_d_desc); - s.bl_desc = new TreeDesc(s.bl_tree, static_bl_desc); - - s.bi_buf = 0; - s.bi_valid = 0; - - /* Initialize the first block of the first file: */ - init_block(s); -}; - - -/* =========================================================================== - * Send a stored block - */ -const _tr_stored_block$1 = (s, buf, stored_len, last) => { -//DeflateState *s; -//charf *buf; /* input block */ -//ulg stored_len; /* length of input block */ -//int last; /* one if this is the last block for a file */ - - send_bits(s, (STORED_BLOCK << 1) + (last ? 1 : 0), 3); /* send block type */ - bi_windup(s); /* align on byte boundary */ - put_short(s, stored_len); - put_short(s, ~stored_len); - if (stored_len) { - s.pending_buf.set(s.window.subarray(buf, buf + stored_len), s.pending); - } - s.pending += stored_len; -}; - - -/* =========================================================================== - * Send one empty static block to give enough lookahead for inflate. - * This takes 10 bits, of which 7 may remain in the bit buffer. - */ -const _tr_align$1 = (s) => { - send_bits(s, STATIC_TREES << 1, 3); - send_code(s, END_BLOCK, static_ltree); - bi_flush(s); -}; - - -/* =========================================================================== - * Determine the best encoding for the current block: dynamic trees, static - * trees or store, and write out the encoded block. - */ -const _tr_flush_block$1 = (s, buf, stored_len, last) => { -//DeflateState *s; -//charf *buf; /* input block, or NULL if too old */ -//ulg stored_len; /* length of input block */ -//int last; /* one if this is the last block for a file */ - - let opt_lenb, static_lenb; /* opt_len and static_len in bytes */ - let max_blindex = 0; /* index of last bit length code of non zero freq */ - - /* Build the Huffman trees unless a stored block is forced */ - if (s.level > 0) { - - /* Check if the file is binary or text */ - if (s.strm.data_type === Z_UNKNOWN$1) { - s.strm.data_type = detect_data_type(s); - } - - /* Construct the literal and distance trees */ - build_tree(s, s.l_desc); - // Tracev((stderr, "\nlit data: dyn %ld, stat %ld", s->opt_len, - // s->static_len)); - - build_tree(s, s.d_desc); - // Tracev((stderr, "\ndist data: dyn %ld, stat %ld", s->opt_len, - // s->static_len)); - /* At this point, opt_len and static_len are the total bit lengths of - * the compressed block data, excluding the tree representations. - */ - - /* Build the bit length tree for the above two trees, and get the index - * in bl_order of the last bit length code to send. - */ - max_blindex = build_bl_tree(s); - - /* Determine the best encoding. Compute the block lengths in bytes. */ - opt_lenb = (s.opt_len + 3 + 7) >>> 3; - static_lenb = (s.static_len + 3 + 7) >>> 3; - - // Tracev((stderr, "\nopt %lu(%lu) stat %lu(%lu) stored %lu lit %u ", - // opt_lenb, s->opt_len, static_lenb, s->static_len, stored_len, - // s->sym_next / 3)); - - if (static_lenb <= opt_lenb) { opt_lenb = static_lenb; } - - } else { - // Assert(buf != (char*)0, "lost buf"); - opt_lenb = static_lenb = stored_len + 5; /* force a stored block */ - } - - if ((stored_len + 4 <= opt_lenb) && (buf !== -1)) { - /* 4: two words for the lengths */ - - /* The test buf != NULL is only necessary if LIT_BUFSIZE > WSIZE. - * Otherwise we can't have processed more than WSIZE input bytes since - * the last block flush, because compression would have been - * successful. If LIT_BUFSIZE <= WSIZE, it is never too late to - * transform a block into a stored block. - */ - _tr_stored_block$1(s, buf, stored_len, last); - - } else if (s.strategy === Z_FIXED$1 || static_lenb === opt_lenb) { - - send_bits(s, (STATIC_TREES << 1) + (last ? 1 : 0), 3); - compress_block(s, static_ltree, static_dtree); - - } else { - send_bits(s, (DYN_TREES << 1) + (last ? 1 : 0), 3); - send_all_trees(s, s.l_desc.max_code + 1, s.d_desc.max_code + 1, max_blindex + 1); - compress_block(s, s.dyn_ltree, s.dyn_dtree); - } - // Assert (s->compressed_len == s->bits_sent, "bad compressed size"); - /* The above check is made mod 2^32, for files larger than 512 MB - * and uLong implemented on 32 bits. - */ - init_block(s); - - if (last) { - bi_windup(s); - } - // Tracev((stderr,"\ncomprlen %lu(%lu) ", s->compressed_len>>3, - // s->compressed_len-7*last)); -}; - -/* =========================================================================== - * Save the match info and tally the frequency counts. Return true if - * the current block must be flushed. - */ -const _tr_tally$1 = (s, dist, lc) => { -// deflate_state *s; -// unsigned dist; /* distance of matched string */ -// unsigned lc; /* match length-MIN_MATCH or unmatched char (if dist==0) */ - - s.pending_buf[s.sym_buf + s.sym_next++] = dist; - s.pending_buf[s.sym_buf + s.sym_next++] = dist >> 8; - s.pending_buf[s.sym_buf + s.sym_next++] = lc; - if (dist === 0) { - /* lc is the unmatched char */ - s.dyn_ltree[lc * 2]/*.Freq*/++; - } else { - s.matches++; - /* Here, lc is the match length - MIN_MATCH */ - dist--; /* dist = match distance - 1 */ - //Assert((ush)dist < (ush)MAX_DIST(s) && - // (ush)lc <= (ush)(MAX_MATCH-MIN_MATCH) && - // (ush)d_code(dist) < (ush)D_CODES, "_tr_tally: bad match"); - - s.dyn_ltree[(_length_code[lc] + LITERALS$1 + 1) * 2]/*.Freq*/++; - s.dyn_dtree[d_code(dist) * 2]/*.Freq*/++; - } - - return (s.sym_next === s.sym_end); -}; - -var _tr_init_1 = _tr_init$1; -var _tr_stored_block_1 = _tr_stored_block$1; -var _tr_flush_block_1 = _tr_flush_block$1; -var _tr_tally_1 = _tr_tally$1; -var _tr_align_1 = _tr_align$1; - -var trees = { - _tr_init: _tr_init_1, - _tr_stored_block: _tr_stored_block_1, - _tr_flush_block: _tr_flush_block_1, - _tr_tally: _tr_tally_1, - _tr_align: _tr_align_1 -}; - -// Note: adler32 takes 12% for level 0 and 2% for level 6. -// It isn't worth it to make additional optimizations as in original. -// Small size is preferable. - -// (C) 1995-2013 Jean-loup Gailly and Mark Adler -// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin -// -// This software is provided 'as-is', without any express or implied -// warranty. In no event will the authors be held liable for any damages -// arising from the use of this software. -// -// Permission is granted to anyone to use this software for any purpose, -// including commercial applications, and to alter it and redistribute it -// freely, subject to the following restrictions: -// -// 1. The origin of this software must not be misrepresented; you must not -// claim that you wrote the original software. If you use this software -// in a product, an acknowledgment in the product documentation would be -// appreciated but is not required. -// 2. Altered source versions must be plainly marked as such, and must not be -// misrepresented as being the original software. -// 3. This notice may not be removed or altered from any source distribution. - -const adler32 = (adler, buf, len, pos) => { - let s1 = (adler & 0xffff) |0, - s2 = ((adler >>> 16) & 0xffff) |0, - n = 0; - - while (len !== 0) { - // Set limit ~ twice less than 5552, to keep - // s2 in 31-bits, because we force signed ints. - // in other case %= will fail. - n = len > 2000 ? 2000 : len; - len -= n; - - do { - s1 = (s1 + buf[pos++]) |0; - s2 = (s2 + s1) |0; - } while (--n); - - s1 %= 65521; - s2 %= 65521; - } - - return (s1 | (s2 << 16)) |0; -}; - - -var adler32_1 = adler32; - -// Note: we can't get significant speed boost here. -// So write code to minimize size - no pregenerated tables -// and array tools dependencies. - -// (C) 1995-2013 Jean-loup Gailly and Mark Adler -// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin -// -// This software is provided 'as-is', without any express or implied -// warranty. In no event will the authors be held liable for any damages -// arising from the use of this software. -// -// Permission is granted to anyone to use this software for any purpose, -// including commercial applications, and to alter it and redistribute it -// freely, subject to the following restrictions: -// -// 1. The origin of this software must not be misrepresented; you must not -// claim that you wrote the original software. If you use this software -// in a product, an acknowledgment in the product documentation would be -// appreciated but is not required. -// 2. Altered source versions must be plainly marked as such, and must not be -// misrepresented as being the original software. -// 3. This notice may not be removed or altered from any source distribution. - -// Use ordinary array, since untyped makes no boost here -const makeTable = () => { - let c, table = []; - - for (var n = 0; n < 256; n++) { - c = n; - for (var k = 0; k < 8; k++) { - c = ((c & 1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1)); - } - table[n] = c; - } - - return table; -}; - -// Create table on load. Just 255 signed longs. Not a problem. -const crcTable = new Uint32Array(makeTable()); - - -const crc32 = (crc, buf, len, pos) => { - const t = crcTable; - const end = pos + len; - - crc ^= -1; - - for (let i = pos; i < end; i++) { - crc = (crc >>> 8) ^ t[(crc ^ buf[i]) & 0xFF]; - } - - return (crc ^ (-1)); // >>> 0; -}; - - -var crc32_1 = crc32; - -// (C) 1995-2013 Jean-loup Gailly and Mark Adler -// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin -// -// This software is provided 'as-is', without any express or implied -// warranty. In no event will the authors be held liable for any damages -// arising from the use of this software. -// -// Permission is granted to anyone to use this software for any purpose, -// including commercial applications, and to alter it and redistribute it -// freely, subject to the following restrictions: -// -// 1. The origin of this software must not be misrepresented; you must not -// claim that you wrote the original software. If you use this software -// in a product, an acknowledgment in the product documentation would be -// appreciated but is not required. -// 2. Altered source versions must be plainly marked as such, and must not be -// misrepresented as being the original software. -// 3. This notice may not be removed or altered from any source distribution. - -var messages = { - 2: 'need dictionary', /* Z_NEED_DICT 2 */ - 1: 'stream end', /* Z_STREAM_END 1 */ - 0: '', /* Z_OK 0 */ - '-1': 'file error', /* Z_ERRNO (-1) */ - '-2': 'stream error', /* Z_STREAM_ERROR (-2) */ - '-3': 'data error', /* Z_DATA_ERROR (-3) */ - '-4': 'insufficient memory', /* Z_MEM_ERROR (-4) */ - '-5': 'buffer error', /* Z_BUF_ERROR (-5) */ - '-6': 'incompatible version' /* Z_VERSION_ERROR (-6) */ -}; - -// (C) 1995-2013 Jean-loup Gailly and Mark Adler -// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin -// -// This software is provided 'as-is', without any express or implied -// warranty. In no event will the authors be held liable for any damages -// arising from the use of this software. -// -// Permission is granted to anyone to use this software for any purpose, -// including commercial applications, and to alter it and redistribute it -// freely, subject to the following restrictions: -// -// 1. The origin of this software must not be misrepresented; you must not -// claim that you wrote the original software. If you use this software -// in a product, an acknowledgment in the product documentation would be -// appreciated but is not required. -// 2. Altered source versions must be plainly marked as such, and must not be -// misrepresented as being the original software. -// 3. This notice may not be removed or altered from any source distribution. - -var constants$2 = { - - /* Allowed flush values; see deflate() and inflate() below for details */ - Z_NO_FLUSH: 0, - Z_PARTIAL_FLUSH: 1, - Z_SYNC_FLUSH: 2, - Z_FULL_FLUSH: 3, - Z_FINISH: 4, - Z_BLOCK: 5, - Z_TREES: 6, - - /* Return codes for the compression/decompression functions. Negative values - * are errors, positive values are used for special but normal events. - */ - Z_OK: 0, - Z_STREAM_END: 1, - Z_NEED_DICT: 2, - Z_ERRNO: -1, - Z_STREAM_ERROR: -2, - Z_DATA_ERROR: -3, - Z_MEM_ERROR: -4, - Z_BUF_ERROR: -5, - //Z_VERSION_ERROR: -6, - - /* compression levels */ - Z_NO_COMPRESSION: 0, - Z_BEST_SPEED: 1, - Z_BEST_COMPRESSION: 9, - Z_DEFAULT_COMPRESSION: -1, - - - Z_FILTERED: 1, - Z_HUFFMAN_ONLY: 2, - Z_RLE: 3, - Z_FIXED: 4, - Z_DEFAULT_STRATEGY: 0, - - /* Possible values of the data_type field (though see inflate()) */ - Z_BINARY: 0, - Z_TEXT: 1, - //Z_ASCII: 1, // = Z_TEXT (deprecated) - Z_UNKNOWN: 2, - - /* The deflate compression method */ - Z_DEFLATED: 8 - //Z_NULL: null // Use -1 or null inline, depending on var type -}; - -// (C) 1995-2013 Jean-loup Gailly and Mark Adler -// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin -// -// This software is provided 'as-is', without any express or implied -// warranty. In no event will the authors be held liable for any damages -// arising from the use of this software. -// -// Permission is granted to anyone to use this software for any purpose, -// including commercial applications, and to alter it and redistribute it -// freely, subject to the following restrictions: -// -// 1. The origin of this software must not be misrepresented; you must not -// claim that you wrote the original software. If you use this software -// in a product, an acknowledgment in the product documentation would be -// appreciated but is not required. -// 2. Altered source versions must be plainly marked as such, and must not be -// misrepresented as being the original software. -// 3. This notice may not be removed or altered from any source distribution. - -const { _tr_init, _tr_stored_block, _tr_flush_block, _tr_tally, _tr_align } = trees; - - - - -/* Public constants ==========================================================*/ -/* ===========================================================================*/ - -const { - Z_NO_FLUSH: Z_NO_FLUSH$2, Z_PARTIAL_FLUSH, Z_FULL_FLUSH: Z_FULL_FLUSH$1, Z_FINISH: Z_FINISH$3, Z_BLOCK: Z_BLOCK$1, - Z_OK: Z_OK$3, Z_STREAM_END: Z_STREAM_END$3, Z_STREAM_ERROR: Z_STREAM_ERROR$2, Z_DATA_ERROR: Z_DATA_ERROR$2, Z_BUF_ERROR: Z_BUF_ERROR$1, - Z_DEFAULT_COMPRESSION: Z_DEFAULT_COMPRESSION$1, - Z_FILTERED, Z_HUFFMAN_ONLY, Z_RLE, Z_FIXED, Z_DEFAULT_STRATEGY: Z_DEFAULT_STRATEGY$1, - Z_UNKNOWN, - Z_DEFLATED: Z_DEFLATED$2 -} = constants$2; - -/*============================================================================*/ - - -const MAX_MEM_LEVEL = 9; -/* Maximum value for memLevel in deflateInit2 */ -const MAX_WBITS$1 = 15; -/* 32K LZ77 window */ -const DEF_MEM_LEVEL = 8; - - -const LENGTH_CODES = 29; -/* number of length codes, not counting the special END_BLOCK code */ -const LITERALS = 256; -/* number of literal bytes 0..255 */ -const L_CODES = LITERALS + 1 + LENGTH_CODES; -/* number of Literal or Length codes, including the END_BLOCK code */ -const D_CODES = 30; -/* number of distance codes */ -const BL_CODES = 19; -/* number of codes used to transfer the bit lengths */ -const HEAP_SIZE = 2 * L_CODES + 1; -/* maximum heap size */ -const MAX_BITS = 15; -/* All codes must not exceed MAX_BITS bits */ - -const MIN_MATCH = 3; -const MAX_MATCH = 258; -const MIN_LOOKAHEAD = (MAX_MATCH + MIN_MATCH + 1); - -const PRESET_DICT = 0x20; - -const INIT_STATE = 42; /* zlib header -> BUSY_STATE */ -//#ifdef GZIP -const GZIP_STATE = 57; /* gzip header -> BUSY_STATE | EXTRA_STATE */ -//#endif -const EXTRA_STATE = 69; /* gzip extra block -> NAME_STATE */ -const NAME_STATE = 73; /* gzip file name -> COMMENT_STATE */ -const COMMENT_STATE = 91; /* gzip comment -> HCRC_STATE */ -const HCRC_STATE = 103; /* gzip header CRC -> BUSY_STATE */ -const BUSY_STATE = 113; /* deflate -> FINISH_STATE */ -const FINISH_STATE = 666; /* stream complete */ - -const BS_NEED_MORE = 1; /* block not completed, need more input or more output */ -const BS_BLOCK_DONE = 2; /* block flush performed */ -const BS_FINISH_STARTED = 3; /* finish started, need only more output at next deflate */ -const BS_FINISH_DONE = 4; /* finish done, accept no more input or output */ - -const OS_CODE = 0x03; // Unix :) . Don't detect, use this default. - -const err = (strm, errorCode) => { - strm.msg = messages[errorCode]; - return errorCode; -}; - -const rank = (f) => { - return ((f) * 2) - ((f) > 4 ? 9 : 0); -}; - -const zero = (buf) => { - let len = buf.length; while (--len >= 0) { buf[len] = 0; } -}; - -/* =========================================================================== - * Slide the hash table when sliding the window down (could be avoided with 32 - * bit values at the expense of memory usage). We slide even when level == 0 to - * keep the hash table consistent if we switch back to level > 0 later. - */ -const slide_hash = (s) => { - let n, m; - let p; - let wsize = s.w_size; - - n = s.hash_size; - p = n; - do { - m = s.head[--p]; - s.head[p] = (m >= wsize ? m - wsize : 0); - } while (--n); - n = wsize; -//#ifndef FASTEST - p = n; - do { - m = s.prev[--p]; - s.prev[p] = (m >= wsize ? m - wsize : 0); - /* If n is not on any hash chain, prev[n] is garbage but - * its value will never be used. - */ - } while (--n); -//#endif -}; - -/* eslint-disable new-cap */ -let HASH_ZLIB = (s, prev, data) => ((prev << s.hash_shift) ^ data) & s.hash_mask; -// This hash causes less collisions, https://github.com/nodeca/pako/issues/135 -// But breaks binary compatibility -//let HASH_FAST = (s, prev, data) => ((prev << 8) + (prev >> 8) + (data << 4)) & s.hash_mask; -let HASH = HASH_ZLIB; - - -/* ========================================================================= - * Flush as much pending output as possible. All deflate() output, except for - * some deflate_stored() output, goes through this function so some - * applications may wish to modify it to avoid allocating a large - * strm->next_out buffer and copying into it. (See also read_buf()). - */ -const flush_pending = (strm) => { - const s = strm.state; - - //_tr_flush_bits(s); - let len = s.pending; - if (len > strm.avail_out) { - len = strm.avail_out; - } - if (len === 0) { return; } - - strm.output.set(s.pending_buf.subarray(s.pending_out, s.pending_out + len), strm.next_out); - strm.next_out += len; - s.pending_out += len; - strm.total_out += len; - strm.avail_out -= len; - s.pending -= len; - if (s.pending === 0) { - s.pending_out = 0; - } -}; - - -const flush_block_only = (s, last) => { - _tr_flush_block(s, (s.block_start >= 0 ? s.block_start : -1), s.strstart - s.block_start, last); - s.block_start = s.strstart; - flush_pending(s.strm); -}; - - -const put_byte = (s, b) => { - s.pending_buf[s.pending++] = b; -}; - - -/* ========================================================================= - * Put a short in the pending buffer. The 16-bit value is put in MSB order. - * IN assertion: the stream state is correct and there is enough room in - * pending_buf. - */ -const putShortMSB = (s, b) => { - - // put_byte(s, (Byte)(b >> 8)); -// put_byte(s, (Byte)(b & 0xff)); - s.pending_buf[s.pending++] = (b >>> 8) & 0xff; - s.pending_buf[s.pending++] = b & 0xff; -}; - - -/* =========================================================================== - * Read a new buffer from the current input stream, update the adler32 - * and total number of bytes read. All deflate() input goes through - * this function so some applications may wish to modify it to avoid - * allocating a large strm->input buffer and copying from it. - * (See also flush_pending()). - */ -const read_buf = (strm, buf, start, size) => { - - let len = strm.avail_in; - - if (len > size) { len = size; } - if (len === 0) { return 0; } - - strm.avail_in -= len; - - // zmemcpy(buf, strm->next_in, len); - buf.set(strm.input.subarray(strm.next_in, strm.next_in + len), start); - if (strm.state.wrap === 1) { - strm.adler = adler32_1(strm.adler, buf, len, start); - } - - else if (strm.state.wrap === 2) { - strm.adler = crc32_1(strm.adler, buf, len, start); - } - - strm.next_in += len; - strm.total_in += len; - - return len; -}; - - -/* =========================================================================== - * Set match_start to the longest match starting at the given string and - * return its length. Matches shorter or equal to prev_length are discarded, - * in which case the result is equal to prev_length and match_start is - * garbage. - * IN assertions: cur_match is the head of the hash chain for the current - * string (strstart) and its distance is <= MAX_DIST, and prev_length >= 1 - * OUT assertion: the match length is not greater than s->lookahead. - */ -const longest_match = (s, cur_match) => { - - let chain_length = s.max_chain_length; /* max hash chain length */ - let scan = s.strstart; /* current string */ - let match; /* matched string */ - let len; /* length of current match */ - let best_len = s.prev_length; /* best match length so far */ - let nice_match = s.nice_match; /* stop if match long enough */ - const limit = (s.strstart > (s.w_size - MIN_LOOKAHEAD)) ? - s.strstart - (s.w_size - MIN_LOOKAHEAD) : 0/*NIL*/; - - const _win = s.window; // shortcut - - const wmask = s.w_mask; - const prev = s.prev; - - /* Stop when cur_match becomes <= limit. To simplify the code, - * we prevent matches with the string of window index 0. - */ - - const strend = s.strstart + MAX_MATCH; - let scan_end1 = _win[scan + best_len - 1]; - let scan_end = _win[scan + best_len]; - - /* The code is optimized for HASH_BITS >= 8 and MAX_MATCH-2 multiple of 16. - * It is easy to get rid of this optimization if necessary. - */ - // Assert(s->hash_bits >= 8 && MAX_MATCH == 258, "Code too clever"); - - /* Do not waste too much time if we already have a good match: */ - if (s.prev_length >= s.good_match) { - chain_length >>= 2; - } - /* Do not look for matches beyond the end of the input. This is necessary - * to make deflate deterministic. - */ - if (nice_match > s.lookahead) { nice_match = s.lookahead; } - - // Assert((ulg)s->strstart <= s->window_size-MIN_LOOKAHEAD, "need lookahead"); - - do { - // Assert(cur_match < s->strstart, "no future"); - match = cur_match; - - /* Skip to next match if the match length cannot increase - * or if the match length is less than 2. Note that the checks below - * for insufficient lookahead only occur occasionally for performance - * reasons. Therefore uninitialized memory will be accessed, and - * conditional jumps will be made that depend on those values. - * However the length of the match is limited to the lookahead, so - * the output of deflate is not affected by the uninitialized values. - */ - - if (_win[match + best_len] !== scan_end || - _win[match + best_len - 1] !== scan_end1 || - _win[match] !== _win[scan] || - _win[++match] !== _win[scan + 1]) { - continue; - } - - /* The check at best_len-1 can be removed because it will be made - * again later. (This heuristic is not always a win.) - * It is not necessary to compare scan[2] and match[2] since they - * are always equal when the other bytes match, given that - * the hash keys are equal and that HASH_BITS >= 8. - */ - scan += 2; - match++; - // Assert(*scan == *match, "match[2]?"); - - /* We check for insufficient lookahead only every 8th comparison; - * the 256th check will be made at strstart+258. - */ - do { - /*jshint noempty:false*/ - } while (_win[++scan] === _win[++match] && _win[++scan] === _win[++match] && - _win[++scan] === _win[++match] && _win[++scan] === _win[++match] && - _win[++scan] === _win[++match] && _win[++scan] === _win[++match] && - _win[++scan] === _win[++match] && _win[++scan] === _win[++match] && - scan < strend); - - // Assert(scan <= s->window+(unsigned)(s->window_size-1), "wild scan"); - - len = MAX_MATCH - (strend - scan); - scan = strend - MAX_MATCH; - - if (len > best_len) { - s.match_start = cur_match; - best_len = len; - if (len >= nice_match) { - break; - } - scan_end1 = _win[scan + best_len - 1]; - scan_end = _win[scan + best_len]; - } - } while ((cur_match = prev[cur_match & wmask]) > limit && --chain_length !== 0); - - if (best_len <= s.lookahead) { - return best_len; - } - return s.lookahead; -}; - - -/* =========================================================================== - * Fill the window when the lookahead becomes insufficient. - * Updates strstart and lookahead. - * - * IN assertion: lookahead < MIN_LOOKAHEAD - * OUT assertions: strstart <= window_size-MIN_LOOKAHEAD - * At least one byte has been read, or avail_in == 0; reads are - * performed for at least two bytes (required for the zip translate_eol - * option -- not supported here). - */ -const fill_window = (s) => { - - const _w_size = s.w_size; - let n, more, str; - - //Assert(s->lookahead < MIN_LOOKAHEAD, "already enough lookahead"); - - do { - more = s.window_size - s.lookahead - s.strstart; - - // JS ints have 32 bit, block below not needed - /* Deal with !@#$% 64K limit: */ - //if (sizeof(int) <= 2) { - // if (more == 0 && s->strstart == 0 && s->lookahead == 0) { - // more = wsize; - // - // } else if (more == (unsigned)(-1)) { - // /* Very unlikely, but possible on 16 bit machine if - // * strstart == 0 && lookahead == 1 (input done a byte at time) - // */ - // more--; - // } - //} - - - /* If the window is almost full and there is insufficient lookahead, - * move the upper half to the lower one to make room in the upper half. - */ - if (s.strstart >= _w_size + (_w_size - MIN_LOOKAHEAD)) { - - s.window.set(s.window.subarray(_w_size, _w_size + _w_size - more), 0); - s.match_start -= _w_size; - s.strstart -= _w_size; - /* we now have strstart >= MAX_DIST */ - s.block_start -= _w_size; - if (s.insert > s.strstart) { - s.insert = s.strstart; - } - slide_hash(s); - more += _w_size; - } - if (s.strm.avail_in === 0) { - break; - } - - /* If there was no sliding: - * strstart <= WSIZE+MAX_DIST-1 && lookahead <= MIN_LOOKAHEAD - 1 && - * more == window_size - lookahead - strstart - * => more >= window_size - (MIN_LOOKAHEAD-1 + WSIZE + MAX_DIST-1) - * => more >= window_size - 2*WSIZE + 2 - * In the BIG_MEM or MMAP case (not yet supported), - * window_size == input_size + MIN_LOOKAHEAD && - * strstart + s->lookahead <= input_size => more >= MIN_LOOKAHEAD. - * Otherwise, window_size == 2*WSIZE so more >= 2. - * If there was sliding, more >= WSIZE. So in all cases, more >= 2. - */ - //Assert(more >= 2, "more < 2"); - n = read_buf(s.strm, s.window, s.strstart + s.lookahead, more); - s.lookahead += n; - - /* Initialize the hash value now that we have some input: */ - if (s.lookahead + s.insert >= MIN_MATCH) { - str = s.strstart - s.insert; - s.ins_h = s.window[str]; - - /* UPDATE_HASH(s, s->ins_h, s->window[str + 1]); */ - s.ins_h = HASH(s, s.ins_h, s.window[str + 1]); -//#if MIN_MATCH != 3 -// Call update_hash() MIN_MATCH-3 more times -//#endif - while (s.insert) { - /* UPDATE_HASH(s, s->ins_h, s->window[str + MIN_MATCH-1]); */ - s.ins_h = HASH(s, s.ins_h, s.window[str + MIN_MATCH - 1]); - - s.prev[str & s.w_mask] = s.head[s.ins_h]; - s.head[s.ins_h] = str; - str++; - s.insert--; - if (s.lookahead + s.insert < MIN_MATCH) { - break; - } - } - } - /* If the whole input has less than MIN_MATCH bytes, ins_h is garbage, - * but this is not important since only literal bytes will be emitted. - */ - - } while (s.lookahead < MIN_LOOKAHEAD && s.strm.avail_in !== 0); - - /* If the WIN_INIT bytes after the end of the current data have never been - * written, then zero those bytes in order to avoid memory check reports of - * the use of uninitialized (or uninitialised as Julian writes) bytes by - * the longest match routines. Update the high water mark for the next - * time through here. WIN_INIT is set to MAX_MATCH since the longest match - * routines allow scanning to strstart + MAX_MATCH, ignoring lookahead. - */ -// if (s.high_water < s.window_size) { -// const curr = s.strstart + s.lookahead; -// let init = 0; -// -// if (s.high_water < curr) { -// /* Previous high water mark below current data -- zero WIN_INIT -// * bytes or up to end of window, whichever is less. -// */ -// init = s.window_size - curr; -// if (init > WIN_INIT) -// init = WIN_INIT; -// zmemzero(s->window + curr, (unsigned)init); -// s->high_water = curr + init; -// } -// else if (s->high_water < (ulg)curr + WIN_INIT) { -// /* High water mark at or above current data, but below current data -// * plus WIN_INIT -- zero out to current data plus WIN_INIT, or up -// * to end of window, whichever is less. -// */ -// init = (ulg)curr + WIN_INIT - s->high_water; -// if (init > s->window_size - s->high_water) -// init = s->window_size - s->high_water; -// zmemzero(s->window + s->high_water, (unsigned)init); -// s->high_water += init; -// } -// } -// -// Assert((ulg)s->strstart <= s->window_size - MIN_LOOKAHEAD, -// "not enough room for search"); -}; - -/* =========================================================================== - * Copy without compression as much as possible from the input stream, return - * the current block state. - * - * In case deflateParams() is used to later switch to a non-zero compression - * level, s->matches (otherwise unused when storing) keeps track of the number - * of hash table slides to perform. If s->matches is 1, then one hash table - * slide will be done when switching. If s->matches is 2, the maximum value - * allowed here, then the hash table will be cleared, since two or more slides - * is the same as a clear. - * - * deflate_stored() is written to minimize the number of times an input byte is - * copied. It is most efficient with large input and output buffers, which - * maximizes the opportunites to have a single copy from next_in to next_out. - */ -const deflate_stored = (s, flush) => { - - /* Smallest worthy block size when not flushing or finishing. By default - * this is 32K. This can be as small as 507 bytes for memLevel == 1. For - * large input and output buffers, the stored block size will be larger. - */ - let min_block = s.pending_buf_size - 5 > s.w_size ? s.w_size : s.pending_buf_size - 5; - - /* Copy as many min_block or larger stored blocks directly to next_out as - * possible. If flushing, copy the remaining available input to next_out as - * stored blocks, if there is enough space. - */ - let len, left, have, last = 0; - let used = s.strm.avail_in; - do { - /* Set len to the maximum size block that we can copy directly with the - * available input data and output space. Set left to how much of that - * would be copied from what's left in the window. - */ - len = 65535/* MAX_STORED */; /* maximum deflate stored block length */ - have = (s.bi_valid + 42) >> 3; /* number of header bytes */ - if (s.strm.avail_out < have) { /* need room for header */ - break; - } - /* maximum stored block length that will fit in avail_out: */ - have = s.strm.avail_out - have; - left = s.strstart - s.block_start; /* bytes left in window */ - if (len > left + s.strm.avail_in) { - len = left + s.strm.avail_in; /* limit len to the input */ - } - if (len > have) { - len = have; /* limit len to the output */ - } - - /* If the stored block would be less than min_block in length, or if - * unable to copy all of the available input when flushing, then try - * copying to the window and the pending buffer instead. Also don't - * write an empty block when flushing -- deflate() does that. - */ - if (len < min_block && ((len === 0 && flush !== Z_FINISH$3) || - flush === Z_NO_FLUSH$2 || - len !== left + s.strm.avail_in)) { - break; - } - - /* Make a dummy stored block in pending to get the header bytes, - * including any pending bits. This also updates the debugging counts. - */ - last = flush === Z_FINISH$3 && len === left + s.strm.avail_in ? 1 : 0; - _tr_stored_block(s, 0, 0, last); - - /* Replace the lengths in the dummy stored block with len. */ - s.pending_buf[s.pending - 4] = len; - s.pending_buf[s.pending - 3] = len >> 8; - s.pending_buf[s.pending - 2] = ~len; - s.pending_buf[s.pending - 1] = ~len >> 8; - - /* Write the stored block header bytes. */ - flush_pending(s.strm); - -//#ifdef ZLIB_DEBUG -// /* Update debugging counts for the data about to be copied. */ -// s->compressed_len += len << 3; -// s->bits_sent += len << 3; -//#endif - - /* Copy uncompressed bytes from the window to next_out. */ - if (left) { - if (left > len) { - left = len; - } - //zmemcpy(s->strm->next_out, s->window + s->block_start, left); - s.strm.output.set(s.window.subarray(s.block_start, s.block_start + left), s.strm.next_out); - s.strm.next_out += left; - s.strm.avail_out -= left; - s.strm.total_out += left; - s.block_start += left; - len -= left; - } - - /* Copy uncompressed bytes directly from next_in to next_out, updating - * the check value. - */ - if (len) { - read_buf(s.strm, s.strm.output, s.strm.next_out, len); - s.strm.next_out += len; - s.strm.avail_out -= len; - s.strm.total_out += len; - } - } while (last === 0); - - /* Update the sliding window with the last s->w_size bytes of the copied - * data, or append all of the copied data to the existing window if less - * than s->w_size bytes were copied. Also update the number of bytes to - * insert in the hash tables, in the event that deflateParams() switches to - * a non-zero compression level. - */ - used -= s.strm.avail_in; /* number of input bytes directly copied */ - if (used) { - /* If any input was used, then no unused input remains in the window, - * therefore s->block_start == s->strstart. - */ - if (used >= s.w_size) { /* supplant the previous history */ - s.matches = 2; /* clear hash */ - //zmemcpy(s->window, s->strm->next_in - s->w_size, s->w_size); - s.window.set(s.strm.input.subarray(s.strm.next_in - s.w_size, s.strm.next_in), 0); - s.strstart = s.w_size; - s.insert = s.strstart; - } - else { - if (s.window_size - s.strstart <= used) { - /* Slide the window down. */ - s.strstart -= s.w_size; - //zmemcpy(s->window, s->window + s->w_size, s->strstart); - s.window.set(s.window.subarray(s.w_size, s.w_size + s.strstart), 0); - if (s.matches < 2) { - s.matches++; /* add a pending slide_hash() */ - } - if (s.insert > s.strstart) { - s.insert = s.strstart; - } - } - //zmemcpy(s->window + s->strstart, s->strm->next_in - used, used); - s.window.set(s.strm.input.subarray(s.strm.next_in - used, s.strm.next_in), s.strstart); - s.strstart += used; - s.insert += used > s.w_size - s.insert ? s.w_size - s.insert : used; - } - s.block_start = s.strstart; - } - if (s.high_water < s.strstart) { - s.high_water = s.strstart; - } - - /* If the last block was written to next_out, then done. */ - if (last) { - return BS_FINISH_DONE; - } - - /* If flushing and all input has been consumed, then done. */ - if (flush !== Z_NO_FLUSH$2 && flush !== Z_FINISH$3 && - s.strm.avail_in === 0 && s.strstart === s.block_start) { - return BS_BLOCK_DONE; - } - - /* Fill the window with any remaining input. */ - have = s.window_size - s.strstart; - if (s.strm.avail_in > have && s.block_start >= s.w_size) { - /* Slide the window down. */ - s.block_start -= s.w_size; - s.strstart -= s.w_size; - //zmemcpy(s->window, s->window + s->w_size, s->strstart); - s.window.set(s.window.subarray(s.w_size, s.w_size + s.strstart), 0); - if (s.matches < 2) { - s.matches++; /* add a pending slide_hash() */ - } - have += s.w_size; /* more space now */ - if (s.insert > s.strstart) { - s.insert = s.strstart; - } - } - if (have > s.strm.avail_in) { - have = s.strm.avail_in; - } - if (have) { - read_buf(s.strm, s.window, s.strstart, have); - s.strstart += have; - s.insert += have > s.w_size - s.insert ? s.w_size - s.insert : have; - } - if (s.high_water < s.strstart) { - s.high_water = s.strstart; - } - - /* There was not enough avail_out to write a complete worthy or flushed - * stored block to next_out. Write a stored block to pending instead, if we - * have enough input for a worthy block, or if flushing and there is enough - * room for the remaining input as a stored block in the pending buffer. - */ - have = (s.bi_valid + 42) >> 3; /* number of header bytes */ - /* maximum stored block length that will fit in pending: */ - have = s.pending_buf_size - have > 65535/* MAX_STORED */ ? 65535/* MAX_STORED */ : s.pending_buf_size - have; - min_block = have > s.w_size ? s.w_size : have; - left = s.strstart - s.block_start; - if (left >= min_block || - ((left || flush === Z_FINISH$3) && flush !== Z_NO_FLUSH$2 && - s.strm.avail_in === 0 && left <= have)) { - len = left > have ? have : left; - last = flush === Z_FINISH$3 && s.strm.avail_in === 0 && - len === left ? 1 : 0; - _tr_stored_block(s, s.block_start, len, last); - s.block_start += len; - flush_pending(s.strm); - } - - /* We've done all we can with the available input and output. */ - return last ? BS_FINISH_STARTED : BS_NEED_MORE; -}; - - -/* =========================================================================== - * Compress as much as possible from the input stream, return the current - * block state. - * This function does not perform lazy evaluation of matches and inserts - * new strings in the dictionary only for unmatched strings or for short - * matches. It is used only for the fast compression options. - */ -const deflate_fast = (s, flush) => { - - let hash_head; /* head of the hash chain */ - let bflush; /* set if current block must be flushed */ - - for (;;) { - /* Make sure that we always have enough lookahead, except - * at the end of the input file. We need MAX_MATCH bytes - * for the next match, plus MIN_MATCH bytes to insert the - * string following the next match. - */ - if (s.lookahead < MIN_LOOKAHEAD) { - fill_window(s); - if (s.lookahead < MIN_LOOKAHEAD && flush === Z_NO_FLUSH$2) { - return BS_NEED_MORE; - } - if (s.lookahead === 0) { - break; /* flush the current block */ - } - } - - /* Insert the string window[strstart .. strstart+2] in the - * dictionary, and set hash_head to the head of the hash chain: - */ - hash_head = 0/*NIL*/; - if (s.lookahead >= MIN_MATCH) { - /*** INSERT_STRING(s, s.strstart, hash_head); ***/ - s.ins_h = HASH(s, s.ins_h, s.window[s.strstart + MIN_MATCH - 1]); - hash_head = s.prev[s.strstart & s.w_mask] = s.head[s.ins_h]; - s.head[s.ins_h] = s.strstart; - /***/ - } - - /* Find the longest match, discarding those <= prev_length. - * At this point we have always match_length < MIN_MATCH - */ - if (hash_head !== 0/*NIL*/ && ((s.strstart - hash_head) <= (s.w_size - MIN_LOOKAHEAD))) { - /* To simplify the code, we prevent matches with the string - * of window index 0 (in particular we have to avoid a match - * of the string with itself at the start of the input file). - */ - s.match_length = longest_match(s, hash_head); - /* longest_match() sets match_start */ - } - if (s.match_length >= MIN_MATCH) { - // check_match(s, s.strstart, s.match_start, s.match_length); // for debug only - - /*** _tr_tally_dist(s, s.strstart - s.match_start, - s.match_length - MIN_MATCH, bflush); ***/ - bflush = _tr_tally(s, s.strstart - s.match_start, s.match_length - MIN_MATCH); - - s.lookahead -= s.match_length; - - /* Insert new strings in the hash table only if the match length - * is not too large. This saves time but degrades compression. - */ - if (s.match_length <= s.max_lazy_match/*max_insert_length*/ && s.lookahead >= MIN_MATCH) { - s.match_length--; /* string at strstart already in table */ - do { - s.strstart++; - /*** INSERT_STRING(s, s.strstart, hash_head); ***/ - s.ins_h = HASH(s, s.ins_h, s.window[s.strstart + MIN_MATCH - 1]); - hash_head = s.prev[s.strstart & s.w_mask] = s.head[s.ins_h]; - s.head[s.ins_h] = s.strstart; - /***/ - /* strstart never exceeds WSIZE-MAX_MATCH, so there are - * always MIN_MATCH bytes ahead. - */ - } while (--s.match_length !== 0); - s.strstart++; - } else - { - s.strstart += s.match_length; - s.match_length = 0; - s.ins_h = s.window[s.strstart]; - /* UPDATE_HASH(s, s.ins_h, s.window[s.strstart+1]); */ - s.ins_h = HASH(s, s.ins_h, s.window[s.strstart + 1]); - -//#if MIN_MATCH != 3 -// Call UPDATE_HASH() MIN_MATCH-3 more times -//#endif - /* If lookahead < MIN_MATCH, ins_h is garbage, but it does not - * matter since it will be recomputed at next deflate call. - */ - } - } else { - /* No match, output a literal byte */ - //Tracevv((stderr,"%c", s.window[s.strstart])); - /*** _tr_tally_lit(s, s.window[s.strstart], bflush); ***/ - bflush = _tr_tally(s, 0, s.window[s.strstart]); - - s.lookahead--; - s.strstart++; - } - if (bflush) { - /*** FLUSH_BLOCK(s, 0); ***/ - flush_block_only(s, false); - if (s.strm.avail_out === 0) { - return BS_NEED_MORE; - } - /***/ - } - } - s.insert = ((s.strstart < (MIN_MATCH - 1)) ? s.strstart : MIN_MATCH - 1); - if (flush === Z_FINISH$3) { - /*** FLUSH_BLOCK(s, 1); ***/ - flush_block_only(s, true); - if (s.strm.avail_out === 0) { - return BS_FINISH_STARTED; - } - /***/ - return BS_FINISH_DONE; - } - if (s.sym_next) { - /*** FLUSH_BLOCK(s, 0); ***/ - flush_block_only(s, false); - if (s.strm.avail_out === 0) { - return BS_NEED_MORE; - } - /***/ - } - return BS_BLOCK_DONE; -}; - -/* =========================================================================== - * Same as above, but achieves better compression. We use a lazy - * evaluation for matches: a match is finally adopted only if there is - * no better match at the next window position. - */ -const deflate_slow = (s, flush) => { - - let hash_head; /* head of hash chain */ - let bflush; /* set if current block must be flushed */ - - let max_insert; - - /* Process the input block. */ - for (;;) { - /* Make sure that we always have enough lookahead, except - * at the end of the input file. We need MAX_MATCH bytes - * for the next match, plus MIN_MATCH bytes to insert the - * string following the next match. - */ - if (s.lookahead < MIN_LOOKAHEAD) { - fill_window(s); - if (s.lookahead < MIN_LOOKAHEAD && flush === Z_NO_FLUSH$2) { - return BS_NEED_MORE; - } - if (s.lookahead === 0) { break; } /* flush the current block */ - } - - /* Insert the string window[strstart .. strstart+2] in the - * dictionary, and set hash_head to the head of the hash chain: - */ - hash_head = 0/*NIL*/; - if (s.lookahead >= MIN_MATCH) { - /*** INSERT_STRING(s, s.strstart, hash_head); ***/ - s.ins_h = HASH(s, s.ins_h, s.window[s.strstart + MIN_MATCH - 1]); - hash_head = s.prev[s.strstart & s.w_mask] = s.head[s.ins_h]; - s.head[s.ins_h] = s.strstart; - /***/ - } - - /* Find the longest match, discarding those <= prev_length. - */ - s.prev_length = s.match_length; - s.prev_match = s.match_start; - s.match_length = MIN_MATCH - 1; - - if (hash_head !== 0/*NIL*/ && s.prev_length < s.max_lazy_match && - s.strstart - hash_head <= (s.w_size - MIN_LOOKAHEAD)/*MAX_DIST(s)*/) { - /* To simplify the code, we prevent matches with the string - * of window index 0 (in particular we have to avoid a match - * of the string with itself at the start of the input file). - */ - s.match_length = longest_match(s, hash_head); - /* longest_match() sets match_start */ - - if (s.match_length <= 5 && - (s.strategy === Z_FILTERED || (s.match_length === MIN_MATCH && s.strstart - s.match_start > 4096/*TOO_FAR*/))) { - - /* If prev_match is also MIN_MATCH, match_start is garbage - * but we will ignore the current match anyway. - */ - s.match_length = MIN_MATCH - 1; - } - } - /* If there was a match at the previous step and the current - * match is not better, output the previous match: - */ - if (s.prev_length >= MIN_MATCH && s.match_length <= s.prev_length) { - max_insert = s.strstart + s.lookahead - MIN_MATCH; - /* Do not insert strings in hash table beyond this. */ - - //check_match(s, s.strstart-1, s.prev_match, s.prev_length); - - /***_tr_tally_dist(s, s.strstart - 1 - s.prev_match, - s.prev_length - MIN_MATCH, bflush);***/ - bflush = _tr_tally(s, s.strstart - 1 - s.prev_match, s.prev_length - MIN_MATCH); - /* Insert in hash table all strings up to the end of the match. - * strstart-1 and strstart are already inserted. If there is not - * enough lookahead, the last two strings are not inserted in - * the hash table. - */ - s.lookahead -= s.prev_length - 1; - s.prev_length -= 2; - do { - if (++s.strstart <= max_insert) { - /*** INSERT_STRING(s, s.strstart, hash_head); ***/ - s.ins_h = HASH(s, s.ins_h, s.window[s.strstart + MIN_MATCH - 1]); - hash_head = s.prev[s.strstart & s.w_mask] = s.head[s.ins_h]; - s.head[s.ins_h] = s.strstart; - /***/ - } - } while (--s.prev_length !== 0); - s.match_available = 0; - s.match_length = MIN_MATCH - 1; - s.strstart++; - - if (bflush) { - /*** FLUSH_BLOCK(s, 0); ***/ - flush_block_only(s, false); - if (s.strm.avail_out === 0) { - return BS_NEED_MORE; - } - /***/ - } - - } else if (s.match_available) { - /* If there was no match at the previous position, output a - * single literal. If there was a match but the current match - * is longer, truncate the previous match to a single literal. - */ - //Tracevv((stderr,"%c", s->window[s->strstart-1])); - /*** _tr_tally_lit(s, s.window[s.strstart-1], bflush); ***/ - bflush = _tr_tally(s, 0, s.window[s.strstart - 1]); - - if (bflush) { - /*** FLUSH_BLOCK_ONLY(s, 0) ***/ - flush_block_only(s, false); - /***/ - } - s.strstart++; - s.lookahead--; - if (s.strm.avail_out === 0) { - return BS_NEED_MORE; - } - } else { - /* There is no previous match to compare with, wait for - * the next step to decide. - */ - s.match_available = 1; - s.strstart++; - s.lookahead--; - } - } - //Assert (flush != Z_NO_FLUSH, "no flush?"); - if (s.match_available) { - //Tracevv((stderr,"%c", s->window[s->strstart-1])); - /*** _tr_tally_lit(s, s.window[s.strstart-1], bflush); ***/ - bflush = _tr_tally(s, 0, s.window[s.strstart - 1]); - - s.match_available = 0; - } - s.insert = s.strstart < MIN_MATCH - 1 ? s.strstart : MIN_MATCH - 1; - if (flush === Z_FINISH$3) { - /*** FLUSH_BLOCK(s, 1); ***/ - flush_block_only(s, true); - if (s.strm.avail_out === 0) { - return BS_FINISH_STARTED; - } - /***/ - return BS_FINISH_DONE; - } - if (s.sym_next) { - /*** FLUSH_BLOCK(s, 0); ***/ - flush_block_only(s, false); - if (s.strm.avail_out === 0) { - return BS_NEED_MORE; - } - /***/ - } - - return BS_BLOCK_DONE; -}; - - -/* =========================================================================== - * For Z_RLE, simply look for runs of bytes, generate matches only of distance - * one. Do not maintain a hash table. (It will be regenerated if this run of - * deflate switches away from Z_RLE.) - */ -const deflate_rle = (s, flush) => { - - let bflush; /* set if current block must be flushed */ - let prev; /* byte at distance one to match */ - let scan, strend; /* scan goes up to strend for length of run */ - - const _win = s.window; - - for (;;) { - /* Make sure that we always have enough lookahead, except - * at the end of the input file. We need MAX_MATCH bytes - * for the longest run, plus one for the unrolled loop. - */ - if (s.lookahead <= MAX_MATCH) { - fill_window(s); - if (s.lookahead <= MAX_MATCH && flush === Z_NO_FLUSH$2) { - return BS_NEED_MORE; - } - if (s.lookahead === 0) { break; } /* flush the current block */ - } - - /* See how many times the previous byte repeats */ - s.match_length = 0; - if (s.lookahead >= MIN_MATCH && s.strstart > 0) { - scan = s.strstart - 1; - prev = _win[scan]; - if (prev === _win[++scan] && prev === _win[++scan] && prev === _win[++scan]) { - strend = s.strstart + MAX_MATCH; - do { - /*jshint noempty:false*/ - } while (prev === _win[++scan] && prev === _win[++scan] && - prev === _win[++scan] && prev === _win[++scan] && - prev === _win[++scan] && prev === _win[++scan] && - prev === _win[++scan] && prev === _win[++scan] && - scan < strend); - s.match_length = MAX_MATCH - (strend - scan); - if (s.match_length > s.lookahead) { - s.match_length = s.lookahead; - } - } - //Assert(scan <= s->window+(uInt)(s->window_size-1), "wild scan"); - } - - /* Emit match if have run of MIN_MATCH or longer, else emit literal */ - if (s.match_length >= MIN_MATCH) { - //check_match(s, s.strstart, s.strstart - 1, s.match_length); - - /*** _tr_tally_dist(s, 1, s.match_length - MIN_MATCH, bflush); ***/ - bflush = _tr_tally(s, 1, s.match_length - MIN_MATCH); - - s.lookahead -= s.match_length; - s.strstart += s.match_length; - s.match_length = 0; - } else { - /* No match, output a literal byte */ - //Tracevv((stderr,"%c", s->window[s->strstart])); - /*** _tr_tally_lit(s, s.window[s.strstart], bflush); ***/ - bflush = _tr_tally(s, 0, s.window[s.strstart]); - - s.lookahead--; - s.strstart++; - } - if (bflush) { - /*** FLUSH_BLOCK(s, 0); ***/ - flush_block_only(s, false); - if (s.strm.avail_out === 0) { - return BS_NEED_MORE; - } - /***/ - } - } - s.insert = 0; - if (flush === Z_FINISH$3) { - /*** FLUSH_BLOCK(s, 1); ***/ - flush_block_only(s, true); - if (s.strm.avail_out === 0) { - return BS_FINISH_STARTED; - } - /***/ - return BS_FINISH_DONE; - } - if (s.sym_next) { - /*** FLUSH_BLOCK(s, 0); ***/ - flush_block_only(s, false); - if (s.strm.avail_out === 0) { - return BS_NEED_MORE; - } - /***/ - } - return BS_BLOCK_DONE; -}; - -/* =========================================================================== - * For Z_HUFFMAN_ONLY, do not look for matches. Do not maintain a hash table. - * (It will be regenerated if this run of deflate switches away from Huffman.) - */ -const deflate_huff = (s, flush) => { - - let bflush; /* set if current block must be flushed */ - - for (;;) { - /* Make sure that we have a literal to write. */ - if (s.lookahead === 0) { - fill_window(s); - if (s.lookahead === 0) { - if (flush === Z_NO_FLUSH$2) { - return BS_NEED_MORE; - } - break; /* flush the current block */ - } - } - - /* Output a literal byte */ - s.match_length = 0; - //Tracevv((stderr,"%c", s->window[s->strstart])); - /*** _tr_tally_lit(s, s.window[s.strstart], bflush); ***/ - bflush = _tr_tally(s, 0, s.window[s.strstart]); - s.lookahead--; - s.strstart++; - if (bflush) { - /*** FLUSH_BLOCK(s, 0); ***/ - flush_block_only(s, false); - if (s.strm.avail_out === 0) { - return BS_NEED_MORE; - } - /***/ - } - } - s.insert = 0; - if (flush === Z_FINISH$3) { - /*** FLUSH_BLOCK(s, 1); ***/ - flush_block_only(s, true); - if (s.strm.avail_out === 0) { - return BS_FINISH_STARTED; - } - /***/ - return BS_FINISH_DONE; - } - if (s.sym_next) { - /*** FLUSH_BLOCK(s, 0); ***/ - flush_block_only(s, false); - if (s.strm.avail_out === 0) { - return BS_NEED_MORE; - } - /***/ - } - return BS_BLOCK_DONE; -}; - -/* Values for max_lazy_match, good_match and max_chain_length, depending on - * the desired pack level (0..9). The values given below have been tuned to - * exclude worst case performance for pathological files. Better values may be - * found for specific files. - */ -function Config(good_length, max_lazy, nice_length, max_chain, func) { - - this.good_length = good_length; - this.max_lazy = max_lazy; - this.nice_length = nice_length; - this.max_chain = max_chain; - this.func = func; -} - -const configuration_table = [ - /* good lazy nice chain */ - new Config(0, 0, 0, 0, deflate_stored), /* 0 store only */ - new Config(4, 4, 8, 4, deflate_fast), /* 1 max speed, no lazy matches */ - new Config(4, 5, 16, 8, deflate_fast), /* 2 */ - new Config(4, 6, 32, 32, deflate_fast), /* 3 */ - - new Config(4, 4, 16, 16, deflate_slow), /* 4 lazy matches */ - new Config(8, 16, 32, 32, deflate_slow), /* 5 */ - new Config(8, 16, 128, 128, deflate_slow), /* 6 */ - new Config(8, 32, 128, 256, deflate_slow), /* 7 */ - new Config(32, 128, 258, 1024, deflate_slow), /* 8 */ - new Config(32, 258, 258, 4096, deflate_slow) /* 9 max compression */ -]; - - -/* =========================================================================== - * Initialize the "longest match" routines for a new zlib stream - */ -const lm_init = (s) => { - - s.window_size = 2 * s.w_size; - - /*** CLEAR_HASH(s); ***/ - zero(s.head); // Fill with NIL (= 0); - - /* Set the default configuration parameters: - */ - s.max_lazy_match = configuration_table[s.level].max_lazy; - s.good_match = configuration_table[s.level].good_length; - s.nice_match = configuration_table[s.level].nice_length; - s.max_chain_length = configuration_table[s.level].max_chain; - - s.strstart = 0; - s.block_start = 0; - s.lookahead = 0; - s.insert = 0; - s.match_length = s.prev_length = MIN_MATCH - 1; - s.match_available = 0; - s.ins_h = 0; -}; - - -function DeflateState() { - this.strm = null; /* pointer back to this zlib stream */ - this.status = 0; /* as the name implies */ - this.pending_buf = null; /* output still pending */ - this.pending_buf_size = 0; /* size of pending_buf */ - this.pending_out = 0; /* next pending byte to output to the stream */ - this.pending = 0; /* nb of bytes in the pending buffer */ - this.wrap = 0; /* bit 0 true for zlib, bit 1 true for gzip */ - this.gzhead = null; /* gzip header information to write */ - this.gzindex = 0; /* where in extra, name, or comment */ - this.method = Z_DEFLATED$2; /* can only be DEFLATED */ - this.last_flush = -1; /* value of flush param for previous deflate call */ - - this.w_size = 0; /* LZ77 window size (32K by default) */ - this.w_bits = 0; /* log2(w_size) (8..16) */ - this.w_mask = 0; /* w_size - 1 */ - - this.window = null; - /* Sliding window. Input bytes are read into the second half of the window, - * and move to the first half later to keep a dictionary of at least wSize - * bytes. With this organization, matches are limited to a distance of - * wSize-MAX_MATCH bytes, but this ensures that IO is always - * performed with a length multiple of the block size. - */ - - this.window_size = 0; - /* Actual size of window: 2*wSize, except when the user input buffer - * is directly used as sliding window. - */ - - this.prev = null; - /* Link to older string with same hash index. To limit the size of this - * array to 64K, this link is maintained only for the last 32K strings. - * An index in this array is thus a window index modulo 32K. - */ - - this.head = null; /* Heads of the hash chains or NIL. */ - - this.ins_h = 0; /* hash index of string to be inserted */ - this.hash_size = 0; /* number of elements in hash table */ - this.hash_bits = 0; /* log2(hash_size) */ - this.hash_mask = 0; /* hash_size-1 */ - - this.hash_shift = 0; - /* Number of bits by which ins_h must be shifted at each input - * step. It must be such that after MIN_MATCH steps, the oldest - * byte no longer takes part in the hash key, that is: - * hash_shift * MIN_MATCH >= hash_bits - */ - - this.block_start = 0; - /* Window position at the beginning of the current output block. Gets - * negative when the window is moved backwards. - */ - - this.match_length = 0; /* length of best match */ - this.prev_match = 0; /* previous match */ - this.match_available = 0; /* set if previous match exists */ - this.strstart = 0; /* start of string to insert */ - this.match_start = 0; /* start of matching string */ - this.lookahead = 0; /* number of valid bytes ahead in window */ - - this.prev_length = 0; - /* Length of the best match at previous step. Matches not greater than this - * are discarded. This is used in the lazy match evaluation. - */ - - this.max_chain_length = 0; - /* To speed up deflation, hash chains are never searched beyond this - * length. A higher limit improves compression ratio but degrades the - * speed. - */ - - this.max_lazy_match = 0; - /* Attempt to find a better match only when the current match is strictly - * smaller than this value. This mechanism is used only for compression - * levels >= 4. - */ - // That's alias to max_lazy_match, don't use directly - //this.max_insert_length = 0; - /* Insert new strings in the hash table only if the match length is not - * greater than this length. This saves time but degrades compression. - * max_insert_length is used only for compression levels <= 3. - */ - - this.level = 0; /* compression level (1..9) */ - this.strategy = 0; /* favor or force Huffman coding*/ - - this.good_match = 0; - /* Use a faster search when the previous match is longer than this */ - - this.nice_match = 0; /* Stop searching when current match exceeds this */ - - /* used by trees.c: */ - - /* Didn't use ct_data typedef below to suppress compiler warning */ - - // struct ct_data_s dyn_ltree[HEAP_SIZE]; /* literal and length tree */ - // struct ct_data_s dyn_dtree[2*D_CODES+1]; /* distance tree */ - // struct ct_data_s bl_tree[2*BL_CODES+1]; /* Huffman tree for bit lengths */ - - // Use flat array of DOUBLE size, with interleaved fata, - // because JS does not support effective - this.dyn_ltree = new Uint16Array(HEAP_SIZE * 2); - this.dyn_dtree = new Uint16Array((2 * D_CODES + 1) * 2); - this.bl_tree = new Uint16Array((2 * BL_CODES + 1) * 2); - zero(this.dyn_ltree); - zero(this.dyn_dtree); - zero(this.bl_tree); - - this.l_desc = null; /* desc. for literal tree */ - this.d_desc = null; /* desc. for distance tree */ - this.bl_desc = null; /* desc. for bit length tree */ - - //ush bl_count[MAX_BITS+1]; - this.bl_count = new Uint16Array(MAX_BITS + 1); - /* number of codes at each bit length for an optimal tree */ - - //int heap[2*L_CODES+1]; /* heap used to build the Huffman trees */ - this.heap = new Uint16Array(2 * L_CODES + 1); /* heap used to build the Huffman trees */ - zero(this.heap); - - this.heap_len = 0; /* number of elements in the heap */ - this.heap_max = 0; /* element of largest frequency */ - /* The sons of heap[n] are heap[2*n] and heap[2*n+1]. heap[0] is not used. - * The same heap array is used to build all trees. - */ - - this.depth = new Uint16Array(2 * L_CODES + 1); //uch depth[2*L_CODES+1]; - zero(this.depth); - /* Depth of each subtree used as tie breaker for trees of equal frequency - */ - - this.sym_buf = 0; /* buffer for distances and literals/lengths */ - - this.lit_bufsize = 0; - /* Size of match buffer for literals/lengths. There are 4 reasons for - * limiting lit_bufsize to 64K: - * - frequencies can be kept in 16 bit counters - * - if compression is not successful for the first block, all input - * data is still in the window so we can still emit a stored block even - * when input comes from standard input. (This can also be done for - * all blocks if lit_bufsize is not greater than 32K.) - * - if compression is not successful for a file smaller than 64K, we can - * even emit a stored file instead of a stored block (saving 5 bytes). - * This is applicable only for zip (not gzip or zlib). - * - creating new Huffman trees less frequently may not provide fast - * adaptation to changes in the input data statistics. (Take for - * example a binary file with poorly compressible code followed by - * a highly compressible string table.) Smaller buffer sizes give - * fast adaptation but have of course the overhead of transmitting - * trees more frequently. - * - I can't count above 4 - */ - - this.sym_next = 0; /* running index in sym_buf */ - this.sym_end = 0; /* symbol table full when sym_next reaches this */ - - this.opt_len = 0; /* bit length of current block with optimal trees */ - this.static_len = 0; /* bit length of current block with static trees */ - this.matches = 0; /* number of string matches in current block */ - this.insert = 0; /* bytes at end of window left to insert */ - - - this.bi_buf = 0; - /* Output buffer. bits are inserted starting at the bottom (least - * significant bits). - */ - this.bi_valid = 0; - /* Number of valid bits in bi_buf. All bits above the last valid bit - * are always zero. - */ - - // Used for window memory init. We safely ignore it for JS. That makes - // sense only for pointers and memory check tools. - //this.high_water = 0; - /* High water mark offset in window for initialized bytes -- bytes above - * this are set to zero in order to avoid memory check warnings when - * longest match routines access bytes past the input. This is then - * updated to the new high water mark. - */ -} - - -/* ========================================================================= - * Check for a valid deflate stream state. Return 0 if ok, 1 if not. - */ -const deflateStateCheck = (strm) => { - - if (!strm) { - return 1; - } - const s = strm.state; - if (!s || s.strm !== strm || (s.status !== INIT_STATE && -//#ifdef GZIP - s.status !== GZIP_STATE && -//#endif - s.status !== EXTRA_STATE && - s.status !== NAME_STATE && - s.status !== COMMENT_STATE && - s.status !== HCRC_STATE && - s.status !== BUSY_STATE && - s.status !== FINISH_STATE)) { - return 1; - } - return 0; -}; - - -const deflateResetKeep = (strm) => { - - if (deflateStateCheck(strm)) { - return err(strm, Z_STREAM_ERROR$2); - } - - strm.total_in = strm.total_out = 0; - strm.data_type = Z_UNKNOWN; - - const s = strm.state; - s.pending = 0; - s.pending_out = 0; - - if (s.wrap < 0) { - s.wrap = -s.wrap; - /* was made negative by deflate(..., Z_FINISH); */ - } - s.status = -//#ifdef GZIP - s.wrap === 2 ? GZIP_STATE : -//#endif - s.wrap ? INIT_STATE : BUSY_STATE; - strm.adler = (s.wrap === 2) ? - 0 // crc32(0, Z_NULL, 0) - : - 1; // adler32(0, Z_NULL, 0) - s.last_flush = -2; - _tr_init(s); - return Z_OK$3; -}; - - -const deflateReset = (strm) => { - - const ret = deflateResetKeep(strm); - if (ret === Z_OK$3) { - lm_init(strm.state); - } - return ret; -}; - - -const deflateSetHeader = (strm, head) => { - - if (deflateStateCheck(strm) || strm.state.wrap !== 2) { - return Z_STREAM_ERROR$2; - } - strm.state.gzhead = head; - return Z_OK$3; -}; - - -const deflateInit2 = (strm, level, method, windowBits, memLevel, strategy) => { - - if (!strm) { // === Z_NULL - return Z_STREAM_ERROR$2; - } - let wrap = 1; - - if (level === Z_DEFAULT_COMPRESSION$1) { - level = 6; - } - - if (windowBits < 0) { /* suppress zlib wrapper */ - wrap = 0; - windowBits = -windowBits; - } - - else if (windowBits > 15) { - wrap = 2; /* write gzip wrapper instead */ - windowBits -= 16; - } - - - if (memLevel < 1 || memLevel > MAX_MEM_LEVEL || method !== Z_DEFLATED$2 || - windowBits < 8 || windowBits > 15 || level < 0 || level > 9 || - strategy < 0 || strategy > Z_FIXED || (windowBits === 8 && wrap !== 1)) { - return err(strm, Z_STREAM_ERROR$2); - } - - - if (windowBits === 8) { - windowBits = 9; - } - /* until 256-byte window bug fixed */ - - const s = new DeflateState(); - - strm.state = s; - s.strm = strm; - s.status = INIT_STATE; /* to pass state test in deflateReset() */ - - s.wrap = wrap; - s.gzhead = null; - s.w_bits = windowBits; - s.w_size = 1 << s.w_bits; - s.w_mask = s.w_size - 1; - - s.hash_bits = memLevel + 7; - s.hash_size = 1 << s.hash_bits; - s.hash_mask = s.hash_size - 1; - s.hash_shift = ~~((s.hash_bits + MIN_MATCH - 1) / MIN_MATCH); - - s.window = new Uint8Array(s.w_size * 2); - s.head = new Uint16Array(s.hash_size); - s.prev = new Uint16Array(s.w_size); - - // Don't need mem init magic for JS. - //s.high_water = 0; /* nothing written to s->window yet */ - - s.lit_bufsize = 1 << (memLevel + 6); /* 16K elements by default */ - - /* We overlay pending_buf and sym_buf. This works since the average size - * for length/distance pairs over any compressed block is assured to be 31 - * bits or less. - * - * Analysis: The longest fixed codes are a length code of 8 bits plus 5 - * extra bits, for lengths 131 to 257. The longest fixed distance codes are - * 5 bits plus 13 extra bits, for distances 16385 to 32768. The longest - * possible fixed-codes length/distance pair is then 31 bits total. - * - * sym_buf starts one-fourth of the way into pending_buf. So there are - * three bytes in sym_buf for every four bytes in pending_buf. Each symbol - * in sym_buf is three bytes -- two for the distance and one for the - * literal/length. As each symbol is consumed, the pointer to the next - * sym_buf value to read moves forward three bytes. From that symbol, up to - * 31 bits are written to pending_buf. The closest the written pending_buf - * bits gets to the next sym_buf symbol to read is just before the last - * code is written. At that time, 31*(n-2) bits have been written, just - * after 24*(n-2) bits have been consumed from sym_buf. sym_buf starts at - * 8*n bits into pending_buf. (Note that the symbol buffer fills when n-1 - * symbols are written.) The closest the writing gets to what is unread is - * then n+14 bits. Here n is lit_bufsize, which is 16384 by default, and - * can range from 128 to 32768. - * - * Therefore, at a minimum, there are 142 bits of space between what is - * written and what is read in the overlain buffers, so the symbols cannot - * be overwritten by the compressed data. That space is actually 139 bits, - * due to the three-bit fixed-code block header. - * - * That covers the case where either Z_FIXED is specified, forcing fixed - * codes, or when the use of fixed codes is chosen, because that choice - * results in a smaller compressed block than dynamic codes. That latter - * condition then assures that the above analysis also covers all dynamic - * blocks. A dynamic-code block will only be chosen to be emitted if it has - * fewer bits than a fixed-code block would for the same set of symbols. - * Therefore its average symbol length is assured to be less than 31. So - * the compressed data for a dynamic block also cannot overwrite the - * symbols from which it is being constructed. - */ - - s.pending_buf_size = s.lit_bufsize * 4; - s.pending_buf = new Uint8Array(s.pending_buf_size); - - // It is offset from `s.pending_buf` (size is `s.lit_bufsize * 2`) - //s->sym_buf = s->pending_buf + s->lit_bufsize; - s.sym_buf = s.lit_bufsize; - - //s->sym_end = (s->lit_bufsize - 1) * 3; - s.sym_end = (s.lit_bufsize - 1) * 3; - /* We avoid equality with lit_bufsize*3 because of wraparound at 64K - * on 16 bit machines and because stored blocks are restricted to - * 64K-1 bytes. - */ - - s.level = level; - s.strategy = strategy; - s.method = method; - - return deflateReset(strm); -}; - -const deflateInit = (strm, level) => { - - return deflateInit2(strm, level, Z_DEFLATED$2, MAX_WBITS$1, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY$1); -}; - - -/* ========================================================================= */ -const deflate$2 = (strm, flush) => { - - if (deflateStateCheck(strm) || flush > Z_BLOCK$1 || flush < 0) { - return strm ? err(strm, Z_STREAM_ERROR$2) : Z_STREAM_ERROR$2; - } - - const s = strm.state; - - if (!strm.output || - (strm.avail_in !== 0 && !strm.input) || - (s.status === FINISH_STATE && flush !== Z_FINISH$3)) { - return err(strm, (strm.avail_out === 0) ? Z_BUF_ERROR$1 : Z_STREAM_ERROR$2); - } - - const old_flush = s.last_flush; - s.last_flush = flush; - - /* Flush as much pending output as possible */ - if (s.pending !== 0) { - flush_pending(strm); - if (strm.avail_out === 0) { - /* Since avail_out is 0, deflate will be called again with - * more output space, but possibly with both pending and - * avail_in equal to zero. There won't be anything to do, - * but this is not an error situation so make sure we - * return OK instead of BUF_ERROR at next call of deflate: - */ - s.last_flush = -1; - return Z_OK$3; - } - - /* Make sure there is something to do and avoid duplicate consecutive - * flushes. For repeated and useless calls with Z_FINISH, we keep - * returning Z_STREAM_END instead of Z_BUF_ERROR. - */ - } else if (strm.avail_in === 0 && rank(flush) <= rank(old_flush) && - flush !== Z_FINISH$3) { - return err(strm, Z_BUF_ERROR$1); - } - - /* User must not provide more input after the first FINISH: */ - if (s.status === FINISH_STATE && strm.avail_in !== 0) { - return err(strm, Z_BUF_ERROR$1); - } - - /* Write the header */ - if (s.status === INIT_STATE && s.wrap === 0) { - s.status = BUSY_STATE; - } - if (s.status === INIT_STATE) { - /* zlib header */ - let header = (Z_DEFLATED$2 + ((s.w_bits - 8) << 4)) << 8; - let level_flags = -1; - - if (s.strategy >= Z_HUFFMAN_ONLY || s.level < 2) { - level_flags = 0; - } else if (s.level < 6) { - level_flags = 1; - } else if (s.level === 6) { - level_flags = 2; - } else { - level_flags = 3; - } - header |= (level_flags << 6); - if (s.strstart !== 0) { header |= PRESET_DICT; } - header += 31 - (header % 31); - - putShortMSB(s, header); - - /* Save the adler32 of the preset dictionary: */ - if (s.strstart !== 0) { - putShortMSB(s, strm.adler >>> 16); - putShortMSB(s, strm.adler & 0xffff); - } - strm.adler = 1; // adler32(0L, Z_NULL, 0); - s.status = BUSY_STATE; - - /* Compression must start with an empty pending buffer */ - flush_pending(strm); - if (s.pending !== 0) { - s.last_flush = -1; - return Z_OK$3; - } - } -//#ifdef GZIP - if (s.status === GZIP_STATE) { - /* gzip header */ - strm.adler = 0; //crc32(0L, Z_NULL, 0); - put_byte(s, 31); - put_byte(s, 139); - put_byte(s, 8); - if (!s.gzhead) { // s->gzhead == Z_NULL - put_byte(s, 0); - put_byte(s, 0); - put_byte(s, 0); - put_byte(s, 0); - put_byte(s, 0); - put_byte(s, s.level === 9 ? 2 : - (s.strategy >= Z_HUFFMAN_ONLY || s.level < 2 ? - 4 : 0)); - put_byte(s, OS_CODE); - s.status = BUSY_STATE; - - /* Compression must start with an empty pending buffer */ - flush_pending(strm); - if (s.pending !== 0) { - s.last_flush = -1; - return Z_OK$3; - } - } - else { - put_byte(s, (s.gzhead.text ? 1 : 0) + - (s.gzhead.hcrc ? 2 : 0) + - (!s.gzhead.extra ? 0 : 4) + - (!s.gzhead.name ? 0 : 8) + - (!s.gzhead.comment ? 0 : 16) - ); - put_byte(s, s.gzhead.time & 0xff); - put_byte(s, (s.gzhead.time >> 8) & 0xff); - put_byte(s, (s.gzhead.time >> 16) & 0xff); - put_byte(s, (s.gzhead.time >> 24) & 0xff); - put_byte(s, s.level === 9 ? 2 : - (s.strategy >= Z_HUFFMAN_ONLY || s.level < 2 ? - 4 : 0)); - put_byte(s, s.gzhead.os & 0xff); - if (s.gzhead.extra && s.gzhead.extra.length) { - put_byte(s, s.gzhead.extra.length & 0xff); - put_byte(s, (s.gzhead.extra.length >> 8) & 0xff); - } - if (s.gzhead.hcrc) { - strm.adler = crc32_1(strm.adler, s.pending_buf, s.pending, 0); - } - s.gzindex = 0; - s.status = EXTRA_STATE; - } - } - if (s.status === EXTRA_STATE) { - if (s.gzhead.extra/* != Z_NULL*/) { - let beg = s.pending; /* start of bytes to update crc */ - let left = (s.gzhead.extra.length & 0xffff) - s.gzindex; - while (s.pending + left > s.pending_buf_size) { - let copy = s.pending_buf_size - s.pending; - // zmemcpy(s.pending_buf + s.pending, - // s.gzhead.extra + s.gzindex, copy); - s.pending_buf.set(s.gzhead.extra.subarray(s.gzindex, s.gzindex + copy), s.pending); - s.pending = s.pending_buf_size; - //--- HCRC_UPDATE(beg) ---// - if (s.gzhead.hcrc && s.pending > beg) { - strm.adler = crc32_1(strm.adler, s.pending_buf, s.pending - beg, beg); - } - //---// - s.gzindex += copy; - flush_pending(strm); - if (s.pending !== 0) { - s.last_flush = -1; - return Z_OK$3; - } - beg = 0; - left -= copy; - } - // JS specific: s.gzhead.extra may be TypedArray or Array for backward compatibility - // TypedArray.slice and TypedArray.from don't exist in IE10-IE11 - let gzhead_extra = new Uint8Array(s.gzhead.extra); - // zmemcpy(s->pending_buf + s->pending, - // s->gzhead->extra + s->gzindex, left); - s.pending_buf.set(gzhead_extra.subarray(s.gzindex, s.gzindex + left), s.pending); - s.pending += left; - //--- HCRC_UPDATE(beg) ---// - if (s.gzhead.hcrc && s.pending > beg) { - strm.adler = crc32_1(strm.adler, s.pending_buf, s.pending - beg, beg); - } - //---// - s.gzindex = 0; - } - s.status = NAME_STATE; - } - if (s.status === NAME_STATE) { - if (s.gzhead.name/* != Z_NULL*/) { - let beg = s.pending; /* start of bytes to update crc */ - let val; - do { - if (s.pending === s.pending_buf_size) { - //--- HCRC_UPDATE(beg) ---// - if (s.gzhead.hcrc && s.pending > beg) { - strm.adler = crc32_1(strm.adler, s.pending_buf, s.pending - beg, beg); - } - //---// - flush_pending(strm); - if (s.pending !== 0) { - s.last_flush = -1; - return Z_OK$3; - } - beg = 0; - } - // JS specific: little magic to add zero terminator to end of string - if (s.gzindex < s.gzhead.name.length) { - val = s.gzhead.name.charCodeAt(s.gzindex++) & 0xff; - } else { - val = 0; - } - put_byte(s, val); - } while (val !== 0); - //--- HCRC_UPDATE(beg) ---// - if (s.gzhead.hcrc && s.pending > beg) { - strm.adler = crc32_1(strm.adler, s.pending_buf, s.pending - beg, beg); - } - //---// - s.gzindex = 0; - } - s.status = COMMENT_STATE; - } - if (s.status === COMMENT_STATE) { - if (s.gzhead.comment/* != Z_NULL*/) { - let beg = s.pending; /* start of bytes to update crc */ - let val; - do { - if (s.pending === s.pending_buf_size) { - //--- HCRC_UPDATE(beg) ---// - if (s.gzhead.hcrc && s.pending > beg) { - strm.adler = crc32_1(strm.adler, s.pending_buf, s.pending - beg, beg); - } - //---// - flush_pending(strm); - if (s.pending !== 0) { - s.last_flush = -1; - return Z_OK$3; - } - beg = 0; - } - // JS specific: little magic to add zero terminator to end of string - if (s.gzindex < s.gzhead.comment.length) { - val = s.gzhead.comment.charCodeAt(s.gzindex++) & 0xff; - } else { - val = 0; - } - put_byte(s, val); - } while (val !== 0); - //--- HCRC_UPDATE(beg) ---// - if (s.gzhead.hcrc && s.pending > beg) { - strm.adler = crc32_1(strm.adler, s.pending_buf, s.pending - beg, beg); - } - //---// - } - s.status = HCRC_STATE; - } - if (s.status === HCRC_STATE) { - if (s.gzhead.hcrc) { - if (s.pending + 2 > s.pending_buf_size) { - flush_pending(strm); - if (s.pending !== 0) { - s.last_flush = -1; - return Z_OK$3; - } - } - put_byte(s, strm.adler & 0xff); - put_byte(s, (strm.adler >> 8) & 0xff); - strm.adler = 0; //crc32(0L, Z_NULL, 0); - } - s.status = BUSY_STATE; - - /* Compression must start with an empty pending buffer */ - flush_pending(strm); - if (s.pending !== 0) { - s.last_flush = -1; - return Z_OK$3; - } - } -//#endif - - /* Start a new block or continue the current one. - */ - if (strm.avail_in !== 0 || s.lookahead !== 0 || - (flush !== Z_NO_FLUSH$2 && s.status !== FINISH_STATE)) { - let bstate = s.level === 0 ? deflate_stored(s, flush) : - s.strategy === Z_HUFFMAN_ONLY ? deflate_huff(s, flush) : - s.strategy === Z_RLE ? deflate_rle(s, flush) : - configuration_table[s.level].func(s, flush); - - if (bstate === BS_FINISH_STARTED || bstate === BS_FINISH_DONE) { - s.status = FINISH_STATE; - } - if (bstate === BS_NEED_MORE || bstate === BS_FINISH_STARTED) { - if (strm.avail_out === 0) { - s.last_flush = -1; - /* avoid BUF_ERROR next call, see above */ - } - return Z_OK$3; - /* If flush != Z_NO_FLUSH && avail_out == 0, the next call - * of deflate should use the same flush parameter to make sure - * that the flush is complete. So we don't have to output an - * empty block here, this will be done at next call. This also - * ensures that for a very small output buffer, we emit at most - * one empty block. - */ - } - if (bstate === BS_BLOCK_DONE) { - if (flush === Z_PARTIAL_FLUSH) { - _tr_align(s); - } - else if (flush !== Z_BLOCK$1) { /* FULL_FLUSH or SYNC_FLUSH */ - - _tr_stored_block(s, 0, 0, false); - /* For a full flush, this empty block will be recognized - * as a special marker by inflate_sync(). - */ - if (flush === Z_FULL_FLUSH$1) { - /*** CLEAR_HASH(s); ***/ /* forget history */ - zero(s.head); // Fill with NIL (= 0); - - if (s.lookahead === 0) { - s.strstart = 0; - s.block_start = 0; - s.insert = 0; - } - } - } - flush_pending(strm); - if (strm.avail_out === 0) { - s.last_flush = -1; /* avoid BUF_ERROR at next call, see above */ - return Z_OK$3; - } - } - } - - if (flush !== Z_FINISH$3) { return Z_OK$3; } - if (s.wrap <= 0) { return Z_STREAM_END$3; } - - /* Write the trailer */ - if (s.wrap === 2) { - put_byte(s, strm.adler & 0xff); - put_byte(s, (strm.adler >> 8) & 0xff); - put_byte(s, (strm.adler >> 16) & 0xff); - put_byte(s, (strm.adler >> 24) & 0xff); - put_byte(s, strm.total_in & 0xff); - put_byte(s, (strm.total_in >> 8) & 0xff); - put_byte(s, (strm.total_in >> 16) & 0xff); - put_byte(s, (strm.total_in >> 24) & 0xff); - } - else - { - putShortMSB(s, strm.adler >>> 16); - putShortMSB(s, strm.adler & 0xffff); - } - - flush_pending(strm); - /* If avail_out is zero, the application will call deflate again - * to flush the rest. - */ - if (s.wrap > 0) { s.wrap = -s.wrap; } - /* write the trailer only once! */ - return s.pending !== 0 ? Z_OK$3 : Z_STREAM_END$3; -}; - - -const deflateEnd = (strm) => { - - if (deflateStateCheck(strm)) { - return Z_STREAM_ERROR$2; - } - - const status = strm.state.status; - - strm.state = null; - - return status === BUSY_STATE ? err(strm, Z_DATA_ERROR$2) : Z_OK$3; -}; - - -/* ========================================================================= - * Initializes the compression dictionary from the given byte - * sequence without producing any compressed output. - */ -const deflateSetDictionary = (strm, dictionary) => { - - let dictLength = dictionary.length; - - if (deflateStateCheck(strm)) { - return Z_STREAM_ERROR$2; - } - - const s = strm.state; - const wrap = s.wrap; - - if (wrap === 2 || (wrap === 1 && s.status !== INIT_STATE) || s.lookahead) { - return Z_STREAM_ERROR$2; - } - - /* when using zlib wrappers, compute Adler-32 for provided dictionary */ - if (wrap === 1) { - /* adler32(strm->adler, dictionary, dictLength); */ - strm.adler = adler32_1(strm.adler, dictionary, dictLength, 0); - } - - s.wrap = 0; /* avoid computing Adler-32 in read_buf */ - - /* if dictionary would fill window, just replace the history */ - if (dictLength >= s.w_size) { - if (wrap === 0) { /* already empty otherwise */ - /*** CLEAR_HASH(s); ***/ - zero(s.head); // Fill with NIL (= 0); - s.strstart = 0; - s.block_start = 0; - s.insert = 0; - } - /* use the tail */ - // dictionary = dictionary.slice(dictLength - s.w_size); - let tmpDict = new Uint8Array(s.w_size); - tmpDict.set(dictionary.subarray(dictLength - s.w_size, dictLength), 0); - dictionary = tmpDict; - dictLength = s.w_size; - } - /* insert dictionary into window and hash */ - const avail = strm.avail_in; - const next = strm.next_in; - const input = strm.input; - strm.avail_in = dictLength; - strm.next_in = 0; - strm.input = dictionary; - fill_window(s); - while (s.lookahead >= MIN_MATCH) { - let str = s.strstart; - let n = s.lookahead - (MIN_MATCH - 1); - do { - /* UPDATE_HASH(s, s->ins_h, s->window[str + MIN_MATCH-1]); */ - s.ins_h = HASH(s, s.ins_h, s.window[str + MIN_MATCH - 1]); - - s.prev[str & s.w_mask] = s.head[s.ins_h]; - - s.head[s.ins_h] = str; - str++; - } while (--n); - s.strstart = str; - s.lookahead = MIN_MATCH - 1; - fill_window(s); - } - s.strstart += s.lookahead; - s.block_start = s.strstart; - s.insert = s.lookahead; - s.lookahead = 0; - s.match_length = s.prev_length = MIN_MATCH - 1; - s.match_available = 0; - strm.next_in = next; - strm.input = input; - strm.avail_in = avail; - s.wrap = wrap; - return Z_OK$3; -}; - - -var deflateInit_1 = deflateInit; -var deflateInit2_1 = deflateInit2; -var deflateReset_1 = deflateReset; -var deflateResetKeep_1 = deflateResetKeep; -var deflateSetHeader_1 = deflateSetHeader; -var deflate_2$1 = deflate$2; -var deflateEnd_1 = deflateEnd; -var deflateSetDictionary_1 = deflateSetDictionary; -var deflateInfo = 'pako deflate (from Nodeca project)'; - -/* Not implemented -module.exports.deflateBound = deflateBound; -module.exports.deflateCopy = deflateCopy; -module.exports.deflateGetDictionary = deflateGetDictionary; -module.exports.deflateParams = deflateParams; -module.exports.deflatePending = deflatePending; -module.exports.deflatePrime = deflatePrime; -module.exports.deflateTune = deflateTune; -*/ - -var deflate_1$2 = { - deflateInit: deflateInit_1, - deflateInit2: deflateInit2_1, - deflateReset: deflateReset_1, - deflateResetKeep: deflateResetKeep_1, - deflateSetHeader: deflateSetHeader_1, - deflate: deflate_2$1, - deflateEnd: deflateEnd_1, - deflateSetDictionary: deflateSetDictionary_1, - deflateInfo: deflateInfo -}; - -const _has = (obj, key) => { - return Object.prototype.hasOwnProperty.call(obj, key); -}; - -var assign = function (obj /*from1, from2, from3, ...*/) { - const sources = Array.prototype.slice.call(arguments, 1); - while (sources.length) { - const source = sources.shift(); - if (!source) { continue; } - - if (typeof source !== 'object') { - throw new TypeError(source + 'must be non-object'); - } - - for (const p in source) { - if (_has(source, p)) { - obj[p] = source[p]; - } - } - } - - return obj; -}; - - -// Join array of chunks to single array. -var flattenChunks = (chunks) => { - // calculate data length - let len = 0; - - for (let i = 0, l = chunks.length; i < l; i++) { - len += chunks[i].length; - } - - // join chunks - const result = new Uint8Array(len); - - for (let i = 0, pos = 0, l = chunks.length; i < l; i++) { - let chunk = chunks[i]; - result.set(chunk, pos); - pos += chunk.length; - } - - return result; -}; - -var common = { - assign: assign, - flattenChunks: flattenChunks -}; - -// String encode/decode helpers - - -// Quick check if we can use fast array to bin string conversion -// -// - apply(Array) can fail on Android 2.2 -// - apply(Uint8Array) can fail on iOS 5.1 Safari -// -let STR_APPLY_UIA_OK = true; - -try { String.fromCharCode.apply(null, new Uint8Array(1)); } catch (__) { STR_APPLY_UIA_OK = false; } - - -// Table with utf8 lengths (calculated by first byte of sequence) -// Note, that 5 & 6-byte values and some 4-byte values can not be represented in JS, -// because max possible codepoint is 0x10ffff -const _utf8len = new Uint8Array(256); -for (let q = 0; q < 256; q++) { - _utf8len[q] = (q >= 252 ? 6 : q >= 248 ? 5 : q >= 240 ? 4 : q >= 224 ? 3 : q >= 192 ? 2 : 1); -} -_utf8len[254] = _utf8len[254] = 1; // Invalid sequence start - - -// convert string to array (typed, when possible) -var string2buf = (str) => { - if (typeof TextEncoder === 'function' && TextEncoder.prototype.encode) { - return new TextEncoder().encode(str); - } - - let buf, c, c2, m_pos, i, str_len = str.length, buf_len = 0; - - // count binary size - for (m_pos = 0; m_pos < str_len; m_pos++) { - c = str.charCodeAt(m_pos); - if ((c & 0xfc00) === 0xd800 && (m_pos + 1 < str_len)) { - c2 = str.charCodeAt(m_pos + 1); - if ((c2 & 0xfc00) === 0xdc00) { - c = 0x10000 + ((c - 0xd800) << 10) + (c2 - 0xdc00); - m_pos++; - } - } - buf_len += c < 0x80 ? 1 : c < 0x800 ? 2 : c < 0x10000 ? 3 : 4; - } - - // allocate buffer - buf = new Uint8Array(buf_len); - - // convert - for (i = 0, m_pos = 0; i < buf_len; m_pos++) { - c = str.charCodeAt(m_pos); - if ((c & 0xfc00) === 0xd800 && (m_pos + 1 < str_len)) { - c2 = str.charCodeAt(m_pos + 1); - if ((c2 & 0xfc00) === 0xdc00) { - c = 0x10000 + ((c - 0xd800) << 10) + (c2 - 0xdc00); - m_pos++; - } - } - if (c < 0x80) { - /* one byte */ - buf[i++] = c; - } else if (c < 0x800) { - /* two bytes */ - buf[i++] = 0xC0 | (c >>> 6); - buf[i++] = 0x80 | (c & 0x3f); - } else if (c < 0x10000) { - /* three bytes */ - buf[i++] = 0xE0 | (c >>> 12); - buf[i++] = 0x80 | (c >>> 6 & 0x3f); - buf[i++] = 0x80 | (c & 0x3f); - } else { - /* four bytes */ - buf[i++] = 0xf0 | (c >>> 18); - buf[i++] = 0x80 | (c >>> 12 & 0x3f); - buf[i++] = 0x80 | (c >>> 6 & 0x3f); - buf[i++] = 0x80 | (c & 0x3f); - } - } - - return buf; -}; - -// Helper -const buf2binstring = (buf, len) => { - // On Chrome, the arguments in a function call that are allowed is `65534`. - // If the length of the buffer is smaller than that, we can use this optimization, - // otherwise we will take a slower path. - if (len < 65534) { - if (buf.subarray && STR_APPLY_UIA_OK) { - return String.fromCharCode.apply(null, buf.length === len ? buf : buf.subarray(0, len)); - } - } - - let result = ''; - for (let i = 0; i < len; i++) { - result += String.fromCharCode(buf[i]); - } - return result; -}; - - -// convert array to string -var buf2string = (buf, max) => { - const len = max || buf.length; - - if (typeof TextDecoder === 'function' && TextDecoder.prototype.decode) { - return new TextDecoder().decode(buf.subarray(0, max)); - } - - let i, out; - - // Reserve max possible length (2 words per char) - // NB: by unknown reasons, Array is significantly faster for - // String.fromCharCode.apply than Uint16Array. - const utf16buf = new Array(len * 2); - - for (out = 0, i = 0; i < len;) { - let c = buf[i++]; - // quick process ascii - if (c < 0x80) { utf16buf[out++] = c; continue; } - - let c_len = _utf8len[c]; - // skip 5 & 6 byte codes - if (c_len > 4) { utf16buf[out++] = 0xfffd; i += c_len - 1; continue; } - - // apply mask on first byte - c &= c_len === 2 ? 0x1f : c_len === 3 ? 0x0f : 0x07; - // join the rest - while (c_len > 1 && i < len) { - c = (c << 6) | (buf[i++] & 0x3f); - c_len--; - } - - // terminated by end of string? - if (c_len > 1) { utf16buf[out++] = 0xfffd; continue; } - - if (c < 0x10000) { - utf16buf[out++] = c; - } else { - c -= 0x10000; - utf16buf[out++] = 0xd800 | ((c >> 10) & 0x3ff); - utf16buf[out++] = 0xdc00 | (c & 0x3ff); - } - } - - return buf2binstring(utf16buf, out); -}; - - -// Calculate max possible position in utf8 buffer, -// that will not break sequence. If that's not possible -// - (very small limits) return max size as is. -// -// buf[] - utf8 bytes array -// max - length limit (mandatory); -var utf8border = (buf, max) => { - - max = max || buf.length; - if (max > buf.length) { max = buf.length; } - - // go back from last position, until start of sequence found - let pos = max - 1; - while (pos >= 0 && (buf[pos] & 0xC0) === 0x80) { pos--; } - - // Very small and broken sequence, - // return max, because we should return something anyway. - if (pos < 0) { return max; } - - // If we came to start of buffer - that means buffer is too small, - // return max too. - if (pos === 0) { return max; } - - return (pos + _utf8len[buf[pos]] > max) ? pos : max; -}; - -var strings = { - string2buf: string2buf, - buf2string: buf2string, - utf8border: utf8border -}; - -// (C) 1995-2013 Jean-loup Gailly and Mark Adler -// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin -// -// This software is provided 'as-is', without any express or implied -// warranty. In no event will the authors be held liable for any damages -// arising from the use of this software. -// -// Permission is granted to anyone to use this software for any purpose, -// including commercial applications, and to alter it and redistribute it -// freely, subject to the following restrictions: -// -// 1. The origin of this software must not be misrepresented; you must not -// claim that you wrote the original software. If you use this software -// in a product, an acknowledgment in the product documentation would be -// appreciated but is not required. -// 2. Altered source versions must be plainly marked as such, and must not be -// misrepresented as being the original software. -// 3. This notice may not be removed or altered from any source distribution. - -function ZStream() { - /* next input byte */ - this.input = null; // JS specific, because we have no pointers - this.next_in = 0; - /* number of bytes available at input */ - this.avail_in = 0; - /* total number of input bytes read so far */ - this.total_in = 0; - /* next output byte should be put there */ - this.output = null; // JS specific, because we have no pointers - this.next_out = 0; - /* remaining free space at output */ - this.avail_out = 0; - /* total number of bytes output so far */ - this.total_out = 0; - /* last error message, NULL if no error */ - this.msg = ''/*Z_NULL*/; - /* not visible by applications */ - this.state = null; - /* best guess about the data type: binary or text */ - this.data_type = 2/*Z_UNKNOWN*/; - /* adler32 value of the uncompressed data */ - this.adler = 0; -} - -var zstream = ZStream; - -const toString$1 = Object.prototype.toString; - -/* Public constants ==========================================================*/ -/* ===========================================================================*/ - -const { - Z_NO_FLUSH: Z_NO_FLUSH$1, Z_SYNC_FLUSH, Z_FULL_FLUSH, Z_FINISH: Z_FINISH$2, - Z_OK: Z_OK$2, Z_STREAM_END: Z_STREAM_END$2, - Z_DEFAULT_COMPRESSION, - Z_DEFAULT_STRATEGY, - Z_DEFLATED: Z_DEFLATED$1 -} = constants$2; - -/* ===========================================================================*/ - - -/** - * class Deflate - * - * Generic JS-style wrapper for zlib calls. If you don't need - * streaming behaviour - use more simple functions: [[deflate]], - * [[deflateRaw]] and [[gzip]]. - **/ - -/* internal - * Deflate.chunks -> Array - * - * Chunks of output data, if [[Deflate#onData]] not overridden. - **/ - -/** - * Deflate.result -> Uint8Array - * - * Compressed result, generated by default [[Deflate#onData]] - * and [[Deflate#onEnd]] handlers. Filled after you push last chunk - * (call [[Deflate#push]] with `Z_FINISH` / `true` param). - **/ - -/** - * Deflate.err -> Number - * - * Error code after deflate finished. 0 (Z_OK) on success. - * You will not need it in real life, because deflate errors - * are possible only on wrong options or bad `onData` / `onEnd` - * custom handlers. - **/ - -/** - * Deflate.msg -> String - * - * Error message, if [[Deflate.err]] != 0 - **/ - - -/** - * new Deflate(options) - * - options (Object): zlib deflate options. - * - * Creates new deflator instance with specified params. Throws exception - * on bad params. Supported options: - * - * - `level` - * - `windowBits` - * - `memLevel` - * - `strategy` - * - `dictionary` - * - * [http://zlib.net/manual.html#Advanced](http://zlib.net/manual.html#Advanced) - * for more information on these. - * - * Additional options, for internal needs: - * - * - `chunkSize` - size of generated data chunks (16K by default) - * - `raw` (Boolean) - do raw deflate - * - `gzip` (Boolean) - create gzip wrapper - * - `header` (Object) - custom header for gzip - * - `text` (Boolean) - true if compressed data believed to be text - * - `time` (Number) - modification time, unix timestamp - * - `os` (Number) - operation system code - * - `extra` (Array) - array of bytes with extra data (max 65536) - * - `name` (String) - file name (binary string) - * - `comment` (String) - comment (binary string) - * - `hcrc` (Boolean) - true if header crc should be added - * - * ##### Example: - * - * ```javascript - * const pako = require('pako') - * , chunk1 = new Uint8Array([1,2,3,4,5,6,7,8,9]) - * , chunk2 = new Uint8Array([10,11,12,13,14,15,16,17,18,19]); - * - * const deflate = new pako.Deflate({ level: 3}); - * - * deflate.push(chunk1, false); - * deflate.push(chunk2, true); // true -> last chunk - * - * if (deflate.err) { throw new Error(deflate.err); } - * - * console.log(deflate.result); - * ``` - **/ -function Deflate$1(options) { - this.options = common.assign({ - level: Z_DEFAULT_COMPRESSION, - method: Z_DEFLATED$1, - chunkSize: 16384, - windowBits: 15, - memLevel: 8, - strategy: Z_DEFAULT_STRATEGY - }, options || {}); - - let opt = this.options; - - if (opt.raw && (opt.windowBits > 0)) { - opt.windowBits = -opt.windowBits; - } - - else if (opt.gzip && (opt.windowBits > 0) && (opt.windowBits < 16)) { - opt.windowBits += 16; - } - - this.err = 0; // error code, if happens (0 = Z_OK) - this.msg = ''; // error message - this.ended = false; // used to avoid multiple onEnd() calls - this.chunks = []; // chunks of compressed data - - this.strm = new zstream(); - this.strm.avail_out = 0; - - let status = deflate_1$2.deflateInit2( - this.strm, - opt.level, - opt.method, - opt.windowBits, - opt.memLevel, - opt.strategy - ); - - if (status !== Z_OK$2) { - throw new Error(messages[status]); - } - - if (opt.header) { - deflate_1$2.deflateSetHeader(this.strm, opt.header); - } - - if (opt.dictionary) { - let dict; - // Convert data if needed - if (typeof opt.dictionary === 'string') { - // If we need to compress text, change encoding to utf8. - dict = strings.string2buf(opt.dictionary); - } else if (toString$1.call(opt.dictionary) === '[object ArrayBuffer]') { - dict = new Uint8Array(opt.dictionary); - } else { - dict = opt.dictionary; - } - - status = deflate_1$2.deflateSetDictionary(this.strm, dict); - - if (status !== Z_OK$2) { - throw new Error(messages[status]); - } - - this._dict_set = true; - } -} - -/** - * Deflate#push(data[, flush_mode]) -> Boolean - * - data (Uint8Array|ArrayBuffer|String): input data. Strings will be - * converted to utf8 byte sequence. - * - flush_mode (Number|Boolean): 0..6 for corresponding Z_NO_FLUSH..Z_TREE modes. - * See constants. Skipped or `false` means Z_NO_FLUSH, `true` means Z_FINISH. - * - * Sends input data to deflate pipe, generating [[Deflate#onData]] calls with - * new compressed chunks. Returns `true` on success. The last data block must - * have `flush_mode` Z_FINISH (or `true`). That will flush internal pending - * buffers and call [[Deflate#onEnd]]. - * - * On fail call [[Deflate#onEnd]] with error code and return false. - * - * ##### Example - * - * ```javascript - * push(chunk, false); // push one of data chunks - * ... - * push(chunk, true); // push last chunk - * ``` - **/ -Deflate$1.prototype.push = function (data, flush_mode) { - const strm = this.strm; - const chunkSize = this.options.chunkSize; - let status, _flush_mode; - - if (this.ended) { return false; } - - if (flush_mode === ~~flush_mode) _flush_mode = flush_mode; - else _flush_mode = flush_mode === true ? Z_FINISH$2 : Z_NO_FLUSH$1; - - // Convert data if needed - if (typeof data === 'string') { - // If we need to compress text, change encoding to utf8. - strm.input = strings.string2buf(data); - } else if (toString$1.call(data) === '[object ArrayBuffer]') { - strm.input = new Uint8Array(data); - } else { - strm.input = data; - } - - strm.next_in = 0; - strm.avail_in = strm.input.length; - - for (;;) { - if (strm.avail_out === 0) { - strm.output = new Uint8Array(chunkSize); - strm.next_out = 0; - strm.avail_out = chunkSize; - } - - // Make sure avail_out > 6 to avoid repeating markers - if ((_flush_mode === Z_SYNC_FLUSH || _flush_mode === Z_FULL_FLUSH) && strm.avail_out <= 6) { - this.onData(strm.output.subarray(0, strm.next_out)); - strm.avail_out = 0; - continue; - } - - status = deflate_1$2.deflate(strm, _flush_mode); - - // Ended => flush and finish - if (status === Z_STREAM_END$2) { - if (strm.next_out > 0) { - this.onData(strm.output.subarray(0, strm.next_out)); - } - status = deflate_1$2.deflateEnd(this.strm); - this.onEnd(status); - this.ended = true; - return status === Z_OK$2; - } - - // Flush if out buffer full - if (strm.avail_out === 0) { - this.onData(strm.output); - continue; - } - - // Flush if requested and has data - if (_flush_mode > 0 && strm.next_out > 0) { - this.onData(strm.output.subarray(0, strm.next_out)); - strm.avail_out = 0; - continue; - } - - if (strm.avail_in === 0) break; - } - - return true; -}; - - -/** - * Deflate#onData(chunk) -> Void - * - chunk (Uint8Array): output data. - * - * By default, stores data blocks in `chunks[]` property and glue - * those in `onEnd`. Override this handler, if you need another behaviour. - **/ -Deflate$1.prototype.onData = function (chunk) { - this.chunks.push(chunk); -}; - - -/** - * Deflate#onEnd(status) -> Void - * - status (Number): deflate status. 0 (Z_OK) on success, - * other if not. - * - * Called once after you tell deflate that the input stream is - * complete (Z_FINISH). By default - join collected chunks, - * free memory and fill `results` / `err` properties. - **/ -Deflate$1.prototype.onEnd = function (status) { - // On success - join - if (status === Z_OK$2) { - this.result = common.flattenChunks(this.chunks); - } - this.chunks = []; - this.err = status; - this.msg = this.strm.msg; -}; - - -/** - * deflate(data[, options]) -> Uint8Array - * - data (Uint8Array|ArrayBuffer|String): input data to compress. - * - options (Object): zlib deflate options. - * - * Compress `data` with deflate algorithm and `options`. - * - * Supported options are: - * - * - level - * - windowBits - * - memLevel - * - strategy - * - dictionary - * - * [http://zlib.net/manual.html#Advanced](http://zlib.net/manual.html#Advanced) - * for more information on these. - * - * Sugar (options): - * - * - `raw` (Boolean) - say that we work with raw stream, if you don't wish to specify - * negative windowBits implicitly. - * - * ##### Example: - * - * ```javascript - * const pako = require('pako') - * const data = new Uint8Array([1,2,3,4,5,6,7,8,9]); - * - * console.log(pako.deflate(data)); - * ``` - **/ -function deflate$1(input, options) { - const deflator = new Deflate$1(options); - - deflator.push(input, true); - - // That will never happens, if you don't cheat with options :) - if (deflator.err) { throw deflator.msg || messages[deflator.err]; } - - return deflator.result; -} - - -/** - * deflateRaw(data[, options]) -> Uint8Array - * - data (Uint8Array|ArrayBuffer|String): input data to compress. - * - options (Object): zlib deflate options. - * - * The same as [[deflate]], but creates raw data, without wrapper - * (header and adler32 crc). - **/ -function deflateRaw$1(input, options) { - options = options || {}; - options.raw = true; - return deflate$1(input, options); -} - - -/** - * gzip(data[, options]) -> Uint8Array - * - data (Uint8Array|ArrayBuffer|String): input data to compress. - * - options (Object): zlib deflate options. - * - * The same as [[deflate]], but create gzip wrapper instead of - * deflate one. - **/ -function gzip$1(input, options) { - options = options || {}; - options.gzip = true; - return deflate$1(input, options); -} - - -var Deflate_1$1 = Deflate$1; -var deflate_2 = deflate$1; -var deflateRaw_1$1 = deflateRaw$1; -var gzip_1$1 = gzip$1; -var constants$1 = constants$2; - -var deflate_1$1 = { - Deflate: Deflate_1$1, - deflate: deflate_2, - deflateRaw: deflateRaw_1$1, - gzip: gzip_1$1, - constants: constants$1 -}; - -// (C) 1995-2013 Jean-loup Gailly and Mark Adler -// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin -// -// This software is provided 'as-is', without any express or implied -// warranty. In no event will the authors be held liable for any damages -// arising from the use of this software. -// -// Permission is granted to anyone to use this software for any purpose, -// including commercial applications, and to alter it and redistribute it -// freely, subject to the following restrictions: -// -// 1. The origin of this software must not be misrepresented; you must not -// claim that you wrote the original software. If you use this software -// in a product, an acknowledgment in the product documentation would be -// appreciated but is not required. -// 2. Altered source versions must be plainly marked as such, and must not be -// misrepresented as being the original software. -// 3. This notice may not be removed or altered from any source distribution. - -// See state defs from inflate.js -const BAD$1 = 16209; /* got a data error -- remain here until reset */ -const TYPE$1 = 16191; /* i: waiting for type bits, including last-flag bit */ - -/* - Decode literal, length, and distance codes and write out the resulting - literal and match bytes until either not enough input or output is - available, an end-of-block is encountered, or a data error is encountered. - When large enough input and output buffers are supplied to inflate(), for - example, a 16K input buffer and a 64K output buffer, more than 95% of the - inflate execution time is spent in this routine. - - Entry assumptions: - - state.mode === LEN - strm.avail_in >= 6 - strm.avail_out >= 258 - start >= strm.avail_out - state.bits < 8 - - On return, state.mode is one of: - - LEN -- ran out of enough output space or enough available input - TYPE -- reached end of block code, inflate() to interpret next block - BAD -- error in block data - - Notes: - - - The maximum input bits used by a length/distance pair is 15 bits for the - length code, 5 bits for the length extra, 15 bits for the distance code, - and 13 bits for the distance extra. This totals 48 bits, or six bytes. - Therefore if strm.avail_in >= 6, then there is enough input to avoid - checking for available input while decoding. - - - The maximum bytes that a single length/distance pair can output is 258 - bytes, which is the maximum length that can be coded. inflate_fast() - requires strm.avail_out >= 258 for each loop to avoid checking for - output space. - */ -var inffast = function inflate_fast(strm, start) { - let _in; /* local strm.input */ - let last; /* have enough input while in < last */ - let _out; /* local strm.output */ - let beg; /* inflate()'s initial strm.output */ - let end; /* while out < end, enough space available */ -//#ifdef INFLATE_STRICT - let dmax; /* maximum distance from zlib header */ -//#endif - let wsize; /* window size or zero if not using window */ - let whave; /* valid bytes in the window */ - let wnext; /* window write index */ - // Use `s_window` instead `window`, avoid conflict with instrumentation tools - let s_window; /* allocated sliding window, if wsize != 0 */ - let hold; /* local strm.hold */ - let bits; /* local strm.bits */ - let lcode; /* local strm.lencode */ - let dcode; /* local strm.distcode */ - let lmask; /* mask for first level of length codes */ - let dmask; /* mask for first level of distance codes */ - let here; /* retrieved table entry */ - let op; /* code bits, operation, extra bits, or */ - /* window position, window bytes to copy */ - let len; /* match length, unused bytes */ - let dist; /* match distance */ - let from; /* where to copy match from */ - let from_source; - - - let input, output; // JS specific, because we have no pointers - - /* copy state to local variables */ - const state = strm.state; - //here = state.here; - _in = strm.next_in; - input = strm.input; - last = _in + (strm.avail_in - 5); - _out = strm.next_out; - output = strm.output; - beg = _out - (start - strm.avail_out); - end = _out + (strm.avail_out - 257); -//#ifdef INFLATE_STRICT - dmax = state.dmax; -//#endif - wsize = state.wsize; - whave = state.whave; - wnext = state.wnext; - s_window = state.window; - hold = state.hold; - bits = state.bits; - lcode = state.lencode; - dcode = state.distcode; - lmask = (1 << state.lenbits) - 1; - dmask = (1 << state.distbits) - 1; - - - /* decode literals and length/distances until end-of-block or not enough - input data or output space */ - - top: - do { - if (bits < 15) { - hold += input[_in++] << bits; - bits += 8; - hold += input[_in++] << bits; - bits += 8; - } - - here = lcode[hold & lmask]; - - dolen: - for (;;) { // Goto emulation - op = here >>> 24/*here.bits*/; - hold >>>= op; - bits -= op; - op = (here >>> 16) & 0xff/*here.op*/; - if (op === 0) { /* literal */ - //Tracevv((stderr, here.val >= 0x20 && here.val < 0x7f ? - // "inflate: literal '%c'\n" : - // "inflate: literal 0x%02x\n", here.val)); - output[_out++] = here & 0xffff/*here.val*/; - } - else if (op & 16) { /* length base */ - len = here & 0xffff/*here.val*/; - op &= 15; /* number of extra bits */ - if (op) { - if (bits < op) { - hold += input[_in++] << bits; - bits += 8; - } - len += hold & ((1 << op) - 1); - hold >>>= op; - bits -= op; - } - //Tracevv((stderr, "inflate: length %u\n", len)); - if (bits < 15) { - hold += input[_in++] << bits; - bits += 8; - hold += input[_in++] << bits; - bits += 8; - } - here = dcode[hold & dmask]; - - dodist: - for (;;) { // goto emulation - op = here >>> 24/*here.bits*/; - hold >>>= op; - bits -= op; - op = (here >>> 16) & 0xff/*here.op*/; - - if (op & 16) { /* distance base */ - dist = here & 0xffff/*here.val*/; - op &= 15; /* number of extra bits */ - if (bits < op) { - hold += input[_in++] << bits; - bits += 8; - if (bits < op) { - hold += input[_in++] << bits; - bits += 8; - } - } - dist += hold & ((1 << op) - 1); -//#ifdef INFLATE_STRICT - if (dist > dmax) { - strm.msg = 'invalid distance too far back'; - state.mode = BAD$1; - break top; - } -//#endif - hold >>>= op; - bits -= op; - //Tracevv((stderr, "inflate: distance %u\n", dist)); - op = _out - beg; /* max distance in output */ - if (dist > op) { /* see if copy from window */ - op = dist - op; /* distance back in window */ - if (op > whave) { - if (state.sane) { - strm.msg = 'invalid distance too far back'; - state.mode = BAD$1; - break top; - } - -// (!) This block is disabled in zlib defaults, -// don't enable it for binary compatibility -//#ifdef INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR -// if (len <= op - whave) { -// do { -// output[_out++] = 0; -// } while (--len); -// continue top; -// } -// len -= op - whave; -// do { -// output[_out++] = 0; -// } while (--op > whave); -// if (op === 0) { -// from = _out - dist; -// do { -// output[_out++] = output[from++]; -// } while (--len); -// continue top; -// } -//#endif - } - from = 0; // window index - from_source = s_window; - if (wnext === 0) { /* very common case */ - from += wsize - op; - if (op < len) { /* some from window */ - len -= op; - do { - output[_out++] = s_window[from++]; - } while (--op); - from = _out - dist; /* rest from output */ - from_source = output; - } - } - else if (wnext < op) { /* wrap around window */ - from += wsize + wnext - op; - op -= wnext; - if (op < len) { /* some from end of window */ - len -= op; - do { - output[_out++] = s_window[from++]; - } while (--op); - from = 0; - if (wnext < len) { /* some from start of window */ - op = wnext; - len -= op; - do { - output[_out++] = s_window[from++]; - } while (--op); - from = _out - dist; /* rest from output */ - from_source = output; - } - } - } - else { /* contiguous in window */ - from += wnext - op; - if (op < len) { /* some from window */ - len -= op; - do { - output[_out++] = s_window[from++]; - } while (--op); - from = _out - dist; /* rest from output */ - from_source = output; - } - } - while (len > 2) { - output[_out++] = from_source[from++]; - output[_out++] = from_source[from++]; - output[_out++] = from_source[from++]; - len -= 3; - } - if (len) { - output[_out++] = from_source[from++]; - if (len > 1) { - output[_out++] = from_source[from++]; - } - } - } - else { - from = _out - dist; /* copy direct from output */ - do { /* minimum length is three */ - output[_out++] = output[from++]; - output[_out++] = output[from++]; - output[_out++] = output[from++]; - len -= 3; - } while (len > 2); - if (len) { - output[_out++] = output[from++]; - if (len > 1) { - output[_out++] = output[from++]; - } - } - } - } - else if ((op & 64) === 0) { /* 2nd level distance code */ - here = dcode[(here & 0xffff)/*here.val*/ + (hold & ((1 << op) - 1))]; - continue dodist; - } - else { - strm.msg = 'invalid distance code'; - state.mode = BAD$1; - break top; - } - - break; // need to emulate goto via "continue" - } - } - else if ((op & 64) === 0) { /* 2nd level length code */ - here = lcode[(here & 0xffff)/*here.val*/ + (hold & ((1 << op) - 1))]; - continue dolen; - } - else if (op & 32) { /* end-of-block */ - //Tracevv((stderr, "inflate: end of block\n")); - state.mode = TYPE$1; - break top; - } - else { - strm.msg = 'invalid literal/length code'; - state.mode = BAD$1; - break top; - } - - break; // need to emulate goto via "continue" - } - } while (_in < last && _out < end); - - /* return unused bytes (on entry, bits < 8, so in won't go too far back) */ - len = bits >> 3; - _in -= len; - bits -= len << 3; - hold &= (1 << bits) - 1; - - /* update state and return */ - strm.next_in = _in; - strm.next_out = _out; - strm.avail_in = (_in < last ? 5 + (last - _in) : 5 - (_in - last)); - strm.avail_out = (_out < end ? 257 + (end - _out) : 257 - (_out - end)); - state.hold = hold; - state.bits = bits; - return; -}; - -// (C) 1995-2013 Jean-loup Gailly and Mark Adler -// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin -// -// This software is provided 'as-is', without any express or implied -// warranty. In no event will the authors be held liable for any damages -// arising from the use of this software. -// -// Permission is granted to anyone to use this software for any purpose, -// including commercial applications, and to alter it and redistribute it -// freely, subject to the following restrictions: -// -// 1. The origin of this software must not be misrepresented; you must not -// claim that you wrote the original software. If you use this software -// in a product, an acknowledgment in the product documentation would be -// appreciated but is not required. -// 2. Altered source versions must be plainly marked as such, and must not be -// misrepresented as being the original software. -// 3. This notice may not be removed or altered from any source distribution. - -const MAXBITS = 15; -const ENOUGH_LENS$1 = 852; -const ENOUGH_DISTS$1 = 592; -//const ENOUGH = (ENOUGH_LENS+ENOUGH_DISTS); - -const CODES$1 = 0; -const LENS$1 = 1; -const DISTS$1 = 2; - -const lbase = new Uint16Array([ /* Length codes 257..285 base */ - 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, - 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0 -]); - -const lext = new Uint8Array([ /* Length codes 257..285 extra */ - 16, 16, 16, 16, 16, 16, 16, 16, 17, 17, 17, 17, 18, 18, 18, 18, - 19, 19, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 16, 72, 78 -]); - -const dbase = new Uint16Array([ /* Distance codes 0..29 base */ - 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, - 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, - 8193, 12289, 16385, 24577, 0, 0 -]); - -const dext = new Uint8Array([ /* Distance codes 0..29 extra */ - 16, 16, 16, 16, 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, - 23, 23, 24, 24, 25, 25, 26, 26, 27, 27, - 28, 28, 29, 29, 64, 64 -]); - -const inflate_table = (type, lens, lens_index, codes, table, table_index, work, opts) => -{ - const bits = opts.bits; - //here = opts.here; /* table entry for duplication */ - - let len = 0; /* a code's length in bits */ - let sym = 0; /* index of code symbols */ - let min = 0, max = 0; /* minimum and maximum code lengths */ - let root = 0; /* number of index bits for root table */ - let curr = 0; /* number of index bits for current table */ - let drop = 0; /* code bits to drop for sub-table */ - let left = 0; /* number of prefix codes available */ - let used = 0; /* code entries in table used */ - let huff = 0; /* Huffman code */ - let incr; /* for incrementing code, index */ - let fill; /* index for replicating entries */ - let low; /* low bits for current root entry */ - let mask; /* mask for low root bits */ - let next; /* next available space in table */ - let base = null; /* base value table to use */ -// let shoextra; /* extra bits table to use */ - let match; /* use base and extra for symbol >= match */ - const count = new Uint16Array(MAXBITS + 1); //[MAXBITS+1]; /* number of codes of each length */ - const offs = new Uint16Array(MAXBITS + 1); //[MAXBITS+1]; /* offsets in table for each length */ - let extra = null; - - let here_bits, here_op, here_val; - - /* - Process a set of code lengths to create a canonical Huffman code. The - code lengths are lens[0..codes-1]. Each length corresponds to the - symbols 0..codes-1. The Huffman code is generated by first sorting the - symbols by length from short to long, and retaining the symbol order - for codes with equal lengths. Then the code starts with all zero bits - for the first code of the shortest length, and the codes are integer - increments for the same length, and zeros are appended as the length - increases. For the deflate format, these bits are stored backwards - from their more natural integer increment ordering, and so when the - decoding tables are built in the large loop below, the integer codes - are incremented backwards. - - This routine assumes, but does not check, that all of the entries in - lens[] are in the range 0..MAXBITS. The caller must assure this. - 1..MAXBITS is interpreted as that code length. zero means that that - symbol does not occur in this code. - - The codes are sorted by computing a count of codes for each length, - creating from that a table of starting indices for each length in the - sorted table, and then entering the symbols in order in the sorted - table. The sorted table is work[], with that space being provided by - the caller. - - The length counts are used for other purposes as well, i.e. finding - the minimum and maximum length codes, determining if there are any - codes at all, checking for a valid set of lengths, and looking ahead - at length counts to determine sub-table sizes when building the - decoding tables. - */ - - /* accumulate lengths for codes (assumes lens[] all in 0..MAXBITS) */ - for (len = 0; len <= MAXBITS; len++) { - count[len] = 0; - } - for (sym = 0; sym < codes; sym++) { - count[lens[lens_index + sym]]++; - } - - /* bound code lengths, force root to be within code lengths */ - root = bits; - for (max = MAXBITS; max >= 1; max--) { - if (count[max] !== 0) { break; } - } - if (root > max) { - root = max; - } - if (max === 0) { /* no symbols to code at all */ - //table.op[opts.table_index] = 64; //here.op = (var char)64; /* invalid code marker */ - //table.bits[opts.table_index] = 1; //here.bits = (var char)1; - //table.val[opts.table_index++] = 0; //here.val = (var short)0; - table[table_index++] = (1 << 24) | (64 << 16) | 0; - - - //table.op[opts.table_index] = 64; - //table.bits[opts.table_index] = 1; - //table.val[opts.table_index++] = 0; - table[table_index++] = (1 << 24) | (64 << 16) | 0; - - opts.bits = 1; - return 0; /* no symbols, but wait for decoding to report error */ - } - for (min = 1; min < max; min++) { - if (count[min] !== 0) { break; } - } - if (root < min) { - root = min; - } - - /* check for an over-subscribed or incomplete set of lengths */ - left = 1; - for (len = 1; len <= MAXBITS; len++) { - left <<= 1; - left -= count[len]; - if (left < 0) { - return -1; - } /* over-subscribed */ - } - if (left > 0 && (type === CODES$1 || max !== 1)) { - return -1; /* incomplete set */ - } - - /* generate offsets into symbol table for each length for sorting */ - offs[1] = 0; - for (len = 1; len < MAXBITS; len++) { - offs[len + 1] = offs[len] + count[len]; - } - - /* sort symbols by length, by symbol order within each length */ - for (sym = 0; sym < codes; sym++) { - if (lens[lens_index + sym] !== 0) { - work[offs[lens[lens_index + sym]]++] = sym; - } - } - - /* - Create and fill in decoding tables. In this loop, the table being - filled is at next and has curr index bits. The code being used is huff - with length len. That code is converted to an index by dropping drop - bits off of the bottom. For codes where len is less than drop + curr, - those top drop + curr - len bits are incremented through all values to - fill the table with replicated entries. - - root is the number of index bits for the root table. When len exceeds - root, sub-tables are created pointed to by the root entry with an index - of the low root bits of huff. This is saved in low to check for when a - new sub-table should be started. drop is zero when the root table is - being filled, and drop is root when sub-tables are being filled. - - When a new sub-table is needed, it is necessary to look ahead in the - code lengths to determine what size sub-table is needed. The length - counts are used for this, and so count[] is decremented as codes are - entered in the tables. - - used keeps track of how many table entries have been allocated from the - provided *table space. It is checked for LENS and DIST tables against - the constants ENOUGH_LENS and ENOUGH_DISTS to guard against changes in - the initial root table size constants. See the comments in inftrees.h - for more information. - - sym increments through all symbols, and the loop terminates when - all codes of length max, i.e. all codes, have been processed. This - routine permits incomplete codes, so another loop after this one fills - in the rest of the decoding tables with invalid code markers. - */ - - /* set up for code type */ - // poor man optimization - use if-else instead of switch, - // to avoid deopts in old v8 - if (type === CODES$1) { - base = extra = work; /* dummy value--not used */ - match = 20; - - } else if (type === LENS$1) { - base = lbase; - extra = lext; - match = 257; - - } else { /* DISTS */ - base = dbase; - extra = dext; - match = 0; - } - - /* initialize opts for loop */ - huff = 0; /* starting code */ - sym = 0; /* starting code symbol */ - len = min; /* starting code length */ - next = table_index; /* current table to fill in */ - curr = root; /* current table index bits */ - drop = 0; /* current bits to drop from code for index */ - low = -1; /* trigger new sub-table when len > root */ - used = 1 << root; /* use root table entries */ - mask = used - 1; /* mask for comparing low */ - - /* check available table space */ - if ((type === LENS$1 && used > ENOUGH_LENS$1) || - (type === DISTS$1 && used > ENOUGH_DISTS$1)) { - return 1; - } - - /* process all codes and make table entries */ - for (;;) { - /* create table entry */ - here_bits = len - drop; - if (work[sym] + 1 < match) { - here_op = 0; - here_val = work[sym]; - } - else if (work[sym] >= match) { - here_op = extra[work[sym] - match]; - here_val = base[work[sym] - match]; - } - else { - here_op = 32 + 64; /* end of block */ - here_val = 0; - } - - /* replicate for those indices with low len bits equal to huff */ - incr = 1 << (len - drop); - fill = 1 << curr; - min = fill; /* save offset to next table */ - do { - fill -= incr; - table[next + (huff >> drop) + fill] = (here_bits << 24) | (here_op << 16) | here_val |0; - } while (fill !== 0); - - /* backwards increment the len-bit code huff */ - incr = 1 << (len - 1); - while (huff & incr) { - incr >>= 1; - } - if (incr !== 0) { - huff &= incr - 1; - huff += incr; - } else { - huff = 0; - } - - /* go to next symbol, update count, len */ - sym++; - if (--count[len] === 0) { - if (len === max) { break; } - len = lens[lens_index + work[sym]]; - } - - /* create new sub-table if needed */ - if (len > root && (huff & mask) !== low) { - /* if first time, transition to sub-tables */ - if (drop === 0) { - drop = root; - } - - /* increment past last table */ - next += min; /* here min is 1 << curr */ - - /* determine length of next table */ - curr = len - drop; - left = 1 << curr; - while (curr + drop < max) { - left -= count[curr + drop]; - if (left <= 0) { break; } - curr++; - left <<= 1; - } - - /* check for enough space */ - used += 1 << curr; - if ((type === LENS$1 && used > ENOUGH_LENS$1) || - (type === DISTS$1 && used > ENOUGH_DISTS$1)) { - return 1; - } - - /* point entry in root table to sub-table */ - low = huff & mask; - /*table.op[low] = curr; - table.bits[low] = root; - table.val[low] = next - opts.table_index;*/ - table[low] = (root << 24) | (curr << 16) | (next - table_index) |0; - } - } - - /* fill in remaining table entry if code is incomplete (guaranteed to have - at most one remaining entry, since if the code is incomplete, the - maximum code length that was allowed to get this far is one bit) */ - if (huff !== 0) { - //table.op[next + huff] = 64; /* invalid code marker */ - //table.bits[next + huff] = len - drop; - //table.val[next + huff] = 0; - table[next + huff] = ((len - drop) << 24) | (64 << 16) |0; - } - - /* set return parameters */ - //opts.table_index += used; - opts.bits = root; - return 0; -}; - - -var inftrees = inflate_table; - -// (C) 1995-2013 Jean-loup Gailly and Mark Adler -// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin -// -// This software is provided 'as-is', without any express or implied -// warranty. In no event will the authors be held liable for any damages -// arising from the use of this software. -// -// Permission is granted to anyone to use this software for any purpose, -// including commercial applications, and to alter it and redistribute it -// freely, subject to the following restrictions: -// -// 1. The origin of this software must not be misrepresented; you must not -// claim that you wrote the original software. If you use this software -// in a product, an acknowledgment in the product documentation would be -// appreciated but is not required. -// 2. Altered source versions must be plainly marked as such, and must not be -// misrepresented as being the original software. -// 3. This notice may not be removed or altered from any source distribution. - - - - - - -const CODES = 0; -const LENS = 1; -const DISTS = 2; - -/* Public constants ==========================================================*/ -/* ===========================================================================*/ - -const { - Z_FINISH: Z_FINISH$1, Z_BLOCK, Z_TREES, - Z_OK: Z_OK$1, Z_STREAM_END: Z_STREAM_END$1, Z_NEED_DICT: Z_NEED_DICT$1, Z_STREAM_ERROR: Z_STREAM_ERROR$1, Z_DATA_ERROR: Z_DATA_ERROR$1, Z_MEM_ERROR: Z_MEM_ERROR$1, Z_BUF_ERROR, - Z_DEFLATED -} = constants$2; - - -/* STATES ====================================================================*/ -/* ===========================================================================*/ - - -const HEAD = 16180; /* i: waiting for magic header */ -const FLAGS = 16181; /* i: waiting for method and flags (gzip) */ -const TIME = 16182; /* i: waiting for modification time (gzip) */ -const OS = 16183; /* i: waiting for extra flags and operating system (gzip) */ -const EXLEN = 16184; /* i: waiting for extra length (gzip) */ -const EXTRA = 16185; /* i: waiting for extra bytes (gzip) */ -const NAME = 16186; /* i: waiting for end of file name (gzip) */ -const COMMENT = 16187; /* i: waiting for end of comment (gzip) */ -const HCRC = 16188; /* i: waiting for header crc (gzip) */ -const DICTID = 16189; /* i: waiting for dictionary check value */ -const DICT = 16190; /* waiting for inflateSetDictionary() call */ -const TYPE = 16191; /* i: waiting for type bits, including last-flag bit */ -const TYPEDO = 16192; /* i: same, but skip check to exit inflate on new block */ -const STORED = 16193; /* i: waiting for stored size (length and complement) */ -const COPY_ = 16194; /* i/o: same as COPY below, but only first time in */ -const COPY = 16195; /* i/o: waiting for input or output to copy stored block */ -const TABLE = 16196; /* i: waiting for dynamic block table lengths */ -const LENLENS = 16197; /* i: waiting for code length code lengths */ -const CODELENS = 16198; /* i: waiting for length/lit and distance code lengths */ -const LEN_ = 16199; /* i: same as LEN below, but only first time in */ -const LEN = 16200; /* i: waiting for length/lit/eob code */ -const LENEXT = 16201; /* i: waiting for length extra bits */ -const DIST = 16202; /* i: waiting for distance code */ -const DISTEXT = 16203; /* i: waiting for distance extra bits */ -const MATCH = 16204; /* o: waiting for output space to copy string */ -const LIT = 16205; /* o: waiting for output space to write literal */ -const CHECK = 16206; /* i: waiting for 32-bit check value */ -const LENGTH = 16207; /* i: waiting for 32-bit length (gzip) */ -const DONE = 16208; /* finished check, done -- remain here until reset */ -const BAD = 16209; /* got a data error -- remain here until reset */ -const MEM = 16210; /* got an inflate() memory error -- remain here until reset */ -const SYNC = 16211; /* looking for synchronization bytes to restart inflate() */ - -/* ===========================================================================*/ - - - -const ENOUGH_LENS = 852; -const ENOUGH_DISTS = 592; -//const ENOUGH = (ENOUGH_LENS+ENOUGH_DISTS); - -const MAX_WBITS = 15; -/* 32K LZ77 window */ -const DEF_WBITS = MAX_WBITS; - - -const zswap32 = (q) => { - - return (((q >>> 24) & 0xff) + - ((q >>> 8) & 0xff00) + - ((q & 0xff00) << 8) + - ((q & 0xff) << 24)); -}; - - -function InflateState() { - this.strm = null; /* pointer back to this zlib stream */ - this.mode = 0; /* current inflate mode */ - this.last = false; /* true if processing last block */ - this.wrap = 0; /* bit 0 true for zlib, bit 1 true for gzip, - bit 2 true to validate check value */ - this.havedict = false; /* true if dictionary provided */ - this.flags = 0; /* gzip header method and flags (0 if zlib), or - -1 if raw or no header yet */ - this.dmax = 0; /* zlib header max distance (INFLATE_STRICT) */ - this.check = 0; /* protected copy of check value */ - this.total = 0; /* protected copy of output count */ - // TODO: may be {} - this.head = null; /* where to save gzip header information */ - - /* sliding window */ - this.wbits = 0; /* log base 2 of requested window size */ - this.wsize = 0; /* window size or zero if not using window */ - this.whave = 0; /* valid bytes in the window */ - this.wnext = 0; /* window write index */ - this.window = null; /* allocated sliding window, if needed */ - - /* bit accumulator */ - this.hold = 0; /* input bit accumulator */ - this.bits = 0; /* number of bits in "in" */ - - /* for string and stored block copying */ - this.length = 0; /* literal or length of data to copy */ - this.offset = 0; /* distance back to copy string from */ - - /* for table and code decoding */ - this.extra = 0; /* extra bits needed */ - - /* fixed and dynamic code tables */ - this.lencode = null; /* starting table for length/literal codes */ - this.distcode = null; /* starting table for distance codes */ - this.lenbits = 0; /* index bits for lencode */ - this.distbits = 0; /* index bits for distcode */ - - /* dynamic table building */ - this.ncode = 0; /* number of code length code lengths */ - this.nlen = 0; /* number of length code lengths */ - this.ndist = 0; /* number of distance code lengths */ - this.have = 0; /* number of code lengths in lens[] */ - this.next = null; /* next available space in codes[] */ - - this.lens = new Uint16Array(320); /* temporary storage for code lengths */ - this.work = new Uint16Array(288); /* work area for code table building */ - - /* - because we don't have pointers in js, we use lencode and distcode directly - as buffers so we don't need codes - */ - //this.codes = new Int32Array(ENOUGH); /* space for code tables */ - this.lendyn = null; /* dynamic table for length/literal codes (JS specific) */ - this.distdyn = null; /* dynamic table for distance codes (JS specific) */ - this.sane = 0; /* if false, allow invalid distance too far */ - this.back = 0; /* bits back of last unprocessed length/lit */ - this.was = 0; /* initial length of match */ -} - - -const inflateStateCheck = (strm) => { - - if (!strm) { - return 1; - } - const state = strm.state; - if (!state || state.strm !== strm || - state.mode < HEAD || state.mode > SYNC) { - return 1; - } - return 0; -}; - - -const inflateResetKeep = (strm) => { - - if (inflateStateCheck(strm)) { return Z_STREAM_ERROR$1; } - const state = strm.state; - strm.total_in = strm.total_out = state.total = 0; - strm.msg = ''; /*Z_NULL*/ - if (state.wrap) { /* to support ill-conceived Java test suite */ - strm.adler = state.wrap & 1; - } - state.mode = HEAD; - state.last = 0; - state.havedict = 0; - state.flags = -1; - state.dmax = 32768; - state.head = null/*Z_NULL*/; - state.hold = 0; - state.bits = 0; - //state.lencode = state.distcode = state.next = state.codes; - state.lencode = state.lendyn = new Int32Array(ENOUGH_LENS); - state.distcode = state.distdyn = new Int32Array(ENOUGH_DISTS); - - state.sane = 1; - state.back = -1; - //Tracev((stderr, "inflate: reset\n")); - return Z_OK$1; -}; - - -const inflateReset = (strm) => { - - if (inflateStateCheck(strm)) { return Z_STREAM_ERROR$1; } - const state = strm.state; - state.wsize = 0; - state.whave = 0; - state.wnext = 0; - return inflateResetKeep(strm); - -}; - - -const inflateReset2 = (strm, windowBits) => { - let wrap; - - /* get the state */ - if (inflateStateCheck(strm)) { return Z_STREAM_ERROR$1; } - const state = strm.state; - - /* extract wrap request from windowBits parameter */ - if (windowBits < 0) { - wrap = 0; - windowBits = -windowBits; - } - else { - wrap = (windowBits >> 4) + 5; - if (windowBits < 48) { - windowBits &= 15; - } - } - - /* set number of window bits, free window if different */ - if (windowBits && (windowBits < 8 || windowBits > 15)) { - return Z_STREAM_ERROR$1; - } - if (state.window !== null && state.wbits !== windowBits) { - state.window = null; - } - - /* update state and reset the rest of it */ - state.wrap = wrap; - state.wbits = windowBits; - return inflateReset(strm); -}; - - -const inflateInit2 = (strm, windowBits) => { - - if (!strm) { return Z_STREAM_ERROR$1; } - //strm.msg = Z_NULL; /* in case we return an error */ - - const state = new InflateState(); - - //if (state === Z_NULL) return Z_MEM_ERROR; - //Tracev((stderr, "inflate: allocated\n")); - strm.state = state; - state.strm = strm; - state.window = null/*Z_NULL*/; - state.mode = HEAD; /* to pass state test in inflateReset2() */ - const ret = inflateReset2(strm, windowBits); - if (ret !== Z_OK$1) { - strm.state = null/*Z_NULL*/; - } - return ret; -}; - - -const inflateInit = (strm) => { - - return inflateInit2(strm, DEF_WBITS); -}; - - -/* - Return state with length and distance decoding tables and index sizes set to - fixed code decoding. Normally this returns fixed tables from inffixed.h. - If BUILDFIXED is defined, then instead this routine builds the tables the - first time it's called, and returns those tables the first time and - thereafter. This reduces the size of the code by about 2K bytes, in - exchange for a little execution time. However, BUILDFIXED should not be - used for threaded applications, since the rewriting of the tables and virgin - may not be thread-safe. - */ -let virgin = true; - -let lenfix, distfix; // We have no pointers in JS, so keep tables separate - - -const fixedtables = (state) => { - - /* build fixed huffman tables if first call (may not be thread safe) */ - if (virgin) { - lenfix = new Int32Array(512); - distfix = new Int32Array(32); - - /* literal/length table */ - let sym = 0; - while (sym < 144) { state.lens[sym++] = 8; } - while (sym < 256) { state.lens[sym++] = 9; } - while (sym < 280) { state.lens[sym++] = 7; } - while (sym < 288) { state.lens[sym++] = 8; } - - inftrees(LENS, state.lens, 0, 288, lenfix, 0, state.work, { bits: 9 }); - - /* distance table */ - sym = 0; - while (sym < 32) { state.lens[sym++] = 5; } - - inftrees(DISTS, state.lens, 0, 32, distfix, 0, state.work, { bits: 5 }); - - /* do this just once */ - virgin = false; - } - - state.lencode = lenfix; - state.lenbits = 9; - state.distcode = distfix; - state.distbits = 5; -}; - - -/* - Update the window with the last wsize (normally 32K) bytes written before - returning. If window does not exist yet, create it. This is only called - when a window is already in use, or when output has been written during this - inflate call, but the end of the deflate stream has not been reached yet. - It is also called to create a window for dictionary data when a dictionary - is loaded. - - Providing output buffers larger than 32K to inflate() should provide a speed - advantage, since only the last 32K of output is copied to the sliding window - upon return from inflate(), and since all distances after the first 32K of - output will fall in the output data, making match copies simpler and faster. - The advantage may be dependent on the size of the processor's data caches. - */ -const updatewindow = (strm, src, end, copy) => { - - let dist; - const state = strm.state; - - /* if it hasn't been done already, allocate space for the window */ - if (state.window === null) { - state.wsize = 1 << state.wbits; - state.wnext = 0; - state.whave = 0; - - state.window = new Uint8Array(state.wsize); - } - - /* copy state->wsize or less output bytes into the circular window */ - if (copy >= state.wsize) { - state.window.set(src.subarray(end - state.wsize, end), 0); - state.wnext = 0; - state.whave = state.wsize; - } - else { - dist = state.wsize - state.wnext; - if (dist > copy) { - dist = copy; - } - //zmemcpy(state->window + state->wnext, end - copy, dist); - state.window.set(src.subarray(end - copy, end - copy + dist), state.wnext); - copy -= dist; - if (copy) { - //zmemcpy(state->window, end - copy, copy); - state.window.set(src.subarray(end - copy, end), 0); - state.wnext = copy; - state.whave = state.wsize; - } - else { - state.wnext += dist; - if (state.wnext === state.wsize) { state.wnext = 0; } - if (state.whave < state.wsize) { state.whave += dist; } - } - } - return 0; -}; - - -const inflate$2 = (strm, flush) => { - - let state; - let input, output; // input/output buffers - let next; /* next input INDEX */ - let put; /* next output INDEX */ - let have, left; /* available input and output */ - let hold; /* bit buffer */ - let bits; /* bits in bit buffer */ - let _in, _out; /* save starting available input and output */ - let copy; /* number of stored or match bytes to copy */ - let from; /* where to copy match bytes from */ - let from_source; - let here = 0; /* current decoding table entry */ - let here_bits, here_op, here_val; // paked "here" denormalized (JS specific) - //let last; /* parent table entry */ - let last_bits, last_op, last_val; // paked "last" denormalized (JS specific) - let len; /* length to copy for repeats, bits to drop */ - let ret; /* return code */ - const hbuf = new Uint8Array(4); /* buffer for gzip header crc calculation */ - let opts; - - let n; // temporary variable for NEED_BITS - - const order = /* permutation of code lengths */ - new Uint8Array([ 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 ]); - - - if (inflateStateCheck(strm) || !strm.output || - (!strm.input && strm.avail_in !== 0)) { - return Z_STREAM_ERROR$1; - } - - state = strm.state; - if (state.mode === TYPE) { state.mode = TYPEDO; } /* skip check */ - - - //--- LOAD() --- - put = strm.next_out; - output = strm.output; - left = strm.avail_out; - next = strm.next_in; - input = strm.input; - have = strm.avail_in; - hold = state.hold; - bits = state.bits; - //--- - - _in = have; - _out = left; - ret = Z_OK$1; - - inf_leave: // goto emulation - for (;;) { - switch (state.mode) { - case HEAD: - if (state.wrap === 0) { - state.mode = TYPEDO; - break; - } - //=== NEEDBITS(16); - while (bits < 16) { - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - } - //===// - if ((state.wrap & 2) && hold === 0x8b1f) { /* gzip header */ - if (state.wbits === 0) { - state.wbits = 15; - } - state.check = 0/*crc32(0L, Z_NULL, 0)*/; - //=== CRC2(state.check, hold); - hbuf[0] = hold & 0xff; - hbuf[1] = (hold >>> 8) & 0xff; - state.check = crc32_1(state.check, hbuf, 2, 0); - //===// - - //=== INITBITS(); - hold = 0; - bits = 0; - //===// - state.mode = FLAGS; - break; - } - if (state.head) { - state.head.done = false; - } - if (!(state.wrap & 1) || /* check if zlib header allowed */ - (((hold & 0xff)/*BITS(8)*/ << 8) + (hold >> 8)) % 31) { - strm.msg = 'incorrect header check'; - state.mode = BAD; - break; - } - if ((hold & 0x0f)/*BITS(4)*/ !== Z_DEFLATED) { - strm.msg = 'unknown compression method'; - state.mode = BAD; - break; - } - //--- DROPBITS(4) ---// - hold >>>= 4; - bits -= 4; - //---// - len = (hold & 0x0f)/*BITS(4)*/ + 8; - if (state.wbits === 0) { - state.wbits = len; - } - if (len > 15 || len > state.wbits) { - strm.msg = 'invalid window size'; - state.mode = BAD; - break; - } - - // !!! pako patch. Force use `options.windowBits` if passed. - // Required to always use max window size by default. - state.dmax = 1 << state.wbits; - //state.dmax = 1 << len; - - state.flags = 0; /* indicate zlib header */ - //Tracev((stderr, "inflate: zlib header ok\n")); - strm.adler = state.check = 1/*adler32(0L, Z_NULL, 0)*/; - state.mode = hold & 0x200 ? DICTID : TYPE; - //=== INITBITS(); - hold = 0; - bits = 0; - //===// - break; - case FLAGS: - //=== NEEDBITS(16); */ - while (bits < 16) { - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - } - //===// - state.flags = hold; - if ((state.flags & 0xff) !== Z_DEFLATED) { - strm.msg = 'unknown compression method'; - state.mode = BAD; - break; - } - if (state.flags & 0xe000) { - strm.msg = 'unknown header flags set'; - state.mode = BAD; - break; - } - if (state.head) { - state.head.text = ((hold >> 8) & 1); - } - if ((state.flags & 0x0200) && (state.wrap & 4)) { - //=== CRC2(state.check, hold); - hbuf[0] = hold & 0xff; - hbuf[1] = (hold >>> 8) & 0xff; - state.check = crc32_1(state.check, hbuf, 2, 0); - //===// - } - //=== INITBITS(); - hold = 0; - bits = 0; - //===// - state.mode = TIME; - /* falls through */ - case TIME: - //=== NEEDBITS(32); */ - while (bits < 32) { - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - } - //===// - if (state.head) { - state.head.time = hold; - } - if ((state.flags & 0x0200) && (state.wrap & 4)) { - //=== CRC4(state.check, hold) - hbuf[0] = hold & 0xff; - hbuf[1] = (hold >>> 8) & 0xff; - hbuf[2] = (hold >>> 16) & 0xff; - hbuf[3] = (hold >>> 24) & 0xff; - state.check = crc32_1(state.check, hbuf, 4, 0); - //=== - } - //=== INITBITS(); - hold = 0; - bits = 0; - //===// - state.mode = OS; - /* falls through */ - case OS: - //=== NEEDBITS(16); */ - while (bits < 16) { - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - } - //===// - if (state.head) { - state.head.xflags = (hold & 0xff); - state.head.os = (hold >> 8); - } - if ((state.flags & 0x0200) && (state.wrap & 4)) { - //=== CRC2(state.check, hold); - hbuf[0] = hold & 0xff; - hbuf[1] = (hold >>> 8) & 0xff; - state.check = crc32_1(state.check, hbuf, 2, 0); - //===// - } - //=== INITBITS(); - hold = 0; - bits = 0; - //===// - state.mode = EXLEN; - /* falls through */ - case EXLEN: - if (state.flags & 0x0400) { - //=== NEEDBITS(16); */ - while (bits < 16) { - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - } - //===// - state.length = hold; - if (state.head) { - state.head.extra_len = hold; - } - if ((state.flags & 0x0200) && (state.wrap & 4)) { - //=== CRC2(state.check, hold); - hbuf[0] = hold & 0xff; - hbuf[1] = (hold >>> 8) & 0xff; - state.check = crc32_1(state.check, hbuf, 2, 0); - //===// - } - //=== INITBITS(); - hold = 0; - bits = 0; - //===// - } - else if (state.head) { - state.head.extra = null/*Z_NULL*/; - } - state.mode = EXTRA; - /* falls through */ - case EXTRA: - if (state.flags & 0x0400) { - copy = state.length; - if (copy > have) { copy = have; } - if (copy) { - if (state.head) { - len = state.head.extra_len - state.length; - if (!state.head.extra) { - // Use untyped array for more convenient processing later - state.head.extra = new Uint8Array(state.head.extra_len); - } - state.head.extra.set( - input.subarray( - next, - // extra field is limited to 65536 bytes - // - no need for additional size check - next + copy - ), - /*len + copy > state.head.extra_max - len ? state.head.extra_max : copy,*/ - len - ); - //zmemcpy(state.head.extra + len, next, - // len + copy > state.head.extra_max ? - // state.head.extra_max - len : copy); - } - if ((state.flags & 0x0200) && (state.wrap & 4)) { - state.check = crc32_1(state.check, input, copy, next); - } - have -= copy; - next += copy; - state.length -= copy; - } - if (state.length) { break inf_leave; } - } - state.length = 0; - state.mode = NAME; - /* falls through */ - case NAME: - if (state.flags & 0x0800) { - if (have === 0) { break inf_leave; } - copy = 0; - do { - // TODO: 2 or 1 bytes? - len = input[next + copy++]; - /* use constant limit because in js we should not preallocate memory */ - if (state.head && len && - (state.length < 65536 /*state.head.name_max*/)) { - state.head.name += String.fromCharCode(len); - } - } while (len && copy < have); - - if ((state.flags & 0x0200) && (state.wrap & 4)) { - state.check = crc32_1(state.check, input, copy, next); - } - have -= copy; - next += copy; - if (len) { break inf_leave; } - } - else if (state.head) { - state.head.name = null; - } - state.length = 0; - state.mode = COMMENT; - /* falls through */ - case COMMENT: - if (state.flags & 0x1000) { - if (have === 0) { break inf_leave; } - copy = 0; - do { - len = input[next + copy++]; - /* use constant limit because in js we should not preallocate memory */ - if (state.head && len && - (state.length < 65536 /*state.head.comm_max*/)) { - state.head.comment += String.fromCharCode(len); - } - } while (len && copy < have); - if ((state.flags & 0x0200) && (state.wrap & 4)) { - state.check = crc32_1(state.check, input, copy, next); - } - have -= copy; - next += copy; - if (len) { break inf_leave; } - } - else if (state.head) { - state.head.comment = null; - } - state.mode = HCRC; - /* falls through */ - case HCRC: - if (state.flags & 0x0200) { - //=== NEEDBITS(16); */ - while (bits < 16) { - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - } - //===// - if ((state.wrap & 4) && hold !== (state.check & 0xffff)) { - strm.msg = 'header crc mismatch'; - state.mode = BAD; - break; - } - //=== INITBITS(); - hold = 0; - bits = 0; - //===// - } - if (state.head) { - state.head.hcrc = ((state.flags >> 9) & 1); - state.head.done = true; - } - strm.adler = state.check = 0; - state.mode = TYPE; - break; - case DICTID: - //=== NEEDBITS(32); */ - while (bits < 32) { - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - } - //===// - strm.adler = state.check = zswap32(hold); - //=== INITBITS(); - hold = 0; - bits = 0; - //===// - state.mode = DICT; - /* falls through */ - case DICT: - if (state.havedict === 0) { - //--- RESTORE() --- - strm.next_out = put; - strm.avail_out = left; - strm.next_in = next; - strm.avail_in = have; - state.hold = hold; - state.bits = bits; - //--- - return Z_NEED_DICT$1; - } - strm.adler = state.check = 1/*adler32(0L, Z_NULL, 0)*/; - state.mode = TYPE; - /* falls through */ - case TYPE: - if (flush === Z_BLOCK || flush === Z_TREES) { break inf_leave; } - /* falls through */ - case TYPEDO: - if (state.last) { - //--- BYTEBITS() ---// - hold >>>= bits & 7; - bits -= bits & 7; - //---// - state.mode = CHECK; - break; - } - //=== NEEDBITS(3); */ - while (bits < 3) { - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - } - //===// - state.last = (hold & 0x01)/*BITS(1)*/; - //--- DROPBITS(1) ---// - hold >>>= 1; - bits -= 1; - //---// - - switch ((hold & 0x03)/*BITS(2)*/) { - case 0: /* stored block */ - //Tracev((stderr, "inflate: stored block%s\n", - // state.last ? " (last)" : "")); - state.mode = STORED; - break; - case 1: /* fixed block */ - fixedtables(state); - //Tracev((stderr, "inflate: fixed codes block%s\n", - // state.last ? " (last)" : "")); - state.mode = LEN_; /* decode codes */ - if (flush === Z_TREES) { - //--- DROPBITS(2) ---// - hold >>>= 2; - bits -= 2; - //---// - break inf_leave; - } - break; - case 2: /* dynamic block */ - //Tracev((stderr, "inflate: dynamic codes block%s\n", - // state.last ? " (last)" : "")); - state.mode = TABLE; - break; - case 3: - strm.msg = 'invalid block type'; - state.mode = BAD; - } - //--- DROPBITS(2) ---// - hold >>>= 2; - bits -= 2; - //---// - break; - case STORED: - //--- BYTEBITS() ---// /* go to byte boundary */ - hold >>>= bits & 7; - bits -= bits & 7; - //---// - //=== NEEDBITS(32); */ - while (bits < 32) { - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - } - //===// - if ((hold & 0xffff) !== ((hold >>> 16) ^ 0xffff)) { - strm.msg = 'invalid stored block lengths'; - state.mode = BAD; - break; - } - state.length = hold & 0xffff; - //Tracev((stderr, "inflate: stored length %u\n", - // state.length)); - //=== INITBITS(); - hold = 0; - bits = 0; - //===// - state.mode = COPY_; - if (flush === Z_TREES) { break inf_leave; } - /* falls through */ - case COPY_: - state.mode = COPY; - /* falls through */ - case COPY: - copy = state.length; - if (copy) { - if (copy > have) { copy = have; } - if (copy > left) { copy = left; } - if (copy === 0) { break inf_leave; } - //--- zmemcpy(put, next, copy); --- - output.set(input.subarray(next, next + copy), put); - //---// - have -= copy; - next += copy; - left -= copy; - put += copy; - state.length -= copy; - break; - } - //Tracev((stderr, "inflate: stored end\n")); - state.mode = TYPE; - break; - case TABLE: - //=== NEEDBITS(14); */ - while (bits < 14) { - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - } - //===// - state.nlen = (hold & 0x1f)/*BITS(5)*/ + 257; - //--- DROPBITS(5) ---// - hold >>>= 5; - bits -= 5; - //---// - state.ndist = (hold & 0x1f)/*BITS(5)*/ + 1; - //--- DROPBITS(5) ---// - hold >>>= 5; - bits -= 5; - //---// - state.ncode = (hold & 0x0f)/*BITS(4)*/ + 4; - //--- DROPBITS(4) ---// - hold >>>= 4; - bits -= 4; - //---// -//#ifndef PKZIP_BUG_WORKAROUND - if (state.nlen > 286 || state.ndist > 30) { - strm.msg = 'too many length or distance symbols'; - state.mode = BAD; - break; - } -//#endif - //Tracev((stderr, "inflate: table sizes ok\n")); - state.have = 0; - state.mode = LENLENS; - /* falls through */ - case LENLENS: - while (state.have < state.ncode) { - //=== NEEDBITS(3); - while (bits < 3) { - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - } - //===// - state.lens[order[state.have++]] = (hold & 0x07);//BITS(3); - //--- DROPBITS(3) ---// - hold >>>= 3; - bits -= 3; - //---// - } - while (state.have < 19) { - state.lens[order[state.have++]] = 0; - } - // We have separate tables & no pointers. 2 commented lines below not needed. - //state.next = state.codes; - //state.lencode = state.next; - // Switch to use dynamic table - state.lencode = state.lendyn; - state.lenbits = 7; - - opts = { bits: state.lenbits }; - ret = inftrees(CODES, state.lens, 0, 19, state.lencode, 0, state.work, opts); - state.lenbits = opts.bits; - - if (ret) { - strm.msg = 'invalid code lengths set'; - state.mode = BAD; - break; - } - //Tracev((stderr, "inflate: code lengths ok\n")); - state.have = 0; - state.mode = CODELENS; - /* falls through */ - case CODELENS: - while (state.have < state.nlen + state.ndist) { - for (;;) { - here = state.lencode[hold & ((1 << state.lenbits) - 1)];/*BITS(state.lenbits)*/ - here_bits = here >>> 24; - here_op = (here >>> 16) & 0xff; - here_val = here & 0xffff; - - if ((here_bits) <= bits) { break; } - //--- PULLBYTE() ---// - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - //---// - } - if (here_val < 16) { - //--- DROPBITS(here.bits) ---// - hold >>>= here_bits; - bits -= here_bits; - //---// - state.lens[state.have++] = here_val; - } - else { - if (here_val === 16) { - //=== NEEDBITS(here.bits + 2); - n = here_bits + 2; - while (bits < n) { - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - } - //===// - //--- DROPBITS(here.bits) ---// - hold >>>= here_bits; - bits -= here_bits; - //---// - if (state.have === 0) { - strm.msg = 'invalid bit length repeat'; - state.mode = BAD; - break; - } - len = state.lens[state.have - 1]; - copy = 3 + (hold & 0x03);//BITS(2); - //--- DROPBITS(2) ---// - hold >>>= 2; - bits -= 2; - //---// - } - else if (here_val === 17) { - //=== NEEDBITS(here.bits + 3); - n = here_bits + 3; - while (bits < n) { - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - } - //===// - //--- DROPBITS(here.bits) ---// - hold >>>= here_bits; - bits -= here_bits; - //---// - len = 0; - copy = 3 + (hold & 0x07);//BITS(3); - //--- DROPBITS(3) ---// - hold >>>= 3; - bits -= 3; - //---// - } - else { - //=== NEEDBITS(here.bits + 7); - n = here_bits + 7; - while (bits < n) { - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - } - //===// - //--- DROPBITS(here.bits) ---// - hold >>>= here_bits; - bits -= here_bits; - //---// - len = 0; - copy = 11 + (hold & 0x7f);//BITS(7); - //--- DROPBITS(7) ---// - hold >>>= 7; - bits -= 7; - //---// - } - if (state.have + copy > state.nlen + state.ndist) { - strm.msg = 'invalid bit length repeat'; - state.mode = BAD; - break; - } - while (copy--) { - state.lens[state.have++] = len; - } - } - } - - /* handle error breaks in while */ - if (state.mode === BAD) { break; } - - /* check for end-of-block code (better have one) */ - if (state.lens[256] === 0) { - strm.msg = 'invalid code -- missing end-of-block'; - state.mode = BAD; - break; - } - - /* build code tables -- note: do not change the lenbits or distbits - values here (9 and 6) without reading the comments in inftrees.h - concerning the ENOUGH constants, which depend on those values */ - state.lenbits = 9; - - opts = { bits: state.lenbits }; - ret = inftrees(LENS, state.lens, 0, state.nlen, state.lencode, 0, state.work, opts); - // We have separate tables & no pointers. 2 commented lines below not needed. - // state.next_index = opts.table_index; - state.lenbits = opts.bits; - // state.lencode = state.next; - - if (ret) { - strm.msg = 'invalid literal/lengths set'; - state.mode = BAD; - break; - } - - state.distbits = 6; - //state.distcode.copy(state.codes); - // Switch to use dynamic table - state.distcode = state.distdyn; - opts = { bits: state.distbits }; - ret = inftrees(DISTS, state.lens, state.nlen, state.ndist, state.distcode, 0, state.work, opts); - // We have separate tables & no pointers. 2 commented lines below not needed. - // state.next_index = opts.table_index; - state.distbits = opts.bits; - // state.distcode = state.next; - - if (ret) { - strm.msg = 'invalid distances set'; - state.mode = BAD; - break; - } - //Tracev((stderr, 'inflate: codes ok\n')); - state.mode = LEN_; - if (flush === Z_TREES) { break inf_leave; } - /* falls through */ - case LEN_: - state.mode = LEN; - /* falls through */ - case LEN: - if (have >= 6 && left >= 258) { - //--- RESTORE() --- - strm.next_out = put; - strm.avail_out = left; - strm.next_in = next; - strm.avail_in = have; - state.hold = hold; - state.bits = bits; - //--- - inffast(strm, _out); - //--- LOAD() --- - put = strm.next_out; - output = strm.output; - left = strm.avail_out; - next = strm.next_in; - input = strm.input; - have = strm.avail_in; - hold = state.hold; - bits = state.bits; - //--- - - if (state.mode === TYPE) { - state.back = -1; - } - break; - } - state.back = 0; - for (;;) { - here = state.lencode[hold & ((1 << state.lenbits) - 1)]; /*BITS(state.lenbits)*/ - here_bits = here >>> 24; - here_op = (here >>> 16) & 0xff; - here_val = here & 0xffff; - - if (here_bits <= bits) { break; } - //--- PULLBYTE() ---// - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - //---// - } - if (here_op && (here_op & 0xf0) === 0) { - last_bits = here_bits; - last_op = here_op; - last_val = here_val; - for (;;) { - here = state.lencode[last_val + - ((hold & ((1 << (last_bits + last_op)) - 1))/*BITS(last.bits + last.op)*/ >> last_bits)]; - here_bits = here >>> 24; - here_op = (here >>> 16) & 0xff; - here_val = here & 0xffff; - - if ((last_bits + here_bits) <= bits) { break; } - //--- PULLBYTE() ---// - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - //---// - } - //--- DROPBITS(last.bits) ---// - hold >>>= last_bits; - bits -= last_bits; - //---// - state.back += last_bits; - } - //--- DROPBITS(here.bits) ---// - hold >>>= here_bits; - bits -= here_bits; - //---// - state.back += here_bits; - state.length = here_val; - if (here_op === 0) { - //Tracevv((stderr, here.val >= 0x20 && here.val < 0x7f ? - // "inflate: literal '%c'\n" : - // "inflate: literal 0x%02x\n", here.val)); - state.mode = LIT; - break; - } - if (here_op & 32) { - //Tracevv((stderr, "inflate: end of block\n")); - state.back = -1; - state.mode = TYPE; - break; - } - if (here_op & 64) { - strm.msg = 'invalid literal/length code'; - state.mode = BAD; - break; - } - state.extra = here_op & 15; - state.mode = LENEXT; - /* falls through */ - case LENEXT: - if (state.extra) { - //=== NEEDBITS(state.extra); - n = state.extra; - while (bits < n) { - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - } - //===// - state.length += hold & ((1 << state.extra) - 1)/*BITS(state.extra)*/; - //--- DROPBITS(state.extra) ---// - hold >>>= state.extra; - bits -= state.extra; - //---// - state.back += state.extra; - } - //Tracevv((stderr, "inflate: length %u\n", state.length)); - state.was = state.length; - state.mode = DIST; - /* falls through */ - case DIST: - for (;;) { - here = state.distcode[hold & ((1 << state.distbits) - 1)];/*BITS(state.distbits)*/ - here_bits = here >>> 24; - here_op = (here >>> 16) & 0xff; - here_val = here & 0xffff; - - if ((here_bits) <= bits) { break; } - //--- PULLBYTE() ---// - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - //---// - } - if ((here_op & 0xf0) === 0) { - last_bits = here_bits; - last_op = here_op; - last_val = here_val; - for (;;) { - here = state.distcode[last_val + - ((hold & ((1 << (last_bits + last_op)) - 1))/*BITS(last.bits + last.op)*/ >> last_bits)]; - here_bits = here >>> 24; - here_op = (here >>> 16) & 0xff; - here_val = here & 0xffff; - - if ((last_bits + here_bits) <= bits) { break; } - //--- PULLBYTE() ---// - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - //---// - } - //--- DROPBITS(last.bits) ---// - hold >>>= last_bits; - bits -= last_bits; - //---// - state.back += last_bits; - } - //--- DROPBITS(here.bits) ---// - hold >>>= here_bits; - bits -= here_bits; - //---// - state.back += here_bits; - if (here_op & 64) { - strm.msg = 'invalid distance code'; - state.mode = BAD; - break; - } - state.offset = here_val; - state.extra = (here_op) & 15; - state.mode = DISTEXT; - /* falls through */ - case DISTEXT: - if (state.extra) { - //=== NEEDBITS(state.extra); - n = state.extra; - while (bits < n) { - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - } - //===// - state.offset += hold & ((1 << state.extra) - 1)/*BITS(state.extra)*/; - //--- DROPBITS(state.extra) ---// - hold >>>= state.extra; - bits -= state.extra; - //---// - state.back += state.extra; - } -//#ifdef INFLATE_STRICT - if (state.offset > state.dmax) { - strm.msg = 'invalid distance too far back'; - state.mode = BAD; - break; - } -//#endif - //Tracevv((stderr, "inflate: distance %u\n", state.offset)); - state.mode = MATCH; - /* falls through */ - case MATCH: - if (left === 0) { break inf_leave; } - copy = _out - left; - if (state.offset > copy) { /* copy from window */ - copy = state.offset - copy; - if (copy > state.whave) { - if (state.sane) { - strm.msg = 'invalid distance too far back'; - state.mode = BAD; - break; - } -// (!) This block is disabled in zlib defaults, -// don't enable it for binary compatibility -//#ifdef INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR -// Trace((stderr, "inflate.c too far\n")); -// copy -= state.whave; -// if (copy > state.length) { copy = state.length; } -// if (copy > left) { copy = left; } -// left -= copy; -// state.length -= copy; -// do { -// output[put++] = 0; -// } while (--copy); -// if (state.length === 0) { state.mode = LEN; } -// break; -//#endif - } - if (copy > state.wnext) { - copy -= state.wnext; - from = state.wsize - copy; - } - else { - from = state.wnext - copy; - } - if (copy > state.length) { copy = state.length; } - from_source = state.window; - } - else { /* copy from output */ - from_source = output; - from = put - state.offset; - copy = state.length; - } - if (copy > left) { copy = left; } - left -= copy; - state.length -= copy; - do { - output[put++] = from_source[from++]; - } while (--copy); - if (state.length === 0) { state.mode = LEN; } - break; - case LIT: - if (left === 0) { break inf_leave; } - output[put++] = state.length; - left--; - state.mode = LEN; - break; - case CHECK: - if (state.wrap) { - //=== NEEDBITS(32); - while (bits < 32) { - if (have === 0) { break inf_leave; } - have--; - // Use '|' instead of '+' to make sure that result is signed - hold |= input[next++] << bits; - bits += 8; - } - //===// - _out -= left; - strm.total_out += _out; - state.total += _out; - if ((state.wrap & 4) && _out) { - strm.adler = state.check = - /*UPDATE_CHECK(state.check, put - _out, _out);*/ - (state.flags ? crc32_1(state.check, output, _out, put - _out) : adler32_1(state.check, output, _out, put - _out)); - - } - _out = left; - // NB: crc32 stored as signed 32-bit int, zswap32 returns signed too - if ((state.wrap & 4) && (state.flags ? hold : zswap32(hold)) !== state.check) { - strm.msg = 'incorrect data check'; - state.mode = BAD; - break; - } - //=== INITBITS(); - hold = 0; - bits = 0; - //===// - //Tracev((stderr, "inflate: check matches trailer\n")); - } - state.mode = LENGTH; - /* falls through */ - case LENGTH: - if (state.wrap && state.flags) { - //=== NEEDBITS(32); - while (bits < 32) { - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - } - //===// - if ((state.wrap & 4) && hold !== (state.total & 0xffffffff)) { - strm.msg = 'incorrect length check'; - state.mode = BAD; - break; - } - //=== INITBITS(); - hold = 0; - bits = 0; - //===// - //Tracev((stderr, "inflate: length matches trailer\n")); - } - state.mode = DONE; - /* falls through */ - case DONE: - ret = Z_STREAM_END$1; - break inf_leave; - case BAD: - ret = Z_DATA_ERROR$1; - break inf_leave; - case MEM: - return Z_MEM_ERROR$1; - case SYNC: - /* falls through */ - default: - return Z_STREAM_ERROR$1; - } - } - - // inf_leave <- here is real place for "goto inf_leave", emulated via "break inf_leave" - - /* - Return from inflate(), updating the total counts and the check value. - If there was no progress during the inflate() call, return a buffer - error. Call updatewindow() to create and/or update the window state. - Note: a memory error from inflate() is non-recoverable. - */ - - //--- RESTORE() --- - strm.next_out = put; - strm.avail_out = left; - strm.next_in = next; - strm.avail_in = have; - state.hold = hold; - state.bits = bits; - //--- - - if (state.wsize || (_out !== strm.avail_out && state.mode < BAD && - (state.mode < CHECK || flush !== Z_FINISH$1))) { - if (updatewindow(strm, strm.output, strm.next_out, _out - strm.avail_out)) ; - } - _in -= strm.avail_in; - _out -= strm.avail_out; - strm.total_in += _in; - strm.total_out += _out; - state.total += _out; - if ((state.wrap & 4) && _out) { - strm.adler = state.check = /*UPDATE_CHECK(state.check, strm.next_out - _out, _out);*/ - (state.flags ? crc32_1(state.check, output, _out, strm.next_out - _out) : adler32_1(state.check, output, _out, strm.next_out - _out)); - } - strm.data_type = state.bits + (state.last ? 64 : 0) + - (state.mode === TYPE ? 128 : 0) + - (state.mode === LEN_ || state.mode === COPY_ ? 256 : 0); - if (((_in === 0 && _out === 0) || flush === Z_FINISH$1) && ret === Z_OK$1) { - ret = Z_BUF_ERROR; - } - return ret; -}; - - -const inflateEnd = (strm) => { - - if (inflateStateCheck(strm)) { - return Z_STREAM_ERROR$1; - } - - let state = strm.state; - if (state.window) { - state.window = null; - } - strm.state = null; - return Z_OK$1; -}; - - -const inflateGetHeader = (strm, head) => { - - /* check state */ - if (inflateStateCheck(strm)) { return Z_STREAM_ERROR$1; } - const state = strm.state; - if ((state.wrap & 2) === 0) { return Z_STREAM_ERROR$1; } - - /* save header structure */ - state.head = head; - head.done = false; - return Z_OK$1; -}; - - -const inflateSetDictionary = (strm, dictionary) => { - const dictLength = dictionary.length; - - let state; - let dictid; - let ret; - - /* check state */ - if (inflateStateCheck(strm)) { return Z_STREAM_ERROR$1; } - state = strm.state; - - if (state.wrap !== 0 && state.mode !== DICT) { - return Z_STREAM_ERROR$1; - } - - /* check for correct dictionary identifier */ - if (state.mode === DICT) { - dictid = 1; /* adler32(0, null, 0)*/ - /* dictid = adler32(dictid, dictionary, dictLength); */ - dictid = adler32_1(dictid, dictionary, dictLength, 0); - if (dictid !== state.check) { - return Z_DATA_ERROR$1; - } - } - /* copy dictionary to window using updatewindow(), which will amend the - existing dictionary if appropriate */ - ret = updatewindow(strm, dictionary, dictLength, dictLength); - if (ret) { - state.mode = MEM; - return Z_MEM_ERROR$1; - } - state.havedict = 1; - // Tracev((stderr, "inflate: dictionary set\n")); - return Z_OK$1; -}; - - -var inflateReset_1 = inflateReset; -var inflateReset2_1 = inflateReset2; -var inflateResetKeep_1 = inflateResetKeep; -var inflateInit_1 = inflateInit; -var inflateInit2_1 = inflateInit2; -var inflate_2$1 = inflate$2; -var inflateEnd_1 = inflateEnd; -var inflateGetHeader_1 = inflateGetHeader; -var inflateSetDictionary_1 = inflateSetDictionary; -var inflateInfo = 'pako inflate (from Nodeca project)'; - -/* Not implemented -module.exports.inflateCodesUsed = inflateCodesUsed; -module.exports.inflateCopy = inflateCopy; -module.exports.inflateGetDictionary = inflateGetDictionary; -module.exports.inflateMark = inflateMark; -module.exports.inflatePrime = inflatePrime; -module.exports.inflateSync = inflateSync; -module.exports.inflateSyncPoint = inflateSyncPoint; -module.exports.inflateUndermine = inflateUndermine; -module.exports.inflateValidate = inflateValidate; -*/ - -var inflate_1$2 = { - inflateReset: inflateReset_1, - inflateReset2: inflateReset2_1, - inflateResetKeep: inflateResetKeep_1, - inflateInit: inflateInit_1, - inflateInit2: inflateInit2_1, - inflate: inflate_2$1, - inflateEnd: inflateEnd_1, - inflateGetHeader: inflateGetHeader_1, - inflateSetDictionary: inflateSetDictionary_1, - inflateInfo: inflateInfo -}; - -// (C) 1995-2013 Jean-loup Gailly and Mark Adler -// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin -// -// This software is provided 'as-is', without any express or implied -// warranty. In no event will the authors be held liable for any damages -// arising from the use of this software. -// -// Permission is granted to anyone to use this software for any purpose, -// including commercial applications, and to alter it and redistribute it -// freely, subject to the following restrictions: -// -// 1. The origin of this software must not be misrepresented; you must not -// claim that you wrote the original software. If you use this software -// in a product, an acknowledgment in the product documentation would be -// appreciated but is not required. -// 2. Altered source versions must be plainly marked as such, and must not be -// misrepresented as being the original software. -// 3. This notice may not be removed or altered from any source distribution. - -function GZheader() { - /* true if compressed data believed to be text */ - this.text = 0; - /* modification time */ - this.time = 0; - /* extra flags (not used when writing a gzip file) */ - this.xflags = 0; - /* operating system */ - this.os = 0; - /* pointer to extra field or Z_NULL if none */ - this.extra = null; - /* extra field length (valid if extra != Z_NULL) */ - this.extra_len = 0; // Actually, we don't need it in JS, - // but leave for few code modifications - - // - // Setup limits is not necessary because in js we should not preallocate memory - // for inflate use constant limit in 65536 bytes - // - - /* space at extra (only when reading header) */ - // this.extra_max = 0; - /* pointer to zero-terminated file name or Z_NULL */ - this.name = ''; - /* space at name (only when reading header) */ - // this.name_max = 0; - /* pointer to zero-terminated comment or Z_NULL */ - this.comment = ''; - /* space at comment (only when reading header) */ - // this.comm_max = 0; - /* true if there was or will be a header crc */ - this.hcrc = 0; - /* true when done reading gzip header (not used when writing a gzip file) */ - this.done = false; -} - -var gzheader = GZheader; - -const toString = Object.prototype.toString; - -/* Public constants ==========================================================*/ -/* ===========================================================================*/ - -const { - Z_NO_FLUSH, Z_FINISH, - Z_OK, Z_STREAM_END, Z_NEED_DICT, Z_STREAM_ERROR, Z_DATA_ERROR, Z_MEM_ERROR -} = constants$2; - -/* ===========================================================================*/ - - -/** - * class Inflate - * - * Generic JS-style wrapper for zlib calls. If you don't need - * streaming behaviour - use more simple functions: [[inflate]] - * and [[inflateRaw]]. - **/ - -/* internal - * inflate.chunks -> Array - * - * Chunks of output data, if [[Inflate#onData]] not overridden. - **/ - -/** - * Inflate.result -> Uint8Array|String - * - * Uncompressed result, generated by default [[Inflate#onData]] - * and [[Inflate#onEnd]] handlers. Filled after you push last chunk - * (call [[Inflate#push]] with `Z_FINISH` / `true` param). - **/ - -/** - * Inflate.err -> Number - * - * Error code after inflate finished. 0 (Z_OK) on success. - * Should be checked if broken data possible. - **/ - -/** - * Inflate.msg -> String - * - * Error message, if [[Inflate.err]] != 0 - **/ - - -/** - * new Inflate(options) - * - options (Object): zlib inflate options. - * - * Creates new inflator instance with specified params. Throws exception - * on bad params. Supported options: - * - * - `windowBits` - * - `dictionary` - * - * [http://zlib.net/manual.html#Advanced](http://zlib.net/manual.html#Advanced) - * for more information on these. - * - * Additional options, for internal needs: - * - * - `chunkSize` - size of generated data chunks (16K by default) - * - `raw` (Boolean) - do raw inflate - * - `to` (String) - if equal to 'string', then result will be converted - * from utf8 to utf16 (javascript) string. When string output requested, - * chunk length can differ from `chunkSize`, depending on content. - * - * By default, when no options set, autodetect deflate/gzip data format via - * wrapper header. - * - * ##### Example: - * - * ```javascript - * const pako = require('pako') - * const chunk1 = new Uint8Array([1,2,3,4,5,6,7,8,9]) - * const chunk2 = new Uint8Array([10,11,12,13,14,15,16,17,18,19]); - * - * const inflate = new pako.Inflate({ level: 3}); - * - * inflate.push(chunk1, false); - * inflate.push(chunk2, true); // true -> last chunk - * - * if (inflate.err) { throw new Error(inflate.err); } - * - * console.log(inflate.result); - * ``` - **/ -function Inflate$1(options) { - this.options = common.assign({ - chunkSize: 1024 * 64, - windowBits: 15, - to: '' - }, options || {}); - - const opt = this.options; - - // Force window size for `raw` data, if not set directly, - // because we have no header for autodetect. - if (opt.raw && (opt.windowBits >= 0) && (opt.windowBits < 16)) { - opt.windowBits = -opt.windowBits; - if (opt.windowBits === 0) { opt.windowBits = -15; } - } - - // If `windowBits` not defined (and mode not raw) - set autodetect flag for gzip/deflate - if ((opt.windowBits >= 0) && (opt.windowBits < 16) && - !(options && options.windowBits)) { - opt.windowBits += 32; - } - - // Gzip header has no info about windows size, we can do autodetect only - // for deflate. So, if window size not set, force it to max when gzip possible - if ((opt.windowBits > 15) && (opt.windowBits < 48)) { - // bit 3 (16) -> gzipped data - // bit 4 (32) -> autodetect gzip/deflate - if ((opt.windowBits & 15) === 0) { - opt.windowBits |= 15; - } - } - - this.err = 0; // error code, if happens (0 = Z_OK) - this.msg = ''; // error message - this.ended = false; // used to avoid multiple onEnd() calls - this.chunks = []; // chunks of compressed data - - this.strm = new zstream(); - this.strm.avail_out = 0; - - let status = inflate_1$2.inflateInit2( - this.strm, - opt.windowBits - ); - - if (status !== Z_OK) { - throw new Error(messages[status]); - } - - this.header = new gzheader(); - - inflate_1$2.inflateGetHeader(this.strm, this.header); - - // Setup dictionary - if (opt.dictionary) { - // Convert data if needed - if (typeof opt.dictionary === 'string') { - opt.dictionary = strings.string2buf(opt.dictionary); - } else if (toString.call(opt.dictionary) === '[object ArrayBuffer]') { - opt.dictionary = new Uint8Array(opt.dictionary); - } - if (opt.raw) { //In raw mode we need to set the dictionary early - status = inflate_1$2.inflateSetDictionary(this.strm, opt.dictionary); - if (status !== Z_OK) { - throw new Error(messages[status]); - } - } - } -} - -/** - * Inflate#push(data[, flush_mode]) -> Boolean - * - data (Uint8Array|ArrayBuffer): input data - * - flush_mode (Number|Boolean): 0..6 for corresponding Z_NO_FLUSH..Z_TREE - * flush modes. See constants. Skipped or `false` means Z_NO_FLUSH, - * `true` means Z_FINISH. - * - * Sends input data to inflate pipe, generating [[Inflate#onData]] calls with - * new output chunks. Returns `true` on success. If end of stream detected, - * [[Inflate#onEnd]] will be called. - * - * `flush_mode` is not needed for normal operation, because end of stream - * detected automatically. You may try to use it for advanced things, but - * this functionality was not tested. - * - * On fail call [[Inflate#onEnd]] with error code and return false. - * - * ##### Example - * - * ```javascript - * push(chunk, false); // push one of data chunks - * ... - * push(chunk, true); // push last chunk - * ``` - **/ -Inflate$1.prototype.push = function (data, flush_mode) { - const strm = this.strm; - const chunkSize = this.options.chunkSize; - const dictionary = this.options.dictionary; - let status, _flush_mode, last_avail_out; - - if (this.ended) return false; - - if (flush_mode === ~~flush_mode) _flush_mode = flush_mode; - else _flush_mode = flush_mode === true ? Z_FINISH : Z_NO_FLUSH; - - // Convert data if needed - if (toString.call(data) === '[object ArrayBuffer]') { - strm.input = new Uint8Array(data); - } else { - strm.input = data; - } - - strm.next_in = 0; - strm.avail_in = strm.input.length; - - for (;;) { - if (strm.avail_out === 0) { - strm.output = new Uint8Array(chunkSize); - strm.next_out = 0; - strm.avail_out = chunkSize; - } - - status = inflate_1$2.inflate(strm, _flush_mode); - - if (status === Z_NEED_DICT && dictionary) { - status = inflate_1$2.inflateSetDictionary(strm, dictionary); - - if (status === Z_OK) { - status = inflate_1$2.inflate(strm, _flush_mode); - } else if (status === Z_DATA_ERROR) { - // Replace code with more verbose - status = Z_NEED_DICT; - } - } - - // Skip snyc markers if more data follows and not raw mode - while (strm.avail_in > 0 && - status === Z_STREAM_END && - strm.state.wrap > 0 && - data[strm.next_in] !== 0) - { - inflate_1$2.inflateReset(strm); - status = inflate_1$2.inflate(strm, _flush_mode); - } - - switch (status) { - case Z_STREAM_ERROR: - case Z_DATA_ERROR: - case Z_NEED_DICT: - case Z_MEM_ERROR: - this.onEnd(status); - this.ended = true; - return false; - } - - // Remember real `avail_out` value, because we may patch out buffer content - // to align utf8 strings boundaries. - last_avail_out = strm.avail_out; - - if (strm.next_out) { - if (strm.avail_out === 0 || status === Z_STREAM_END) { - - if (this.options.to === 'string') { - - let next_out_utf8 = strings.utf8border(strm.output, strm.next_out); - - let tail = strm.next_out - next_out_utf8; - let utf8str = strings.buf2string(strm.output, next_out_utf8); - - // move tail & realign counters - strm.next_out = tail; - strm.avail_out = chunkSize - tail; - if (tail) strm.output.set(strm.output.subarray(next_out_utf8, next_out_utf8 + tail), 0); - - this.onData(utf8str); - - } else { - this.onData(strm.output.length === strm.next_out ? strm.output : strm.output.subarray(0, strm.next_out)); - } - } - } - - // Must repeat iteration if out buffer is full - if (status === Z_OK && last_avail_out === 0) continue; - - // Finalize if end of stream reached. - if (status === Z_STREAM_END) { - status = inflate_1$2.inflateEnd(this.strm); - this.onEnd(status); - this.ended = true; - return true; - } - - if (strm.avail_in === 0) break; - } - - return true; -}; - - -/** - * Inflate#onData(chunk) -> Void - * - chunk (Uint8Array|String): output data. When string output requested, - * each chunk will be string. - * - * By default, stores data blocks in `chunks[]` property and glue - * those in `onEnd`. Override this handler, if you need another behaviour. - **/ -Inflate$1.prototype.onData = function (chunk) { - this.chunks.push(chunk); -}; - - -/** - * Inflate#onEnd(status) -> Void - * - status (Number): inflate status. 0 (Z_OK) on success, - * other if not. - * - * Called either after you tell inflate that the input stream is - * complete (Z_FINISH). By default - join collected chunks, - * free memory and fill `results` / `err` properties. - **/ -Inflate$1.prototype.onEnd = function (status) { - // On success - join - if (status === Z_OK) { - if (this.options.to === 'string') { - this.result = this.chunks.join(''); - } else { - this.result = common.flattenChunks(this.chunks); - } - } - this.chunks = []; - this.err = status; - this.msg = this.strm.msg; -}; - - -/** - * inflate(data[, options]) -> Uint8Array|String - * - data (Uint8Array|ArrayBuffer): input data to decompress. - * - options (Object): zlib inflate options. - * - * Decompress `data` with inflate/ungzip and `options`. Autodetect - * format via wrapper header by default. That's why we don't provide - * separate `ungzip` method. - * - * Supported options are: - * - * - windowBits - * - * [http://zlib.net/manual.html#Advanced](http://zlib.net/manual.html#Advanced) - * for more information. - * - * Sugar (options): - * - * - `raw` (Boolean) - say that we work with raw stream, if you don't wish to specify - * negative windowBits implicitly. - * - `to` (String) - if equal to 'string', then result will be converted - * from utf8 to utf16 (javascript) string. When string output requested, - * chunk length can differ from `chunkSize`, depending on content. - * - * - * ##### Example: - * - * ```javascript - * const pako = require('pako'); - * const input = pako.deflate(new Uint8Array([1,2,3,4,5,6,7,8,9])); - * let output; - * - * try { - * output = pako.inflate(input); - * } catch (err) { - * console.log(err); - * } - * ``` - **/ -function inflate$1(input, options) { - const inflator = new Inflate$1(options); - - inflator.push(input); - - // That will never happens, if you don't cheat with options :) - if (inflator.err) throw inflator.msg || messages[inflator.err]; - - return inflator.result; -} - - -/** - * inflateRaw(data[, options]) -> Uint8Array|String - * - data (Uint8Array|ArrayBuffer): input data to decompress. - * - options (Object): zlib inflate options. - * - * The same as [[inflate]], but creates raw data, without wrapper - * (header and adler32 crc). - **/ -function inflateRaw$1(input, options) { - options = options || {}; - options.raw = true; - return inflate$1(input, options); -} - - -/** - * ungzip(data[, options]) -> Uint8Array|String - * - data (Uint8Array|ArrayBuffer): input data to decompress. - * - options (Object): zlib inflate options. - * - * Just shortcut to [[inflate]], because it autodetects format - * by header.content. Done for convenience. - **/ - - -var Inflate_1$1 = Inflate$1; -var inflate_2 = inflate$1; -var inflateRaw_1$1 = inflateRaw$1; -var ungzip$1 = inflate$1; -var constants = constants$2; - -var inflate_1$1 = { - Inflate: Inflate_1$1, - inflate: inflate_2, - inflateRaw: inflateRaw_1$1, - ungzip: ungzip$1, - constants: constants -}; - -const { Deflate, deflate, deflateRaw, gzip } = deflate_1$1; - -const { Inflate, inflate, inflateRaw, ungzip } = inflate_1$1; - - - -var Deflate_1 = Deflate; -var deflate_1 = deflate; -var deflateRaw_1 = deflateRaw; -var gzip_1 = gzip; -var Inflate_1 = Inflate; -var inflate_1 = inflate; -var inflateRaw_1 = inflateRaw; -var ungzip_1 = ungzip; -var constants_1 = constants$2; - -var pako = { - Deflate: Deflate_1, - deflate: deflate_1, - deflateRaw: deflateRaw_1, - gzip: gzip_1, - Inflate: Inflate_1, - inflate: inflate_1, - inflateRaw: inflateRaw_1, - ungzip: ungzip_1, - constants: constants_1 -}; - -/* https://grpc.io/ */ -class GRPC { - static name = "gRPC"; - static version = "1.0.3"; - static about = () => log("", `🟧 ${this.name} v${this.version}`, ""); - - static decode(bytesBody = new Uint8Array([])) { - log(`☑️ gRPC.decode`, ""); - // 先拆分gRPC校验头和protobuf数据体 - const Header = bytesBody.slice(0, 5); - let body = bytesBody.slice(5); - switch (Header[0]) { - case 0: // unGzip - default: - break; - case 1: // Gzip - switch ($platform) { - case "Surge": - body = $utils.ungzip(body); - break; - default: - body = pako.ungzip(body); // 解压缩protobuf数据体 - break; - } Header[0] = 0; // unGzip - break; - } log(`✅ gRPC.decode`, ""); - return body; - }; - - static encode(body = new Uint8Array([]), encoding = "identity") { - log(`☑️ gRPC.encode`, ""); - // Header: 1位:是否校验数据 (0或者1) + 4位:校验值(数据长度) - const Header = new Uint8Array(5); - const Checksum = this.#Checksum(body.length); // 校验值为未压缩情况下的数据长度, 不是压缩后的长度 - Header.set(Checksum, 1); // 1-4位: 校验值(4位) - switch (encoding) { - case "gzip": - Header.set([1], 0); // 0位:Encoding类型,当为1的时候, app会校验1-4位的校验值是否正确 - body = pako.gzip(body); - break; - case "identity": - case undefined: - default: - Header.set([0], 0); // 0位:Encoding类型,当为1的时候, app会校验1-4位的校验值是否正确 - break; - } const BytesBody = new Uint8Array(Header.length + body.length); - BytesBody.set(Header, 0); // 0-4位:gRPC校验头 - BytesBody.set(body, 5); // 5-end位:protobuf数据 - log(`✅ gRPC.encode`, ""); - return BytesBody; - }; - - // 计算校验和 (B站为数据本体字节数) - static #Checksum(num = 0) { - let array = new ArrayBuffer(4); // an Int32 takes 4 bytes - let view = new DataView(array); - // 首位填充计算过的新数据长度 - view.setUint32(0, num, false); // byteOffset = 0; litteEndian = false - return new Uint8Array(array); - }; -} - -var Settings$7 = { - Switch: true -}; -var Configs$3 = { - Storefront: [ - [ - "AE", - "143481" - ], - [ - "AF", - "143610" - ], - [ - "AG", - "143540" - ], - [ - "AI", - "143538" - ], - [ - "AL", - "143575" - ], - [ - "AM", - "143524" - ], - [ - "AO", - "143564" - ], - [ - "AR", - "143505" - ], - [ - "AT", - "143445" - ], - [ - "AU", - "143460" - ], - [ - "AZ", - "143568" - ], - [ - "BA", - "143612" - ], - [ - "BB", - "143541" - ], - [ - "BD", - "143490" - ], - [ - "BE", - "143446" - ], - [ - "BF", - "143578" - ], - [ - "BG", - "143526" - ], - [ - "BH", - "143559" - ], - [ - "BJ", - "143576" - ], - [ - "BM", - "143542" - ], - [ - "BN", - "143560" - ], - [ - "BO", - "143556" - ], - [ - "BR", - "143503" - ], - [ - "BS", - "143539" - ], - [ - "BT", - "143577" - ], - [ - "BW", - "143525" - ], - [ - "BY", - "143565" - ], - [ - "BZ", - "143555" - ], - [ - "CA", - "143455" - ], - [ - "CD", - "143613" - ], - [ - "CG", - "143582" - ], - [ - "CH", - "143459" - ], - [ - "CI", - "143527" - ], - [ - "CL", - "143483" - ], - [ - "CM", - "143574" - ], - [ - "CN", - "143465" - ], - [ - "CO", - "143501" - ], - [ - "CR", - "143495" - ], - [ - "CV", - "143580" - ], - [ - "CY", - "143557" - ], - [ - "CZ", - "143489" - ], - [ - "DE", - "143443" - ], - [ - "DK", - "143458" - ], - [ - "DM", - "143545" - ], - [ - "DO", - "143508" - ], - [ - "DZ", - "143563" - ], - [ - "EC", - "143509" - ], - [ - "EE", - "143518" - ], - [ - "EG", - "143516" - ], - [ - "ES", - "143454" - ], - [ - "FI", - "143447" - ], - [ - "FJ", - "143583" - ], - [ - "FM", - "143591" - ], - [ - "FR", - "143442" - ], - [ - "GA", - "143614" - ], - [ - "GB", - "143444" - ], - [ - "GD", - "143546" - ], - [ - "GF", - "143615" - ], - [ - "GH", - "143573" - ], - [ - "GM", - "143584" - ], - [ - "GR", - "143448" - ], - [ - "GT", - "143504" - ], - [ - "GW", - "143585" - ], - [ - "GY", - "143553" - ], - [ - "HK", - "143463" - ], - [ - "HN", - "143510" - ], - [ - "HR", - "143494" - ], - [ - "HU", - "143482" - ], - [ - "ID", - "143476" - ], - [ - "IE", - "143449" - ], - [ - "IL", - "143491" - ], - [ - "IN", - "143467" - ], - [ - "IQ", - "143617" - ], - [ - "IS", - "143558" - ], - [ - "IT", - "143450" - ], - [ - "JM", - "143511" - ], - [ - "JO", - "143528" - ], - [ - "JP", - "143462" - ], - [ - "KE", - "143529" - ], - [ - "KG", - "143586" - ], - [ - "KH", - "143579" - ], - [ - "KN", - "143548" - ], - [ - "KP", - "143466" - ], - [ - "KR", - "143466" - ], - [ - "KW", - "143493" - ], - [ - "KY", - "143544" - ], - [ - "KZ", - "143517" - ], - [ - "TC", - "143552" - ], - [ - "TD", - "143581" - ], - [ - "TJ", - "143603" - ], - [ - "TH", - "143475" - ], - [ - "TM", - "143604" - ], - [ - "TN", - "143536" - ], - [ - "TO", - "143608" - ], - [ - "TR", - "143480" - ], - [ - "TT", - "143551" - ], - [ - "TW", - "143470" - ], - [ - "TZ", - "143572" - ], - [ - "LA", - "143587" - ], - [ - "LB", - "143497" - ], - [ - "LC", - "143549" - ], - [ - "LI", - "143522" - ], - [ - "LK", - "143486" - ], - [ - "LR", - "143588" - ], - [ - "LT", - "143520" - ], - [ - "LU", - "143451" - ], - [ - "LV", - "143519" - ], - [ - "LY", - "143567" - ], - [ - "MA", - "143620" - ], - [ - "MD", - "143523" - ], - [ - "ME", - "143619" - ], - [ - "MG", - "143531" - ], - [ - "MK", - "143530" - ], - [ - "ML", - "143532" - ], - [ - "MM", - "143570" - ], - [ - "MN", - "143592" - ], - [ - "MO", - "143515" - ], - [ - "MR", - "143590" - ], - [ - "MS", - "143547" - ], - [ - "MT", - "143521" - ], - [ - "MU", - "143533" - ], - [ - "MV", - "143488" - ], - [ - "MW", - "143589" - ], - [ - "MX", - "143468" - ], - [ - "MY", - "143473" - ], - [ - "MZ", - "143593" - ], - [ - "NA", - "143594" - ], - [ - "NE", - "143534" - ], - [ - "NG", - "143561" - ], - [ - "NI", - "143512" - ], - [ - "NL", - "143452" - ], - [ - "NO", - "143457" - ], - [ - "NP", - "143484" - ], - [ - "NR", - "143606" - ], - [ - "NZ", - "143461" - ], - [ - "OM", - "143562" - ], - [ - "PA", - "143485" - ], - [ - "PE", - "143507" - ], - [ - "PG", - "143597" - ], - [ - "PH", - "143474" - ], - [ - "PK", - "143477" - ], - [ - "PL", - "143478" - ], - [ - "PT", - "143453" - ], - [ - "PW", - "143595" - ], - [ - "PY", - "143513" - ], - [ - "QA", - "143498" - ], - [ - "RO", - "143487" - ], - [ - "RS", - "143500" - ], - [ - "RU", - "143469" - ], - [ - "RW", - "143621" - ], - [ - "SA", - "143479" - ], - [ - "SB", - "143601" - ], - [ - "SC", - "143599" - ], - [ - "SE", - "143456" - ], - [ - "SG", - "143464" - ], - [ - "SI", - "143499" - ], - [ - "SK", - "143496" - ], - [ - "SL", - "143600" - ], - [ - "SN", - "143535" - ], - [ - "SR", - "143554" - ], - [ - "ST", - "143598" - ], - [ - "SV", - "143506" - ], - [ - "SZ", - "143602" - ], - [ - "UA", - "143492" - ], - [ - "UG", - "143537" - ], - [ - "US", - "143441" - ], - [ - "UY", - "143514" - ], - [ - "UZ", - "143566" - ], - [ - "VC", - "143550" - ], - [ - "VE", - "143502" - ], - [ - "VG", - "143543" - ], - [ - "VN", - "143471" - ], - [ - "VU", - "143609" - ], - [ - "XK", - "143624" - ], - [ - "YE", - "143571" - ], - [ - "ZA", - "143472" - ], - [ - "ZM", - "143622" - ], - [ - "ZW", - "143605" - ] - ] -}; -var Default = { - Settings: Settings$7, - Configs: Configs$3 -}; - -var Default$1 = /*#__PURE__*/Object.freeze({ - __proto__: null, - Configs: Configs$3, - Settings: Settings$7, - default: Default -}); - -var Settings$6 = { - Switch: true, - PEP: { - GCC: "US" - } -}; -var Location$1 = { - Settings: Settings$6 -}; - -var Location$2 = /*#__PURE__*/Object.freeze({ - __proto__: null, - Settings: Settings$6, - default: Location$1 -}); - -var Settings$5 = { - Switch: true, - UrlInfoSet: { - Dispatcher: "AutoNavi", - Directions: "AutoNavi", - RAP: "Apple", - LocationShift: "AUTO" - }, - TileSet: { - "Map": "CN", - Satellite: "HYBRID", - Traffic: "CN", - POI: "CN", - Flyover: "XX", - Munin: "XX" - }, - GeoManifest: { - Dynamic: { - Config: { - CountryCode: { - "default": "CN", - iOS: "AUTO", - iPadOS: "AUTO", - watchOS: "US", - macOS: "AUTO" - } - } - } - }, - Config: { - Announcements: { - "Environment:": { - "default": "AUTO", - iOS: "AUTO", - iPadOS: "AUTO", - watchOS: "AUTO", - macOS: "AUTO" - } - } - } -}; -var Configs$2 = { -}; -var Maps = { - Settings: Settings$5, - Configs: Configs$2 -}; - -var Maps$1 = /*#__PURE__*/Object.freeze({ - __proto__: null, - Configs: Configs$2, - Settings: Settings$5, - default: Maps -}); - -var Settings$4 = { - Switch: true, - CountryCode: "US", - NewsPlusUser: true -}; -var News = { - Settings: Settings$4 -}; - -var News$1 = /*#__PURE__*/Object.freeze({ - __proto__: null, - Settings: Settings$4, - default: News -}); - -var Settings$3 = { - Switch: true, - CountryCode: "US", - canUse: true -}; -var PrivateRelay = { - Settings: Settings$3 -}; - -var PrivateRelay$1 = /*#__PURE__*/Object.freeze({ - __proto__: null, - Settings: Settings$3, - default: PrivateRelay -}); - -var Settings$2 = { - Switch: true, - CountryCode: "SG", - Region: "AUTO", - Domains: [ - "web", - "itunes", - "app_store", - "movies", - "restaurants", - "maps" - ], - Functions: [ - "flightutilities", - "lookup", - "mail", - "messages", - "news", - "safari", - "siri", - "spotlight", - "visualintelligence" - ], - Safari_Smart_History: true -}; -var Configs$1 = { - VisualIntelligence: { - enabled_domains: [ - "pets", - "media", - "books", - "art", - "nature", - "landmarks" - ], - supported_domains: [ - "ART", - "BOOK", - "MEDIA", - "LANDMARK", - "ANIMALS", - "BIRDS", - "FOOD", - "SIGN_SYMBOL", - "AUTO_SYMBOL", - "DOGS", - "NATURE", - "NATURAL_LANDMARK", - "INSECTS", - "REPTILES", - "ALBUM", - "STOREFRONT", - "LAUNDRY_CARE_SYMBOL", - "CATS", - "OBJECT_2D", - "SCULPTURE", - "SKYLINE", - "MAMMALS" - ] - } -}; -var Siri = { - Settings: Settings$2, - Configs: Configs$1 -}; - -var Siri$1 = /*#__PURE__*/Object.freeze({ - __proto__: null, - Configs: Configs$1, - Settings: Settings$2, - default: Siri -}); - -var Settings$1 = { - Switch: "true", - CountryCode: "US", - MultiAccount: "false", - Universal: "true" -}; -var TestFlight = { - Settings: Settings$1 -}; - -var TestFlight$1 = /*#__PURE__*/Object.freeze({ - __proto__: null, - Settings: Settings$1, - default: TestFlight -}); - -var Settings = { - Switch: true, - "Third-Party": false, - HLSUrl: "play-edge.itunes.apple.com", - ServerUrl: "play.itunes.apple.com", - Tabs: [ - "WatchNow", - "Originals", - "MLS", - "Sports", - "Kids", - "Store", - "Movies", - "TV", - "ChannelsAndApps", - "Library", - "Search" - ], - CountryCode: { - Configs: "AUTO", - Settings: "AUTO", - View: [ - "SG", - "TW" - ], - WatchNow: "AUTO", - Channels: "AUTO", - Originals: "AUTO", - Sports: "US", - Kids: "US", - Store: "AUTO", - Movies: "AUTO", - TV: "AUTO", - Persons: "SG", - Search: "AUTO", - Others: "AUTO" - } -}; -var Configs = { - Locale: [ - [ - "AU", - "en-AU" - ], - [ - "CA", - "en-CA" - ], - [ - "GB", - "en-GB" - ], - [ - "KR", - "ko-KR" - ], - [ - "HK", - "yue-Hant" - ], - [ - "JP", - "ja-JP" - ], - [ - "MO", - "zh-Hant" - ], - [ - "TW", - "zh-Hant" - ], - [ - "US", - "en-US" - ], - [ - "SG", - "zh-Hans" - ] - ], - Tabs: [ - { - title: "主页", - type: "WatchNow", - universalLinks: [ - "https://tv.apple.com/watch-now", - "https://tv.apple.com/home" - ], - destinationType: "Target", - target: { - id: "tahoma_watchnow", - type: "Root", - url: "https://tv.apple.com/watch-now" - }, - isSelected: true - }, - { - title: "Apple TV+", - type: "Originals", - universalLinks: [ - "https://tv.apple.com/channel/tvs.sbd.4000", - "https://tv.apple.com/atv" - ], - destinationType: "Target", - target: { - id: "tvs.sbd.4000", - type: "Brand", - url: "https://tv.apple.com/us/channel/tvs.sbd.4000" - } - }, - { - title: "MLS Season Pass", - type: "MLS", - universalLinks: [ - "https://tv.apple.com/mls" - ], - destinationType: "Target", - target: { - id: "tvs.sbd.7000", - type: "Brand", - url: "https://tv.apple.com/us/channel/tvs.sbd.7000" - } - }, - { - title: "体育节目", - type: "Sports", - universalLinks: [ - "https://tv.apple.com/sports" - ], - destinationType: "Target", - target: { - id: "tahoma_sports", - type: "Root", - url: "https://tv.apple.com/sports" - } - }, - { - title: "儿童", - type: "Kids", - universalLinks: [ - "https://tv.apple.com/kids" - ], - destinationType: "Target", - target: { - id: "tahoma_kids", - type: "Root", - url: "https://tv.apple.com/kids" - } - }, - { - title: "电影", - type: "Movies", - universalLinks: [ - "https://tv.apple.com/movies" - ], - destinationType: "Target", - target: { - id: "tahoma_movies", - type: "Root", - url: "https://tv.apple.com/movies" - } - }, - { - title: "电视节目", - type: "TV", - universalLinks: [ - "https://tv.apple.com/tv-shows" - ], - destinationType: "Target", - target: { - id: "tahoma_tvshows", - type: "Root", - url: "https://tv.apple.com/tv-shows" - } - }, - { - title: "商店", - type: "Store", - universalLinks: [ - "https://tv.apple.com/store" - ], - destinationType: "SubTabs", - subTabs: [ - { - title: "电影", - type: "Movies", - universalLinks: [ - "https://tv.apple.com/movies" - ], - destinationType: "Target", - target: { - id: "tahoma_movies", - type: "Root", - url: "https://tv.apple.com/movies" - } - }, - { - title: "电视节目", - type: "TV", - universalLinks: [ - "https://tv.apple.com/tv-shows" - ], - destinationType: "Target", - target: { - id: "tahoma_tvshows", - type: "Root", - url: "https://tv.apple.com/tv-shows" - } - } - ] - }, - { - title: "频道和 App", - destinationType: "SubTabs", - subTabsPlacementType: "ExpandedList", - type: "ChannelsAndApps", - subTabs: [ - ] - }, - { - title: "资料库", - type: "Library", - destinationType: "Client" - }, - { - title: "搜索", - type: "Search", - universalLinks: [ - "https://tv.apple.com/search" - ], - destinationType: "Target", - target: { - id: "tahoma_search", - type: "Root", - url: "https://tv.apple.com/search" - } - } - ], - i18n: { - WatchNow: [ - [ - "en", - "Home" - ], - [ - "zh", - "主页" - ], - [ - "zh-Hans", - "主頁" - ], - [ - "zh-Hant", - "主頁" - ] - ], - Movies: [ - [ - "en", - "Movies" - ], - [ - "zh", - "电影" - ], - [ - "zh-Hans", - "电影" - ], - [ - "zh-Hant", - "電影" - ] - ], - TV: [ - [ - "en", - "TV" - ], - [ - "zh", - "电视节目" - ], - [ - "zh-Hans", - "电视节目" - ], - [ - "zh-Hant", - "電視節目" - ] - ], - Store: [ - [ - "en", - "Store" - ], - [ - "zh", - "商店" - ], - [ - "zh-Hans", - "商店" - ], - [ - "zh-Hant", - "商店" - ] - ], - Sports: [ - [ - "en", - "Sports" - ], - [ - "zh", - "体育节目" - ], - [ - "zh-Hans", - "体育节目" - ], - [ - "zh-Hant", - "體育節目" - ] - ], - Kids: [ - [ - "en", - "Kids" - ], - [ - "zh", - "儿童" - ], - [ - "zh-Hans", - "儿童" - ], - [ - "zh-Hant", - "兒童" - ] - ], - Library: [ - [ - "en", - "Library" - ], - [ - "zh", - "资料库" - ], - [ - "zh-Hans", - "资料库" - ], - [ - "zh-Hant", - "資料庫" - ] - ], - Search: [ - [ - "en", - "Search" - ], - [ - "zh", - "搜索" - ], - [ - "zh-Hans", - "搜索" - ], - [ - "zh-Hant", - "蒐索" - ] - ] - } -}; -var TV = { - Settings: Settings, - Configs: Configs -}; - -var TV$1 = /*#__PURE__*/Object.freeze({ - __proto__: null, - Configs: Configs, - Settings: Settings, - default: TV -}); - -var Database$1 = Database = { - "Default": Default$1, - "Location": Location$2, - "Maps": Maps$1, - "News": News$1, - "PrivateRelay": PrivateRelay$1, - "Siri": Siri$1, - "TestFlight": TestFlight$1, - "TV": TV$1 -}; - -/** - * Get Storage Variables - * @link https://github.com/NanoCat-Me/utils/blob/main/getStorage.mjs - * @author VirgilClyne - * @param {String} key - Persistent Store Key - * @param {Array} names - Platform Names - * @param {Object} database - Default Database - * @return {Object} { Settings, Caches, Configs } - */ -function getStorage(key, names, database) { - //log(`☑️ getStorage, Get Environment Variables`, ""); - /***************** BoxJs *****************/ - // 包装为局部变量,用完释放内存 - // BoxJs的清空操作返回假值空字符串, 逻辑或操作符会在左侧操作数为假值时返回右侧操作数。 - let BoxJs = Storage.getItem(key, database); - //log(`🚧 getStorage, Get Environment Variables`, `BoxJs类型: ${typeof BoxJs}`, `BoxJs内容: ${JSON.stringify(BoxJs)}`, ""); - /***************** Argument *****************/ - let Argument = {}; - switch (typeof $argument) { - case "string": - let arg = Object.fromEntries($argument.split("&").map((item) => item.split("=").map(i => i.replace(/\"/g, '')))); - for (let item in arg) Lodash.set(Argument, item, arg[item]); - break; - case "object": - for (let item in $argument) Lodash.set(Argument, item, $argument[item]); - break; - } //log(`✅ getStorage, Get Environment Variables`, `Argument类型: ${typeof Argument}`, `Argument内容: ${JSON.stringify(Argument)}`, ""); - /***************** Store *****************/ - const Store = { Settings: database?.Default?.Settings || {}, Configs: database?.Default?.Configs || {}, Caches: {} }; - if (!Array.isArray(names)) names = [names]; - //log(`🚧 getStorage, Get Environment Variables`, `names类型: ${typeof names}`, `names内容: ${JSON.stringify(names)}`, ""); - for (let name of names) { - Store.Settings = { ...Store.Settings, ...database?.[name]?.Settings, ...Argument, ...BoxJs?.[name]?.Settings }; - Store.Configs = { ...Store.Configs, ...database?.[name]?.Configs }; - if (BoxJs?.[name]?.Caches && typeof BoxJs?.[name]?.Caches === "string") BoxJs[name].Caches = JSON.parse(BoxJs?.[name]?.Caches); - Store.Caches = { ...Store.Caches, ...BoxJs?.[name]?.Caches }; - } //log(`🚧 getStorage, Get Environment Variables`, `Store.Settings类型: ${typeof Store.Settings}`, `Store.Settings: ${JSON.stringify(Store.Settings)}`, ""); - traverseObject(Store.Settings, (key, value) => { - //log(`🚧 getStorage, traverseObject`, `${key}: ${typeof value}`, `${key}: ${JSON.stringify(value)}`, ""); - if (value === "true" || value === "false") value = JSON.parse(value); // 字符串转Boolean - else if (typeof value === "string") { - if (value.includes(",")) value = value.split(",").map(item => string2number(item)); // 字符串转数组转数字 - else value = string2number(value); // 字符串转数字 - } return value; - }); - //log(`✅ getStorage, Get Environment Variables`, `Store: ${typeof Store.Caches}`, `Store内容: ${JSON.stringify(Store)}`, ""); - return Store; - /***************** function *****************/ - function traverseObject(o, c) { for (var t in o) { var n = o[t]; o[t] = "object" == typeof n && null !== n ? traverseObject(n, c) : c(t, n); } return o } - function string2number(string) { if (string && !isNaN(string)) string = parseInt(string, 10); return string } -} - -/** - * Set Environment Variables - * @author VirgilClyne - * @param {String} name - Persistent Store Key - * @param {Array} platforms - Platform Names - * @param {Object} database - Default DataBase - * @return {Object} { Settings, Caches, Configs } - */ -function setENV(name, platforms, database) { - log(`☑️ Set Environment Variables`, ""); - let { Settings, Caches, Configs } = getStorage(name, platforms, database); - /***************** Settings *****************/ - switch (platforms) { - case "WeatherKit": - if (!Array.isArray(Settings?.AQI?.ReplaceProviders)) Lodash.set(Settings, "AQI.ReplaceProviders", (Settings?.AQI?.ReplaceProviders) ? [Settings.AQI.ReplaceProviders.toString()] : []); - if (Settings.AQI.ReplaceProviders.includes("TWC")) Settings.AQI.ReplaceProviders.push("The Weather Channel"); - if (Settings.AQI.ReplaceProviders.includes("QWeather")) Settings.AQI.ReplaceProviders.push("和风天气"); - Settings.AQI.ReplaceProviders.push(undefined); - if (!Array.isArray(Settings?.AQI?.Local?.ReplaceScales)) Lodash.set(Settings, "AQI.Local.ReplaceScales", (Settings?.AQI?.Local?.ReplaceScales) ? [Settings.AQI.Local.ReplaceScales.toString()] : []); - break; - case "Siri": - if (!Array.isArray(Settings?.Domains)) Lodash.set(Settings, "Domains", (Settings?.Domains) ? [Settings.Domains.toString()] : []); - if (!Array.isArray(Settings?.Functions)) Lodash.set(Settings, "Functions", (Settings?.Functions) ? [Settings.Functions.toString()] : []); - break; - case "TV": - if (!Array.isArray(Settings?.Tabs)) Lodash.set(Settings, "Tabs", (Settings?.Tabs) ? [Settings.Tabs.toString()] : []); - break; - } log(`✅ Set Environment Variables, Settings: ${typeof Settings}, Settings内容: ${JSON.stringify(Settings)}`, ""); - /***************** Caches *****************/ - //log(`✅ Set Environment Variables, Caches: ${typeof Caches}, Caches内容: ${JSON.stringify(Caches)}`, ""); - /***************** Configs *****************/ - Configs.Storefront = new Map(Configs.Storefront); - if (Configs.Locale) Configs.Locale = new Map(Configs.Locale); - if (Configs.i18n) for (let type in Configs.i18n) Configs.i18n[type] = new Map(Configs.i18n[type]); - return { Settings, Caches, Configs }; -} - -function modifyPegasusQueryContext(queryContext, Settings) { - console.log(`☑️ modify PegasusQueryContext`, ""); - Locale = queryContext.locale; - [Language, CountryCode] = Locale?.split("_") ?? []; - console.log(`🚧 Locale: ${Locale}, Language: ${Language}, CountryCode: ${CountryCode}`); - switch (Settings.CountryCode) { - case "AUTO": - Settings.CountryCode = CountryCode; - //break; - default: - if (queryContext?.countryCode) queryContext.countryCode = Settings.CountryCode; - //if (data?.siriPegasusContext?.conversationContext?.cc) data.siriPegasusContext.conversationContext.cc = Settings.CountryCode; - break; - } switch (Settings.Region) { - case "AUTO": - break; - default: - if (queryContext?.region) queryContext.region = Settings.Region; - break; - } if (queryContext?.skuRegion === "CH") queryContext.skuRegion = "LL"; - //delete queryContext?.location; - console.log(`✅ modify PegasusQueryContext`, ""); - return queryContext; -} - -/** - * Get the type of a JSON value. - * Distinguishes between array, null and object. - */ -function typeofJsonValue(value) { - let t = typeof value; - if (t == "object") { - if (Array.isArray(value)) - return "array"; - if (value === null) - return "null"; - } - return t; -} -/** - * Is this a JSON object (instead of an array or null)? - */ -function isJsonObject(value) { - return value !== null && typeof value == "object" && !Array.isArray(value); -} - -// lookup table from base64 character to byte -let encTable = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'.split(''); -// lookup table from base64 character *code* to byte because lookup by number is fast -let decTable = []; -for (let i = 0; i < encTable.length; i++) - decTable[encTable[i].charCodeAt(0)] = i; -// support base64url variants -decTable["-".charCodeAt(0)] = encTable.indexOf("+"); -decTable["_".charCodeAt(0)] = encTable.indexOf("/"); -/** - * Decodes a base64 string to a byte array. - * - * - ignores white-space, including line breaks and tabs - * - allows inner padding (can decode concatenated base64 strings) - * - does not require padding - * - understands base64url encoding: - * "-" instead of "+", - * "_" instead of "/", - * no padding - */ -function base64decode(base64Str) { - // estimate byte size, not accounting for inner padding and whitespace - let es = base64Str.length * 3 / 4; - // if (es % 3 !== 0) - // throw new Error('invalid base64 string'); - if (base64Str[base64Str.length - 2] == '=') - es -= 2; - else if (base64Str[base64Str.length - 1] == '=') - es -= 1; - let bytes = new Uint8Array(es), bytePos = 0, // position in byte array - groupPos = 0, // position in base64 group - b, // current byte - p = 0 // previous byte - ; - for (let i = 0; i < base64Str.length; i++) { - b = decTable[base64Str.charCodeAt(i)]; - if (b === undefined) { - // noinspection FallThroughInSwitchStatementJS - switch (base64Str[i]) { - case '=': - groupPos = 0; // reset state when padding found - case '\n': - case '\r': - case '\t': - case ' ': - continue; // skip white-space, and padding - default: - throw Error(`invalid base64 string.`); - } - } - switch (groupPos) { - case 0: - p = b; - groupPos = 1; - break; - case 1: - bytes[bytePos++] = p << 2 | (b & 48) >> 4; - p = b; - groupPos = 2; - break; - case 2: - bytes[bytePos++] = (p & 15) << 4 | (b & 60) >> 2; - p = b; - groupPos = 3; - break; - case 3: - bytes[bytePos++] = (p & 3) << 6 | b; - groupPos = 0; - break; - } - } - if (groupPos == 1) - throw Error(`invalid base64 string.`); - return bytes.subarray(0, bytePos); -} -/** - * Encodes a byte array to a base64 string. - * Adds padding at the end. - * Does not insert newlines. - */ -function base64encode(bytes) { - let base64 = '', groupPos = 0, // position in base64 group - b, // current byte - p = 0; // carry over from previous byte - for (let i = 0; i < bytes.length; i++) { - b = bytes[i]; - switch (groupPos) { - case 0: - base64 += encTable[b >> 2]; - p = (b & 3) << 4; - groupPos = 1; - break; - case 1: - base64 += encTable[p | b >> 4]; - p = (b & 15) << 2; - groupPos = 2; - break; - case 2: - base64 += encTable[p | b >> 6]; - base64 += encTable[b & 63]; - groupPos = 0; - break; - } - } - // padding required? - if (groupPos) { - base64 += encTable[p]; - base64 += '='; - if (groupPos == 1) - base64 += '='; - } - return base64; -} - -/** - * This handler implements the default behaviour for unknown fields. - * When reading data, unknown fields are stored on the message, in a - * symbol property. - * When writing data, the symbol property is queried and unknown fields - * are serialized into the output again. - */ -var UnknownFieldHandler; -(function (UnknownFieldHandler) { - /** - * The symbol used to store unknown fields for a message. - * The property must conform to `UnknownFieldContainer`. - */ - UnknownFieldHandler.symbol = Symbol.for("protobuf-ts/unknown"); - /** - * Store an unknown field during binary read directly on the message. - * This method is compatible with `BinaryReadOptions.readUnknownField`. - */ - UnknownFieldHandler.onRead = (typeName, message, fieldNo, wireType, data) => { - let container = is(message) ? message[UnknownFieldHandler.symbol] : message[UnknownFieldHandler.symbol] = []; - container.push({ no: fieldNo, wireType, data }); - }; - /** - * Write unknown fields stored for the message to the writer. - * This method is compatible with `BinaryWriteOptions.writeUnknownFields`. - */ - UnknownFieldHandler.onWrite = (typeName, message, writer) => { - for (let { no, wireType, data } of UnknownFieldHandler.list(message)) - writer.tag(no, wireType).raw(data); - }; - /** - * List unknown fields stored for the message. - * Note that there may be multiples fields with the same number. - */ - UnknownFieldHandler.list = (message, fieldNo) => { - if (is(message)) { - let all = message[UnknownFieldHandler.symbol]; - return fieldNo ? all.filter(uf => uf.no == fieldNo) : all; - } - return []; - }; - /** - * Returns the last unknown field by field number. - */ - UnknownFieldHandler.last = (message, fieldNo) => UnknownFieldHandler.list(message, fieldNo).slice(-1)[0]; - const is = (message) => message && Array.isArray(message[UnknownFieldHandler.symbol]); -})(UnknownFieldHandler || (UnknownFieldHandler = {})); -/** - * Protobuf binary format wire types. - * - * A wire type provides just enough information to find the length of the - * following value. - * - * See https://developers.google.com/protocol-buffers/docs/encoding#structure - */ -var WireType; -(function (WireType) { - /** - * Used for int32, int64, uint32, uint64, sint32, sint64, bool, enum - */ - WireType[WireType["Varint"] = 0] = "Varint"; - /** - * Used for fixed64, sfixed64, double. - * Always 8 bytes with little-endian byte order. - */ - WireType[WireType["Bit64"] = 1] = "Bit64"; - /** - * Used for string, bytes, embedded messages, packed repeated fields - * - * Only repeated numeric types (types which use the varint, 32-bit, - * or 64-bit wire types) can be packed. In proto3, such fields are - * packed by default. - */ - WireType[WireType["LengthDelimited"] = 2] = "LengthDelimited"; - /** - * Used for groups - * @deprecated - */ - WireType[WireType["StartGroup"] = 3] = "StartGroup"; - /** - * Used for groups - * @deprecated - */ - WireType[WireType["EndGroup"] = 4] = "EndGroup"; - /** - * Used for fixed32, sfixed32, float. - * Always 4 bytes with little-endian byte order. - */ - WireType[WireType["Bit32"] = 5] = "Bit32"; -})(WireType || (WireType = {})); - -// Copyright 2008 Google Inc. All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are -// met: -// -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above -// copyright notice, this list of conditions and the following disclaimer -// in the documentation and/or other materials provided with the -// distribution. -// * Neither the name of Google Inc. nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -// Code generated by the Protocol Buffer compiler is owned by the owner -// of the input file used when generating it. This code is not -// standalone and requires a support library to be linked with it. This -// support library is itself covered by the above license. -/** - * Read a 64 bit varint as two JS numbers. - * - * Returns tuple: - * [0]: low bits - * [0]: high bits - * - * Copyright 2008 Google Inc. All rights reserved. - * - * See https://github.com/protocolbuffers/protobuf/blob/8a71927d74a4ce34efe2d8769fda198f52d20d12/js/experimental/runtime/kernel/buffer_decoder.js#L175 - */ -function varint64read() { - let lowBits = 0; - let highBits = 0; - for (let shift = 0; shift < 28; shift += 7) { - let b = this.buf[this.pos++]; - lowBits |= (b & 0x7F) << shift; - if ((b & 0x80) == 0) { - this.assertBounds(); - return [lowBits, highBits]; - } - } - let middleByte = this.buf[this.pos++]; - // last four bits of the first 32 bit number - lowBits |= (middleByte & 0x0F) << 28; - // 3 upper bits are part of the next 32 bit number - highBits = (middleByte & 0x70) >> 4; - if ((middleByte & 0x80) == 0) { - this.assertBounds(); - return [lowBits, highBits]; - } - for (let shift = 3; shift <= 31; shift += 7) { - let b = this.buf[this.pos++]; - highBits |= (b & 0x7F) << shift; - if ((b & 0x80) == 0) { - this.assertBounds(); - return [lowBits, highBits]; - } - } - throw new Error('invalid varint'); -} -/** - * Write a 64 bit varint, given as two JS numbers, to the given bytes array. - * - * Copyright 2008 Google Inc. All rights reserved. - * - * See https://github.com/protocolbuffers/protobuf/blob/8a71927d74a4ce34efe2d8769fda198f52d20d12/js/experimental/runtime/kernel/writer.js#L344 - */ -function varint64write(lo, hi, bytes) { - for (let i = 0; i < 28; i = i + 7) { - const shift = lo >>> i; - const hasNext = !((shift >>> 7) == 0 && hi == 0); - const byte = (hasNext ? shift | 0x80 : shift) & 0xFF; - bytes.push(byte); - if (!hasNext) { - return; - } - } - const splitBits = ((lo >>> 28) & 0x0F) | ((hi & 0x07) << 4); - const hasMoreBits = !((hi >> 3) == 0); - bytes.push((hasMoreBits ? splitBits | 0x80 : splitBits) & 0xFF); - if (!hasMoreBits) { - return; - } - for (let i = 3; i < 31; i = i + 7) { - const shift = hi >>> i; - const hasNext = !((shift >>> 7) == 0); - const byte = (hasNext ? shift | 0x80 : shift) & 0xFF; - bytes.push(byte); - if (!hasNext) { - return; - } - } - bytes.push((hi >>> 31) & 0x01); -} -// constants for binary math -const TWO_PWR_32_DBL$1 = (1 << 16) * (1 << 16); -/** - * Parse decimal string of 64 bit integer value as two JS numbers. - * - * Returns tuple: - * [0]: minus sign? - * [1]: low bits - * [2]: high bits - * - * Copyright 2008 Google Inc. - */ -function int64fromString(dec) { - // Check for minus sign. - let minus = dec[0] == '-'; - if (minus) - dec = dec.slice(1); - // Work 6 decimal digits at a time, acting like we're converting base 1e6 - // digits to binary. This is safe to do with floating point math because - // Number.isSafeInteger(ALL_32_BITS * 1e6) == true. - const base = 1e6; - let lowBits = 0; - let highBits = 0; - function add1e6digit(begin, end) { - // Note: Number('') is 0. - const digit1e6 = Number(dec.slice(begin, end)); - highBits *= base; - lowBits = lowBits * base + digit1e6; - // Carry bits from lowBits to highBits - if (lowBits >= TWO_PWR_32_DBL$1) { - highBits = highBits + ((lowBits / TWO_PWR_32_DBL$1) | 0); - lowBits = lowBits % TWO_PWR_32_DBL$1; - } - } - add1e6digit(-24, -18); - add1e6digit(-18, -12); - add1e6digit(-12, -6); - add1e6digit(-6); - return [minus, lowBits, highBits]; -} -/** - * Format 64 bit integer value (as two JS numbers) to decimal string. - * - * Copyright 2008 Google Inc. - */ -function int64toString(bitsLow, bitsHigh) { - // Skip the expensive conversion if the number is small enough to use the - // built-in conversions. - if ((bitsHigh >>> 0) <= 0x1FFFFF) { - return '' + (TWO_PWR_32_DBL$1 * bitsHigh + (bitsLow >>> 0)); - } - // What this code is doing is essentially converting the input number from - // base-2 to base-1e7, which allows us to represent the 64-bit range with - // only 3 (very large) digits. Those digits are then trivial to convert to - // a base-10 string. - // The magic numbers used here are - - // 2^24 = 16777216 = (1,6777216) in base-1e7. - // 2^48 = 281474976710656 = (2,8147497,6710656) in base-1e7. - // Split 32:32 representation into 16:24:24 representation so our - // intermediate digits don't overflow. - let low = bitsLow & 0xFFFFFF; - let mid = (((bitsLow >>> 24) | (bitsHigh << 8)) >>> 0) & 0xFFFFFF; - let high = (bitsHigh >> 16) & 0xFFFF; - // Assemble our three base-1e7 digits, ignoring carries. The maximum - // value in a digit at this step is representable as a 48-bit integer, which - // can be stored in a 64-bit floating point number. - let digitA = low + (mid * 6777216) + (high * 6710656); - let digitB = mid + (high * 8147497); - let digitC = (high * 2); - // Apply carries from A to B and from B to C. - let base = 10000000; - if (digitA >= base) { - digitB += Math.floor(digitA / base); - digitA %= base; - } - if (digitB >= base) { - digitC += Math.floor(digitB / base); - digitB %= base; - } - // Convert base-1e7 digits to base-10, with optional leading zeroes. - function decimalFrom1e7(digit1e7, needLeadingZeros) { - let partial = digit1e7 ? String(digit1e7) : ''; - if (needLeadingZeros) { - return '0000000'.slice(partial.length) + partial; - } - return partial; - } - return decimalFrom1e7(digitC, /*needLeadingZeros=*/ 0) + - decimalFrom1e7(digitB, /*needLeadingZeros=*/ digitC) + - // If the final 1e7 digit didn't need leading zeros, we would have - // returned via the trivial code path at the top. - decimalFrom1e7(digitA, /*needLeadingZeros=*/ 1); -} -/** - * Write a 32 bit varint, signed or unsigned. Same as `varint64write(0, value, bytes)` - * - * Copyright 2008 Google Inc. All rights reserved. - * - * See https://github.com/protocolbuffers/protobuf/blob/1b18833f4f2a2f681f4e4a25cdf3b0a43115ec26/js/binary/encoder.js#L144 - */ -function varint32write(value, bytes) { - if (value >= 0) { - // write value as varint 32 - while (value > 0x7f) { - bytes.push((value & 0x7f) | 0x80); - value = value >>> 7; - } - bytes.push(value); - } - else { - for (let i = 0; i < 9; i++) { - bytes.push(value & 127 | 128); - value = value >> 7; - } - bytes.push(1); - } -} -/** - * Read an unsigned 32 bit varint. - * - * See https://github.com/protocolbuffers/protobuf/blob/8a71927d74a4ce34efe2d8769fda198f52d20d12/js/experimental/runtime/kernel/buffer_decoder.js#L220 - */ -function varint32read() { - let b = this.buf[this.pos++]; - let result = b & 0x7F; - if ((b & 0x80) == 0) { - this.assertBounds(); - return result; - } - b = this.buf[this.pos++]; - result |= (b & 0x7F) << 7; - if ((b & 0x80) == 0) { - this.assertBounds(); - return result; - } - b = this.buf[this.pos++]; - result |= (b & 0x7F) << 14; - if ((b & 0x80) == 0) { - this.assertBounds(); - return result; - } - b = this.buf[this.pos++]; - result |= (b & 0x7F) << 21; - if ((b & 0x80) == 0) { - this.assertBounds(); - return result; - } - // Extract only last 4 bits - b = this.buf[this.pos++]; - result |= (b & 0x0F) << 28; - for (let readBytes = 5; ((b & 0x80) !== 0) && readBytes < 10; readBytes++) - b = this.buf[this.pos++]; - if ((b & 0x80) != 0) - throw new Error('invalid varint'); - this.assertBounds(); - // Result can have 32 bits, convert it to unsigned - return result >>> 0; -} - -let BI; -function detectBi() { - const dv = new DataView(new ArrayBuffer(8)); - const ok = globalThis.BigInt !== undefined - && typeof dv.getBigInt64 === "function" - && typeof dv.getBigUint64 === "function" - && typeof dv.setBigInt64 === "function" - && typeof dv.setBigUint64 === "function"; - BI = ok ? { - MIN: BigInt("-9223372036854775808"), - MAX: BigInt("9223372036854775807"), - UMIN: BigInt("0"), - UMAX: BigInt("18446744073709551615"), - C: BigInt, - V: dv, - } : undefined; -} -detectBi(); -function assertBi(bi) { - if (!bi) - throw new Error("BigInt unavailable, see https://github.com/timostamm/protobuf-ts/blob/v1.0.8/MANUAL.md#bigint-support"); -} -// used to validate from(string) input (when bigint is unavailable) -const RE_DECIMAL_STR = /^-?[0-9]+$/; -// constants for binary math -const TWO_PWR_32_DBL = 0x100000000; -const HALF_2_PWR_32 = 0x080000000; -// base class for PbLong and PbULong provides shared code -class SharedPbLong { - /** - * Create a new instance with the given bits. - */ - constructor(lo, hi) { - this.lo = lo | 0; - this.hi = hi | 0; - } - /** - * Is this instance equal to 0? - */ - isZero() { - return this.lo == 0 && this.hi == 0; - } - /** - * Convert to a native number. - */ - toNumber() { - let result = this.hi * TWO_PWR_32_DBL + (this.lo >>> 0); - if (!Number.isSafeInteger(result)) - throw new Error("cannot convert to safe number"); - return result; - } -} -/** - * 64-bit unsigned integer as two 32-bit values. - * Converts between `string`, `number` and `bigint` representations. - */ -class PbULong extends SharedPbLong { - /** - * Create instance from a `string`, `number` or `bigint`. - */ - static from(value) { - if (BI) - // noinspection FallThroughInSwitchStatementJS - switch (typeof value) { - case "string": - if (value == "0") - return this.ZERO; - if (value == "") - throw new Error('string is no integer'); - value = BI.C(value); - case "number": - if (value === 0) - return this.ZERO; - value = BI.C(value); - case "bigint": - if (!value) - return this.ZERO; - if (value < BI.UMIN) - throw new Error('signed value for ulong'); - if (value > BI.UMAX) - throw new Error('ulong too large'); - BI.V.setBigUint64(0, value, true); - return new PbULong(BI.V.getInt32(0, true), BI.V.getInt32(4, true)); - } - else - switch (typeof value) { - case "string": - if (value == "0") - return this.ZERO; - value = value.trim(); - if (!RE_DECIMAL_STR.test(value)) - throw new Error('string is no integer'); - let [minus, lo, hi] = int64fromString(value); - if (minus) - throw new Error('signed value for ulong'); - return new PbULong(lo, hi); - case "number": - if (value == 0) - return this.ZERO; - if (!Number.isSafeInteger(value)) - throw new Error('number is no integer'); - if (value < 0) - throw new Error('signed value for ulong'); - return new PbULong(value, value / TWO_PWR_32_DBL); - } - throw new Error('unknown value ' + typeof value); - } - /** - * Convert to decimal string. - */ - toString() { - return BI ? this.toBigInt().toString() : int64toString(this.lo, this.hi); - } - /** - * Convert to native bigint. - */ - toBigInt() { - assertBi(BI); - BI.V.setInt32(0, this.lo, true); - BI.V.setInt32(4, this.hi, true); - return BI.V.getBigUint64(0, true); - } -} -/** - * ulong 0 singleton. - */ -PbULong.ZERO = new PbULong(0, 0); -/** - * 64-bit signed integer as two 32-bit values. - * Converts between `string`, `number` and `bigint` representations. - */ -class PbLong extends SharedPbLong { - /** - * Create instance from a `string`, `number` or `bigint`. - */ - static from(value) { - if (BI) - // noinspection FallThroughInSwitchStatementJS - switch (typeof value) { - case "string": - if (value == "0") - return this.ZERO; - if (value == "") - throw new Error('string is no integer'); - value = BI.C(value); - case "number": - if (value === 0) - return this.ZERO; - value = BI.C(value); - case "bigint": - if (!value) - return this.ZERO; - if (value < BI.MIN) - throw new Error('signed long too small'); - if (value > BI.MAX) - throw new Error('signed long too large'); - BI.V.setBigInt64(0, value, true); - return new PbLong(BI.V.getInt32(0, true), BI.V.getInt32(4, true)); - } - else - switch (typeof value) { - case "string": - if (value == "0") - return this.ZERO; - value = value.trim(); - if (!RE_DECIMAL_STR.test(value)) - throw new Error('string is no integer'); - let [minus, lo, hi] = int64fromString(value); - if (minus) { - if (hi > HALF_2_PWR_32 || (hi == HALF_2_PWR_32 && lo != 0)) - throw new Error('signed long too small'); - } - else if (hi >= HALF_2_PWR_32) - throw new Error('signed long too large'); - let pbl = new PbLong(lo, hi); - return minus ? pbl.negate() : pbl; - case "number": - if (value == 0) - return this.ZERO; - if (!Number.isSafeInteger(value)) - throw new Error('number is no integer'); - return value > 0 - ? new PbLong(value, value / TWO_PWR_32_DBL) - : new PbLong(-value, -value / TWO_PWR_32_DBL).negate(); - } - throw new Error('unknown value ' + typeof value); - } - /** - * Do we have a minus sign? - */ - isNegative() { - return (this.hi & HALF_2_PWR_32) !== 0; - } - /** - * Negate two's complement. - * Invert all the bits and add one to the result. - */ - negate() { - let hi = ~this.hi, lo = this.lo; - if (lo) - lo = ~lo + 1; - else - hi += 1; - return new PbLong(lo, hi); - } - /** - * Convert to decimal string. - */ - toString() { - if (BI) - return this.toBigInt().toString(); - if (this.isNegative()) { - let n = this.negate(); - return '-' + int64toString(n.lo, n.hi); - } - return int64toString(this.lo, this.hi); - } - /** - * Convert to native bigint. - */ - toBigInt() { - assertBi(BI); - BI.V.setInt32(0, this.lo, true); - BI.V.setInt32(4, this.hi, true); - return BI.V.getBigInt64(0, true); - } -} -/** - * long 0 singleton. - */ -PbLong.ZERO = new PbLong(0, 0); - -const defaultsRead$1 = { - readUnknownField: true, - readerFactory: bytes => new BinaryReader(bytes), -}; -/** - * Make options for reading binary data form partial options. - */ -function binaryReadOptions(options) { - return options ? Object.assign(Object.assign({}, defaultsRead$1), options) : defaultsRead$1; -} -class BinaryReader { - constructor(buf, textDecoder) { - this.varint64 = varint64read; // dirty cast for `this` - /** - * Read a `uint32` field, an unsigned 32 bit varint. - */ - this.uint32 = varint32read; // dirty cast for `this` and access to protected `buf` - this.buf = buf; - this.len = buf.length; - this.pos = 0; - this.view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength); - this.textDecoder = textDecoder !== null && textDecoder !== void 0 ? textDecoder : new TextDecoder("utf-8", { - fatal: true, - ignoreBOM: true, - }); - } - /** - * Reads a tag - field number and wire type. - */ - tag() { - let tag = this.uint32(), fieldNo = tag >>> 3, wireType = tag & 7; - if (fieldNo <= 0 || wireType < 0 || wireType > 5) - throw new Error("illegal tag: field no " + fieldNo + " wire type " + wireType); - return [fieldNo, wireType]; - } - /** - * Skip one element on the wire and return the skipped data. - * Supports WireType.StartGroup since v2.0.0-alpha.23. - */ - skip(wireType) { - let start = this.pos; - // noinspection FallThroughInSwitchStatementJS - switch (wireType) { - case WireType.Varint: - while (this.buf[this.pos++] & 0x80) { - // ignore - } - break; - case WireType.Bit64: - this.pos += 4; - case WireType.Bit32: - this.pos += 4; - break; - case WireType.LengthDelimited: - let len = this.uint32(); - this.pos += len; - break; - case WireType.StartGroup: - // From descriptor.proto: Group type is deprecated, not supported in proto3. - // But we must still be able to parse and treat as unknown. - let t; - while ((t = this.tag()[1]) !== WireType.EndGroup) { - this.skip(t); - } - break; - default: - throw new Error("cant skip wire type " + wireType); - } - this.assertBounds(); - return this.buf.subarray(start, this.pos); - } - /** - * Throws error if position in byte array is out of range. - */ - assertBounds() { - if (this.pos > this.len) - throw new RangeError("premature EOF"); - } - /** - * Read a `int32` field, a signed 32 bit varint. - */ - int32() { - return this.uint32() | 0; - } - /** - * Read a `sint32` field, a signed, zigzag-encoded 32-bit varint. - */ - sint32() { - let zze = this.uint32(); - // decode zigzag - return (zze >>> 1) ^ -(zze & 1); - } - /** - * Read a `int64` field, a signed 64-bit varint. - */ - int64() { - return new PbLong(...this.varint64()); - } - /** - * Read a `uint64` field, an unsigned 64-bit varint. - */ - uint64() { - return new PbULong(...this.varint64()); - } - /** - * Read a `sint64` field, a signed, zig-zag-encoded 64-bit varint. - */ - sint64() { - let [lo, hi] = this.varint64(); - // decode zig zag - let s = -(lo & 1); - lo = ((lo >>> 1 | (hi & 1) << 31) ^ s); - hi = (hi >>> 1 ^ s); - return new PbLong(lo, hi); - } - /** - * Read a `bool` field, a variant. - */ - bool() { - let [lo, hi] = this.varint64(); - return lo !== 0 || hi !== 0; - } - /** - * Read a `fixed32` field, an unsigned, fixed-length 32-bit integer. - */ - fixed32() { - return this.view.getUint32((this.pos += 4) - 4, true); - } - /** - * Read a `sfixed32` field, a signed, fixed-length 32-bit integer. - */ - sfixed32() { - return this.view.getInt32((this.pos += 4) - 4, true); - } - /** - * Read a `fixed64` field, an unsigned, fixed-length 64 bit integer. - */ - fixed64() { - return new PbULong(this.sfixed32(), this.sfixed32()); - } - /** - * Read a `fixed64` field, a signed, fixed-length 64-bit integer. - */ - sfixed64() { - return new PbLong(this.sfixed32(), this.sfixed32()); - } - /** - * Read a `float` field, 32-bit floating point number. - */ - float() { - return this.view.getFloat32((this.pos += 4) - 4, true); - } - /** - * Read a `double` field, a 64-bit floating point number. - */ - double() { - return this.view.getFloat64((this.pos += 8) - 8, true); - } - /** - * Read a `bytes` field, length-delimited arbitrary data. - */ - bytes() { - let len = this.uint32(); - let start = this.pos; - this.pos += len; - this.assertBounds(); - return this.buf.subarray(start, start + len); - } - /** - * Read a `string` field, length-delimited data converted to UTF-8 text. - */ - string() { - return this.textDecoder.decode(this.bytes()); - } -} - -/** - * assert that condition is true or throw error (with message) - */ -function assert(condition, msg) { - if (!condition) { - throw new Error(msg); - } -} -const FLOAT32_MAX = 3.4028234663852886e+38, FLOAT32_MIN = -3.4028234663852886e+38, UINT32_MAX = 0xFFFFFFFF, INT32_MAX = 0X7FFFFFFF, INT32_MIN = -0X80000000; -function assertInt32(arg) { - if (typeof arg !== "number") - throw new Error('invalid int 32: ' + typeof arg); - if (!Number.isInteger(arg) || arg > INT32_MAX || arg < INT32_MIN) - throw new Error('invalid int 32: ' + arg); -} -function assertUInt32(arg) { - if (typeof arg !== "number") - throw new Error('invalid uint 32: ' + typeof arg); - if (!Number.isInteger(arg) || arg > UINT32_MAX || arg < 0) - throw new Error('invalid uint 32: ' + arg); -} -function assertFloat32(arg) { - if (typeof arg !== "number") - throw new Error('invalid float 32: ' + typeof arg); - if (!Number.isFinite(arg)) - return; - if (arg > FLOAT32_MAX || arg < FLOAT32_MIN) - throw new Error('invalid float 32: ' + arg); -} - -const defaultsWrite$1 = { - writeUnknownFields: true, - writerFactory: () => new BinaryWriter(), -}; -/** - * Make options for writing binary data form partial options. - */ -function binaryWriteOptions(options) { - return options ? Object.assign(Object.assign({}, defaultsWrite$1), options) : defaultsWrite$1; -} -class BinaryWriter { - constructor(textEncoder) { - /** - * Previous fork states. - */ - this.stack = []; - this.textEncoder = textEncoder !== null && textEncoder !== void 0 ? textEncoder : new TextEncoder(); - this.chunks = []; - this.buf = []; - } - /** - * Return all bytes written and reset this writer. - */ - finish() { - this.chunks.push(new Uint8Array(this.buf)); // flush the buffer - let len = 0; - for (let i = 0; i < this.chunks.length; i++) - len += this.chunks[i].length; - let bytes = new Uint8Array(len); - let offset = 0; - for (let i = 0; i < this.chunks.length; i++) { - bytes.set(this.chunks[i], offset); - offset += this.chunks[i].length; - } - this.chunks = []; - return bytes; - } - /** - * Start a new fork for length-delimited data like a message - * or a packed repeated field. - * - * Must be joined later with `join()`. - */ - fork() { - this.stack.push({ chunks: this.chunks, buf: this.buf }); - this.chunks = []; - this.buf = []; - return this; - } - /** - * Join the last fork. Write its length and bytes, then - * return to the previous state. - */ - join() { - // get chunk of fork - let chunk = this.finish(); - // restore previous state - let prev = this.stack.pop(); - if (!prev) - throw new Error('invalid state, fork stack empty'); - this.chunks = prev.chunks; - this.buf = prev.buf; - // write length of chunk as varint - this.uint32(chunk.byteLength); - return this.raw(chunk); - } - /** - * Writes a tag (field number and wire type). - * - * Equivalent to `uint32( (fieldNo << 3 | type) >>> 0 )`. - * - * Generated code should compute the tag ahead of time and call `uint32()`. - */ - tag(fieldNo, type) { - return this.uint32((fieldNo << 3 | type) >>> 0); - } - /** - * Write a chunk of raw bytes. - */ - raw(chunk) { - if (this.buf.length) { - this.chunks.push(new Uint8Array(this.buf)); - this.buf = []; - } - this.chunks.push(chunk); - return this; - } - /** - * Write a `uint32` value, an unsigned 32 bit varint. - */ - uint32(value) { - assertUInt32(value); - // write value as varint 32, inlined for speed - while (value > 0x7f) { - this.buf.push((value & 0x7f) | 0x80); - value = value >>> 7; - } - this.buf.push(value); - return this; - } - /** - * Write a `int32` value, a signed 32 bit varint. - */ - int32(value) { - assertInt32(value); - varint32write(value, this.buf); - return this; - } - /** - * Write a `bool` value, a variant. - */ - bool(value) { - this.buf.push(value ? 1 : 0); - return this; - } - /** - * Write a `bytes` value, length-delimited arbitrary data. - */ - bytes(value) { - this.uint32(value.byteLength); // write length of chunk as varint - return this.raw(value); - } - /** - * Write a `string` value, length-delimited data converted to UTF-8 text. - */ - string(value) { - let chunk = this.textEncoder.encode(value); - this.uint32(chunk.byteLength); // write length of chunk as varint - return this.raw(chunk); - } - /** - * Write a `float` value, 32-bit floating point number. - */ - float(value) { - assertFloat32(value); - let chunk = new Uint8Array(4); - new DataView(chunk.buffer).setFloat32(0, value, true); - return this.raw(chunk); - } - /** - * Write a `double` value, a 64-bit floating point number. - */ - double(value) { - let chunk = new Uint8Array(8); - new DataView(chunk.buffer).setFloat64(0, value, true); - return this.raw(chunk); - } - /** - * Write a `fixed32` value, an unsigned, fixed-length 32-bit integer. - */ - fixed32(value) { - assertUInt32(value); - let chunk = new Uint8Array(4); - new DataView(chunk.buffer).setUint32(0, value, true); - return this.raw(chunk); - } - /** - * Write a `sfixed32` value, a signed, fixed-length 32-bit integer. - */ - sfixed32(value) { - assertInt32(value); - let chunk = new Uint8Array(4); - new DataView(chunk.buffer).setInt32(0, value, true); - return this.raw(chunk); - } - /** - * Write a `sint32` value, a signed, zigzag-encoded 32-bit varint. - */ - sint32(value) { - assertInt32(value); - // zigzag encode - value = ((value << 1) ^ (value >> 31)) >>> 0; - varint32write(value, this.buf); - return this; - } - /** - * Write a `fixed64` value, a signed, fixed-length 64-bit integer. - */ - sfixed64(value) { - let chunk = new Uint8Array(8); - let view = new DataView(chunk.buffer); - let long = PbLong.from(value); - view.setInt32(0, long.lo, true); - view.setInt32(4, long.hi, true); - return this.raw(chunk); - } - /** - * Write a `fixed64` value, an unsigned, fixed-length 64 bit integer. - */ - fixed64(value) { - let chunk = new Uint8Array(8); - let view = new DataView(chunk.buffer); - let long = PbULong.from(value); - view.setInt32(0, long.lo, true); - view.setInt32(4, long.hi, true); - return this.raw(chunk); - } - /** - * Write a `int64` value, a signed 64-bit varint. - */ - int64(value) { - let long = PbLong.from(value); - varint64write(long.lo, long.hi, this.buf); - return this; - } - /** - * Write a `sint64` value, a signed, zig-zag-encoded 64-bit varint. - */ - sint64(value) { - let long = PbLong.from(value), - // zigzag encode - sign = long.hi >> 31, lo = (long.lo << 1) ^ sign, hi = ((long.hi << 1) | (long.lo >>> 31)) ^ sign; - varint64write(lo, hi, this.buf); - return this; - } - /** - * Write a `uint64` value, an unsigned 64-bit varint. - */ - uint64(value) { - let long = PbULong.from(value); - varint64write(long.lo, long.hi, this.buf); - return this; - } -} - -const defaultsWrite = { - emitDefaultValues: false, - enumAsInteger: false, - useProtoFieldName: false, - prettySpaces: 0, -}, defaultsRead = { - ignoreUnknownFields: false, -}; -/** - * Make options for reading JSON data from partial options. - */ -function jsonReadOptions(options) { - return options ? Object.assign(Object.assign({}, defaultsRead), options) : defaultsRead; -} -/** - * Make options for writing JSON data from partial options. - */ -function jsonWriteOptions(options) { - return options ? Object.assign(Object.assign({}, defaultsWrite), options) : defaultsWrite; -} - -/** - * The symbol used as a key on message objects to store the message type. - * - * Note that this is an experimental feature - it is here to stay, but - * implementation details may change without notice. - */ -const MESSAGE_TYPE = Symbol.for("protobuf-ts/message-type"); - -/** - * Converts snake_case to lowerCamelCase. - * - * Should behave like protoc: - * https://github.com/protocolbuffers/protobuf/blob/e8ae137c96444ea313485ed1118c5e43b2099cf1/src/google/protobuf/compiler/java/java_helpers.cc#L118 - */ -function lowerCamelCase(snakeCase) { - let capNext = false; - const sb = []; - for (let i = 0; i < snakeCase.length; i++) { - let next = snakeCase.charAt(i); - if (next == '_') { - capNext = true; - } - else if (/\d/.test(next)) { - sb.push(next); - capNext = true; - } - else if (capNext) { - sb.push(next.toUpperCase()); - capNext = false; - } - else if (i == 0) { - sb.push(next.toLowerCase()); - } - else { - sb.push(next); - } - } - return sb.join(''); -} - -/** - * Scalar value types. This is a subset of field types declared by protobuf - * enum google.protobuf.FieldDescriptorProto.Type The types GROUP and MESSAGE - * are omitted, but the numerical values are identical. - */ -var ScalarType; -(function (ScalarType) { - // 0 is reserved for errors. - // Order is weird for historical reasons. - ScalarType[ScalarType["DOUBLE"] = 1] = "DOUBLE"; - ScalarType[ScalarType["FLOAT"] = 2] = "FLOAT"; - // Not ZigZag encoded. Negative numbers take 10 bytes. Use TYPE_SINT64 if - // negative values are likely. - ScalarType[ScalarType["INT64"] = 3] = "INT64"; - ScalarType[ScalarType["UINT64"] = 4] = "UINT64"; - // Not ZigZag encoded. Negative numbers take 10 bytes. Use TYPE_SINT32 if - // negative values are likely. - ScalarType[ScalarType["INT32"] = 5] = "INT32"; - ScalarType[ScalarType["FIXED64"] = 6] = "FIXED64"; - ScalarType[ScalarType["FIXED32"] = 7] = "FIXED32"; - ScalarType[ScalarType["BOOL"] = 8] = "BOOL"; - ScalarType[ScalarType["STRING"] = 9] = "STRING"; - // Tag-delimited aggregate. - // Group type is deprecated and not supported in proto3. However, Proto3 - // implementations should still be able to parse the group wire format and - // treat group fields as unknown fields. - // TYPE_GROUP = 10, - // TYPE_MESSAGE = 11, // Length-delimited aggregate. - // New in version 2. - ScalarType[ScalarType["BYTES"] = 12] = "BYTES"; - ScalarType[ScalarType["UINT32"] = 13] = "UINT32"; - // TYPE_ENUM = 14, - ScalarType[ScalarType["SFIXED32"] = 15] = "SFIXED32"; - ScalarType[ScalarType["SFIXED64"] = 16] = "SFIXED64"; - ScalarType[ScalarType["SINT32"] = 17] = "SINT32"; - ScalarType[ScalarType["SINT64"] = 18] = "SINT64"; -})(ScalarType || (ScalarType = {})); -/** - * JavaScript representation of 64 bit integral types. Equivalent to the - * field option "jstype". - * - * By default, protobuf-ts represents 64 bit types as `bigint`. - * - * You can change the default behaviour by enabling the plugin parameter - * `long_type_string`, which will represent 64 bit types as `string`. - * - * Alternatively, you can change the behaviour for individual fields - * with the field option "jstype": - * - * ```protobuf - * uint64 my_field = 1 [jstype = JS_STRING]; - * uint64 other_field = 2 [jstype = JS_NUMBER]; - * ``` - */ -var LongType; -(function (LongType) { - /** - * Use JavaScript `bigint`. - * - * Field option `[jstype = JS_NORMAL]`. - */ - LongType[LongType["BIGINT"] = 0] = "BIGINT"; - /** - * Use JavaScript `string`. - * - * Field option `[jstype = JS_STRING]`. - */ - LongType[LongType["STRING"] = 1] = "STRING"; - /** - * Use JavaScript `number`. - * - * Large values will loose precision. - * - * Field option `[jstype = JS_NUMBER]`. - */ - LongType[LongType["NUMBER"] = 2] = "NUMBER"; -})(LongType || (LongType = {})); -/** - * Protobuf 2.1.0 introduced packed repeated fields. - * Setting the field option `[packed = true]` enables packing. - * - * In proto3, all repeated fields are packed by default. - * Setting the field option `[packed = false]` disables packing. - * - * Packed repeated fields are encoded with a single tag, - * then a length-delimiter, then the element values. - * - * Unpacked repeated fields are encoded with a tag and - * value for each element. - * - * `bytes` and `string` cannot be packed. - */ -var RepeatType; -(function (RepeatType) { - /** - * The field is not repeated. - */ - RepeatType[RepeatType["NO"] = 0] = "NO"; - /** - * The field is repeated and should be packed. - * Invalid for `bytes` and `string`, they cannot be packed. - */ - RepeatType[RepeatType["PACKED"] = 1] = "PACKED"; - /** - * The field is repeated but should not be packed. - * The only valid repeat type for repeated `bytes` and `string`. - */ - RepeatType[RepeatType["UNPACKED"] = 2] = "UNPACKED"; -})(RepeatType || (RepeatType = {})); -/** - * Turns PartialFieldInfo into FieldInfo. - */ -function normalizeFieldInfo(field) { - var _a, _b, _c, _d; - field.localName = (_a = field.localName) !== null && _a !== void 0 ? _a : lowerCamelCase(field.name); - field.jsonName = (_b = field.jsonName) !== null && _b !== void 0 ? _b : lowerCamelCase(field.name); - field.repeat = (_c = field.repeat) !== null && _c !== void 0 ? _c : RepeatType.NO; - field.opt = (_d = field.opt) !== null && _d !== void 0 ? _d : (field.repeat ? false : field.oneof ? false : field.kind == "message"); - return field; -} - -/** - * Is the given value a valid oneof group? - * - * We represent protobuf `oneof` as algebraic data types (ADT) in generated - * code. But when working with messages of unknown type, the ADT does not - * help us. - * - * This type guard checks if the given object adheres to the ADT rules, which - * are as follows: - * - * 1) Must be an object. - * - * 2) Must have a "oneofKind" discriminator property. - * - * 3) If "oneofKind" is `undefined`, no member field is selected. The object - * must not have any other properties. - * - * 4) If "oneofKind" is a `string`, the member field with this name is - * selected. - * - * 5) If a member field is selected, the object must have a second property - * with this name. The property must not be `undefined`. - * - * 6) No extra properties are allowed. The object has either one property - * (no selection) or two properties (selection). - * - */ -function isOneofGroup(any) { - if (typeof any != 'object' || any === null || !any.hasOwnProperty('oneofKind')) { - return false; - } - switch (typeof any.oneofKind) { - case "string": - if (any[any.oneofKind] === undefined) - return false; - return Object.keys(any).length == 2; - case "undefined": - return Object.keys(any).length == 1; - default: - return false; - } -} - -// noinspection JSMethodCanBeStatic -class ReflectionTypeCheck { - constructor(info) { - var _a; - this.fields = (_a = info.fields) !== null && _a !== void 0 ? _a : []; - } - prepare() { - if (this.data) - return; - const req = [], known = [], oneofs = []; - for (let field of this.fields) { - if (field.oneof) { - if (!oneofs.includes(field.oneof)) { - oneofs.push(field.oneof); - req.push(field.oneof); - known.push(field.oneof); - } - } - else { - known.push(field.localName); - switch (field.kind) { - case "scalar": - case "enum": - if (!field.opt || field.repeat) - req.push(field.localName); - break; - case "message": - if (field.repeat) - req.push(field.localName); - break; - case "map": - req.push(field.localName); - break; - } - } - } - this.data = { req, known, oneofs: Object.values(oneofs) }; - } - /** - * Is the argument a valid message as specified by the - * reflection information? - * - * Checks all field types recursively. The `depth` - * specifies how deep into the structure the check will be. - * - * With a depth of 0, only the presence of fields - * is checked. - * - * With a depth of 1 or more, the field types are checked. - * - * With a depth of 2 or more, the members of map, repeated - * and message fields are checked. - * - * Message fields will be checked recursively with depth - 1. - * - * The number of map entries / repeated values being checked - * is < depth. - */ - is(message, depth, allowExcessProperties = false) { - if (depth < 0) - return true; - if (message === null || message === undefined || typeof message != 'object') - return false; - this.prepare(); - let keys = Object.keys(message), data = this.data; - // if a required field is missing in arg, this cannot be a T - if (keys.length < data.req.length || data.req.some(n => !keys.includes(n))) - return false; - if (!allowExcessProperties) { - // if the arg contains a key we dont know, this is not a literal T - if (keys.some(k => !data.known.includes(k))) - return false; - } - // "With a depth of 0, only the presence and absence of fields is checked." - // "With a depth of 1 or more, the field types are checked." - if (depth < 1) { - return true; - } - // check oneof group - for (const name of data.oneofs) { - const group = message[name]; - if (!isOneofGroup(group)) - return false; - if (group.oneofKind === undefined) - continue; - const field = this.fields.find(f => f.localName === group.oneofKind); - if (!field) - return false; // we found no field, but have a kind, something is wrong - if (!this.field(group[group.oneofKind], field, allowExcessProperties, depth)) - return false; - } - // check types - for (const field of this.fields) { - if (field.oneof !== undefined) - continue; - if (!this.field(message[field.localName], field, allowExcessProperties, depth)) - return false; - } - return true; - } - field(arg, field, allowExcessProperties, depth) { - let repeated = field.repeat; - switch (field.kind) { - case "scalar": - if (arg === undefined) - return field.opt; - if (repeated) - return this.scalars(arg, field.T, depth, field.L); - return this.scalar(arg, field.T, field.L); - case "enum": - if (arg === undefined) - return field.opt; - if (repeated) - return this.scalars(arg, ScalarType.INT32, depth); - return this.scalar(arg, ScalarType.INT32); - case "message": - if (arg === undefined) - return true; - if (repeated) - return this.messages(arg, field.T(), allowExcessProperties, depth); - return this.message(arg, field.T(), allowExcessProperties, depth); - case "map": - if (typeof arg != 'object' || arg === null) - return false; - if (depth < 2) - return true; - if (!this.mapKeys(arg, field.K, depth)) - return false; - switch (field.V.kind) { - case "scalar": - return this.scalars(Object.values(arg), field.V.T, depth, field.V.L); - case "enum": - return this.scalars(Object.values(arg), ScalarType.INT32, depth); - case "message": - return this.messages(Object.values(arg), field.V.T(), allowExcessProperties, depth); - } - break; - } - return true; - } - message(arg, type, allowExcessProperties, depth) { - if (allowExcessProperties) { - return type.isAssignable(arg, depth); - } - return type.is(arg, depth); - } - messages(arg, type, allowExcessProperties, depth) { - if (!Array.isArray(arg)) - return false; - if (depth < 2) - return true; - if (allowExcessProperties) { - for (let i = 0; i < arg.length && i < depth; i++) - if (!type.isAssignable(arg[i], depth - 1)) - return false; - } - else { - for (let i = 0; i < arg.length && i < depth; i++) - if (!type.is(arg[i], depth - 1)) - return false; - } - return true; - } - scalar(arg, type, longType) { - let argType = typeof arg; - switch (type) { - case ScalarType.UINT64: - case ScalarType.FIXED64: - case ScalarType.INT64: - case ScalarType.SFIXED64: - case ScalarType.SINT64: - switch (longType) { - case LongType.BIGINT: - return argType == "bigint"; - case LongType.NUMBER: - return argType == "number" && !isNaN(arg); - default: - return argType == "string"; - } - case ScalarType.BOOL: - return argType == 'boolean'; - case ScalarType.STRING: - return argType == 'string'; - case ScalarType.BYTES: - return arg instanceof Uint8Array; - case ScalarType.DOUBLE: - case ScalarType.FLOAT: - return argType == 'number' && !isNaN(arg); - default: - // case ScalarType.UINT32: - // case ScalarType.FIXED32: - // case ScalarType.INT32: - // case ScalarType.SINT32: - // case ScalarType.SFIXED32: - return argType == 'number' && Number.isInteger(arg); - } - } - scalars(arg, type, depth, longType) { - if (!Array.isArray(arg)) - return false; - if (depth < 2) - return true; - if (Array.isArray(arg)) - for (let i = 0; i < arg.length && i < depth; i++) - if (!this.scalar(arg[i], type, longType)) - return false; - return true; - } - mapKeys(map, type, depth) { - let keys = Object.keys(map); - switch (type) { - case ScalarType.INT32: - case ScalarType.FIXED32: - case ScalarType.SFIXED32: - case ScalarType.SINT32: - case ScalarType.UINT32: - return this.scalars(keys.slice(0, depth).map(k => parseInt(k)), type, depth); - case ScalarType.BOOL: - return this.scalars(keys.slice(0, depth).map(k => k == 'true' ? true : k == 'false' ? false : k), type, depth); - default: - return this.scalars(keys, type, depth, LongType.STRING); - } - } -} - -/** - * Utility method to convert a PbLong or PbUlong to a JavaScript - * representation during runtime. - * - * Works with generated field information, `undefined` is equivalent - * to `STRING`. - */ -function reflectionLongConvert(long, type) { - switch (type) { - case LongType.BIGINT: - return long.toBigInt(); - case LongType.NUMBER: - return long.toNumber(); - default: - // case undefined: - // case LongType.STRING: - return long.toString(); - } -} - -/** - * Reads proto3 messages in canonical JSON format using reflection information. - * - * https://developers.google.com/protocol-buffers/docs/proto3#json - */ -class ReflectionJsonReader { - constructor(info) { - this.info = info; - } - prepare() { - var _a; - if (this.fMap === undefined) { - this.fMap = {}; - const fieldsInput = (_a = this.info.fields) !== null && _a !== void 0 ? _a : []; - for (const field of fieldsInput) { - this.fMap[field.name] = field; - this.fMap[field.jsonName] = field; - this.fMap[field.localName] = field; - } - } - } - // Cannot parse JSON for #. - assert(condition, fieldName, jsonValue) { - if (!condition) { - let what = typeofJsonValue(jsonValue); - if (what == "number" || what == "boolean") - what = jsonValue.toString(); - throw new Error(`Cannot parse JSON ${what} for ${this.info.typeName}#${fieldName}`); - } - } - /** - * Reads a message from canonical JSON format into the target message. - * - * Repeated fields are appended. Map entries are added, overwriting - * existing keys. - * - * If a message field is already present, it will be merged with the - * new data. - */ - read(input, message, options) { - this.prepare(); - const oneofsHandled = []; - for (const [jsonKey, jsonValue] of Object.entries(input)) { - const field = this.fMap[jsonKey]; - if (!field) { - if (!options.ignoreUnknownFields) - throw new Error(`Found unknown field while reading ${this.info.typeName} from JSON format. JSON key: ${jsonKey}`); - continue; - } - const localName = field.localName; - // handle oneof ADT - let target; // this will be the target for the field value, whether it is member of a oneof or not - if (field.oneof) { - if (jsonValue === null && (field.kind !== 'enum' || field.T()[0] !== 'google.protobuf.NullValue')) { - continue; - } - // since json objects are unordered by specification, it is not possible to take the last of multiple oneofs - if (oneofsHandled.includes(field.oneof)) - throw new Error(`Multiple members of the oneof group "${field.oneof}" of ${this.info.typeName} are present in JSON.`); - oneofsHandled.push(field.oneof); - target = message[field.oneof] = { - oneofKind: localName - }; - } - else { - target = message; - } - // we have handled oneof above. we just have read the value into `target`. - if (field.kind == 'map') { - if (jsonValue === null) { - continue; - } - // check input - this.assert(isJsonObject(jsonValue), field.name, jsonValue); - // our target to put map entries into - const fieldObj = target[localName]; - // read entries - for (const [jsonObjKey, jsonObjValue] of Object.entries(jsonValue)) { - this.assert(jsonObjValue !== null, field.name + " map value", null); - // read value - let val; - switch (field.V.kind) { - case "message": - val = field.V.T().internalJsonRead(jsonObjValue, options); - break; - case "enum": - val = this.enum(field.V.T(), jsonObjValue, field.name, options.ignoreUnknownFields); - if (val === false) - continue; - break; - case "scalar": - val = this.scalar(jsonObjValue, field.V.T, field.V.L, field.name); - break; - } - this.assert(val !== undefined, field.name + " map value", jsonObjValue); - // read key - let key = jsonObjKey; - if (field.K == ScalarType.BOOL) - key = key == "true" ? true : key == "false" ? false : key; - key = this.scalar(key, field.K, LongType.STRING, field.name).toString(); - fieldObj[key] = val; - } - } - else if (field.repeat) { - if (jsonValue === null) - continue; - // check input - this.assert(Array.isArray(jsonValue), field.name, jsonValue); - // our target to put array entries into - const fieldArr = target[localName]; - // read array entries - for (const jsonItem of jsonValue) { - this.assert(jsonItem !== null, field.name, null); - let val; - switch (field.kind) { - case "message": - val = field.T().internalJsonRead(jsonItem, options); - break; - case "enum": - val = this.enum(field.T(), jsonItem, field.name, options.ignoreUnknownFields); - if (val === false) - continue; - break; - case "scalar": - val = this.scalar(jsonItem, field.T, field.L, field.name); - break; - } - this.assert(val !== undefined, field.name, jsonValue); - fieldArr.push(val); - } - } - else { - switch (field.kind) { - case "message": - if (jsonValue === null && field.T().typeName != 'google.protobuf.Value') { - this.assert(field.oneof === undefined, field.name + " (oneof member)", null); - continue; - } - target[localName] = field.T().internalJsonRead(jsonValue, options, target[localName]); - break; - case "enum": - let val = this.enum(field.T(), jsonValue, field.name, options.ignoreUnknownFields); - if (val === false) - continue; - target[localName] = val; - break; - case "scalar": - target[localName] = this.scalar(jsonValue, field.T, field.L, field.name); - break; - } - } - } - } - /** - * Returns `false` for unrecognized string representations. - * - * google.protobuf.NullValue accepts only JSON `null` (or the old `"NULL_VALUE"`). - */ - enum(type, json, fieldName, ignoreUnknownFields) { - if (type[0] == 'google.protobuf.NullValue') - assert(json === null || json === "NULL_VALUE", `Unable to parse field ${this.info.typeName}#${fieldName}, enum ${type[0]} only accepts null.`); - if (json === null) - // we require 0 to be default value for all enums - return 0; - switch (typeof json) { - case "number": - assert(Number.isInteger(json), `Unable to parse field ${this.info.typeName}#${fieldName}, enum can only be integral number, got ${json}.`); - return json; - case "string": - let localEnumName = json; - if (type[2] && json.substring(0, type[2].length) === type[2]) - // lookup without the shared prefix - localEnumName = json.substring(type[2].length); - let enumNumber = type[1][localEnumName]; - if (typeof enumNumber === 'undefined' && ignoreUnknownFields) { - return false; - } - assert(typeof enumNumber == "number", `Unable to parse field ${this.info.typeName}#${fieldName}, enum ${type[0]} has no value for "${json}".`); - return enumNumber; - } - assert(false, `Unable to parse field ${this.info.typeName}#${fieldName}, cannot parse enum value from ${typeof json}".`); - } - scalar(json, type, longType, fieldName) { - let e; - try { - switch (type) { - // float, double: JSON value will be a number or one of the special string values "NaN", "Infinity", and "-Infinity". - // Either numbers or strings are accepted. Exponent notation is also accepted. - case ScalarType.DOUBLE: - case ScalarType.FLOAT: - if (json === null) - return .0; - if (json === "NaN") - return Number.NaN; - if (json === "Infinity") - return Number.POSITIVE_INFINITY; - if (json === "-Infinity") - return Number.NEGATIVE_INFINITY; - if (json === "") { - e = "empty string"; - break; - } - if (typeof json == "string" && json.trim().length !== json.length) { - e = "extra whitespace"; - break; - } - if (typeof json != "string" && typeof json != "number") { - break; - } - let float = Number(json); - if (Number.isNaN(float)) { - e = "not a number"; - break; - } - if (!Number.isFinite(float)) { - // infinity and -infinity are handled by string representation above, so this is an error - e = "too large or small"; - break; - } - if (type == ScalarType.FLOAT) - assertFloat32(float); - return float; - // int32, fixed32, uint32: JSON value will be a decimal number. Either numbers or strings are accepted. - case ScalarType.INT32: - case ScalarType.FIXED32: - case ScalarType.SFIXED32: - case ScalarType.SINT32: - case ScalarType.UINT32: - if (json === null) - return 0; - let int32; - if (typeof json == "number") - int32 = json; - else if (json === "") - e = "empty string"; - else if (typeof json == "string") { - if (json.trim().length !== json.length) - e = "extra whitespace"; - else - int32 = Number(json); - } - if (int32 === undefined) - break; - if (type == ScalarType.UINT32) - assertUInt32(int32); - else - assertInt32(int32); - return int32; - // int64, fixed64, uint64: JSON value will be a decimal string. Either numbers or strings are accepted. - case ScalarType.INT64: - case ScalarType.SFIXED64: - case ScalarType.SINT64: - if (json === null) - return reflectionLongConvert(PbLong.ZERO, longType); - if (typeof json != "number" && typeof json != "string") - break; - return reflectionLongConvert(PbLong.from(json), longType); - case ScalarType.FIXED64: - case ScalarType.UINT64: - if (json === null) - return reflectionLongConvert(PbULong.ZERO, longType); - if (typeof json != "number" && typeof json != "string") - break; - return reflectionLongConvert(PbULong.from(json), longType); - // bool: - case ScalarType.BOOL: - if (json === null) - return false; - if (typeof json !== "boolean") - break; - return json; - // string: - case ScalarType.STRING: - if (json === null) - return ""; - if (typeof json !== "string") { - e = "extra whitespace"; - break; - } - try { - encodeURIComponent(json); - } - catch (e) { - e = "invalid UTF8"; - break; - } - return json; - // bytes: JSON value will be the data encoded as a string using standard base64 encoding with paddings. - // Either standard or URL-safe base64 encoding with/without paddings are accepted. - case ScalarType.BYTES: - if (json === null || json === "") - return new Uint8Array(0); - if (typeof json !== 'string') - break; - return base64decode(json); - } - } - catch (error) { - e = error.message; - } - this.assert(false, fieldName + (e ? " - " + e : ""), json); - } -} - -/** - * Writes proto3 messages in canonical JSON format using reflection - * information. - * - * https://developers.google.com/protocol-buffers/docs/proto3#json - */ -class ReflectionJsonWriter { - constructor(info) { - var _a; - this.fields = (_a = info.fields) !== null && _a !== void 0 ? _a : []; - } - /** - * Converts the message to a JSON object, based on the field descriptors. - */ - write(message, options) { - const json = {}, source = message; - for (const field of this.fields) { - // field is not part of a oneof, simply write as is - if (!field.oneof) { - let jsonValue = this.field(field, source[field.localName], options); - if (jsonValue !== undefined) - json[options.useProtoFieldName ? field.name : field.jsonName] = jsonValue; - continue; - } - // field is part of a oneof - const group = source[field.oneof]; - if (group.oneofKind !== field.localName) - continue; // not selected, skip - const opt = field.kind == 'scalar' || field.kind == 'enum' - ? Object.assign(Object.assign({}, options), { emitDefaultValues: true }) : options; - let jsonValue = this.field(field, group[field.localName], opt); - assert(jsonValue !== undefined); - json[options.useProtoFieldName ? field.name : field.jsonName] = jsonValue; - } - return json; - } - field(field, value, options) { - let jsonValue = undefined; - if (field.kind == 'map') { - assert(typeof value == "object" && value !== null); - const jsonObj = {}; - switch (field.V.kind) { - case "scalar": - for (const [entryKey, entryValue] of Object.entries(value)) { - const val = this.scalar(field.V.T, entryValue, field.name, false, true); - assert(val !== undefined); - jsonObj[entryKey.toString()] = val; // JSON standard allows only (double quoted) string as property key - } - break; - case "message": - const messageType = field.V.T(); - for (const [entryKey, entryValue] of Object.entries(value)) { - const val = this.message(messageType, entryValue, field.name, options); - assert(val !== undefined); - jsonObj[entryKey.toString()] = val; // JSON standard allows only (double quoted) string as property key - } - break; - case "enum": - const enumInfo = field.V.T(); - for (const [entryKey, entryValue] of Object.entries(value)) { - assert(entryValue === undefined || typeof entryValue == 'number'); - const val = this.enum(enumInfo, entryValue, field.name, false, true, options.enumAsInteger); - assert(val !== undefined); - jsonObj[entryKey.toString()] = val; // JSON standard allows only (double quoted) string as property key - } - break; - } - if (options.emitDefaultValues || Object.keys(jsonObj).length > 0) - jsonValue = jsonObj; - } - else if (field.repeat) { - assert(Array.isArray(value)); - const jsonArr = []; - switch (field.kind) { - case "scalar": - for (let i = 0; i < value.length; i++) { - const val = this.scalar(field.T, value[i], field.name, field.opt, true); - assert(val !== undefined); - jsonArr.push(val); - } - break; - case "enum": - const enumInfo = field.T(); - for (let i = 0; i < value.length; i++) { - assert(value[i] === undefined || typeof value[i] == 'number'); - const val = this.enum(enumInfo, value[i], field.name, field.opt, true, options.enumAsInteger); - assert(val !== undefined); - jsonArr.push(val); - } - break; - case "message": - const messageType = field.T(); - for (let i = 0; i < value.length; i++) { - const val = this.message(messageType, value[i], field.name, options); - assert(val !== undefined); - jsonArr.push(val); - } - break; - } - // add converted array to json output - if (options.emitDefaultValues || jsonArr.length > 0 || options.emitDefaultValues) - jsonValue = jsonArr; - } - else { - switch (field.kind) { - case "scalar": - jsonValue = this.scalar(field.T, value, field.name, field.opt, options.emitDefaultValues); - break; - case "enum": - jsonValue = this.enum(field.T(), value, field.name, field.opt, options.emitDefaultValues, options.enumAsInteger); - break; - case "message": - jsonValue = this.message(field.T(), value, field.name, options); - break; - } - } - return jsonValue; - } - /** - * Returns `null` as the default for google.protobuf.NullValue. - */ - enum(type, value, fieldName, optional, emitDefaultValues, enumAsInteger) { - if (type[0] == 'google.protobuf.NullValue') - return !emitDefaultValues && !optional ? undefined : null; - if (value === undefined) { - assert(optional); - return undefined; - } - if (value === 0 && !emitDefaultValues && !optional) - // we require 0 to be default value for all enums - return undefined; - assert(typeof value == 'number'); - assert(Number.isInteger(value)); - if (enumAsInteger || !type[1].hasOwnProperty(value)) - // if we don't now the enum value, just return the number - return value; - if (type[2]) - // restore the dropped prefix - return type[2] + type[1][value]; - return type[1][value]; - } - message(type, value, fieldName, options) { - if (value === undefined) - return options.emitDefaultValues ? null : undefined; - return type.internalJsonWrite(value, options); - } - scalar(type, value, fieldName, optional, emitDefaultValues) { - if (value === undefined) { - assert(optional); - return undefined; - } - const ed = emitDefaultValues || optional; - // noinspection FallThroughInSwitchStatementJS - switch (type) { - // int32, fixed32, uint32: JSON value will be a decimal number. Either numbers or strings are accepted. - case ScalarType.INT32: - case ScalarType.SFIXED32: - case ScalarType.SINT32: - if (value === 0) - return ed ? 0 : undefined; - assertInt32(value); - return value; - case ScalarType.FIXED32: - case ScalarType.UINT32: - if (value === 0) - return ed ? 0 : undefined; - assertUInt32(value); - return value; - // float, double: JSON value will be a number or one of the special string values "NaN", "Infinity", and "-Infinity". - // Either numbers or strings are accepted. Exponent notation is also accepted. - case ScalarType.FLOAT: - assertFloat32(value); - case ScalarType.DOUBLE: - if (value === 0) - return ed ? 0 : undefined; - assert(typeof value == 'number'); - if (Number.isNaN(value)) - return 'NaN'; - if (value === Number.POSITIVE_INFINITY) - return 'Infinity'; - if (value === Number.NEGATIVE_INFINITY) - return '-Infinity'; - return value; - // string: - case ScalarType.STRING: - if (value === "") - return ed ? '' : undefined; - assert(typeof value == 'string'); - return value; - // bool: - case ScalarType.BOOL: - if (value === false) - return ed ? false : undefined; - assert(typeof value == 'boolean'); - return value; - // JSON value will be a decimal string. Either numbers or strings are accepted. - case ScalarType.UINT64: - case ScalarType.FIXED64: - assert(typeof value == 'number' || typeof value == 'string' || typeof value == 'bigint'); - let ulong = PbULong.from(value); - if (ulong.isZero() && !ed) - return undefined; - return ulong.toString(); - // JSON value will be a decimal string. Either numbers or strings are accepted. - case ScalarType.INT64: - case ScalarType.SFIXED64: - case ScalarType.SINT64: - assert(typeof value == 'number' || typeof value == 'string' || typeof value == 'bigint'); - let long = PbLong.from(value); - if (long.isZero() && !ed) - return undefined; - return long.toString(); - // bytes: JSON value will be the data encoded as a string using standard base64 encoding with paddings. - // Either standard or URL-safe base64 encoding with/without paddings are accepted. - case ScalarType.BYTES: - assert(value instanceof Uint8Array); - if (!value.byteLength) - return ed ? "" : undefined; - return base64encode(value); - } - } -} - -/** - * Creates the default value for a scalar type. - */ -function reflectionScalarDefault(type, longType = LongType.STRING) { - switch (type) { - case ScalarType.BOOL: - return false; - case ScalarType.UINT64: - case ScalarType.FIXED64: - return reflectionLongConvert(PbULong.ZERO, longType); - case ScalarType.INT64: - case ScalarType.SFIXED64: - case ScalarType.SINT64: - return reflectionLongConvert(PbLong.ZERO, longType); - case ScalarType.DOUBLE: - case ScalarType.FLOAT: - return 0.0; - case ScalarType.BYTES: - return new Uint8Array(0); - case ScalarType.STRING: - return ""; - default: - // case ScalarType.INT32: - // case ScalarType.UINT32: - // case ScalarType.SINT32: - // case ScalarType.FIXED32: - // case ScalarType.SFIXED32: - return 0; - } -} - -/** - * Reads proto3 messages in binary format using reflection information. - * - * https://developers.google.com/protocol-buffers/docs/encoding - */ -class ReflectionBinaryReader { - constructor(info) { - this.info = info; - } - prepare() { - var _a; - if (!this.fieldNoToField) { - const fieldsInput = (_a = this.info.fields) !== null && _a !== void 0 ? _a : []; - this.fieldNoToField = new Map(fieldsInput.map(field => [field.no, field])); - } - } - /** - * Reads a message from binary format into the target message. - * - * Repeated fields are appended. Map entries are added, overwriting - * existing keys. - * - * If a message field is already present, it will be merged with the - * new data. - */ - read(reader, message, options, length) { - this.prepare(); - const end = length === undefined ? reader.len : reader.pos + length; - while (reader.pos < end) { - // read the tag and find the field - const [fieldNo, wireType] = reader.tag(), field = this.fieldNoToField.get(fieldNo); - if (!field) { - let u = options.readUnknownField; - if (u == "throw") - throw new Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.info.typeName}`); - let d = reader.skip(wireType); - if (u !== false) - (u === true ? UnknownFieldHandler.onRead : u)(this.info.typeName, message, fieldNo, wireType, d); - continue; - } - // target object for the field we are reading - let target = message, repeated = field.repeat, localName = field.localName; - // if field is member of oneof ADT, use ADT as target - if (field.oneof) { - target = target[field.oneof]; - // if other oneof member selected, set new ADT - if (target.oneofKind !== localName) - target = message[field.oneof] = { - oneofKind: localName - }; - } - // we have handled oneof above, we just have read the value into `target[localName]` - switch (field.kind) { - case "scalar": - case "enum": - let T = field.kind == "enum" ? ScalarType.INT32 : field.T; - let L = field.kind == "scalar" ? field.L : undefined; - if (repeated) { - let arr = target[localName]; // safe to assume presence of array, oneof cannot contain repeated values - if (wireType == WireType.LengthDelimited && T != ScalarType.STRING && T != ScalarType.BYTES) { - let e = reader.uint32() + reader.pos; - while (reader.pos < e) - arr.push(this.scalar(reader, T, L)); - } - else - arr.push(this.scalar(reader, T, L)); - } - else - target[localName] = this.scalar(reader, T, L); - break; - case "message": - if (repeated) { - let arr = target[localName]; // safe to assume presence of array, oneof cannot contain repeated values - let msg = field.T().internalBinaryRead(reader, reader.uint32(), options); - arr.push(msg); - } - else - target[localName] = field.T().internalBinaryRead(reader, reader.uint32(), options, target[localName]); - break; - case "map": - let [mapKey, mapVal] = this.mapEntry(field, reader, options); - // safe to assume presence of map object, oneof cannot contain repeated values - target[localName][mapKey] = mapVal; - break; - } - } - } - /** - * Read a map field, expecting key field = 1, value field = 2 - */ - mapEntry(field, reader, options) { - let length = reader.uint32(); - let end = reader.pos + length; - let key = undefined; // javascript only allows number or string for object properties - let val = undefined; - while (reader.pos < end) { - let [fieldNo, wireType] = reader.tag(); - switch (fieldNo) { - case 1: - if (field.K == ScalarType.BOOL) - key = reader.bool().toString(); - else - // long types are read as string, number types are okay as number - key = this.scalar(reader, field.K, LongType.STRING); - break; - case 2: - switch (field.V.kind) { - case "scalar": - val = this.scalar(reader, field.V.T, field.V.L); - break; - case "enum": - val = reader.int32(); - break; - case "message": - val = field.V.T().internalBinaryRead(reader, reader.uint32(), options); - break; - } - break; - default: - throw new Error(`Unknown field ${fieldNo} (wire type ${wireType}) in map entry for ${this.info.typeName}#${field.name}`); - } - } - if (key === undefined) { - let keyRaw = reflectionScalarDefault(field.K); - key = field.K == ScalarType.BOOL ? keyRaw.toString() : keyRaw; - } - if (val === undefined) - switch (field.V.kind) { - case "scalar": - val = reflectionScalarDefault(field.V.T, field.V.L); - break; - case "enum": - val = 0; - break; - case "message": - val = field.V.T().create(); - break; - } - return [key, val]; - } - scalar(reader, type, longType) { - switch (type) { - case ScalarType.INT32: - return reader.int32(); - case ScalarType.STRING: - return reader.string(); - case ScalarType.BOOL: - return reader.bool(); - case ScalarType.DOUBLE: - return reader.double(); - case ScalarType.FLOAT: - return reader.float(); - case ScalarType.INT64: - return reflectionLongConvert(reader.int64(), longType); - case ScalarType.UINT64: - return reflectionLongConvert(reader.uint64(), longType); - case ScalarType.FIXED64: - return reflectionLongConvert(reader.fixed64(), longType); - case ScalarType.FIXED32: - return reader.fixed32(); - case ScalarType.BYTES: - return reader.bytes(); - case ScalarType.UINT32: - return reader.uint32(); - case ScalarType.SFIXED32: - return reader.sfixed32(); - case ScalarType.SFIXED64: - return reflectionLongConvert(reader.sfixed64(), longType); - case ScalarType.SINT32: - return reader.sint32(); - case ScalarType.SINT64: - return reflectionLongConvert(reader.sint64(), longType); - } - } -} - -/** - * Writes proto3 messages in binary format using reflection information. - * - * https://developers.google.com/protocol-buffers/docs/encoding - */ -class ReflectionBinaryWriter { - constructor(info) { - this.info = info; - } - prepare() { - if (!this.fields) { - const fieldsInput = this.info.fields ? this.info.fields.concat() : []; - this.fields = fieldsInput.sort((a, b) => a.no - b.no); - } - } - /** - * Writes the message to binary format. - */ - write(message, writer, options) { - this.prepare(); - for (const field of this.fields) { - let value, // this will be our field value, whether it is member of a oneof or not - emitDefault, // whether we emit the default value (only true for oneof members) - repeated = field.repeat, localName = field.localName; - // handle oneof ADT - if (field.oneof) { - const group = message[field.oneof]; - if (group.oneofKind !== localName) - continue; // if field is not selected, skip - value = group[localName]; - emitDefault = true; - } - else { - value = message[localName]; - emitDefault = false; - } - // we have handled oneof above. we just have to honor `emitDefault`. - switch (field.kind) { - case "scalar": - case "enum": - let T = field.kind == "enum" ? ScalarType.INT32 : field.T; - if (repeated) { - assert(Array.isArray(value)); - if (repeated == RepeatType.PACKED) - this.packed(writer, T, field.no, value); - else - for (const item of value) - this.scalar(writer, T, field.no, item, true); - } - else if (value === undefined) - assert(field.opt); - else - this.scalar(writer, T, field.no, value, emitDefault || field.opt); - break; - case "message": - if (repeated) { - assert(Array.isArray(value)); - for (const item of value) - this.message(writer, options, field.T(), field.no, item); - } - else { - this.message(writer, options, field.T(), field.no, value); - } - break; - case "map": - assert(typeof value == 'object' && value !== null); - for (const [key, val] of Object.entries(value)) - this.mapEntry(writer, options, field, key, val); - break; - } - } - let u = options.writeUnknownFields; - if (u !== false) - (u === true ? UnknownFieldHandler.onWrite : u)(this.info.typeName, message, writer); - } - mapEntry(writer, options, field, key, value) { - writer.tag(field.no, WireType.LengthDelimited); - writer.fork(); - // javascript only allows number or string for object properties - // we convert from our representation to the protobuf type - let keyValue = key; - switch (field.K) { - case ScalarType.INT32: - case ScalarType.FIXED32: - case ScalarType.UINT32: - case ScalarType.SFIXED32: - case ScalarType.SINT32: - keyValue = Number.parseInt(key); - break; - case ScalarType.BOOL: - assert(key == 'true' || key == 'false'); - keyValue = key == 'true'; - break; - } - // write key, expecting key field number = 1 - this.scalar(writer, field.K, 1, keyValue, true); - // write value, expecting value field number = 2 - switch (field.V.kind) { - case 'scalar': - this.scalar(writer, field.V.T, 2, value, true); - break; - case 'enum': - this.scalar(writer, ScalarType.INT32, 2, value, true); - break; - case 'message': - this.message(writer, options, field.V.T(), 2, value); - break; - } - writer.join(); - } - message(writer, options, handler, fieldNo, value) { - if (value === undefined) - return; - handler.internalBinaryWrite(value, writer.tag(fieldNo, WireType.LengthDelimited).fork(), options); - writer.join(); - } - /** - * Write a single scalar value. - */ - scalar(writer, type, fieldNo, value, emitDefault) { - let [wireType, method, isDefault] = this.scalarInfo(type, value); - if (!isDefault || emitDefault) { - writer.tag(fieldNo, wireType); - writer[method](value); - } - } - /** - * Write an array of scalar values in packed format. - */ - packed(writer, type, fieldNo, value) { - if (!value.length) - return; - assert(type !== ScalarType.BYTES && type !== ScalarType.STRING); - // write tag - writer.tag(fieldNo, WireType.LengthDelimited); - // begin length-delimited - writer.fork(); - // write values without tags - let [, method,] = this.scalarInfo(type); - for (let i = 0; i < value.length; i++) - writer[method](value[i]); - // end length delimited - writer.join(); - } - /** - * Get information for writing a scalar value. - * - * Returns tuple: - * [0]: appropriate WireType - * [1]: name of the appropriate method of IBinaryWriter - * [2]: whether the given value is a default value - * - * If argument `value` is omitted, [2] is always false. - */ - scalarInfo(type, value) { - let t = WireType.Varint; - let m; - let i = value === undefined; - let d = value === 0; - switch (type) { - case ScalarType.INT32: - m = "int32"; - break; - case ScalarType.STRING: - d = i || !value.length; - t = WireType.LengthDelimited; - m = "string"; - break; - case ScalarType.BOOL: - d = value === false; - m = "bool"; - break; - case ScalarType.UINT32: - m = "uint32"; - break; - case ScalarType.DOUBLE: - t = WireType.Bit64; - m = "double"; - break; - case ScalarType.FLOAT: - t = WireType.Bit32; - m = "float"; - break; - case ScalarType.INT64: - d = i || PbLong.from(value).isZero(); - m = "int64"; - break; - case ScalarType.UINT64: - d = i || PbULong.from(value).isZero(); - m = "uint64"; - break; - case ScalarType.FIXED64: - d = i || PbULong.from(value).isZero(); - t = WireType.Bit64; - m = "fixed64"; - break; - case ScalarType.BYTES: - d = i || !value.byteLength; - t = WireType.LengthDelimited; - m = "bytes"; - break; - case ScalarType.FIXED32: - t = WireType.Bit32; - m = "fixed32"; - break; - case ScalarType.SFIXED32: - t = WireType.Bit32; - m = "sfixed32"; - break; - case ScalarType.SFIXED64: - d = i || PbLong.from(value).isZero(); - t = WireType.Bit64; - m = "sfixed64"; - break; - case ScalarType.SINT32: - m = "sint32"; - break; - case ScalarType.SINT64: - d = i || PbLong.from(value).isZero(); - m = "sint64"; - break; - } - return [t, m, i || d]; - } -} - -/** - * Creates an instance of the generic message, using the field - * information. - */ -function reflectionCreate(type) { - /** - * This ternary can be removed in the next major version. - * The `Object.create()` code path utilizes a new `messagePrototype` - * property on the `IMessageType` which has this same `MESSAGE_TYPE` - * non-enumerable property on it. Doing it this way means that we only - * pay the cost of `Object.defineProperty()` once per `IMessageType` - * class of once per "instance". The falsy code path is only provided - * for backwards compatibility in cases where the runtime library is - * updated without also updating the generated code. - */ - const msg = type.messagePrototype - ? Object.create(type.messagePrototype) - : Object.defineProperty({}, MESSAGE_TYPE, { value: type }); - for (let field of type.fields) { - let name = field.localName; - if (field.opt) - continue; - if (field.oneof) - msg[field.oneof] = { oneofKind: undefined }; - else if (field.repeat) - msg[name] = []; - else - switch (field.kind) { - case "scalar": - msg[name] = reflectionScalarDefault(field.T, field.L); - break; - case "enum": - // we require 0 to be default value for all enums - msg[name] = 0; - break; - case "map": - msg[name] = {}; - break; - } - } - return msg; -} - -/** - * Copy partial data into the target message. - * - * If a singular scalar or enum field is present in the source, it - * replaces the field in the target. - * - * If a singular message field is present in the source, it is merged - * with the target field by calling mergePartial() of the responsible - * message type. - * - * If a repeated field is present in the source, its values replace - * all values in the target array, removing extraneous values. - * Repeated message fields are copied, not merged. - * - * If a map field is present in the source, entries are added to the - * target map, replacing entries with the same key. Entries that only - * exist in the target remain. Entries with message values are copied, - * not merged. - * - * Note that this function differs from protobuf merge semantics, - * which appends repeated fields. - */ -function reflectionMergePartial(info, target, source) { - let fieldValue, // the field value we are working with - input = source, output; // where we want our field value to go - for (let field of info.fields) { - let name = field.localName; - if (field.oneof) { - const group = input[field.oneof]; // this is the oneof`s group in the source - if ((group === null || group === void 0 ? void 0 : group.oneofKind) == undefined) { // the user is free to omit - continue; // we skip this field, and all other members too - } - fieldValue = group[name]; // our value comes from the the oneof group of the source - output = target[field.oneof]; // and our output is the oneof group of the target - output.oneofKind = group.oneofKind; // always update discriminator - if (fieldValue == undefined) { - delete output[name]; // remove any existing value - continue; // skip further work on field - } - } - else { - fieldValue = input[name]; // we are using the source directly - output = target; // we want our field value to go directly into the target - if (fieldValue == undefined) { - continue; // skip further work on field, existing value is used as is - } - } - if (field.repeat) - output[name].length = fieldValue.length; // resize target array to match source array - // now we just work with `fieldValue` and `output` to merge the value - switch (field.kind) { - case "scalar": - case "enum": - if (field.repeat) - for (let i = 0; i < fieldValue.length; i++) - output[name][i] = fieldValue[i]; // not a reference type - else - output[name] = fieldValue; // not a reference type - break; - case "message": - let T = field.T(); - if (field.repeat) - for (let i = 0; i < fieldValue.length; i++) - output[name][i] = T.create(fieldValue[i]); - else if (output[name] === undefined) - output[name] = T.create(fieldValue); // nothing to merge with - else - T.mergePartial(output[name], fieldValue); - break; - case "map": - // Map and repeated fields are simply overwritten, not appended or merged - switch (field.V.kind) { - case "scalar": - case "enum": - Object.assign(output[name], fieldValue); // elements are not reference types - break; - case "message": - let T = field.V.T(); - for (let k of Object.keys(fieldValue)) - output[name][k] = T.create(fieldValue[k]); - break; - } - break; - } - } -} - -/** - * Determines whether two message of the same type have the same field values. - * Checks for deep equality, traversing repeated fields, oneof groups, maps - * and messages recursively. - * Will also return true if both messages are `undefined`. - */ -function reflectionEquals(info, a, b) { - if (a === b) - return true; - if (!a || !b) - return false; - for (let field of info.fields) { - let localName = field.localName; - let val_a = field.oneof ? a[field.oneof][localName] : a[localName]; - let val_b = field.oneof ? b[field.oneof][localName] : b[localName]; - switch (field.kind) { - case "enum": - case "scalar": - let t = field.kind == "enum" ? ScalarType.INT32 : field.T; - if (!(field.repeat - ? repeatedPrimitiveEq(t, val_a, val_b) - : primitiveEq(t, val_a, val_b))) - return false; - break; - case "map": - if (!(field.V.kind == "message" - ? repeatedMsgEq(field.V.T(), objectValues(val_a), objectValues(val_b)) - : repeatedPrimitiveEq(field.V.kind == "enum" ? ScalarType.INT32 : field.V.T, objectValues(val_a), objectValues(val_b)))) - return false; - break; - case "message": - let T = field.T(); - if (!(field.repeat - ? repeatedMsgEq(T, val_a, val_b) - : T.equals(val_a, val_b))) - return false; - break; - } - } - return true; -} -const objectValues = Object.values; -function primitiveEq(type, a, b) { - if (a === b) - return true; - if (type !== ScalarType.BYTES) - return false; - let ba = a; - let bb = b; - if (ba.length !== bb.length) - return false; - for (let i = 0; i < ba.length; i++) - if (ba[i] != bb[i]) - return false; - return true; -} -function repeatedPrimitiveEq(type, a, b) { - if (a.length !== b.length) - return false; - for (let i = 0; i < a.length; i++) - if (!primitiveEq(type, a[i], b[i])) - return false; - return true; -} -function repeatedMsgEq(type, a, b) { - if (a.length !== b.length) - return false; - for (let i = 0; i < a.length; i++) - if (!type.equals(a[i], b[i])) - return false; - return true; -} - -const baseDescriptors = Object.getOwnPropertyDescriptors(Object.getPrototypeOf({})); -/** - * This standard message type provides reflection-based - * operations to work with a message. - */ -class MessageType { - constructor(name, fields, options) { - this.defaultCheckDepth = 16; - this.typeName = name; - this.fields = fields.map(normalizeFieldInfo); - this.options = options !== null && options !== void 0 ? options : {}; - this.messagePrototype = Object.create(null, Object.assign(Object.assign({}, baseDescriptors), { [MESSAGE_TYPE]: { value: this } })); - this.refTypeCheck = new ReflectionTypeCheck(this); - this.refJsonReader = new ReflectionJsonReader(this); - this.refJsonWriter = new ReflectionJsonWriter(this); - this.refBinReader = new ReflectionBinaryReader(this); - this.refBinWriter = new ReflectionBinaryWriter(this); - } - create(value) { - let message = reflectionCreate(this); - if (value !== undefined) { - reflectionMergePartial(this, message, value); - } - return message; - } - /** - * Clone the message. - * - * Unknown fields are discarded. - */ - clone(message) { - let copy = this.create(); - reflectionMergePartial(this, copy, message); - return copy; - } - /** - * Determines whether two message of the same type have the same field values. - * Checks for deep equality, traversing repeated fields, oneof groups, maps - * and messages recursively. - * Will also return true if both messages are `undefined`. - */ - equals(a, b) { - return reflectionEquals(this, a, b); - } - /** - * Is the given value assignable to our message type - * and contains no [excess properties](https://www.typescriptlang.org/docs/handbook/interfaces.html#excess-property-checks)? - */ - is(arg, depth = this.defaultCheckDepth) { - return this.refTypeCheck.is(arg, depth, false); - } - /** - * Is the given value assignable to our message type, - * regardless of [excess properties](https://www.typescriptlang.org/docs/handbook/interfaces.html#excess-property-checks)? - */ - isAssignable(arg, depth = this.defaultCheckDepth) { - return this.refTypeCheck.is(arg, depth, true); - } - /** - * Copy partial data into the target message. - */ - mergePartial(target, source) { - reflectionMergePartial(this, target, source); - } - /** - * Create a new message from binary format. - */ - fromBinary(data, options) { - let opt = binaryReadOptions(options); - return this.internalBinaryRead(opt.readerFactory(data), data.byteLength, opt); - } - /** - * Read a new message from a JSON value. - */ - fromJson(json, options) { - return this.internalJsonRead(json, jsonReadOptions(options)); - } - /** - * Read a new message from a JSON string. - * This is equivalent to `T.fromJson(JSON.parse(json))`. - */ - fromJsonString(json, options) { - let value = JSON.parse(json); - return this.fromJson(value, options); - } - /** - * Write the message to canonical JSON value. - */ - toJson(message, options) { - return this.internalJsonWrite(message, jsonWriteOptions(options)); - } - /** - * Convert the message to canonical JSON string. - * This is equivalent to `JSON.stringify(T.toJson(t))` - */ - toJsonString(message, options) { - var _a; - let value = this.toJson(message, options); - return JSON.stringify(value, null, (_a = options === null || options === void 0 ? void 0 : options.prettySpaces) !== null && _a !== void 0 ? _a : 0); - } - /** - * Write the message to binary format. - */ - toBinary(message, options) { - let opt = binaryWriteOptions(options); - return this.internalBinaryWrite(message, opt.writerFactory(), opt).finish(); - } - /** - * This is an internal method. If you just want to read a message from - * JSON, use `fromJson()` or `fromJsonString()`. - * - * Reads JSON value and merges the fields into the target - * according to protobuf rules. If the target is omitted, - * a new instance is created first. - */ - internalJsonRead(json, options, target) { - if (json !== null && typeof json == "object" && !Array.isArray(json)) { - let message = target !== null && target !== void 0 ? target : this.create(); - this.refJsonReader.read(json, message, options); - return message; - } - throw new Error(`Unable to parse message ${this.typeName} from JSON ${typeofJsonValue(json)}.`); - } - /** - * This is an internal method. If you just want to write a message - * to JSON, use `toJson()` or `toJsonString(). - * - * Writes JSON value and returns it. - */ - internalJsonWrite(message, options) { - return this.refJsonWriter.write(message, options); - } - /** - * This is an internal method. If you just want to write a message - * in binary format, use `toBinary()`. - * - * Serializes the message in binary format and appends it to the given - * writer. Returns passed writer. - */ - internalBinaryWrite(message, writer, options) { - this.refBinWriter.write(message, writer, options); - return writer; - } - /** - * This is an internal method. If you just want to read a message from - * binary data, use `fromBinary()`. - * - * Reads data from binary format and merges the fields into - * the target according to protobuf rules. If the target is - * omitted, a new instance is created first. - */ - internalBinaryRead(reader, length, options, target) { - let message = target !== null && target !== void 0 ? target : this.create(); - this.refBinReader.read(reader, message, options, length); - return message; - } -} - -// @generated by protobuf-ts 2.9.4 with parameter generate_dependencies,long_type_number,output_javascript -// @generated from protobuf file "apple.parsec.siri.v2alpha.DeviceState.proto" (syntax proto3) -// tslint:disable -// @generated by protobuf-ts 2.9.4 with parameter generate_dependencies,long_type_number,output_javascript -// @generated from protobuf file "apple.parsec.siri.v2alpha.DeviceState.proto" (syntax proto3) -// tslint:disable -// @generated message type with reflection information, may provide speed optimized methods -class DeviceState$Type extends MessageType { - constructor() { - super("DeviceState", [ - { no: 1, name: "companionName", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ }, - { no: 2, name: "deviceName", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ }, - { no: 3, name: "inputOrigin", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ }, - { no: 6, name: "isAppleTv", kind: "scalar", opt: true, T: 8 /*ScalarType.BOOL*/ }, - { no: 7, name: "isCarDnd", kind: "scalar", opt: true, T: 8 /*ScalarType.BOOL*/ }, - { no: 8, name: "isCarPlay", kind: "scalar", opt: true, T: 8 /*ScalarType.BOOL*/ }, - { no: 9, name: "isEyesFree", kind: "scalar", opt: true, T: 8 /*ScalarType.BOOL*/ }, - { no: 10, name: "isHomePod", kind: "scalar", opt: true, T: 8 /*ScalarType.BOOL*/ }, - { no: 11, name: "isLockedWithPasscode", kind: "scalar", opt: true, T: 8 /*ScalarType.BOOL*/ }, - { no: 12, name: "isMac", kind: "scalar", opt: true, T: 8 /*ScalarType.BOOL*/ }, - { no: 13, name: "isMultiUser", kind: "scalar", opt: true, T: 8 /*ScalarType.BOOL*/ }, - { no: 14, name: "isPad", kind: "scalar", opt: true, T: 8 /*ScalarType.BOOL*/ }, - { no: 15, name: "isPhone", kind: "scalar", opt: true, T: 8 /*ScalarType.BOOL*/ }, - { no: 16, name: "isTextToSpeechEnabled", kind: "scalar", opt: true, T: 8 /*ScalarType.BOOL*/ }, - { no: 17, name: "isVox", kind: "scalar", opt: true, T: 8 /*ScalarType.BOOL*/ }, - { no: 18, name: "isVoiceGenderFemale", kind: "scalar", opt: true, T: 8 /*ScalarType.BOOL*/ }, - { no: 19, name: "isVoiceGenderMale", kind: "scalar", opt: true, T: 8 /*ScalarType.BOOL*/ }, - { no: 20, name: "isVoiceGenderUnknown", kind: "scalar", opt: true, T: 8 /*ScalarType.BOOL*/ }, - { no: 21, name: "isVoiceTriggerEnabled", kind: "scalar", opt: true, T: 8 /*ScalarType.BOOL*/ }, - { no: 22, name: "isWatch", kind: "scalar", opt: true, T: 8 /*ScalarType.BOOL*/ }, - { no: 23, name: "siriLocale", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ }, - { no: 24, name: "userAssignedDeviceName", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ } - ]); - } -} -/** - * @generated MessageType for protobuf message DeviceState - */ -const DeviceState = new DeviceState$Type(); - -// @generated by protobuf-ts 2.9.4 with parameter generate_dependencies,long_type_number,output_javascript -// @generated from protobuf file "apple.parsec.siri.v2alpha.SiriPegasusContext.proto" (syntax proto3) -// tslint:disable -// @generated by protobuf-ts 2.9.4 with parameter generate_dependencies,long_type_number,output_javascript -// @generated from protobuf file "apple.parsec.siri.v2alpha.SiriPegasusContext.proto" (syntax proto3) -// tslint:disable -// @generated message type with reflection information, may provide speed optimized methods -class SiriPegasusContext$Type extends MessageType { - constructor() { - super("SiriPegasusContext", [ - { no: 1, name: "deviceState", kind: "message", T: () => DeviceState }, - { no: 8, name: "assistantID", kind: "scalar", T: 9 /*ScalarType.STRING*/ }, - { no: 10, name: "interactionID", kind: "scalar", T: 9 /*ScalarType.STRING*/ } - ]); - } -} -/** - * @generated MessageType for protobuf message SiriPegasusContext - */ -const SiriPegasusContext = new SiriPegasusContext$Type(); - -// @generated by protobuf-ts 2.9.4 with parameter generate_dependencies,long_type_number,output_javascript -// @generated from protobuf file "apple.parsec.search.PegasusQueryContext.proto" (syntax proto3) -// tslint:disable -// @generated by protobuf-ts 2.9.4 with parameter generate_dependencies,long_type_number,output_javascript -// @generated from protobuf file "apple.parsec.search.PegasusQueryContext.proto" (syntax proto3) -// tslint:disable -/** - * @generated from protobuf enum TemperatureUnit - */ -var TemperatureUnit; -(function (TemperatureUnit) { - /** - * @generated from protobuf enum value: TemperatureUnitUnknown = 0; - */ - TemperatureUnit[TemperatureUnit["TemperatureUnitUnknown"] = 0] = "TemperatureUnitUnknown"; - /** - * celsius - * - * @generated from protobuf enum value: TemperatureUnitCelsius = 1; - */ - TemperatureUnit[TemperatureUnit["TemperatureUnitCelsius"] = 1] = "TemperatureUnitCelsius"; - /** - * fahrenheit - * - * @generated from protobuf enum value: TemperatureUnitFahrenheit = 2; - */ - TemperatureUnit[TemperatureUnit["TemperatureUnitFahrenheit"] = 2] = "TemperatureUnitFahrenheit"; -})(TemperatureUnit || (TemperatureUnit = {})); -/** - * @generated from protobuf enum MeasurementSystem - */ -var MeasurementSystem; -(function (MeasurementSystem) { - /** - * @generated from protobuf enum value: MeasurementSystemUnknown = 0; - */ - MeasurementSystem[MeasurementSystem["MeasurementSystemUnknown"] = 0] = "MeasurementSystemUnknown"; - /** - * @generated from protobuf enum value: MeasurementSystemSI = 1; - */ - MeasurementSystem[MeasurementSystem["MeasurementSystemSI"] = 1] = "MeasurementSystemSI"; - /** - * @generated from protobuf enum value: MeasurementSystemUS = 2; - */ - MeasurementSystem[MeasurementSystem["MeasurementSystemUS"] = 2] = "MeasurementSystemUS"; - /** - * @generated from protobuf enum value: MeasurementSystemUK = 3; - */ - MeasurementSystem[MeasurementSystem["MeasurementSystemUK"] = 3] = "MeasurementSystemUK"; -})(MeasurementSystem || (MeasurementSystem = {})); -/** - * @generated from protobuf enum HourFormat - */ -var HourFormat; -(function (HourFormat) { - /** - * @generated from protobuf enum value: HourFormatUnknown = 0; - */ - HourFormat[HourFormat["HourFormatUnknown"] = 0] = "HourFormatUnknown"; - /** - * twelve - * - * @generated from protobuf enum value: HourFormatTwelve = 1; - */ - HourFormat[HourFormat["HourFormatTwelve"] = 1] = "HourFormatTwelve"; - /** - * twentyFour - * - * @generated from protobuf enum value: HourFormatTwentyFour = 2; - */ - HourFormat[HourFormat["HourFormatTwentyFour"] = 2] = "HourFormatTwentyFour"; -})(HourFormat || (HourFormat = {})); -/** - * @generated from protobuf enum Source - */ -var Source; -(function (Source) { - /** - * @generated from protobuf enum value: SourceUnknown = 0; - */ - Source[Source["SourceUnknown"] = 0] = "SourceUnknown"; - /** - * disabledByUser - * - * @generated from protobuf enum value: SourceDisabledByUser = 1; - */ - Source[Source["SourceDisabledByUser"] = 1] = "SourceDisabledByUser"; - /** - * disabledByBag - * - * @generated from protobuf enum value: SourceDisabledByBag = 2; - */ - Source[Source["SourceDisabledByBag"] = 2] = "SourceDisabledByBag"; - /** - * tooSlow - * - * @generated from protobuf enum value: SourceTooSlow = 3; - */ - Source[Source["SourceTooSlow"] = 3] = "SourceTooSlow"; - /** - * @generated from protobuf enum value: SourceError = 4; - */ - Source[Source["SourceError"] = 4] = "SourceError"; - /** - * @generated from protobuf enum value: SourceGPS = 5; - */ - Source[Source["SourceGPS"] = 5] = "SourceGPS"; - /** - * cellular - * - * @generated from protobuf enum value: SourceCellular = 6; - */ - Source[Source["SourceCellular"] = 6] = "SourceCellular"; - /** - * wiFi - * - * @generated from protobuf enum value: SourceWiFi = 7; - */ - Source[Source["SourceWiFi"] = 7] = "SourceWiFi"; -})(Source || (Source = {})); -// @generated message type with reflection information, may provide speed optimized methods -class PegasusQueryContext$Type extends MessageType { - constructor() { - super("PegasusQueryContext", [ - { no: 1, name: "secretKey", kind: "scalar", T: 9 /*ScalarType.STRING*/ }, - { no: 2, name: "countryCode", kind: "scalar", T: 9 /*ScalarType.STRING*/ }, - { no: 3, name: "locale", kind: "scalar", T: 9 /*ScalarType.STRING*/ }, - { no: 4, name: "effectiveSystemLanguage", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ }, - { no: 5, name: "preferredLanguages", kind: "scalar", repeat: 2 /*RepeatType.UNPACKED*/, T: 9 /*ScalarType.STRING*/ }, - { no: 6, name: "storeFront", kind: "scalar", T: 9 /*ScalarType.STRING*/ }, - { no: 7, name: "userGuid", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ }, - { no: 8, name: "timeZone", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ }, - { no: 9, name: "skuRegion", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ }, - { no: 10, name: "calendar", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ }, - { no: 11, name: "temperatureUnit", kind: "enum", T: () => ["TemperatureUnit", TemperatureUnit] }, - { no: 12, name: "measurementSystem", kind: "enum", T: () => ["MeasurementSystem", MeasurementSystem] }, - { no: 13, name: "hourFormat", kind: "enum", T: () => ["HourFormat", HourFormat] }, - { no: 14, name: "location", kind: "message", T: () => Location }, - { no: 18, name: "installedAppsSignature", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ }, - { no: 19, name: "uiScale", kind: "scalar", opt: true, T: 2 /*ScalarType.FLOAT*/ }, - { no: 20, name: "internalBuild", kind: "scalar", opt: true, T: 13 /*ScalarType.UINT32*/ }, - { no: 21, name: "seedBuild", kind: "scalar", opt: true, T: 13 /*ScalarType.UINT32*/ }, - { no: 22, name: "hsEnabled", kind: "scalar", opt: true, T: 8 /*ScalarType.BOOL*/ }, - { no: 26, name: "region", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ }, - { no: 28, name: "trialIdentifiers", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ }, - { no: 33, name: "deviceModel", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ }, - { no: 34, name: "isGenerativeModelDevice", kind: "scalar", opt: true, T: 8 /*ScalarType.BOOL*/ }, - { no: 38, name: "longSecretKey", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ } - ]); - } -} -/** - * @generated MessageType for protobuf message PegasusQueryContext - */ -const PegasusQueryContext = new PegasusQueryContext$Type(); -// @generated message type with reflection information, may provide speed optimized methods -class Location$Type extends MessageType { - constructor() { - super("Location", [ - { no: 1, name: "latitude", kind: "scalar", opt: true, T: 2 /*ScalarType.FLOAT*/ }, - { no: 2, name: "longitude", kind: "scalar", opt: true, T: 2 /*ScalarType.FLOAT*/ }, - { no: 3, name: "source", kind: "enum", opt: true, T: () => ["Source", Source] }, - { no: 4, name: "horizontalAccuracy", kind: "scalar", opt: true, T: 2 /*ScalarType.FLOAT*/ } - ]); - } -} -/** - * @generated MessageType for protobuf message Location - */ -const Location = new Location$Type(); - -// @generated by protobuf-ts 2.9.4 with parameter generate_dependencies,long_type_number,keep_enum_prefix,output_javascript -// @generated from protobuf file "google/protobuf/any.proto" (package "google.protobuf", syntax proto3) -// tslint:disable -// -// Protocol Buffers - Google's data interchange format -// Copyright 2008 Google Inc. All rights reserved. -// https://developers.google.com/protocol-buffers/ -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are -// met: -// -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above -// copyright notice, this list of conditions and the following disclaimer -// in the documentation and/or other materials provided with the -// distribution. -// * Neither the name of Google Inc. nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -// @generated message type with reflection information, may provide speed optimized methods -class Any$Type extends MessageType { - constructor() { - super("google.protobuf.Any", [ - { no: 1, name: "type_url", kind: "scalar", T: 9 /*ScalarType.STRING*/ }, - { no: 2, name: "value", kind: "scalar", T: 12 /*ScalarType.BYTES*/ } - ]); - } - /** - * Pack the message into a new `Any`. - * - * Uses 'type.googleapis.com/full.type.name' as the type URL. - */ - pack(message, type) { - return { - typeUrl: this.typeNameToUrl(type.typeName), value: type.toBinary(message), - }; - } - /** - * Unpack the message from the `Any`. - */ - unpack(any, type, options) { - if (!this.contains(any, type)) - throw new Error("Cannot unpack google.protobuf.Any with typeUrl '" + any.typeUrl + "' as " + type.typeName + "."); - return type.fromBinary(any.value, options); - } - /** - * Does the given `Any` contain a packed message of the given type? - */ - contains(any, type) { - if (!any.typeUrl.length) - return false; - let wants = typeof type == "string" ? type : type.typeName; - let has = this.typeUrlToName(any.typeUrl); - return wants === has; - } - /** - * Convert the message to canonical JSON value. - * - * You have to provide the `typeRegistry` option so that the - * packed message can be converted to JSON. - * - * The `typeRegistry` option is also required to read - * `google.protobuf.Any` from JSON format. - */ - internalJsonWrite(any, options) { - if (any.typeUrl === "") - return {}; - let typeName = this.typeUrlToName(any.typeUrl); - let opt = jsonWriteOptions(options); - let type = opt.typeRegistry?.find(t => t.typeName === typeName); - if (!type) - throw new globalThis.Error("Unable to convert google.protobuf.Any with typeUrl '" + any.typeUrl + "' to JSON. The specified type " + typeName + " is not available in the type registry."); - let value = type.fromBinary(any.value, { readUnknownField: false }); - let json = type.internalJsonWrite(value, opt); - if (typeName.startsWith("google.protobuf.") || !isJsonObject(json)) - json = { value: json }; - json["@type"] = any.typeUrl; - return json; - } - internalJsonRead(json, options, target) { - if (!isJsonObject(json)) - throw new globalThis.Error("Unable to parse google.protobuf.Any from JSON " + typeofJsonValue(json) + "."); - if (typeof json["@type"] != "string" || json["@type"] == "") - return this.create(); - let typeName = this.typeUrlToName(json["@type"]); - let type = options?.typeRegistry?.find(t => t.typeName == typeName); - if (!type) - throw new globalThis.Error("Unable to parse google.protobuf.Any from JSON. The specified type " + typeName + " is not available in the type registry."); - let value; - if (typeName.startsWith("google.protobuf.") && json.hasOwnProperty("value")) - value = type.fromJson(json["value"], options); - else { - let copy = Object.assign({}, json); - delete copy["@type"]; - value = type.fromJson(copy, options); - } - if (target === undefined) - target = this.create(); - target.typeUrl = json["@type"]; - target.value = type.toBinary(value); - return target; - } - typeNameToUrl(name) { - if (!name.length) - throw new Error("invalid type name: " + name); - return "type.googleapis.com/" + name; - } - typeUrlToName(url) { - if (!url.length) - throw new Error("invalid type url: " + url); - let slash = url.lastIndexOf("/"); - let name = slash > 0 ? url.substring(slash + 1) : url; - if (!name.length) - throw new Error("invalid type url: " + url); - return name; - } - create(value) { - const message = globalThis.Object.create((this.messagePrototype)); - message.typeUrl = ""; - message.value = new Uint8Array(0); - if (value !== undefined) - reflectionMergePartial(this, message, value); - return message; - } - internalBinaryRead(reader, length, options, target) { - let message = target ?? this.create(), end = reader.pos + length; - while (reader.pos < end) { - let [fieldNo, wireType] = reader.tag(); - switch (fieldNo) { - case /* string type_url */ 1: - message.typeUrl = reader.string(); - break; - case /* bytes value */ 2: - message.value = reader.bytes(); - break; - default: - let u = options.readUnknownField; - if (u === "throw") - throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); - let d = reader.skip(wireType); - if (u !== false) - (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); - } - } - return message; - } - internalBinaryWrite(message, writer, options) { - /* string type_url = 1; */ - if (message.typeUrl !== "") - writer.tag(1, WireType.LengthDelimited).string(message.typeUrl); - /* bytes value = 2; */ - if (message.value.length) - writer.tag(2, WireType.LengthDelimited).bytes(message.value); - let u = options.writeUnknownFields; - if (u !== false) - (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); - return writer; - } -} -/** - * @generated MessageType for protobuf message google.protobuf.Any - */ -const Any = new Any$Type(); - -// @generated by protobuf-ts 2.9.4 with parameter generate_dependencies,long_type_number,output_javascript -// @generated from protobuf file "apple.parsec.siri.v2alpha.PegasusSearchQuery.proto" (syntax proto3) -// tslint:disable -// @generated by protobuf-ts 2.9.4 with parameter generate_dependencies,long_type_number,output_javascript -// @generated from protobuf file "apple.parsec.siri.v2alpha.PegasusSearchQuery.proto" (syntax proto3) -// tslint:disable -// @generated message type with reflection information, may provide speed optimized methods -class PegasusSearchQuery$Type extends MessageType { - constructor() { - super("PegasusSearchQuery", [ - { no: 1, name: "queryString", kind: "scalar", T: 9 /*ScalarType.STRING*/ }, - { no: 2, name: "queryID", kind: "scalar", T: 9 /*ScalarType.STRING*/ }, - { no: 2002, name: "unknown2002", kind: "message", repeat: 1 /*RepeatType.PACKED*/, T: () => Unknown2002 } - ]); - } -} -/** - * @generated MessageType for protobuf message PegasusSearchQuery - */ -const PegasusSearchQuery = new PegasusSearchQuery$Type(); -// @generated message type with reflection information, may provide speed optimized methods -class Unknown2002$Type extends MessageType { - constructor() { - super("Unknown2002", [ - { no: 2, name: "n2", kind: "message", T: () => U2002N2 } - ]); - } -} -/** - * @generated MessageType for protobuf message Unknown2002 - */ -const Unknown2002 = new Unknown2002$Type(); -// @generated message type with reflection information, may provide speed optimized methods -class U2002N2$Type extends MessageType { - constructor() { - super("U2002N2", [ - { no: 1, name: "unknown1", kind: "scalar", T: 5 /*ScalarType.INT32*/ }, - { no: 2, name: "supplement", kind: "message", T: () => Any }, - { no: 3, name: "unknown3", kind: "scalar", T: 5 /*ScalarType.INT32*/ } - ]); - } -} -/** - * @generated MessageType for protobuf message U2002N2 - */ -const U2002N2 = new U2002N2$Type(); - -// @generated by protobuf-ts 2.9.4 with parameter generate_dependencies,long_type_number,output_javascript -// @generated from protobuf file "apple.parsec.siri.v2alpha.SiriPegasusRequest.proto" (syntax proto3) -// tslint:disable -// @generated by protobuf-ts 2.9.4 with parameter generate_dependencies,long_type_number,output_javascript -// @generated from protobuf file "apple.parsec.siri.v2alpha.SiriPegasusRequest.proto" (syntax proto3) -// tslint:disable -// @generated message type with reflection information, may provide speed optimized methods -class SiriPegasusRequest$Type extends MessageType { - constructor() { - super("SiriPegasusRequest", [ - { no: 1, name: "queries", kind: "message", repeat: 1 /*RepeatType.PACKED*/, T: () => PegasusSearchQuery }, - { no: 2, name: "queryContext", kind: "message", T: () => PegasusQueryContext }, - { no: 3, name: "userDataShareOptIn", kind: "scalar", opt: true, T: 8 /*ScalarType.BOOL*/ }, - { no: 4, name: "featureFlag", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ }, - { no: 5, name: "siriPegasusContext", kind: "message", T: () => SiriPegasusContext }, - { no: 8, name: "isDataOnlyRequest", kind: "scalar", opt: true, T: 8 /*ScalarType.BOOL*/ } - ]); - } -} -/** - * @generated MessageType for protobuf message SiriPegasusRequest - */ -const SiriPegasusRequest = new SiriPegasusRequest$Type(); - -// @generated by protobuf-ts 2.9.4 with parameter generate_dependencies,long_type_number,keep_enum_prefix,output_javascript -// @generated from protobuf file "apple.parsec.lookup.v1alpha.LookupSearchRequest.proto" (syntax proto3) -// tslint:disable -// @generated by protobuf-ts 2.9.4 with parameter generate_dependencies,long_type_number,keep_enum_prefix,output_javascript -// @generated from protobuf file "apple.parsec.lookup.v1alpha.LookupSearchRequest.proto" (syntax proto3) -// tslint:disable -// @generated message type with reflection information, may provide speed optimized methods -class LookupSearchRequest$Type extends MessageType { - constructor() { - super("LookupSearchRequest", [ - { no: 2, name: "queryContext", kind: "message", T: () => PegasusQueryContext } - ]); - } -} -/** - * @generated MessageType for protobuf message LookupSearchRequest - */ -const LookupSearchRequest = new LookupSearchRequest$Type(); - -// @generated by protobuf-ts 2.9.4 with parameter generate_dependencies,long_type_number,output_javascript -// @generated from protobuf file "apple.parsec.visualsearch.v2.VisualSearchRequest.proto" (syntax proto3) -// tslint:disable -// @generated by protobuf-ts 2.9.4 with parameter generate_dependencies,long_type_number,output_javascript -// @generated from protobuf file "apple.parsec.visualsearch.v2.VisualSearchRequest.proto" (syntax proto3) -// tslint:disable -// @generated message type with reflection information, may provide speed optimized methods -class VisualSearchRequest$Type extends MessageType { - constructor() { - super("VisualSearchRequest", [ - { no: 3, name: "queryContext", kind: "message", T: () => PegasusQueryContext } - ]); - } -} -/** - * @generated MessageType for protobuf message VisualSearchRequest - */ -const VisualSearchRequest = new VisualSearchRequest$Type(); - -log("v4.2.3(4049)"); -// 构造回复数据 -let $response = undefined; -/***************** Processing *****************/ -// 解构URL -const url = new URL($request.url); -log(`⚠ url: ${url.toJSON()}`, ""); -// 获取连接参数 -const METHOD = $request.method, HOST = url.hostname, PATH = url.pathname; url.pathname.split("/").filter(Boolean); -log(`⚠ METHOD: ${METHOD}, HOST: ${HOST}, PATH: ${PATH}`, ""); -// 解析格式 -const FORMAT = ($request.headers?.["Content-Type"] ?? $request.headers?.["content-type"])?.split(";")?.[0]; -log(`⚠ FORMAT: ${FORMAT}`, ""); -!(async () => { - const { Settings, Caches, Configs } = setENV("iRingo", "Siri", Database$1); - log(`⚠ Settings.Switch: ${Settings?.Switch}`, ""); - switch (Settings.Switch) { - case true: - default: - // 创建空数据 - let Locale, Language, CountryCode; - let body = {}; - // 方法判断 - switch (METHOD) { - case "POST": - case "PUT": - case "PATCH": - case "DELETE": - // 格式判断 - switch (FORMAT) { - case undefined: // 视为无body - break; - case "application/x-www-form-urlencoded": - case "text/plain": - default: - //log(`🚧 body: ${body}`, ""); - break; - case "application/x-mpegURL": - case "application/x-mpegurl": - case "application/vnd.apple.mpegurl": - case "audio/mpegurl": - //body = M3U8.parse($request.body); - //log(`🚧 body: ${JSON.stringify(body)}`, ""); - //$request.body = M3U8.stringify(body); - break; - case "text/xml": - case "text/html": - case "text/plist": - case "application/xml": - case "application/plist": - case "application/x-plist": - //body = XML.parse($request.body); - //log(`🚧 body: ${JSON.stringify(body)}`, ""); - //$request.body = XML.stringify(body); - break; - case "text/vtt": - case "application/vtt": - //body = VTT.parse($request.body); - //log(`🚧 body: ${JSON.stringify(body)}`, ""); - //$request.body = VTT.stringify(body); - break; - case "text/json": - case "application/json": - //body = JSON.parse($request.body ?? "{}"); - //log(`🚧 body: ${JSON.stringify(body)}`, ""); - //$request.body = JSON.stringify(body); - break; - case "application/protobuf": - case "application/x-protobuf": - case "application/vnd.google.protobuf": - case "application/grpc": - case "application/grpc+proto": - case "applecation/octet-stream": - //log(`🚧 $request.body: ${JSON.stringify($request.body)}`, ""); - let rawBody = ($platform === "Quantumult X") ? new Uint8Array($request.bodyBytes ?? []) : $request.body ?? new Uint8Array(); - //log(`🚧 isBuffer? ${ArrayBuffer.isView(rawBody)}: ${JSON.stringify(rawBody)}`, ""); - switch (FORMAT) { - case "application/protobuf": - case "application/x-protobuf": - case "application/vnd.google.protobuf": - break; - case "application/grpc": - case "application/grpc+proto": - rawBody = GRPC.decode(rawBody); - // 解析链接并处理protobuf数据 - // 主机判断 - switch (HOST) { - case "guzzoni.smoot.apple.com": - case "api-siri.smoot.apple.com": - case "api2.smoot.apple.com": - default: - //$request.headers["content-type"] = "application/grpc+proto"; - if ($request.headers["user-agent"]?.includes("grpc-node-js")) $request.headers["user-agent"] = "PegasusKit/1 (iPhone14,3; iPhone OS 18.1 22B5054e) siri/1"; - // 路径判断 - switch (PATH) { - case "/apple.parsec.siri.v2alpha.SiriSearch/SiriSearch": // Siri搜索 - body = SiriPegasusRequest.fromBinary(rawBody); - log(`🚧 body: ${JSON.stringify(body)}`, ""); - body.queryContext = modifyPegasusQueryContext(body.queryContext, Settings); - let fixLocation = true; - body?.queries?.[0]?.unknown2002.forEach((unknown2002, index) => { - switch (unknown2002?.n2?.supplement?.typeUrl) { - case "type.googleapis.com/apple.parsec.siri.v2alpha.AppInfo": - /****************** initialization start *******************/ - class ApplicationInfomationRequest$Type extends MessageType { - constructor() { - super("ApplicationInfomationRequest", [ - { no: 2, name: "bundleID", kind: "scalar", T: 9 /*ScalarType.STRING*/ }, - { no: 4, name: "launchIntent", kind: "scalar", T: 9 /*ScalarType.STRING*/ } - ]); - } - } - const ApplicationInfomationRequest = new ApplicationInfomationRequest$Type(); - /****************** initialization finish *******************/ - const AppInfo = ApplicationInfomationRequest.fromBinary(unknown2002?.n2?.supplement?.value); - log(`🚧 AppInfo: ${JSON.stringify(AppInfo)}`, ""); - switch (AppInfo?.bundleID) { - case "com.apple.weather": - case "com.heweather.weatherapp": - fixLocation = false; - break; - case "com.apple.store.Jolly": - fixLocation = false; - break; - case "com.apple.Music": - case "com.apple.AppStore": - fixLocation = false; - break; - } break; - } }); - if (fixLocation) delete body?.queryContext?.location; - log(`🚧 body: ${JSON.stringify(body)}`, ""); - rawBody = SiriPegasusRequest.toBinary(body); - break; - case "/apple.parsec.lookup.v1alpha.LookupSearch/LookupSearch": // 查询搜索 - body = LookupSearchRequest.fromBinary(rawBody); - log(`🚧 body: ${JSON.stringify(body)}`, ""); - body.queryContext = modifyPegasusQueryContext(body.queryContext, Settings); - log(`🚧 body: ${JSON.stringify(body)}`, ""); - rawBody = LookupSearchRequest.toBinary(body); - break; - case "/apple.parsec.visualsearch.v2.VisualSearch/VisualSearch": { // 视觉搜索 - body = VisualSearchRequest.fromBinary(rawBody); - log(`🚧 body: ${JSON.stringify(body)}`, ""); - body.queryContext = modifyPegasusQueryContext(body.queryContext, Settings); - log(`🚧 body: ${JSON.stringify(body)}`, ""); - rawBody = VisualSearchRequest.toBinary(body); - break; - } case "/apple.parsec.responseframework.engagement.v1alpha.EngagementSearch/EngagementSearch": // - /****************** initialization start *******************/ - class EngagementRequest$Type extends MessageType { - constructor() { - super("EngagementRequest", [ - { no: 1, name: "queryContext", kind: "message", T: () => PegasusQueryContext } - ]); - } - } - const EngagementRequest = new EngagementRequest$Type(); - /****************** initialization finish *******************/ - body = EngagementRequest.fromBinary(rawBody); - log(`🚧 body: ${JSON.stringify(body)}`, ""); - body.queryContext = modifyPegasusQueryContext(body.queryContext, Settings); - log(`🚧 body: ${JSON.stringify(body)}`, ""); - rawBody = EngagementRequest.toBinary(body); - break; - case "/apple.parsec.spotlight.v1alpha.ZkwSuggestService/Suggest": // 新闻建议 - /****************** initialization start *******************/ - class ZkwSuggestRequest$Type extends MessageType { - constructor() { - super("ZkwSuggestRequest", [ - //{ no: 1, name: "queries", kind: "message", repeat: 1 /*RepeatType.PACKED*/, T: () => Query }, - { no: 2, name: "queryContext", kind: "message", T: () => PegasusQueryContext } - ]); - } - } - const ZkwSuggestRequest = new ZkwSuggestRequest$Type(); - /****************** initialization finish *******************/ - body = ZkwSuggestRequest.fromBinary(rawBody); - log(`🚧 body: ${JSON.stringify(body)}`, ""); - body.queryContext = modifyPegasusQueryContext(body.queryContext, Settings); - log(`🚧 body: ${JSON.stringify(body)}`, ""); - rawBody = ZkwSuggestRequest.toBinary(body); - break; - } break; - } rawBody = GRPC.encode(rawBody); - break; - } // 写入二进制数据 - $request.body = rawBody; - break; - } //break; // 不中断,继续处理URL - case "GET": - case "HEAD": - case "OPTIONS": - default: - Locale = Locale ?? url.searchParams.get("locale"); - [Language, CountryCode] = Locale?.split("_") ?? []; - log(`🚧 Locale: ${Locale}, Language: ${Language}, CountryCode: ${CountryCode}`, ""); - switch (Settings.CountryCode) { - case "AUTO": - Settings.CountryCode = CountryCode; - break; - default: - if (url.searchParams.has("cc")) url.searchParams.set("cc", Settings.CountryCode); - break; - } // 主机判断 - switch (HOST) { - case "api.smoot.apple.cn": - case "api.smoot.apple.com": - case "api2.smoot.apple.com": - case "api-siri.smoot.apple.com": - default: // 其他主机 - let q = url.searchParams.get("q"); - // 路径判断 - switch (PATH) { - case "/bag": // 配置 - break; - case "/search": // 搜索 - switch (url.searchParams.get("qtype")) { - case "zkw": // 处理"新闻"小组件 - switch (Settings.CountryCode) { - case "CN": - case "HK": - case "MO": - case "TW": - case "SG": - url.searchParams.set("locale", `${Language}_SG`); - break; - case "US": - case "CA": - case "UK": - case "AU": - // 不做修正 - break; - default: - url.searchParams.set("locale", `${Language}_US`); - break; - } break; - default: // 其他搜索 - if (q?.startsWith?.("%E5%A4%A9%E6%B0%94%20")) { // 处理"天气"搜索,搜索词"天气 "开头 - console.log("'天气 '开头"); - url.searchParams.set("q", q.replace(/%E5%A4%A9%E6%B0%94/, "weather")); // "天气"替换为"weather" - if (/^weather%20.*%E5%B8%82$/.test(q)) url.searchParams.set("q", q.replace(/$/, "%E5%B8%82")); - } else if (q?.endsWith?.("%20%E5%A4%A9%E6%B0%94")) {// 处理"天气"搜索,搜索词" 天气"结尾 - console.log("' 天气'结尾"); - url.searchParams.set("q", q.replace(/%E5%A4%A9%E6%B0%94/, "weather")); // "天气"替换为"weather" - if (/.*%E5%B8%82%20weather$/.test(q)) url.searchParams.set("q", q.replace(/%20weather$/, "%E5%B8%82%20weather")); - } break; - } break; - case "/card": // 卡片 - switch (url.searchParams.get("include")) { - case "tv": - case "movies": - url.searchParams.set("card_locale", `${Language}_${Settings.CountryCode}`); - const storefront = url.searchParams.get("storefront")?.match(/[\d]{6}/g); - switch (storefront) { //StoreFront ID, from App Store Region - case "143463": // HK - url.searchParams.set("q", q.replace(/%2F[a-z]{2}-[A-Z]{2}/, "%2Fzh-HK")); - break; - case "143464": // SG - url.searchParams.set("q", q.replace(/%2F[a-z]{2}-[A-Z]{2}/, "%2Fzh-SG")); - break; - case "143465": // CN - url.searchParams.set("q", q.replace(/%2F[a-z]{2}-[A-Z]{2}/, "%2Fzh-HK")); - break; - case "143470": // TW - url.searchParams.set("q", q.replace(/%2F[a-z]{2}-[A-Z]{2}/, "%2Fzh-TW")); - break; - } break; - case "apps": - case "music": - url.searchParams.set("card_locale", `${Language}_${Settings.CountryCode}`); - break; - case "dictionary": - switch (Language) { - case "zh-Hans": - case "zh-Hant": - url.searchParams.set("card_locale", `en_${Settings.CountryCode}`); - break; - } break; - default: - url.searchParams.set("card_locale", `${Language}_${Settings.CountryCode}`); - break; - } break; - } break; - case "guzzoni.smoot.apple.com": - break; - case "fbs.smoot.apple.com": - break; - case "cdn.smoot.apple.com": - break; - } break; - case "CONNECT": - case "TRACE": - break; - } $request.url = url.toString(); - log(`🚧 调试信息`, `$request.url: ${$request.url}`, ""); - break; - case false: - break; - }})() - .catch((e) => logError(e)) - .finally(() => { - switch ($response) { - default: // 有构造回复数据,返回构造的回复数据 - //log(`🚧 finally`, `echo $response: ${JSON.stringify($response, null, 2)}`, ""); - if ($response.headers?.["Content-Encoding"]) ; - if ($response.headers?.["content-encoding"]) ; - switch ($platform) { - default: - done({ response: $response }); - break; - case "Quantumult X": - if (!$response.status) $response.status = "HTTP/1.1 200 OK"; - delete $response.headers?.["Content-Length"]; - delete $response.headers?.["content-length"]; - delete $response.headers?.["Transfer-Encoding"]; - done($response); - break; - } break; - case undefined: // 无构造回复数据,发送修改的请求数据 - //log(`🚧 finally`, `$request: ${JSON.stringify($request, null, 2)}`, ""); - done($request); - break; - } }); diff --git a/js/Siri.request.js b/js/Siri.request.js deleted file mode 100644 index 6de575a52..000000000 --- a/js/Siri.request.js +++ /dev/null @@ -1 +0,0 @@ -console.log(" iRingo: ⭕ Siri Request");const e=function(){if("undefined"!=typeof $environment&&$environment["surge-version"])return"Surge";if("undefined"!=typeof $environment&&$environment["stash-version"])return"Stash";if("undefined"!=typeof module&&module.exports)return"Node.js";if("undefined"!=typeof $task)return"Quantumult X";if("undefined"!=typeof $loon)return"Loon";if("undefined"!=typeof $rocket)return"Shadowrocket";if("undefined"!=typeof Egern)return"Egern"}();class t{static name="Lodash";static version="1.2.2";static about(){return console.log(`\n🟧 ${this.name} v${this.version}\n`)}static get(e={},t="",s=void 0){Array.isArray(t)||(t=this.toPath(t));const a=t.reduce(((e,t)=>Object(e)[t]),e);return void 0===a?s:a}static set(e={},t="",s){return Array.isArray(t)||(t=this.toPath(t)),t.slice(0,-1).reduce(((e,s,a)=>Object(e[s])===e[s]?e[s]:e[s]=/^\d+$/.test(t[a+1])?[]:{}),e)[t[t.length-1]]=s,e}static unset(e={},t=""){return Array.isArray(t)||(t=this.toPath(t)),t.reduce(((e,s,a)=>a===t.length-1?(delete e[s],!0):Object(e)[s]),e)}static toPath(e){return e.replace(/\[(\d+)\]/g,".$1").split(".").filter(Boolean)}static escape(e){const t={"&":"&","<":"<",">":">",'"':""","'":"'"};return e.replace(/[&<>"']/g,(e=>t[e]))}static unescape(e){const t={"&":"&","<":"<",">":">",""":'"',"'":"'"};return e.replace(/&|<|>|"|'/g,(e=>t[e]))}}class s{static name="Storage";static version="1.1.0";static about(){return r("",`🟧 ${this.name} v${this.version}`,"")}static data=null;static dataFile="box.dat";static#e=/^@(?[^.]+)(?:\.(?.*))?$/;static getItem(s=new String,a=null){let r=a;if(!0===s.startsWith("@")){const{key:e,path:a}=s.match(this.#e)?.groups;s=e;let i=this.getItem(s,{});"object"!=typeof i&&(i={}),r=t.get(i,a);try{r=JSON.parse(r)}catch(e){}}else{switch(e){case"Surge":case"Loon":case"Stash":case"Egern":case"Shadowrocket":r=$persistentStore.read(s);break;case"Quantumult X":r=$prefs.valueForKey(s);break;case"Node.js":this.data=this.#t(this.dataFile),r=this.data?.[s];break;default:r=this.data?.[s]||null}try{r=JSON.parse(r)}catch(e){}}return r??a}static setItem(s=new String,a=new String){let r=!1;if("object"==typeof a)a=JSON.stringify(a);else a=String(a);if(!0===s.startsWith("@")){const{key:e,path:i}=s.match(this.#e)?.groups;s=e;let o=this.getItem(s,{});"object"!=typeof o&&(o={}),t.set(o,i,a),r=this.setItem(s,o)}else switch(e){case"Surge":case"Loon":case"Stash":case"Egern":case"Shadowrocket":r=$persistentStore.write(a,s);break;case"Quantumult X":r=$prefs.setValueForKey(a,s);break;case"Node.js":this.data=this.#t(this.dataFile),this.data[s]=a,this.#s(this.dataFile),r=!0;break;default:r=this.data?.[s]||null}return r}static removeItem(s){let a=!1;if(!0===s.startsWith("@")){const{key:e,path:r}=s.match(this.#e)?.groups;s=e;let i=this.getItem(s);"object"!=typeof i&&(i={}),keyValue=t.unset(i,r),a=this.setItem(s,i)}else switch(e){case"Surge":case"Loon":case"Stash":case"Egern":case"Shadowrocket":case"Node.js":default:a=!1;break;case"Quantumult X":a=$prefs.removeValueForKey(s)}return a}static clear(){let t=!1;switch(e){case"Surge":case"Loon":case"Stash":case"Egern":case"Shadowrocket":case"Node.js":default:t=!1;break;case"Quantumult X":t=$prefs.removeAllValues()}return t}static#t(e){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(e),s=this.path.resolve(process.cwd(),e),a=this.fs.existsSync(t),r=!a&&this.fs.existsSync(s);if(!a&&!r)return{};{const e=a?t:s;try{return JSON.parse(this.fs.readFileSync(e))}catch(e){return{}}}}}static#s(e=this.dataFile){if(this.isNode()){this.fs=this.fs?this.fs:require("fs"),this.path=this.path?this.path:require("path");const t=this.path.resolve(e),s=this.path.resolve(process.cwd(),e),a=this.fs.existsSync(t),r=!a&&this.fs.existsSync(s),i=JSON.stringify(this.data);a?this.fs.writeFileSync(t,i):r?this.fs.writeFileSync(s,i):this.fs.writeFileSync(t,i)}}}function a(s={}){switch(e){case"Surge":s.policy&&t.set(s,"headers.X-Surge-Policy",s.policy),r("",`🚩 执行结束! 🕛 ${(new Date).getTime()/1e3-$script.startTime} 秒`,""),$done(s);break;case"Loon":s.policy&&(s.node=s.policy),r("",`🚩 执行结束! 🕛 ${(new Date-$script.startTime)/1e3} 秒`,""),$done(s);break;case"Stash":s.policy&&t.set(s,"headers.X-Stash-Selected-Proxy",encodeURI(s.policy)),r("",`🚩 执行结束! 🕛 ${(new Date-$script.startTime)/1e3} 秒`,""),$done(s);break;case"Egern":case"Shadowrocket":default:r("","🚩 执行结束!",""),$done(s);break;case"Quantumult X":s.policy&&t.set(s,"opts.policy",s.policy),delete s["auto-redirect"],delete s["auto-cookie"],delete s["binary-mode"],delete s.charset,delete s.host,delete s.insecure,delete s.method,delete s.opt,delete s.path,delete s.policy,delete s["policy-descriptor"],delete s.scheme,delete s.sessionIndex,delete s.statusCode,delete s.timeout,s.body instanceof ArrayBuffer?(s.bodyBytes=s.body,delete s.body):ArrayBuffer.isView(s.body)?(s.bodyBytes=s.body.buffer.slice(s.body.byteOffset,s.body.byteLength+s.body.byteOffset),delete s.body):s.body&&delete s.bodyBytes,r("","🚩 执行结束!",""),$done(s);break;case"Node.js":r("","🚩 执行结束!",""),process.exit(1)}}const r=(...e)=>console.log(e.join("\n"));var i={Switch:!0},o={Storefront:[["AE","143481"],["AF","143610"],["AG","143540"],["AI","143538"],["AL","143575"],["AM","143524"],["AO","143564"],["AR","143505"],["AT","143445"],["AU","143460"],["AZ","143568"],["BA","143612"],["BB","143541"],["BD","143490"],["BE","143446"],["BF","143578"],["BG","143526"],["BH","143559"],["BJ","143576"],["BM","143542"],["BN","143560"],["BO","143556"],["BR","143503"],["BS","143539"],["BT","143577"],["BW","143525"],["BY","143565"],["BZ","143555"],["CA","143455"],["CD","143613"],["CG","143582"],["CH","143459"],["CI","143527"],["CL","143483"],["CM","143574"],["CN","143465"],["CO","143501"],["CR","143495"],["CV","143580"],["CY","143557"],["CZ","143489"],["DE","143443"],["DK","143458"],["DM","143545"],["DO","143508"],["DZ","143563"],["EC","143509"],["EE","143518"],["EG","143516"],["ES","143454"],["FI","143447"],["FJ","143583"],["FM","143591"],["FR","143442"],["GA","143614"],["GB","143444"],["GD","143546"],["GF","143615"],["GH","143573"],["GM","143584"],["GR","143448"],["GT","143504"],["GW","143585"],["GY","143553"],["HK","143463"],["HN","143510"],["HR","143494"],["HU","143482"],["ID","143476"],["IE","143449"],["IL","143491"],["IN","143467"],["IQ","143617"],["IS","143558"],["IT","143450"],["JM","143511"],["JO","143528"],["JP","143462"],["KE","143529"],["KG","143586"],["KH","143579"],["KN","143548"],["KP","143466"],["KR","143466"],["KW","143493"],["KY","143544"],["KZ","143517"],["TC","143552"],["TD","143581"],["TJ","143603"],["TH","143475"],["TM","143604"],["TN","143536"],["TO","143608"],["TR","143480"],["TT","143551"],["TW","143470"],["TZ","143572"],["LA","143587"],["LB","143497"],["LC","143549"],["LI","143522"],["LK","143486"],["LR","143588"],["LT","143520"],["LU","143451"],["LV","143519"],["LY","143567"],["MA","143620"],["MD","143523"],["ME","143619"],["MG","143531"],["MK","143530"],["ML","143532"],["MM","143570"],["MN","143592"],["MO","143515"],["MR","143590"],["MS","143547"],["MT","143521"],["MU","143533"],["MV","143488"],["MW","143589"],["MX","143468"],["MY","143473"],["MZ","143593"],["NA","143594"],["NE","143534"],["NG","143561"],["NI","143512"],["NL","143452"],["NO","143457"],["NP","143484"],["NR","143606"],["NZ","143461"],["OM","143562"],["PA","143485"],["PE","143507"],["PG","143597"],["PH","143474"],["PK","143477"],["PL","143478"],["PT","143453"],["PW","143595"],["PY","143513"],["QA","143498"],["RO","143487"],["RS","143500"],["RU","143469"],["RW","143621"],["SA","143479"],["SB","143601"],["SC","143599"],["SE","143456"],["SG","143464"],["SI","143499"],["SK","143496"],["SL","143600"],["SN","143535"],["SR","143554"],["ST","143598"],["SV","143506"],["SZ","143602"],["UA","143492"],["UG","143537"],["US","143441"],["UY","143514"],["UZ","143566"],["VC","143550"],["VE","143502"],["VG","143543"],["VN","143471"],["VU","143609"],["XK","143624"],["YE","143571"],["ZA","143472"],["ZM","143622"],["ZW","143605"]]},n={Settings:i,Configs:o},c={Switch:!0,PEP:{GCC:"US"}},h={Settings:c},l={Switch:!0,UrlInfoSet:{Dispatcher:"AutoNavi",Directions:"AutoNavi",RAP:"Apple",LocationShift:"AUTO"},TileSet:{Map:"CN",Satellite:"HYBRID",Traffic:"CN",POI:"CN",Flyover:"XX",Munin:"XX"},GeoManifest:{Dynamic:{Config:{CountryCode:{default:"CN",iOS:"AUTO",iPadOS:"AUTO",watchOS:"US",macOS:"AUTO"}}}},Config:{Announcements:{"Environment:":{default:"AUTO",iOS:"AUTO",iPadOS:"AUTO",watchOS:"AUTO",macOS:"AUTO"}}}},p={},u={Settings:l,Configs:p},d={Switch:!0,CountryCode:"US",NewsPlusUser:!0},S={Settings:d},m={Switch:!0,CountryCode:"US",canUse:!0},f={Settings:m},g={Switch:!0,CountryCode:"SG",Region:"AUTO",Domains:["web","itunes","app_store","movies","restaurants","maps"],Functions:["flightutilities","lookup","mail","messages","news","safari","siri","spotlight","visualintelligence"],Safari_Smart_History:!0},y={VisualIntelligence:{enabled_domains:["pets","media","books","art","nature","landmarks"],supported_domains:["ART","BOOK","MEDIA","LANDMARK","ANIMALS","BIRDS","FOOD","SIGN_SYMBOL","AUTO_SYMBOL","DOGS","NATURE","NATURAL_LANDMARK","INSECTS","REPTILES","ALBUM","STOREFRONT","LAUNDRY_CARE_SYMBOL","CATS","OBJECT_2D","SCULPTURE","SKYLINE","MAMMALS"]}},A={Settings:g,Configs:y},T={Switch:"true",CountryCode:"US",MultiAccount:"false",Universal:"true"},v={Settings:T},C={Switch:!0,"Third-Party":!1,HLSUrl:"play-edge.itunes.apple.com",ServerUrl:"play.itunes.apple.com",Tabs:["WatchNow","Originals","MLS","Sports","Kids","Store","Movies","TV","ChannelsAndApps","Library","Search"],CountryCode:{Configs:"AUTO",Settings:"AUTO",View:["SG","TW"],WatchNow:"AUTO",Channels:"AUTO",Originals:"AUTO",Sports:"US",Kids:"US",Store:"AUTO",Movies:"AUTO",TV:"AUTO",Persons:"SG",Search:"AUTO",Others:"AUTO"}},b={Locale:[["AU","en-AU"],["CA","en-CA"],["GB","en-GB"],["KR","ko-KR"],["HK","yue-Hant"],["JP","ja-JP"],["MO","zh-Hant"],["TW","zh-Hant"],["US","en-US"],["SG","zh-Hans"]],Tabs:[{title:"主页",type:"WatchNow",universalLinks:["https://tv.apple.com/watch-now","https://tv.apple.com/home"],destinationType:"Target",target:{id:"tahoma_watchnow",type:"Root",url:"https://tv.apple.com/watch-now"},isSelected:!0},{title:"Apple TV+",type:"Originals",universalLinks:["https://tv.apple.com/channel/tvs.sbd.4000","https://tv.apple.com/atv"],destinationType:"Target",target:{id:"tvs.sbd.4000",type:"Brand",url:"https://tv.apple.com/us/channel/tvs.sbd.4000"}},{title:"MLS Season Pass",type:"MLS",universalLinks:["https://tv.apple.com/mls"],destinationType:"Target",target:{id:"tvs.sbd.7000",type:"Brand",url:"https://tv.apple.com/us/channel/tvs.sbd.7000"}},{title:"体育节目",type:"Sports",universalLinks:["https://tv.apple.com/sports"],destinationType:"Target",target:{id:"tahoma_sports",type:"Root",url:"https://tv.apple.com/sports"}},{title:"儿童",type:"Kids",universalLinks:["https://tv.apple.com/kids"],destinationType:"Target",target:{id:"tahoma_kids",type:"Root",url:"https://tv.apple.com/kids"}},{title:"电影",type:"Movies",universalLinks:["https://tv.apple.com/movies"],destinationType:"Target",target:{id:"tahoma_movies",type:"Root",url:"https://tv.apple.com/movies"}},{title:"电视节目",type:"TV",universalLinks:["https://tv.apple.com/tv-shows"],destinationType:"Target",target:{id:"tahoma_tvshows",type:"Root",url:"https://tv.apple.com/tv-shows"}},{title:"商店",type:"Store",universalLinks:["https://tv.apple.com/store"],destinationType:"SubTabs",subTabs:[{title:"电影",type:"Movies",universalLinks:["https://tv.apple.com/movies"],destinationType:"Target",target:{id:"tahoma_movies",type:"Root",url:"https://tv.apple.com/movies"}},{title:"电视节目",type:"TV",universalLinks:["https://tv.apple.com/tv-shows"],destinationType:"Target",target:{id:"tahoma_tvshows",type:"Root",url:"https://tv.apple.com/tv-shows"}}]},{title:"频道和 App",destinationType:"SubTabs",subTabsPlacementType:"ExpandedList",type:"ChannelsAndApps",subTabs:[]},{title:"资料库",type:"Library",destinationType:"Client"},{title:"搜索",type:"Search",universalLinks:["https://tv.apple.com/search"],destinationType:"Target",target:{id:"tahoma_search",type:"Root",url:"https://tv.apple.com/search"}}],i18n:{WatchNow:[["en","Home"],["zh","主页"],["zh-Hans","主頁"],["zh-Hant","主頁"]],Movies:[["en","Movies"],["zh","电影"],["zh-Hans","电影"],["zh-Hant","電影"]],TV:[["en","TV"],["zh","电视节目"],["zh-Hans","电视节目"],["zh-Hant","電視節目"]],Store:[["en","Store"],["zh","商店"],["zh-Hans","商店"],["zh-Hant","商店"]],Sports:[["en","Sports"],["zh","体育节目"],["zh-Hans","体育节目"],["zh-Hant","體育節目"]],Kids:[["en","Kids"],["zh","儿童"],["zh-Hans","儿童"],["zh-Hant","兒童"]],Library:[["en","Library"],["zh","资料库"],["zh-Hans","资料库"],["zh-Hant","資料庫"]],Search:[["en","Search"],["zh","搜索"],["zh-Hans","搜索"],["zh-Hant","蒐索"]]}},O={Settings:C,Configs:b},w=Database={Default:Object.freeze({__proto__:null,Configs:o,Settings:i,default:n}),Location:Object.freeze({__proto__:null,Settings:c,default:h}),Maps:Object.freeze({__proto__:null,Configs:p,Settings:l,default:u}),News:Object.freeze({__proto__:null,Settings:d,default:S}),PrivateRelay:Object.freeze({__proto__:null,Settings:m,default:f}),Siri:Object.freeze({__proto__:null,Configs:y,Settings:g,default:A}),TestFlight:Object.freeze({__proto__:null,Settings:T,default:v}),TV:Object.freeze({__proto__:null,Configs:b,Settings:C,default:O})};function L(e,a,i){r("☑️ Set Environment Variables","");let{Settings:o,Caches:n,Configs:c}=function(e,a,r){let i=s.getItem(e,r),o={};switch(typeof $argument){case"string":let e=Object.fromEntries($argument.split("&").map((e=>e.split("=").map((e=>e.replace(/\"/g,""))))));for(let s in e)t.set(o,s,e[s]);break;case"object":for(let e in $argument)t.set(o,e,$argument[e])}const n={Settings:r?.Default?.Settings||{},Configs:r?.Default?.Configs||{},Caches:{}};Array.isArray(a)||(a=[a]);for(let e of a)n.Settings={...n.Settings,...r?.[e]?.Settings,...o,...i?.[e]?.Settings},n.Configs={...n.Configs,...r?.[e]?.Configs},i?.[e]?.Caches&&"string"==typeof i?.[e]?.Caches&&(i[e].Caches=JSON.parse(i?.[e]?.Caches)),n.Caches={...n.Caches,...i?.[e]?.Caches};return function e(t,s){for(var a in t){var r=t[a];t[a]="object"==typeof r&&null!==r?e(r,s):s(a,r)}return t}(n.Settings,((e,t)=>("true"===t||"false"===t?t=JSON.parse(t):"string"==typeof t&&(t=t.includes(",")?t.split(",").map((e=>c(e))):c(t)),t))),n;function c(e){return e&&!isNaN(e)&&(e=parseInt(e,10)),e}}(e,a,i);switch(a){case"WeatherKit":Array.isArray(o?.AQI?.ReplaceProviders)||t.set(o,"AQI.ReplaceProviders",o?.AQI?.ReplaceProviders?[o.AQI.ReplaceProviders.toString()]:[]),o.AQI.ReplaceProviders.includes("TWC")&&o.AQI.ReplaceProviders.push("The Weather Channel"),o.AQI.ReplaceProviders.includes("QWeather")&&o.AQI.ReplaceProviders.push("和风天气"),o.AQI.ReplaceProviders.push(void 0),Array.isArray(o?.AQI?.Local?.ReplaceScales)||t.set(o,"AQI.Local.ReplaceScales",o?.AQI?.Local?.ReplaceScales?[o.AQI.Local.ReplaceScales.toString()]:[]);break;case"Siri":Array.isArray(o?.Domains)||t.set(o,"Domains",o?.Domains?[o.Domains.toString()]:[]),Array.isArray(o?.Functions)||t.set(o,"Functions",o?.Functions?[o.Functions.toString()]:[]);break;case"TV":Array.isArray(o?.Tabs)||t.set(o,"Tabs",o?.Tabs?[o.Tabs.toString()]:[])}if(r(`✅ Set Environment Variables, Settings: ${typeof o}, Settings内容: ${JSON.stringify(o)}`,""),c.Storefront=new Map(c.Storefront),c.Locale&&(c.Locale=new Map(c.Locale)),c.i18n)for(let e in c.i18n)c.i18n[e]=new Map(c.i18n[e]);return{Settings:o,Caches:n,Configs:c}}let P;r("v4.0.0(4010)");const $=new class{constructor(e,t=void 0){return console.log("\n🟧 URL v2.1.2\n"),e=this.#a(e,t),this}#a(e,t=void 0){const s=/(?:(?\w+:)\/\/(?:(?[^\s:"]+)(?::(?[^\s:"]+))?@)?(?[^\s@/]+))?(?\/?[^\s@?]+)?(?\?[^\s?]+)?/,a=/(?.+):(?\d+)$/;if(e=e.match(s)?.groups||{},t&&(!(t=t?.match(s)?.groups||{}).protocol||!t.hostname))throw new Error(`🚨 ${name}, ${t} is not a valid URL`);if((e.protocol||t?.protocol)&&(this.protocol=e.protocol||t.protocol),(e.username||t?.username)&&(this.username=e.username||t.username),(e.password||t?.password)&&(this.password=e.password||t.password),(e.host||t?.host)&&(this.host=e.host||t.host,Object.freeze(this.host),this.hostname=this.host.match(a)?.groups.hostname??this.host,this.port=this.host.match(a)?.groups.port??""),e.pathname||t?.pathname){if(this.pathname=e.pathname||t?.pathname,this.pathname.startsWith("/")||(this.pathname="/"+this.pathname),this.paths=this.pathname.split("/").filter(Boolean),Object.freeze(this.paths),this.paths){const e=this.paths[this.paths.length-1];if(e?.includes(".")){const t=e.split(".");this.format=t[t.length-1],Object.freeze(this.format)}}}else this.pathname="";return(e.search||t?.search)&&(this.search=e.search||t.search,Object.freeze(this.search),this.search&&(this.searchParams=this.search.slice(1).split("&").map((e=>e.split("="))))),this.searchParams=new Map(this.searchParams||[]),this.harf=this.toString(),Object.freeze(this.harf),this}toString(){let e="";return this.protocol&&(e+=this.protocol+"//"),this.username&&(e+=this.username+(this.password?":"+this.password:"")+"@"),this.hostname&&(e+=this.hostname),this.port&&(e+=":"+this.port),this.pathname&&(e+=this.pathname),0!==this.searchParams.size&&(e+="?"+Array.from(this.searchParams).map((e=>e.join("="))).join("&")),e}toJSON(){return JSON.stringify({...this})}}($request.url);r(`⚠ url: ${$.toJSON()}`,"");const R=$request.method,N=$.hostname,U=$.pathname;$.pathname.split("/").filter(Boolean),r(`⚠ METHOD: ${R}, HOST: ${N}, PATH: ${U}`,"");const E=($request.headers?.["Content-Type"]??$request.headers?.["content-type"])?.split(";")?.[0];r(`⚠ FORMAT: ${E}`,""),(async()=>{const{Settings:e,Caches:t,Configs:s}=L("iRingo","Siri",w);switch(r(`⚠ Settings.Switch: ${e?.Switch}`,""),e.Switch){case!0:default:let t,s,a;switch(R){case"POST":case"PUT":case"PATCH":case"DELETE":case"GET":case"HEAD":case"OPTIONS":default:if(t=t??$.searchParams.get("locale"),[s,a]=t?.split("_")??[],r(`🚧 Locale: ${t}, Language: ${s}, CountryCode: ${a}`,""),"AUTO"===e.CountryCode)e.CountryCode=a;else $.searchParams.has("cc")&&$.searchParams.set("cc",e.CountryCode);switch(N){case"api.smoot.apple.cn":case"api.smoot.apple.com":case"api2.smoot.apple.com":case"api-siri.smoot.apple.com":default:let t=$.searchParams.get("q");switch(U){case"/bag":break;case"/search":if("zkw"===$.searchParams.get("qtype"))switch(e.CountryCode){case"CN":case"HK":case"MO":case"TW":case"SG":$.searchParams.set("locale",`${s}_SG`);break;case"US":case"CA":case"UK":case"AU":break;default:$.searchParams.set("locale",`${s}_US`)}else t?.startsWith?.("%E5%A4%A9%E6%B0%94%20")?(console.log("'天气 '开头"),$.searchParams.set("q",t.replace(/%E5%A4%A9%E6%B0%94/,"weather")),/^weather%20.*%E5%B8%82$/.test(t)&&$.searchParams.set("q",t.replace(/$/,"%E5%B8%82"))):t?.endsWith?.("%20%E5%A4%A9%E6%B0%94")&&(console.log("' 天气'结尾"),$.searchParams.set("q",t.replace(/%E5%A4%A9%E6%B0%94/,"weather")),/.*%E5%B8%82%20weather$/.test(t)&&$.searchParams.set("q",t.replace(/%20weather$/,"%E5%B8%82%20weather")));break;case"/card":switch($.searchParams.get("include")){case"tv":case"movies":$.searchParams.set("card_locale",`${s}_${e.CountryCode}`);const a=$.searchParams.get("storefront")?.match(/[\d]{6}/g);switch(a){case"143463":case"143465":$.searchParams.set("q",t.replace(/%2F[a-z]{2}-[A-Z]{2}/,"%2Fzh-HK"));break;case"143464":$.searchParams.set("q",t.replace(/%2F[a-z]{2}-[A-Z]{2}/,"%2Fzh-SG"));break;case"143470":$.searchParams.set("q",t.replace(/%2F[a-z]{2}-[A-Z]{2}/,"%2Fzh-TW"))}break;case"apps":case"music":default:$.searchParams.set("card_locale",`${s}_${e.CountryCode}`);break;case"dictionary":switch(s){case"zh-Hans":case"zh-Hant":$.searchParams.set("card_locale",`en_${e.CountryCode}`)}}}case"guzzoni.smoot.apple.com":case"fbs.smoot.apple.com":case"cdn.smoot.apple.com":}case"CONNECT":case"TRACE":}$request.url=$.toString(),r("🚧 调试信息",`$request.url: ${$request.url}`,"");case!1:}})().catch((t=>function(t){switch(e){case"Surge":case"Loon":case"Stash":case"Egern":case"Shadowrocket":case"Quantumult X":default:r("","❗️执行错误!",t,"");break;case"Node.js":r("","❗️执行错误!",t.stack,"")}}(t))).finally((()=>{if(void 0===P)a($request);else if(P.headers,P.headers,"Quantumult X"===e)P.status||(P.status="HTTP/1.1 200 OK"),delete P.headers?.["Content-Length"],delete P.headers?.["content-length"],delete P.headers?.["Transfer-Encoding"],a(P);else a({response:P})})); diff --git a/js/Siri.response.beta.js b/js/Siri.response.beta.js deleted file mode 100644 index 89c894fde..000000000 --- a/js/Siri.response.beta.js +++ /dev/null @@ -1,8968 +0,0 @@ -/* README: https://github.com/VirgilClyne/iRingo */ -console.log(' iRingo: ⭕ Siri β Response') -const $platform = platform(); -function platform() { - if ("undefined" !== typeof $environment && $environment["surge-version"]) - return "Surge" - if ("undefined" !== typeof $environment && $environment["stash-version"]) - return "Stash" - if ("undefined" !== typeof module && !!module.exports) return "Node.js" - if ("undefined" !== typeof $task) return "Quantumult X" - if ("undefined" !== typeof $loon) return "Loon" - if ("undefined" !== typeof $rocket) return "Shadowrocket" - if ("undefined" !== typeof Egern) return "Egern" -} - -class URL { - constructor(url, base = undefined) { - const name = "URL"; - const version = "2.1.2"; - console.log(`\n🟧 ${name} v${version}\n`); - url = this.#parse(url, base); - return this; - }; - - #parse(url, base = undefined) { - const URLRegex = /(?:(?\w+:)\/\/(?:(?[^\s:"]+)(?::(?[^\s:"]+))?@)?(?[^\s@/]+))?(?\/?[^\s@?]+)?(?\?[^\s?]+)?/; - const PortRegex = /(?.+):(?\d+)$/; - url = url.match(URLRegex)?.groups || {}; - if (base) { - base = base?.match(URLRegex)?.groups || {}; - if (!base.protocol || !base.hostname) throw new Error(`🚨 ${name}, ${base} is not a valid URL`); - } if (url.protocol || base?.protocol) this.protocol = url.protocol || base.protocol; - if (url.username || base?.username) this.username = url.username || base.username; - if (url.password || base?.password) this.password = url.password || base.password; - if (url.host || base?.host) { - this.host = url.host || base.host; - Object.freeze(this.host); - this.hostname = this.host.match(PortRegex)?.groups.hostname ?? this.host; - this.port = this.host.match(PortRegex)?.groups.port ?? ""; - } if (url.pathname || base?.pathname) { - this.pathname = url.pathname || base?.pathname; - if (!this.pathname.startsWith("/")) this.pathname = "/" + this.pathname; - this.paths = this.pathname.split("/").filter(Boolean); - Object.freeze(this.paths); - if (this.paths) { - const fileName = this.paths[this.paths.length - 1]; - if (fileName?.includes(".")) { - const list = fileName.split("."); - this.format = list[list.length - 1]; - Object.freeze(this.format); - } - } } else this.pathname = ""; - if (url.search || base?.search) { - this.search = url.search || base.search; - Object.freeze(this.search); - if (this.search) this.searchParams = this.search.slice(1).split("&").map((param) => param.split("=")); - } this.searchParams = new Map(this.searchParams || []); - this.harf = this.toString(); - Object.freeze(this.harf); - return this; - }; - - toString() { - let string = ""; - if (this.protocol) string += this.protocol + "//"; - if (this.username) string += this.username + (this.password ? ":" + this.password : "") + "@"; - if (this.hostname) string += this.hostname; - if (this.port) string += ":" + this.port; - if (this.pathname) string += this.pathname; - if (this.searchParams.size !== 0) string += "?" + Array.from(this.searchParams).map(param => param.join("=")).join("&"); - return string; - }; - - toJSON() { return JSON.stringify({ ...this }) }; -} - -/* https://www.lodashjs.com */ -class Lodash { - static name = "Lodash"; - static version = "1.2.2"; - static about() { return console.log(`\n🟧 ${this.name} v${this.version}\n`) }; - - static get(object = {}, path = "", defaultValue = undefined) { - // translate array case to dot case, then split with . - // a[0].b -> a.0.b -> ['a', '0', 'b'] - if (!Array.isArray(path)) path = this.toPath(path); - - const result = path.reduce((previousValue, currentValue) => { - return Object(previousValue)[currentValue]; // null undefined get attribute will throwError, Object() can return a object - }, object); - return (result === undefined) ? defaultValue : result; - } - - static set(object = {}, path = "", value) { - if (!Array.isArray(path)) path = this.toPath(path); - path - .slice(0, -1) - .reduce( - (previousValue, currentValue, currentIndex) => - (Object(previousValue[currentValue]) === previousValue[currentValue]) - ? previousValue[currentValue] - : previousValue[currentValue] = (/^\d+$/.test(path[currentIndex + 1]) ? [] : {}), - object - )[path[path.length - 1]] = value; - return object - } - - static unset(object = {}, path = "") { - if (!Array.isArray(path)) path = this.toPath(path); - let result = path.reduce((previousValue, currentValue, currentIndex) => { - if (currentIndex === path.length - 1) { - delete previousValue[currentValue]; - return true - } - return Object(previousValue)[currentValue] - }, object); - return result - } - - static toPath(value) { - return value.replace(/\[(\d+)\]/g, '.$1').split('.').filter(Boolean); - } - - static escape(string) { - const map = { - '&': '&', - '<': '<', - '>': '>', - '"': '"', - "'": ''', - }; - return string.replace(/[&<>"']/g, m => map[m]) - }; - - static unescape(string) { - const map = { - '&': '&', - '<': '<', - '>': '>', - '"': '"', - ''': "'", - }; - return string.replace(/&|<|>|"|'/g, m => map[m]) - } - -} - -/* https://developer.mozilla.org/zh-CN/docs/Web/API/Storage/setItem */ -class Storage { - static name = "Storage"; - static version = "1.1.0"; - static about () { return log("", `🟧 ${this.name} v${this.version}`, "") }; - static data = null; - static dataFile = 'box.dat'; - static #nameRegex = /^@(?[^.]+)(?:\.(?.*))?$/; - - static getItem(keyName = new String, defaultValue = null) { - let keyValue = defaultValue; - // 如果以 @ - switch (keyName.startsWith('@')) { - case true: - const { key, path } = keyName.match(this.#nameRegex)?.groups; - //log(`1: ${key}, ${path}`); - keyName = key; - let value = this.getItem(keyName, {}); - //log(`2: ${JSON.stringify(value)}`) - if (typeof value !== "object") value = {}; - //log(`3: ${JSON.stringify(value)}`) - keyValue = Lodash.get(value, path); - //log(`4: ${JSON.stringify(keyValue)}`) - try { - keyValue = JSON.parse(keyValue); - } catch (e) { - // do nothing - } //log(`5: ${JSON.stringify(keyValue)}`) - break; - default: - switch ($platform) { - case 'Surge': - case 'Loon': - case 'Stash': - case 'Egern': - case 'Shadowrocket': - keyValue = $persistentStore.read(keyName); - break; - case 'Quantumult X': - keyValue = $prefs.valueForKey(keyName); - break; - case 'Node.js': - this.data = this.#loaddata(this.dataFile); - keyValue = this.data?.[keyName]; - break; - default: - keyValue = this.data?.[keyName] || null; - break; - } try { - keyValue = JSON.parse(keyValue); - } catch (e) { - // do nothing - } break; - } return keyValue ?? defaultValue; - }; - - static setItem(keyName = new String, keyValue = new String) { - let result = false; - //log(`0: ${typeof keyValue}`); - switch (typeof keyValue) { - case "object": - keyValue = JSON.stringify(keyValue); - break; - default: - keyValue = String(keyValue); - break; - } switch (keyName.startsWith('@')) { - case true: - const { key, path } = keyName.match(this.#nameRegex)?.groups; - //log(`1: ${key}, ${path}`); - keyName = key; - let value = this.getItem(keyName, {}); - //log(`2: ${JSON.stringify(value)}`) - if (typeof value !== "object") value = {}; - //log(`3: ${JSON.stringify(value)}`) - Lodash.set(value, path, keyValue); - //log(`4: ${JSON.stringify(value)}`) - result = this.setItem(keyName, value); - //log(`5: ${result}`) - break; - default: - switch ($platform) { - case 'Surge': - case 'Loon': - case 'Stash': - case 'Egern': - case 'Shadowrocket': - result = $persistentStore.write(keyValue, keyName); - break; - case 'Quantumult X': - result =$prefs.setValueForKey(keyValue, keyName); - break; - case 'Node.js': - this.data = this.#loaddata(this.dataFile); - this.data[keyName] = keyValue; - this.#writedata(this.dataFile); - result = true; - break; - default: - result = this.data?.[keyName] || null; - break; - } break; - } return result; - }; - - static removeItem(keyName){ - let result = false; - switch (keyName.startsWith('@')) { - case true: - const { key, path } = keyName.match(this.#nameRegex)?.groups; - keyName = key; - let value = this.getItem(keyName); - if (typeof value !== "object") value = {}; - keyValue = Lodash.unset(value, path); - result = this.setItem(keyName, value); - break; - default: - switch ($platform) { - case 'Surge': - case 'Loon': - case 'Stash': - case 'Egern': - case 'Shadowrocket': - result = false; - break; - case 'Quantumult X': - result = $prefs.removeValueForKey(keyName); - break; - case 'Node.js': - result = false; - break; - default: - result = false; - break; - } break; - } return result; - } - - static clear() { - let result = false; - switch ($platform) { - case 'Surge': - case 'Loon': - case 'Stash': - case 'Egern': - case 'Shadowrocket': - result = false; - break; - case 'Quantumult X': - result = $prefs.removeAllValues(); - break; - case 'Node.js': - result = false; - break; - default: - result = false; - break; - } return result; - } - - static #loaddata(dataFile) { - if (this.isNode()) { - this.fs = this.fs ? this.fs : require('fs'); - this.path = this.path ? this.path : require('path'); - const curDirDataFilePath = this.path.resolve(dataFile); - const rootDirDataFilePath = this.path.resolve( - process.cwd(), - dataFile - ); - const isCurDirDataFile = this.fs.existsSync(curDirDataFilePath); - const isRootDirDataFile = - !isCurDirDataFile && this.fs.existsSync(rootDirDataFilePath); - if (isCurDirDataFile || isRootDirDataFile) { - const datPath = isCurDirDataFile - ? curDirDataFilePath - : rootDirDataFilePath; - try { - return JSON.parse(this.fs.readFileSync(datPath)) - } catch (e) { - return {} - } - } else return {} - } else return {} - } - - static #writedata(dataFile = this.dataFile) { - if (this.isNode()) { - this.fs = this.fs ? this.fs : require('fs'); - this.path = this.path ? this.path : require('path'); - const curDirDataFilePath = this.path.resolve(dataFile); - const rootDirDataFilePath = this.path.resolve( - process.cwd(), - dataFile - ); - const isCurDirDataFile = this.fs.existsSync(curDirDataFilePath); - const isRootDirDataFile = - !isCurDirDataFile && this.fs.existsSync(rootDirDataFilePath); - const jsondata = JSON.stringify(this.data); - if (isCurDirDataFile) { - this.fs.writeFileSync(curDirDataFilePath, jsondata); - } else if (isRootDirDataFile) { - this.fs.writeFileSync(rootDirDataFilePath, jsondata); - } else { - this.fs.writeFileSync(curDirDataFilePath, jsondata); - } - } - }; - -} - -function logError(error) { - switch ($platform) { - case "Surge": - case "Loon": - case "Stash": - case "Egern": - case "Shadowrocket": - case "Quantumult X": - default: - log("", `❗️执行错误!`, error, ""); - break - case "Node.js": - log("", `❗️执行错误!`, error.stack, ""); - break - }} - -function done(object = {}) { - switch ($platform) { - case "Surge": - if (object.policy) Lodash.set(object, "headers.X-Surge-Policy", object.policy); - log("", `🚩 执行结束! 🕛 ${(new Date().getTime() / 1000 - $script.startTime)} 秒`, ""); - $done(object); - break; - case "Loon": - if (object.policy) object.node = object.policy; - log("", `🚩 执行结束! 🕛 ${(new Date() - $script.startTime) / 1000} 秒`, ""); - $done(object); - break; - case "Stash": - if (object.policy) Lodash.set(object, "headers.X-Stash-Selected-Proxy", encodeURI(object.policy)); - log("", `🚩 执行结束! 🕛 ${(new Date() - $script.startTime) / 1000} 秒`, ""); - $done(object); - break; - case "Egern": - log("", `🚩 执行结束!`, ""); - $done(object); - break; - case "Shadowrocket": - default: - log("", `🚩 执行结束!`, ""); - $done(object); - break; - case "Quantumult X": - if (object.policy) Lodash.set(object, "opts.policy", object.policy); - // 移除不可写字段 - delete object["auto-redirect"]; - delete object["auto-cookie"]; - delete object["binary-mode"]; - delete object.charset; - delete object.host; - delete object.insecure; - delete object.method; // 1.4.x 不可写 - delete object.opt; // $task.fetch() 参数, 不可写 - delete object.path; // 可写, 但会与 url 冲突 - delete object.policy; - delete object["policy-descriptor"]; - delete object.scheme; - delete object.sessionIndex; - delete object.statusCode; - delete object.timeout; - if (object.body instanceof ArrayBuffer) { - object.bodyBytes = object.body; - delete object.body; - } else if (ArrayBuffer.isView(object.body)) { - object.bodyBytes = object.body.buffer.slice(object.body.byteOffset, object.body.byteLength + object.body.byteOffset); - delete object.body; - } else if (object.body) delete object.bodyBytes; - log("", `🚩 执行结束!`, ""); - $done(object); - break; - case "Node.js": - log("", `🚩 执行结束!`, ""); - process.exit(1); - break; - } -} - -const log = (...logs) => console.log(logs.join("\n")); - -/*! pako 2.1.0 https://github.com/nodeca/pako @license (MIT AND Zlib) */ -// (C) 1995-2013 Jean-loup Gailly and Mark Adler -// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin -// -// This software is provided 'as-is', without any express or implied -// warranty. In no event will the authors be held liable for any damages -// arising from the use of this software. -// -// Permission is granted to anyone to use this software for any purpose, -// including commercial applications, and to alter it and redistribute it -// freely, subject to the following restrictions: -// -// 1. The origin of this software must not be misrepresented; you must not -// claim that you wrote the original software. If you use this software -// in a product, an acknowledgment in the product documentation would be -// appreciated but is not required. -// 2. Altered source versions must be plainly marked as such, and must not be -// misrepresented as being the original software. -// 3. This notice may not be removed or altered from any source distribution. - -/* eslint-disable space-unary-ops */ - -/* Public constants ==========================================================*/ -/* ===========================================================================*/ - - -//const Z_FILTERED = 1; -//const Z_HUFFMAN_ONLY = 2; -//const Z_RLE = 3; -const Z_FIXED$1 = 4; -//const Z_DEFAULT_STRATEGY = 0; - -/* Possible values of the data_type field (though see inflate()) */ -const Z_BINARY = 0; -const Z_TEXT = 1; -//const Z_ASCII = 1; // = Z_TEXT -const Z_UNKNOWN$1 = 2; - -/*============================================================================*/ - - -function zero$1(buf) { let len = buf.length; while (--len >= 0) { buf[len] = 0; } } - -// From zutil.h - -const STORED_BLOCK = 0; -const STATIC_TREES = 1; -const DYN_TREES = 2; -/* The three kinds of block type */ - -const MIN_MATCH$1 = 3; -const MAX_MATCH$1 = 258; -/* The minimum and maximum match lengths */ - -// From deflate.h -/* =========================================================================== - * Internal compression state. - */ - -const LENGTH_CODES$1 = 29; -/* number of length codes, not counting the special END_BLOCK code */ - -const LITERALS$1 = 256; -/* number of literal bytes 0..255 */ - -const L_CODES$1 = LITERALS$1 + 1 + LENGTH_CODES$1; -/* number of Literal or Length codes, including the END_BLOCK code */ - -const D_CODES$1 = 30; -/* number of distance codes */ - -const BL_CODES$1 = 19; -/* number of codes used to transfer the bit lengths */ - -const HEAP_SIZE$1 = 2 * L_CODES$1 + 1; -/* maximum heap size */ - -const MAX_BITS$1 = 15; -/* All codes must not exceed MAX_BITS bits */ - -const Buf_size = 16; -/* size of bit buffer in bi_buf */ - - -/* =========================================================================== - * Constants - */ - -const MAX_BL_BITS = 7; -/* Bit length codes must not exceed MAX_BL_BITS bits */ - -const END_BLOCK = 256; -/* end of block literal code */ - -const REP_3_6 = 16; -/* repeat previous bit length 3-6 times (2 bits of repeat count) */ - -const REPZ_3_10 = 17; -/* repeat a zero length 3-10 times (3 bits of repeat count) */ - -const REPZ_11_138 = 18; -/* repeat a zero length 11-138 times (7 bits of repeat count) */ - -/* eslint-disable comma-spacing,array-bracket-spacing */ -const extra_lbits = /* extra bits for each length code */ - new Uint8Array([0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0]); - -const extra_dbits = /* extra bits for each distance code */ - new Uint8Array([0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13]); - -const extra_blbits = /* extra bits for each bit length code */ - new Uint8Array([0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,3,7]); - -const bl_order = - new Uint8Array([16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15]); -/* eslint-enable comma-spacing,array-bracket-spacing */ - -/* The lengths of the bit length codes are sent in order of decreasing - * probability, to avoid transmitting the lengths for unused bit length codes. - */ - -/* =========================================================================== - * Local data. These are initialized only once. - */ - -// We pre-fill arrays with 0 to avoid uninitialized gaps - -const DIST_CODE_LEN = 512; /* see definition of array dist_code below */ - -// !!!! Use flat array instead of structure, Freq = i*2, Len = i*2+1 -const static_ltree = new Array((L_CODES$1 + 2) * 2); -zero$1(static_ltree); -/* The static literal tree. Since the bit lengths are imposed, there is no - * need for the L_CODES extra codes used during heap construction. However - * The codes 286 and 287 are needed to build a canonical tree (see _tr_init - * below). - */ - -const static_dtree = new Array(D_CODES$1 * 2); -zero$1(static_dtree); -/* The static distance tree. (Actually a trivial tree since all codes use - * 5 bits.) - */ - -const _dist_code = new Array(DIST_CODE_LEN); -zero$1(_dist_code); -/* Distance codes. The first 256 values correspond to the distances - * 3 .. 258, the last 256 values correspond to the top 8 bits of - * the 15 bit distances. - */ - -const _length_code = new Array(MAX_MATCH$1 - MIN_MATCH$1 + 1); -zero$1(_length_code); -/* length code for each normalized match length (0 == MIN_MATCH) */ - -const base_length = new Array(LENGTH_CODES$1); -zero$1(base_length); -/* First normalized length for each code (0 = MIN_MATCH) */ - -const base_dist = new Array(D_CODES$1); -zero$1(base_dist); -/* First normalized distance for each code (0 = distance of 1) */ - - -function StaticTreeDesc(static_tree, extra_bits, extra_base, elems, max_length) { - - this.static_tree = static_tree; /* static tree or NULL */ - this.extra_bits = extra_bits; /* extra bits for each code or NULL */ - this.extra_base = extra_base; /* base index for extra_bits */ - this.elems = elems; /* max number of elements in the tree */ - this.max_length = max_length; /* max bit length for the codes */ - - // show if `static_tree` has data or dummy - needed for monomorphic objects - this.has_stree = static_tree && static_tree.length; -} - - -let static_l_desc; -let static_d_desc; -let static_bl_desc; - - -function TreeDesc(dyn_tree, stat_desc) { - this.dyn_tree = dyn_tree; /* the dynamic tree */ - this.max_code = 0; /* largest code with non zero frequency */ - this.stat_desc = stat_desc; /* the corresponding static tree */ -} - - - -const d_code = (dist) => { - - return dist < 256 ? _dist_code[dist] : _dist_code[256 + (dist >>> 7)]; -}; - - -/* =========================================================================== - * Output a short LSB first on the stream. - * IN assertion: there is enough room in pendingBuf. - */ -const put_short = (s, w) => { -// put_byte(s, (uch)((w) & 0xff)); -// put_byte(s, (uch)((ush)(w) >> 8)); - s.pending_buf[s.pending++] = (w) & 0xff; - s.pending_buf[s.pending++] = (w >>> 8) & 0xff; -}; - - -/* =========================================================================== - * Send a value on a given number of bits. - * IN assertion: length <= 16 and value fits in length bits. - */ -const send_bits = (s, value, length) => { - - if (s.bi_valid > (Buf_size - length)) { - s.bi_buf |= (value << s.bi_valid) & 0xffff; - put_short(s, s.bi_buf); - s.bi_buf = value >> (Buf_size - s.bi_valid); - s.bi_valid += length - Buf_size; - } else { - s.bi_buf |= (value << s.bi_valid) & 0xffff; - s.bi_valid += length; - } -}; - - -const send_code = (s, c, tree) => { - - send_bits(s, tree[c * 2]/*.Code*/, tree[c * 2 + 1]/*.Len*/); -}; - - -/* =========================================================================== - * Reverse the first len bits of a code, using straightforward code (a faster - * method would use a table) - * IN assertion: 1 <= len <= 15 - */ -const bi_reverse = (code, len) => { - - let res = 0; - do { - res |= code & 1; - code >>>= 1; - res <<= 1; - } while (--len > 0); - return res >>> 1; -}; - - -/* =========================================================================== - * Flush the bit buffer, keeping at most 7 bits in it. - */ -const bi_flush = (s) => { - - if (s.bi_valid === 16) { - put_short(s, s.bi_buf); - s.bi_buf = 0; - s.bi_valid = 0; - - } else if (s.bi_valid >= 8) { - s.pending_buf[s.pending++] = s.bi_buf & 0xff; - s.bi_buf >>= 8; - s.bi_valid -= 8; - } -}; - - -/* =========================================================================== - * Compute the optimal bit lengths for a tree and update the total bit length - * for the current block. - * IN assertion: the fields freq and dad are set, heap[heap_max] and - * above are the tree nodes sorted by increasing frequency. - * OUT assertions: the field len is set to the optimal bit length, the - * array bl_count contains the frequencies for each bit length. - * The length opt_len is updated; static_len is also updated if stree is - * not null. - */ -const gen_bitlen = (s, desc) => { -// deflate_state *s; -// tree_desc *desc; /* the tree descriptor */ - - const tree = desc.dyn_tree; - const max_code = desc.max_code; - const stree = desc.stat_desc.static_tree; - const has_stree = desc.stat_desc.has_stree; - const extra = desc.stat_desc.extra_bits; - const base = desc.stat_desc.extra_base; - const max_length = desc.stat_desc.max_length; - let h; /* heap index */ - let n, m; /* iterate over the tree elements */ - let bits; /* bit length */ - let xbits; /* extra bits */ - let f; /* frequency */ - let overflow = 0; /* number of elements with bit length too large */ - - for (bits = 0; bits <= MAX_BITS$1; bits++) { - s.bl_count[bits] = 0; - } - - /* In a first pass, compute the optimal bit lengths (which may - * overflow in the case of the bit length tree). - */ - tree[s.heap[s.heap_max] * 2 + 1]/*.Len*/ = 0; /* root of the heap */ - - for (h = s.heap_max + 1; h < HEAP_SIZE$1; h++) { - n = s.heap[h]; - bits = tree[tree[n * 2 + 1]/*.Dad*/ * 2 + 1]/*.Len*/ + 1; - if (bits > max_length) { - bits = max_length; - overflow++; - } - tree[n * 2 + 1]/*.Len*/ = bits; - /* We overwrite tree[n].Dad which is no longer needed */ - - if (n > max_code) { continue; } /* not a leaf node */ - - s.bl_count[bits]++; - xbits = 0; - if (n >= base) { - xbits = extra[n - base]; - } - f = tree[n * 2]/*.Freq*/; - s.opt_len += f * (bits + xbits); - if (has_stree) { - s.static_len += f * (stree[n * 2 + 1]/*.Len*/ + xbits); - } - } - if (overflow === 0) { return; } - - // Tracev((stderr,"\nbit length overflow\n")); - /* This happens for example on obj2 and pic of the Calgary corpus */ - - /* Find the first bit length which could increase: */ - do { - bits = max_length - 1; - while (s.bl_count[bits] === 0) { bits--; } - s.bl_count[bits]--; /* move one leaf down the tree */ - s.bl_count[bits + 1] += 2; /* move one overflow item as its brother */ - s.bl_count[max_length]--; - /* The brother of the overflow item also moves one step up, - * but this does not affect bl_count[max_length] - */ - overflow -= 2; - } while (overflow > 0); - - /* Now recompute all bit lengths, scanning in increasing frequency. - * h is still equal to HEAP_SIZE. (It is simpler to reconstruct all - * lengths instead of fixing only the wrong ones. This idea is taken - * from 'ar' written by Haruhiko Okumura.) - */ - for (bits = max_length; bits !== 0; bits--) { - n = s.bl_count[bits]; - while (n !== 0) { - m = s.heap[--h]; - if (m > max_code) { continue; } - if (tree[m * 2 + 1]/*.Len*/ !== bits) { - // Tracev((stderr,"code %d bits %d->%d\n", m, tree[m].Len, bits)); - s.opt_len += (bits - tree[m * 2 + 1]/*.Len*/) * tree[m * 2]/*.Freq*/; - tree[m * 2 + 1]/*.Len*/ = bits; - } - n--; - } - } -}; - - -/* =========================================================================== - * Generate the codes for a given tree and bit counts (which need not be - * optimal). - * IN assertion: the array bl_count contains the bit length statistics for - * the given tree and the field len is set for all tree elements. - * OUT assertion: the field code is set for all tree elements of non - * zero code length. - */ -const gen_codes = (tree, max_code, bl_count) => { -// ct_data *tree; /* the tree to decorate */ -// int max_code; /* largest code with non zero frequency */ -// ushf *bl_count; /* number of codes at each bit length */ - - const next_code = new Array(MAX_BITS$1 + 1); /* next code value for each bit length */ - let code = 0; /* running code value */ - let bits; /* bit index */ - let n; /* code index */ - - /* The distribution counts are first used to generate the code values - * without bit reversal. - */ - for (bits = 1; bits <= MAX_BITS$1; bits++) { - code = (code + bl_count[bits - 1]) << 1; - next_code[bits] = code; - } - /* Check that the bit counts in bl_count are consistent. The last code - * must be all ones. - */ - //Assert (code + bl_count[MAX_BITS]-1 == (1< { - - let n; /* iterates over tree elements */ - let bits; /* bit counter */ - let length; /* length value */ - let code; /* code value */ - let dist; /* distance index */ - const bl_count = new Array(MAX_BITS$1 + 1); - /* number of codes at each bit length for an optimal tree */ - - // do check in _tr_init() - //if (static_init_done) return; - - /* For some embedded targets, global variables are not initialized: */ -/*#ifdef NO_INIT_GLOBAL_POINTERS - static_l_desc.static_tree = static_ltree; - static_l_desc.extra_bits = extra_lbits; - static_d_desc.static_tree = static_dtree; - static_d_desc.extra_bits = extra_dbits; - static_bl_desc.extra_bits = extra_blbits; -#endif*/ - - /* Initialize the mapping length (0..255) -> length code (0..28) */ - length = 0; - for (code = 0; code < LENGTH_CODES$1 - 1; code++) { - base_length[code] = length; - for (n = 0; n < (1 << extra_lbits[code]); n++) { - _length_code[length++] = code; - } - } - //Assert (length == 256, "tr_static_init: length != 256"); - /* Note that the length 255 (match length 258) can be represented - * in two different ways: code 284 + 5 bits or code 285, so we - * overwrite length_code[255] to use the best encoding: - */ - _length_code[length - 1] = code; - - /* Initialize the mapping dist (0..32K) -> dist code (0..29) */ - dist = 0; - for (code = 0; code < 16; code++) { - base_dist[code] = dist; - for (n = 0; n < (1 << extra_dbits[code]); n++) { - _dist_code[dist++] = code; - } - } - //Assert (dist == 256, "tr_static_init: dist != 256"); - dist >>= 7; /* from now on, all distances are divided by 128 */ - for (; code < D_CODES$1; code++) { - base_dist[code] = dist << 7; - for (n = 0; n < (1 << (extra_dbits[code] - 7)); n++) { - _dist_code[256 + dist++] = code; - } - } - //Assert (dist == 256, "tr_static_init: 256+dist != 512"); - - /* Construct the codes of the static literal tree */ - for (bits = 0; bits <= MAX_BITS$1; bits++) { - bl_count[bits] = 0; - } - - n = 0; - while (n <= 143) { - static_ltree[n * 2 + 1]/*.Len*/ = 8; - n++; - bl_count[8]++; - } - while (n <= 255) { - static_ltree[n * 2 + 1]/*.Len*/ = 9; - n++; - bl_count[9]++; - } - while (n <= 279) { - static_ltree[n * 2 + 1]/*.Len*/ = 7; - n++; - bl_count[7]++; - } - while (n <= 287) { - static_ltree[n * 2 + 1]/*.Len*/ = 8; - n++; - bl_count[8]++; - } - /* Codes 286 and 287 do not exist, but we must include them in the - * tree construction to get a canonical Huffman tree (longest code - * all ones) - */ - gen_codes(static_ltree, L_CODES$1 + 1, bl_count); - - /* The static distance tree is trivial: */ - for (n = 0; n < D_CODES$1; n++) { - static_dtree[n * 2 + 1]/*.Len*/ = 5; - static_dtree[n * 2]/*.Code*/ = bi_reverse(n, 5); - } - - // Now data ready and we can init static trees - static_l_desc = new StaticTreeDesc(static_ltree, extra_lbits, LITERALS$1 + 1, L_CODES$1, MAX_BITS$1); - static_d_desc = new StaticTreeDesc(static_dtree, extra_dbits, 0, D_CODES$1, MAX_BITS$1); - static_bl_desc = new StaticTreeDesc(new Array(0), extra_blbits, 0, BL_CODES$1, MAX_BL_BITS); - - //static_init_done = true; -}; - - -/* =========================================================================== - * Initialize a new block. - */ -const init_block = (s) => { - - let n; /* iterates over tree elements */ - - /* Initialize the trees. */ - for (n = 0; n < L_CODES$1; n++) { s.dyn_ltree[n * 2]/*.Freq*/ = 0; } - for (n = 0; n < D_CODES$1; n++) { s.dyn_dtree[n * 2]/*.Freq*/ = 0; } - for (n = 0; n < BL_CODES$1; n++) { s.bl_tree[n * 2]/*.Freq*/ = 0; } - - s.dyn_ltree[END_BLOCK * 2]/*.Freq*/ = 1; - s.opt_len = s.static_len = 0; - s.sym_next = s.matches = 0; -}; - - -/* =========================================================================== - * Flush the bit buffer and align the output on a byte boundary - */ -const bi_windup = (s) => -{ - if (s.bi_valid > 8) { - put_short(s, s.bi_buf); - } else if (s.bi_valid > 0) { - //put_byte(s, (Byte)s->bi_buf); - s.pending_buf[s.pending++] = s.bi_buf; - } - s.bi_buf = 0; - s.bi_valid = 0; -}; - -/* =========================================================================== - * Compares to subtrees, using the tree depth as tie breaker when - * the subtrees have equal frequency. This minimizes the worst case length. - */ -const smaller = (tree, n, m, depth) => { - - const _n2 = n * 2; - const _m2 = m * 2; - return (tree[_n2]/*.Freq*/ < tree[_m2]/*.Freq*/ || - (tree[_n2]/*.Freq*/ === tree[_m2]/*.Freq*/ && depth[n] <= depth[m])); -}; - -/* =========================================================================== - * Restore the heap property by moving down the tree starting at node k, - * exchanging a node with the smallest of its two sons if necessary, stopping - * when the heap property is re-established (each father smaller than its - * two sons). - */ -const pqdownheap = (s, tree, k) => { -// deflate_state *s; -// ct_data *tree; /* the tree to restore */ -// int k; /* node to move down */ - - const v = s.heap[k]; - let j = k << 1; /* left son of k */ - while (j <= s.heap_len) { - /* Set j to the smallest of the two sons: */ - if (j < s.heap_len && - smaller(tree, s.heap[j + 1], s.heap[j], s.depth)) { - j++; - } - /* Exit if v is smaller than both sons */ - if (smaller(tree, v, s.heap[j], s.depth)) { break; } - - /* Exchange v with the smallest son */ - s.heap[k] = s.heap[j]; - k = j; - - /* And continue down the tree, setting j to the left son of k */ - j <<= 1; - } - s.heap[k] = v; -}; - - -// inlined manually -// const SMALLEST = 1; - -/* =========================================================================== - * Send the block data compressed using the given Huffman trees - */ -const compress_block = (s, ltree, dtree) => { -// deflate_state *s; -// const ct_data *ltree; /* literal tree */ -// const ct_data *dtree; /* distance tree */ - - let dist; /* distance of matched string */ - let lc; /* match length or unmatched char (if dist == 0) */ - let sx = 0; /* running index in sym_buf */ - let code; /* the code to send */ - let extra; /* number of extra bits to send */ - - if (s.sym_next !== 0) { - do { - dist = s.pending_buf[s.sym_buf + sx++] & 0xff; - dist += (s.pending_buf[s.sym_buf + sx++] & 0xff) << 8; - lc = s.pending_buf[s.sym_buf + sx++]; - if (dist === 0) { - send_code(s, lc, ltree); /* send a literal byte */ - //Tracecv(isgraph(lc), (stderr," '%c' ", lc)); - } else { - /* Here, lc is the match length - MIN_MATCH */ - code = _length_code[lc]; - send_code(s, code + LITERALS$1 + 1, ltree); /* send the length code */ - extra = extra_lbits[code]; - if (extra !== 0) { - lc -= base_length[code]; - send_bits(s, lc, extra); /* send the extra length bits */ - } - dist--; /* dist is now the match distance - 1 */ - code = d_code(dist); - //Assert (code < D_CODES, "bad d_code"); - - send_code(s, code, dtree); /* send the distance code */ - extra = extra_dbits[code]; - if (extra !== 0) { - dist -= base_dist[code]; - send_bits(s, dist, extra); /* send the extra distance bits */ - } - } /* literal or match pair ? */ - - /* Check that the overlay between pending_buf and sym_buf is ok: */ - //Assert(s->pending < s->lit_bufsize + sx, "pendingBuf overflow"); - - } while (sx < s.sym_next); - } - - send_code(s, END_BLOCK, ltree); -}; - - -/* =========================================================================== - * Construct one Huffman tree and assigns the code bit strings and lengths. - * Update the total bit length for the current block. - * IN assertion: the field freq is set for all tree elements. - * OUT assertions: the fields len and code are set to the optimal bit length - * and corresponding code. The length opt_len is updated; static_len is - * also updated if stree is not null. The field max_code is set. - */ -const build_tree = (s, desc) => { -// deflate_state *s; -// tree_desc *desc; /* the tree descriptor */ - - const tree = desc.dyn_tree; - const stree = desc.stat_desc.static_tree; - const has_stree = desc.stat_desc.has_stree; - const elems = desc.stat_desc.elems; - let n, m; /* iterate over heap elements */ - let max_code = -1; /* largest code with non zero frequency */ - let node; /* new node being created */ - - /* Construct the initial heap, with least frequent element in - * heap[SMALLEST]. The sons of heap[n] are heap[2*n] and heap[2*n+1]. - * heap[0] is not used. - */ - s.heap_len = 0; - s.heap_max = HEAP_SIZE$1; - - for (n = 0; n < elems; n++) { - if (tree[n * 2]/*.Freq*/ !== 0) { - s.heap[++s.heap_len] = max_code = n; - s.depth[n] = 0; - - } else { - tree[n * 2 + 1]/*.Len*/ = 0; - } - } - - /* The pkzip format requires that at least one distance code exists, - * and that at least one bit should be sent even if there is only one - * possible code. So to avoid special checks later on we force at least - * two codes of non zero frequency. - */ - while (s.heap_len < 2) { - node = s.heap[++s.heap_len] = (max_code < 2 ? ++max_code : 0); - tree[node * 2]/*.Freq*/ = 1; - s.depth[node] = 0; - s.opt_len--; - - if (has_stree) { - s.static_len -= stree[node * 2 + 1]/*.Len*/; - } - /* node is 0 or 1 so it does not have extra bits */ - } - desc.max_code = max_code; - - /* The elements heap[heap_len/2+1 .. heap_len] are leaves of the tree, - * establish sub-heaps of increasing lengths: - */ - for (n = (s.heap_len >> 1/*int /2*/); n >= 1; n--) { pqdownheap(s, tree, n); } - - /* Construct the Huffman tree by repeatedly combining the least two - * frequent nodes. - */ - node = elems; /* next internal node of the tree */ - do { - //pqremove(s, tree, n); /* n = node of least frequency */ - /*** pqremove ***/ - n = s.heap[1/*SMALLEST*/]; - s.heap[1/*SMALLEST*/] = s.heap[s.heap_len--]; - pqdownheap(s, tree, 1/*SMALLEST*/); - /***/ - - m = s.heap[1/*SMALLEST*/]; /* m = node of next least frequency */ - - s.heap[--s.heap_max] = n; /* keep the nodes sorted by frequency */ - s.heap[--s.heap_max] = m; - - /* Create a new node father of n and m */ - tree[node * 2]/*.Freq*/ = tree[n * 2]/*.Freq*/ + tree[m * 2]/*.Freq*/; - s.depth[node] = (s.depth[n] >= s.depth[m] ? s.depth[n] : s.depth[m]) + 1; - tree[n * 2 + 1]/*.Dad*/ = tree[m * 2 + 1]/*.Dad*/ = node; - - /* and insert the new node in the heap */ - s.heap[1/*SMALLEST*/] = node++; - pqdownheap(s, tree, 1/*SMALLEST*/); - - } while (s.heap_len >= 2); - - s.heap[--s.heap_max] = s.heap[1/*SMALLEST*/]; - - /* At this point, the fields freq and dad are set. We can now - * generate the bit lengths. - */ - gen_bitlen(s, desc); - - /* The field len is now set, we can generate the bit codes */ - gen_codes(tree, max_code, s.bl_count); -}; - - -/* =========================================================================== - * Scan a literal or distance tree to determine the frequencies of the codes - * in the bit length tree. - */ -const scan_tree = (s, tree, max_code) => { -// deflate_state *s; -// ct_data *tree; /* the tree to be scanned */ -// int max_code; /* and its largest code of non zero frequency */ - - let n; /* iterates over all tree elements */ - let prevlen = -1; /* last emitted length */ - let curlen; /* length of current code */ - - let nextlen = tree[0 * 2 + 1]/*.Len*/; /* length of next code */ - - let count = 0; /* repeat count of the current code */ - let max_count = 7; /* max repeat count */ - let min_count = 4; /* min repeat count */ - - if (nextlen === 0) { - max_count = 138; - min_count = 3; - } - tree[(max_code + 1) * 2 + 1]/*.Len*/ = 0xffff; /* guard */ - - for (n = 0; n <= max_code; n++) { - curlen = nextlen; - nextlen = tree[(n + 1) * 2 + 1]/*.Len*/; - - if (++count < max_count && curlen === nextlen) { - continue; - - } else if (count < min_count) { - s.bl_tree[curlen * 2]/*.Freq*/ += count; - - } else if (curlen !== 0) { - - if (curlen !== prevlen) { s.bl_tree[curlen * 2]/*.Freq*/++; } - s.bl_tree[REP_3_6 * 2]/*.Freq*/++; - - } else if (count <= 10) { - s.bl_tree[REPZ_3_10 * 2]/*.Freq*/++; - - } else { - s.bl_tree[REPZ_11_138 * 2]/*.Freq*/++; - } - - count = 0; - prevlen = curlen; - - if (nextlen === 0) { - max_count = 138; - min_count = 3; - - } else if (curlen === nextlen) { - max_count = 6; - min_count = 3; - - } else { - max_count = 7; - min_count = 4; - } - } -}; - - -/* =========================================================================== - * Send a literal or distance tree in compressed form, using the codes in - * bl_tree. - */ -const send_tree = (s, tree, max_code) => { -// deflate_state *s; -// ct_data *tree; /* the tree to be scanned */ -// int max_code; /* and its largest code of non zero frequency */ - - let n; /* iterates over all tree elements */ - let prevlen = -1; /* last emitted length */ - let curlen; /* length of current code */ - - let nextlen = tree[0 * 2 + 1]/*.Len*/; /* length of next code */ - - let count = 0; /* repeat count of the current code */ - let max_count = 7; /* max repeat count */ - let min_count = 4; /* min repeat count */ - - /* tree[max_code+1].Len = -1; */ /* guard already set */ - if (nextlen === 0) { - max_count = 138; - min_count = 3; - } - - for (n = 0; n <= max_code; n++) { - curlen = nextlen; - nextlen = tree[(n + 1) * 2 + 1]/*.Len*/; - - if (++count < max_count && curlen === nextlen) { - continue; - - } else if (count < min_count) { - do { send_code(s, curlen, s.bl_tree); } while (--count !== 0); - - } else if (curlen !== 0) { - if (curlen !== prevlen) { - send_code(s, curlen, s.bl_tree); - count--; - } - //Assert(count >= 3 && count <= 6, " 3_6?"); - send_code(s, REP_3_6, s.bl_tree); - send_bits(s, count - 3, 2); - - } else if (count <= 10) { - send_code(s, REPZ_3_10, s.bl_tree); - send_bits(s, count - 3, 3); - - } else { - send_code(s, REPZ_11_138, s.bl_tree); - send_bits(s, count - 11, 7); - } - - count = 0; - prevlen = curlen; - if (nextlen === 0) { - max_count = 138; - min_count = 3; - - } else if (curlen === nextlen) { - max_count = 6; - min_count = 3; - - } else { - max_count = 7; - min_count = 4; - } - } -}; - - -/* =========================================================================== - * Construct the Huffman tree for the bit lengths and return the index in - * bl_order of the last bit length code to send. - */ -const build_bl_tree = (s) => { - - let max_blindex; /* index of last bit length code of non zero freq */ - - /* Determine the bit length frequencies for literal and distance trees */ - scan_tree(s, s.dyn_ltree, s.l_desc.max_code); - scan_tree(s, s.dyn_dtree, s.d_desc.max_code); - - /* Build the bit length tree: */ - build_tree(s, s.bl_desc); - /* opt_len now includes the length of the tree representations, except - * the lengths of the bit lengths codes and the 5+5+4 bits for the counts. - */ - - /* Determine the number of bit length codes to send. The pkzip format - * requires that at least 4 bit length codes be sent. (appnote.txt says - * 3 but the actual value used is 4.) - */ - for (max_blindex = BL_CODES$1 - 1; max_blindex >= 3; max_blindex--) { - if (s.bl_tree[bl_order[max_blindex] * 2 + 1]/*.Len*/ !== 0) { - break; - } - } - /* Update opt_len to include the bit length tree and counts */ - s.opt_len += 3 * (max_blindex + 1) + 5 + 5 + 4; - //Tracev((stderr, "\ndyn trees: dyn %ld, stat %ld", - // s->opt_len, s->static_len)); - - return max_blindex; -}; - - -/* =========================================================================== - * Send the header for a block using dynamic Huffman trees: the counts, the - * lengths of the bit length codes, the literal tree and the distance tree. - * IN assertion: lcodes >= 257, dcodes >= 1, blcodes >= 4. - */ -const send_all_trees = (s, lcodes, dcodes, blcodes) => { -// deflate_state *s; -// int lcodes, dcodes, blcodes; /* number of codes for each tree */ - - let rank; /* index in bl_order */ - - //Assert (lcodes >= 257 && dcodes >= 1 && blcodes >= 4, "not enough codes"); - //Assert (lcodes <= L_CODES && dcodes <= D_CODES && blcodes <= BL_CODES, - // "too many codes"); - //Tracev((stderr, "\nbl counts: ")); - send_bits(s, lcodes - 257, 5); /* not +255 as stated in appnote.txt */ - send_bits(s, dcodes - 1, 5); - send_bits(s, blcodes - 4, 4); /* not -3 as stated in appnote.txt */ - for (rank = 0; rank < blcodes; rank++) { - //Tracev((stderr, "\nbl code %2d ", bl_order[rank])); - send_bits(s, s.bl_tree[bl_order[rank] * 2 + 1]/*.Len*/, 3); - } - //Tracev((stderr, "\nbl tree: sent %ld", s->bits_sent)); - - send_tree(s, s.dyn_ltree, lcodes - 1); /* literal tree */ - //Tracev((stderr, "\nlit tree: sent %ld", s->bits_sent)); - - send_tree(s, s.dyn_dtree, dcodes - 1); /* distance tree */ - //Tracev((stderr, "\ndist tree: sent %ld", s->bits_sent)); -}; - - -/* =========================================================================== - * Check if the data type is TEXT or BINARY, using the following algorithm: - * - TEXT if the two conditions below are satisfied: - * a) There are no non-portable control characters belonging to the - * "block list" (0..6, 14..25, 28..31). - * b) There is at least one printable character belonging to the - * "allow list" (9 {TAB}, 10 {LF}, 13 {CR}, 32..255). - * - BINARY otherwise. - * - The following partially-portable control characters form a - * "gray list" that is ignored in this detection algorithm: - * (7 {BEL}, 8 {BS}, 11 {VT}, 12 {FF}, 26 {SUB}, 27 {ESC}). - * IN assertion: the fields Freq of dyn_ltree are set. - */ -const detect_data_type = (s) => { - /* block_mask is the bit mask of block-listed bytes - * set bits 0..6, 14..25, and 28..31 - * 0xf3ffc07f = binary 11110011111111111100000001111111 - */ - let block_mask = 0xf3ffc07f; - let n; - - /* Check for non-textual ("block-listed") bytes. */ - for (n = 0; n <= 31; n++, block_mask >>>= 1) { - if ((block_mask & 1) && (s.dyn_ltree[n * 2]/*.Freq*/ !== 0)) { - return Z_BINARY; - } - } - - /* Check for textual ("allow-listed") bytes. */ - if (s.dyn_ltree[9 * 2]/*.Freq*/ !== 0 || s.dyn_ltree[10 * 2]/*.Freq*/ !== 0 || - s.dyn_ltree[13 * 2]/*.Freq*/ !== 0) { - return Z_TEXT; - } - for (n = 32; n < LITERALS$1; n++) { - if (s.dyn_ltree[n * 2]/*.Freq*/ !== 0) { - return Z_TEXT; - } - } - - /* There are no "block-listed" or "allow-listed" bytes: - * this stream either is empty or has tolerated ("gray-listed") bytes only. - */ - return Z_BINARY; -}; - - -let static_init_done = false; - -/* =========================================================================== - * Initialize the tree data structures for a new zlib stream. - */ -const _tr_init$1 = (s) => -{ - - if (!static_init_done) { - tr_static_init(); - static_init_done = true; - } - - s.l_desc = new TreeDesc(s.dyn_ltree, static_l_desc); - s.d_desc = new TreeDesc(s.dyn_dtree, static_d_desc); - s.bl_desc = new TreeDesc(s.bl_tree, static_bl_desc); - - s.bi_buf = 0; - s.bi_valid = 0; - - /* Initialize the first block of the first file: */ - init_block(s); -}; - - -/* =========================================================================== - * Send a stored block - */ -const _tr_stored_block$1 = (s, buf, stored_len, last) => { -//DeflateState *s; -//charf *buf; /* input block */ -//ulg stored_len; /* length of input block */ -//int last; /* one if this is the last block for a file */ - - send_bits(s, (STORED_BLOCK << 1) + (last ? 1 : 0), 3); /* send block type */ - bi_windup(s); /* align on byte boundary */ - put_short(s, stored_len); - put_short(s, ~stored_len); - if (stored_len) { - s.pending_buf.set(s.window.subarray(buf, buf + stored_len), s.pending); - } - s.pending += stored_len; -}; - - -/* =========================================================================== - * Send one empty static block to give enough lookahead for inflate. - * This takes 10 bits, of which 7 may remain in the bit buffer. - */ -const _tr_align$1 = (s) => { - send_bits(s, STATIC_TREES << 1, 3); - send_code(s, END_BLOCK, static_ltree); - bi_flush(s); -}; - - -/* =========================================================================== - * Determine the best encoding for the current block: dynamic trees, static - * trees or store, and write out the encoded block. - */ -const _tr_flush_block$1 = (s, buf, stored_len, last) => { -//DeflateState *s; -//charf *buf; /* input block, or NULL if too old */ -//ulg stored_len; /* length of input block */ -//int last; /* one if this is the last block for a file */ - - let opt_lenb, static_lenb; /* opt_len and static_len in bytes */ - let max_blindex = 0; /* index of last bit length code of non zero freq */ - - /* Build the Huffman trees unless a stored block is forced */ - if (s.level > 0) { - - /* Check if the file is binary or text */ - if (s.strm.data_type === Z_UNKNOWN$1) { - s.strm.data_type = detect_data_type(s); - } - - /* Construct the literal and distance trees */ - build_tree(s, s.l_desc); - // Tracev((stderr, "\nlit data: dyn %ld, stat %ld", s->opt_len, - // s->static_len)); - - build_tree(s, s.d_desc); - // Tracev((stderr, "\ndist data: dyn %ld, stat %ld", s->opt_len, - // s->static_len)); - /* At this point, opt_len and static_len are the total bit lengths of - * the compressed block data, excluding the tree representations. - */ - - /* Build the bit length tree for the above two trees, and get the index - * in bl_order of the last bit length code to send. - */ - max_blindex = build_bl_tree(s); - - /* Determine the best encoding. Compute the block lengths in bytes. */ - opt_lenb = (s.opt_len + 3 + 7) >>> 3; - static_lenb = (s.static_len + 3 + 7) >>> 3; - - // Tracev((stderr, "\nopt %lu(%lu) stat %lu(%lu) stored %lu lit %u ", - // opt_lenb, s->opt_len, static_lenb, s->static_len, stored_len, - // s->sym_next / 3)); - - if (static_lenb <= opt_lenb) { opt_lenb = static_lenb; } - - } else { - // Assert(buf != (char*)0, "lost buf"); - opt_lenb = static_lenb = stored_len + 5; /* force a stored block */ - } - - if ((stored_len + 4 <= opt_lenb) && (buf !== -1)) { - /* 4: two words for the lengths */ - - /* The test buf != NULL is only necessary if LIT_BUFSIZE > WSIZE. - * Otherwise we can't have processed more than WSIZE input bytes since - * the last block flush, because compression would have been - * successful. If LIT_BUFSIZE <= WSIZE, it is never too late to - * transform a block into a stored block. - */ - _tr_stored_block$1(s, buf, stored_len, last); - - } else if (s.strategy === Z_FIXED$1 || static_lenb === opt_lenb) { - - send_bits(s, (STATIC_TREES << 1) + (last ? 1 : 0), 3); - compress_block(s, static_ltree, static_dtree); - - } else { - send_bits(s, (DYN_TREES << 1) + (last ? 1 : 0), 3); - send_all_trees(s, s.l_desc.max_code + 1, s.d_desc.max_code + 1, max_blindex + 1); - compress_block(s, s.dyn_ltree, s.dyn_dtree); - } - // Assert (s->compressed_len == s->bits_sent, "bad compressed size"); - /* The above check is made mod 2^32, for files larger than 512 MB - * and uLong implemented on 32 bits. - */ - init_block(s); - - if (last) { - bi_windup(s); - } - // Tracev((stderr,"\ncomprlen %lu(%lu) ", s->compressed_len>>3, - // s->compressed_len-7*last)); -}; - -/* =========================================================================== - * Save the match info and tally the frequency counts. Return true if - * the current block must be flushed. - */ -const _tr_tally$1 = (s, dist, lc) => { -// deflate_state *s; -// unsigned dist; /* distance of matched string */ -// unsigned lc; /* match length-MIN_MATCH or unmatched char (if dist==0) */ - - s.pending_buf[s.sym_buf + s.sym_next++] = dist; - s.pending_buf[s.sym_buf + s.sym_next++] = dist >> 8; - s.pending_buf[s.sym_buf + s.sym_next++] = lc; - if (dist === 0) { - /* lc is the unmatched char */ - s.dyn_ltree[lc * 2]/*.Freq*/++; - } else { - s.matches++; - /* Here, lc is the match length - MIN_MATCH */ - dist--; /* dist = match distance - 1 */ - //Assert((ush)dist < (ush)MAX_DIST(s) && - // (ush)lc <= (ush)(MAX_MATCH-MIN_MATCH) && - // (ush)d_code(dist) < (ush)D_CODES, "_tr_tally: bad match"); - - s.dyn_ltree[(_length_code[lc] + LITERALS$1 + 1) * 2]/*.Freq*/++; - s.dyn_dtree[d_code(dist) * 2]/*.Freq*/++; - } - - return (s.sym_next === s.sym_end); -}; - -var _tr_init_1 = _tr_init$1; -var _tr_stored_block_1 = _tr_stored_block$1; -var _tr_flush_block_1 = _tr_flush_block$1; -var _tr_tally_1 = _tr_tally$1; -var _tr_align_1 = _tr_align$1; - -var trees = { - _tr_init: _tr_init_1, - _tr_stored_block: _tr_stored_block_1, - _tr_flush_block: _tr_flush_block_1, - _tr_tally: _tr_tally_1, - _tr_align: _tr_align_1 -}; - -// Note: adler32 takes 12% for level 0 and 2% for level 6. -// It isn't worth it to make additional optimizations as in original. -// Small size is preferable. - -// (C) 1995-2013 Jean-loup Gailly and Mark Adler -// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin -// -// This software is provided 'as-is', without any express or implied -// warranty. In no event will the authors be held liable for any damages -// arising from the use of this software. -// -// Permission is granted to anyone to use this software for any purpose, -// including commercial applications, and to alter it and redistribute it -// freely, subject to the following restrictions: -// -// 1. The origin of this software must not be misrepresented; you must not -// claim that you wrote the original software. If you use this software -// in a product, an acknowledgment in the product documentation would be -// appreciated but is not required. -// 2. Altered source versions must be plainly marked as such, and must not be -// misrepresented as being the original software. -// 3. This notice may not be removed or altered from any source distribution. - -const adler32 = (adler, buf, len, pos) => { - let s1 = (adler & 0xffff) |0, - s2 = ((adler >>> 16) & 0xffff) |0, - n = 0; - - while (len !== 0) { - // Set limit ~ twice less than 5552, to keep - // s2 in 31-bits, because we force signed ints. - // in other case %= will fail. - n = len > 2000 ? 2000 : len; - len -= n; - - do { - s1 = (s1 + buf[pos++]) |0; - s2 = (s2 + s1) |0; - } while (--n); - - s1 %= 65521; - s2 %= 65521; - } - - return (s1 | (s2 << 16)) |0; -}; - - -var adler32_1 = adler32; - -// Note: we can't get significant speed boost here. -// So write code to minimize size - no pregenerated tables -// and array tools dependencies. - -// (C) 1995-2013 Jean-loup Gailly and Mark Adler -// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin -// -// This software is provided 'as-is', without any express or implied -// warranty. In no event will the authors be held liable for any damages -// arising from the use of this software. -// -// Permission is granted to anyone to use this software for any purpose, -// including commercial applications, and to alter it and redistribute it -// freely, subject to the following restrictions: -// -// 1. The origin of this software must not be misrepresented; you must not -// claim that you wrote the original software. If you use this software -// in a product, an acknowledgment in the product documentation would be -// appreciated but is not required. -// 2. Altered source versions must be plainly marked as such, and must not be -// misrepresented as being the original software. -// 3. This notice may not be removed or altered from any source distribution. - -// Use ordinary array, since untyped makes no boost here -const makeTable = () => { - let c, table = []; - - for (var n = 0; n < 256; n++) { - c = n; - for (var k = 0; k < 8; k++) { - c = ((c & 1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1)); - } - table[n] = c; - } - - return table; -}; - -// Create table on load. Just 255 signed longs. Not a problem. -const crcTable = new Uint32Array(makeTable()); - - -const crc32 = (crc, buf, len, pos) => { - const t = crcTable; - const end = pos + len; - - crc ^= -1; - - for (let i = pos; i < end; i++) { - crc = (crc >>> 8) ^ t[(crc ^ buf[i]) & 0xFF]; - } - - return (crc ^ (-1)); // >>> 0; -}; - - -var crc32_1 = crc32; - -// (C) 1995-2013 Jean-loup Gailly and Mark Adler -// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin -// -// This software is provided 'as-is', without any express or implied -// warranty. In no event will the authors be held liable for any damages -// arising from the use of this software. -// -// Permission is granted to anyone to use this software for any purpose, -// including commercial applications, and to alter it and redistribute it -// freely, subject to the following restrictions: -// -// 1. The origin of this software must not be misrepresented; you must not -// claim that you wrote the original software. If you use this software -// in a product, an acknowledgment in the product documentation would be -// appreciated but is not required. -// 2. Altered source versions must be plainly marked as such, and must not be -// misrepresented as being the original software. -// 3. This notice may not be removed or altered from any source distribution. - -var messages = { - 2: 'need dictionary', /* Z_NEED_DICT 2 */ - 1: 'stream end', /* Z_STREAM_END 1 */ - 0: '', /* Z_OK 0 */ - '-1': 'file error', /* Z_ERRNO (-1) */ - '-2': 'stream error', /* Z_STREAM_ERROR (-2) */ - '-3': 'data error', /* Z_DATA_ERROR (-3) */ - '-4': 'insufficient memory', /* Z_MEM_ERROR (-4) */ - '-5': 'buffer error', /* Z_BUF_ERROR (-5) */ - '-6': 'incompatible version' /* Z_VERSION_ERROR (-6) */ -}; - -// (C) 1995-2013 Jean-loup Gailly and Mark Adler -// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin -// -// This software is provided 'as-is', without any express or implied -// warranty. In no event will the authors be held liable for any damages -// arising from the use of this software. -// -// Permission is granted to anyone to use this software for any purpose, -// including commercial applications, and to alter it and redistribute it -// freely, subject to the following restrictions: -// -// 1. The origin of this software must not be misrepresented; you must not -// claim that you wrote the original software. If you use this software -// in a product, an acknowledgment in the product documentation would be -// appreciated but is not required. -// 2. Altered source versions must be plainly marked as such, and must not be -// misrepresented as being the original software. -// 3. This notice may not be removed or altered from any source distribution. - -var constants$2 = { - - /* Allowed flush values; see deflate() and inflate() below for details */ - Z_NO_FLUSH: 0, - Z_PARTIAL_FLUSH: 1, - Z_SYNC_FLUSH: 2, - Z_FULL_FLUSH: 3, - Z_FINISH: 4, - Z_BLOCK: 5, - Z_TREES: 6, - - /* Return codes for the compression/decompression functions. Negative values - * are errors, positive values are used for special but normal events. - */ - Z_OK: 0, - Z_STREAM_END: 1, - Z_NEED_DICT: 2, - Z_ERRNO: -1, - Z_STREAM_ERROR: -2, - Z_DATA_ERROR: -3, - Z_MEM_ERROR: -4, - Z_BUF_ERROR: -5, - //Z_VERSION_ERROR: -6, - - /* compression levels */ - Z_NO_COMPRESSION: 0, - Z_BEST_SPEED: 1, - Z_BEST_COMPRESSION: 9, - Z_DEFAULT_COMPRESSION: -1, - - - Z_FILTERED: 1, - Z_HUFFMAN_ONLY: 2, - Z_RLE: 3, - Z_FIXED: 4, - Z_DEFAULT_STRATEGY: 0, - - /* Possible values of the data_type field (though see inflate()) */ - Z_BINARY: 0, - Z_TEXT: 1, - //Z_ASCII: 1, // = Z_TEXT (deprecated) - Z_UNKNOWN: 2, - - /* The deflate compression method */ - Z_DEFLATED: 8 - //Z_NULL: null // Use -1 or null inline, depending on var type -}; - -// (C) 1995-2013 Jean-loup Gailly and Mark Adler -// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin -// -// This software is provided 'as-is', without any express or implied -// warranty. In no event will the authors be held liable for any damages -// arising from the use of this software. -// -// Permission is granted to anyone to use this software for any purpose, -// including commercial applications, and to alter it and redistribute it -// freely, subject to the following restrictions: -// -// 1. The origin of this software must not be misrepresented; you must not -// claim that you wrote the original software. If you use this software -// in a product, an acknowledgment in the product documentation would be -// appreciated but is not required. -// 2. Altered source versions must be plainly marked as such, and must not be -// misrepresented as being the original software. -// 3. This notice may not be removed or altered from any source distribution. - -const { _tr_init, _tr_stored_block, _tr_flush_block, _tr_tally, _tr_align } = trees; - - - - -/* Public constants ==========================================================*/ -/* ===========================================================================*/ - -const { - Z_NO_FLUSH: Z_NO_FLUSH$2, Z_PARTIAL_FLUSH, Z_FULL_FLUSH: Z_FULL_FLUSH$1, Z_FINISH: Z_FINISH$3, Z_BLOCK: Z_BLOCK$1, - Z_OK: Z_OK$3, Z_STREAM_END: Z_STREAM_END$3, Z_STREAM_ERROR: Z_STREAM_ERROR$2, Z_DATA_ERROR: Z_DATA_ERROR$2, Z_BUF_ERROR: Z_BUF_ERROR$1, - Z_DEFAULT_COMPRESSION: Z_DEFAULT_COMPRESSION$1, - Z_FILTERED, Z_HUFFMAN_ONLY, Z_RLE, Z_FIXED, Z_DEFAULT_STRATEGY: Z_DEFAULT_STRATEGY$1, - Z_UNKNOWN, - Z_DEFLATED: Z_DEFLATED$2 -} = constants$2; - -/*============================================================================*/ - - -const MAX_MEM_LEVEL = 9; -/* Maximum value for memLevel in deflateInit2 */ -const MAX_WBITS$1 = 15; -/* 32K LZ77 window */ -const DEF_MEM_LEVEL = 8; - - -const LENGTH_CODES = 29; -/* number of length codes, not counting the special END_BLOCK code */ -const LITERALS = 256; -/* number of literal bytes 0..255 */ -const L_CODES = LITERALS + 1 + LENGTH_CODES; -/* number of Literal or Length codes, including the END_BLOCK code */ -const D_CODES = 30; -/* number of distance codes */ -const BL_CODES = 19; -/* number of codes used to transfer the bit lengths */ -const HEAP_SIZE = 2 * L_CODES + 1; -/* maximum heap size */ -const MAX_BITS = 15; -/* All codes must not exceed MAX_BITS bits */ - -const MIN_MATCH = 3; -const MAX_MATCH = 258; -const MIN_LOOKAHEAD = (MAX_MATCH + MIN_MATCH + 1); - -const PRESET_DICT = 0x20; - -const INIT_STATE = 42; /* zlib header -> BUSY_STATE */ -//#ifdef GZIP -const GZIP_STATE = 57; /* gzip header -> BUSY_STATE | EXTRA_STATE */ -//#endif -const EXTRA_STATE = 69; /* gzip extra block -> NAME_STATE */ -const NAME_STATE = 73; /* gzip file name -> COMMENT_STATE */ -const COMMENT_STATE = 91; /* gzip comment -> HCRC_STATE */ -const HCRC_STATE = 103; /* gzip header CRC -> BUSY_STATE */ -const BUSY_STATE = 113; /* deflate -> FINISH_STATE */ -const FINISH_STATE = 666; /* stream complete */ - -const BS_NEED_MORE = 1; /* block not completed, need more input or more output */ -const BS_BLOCK_DONE = 2; /* block flush performed */ -const BS_FINISH_STARTED = 3; /* finish started, need only more output at next deflate */ -const BS_FINISH_DONE = 4; /* finish done, accept no more input or output */ - -const OS_CODE = 0x03; // Unix :) . Don't detect, use this default. - -const err = (strm, errorCode) => { - strm.msg = messages[errorCode]; - return errorCode; -}; - -const rank = (f) => { - return ((f) * 2) - ((f) > 4 ? 9 : 0); -}; - -const zero = (buf) => { - let len = buf.length; while (--len >= 0) { buf[len] = 0; } -}; - -/* =========================================================================== - * Slide the hash table when sliding the window down (could be avoided with 32 - * bit values at the expense of memory usage). We slide even when level == 0 to - * keep the hash table consistent if we switch back to level > 0 later. - */ -const slide_hash = (s) => { - let n, m; - let p; - let wsize = s.w_size; - - n = s.hash_size; - p = n; - do { - m = s.head[--p]; - s.head[p] = (m >= wsize ? m - wsize : 0); - } while (--n); - n = wsize; -//#ifndef FASTEST - p = n; - do { - m = s.prev[--p]; - s.prev[p] = (m >= wsize ? m - wsize : 0); - /* If n is not on any hash chain, prev[n] is garbage but - * its value will never be used. - */ - } while (--n); -//#endif -}; - -/* eslint-disable new-cap */ -let HASH_ZLIB = (s, prev, data) => ((prev << s.hash_shift) ^ data) & s.hash_mask; -// This hash causes less collisions, https://github.com/nodeca/pako/issues/135 -// But breaks binary compatibility -//let HASH_FAST = (s, prev, data) => ((prev << 8) + (prev >> 8) + (data << 4)) & s.hash_mask; -let HASH = HASH_ZLIB; - - -/* ========================================================================= - * Flush as much pending output as possible. All deflate() output, except for - * some deflate_stored() output, goes through this function so some - * applications may wish to modify it to avoid allocating a large - * strm->next_out buffer and copying into it. (See also read_buf()). - */ -const flush_pending = (strm) => { - const s = strm.state; - - //_tr_flush_bits(s); - let len = s.pending; - if (len > strm.avail_out) { - len = strm.avail_out; - } - if (len === 0) { return; } - - strm.output.set(s.pending_buf.subarray(s.pending_out, s.pending_out + len), strm.next_out); - strm.next_out += len; - s.pending_out += len; - strm.total_out += len; - strm.avail_out -= len; - s.pending -= len; - if (s.pending === 0) { - s.pending_out = 0; - } -}; - - -const flush_block_only = (s, last) => { - _tr_flush_block(s, (s.block_start >= 0 ? s.block_start : -1), s.strstart - s.block_start, last); - s.block_start = s.strstart; - flush_pending(s.strm); -}; - - -const put_byte = (s, b) => { - s.pending_buf[s.pending++] = b; -}; - - -/* ========================================================================= - * Put a short in the pending buffer. The 16-bit value is put in MSB order. - * IN assertion: the stream state is correct and there is enough room in - * pending_buf. - */ -const putShortMSB = (s, b) => { - - // put_byte(s, (Byte)(b >> 8)); -// put_byte(s, (Byte)(b & 0xff)); - s.pending_buf[s.pending++] = (b >>> 8) & 0xff; - s.pending_buf[s.pending++] = b & 0xff; -}; - - -/* =========================================================================== - * Read a new buffer from the current input stream, update the adler32 - * and total number of bytes read. All deflate() input goes through - * this function so some applications may wish to modify it to avoid - * allocating a large strm->input buffer and copying from it. - * (See also flush_pending()). - */ -const read_buf = (strm, buf, start, size) => { - - let len = strm.avail_in; - - if (len > size) { len = size; } - if (len === 0) { return 0; } - - strm.avail_in -= len; - - // zmemcpy(buf, strm->next_in, len); - buf.set(strm.input.subarray(strm.next_in, strm.next_in + len), start); - if (strm.state.wrap === 1) { - strm.adler = adler32_1(strm.adler, buf, len, start); - } - - else if (strm.state.wrap === 2) { - strm.adler = crc32_1(strm.adler, buf, len, start); - } - - strm.next_in += len; - strm.total_in += len; - - return len; -}; - - -/* =========================================================================== - * Set match_start to the longest match starting at the given string and - * return its length. Matches shorter or equal to prev_length are discarded, - * in which case the result is equal to prev_length and match_start is - * garbage. - * IN assertions: cur_match is the head of the hash chain for the current - * string (strstart) and its distance is <= MAX_DIST, and prev_length >= 1 - * OUT assertion: the match length is not greater than s->lookahead. - */ -const longest_match = (s, cur_match) => { - - let chain_length = s.max_chain_length; /* max hash chain length */ - let scan = s.strstart; /* current string */ - let match; /* matched string */ - let len; /* length of current match */ - let best_len = s.prev_length; /* best match length so far */ - let nice_match = s.nice_match; /* stop if match long enough */ - const limit = (s.strstart > (s.w_size - MIN_LOOKAHEAD)) ? - s.strstart - (s.w_size - MIN_LOOKAHEAD) : 0/*NIL*/; - - const _win = s.window; // shortcut - - const wmask = s.w_mask; - const prev = s.prev; - - /* Stop when cur_match becomes <= limit. To simplify the code, - * we prevent matches with the string of window index 0. - */ - - const strend = s.strstart + MAX_MATCH; - let scan_end1 = _win[scan + best_len - 1]; - let scan_end = _win[scan + best_len]; - - /* The code is optimized for HASH_BITS >= 8 and MAX_MATCH-2 multiple of 16. - * It is easy to get rid of this optimization if necessary. - */ - // Assert(s->hash_bits >= 8 && MAX_MATCH == 258, "Code too clever"); - - /* Do not waste too much time if we already have a good match: */ - if (s.prev_length >= s.good_match) { - chain_length >>= 2; - } - /* Do not look for matches beyond the end of the input. This is necessary - * to make deflate deterministic. - */ - if (nice_match > s.lookahead) { nice_match = s.lookahead; } - - // Assert((ulg)s->strstart <= s->window_size-MIN_LOOKAHEAD, "need lookahead"); - - do { - // Assert(cur_match < s->strstart, "no future"); - match = cur_match; - - /* Skip to next match if the match length cannot increase - * or if the match length is less than 2. Note that the checks below - * for insufficient lookahead only occur occasionally for performance - * reasons. Therefore uninitialized memory will be accessed, and - * conditional jumps will be made that depend on those values. - * However the length of the match is limited to the lookahead, so - * the output of deflate is not affected by the uninitialized values. - */ - - if (_win[match + best_len] !== scan_end || - _win[match + best_len - 1] !== scan_end1 || - _win[match] !== _win[scan] || - _win[++match] !== _win[scan + 1]) { - continue; - } - - /* The check at best_len-1 can be removed because it will be made - * again later. (This heuristic is not always a win.) - * It is not necessary to compare scan[2] and match[2] since they - * are always equal when the other bytes match, given that - * the hash keys are equal and that HASH_BITS >= 8. - */ - scan += 2; - match++; - // Assert(*scan == *match, "match[2]?"); - - /* We check for insufficient lookahead only every 8th comparison; - * the 256th check will be made at strstart+258. - */ - do { - /*jshint noempty:false*/ - } while (_win[++scan] === _win[++match] && _win[++scan] === _win[++match] && - _win[++scan] === _win[++match] && _win[++scan] === _win[++match] && - _win[++scan] === _win[++match] && _win[++scan] === _win[++match] && - _win[++scan] === _win[++match] && _win[++scan] === _win[++match] && - scan < strend); - - // Assert(scan <= s->window+(unsigned)(s->window_size-1), "wild scan"); - - len = MAX_MATCH - (strend - scan); - scan = strend - MAX_MATCH; - - if (len > best_len) { - s.match_start = cur_match; - best_len = len; - if (len >= nice_match) { - break; - } - scan_end1 = _win[scan + best_len - 1]; - scan_end = _win[scan + best_len]; - } - } while ((cur_match = prev[cur_match & wmask]) > limit && --chain_length !== 0); - - if (best_len <= s.lookahead) { - return best_len; - } - return s.lookahead; -}; - - -/* =========================================================================== - * Fill the window when the lookahead becomes insufficient. - * Updates strstart and lookahead. - * - * IN assertion: lookahead < MIN_LOOKAHEAD - * OUT assertions: strstart <= window_size-MIN_LOOKAHEAD - * At least one byte has been read, or avail_in == 0; reads are - * performed for at least two bytes (required for the zip translate_eol - * option -- not supported here). - */ -const fill_window = (s) => { - - const _w_size = s.w_size; - let n, more, str; - - //Assert(s->lookahead < MIN_LOOKAHEAD, "already enough lookahead"); - - do { - more = s.window_size - s.lookahead - s.strstart; - - // JS ints have 32 bit, block below not needed - /* Deal with !@#$% 64K limit: */ - //if (sizeof(int) <= 2) { - // if (more == 0 && s->strstart == 0 && s->lookahead == 0) { - // more = wsize; - // - // } else if (more == (unsigned)(-1)) { - // /* Very unlikely, but possible on 16 bit machine if - // * strstart == 0 && lookahead == 1 (input done a byte at time) - // */ - // more--; - // } - //} - - - /* If the window is almost full and there is insufficient lookahead, - * move the upper half to the lower one to make room in the upper half. - */ - if (s.strstart >= _w_size + (_w_size - MIN_LOOKAHEAD)) { - - s.window.set(s.window.subarray(_w_size, _w_size + _w_size - more), 0); - s.match_start -= _w_size; - s.strstart -= _w_size; - /* we now have strstart >= MAX_DIST */ - s.block_start -= _w_size; - if (s.insert > s.strstart) { - s.insert = s.strstart; - } - slide_hash(s); - more += _w_size; - } - if (s.strm.avail_in === 0) { - break; - } - - /* If there was no sliding: - * strstart <= WSIZE+MAX_DIST-1 && lookahead <= MIN_LOOKAHEAD - 1 && - * more == window_size - lookahead - strstart - * => more >= window_size - (MIN_LOOKAHEAD-1 + WSIZE + MAX_DIST-1) - * => more >= window_size - 2*WSIZE + 2 - * In the BIG_MEM or MMAP case (not yet supported), - * window_size == input_size + MIN_LOOKAHEAD && - * strstart + s->lookahead <= input_size => more >= MIN_LOOKAHEAD. - * Otherwise, window_size == 2*WSIZE so more >= 2. - * If there was sliding, more >= WSIZE. So in all cases, more >= 2. - */ - //Assert(more >= 2, "more < 2"); - n = read_buf(s.strm, s.window, s.strstart + s.lookahead, more); - s.lookahead += n; - - /* Initialize the hash value now that we have some input: */ - if (s.lookahead + s.insert >= MIN_MATCH) { - str = s.strstart - s.insert; - s.ins_h = s.window[str]; - - /* UPDATE_HASH(s, s->ins_h, s->window[str + 1]); */ - s.ins_h = HASH(s, s.ins_h, s.window[str + 1]); -//#if MIN_MATCH != 3 -// Call update_hash() MIN_MATCH-3 more times -//#endif - while (s.insert) { - /* UPDATE_HASH(s, s->ins_h, s->window[str + MIN_MATCH-1]); */ - s.ins_h = HASH(s, s.ins_h, s.window[str + MIN_MATCH - 1]); - - s.prev[str & s.w_mask] = s.head[s.ins_h]; - s.head[s.ins_h] = str; - str++; - s.insert--; - if (s.lookahead + s.insert < MIN_MATCH) { - break; - } - } - } - /* If the whole input has less than MIN_MATCH bytes, ins_h is garbage, - * but this is not important since only literal bytes will be emitted. - */ - - } while (s.lookahead < MIN_LOOKAHEAD && s.strm.avail_in !== 0); - - /* If the WIN_INIT bytes after the end of the current data have never been - * written, then zero those bytes in order to avoid memory check reports of - * the use of uninitialized (or uninitialised as Julian writes) bytes by - * the longest match routines. Update the high water mark for the next - * time through here. WIN_INIT is set to MAX_MATCH since the longest match - * routines allow scanning to strstart + MAX_MATCH, ignoring lookahead. - */ -// if (s.high_water < s.window_size) { -// const curr = s.strstart + s.lookahead; -// let init = 0; -// -// if (s.high_water < curr) { -// /* Previous high water mark below current data -- zero WIN_INIT -// * bytes or up to end of window, whichever is less. -// */ -// init = s.window_size - curr; -// if (init > WIN_INIT) -// init = WIN_INIT; -// zmemzero(s->window + curr, (unsigned)init); -// s->high_water = curr + init; -// } -// else if (s->high_water < (ulg)curr + WIN_INIT) { -// /* High water mark at or above current data, but below current data -// * plus WIN_INIT -- zero out to current data plus WIN_INIT, or up -// * to end of window, whichever is less. -// */ -// init = (ulg)curr + WIN_INIT - s->high_water; -// if (init > s->window_size - s->high_water) -// init = s->window_size - s->high_water; -// zmemzero(s->window + s->high_water, (unsigned)init); -// s->high_water += init; -// } -// } -// -// Assert((ulg)s->strstart <= s->window_size - MIN_LOOKAHEAD, -// "not enough room for search"); -}; - -/* =========================================================================== - * Copy without compression as much as possible from the input stream, return - * the current block state. - * - * In case deflateParams() is used to later switch to a non-zero compression - * level, s->matches (otherwise unused when storing) keeps track of the number - * of hash table slides to perform. If s->matches is 1, then one hash table - * slide will be done when switching. If s->matches is 2, the maximum value - * allowed here, then the hash table will be cleared, since two or more slides - * is the same as a clear. - * - * deflate_stored() is written to minimize the number of times an input byte is - * copied. It is most efficient with large input and output buffers, which - * maximizes the opportunites to have a single copy from next_in to next_out. - */ -const deflate_stored = (s, flush) => { - - /* Smallest worthy block size when not flushing or finishing. By default - * this is 32K. This can be as small as 507 bytes for memLevel == 1. For - * large input and output buffers, the stored block size will be larger. - */ - let min_block = s.pending_buf_size - 5 > s.w_size ? s.w_size : s.pending_buf_size - 5; - - /* Copy as many min_block or larger stored blocks directly to next_out as - * possible. If flushing, copy the remaining available input to next_out as - * stored blocks, if there is enough space. - */ - let len, left, have, last = 0; - let used = s.strm.avail_in; - do { - /* Set len to the maximum size block that we can copy directly with the - * available input data and output space. Set left to how much of that - * would be copied from what's left in the window. - */ - len = 65535/* MAX_STORED */; /* maximum deflate stored block length */ - have = (s.bi_valid + 42) >> 3; /* number of header bytes */ - if (s.strm.avail_out < have) { /* need room for header */ - break; - } - /* maximum stored block length that will fit in avail_out: */ - have = s.strm.avail_out - have; - left = s.strstart - s.block_start; /* bytes left in window */ - if (len > left + s.strm.avail_in) { - len = left + s.strm.avail_in; /* limit len to the input */ - } - if (len > have) { - len = have; /* limit len to the output */ - } - - /* If the stored block would be less than min_block in length, or if - * unable to copy all of the available input when flushing, then try - * copying to the window and the pending buffer instead. Also don't - * write an empty block when flushing -- deflate() does that. - */ - if (len < min_block && ((len === 0 && flush !== Z_FINISH$3) || - flush === Z_NO_FLUSH$2 || - len !== left + s.strm.avail_in)) { - break; - } - - /* Make a dummy stored block in pending to get the header bytes, - * including any pending bits. This also updates the debugging counts. - */ - last = flush === Z_FINISH$3 && len === left + s.strm.avail_in ? 1 : 0; - _tr_stored_block(s, 0, 0, last); - - /* Replace the lengths in the dummy stored block with len. */ - s.pending_buf[s.pending - 4] = len; - s.pending_buf[s.pending - 3] = len >> 8; - s.pending_buf[s.pending - 2] = ~len; - s.pending_buf[s.pending - 1] = ~len >> 8; - - /* Write the stored block header bytes. */ - flush_pending(s.strm); - -//#ifdef ZLIB_DEBUG -// /* Update debugging counts for the data about to be copied. */ -// s->compressed_len += len << 3; -// s->bits_sent += len << 3; -//#endif - - /* Copy uncompressed bytes from the window to next_out. */ - if (left) { - if (left > len) { - left = len; - } - //zmemcpy(s->strm->next_out, s->window + s->block_start, left); - s.strm.output.set(s.window.subarray(s.block_start, s.block_start + left), s.strm.next_out); - s.strm.next_out += left; - s.strm.avail_out -= left; - s.strm.total_out += left; - s.block_start += left; - len -= left; - } - - /* Copy uncompressed bytes directly from next_in to next_out, updating - * the check value. - */ - if (len) { - read_buf(s.strm, s.strm.output, s.strm.next_out, len); - s.strm.next_out += len; - s.strm.avail_out -= len; - s.strm.total_out += len; - } - } while (last === 0); - - /* Update the sliding window with the last s->w_size bytes of the copied - * data, or append all of the copied data to the existing window if less - * than s->w_size bytes were copied. Also update the number of bytes to - * insert in the hash tables, in the event that deflateParams() switches to - * a non-zero compression level. - */ - used -= s.strm.avail_in; /* number of input bytes directly copied */ - if (used) { - /* If any input was used, then no unused input remains in the window, - * therefore s->block_start == s->strstart. - */ - if (used >= s.w_size) { /* supplant the previous history */ - s.matches = 2; /* clear hash */ - //zmemcpy(s->window, s->strm->next_in - s->w_size, s->w_size); - s.window.set(s.strm.input.subarray(s.strm.next_in - s.w_size, s.strm.next_in), 0); - s.strstart = s.w_size; - s.insert = s.strstart; - } - else { - if (s.window_size - s.strstart <= used) { - /* Slide the window down. */ - s.strstart -= s.w_size; - //zmemcpy(s->window, s->window + s->w_size, s->strstart); - s.window.set(s.window.subarray(s.w_size, s.w_size + s.strstart), 0); - if (s.matches < 2) { - s.matches++; /* add a pending slide_hash() */ - } - if (s.insert > s.strstart) { - s.insert = s.strstart; - } - } - //zmemcpy(s->window + s->strstart, s->strm->next_in - used, used); - s.window.set(s.strm.input.subarray(s.strm.next_in - used, s.strm.next_in), s.strstart); - s.strstart += used; - s.insert += used > s.w_size - s.insert ? s.w_size - s.insert : used; - } - s.block_start = s.strstart; - } - if (s.high_water < s.strstart) { - s.high_water = s.strstart; - } - - /* If the last block was written to next_out, then done. */ - if (last) { - return BS_FINISH_DONE; - } - - /* If flushing and all input has been consumed, then done. */ - if (flush !== Z_NO_FLUSH$2 && flush !== Z_FINISH$3 && - s.strm.avail_in === 0 && s.strstart === s.block_start) { - return BS_BLOCK_DONE; - } - - /* Fill the window with any remaining input. */ - have = s.window_size - s.strstart; - if (s.strm.avail_in > have && s.block_start >= s.w_size) { - /* Slide the window down. */ - s.block_start -= s.w_size; - s.strstart -= s.w_size; - //zmemcpy(s->window, s->window + s->w_size, s->strstart); - s.window.set(s.window.subarray(s.w_size, s.w_size + s.strstart), 0); - if (s.matches < 2) { - s.matches++; /* add a pending slide_hash() */ - } - have += s.w_size; /* more space now */ - if (s.insert > s.strstart) { - s.insert = s.strstart; - } - } - if (have > s.strm.avail_in) { - have = s.strm.avail_in; - } - if (have) { - read_buf(s.strm, s.window, s.strstart, have); - s.strstart += have; - s.insert += have > s.w_size - s.insert ? s.w_size - s.insert : have; - } - if (s.high_water < s.strstart) { - s.high_water = s.strstart; - } - - /* There was not enough avail_out to write a complete worthy or flushed - * stored block to next_out. Write a stored block to pending instead, if we - * have enough input for a worthy block, or if flushing and there is enough - * room for the remaining input as a stored block in the pending buffer. - */ - have = (s.bi_valid + 42) >> 3; /* number of header bytes */ - /* maximum stored block length that will fit in pending: */ - have = s.pending_buf_size - have > 65535/* MAX_STORED */ ? 65535/* MAX_STORED */ : s.pending_buf_size - have; - min_block = have > s.w_size ? s.w_size : have; - left = s.strstart - s.block_start; - if (left >= min_block || - ((left || flush === Z_FINISH$3) && flush !== Z_NO_FLUSH$2 && - s.strm.avail_in === 0 && left <= have)) { - len = left > have ? have : left; - last = flush === Z_FINISH$3 && s.strm.avail_in === 0 && - len === left ? 1 : 0; - _tr_stored_block(s, s.block_start, len, last); - s.block_start += len; - flush_pending(s.strm); - } - - /* We've done all we can with the available input and output. */ - return last ? BS_FINISH_STARTED : BS_NEED_MORE; -}; - - -/* =========================================================================== - * Compress as much as possible from the input stream, return the current - * block state. - * This function does not perform lazy evaluation of matches and inserts - * new strings in the dictionary only for unmatched strings or for short - * matches. It is used only for the fast compression options. - */ -const deflate_fast = (s, flush) => { - - let hash_head; /* head of the hash chain */ - let bflush; /* set if current block must be flushed */ - - for (;;) { - /* Make sure that we always have enough lookahead, except - * at the end of the input file. We need MAX_MATCH bytes - * for the next match, plus MIN_MATCH bytes to insert the - * string following the next match. - */ - if (s.lookahead < MIN_LOOKAHEAD) { - fill_window(s); - if (s.lookahead < MIN_LOOKAHEAD && flush === Z_NO_FLUSH$2) { - return BS_NEED_MORE; - } - if (s.lookahead === 0) { - break; /* flush the current block */ - } - } - - /* Insert the string window[strstart .. strstart+2] in the - * dictionary, and set hash_head to the head of the hash chain: - */ - hash_head = 0/*NIL*/; - if (s.lookahead >= MIN_MATCH) { - /*** INSERT_STRING(s, s.strstart, hash_head); ***/ - s.ins_h = HASH(s, s.ins_h, s.window[s.strstart + MIN_MATCH - 1]); - hash_head = s.prev[s.strstart & s.w_mask] = s.head[s.ins_h]; - s.head[s.ins_h] = s.strstart; - /***/ - } - - /* Find the longest match, discarding those <= prev_length. - * At this point we have always match_length < MIN_MATCH - */ - if (hash_head !== 0/*NIL*/ && ((s.strstart - hash_head) <= (s.w_size - MIN_LOOKAHEAD))) { - /* To simplify the code, we prevent matches with the string - * of window index 0 (in particular we have to avoid a match - * of the string with itself at the start of the input file). - */ - s.match_length = longest_match(s, hash_head); - /* longest_match() sets match_start */ - } - if (s.match_length >= MIN_MATCH) { - // check_match(s, s.strstart, s.match_start, s.match_length); // for debug only - - /*** _tr_tally_dist(s, s.strstart - s.match_start, - s.match_length - MIN_MATCH, bflush); ***/ - bflush = _tr_tally(s, s.strstart - s.match_start, s.match_length - MIN_MATCH); - - s.lookahead -= s.match_length; - - /* Insert new strings in the hash table only if the match length - * is not too large. This saves time but degrades compression. - */ - if (s.match_length <= s.max_lazy_match/*max_insert_length*/ && s.lookahead >= MIN_MATCH) { - s.match_length--; /* string at strstart already in table */ - do { - s.strstart++; - /*** INSERT_STRING(s, s.strstart, hash_head); ***/ - s.ins_h = HASH(s, s.ins_h, s.window[s.strstart + MIN_MATCH - 1]); - hash_head = s.prev[s.strstart & s.w_mask] = s.head[s.ins_h]; - s.head[s.ins_h] = s.strstart; - /***/ - /* strstart never exceeds WSIZE-MAX_MATCH, so there are - * always MIN_MATCH bytes ahead. - */ - } while (--s.match_length !== 0); - s.strstart++; - } else - { - s.strstart += s.match_length; - s.match_length = 0; - s.ins_h = s.window[s.strstart]; - /* UPDATE_HASH(s, s.ins_h, s.window[s.strstart+1]); */ - s.ins_h = HASH(s, s.ins_h, s.window[s.strstart + 1]); - -//#if MIN_MATCH != 3 -// Call UPDATE_HASH() MIN_MATCH-3 more times -//#endif - /* If lookahead < MIN_MATCH, ins_h is garbage, but it does not - * matter since it will be recomputed at next deflate call. - */ - } - } else { - /* No match, output a literal byte */ - //Tracevv((stderr,"%c", s.window[s.strstart])); - /*** _tr_tally_lit(s, s.window[s.strstart], bflush); ***/ - bflush = _tr_tally(s, 0, s.window[s.strstart]); - - s.lookahead--; - s.strstart++; - } - if (bflush) { - /*** FLUSH_BLOCK(s, 0); ***/ - flush_block_only(s, false); - if (s.strm.avail_out === 0) { - return BS_NEED_MORE; - } - /***/ - } - } - s.insert = ((s.strstart < (MIN_MATCH - 1)) ? s.strstart : MIN_MATCH - 1); - if (flush === Z_FINISH$3) { - /*** FLUSH_BLOCK(s, 1); ***/ - flush_block_only(s, true); - if (s.strm.avail_out === 0) { - return BS_FINISH_STARTED; - } - /***/ - return BS_FINISH_DONE; - } - if (s.sym_next) { - /*** FLUSH_BLOCK(s, 0); ***/ - flush_block_only(s, false); - if (s.strm.avail_out === 0) { - return BS_NEED_MORE; - } - /***/ - } - return BS_BLOCK_DONE; -}; - -/* =========================================================================== - * Same as above, but achieves better compression. We use a lazy - * evaluation for matches: a match is finally adopted only if there is - * no better match at the next window position. - */ -const deflate_slow = (s, flush) => { - - let hash_head; /* head of hash chain */ - let bflush; /* set if current block must be flushed */ - - let max_insert; - - /* Process the input block. */ - for (;;) { - /* Make sure that we always have enough lookahead, except - * at the end of the input file. We need MAX_MATCH bytes - * for the next match, plus MIN_MATCH bytes to insert the - * string following the next match. - */ - if (s.lookahead < MIN_LOOKAHEAD) { - fill_window(s); - if (s.lookahead < MIN_LOOKAHEAD && flush === Z_NO_FLUSH$2) { - return BS_NEED_MORE; - } - if (s.lookahead === 0) { break; } /* flush the current block */ - } - - /* Insert the string window[strstart .. strstart+2] in the - * dictionary, and set hash_head to the head of the hash chain: - */ - hash_head = 0/*NIL*/; - if (s.lookahead >= MIN_MATCH) { - /*** INSERT_STRING(s, s.strstart, hash_head); ***/ - s.ins_h = HASH(s, s.ins_h, s.window[s.strstart + MIN_MATCH - 1]); - hash_head = s.prev[s.strstart & s.w_mask] = s.head[s.ins_h]; - s.head[s.ins_h] = s.strstart; - /***/ - } - - /* Find the longest match, discarding those <= prev_length. - */ - s.prev_length = s.match_length; - s.prev_match = s.match_start; - s.match_length = MIN_MATCH - 1; - - if (hash_head !== 0/*NIL*/ && s.prev_length < s.max_lazy_match && - s.strstart - hash_head <= (s.w_size - MIN_LOOKAHEAD)/*MAX_DIST(s)*/) { - /* To simplify the code, we prevent matches with the string - * of window index 0 (in particular we have to avoid a match - * of the string with itself at the start of the input file). - */ - s.match_length = longest_match(s, hash_head); - /* longest_match() sets match_start */ - - if (s.match_length <= 5 && - (s.strategy === Z_FILTERED || (s.match_length === MIN_MATCH && s.strstart - s.match_start > 4096/*TOO_FAR*/))) { - - /* If prev_match is also MIN_MATCH, match_start is garbage - * but we will ignore the current match anyway. - */ - s.match_length = MIN_MATCH - 1; - } - } - /* If there was a match at the previous step and the current - * match is not better, output the previous match: - */ - if (s.prev_length >= MIN_MATCH && s.match_length <= s.prev_length) { - max_insert = s.strstart + s.lookahead - MIN_MATCH; - /* Do not insert strings in hash table beyond this. */ - - //check_match(s, s.strstart-1, s.prev_match, s.prev_length); - - /***_tr_tally_dist(s, s.strstart - 1 - s.prev_match, - s.prev_length - MIN_MATCH, bflush);***/ - bflush = _tr_tally(s, s.strstart - 1 - s.prev_match, s.prev_length - MIN_MATCH); - /* Insert in hash table all strings up to the end of the match. - * strstart-1 and strstart are already inserted. If there is not - * enough lookahead, the last two strings are not inserted in - * the hash table. - */ - s.lookahead -= s.prev_length - 1; - s.prev_length -= 2; - do { - if (++s.strstart <= max_insert) { - /*** INSERT_STRING(s, s.strstart, hash_head); ***/ - s.ins_h = HASH(s, s.ins_h, s.window[s.strstart + MIN_MATCH - 1]); - hash_head = s.prev[s.strstart & s.w_mask] = s.head[s.ins_h]; - s.head[s.ins_h] = s.strstart; - /***/ - } - } while (--s.prev_length !== 0); - s.match_available = 0; - s.match_length = MIN_MATCH - 1; - s.strstart++; - - if (bflush) { - /*** FLUSH_BLOCK(s, 0); ***/ - flush_block_only(s, false); - if (s.strm.avail_out === 0) { - return BS_NEED_MORE; - } - /***/ - } - - } else if (s.match_available) { - /* If there was no match at the previous position, output a - * single literal. If there was a match but the current match - * is longer, truncate the previous match to a single literal. - */ - //Tracevv((stderr,"%c", s->window[s->strstart-1])); - /*** _tr_tally_lit(s, s.window[s.strstart-1], bflush); ***/ - bflush = _tr_tally(s, 0, s.window[s.strstart - 1]); - - if (bflush) { - /*** FLUSH_BLOCK_ONLY(s, 0) ***/ - flush_block_only(s, false); - /***/ - } - s.strstart++; - s.lookahead--; - if (s.strm.avail_out === 0) { - return BS_NEED_MORE; - } - } else { - /* There is no previous match to compare with, wait for - * the next step to decide. - */ - s.match_available = 1; - s.strstart++; - s.lookahead--; - } - } - //Assert (flush != Z_NO_FLUSH, "no flush?"); - if (s.match_available) { - //Tracevv((stderr,"%c", s->window[s->strstart-1])); - /*** _tr_tally_lit(s, s.window[s.strstart-1], bflush); ***/ - bflush = _tr_tally(s, 0, s.window[s.strstart - 1]); - - s.match_available = 0; - } - s.insert = s.strstart < MIN_MATCH - 1 ? s.strstart : MIN_MATCH - 1; - if (flush === Z_FINISH$3) { - /*** FLUSH_BLOCK(s, 1); ***/ - flush_block_only(s, true); - if (s.strm.avail_out === 0) { - return BS_FINISH_STARTED; - } - /***/ - return BS_FINISH_DONE; - } - if (s.sym_next) { - /*** FLUSH_BLOCK(s, 0); ***/ - flush_block_only(s, false); - if (s.strm.avail_out === 0) { - return BS_NEED_MORE; - } - /***/ - } - - return BS_BLOCK_DONE; -}; - - -/* =========================================================================== - * For Z_RLE, simply look for runs of bytes, generate matches only of distance - * one. Do not maintain a hash table. (It will be regenerated if this run of - * deflate switches away from Z_RLE.) - */ -const deflate_rle = (s, flush) => { - - let bflush; /* set if current block must be flushed */ - let prev; /* byte at distance one to match */ - let scan, strend; /* scan goes up to strend for length of run */ - - const _win = s.window; - - for (;;) { - /* Make sure that we always have enough lookahead, except - * at the end of the input file. We need MAX_MATCH bytes - * for the longest run, plus one for the unrolled loop. - */ - if (s.lookahead <= MAX_MATCH) { - fill_window(s); - if (s.lookahead <= MAX_MATCH && flush === Z_NO_FLUSH$2) { - return BS_NEED_MORE; - } - if (s.lookahead === 0) { break; } /* flush the current block */ - } - - /* See how many times the previous byte repeats */ - s.match_length = 0; - if (s.lookahead >= MIN_MATCH && s.strstart > 0) { - scan = s.strstart - 1; - prev = _win[scan]; - if (prev === _win[++scan] && prev === _win[++scan] && prev === _win[++scan]) { - strend = s.strstart + MAX_MATCH; - do { - /*jshint noempty:false*/ - } while (prev === _win[++scan] && prev === _win[++scan] && - prev === _win[++scan] && prev === _win[++scan] && - prev === _win[++scan] && prev === _win[++scan] && - prev === _win[++scan] && prev === _win[++scan] && - scan < strend); - s.match_length = MAX_MATCH - (strend - scan); - if (s.match_length > s.lookahead) { - s.match_length = s.lookahead; - } - } - //Assert(scan <= s->window+(uInt)(s->window_size-1), "wild scan"); - } - - /* Emit match if have run of MIN_MATCH or longer, else emit literal */ - if (s.match_length >= MIN_MATCH) { - //check_match(s, s.strstart, s.strstart - 1, s.match_length); - - /*** _tr_tally_dist(s, 1, s.match_length - MIN_MATCH, bflush); ***/ - bflush = _tr_tally(s, 1, s.match_length - MIN_MATCH); - - s.lookahead -= s.match_length; - s.strstart += s.match_length; - s.match_length = 0; - } else { - /* No match, output a literal byte */ - //Tracevv((stderr,"%c", s->window[s->strstart])); - /*** _tr_tally_lit(s, s.window[s.strstart], bflush); ***/ - bflush = _tr_tally(s, 0, s.window[s.strstart]); - - s.lookahead--; - s.strstart++; - } - if (bflush) { - /*** FLUSH_BLOCK(s, 0); ***/ - flush_block_only(s, false); - if (s.strm.avail_out === 0) { - return BS_NEED_MORE; - } - /***/ - } - } - s.insert = 0; - if (flush === Z_FINISH$3) { - /*** FLUSH_BLOCK(s, 1); ***/ - flush_block_only(s, true); - if (s.strm.avail_out === 0) { - return BS_FINISH_STARTED; - } - /***/ - return BS_FINISH_DONE; - } - if (s.sym_next) { - /*** FLUSH_BLOCK(s, 0); ***/ - flush_block_only(s, false); - if (s.strm.avail_out === 0) { - return BS_NEED_MORE; - } - /***/ - } - return BS_BLOCK_DONE; -}; - -/* =========================================================================== - * For Z_HUFFMAN_ONLY, do not look for matches. Do not maintain a hash table. - * (It will be regenerated if this run of deflate switches away from Huffman.) - */ -const deflate_huff = (s, flush) => { - - let bflush; /* set if current block must be flushed */ - - for (;;) { - /* Make sure that we have a literal to write. */ - if (s.lookahead === 0) { - fill_window(s); - if (s.lookahead === 0) { - if (flush === Z_NO_FLUSH$2) { - return BS_NEED_MORE; - } - break; /* flush the current block */ - } - } - - /* Output a literal byte */ - s.match_length = 0; - //Tracevv((stderr,"%c", s->window[s->strstart])); - /*** _tr_tally_lit(s, s.window[s.strstart], bflush); ***/ - bflush = _tr_tally(s, 0, s.window[s.strstart]); - s.lookahead--; - s.strstart++; - if (bflush) { - /*** FLUSH_BLOCK(s, 0); ***/ - flush_block_only(s, false); - if (s.strm.avail_out === 0) { - return BS_NEED_MORE; - } - /***/ - } - } - s.insert = 0; - if (flush === Z_FINISH$3) { - /*** FLUSH_BLOCK(s, 1); ***/ - flush_block_only(s, true); - if (s.strm.avail_out === 0) { - return BS_FINISH_STARTED; - } - /***/ - return BS_FINISH_DONE; - } - if (s.sym_next) { - /*** FLUSH_BLOCK(s, 0); ***/ - flush_block_only(s, false); - if (s.strm.avail_out === 0) { - return BS_NEED_MORE; - } - /***/ - } - return BS_BLOCK_DONE; -}; - -/* Values for max_lazy_match, good_match and max_chain_length, depending on - * the desired pack level (0..9). The values given below have been tuned to - * exclude worst case performance for pathological files. Better values may be - * found for specific files. - */ -function Config(good_length, max_lazy, nice_length, max_chain, func) { - - this.good_length = good_length; - this.max_lazy = max_lazy; - this.nice_length = nice_length; - this.max_chain = max_chain; - this.func = func; -} - -const configuration_table = [ - /* good lazy nice chain */ - new Config(0, 0, 0, 0, deflate_stored), /* 0 store only */ - new Config(4, 4, 8, 4, deflate_fast), /* 1 max speed, no lazy matches */ - new Config(4, 5, 16, 8, deflate_fast), /* 2 */ - new Config(4, 6, 32, 32, deflate_fast), /* 3 */ - - new Config(4, 4, 16, 16, deflate_slow), /* 4 lazy matches */ - new Config(8, 16, 32, 32, deflate_slow), /* 5 */ - new Config(8, 16, 128, 128, deflate_slow), /* 6 */ - new Config(8, 32, 128, 256, deflate_slow), /* 7 */ - new Config(32, 128, 258, 1024, deflate_slow), /* 8 */ - new Config(32, 258, 258, 4096, deflate_slow) /* 9 max compression */ -]; - - -/* =========================================================================== - * Initialize the "longest match" routines for a new zlib stream - */ -const lm_init = (s) => { - - s.window_size = 2 * s.w_size; - - /*** CLEAR_HASH(s); ***/ - zero(s.head); // Fill with NIL (= 0); - - /* Set the default configuration parameters: - */ - s.max_lazy_match = configuration_table[s.level].max_lazy; - s.good_match = configuration_table[s.level].good_length; - s.nice_match = configuration_table[s.level].nice_length; - s.max_chain_length = configuration_table[s.level].max_chain; - - s.strstart = 0; - s.block_start = 0; - s.lookahead = 0; - s.insert = 0; - s.match_length = s.prev_length = MIN_MATCH - 1; - s.match_available = 0; - s.ins_h = 0; -}; - - -function DeflateState() { - this.strm = null; /* pointer back to this zlib stream */ - this.status = 0; /* as the name implies */ - this.pending_buf = null; /* output still pending */ - this.pending_buf_size = 0; /* size of pending_buf */ - this.pending_out = 0; /* next pending byte to output to the stream */ - this.pending = 0; /* nb of bytes in the pending buffer */ - this.wrap = 0; /* bit 0 true for zlib, bit 1 true for gzip */ - this.gzhead = null; /* gzip header information to write */ - this.gzindex = 0; /* where in extra, name, or comment */ - this.method = Z_DEFLATED$2; /* can only be DEFLATED */ - this.last_flush = -1; /* value of flush param for previous deflate call */ - - this.w_size = 0; /* LZ77 window size (32K by default) */ - this.w_bits = 0; /* log2(w_size) (8..16) */ - this.w_mask = 0; /* w_size - 1 */ - - this.window = null; - /* Sliding window. Input bytes are read into the second half of the window, - * and move to the first half later to keep a dictionary of at least wSize - * bytes. With this organization, matches are limited to a distance of - * wSize-MAX_MATCH bytes, but this ensures that IO is always - * performed with a length multiple of the block size. - */ - - this.window_size = 0; - /* Actual size of window: 2*wSize, except when the user input buffer - * is directly used as sliding window. - */ - - this.prev = null; - /* Link to older string with same hash index. To limit the size of this - * array to 64K, this link is maintained only for the last 32K strings. - * An index in this array is thus a window index modulo 32K. - */ - - this.head = null; /* Heads of the hash chains or NIL. */ - - this.ins_h = 0; /* hash index of string to be inserted */ - this.hash_size = 0; /* number of elements in hash table */ - this.hash_bits = 0; /* log2(hash_size) */ - this.hash_mask = 0; /* hash_size-1 */ - - this.hash_shift = 0; - /* Number of bits by which ins_h must be shifted at each input - * step. It must be such that after MIN_MATCH steps, the oldest - * byte no longer takes part in the hash key, that is: - * hash_shift * MIN_MATCH >= hash_bits - */ - - this.block_start = 0; - /* Window position at the beginning of the current output block. Gets - * negative when the window is moved backwards. - */ - - this.match_length = 0; /* length of best match */ - this.prev_match = 0; /* previous match */ - this.match_available = 0; /* set if previous match exists */ - this.strstart = 0; /* start of string to insert */ - this.match_start = 0; /* start of matching string */ - this.lookahead = 0; /* number of valid bytes ahead in window */ - - this.prev_length = 0; - /* Length of the best match at previous step. Matches not greater than this - * are discarded. This is used in the lazy match evaluation. - */ - - this.max_chain_length = 0; - /* To speed up deflation, hash chains are never searched beyond this - * length. A higher limit improves compression ratio but degrades the - * speed. - */ - - this.max_lazy_match = 0; - /* Attempt to find a better match only when the current match is strictly - * smaller than this value. This mechanism is used only for compression - * levels >= 4. - */ - // That's alias to max_lazy_match, don't use directly - //this.max_insert_length = 0; - /* Insert new strings in the hash table only if the match length is not - * greater than this length. This saves time but degrades compression. - * max_insert_length is used only for compression levels <= 3. - */ - - this.level = 0; /* compression level (1..9) */ - this.strategy = 0; /* favor or force Huffman coding*/ - - this.good_match = 0; - /* Use a faster search when the previous match is longer than this */ - - this.nice_match = 0; /* Stop searching when current match exceeds this */ - - /* used by trees.c: */ - - /* Didn't use ct_data typedef below to suppress compiler warning */ - - // struct ct_data_s dyn_ltree[HEAP_SIZE]; /* literal and length tree */ - // struct ct_data_s dyn_dtree[2*D_CODES+1]; /* distance tree */ - // struct ct_data_s bl_tree[2*BL_CODES+1]; /* Huffman tree for bit lengths */ - - // Use flat array of DOUBLE size, with interleaved fata, - // because JS does not support effective - this.dyn_ltree = new Uint16Array(HEAP_SIZE * 2); - this.dyn_dtree = new Uint16Array((2 * D_CODES + 1) * 2); - this.bl_tree = new Uint16Array((2 * BL_CODES + 1) * 2); - zero(this.dyn_ltree); - zero(this.dyn_dtree); - zero(this.bl_tree); - - this.l_desc = null; /* desc. for literal tree */ - this.d_desc = null; /* desc. for distance tree */ - this.bl_desc = null; /* desc. for bit length tree */ - - //ush bl_count[MAX_BITS+1]; - this.bl_count = new Uint16Array(MAX_BITS + 1); - /* number of codes at each bit length for an optimal tree */ - - //int heap[2*L_CODES+1]; /* heap used to build the Huffman trees */ - this.heap = new Uint16Array(2 * L_CODES + 1); /* heap used to build the Huffman trees */ - zero(this.heap); - - this.heap_len = 0; /* number of elements in the heap */ - this.heap_max = 0; /* element of largest frequency */ - /* The sons of heap[n] are heap[2*n] and heap[2*n+1]. heap[0] is not used. - * The same heap array is used to build all trees. - */ - - this.depth = new Uint16Array(2 * L_CODES + 1); //uch depth[2*L_CODES+1]; - zero(this.depth); - /* Depth of each subtree used as tie breaker for trees of equal frequency - */ - - this.sym_buf = 0; /* buffer for distances and literals/lengths */ - - this.lit_bufsize = 0; - /* Size of match buffer for literals/lengths. There are 4 reasons for - * limiting lit_bufsize to 64K: - * - frequencies can be kept in 16 bit counters - * - if compression is not successful for the first block, all input - * data is still in the window so we can still emit a stored block even - * when input comes from standard input. (This can also be done for - * all blocks if lit_bufsize is not greater than 32K.) - * - if compression is not successful for a file smaller than 64K, we can - * even emit a stored file instead of a stored block (saving 5 bytes). - * This is applicable only for zip (not gzip or zlib). - * - creating new Huffman trees less frequently may not provide fast - * adaptation to changes in the input data statistics. (Take for - * example a binary file with poorly compressible code followed by - * a highly compressible string table.) Smaller buffer sizes give - * fast adaptation but have of course the overhead of transmitting - * trees more frequently. - * - I can't count above 4 - */ - - this.sym_next = 0; /* running index in sym_buf */ - this.sym_end = 0; /* symbol table full when sym_next reaches this */ - - this.opt_len = 0; /* bit length of current block with optimal trees */ - this.static_len = 0; /* bit length of current block with static trees */ - this.matches = 0; /* number of string matches in current block */ - this.insert = 0; /* bytes at end of window left to insert */ - - - this.bi_buf = 0; - /* Output buffer. bits are inserted starting at the bottom (least - * significant bits). - */ - this.bi_valid = 0; - /* Number of valid bits in bi_buf. All bits above the last valid bit - * are always zero. - */ - - // Used for window memory init. We safely ignore it for JS. That makes - // sense only for pointers and memory check tools. - //this.high_water = 0; - /* High water mark offset in window for initialized bytes -- bytes above - * this are set to zero in order to avoid memory check warnings when - * longest match routines access bytes past the input. This is then - * updated to the new high water mark. - */ -} - - -/* ========================================================================= - * Check for a valid deflate stream state. Return 0 if ok, 1 if not. - */ -const deflateStateCheck = (strm) => { - - if (!strm) { - return 1; - } - const s = strm.state; - if (!s || s.strm !== strm || (s.status !== INIT_STATE && -//#ifdef GZIP - s.status !== GZIP_STATE && -//#endif - s.status !== EXTRA_STATE && - s.status !== NAME_STATE && - s.status !== COMMENT_STATE && - s.status !== HCRC_STATE && - s.status !== BUSY_STATE && - s.status !== FINISH_STATE)) { - return 1; - } - return 0; -}; - - -const deflateResetKeep = (strm) => { - - if (deflateStateCheck(strm)) { - return err(strm, Z_STREAM_ERROR$2); - } - - strm.total_in = strm.total_out = 0; - strm.data_type = Z_UNKNOWN; - - const s = strm.state; - s.pending = 0; - s.pending_out = 0; - - if (s.wrap < 0) { - s.wrap = -s.wrap; - /* was made negative by deflate(..., Z_FINISH); */ - } - s.status = -//#ifdef GZIP - s.wrap === 2 ? GZIP_STATE : -//#endif - s.wrap ? INIT_STATE : BUSY_STATE; - strm.adler = (s.wrap === 2) ? - 0 // crc32(0, Z_NULL, 0) - : - 1; // adler32(0, Z_NULL, 0) - s.last_flush = -2; - _tr_init(s); - return Z_OK$3; -}; - - -const deflateReset = (strm) => { - - const ret = deflateResetKeep(strm); - if (ret === Z_OK$3) { - lm_init(strm.state); - } - return ret; -}; - - -const deflateSetHeader = (strm, head) => { - - if (deflateStateCheck(strm) || strm.state.wrap !== 2) { - return Z_STREAM_ERROR$2; - } - strm.state.gzhead = head; - return Z_OK$3; -}; - - -const deflateInit2 = (strm, level, method, windowBits, memLevel, strategy) => { - - if (!strm) { // === Z_NULL - return Z_STREAM_ERROR$2; - } - let wrap = 1; - - if (level === Z_DEFAULT_COMPRESSION$1) { - level = 6; - } - - if (windowBits < 0) { /* suppress zlib wrapper */ - wrap = 0; - windowBits = -windowBits; - } - - else if (windowBits > 15) { - wrap = 2; /* write gzip wrapper instead */ - windowBits -= 16; - } - - - if (memLevel < 1 || memLevel > MAX_MEM_LEVEL || method !== Z_DEFLATED$2 || - windowBits < 8 || windowBits > 15 || level < 0 || level > 9 || - strategy < 0 || strategy > Z_FIXED || (windowBits === 8 && wrap !== 1)) { - return err(strm, Z_STREAM_ERROR$2); - } - - - if (windowBits === 8) { - windowBits = 9; - } - /* until 256-byte window bug fixed */ - - const s = new DeflateState(); - - strm.state = s; - s.strm = strm; - s.status = INIT_STATE; /* to pass state test in deflateReset() */ - - s.wrap = wrap; - s.gzhead = null; - s.w_bits = windowBits; - s.w_size = 1 << s.w_bits; - s.w_mask = s.w_size - 1; - - s.hash_bits = memLevel + 7; - s.hash_size = 1 << s.hash_bits; - s.hash_mask = s.hash_size - 1; - s.hash_shift = ~~((s.hash_bits + MIN_MATCH - 1) / MIN_MATCH); - - s.window = new Uint8Array(s.w_size * 2); - s.head = new Uint16Array(s.hash_size); - s.prev = new Uint16Array(s.w_size); - - // Don't need mem init magic for JS. - //s.high_water = 0; /* nothing written to s->window yet */ - - s.lit_bufsize = 1 << (memLevel + 6); /* 16K elements by default */ - - /* We overlay pending_buf and sym_buf. This works since the average size - * for length/distance pairs over any compressed block is assured to be 31 - * bits or less. - * - * Analysis: The longest fixed codes are a length code of 8 bits plus 5 - * extra bits, for lengths 131 to 257. The longest fixed distance codes are - * 5 bits plus 13 extra bits, for distances 16385 to 32768. The longest - * possible fixed-codes length/distance pair is then 31 bits total. - * - * sym_buf starts one-fourth of the way into pending_buf. So there are - * three bytes in sym_buf for every four bytes in pending_buf. Each symbol - * in sym_buf is three bytes -- two for the distance and one for the - * literal/length. As each symbol is consumed, the pointer to the next - * sym_buf value to read moves forward three bytes. From that symbol, up to - * 31 bits are written to pending_buf. The closest the written pending_buf - * bits gets to the next sym_buf symbol to read is just before the last - * code is written. At that time, 31*(n-2) bits have been written, just - * after 24*(n-2) bits have been consumed from sym_buf. sym_buf starts at - * 8*n bits into pending_buf. (Note that the symbol buffer fills when n-1 - * symbols are written.) The closest the writing gets to what is unread is - * then n+14 bits. Here n is lit_bufsize, which is 16384 by default, and - * can range from 128 to 32768. - * - * Therefore, at a minimum, there are 142 bits of space between what is - * written and what is read in the overlain buffers, so the symbols cannot - * be overwritten by the compressed data. That space is actually 139 bits, - * due to the three-bit fixed-code block header. - * - * That covers the case where either Z_FIXED is specified, forcing fixed - * codes, or when the use of fixed codes is chosen, because that choice - * results in a smaller compressed block than dynamic codes. That latter - * condition then assures that the above analysis also covers all dynamic - * blocks. A dynamic-code block will only be chosen to be emitted if it has - * fewer bits than a fixed-code block would for the same set of symbols. - * Therefore its average symbol length is assured to be less than 31. So - * the compressed data for a dynamic block also cannot overwrite the - * symbols from which it is being constructed. - */ - - s.pending_buf_size = s.lit_bufsize * 4; - s.pending_buf = new Uint8Array(s.pending_buf_size); - - // It is offset from `s.pending_buf` (size is `s.lit_bufsize * 2`) - //s->sym_buf = s->pending_buf + s->lit_bufsize; - s.sym_buf = s.lit_bufsize; - - //s->sym_end = (s->lit_bufsize - 1) * 3; - s.sym_end = (s.lit_bufsize - 1) * 3; - /* We avoid equality with lit_bufsize*3 because of wraparound at 64K - * on 16 bit machines and because stored blocks are restricted to - * 64K-1 bytes. - */ - - s.level = level; - s.strategy = strategy; - s.method = method; - - return deflateReset(strm); -}; - -const deflateInit = (strm, level) => { - - return deflateInit2(strm, level, Z_DEFLATED$2, MAX_WBITS$1, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY$1); -}; - - -/* ========================================================================= */ -const deflate$2 = (strm, flush) => { - - if (deflateStateCheck(strm) || flush > Z_BLOCK$1 || flush < 0) { - return strm ? err(strm, Z_STREAM_ERROR$2) : Z_STREAM_ERROR$2; - } - - const s = strm.state; - - if (!strm.output || - (strm.avail_in !== 0 && !strm.input) || - (s.status === FINISH_STATE && flush !== Z_FINISH$3)) { - return err(strm, (strm.avail_out === 0) ? Z_BUF_ERROR$1 : Z_STREAM_ERROR$2); - } - - const old_flush = s.last_flush; - s.last_flush = flush; - - /* Flush as much pending output as possible */ - if (s.pending !== 0) { - flush_pending(strm); - if (strm.avail_out === 0) { - /* Since avail_out is 0, deflate will be called again with - * more output space, but possibly with both pending and - * avail_in equal to zero. There won't be anything to do, - * but this is not an error situation so make sure we - * return OK instead of BUF_ERROR at next call of deflate: - */ - s.last_flush = -1; - return Z_OK$3; - } - - /* Make sure there is something to do and avoid duplicate consecutive - * flushes. For repeated and useless calls with Z_FINISH, we keep - * returning Z_STREAM_END instead of Z_BUF_ERROR. - */ - } else if (strm.avail_in === 0 && rank(flush) <= rank(old_flush) && - flush !== Z_FINISH$3) { - return err(strm, Z_BUF_ERROR$1); - } - - /* User must not provide more input after the first FINISH: */ - if (s.status === FINISH_STATE && strm.avail_in !== 0) { - return err(strm, Z_BUF_ERROR$1); - } - - /* Write the header */ - if (s.status === INIT_STATE && s.wrap === 0) { - s.status = BUSY_STATE; - } - if (s.status === INIT_STATE) { - /* zlib header */ - let header = (Z_DEFLATED$2 + ((s.w_bits - 8) << 4)) << 8; - let level_flags = -1; - - if (s.strategy >= Z_HUFFMAN_ONLY || s.level < 2) { - level_flags = 0; - } else if (s.level < 6) { - level_flags = 1; - } else if (s.level === 6) { - level_flags = 2; - } else { - level_flags = 3; - } - header |= (level_flags << 6); - if (s.strstart !== 0) { header |= PRESET_DICT; } - header += 31 - (header % 31); - - putShortMSB(s, header); - - /* Save the adler32 of the preset dictionary: */ - if (s.strstart !== 0) { - putShortMSB(s, strm.adler >>> 16); - putShortMSB(s, strm.adler & 0xffff); - } - strm.adler = 1; // adler32(0L, Z_NULL, 0); - s.status = BUSY_STATE; - - /* Compression must start with an empty pending buffer */ - flush_pending(strm); - if (s.pending !== 0) { - s.last_flush = -1; - return Z_OK$3; - } - } -//#ifdef GZIP - if (s.status === GZIP_STATE) { - /* gzip header */ - strm.adler = 0; //crc32(0L, Z_NULL, 0); - put_byte(s, 31); - put_byte(s, 139); - put_byte(s, 8); - if (!s.gzhead) { // s->gzhead == Z_NULL - put_byte(s, 0); - put_byte(s, 0); - put_byte(s, 0); - put_byte(s, 0); - put_byte(s, 0); - put_byte(s, s.level === 9 ? 2 : - (s.strategy >= Z_HUFFMAN_ONLY || s.level < 2 ? - 4 : 0)); - put_byte(s, OS_CODE); - s.status = BUSY_STATE; - - /* Compression must start with an empty pending buffer */ - flush_pending(strm); - if (s.pending !== 0) { - s.last_flush = -1; - return Z_OK$3; - } - } - else { - put_byte(s, (s.gzhead.text ? 1 : 0) + - (s.gzhead.hcrc ? 2 : 0) + - (!s.gzhead.extra ? 0 : 4) + - (!s.gzhead.name ? 0 : 8) + - (!s.gzhead.comment ? 0 : 16) - ); - put_byte(s, s.gzhead.time & 0xff); - put_byte(s, (s.gzhead.time >> 8) & 0xff); - put_byte(s, (s.gzhead.time >> 16) & 0xff); - put_byte(s, (s.gzhead.time >> 24) & 0xff); - put_byte(s, s.level === 9 ? 2 : - (s.strategy >= Z_HUFFMAN_ONLY || s.level < 2 ? - 4 : 0)); - put_byte(s, s.gzhead.os & 0xff); - if (s.gzhead.extra && s.gzhead.extra.length) { - put_byte(s, s.gzhead.extra.length & 0xff); - put_byte(s, (s.gzhead.extra.length >> 8) & 0xff); - } - if (s.gzhead.hcrc) { - strm.adler = crc32_1(strm.adler, s.pending_buf, s.pending, 0); - } - s.gzindex = 0; - s.status = EXTRA_STATE; - } - } - if (s.status === EXTRA_STATE) { - if (s.gzhead.extra/* != Z_NULL*/) { - let beg = s.pending; /* start of bytes to update crc */ - let left = (s.gzhead.extra.length & 0xffff) - s.gzindex; - while (s.pending + left > s.pending_buf_size) { - let copy = s.pending_buf_size - s.pending; - // zmemcpy(s.pending_buf + s.pending, - // s.gzhead.extra + s.gzindex, copy); - s.pending_buf.set(s.gzhead.extra.subarray(s.gzindex, s.gzindex + copy), s.pending); - s.pending = s.pending_buf_size; - //--- HCRC_UPDATE(beg) ---// - if (s.gzhead.hcrc && s.pending > beg) { - strm.adler = crc32_1(strm.adler, s.pending_buf, s.pending - beg, beg); - } - //---// - s.gzindex += copy; - flush_pending(strm); - if (s.pending !== 0) { - s.last_flush = -1; - return Z_OK$3; - } - beg = 0; - left -= copy; - } - // JS specific: s.gzhead.extra may be TypedArray or Array for backward compatibility - // TypedArray.slice and TypedArray.from don't exist in IE10-IE11 - let gzhead_extra = new Uint8Array(s.gzhead.extra); - // zmemcpy(s->pending_buf + s->pending, - // s->gzhead->extra + s->gzindex, left); - s.pending_buf.set(gzhead_extra.subarray(s.gzindex, s.gzindex + left), s.pending); - s.pending += left; - //--- HCRC_UPDATE(beg) ---// - if (s.gzhead.hcrc && s.pending > beg) { - strm.adler = crc32_1(strm.adler, s.pending_buf, s.pending - beg, beg); - } - //---// - s.gzindex = 0; - } - s.status = NAME_STATE; - } - if (s.status === NAME_STATE) { - if (s.gzhead.name/* != Z_NULL*/) { - let beg = s.pending; /* start of bytes to update crc */ - let val; - do { - if (s.pending === s.pending_buf_size) { - //--- HCRC_UPDATE(beg) ---// - if (s.gzhead.hcrc && s.pending > beg) { - strm.adler = crc32_1(strm.adler, s.pending_buf, s.pending - beg, beg); - } - //---// - flush_pending(strm); - if (s.pending !== 0) { - s.last_flush = -1; - return Z_OK$3; - } - beg = 0; - } - // JS specific: little magic to add zero terminator to end of string - if (s.gzindex < s.gzhead.name.length) { - val = s.gzhead.name.charCodeAt(s.gzindex++) & 0xff; - } else { - val = 0; - } - put_byte(s, val); - } while (val !== 0); - //--- HCRC_UPDATE(beg) ---// - if (s.gzhead.hcrc && s.pending > beg) { - strm.adler = crc32_1(strm.adler, s.pending_buf, s.pending - beg, beg); - } - //---// - s.gzindex = 0; - } - s.status = COMMENT_STATE; - } - if (s.status === COMMENT_STATE) { - if (s.gzhead.comment/* != Z_NULL*/) { - let beg = s.pending; /* start of bytes to update crc */ - let val; - do { - if (s.pending === s.pending_buf_size) { - //--- HCRC_UPDATE(beg) ---// - if (s.gzhead.hcrc && s.pending > beg) { - strm.adler = crc32_1(strm.adler, s.pending_buf, s.pending - beg, beg); - } - //---// - flush_pending(strm); - if (s.pending !== 0) { - s.last_flush = -1; - return Z_OK$3; - } - beg = 0; - } - // JS specific: little magic to add zero terminator to end of string - if (s.gzindex < s.gzhead.comment.length) { - val = s.gzhead.comment.charCodeAt(s.gzindex++) & 0xff; - } else { - val = 0; - } - put_byte(s, val); - } while (val !== 0); - //--- HCRC_UPDATE(beg) ---// - if (s.gzhead.hcrc && s.pending > beg) { - strm.adler = crc32_1(strm.adler, s.pending_buf, s.pending - beg, beg); - } - //---// - } - s.status = HCRC_STATE; - } - if (s.status === HCRC_STATE) { - if (s.gzhead.hcrc) { - if (s.pending + 2 > s.pending_buf_size) { - flush_pending(strm); - if (s.pending !== 0) { - s.last_flush = -1; - return Z_OK$3; - } - } - put_byte(s, strm.adler & 0xff); - put_byte(s, (strm.adler >> 8) & 0xff); - strm.adler = 0; //crc32(0L, Z_NULL, 0); - } - s.status = BUSY_STATE; - - /* Compression must start with an empty pending buffer */ - flush_pending(strm); - if (s.pending !== 0) { - s.last_flush = -1; - return Z_OK$3; - } - } -//#endif - - /* Start a new block or continue the current one. - */ - if (strm.avail_in !== 0 || s.lookahead !== 0 || - (flush !== Z_NO_FLUSH$2 && s.status !== FINISH_STATE)) { - let bstate = s.level === 0 ? deflate_stored(s, flush) : - s.strategy === Z_HUFFMAN_ONLY ? deflate_huff(s, flush) : - s.strategy === Z_RLE ? deflate_rle(s, flush) : - configuration_table[s.level].func(s, flush); - - if (bstate === BS_FINISH_STARTED || bstate === BS_FINISH_DONE) { - s.status = FINISH_STATE; - } - if (bstate === BS_NEED_MORE || bstate === BS_FINISH_STARTED) { - if (strm.avail_out === 0) { - s.last_flush = -1; - /* avoid BUF_ERROR next call, see above */ - } - return Z_OK$3; - /* If flush != Z_NO_FLUSH && avail_out == 0, the next call - * of deflate should use the same flush parameter to make sure - * that the flush is complete. So we don't have to output an - * empty block here, this will be done at next call. This also - * ensures that for a very small output buffer, we emit at most - * one empty block. - */ - } - if (bstate === BS_BLOCK_DONE) { - if (flush === Z_PARTIAL_FLUSH) { - _tr_align(s); - } - else if (flush !== Z_BLOCK$1) { /* FULL_FLUSH or SYNC_FLUSH */ - - _tr_stored_block(s, 0, 0, false); - /* For a full flush, this empty block will be recognized - * as a special marker by inflate_sync(). - */ - if (flush === Z_FULL_FLUSH$1) { - /*** CLEAR_HASH(s); ***/ /* forget history */ - zero(s.head); // Fill with NIL (= 0); - - if (s.lookahead === 0) { - s.strstart = 0; - s.block_start = 0; - s.insert = 0; - } - } - } - flush_pending(strm); - if (strm.avail_out === 0) { - s.last_flush = -1; /* avoid BUF_ERROR at next call, see above */ - return Z_OK$3; - } - } - } - - if (flush !== Z_FINISH$3) { return Z_OK$3; } - if (s.wrap <= 0) { return Z_STREAM_END$3; } - - /* Write the trailer */ - if (s.wrap === 2) { - put_byte(s, strm.adler & 0xff); - put_byte(s, (strm.adler >> 8) & 0xff); - put_byte(s, (strm.adler >> 16) & 0xff); - put_byte(s, (strm.adler >> 24) & 0xff); - put_byte(s, strm.total_in & 0xff); - put_byte(s, (strm.total_in >> 8) & 0xff); - put_byte(s, (strm.total_in >> 16) & 0xff); - put_byte(s, (strm.total_in >> 24) & 0xff); - } - else - { - putShortMSB(s, strm.adler >>> 16); - putShortMSB(s, strm.adler & 0xffff); - } - - flush_pending(strm); - /* If avail_out is zero, the application will call deflate again - * to flush the rest. - */ - if (s.wrap > 0) { s.wrap = -s.wrap; } - /* write the trailer only once! */ - return s.pending !== 0 ? Z_OK$3 : Z_STREAM_END$3; -}; - - -const deflateEnd = (strm) => { - - if (deflateStateCheck(strm)) { - return Z_STREAM_ERROR$2; - } - - const status = strm.state.status; - - strm.state = null; - - return status === BUSY_STATE ? err(strm, Z_DATA_ERROR$2) : Z_OK$3; -}; - - -/* ========================================================================= - * Initializes the compression dictionary from the given byte - * sequence without producing any compressed output. - */ -const deflateSetDictionary = (strm, dictionary) => { - - let dictLength = dictionary.length; - - if (deflateStateCheck(strm)) { - return Z_STREAM_ERROR$2; - } - - const s = strm.state; - const wrap = s.wrap; - - if (wrap === 2 || (wrap === 1 && s.status !== INIT_STATE) || s.lookahead) { - return Z_STREAM_ERROR$2; - } - - /* when using zlib wrappers, compute Adler-32 for provided dictionary */ - if (wrap === 1) { - /* adler32(strm->adler, dictionary, dictLength); */ - strm.adler = adler32_1(strm.adler, dictionary, dictLength, 0); - } - - s.wrap = 0; /* avoid computing Adler-32 in read_buf */ - - /* if dictionary would fill window, just replace the history */ - if (dictLength >= s.w_size) { - if (wrap === 0) { /* already empty otherwise */ - /*** CLEAR_HASH(s); ***/ - zero(s.head); // Fill with NIL (= 0); - s.strstart = 0; - s.block_start = 0; - s.insert = 0; - } - /* use the tail */ - // dictionary = dictionary.slice(dictLength - s.w_size); - let tmpDict = new Uint8Array(s.w_size); - tmpDict.set(dictionary.subarray(dictLength - s.w_size, dictLength), 0); - dictionary = tmpDict; - dictLength = s.w_size; - } - /* insert dictionary into window and hash */ - const avail = strm.avail_in; - const next = strm.next_in; - const input = strm.input; - strm.avail_in = dictLength; - strm.next_in = 0; - strm.input = dictionary; - fill_window(s); - while (s.lookahead >= MIN_MATCH) { - let str = s.strstart; - let n = s.lookahead - (MIN_MATCH - 1); - do { - /* UPDATE_HASH(s, s->ins_h, s->window[str + MIN_MATCH-1]); */ - s.ins_h = HASH(s, s.ins_h, s.window[str + MIN_MATCH - 1]); - - s.prev[str & s.w_mask] = s.head[s.ins_h]; - - s.head[s.ins_h] = str; - str++; - } while (--n); - s.strstart = str; - s.lookahead = MIN_MATCH - 1; - fill_window(s); - } - s.strstart += s.lookahead; - s.block_start = s.strstart; - s.insert = s.lookahead; - s.lookahead = 0; - s.match_length = s.prev_length = MIN_MATCH - 1; - s.match_available = 0; - strm.next_in = next; - strm.input = input; - strm.avail_in = avail; - s.wrap = wrap; - return Z_OK$3; -}; - - -var deflateInit_1 = deflateInit; -var deflateInit2_1 = deflateInit2; -var deflateReset_1 = deflateReset; -var deflateResetKeep_1 = deflateResetKeep; -var deflateSetHeader_1 = deflateSetHeader; -var deflate_2$1 = deflate$2; -var deflateEnd_1 = deflateEnd; -var deflateSetDictionary_1 = deflateSetDictionary; -var deflateInfo = 'pako deflate (from Nodeca project)'; - -/* Not implemented -module.exports.deflateBound = deflateBound; -module.exports.deflateCopy = deflateCopy; -module.exports.deflateGetDictionary = deflateGetDictionary; -module.exports.deflateParams = deflateParams; -module.exports.deflatePending = deflatePending; -module.exports.deflatePrime = deflatePrime; -module.exports.deflateTune = deflateTune; -*/ - -var deflate_1$2 = { - deflateInit: deflateInit_1, - deflateInit2: deflateInit2_1, - deflateReset: deflateReset_1, - deflateResetKeep: deflateResetKeep_1, - deflateSetHeader: deflateSetHeader_1, - deflate: deflate_2$1, - deflateEnd: deflateEnd_1, - deflateSetDictionary: deflateSetDictionary_1, - deflateInfo: deflateInfo -}; - -const _has = (obj, key) => { - return Object.prototype.hasOwnProperty.call(obj, key); -}; - -var assign = function (obj /*from1, from2, from3, ...*/) { - const sources = Array.prototype.slice.call(arguments, 1); - while (sources.length) { - const source = sources.shift(); - if (!source) { continue; } - - if (typeof source !== 'object') { - throw new TypeError(source + 'must be non-object'); - } - - for (const p in source) { - if (_has(source, p)) { - obj[p] = source[p]; - } - } - } - - return obj; -}; - - -// Join array of chunks to single array. -var flattenChunks = (chunks) => { - // calculate data length - let len = 0; - - for (let i = 0, l = chunks.length; i < l; i++) { - len += chunks[i].length; - } - - // join chunks - const result = new Uint8Array(len); - - for (let i = 0, pos = 0, l = chunks.length; i < l; i++) { - let chunk = chunks[i]; - result.set(chunk, pos); - pos += chunk.length; - } - - return result; -}; - -var common = { - assign: assign, - flattenChunks: flattenChunks -}; - -// String encode/decode helpers - - -// Quick check if we can use fast array to bin string conversion -// -// - apply(Array) can fail on Android 2.2 -// - apply(Uint8Array) can fail on iOS 5.1 Safari -// -let STR_APPLY_UIA_OK = true; - -try { String.fromCharCode.apply(null, new Uint8Array(1)); } catch (__) { STR_APPLY_UIA_OK = false; } - - -// Table with utf8 lengths (calculated by first byte of sequence) -// Note, that 5 & 6-byte values and some 4-byte values can not be represented in JS, -// because max possible codepoint is 0x10ffff -const _utf8len = new Uint8Array(256); -for (let q = 0; q < 256; q++) { - _utf8len[q] = (q >= 252 ? 6 : q >= 248 ? 5 : q >= 240 ? 4 : q >= 224 ? 3 : q >= 192 ? 2 : 1); -} -_utf8len[254] = _utf8len[254] = 1; // Invalid sequence start - - -// convert string to array (typed, when possible) -var string2buf = (str) => { - if (typeof TextEncoder === 'function' && TextEncoder.prototype.encode) { - return new TextEncoder().encode(str); - } - - let buf, c, c2, m_pos, i, str_len = str.length, buf_len = 0; - - // count binary size - for (m_pos = 0; m_pos < str_len; m_pos++) { - c = str.charCodeAt(m_pos); - if ((c & 0xfc00) === 0xd800 && (m_pos + 1 < str_len)) { - c2 = str.charCodeAt(m_pos + 1); - if ((c2 & 0xfc00) === 0xdc00) { - c = 0x10000 + ((c - 0xd800) << 10) + (c2 - 0xdc00); - m_pos++; - } - } - buf_len += c < 0x80 ? 1 : c < 0x800 ? 2 : c < 0x10000 ? 3 : 4; - } - - // allocate buffer - buf = new Uint8Array(buf_len); - - // convert - for (i = 0, m_pos = 0; i < buf_len; m_pos++) { - c = str.charCodeAt(m_pos); - if ((c & 0xfc00) === 0xd800 && (m_pos + 1 < str_len)) { - c2 = str.charCodeAt(m_pos + 1); - if ((c2 & 0xfc00) === 0xdc00) { - c = 0x10000 + ((c - 0xd800) << 10) + (c2 - 0xdc00); - m_pos++; - } - } - if (c < 0x80) { - /* one byte */ - buf[i++] = c; - } else if (c < 0x800) { - /* two bytes */ - buf[i++] = 0xC0 | (c >>> 6); - buf[i++] = 0x80 | (c & 0x3f); - } else if (c < 0x10000) { - /* three bytes */ - buf[i++] = 0xE0 | (c >>> 12); - buf[i++] = 0x80 | (c >>> 6 & 0x3f); - buf[i++] = 0x80 | (c & 0x3f); - } else { - /* four bytes */ - buf[i++] = 0xf0 | (c >>> 18); - buf[i++] = 0x80 | (c >>> 12 & 0x3f); - buf[i++] = 0x80 | (c >>> 6 & 0x3f); - buf[i++] = 0x80 | (c & 0x3f); - } - } - - return buf; -}; - -// Helper -const buf2binstring = (buf, len) => { - // On Chrome, the arguments in a function call that are allowed is `65534`. - // If the length of the buffer is smaller than that, we can use this optimization, - // otherwise we will take a slower path. - if (len < 65534) { - if (buf.subarray && STR_APPLY_UIA_OK) { - return String.fromCharCode.apply(null, buf.length === len ? buf : buf.subarray(0, len)); - } - } - - let result = ''; - for (let i = 0; i < len; i++) { - result += String.fromCharCode(buf[i]); - } - return result; -}; - - -// convert array to string -var buf2string = (buf, max) => { - const len = max || buf.length; - - if (typeof TextDecoder === 'function' && TextDecoder.prototype.decode) { - return new TextDecoder().decode(buf.subarray(0, max)); - } - - let i, out; - - // Reserve max possible length (2 words per char) - // NB: by unknown reasons, Array is significantly faster for - // String.fromCharCode.apply than Uint16Array. - const utf16buf = new Array(len * 2); - - for (out = 0, i = 0; i < len;) { - let c = buf[i++]; - // quick process ascii - if (c < 0x80) { utf16buf[out++] = c; continue; } - - let c_len = _utf8len[c]; - // skip 5 & 6 byte codes - if (c_len > 4) { utf16buf[out++] = 0xfffd; i += c_len - 1; continue; } - - // apply mask on first byte - c &= c_len === 2 ? 0x1f : c_len === 3 ? 0x0f : 0x07; - // join the rest - while (c_len > 1 && i < len) { - c = (c << 6) | (buf[i++] & 0x3f); - c_len--; - } - - // terminated by end of string? - if (c_len > 1) { utf16buf[out++] = 0xfffd; continue; } - - if (c < 0x10000) { - utf16buf[out++] = c; - } else { - c -= 0x10000; - utf16buf[out++] = 0xd800 | ((c >> 10) & 0x3ff); - utf16buf[out++] = 0xdc00 | (c & 0x3ff); - } - } - - return buf2binstring(utf16buf, out); -}; - - -// Calculate max possible position in utf8 buffer, -// that will not break sequence. If that's not possible -// - (very small limits) return max size as is. -// -// buf[] - utf8 bytes array -// max - length limit (mandatory); -var utf8border = (buf, max) => { - - max = max || buf.length; - if (max > buf.length) { max = buf.length; } - - // go back from last position, until start of sequence found - let pos = max - 1; - while (pos >= 0 && (buf[pos] & 0xC0) === 0x80) { pos--; } - - // Very small and broken sequence, - // return max, because we should return something anyway. - if (pos < 0) { return max; } - - // If we came to start of buffer - that means buffer is too small, - // return max too. - if (pos === 0) { return max; } - - return (pos + _utf8len[buf[pos]] > max) ? pos : max; -}; - -var strings = { - string2buf: string2buf, - buf2string: buf2string, - utf8border: utf8border -}; - -// (C) 1995-2013 Jean-loup Gailly and Mark Adler -// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin -// -// This software is provided 'as-is', without any express or implied -// warranty. In no event will the authors be held liable for any damages -// arising from the use of this software. -// -// Permission is granted to anyone to use this software for any purpose, -// including commercial applications, and to alter it and redistribute it -// freely, subject to the following restrictions: -// -// 1. The origin of this software must not be misrepresented; you must not -// claim that you wrote the original software. If you use this software -// in a product, an acknowledgment in the product documentation would be -// appreciated but is not required. -// 2. Altered source versions must be plainly marked as such, and must not be -// misrepresented as being the original software. -// 3. This notice may not be removed or altered from any source distribution. - -function ZStream() { - /* next input byte */ - this.input = null; // JS specific, because we have no pointers - this.next_in = 0; - /* number of bytes available at input */ - this.avail_in = 0; - /* total number of input bytes read so far */ - this.total_in = 0; - /* next output byte should be put there */ - this.output = null; // JS specific, because we have no pointers - this.next_out = 0; - /* remaining free space at output */ - this.avail_out = 0; - /* total number of bytes output so far */ - this.total_out = 0; - /* last error message, NULL if no error */ - this.msg = ''/*Z_NULL*/; - /* not visible by applications */ - this.state = null; - /* best guess about the data type: binary or text */ - this.data_type = 2/*Z_UNKNOWN*/; - /* adler32 value of the uncompressed data */ - this.adler = 0; -} - -var zstream = ZStream; - -const toString$1 = Object.prototype.toString; - -/* Public constants ==========================================================*/ -/* ===========================================================================*/ - -const { - Z_NO_FLUSH: Z_NO_FLUSH$1, Z_SYNC_FLUSH, Z_FULL_FLUSH, Z_FINISH: Z_FINISH$2, - Z_OK: Z_OK$2, Z_STREAM_END: Z_STREAM_END$2, - Z_DEFAULT_COMPRESSION, - Z_DEFAULT_STRATEGY, - Z_DEFLATED: Z_DEFLATED$1 -} = constants$2; - -/* ===========================================================================*/ - - -/** - * class Deflate - * - * Generic JS-style wrapper for zlib calls. If you don't need - * streaming behaviour - use more simple functions: [[deflate]], - * [[deflateRaw]] and [[gzip]]. - **/ - -/* internal - * Deflate.chunks -> Array - * - * Chunks of output data, if [[Deflate#onData]] not overridden. - **/ - -/** - * Deflate.result -> Uint8Array - * - * Compressed result, generated by default [[Deflate#onData]] - * and [[Deflate#onEnd]] handlers. Filled after you push last chunk - * (call [[Deflate#push]] with `Z_FINISH` / `true` param). - **/ - -/** - * Deflate.err -> Number - * - * Error code after deflate finished. 0 (Z_OK) on success. - * You will not need it in real life, because deflate errors - * are possible only on wrong options or bad `onData` / `onEnd` - * custom handlers. - **/ - -/** - * Deflate.msg -> String - * - * Error message, if [[Deflate.err]] != 0 - **/ - - -/** - * new Deflate(options) - * - options (Object): zlib deflate options. - * - * Creates new deflator instance with specified params. Throws exception - * on bad params. Supported options: - * - * - `level` - * - `windowBits` - * - `memLevel` - * - `strategy` - * - `dictionary` - * - * [http://zlib.net/manual.html#Advanced](http://zlib.net/manual.html#Advanced) - * for more information on these. - * - * Additional options, for internal needs: - * - * - `chunkSize` - size of generated data chunks (16K by default) - * - `raw` (Boolean) - do raw deflate - * - `gzip` (Boolean) - create gzip wrapper - * - `header` (Object) - custom header for gzip - * - `text` (Boolean) - true if compressed data believed to be text - * - `time` (Number) - modification time, unix timestamp - * - `os` (Number) - operation system code - * - `extra` (Array) - array of bytes with extra data (max 65536) - * - `name` (String) - file name (binary string) - * - `comment` (String) - comment (binary string) - * - `hcrc` (Boolean) - true if header crc should be added - * - * ##### Example: - * - * ```javascript - * const pako = require('pako') - * , chunk1 = new Uint8Array([1,2,3,4,5,6,7,8,9]) - * , chunk2 = new Uint8Array([10,11,12,13,14,15,16,17,18,19]); - * - * const deflate = new pako.Deflate({ level: 3}); - * - * deflate.push(chunk1, false); - * deflate.push(chunk2, true); // true -> last chunk - * - * if (deflate.err) { throw new Error(deflate.err); } - * - * console.log(deflate.result); - * ``` - **/ -function Deflate$1(options) { - this.options = common.assign({ - level: Z_DEFAULT_COMPRESSION, - method: Z_DEFLATED$1, - chunkSize: 16384, - windowBits: 15, - memLevel: 8, - strategy: Z_DEFAULT_STRATEGY - }, options || {}); - - let opt = this.options; - - if (opt.raw && (opt.windowBits > 0)) { - opt.windowBits = -opt.windowBits; - } - - else if (opt.gzip && (opt.windowBits > 0) && (opt.windowBits < 16)) { - opt.windowBits += 16; - } - - this.err = 0; // error code, if happens (0 = Z_OK) - this.msg = ''; // error message - this.ended = false; // used to avoid multiple onEnd() calls - this.chunks = []; // chunks of compressed data - - this.strm = new zstream(); - this.strm.avail_out = 0; - - let status = deflate_1$2.deflateInit2( - this.strm, - opt.level, - opt.method, - opt.windowBits, - opt.memLevel, - opt.strategy - ); - - if (status !== Z_OK$2) { - throw new Error(messages[status]); - } - - if (opt.header) { - deflate_1$2.deflateSetHeader(this.strm, opt.header); - } - - if (opt.dictionary) { - let dict; - // Convert data if needed - if (typeof opt.dictionary === 'string') { - // If we need to compress text, change encoding to utf8. - dict = strings.string2buf(opt.dictionary); - } else if (toString$1.call(opt.dictionary) === '[object ArrayBuffer]') { - dict = new Uint8Array(opt.dictionary); - } else { - dict = opt.dictionary; - } - - status = deflate_1$2.deflateSetDictionary(this.strm, dict); - - if (status !== Z_OK$2) { - throw new Error(messages[status]); - } - - this._dict_set = true; - } -} - -/** - * Deflate#push(data[, flush_mode]) -> Boolean - * - data (Uint8Array|ArrayBuffer|String): input data. Strings will be - * converted to utf8 byte sequence. - * - flush_mode (Number|Boolean): 0..6 for corresponding Z_NO_FLUSH..Z_TREE modes. - * See constants. Skipped or `false` means Z_NO_FLUSH, `true` means Z_FINISH. - * - * Sends input data to deflate pipe, generating [[Deflate#onData]] calls with - * new compressed chunks. Returns `true` on success. The last data block must - * have `flush_mode` Z_FINISH (or `true`). That will flush internal pending - * buffers and call [[Deflate#onEnd]]. - * - * On fail call [[Deflate#onEnd]] with error code and return false. - * - * ##### Example - * - * ```javascript - * push(chunk, false); // push one of data chunks - * ... - * push(chunk, true); // push last chunk - * ``` - **/ -Deflate$1.prototype.push = function (data, flush_mode) { - const strm = this.strm; - const chunkSize = this.options.chunkSize; - let status, _flush_mode; - - if (this.ended) { return false; } - - if (flush_mode === ~~flush_mode) _flush_mode = flush_mode; - else _flush_mode = flush_mode === true ? Z_FINISH$2 : Z_NO_FLUSH$1; - - // Convert data if needed - if (typeof data === 'string') { - // If we need to compress text, change encoding to utf8. - strm.input = strings.string2buf(data); - } else if (toString$1.call(data) === '[object ArrayBuffer]') { - strm.input = new Uint8Array(data); - } else { - strm.input = data; - } - - strm.next_in = 0; - strm.avail_in = strm.input.length; - - for (;;) { - if (strm.avail_out === 0) { - strm.output = new Uint8Array(chunkSize); - strm.next_out = 0; - strm.avail_out = chunkSize; - } - - // Make sure avail_out > 6 to avoid repeating markers - if ((_flush_mode === Z_SYNC_FLUSH || _flush_mode === Z_FULL_FLUSH) && strm.avail_out <= 6) { - this.onData(strm.output.subarray(0, strm.next_out)); - strm.avail_out = 0; - continue; - } - - status = deflate_1$2.deflate(strm, _flush_mode); - - // Ended => flush and finish - if (status === Z_STREAM_END$2) { - if (strm.next_out > 0) { - this.onData(strm.output.subarray(0, strm.next_out)); - } - status = deflate_1$2.deflateEnd(this.strm); - this.onEnd(status); - this.ended = true; - return status === Z_OK$2; - } - - // Flush if out buffer full - if (strm.avail_out === 0) { - this.onData(strm.output); - continue; - } - - // Flush if requested and has data - if (_flush_mode > 0 && strm.next_out > 0) { - this.onData(strm.output.subarray(0, strm.next_out)); - strm.avail_out = 0; - continue; - } - - if (strm.avail_in === 0) break; - } - - return true; -}; - - -/** - * Deflate#onData(chunk) -> Void - * - chunk (Uint8Array): output data. - * - * By default, stores data blocks in `chunks[]` property and glue - * those in `onEnd`. Override this handler, if you need another behaviour. - **/ -Deflate$1.prototype.onData = function (chunk) { - this.chunks.push(chunk); -}; - - -/** - * Deflate#onEnd(status) -> Void - * - status (Number): deflate status. 0 (Z_OK) on success, - * other if not. - * - * Called once after you tell deflate that the input stream is - * complete (Z_FINISH). By default - join collected chunks, - * free memory and fill `results` / `err` properties. - **/ -Deflate$1.prototype.onEnd = function (status) { - // On success - join - if (status === Z_OK$2) { - this.result = common.flattenChunks(this.chunks); - } - this.chunks = []; - this.err = status; - this.msg = this.strm.msg; -}; - - -/** - * deflate(data[, options]) -> Uint8Array - * - data (Uint8Array|ArrayBuffer|String): input data to compress. - * - options (Object): zlib deflate options. - * - * Compress `data` with deflate algorithm and `options`. - * - * Supported options are: - * - * - level - * - windowBits - * - memLevel - * - strategy - * - dictionary - * - * [http://zlib.net/manual.html#Advanced](http://zlib.net/manual.html#Advanced) - * for more information on these. - * - * Sugar (options): - * - * - `raw` (Boolean) - say that we work with raw stream, if you don't wish to specify - * negative windowBits implicitly. - * - * ##### Example: - * - * ```javascript - * const pako = require('pako') - * const data = new Uint8Array([1,2,3,4,5,6,7,8,9]); - * - * console.log(pako.deflate(data)); - * ``` - **/ -function deflate$1(input, options) { - const deflator = new Deflate$1(options); - - deflator.push(input, true); - - // That will never happens, if you don't cheat with options :) - if (deflator.err) { throw deflator.msg || messages[deflator.err]; } - - return deflator.result; -} - - -/** - * deflateRaw(data[, options]) -> Uint8Array - * - data (Uint8Array|ArrayBuffer|String): input data to compress. - * - options (Object): zlib deflate options. - * - * The same as [[deflate]], but creates raw data, without wrapper - * (header and adler32 crc). - **/ -function deflateRaw$1(input, options) { - options = options || {}; - options.raw = true; - return deflate$1(input, options); -} - - -/** - * gzip(data[, options]) -> Uint8Array - * - data (Uint8Array|ArrayBuffer|String): input data to compress. - * - options (Object): zlib deflate options. - * - * The same as [[deflate]], but create gzip wrapper instead of - * deflate one. - **/ -function gzip$1(input, options) { - options = options || {}; - options.gzip = true; - return deflate$1(input, options); -} - - -var Deflate_1$1 = Deflate$1; -var deflate_2 = deflate$1; -var deflateRaw_1$1 = deflateRaw$1; -var gzip_1$1 = gzip$1; -var constants$1 = constants$2; - -var deflate_1$1 = { - Deflate: Deflate_1$1, - deflate: deflate_2, - deflateRaw: deflateRaw_1$1, - gzip: gzip_1$1, - constants: constants$1 -}; - -// (C) 1995-2013 Jean-loup Gailly and Mark Adler -// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin -// -// This software is provided 'as-is', without any express or implied -// warranty. In no event will the authors be held liable for any damages -// arising from the use of this software. -// -// Permission is granted to anyone to use this software for any purpose, -// including commercial applications, and to alter it and redistribute it -// freely, subject to the following restrictions: -// -// 1. The origin of this software must not be misrepresented; you must not -// claim that you wrote the original software. If you use this software -// in a product, an acknowledgment in the product documentation would be -// appreciated but is not required. -// 2. Altered source versions must be plainly marked as such, and must not be -// misrepresented as being the original software. -// 3. This notice may not be removed or altered from any source distribution. - -// See state defs from inflate.js -const BAD$1 = 16209; /* got a data error -- remain here until reset */ -const TYPE$1 = 16191; /* i: waiting for type bits, including last-flag bit */ - -/* - Decode literal, length, and distance codes and write out the resulting - literal and match bytes until either not enough input or output is - available, an end-of-block is encountered, or a data error is encountered. - When large enough input and output buffers are supplied to inflate(), for - example, a 16K input buffer and a 64K output buffer, more than 95% of the - inflate execution time is spent in this routine. - - Entry assumptions: - - state.mode === LEN - strm.avail_in >= 6 - strm.avail_out >= 258 - start >= strm.avail_out - state.bits < 8 - - On return, state.mode is one of: - - LEN -- ran out of enough output space or enough available input - TYPE -- reached end of block code, inflate() to interpret next block - BAD -- error in block data - - Notes: - - - The maximum input bits used by a length/distance pair is 15 bits for the - length code, 5 bits for the length extra, 15 bits for the distance code, - and 13 bits for the distance extra. This totals 48 bits, or six bytes. - Therefore if strm.avail_in >= 6, then there is enough input to avoid - checking for available input while decoding. - - - The maximum bytes that a single length/distance pair can output is 258 - bytes, which is the maximum length that can be coded. inflate_fast() - requires strm.avail_out >= 258 for each loop to avoid checking for - output space. - */ -var inffast = function inflate_fast(strm, start) { - let _in; /* local strm.input */ - let last; /* have enough input while in < last */ - let _out; /* local strm.output */ - let beg; /* inflate()'s initial strm.output */ - let end; /* while out < end, enough space available */ -//#ifdef INFLATE_STRICT - let dmax; /* maximum distance from zlib header */ -//#endif - let wsize; /* window size or zero if not using window */ - let whave; /* valid bytes in the window */ - let wnext; /* window write index */ - // Use `s_window` instead `window`, avoid conflict with instrumentation tools - let s_window; /* allocated sliding window, if wsize != 0 */ - let hold; /* local strm.hold */ - let bits; /* local strm.bits */ - let lcode; /* local strm.lencode */ - let dcode; /* local strm.distcode */ - let lmask; /* mask for first level of length codes */ - let dmask; /* mask for first level of distance codes */ - let here; /* retrieved table entry */ - let op; /* code bits, operation, extra bits, or */ - /* window position, window bytes to copy */ - let len; /* match length, unused bytes */ - let dist; /* match distance */ - let from; /* where to copy match from */ - let from_source; - - - let input, output; // JS specific, because we have no pointers - - /* copy state to local variables */ - const state = strm.state; - //here = state.here; - _in = strm.next_in; - input = strm.input; - last = _in + (strm.avail_in - 5); - _out = strm.next_out; - output = strm.output; - beg = _out - (start - strm.avail_out); - end = _out + (strm.avail_out - 257); -//#ifdef INFLATE_STRICT - dmax = state.dmax; -//#endif - wsize = state.wsize; - whave = state.whave; - wnext = state.wnext; - s_window = state.window; - hold = state.hold; - bits = state.bits; - lcode = state.lencode; - dcode = state.distcode; - lmask = (1 << state.lenbits) - 1; - dmask = (1 << state.distbits) - 1; - - - /* decode literals and length/distances until end-of-block or not enough - input data or output space */ - - top: - do { - if (bits < 15) { - hold += input[_in++] << bits; - bits += 8; - hold += input[_in++] << bits; - bits += 8; - } - - here = lcode[hold & lmask]; - - dolen: - for (;;) { // Goto emulation - op = here >>> 24/*here.bits*/; - hold >>>= op; - bits -= op; - op = (here >>> 16) & 0xff/*here.op*/; - if (op === 0) { /* literal */ - //Tracevv((stderr, here.val >= 0x20 && here.val < 0x7f ? - // "inflate: literal '%c'\n" : - // "inflate: literal 0x%02x\n", here.val)); - output[_out++] = here & 0xffff/*here.val*/; - } - else if (op & 16) { /* length base */ - len = here & 0xffff/*here.val*/; - op &= 15; /* number of extra bits */ - if (op) { - if (bits < op) { - hold += input[_in++] << bits; - bits += 8; - } - len += hold & ((1 << op) - 1); - hold >>>= op; - bits -= op; - } - //Tracevv((stderr, "inflate: length %u\n", len)); - if (bits < 15) { - hold += input[_in++] << bits; - bits += 8; - hold += input[_in++] << bits; - bits += 8; - } - here = dcode[hold & dmask]; - - dodist: - for (;;) { // goto emulation - op = here >>> 24/*here.bits*/; - hold >>>= op; - bits -= op; - op = (here >>> 16) & 0xff/*here.op*/; - - if (op & 16) { /* distance base */ - dist = here & 0xffff/*here.val*/; - op &= 15; /* number of extra bits */ - if (bits < op) { - hold += input[_in++] << bits; - bits += 8; - if (bits < op) { - hold += input[_in++] << bits; - bits += 8; - } - } - dist += hold & ((1 << op) - 1); -//#ifdef INFLATE_STRICT - if (dist > dmax) { - strm.msg = 'invalid distance too far back'; - state.mode = BAD$1; - break top; - } -//#endif - hold >>>= op; - bits -= op; - //Tracevv((stderr, "inflate: distance %u\n", dist)); - op = _out - beg; /* max distance in output */ - if (dist > op) { /* see if copy from window */ - op = dist - op; /* distance back in window */ - if (op > whave) { - if (state.sane) { - strm.msg = 'invalid distance too far back'; - state.mode = BAD$1; - break top; - } - -// (!) This block is disabled in zlib defaults, -// don't enable it for binary compatibility -//#ifdef INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR -// if (len <= op - whave) { -// do { -// output[_out++] = 0; -// } while (--len); -// continue top; -// } -// len -= op - whave; -// do { -// output[_out++] = 0; -// } while (--op > whave); -// if (op === 0) { -// from = _out - dist; -// do { -// output[_out++] = output[from++]; -// } while (--len); -// continue top; -// } -//#endif - } - from = 0; // window index - from_source = s_window; - if (wnext === 0) { /* very common case */ - from += wsize - op; - if (op < len) { /* some from window */ - len -= op; - do { - output[_out++] = s_window[from++]; - } while (--op); - from = _out - dist; /* rest from output */ - from_source = output; - } - } - else if (wnext < op) { /* wrap around window */ - from += wsize + wnext - op; - op -= wnext; - if (op < len) { /* some from end of window */ - len -= op; - do { - output[_out++] = s_window[from++]; - } while (--op); - from = 0; - if (wnext < len) { /* some from start of window */ - op = wnext; - len -= op; - do { - output[_out++] = s_window[from++]; - } while (--op); - from = _out - dist; /* rest from output */ - from_source = output; - } - } - } - else { /* contiguous in window */ - from += wnext - op; - if (op < len) { /* some from window */ - len -= op; - do { - output[_out++] = s_window[from++]; - } while (--op); - from = _out - dist; /* rest from output */ - from_source = output; - } - } - while (len > 2) { - output[_out++] = from_source[from++]; - output[_out++] = from_source[from++]; - output[_out++] = from_source[from++]; - len -= 3; - } - if (len) { - output[_out++] = from_source[from++]; - if (len > 1) { - output[_out++] = from_source[from++]; - } - } - } - else { - from = _out - dist; /* copy direct from output */ - do { /* minimum length is three */ - output[_out++] = output[from++]; - output[_out++] = output[from++]; - output[_out++] = output[from++]; - len -= 3; - } while (len > 2); - if (len) { - output[_out++] = output[from++]; - if (len > 1) { - output[_out++] = output[from++]; - } - } - } - } - else if ((op & 64) === 0) { /* 2nd level distance code */ - here = dcode[(here & 0xffff)/*here.val*/ + (hold & ((1 << op) - 1))]; - continue dodist; - } - else { - strm.msg = 'invalid distance code'; - state.mode = BAD$1; - break top; - } - - break; // need to emulate goto via "continue" - } - } - else if ((op & 64) === 0) { /* 2nd level length code */ - here = lcode[(here & 0xffff)/*here.val*/ + (hold & ((1 << op) - 1))]; - continue dolen; - } - else if (op & 32) { /* end-of-block */ - //Tracevv((stderr, "inflate: end of block\n")); - state.mode = TYPE$1; - break top; - } - else { - strm.msg = 'invalid literal/length code'; - state.mode = BAD$1; - break top; - } - - break; // need to emulate goto via "continue" - } - } while (_in < last && _out < end); - - /* return unused bytes (on entry, bits < 8, so in won't go too far back) */ - len = bits >> 3; - _in -= len; - bits -= len << 3; - hold &= (1 << bits) - 1; - - /* update state and return */ - strm.next_in = _in; - strm.next_out = _out; - strm.avail_in = (_in < last ? 5 + (last - _in) : 5 - (_in - last)); - strm.avail_out = (_out < end ? 257 + (end - _out) : 257 - (_out - end)); - state.hold = hold; - state.bits = bits; - return; -}; - -// (C) 1995-2013 Jean-loup Gailly and Mark Adler -// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin -// -// This software is provided 'as-is', without any express or implied -// warranty. In no event will the authors be held liable for any damages -// arising from the use of this software. -// -// Permission is granted to anyone to use this software for any purpose, -// including commercial applications, and to alter it and redistribute it -// freely, subject to the following restrictions: -// -// 1. The origin of this software must not be misrepresented; you must not -// claim that you wrote the original software. If you use this software -// in a product, an acknowledgment in the product documentation would be -// appreciated but is not required. -// 2. Altered source versions must be plainly marked as such, and must not be -// misrepresented as being the original software. -// 3. This notice may not be removed or altered from any source distribution. - -const MAXBITS = 15; -const ENOUGH_LENS$1 = 852; -const ENOUGH_DISTS$1 = 592; -//const ENOUGH = (ENOUGH_LENS+ENOUGH_DISTS); - -const CODES$1 = 0; -const LENS$1 = 1; -const DISTS$1 = 2; - -const lbase = new Uint16Array([ /* Length codes 257..285 base */ - 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, - 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0 -]); - -const lext = new Uint8Array([ /* Length codes 257..285 extra */ - 16, 16, 16, 16, 16, 16, 16, 16, 17, 17, 17, 17, 18, 18, 18, 18, - 19, 19, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 16, 72, 78 -]); - -const dbase = new Uint16Array([ /* Distance codes 0..29 base */ - 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, - 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, - 8193, 12289, 16385, 24577, 0, 0 -]); - -const dext = new Uint8Array([ /* Distance codes 0..29 extra */ - 16, 16, 16, 16, 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, - 23, 23, 24, 24, 25, 25, 26, 26, 27, 27, - 28, 28, 29, 29, 64, 64 -]); - -const inflate_table = (type, lens, lens_index, codes, table, table_index, work, opts) => -{ - const bits = opts.bits; - //here = opts.here; /* table entry for duplication */ - - let len = 0; /* a code's length in bits */ - let sym = 0; /* index of code symbols */ - let min = 0, max = 0; /* minimum and maximum code lengths */ - let root = 0; /* number of index bits for root table */ - let curr = 0; /* number of index bits for current table */ - let drop = 0; /* code bits to drop for sub-table */ - let left = 0; /* number of prefix codes available */ - let used = 0; /* code entries in table used */ - let huff = 0; /* Huffman code */ - let incr; /* for incrementing code, index */ - let fill; /* index for replicating entries */ - let low; /* low bits for current root entry */ - let mask; /* mask for low root bits */ - let next; /* next available space in table */ - let base = null; /* base value table to use */ -// let shoextra; /* extra bits table to use */ - let match; /* use base and extra for symbol >= match */ - const count = new Uint16Array(MAXBITS + 1); //[MAXBITS+1]; /* number of codes of each length */ - const offs = new Uint16Array(MAXBITS + 1); //[MAXBITS+1]; /* offsets in table for each length */ - let extra = null; - - let here_bits, here_op, here_val; - - /* - Process a set of code lengths to create a canonical Huffman code. The - code lengths are lens[0..codes-1]. Each length corresponds to the - symbols 0..codes-1. The Huffman code is generated by first sorting the - symbols by length from short to long, and retaining the symbol order - for codes with equal lengths. Then the code starts with all zero bits - for the first code of the shortest length, and the codes are integer - increments for the same length, and zeros are appended as the length - increases. For the deflate format, these bits are stored backwards - from their more natural integer increment ordering, and so when the - decoding tables are built in the large loop below, the integer codes - are incremented backwards. - - This routine assumes, but does not check, that all of the entries in - lens[] are in the range 0..MAXBITS. The caller must assure this. - 1..MAXBITS is interpreted as that code length. zero means that that - symbol does not occur in this code. - - The codes are sorted by computing a count of codes for each length, - creating from that a table of starting indices for each length in the - sorted table, and then entering the symbols in order in the sorted - table. The sorted table is work[], with that space being provided by - the caller. - - The length counts are used for other purposes as well, i.e. finding - the minimum and maximum length codes, determining if there are any - codes at all, checking for a valid set of lengths, and looking ahead - at length counts to determine sub-table sizes when building the - decoding tables. - */ - - /* accumulate lengths for codes (assumes lens[] all in 0..MAXBITS) */ - for (len = 0; len <= MAXBITS; len++) { - count[len] = 0; - } - for (sym = 0; sym < codes; sym++) { - count[lens[lens_index + sym]]++; - } - - /* bound code lengths, force root to be within code lengths */ - root = bits; - for (max = MAXBITS; max >= 1; max--) { - if (count[max] !== 0) { break; } - } - if (root > max) { - root = max; - } - if (max === 0) { /* no symbols to code at all */ - //table.op[opts.table_index] = 64; //here.op = (var char)64; /* invalid code marker */ - //table.bits[opts.table_index] = 1; //here.bits = (var char)1; - //table.val[opts.table_index++] = 0; //here.val = (var short)0; - table[table_index++] = (1 << 24) | (64 << 16) | 0; - - - //table.op[opts.table_index] = 64; - //table.bits[opts.table_index] = 1; - //table.val[opts.table_index++] = 0; - table[table_index++] = (1 << 24) | (64 << 16) | 0; - - opts.bits = 1; - return 0; /* no symbols, but wait for decoding to report error */ - } - for (min = 1; min < max; min++) { - if (count[min] !== 0) { break; } - } - if (root < min) { - root = min; - } - - /* check for an over-subscribed or incomplete set of lengths */ - left = 1; - for (len = 1; len <= MAXBITS; len++) { - left <<= 1; - left -= count[len]; - if (left < 0) { - return -1; - } /* over-subscribed */ - } - if (left > 0 && (type === CODES$1 || max !== 1)) { - return -1; /* incomplete set */ - } - - /* generate offsets into symbol table for each length for sorting */ - offs[1] = 0; - for (len = 1; len < MAXBITS; len++) { - offs[len + 1] = offs[len] + count[len]; - } - - /* sort symbols by length, by symbol order within each length */ - for (sym = 0; sym < codes; sym++) { - if (lens[lens_index + sym] !== 0) { - work[offs[lens[lens_index + sym]]++] = sym; - } - } - - /* - Create and fill in decoding tables. In this loop, the table being - filled is at next and has curr index bits. The code being used is huff - with length len. That code is converted to an index by dropping drop - bits off of the bottom. For codes where len is less than drop + curr, - those top drop + curr - len bits are incremented through all values to - fill the table with replicated entries. - - root is the number of index bits for the root table. When len exceeds - root, sub-tables are created pointed to by the root entry with an index - of the low root bits of huff. This is saved in low to check for when a - new sub-table should be started. drop is zero when the root table is - being filled, and drop is root when sub-tables are being filled. - - When a new sub-table is needed, it is necessary to look ahead in the - code lengths to determine what size sub-table is needed. The length - counts are used for this, and so count[] is decremented as codes are - entered in the tables. - - used keeps track of how many table entries have been allocated from the - provided *table space. It is checked for LENS and DIST tables against - the constants ENOUGH_LENS and ENOUGH_DISTS to guard against changes in - the initial root table size constants. See the comments in inftrees.h - for more information. - - sym increments through all symbols, and the loop terminates when - all codes of length max, i.e. all codes, have been processed. This - routine permits incomplete codes, so another loop after this one fills - in the rest of the decoding tables with invalid code markers. - */ - - /* set up for code type */ - // poor man optimization - use if-else instead of switch, - // to avoid deopts in old v8 - if (type === CODES$1) { - base = extra = work; /* dummy value--not used */ - match = 20; - - } else if (type === LENS$1) { - base = lbase; - extra = lext; - match = 257; - - } else { /* DISTS */ - base = dbase; - extra = dext; - match = 0; - } - - /* initialize opts for loop */ - huff = 0; /* starting code */ - sym = 0; /* starting code symbol */ - len = min; /* starting code length */ - next = table_index; /* current table to fill in */ - curr = root; /* current table index bits */ - drop = 0; /* current bits to drop from code for index */ - low = -1; /* trigger new sub-table when len > root */ - used = 1 << root; /* use root table entries */ - mask = used - 1; /* mask for comparing low */ - - /* check available table space */ - if ((type === LENS$1 && used > ENOUGH_LENS$1) || - (type === DISTS$1 && used > ENOUGH_DISTS$1)) { - return 1; - } - - /* process all codes and make table entries */ - for (;;) { - /* create table entry */ - here_bits = len - drop; - if (work[sym] + 1 < match) { - here_op = 0; - here_val = work[sym]; - } - else if (work[sym] >= match) { - here_op = extra[work[sym] - match]; - here_val = base[work[sym] - match]; - } - else { - here_op = 32 + 64; /* end of block */ - here_val = 0; - } - - /* replicate for those indices with low len bits equal to huff */ - incr = 1 << (len - drop); - fill = 1 << curr; - min = fill; /* save offset to next table */ - do { - fill -= incr; - table[next + (huff >> drop) + fill] = (here_bits << 24) | (here_op << 16) | here_val |0; - } while (fill !== 0); - - /* backwards increment the len-bit code huff */ - incr = 1 << (len - 1); - while (huff & incr) { - incr >>= 1; - } - if (incr !== 0) { - huff &= incr - 1; - huff += incr; - } else { - huff = 0; - } - - /* go to next symbol, update count, len */ - sym++; - if (--count[len] === 0) { - if (len === max) { break; } - len = lens[lens_index + work[sym]]; - } - - /* create new sub-table if needed */ - if (len > root && (huff & mask) !== low) { - /* if first time, transition to sub-tables */ - if (drop === 0) { - drop = root; - } - - /* increment past last table */ - next += min; /* here min is 1 << curr */ - - /* determine length of next table */ - curr = len - drop; - left = 1 << curr; - while (curr + drop < max) { - left -= count[curr + drop]; - if (left <= 0) { break; } - curr++; - left <<= 1; - } - - /* check for enough space */ - used += 1 << curr; - if ((type === LENS$1 && used > ENOUGH_LENS$1) || - (type === DISTS$1 && used > ENOUGH_DISTS$1)) { - return 1; - } - - /* point entry in root table to sub-table */ - low = huff & mask; - /*table.op[low] = curr; - table.bits[low] = root; - table.val[low] = next - opts.table_index;*/ - table[low] = (root << 24) | (curr << 16) | (next - table_index) |0; - } - } - - /* fill in remaining table entry if code is incomplete (guaranteed to have - at most one remaining entry, since if the code is incomplete, the - maximum code length that was allowed to get this far is one bit) */ - if (huff !== 0) { - //table.op[next + huff] = 64; /* invalid code marker */ - //table.bits[next + huff] = len - drop; - //table.val[next + huff] = 0; - table[next + huff] = ((len - drop) << 24) | (64 << 16) |0; - } - - /* set return parameters */ - //opts.table_index += used; - opts.bits = root; - return 0; -}; - - -var inftrees = inflate_table; - -// (C) 1995-2013 Jean-loup Gailly and Mark Adler -// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin -// -// This software is provided 'as-is', without any express or implied -// warranty. In no event will the authors be held liable for any damages -// arising from the use of this software. -// -// Permission is granted to anyone to use this software for any purpose, -// including commercial applications, and to alter it and redistribute it -// freely, subject to the following restrictions: -// -// 1. The origin of this software must not be misrepresented; you must not -// claim that you wrote the original software. If you use this software -// in a product, an acknowledgment in the product documentation would be -// appreciated but is not required. -// 2. Altered source versions must be plainly marked as such, and must not be -// misrepresented as being the original software. -// 3. This notice may not be removed or altered from any source distribution. - - - - - - -const CODES = 0; -const LENS = 1; -const DISTS = 2; - -/* Public constants ==========================================================*/ -/* ===========================================================================*/ - -const { - Z_FINISH: Z_FINISH$1, Z_BLOCK, Z_TREES, - Z_OK: Z_OK$1, Z_STREAM_END: Z_STREAM_END$1, Z_NEED_DICT: Z_NEED_DICT$1, Z_STREAM_ERROR: Z_STREAM_ERROR$1, Z_DATA_ERROR: Z_DATA_ERROR$1, Z_MEM_ERROR: Z_MEM_ERROR$1, Z_BUF_ERROR, - Z_DEFLATED -} = constants$2; - - -/* STATES ====================================================================*/ -/* ===========================================================================*/ - - -const HEAD = 16180; /* i: waiting for magic header */ -const FLAGS = 16181; /* i: waiting for method and flags (gzip) */ -const TIME = 16182; /* i: waiting for modification time (gzip) */ -const OS = 16183; /* i: waiting for extra flags and operating system (gzip) */ -const EXLEN = 16184; /* i: waiting for extra length (gzip) */ -const EXTRA = 16185; /* i: waiting for extra bytes (gzip) */ -const NAME = 16186; /* i: waiting for end of file name (gzip) */ -const COMMENT = 16187; /* i: waiting for end of comment (gzip) */ -const HCRC = 16188; /* i: waiting for header crc (gzip) */ -const DICTID = 16189; /* i: waiting for dictionary check value */ -const DICT = 16190; /* waiting for inflateSetDictionary() call */ -const TYPE = 16191; /* i: waiting for type bits, including last-flag bit */ -const TYPEDO = 16192; /* i: same, but skip check to exit inflate on new block */ -const STORED = 16193; /* i: waiting for stored size (length and complement) */ -const COPY_ = 16194; /* i/o: same as COPY below, but only first time in */ -const COPY = 16195; /* i/o: waiting for input or output to copy stored block */ -const TABLE = 16196; /* i: waiting for dynamic block table lengths */ -const LENLENS = 16197; /* i: waiting for code length code lengths */ -const CODELENS = 16198; /* i: waiting for length/lit and distance code lengths */ -const LEN_ = 16199; /* i: same as LEN below, but only first time in */ -const LEN = 16200; /* i: waiting for length/lit/eob code */ -const LENEXT = 16201; /* i: waiting for length extra bits */ -const DIST = 16202; /* i: waiting for distance code */ -const DISTEXT = 16203; /* i: waiting for distance extra bits */ -const MATCH = 16204; /* o: waiting for output space to copy string */ -const LIT = 16205; /* o: waiting for output space to write literal */ -const CHECK = 16206; /* i: waiting for 32-bit check value */ -const LENGTH = 16207; /* i: waiting for 32-bit length (gzip) */ -const DONE = 16208; /* finished check, done -- remain here until reset */ -const BAD = 16209; /* got a data error -- remain here until reset */ -const MEM = 16210; /* got an inflate() memory error -- remain here until reset */ -const SYNC = 16211; /* looking for synchronization bytes to restart inflate() */ - -/* ===========================================================================*/ - - - -const ENOUGH_LENS = 852; -const ENOUGH_DISTS = 592; -//const ENOUGH = (ENOUGH_LENS+ENOUGH_DISTS); - -const MAX_WBITS = 15; -/* 32K LZ77 window */ -const DEF_WBITS = MAX_WBITS; - - -const zswap32 = (q) => { - - return (((q >>> 24) & 0xff) + - ((q >>> 8) & 0xff00) + - ((q & 0xff00) << 8) + - ((q & 0xff) << 24)); -}; - - -function InflateState() { - this.strm = null; /* pointer back to this zlib stream */ - this.mode = 0; /* current inflate mode */ - this.last = false; /* true if processing last block */ - this.wrap = 0; /* bit 0 true for zlib, bit 1 true for gzip, - bit 2 true to validate check value */ - this.havedict = false; /* true if dictionary provided */ - this.flags = 0; /* gzip header method and flags (0 if zlib), or - -1 if raw or no header yet */ - this.dmax = 0; /* zlib header max distance (INFLATE_STRICT) */ - this.check = 0; /* protected copy of check value */ - this.total = 0; /* protected copy of output count */ - // TODO: may be {} - this.head = null; /* where to save gzip header information */ - - /* sliding window */ - this.wbits = 0; /* log base 2 of requested window size */ - this.wsize = 0; /* window size or zero if not using window */ - this.whave = 0; /* valid bytes in the window */ - this.wnext = 0; /* window write index */ - this.window = null; /* allocated sliding window, if needed */ - - /* bit accumulator */ - this.hold = 0; /* input bit accumulator */ - this.bits = 0; /* number of bits in "in" */ - - /* for string and stored block copying */ - this.length = 0; /* literal or length of data to copy */ - this.offset = 0; /* distance back to copy string from */ - - /* for table and code decoding */ - this.extra = 0; /* extra bits needed */ - - /* fixed and dynamic code tables */ - this.lencode = null; /* starting table for length/literal codes */ - this.distcode = null; /* starting table for distance codes */ - this.lenbits = 0; /* index bits for lencode */ - this.distbits = 0; /* index bits for distcode */ - - /* dynamic table building */ - this.ncode = 0; /* number of code length code lengths */ - this.nlen = 0; /* number of length code lengths */ - this.ndist = 0; /* number of distance code lengths */ - this.have = 0; /* number of code lengths in lens[] */ - this.next = null; /* next available space in codes[] */ - - this.lens = new Uint16Array(320); /* temporary storage for code lengths */ - this.work = new Uint16Array(288); /* work area for code table building */ - - /* - because we don't have pointers in js, we use lencode and distcode directly - as buffers so we don't need codes - */ - //this.codes = new Int32Array(ENOUGH); /* space for code tables */ - this.lendyn = null; /* dynamic table for length/literal codes (JS specific) */ - this.distdyn = null; /* dynamic table for distance codes (JS specific) */ - this.sane = 0; /* if false, allow invalid distance too far */ - this.back = 0; /* bits back of last unprocessed length/lit */ - this.was = 0; /* initial length of match */ -} - - -const inflateStateCheck = (strm) => { - - if (!strm) { - return 1; - } - const state = strm.state; - if (!state || state.strm !== strm || - state.mode < HEAD || state.mode > SYNC) { - return 1; - } - return 0; -}; - - -const inflateResetKeep = (strm) => { - - if (inflateStateCheck(strm)) { return Z_STREAM_ERROR$1; } - const state = strm.state; - strm.total_in = strm.total_out = state.total = 0; - strm.msg = ''; /*Z_NULL*/ - if (state.wrap) { /* to support ill-conceived Java test suite */ - strm.adler = state.wrap & 1; - } - state.mode = HEAD; - state.last = 0; - state.havedict = 0; - state.flags = -1; - state.dmax = 32768; - state.head = null/*Z_NULL*/; - state.hold = 0; - state.bits = 0; - //state.lencode = state.distcode = state.next = state.codes; - state.lencode = state.lendyn = new Int32Array(ENOUGH_LENS); - state.distcode = state.distdyn = new Int32Array(ENOUGH_DISTS); - - state.sane = 1; - state.back = -1; - //Tracev((stderr, "inflate: reset\n")); - return Z_OK$1; -}; - - -const inflateReset = (strm) => { - - if (inflateStateCheck(strm)) { return Z_STREAM_ERROR$1; } - const state = strm.state; - state.wsize = 0; - state.whave = 0; - state.wnext = 0; - return inflateResetKeep(strm); - -}; - - -const inflateReset2 = (strm, windowBits) => { - let wrap; - - /* get the state */ - if (inflateStateCheck(strm)) { return Z_STREAM_ERROR$1; } - const state = strm.state; - - /* extract wrap request from windowBits parameter */ - if (windowBits < 0) { - wrap = 0; - windowBits = -windowBits; - } - else { - wrap = (windowBits >> 4) + 5; - if (windowBits < 48) { - windowBits &= 15; - } - } - - /* set number of window bits, free window if different */ - if (windowBits && (windowBits < 8 || windowBits > 15)) { - return Z_STREAM_ERROR$1; - } - if (state.window !== null && state.wbits !== windowBits) { - state.window = null; - } - - /* update state and reset the rest of it */ - state.wrap = wrap; - state.wbits = windowBits; - return inflateReset(strm); -}; - - -const inflateInit2 = (strm, windowBits) => { - - if (!strm) { return Z_STREAM_ERROR$1; } - //strm.msg = Z_NULL; /* in case we return an error */ - - const state = new InflateState(); - - //if (state === Z_NULL) return Z_MEM_ERROR; - //Tracev((stderr, "inflate: allocated\n")); - strm.state = state; - state.strm = strm; - state.window = null/*Z_NULL*/; - state.mode = HEAD; /* to pass state test in inflateReset2() */ - const ret = inflateReset2(strm, windowBits); - if (ret !== Z_OK$1) { - strm.state = null/*Z_NULL*/; - } - return ret; -}; - - -const inflateInit = (strm) => { - - return inflateInit2(strm, DEF_WBITS); -}; - - -/* - Return state with length and distance decoding tables and index sizes set to - fixed code decoding. Normally this returns fixed tables from inffixed.h. - If BUILDFIXED is defined, then instead this routine builds the tables the - first time it's called, and returns those tables the first time and - thereafter. This reduces the size of the code by about 2K bytes, in - exchange for a little execution time. However, BUILDFIXED should not be - used for threaded applications, since the rewriting of the tables and virgin - may not be thread-safe. - */ -let virgin = true; - -let lenfix, distfix; // We have no pointers in JS, so keep tables separate - - -const fixedtables = (state) => { - - /* build fixed huffman tables if first call (may not be thread safe) */ - if (virgin) { - lenfix = new Int32Array(512); - distfix = new Int32Array(32); - - /* literal/length table */ - let sym = 0; - while (sym < 144) { state.lens[sym++] = 8; } - while (sym < 256) { state.lens[sym++] = 9; } - while (sym < 280) { state.lens[sym++] = 7; } - while (sym < 288) { state.lens[sym++] = 8; } - - inftrees(LENS, state.lens, 0, 288, lenfix, 0, state.work, { bits: 9 }); - - /* distance table */ - sym = 0; - while (sym < 32) { state.lens[sym++] = 5; } - - inftrees(DISTS, state.lens, 0, 32, distfix, 0, state.work, { bits: 5 }); - - /* do this just once */ - virgin = false; - } - - state.lencode = lenfix; - state.lenbits = 9; - state.distcode = distfix; - state.distbits = 5; -}; - - -/* - Update the window with the last wsize (normally 32K) bytes written before - returning. If window does not exist yet, create it. This is only called - when a window is already in use, or when output has been written during this - inflate call, but the end of the deflate stream has not been reached yet. - It is also called to create a window for dictionary data when a dictionary - is loaded. - - Providing output buffers larger than 32K to inflate() should provide a speed - advantage, since only the last 32K of output is copied to the sliding window - upon return from inflate(), and since all distances after the first 32K of - output will fall in the output data, making match copies simpler and faster. - The advantage may be dependent on the size of the processor's data caches. - */ -const updatewindow = (strm, src, end, copy) => { - - let dist; - const state = strm.state; - - /* if it hasn't been done already, allocate space for the window */ - if (state.window === null) { - state.wsize = 1 << state.wbits; - state.wnext = 0; - state.whave = 0; - - state.window = new Uint8Array(state.wsize); - } - - /* copy state->wsize or less output bytes into the circular window */ - if (copy >= state.wsize) { - state.window.set(src.subarray(end - state.wsize, end), 0); - state.wnext = 0; - state.whave = state.wsize; - } - else { - dist = state.wsize - state.wnext; - if (dist > copy) { - dist = copy; - } - //zmemcpy(state->window + state->wnext, end - copy, dist); - state.window.set(src.subarray(end - copy, end - copy + dist), state.wnext); - copy -= dist; - if (copy) { - //zmemcpy(state->window, end - copy, copy); - state.window.set(src.subarray(end - copy, end), 0); - state.wnext = copy; - state.whave = state.wsize; - } - else { - state.wnext += dist; - if (state.wnext === state.wsize) { state.wnext = 0; } - if (state.whave < state.wsize) { state.whave += dist; } - } - } - return 0; -}; - - -const inflate$2 = (strm, flush) => { - - let state; - let input, output; // input/output buffers - let next; /* next input INDEX */ - let put; /* next output INDEX */ - let have, left; /* available input and output */ - let hold; /* bit buffer */ - let bits; /* bits in bit buffer */ - let _in, _out; /* save starting available input and output */ - let copy; /* number of stored or match bytes to copy */ - let from; /* where to copy match bytes from */ - let from_source; - let here = 0; /* current decoding table entry */ - let here_bits, here_op, here_val; // paked "here" denormalized (JS specific) - //let last; /* parent table entry */ - let last_bits, last_op, last_val; // paked "last" denormalized (JS specific) - let len; /* length to copy for repeats, bits to drop */ - let ret; /* return code */ - const hbuf = new Uint8Array(4); /* buffer for gzip header crc calculation */ - let opts; - - let n; // temporary variable for NEED_BITS - - const order = /* permutation of code lengths */ - new Uint8Array([ 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 ]); - - - if (inflateStateCheck(strm) || !strm.output || - (!strm.input && strm.avail_in !== 0)) { - return Z_STREAM_ERROR$1; - } - - state = strm.state; - if (state.mode === TYPE) { state.mode = TYPEDO; } /* skip check */ - - - //--- LOAD() --- - put = strm.next_out; - output = strm.output; - left = strm.avail_out; - next = strm.next_in; - input = strm.input; - have = strm.avail_in; - hold = state.hold; - bits = state.bits; - //--- - - _in = have; - _out = left; - ret = Z_OK$1; - - inf_leave: // goto emulation - for (;;) { - switch (state.mode) { - case HEAD: - if (state.wrap === 0) { - state.mode = TYPEDO; - break; - } - //=== NEEDBITS(16); - while (bits < 16) { - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - } - //===// - if ((state.wrap & 2) && hold === 0x8b1f) { /* gzip header */ - if (state.wbits === 0) { - state.wbits = 15; - } - state.check = 0/*crc32(0L, Z_NULL, 0)*/; - //=== CRC2(state.check, hold); - hbuf[0] = hold & 0xff; - hbuf[1] = (hold >>> 8) & 0xff; - state.check = crc32_1(state.check, hbuf, 2, 0); - //===// - - //=== INITBITS(); - hold = 0; - bits = 0; - //===// - state.mode = FLAGS; - break; - } - if (state.head) { - state.head.done = false; - } - if (!(state.wrap & 1) || /* check if zlib header allowed */ - (((hold & 0xff)/*BITS(8)*/ << 8) + (hold >> 8)) % 31) { - strm.msg = 'incorrect header check'; - state.mode = BAD; - break; - } - if ((hold & 0x0f)/*BITS(4)*/ !== Z_DEFLATED) { - strm.msg = 'unknown compression method'; - state.mode = BAD; - break; - } - //--- DROPBITS(4) ---// - hold >>>= 4; - bits -= 4; - //---// - len = (hold & 0x0f)/*BITS(4)*/ + 8; - if (state.wbits === 0) { - state.wbits = len; - } - if (len > 15 || len > state.wbits) { - strm.msg = 'invalid window size'; - state.mode = BAD; - break; - } - - // !!! pako patch. Force use `options.windowBits` if passed. - // Required to always use max window size by default. - state.dmax = 1 << state.wbits; - //state.dmax = 1 << len; - - state.flags = 0; /* indicate zlib header */ - //Tracev((stderr, "inflate: zlib header ok\n")); - strm.adler = state.check = 1/*adler32(0L, Z_NULL, 0)*/; - state.mode = hold & 0x200 ? DICTID : TYPE; - //=== INITBITS(); - hold = 0; - bits = 0; - //===// - break; - case FLAGS: - //=== NEEDBITS(16); */ - while (bits < 16) { - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - } - //===// - state.flags = hold; - if ((state.flags & 0xff) !== Z_DEFLATED) { - strm.msg = 'unknown compression method'; - state.mode = BAD; - break; - } - if (state.flags & 0xe000) { - strm.msg = 'unknown header flags set'; - state.mode = BAD; - break; - } - if (state.head) { - state.head.text = ((hold >> 8) & 1); - } - if ((state.flags & 0x0200) && (state.wrap & 4)) { - //=== CRC2(state.check, hold); - hbuf[0] = hold & 0xff; - hbuf[1] = (hold >>> 8) & 0xff; - state.check = crc32_1(state.check, hbuf, 2, 0); - //===// - } - //=== INITBITS(); - hold = 0; - bits = 0; - //===// - state.mode = TIME; - /* falls through */ - case TIME: - //=== NEEDBITS(32); */ - while (bits < 32) { - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - } - //===// - if (state.head) { - state.head.time = hold; - } - if ((state.flags & 0x0200) && (state.wrap & 4)) { - //=== CRC4(state.check, hold) - hbuf[0] = hold & 0xff; - hbuf[1] = (hold >>> 8) & 0xff; - hbuf[2] = (hold >>> 16) & 0xff; - hbuf[3] = (hold >>> 24) & 0xff; - state.check = crc32_1(state.check, hbuf, 4, 0); - //=== - } - //=== INITBITS(); - hold = 0; - bits = 0; - //===// - state.mode = OS; - /* falls through */ - case OS: - //=== NEEDBITS(16); */ - while (bits < 16) { - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - } - //===// - if (state.head) { - state.head.xflags = (hold & 0xff); - state.head.os = (hold >> 8); - } - if ((state.flags & 0x0200) && (state.wrap & 4)) { - //=== CRC2(state.check, hold); - hbuf[0] = hold & 0xff; - hbuf[1] = (hold >>> 8) & 0xff; - state.check = crc32_1(state.check, hbuf, 2, 0); - //===// - } - //=== INITBITS(); - hold = 0; - bits = 0; - //===// - state.mode = EXLEN; - /* falls through */ - case EXLEN: - if (state.flags & 0x0400) { - //=== NEEDBITS(16); */ - while (bits < 16) { - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - } - //===// - state.length = hold; - if (state.head) { - state.head.extra_len = hold; - } - if ((state.flags & 0x0200) && (state.wrap & 4)) { - //=== CRC2(state.check, hold); - hbuf[0] = hold & 0xff; - hbuf[1] = (hold >>> 8) & 0xff; - state.check = crc32_1(state.check, hbuf, 2, 0); - //===// - } - //=== INITBITS(); - hold = 0; - bits = 0; - //===// - } - else if (state.head) { - state.head.extra = null/*Z_NULL*/; - } - state.mode = EXTRA; - /* falls through */ - case EXTRA: - if (state.flags & 0x0400) { - copy = state.length; - if (copy > have) { copy = have; } - if (copy) { - if (state.head) { - len = state.head.extra_len - state.length; - if (!state.head.extra) { - // Use untyped array for more convenient processing later - state.head.extra = new Uint8Array(state.head.extra_len); - } - state.head.extra.set( - input.subarray( - next, - // extra field is limited to 65536 bytes - // - no need for additional size check - next + copy - ), - /*len + copy > state.head.extra_max - len ? state.head.extra_max : copy,*/ - len - ); - //zmemcpy(state.head.extra + len, next, - // len + copy > state.head.extra_max ? - // state.head.extra_max - len : copy); - } - if ((state.flags & 0x0200) && (state.wrap & 4)) { - state.check = crc32_1(state.check, input, copy, next); - } - have -= copy; - next += copy; - state.length -= copy; - } - if (state.length) { break inf_leave; } - } - state.length = 0; - state.mode = NAME; - /* falls through */ - case NAME: - if (state.flags & 0x0800) { - if (have === 0) { break inf_leave; } - copy = 0; - do { - // TODO: 2 or 1 bytes? - len = input[next + copy++]; - /* use constant limit because in js we should not preallocate memory */ - if (state.head && len && - (state.length < 65536 /*state.head.name_max*/)) { - state.head.name += String.fromCharCode(len); - } - } while (len && copy < have); - - if ((state.flags & 0x0200) && (state.wrap & 4)) { - state.check = crc32_1(state.check, input, copy, next); - } - have -= copy; - next += copy; - if (len) { break inf_leave; } - } - else if (state.head) { - state.head.name = null; - } - state.length = 0; - state.mode = COMMENT; - /* falls through */ - case COMMENT: - if (state.flags & 0x1000) { - if (have === 0) { break inf_leave; } - copy = 0; - do { - len = input[next + copy++]; - /* use constant limit because in js we should not preallocate memory */ - if (state.head && len && - (state.length < 65536 /*state.head.comm_max*/)) { - state.head.comment += String.fromCharCode(len); - } - } while (len && copy < have); - if ((state.flags & 0x0200) && (state.wrap & 4)) { - state.check = crc32_1(state.check, input, copy, next); - } - have -= copy; - next += copy; - if (len) { break inf_leave; } - } - else if (state.head) { - state.head.comment = null; - } - state.mode = HCRC; - /* falls through */ - case HCRC: - if (state.flags & 0x0200) { - //=== NEEDBITS(16); */ - while (bits < 16) { - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - } - //===// - if ((state.wrap & 4) && hold !== (state.check & 0xffff)) { - strm.msg = 'header crc mismatch'; - state.mode = BAD; - break; - } - //=== INITBITS(); - hold = 0; - bits = 0; - //===// - } - if (state.head) { - state.head.hcrc = ((state.flags >> 9) & 1); - state.head.done = true; - } - strm.adler = state.check = 0; - state.mode = TYPE; - break; - case DICTID: - //=== NEEDBITS(32); */ - while (bits < 32) { - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - } - //===// - strm.adler = state.check = zswap32(hold); - //=== INITBITS(); - hold = 0; - bits = 0; - //===// - state.mode = DICT; - /* falls through */ - case DICT: - if (state.havedict === 0) { - //--- RESTORE() --- - strm.next_out = put; - strm.avail_out = left; - strm.next_in = next; - strm.avail_in = have; - state.hold = hold; - state.bits = bits; - //--- - return Z_NEED_DICT$1; - } - strm.adler = state.check = 1/*adler32(0L, Z_NULL, 0)*/; - state.mode = TYPE; - /* falls through */ - case TYPE: - if (flush === Z_BLOCK || flush === Z_TREES) { break inf_leave; } - /* falls through */ - case TYPEDO: - if (state.last) { - //--- BYTEBITS() ---// - hold >>>= bits & 7; - bits -= bits & 7; - //---// - state.mode = CHECK; - break; - } - //=== NEEDBITS(3); */ - while (bits < 3) { - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - } - //===// - state.last = (hold & 0x01)/*BITS(1)*/; - //--- DROPBITS(1) ---// - hold >>>= 1; - bits -= 1; - //---// - - switch ((hold & 0x03)/*BITS(2)*/) { - case 0: /* stored block */ - //Tracev((stderr, "inflate: stored block%s\n", - // state.last ? " (last)" : "")); - state.mode = STORED; - break; - case 1: /* fixed block */ - fixedtables(state); - //Tracev((stderr, "inflate: fixed codes block%s\n", - // state.last ? " (last)" : "")); - state.mode = LEN_; /* decode codes */ - if (flush === Z_TREES) { - //--- DROPBITS(2) ---// - hold >>>= 2; - bits -= 2; - //---// - break inf_leave; - } - break; - case 2: /* dynamic block */ - //Tracev((stderr, "inflate: dynamic codes block%s\n", - // state.last ? " (last)" : "")); - state.mode = TABLE; - break; - case 3: - strm.msg = 'invalid block type'; - state.mode = BAD; - } - //--- DROPBITS(2) ---// - hold >>>= 2; - bits -= 2; - //---// - break; - case STORED: - //--- BYTEBITS() ---// /* go to byte boundary */ - hold >>>= bits & 7; - bits -= bits & 7; - //---// - //=== NEEDBITS(32); */ - while (bits < 32) { - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - } - //===// - if ((hold & 0xffff) !== ((hold >>> 16) ^ 0xffff)) { - strm.msg = 'invalid stored block lengths'; - state.mode = BAD; - break; - } - state.length = hold & 0xffff; - //Tracev((stderr, "inflate: stored length %u\n", - // state.length)); - //=== INITBITS(); - hold = 0; - bits = 0; - //===// - state.mode = COPY_; - if (flush === Z_TREES) { break inf_leave; } - /* falls through */ - case COPY_: - state.mode = COPY; - /* falls through */ - case COPY: - copy = state.length; - if (copy) { - if (copy > have) { copy = have; } - if (copy > left) { copy = left; } - if (copy === 0) { break inf_leave; } - //--- zmemcpy(put, next, copy); --- - output.set(input.subarray(next, next + copy), put); - //---// - have -= copy; - next += copy; - left -= copy; - put += copy; - state.length -= copy; - break; - } - //Tracev((stderr, "inflate: stored end\n")); - state.mode = TYPE; - break; - case TABLE: - //=== NEEDBITS(14); */ - while (bits < 14) { - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - } - //===// - state.nlen = (hold & 0x1f)/*BITS(5)*/ + 257; - //--- DROPBITS(5) ---// - hold >>>= 5; - bits -= 5; - //---// - state.ndist = (hold & 0x1f)/*BITS(5)*/ + 1; - //--- DROPBITS(5) ---// - hold >>>= 5; - bits -= 5; - //---// - state.ncode = (hold & 0x0f)/*BITS(4)*/ + 4; - //--- DROPBITS(4) ---// - hold >>>= 4; - bits -= 4; - //---// -//#ifndef PKZIP_BUG_WORKAROUND - if (state.nlen > 286 || state.ndist > 30) { - strm.msg = 'too many length or distance symbols'; - state.mode = BAD; - break; - } -//#endif - //Tracev((stderr, "inflate: table sizes ok\n")); - state.have = 0; - state.mode = LENLENS; - /* falls through */ - case LENLENS: - while (state.have < state.ncode) { - //=== NEEDBITS(3); - while (bits < 3) { - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - } - //===// - state.lens[order[state.have++]] = (hold & 0x07);//BITS(3); - //--- DROPBITS(3) ---// - hold >>>= 3; - bits -= 3; - //---// - } - while (state.have < 19) { - state.lens[order[state.have++]] = 0; - } - // We have separate tables & no pointers. 2 commented lines below not needed. - //state.next = state.codes; - //state.lencode = state.next; - // Switch to use dynamic table - state.lencode = state.lendyn; - state.lenbits = 7; - - opts = { bits: state.lenbits }; - ret = inftrees(CODES, state.lens, 0, 19, state.lencode, 0, state.work, opts); - state.lenbits = opts.bits; - - if (ret) { - strm.msg = 'invalid code lengths set'; - state.mode = BAD; - break; - } - //Tracev((stderr, "inflate: code lengths ok\n")); - state.have = 0; - state.mode = CODELENS; - /* falls through */ - case CODELENS: - while (state.have < state.nlen + state.ndist) { - for (;;) { - here = state.lencode[hold & ((1 << state.lenbits) - 1)];/*BITS(state.lenbits)*/ - here_bits = here >>> 24; - here_op = (here >>> 16) & 0xff; - here_val = here & 0xffff; - - if ((here_bits) <= bits) { break; } - //--- PULLBYTE() ---// - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - //---// - } - if (here_val < 16) { - //--- DROPBITS(here.bits) ---// - hold >>>= here_bits; - bits -= here_bits; - //---// - state.lens[state.have++] = here_val; - } - else { - if (here_val === 16) { - //=== NEEDBITS(here.bits + 2); - n = here_bits + 2; - while (bits < n) { - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - } - //===// - //--- DROPBITS(here.bits) ---// - hold >>>= here_bits; - bits -= here_bits; - //---// - if (state.have === 0) { - strm.msg = 'invalid bit length repeat'; - state.mode = BAD; - break; - } - len = state.lens[state.have - 1]; - copy = 3 + (hold & 0x03);//BITS(2); - //--- DROPBITS(2) ---// - hold >>>= 2; - bits -= 2; - //---// - } - else if (here_val === 17) { - //=== NEEDBITS(here.bits + 3); - n = here_bits + 3; - while (bits < n) { - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - } - //===// - //--- DROPBITS(here.bits) ---// - hold >>>= here_bits; - bits -= here_bits; - //---// - len = 0; - copy = 3 + (hold & 0x07);//BITS(3); - //--- DROPBITS(3) ---// - hold >>>= 3; - bits -= 3; - //---// - } - else { - //=== NEEDBITS(here.bits + 7); - n = here_bits + 7; - while (bits < n) { - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - } - //===// - //--- DROPBITS(here.bits) ---// - hold >>>= here_bits; - bits -= here_bits; - //---// - len = 0; - copy = 11 + (hold & 0x7f);//BITS(7); - //--- DROPBITS(7) ---// - hold >>>= 7; - bits -= 7; - //---// - } - if (state.have + copy > state.nlen + state.ndist) { - strm.msg = 'invalid bit length repeat'; - state.mode = BAD; - break; - } - while (copy--) { - state.lens[state.have++] = len; - } - } - } - - /* handle error breaks in while */ - if (state.mode === BAD) { break; } - - /* check for end-of-block code (better have one) */ - if (state.lens[256] === 0) { - strm.msg = 'invalid code -- missing end-of-block'; - state.mode = BAD; - break; - } - - /* build code tables -- note: do not change the lenbits or distbits - values here (9 and 6) without reading the comments in inftrees.h - concerning the ENOUGH constants, which depend on those values */ - state.lenbits = 9; - - opts = { bits: state.lenbits }; - ret = inftrees(LENS, state.lens, 0, state.nlen, state.lencode, 0, state.work, opts); - // We have separate tables & no pointers. 2 commented lines below not needed. - // state.next_index = opts.table_index; - state.lenbits = opts.bits; - // state.lencode = state.next; - - if (ret) { - strm.msg = 'invalid literal/lengths set'; - state.mode = BAD; - break; - } - - state.distbits = 6; - //state.distcode.copy(state.codes); - // Switch to use dynamic table - state.distcode = state.distdyn; - opts = { bits: state.distbits }; - ret = inftrees(DISTS, state.lens, state.nlen, state.ndist, state.distcode, 0, state.work, opts); - // We have separate tables & no pointers. 2 commented lines below not needed. - // state.next_index = opts.table_index; - state.distbits = opts.bits; - // state.distcode = state.next; - - if (ret) { - strm.msg = 'invalid distances set'; - state.mode = BAD; - break; - } - //Tracev((stderr, 'inflate: codes ok\n')); - state.mode = LEN_; - if (flush === Z_TREES) { break inf_leave; } - /* falls through */ - case LEN_: - state.mode = LEN; - /* falls through */ - case LEN: - if (have >= 6 && left >= 258) { - //--- RESTORE() --- - strm.next_out = put; - strm.avail_out = left; - strm.next_in = next; - strm.avail_in = have; - state.hold = hold; - state.bits = bits; - //--- - inffast(strm, _out); - //--- LOAD() --- - put = strm.next_out; - output = strm.output; - left = strm.avail_out; - next = strm.next_in; - input = strm.input; - have = strm.avail_in; - hold = state.hold; - bits = state.bits; - //--- - - if (state.mode === TYPE) { - state.back = -1; - } - break; - } - state.back = 0; - for (;;) { - here = state.lencode[hold & ((1 << state.lenbits) - 1)]; /*BITS(state.lenbits)*/ - here_bits = here >>> 24; - here_op = (here >>> 16) & 0xff; - here_val = here & 0xffff; - - if (here_bits <= bits) { break; } - //--- PULLBYTE() ---// - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - //---// - } - if (here_op && (here_op & 0xf0) === 0) { - last_bits = here_bits; - last_op = here_op; - last_val = here_val; - for (;;) { - here = state.lencode[last_val + - ((hold & ((1 << (last_bits + last_op)) - 1))/*BITS(last.bits + last.op)*/ >> last_bits)]; - here_bits = here >>> 24; - here_op = (here >>> 16) & 0xff; - here_val = here & 0xffff; - - if ((last_bits + here_bits) <= bits) { break; } - //--- PULLBYTE() ---// - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - //---// - } - //--- DROPBITS(last.bits) ---// - hold >>>= last_bits; - bits -= last_bits; - //---// - state.back += last_bits; - } - //--- DROPBITS(here.bits) ---// - hold >>>= here_bits; - bits -= here_bits; - //---// - state.back += here_bits; - state.length = here_val; - if (here_op === 0) { - //Tracevv((stderr, here.val >= 0x20 && here.val < 0x7f ? - // "inflate: literal '%c'\n" : - // "inflate: literal 0x%02x\n", here.val)); - state.mode = LIT; - break; - } - if (here_op & 32) { - //Tracevv((stderr, "inflate: end of block\n")); - state.back = -1; - state.mode = TYPE; - break; - } - if (here_op & 64) { - strm.msg = 'invalid literal/length code'; - state.mode = BAD; - break; - } - state.extra = here_op & 15; - state.mode = LENEXT; - /* falls through */ - case LENEXT: - if (state.extra) { - //=== NEEDBITS(state.extra); - n = state.extra; - while (bits < n) { - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - } - //===// - state.length += hold & ((1 << state.extra) - 1)/*BITS(state.extra)*/; - //--- DROPBITS(state.extra) ---// - hold >>>= state.extra; - bits -= state.extra; - //---// - state.back += state.extra; - } - //Tracevv((stderr, "inflate: length %u\n", state.length)); - state.was = state.length; - state.mode = DIST; - /* falls through */ - case DIST: - for (;;) { - here = state.distcode[hold & ((1 << state.distbits) - 1)];/*BITS(state.distbits)*/ - here_bits = here >>> 24; - here_op = (here >>> 16) & 0xff; - here_val = here & 0xffff; - - if ((here_bits) <= bits) { break; } - //--- PULLBYTE() ---// - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - //---// - } - if ((here_op & 0xf0) === 0) { - last_bits = here_bits; - last_op = here_op; - last_val = here_val; - for (;;) { - here = state.distcode[last_val + - ((hold & ((1 << (last_bits + last_op)) - 1))/*BITS(last.bits + last.op)*/ >> last_bits)]; - here_bits = here >>> 24; - here_op = (here >>> 16) & 0xff; - here_val = here & 0xffff; - - if ((last_bits + here_bits) <= bits) { break; } - //--- PULLBYTE() ---// - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - //---// - } - //--- DROPBITS(last.bits) ---// - hold >>>= last_bits; - bits -= last_bits; - //---// - state.back += last_bits; - } - //--- DROPBITS(here.bits) ---// - hold >>>= here_bits; - bits -= here_bits; - //---// - state.back += here_bits; - if (here_op & 64) { - strm.msg = 'invalid distance code'; - state.mode = BAD; - break; - } - state.offset = here_val; - state.extra = (here_op) & 15; - state.mode = DISTEXT; - /* falls through */ - case DISTEXT: - if (state.extra) { - //=== NEEDBITS(state.extra); - n = state.extra; - while (bits < n) { - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - } - //===// - state.offset += hold & ((1 << state.extra) - 1)/*BITS(state.extra)*/; - //--- DROPBITS(state.extra) ---// - hold >>>= state.extra; - bits -= state.extra; - //---// - state.back += state.extra; - } -//#ifdef INFLATE_STRICT - if (state.offset > state.dmax) { - strm.msg = 'invalid distance too far back'; - state.mode = BAD; - break; - } -//#endif - //Tracevv((stderr, "inflate: distance %u\n", state.offset)); - state.mode = MATCH; - /* falls through */ - case MATCH: - if (left === 0) { break inf_leave; } - copy = _out - left; - if (state.offset > copy) { /* copy from window */ - copy = state.offset - copy; - if (copy > state.whave) { - if (state.sane) { - strm.msg = 'invalid distance too far back'; - state.mode = BAD; - break; - } -// (!) This block is disabled in zlib defaults, -// don't enable it for binary compatibility -//#ifdef INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR -// Trace((stderr, "inflate.c too far\n")); -// copy -= state.whave; -// if (copy > state.length) { copy = state.length; } -// if (copy > left) { copy = left; } -// left -= copy; -// state.length -= copy; -// do { -// output[put++] = 0; -// } while (--copy); -// if (state.length === 0) { state.mode = LEN; } -// break; -//#endif - } - if (copy > state.wnext) { - copy -= state.wnext; - from = state.wsize - copy; - } - else { - from = state.wnext - copy; - } - if (copy > state.length) { copy = state.length; } - from_source = state.window; - } - else { /* copy from output */ - from_source = output; - from = put - state.offset; - copy = state.length; - } - if (copy > left) { copy = left; } - left -= copy; - state.length -= copy; - do { - output[put++] = from_source[from++]; - } while (--copy); - if (state.length === 0) { state.mode = LEN; } - break; - case LIT: - if (left === 0) { break inf_leave; } - output[put++] = state.length; - left--; - state.mode = LEN; - break; - case CHECK: - if (state.wrap) { - //=== NEEDBITS(32); - while (bits < 32) { - if (have === 0) { break inf_leave; } - have--; - // Use '|' instead of '+' to make sure that result is signed - hold |= input[next++] << bits; - bits += 8; - } - //===// - _out -= left; - strm.total_out += _out; - state.total += _out; - if ((state.wrap & 4) && _out) { - strm.adler = state.check = - /*UPDATE_CHECK(state.check, put - _out, _out);*/ - (state.flags ? crc32_1(state.check, output, _out, put - _out) : adler32_1(state.check, output, _out, put - _out)); - - } - _out = left; - // NB: crc32 stored as signed 32-bit int, zswap32 returns signed too - if ((state.wrap & 4) && (state.flags ? hold : zswap32(hold)) !== state.check) { - strm.msg = 'incorrect data check'; - state.mode = BAD; - break; - } - //=== INITBITS(); - hold = 0; - bits = 0; - //===// - //Tracev((stderr, "inflate: check matches trailer\n")); - } - state.mode = LENGTH; - /* falls through */ - case LENGTH: - if (state.wrap && state.flags) { - //=== NEEDBITS(32); - while (bits < 32) { - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - } - //===// - if ((state.wrap & 4) && hold !== (state.total & 0xffffffff)) { - strm.msg = 'incorrect length check'; - state.mode = BAD; - break; - } - //=== INITBITS(); - hold = 0; - bits = 0; - //===// - //Tracev((stderr, "inflate: length matches trailer\n")); - } - state.mode = DONE; - /* falls through */ - case DONE: - ret = Z_STREAM_END$1; - break inf_leave; - case BAD: - ret = Z_DATA_ERROR$1; - break inf_leave; - case MEM: - return Z_MEM_ERROR$1; - case SYNC: - /* falls through */ - default: - return Z_STREAM_ERROR$1; - } - } - - // inf_leave <- here is real place for "goto inf_leave", emulated via "break inf_leave" - - /* - Return from inflate(), updating the total counts and the check value. - If there was no progress during the inflate() call, return a buffer - error. Call updatewindow() to create and/or update the window state. - Note: a memory error from inflate() is non-recoverable. - */ - - //--- RESTORE() --- - strm.next_out = put; - strm.avail_out = left; - strm.next_in = next; - strm.avail_in = have; - state.hold = hold; - state.bits = bits; - //--- - - if (state.wsize || (_out !== strm.avail_out && state.mode < BAD && - (state.mode < CHECK || flush !== Z_FINISH$1))) { - if (updatewindow(strm, strm.output, strm.next_out, _out - strm.avail_out)) ; - } - _in -= strm.avail_in; - _out -= strm.avail_out; - strm.total_in += _in; - strm.total_out += _out; - state.total += _out; - if ((state.wrap & 4) && _out) { - strm.adler = state.check = /*UPDATE_CHECK(state.check, strm.next_out - _out, _out);*/ - (state.flags ? crc32_1(state.check, output, _out, strm.next_out - _out) : adler32_1(state.check, output, _out, strm.next_out - _out)); - } - strm.data_type = state.bits + (state.last ? 64 : 0) + - (state.mode === TYPE ? 128 : 0) + - (state.mode === LEN_ || state.mode === COPY_ ? 256 : 0); - if (((_in === 0 && _out === 0) || flush === Z_FINISH$1) && ret === Z_OK$1) { - ret = Z_BUF_ERROR; - } - return ret; -}; - - -const inflateEnd = (strm) => { - - if (inflateStateCheck(strm)) { - return Z_STREAM_ERROR$1; - } - - let state = strm.state; - if (state.window) { - state.window = null; - } - strm.state = null; - return Z_OK$1; -}; - - -const inflateGetHeader = (strm, head) => { - - /* check state */ - if (inflateStateCheck(strm)) { return Z_STREAM_ERROR$1; } - const state = strm.state; - if ((state.wrap & 2) === 0) { return Z_STREAM_ERROR$1; } - - /* save header structure */ - state.head = head; - head.done = false; - return Z_OK$1; -}; - - -const inflateSetDictionary = (strm, dictionary) => { - const dictLength = dictionary.length; - - let state; - let dictid; - let ret; - - /* check state */ - if (inflateStateCheck(strm)) { return Z_STREAM_ERROR$1; } - state = strm.state; - - if (state.wrap !== 0 && state.mode !== DICT) { - return Z_STREAM_ERROR$1; - } - - /* check for correct dictionary identifier */ - if (state.mode === DICT) { - dictid = 1; /* adler32(0, null, 0)*/ - /* dictid = adler32(dictid, dictionary, dictLength); */ - dictid = adler32_1(dictid, dictionary, dictLength, 0); - if (dictid !== state.check) { - return Z_DATA_ERROR$1; - } - } - /* copy dictionary to window using updatewindow(), which will amend the - existing dictionary if appropriate */ - ret = updatewindow(strm, dictionary, dictLength, dictLength); - if (ret) { - state.mode = MEM; - return Z_MEM_ERROR$1; - } - state.havedict = 1; - // Tracev((stderr, "inflate: dictionary set\n")); - return Z_OK$1; -}; - - -var inflateReset_1 = inflateReset; -var inflateReset2_1 = inflateReset2; -var inflateResetKeep_1 = inflateResetKeep; -var inflateInit_1 = inflateInit; -var inflateInit2_1 = inflateInit2; -var inflate_2$1 = inflate$2; -var inflateEnd_1 = inflateEnd; -var inflateGetHeader_1 = inflateGetHeader; -var inflateSetDictionary_1 = inflateSetDictionary; -var inflateInfo = 'pako inflate (from Nodeca project)'; - -/* Not implemented -module.exports.inflateCodesUsed = inflateCodesUsed; -module.exports.inflateCopy = inflateCopy; -module.exports.inflateGetDictionary = inflateGetDictionary; -module.exports.inflateMark = inflateMark; -module.exports.inflatePrime = inflatePrime; -module.exports.inflateSync = inflateSync; -module.exports.inflateSyncPoint = inflateSyncPoint; -module.exports.inflateUndermine = inflateUndermine; -module.exports.inflateValidate = inflateValidate; -*/ - -var inflate_1$2 = { - inflateReset: inflateReset_1, - inflateReset2: inflateReset2_1, - inflateResetKeep: inflateResetKeep_1, - inflateInit: inflateInit_1, - inflateInit2: inflateInit2_1, - inflate: inflate_2$1, - inflateEnd: inflateEnd_1, - inflateGetHeader: inflateGetHeader_1, - inflateSetDictionary: inflateSetDictionary_1, - inflateInfo: inflateInfo -}; - -// (C) 1995-2013 Jean-loup Gailly and Mark Adler -// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin -// -// This software is provided 'as-is', without any express or implied -// warranty. In no event will the authors be held liable for any damages -// arising from the use of this software. -// -// Permission is granted to anyone to use this software for any purpose, -// including commercial applications, and to alter it and redistribute it -// freely, subject to the following restrictions: -// -// 1. The origin of this software must not be misrepresented; you must not -// claim that you wrote the original software. If you use this software -// in a product, an acknowledgment in the product documentation would be -// appreciated but is not required. -// 2. Altered source versions must be plainly marked as such, and must not be -// misrepresented as being the original software. -// 3. This notice may not be removed or altered from any source distribution. - -function GZheader() { - /* true if compressed data believed to be text */ - this.text = 0; - /* modification time */ - this.time = 0; - /* extra flags (not used when writing a gzip file) */ - this.xflags = 0; - /* operating system */ - this.os = 0; - /* pointer to extra field or Z_NULL if none */ - this.extra = null; - /* extra field length (valid if extra != Z_NULL) */ - this.extra_len = 0; // Actually, we don't need it in JS, - // but leave for few code modifications - - // - // Setup limits is not necessary because in js we should not preallocate memory - // for inflate use constant limit in 65536 bytes - // - - /* space at extra (only when reading header) */ - // this.extra_max = 0; - /* pointer to zero-terminated file name or Z_NULL */ - this.name = ''; - /* space at name (only when reading header) */ - // this.name_max = 0; - /* pointer to zero-terminated comment or Z_NULL */ - this.comment = ''; - /* space at comment (only when reading header) */ - // this.comm_max = 0; - /* true if there was or will be a header crc */ - this.hcrc = 0; - /* true when done reading gzip header (not used when writing a gzip file) */ - this.done = false; -} - -var gzheader = GZheader; - -const toString = Object.prototype.toString; - -/* Public constants ==========================================================*/ -/* ===========================================================================*/ - -const { - Z_NO_FLUSH, Z_FINISH, - Z_OK, Z_STREAM_END, Z_NEED_DICT, Z_STREAM_ERROR, Z_DATA_ERROR, Z_MEM_ERROR -} = constants$2; - -/* ===========================================================================*/ - - -/** - * class Inflate - * - * Generic JS-style wrapper for zlib calls. If you don't need - * streaming behaviour - use more simple functions: [[inflate]] - * and [[inflateRaw]]. - **/ - -/* internal - * inflate.chunks -> Array - * - * Chunks of output data, if [[Inflate#onData]] not overridden. - **/ - -/** - * Inflate.result -> Uint8Array|String - * - * Uncompressed result, generated by default [[Inflate#onData]] - * and [[Inflate#onEnd]] handlers. Filled after you push last chunk - * (call [[Inflate#push]] with `Z_FINISH` / `true` param). - **/ - -/** - * Inflate.err -> Number - * - * Error code after inflate finished. 0 (Z_OK) on success. - * Should be checked if broken data possible. - **/ - -/** - * Inflate.msg -> String - * - * Error message, if [[Inflate.err]] != 0 - **/ - - -/** - * new Inflate(options) - * - options (Object): zlib inflate options. - * - * Creates new inflator instance with specified params. Throws exception - * on bad params. Supported options: - * - * - `windowBits` - * - `dictionary` - * - * [http://zlib.net/manual.html#Advanced](http://zlib.net/manual.html#Advanced) - * for more information on these. - * - * Additional options, for internal needs: - * - * - `chunkSize` - size of generated data chunks (16K by default) - * - `raw` (Boolean) - do raw inflate - * - `to` (String) - if equal to 'string', then result will be converted - * from utf8 to utf16 (javascript) string. When string output requested, - * chunk length can differ from `chunkSize`, depending on content. - * - * By default, when no options set, autodetect deflate/gzip data format via - * wrapper header. - * - * ##### Example: - * - * ```javascript - * const pako = require('pako') - * const chunk1 = new Uint8Array([1,2,3,4,5,6,7,8,9]) - * const chunk2 = new Uint8Array([10,11,12,13,14,15,16,17,18,19]); - * - * const inflate = new pako.Inflate({ level: 3}); - * - * inflate.push(chunk1, false); - * inflate.push(chunk2, true); // true -> last chunk - * - * if (inflate.err) { throw new Error(inflate.err); } - * - * console.log(inflate.result); - * ``` - **/ -function Inflate$1(options) { - this.options = common.assign({ - chunkSize: 1024 * 64, - windowBits: 15, - to: '' - }, options || {}); - - const opt = this.options; - - // Force window size for `raw` data, if not set directly, - // because we have no header for autodetect. - if (opt.raw && (opt.windowBits >= 0) && (opt.windowBits < 16)) { - opt.windowBits = -opt.windowBits; - if (opt.windowBits === 0) { opt.windowBits = -15; } - } - - // If `windowBits` not defined (and mode not raw) - set autodetect flag for gzip/deflate - if ((opt.windowBits >= 0) && (opt.windowBits < 16) && - !(options && options.windowBits)) { - opt.windowBits += 32; - } - - // Gzip header has no info about windows size, we can do autodetect only - // for deflate. So, if window size not set, force it to max when gzip possible - if ((opt.windowBits > 15) && (opt.windowBits < 48)) { - // bit 3 (16) -> gzipped data - // bit 4 (32) -> autodetect gzip/deflate - if ((opt.windowBits & 15) === 0) { - opt.windowBits |= 15; - } - } - - this.err = 0; // error code, if happens (0 = Z_OK) - this.msg = ''; // error message - this.ended = false; // used to avoid multiple onEnd() calls - this.chunks = []; // chunks of compressed data - - this.strm = new zstream(); - this.strm.avail_out = 0; - - let status = inflate_1$2.inflateInit2( - this.strm, - opt.windowBits - ); - - if (status !== Z_OK) { - throw new Error(messages[status]); - } - - this.header = new gzheader(); - - inflate_1$2.inflateGetHeader(this.strm, this.header); - - // Setup dictionary - if (opt.dictionary) { - // Convert data if needed - if (typeof opt.dictionary === 'string') { - opt.dictionary = strings.string2buf(opt.dictionary); - } else if (toString.call(opt.dictionary) === '[object ArrayBuffer]') { - opt.dictionary = new Uint8Array(opt.dictionary); - } - if (opt.raw) { //In raw mode we need to set the dictionary early - status = inflate_1$2.inflateSetDictionary(this.strm, opt.dictionary); - if (status !== Z_OK) { - throw new Error(messages[status]); - } - } - } -} - -/** - * Inflate#push(data[, flush_mode]) -> Boolean - * - data (Uint8Array|ArrayBuffer): input data - * - flush_mode (Number|Boolean): 0..6 for corresponding Z_NO_FLUSH..Z_TREE - * flush modes. See constants. Skipped or `false` means Z_NO_FLUSH, - * `true` means Z_FINISH. - * - * Sends input data to inflate pipe, generating [[Inflate#onData]] calls with - * new output chunks. Returns `true` on success. If end of stream detected, - * [[Inflate#onEnd]] will be called. - * - * `flush_mode` is not needed for normal operation, because end of stream - * detected automatically. You may try to use it for advanced things, but - * this functionality was not tested. - * - * On fail call [[Inflate#onEnd]] with error code and return false. - * - * ##### Example - * - * ```javascript - * push(chunk, false); // push one of data chunks - * ... - * push(chunk, true); // push last chunk - * ``` - **/ -Inflate$1.prototype.push = function (data, flush_mode) { - const strm = this.strm; - const chunkSize = this.options.chunkSize; - const dictionary = this.options.dictionary; - let status, _flush_mode, last_avail_out; - - if (this.ended) return false; - - if (flush_mode === ~~flush_mode) _flush_mode = flush_mode; - else _flush_mode = flush_mode === true ? Z_FINISH : Z_NO_FLUSH; - - // Convert data if needed - if (toString.call(data) === '[object ArrayBuffer]') { - strm.input = new Uint8Array(data); - } else { - strm.input = data; - } - - strm.next_in = 0; - strm.avail_in = strm.input.length; - - for (;;) { - if (strm.avail_out === 0) { - strm.output = new Uint8Array(chunkSize); - strm.next_out = 0; - strm.avail_out = chunkSize; - } - - status = inflate_1$2.inflate(strm, _flush_mode); - - if (status === Z_NEED_DICT && dictionary) { - status = inflate_1$2.inflateSetDictionary(strm, dictionary); - - if (status === Z_OK) { - status = inflate_1$2.inflate(strm, _flush_mode); - } else if (status === Z_DATA_ERROR) { - // Replace code with more verbose - status = Z_NEED_DICT; - } - } - - // Skip snyc markers if more data follows and not raw mode - while (strm.avail_in > 0 && - status === Z_STREAM_END && - strm.state.wrap > 0 && - data[strm.next_in] !== 0) - { - inflate_1$2.inflateReset(strm); - status = inflate_1$2.inflate(strm, _flush_mode); - } - - switch (status) { - case Z_STREAM_ERROR: - case Z_DATA_ERROR: - case Z_NEED_DICT: - case Z_MEM_ERROR: - this.onEnd(status); - this.ended = true; - return false; - } - - // Remember real `avail_out` value, because we may patch out buffer content - // to align utf8 strings boundaries. - last_avail_out = strm.avail_out; - - if (strm.next_out) { - if (strm.avail_out === 0 || status === Z_STREAM_END) { - - if (this.options.to === 'string') { - - let next_out_utf8 = strings.utf8border(strm.output, strm.next_out); - - let tail = strm.next_out - next_out_utf8; - let utf8str = strings.buf2string(strm.output, next_out_utf8); - - // move tail & realign counters - strm.next_out = tail; - strm.avail_out = chunkSize - tail; - if (tail) strm.output.set(strm.output.subarray(next_out_utf8, next_out_utf8 + tail), 0); - - this.onData(utf8str); - - } else { - this.onData(strm.output.length === strm.next_out ? strm.output : strm.output.subarray(0, strm.next_out)); - } - } - } - - // Must repeat iteration if out buffer is full - if (status === Z_OK && last_avail_out === 0) continue; - - // Finalize if end of stream reached. - if (status === Z_STREAM_END) { - status = inflate_1$2.inflateEnd(this.strm); - this.onEnd(status); - this.ended = true; - return true; - } - - if (strm.avail_in === 0) break; - } - - return true; -}; - - -/** - * Inflate#onData(chunk) -> Void - * - chunk (Uint8Array|String): output data. When string output requested, - * each chunk will be string. - * - * By default, stores data blocks in `chunks[]` property and glue - * those in `onEnd`. Override this handler, if you need another behaviour. - **/ -Inflate$1.prototype.onData = function (chunk) { - this.chunks.push(chunk); -}; - - -/** - * Inflate#onEnd(status) -> Void - * - status (Number): inflate status. 0 (Z_OK) on success, - * other if not. - * - * Called either after you tell inflate that the input stream is - * complete (Z_FINISH). By default - join collected chunks, - * free memory and fill `results` / `err` properties. - **/ -Inflate$1.prototype.onEnd = function (status) { - // On success - join - if (status === Z_OK) { - if (this.options.to === 'string') { - this.result = this.chunks.join(''); - } else { - this.result = common.flattenChunks(this.chunks); - } - } - this.chunks = []; - this.err = status; - this.msg = this.strm.msg; -}; - - -/** - * inflate(data[, options]) -> Uint8Array|String - * - data (Uint8Array|ArrayBuffer): input data to decompress. - * - options (Object): zlib inflate options. - * - * Decompress `data` with inflate/ungzip and `options`. Autodetect - * format via wrapper header by default. That's why we don't provide - * separate `ungzip` method. - * - * Supported options are: - * - * - windowBits - * - * [http://zlib.net/manual.html#Advanced](http://zlib.net/manual.html#Advanced) - * for more information. - * - * Sugar (options): - * - * - `raw` (Boolean) - say that we work with raw stream, if you don't wish to specify - * negative windowBits implicitly. - * - `to` (String) - if equal to 'string', then result will be converted - * from utf8 to utf16 (javascript) string. When string output requested, - * chunk length can differ from `chunkSize`, depending on content. - * - * - * ##### Example: - * - * ```javascript - * const pako = require('pako'); - * const input = pako.deflate(new Uint8Array([1,2,3,4,5,6,7,8,9])); - * let output; - * - * try { - * output = pako.inflate(input); - * } catch (err) { - * console.log(err); - * } - * ``` - **/ -function inflate$1(input, options) { - const inflator = new Inflate$1(options); - - inflator.push(input); - - // That will never happens, if you don't cheat with options :) - if (inflator.err) throw inflator.msg || messages[inflator.err]; - - return inflator.result; -} - - -/** - * inflateRaw(data[, options]) -> Uint8Array|String - * - data (Uint8Array|ArrayBuffer): input data to decompress. - * - options (Object): zlib inflate options. - * - * The same as [[inflate]], but creates raw data, without wrapper - * (header and adler32 crc). - **/ -function inflateRaw$1(input, options) { - options = options || {}; - options.raw = true; - return inflate$1(input, options); -} - - -/** - * ungzip(data[, options]) -> Uint8Array|String - * - data (Uint8Array|ArrayBuffer): input data to decompress. - * - options (Object): zlib inflate options. - * - * Just shortcut to [[inflate]], because it autodetects format - * by header.content. Done for convenience. - **/ - - -var Inflate_1$1 = Inflate$1; -var inflate_2 = inflate$1; -var inflateRaw_1$1 = inflateRaw$1; -var ungzip$1 = inflate$1; -var constants = constants$2; - -var inflate_1$1 = { - Inflate: Inflate_1$1, - inflate: inflate_2, - inflateRaw: inflateRaw_1$1, - ungzip: ungzip$1, - constants: constants -}; - -const { Deflate, deflate, deflateRaw, gzip } = deflate_1$1; - -const { Inflate, inflate, inflateRaw, ungzip } = inflate_1$1; - - - -var Deflate_1 = Deflate; -var deflate_1 = deflate; -var deflateRaw_1 = deflateRaw; -var gzip_1 = gzip; -var Inflate_1 = Inflate; -var inflate_1 = inflate; -var inflateRaw_1 = inflateRaw; -var ungzip_1 = ungzip; -var constants_1 = constants$2; - -var pako = { - Deflate: Deflate_1, - deflate: deflate_1, - deflateRaw: deflateRaw_1, - gzip: gzip_1, - Inflate: Inflate_1, - inflate: inflate_1, - inflateRaw: inflateRaw_1, - ungzip: ungzip_1, - constants: constants_1 -}; - -/* https://grpc.io/ */ -class GRPC { - static name = "gRPC"; - static version = "1.0.3"; - static about = () => log("", `🟧 ${this.name} v${this.version}`, ""); - - static decode(bytesBody = new Uint8Array([])) { - log(`☑️ gRPC.decode`, ""); - // 先拆分gRPC校验头和protobuf数据体 - const Header = bytesBody.slice(0, 5); - let body = bytesBody.slice(5); - switch (Header[0]) { - case 0: // unGzip - default: - break; - case 1: // Gzip - switch ($platform) { - case "Surge": - body = $utils.ungzip(body); - break; - default: - body = pako.ungzip(body); // 解压缩protobuf数据体 - break; - } Header[0] = 0; // unGzip - break; - } log(`✅ gRPC.decode`, ""); - return body; - }; - - static encode(body = new Uint8Array([]), encoding = "identity") { - log(`☑️ gRPC.encode`, ""); - // Header: 1位:是否校验数据 (0或者1) + 4位:校验值(数据长度) - const Header = new Uint8Array(5); - const Checksum = this.#Checksum(body.length); // 校验值为未压缩情况下的数据长度, 不是压缩后的长度 - Header.set(Checksum, 1); // 1-4位: 校验值(4位) - switch (encoding) { - case "gzip": - Header.set([1], 0); // 0位:Encoding类型,当为1的时候, app会校验1-4位的校验值是否正确 - body = pako.gzip(body); - break; - case "identity": - case undefined: - default: - Header.set([0], 0); // 0位:Encoding类型,当为1的时候, app会校验1-4位的校验值是否正确 - break; - } const BytesBody = new Uint8Array(Header.length + body.length); - BytesBody.set(Header, 0); // 0-4位:gRPC校验头 - BytesBody.set(body, 5); // 5-end位:protobuf数据 - log(`✅ gRPC.encode`, ""); - return BytesBody; - }; - - // 计算校验和 (B站为数据本体字节数) - static #Checksum(num = 0) { - let array = new ArrayBuffer(4); // an Int32 takes 4 bytes - let view = new DataView(array); - // 首位填充计算过的新数据长度 - view.setUint32(0, num, false); // byteOffset = 0; litteEndian = false - return new Uint8Array(array); - }; -} - -var Settings$7 = { - Switch: true -}; -var Configs$3 = { - Storefront: [ - [ - "AE", - "143481" - ], - [ - "AF", - "143610" - ], - [ - "AG", - "143540" - ], - [ - "AI", - "143538" - ], - [ - "AL", - "143575" - ], - [ - "AM", - "143524" - ], - [ - "AO", - "143564" - ], - [ - "AR", - "143505" - ], - [ - "AT", - "143445" - ], - [ - "AU", - "143460" - ], - [ - "AZ", - "143568" - ], - [ - "BA", - "143612" - ], - [ - "BB", - "143541" - ], - [ - "BD", - "143490" - ], - [ - "BE", - "143446" - ], - [ - "BF", - "143578" - ], - [ - "BG", - "143526" - ], - [ - "BH", - "143559" - ], - [ - "BJ", - "143576" - ], - [ - "BM", - "143542" - ], - [ - "BN", - "143560" - ], - [ - "BO", - "143556" - ], - [ - "BR", - "143503" - ], - [ - "BS", - "143539" - ], - [ - "BT", - "143577" - ], - [ - "BW", - "143525" - ], - [ - "BY", - "143565" - ], - [ - "BZ", - "143555" - ], - [ - "CA", - "143455" - ], - [ - "CD", - "143613" - ], - [ - "CG", - "143582" - ], - [ - "CH", - "143459" - ], - [ - "CI", - "143527" - ], - [ - "CL", - "143483" - ], - [ - "CM", - "143574" - ], - [ - "CN", - "143465" - ], - [ - "CO", - "143501" - ], - [ - "CR", - "143495" - ], - [ - "CV", - "143580" - ], - [ - "CY", - "143557" - ], - [ - "CZ", - "143489" - ], - [ - "DE", - "143443" - ], - [ - "DK", - "143458" - ], - [ - "DM", - "143545" - ], - [ - "DO", - "143508" - ], - [ - "DZ", - "143563" - ], - [ - "EC", - "143509" - ], - [ - "EE", - "143518" - ], - [ - "EG", - "143516" - ], - [ - "ES", - "143454" - ], - [ - "FI", - "143447" - ], - [ - "FJ", - "143583" - ], - [ - "FM", - "143591" - ], - [ - "FR", - "143442" - ], - [ - "GA", - "143614" - ], - [ - "GB", - "143444" - ], - [ - "GD", - "143546" - ], - [ - "GF", - "143615" - ], - [ - "GH", - "143573" - ], - [ - "GM", - "143584" - ], - [ - "GR", - "143448" - ], - [ - "GT", - "143504" - ], - [ - "GW", - "143585" - ], - [ - "GY", - "143553" - ], - [ - "HK", - "143463" - ], - [ - "HN", - "143510" - ], - [ - "HR", - "143494" - ], - [ - "HU", - "143482" - ], - [ - "ID", - "143476" - ], - [ - "IE", - "143449" - ], - [ - "IL", - "143491" - ], - [ - "IN", - "143467" - ], - [ - "IQ", - "143617" - ], - [ - "IS", - "143558" - ], - [ - "IT", - "143450" - ], - [ - "JM", - "143511" - ], - [ - "JO", - "143528" - ], - [ - "JP", - "143462" - ], - [ - "KE", - "143529" - ], - [ - "KG", - "143586" - ], - [ - "KH", - "143579" - ], - [ - "KN", - "143548" - ], - [ - "KP", - "143466" - ], - [ - "KR", - "143466" - ], - [ - "KW", - "143493" - ], - [ - "KY", - "143544" - ], - [ - "KZ", - "143517" - ], - [ - "TC", - "143552" - ], - [ - "TD", - "143581" - ], - [ - "TJ", - "143603" - ], - [ - "TH", - "143475" - ], - [ - "TM", - "143604" - ], - [ - "TN", - "143536" - ], - [ - "TO", - "143608" - ], - [ - "TR", - "143480" - ], - [ - "TT", - "143551" - ], - [ - "TW", - "143470" - ], - [ - "TZ", - "143572" - ], - [ - "LA", - "143587" - ], - [ - "LB", - "143497" - ], - [ - "LC", - "143549" - ], - [ - "LI", - "143522" - ], - [ - "LK", - "143486" - ], - [ - "LR", - "143588" - ], - [ - "LT", - "143520" - ], - [ - "LU", - "143451" - ], - [ - "LV", - "143519" - ], - [ - "LY", - "143567" - ], - [ - "MA", - "143620" - ], - [ - "MD", - "143523" - ], - [ - "ME", - "143619" - ], - [ - "MG", - "143531" - ], - [ - "MK", - "143530" - ], - [ - "ML", - "143532" - ], - [ - "MM", - "143570" - ], - [ - "MN", - "143592" - ], - [ - "MO", - "143515" - ], - [ - "MR", - "143590" - ], - [ - "MS", - "143547" - ], - [ - "MT", - "143521" - ], - [ - "MU", - "143533" - ], - [ - "MV", - "143488" - ], - [ - "MW", - "143589" - ], - [ - "MX", - "143468" - ], - [ - "MY", - "143473" - ], - [ - "MZ", - "143593" - ], - [ - "NA", - "143594" - ], - [ - "NE", - "143534" - ], - [ - "NG", - "143561" - ], - [ - "NI", - "143512" - ], - [ - "NL", - "143452" - ], - [ - "NO", - "143457" - ], - [ - "NP", - "143484" - ], - [ - "NR", - "143606" - ], - [ - "NZ", - "143461" - ], - [ - "OM", - "143562" - ], - [ - "PA", - "143485" - ], - [ - "PE", - "143507" - ], - [ - "PG", - "143597" - ], - [ - "PH", - "143474" - ], - [ - "PK", - "143477" - ], - [ - "PL", - "143478" - ], - [ - "PT", - "143453" - ], - [ - "PW", - "143595" - ], - [ - "PY", - "143513" - ], - [ - "QA", - "143498" - ], - [ - "RO", - "143487" - ], - [ - "RS", - "143500" - ], - [ - "RU", - "143469" - ], - [ - "RW", - "143621" - ], - [ - "SA", - "143479" - ], - [ - "SB", - "143601" - ], - [ - "SC", - "143599" - ], - [ - "SE", - "143456" - ], - [ - "SG", - "143464" - ], - [ - "SI", - "143499" - ], - [ - "SK", - "143496" - ], - [ - "SL", - "143600" - ], - [ - "SN", - "143535" - ], - [ - "SR", - "143554" - ], - [ - "ST", - "143598" - ], - [ - "SV", - "143506" - ], - [ - "SZ", - "143602" - ], - [ - "UA", - "143492" - ], - [ - "UG", - "143537" - ], - [ - "US", - "143441" - ], - [ - "UY", - "143514" - ], - [ - "UZ", - "143566" - ], - [ - "VC", - "143550" - ], - [ - "VE", - "143502" - ], - [ - "VG", - "143543" - ], - [ - "VN", - "143471" - ], - [ - "VU", - "143609" - ], - [ - "XK", - "143624" - ], - [ - "YE", - "143571" - ], - [ - "ZA", - "143472" - ], - [ - "ZM", - "143622" - ], - [ - "ZW", - "143605" - ] - ] -}; -var Default = { - Settings: Settings$7, - Configs: Configs$3 -}; - -var Default$1 = /*#__PURE__*/Object.freeze({ - __proto__: null, - Configs: Configs$3, - Settings: Settings$7, - default: Default -}); - -var Settings$6 = { - Switch: true, - PEP: { - GCC: "US" - } -}; -var Location = { - Settings: Settings$6 -}; - -var Location$1 = /*#__PURE__*/Object.freeze({ - __proto__: null, - Settings: Settings$6, - default: Location -}); - -var Settings$5 = { - Switch: true, - UrlInfoSet: { - Dispatcher: "AutoNavi", - Directions: "AutoNavi", - RAP: "Apple", - LocationShift: "AUTO" - }, - TileSet: { - "Map": "CN", - Satellite: "HYBRID", - Traffic: "CN", - POI: "CN", - Flyover: "XX", - Munin: "XX" - }, - GeoManifest: { - Dynamic: { - Config: { - CountryCode: { - "default": "CN", - iOS: "AUTO", - iPadOS: "AUTO", - watchOS: "US", - macOS: "AUTO" - } - } - } - }, - Config: { - Announcements: { - "Environment:": { - "default": "AUTO", - iOS: "AUTO", - iPadOS: "AUTO", - watchOS: "AUTO", - macOS: "AUTO" - } - } - } -}; -var Configs$2 = { -}; -var Maps = { - Settings: Settings$5, - Configs: Configs$2 -}; - -var Maps$1 = /*#__PURE__*/Object.freeze({ - __proto__: null, - Configs: Configs$2, - Settings: Settings$5, - default: Maps -}); - -var Settings$4 = { - Switch: true, - CountryCode: "US", - NewsPlusUser: true -}; -var News = { - Settings: Settings$4 -}; - -var News$1 = /*#__PURE__*/Object.freeze({ - __proto__: null, - Settings: Settings$4, - default: News -}); - -var Settings$3 = { - Switch: true, - CountryCode: "US", - canUse: true -}; -var PrivateRelay = { - Settings: Settings$3 -}; - -var PrivateRelay$1 = /*#__PURE__*/Object.freeze({ - __proto__: null, - Settings: Settings$3, - default: PrivateRelay -}); - -var Settings$2 = { - Switch: true, - CountryCode: "SG", - Region: "AUTO", - Domains: [ - "web", - "itunes", - "app_store", - "movies", - "restaurants", - "maps" - ], - Functions: [ - "flightutilities", - "lookup", - "mail", - "messages", - "news", - "safari", - "siri", - "spotlight", - "visualintelligence" - ], - Safari_Smart_History: true -}; -var Configs$1 = { - VisualIntelligence: { - enabled_domains: [ - "pets", - "media", - "books", - "art", - "nature", - "landmarks" - ], - supported_domains: [ - "ART", - "BOOK", - "MEDIA", - "LANDMARK", - "ANIMALS", - "BIRDS", - "FOOD", - "SIGN_SYMBOL", - "AUTO_SYMBOL", - "DOGS", - "NATURE", - "NATURAL_LANDMARK", - "INSECTS", - "REPTILES", - "ALBUM", - "STOREFRONT", - "LAUNDRY_CARE_SYMBOL", - "CATS", - "OBJECT_2D", - "SCULPTURE", - "SKYLINE", - "MAMMALS" - ] - } -}; -var Siri = { - Settings: Settings$2, - Configs: Configs$1 -}; - -var Siri$1 = /*#__PURE__*/Object.freeze({ - __proto__: null, - Configs: Configs$1, - Settings: Settings$2, - default: Siri -}); - -var Settings$1 = { - Switch: "true", - CountryCode: "US", - MultiAccount: "false", - Universal: "true" -}; -var TestFlight = { - Settings: Settings$1 -}; - -var TestFlight$1 = /*#__PURE__*/Object.freeze({ - __proto__: null, - Settings: Settings$1, - default: TestFlight -}); - -var Settings = { - Switch: true, - "Third-Party": false, - HLSUrl: "play-edge.itunes.apple.com", - ServerUrl: "play.itunes.apple.com", - Tabs: [ - "WatchNow", - "Originals", - "MLS", - "Sports", - "Kids", - "Store", - "Movies", - "TV", - "ChannelsAndApps", - "Library", - "Search" - ], - CountryCode: { - Configs: "AUTO", - Settings: "AUTO", - View: [ - "SG", - "TW" - ], - WatchNow: "AUTO", - Channels: "AUTO", - Originals: "AUTO", - Sports: "US", - Kids: "US", - Store: "AUTO", - Movies: "AUTO", - TV: "AUTO", - Persons: "SG", - Search: "AUTO", - Others: "AUTO" - } -}; -var Configs = { - Locale: [ - [ - "AU", - "en-AU" - ], - [ - "CA", - "en-CA" - ], - [ - "GB", - "en-GB" - ], - [ - "KR", - "ko-KR" - ], - [ - "HK", - "yue-Hant" - ], - [ - "JP", - "ja-JP" - ], - [ - "MO", - "zh-Hant" - ], - [ - "TW", - "zh-Hant" - ], - [ - "US", - "en-US" - ], - [ - "SG", - "zh-Hans" - ] - ], - Tabs: [ - { - title: "主页", - type: "WatchNow", - universalLinks: [ - "https://tv.apple.com/watch-now", - "https://tv.apple.com/home" - ], - destinationType: "Target", - target: { - id: "tahoma_watchnow", - type: "Root", - url: "https://tv.apple.com/watch-now" - }, - isSelected: true - }, - { - title: "Apple TV+", - type: "Originals", - universalLinks: [ - "https://tv.apple.com/channel/tvs.sbd.4000", - "https://tv.apple.com/atv" - ], - destinationType: "Target", - target: { - id: "tvs.sbd.4000", - type: "Brand", - url: "https://tv.apple.com/us/channel/tvs.sbd.4000" - } - }, - { - title: "MLS Season Pass", - type: "MLS", - universalLinks: [ - "https://tv.apple.com/mls" - ], - destinationType: "Target", - target: { - id: "tvs.sbd.7000", - type: "Brand", - url: "https://tv.apple.com/us/channel/tvs.sbd.7000" - } - }, - { - title: "体育节目", - type: "Sports", - universalLinks: [ - "https://tv.apple.com/sports" - ], - destinationType: "Target", - target: { - id: "tahoma_sports", - type: "Root", - url: "https://tv.apple.com/sports" - } - }, - { - title: "儿童", - type: "Kids", - universalLinks: [ - "https://tv.apple.com/kids" - ], - destinationType: "Target", - target: { - id: "tahoma_kids", - type: "Root", - url: "https://tv.apple.com/kids" - } - }, - { - title: "电影", - type: "Movies", - universalLinks: [ - "https://tv.apple.com/movies" - ], - destinationType: "Target", - target: { - id: "tahoma_movies", - type: "Root", - url: "https://tv.apple.com/movies" - } - }, - { - title: "电视节目", - type: "TV", - universalLinks: [ - "https://tv.apple.com/tv-shows" - ], - destinationType: "Target", - target: { - id: "tahoma_tvshows", - type: "Root", - url: "https://tv.apple.com/tv-shows" - } - }, - { - title: "商店", - type: "Store", - universalLinks: [ - "https://tv.apple.com/store" - ], - destinationType: "SubTabs", - subTabs: [ - { - title: "电影", - type: "Movies", - universalLinks: [ - "https://tv.apple.com/movies" - ], - destinationType: "Target", - target: { - id: "tahoma_movies", - type: "Root", - url: "https://tv.apple.com/movies" - } - }, - { - title: "电视节目", - type: "TV", - universalLinks: [ - "https://tv.apple.com/tv-shows" - ], - destinationType: "Target", - target: { - id: "tahoma_tvshows", - type: "Root", - url: "https://tv.apple.com/tv-shows" - } - } - ] - }, - { - title: "频道和 App", - destinationType: "SubTabs", - subTabsPlacementType: "ExpandedList", - type: "ChannelsAndApps", - subTabs: [ - ] - }, - { - title: "资料库", - type: "Library", - destinationType: "Client" - }, - { - title: "搜索", - type: "Search", - universalLinks: [ - "https://tv.apple.com/search" - ], - destinationType: "Target", - target: { - id: "tahoma_search", - type: "Root", - url: "https://tv.apple.com/search" - } - } - ], - i18n: { - WatchNow: [ - [ - "en", - "Home" - ], - [ - "zh", - "主页" - ], - [ - "zh-Hans", - "主頁" - ], - [ - "zh-Hant", - "主頁" - ] - ], - Movies: [ - [ - "en", - "Movies" - ], - [ - "zh", - "电影" - ], - [ - "zh-Hans", - "电影" - ], - [ - "zh-Hant", - "電影" - ] - ], - TV: [ - [ - "en", - "TV" - ], - [ - "zh", - "电视节目" - ], - [ - "zh-Hans", - "电视节目" - ], - [ - "zh-Hant", - "電視節目" - ] - ], - Store: [ - [ - "en", - "Store" - ], - [ - "zh", - "商店" - ], - [ - "zh-Hans", - "商店" - ], - [ - "zh-Hant", - "商店" - ] - ], - Sports: [ - [ - "en", - "Sports" - ], - [ - "zh", - "体育节目" - ], - [ - "zh-Hans", - "体育节目" - ], - [ - "zh-Hant", - "體育節目" - ] - ], - Kids: [ - [ - "en", - "Kids" - ], - [ - "zh", - "儿童" - ], - [ - "zh-Hans", - "儿童" - ], - [ - "zh-Hant", - "兒童" - ] - ], - Library: [ - [ - "en", - "Library" - ], - [ - "zh", - "资料库" - ], - [ - "zh-Hans", - "资料库" - ], - [ - "zh-Hant", - "資料庫" - ] - ], - Search: [ - [ - "en", - "Search" - ], - [ - "zh", - "搜索" - ], - [ - "zh-Hans", - "搜索" - ], - [ - "zh-Hant", - "蒐索" - ] - ] - } -}; -var TV = { - Settings: Settings, - Configs: Configs -}; - -var TV$1 = /*#__PURE__*/Object.freeze({ - __proto__: null, - Configs: Configs, - Settings: Settings, - default: TV -}); - -var Database$1 = Database = { - "Default": Default$1, - "Location": Location$1, - "Maps": Maps$1, - "News": News$1, - "PrivateRelay": PrivateRelay$1, - "Siri": Siri$1, - "TestFlight": TestFlight$1, - "TV": TV$1 -}; - -/** - * Get Storage Variables - * @link https://github.com/NanoCat-Me/utils/blob/main/getStorage.mjs - * @author VirgilClyne - * @param {String} key - Persistent Store Key - * @param {Array} names - Platform Names - * @param {Object} database - Default Database - * @return {Object} { Settings, Caches, Configs } - */ -function getStorage(key, names, database) { - //log(`☑️ getStorage, Get Environment Variables`, ""); - /***************** BoxJs *****************/ - // 包装为局部变量,用完释放内存 - // BoxJs的清空操作返回假值空字符串, 逻辑或操作符会在左侧操作数为假值时返回右侧操作数。 - let BoxJs = Storage.getItem(key, database); - //log(`🚧 getStorage, Get Environment Variables`, `BoxJs类型: ${typeof BoxJs}`, `BoxJs内容: ${JSON.stringify(BoxJs)}`, ""); - /***************** Argument *****************/ - let Argument = {}; - switch (typeof $argument) { - case "string": - let arg = Object.fromEntries($argument.split("&").map((item) => item.split("=").map(i => i.replace(/\"/g, '')))); - for (let item in arg) Lodash.set(Argument, item, arg[item]); - break; - case "object": - for (let item in $argument) Lodash.set(Argument, item, $argument[item]); - break; - } //log(`✅ getStorage, Get Environment Variables`, `Argument类型: ${typeof Argument}`, `Argument内容: ${JSON.stringify(Argument)}`, ""); - /***************** Store *****************/ - const Store = { Settings: database?.Default?.Settings || {}, Configs: database?.Default?.Configs || {}, Caches: {} }; - if (!Array.isArray(names)) names = [names]; - //log(`🚧 getStorage, Get Environment Variables`, `names类型: ${typeof names}`, `names内容: ${JSON.stringify(names)}`, ""); - for (let name of names) { - Store.Settings = { ...Store.Settings, ...database?.[name]?.Settings, ...Argument, ...BoxJs?.[name]?.Settings }; - Store.Configs = { ...Store.Configs, ...database?.[name]?.Configs }; - if (BoxJs?.[name]?.Caches && typeof BoxJs?.[name]?.Caches === "string") BoxJs[name].Caches = JSON.parse(BoxJs?.[name]?.Caches); - Store.Caches = { ...Store.Caches, ...BoxJs?.[name]?.Caches }; - } //log(`🚧 getStorage, Get Environment Variables`, `Store.Settings类型: ${typeof Store.Settings}`, `Store.Settings: ${JSON.stringify(Store.Settings)}`, ""); - traverseObject(Store.Settings, (key, value) => { - //log(`🚧 getStorage, traverseObject`, `${key}: ${typeof value}`, `${key}: ${JSON.stringify(value)}`, ""); - if (value === "true" || value === "false") value = JSON.parse(value); // 字符串转Boolean - else if (typeof value === "string") { - if (value.includes(",")) value = value.split(",").map(item => string2number(item)); // 字符串转数组转数字 - else value = string2number(value); // 字符串转数字 - } return value; - }); - //log(`✅ getStorage, Get Environment Variables`, `Store: ${typeof Store.Caches}`, `Store内容: ${JSON.stringify(Store)}`, ""); - return Store; - /***************** function *****************/ - function traverseObject(o, c) { for (var t in o) { var n = o[t]; o[t] = "object" == typeof n && null !== n ? traverseObject(n, c) : c(t, n); } return o } - function string2number(string) { if (string && !isNaN(string)) string = parseInt(string, 10); return string } -} - -/** - * Set Environment Variables - * @author VirgilClyne - * @param {String} name - Persistent Store Key - * @param {Array} platforms - Platform Names - * @param {Object} database - Default DataBase - * @return {Object} { Settings, Caches, Configs } - */ -function setENV(name, platforms, database) { - log(`☑️ Set Environment Variables`, ""); - let { Settings, Caches, Configs } = getStorage(name, platforms, database); - /***************** Settings *****************/ - switch (platforms) { - case "WeatherKit": - if (!Array.isArray(Settings?.AQI?.ReplaceProviders)) Lodash.set(Settings, "AQI.ReplaceProviders", (Settings?.AQI?.ReplaceProviders) ? [Settings.AQI.ReplaceProviders.toString()] : []); - if (Settings.AQI.ReplaceProviders.includes("TWC")) Settings.AQI.ReplaceProviders.push("The Weather Channel"); - if (Settings.AQI.ReplaceProviders.includes("QWeather")) Settings.AQI.ReplaceProviders.push("和风天气"); - Settings.AQI.ReplaceProviders.push(undefined); - if (!Array.isArray(Settings?.AQI?.Local?.ReplaceScales)) Lodash.set(Settings, "AQI.Local.ReplaceScales", (Settings?.AQI?.Local?.ReplaceScales) ? [Settings.AQI.Local.ReplaceScales.toString()] : []); - break; - case "Siri": - if (!Array.isArray(Settings?.Domains)) Lodash.set(Settings, "Domains", (Settings?.Domains) ? [Settings.Domains.toString()] : []); - if (!Array.isArray(Settings?.Functions)) Lodash.set(Settings, "Functions", (Settings?.Functions) ? [Settings.Functions.toString()] : []); - break; - case "TV": - if (!Array.isArray(Settings?.Tabs)) Lodash.set(Settings, "Tabs", (Settings?.Tabs) ? [Settings.Tabs.toString()] : []); - break; - } log(`✅ Set Environment Variables, Settings: ${typeof Settings}, Settings内容: ${JSON.stringify(Settings)}`, ""); - /***************** Caches *****************/ - //log(`✅ Set Environment Variables, Caches: ${typeof Caches}, Caches内容: ${JSON.stringify(Caches)}`, ""); - /***************** Configs *****************/ - Configs.Storefront = new Map(Configs.Storefront); - if (Configs.Locale) Configs.Locale = new Map(Configs.Locale); - if (Configs.i18n) for (let type in Configs.i18n) Configs.i18n[type] = new Map(Configs.i18n[type]); - return { Settings, Caches, Configs }; -} - -log("v4.0.2(4003)"); -/***************** Processing *****************/ -// 解构URL -const url = new URL($request.url); -log(`⚠ url: ${url.toJSON()}`, ""); -// 获取连接参数 -const METHOD = $request.method, HOST = url.hostname, PATH = url.pathname; url.pathname.split("/").filter(Boolean); -log(`⚠ METHOD: ${METHOD}, HOST: ${HOST}, PATH: ${PATH}`, ""); -// 解析格式 -const FORMAT = ($response.headers?.["Content-Type"] ?? $response.headers?.["content-type"])?.split(";")?.[0]; -log(`⚠ FORMAT: ${FORMAT}`, ""); -!(async () => { - const { Settings, Caches, Configs } = setENV("iRingo", "Siri", Database$1); - log(`⚠ Settings.Switch: ${Settings?.Switch}`, ""); - switch (Settings.Switch) { - case true: - default: - // 创建空数据 - let body = {}; - // 格式判断 - switch (FORMAT) { - case undefined: // 视为无body - break; - case "application/x-www-form-urlencoded": - case "text/plain": - default: - break; - case "application/x-mpegURL": - case "application/x-mpegurl": - case "application/vnd.apple.mpegurl": - case "audio/mpegurl": - //body = M3U8.parse($response.body); - //log(`🚧 body: ${JSON.stringify(body)}`, ""); - //$response.body = M3U8.stringify(body); - break; - case "text/xml": - case "text/html": - case "text/plist": - case "application/xml": - case "application/plist": - case "application/x-plist": - //body = XML.parse($response.body); - //log(`🚧 body: ${JSON.stringify(body)}`, ""); - break; - case "text/vtt": - case "application/vtt": - //body = VTT.parse($response.body); - //log(`🚧 body: ${JSON.stringify(body)}`, ""); - //$response.body = VTT.stringify(body); - break; - case "text/json": - case "application/json": - body = JSON.parse($response.body ?? "{}"); - // 主机判断 - switch (HOST) { - case "api.smoot.apple.com": - case "api.smoot.apple.cn": - // 路径判断 - switch (PATH) { - case "/bag": // 配置 - body.enabled = true; - body.feedback_enabled = true; - //body.search_url = body?.search_url || "https:\/\/api-glb-apne1c.smoot.apple.com\/search"; - //body.feedback_url = body?.feedback_url || "https:\/\/fbs.smoot.apple.com\/fb"; - if (body?.enabled_domains) { - body.enabled_domains = [...new Set([...body?.enabled_domains ?? [], ...Settings.Domains])]; - log(`🎉 领域列表`, `enabled_domains: ${JSON.stringify(body.enabled_domains)}`, ""); - } - if (body?.scene_aware_lookup_enabled_domains) { - body.scene_aware_lookup_enabled_domains = [...new Set([...body?.scene_aware_lookup_enabled_domains ?? [], ...Settings.Domains])]; - log(`🎉 领域列表`, `scene_aware_lookup_enabled_domains: ${JSON.stringify(body.scene_aware_lookup_enabled_domains)}`, ""); - } - body.min_query_len = 3; - let Overrides = body?.overrides; - if (Overrides) [...new Set([...Object.keys(Overrides), ...Settings.Functions])].forEach(Function => { - log(`🎉 覆盖列表`, `Function: ${Function}`, ""); - //_.set(Overrides, `${Function}.enabled`, true); - //_.set(Overrides, `${Function}.feedback_enabled`, true); - switch (Function) { - case "flightutilities": - Lodash.set(Overrides, "flightutilities.enabled", true); - Lodash.set(Overrides, "flightutilities.feedback_enabled", true); - //_.set(Overrides, "flightutilities.flight_url", "https:\/\/api-glb-aps1b.smoot.apple.com\/flight"); - //_.set(Overrides, "flightutilities.fallback_flight_url", "https:\/\/api-glb-apse1c.smoot.apple.com\/flight"); - break; - case "lookup": - Lodash.set(Overrides, "lookup.enabled", true); - Lodash.set(Overrides, "lookup.feedback_enabled", true); - //_.set(Overrides, "lookup.min_query_len", 2); - //_.set(Overrides, "lookup.search_render_timeout", 2000); - break; - case "mail": - Lodash.set(Overrides, "mail.enabled", true); - Lodash.set(Overrides, "mail.feedback_enabled", true); - break; - case "messages": - Lodash.set(Overrides, "messages.enabled", true); - Lodash.set(Overrides, "messages.feedback_enabled", true); - break; - case "news": - Lodash.set(Overrides, "news.enabled", true); - Lodash.set(Overrides, "news.feedback_enabled", true); - break; - case "safari": - Lodash.set(Overrides, "safari.enabled", true); - Lodash.set(Overrides, "safari.feedback_enabled", true); - Lodash.set(Overrides, "safari.experiments_custom_feedback_enabled", true); - break; - case "spotlight": - Lodash.set(Overrides, "spotlight.enabled", true); - Lodash.set(Overrides, "spotlight.feedback_enabled", true); - //_.set(Overrides, "spotlight.use_twolayer_ranking", true); - //_.set(Overrides, "spotlight.experiments_custom_feedback_enabled", true); - //_.set(Overrides, "spotlight.min_query_len", 2); - //_.set(Overrides, "spotlight.collect_scores", true); - //_.set(Overrides, "spotlight.collect_anonymous_metadata", true); - break; - case "visualintelligence": - Lodash.set(Overrides, "visualintelligence.enabled", true); - Lodash.set(Overrides, "visualintelligence.feedback_enabled", true); - Lodash.set(Overrides, "visualintelligence.enabled_domains", [...new Set([...Overrides.visualIntelligence?.enabled_domains ?? [], ...Configs.VisualIntelligence.enabled_domains])]); - Lodash.set(Overrides, "visualintelligence.supported_domains", [...new Set([...Overrides.visualIntelligence?.supported_domains ?? [], ...Configs.VisualIntelligence.supported_domains])]); - break; - } - }); - // Safari Smart History - body.safari_smart_history_enabled = (Settings.Safari_Smart_History) ? true : false; - body.smart_history_feature_feedback_enabled = (Settings.Safari_Smart_History) ? true : false; - /* - if (body?.mescal_enabled) { - body.mescal_enabled = true; - body.mescal_version = 200; - body.mescal_cert_url = "https://init.itunes.apple.com/WebObjects/MZInit.woa/wa/signSapSetupCert"; - body.mescal_setup_url = "https://play.itunes.apple.com/WebObjects/MZPlay.woa/wa/signSapSetup"; - } - let smart_search_v2 = body?.smart_search_v2_parameters; - if (smart_search_v2) { - smart_search_v2.smart_history_score_v2_enabled = true; - smart_search_v2.smart_history_score_v2_enable_count = true; - }; - body.session_experiment_metadata_enabled = true; - //body.sample_features = true; - //body.use_ledbelly = true; - */ - break; - } break; - } $response.body = JSON.stringify(body); - break; - case "application/protobuf": - case "application/x-protobuf": - case "application/vnd.google.protobuf": - case "application/grpc": - case "application/grpc+proto": - case "application/octet-stream": - //log(`🚧 $response.body: ${JSON.stringify($response.body)}`, ""); - let rawBody = ($platform === "Quantumult X") ? new Uint8Array($response.bodyBytes ?? []) : $response.body ?? new Uint8Array(); - //log(`🚧 isBuffer? ${ArrayBuffer.isView(rawBody)}: ${JSON.stringify(rawBody)}`, ""); - switch (FORMAT) { - case "application/protobuf": - case "application/x-protobuf": - case "application/vnd.google.protobuf": - break; - case "application/grpc": - case "application/grpc+proto": - rawBody = GRPC.decode(rawBody); - rawBody = GRPC.encode(rawBody); - break; - } // 写入二进制数据 - $response.body = rawBody; - break; - } break; - case false: - break; - }})() - .catch((e) => logError(e)) - .finally(() => done($response)); diff --git a/js/Siri.response.js b/js/Siri.response.js deleted file mode 100644 index 9532c478e..000000000 --- a/js/Siri.response.js +++ /dev/null @@ -1 +0,0 @@ -console.log(" iRingo: ⭕ Siri Response");const e=function(){if("undefined"!=typeof $environment&&$environment["surge-version"])return"Surge";if("undefined"!=typeof $environment&&$environment["stash-version"])return"Stash";if("undefined"!=typeof module&&module.exports)return"Node.js";if("undefined"!=typeof $task)return"Quantumult X";if("undefined"!=typeof $loon)return"Loon";if("undefined"!=typeof $rocket)return"Shadowrocket";if("undefined"!=typeof Egern)return"Egern"}();class t{static name="Lodash";static version="1.2.2";static about(){return console.log(`\n🟧 ${this.name} v${this.version}\n`)}static get(e={},t="",s=void 0){Array.isArray(t)||(t=this.toPath(t));const a=t.reduce(((e,t)=>Object(e)[t]),e);return void 0===a?s:a}static set(e={},t="",s){return Array.isArray(t)||(t=this.toPath(t)),t.slice(0,-1).reduce(((e,s,a)=>Object(e[s])===e[s]?e[s]:e[s]=/^\d+$/.test(t[a+1])?[]:{}),e)[t[t.length-1]]=s,e}static unset(e={},t=""){return Array.isArray(t)||(t=this.toPath(t)),t.reduce(((e,s,a)=>a===t.length-1?(delete e[s],!0):Object(e)[s]),e)}static toPath(e){return e.replace(/\[(\d+)\]/g,".$1").split(".").filter(Boolean)}static escape(e){const t={"&":"&","<":"<",">":">",'"':""","'":"'"};return e.replace(/[&<>"']/g,(e=>t[e]))}static unescape(e){const t={"&":"&","<":"<",">":">",""":'"',"'":"'"};return e.replace(/&|<|>|"|'/g,(e=>t[e]))}}class s{static name="Storage";static version="1.1.0";static about(){return a("",`🟧 ${this.name} v${this.version}`,"")}static data=null;static dataFile="box.dat";static#e=/^@(?[^.]+)(?:\.(?.*))?$/;static getItem(s=new String,a=null){let i=a;if(!0===s.startsWith("@")){const{key:e,path:a}=s.match(this.#e)?.groups;s=e;let n=this.getItem(s,{});"object"!=typeof n&&(n={}),i=t.get(n,a);try{i=JSON.parse(i)}catch(e){}}else{switch(e){case"Surge":case"Loon":case"Stash":case"Egern":case"Shadowrocket":i=$persistentStore.read(s);break;case"Quantumult X":i=$prefs.valueForKey(s);break;case"Node.js":this.data=this.#t(this.dataFile),i=this.data?.[s];break;default:i=this.data?.[s]||null}try{i=JSON.parse(i)}catch(e){}}return i??a}static setItem(s=new String,a=new String){let i=!1;if("object"==typeof a)a=JSON.stringify(a);else a=String(a);if(!0===s.startsWith("@")){const{key:e,path:n}=s.match(this.#e)?.groups;s=e;let o=this.getItem(s,{});"object"!=typeof o&&(o={}),t.set(o,n,a),i=this.setItem(s,o)}else switch(e){case"Surge":case"Loon":case"Stash":case"Egern":case"Shadowrocket":i=$persistentStore.write(a,s);break;case"Quantumult X":i=$prefs.setValueForKey(a,s);break;case"Node.js":this.data=this.#t(this.dataFile),this.data[s]=a,this.#s(this.dataFile),i=!0;break;default:i=this.data?.[s]||null}return i}static removeItem(s){let a=!1;if(!0===s.startsWith("@")){const{key:e,path:i}=s.match(this.#e)?.groups;s=e;let n=this.getItem(s);"object"!=typeof n&&(n={}),keyValue=t.unset(n,i),a=this.setItem(s,n)}else switch(e){case"Surge":case"Loon":case"Stash":case"Egern":case"Shadowrocket":case"Node.js":default:a=!1;break;case"Quantumult X":a=$prefs.removeValueForKey(s)}return a}static clear(){let t=!1;switch(e){case"Surge":case"Loon":case"Stash":case"Egern":case"Shadowrocket":case"Node.js":default:t=!1;break;case"Quantumult X":t=$prefs.removeAllValues()}return t}static#t(e){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(e),s=this.path.resolve(process.cwd(),e),a=this.fs.existsSync(t),i=!a&&this.fs.existsSync(s);if(!a&&!i)return{};{const e=a?t:s;try{return JSON.parse(this.fs.readFileSync(e))}catch(e){return{}}}}}static#s(e=this.dataFile){if(this.isNode()){this.fs=this.fs?this.fs:require("fs"),this.path=this.path?this.path:require("path");const t=this.path.resolve(e),s=this.path.resolve(process.cwd(),e),a=this.fs.existsSync(t),i=!a&&this.fs.existsSync(s),n=JSON.stringify(this.data);a?this.fs.writeFileSync(t,n):i?this.fs.writeFileSync(s,n):this.fs.writeFileSync(t,n)}}}const a=(...e)=>console.log(e.join("\n"));var i={Switch:!0},n={Storefront:[["AE","143481"],["AF","143610"],["AG","143540"],["AI","143538"],["AL","143575"],["AM","143524"],["AO","143564"],["AR","143505"],["AT","143445"],["AU","143460"],["AZ","143568"],["BA","143612"],["BB","143541"],["BD","143490"],["BE","143446"],["BF","143578"],["BG","143526"],["BH","143559"],["BJ","143576"],["BM","143542"],["BN","143560"],["BO","143556"],["BR","143503"],["BS","143539"],["BT","143577"],["BW","143525"],["BY","143565"],["BZ","143555"],["CA","143455"],["CD","143613"],["CG","143582"],["CH","143459"],["CI","143527"],["CL","143483"],["CM","143574"],["CN","143465"],["CO","143501"],["CR","143495"],["CV","143580"],["CY","143557"],["CZ","143489"],["DE","143443"],["DK","143458"],["DM","143545"],["DO","143508"],["DZ","143563"],["EC","143509"],["EE","143518"],["EG","143516"],["ES","143454"],["FI","143447"],["FJ","143583"],["FM","143591"],["FR","143442"],["GA","143614"],["GB","143444"],["GD","143546"],["GF","143615"],["GH","143573"],["GM","143584"],["GR","143448"],["GT","143504"],["GW","143585"],["GY","143553"],["HK","143463"],["HN","143510"],["HR","143494"],["HU","143482"],["ID","143476"],["IE","143449"],["IL","143491"],["IN","143467"],["IQ","143617"],["IS","143558"],["IT","143450"],["JM","143511"],["JO","143528"],["JP","143462"],["KE","143529"],["KG","143586"],["KH","143579"],["KN","143548"],["KP","143466"],["KR","143466"],["KW","143493"],["KY","143544"],["KZ","143517"],["TC","143552"],["TD","143581"],["TJ","143603"],["TH","143475"],["TM","143604"],["TN","143536"],["TO","143608"],["TR","143480"],["TT","143551"],["TW","143470"],["TZ","143572"],["LA","143587"],["LB","143497"],["LC","143549"],["LI","143522"],["LK","143486"],["LR","143588"],["LT","143520"],["LU","143451"],["LV","143519"],["LY","143567"],["MA","143620"],["MD","143523"],["ME","143619"],["MG","143531"],["MK","143530"],["ML","143532"],["MM","143570"],["MN","143592"],["MO","143515"],["MR","143590"],["MS","143547"],["MT","143521"],["MU","143533"],["MV","143488"],["MW","143589"],["MX","143468"],["MY","143473"],["MZ","143593"],["NA","143594"],["NE","143534"],["NG","143561"],["NI","143512"],["NL","143452"],["NO","143457"],["NP","143484"],["NR","143606"],["NZ","143461"],["OM","143562"],["PA","143485"],["PE","143507"],["PG","143597"],["PH","143474"],["PK","143477"],["PL","143478"],["PT","143453"],["PW","143595"],["PY","143513"],["QA","143498"],["RO","143487"],["RS","143500"],["RU","143469"],["RW","143621"],["SA","143479"],["SB","143601"],["SC","143599"],["SE","143456"],["SG","143464"],["SI","143499"],["SK","143496"],["SL","143600"],["SN","143535"],["SR","143554"],["ST","143598"],["SV","143506"],["SZ","143602"],["UA","143492"],["UG","143537"],["US","143441"],["UY","143514"],["UZ","143566"],["VC","143550"],["VE","143502"],["VG","143543"],["VN","143471"],["VU","143609"],["XK","143624"],["YE","143571"],["ZA","143472"],["ZM","143622"],["ZW","143605"]]},o={Settings:i,Configs:n},r={Switch:!0,PEP:{GCC:"US"}},c={Settings:r},l={Switch:!0,UrlInfoSet:{Dispatcher:"AutoNavi",Directions:"AutoNavi",RAP:"Apple",LocationShift:"AUTO"},TileSet:{Map:"CN",Satellite:"HYBRID",Traffic:"CN",POI:"CN",Flyover:"XX",Munin:"XX"},GeoManifest:{Dynamic:{Config:{CountryCode:{default:"CN",iOS:"AUTO",iPadOS:"AUTO",watchOS:"US",macOS:"AUTO"}}}},Config:{Announcements:{"Environment:":{default:"AUTO",iOS:"AUTO",iPadOS:"AUTO",watchOS:"AUTO",macOS:"AUTO"}}}},p={},h={Settings:l,Configs:p},d={Switch:!0,CountryCode:"US",NewsPlusUser:!0},u={Settings:d},S={Switch:!0,CountryCode:"US",canUse:!0},m={Settings:S},f={Switch:!0,CountryCode:"SG",Region:"AUTO",Domains:["web","itunes","app_store","movies","restaurants","maps"],Functions:["flightutilities","lookup","mail","messages","news","safari","siri","spotlight","visualintelligence"],Safari_Smart_History:!0},g={VisualIntelligence:{enabled_domains:["pets","media","books","art","nature","landmarks"],supported_domains:["ART","BOOK","MEDIA","LANDMARK","ANIMALS","BIRDS","FOOD","SIGN_SYMBOL","AUTO_SYMBOL","DOGS","NATURE","NATURAL_LANDMARK","INSECTS","REPTILES","ALBUM","STOREFRONT","LAUNDRY_CARE_SYMBOL","CATS","OBJECT_2D","SCULPTURE","SKYLINE","MAMMALS"]}},y={Settings:f,Configs:g},b={Switch:"true",CountryCode:"US",MultiAccount:"false",Universal:"true"},v={Settings:b},A={Switch:!0,"Third-Party":!1,HLSUrl:"play-edge.itunes.apple.com",ServerUrl:"play.itunes.apple.com",Tabs:["WatchNow","Originals","MLS","Sports","Kids","Store","Movies","TV","ChannelsAndApps","Library","Search"],CountryCode:{Configs:"AUTO",Settings:"AUTO",View:["SG","TW"],WatchNow:"AUTO",Channels:"AUTO",Originals:"AUTO",Sports:"US",Kids:"US",Store:"AUTO",Movies:"AUTO",TV:"AUTO",Persons:"SG",Search:"AUTO",Others:"AUTO"}},T={Locale:[["AU","en-AU"],["CA","en-CA"],["GB","en-GB"],["KR","ko-KR"],["HK","yue-Hant"],["JP","ja-JP"],["MO","zh-Hant"],["TW","zh-Hant"],["US","en-US"],["SG","zh-Hans"]],Tabs:[{title:"主页",type:"WatchNow",universalLinks:["https://tv.apple.com/watch-now","https://tv.apple.com/home"],destinationType:"Target",target:{id:"tahoma_watchnow",type:"Root",url:"https://tv.apple.com/watch-now"},isSelected:!0},{title:"Apple TV+",type:"Originals",universalLinks:["https://tv.apple.com/channel/tvs.sbd.4000","https://tv.apple.com/atv"],destinationType:"Target",target:{id:"tvs.sbd.4000",type:"Brand",url:"https://tv.apple.com/us/channel/tvs.sbd.4000"}},{title:"MLS Season Pass",type:"MLS",universalLinks:["https://tv.apple.com/mls"],destinationType:"Target",target:{id:"tvs.sbd.7000",type:"Brand",url:"https://tv.apple.com/us/channel/tvs.sbd.7000"}},{title:"体育节目",type:"Sports",universalLinks:["https://tv.apple.com/sports"],destinationType:"Target",target:{id:"tahoma_sports",type:"Root",url:"https://tv.apple.com/sports"}},{title:"儿童",type:"Kids",universalLinks:["https://tv.apple.com/kids"],destinationType:"Target",target:{id:"tahoma_kids",type:"Root",url:"https://tv.apple.com/kids"}},{title:"电影",type:"Movies",universalLinks:["https://tv.apple.com/movies"],destinationType:"Target",target:{id:"tahoma_movies",type:"Root",url:"https://tv.apple.com/movies"}},{title:"电视节目",type:"TV",universalLinks:["https://tv.apple.com/tv-shows"],destinationType:"Target",target:{id:"tahoma_tvshows",type:"Root",url:"https://tv.apple.com/tv-shows"}},{title:"商店",type:"Store",universalLinks:["https://tv.apple.com/store"],destinationType:"SubTabs",subTabs:[{title:"电影",type:"Movies",universalLinks:["https://tv.apple.com/movies"],destinationType:"Target",target:{id:"tahoma_movies",type:"Root",url:"https://tv.apple.com/movies"}},{title:"电视节目",type:"TV",universalLinks:["https://tv.apple.com/tv-shows"],destinationType:"Target",target:{id:"tahoma_tvshows",type:"Root",url:"https://tv.apple.com/tv-shows"}}]},{title:"频道和 App",destinationType:"SubTabs",subTabsPlacementType:"ExpandedList",type:"ChannelsAndApps",subTabs:[]},{title:"资料库",type:"Library",destinationType:"Client"},{title:"搜索",type:"Search",universalLinks:["https://tv.apple.com/search"],destinationType:"Target",target:{id:"tahoma_search",type:"Root",url:"https://tv.apple.com/search"}}],i18n:{WatchNow:[["en","Home"],["zh","主页"],["zh-Hans","主頁"],["zh-Hant","主頁"]],Movies:[["en","Movies"],["zh","电影"],["zh-Hans","电影"],["zh-Hant","電影"]],TV:[["en","TV"],["zh","电视节目"],["zh-Hans","电视节目"],["zh-Hant","電視節目"]],Store:[["en","Store"],["zh","商店"],["zh-Hans","商店"],["zh-Hant","商店"]],Sports:[["en","Sports"],["zh","体育节目"],["zh-Hans","体育节目"],["zh-Hant","體育節目"]],Kids:[["en","Kids"],["zh","儿童"],["zh-Hans","儿童"],["zh-Hant","兒童"]],Library:[["en","Library"],["zh","资料库"],["zh-Hans","资料库"],["zh-Hant","資料庫"]],Search:[["en","Search"],["zh","搜索"],["zh-Hans","搜索"],["zh-Hant","蒐索"]]}},_={Settings:A,Configs:T},w=Database={Default:Object.freeze({__proto__:null,Configs:n,Settings:i,default:o}),Location:Object.freeze({__proto__:null,Settings:r,default:c}),Maps:Object.freeze({__proto__:null,Configs:p,Settings:l,default:h}),News:Object.freeze({__proto__:null,Settings:d,default:u}),PrivateRelay:Object.freeze({__proto__:null,Settings:S,default:m}),Siri:Object.freeze({__proto__:null,Configs:g,Settings:f,default:y}),TestFlight:Object.freeze({__proto__:null,Settings:b,default:v}),TV:Object.freeze({__proto__:null,Configs:T,Settings:A,default:_})};function O(e,i,n){a("☑️ Set Environment Variables","");let{Settings:o,Caches:r,Configs:c}=function(e,a,i){let n=s.getItem(e,i),o={};switch(typeof $argument){case"string":let e=Object.fromEntries($argument.split("&").map((e=>e.split("=").map((e=>e.replace(/\"/g,""))))));for(let s in e)t.set(o,s,e[s]);break;case"object":for(let e in $argument)t.set(o,e,$argument[e])}const r={Settings:i?.Default?.Settings||{},Configs:i?.Default?.Configs||{},Caches:{}};Array.isArray(a)||(a=[a]);for(let e of a)r.Settings={...r.Settings,...i?.[e]?.Settings,...o,...n?.[e]?.Settings},r.Configs={...r.Configs,...i?.[e]?.Configs},n?.[e]?.Caches&&"string"==typeof n?.[e]?.Caches&&(n[e].Caches=JSON.parse(n?.[e]?.Caches)),r.Caches={...r.Caches,...n?.[e]?.Caches};return function e(t,s){for(var a in t){var i=t[a];t[a]="object"==typeof i&&null!==i?e(i,s):s(a,i)}return t}(r.Settings,((e,t)=>("true"===t||"false"===t?t=JSON.parse(t):"string"==typeof t&&(t=t.includes(",")?t.split(",").map((e=>c(e))):c(t)),t))),r;function c(e){return e&&!isNaN(e)&&(e=parseInt(e,10)),e}}(e,i,n);switch(i){case"WeatherKit":Array.isArray(o?.AQI?.ReplaceProviders)||t.set(o,"AQI.ReplaceProviders",o?.AQI?.ReplaceProviders?[o.AQI.ReplaceProviders.toString()]:[]),o.AQI.ReplaceProviders.includes("TWC")&&o.AQI.ReplaceProviders.push("The Weather Channel"),o.AQI.ReplaceProviders.includes("QWeather")&&o.AQI.ReplaceProviders.push("和风天气"),o.AQI.ReplaceProviders.push(void 0),Array.isArray(o?.AQI?.Local?.ReplaceScales)||t.set(o,"AQI.Local.ReplaceScales",o?.AQI?.Local?.ReplaceScales?[o.AQI.Local.ReplaceScales.toString()]:[]);break;case"Siri":Array.isArray(o?.Domains)||t.set(o,"Domains",o?.Domains?[o.Domains.toString()]:[]),Array.isArray(o?.Functions)||t.set(o,"Functions",o?.Functions?[o.Functions.toString()]:[]);break;case"TV":Array.isArray(o?.Tabs)||t.set(o,"Tabs",o?.Tabs?[o.Tabs.toString()]:[])}if(a(`✅ Set Environment Variables, Settings: ${typeof o}, Settings内容: ${JSON.stringify(o)}`,""),c.Storefront=new Map(c.Storefront),c.Locale&&(c.Locale=new Map(c.Locale)),c.i18n)for(let e in c.i18n)c.i18n[e]=new Map(c.i18n[e]);return{Settings:o,Caches:r,Configs:c}}a("v4.0.2(4003)");const C=new class{constructor(e,t=void 0){return console.log("\n🟧 URL v2.1.2\n"),e=this.#a(e,t),this}#a(e,t=void 0){const s=/(?:(?\w+:)\/\/(?:(?[^\s:"]+)(?::(?[^\s:"]+))?@)?(?[^\s@/]+))?(?\/?[^\s@?]+)?(?\?[^\s?]+)?/,a=/(?.+):(?\d+)$/;if(e=e.match(s)?.groups||{},t&&(!(t=t?.match(s)?.groups||{}).protocol||!t.hostname))throw new Error(`🚨 ${name}, ${t} is not a valid URL`);if((e.protocol||t?.protocol)&&(this.protocol=e.protocol||t.protocol),(e.username||t?.username)&&(this.username=e.username||t.username),(e.password||t?.password)&&(this.password=e.password||t.password),(e.host||t?.host)&&(this.host=e.host||t.host,Object.freeze(this.host),this.hostname=this.host.match(a)?.groups.hostname??this.host,this.port=this.host.match(a)?.groups.port??""),e.pathname||t?.pathname){if(this.pathname=e.pathname||t?.pathname,this.pathname.startsWith("/")||(this.pathname="/"+this.pathname),this.paths=this.pathname.split("/").filter(Boolean),Object.freeze(this.paths),this.paths){const e=this.paths[this.paths.length-1];if(e?.includes(".")){const t=e.split(".");this.format=t[t.length-1],Object.freeze(this.format)}}}else this.pathname="";return(e.search||t?.search)&&(this.search=e.search||t.search,Object.freeze(this.search),this.search&&(this.searchParams=this.search.slice(1).split("&").map((e=>e.split("="))))),this.searchParams=new Map(this.searchParams||[]),this.harf=this.toString(),Object.freeze(this.harf),this}toString(){let e="";return this.protocol&&(e+=this.protocol+"//"),this.username&&(e+=this.username+(this.password?":"+this.password:"")+"@"),this.hostname&&(e+=this.hostname),this.port&&(e+=":"+this.port),this.pathname&&(e+=this.pathname),0!==this.searchParams.size&&(e+="?"+Array.from(this.searchParams).map((e=>e.join("="))).join("&")),e}toJSON(){return JSON.stringify({...this})}}($request.url);a(`⚠ url: ${C.toJSON()}`,"");const k=$request.method,L=C.hostname,R=C.pathname;C.pathname.split("/").filter(Boolean),a(`⚠ METHOD: ${k}, HOST: ${L}, PATH: ${R}`,"");const N=($response.headers?.["Content-Type"]??$response.headers?.["content-type"])?.split(";")?.[0];a(`⚠ FORMAT: ${N}`,""),(async()=>{const{Settings:e,Caches:s,Configs:i}=O("iRingo","Siri",w);switch(a(`⚠ Settings.Switch: ${e?.Switch}`,""),e.Switch){case!0:default:let s={};switch(N){case void 0:case"application/x-www-form-urlencoded":case"text/plain":default:case"application/x-mpegURL":case"application/x-mpegurl":case"application/vnd.apple.mpegurl":case"audio/mpegurl":case"text/xml":case"text/html":case"text/plist":case"application/xml":case"application/plist":case"application/x-plist":case"text/vtt":case"application/vtt":break;case"text/json":case"application/json":switch(s=JSON.parse($response.body??"{}"),L){case"api.smoot.apple.com":case"api.smoot.apple.cn":if("/bag"===R){s.enabled=!0,s.feedback_enabled=!0,s?.enabled_domains&&(s.enabled_domains=[...new Set([...s?.enabled_domains??[],...e.Domains])],a("🎉 领域列表",`enabled_domains: ${JSON.stringify(s.enabled_domains)}`,"")),s?.scene_aware_lookup_enabled_domains&&(s.scene_aware_lookup_enabled_domains=[...new Set([...s?.scene_aware_lookup_enabled_domains??[],...e.Domains])],a("🎉 领域列表",`scene_aware_lookup_enabled_domains: ${JSON.stringify(s.scene_aware_lookup_enabled_domains)}`,"")),s.min_query_len=3;let n=s?.overrides;n&&[...new Set([...Object.keys(n),...e.Functions])].forEach((e=>{switch(a("🎉 覆盖列表",`Function: ${e}`,""),e){case"flightutilities":t.set(n,"flightutilities.enabled",!0),t.set(n,"flightutilities.feedback_enabled",!0);break;case"lookup":t.set(n,"lookup.enabled",!0),t.set(n,"lookup.feedback_enabled",!0);break;case"mail":t.set(n,"mail.enabled",!0),t.set(n,"mail.feedback_enabled",!0);break;case"messages":t.set(n,"messages.enabled",!0),t.set(n,"messages.feedback_enabled",!0);break;case"news":t.set(n,"news.enabled",!0),t.set(n,"news.feedback_enabled",!0);break;case"safari":t.set(n,"safari.enabled",!0),t.set(n,"safari.feedback_enabled",!0),t.set(n,"safari.experiments_custom_feedback_enabled",!0);break;case"spotlight":t.set(n,"spotlight.enabled",!0),t.set(n,"spotlight.feedback_enabled",!0);break;case"visualintelligence":t.set(n,"visualintelligence.enabled",!0),t.set(n,"visualintelligence.feedback_enabled",!0),t.set(n,"visualintelligence.enabled_domains",[...new Set([...n.visualIntelligence?.enabled_domains??[],...i.VisualIntelligence.enabled_domains])]),t.set(n,"visualintelligence.supported_domains",[...new Set([...n.visualIntelligence?.supported_domains??[],...i.VisualIntelligence.supported_domains])])}})),s.safari_smart_history_enabled=!!e.Safari_Smart_History,s.smart_history_feature_feedback_enabled=!!e.Safari_Smart_History}}$response.body=JSON.stringify(s);case"application/protobuf":case"application/x-protobuf":case"application/vnd.google.protobuf":case"application/grpc":case"application/grpc+proto":case"application/octet-stream":}case!1:}})().catch((t=>function(t){switch(e){case"Surge":case"Loon":case"Stash":case"Egern":case"Shadowrocket":case"Quantumult X":default:a("","❗️执行错误!",t,"");break;case"Node.js":a("","❗️执行错误!",t.stack,"")}}(t))).finally((()=>function(s={}){switch(e){case"Surge":s.policy&&t.set(s,"headers.X-Surge-Policy",s.policy),a("",`🚩 执行结束! 🕛 ${(new Date).getTime()/1e3-$script.startTime} 秒`,""),$done(s);break;case"Loon":s.policy&&(s.node=s.policy),a("",`🚩 执行结束! 🕛 ${(new Date-$script.startTime)/1e3} 秒`,""),$done(s);break;case"Stash":s.policy&&t.set(s,"headers.X-Stash-Selected-Proxy",encodeURI(s.policy)),a("",`🚩 执行结束! 🕛 ${(new Date-$script.startTime)/1e3} 秒`,""),$done(s);break;case"Egern":case"Shadowrocket":default:a("","🚩 执行结束!",""),$done(s);break;case"Quantumult X":s.policy&&t.set(s,"opts.policy",s.policy),delete s["auto-redirect"],delete s["auto-cookie"],delete s["binary-mode"],delete s.charset,delete s.host,delete s.insecure,delete s.method,delete s.opt,delete s.path,delete s.policy,delete s["policy-descriptor"],delete s.scheme,delete s.sessionIndex,delete s.statusCode,delete s.timeout,s.body instanceof ArrayBuffer?(s.bodyBytes=s.body,delete s.body):ArrayBuffer.isView(s.body)?(s.bodyBytes=s.body.buffer.slice(s.body.byteOffset,s.body.byteLength+s.body.byteOffset),delete s.body):s.body&&delete s.bodyBytes,a("","🚩 执行结束!",""),$done(s);break;case"Node.js":a("","🚩 执行结束!",""),process.exit(1)}}($response))); diff --git a/js/TV.response.beta.js b/js/TV.response.beta.js index 469dbb14d..afdec6d19 100644 --- a/js/TV.response.beta.js +++ b/js/TV.response.beta.js @@ -2155,6 +2155,160 @@ log(`⚠ FORMAT: ${FORMAT}`, ""); case "shows": // uts/v3/shows/ case "episodes": // uts/v3/episodes/ case "sporting-events": // uts/v3/sporting-events/ + let Type = "restoreLowPriceRegion"; + if (Settings[Type] !== "DISABLED") { + let sf_ = Configs.Storefront.get(Settings.CountryCode[Type]); + console.log("ss sf_:" + sf_); + let requestUrl = $request.url; + let requestHeaders = $request.headers; + let url1 = new URL(requestUrl); + + let sf = url1.searchParams.get("sf"); + console.log("ss sf:" + sf); + url1.searchParams.set("sf", "143467"); + requestUrl = url1.toString(); + + try { + let bodyModify = body; + let result = await makeRequest(requestUrl, requestHeaders); + let newBody = JSON.parse(result.data); + + if (newBody.data && newBody.data.content && newBody.data.content) { + if (bodyModify.data.content.caption) { + console.log("bodyModify.data.content.caption: " + bodyModify.data.content.caption); + console.log("newBody.data.content.caption: " + newBody.data.content.caption); + newBody.data.content.caption = bodyModify.data.content.caption; + } + if (bodyModify.data.content.title) { + console.log("bodyModify.data.content.title: " + bodyModify.data.content.title); + console.log("newBody.data.content.title: " + newBody.data.content.title); + newBody.data.content.title = bodyModify.data.content.title; + } + if (bodyModify.data.content.description) { + console.log("bodyModify.data.content.description: " + bodyModify.data.content.description); + console.log("newBody.data.content.description: " + newBody.data.content.description); + newBody.data.content.description = bodyModify.data.content.description; + } + } + if (newBody.data && newBody.data.content && newBody.data.content.images) { + newBody.data.content.images = bodyModify.data.content.images; + } + if (newBody.data && newBody.data.smartEpisode && newBody.data.smartEpisode.description) { + newBody.data.smartEpisode.description = bodyModify.data.smartEpisode.description; + } + if (newBody.data && newBody.data.content && newBody.data.content.genres) { + newBody.data.content.genres = bodyModify.data.content.genres; + } + let newPlayablesKeys = newBody.data && newBody.data.playables ? Object.keys(newBody.data.playables) : []; + console.log("newBody.data.playables 的键: " + newPlayablesKeys.join(", ")); + + let oldPlayablesKeys = bodyModify.data && bodyModify.data.playables ? Object.keys(bodyModify.data.playables) : []; + + if (newBody.data && newBody.data.playables && Object.keys(newBody.data.playables).length > 0) { + newPlayablesKeys.forEach(newKey => { + let newPlayable = newBody.data.playables[newKey]; + // 使用正则表达式匹配键,忽略最后的哈希部分 + let matchingOldKey = oldPlayablesKeys.find(oldKey => { + return oldKey.replace(/:[^:]+$/, '') === newKey.replace(/:[^:]+$/, ''); + }); + + if (matchingOldKey && bodyModify.data.playables[matchingOldKey]) { + if (newPlayable.canonicalMetadata && newPlayable.canonicalMetadata.episodeTitle) { + newBody.data.playables[newKey].canonicalMetadata.episodeTitle = bodyModify.data.playables[matchingOldKey].canonicalMetadata.episodeTitle; + } + if (newPlayable.title) { + newBody.data.playables[newKey].title = bodyModify.data.playables[matchingOldKey].title; + } + } + }); + } + + if (newBody.data && newBody.data.smartEpisode && newBody.data.smartEpisode.description) { + newBody.data.smartEpisode.description = bodyModify.data.smartEpisode.description; + newBody.data.smartEpisode.title = bodyModify.data.smartEpisode.title; + } + + if (newBody.data && newBody.data.episodes && newBody.data.episodes.title) { + console.log("newBody.data.episodes.title: " + newBody.data.episodes.title); + console.log("bodyModify.data.episodes.title: " + bodyModify.data.episodes.title); + newBody.data.episodes.title = bodyModify.data.episodes.title; + } + + if (newBody.data && newBody.data.episodes && Array.isArray(newBody.data.episodes)) { + bodyModify.data.episodes = bodyModify.data.episodes || []; + let updatedEpisodes = newBody.data.episodes.map((newEpisode, index) => { + let modifiedEpisode = { ...newEpisode }; + if (index < bodyModify.data.episodes.length) { + // 保留原始的标题和描述 + if (bodyModify.data.episodes[index].title) { + modifiedEpisode.title = bodyModify.data.episodes[index].title; + } + if (bodyModify.data.episodes[index].description) { + modifiedEpisode.description = bodyModify.data.episodes[index].description; + } + + } + return modifiedEpisode; + }); + newBody.data.episodes = updatedEpisodes; + } + + if (newBody.data && newBody.data.seasonSummaries && Array.isArray(newBody.data.seasonSummaries)) { + bodyModify.data.seasonSummaries = bodyModify.data.seasonSummaries || []; + let updatedSeasonSummaries = newBody.data.seasonSummaries.map((newSeasonSummary, index) => { + let modifiedSeasonSummary = { ...newSeasonSummary }; + if (index < bodyModify.data.seasonSummaries.length) { + // 保留原始的标题 + if (bodyModify.data.seasonSummaries[index].title) { + modifiedSeasonSummary.title = bodyModify.data.seasonSummaries[index].title; + } + } + return modifiedSeasonSummary; + }); + newBody.data.seasonSummaries = updatedSeasonSummaries; + } + + // shelves title + if (newBody.data && newBody.data.canvas && newBody.data.canvas.shelves && Array.isArray(newBody.data.canvas.shelves)) { + newBody.data.canvas.shelves.forEach((newShelf, index) => { + if (index < bodyModify.data.canvas.shelves.length) { + // 保留原始的标题 + if (bodyModify.data.canvas.shelves[index].title) { + newBody.data.canvas.shelves[index].title = bodyModify.data.canvas.shelves[index].title; + } + if (bodyModify.data.canvas.shelves[index].items && Array.isArray(bodyModify.data.canvas.shelves[index].items)) { + bodyModify.data.canvas.shelves[index].items.forEach((newItem, itemIndex) => { + if (itemIndex < newBody.data.canvas.shelves[index].items.length) { + // 保留原始的标题和描述 + if (newItem.title) { + newBody.data.canvas.shelves[index].items[itemIndex].title = newItem.title; + } + if (newItem.images) { + newBody.data.canvas.shelves[index].items[itemIndex].images = newItem.images; + } + if (newItem.description) { + newBody.data.canvas.shelves[index].items[itemIndex].description = newItem.description; + } + if (newItem.shortNote) { + newBody.data.canvas.shelves[index].items[itemIndex].shortNote = newItem.shortNote; + } + if (newItem.characterName) { + newBody.data.canvas.shelves[index].items[itemIndex].characterName = newItem.characterName; + } + } + }); + } + } + }); + } + + body = newBody; + } catch (e) { + console.log(e); + } + } + console.log("ccccccccccccccccccccccccccccc"); + let shelves = body?.data?.canvas?.shelves; let backgroundVideo = body?.data?.content?.backgroundVideo; let playables = body?.data?.playables; @@ -2281,6 +2435,19 @@ function setPlayable(playable, HLSUrl, ServerUrl) { fpsNonceServerUrl.pathname = "/WebObjects/MZPlayLocal.woa/wa/checkInNonceRequest"; break; } asset.fpsNonceServerUrl = fpsNonceServerUrl.toString(); - } log(`✅ Set Url`, ""); + } log(`✅ Set Url`, ""); return asset; }} + + +function makeRequest(url, headers) { + return new Promise((resolve, reject) => { + $httpClient.get({ url: url, headers: headers }, function (error, response, data) { + if (error) { + reject("请求失败: " + error); + } else { + resolve({ response: response, data: data }); + } + }); + }); +} \ No newline at end of file diff --git a/js/archive/Siri.request.js b/js/archive/Siri.request.js deleted file mode 100644 index 29f682fd0..000000000 --- a/js/archive/Siri.request.js +++ /dev/null @@ -1 +0,0 @@ -class e{static name="Lodash";static version="1.2.2";static about(){return console.log(`\n🟧 ${this.name} v${this.version}\n`)}static get(e={},i="",a=void 0){Array.isArray(i)||(i=this.toPath(i));const t=i.reduce(((e,i)=>Object(e)[i]),e);return void 0===t?a:t}static set(e={},i="",a){return Array.isArray(i)||(i=this.toPath(i)),i.slice(0,-1).reduce(((e,a,t)=>Object(e[a])===e[a]?e[a]:e[a]=/^\d+$/.test(i[t+1])?[]:{}),e)[i[i.length-1]]=a,e}static unset(e={},i=""){return Array.isArray(i)||(i=this.toPath(i)),i.reduce(((e,a,t)=>t===i.length-1?(delete e[a],!0):Object(e)[a]),e)}static toPath(e){return e.replace(/\[(\d+)\]/g,".$1").split(".").filter(Boolean)}static escape(e){const i={"&":"&","<":"<",">":">",'"':""","'":"'"};return e.replace(/[&<>"']/g,(e=>i[e]))}static unescape(e){const i={"&":"&","<":"<",">":">",""":'"',"'":"'"};return e.replace(/&|<|>|"|'/g,(e=>i[e]))}}class i{static name="$Storage";static version="1.0.9";static about(){return console.log(`\n🟧 ${this.name} v${this.version}\n`)}static data=null;static dataFile="box.dat";static#e=/^@(?[^.]+)(?:\.(?.*))?$/;static#i(){return"undefined"!=typeof $environment&&$environment["surge-version"]?"Surge":"undefined"!=typeof $environment&&$environment["stash-version"]?"Stash":"undefined"!=typeof module&&module.exports?"Node.js":"undefined"!=typeof $task?"Quantumult X":"undefined"!=typeof $loon?"Loon":"undefined"!=typeof $rocket?"Shadowrocket":"undefined"!=typeof Egern?"Egern":void 0}static getItem(i=new String,a=null){let t=a;if(!0===i.startsWith("@")){const{key:a,path:m}=i.match(this.#e)?.groups;i=a;let s=this.getItem(i,{});"object"!=typeof s&&(s={}),t=e.get(s,m);try{t=JSON.parse(t)}catch(e){}}else{switch(this.#i()){case"Surge":case"Loon":case"Stash":case"Egern":case"Shadowrocket":t=$persistentStore.read(i);break;case"Quantumult X":t=$prefs.valueForKey(i);break;case"Node.js":this.data=this.#a(this.dataFile),t=this.data?.[i];break;default:t=this.data?.[i]||null}try{t=JSON.parse(t)}catch(e){}}return t??a}static setItem(i=new String,a=new String){let t=!1;if("object"==typeof a)a=JSON.stringify(a);else a=String(a);if(!0===i.startsWith("@")){const{key:m,path:s}=i.match(this.#e)?.groups;i=m;let n=this.getItem(i,{});"object"!=typeof n&&(n={}),e.set(n,s,a),t=this.setItem(i,n)}else switch(this.#i()){case"Surge":case"Loon":case"Stash":case"Egern":case"Shadowrocket":t=$persistentStore.write(a,i);break;case"Quantumult X":t=$prefs.setValueForKey(a,i);break;case"Node.js":this.data=this.#a(this.dataFile),this.data[i]=a,this.#t(this.dataFile),t=!0;break;default:t=this.data?.[i]||null}return t}static removeItem(i){let a=!1;if(!0===i.startsWith("@")){const{key:t,path:m}=i.match(this.#e)?.groups;i=t;let s=this.getItem(i);"object"!=typeof s&&(s={}),keyValue=e.unset(s,m),a=this.setItem(i,s)}else switch(this.#i()){case"Surge":case"Loon":case"Stash":case"Egern":case"Shadowrocket":case"Node.js":default:a=!1;break;case"Quantumult X":a=$prefs.removeValueForKey(i)}return a}static clear(){let e=!1;switch(this.#i()){case"Surge":case"Loon":case"Stash":case"Egern":case"Shadowrocket":case"Node.js":default:e=!1;break;case"Quantumult X":e=$prefs.removeAllValues()}return e}static#a(e){if(!this.isNode())return{};{this.fs=this.fs?this.fs:require("fs"),this.path=this.path?this.path:require("path");const i=this.path.resolve(e),a=this.path.resolve(process.cwd(),e),t=this.fs.existsSync(i),m=!t&&this.fs.existsSync(a);if(!t&&!m)return{};{const e=t?i:a;try{return JSON.parse(this.fs.readFileSync(e))}catch(e){return{}}}}}static#t(e=this.dataFile){if(this.isNode()){this.fs=this.fs?this.fs:require("fs"),this.path=this.path?this.path:require("path");const i=this.path.resolve(e),a=this.path.resolve(process.cwd(),e),t=this.fs.existsSync(i),m=!t&&this.fs.existsSync(a),s=JSON.stringify(this.data);t?this.fs.writeFileSync(i,s):m?this.fs.writeFileSync(a,s):this.fs.writeFileSync(i,s)}}}class a{static name="ENV";static version="1.8.3";static about(){return console.log(`\n🟧 ${this.name} v${this.version}\n`)}constructor(e,i){console.log(`\n🟧 ${a.name} v${a.version}\n`),this.name=e,this.logs=[],this.isMute=!1,this.isMuteLog=!1,this.logSeparator="\n",this.encoding="utf-8",this.startTime=(new Date).getTime(),Object.assign(this,i),this.log(`\n🚩 开始!\n${e}\n`)}environment(){switch(this.platform()){case"Surge":return $environment.app="Surge",$environment;case"Stash":return $environment.app="Stash",$environment;case"Egern":return $environment.app="Egern",$environment;case"Loon":let e=$loon.split(" ");return{device:e[0],ios:e[1],"loon-version":e[2],app:"Loon"};case"Quantumult X":return{app:"Quantumult X"};case"Node.js":return process.env.app="Node.js",process.env;default:return{}}}platform(){return"undefined"!=typeof $environment&&$environment["surge-version"]?"Surge":"undefined"!=typeof $environment&&$environment["stash-version"]?"Stash":"undefined"!=typeof module&&module.exports?"Node.js":"undefined"!=typeof $task?"Quantumult X":"undefined"!=typeof $loon?"Loon":"undefined"!=typeof $rocket?"Shadowrocket":"undefined"!=typeof Egern?"Egern":void 0}isNode(){return"Node.js"===this.platform()}isQuanX(){return"Quantumult X"===this.platform()}isSurge(){return"Surge"===this.platform()}isLoon(){return"Loon"===this.platform()}isShadowrocket(){return"Shadowrocket"===this.platform()}isStash(){return"Stash"===this.platform()}isEgern(){return"Egern"===this.platform()}async getScript(e){return await this.fetch(e).then((e=>e.body))}async runScript(e,a){let t=i.getItem("@chavy_boxjs_userCfgs.httpapi");t=t?.replace?.(/\n/g,"")?.trim();let m=i.getItem("@chavy_boxjs_userCfgs.httpapi_timeout");m=1*m??20,m=a?.timeout??m;const[s,n]=t.split("@"),l={url:`http://${n}/v1/scripting/evaluate`,body:{script_text:e,mock_type:"cron",timeout:m},headers:{"X-Key":s,Accept:"*/*"},timeout:m};await this.fetch(l).then((e=>e.body),(e=>this.logErr(e)))}initGotEnv(e){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,e&&(e.headers=e.headers?e.headers:{},void 0===e.headers.Cookie&&void 0===e.cookieJar&&(e.cookieJar=this.ckjar))}async fetch(i={}||"",a={}){switch(i.constructor){case Object:i={...a,...i};break;case String:i={...a,url:i}}i.method||(i.method="GET",(i.body??i.bodyBytes)&&(i.method="POST")),delete i.headers?.Host,delete i.headers?.[":authority"],delete i.headers?.["Content-Length"],delete i.headers?.["content-length"];const t=i.method.toLocaleLowerCase();switch(this.platform()){case"Loon":case"Surge":case"Stash":case"Egern":case"Shadowrocket":default:return i.timeout&&(i.timeout=parseInt(i.timeout,10),this.isSurge()||(i.timeout=1e3*i.timeout)),i.policy&&(this.isLoon()&&(i.node=i.policy),this.isStash()&&e.set(i,"headers.X-Stash-Selected-Proxy",encodeURI(i.policy)),this.isShadowrocket()&&e.set(i,"headers.X-Surge-Proxy",i.policy)),"boolean"==typeof i.redirection&&(i["auto-redirect"]=i.redirection),i.bodyBytes&&!i.body&&(i.body=i.bodyBytes,delete i.bodyBytes),await new Promise(((e,a)=>{$httpClient[t](i,((t,m,s)=>{t?a(t):(m.ok=/^2\d\d$/.test(m.status),m.statusCode=m.status,s&&(m.body=s,1==i["binary-mode"]&&(m.bodyBytes=s)),e(m))}))}));case"Quantumult X":return i.policy&&e.set(i,"opts.policy",i.policy),"boolean"==typeof i["auto-redirect"]&&e.set(i,"opts.redirection",i["auto-redirect"]),i.body instanceof ArrayBuffer?(i.bodyBytes=i.body,delete i.body):ArrayBuffer.isView(i.body)?(i.bodyBytes=i.body.buffer.slice(i.body.byteOffset,i.body.byteLength+i.body.byteOffset),delete object.body):i.body&&delete i.bodyBytes,await $task.fetch(i).then((e=>(e.ok=/^2\d\d$/.test(e.statusCode),e.status=e.statusCode,e)),(e=>Promise.reject(e.error)));case"Node.js":let a=require("iconv-lite");this.initGotEnv(i);const{url:m,...s}=i;return await this.got[t](m,s).on("redirect",((e,i)=>{try{if(e.headers["set-cookie"]){const a=e.headers["set-cookie"].map(this.cktough.Cookie.parse).toString();a&&this.ckjar.setCookieSync(a,null),i.cookieJar=this.ckjar}}catch(e){this.logErr(e)}})).then((e=>(e.statusCode=e.status,e.body=a.decode(e.rawBody,this.encoding),e.bodyBytes=e.rawBody,e)),(e=>Promise.reject(e.message)))}}time(e,i=null){const a=i?new Date(i):new Date;let t={"M+":a.getMonth()+1,"d+":a.getDate(),"H+":a.getHours(),"m+":a.getMinutes(),"s+":a.getSeconds(),"q+":Math.floor((a.getMonth()+3)/3),S:a.getMilliseconds()};/(y+)/.test(e)&&(e=e.replace(RegExp.$1,(a.getFullYear()+"").substr(4-RegExp.$1.length)));for(let i in t)new RegExp("("+i+")").test(e)&&(e=e.replace(RegExp.$1,1==RegExp.$1.length?t[i]:("00"+t[i]).substr((""+t[i]).length)));return e}msg(e=name,i="",a="",t){const m=e=>{switch(typeof e){case void 0:return e;case"string":switch(this.platform()){case"Surge":case"Stash":case"Egern":default:return{url:e};case"Loon":case"Shadowrocket":return e;case"Quantumult X":return{"open-url":e};case"Node.js":return}case"object":switch(this.platform()){case"Surge":case"Stash":case"Egern":case"Shadowrocket":default:return{url:e.url||e.openUrl||e["open-url"]};case"Loon":return{openUrl:e.openUrl||e.url||e["open-url"],mediaUrl:e.mediaUrl||e["media-url"]};case"Quantumult X":return{"open-url":e["open-url"]||e.url||e.openUrl,"media-url":e["media-url"]||e.mediaUrl,"update-pasteboard":e["update-pasteboard"]||e.updatePasteboard};case"Node.js":return}default:return}};if(!this.isMute)switch(this.platform()){case"Surge":case"Loon":case"Stash":case"Egern":case"Shadowrocket":default:$notification.post(e,i,a,m(t));break;case"Quantumult X":$notify(e,i,a,m(t));case"Node.js":}if(!this.isMuteLog){let t=["","==============📣系统通知📣=============="];t.push(e),i&&t.push(i),a&&t.push(a),console.log(t.join("\n")),this.logs=this.logs.concat(t)}}log(...e){e.length>0&&(this.logs=[...this.logs,...e]),console.log(e.join(this.logSeparator))}logErr(e){switch(this.platform()){case"Surge":case"Loon":case"Stash":case"Egern":case"Shadowrocket":case"Quantumult X":default:this.log("",`❗️ ${this.name}, 错误!`,e);break;case"Node.js":this.log("",`❗️${this.name}, 错误!`,e.stack)}}wait(e){return new Promise((i=>setTimeout(i,e)))}done(i={}){const a=((new Date).getTime()-this.startTime)/1e3;switch(this.log("",`🚩 ${this.name}, 结束! 🕛 ${a} 秒`,""),this.platform()){case"Surge":i.policy&&e.set(i,"headers.X-Surge-Policy",i.policy),$done(i);break;case"Loon":i.policy&&(i.node=i.policy),$done(i);break;case"Stash":i.policy&&e.set(i,"headers.X-Stash-Selected-Proxy",encodeURI(i.policy)),$done(i);break;case"Egern":case"Shadowrocket":default:$done(i);break;case"Quantumult X":i.policy&&e.set(i,"opts.policy",i.policy),delete i["auto-redirect"],delete i["auto-cookie"],delete i["binary-mode"],delete i.charset,delete i.host,delete i.insecure,delete i.method,delete i.opt,delete i.path,delete i.policy,delete i["policy-descriptor"],delete i.scheme,delete i.sessionIndex,delete i.statusCode,delete i.timeout,i.body instanceof ArrayBuffer?(i.bodyBytes=i.body,delete i.body):ArrayBuffer.isView(i.body)?(i.bodyBytes=i.body.buffer.slice(i.body.byteOffset,i.body.byteLength+i.body.byteOffset),delete i.body):i.body&&delete i.bodyBytes,$done(i);break;case"Node.js":process.exit(1)}}}class t{static name="URI";static version="1.2.7";static about(){return console.log(`\n🟧 ${this.name} v${this.version}\n`)}static#m={scheme:"",host:"",path:"",query:{}};static parse(e){let i=e.match(/(?:(?.+):\/\/(?[^/]+))?\/?(?[^?]+)?\??(?[^?]+)?/)?.groups??null;if(i?.path?i.paths=i.path.split("/"):i.path="",i?.paths){const e=i.paths[i.paths.length-1];if(e?.includes(".")){const a=e.split(".");i.format=a[a.length-1]}}return i?.query&&(i.query=Object.fromEntries(i.query.split("&").map((e=>e.split("="))))),i}static stringify(e=this.#m){let i="";return e?.scheme&&e?.host&&(i+=e.scheme+"://"+e.host),e?.path&&(i+=e?.host?"/"+e.path:e.path),e?.query&&(i+="?"+Object.entries(e.query).map((e=>e.join("="))).join("&")),i}}var m={Switch:!0},s={Storefront:[["AE","143481"],["AF","143610"],["AG","143540"],["AI","143538"],["AL","143575"],["AM","143524"],["AO","143564"],["AR","143505"],["AT","143445"],["AU","143460"],["AZ","143568"],["BA","143612"],["BB","143541"],["BD","143490"],["BE","143446"],["BF","143578"],["BG","143526"],["BH","143559"],["BJ","143576"],["BM","143542"],["BN","143560"],["BO","143556"],["BR","143503"],["BS","143539"],["BT","143577"],["BW","143525"],["BY","143565"],["BZ","143555"],["CA","143455"],["CD","143613"],["CG","143582"],["CH","143459"],["CI","143527"],["CL","143483"],["CM","143574"],["CN","143465"],["CO","143501"],["CR","143495"],["CV","143580"],["CY","143557"],["CZ","143489"],["DE","143443"],["DK","143458"],["DM","143545"],["DO","143508"],["DZ","143563"],["EC","143509"],["EE","143518"],["EG","143516"],["ES","143454"],["FI","143447"],["FJ","143583"],["FM","143591"],["FR","143442"],["GA","143614"],["GB","143444"],["GD","143546"],["GF","143615"],["GH","143573"],["GM","143584"],["GR","143448"],["GT","143504"],["GW","143585"],["GY","143553"],["HK","143463"],["HN","143510"],["HR","143494"],["HU","143482"],["ID","143476"],["IE","143449"],["IL","143491"],["IN","143467"],["IQ","143617"],["IS","143558"],["IT","143450"],["JM","143511"],["JO","143528"],["JP","143462"],["KE","143529"],["KG","143586"],["KH","143579"],["KN","143548"],["KP","143466"],["KR","143466"],["KW","143493"],["KY","143544"],["KZ","143517"],["TC","143552"],["TD","143581"],["TJ","143603"],["TH","143475"],["TM","143604"],["TN","143536"],["TO","143608"],["TR","143480"],["TT","143551"],["TW","143470"],["TZ","143572"],["LA","143587"],["LB","143497"],["LC","143549"],["LI","143522"],["LK","143486"],["LR","143588"],["LT","143520"],["LU","143451"],["LV","143519"],["LY","143567"],["MA","143620"],["MD","143523"],["ME","143619"],["MG","143531"],["MK","143530"],["ML","143532"],["MM","143570"],["MN","143592"],["MO","143515"],["MR","143590"],["MS","143547"],["MT","143521"],["MU","143533"],["MV","143488"],["MW","143589"],["MX","143468"],["MY","143473"],["MZ","143593"],["NA","143594"],["NE","143534"],["NG","143561"],["NI","143512"],["NL","143452"],["NO","143457"],["NP","143484"],["NR","143606"],["NZ","143461"],["OM","143562"],["PA","143485"],["PE","143507"],["PG","143597"],["PH","143474"],["PK","143477"],["PL","143478"],["PT","143453"],["PW","143595"],["PY","143513"],["QA","143498"],["RO","143487"],["RS","143500"],["RU","143469"],["RW","143621"],["SA","143479"],["SB","143601"],["SC","143599"],["SE","143456"],["SG","143464"],["SI","143499"],["SK","143496"],["SL","143600"],["SN","143535"],["SR","143554"],["ST","143598"],["SV","143506"],["SZ","143602"],["UA","143492"],["UG","143537"],["US","143441"],["UY","143514"],["UZ","143566"],["VC","143550"],["VE","143502"],["VG","143543"],["VN","143471"],["VU","143609"],["XK","143624"],["YE","143571"],["ZA","143472"],["ZM","143622"],["ZW","143605"]]},n={Settings:m,Configs:s},l={Switch:!0,PEP:{GCC:"US"}},r={Settings:l},o={Switch:!0,UrlInfoSet:{Dispatcher:"AutoNavi",Directions:"AutoNavi",RAP:"Apple",LocationShift:"AUTO"},TileSet:{Map:"CN",Satellite:"HYBRID",Traffic:"CN",POI:"CN",Flyover:"XX",Munin:"XX"},GeoManifest:{Dynamic:{Config:{CountryCode:{default:"CN",iOS:"AUTO",iPadOS:"AUTO",watchOS:"US",macOS:"AUTO"}}}},Config:{Announcements:{"Environment:":{default:"AUTO",iOS:"AUTO",iPadOS:"AUTO",watchOS:"AUTO",macOS:"AUTO"}}}},p={CN:{tileSet:[{style:1,validVersion:[{identifier:2112,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:1},{minX:0,minY:0,maxX:3,maxY:3,minZ:2,maxZ:2},{minX:0,minY:0,maxX:7,maxY:7,minZ:3,maxZ:3},{minX:0,minY:0,maxX:15,maxY:15,minZ:4,maxZ:4},{minX:0,minY:0,maxX:31,maxY:31,minZ:5,maxZ:5},{minX:0,minY:0,maxX:63,maxY:63,minZ:6,maxZ:6},{minX:0,minY:0,maxX:127,maxY:127,minZ:7,maxZ:7},{minX:0,minY:0,maxX:255,maxY:255,minZ:8,maxZ:8},{minX:0,minY:0,maxX:1023,maxY:1023,minZ:10,maxZ:10},{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12},{minX:0,minY:0,maxX:16383,maxY:16383,minZ:14,maxZ:14}],genericTile:[],supportedLanguagesVersion:1}],scale:0,size:2,supportedLanguage:[{identifier:1,language:["ar","ca","cs","da","de","el","en","en-AU","en-GB","es","es-MX","fi","fr","fr-CA","he","hi","hr","hu","id","it","ja","ko","ms","nb","nl","pl","pt","pt-PT","ro","ru","sk","sv","th","tr","uk","vi","zh-Hans","zh-Hant","zh-HK"]}],countryRegionWhitelist:[{countryCode:"AE",region:"AE"},{countryCode:"AE",region:"SA"},{countryCode:"IN",region:"IN"},{countryCode:"JP",region:"JP"},{countryCode:"KR",region:"KR"},{countryCode:"MA",region:"MA"},{countryCode:"RU",region:"RU"},{countryCode:"SA",region:"AE"},{countryCode:"SA",region:"SA"}],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe19-cn-ssl.ls.apple.com/tiles?flags=8",supportsMultipathTCP:!1},{style:7,validVersion:[{identifier:51,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:7},{minX:179,minY:80,maxX:224,maxY:128,minZ:8,maxZ:8},{minX:359,minY:161,maxX:449,maxY:257,minZ:9,maxZ:9},{minX:719,minY:323,maxX:898,maxY:915,minZ:10,maxZ:10},{minX:1438,minY:646,maxX:1797,maxY:1031,minZ:11,maxZ:11},{minX:2876,minY:1292,maxX:3594,maxY:2062,minZ:12,maxZ:12},{minX:5752,minY:2584,maxX:7188,maxY:4124,minZ:13,maxZ:13},{minX:11504,minY:5168,maxX:14376,maxY:8248,minZ:14,maxZ:14},{minX:23008,minY:10336,maxX:28752,maxY:16496,minZ:15,maxZ:15},{minX:46016,minY:20672,maxX:57504,maxY:32992,minZ:16,maxZ:16},{minX:92032,minY:41344,maxX:115008,maxY:65984,minZ:17,maxZ:17},{minX:184064,minY:82668,maxX:230016,maxY:131976,minZ:18,maxZ:18}],genericTile:[{tileType:2,textureIndex:0,resourceIndex:1971}]}],scale:1,size:1,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe11-2-cn-ssl.ls.apple.com/2/tiles",supportsMultipathTCP:!1},{style:7,validVersion:[{identifier:51,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:7},{minX:179,minY:80,maxX:224,maxY:128,minZ:8,maxZ:8},{minX:359,minY:161,maxX:449,maxY:257,minZ:9,maxZ:9},{minX:719,minY:323,maxX:898,maxY:915,minZ:10,maxZ:10},{minX:1438,minY:646,maxX:1797,maxY:1031,minZ:11,maxZ:11},{minX:2876,minY:1292,maxX:3594,maxY:2062,minZ:12,maxZ:12},{minX:5752,minY:2584,maxX:7188,maxY:4124,minZ:13,maxZ:13},{minX:11504,minY:5168,maxX:14376,maxY:8248,minZ:14,maxZ:14},{minX:23008,minY:10336,maxX:28752,maxY:16496,minZ:15,maxZ:15},{minX:46016,minY:20672,maxX:57504,maxY:32992,minZ:16,maxZ:16},{minX:92032,minY:41344,maxX:115008,maxY:65984,minZ:17,maxZ:17},{minX:184064,minY:82668,maxX:230016,maxY:131976,minZ:18,maxZ:18}],genericTile:[{tileType:2,textureIndex:0,resourceIndex:1971}]}],scale:2,size:1,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe11-2-cn-ssl.ls.apple.com/2/tiles",supportsMultipathTCP:!1},{style:11,validVersion:[{identifier:470,availableTiles:[{minX:0,minY:0,maxX:8191,maxY:8191,minZ:13,maxZ:13},{minX:0,minY:0,maxX:16383,maxY:16383,minZ:14,maxZ:14},{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15}],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe19-cn-ssl.ls.apple.com/tiles?flags=1",supportsMultipathTCP:!1},{style:12,validVersion:[{identifier:2111,availableTiles:[{minX:0,minY:0,maxX:255,maxY:255,minZ:8,maxZ:8},{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12}],genericTile:[],timeToLiveSeconds:120}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe12-cn-ssl.ls.apple.com/traffic",supportsMultipathTCP:!1},{style:13,validVersion:[{identifier:2092,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:2},{minX:0,minY:0,maxX:15,maxY:15,minZ:4,maxZ:4},{minX:0,minY:0,maxX:63,maxY:63,minZ:6,maxZ:10},{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:15},{minX:0,minY:0,maxX:65535,maxY:65535,minZ:16,maxZ:16},{minX:0,minY:0,maxX:131071,maxY:131071,minZ:17,maxZ:17}],genericTile:[],timeToLiveSeconds:604800,supportedLanguagesVersion:1}],scale:0,size:2,supportedLanguage:[{identifier:1,language:["ar","ca","cs","da","de","el","en","en-AU","en-GB","es","es-MX","fi","fr","fr-CA","he","hi","hr","hu","id","it","ja","ko","ms","nb","nl","pl","pt","pt-PT","ro","ru","sk","sv","th","tr","uk","vi","zh-Hans","zh-Hant","zh-HK"]}],countryRegionWhitelist:[],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe19-cn-ssl.ls.apple.com/tiles?flags=2",supportsMultipathTCP:!1},{style:18,validVersion:[{identifier:2112,availableTiles:[{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15}],genericTile:[],supportedLanguagesVersion:1}],scale:0,size:2,supportedLanguage:[{identifier:1,language:["ar","ca","cs","da","de","el","en","en-AU","en-GB","es","es-MX","fi","fr","fr-CA","he","hi","hr","hu","id","it","ja","ko","ms","nb","nl","pl","pt","pt-PT","ro","ru","sk","sv","th","tr","uk","vi","zh-Hans","zh-Hant","zh-HK"]}],countryRegionWhitelist:[],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe19-cn-ssl.ls.apple.com/tiles",supportsMultipathTCP:!1},{style:20,validVersion:[{identifier:2112,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:1},{minX:0,minY:0,maxX:3,maxY:3,minZ:2,maxZ:2},{minX:0,minY:0,maxX:7,maxY:7,minZ:3,maxZ:3},{minX:0,minY:0,maxX:15,maxY:15,minZ:4,maxZ:4},{minX:0,minY:0,maxX:31,maxY:31,minZ:5,maxZ:5},{minX:0,minY:0,maxX:63,maxY:63,minZ:6,maxZ:6},{minX:0,minY:0,maxX:127,maxY:127,minZ:7,maxZ:7},{minX:0,minY:0,maxX:255,maxY:255,minZ:8,maxZ:8},{minX:0,minY:0,maxX:511,maxY:511,minZ:9,maxZ:9},{minX:0,minY:0,maxX:1023,maxY:1023,minZ:10,maxZ:10},{minX:0,minY:0,maxX:2047,maxY:2047,minZ:11,maxZ:11},{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12},{minX:0,minY:0,maxX:8191,maxY:8191,minZ:13,maxZ:13},{minX:0,minY:0,maxX:16383,maxY:16383,minZ:14,maxZ:14},{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15}],genericTile:[],supportedLanguagesVersion:1}],scale:0,size:2,supportedLanguage:[{identifier:1,language:["ar","ca","cs","da","de","el","en","en-AU","en-GB","es","es-MX","fi","fr","fr-CA","he","hi","hr","hu","id","it","ja","ko","ms","nb","nl","pl","pt","pt-PT","ro","ru","sk","sv","th","tr","uk","vi","zh-Hans","zh-Hant","zh-HK"]}],countryRegionWhitelist:[{countryCode:"AE",region:"AE"},{countryCode:"AE",region:"SA"},{countryCode:"IN",region:"IN"},{countryCode:"JP",region:"JP"},{countryCode:"KR",region:"KR"},{countryCode:"MA",region:"MA"},{countryCode:"RU",region:"RU"},{countryCode:"SA",region:"AE"},{countryCode:"SA",region:"SA"}],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe19-cn-ssl.ls.apple.com/tiles",supportsMultipathTCP:!1},{style:22,validVersion:[{identifier:2112,availableTiles:[{minX:0,minY:0,maxX:127,maxY:127,minZ:7,maxZ:15}],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe19-cn-ssl.ls.apple.com/tiles",supportsMultipathTCP:!1},{style:30,validVersion:[{identifier:146,availableTiles:[{minX:0,minY:0,maxX:1023,maxY:1023,minZ:10,maxZ:10},{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15},{minX:0,minY:0,maxX:262143,maxY:262143,minZ:18,maxZ:18}],genericTile:[],supportedLanguagesVersion:1}],scale:0,size:2,supportedLanguage:[{identifier:1,language:["ar","ca","cs","da","de","el","en","en-AU","en-GB","es","es-MX","fi","fr","fr-CA","he","hi","hr","hu","id","it","ja","ko","ms","nb","nl","pl","pt","pt-PT","ro","ru","sk","sv","th","tr","uk","vi","zh-Hans","zh-Hant","zh-HK"]}],countryRegionWhitelist:[],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe19-cn-ssl.ls.apple.com/tiles",supportsMultipathTCP:!1},{style:37,validVersion:[{identifier:1904,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:1},{minX:0,minY:0,maxX:7,maxY:7,minZ:3,maxZ:3},{minX:0,minY:0,maxX:31,maxY:31,minZ:5,maxZ:5},{minX:0,minY:0,maxX:127,maxY:127,minZ:7,maxZ:7},{minX:0,minY:0,maxX:511,maxY:511,minZ:9,maxZ:9},{minX:0,minY:0,maxX:1023,maxY:1023,minZ:10,maxZ:10},{minX:0,minY:0,maxX:2047,maxY:2047,minZ:11,maxZ:11},{minX:0,minY:0,maxX:8191,maxY:8191,minZ:13,maxZ:13},{minX:0,minY:0,maxX:16383,maxY:16383,minZ:14,maxZ:14},{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15}],genericTile:[],supportedLanguagesVersion:1}],scale:0,size:2,supportedLanguage:[{identifier:1,language:["ar","ca","cs","da","de","el","en","en-AU","en-GB","es","es-MX","fi","fr","fr-CA","he","hi","hr","hu","id","it","ja","ko","ms","nb","nl","pl","pt","pt-PT","ro","ru","sk","sv","th","tr","uk","vi","zh-Hans","zh-Hant","zh-HK"]}],countryRegionWhitelist:[],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe19-cn-ssl.ls.apple.com/tiles?flags=2",supportsMultipathTCP:!1},{style:47,validVersion:[{identifier:1904,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:1},{minX:0,minY:0,maxX:7,maxY:7,minZ:3,maxZ:3},{minX:0,minY:0,maxX:31,maxY:31,minZ:5,maxZ:5},{minX:0,minY:0,maxX:127,maxY:127,minZ:7,maxZ:7},{minX:0,minY:0,maxX:511,maxY:511,minZ:9,maxZ:9},{minX:0,minY:0,maxX:1023,maxY:1023,minZ:10,maxZ:10},{minX:0,minY:0,maxX:2047,maxY:2047,minZ:11,maxZ:11},{minX:0,minY:0,maxX:8191,maxY:8191,minZ:13,maxZ:13},{minX:0,minY:0,maxX:16383,maxY:16383,minZ:14,maxZ:14},{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15}],genericTile:[],supportedLanguagesVersion:1}],scale:0,size:2,supportedLanguage:[{identifier:1,language:["ar","ca","cs","da","de","el","en","en-AU","en-GB","es","es-MX","fi","fr","fr-CA","he","hi","hr","hu","id","it","ja","ko","ms","nb","nl","pl","pt","pt-PT","ro","ru","sk","sv","th","tr","uk","vi","zh-Hans","zh-Hant","zh-HK"]}],countryRegionWhitelist:[],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe19-cn-ssl.ls.apple.com/tiles",supportsMultipathTCP:!1},{style:48,validVersion:[{identifier:1904,availableTiles:[{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12},{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15}],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe19-cn-ssl.ls.apple.com/tiles",supportsMultipathTCP:!1},{style:53,validVersion:[{identifier:2112,availableTiles:[{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15}],genericTile:[],supportedLanguagesVersion:1}],scale:0,size:2,supportedLanguage:[{identifier:1,language:["ar","ca","cs","da","de","el","en","en-AU","en-GB","es","es-MX","fi","fr","fr-CA","he","hi","hr","hu","id","it","ja","ko","ms","nb","nl","pl","pt","pt-PT","ro","ru","sk","sv","th","tr","uk","vi","zh-Hans","zh-Hant","zh-HK"]}],countryRegionWhitelist:[],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe19-cn-ssl.ls.apple.com/tiles",supportsMultipathTCP:!1},{style:54,validVersion:[{identifier:2112,availableTiles:[{minX:0,minY:0,maxX:8191,maxY:8191,minZ:13,maxZ:13},{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15}],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe19-cn-ssl.ls.apple.com/tiles",supportsMultipathTCP:!1},{style:56,validVersion:[{identifier:16,availableTiles:[{minX:0,minY:0,maxX:131071,maxY:131071,minZ:17,maxZ:17}],genericTile:[],supportedLanguagesVersion:1}],scale:0,size:2,supportedLanguage:[{identifier:1,language:["ar","ca","cs","da","de","el","en","en-AU","en-GB","es","es-MX","fi","fr","fr-CA","he","hi","hr","hu","id","it","ja","ko","ms","nb","nl","pl","pt","pt-PT","ro","ru","sk","sv","th","tr","uk","vi","zh-Hans","zh-Hant","zh-HK"]}],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-cn-ssl.ls.apple.com/tiles",supportsMultipathTCP:!1},{style:57,validVersion:[{identifier:0,availableTiles:[{minX:0,minY:0,maxX:131071,maxY:131071,minZ:17,maxZ:17}],genericTile:[],timeToLiveSeconds:3600}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gsp76-cn-ssl.ls.apple.com/api/tile",supportsMultipathTCP:!1},{style:58,validVersion:[{identifier:137,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:1},{minX:0,minY:0,maxX:3,maxY:3,minZ:2,maxZ:2},{minX:0,minY:0,maxX:7,maxY:7,minZ:3,maxZ:3},{minX:0,minY:0,maxX:15,maxY:15,minZ:4,maxZ:4},{minX:0,minY:0,maxX:31,maxY:31,minZ:5,maxZ:5},{minX:0,minY:0,maxX:63,maxY:63,minZ:6,maxZ:6},{minX:0,minY:0,maxX:127,maxY:127,minZ:7,maxZ:7},{minX:0,minY:0,maxX:255,maxY:255,minZ:8,maxZ:8},{minX:0,minY:0,maxX:511,maxY:511,minZ:9,maxZ:9},{minX:0,minY:0,maxX:1023,maxY:1023,minZ:10,maxZ:10},{minX:0,minY:0,maxX:2047,maxY:2047,minZ:11,maxZ:11},{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12},{minX:0,minY:0,maxX:8191,maxY:8191,minZ:13,maxZ:13},{minX:0,minY:0,maxX:16383,maxY:16383,minZ:14,maxZ:14},{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15},{minX:0,minY:0,maxX:65535,maxY:65535,minZ:16,maxZ:16},{minX:0,minY:0,maxX:131071,maxY:131071,minZ:17,maxZ:17}],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-cn-ssl.ls.apple.com/tiles",supportsMultipathTCP:!1},{style:59,validVersion:[{identifier:80,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:1}],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-cn-ssl.ls.apple.com/asset/v3/model",supportsMultipathTCP:!1},{style:60,validVersion:[{identifier:30,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:1},{minX:0,minY:0,maxX:3,maxY:3,minZ:2,maxZ:2},{minX:0,minY:0,maxX:7,maxY:7,minZ:3,maxZ:3},{minX:0,minY:0,maxX:15,maxY:15,minZ:4,maxZ:4},{minX:0,minY:0,maxX:31,maxY:31,minZ:5,maxZ:5},{minX:0,minY:0,maxX:63,maxY:63,minZ:6,maxZ:6},{minX:0,minY:0,maxX:127,maxY:127,minZ:7,maxZ:7},{minX:0,minY:0,maxX:255,maxY:255,minZ:8,maxZ:8},{minX:0,minY:0,maxX:511,maxY:511,minZ:9,maxZ:9}],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-cn-ssl.ls.apple.com/asset/v3/material",supportsMultipathTCP:!1},{style:61,validVersion:[{identifier:30,availableTiles:[{minX:0,minY:0,maxX:127,maxY:127,minZ:7,maxZ:7}],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-cn-ssl.ls.apple.com/tiles",supportsMultipathTCP:!1},{style:64,validVersion:[{identifier:16,availableTiles:[{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12}],genericTile:[],supportedLanguagesVersion:1}],scale:0,size:2,supportedLanguage:[{identifier:1,language:["ar","ca","cs","da","de","el","en","en-AU","en-GB","es","es-MX","fi","fr","fr-CA","he","hi","hr","hu","id","it","ja","ko","ms","nb","nl","pl","pt","pt-PT","ro","ru","sk","sv","th","tr","uk","vi","zh-Hans","zh-Hant","zh-HK"]}],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-cn-ssl.ls.apple.com/tiles",supportsMultipathTCP:!1},{style:65,validVersion:[{identifier:2,availableTiles:[{minX:0,minY:0,maxX:255,maxY:255,minZ:8,maxZ:8}],genericTile:[],timeToLiveSeconds:3600}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe79-cn-ssl.ls.apple.com/65/v1",supportsMultipathTCP:!1},{style:66,validVersion:[{identifier:2112,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:1},{minX:0,minY:0,maxX:3,maxY:3,minZ:2,maxZ:2},{minX:0,minY:0,maxX:7,maxY:7,minZ:3,maxZ:3},{minX:0,minY:0,maxX:15,maxY:15,minZ:4,maxZ:4},{minX:0,minY:0,maxX:31,maxY:31,minZ:5,maxZ:5},{minX:0,minY:0,maxX:63,maxY:63,minZ:6,maxZ:6},{minX:0,minY:0,maxX:127,maxY:127,minZ:7,maxZ:7},{minX:0,minY:0,maxX:255,maxY:255,minZ:8,maxZ:8},{minX:0,minY:0,maxX:511,maxY:511,minZ:9,maxZ:9},{minX:0,minY:0,maxX:1023,maxY:1023,minZ:10,maxZ:10},{minX:0,minY:0,maxX:2047,maxY:2047,minZ:11,maxZ:11},{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12},{minX:0,minY:0,maxX:8191,maxY:8191,minZ:13,maxZ:13},{minX:0,minY:0,maxX:16383,maxY:16383,minZ:14,maxZ:14},{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15}],genericTile:[],supportedLanguagesVersion:1}],scale:0,size:2,supportedLanguage:[{identifier:1,language:["ar","ca","cs","da","de","el","en","en-AU","en-GB","es","es-MX","fi","fr","fr-CA","he","hi","hr","hu","id","it","ja","ko","ms","nb","nl","pl","pt","pt-PT","ro","ru","sk","sv","th","tr","uk","vi","zh-Hans","zh-Hant","zh-HK"]}],countryRegionWhitelist:[{countryCode:"AE",region:"AE"},{countryCode:"AE",region:"SA"},{countryCode:"IN",region:"IN"},{countryCode:"JP",region:"JP"},{countryCode:"KR",region:"KR"},{countryCode:"MA",region:"MA"},{countryCode:"RU",region:"RU"},{countryCode:"SA",region:"AE"},{countryCode:"SA",region:"SA"}],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-cn-ssl.ls.apple.com/tiles",supportsMultipathTCP:!1},{style:67,validVersion:[{identifier:2112,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:1},{minX:0,minY:0,maxX:3,maxY:3,minZ:2,maxZ:2},{minX:0,minY:0,maxX:7,maxY:7,minZ:3,maxZ:3},{minX:0,minY:0,maxX:15,maxY:15,minZ:4,maxZ:4},{minX:0,minY:0,maxX:31,maxY:31,minZ:5,maxZ:5},{minX:0,minY:0,maxX:63,maxY:63,minZ:6,maxZ:6},{minX:0,minY:0,maxX:127,maxY:127,minZ:7,maxZ:7},{minX:0,minY:0,maxX:255,maxY:255,minZ:8,maxZ:8},{minX:0,minY:0,maxX:1023,maxY:1023,minZ:10,maxZ:10},{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12},{minX:0,minY:0,maxX:16383,maxY:16383,minZ:14,maxZ:14}],genericTile:[],supportedLanguagesVersion:1}],scale:0,size:2,supportedLanguage:[{identifier:1,language:["ar","ca","cs","da","de","el","en","en-AU","en-GB","es","es-MX","fi","fr","fr-CA","he","hi","hr","hu","id","it","ja","ko","ms","nb","nl","pl","pt","pt-PT","ro","ru","sk","sv","th","tr","uk","vi","zh-Hans","zh-Hant","zh-HK"]}],countryRegionWhitelist:[{countryCode:"AE",region:"AE"},{countryCode:"AE",region:"SA"},{countryCode:"IN",region:"IN"},{countryCode:"JP",region:"JP"},{countryCode:"KR",region:"KR"},{countryCode:"MA",region:"MA"},{countryCode:"RU",region:"RU"},{countryCode:"SA",region:"AE"},{countryCode:"SA",region:"SA"}],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-cn-ssl.ls.apple.com/tiles",supportsMultipathTCP:!1},{style:68,validVersion:[{identifier:2092,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:1},{minX:0,minY:0,maxX:3,maxY:3,minZ:2,maxZ:2},{minX:0,minY:0,maxX:15,maxY:15,minZ:4,maxZ:4},{minX:0,minY:0,maxX:63,maxY:63,minZ:6,maxZ:6},{minX:0,minY:0,maxX:127,maxY:127,minZ:7,maxZ:7},{minX:0,minY:0,maxX:255,maxY:255,minZ:8,maxZ:8},{minX:0,minY:0,maxX:511,maxY:511,minZ:9,maxZ:9},{minX:0,minY:0,maxX:1023,maxY:1023,minZ:10,maxZ:10},{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12},{minX:0,minY:0,maxX:8191,maxY:8191,minZ:13,maxZ:13},{minX:0,minY:0,maxX:16383,maxY:16383,minZ:14,maxZ:14},{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15},{minX:0,minY:0,maxX:65535,maxY:65535,minZ:16,maxZ:16},{minX:0,minY:0,maxX:131071,maxY:131071,minZ:17,maxZ:17}],genericTile:[],supportedLanguagesVersion:1}],scale:0,size:2,supportedLanguage:[{identifier:1,language:["ar","ca","cs","da","de","el","en","en-AU","en-GB","es","es-MX","fi","fr","fr-CA","he","hi","hr","hu","id","it","ja","ko","ms","nb","nl","pl","pt","pt-PT","ro","ru","sk","sv","th","tr","uk","vi","zh-Hans","zh-Hant","zh-HK"]}],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-cn-ssl.ls.apple.com/tiles",supportsMultipathTCP:!1},{style:69,validVersion:[{identifier:21,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:1},{minX:0,minY:0,maxX:7,maxY:7,minZ:3,maxZ:3},{minX:0,minY:0,maxX:63,maxY:63,minZ:6,maxZ:6},{minX:0,minY:0,maxX:511,maxY:511,minZ:9,maxZ:9},{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12},{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15}],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe19-cn-ssl.ls.apple.com/tiles",supportsMultipathTCP:!1},{style:72,validVersion:[{identifier:2,availableTiles:[{minX:0,minY:0,maxX:8191,maxY:8191,minZ:13,maxZ:13}],genericTile:[],timeToLiveSeconds:3600}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],supportsMultipathTCP:!1},{style:73,validVersion:[{identifier:470,availableTiles:[{minX:0,minY:0,maxX:8191,maxY:8191,minZ:13,maxZ:13},{minX:0,minY:0,maxX:16383,maxY:16383,minZ:14,maxZ:14},{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15}],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-cn-ssl.ls.apple.com/tiles",supportsMultipathTCP:!1},{style:76,validVersion:[{identifier:0,availableTiles:[{minX:0,minY:0,maxX:8191,maxY:8191,minZ:13,maxZ:13},{minX:0,minY:0,maxX:524287,maxY:524287,minZ:19,maxZ:19}],genericTile:[],timeToLiveSeconds:86400}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe79-cn-ssl.ls.apple.com/sis/v1",supportsMultipathTCP:!1},{style:79,validVersion:[{identifier:29,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:1},{minX:0,minY:0,maxX:3,maxY:3,minZ:2,maxZ:2},{minX:0,minY:0,maxX:7,maxY:7,minZ:3,maxZ:3},{minX:0,minY:0,maxX:15,maxY:15,minZ:4,maxZ:4},{minX:0,minY:0,maxX:31,maxY:31,minZ:5,maxZ:5},{minX:0,minY:0,maxX:63,maxY:63,minZ:6,maxZ:6},{minX:0,minY:0,maxX:127,maxY:127,minZ:7,maxZ:7},{minX:0,minY:0,maxX:255,maxY:255,minZ:8,maxZ:8},{minX:0,minY:0,maxX:511,maxY:511,minZ:9,maxZ:9}],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-cn-ssl.ls.apple.com/tiles",supportsMultipathTCP:!1},{style:84,validVersion:[{identifier:2092,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:1},{minX:0,minY:0,maxX:3,maxY:3,minZ:2,maxZ:2},{minX:0,minY:0,maxX:15,maxY:15,minZ:4,maxZ:4},{minX:0,minY:0,maxX:63,maxY:63,minZ:6,maxZ:6},{minX:0,minY:0,maxX:127,maxY:127,minZ:7,maxZ:7},{minX:0,minY:0,maxX:255,maxY:255,minZ:8,maxZ:8},{minX:0,minY:0,maxX:511,maxY:511,minZ:9,maxZ:9},{minX:0,minY:0,maxX:1023,maxY:1023,minZ:10,maxZ:10},{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12},{minX:0,minY:0,maxX:8191,maxY:8191,minZ:13,maxZ:13},{minX:0,minY:0,maxX:16383,maxY:16383,minZ:14,maxZ:14},{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15},{minX:0,minY:0,maxX:65535,maxY:65535,minZ:16,maxZ:16},{minX:0,minY:0,maxX:131071,maxY:131071,minZ:17,maxZ:17}],genericTile:[],timeToLiveSeconds:1800,supportedLanguagesVersion:1}],scale:0,size:2,supportedLanguage:[{identifier:1,language:["ar","ca","cs","da","de","el","en","en-AU","en-GB","es","es-MX","fi","fr","fr-CA","he","hi","hr","hu","id","it","ja","ko","ms","nb","nl","pl","pt","pt-PT","ro","ru","sk","sv","th","tr","uk","vi","zh-Hans","zh-Hant","zh-HK"]}],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-2-cn-ssl.ls.apple.com/poi_update",supportsMultipathTCP:!1}],attribution:[{name:"AutoNavi",url:"https://gspe21-ssl.ls.apple.com/html/attribution-cn2-66.html",resource:[{resourceType:6,filename:"autonavi-4.png",checksum:{0:61,1:130,2:126,3:203,4:170,5:234,6:91,7:182,8:191,9:120,10:72,11:19,12:46,13:58,14:235,15:55,16:221,17:53,18:252,19:219},region:[],filter:[],validationMethod:0,updateMethod:0},{resourceType:6,filename:"autonavi-4@2x.png",checksum:{0:101,1:191,2:219,3:234,4:178,5:237,6:6,7:231,8:236,9:110,10:3,11:82,12:194,13:129,14:29,15:221,16:225,17:55,18:26,19:203},region:[],filter:[],validationMethod:0,updateMethod:0},{resourceType:6,filename:"autonavi-4@2x.png",checksum:{0:101,1:191,2:219,3:234,4:178,5:237,6:6,7:231,8:236,9:110,10:3,11:82,12:194,13:129,14:29,15:221,16:225,17:55,18:26,19:203},region:[],filter:[],validationMethod:0,updateMethod:0},{resourceType:5,filename:"autonavi-logo-mask-1.png",checksum:{0:247,1:152,2:81,3:90,4:135,5:206,6:171,7:138,8:151,9:37,10:167,11:77,12:112,13:223,14:89,15:164,16:242,17:201,18:164,19:74},region:[],filter:[],validationMethod:0,updateMethod:0},{resourceType:5,filename:"autonavi-logo-mask-1@2x.png",checksum:{0:54,1:203,2:95,3:5,4:82,5:108,6:189,7:170,8:124,9:255,10:39,11:153,12:245,13:47,14:224,15:93,16:202,17:181,18:11,19:127},region:[],filter:[],validationMethod:0,updateMethod:0},{resourceType:5,filename:"autonavi-logo-mask-1@3x.png",checksum:{0:131,1:225,2:158,3:241,4:69,5:218,6:172,7:162,8:166,9:241,10:48,11:174,12:31,13:104,14:225,15:155,16:97,17:143,18:15,19:99},region:[],filter:[],validationMethod:0,updateMethod:0}],region:[],linkDisplayStringIndex:0},{name:"© GeoTechnologies, Inc.",url:"https://gspe21-ssl.ls.apple.com/html/attribution-cn2-66.html",resource:[],region:[{minX:218,minY:102,maxX:225,maxY:104,minZ:8,maxZ:21},{minX:221,minY:98,maxX:228,maxY:101,minZ:8,maxZ:21},{minX:226,minY:91,maxX:231,maxY:97,minZ:8,maxZ:21}],linkDisplayStringIndex:0}],urlInfoSet:[{alternateResourcesURL:[{url:"https://cdn.apple-mapkit.com/rap",supportsMultipathTCP:!1},{url:"https://limit-rule.is.autonavi.com/lpr/rules/download",supportsMultipathTCP:!1}],resourcesURL:{url:"https://gspe21-ssl.ls.apple.com/",supportsMultipathTCP:!1},searchAttributionManifestURL:{url:"https://gspe21-ssl.ls.apple.com/config/search-attribution-1263",supportsMultipathTCP:!1},directionsURL:{url:"https://direction2.is.autonavi.com/direction",supportsMultipathTCP:!1},etaURL:{url:"https://direction2.is.autonavi.com/direction",supportsMultipathTCP:!1},batchReverseGeocoderURL:{url:"https://batch-rgeo.is.autonavi.com/batchRGeo",supportsMultipathTCP:!1},simpleETAURL:{url:"https://direction2.is.autonavi.com/direction",supportsMultipathTCP:!1},polyLocationShiftURL:{url:"https://shift.is.autonavi.com/localshift",supportsMultipathTCP:!1},problemSubmissionURL:{url:"https://rap.is.autonavi.com/rap",supportsMultipathTCP:!1},problemStatusURL:{url:"https://rap.is.autonavi.com/rapstatus",supportsMultipathTCP:!1},reverseGeocoderVersionsURL:{url:"https://gspe21-ssl.ls.apple.com/config/revgeo-version-11.plist",supportsMultipathTCP:!1},problemCategoriesURL:{url:"https://gspe21-ssl.ls.apple.com/config/com.apple.GEO.BusinessLocalizedCategories-424.plist",supportsMultipathTCP:!1},announcementsURL:{url:"https://gspe35-ssl.ls.apple.com/config/announcements?environment=prod-cn",supportsMultipathTCP:!1},dispatcherURL:{url:"https://dispatcher.is.autonavi.com/dispatcher",supportsMultipathTCP:!1},abExperimentURL:{url:"https://gsp-ssl.ls.apple.com/cn/ab.arpc",supportsMultipathTCP:!1},logMessageUsageURL:{url:"https://gsp64-ssl.ls.apple.com/a/v2/use",supportsMultipathTCP:!1},spatialLookupURL:{url:"https://spatialsearch.is.autonavi.com/spatialsearch",supportsMultipathTCP:!1},realtimeTrafficProbeURL:{url:"https://gsp9-ssl.apple.com/hvr/v2/rtloc",supportsMultipathTCP:!1},batchTrafficProbeURL:{url:"https://gsp10-ssl.ls.apple.com/hvr/v2/loc",supportsMultipathTCP:!1},logMessageUsageV3URL:{url:"https://gsp64-ssl.ls.apple.com/hvr/v3/use",supportsMultipathTCP:!1},backgroundDispatcherURL:{url:"https://dispatcher.is.autonavi.com/dispatcher",supportsMultipathTCP:!1},backgroundRevGeoURL:{url:"https://dispatcher.is.autonavi.com/dispatcher",supportsMultipathTCP:!1},wifiConnectionQualityProbeURL:{url:"https://gsp10-ssl-cn.ls.apple.com/hvr/wcq",supportsMultipathTCP:!1},wifiQualityURL:{url:"https://gsp85-cn-ssl.ls.apple.com/wifi_request",supportsMultipathTCP:!1},feedbackSubmissionURL:{url:"https://rap.is.autonavi.com/rap",supportsMultipathTCP:!1},feedbackLookupURL:{url:"https://rap.is.autonavi.com/lookup",supportsMultipathTCP:!1},junctionImageServiceURL:{url:"https://direction2.is.autonavi.com/direction",supportsMultipathTCP:!1},analyticsCohortSessionURL:{url:"https://gsp64-ssl.ls.apple.com/hvr/v3/use",supportsMultipathTCP:!1},analyticsLongSessionURL:{url:"https://gsp64-ssl.ls.apple.com/hvr/v3/use",supportsMultipathTCP:!1},analyticsShortSessionURL:{url:"https://gsp64-ssl.ls.apple.com/hvr/v3/use",supportsMultipathTCP:!1},analyticsSessionlessURL:{url:"https://gsp64-ssl.ls.apple.com/hvr/v3/use",supportsMultipathTCP:!1},webModuleBaseURL:{url:"https://placecard-server-wm.is.autonavi.com",supportsMultipathTCP:!1},wifiQualityTileURL:{url:"https://gspe85-cn-ssl.ls.apple.com/wifi_request_tile",supportsMultipathTCP:!1},batchReverseGeocoderPlaceRequestURL:{url:"https://dispatcher.is.autonavi.com/dispatcher",supportsMultipathTCP:!1},poiBusynessActivityCollectionURL:{url:"https://gsp53-ssl.ls.apple.com/hvr/rt_poi_activity",supportsMultipathTCP:!1},rapWebBundleURL:{url:"https://cdn.apple-mapkit.com/rap",supportsMultipathTCP:!1},offlineDataBatchListURL:{url:"https://ods.is.autonavi.com/api/batchesForRegion",supportsMultipathTCP:!1},offlineDataSizeURL:{url:"https://ods.is.autonavi.com/api/sizeForRegion",supportsMultipathTCP:!1},offlineDataDownloadBaseURL:{url:"https://gspe121-cn-ssl.ls.apple.com",supportsMultipathTCP:!1}}],muninBucket:[{bucketID:2,bucketURL:"https://gspe72-cn-ssl.ls.apple.com/mnn_us"},{bucketID:6,bucketURL:"https://gspe72-cn-ssl.ls.apple.com/mnn_us"}]},XX:{tileSet:[{style:1,validVersion:[{identifier:16034178,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:1},{minX:0,minY:0,maxX:3,maxY:3,minZ:2,maxZ:2},{minX:0,minY:0,maxX:7,maxY:7,minZ:3,maxZ:3},{minX:0,minY:0,maxX:15,maxY:15,minZ:4,maxZ:4},{minX:0,minY:0,maxX:31,maxY:31,minZ:5,maxZ:5},{minX:0,minY:0,maxX:63,maxY:63,minZ:6,maxZ:6},{minX:0,minY:0,maxX:127,maxY:127,minZ:7,maxZ:7},{minX:0,minY:0,maxX:255,maxY:255,minZ:8,maxZ:8},{minX:0,minY:0,maxX:1023,maxY:1023,minZ:10,maxZ:10},{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12},{minX:0,minY:0,maxX:16383,maxY:16383,minZ:14,maxZ:14}],genericTile:[],supportedLanguagesVersion:1}],scale:0,size:2,supportedLanguage:[{identifier:1,language:["ar","ca","cs","da","de","el","en","en-AU","en-GB","es","es-MX","es-US","fi","fr","fr-CA","he","hi","hr","hu","id","it","ja","ko","ms","nb","nl","pl","pt","pt-PT","ro","ru","sk","sv","th","tr","uk","vi","zh-Hans","zh-Hant","zh-HK"]}],countryRegionWhitelist:[{countryCode:"AE",region:"AE"},{countryCode:"AE",region:"SA"},{countryCode:"IN",region:""},{countryCode:"JP",region:"JP"},{countryCode:"KR",region:"KR"},{countryCode:"MA",region:"MA"},{countryCode:"RU",region:"RU"},{countryCode:"SA",region:"AE"},{countryCode:"SA",region:"SA"},{countryCode:"VN",region:"VN"}],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe19-ssl.ls.apple.com/tile.vf?flags=8",dataSet:0,supportsMultipathTCP:!1},{style:1,validVersion:[{identifier:16030619,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:1},{minX:0,minY:0,maxX:3,maxY:3,minZ:2,maxZ:2},{minX:0,minY:0,maxX:7,maxY:7,minZ:3,maxZ:3},{minX:0,minY:0,maxX:15,maxY:15,minZ:4,maxZ:4},{minX:0,minY:0,maxX:31,maxY:31,minZ:5,maxZ:5},{minX:0,minY:0,maxX:63,maxY:63,minZ:6,maxZ:6},{minX:0,minY:0,maxX:127,maxY:127,minZ:7,maxZ:7},{minX:0,minY:0,maxX:255,maxY:255,minZ:8,maxZ:8},{minX:0,minY:0,maxX:1023,maxY:1023,minZ:10,maxZ:10},{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12},{minX:0,minY:0,maxX:16383,maxY:16383,minZ:14,maxZ:14}],genericTile:[],supportedLanguagesVersion:1}],scale:0,size:2,supportedLanguage:[{identifier:1,language:["ar","ca","cs","da","de","el","en","en-AU","en-GB","es","es-MX","es-US","fi","fr","fr-CA","he","hi","hr","hu","id","it","ja","ko","ms","nb","nl","pl","pt","pt-PT","ro","ru","sk","sv","th","tr","uk","vi","zh-Hans","zh-Hant","zh-HK"]}],countryRegionWhitelist:[{countryCode:"AE",region:"AE"},{countryCode:"AE",region:"SA"},{countryCode:"IN",region:""},{countryCode:"JP",region:"JP"},{countryCode:"KR",region:"KR"},{countryCode:"MA",region:"MA"},{countryCode:"RU",region:"RU"},{countryCode:"SA",region:"AE"},{countryCode:"SA",region:"SA"},{countryCode:"VN",region:"VN"}],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe19-kittyhawk-ssl.ls.apple.com/tile.vf?flags=8",dataSet:1,supportsMultipathTCP:!1},{style:7,validVersion:[{identifier:9711,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:22}],genericTile:[{tileType:2,textureIndex:0,resourceIndex:1971}]}],scale:1,size:1,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe11-ssl.ls.apple.com/tile",supportsMultipathTCP:!1},{style:7,validVersion:[{identifier:9711,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:22}],genericTile:[{tileType:2,textureIndex:0,resourceIndex:1971}]}],scale:2,size:1,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe11-ssl.ls.apple.com/tile",supportsMultipathTCP:!1},{style:11,validVersion:[{identifier:16034178,availableTiles:[{minX:0,minY:0,maxX:8191,maxY:8191,minZ:13,maxZ:13},{minX:0,minY:0,maxX:16383,maxY:16383,minZ:14,maxZ:14},{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15}],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe19-ssl.ls.apple.com/tile.vf?flags=1",dataSet:0,supportsMultipathTCP:!1},{style:11,validVersion:[{identifier:16030619,availableTiles:[{minX:0,minY:0,maxX:8191,maxY:8191,minZ:13,maxZ:13},{minX:0,minY:0,maxX:16383,maxY:16383,minZ:14,maxZ:14},{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15}],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe19-kittyhawk-ssl.ls.apple.com/tile.vf?flags=1",dataSet:1,supportsMultipathTCP:!1},{style:12,validVersion:[{identifier:16034178,availableTiles:[{minX:0,minY:0,maxX:255,maxY:255,minZ:8,maxZ:8},{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12}],genericTile:[],timeToLiveSeconds:120}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe12-ssl.ls.apple.com/traffic",dataSet:0,supportsMultipathTCP:!1},{style:12,validVersion:[{identifier:16030619,availableTiles:[{minX:0,minY:0,maxX:255,maxY:255,minZ:8,maxZ:8},{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12}],genericTile:[],timeToLiveSeconds:120}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe12-kittyhawk-ssl.ls.apple.com/traffic",dataSet:1,supportsMultipathTCP:!1},{style:13,validVersion:[{identifier:16034178,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:1},{minX:0,minY:0,maxX:3,maxY:3,minZ:2,maxZ:2},{minX:0,minY:0,maxX:15,maxY:15,minZ:4,maxZ:4},{minX:0,minY:0,maxX:63,maxY:63,minZ:6,maxZ:6},{minX:0,minY:0,maxX:127,maxY:127,minZ:7,maxZ:7},{minX:0,minY:0,maxX:255,maxY:255,minZ:8,maxZ:8},{minX:0,minY:0,maxX:511,maxY:511,minZ:9,maxZ:9},{minX:0,minY:0,maxX:1023,maxY:1023,minZ:10,maxZ:10},{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12},{minX:0,minY:0,maxX:8191,maxY:8191,minZ:13,maxZ:13},{minX:0,minY:0,maxX:16383,maxY:16383,minZ:14,maxZ:14},{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15},{minX:0,minY:0,maxX:65535,maxY:65535,minZ:16,maxZ:16},{minX:0,minY:0,maxX:131071,maxY:131071,minZ:17,maxZ:17}],genericTile:[],supportedLanguagesVersion:1}],scale:0,size:2,supportedLanguage:[{identifier:1,language:["ar","ca","cs","da","de","el","en","en-AU","en-GB","es","es-MX","es-US","fi","fr","fr-CA","he","hi","hr","hu","id","it","ja","ko","ms","nb","nl","pl","pt","pt-PT","ro","ru","sk","sv","th","tr","uk","vi","zh-Hans","zh-Hant","zh-HK"]}],countryRegionWhitelist:[],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe19-ssl.ls.apple.com/tile.vf?flags=2",dataSet:0,supportsMultipathTCP:!1},{style:13,validVersion:[{identifier:16030619,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:1},{minX:0,minY:0,maxX:3,maxY:3,minZ:2,maxZ:2},{minX:0,minY:0,maxX:15,maxY:15,minZ:4,maxZ:4},{minX:0,minY:0,maxX:63,maxY:63,minZ:6,maxZ:6},{minX:0,minY:0,maxX:127,maxY:127,minZ:7,maxZ:7},{minX:0,minY:0,maxX:255,maxY:255,minZ:8,maxZ:8},{minX:0,minY:0,maxX:511,maxY:511,minZ:9,maxZ:9},{minX:0,minY:0,maxX:1023,maxY:1023,minZ:10,maxZ:10},{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12},{minX:0,minY:0,maxX:8191,maxY:8191,minZ:13,maxZ:13},{minX:0,minY:0,maxX:16383,maxY:16383,minZ:14,maxZ:14},{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15},{minX:0,minY:0,maxX:65535,maxY:65535,minZ:16,maxZ:16},{minX:0,minY:0,maxX:131071,maxY:131071,minZ:17,maxZ:17}],genericTile:[],supportedLanguagesVersion:1}],scale:0,size:2,supportedLanguage:[{identifier:1,language:["ar","ca","cs","da","de","el","en","en-AU","en-GB","es","es-MX","es-US","fi","fr","fr-CA","he","hi","hr","hu","id","it","ja","ko","ms","nb","nl","pl","pt","pt-PT","ro","ru","sk","sv","th","tr","uk","vi","zh-Hans","zh-Hant","zh-HK"]}],countryRegionWhitelist:[],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe19-kittyhawk-ssl.ls.apple.com/tile.vf?flags=2",dataSet:1,supportsMultipathTCP:!1},{style:14,validVersion:[{identifier:1,availableTiles:[],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe11-ssl.ls.apple.com/tile",supportsMultipathTCP:!1},{style:15,validVersion:[{identifier:1,availableTiles:[],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe11-ssl.ls.apple.com/tile",supportsMultipathTCP:!1},{style:16,validVersion:[{identifier:1,availableTiles:[],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe11-ssl.ls.apple.com/tile",supportsMultipathTCP:!1},{style:17,validVersion:[{identifier:27,availableTiles:[{minX:0,minY:0,maxX:127,maxY:127,minZ:7,maxZ:7},{minX:0,minY:0,maxX:2047,maxY:2047,minZ:11,maxZ:11},{minX:408,minY:2760,maxX:2583,maxY:3659,minZ:13,maxZ:13},{minX:3848,minY:2332,maxX:4535,maxY:3235,minZ:13,maxZ:13}],genericTile:[]}],scale:1,size:1,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe11-ssl.ls.apple.com/tile",supportsMultipathTCP:!1},{style:18,validVersion:[{identifier:16034178,availableTiles:[{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15}],genericTile:[],supportedLanguagesVersion:1}],scale:0,size:2,supportedLanguage:[{identifier:1,language:["ar","ca","cs","da","de","el","en","en-AU","en-GB","es","es-MX","es-US","fi","fr","fr-CA","he","hi","hr","hu","id","it","ja","ko","ms","nb","nl","pl","pt","pt-PT","ro","ru","sk","sv","th","tr","uk","vi","zh-Hans","zh-Hant","zh-HK"]}],countryRegionWhitelist:[],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe19-ssl.ls.apple.com/tile.vf",dataSet:0,supportsMultipathTCP:!1},{style:18,validVersion:[{identifier:16030619,availableTiles:[{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15}],genericTile:[],supportedLanguagesVersion:1}],scale:0,size:2,supportedLanguage:[{identifier:1,language:["ar","ca","cs","da","de","el","en","en-AU","en-GB","es","es-MX","es-US","fi","fr","fr-CA","he","hi","hr","hu","id","it","ja","ko","ms","nb","nl","pl","pt","pt-PT","ro","ru","sk","sv","th","tr","uk","vi","zh-Hans","zh-Hant","zh-HK"]}],countryRegionWhitelist:[],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe19-kittyhawk-ssl.ls.apple.com/tile.vf",dataSet:1,supportsMultipathTCP:!1},{style:20,validVersion:[{identifier:16034178,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:1},{minX:0,minY:0,maxX:3,maxY:3,minZ:2,maxZ:2},{minX:0,minY:0,maxX:7,maxY:7,minZ:3,maxZ:3},{minX:0,minY:0,maxX:15,maxY:15,minZ:4,maxZ:4},{minX:0,minY:0,maxX:31,maxY:31,minZ:5,maxZ:5},{minX:0,minY:0,maxX:63,maxY:63,minZ:6,maxZ:6},{minX:0,minY:0,maxX:127,maxY:127,minZ:7,maxZ:7},{minX:0,minY:0,maxX:255,maxY:255,minZ:8,maxZ:8},{minX:0,minY:0,maxX:511,maxY:511,minZ:9,maxZ:9},{minX:0,minY:0,maxX:1023,maxY:1023,minZ:10,maxZ:10},{minX:0,minY:0,maxX:2047,maxY:2047,minZ:11,maxZ:11},{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12},{minX:0,minY:0,maxX:8191,maxY:8191,minZ:13,maxZ:13},{minX:0,minY:0,maxX:16383,maxY:16383,minZ:14,maxZ:14},{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15}],genericTile:[],supportedLanguagesVersion:1}],scale:0,size:2,supportedLanguage:[{identifier:1,language:["ar","ca","cs","da","de","el","en","en-AU","en-GB","es","es-MX","es-US","fi","fr","fr-CA","he","hi","hr","hu","id","it","ja","ko","ms","nb","nl","pl","pt","pt-PT","ro","ru","sk","sv","th","tr","uk","vi","zh-Hans","zh-Hant","zh-HK"]}],countryRegionWhitelist:[{countryCode:"AE",region:"AE"},{countryCode:"AE",region:"SA"},{countryCode:"IN",region:""},{countryCode:"JP",region:"JP"},{countryCode:"KR",region:"KR"},{countryCode:"MA",region:"MA"},{countryCode:"RU",region:"RU"},{countryCode:"SA",region:"AE"},{countryCode:"SA",region:"SA"},{countryCode:"VN",region:"VN"}],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe19-ssl.ls.apple.com/tile.vf",dataSet:0,supportsMultipathTCP:!1},{style:20,validVersion:[{identifier:16030619,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:1},{minX:0,minY:0,maxX:3,maxY:3,minZ:2,maxZ:2},{minX:0,minY:0,maxX:7,maxY:7,minZ:3,maxZ:3},{minX:0,minY:0,maxX:15,maxY:15,minZ:4,maxZ:4},{minX:0,minY:0,maxX:31,maxY:31,minZ:5,maxZ:5},{minX:0,minY:0,maxX:63,maxY:63,minZ:6,maxZ:6},{minX:0,minY:0,maxX:127,maxY:127,minZ:7,maxZ:7},{minX:0,minY:0,maxX:255,maxY:255,minZ:8,maxZ:8},{minX:0,minY:0,maxX:511,maxY:511,minZ:9,maxZ:9},{minX:0,minY:0,maxX:1023,maxY:1023,minZ:10,maxZ:10},{minX:0,minY:0,maxX:2047,maxY:2047,minZ:11,maxZ:11},{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12},{minX:0,minY:0,maxX:8191,maxY:8191,minZ:13,maxZ:13},{minX:0,minY:0,maxX:16383,maxY:16383,minZ:14,maxZ:14},{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15}],genericTile:[],supportedLanguagesVersion:1}],scale:0,size:2,supportedLanguage:[{identifier:1,language:["ar","ca","cs","da","de","el","en","en-AU","en-GB","es","es-MX","es-US","fi","fr","fr-CA","he","hi","hr","hu","id","it","ja","ko","ms","nb","nl","pl","pt","pt-PT","ro","ru","sk","sv","th","tr","uk","vi","zh-Hans","zh-Hant","zh-HK"]}],countryRegionWhitelist:[{countryCode:"AE",region:"AE"},{countryCode:"AE",region:"SA"},{countryCode:"IN",region:""},{countryCode:"JP",region:"JP"},{countryCode:"KR",region:"KR"},{countryCode:"MA",region:"MA"},{countryCode:"RU",region:"RU"},{countryCode:"SA",region:"AE"},{countryCode:"SA",region:"SA"},{countryCode:"VN",region:"VN"}],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe19-kittyhawk-ssl.ls.apple.com/tile.vf",dataSet:1,supportsMultipathTCP:!1},{style:22,validVersion:[{identifier:16034178,availableTiles:[{minX:0,minY:0,maxX:127,maxY:127,minZ:7,maxZ:7},{minX:0,minY:0,maxX:255,maxY:255,minZ:8,maxZ:8},{minX:0,minY:0,maxX:511,maxY:511,minZ:9,maxZ:9},{minX:0,minY:0,maxX:1023,maxY:1023,minZ:10,maxZ:10},{minX:0,minY:0,maxX:2047,maxY:2047,minZ:11,maxZ:11},{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12},{minX:0,minY:0,maxX:8191,maxY:8191,minZ:13,maxZ:13},{minX:0,minY:0,maxX:16383,maxY:16383,minZ:14,maxZ:14},{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15}],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe19-ssl.ls.apple.com/tile.vf",dataSet:0,supportsMultipathTCP:!1},{style:22,validVersion:[{identifier:16030619,availableTiles:[{minX:0,minY:0,maxX:127,maxY:127,minZ:7,maxZ:7},{minX:0,minY:0,maxX:255,maxY:255,minZ:8,maxZ:8},{minX:0,minY:0,maxX:511,maxY:511,minZ:9,maxZ:9},{minX:0,minY:0,maxX:1023,maxY:1023,minZ:10,maxZ:10},{minX:0,minY:0,maxX:2047,maxY:2047,minZ:11,maxZ:11},{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12},{minX:0,minY:0,maxX:8191,maxY:8191,minZ:13,maxZ:13},{minX:0,minY:0,maxX:16383,maxY:16383,minZ:14,maxZ:14},{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15}],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe19-kittyhawk-ssl.ls.apple.com/tile.vf",dataSet:1,supportsMultipathTCP:!1},{style:30,validVersion:[{identifier:16034178,availableTiles:[{minX:0,minY:0,maxX:1023,maxY:1023,minZ:10,maxZ:10},{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15},{minX:0,minY:0,maxX:262143,maxY:262143,minZ:18,maxZ:18}],genericTile:[],supportedLanguagesVersion:1}],scale:0,size:2,supportedLanguage:[{identifier:1,language:["ar","ca","cs","da","de","el","en","en-AU","en-GB","es","es-MX","es-US","fi","fr","fr-CA","he","hi","hr","hu","id","it","ja","ko","ms","nb","nl","pl","pt","pt-PT","ro","ru","sk","sv","th","tr","uk","vi","zh-Hans","zh-Hant","zh-HK"]}],countryRegionWhitelist:[],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe19-ssl.ls.apple.com/tile.vf",dataSet:0,supportsMultipathTCP:!1},{style:30,validVersion:[{identifier:16030619,availableTiles:[{minX:0,minY:0,maxX:1023,maxY:1023,minZ:10,maxZ:10},{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15},{minX:0,minY:0,maxX:262143,maxY:262143,minZ:18,maxZ:18}],genericTile:[],supportedLanguagesVersion:1}],scale:0,size:2,supportedLanguage:[{identifier:1,language:["ar","ca","cs","da","de","el","en","en-AU","en-GB","es","es-MX","es-US","fi","fr","fr-CA","he","hi","hr","hu","id","it","ja","ko","ms","nb","nl","pl","pt","pt-PT","ro","ru","sk","sv","th","tr","uk","vi","zh-Hans","zh-Hant","zh-HK"]}],countryRegionWhitelist:[],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe19-kittyhawk-ssl.ls.apple.com/tile.vf",dataSet:1,supportsMultipathTCP:!1},{style:33,validVersion:[{identifier:4,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:7}],genericTile:[]}],scale:1,size:1,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe11-ssl.ls.apple.com/tile",supportsMultipathTCP:!1},{style:37,validVersion:[{identifier:16034178,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:1},{minX:0,minY:0,maxX:3,maxY:3,minZ:2,maxZ:2},{minX:0,minY:0,maxX:7,maxY:7,minZ:3,maxZ:3},{minX:0,minY:0,maxX:31,maxY:31,minZ:5,maxZ:5},{minX:0,minY:0,maxX:127,maxY:127,minZ:7,maxZ:7},{minX:0,minY:0,maxX:511,maxY:511,minZ:9,maxZ:9},{minX:0,minY:0,maxX:1023,maxY:1023,minZ:10,maxZ:10},{minX:0,minY:0,maxX:2047,maxY:2047,minZ:11,maxZ:11},{minX:0,minY:0,maxX:8191,maxY:8191,minZ:13,maxZ:13},{minX:0,minY:0,maxX:16383,maxY:16383,minZ:14,maxZ:14},{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15}],genericTile:[],supportedLanguagesVersion:1}],scale:0,size:2,supportedLanguage:[{identifier:1,language:["ar","ca","cs","da","de","el","en","en-AU","en-GB","es","es-MX","es-US","fi","fr","fr-CA","he","hi","hr","hu","id","it","ja","ko","ms","nb","nl","pl","pt","pt-PT","ro","ru","sk","sv","th","tr","uk","vi","zh-Hans","zh-Hant","zh-HK"]}],countryRegionWhitelist:[],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe19-ssl.ls.apple.com/tile.vf?flags=2",dataSet:0,supportsMultipathTCP:!1},{style:37,validVersion:[{identifier:16030619,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:1},{minX:0,minY:0,maxX:3,maxY:3,minZ:2,maxZ:2},{minX:0,minY:0,maxX:7,maxY:7,minZ:3,maxZ:3},{minX:0,minY:0,maxX:31,maxY:31,minZ:5,maxZ:5},{minX:0,minY:0,maxX:127,maxY:127,minZ:7,maxZ:7},{minX:0,minY:0,maxX:511,maxY:511,minZ:9,maxZ:9},{minX:0,minY:0,maxX:1023,maxY:1023,minZ:10,maxZ:10},{minX:0,minY:0,maxX:2047,maxY:2047,minZ:11,maxZ:11},{minX:0,minY:0,maxX:8191,maxY:8191,minZ:13,maxZ:13},{minX:0,minY:0,maxX:16383,maxY:16383,minZ:14,maxZ:14},{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15}],genericTile:[],supportedLanguagesVersion:1}],scale:0,size:2,supportedLanguage:[{identifier:1,language:["ar","ca","cs","da","de","el","en","en-AU","en-GB","es","es-MX","es-US","fi","fr","fr-CA","he","hi","hr","hu","id","it","ja","ko","ms","nb","nl","pl","pt","pt-PT","ro","ru","sk","sv","th","tr","uk","vi","zh-Hans","zh-Hant","zh-HK"]}],countryRegionWhitelist:[],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe19-kittyhawk-ssl.ls.apple.com/tile.vf?flags=2",dataSet:1,supportsMultipathTCP:!1},{style:42,validVersion:[{identifier:1,availableTiles:[],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe11-ssl.ls.apple.com/tile",supportsMultipathTCP:!1},{style:43,validVersion:[{identifier:1,availableTiles:[],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe11-ssl.ls.apple.com/tile",supportsMultipathTCP:!1},{style:44,validVersion:[{identifier:1,availableTiles:[],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe11-ssl.ls.apple.com/tile",supportsMultipathTCP:!1},{style:47,validVersion:[{identifier:16034178,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:1},{minX:0,minY:0,maxX:3,maxY:3,minZ:2,maxZ:2},{minX:0,minY:0,maxX:7,maxY:7,minZ:3,maxZ:3},{minX:0,minY:0,maxX:31,maxY:31,minZ:5,maxZ:5},{minX:0,minY:0,maxX:127,maxY:127,minZ:7,maxZ:7},{minX:0,minY:0,maxX:511,maxY:511,minZ:9,maxZ:9},{minX:0,minY:0,maxX:1023,maxY:1023,minZ:10,maxZ:10},{minX:0,minY:0,maxX:2047,maxY:2047,minZ:11,maxZ:11},{minX:0,minY:0,maxX:8191,maxY:8191,minZ:13,maxZ:13},{minX:0,minY:0,maxX:16383,maxY:16383,minZ:14,maxZ:14},{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15}],genericTile:[],supportedLanguagesVersion:1}],scale:0,size:2,supportedLanguage:[{identifier:1,language:["ar","ca","cs","da","de","el","en","en-AU","en-GB","es","es-MX","es-US","fi","fr","fr-CA","he","hi","hr","hu","id","it","ja","ko","ms","nb","nl","pl","pt","pt-PT","ro","ru","sk","sv","th","tr","uk","vi","zh-Hans","zh-Hant","zh-HK"]}],countryRegionWhitelist:[],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe19-ssl.ls.apple.com/tile.vf",dataSet:0,supportsMultipathTCP:!1},{style:47,validVersion:[{identifier:16030619,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:1},{minX:0,minY:0,maxX:3,maxY:3,minZ:2,maxZ:2},{minX:0,minY:0,maxX:7,maxY:7,minZ:3,maxZ:3},{minX:0,minY:0,maxX:31,maxY:31,minZ:5,maxZ:5},{minX:0,minY:0,maxX:127,maxY:127,minZ:7,maxZ:7},{minX:0,minY:0,maxX:511,maxY:511,minZ:9,maxZ:9},{minX:0,minY:0,maxX:1023,maxY:1023,minZ:10,maxZ:10},{minX:0,minY:0,maxX:2047,maxY:2047,minZ:11,maxZ:11},{minX:0,minY:0,maxX:8191,maxY:8191,minZ:13,maxZ:13},{minX:0,minY:0,maxX:16383,maxY:16383,minZ:14,maxZ:14},{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15}],genericTile:[],supportedLanguagesVersion:1}],scale:0,size:2,supportedLanguage:[{identifier:1,language:["ar","ca","cs","da","de","el","en","en-AU","en-GB","es","es-MX","es-US","fi","fr","fr-CA","he","hi","hr","hu","id","it","ja","ko","ms","nb","nl","pl","pt","pt-PT","ro","ru","sk","sv","th","tr","uk","vi","zh-Hans","zh-Hant","zh-HK"]}],countryRegionWhitelist:[],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe19-kittyhawk-ssl.ls.apple.com/tile.vf",dataSet:1,supportsMultipathTCP:!1},{style:48,validVersion:[{identifier:11201196,availableTiles:[{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12},{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15}],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe19-ssl.ls.apple.com/tile.vf",dataSet:0,supportsMultipathTCP:!1},{style:48,validVersion:[{identifier:11201196,availableTiles:[{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12},{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15}],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe19-kittyhawk-ssl.ls.apple.com/tile.vf",dataSet:1,supportsMultipathTCP:!1},{style:52,validVersion:[{identifier:1,availableTiles:[],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe11-ssl.ls.apple.com/tile",supportsMultipathTCP:!1},{style:53,validVersion:[{identifier:16034178,availableTiles:[{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15}],genericTile:[],supportedLanguagesVersion:1}],scale:0,size:2,supportedLanguage:[{identifier:1,language:["ar","ca","cs","da","de","el","en","en-AU","en-GB","es","es-MX","es-US","fi","fr","fr-CA","he","hi","hr","hu","id","it","ja","ko","ms","nb","nl","pl","pt","pt-PT","ro","ru","sk","sv","th","tr","uk","vi","zh-Hans","zh-Hant","zh-HK"]}],countryRegionWhitelist:[],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe19-ssl.ls.apple.com/tile.vf",dataSet:0,supportsMultipathTCP:!1},{style:53,validVersion:[{identifier:16030619,availableTiles:[{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15}],genericTile:[],supportedLanguagesVersion:1}],scale:0,size:2,supportedLanguage:[{identifier:1,language:["ar","ca","cs","da","de","el","en","en-AU","en-GB","es","es-MX","es-US","fi","fr","fr-CA","he","hi","hr","hu","id","it","ja","ko","ms","nb","nl","pl","pt","pt-PT","ro","ru","sk","sv","th","tr","uk","vi","zh-Hans","zh-Hant","zh-HK"]}],countryRegionWhitelist:[],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe19-kittyhawk-ssl.ls.apple.com/tile.vf",dataSet:1,supportsMultipathTCP:!1},{style:54,validVersion:[{identifier:13658945,availableTiles:[{minX:0,minY:0,maxX:8191,maxY:8191,minZ:13,maxZ:13},{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15}],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe19-ssl.ls.apple.com/tile.vf",dataSet:0,supportsMultipathTCP:!1},{style:54,validVersion:[{identifier:13659050,availableTiles:[{minX:0,minY:0,maxX:8191,maxY:8191,minZ:13,maxZ:13},{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15}],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe19-kittyhawk-ssl.ls.apple.com/tile.vf",dataSet:1,supportsMultipathTCP:!1},{style:56,validVersion:[{identifier:16034178,availableTiles:[{minX:0,minY:0,maxX:131071,maxY:131071,minZ:17,maxZ:17}],genericTile:[],supportedLanguagesVersion:1}],scale:0,size:2,supportedLanguage:[{identifier:1,language:["ar","ca","cs","da","de","el","en","en-AU","en-GB","es","es-MX","es-US","fi","fr","fr-CA","he","hi","hr","hu","id","it","ja","ko","ms","nb","nl","pl","pt","pt-PT","ro","ru","sk","sv","th","tr","uk","vi","zh-Hans","zh-Hant","zh-HK"]}],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-ssl.ls.apple.com/tile.vf",dataSet:0,supportsMultipathTCP:!1},{style:56,validVersion:[{identifier:16030619,availableTiles:[{minX:0,minY:0,maxX:131071,maxY:131071,minZ:17,maxZ:17}],genericTile:[],supportedLanguagesVersion:1}],scale:0,size:2,supportedLanguage:[{identifier:1,language:["ar","ca","cs","da","de","el","en","en-AU","en-GB","es","es-MX","es-US","fi","fr","fr-CA","he","hi","hr","hu","id","it","ja","ko","ms","nb","nl","pl","pt","pt-PT","ro","ru","sk","sv","th","tr","uk","vi","zh-Hans","zh-Hant","zh-HK"]}],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-kittyhawk-ssl.ls.apple.com/tile.vf",dataSet:1,supportsMultipathTCP:!1},{style:57,validVersion:[{identifier:0,availableTiles:[{minX:0,minY:0,maxX:131071,maxY:131071,minZ:17,maxZ:17}],genericTile:[],timeToLiveSeconds:3600}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe76-ssl.ls.apple.com/api/tile",supportsMultipathTCP:!1},{style:58,validVersion:[{identifier:16034178,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:1},{minX:0,minY:0,maxX:3,maxY:3,minZ:2,maxZ:2},{minX:0,minY:0,maxX:7,maxY:7,minZ:3,maxZ:3},{minX:0,minY:0,maxX:15,maxY:15,minZ:4,maxZ:4},{minX:0,minY:0,maxX:31,maxY:31,minZ:5,maxZ:5},{minX:0,minY:0,maxX:63,maxY:63,minZ:6,maxZ:6},{minX:0,minY:0,maxX:127,maxY:127,minZ:7,maxZ:7},{minX:0,minY:0,maxX:255,maxY:255,minZ:8,maxZ:8},{minX:0,minY:0,maxX:511,maxY:511,minZ:9,maxZ:9},{minX:0,minY:0,maxX:1023,maxY:1023,minZ:10,maxZ:10},{minX:0,minY:0,maxX:2047,maxY:2047,minZ:11,maxZ:11},{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12},{minX:0,minY:0,maxX:8191,maxY:8191,minZ:13,maxZ:13},{minX:0,minY:0,maxX:16383,maxY:16383,minZ:14,maxZ:14},{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15},{minX:0,minY:0,maxX:65535,maxY:65535,minZ:16,maxZ:16},{minX:0,minY:0,maxX:131071,maxY:131071,minZ:17,maxZ:17}],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-ssl.ls.apple.com/tile.vf",dataSet:0,supportsMultipathTCP:!1},{style:58,validVersion:[{identifier:16030619,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:1},{minX:0,minY:0,maxX:3,maxY:3,minZ:2,maxZ:2},{minX:0,minY:0,maxX:7,maxY:7,minZ:3,maxZ:3},{minX:0,minY:0,maxX:15,maxY:15,minZ:4,maxZ:4},{minX:0,minY:0,maxX:31,maxY:31,minZ:5,maxZ:5},{minX:0,minY:0,maxX:63,maxY:63,minZ:6,maxZ:6},{minX:0,minY:0,maxX:127,maxY:127,minZ:7,maxZ:7},{minX:0,minY:0,maxX:255,maxY:255,minZ:8,maxZ:8},{minX:0,minY:0,maxX:511,maxY:511,minZ:9,maxZ:9},{minX:0,minY:0,maxX:1023,maxY:1023,minZ:10,maxZ:10},{minX:0,minY:0,maxX:2047,maxY:2047,minZ:11,maxZ:11},{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12},{minX:0,minY:0,maxX:8191,maxY:8191,minZ:13,maxZ:13},{minX:0,minY:0,maxX:16383,maxY:16383,minZ:14,maxZ:14},{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15},{minX:0,minY:0,maxX:65535,maxY:65535,minZ:16,maxZ:16},{minX:0,minY:0,maxX:131071,maxY:131071,minZ:17,maxZ:17}],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-kittyhawk-ssl.ls.apple.com/tile.vf",dataSet:1,supportsMultipathTCP:!1},{style:59,validVersion:[{identifier:16034178,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:1}],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-ssl.ls.apple.com/asset/v3/model",dataSet:0,supportsMultipathTCP:!1},{style:59,validVersion:[{identifier:16030619,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:1}],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-kittyhawk-ssl.ls.apple.com/asset/v3/model",dataSet:1,supportsMultipathTCP:!1},{style:60,validVersion:[{identifier:16034178,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:1},{minX:0,minY:0,maxX:3,maxY:3,minZ:2,maxZ:2},{minX:0,minY:0,maxX:7,maxY:7,minZ:3,maxZ:3},{minX:0,minY:0,maxX:15,maxY:15,minZ:4,maxZ:4},{minX:0,minY:0,maxX:31,maxY:31,minZ:5,maxZ:5},{minX:0,minY:0,maxX:63,maxY:63,minZ:6,maxZ:6},{minX:0,minY:0,maxX:127,maxY:127,minZ:7,maxZ:7},{minX:0,minY:0,maxX:255,maxY:255,minZ:8,maxZ:8},{minX:0,minY:0,maxX:511,maxY:511,minZ:9,maxZ:9}],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-ssl.ls.apple.com/asset/v3/material",dataSet:0,supportsMultipathTCP:!1},{style:60,validVersion:[{identifier:16030619,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:1},{minX:0,minY:0,maxX:3,maxY:3,minZ:2,maxZ:2},{minX:0,minY:0,maxX:7,maxY:7,minZ:3,maxZ:3},{minX:0,minY:0,maxX:15,maxY:15,minZ:4,maxZ:4},{minX:0,minY:0,maxX:31,maxY:31,minZ:5,maxZ:5},{minX:0,minY:0,maxX:63,maxY:63,minZ:6,maxZ:6},{minX:0,minY:0,maxX:127,maxY:127,minZ:7,maxZ:7},{minX:0,minY:0,maxX:255,maxY:255,minZ:8,maxZ:8},{minX:0,minY:0,maxX:511,maxY:511,minZ:9,maxZ:9}],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-kittyhawk-ssl.ls.apple.com/asset/v3/material",dataSet:1,supportsMultipathTCP:!1},{style:61,validVersion:[{identifier:16034178,availableTiles:[{minX:0,minY:0,maxX:127,maxY:127,minZ:7,maxZ:7}],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-ssl.ls.apple.com/tile.vf",dataSet:0,supportsMultipathTCP:!1},{style:61,validVersion:[{identifier:16030619,availableTiles:[{minX:0,minY:0,maxX:127,maxY:127,minZ:7,maxZ:7}],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-kittyhawk-ssl.ls.apple.com/tile.vf",dataSet:1,supportsMultipathTCP:!1},{style:62,validVersion:[{identifier:16034178,availableTiles:[{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12}],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-ssl.ls.apple.com/tile.vf",dataSet:0,supportsMultipathTCP:!1},{style:62,validVersion:[{identifier:16030619,availableTiles:[{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12}],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-kittyhawk-ssl.ls.apple.com/tile.vf",dataSet:1,supportsMultipathTCP:!1},{style:64,validVersion:[{identifier:16034178,availableTiles:[{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12}],genericTile:[],supportedLanguagesVersion:1}],scale:0,size:2,supportedLanguage:[{identifier:1,language:["ar","ca","cs","da","de","el","en","en-AU","en-GB","es","es-MX","es-US","fi","fr","fr-CA","he","hi","hr","hu","id","it","ja","ko","ms","nb","nl","pl","pt","pt-PT","ro","ru","sk","sv","th","tr","uk","vi","zh-Hans","zh-Hant","zh-HK"]}],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-ssl.ls.apple.com/tile.vf",dataSet:0,supportsMultipathTCP:!1},{style:64,validVersion:[{identifier:16030619,availableTiles:[{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12}],genericTile:[],supportedLanguagesVersion:1}],scale:0,size:2,supportedLanguage:[{identifier:1,language:["ar","ca","cs","da","de","el","en","en-AU","en-GB","es","es-MX","es-US","fi","fr","fr-CA","he","hi","hr","hu","id","it","ja","ko","ms","nb","nl","pl","pt","pt-PT","ro","ru","sk","sv","th","tr","uk","vi","zh-Hans","zh-Hant","zh-HK"]}],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-kittyhawk-ssl.ls.apple.com/tile.vf",dataSet:1,supportsMultipathTCP:!1},{style:65,validVersion:[{identifier:2,availableTiles:[{minX:0,minY:0,maxX:255,maxY:255,minZ:8,maxZ:8}],genericTile:[],timeToLiveSeconds:3600}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe79-ssl.ls.apple.com/65/v1",supportsMultipathTCP:!1},{style:66,validVersion:[{identifier:16034178,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:1},{minX:0,minY:0,maxX:3,maxY:3,minZ:2,maxZ:2},{minX:0,minY:0,maxX:7,maxY:7,minZ:3,maxZ:3},{minX:0,minY:0,maxX:15,maxY:15,minZ:4,maxZ:4},{minX:0,minY:0,maxX:31,maxY:31,minZ:5,maxZ:5},{minX:0,minY:0,maxX:63,maxY:63,minZ:6,maxZ:6},{minX:0,minY:0,maxX:127,maxY:127,minZ:7,maxZ:7},{minX:0,minY:0,maxX:255,maxY:255,minZ:8,maxZ:8},{minX:0,minY:0,maxX:511,maxY:511,minZ:9,maxZ:9},{minX:0,minY:0,maxX:1023,maxY:1023,minZ:10,maxZ:10},{minX:0,minY:0,maxX:2047,maxY:2047,minZ:11,maxZ:11},{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12},{minX:0,minY:0,maxX:8191,maxY:8191,minZ:13,maxZ:13},{minX:0,minY:0,maxX:16383,maxY:16383,minZ:14,maxZ:14},{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15}],genericTile:[],supportedLanguagesVersion:1}],scale:0,size:2,supportedLanguage:[{identifier:1,language:["ar","ca","cs","da","de","el","en","en-AU","en-GB","es","es-MX","es-US","fi","fr","fr-CA","he","hi","hr","hu","id","it","ja","ko","ms","nb","nl","pl","pt","pt-PT","ro","ru","sk","sv","th","tr","uk","vi","zh-Hans","zh-Hant","zh-HK"]}],countryRegionWhitelist:[{countryCode:"AE",region:"AE"},{countryCode:"AE",region:"SA"},{countryCode:"IN",region:""},{countryCode:"JP",region:"JP"},{countryCode:"KR",region:"KR"},{countryCode:"MA",region:"MA"},{countryCode:"RU",region:"RU"},{countryCode:"SA",region:"AE"},{countryCode:"SA",region:"SA"},{countryCode:"VN",region:"VN"}],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-ssl.ls.apple.com/tile.vf",dataSet:0,supportsMultipathTCP:!1},{style:66,validVersion:[{identifier:16030619,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:1},{minX:0,minY:0,maxX:3,maxY:3,minZ:2,maxZ:2},{minX:0,minY:0,maxX:7,maxY:7,minZ:3,maxZ:3},{minX:0,minY:0,maxX:15,maxY:15,minZ:4,maxZ:4},{minX:0,minY:0,maxX:31,maxY:31,minZ:5,maxZ:5},{minX:0,minY:0,maxX:63,maxY:63,minZ:6,maxZ:6},{minX:0,minY:0,maxX:127,maxY:127,minZ:7,maxZ:7},{minX:0,minY:0,maxX:255,maxY:255,minZ:8,maxZ:8},{minX:0,minY:0,maxX:511,maxY:511,minZ:9,maxZ:9},{minX:0,minY:0,maxX:1023,maxY:1023,minZ:10,maxZ:10},{minX:0,minY:0,maxX:2047,maxY:2047,minZ:11,maxZ:11},{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12},{minX:0,minY:0,maxX:8191,maxY:8191,minZ:13,maxZ:13},{minX:0,minY:0,maxX:16383,maxY:16383,minZ:14,maxZ:14},{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15}],genericTile:[],supportedLanguagesVersion:1}],scale:0,size:2,supportedLanguage:[{identifier:1,language:["ar","ca","cs","da","de","el","en","en-AU","en-GB","es","es-MX","es-US","fi","fr","fr-CA","he","hi","hr","hu","id","it","ja","ko","ms","nb","nl","pl","pt","pt-PT","ro","ru","sk","sv","th","tr","uk","vi","zh-Hans","zh-Hant","zh-HK"]}],countryRegionWhitelist:[{countryCode:"AE",region:"AE"},{countryCode:"AE",region:"SA"},{countryCode:"IN",region:""},{countryCode:"JP",region:"JP"},{countryCode:"KR",region:"KR"},{countryCode:"MA",region:"MA"},{countryCode:"RU",region:"RU"},{countryCode:"SA",region:"AE"},{countryCode:"SA",region:"SA"},{countryCode:"VN",region:"VN"}],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-kittyhawk-ssl.ls.apple.com/tile.vf",dataSet:1,supportsMultipathTCP:!1},{style:67,validVersion:[{identifier:16034178,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:1},{minX:0,minY:0,maxX:3,maxY:3,minZ:2,maxZ:2},{minX:0,minY:0,maxX:7,maxY:7,minZ:3,maxZ:3},{minX:0,minY:0,maxX:15,maxY:15,minZ:4,maxZ:4},{minX:0,minY:0,maxX:31,maxY:31,minZ:5,maxZ:5},{minX:0,minY:0,maxX:63,maxY:63,minZ:6,maxZ:6},{minX:0,minY:0,maxX:127,maxY:127,minZ:7,maxZ:7},{minX:0,minY:0,maxX:255,maxY:255,minZ:8,maxZ:8},{minX:0,minY:0,maxX:1023,maxY:1023,minZ:10,maxZ:10},{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12},{minX:0,minY:0,maxX:16383,maxY:16383,minZ:14,maxZ:14}],genericTile:[],supportedLanguagesVersion:1}],scale:0,size:2,supportedLanguage:[{identifier:1,language:["ar","ca","cs","da","de","el","en","en-AU","en-GB","es","es-MX","es-US","fi","fr","fr-CA","he","hi","hr","hu","id","it","ja","ko","ms","nb","nl","pl","pt","pt-PT","ro","ru","sk","sv","th","tr","uk","vi","zh-Hans","zh-Hant","zh-HK"]}],countryRegionWhitelist:[{countryCode:"AE",region:"AE"},{countryCode:"AE",region:"SA"},{countryCode:"IN",region:""},{countryCode:"JP",region:"JP"},{countryCode:"KR",region:"KR"},{countryCode:"MA",region:"MA"},{countryCode:"RU",region:"RU"},{countryCode:"SA",region:"AE"},{countryCode:"SA",region:"SA"},{countryCode:"VN",region:"VN"}],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-ssl.ls.apple.com/tile.vf",dataSet:0,supportsMultipathTCP:!1},{style:67,validVersion:[{identifier:16030619,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:1},{minX:0,minY:0,maxX:3,maxY:3,minZ:2,maxZ:2},{minX:0,minY:0,maxX:7,maxY:7,minZ:3,maxZ:3},{minX:0,minY:0,maxX:15,maxY:15,minZ:4,maxZ:4},{minX:0,minY:0,maxX:31,maxY:31,minZ:5,maxZ:5},{minX:0,minY:0,maxX:63,maxY:63,minZ:6,maxZ:6},{minX:0,minY:0,maxX:127,maxY:127,minZ:7,maxZ:7},{minX:0,minY:0,maxX:255,maxY:255,minZ:8,maxZ:8},{minX:0,minY:0,maxX:1023,maxY:1023,minZ:10,maxZ:10},{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12},{minX:0,minY:0,maxX:16383,maxY:16383,minZ:14,maxZ:14}],genericTile:[],supportedLanguagesVersion:1}],scale:0,size:2,supportedLanguage:[{identifier:1,language:["ar","ca","cs","da","de","el","en","en-AU","en-GB","es","es-MX","es-US","fi","fr","fr-CA","he","hi","hr","hu","id","it","ja","ko","ms","nb","nl","pl","pt","pt-PT","ro","ru","sk","sv","th","tr","uk","vi","zh-Hans","zh-Hant","zh-HK"]}],countryRegionWhitelist:[{countryCode:"AE",region:"AE"},{countryCode:"AE",region:"SA"},{countryCode:"IN",region:""},{countryCode:"JP",region:"JP"},{countryCode:"KR",region:"KR"},{countryCode:"MA",region:"MA"},{countryCode:"RU",region:"RU"},{countryCode:"SA",region:"AE"},{countryCode:"SA",region:"SA"},{countryCode:"VN",region:"VN"}],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-kittyhawk-ssl.ls.apple.com/tile.vf",dataSet:1,supportsMultipathTCP:!1},{style:68,validVersion:[{identifier:16034178,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:1},{minX:0,minY:0,maxX:3,maxY:3,minZ:2,maxZ:2},{minX:0,minY:0,maxX:15,maxY:15,minZ:4,maxZ:4},{minX:0,minY:0,maxX:63,maxY:63,minZ:6,maxZ:6},{minX:0,minY:0,maxX:127,maxY:127,minZ:7,maxZ:7},{minX:0,minY:0,maxX:255,maxY:255,minZ:8,maxZ:8},{minX:0,minY:0,maxX:511,maxY:511,minZ:9,maxZ:9},{minX:0,minY:0,maxX:1023,maxY:1023,minZ:10,maxZ:10},{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12},{minX:0,minY:0,maxX:8191,maxY:8191,minZ:13,maxZ:13},{minX:0,minY:0,maxX:16383,maxY:16383,minZ:14,maxZ:14},{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15},{minX:0,minY:0,maxX:65535,maxY:65535,minZ:16,maxZ:16},{minX:0,minY:0,maxX:131071,maxY:131071,minZ:17,maxZ:17}],genericTile:[],supportedLanguagesVersion:1}],scale:0,size:2,supportedLanguage:[{identifier:1,language:["ar","ca","cs","da","de","el","en","en-AU","en-GB","es","es-MX","es-US","fi","fr","fr-CA","he","hi","hr","hu","id","it","ja","ko","ms","nb","nl","pl","pt","pt-PT","ro","ru","sk","sv","th","tr","uk","vi","zh-Hans","zh-Hant","zh-HK"]}],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-ssl.ls.apple.com/tile.vf",dataSet:0,supportsMultipathTCP:!1},{style:68,validVersion:[{identifier:16030619,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:1},{minX:0,minY:0,maxX:3,maxY:3,minZ:2,maxZ:2},{minX:0,minY:0,maxX:15,maxY:15,minZ:4,maxZ:4},{minX:0,minY:0,maxX:63,maxY:63,minZ:6,maxZ:6},{minX:0,minY:0,maxX:127,maxY:127,minZ:7,maxZ:7},{minX:0,minY:0,maxX:255,maxY:255,minZ:8,maxZ:8},{minX:0,minY:0,maxX:511,maxY:511,minZ:9,maxZ:9},{minX:0,minY:0,maxX:1023,maxY:1023,minZ:10,maxZ:10},{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12},{minX:0,minY:0,maxX:8191,maxY:8191,minZ:13,maxZ:13},{minX:0,minY:0,maxX:16383,maxY:16383,minZ:14,maxZ:14},{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15},{minX:0,minY:0,maxX:65535,maxY:65535,minZ:16,maxZ:16},{minX:0,minY:0,maxX:131071,maxY:131071,minZ:17,maxZ:17}],genericTile:[],supportedLanguagesVersion:1}],scale:0,size:2,supportedLanguage:[{identifier:1,language:["ar","ca","cs","da","de","el","en","en-AU","en-GB","es","es-MX","es-US","fi","fr","fr-CA","he","hi","hr","hu","id","it","ja","ko","ms","nb","nl","pl","pt","pt-PT","ro","ru","sk","sv","th","tr","uk","vi","zh-Hans","zh-Hant","zh-HK"]}],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-kittyhawk-ssl.ls.apple.com/tile.vf",dataSet:1,supportsMultipathTCP:!1},{style:69,validVersion:[{identifier:16034178,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:1},{minX:0,minY:0,maxX:7,maxY:7,minZ:3,maxZ:3},{minX:0,minY:0,maxX:63,maxY:63,minZ:6,maxZ:6},{minX:0,minY:0,maxX:511,maxY:511,minZ:9,maxZ:9},{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12},{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15}],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-ssl.ls.apple.com/tile.vf",dataSet:0,supportsMultipathTCP:!1},{style:69,validVersion:[{identifier:16030619,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:1},{minX:0,minY:0,maxX:7,maxY:7,minZ:3,maxZ:3},{minX:0,minY:0,maxX:63,maxY:63,minZ:6,maxZ:6},{minX:0,minY:0,maxX:511,maxY:511,minZ:9,maxZ:9},{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12},{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15}],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-kittyhawk-ssl.ls.apple.com/tile.vf",dataSet:1,supportsMultipathTCP:!1},{style:70,validVersion:[{identifier:1,availableTiles:[{minX:0,minY:0,maxX:8191,maxY:8191,minZ:13,maxZ:13}],genericTile:[],timeToLiveSeconds:86400}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe76-ssl.ls.apple.com/api/vltile",supportsMultipathTCP:!1},{style:71,validVersion:[{identifier:1,availableTiles:[{minX:0,minY:0,maxX:2097151,maxY:2097151,minZ:21,maxZ:21}],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe92-ssl.ls.apple.com",supportsMultipathTCP:!1},{style:72,validVersion:[{identifier:2,availableTiles:[{minX:0,minY:0,maxX:8191,maxY:8191,minZ:13,maxZ:13}],genericTile:[],timeToLiveSeconds:3600}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe79-ssl.ls.apple.com/72/v2",supportsMultipathTCP:!1},{style:73,validVersion:[{identifier:16034178,availableTiles:[{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12},{minX:0,minY:0,maxX:8191,maxY:8191,minZ:13,maxZ:13},{minX:0,minY:0,maxX:16383,maxY:16383,minZ:14,maxZ:14},{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15}],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-ssl.ls.apple.com/tile.vf",dataSet:0,supportsMultipathTCP:!1},{style:73,validVersion:[{identifier:16030619,availableTiles:[{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12},{minX:0,minY:0,maxX:8191,maxY:8191,minZ:13,maxZ:13},{minX:0,minY:0,maxX:16383,maxY:16383,minZ:14,maxZ:14},{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15}],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-kittyhawk-ssl.ls.apple.com/tile.vf",dataSet:1,supportsMultipathTCP:!1},{style:74,validVersion:[{identifier:0,availableTiles:[{minX:0,minY:0,maxX:1023,maxY:1023,minZ:10,maxZ:10},{minX:0,minY:0,maxX:2097151,maxY:2097151,minZ:21,maxZ:21}],genericTile:[],timeToLiveSeconds:86400}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe79-ssl.ls.apple.com/pbz/v1",supportsMultipathTCP:!1},{style:75,validVersion:[{identifier:0,availableTiles:[{minX:0,minY:0,maxX:1023,maxY:1023,minZ:10,maxZ:10},{minX:0,minY:0,maxX:131071,maxY:131071,minZ:17,maxZ:17}],genericTile:[],timeToLiveSeconds:86400}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe79-ssl.ls.apple.com/pbz/v1",supportsMultipathTCP:!1},{style:76,validVersion:[{identifier:0,availableTiles:[{minX:0,minY:0,maxX:8191,maxY:8191,minZ:13,maxZ:13},{minX:0,minY:0,maxX:524287,maxY:524287,minZ:19,maxZ:19}],genericTile:[],timeToLiveSeconds:86400}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe79-ssl.ls.apple.com/sis/v1",supportsMultipathTCP:!1},{style:78,validVersion:[{identifier:16034178,availableTiles:[{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12}],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-ssl.ls.apple.com/tile.vf",dataSet:0,supportsMultipathTCP:!1},{style:78,validVersion:[{identifier:16030619,availableTiles:[{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12}],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-kittyhawk-ssl.ls.apple.com/tile.vf",dataSet:1,supportsMultipathTCP:!1},{style:79,validVersion:[{identifier:16034178,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:1},{minX:0,minY:0,maxX:3,maxY:3,minZ:2,maxZ:2},{minX:0,minY:0,maxX:7,maxY:7,minZ:3,maxZ:3},{minX:0,minY:0,maxX:15,maxY:15,minZ:4,maxZ:4},{minX:0,minY:0,maxX:31,maxY:31,minZ:5,maxZ:5},{minX:0,minY:0,maxX:63,maxY:63,minZ:6,maxZ:6},{minX:0,minY:0,maxX:127,maxY:127,minZ:7,maxZ:7},{minX:0,minY:0,maxX:255,maxY:255,minZ:8,maxZ:8},{minX:0,minY:0,maxX:511,maxY:511,minZ:9,maxZ:9}],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-ssl.ls.apple.com/tile.vf",dataSet:0,supportsMultipathTCP:!1},{style:79,validVersion:[{identifier:16030619,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:1},{minX:0,minY:0,maxX:3,maxY:3,minZ:2,maxZ:2},{minX:0,minY:0,maxX:7,maxY:7,minZ:3,maxZ:3},{minX:0,minY:0,maxX:15,maxY:15,minZ:4,maxZ:4},{minX:0,minY:0,maxX:31,maxY:31,minZ:5,maxZ:5},{minX:0,minY:0,maxX:63,maxY:63,minZ:6,maxZ:6},{minX:0,minY:0,maxX:127,maxY:127,minZ:7,maxZ:7},{minX:0,minY:0,maxX:255,maxY:255,minZ:8,maxZ:8},{minX:0,minY:0,maxX:511,maxY:511,minZ:9,maxZ:9}],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-kittyhawk-ssl.ls.apple.com/tile.vf",dataSet:1,supportsMultipathTCP:!1},{style:80,validVersion:[{identifier:0,availableTiles:[{minX:0,minY:0,maxX:1023,maxY:1023,minZ:10,maxZ:10},{minX:0,minY:0,maxX:65535,maxY:65535,minZ:16,maxZ:16}],genericTile:[],timeToLiveSeconds:86400}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe79-ssl.ls.apple.com/sdm/v1",supportsMultipathTCP:!1},{style:82,validVersion:[{identifier:16034178,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:1}],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-ssl.ls.apple.com/asset/v3/model-occlusion",dataSet:0,supportsMultipathTCP:!1},{style:82,validVersion:[{identifier:16030619,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:1}],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-kittyhawk-ssl.ls.apple.com/asset/v3/model-occlusion",dataSet:1,supportsMultipathTCP:!1},{style:84,validVersion:[{identifier:16034178,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:1},{minX:0,minY:0,maxX:3,maxY:3,minZ:2,maxZ:2},{minX:0,minY:0,maxX:15,maxY:15,minZ:4,maxZ:4},{minX:0,minY:0,maxX:63,maxY:63,minZ:6,maxZ:6},{minX:0,minY:0,maxX:127,maxY:127,minZ:7,maxZ:7},{minX:0,minY:0,maxX:255,maxY:255,minZ:8,maxZ:8},{minX:0,minY:0,maxX:511,maxY:511,minZ:9,maxZ:9},{minX:0,minY:0,maxX:1023,maxY:1023,minZ:10,maxZ:10},{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12},{minX:0,minY:0,maxX:8191,maxY:8191,minZ:13,maxZ:13},{minX:0,minY:0,maxX:16383,maxY:16383,minZ:14,maxZ:14},{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15},{minX:0,minY:0,maxX:65535,maxY:65535,minZ:16,maxZ:16},{minX:0,minY:0,maxX:131071,maxY:131071,minZ:17,maxZ:17}],genericTile:[],timeToLiveSeconds:1800,supportedLanguagesVersion:1}],scale:0,size:2,supportedLanguage:[{identifier:1,language:["ar","ca","cs","da","de","el","en","en-AU","en-GB","es","es-MX","es-US","fi","fr","fr-CA","he","hi","hr","hu","id","it","ja","ko","ms","nb","nl","pl","pt","pt-PT","ro","ru","sk","sv","th","tr","uk","vi","zh-Hans","zh-Hant","zh-HK"]}],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-2-ssl.ls.apple.com/poi_update",dataSet:0,supportsMultipathTCP:!1},{style:84,validVersion:[{identifier:16030619,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:1},{minX:0,minY:0,maxX:3,maxY:3,minZ:2,maxZ:2},{minX:0,minY:0,maxX:15,maxY:15,minZ:4,maxZ:4},{minX:0,minY:0,maxX:63,maxY:63,minZ:6,maxZ:6},{minX:0,minY:0,maxX:127,maxY:127,minZ:7,maxZ:7},{minX:0,minY:0,maxX:255,maxY:255,minZ:8,maxZ:8},{minX:0,minY:0,maxX:511,maxY:511,minZ:9,maxZ:9},{minX:0,minY:0,maxX:1023,maxY:1023,minZ:10,maxZ:10},{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12},{minX:0,minY:0,maxX:8191,maxY:8191,minZ:13,maxZ:13},{minX:0,minY:0,maxX:16383,maxY:16383,minZ:14,maxZ:14},{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15},{minX:0,minY:0,maxX:65535,maxY:65535,minZ:16,maxZ:16},{minX:0,minY:0,maxX:131071,maxY:131071,minZ:17,maxZ:17}],genericTile:[],timeToLiveSeconds:1800,supportedLanguagesVersion:1}],scale:0,size:2,supportedLanguage:[{identifier:1,language:["ar","ca","cs","da","de","el","en","en-AU","en-GB","es","es-MX","es-US","fi","fr","fr-CA","he","hi","hr","hu","id","it","ja","ko","ms","nb","nl","pl","pt","pt-PT","ro","ru","sk","sv","th","tr","uk","vi","zh-Hans","zh-Hant","zh-HK"]}],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-2-ssl.ls.apple.com/poi_update",dataSet:1,supportsMultipathTCP:!1},{style:85,validVersion:[{identifier:16034178,availableTiles:[{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12}],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-2-ssl.ls.apple.com/live_tile.vf",dataSet:0,supportsMultipathTCP:!1},{style:85,validVersion:[{identifier:16030619,availableTiles:[{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12}],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-2-ssl.ls.apple.com/live_tile.vf",dataSet:1,supportsMultipathTCP:!1},{style:87,validVersion:[{identifier:16034178,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:1},{minX:0,minY:0,maxX:3,maxY:3,minZ:2,maxZ:2},{minX:0,minY:0,maxX:7,maxY:7,minZ:3,maxZ:3},{minX:0,minY:0,maxX:15,maxY:15,minZ:4,maxZ:4},{minX:0,minY:0,maxX:31,maxY:31,minZ:5,maxZ:5},{minX:0,minY:0,maxX:63,maxY:63,minZ:6,maxZ:6},{minX:0,minY:0,maxX:127,maxY:127,minZ:7,maxZ:7},{minX:0,minY:0,maxX:255,maxY:255,minZ:8,maxZ:8},{minX:0,minY:0,maxX:511,maxY:511,minZ:9,maxZ:9},{minX:0,minY:0,maxX:1023,maxY:1023,minZ:10,maxZ:10},{minX:0,minY:0,maxX:2047,maxY:2047,minZ:11,maxZ:11},{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12},{minX:0,minY:0,maxX:8191,maxY:8191,minZ:13,maxZ:13},{minX:0,minY:0,maxX:16383,maxY:16383,minZ:14,maxZ:14},{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15}],genericTile:[],supportedLanguagesVersion:1}],scale:0,size:2,supportedLanguage:[{identifier:1,language:["ar","ca","cs","da","de","el","en","en-AU","en-GB","es","es-MX","es-US","fi","fr","fr-CA","he","hi","hr","hu","id","it","ja","ko","ms","nb","nl","pl","pt","pt-PT","ro","ru","sk","sv","th","tr","uk","vi","zh-Hans","zh-Hant","zh-HK"]}],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-ssl.ls.apple.com/tile.vf",dataSet:0,supportsMultipathTCP:!1},{style:87,validVersion:[{identifier:16030619,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:1},{minX:0,minY:0,maxX:3,maxY:3,minZ:2,maxZ:2},{minX:0,minY:0,maxX:7,maxY:7,minZ:3,maxZ:3},{minX:0,minY:0,maxX:15,maxY:15,minZ:4,maxZ:4},{minX:0,minY:0,maxX:31,maxY:31,minZ:5,maxZ:5},{minX:0,minY:0,maxX:63,maxY:63,minZ:6,maxZ:6},{minX:0,minY:0,maxX:127,maxY:127,minZ:7,maxZ:7},{minX:0,minY:0,maxX:255,maxY:255,minZ:8,maxZ:8},{minX:0,minY:0,maxX:511,maxY:511,minZ:9,maxZ:9},{minX:0,minY:0,maxX:1023,maxY:1023,minZ:10,maxZ:10},{minX:0,minY:0,maxX:2047,maxY:2047,minZ:11,maxZ:11},{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12},{minX:0,minY:0,maxX:8191,maxY:8191,minZ:13,maxZ:13},{minX:0,minY:0,maxX:16383,maxY:16383,minZ:14,maxZ:14},{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15}],genericTile:[],supportedLanguagesVersion:1}],scale:0,size:2,supportedLanguage:[{identifier:1,language:["ar","ca","cs","da","de","el","en","en-AU","en-GB","es","es-MX","es-US","fi","fr","fr-CA","he","hi","hr","hu","id","it","ja","ko","ms","nb","nl","pl","pt","pt-PT","ro","ru","sk","sv","th","tr","uk","vi","zh-Hans","zh-Hant","zh-HK"]}],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-kittyhawk-ssl.ls.apple.com/tile.vf",dataSet:1,supportsMultipathTCP:!1},{style:88,validVersion:[{identifier:16034178,availableTiles:[{minX:0,minY:0,maxX:127,maxY:127,minZ:7,maxZ:7},{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12}],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-ssl.ls.apple.com/tile.vf",dataSet:0,supportsMultipathTCP:!1},{style:88,validVersion:[{identifier:16030619,availableTiles:[{minX:0,minY:0,maxX:127,maxY:127,minZ:7,maxZ:7},{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12}],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-kittyhawk-ssl.ls.apple.com/tile.vf",dataSet:1,supportsMultipathTCP:!1},{style:89,validVersion:[{identifier:1,availableTiles:[{minX:0,minY:0,maxX:262143,maxY:262143,minZ:18,maxZ:18}],genericTile:[],timeToLiveSeconds:86400}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe79-ssl.ls.apple.com/ray/v1",supportsMultipathTCP:!1},{style:90,validVersion:[{identifier:16034178,availableTiles:[{minX:0,minY:0,maxX:8191,maxY:8191,minZ:13,maxZ:13},{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15}],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-ssl.ls.apple.com/tile.vf",dataSet:0,supportsMultipathTCP:!1},{style:90,validVersion:[{identifier:16030619,availableTiles:[{minX:0,minY:0,maxX:8191,maxY:8191,minZ:13,maxZ:13},{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15}],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-kittyhawk-ssl.ls.apple.com/tile.vf",dataSet:1,supportsMultipathTCP:!1}],attribution:[{name:"‎",url:"https://gspe21-ssl.ls.apple.com/html/attribution-275.html",resource:[],region:[],linkDisplayStringIndex:0,plainTextURL:"https://gspe21-ssl.ls.apple.com/html/attribution-274.txt",plainTextURLSHA256Checksum:{0:95,1:21,2:102,3:110,4:8,5:247,6:232,7:236,8:45,9:156,10:70,11:137,12:179,13:197,14:80,15:243,16:60,17:246,18:254,19:239,20:198,21:57,22:65,23:219,24:22,25:147,26:180,27:123,28:186,29:78,30:122,31:162}},{name:"MMI",url:"https://gspe21-ssl.ls.apple.com/html/attribution-275.html",resource:[{resourceType:5,filename:"mmi-mask-2.png",checksum:{0:35,1:54,2:2,3:219,4:218,5:184,6:124,7:50,8:35,9:32,10:86,11:20,12:147,13:223,14:7,15:41,16:209,17:238,18:32,19:41},region:[],filter:[],validationMethod:0,updateMethod:0},{resourceType:5,filename:"mmi-mask-2@2x.png",checksum:{0:5,1:160,2:112,3:185,4:3,5:255,6:7,7:75,8:78,9:139,10:52,11:81,12:151,13:231,14:143,15:29,16:187,17:109,18:220,19:80},region:[],filter:[],validationMethod:0,updateMethod:0},{resourceType:5,filename:"mmi-mask-2@3x.png",checksum:{0:240,1:170,2:204,3:91,4:161,5:113,6:81,7:101,8:136,9:205,10:115,11:2,12:192,13:97,14:106,15:34,16:227,17:214,18:74,19:220},region:[],filter:[],validationMethod:0,updateMethod:0}],region:[{minX:176,minY:110,maxX:183,maxY:122,minZ:8,maxZ:21},{minX:178,minY:107,maxX:188,maxY:107,minZ:8,maxZ:21},{minX:178,minY:108,maxX:183,maxY:109,minZ:8,maxZ:21},{minX:180,minY:105,maxX:180,maxY:106,minZ:8,maxZ:21},{minX:181,minY:104,maxX:183,maxY:106,minZ:8,maxZ:21},{minX:182,minY:103,maxX:182,maxY:103,minZ:8,maxZ:21},{minX:184,minY:104,maxX:184,maxY:106,minZ:8,maxZ:21},{minX:184,minY:108,maxX:195,maxY:110,minZ:8,maxZ:21},{minX:184,minY:111,maxX:194,maxY:111,minZ:8,maxZ:21},{minX:184,minY:112,maxX:191,maxY:120,minZ:8,maxZ:21},{minX:184,minY:121,maxX:184,maxY:121,minZ:8,maxZ:21},{minX:185,minY:105,maxX:185,maxY:106,minZ:8,maxZ:21},{minX:190,minY:107,maxX:190,maxY:107,minZ:8,maxZ:21},{minX:193,minY:118,maxX:194,maxY:123,minZ:8,maxZ:21},{minX:195,minY:118,maxX:195,maxY:118,minZ:8,maxZ:21}],linkDisplayStringIndex:0},{name:"© GeoTechnologies, Inc.",url:"https://gspe21-ssl.ls.apple.com/html/attribution-275.html",resource:[],region:[{minX:218,minY:102,maxX:225,maxY:104,minZ:8,maxZ:21},{minX:221,minY:98,maxX:228,maxY:101,minZ:8,maxZ:21},{minX:226,minY:91,maxX:231,maxY:97,minZ:8,maxZ:21}],linkDisplayStringIndex:0}],urlInfoSet:[{alternateResourcesURL:[{url:"https://cdn.apple-mapkit.com/rap",supportsMultipathTCP:!1}],resourcesURL:{url:"https://gspe21-ssl.ls.apple.com/",supportsMultipathTCP:!1},searchAttributionManifestURL:{url:"https://gspe21-ssl.ls.apple.com/config/search-attribution-1262",supportsMultipathTCP:!1},directionsURL:{url:"https://gsp-ssl.ls.apple.com/directions.arpc",supportsMultipathTCP:!0,alternativeMultipathTCPPort:5228},etaURL:{url:"https://gsp-ssl.ls.apple.com/directions.arpc",supportsMultipathTCP:!0,alternativeMultipathTCPPort:5228},batchReverseGeocoderURL:{url:"https://gsp36-ssl.ls.apple.com/revgeo.arpc",supportsMultipathTCP:!1},simpleETAURL:{url:"https://gsp-ssl.ls.apple.com/directions.arpc",supportsMultipathTCP:!0,alternativeMultipathTCPPort:5228},addressCorrectionInitURL:{url:"https://gsp47-ssl.ls.apple.com/ac",supportsMultipathTCP:!1},addressCorrectionUpdateURL:{url:"https://gsp47-ssl.ls.apple.com/ac",supportsMultipathTCP:!1},problemSubmissionURL:{url:"https://sundew.ls.apple.com/v1/feedback/submission.arpc",supportsMultipathTCP:!1},problemStatusURL:{url:"https://sundew.ls.apple.com/grp/st",supportsMultipathTCP:!1},reverseGeocoderVersionsURL:{url:"https://gspe21-ssl.ls.apple.com/config/revgeo-version-11.plist",supportsMultipathTCP:!1},problemCategoriesURL:{url:"https://gspe21-ssl.ls.apple.com/config/com.apple.GEO.BusinessLocalizedCategories-424.plist",supportsMultipathTCP:!1},announcementsURL:{url:"https://gspe35-ssl.ls.apple.com/config/announcements?environment=prod",supportsMultipathTCP:!1},dispatcherURL:{url:"https://gsp-ssl.ls.apple.com/dispatcher.arpc",supportsMultipathTCP:!0,alternativeMultipathTCPPort:5228},problemOptInURL:{url:"https://sundew.ls.apple.com/grp/oi",supportsMultipathTCP:!1},abExperimentURL:{url:"https://gsp-ssl.ls.apple.com/ab.arpc",supportsMultipathTCP:!1},businessPortalBaseURL:{url:"https://mapsconnect.apple.com/business/ui/claimPlace",supportsMultipathTCP:!1},logMessageUsageURL:{url:"https://gsp64-ssl.ls.apple.com/a/v2/use",supportsMultipathTCP:!1},spatialLookupURL:{url:"https://gsp51-ssl.ls.apple.com/api/v1.0/poi/data",supportsMultipathTCP:!1},realtimeTrafficProbeURL:{url:"https://gsp9-ssl.apple.com/hvr/v2/rtloc",supportsMultipathTCP:!1},batchTrafficProbeURL:{url:"https://gsp10-ssl.ls.apple.com/hvr/v2/loc",supportsMultipathTCP:!1},proactiveRoutingURL:{url:"https://gsp-ssl-commute.ls.apple.com/directions.arpc",supportsMultipathTCP:!0,alternativeMultipathTCPPort:5228},logMessageUsageV3URL:{url:"https://gsp64-ssl.ls.apple.com/hvr/v3/use",supportsMultipathTCP:!1},backgroundDispatcherURL:{url:"https://gsp57-ssl-background.ls.apple.com/dispatcher.arpc",supportsMultipathTCP:!0,alternativeMultipathTCPPort:5228},bluePOIDispatcherURL:{url:"https://gsp57-ssl-locus.ls.apple.com/dispatcher.arpc",supportsMultipathTCP:!0,alternativeMultipathTCPPort:5228},backgroundRevGeoURL:{url:"https://gsp57-ssl-revgeo.ls.apple.com/dispatcher.arpc",supportsMultipathTCP:!1},wifiConnectionQualityProbeURL:{url:"https://gsp10-ssl.ls.apple.com/hvr/wcq",supportsMultipathTCP:!1},wifiQualityURL:{url:"https://gsp85-ssl.ls.apple.com/wifi_request",supportsMultipathTCP:!1},feedbackSubmissionURL:{url:"https://sundew.ls.apple.com/v1/feedback/submission.arpc",supportsMultipathTCP:!1},feedbackLookupURL:{url:"https://gsp-ssl.ls.apple.com/feedback.arpc",supportsMultipathTCP:!1},analyticsCohortSessionURL:{url:"https://gsp64-ssl.ls.apple.com/hvr/v3/use",supportsMultipathTCP:!1},analyticsLongSessionURL:{url:"https://gsp64-ssl.ls.apple.com/hvr/v3/use",supportsMultipathTCP:!1},analyticsShortSessionURL:{url:"https://gsp64-ssl.ls.apple.com/hvr/v3/use",supportsMultipathTCP:!1},analyticsSessionlessURL:{url:"https://gsp64-ssl.ls.apple.com/hvr/v3/use",supportsMultipathTCP:!1},webModuleBaseURL:{url:"https://maps.apple.com",supportsMultipathTCP:!1},wifiQualityTileURL:{url:"https://gspe85-ssl.ls.apple.com/wifi_request_tile",supportsMultipathTCP:!1},addressCorrectionTaggedLocationURL:{url:"https://gsp47-ssl.ls.apple.com/ac",supportsMultipathTCP:!1},proactiveAppClipURL:{url:"https://gspe79-ssl.ls.apple.com/72/v2",supportsMultipathTCP:!1},enrichmentSubmissionURL:{url:"https://sundew.ls.apple.com/v1/feedback/submission.arpc",supportsMultipathTCP:!1},ugcLogDiscardURL:{url:"https://sundew.ls.apple.com/v1/log_message",supportsMultipathTCP:!1},batchReverseGeocoderPlaceRequestURL:{url:"https://gsp36-ssl.ls.apple.com/revgeo_pr.arpc",supportsMultipathTCP:!1},pressureProbeDataURL:{url:"https://gsp10-ssl.ls.apple.com/hvr/cpr",supportsMultipathTCP:!1},poiBusynessActivityCollectionURL:{url:"https://gsp53-ssl.ls.apple.com/hvr/rt_poi_activity",supportsMultipathTCP:!1},rapWebBundleURL:{url:"https://cdn.apple-mapkit.com/rap",supportsMultipathTCP:!1},networkSelectionHarvestURL:{url:"https://gsp10-ssl.ls.apple.com/hvr/strn",supportsMultipathTCP:!1},offlineDataBatchListURL:{url:"https://gspe121-ssl.ls.apple.com/api/batchesForRegion",supportsMultipathTCP:!1},offlineDataSizeURL:{url:"https://gspe121-ssl.ls.apple.com/api/sizeForRegion",supportsMultipathTCP:!1},offlineDataDownloadBaseURL:{url:"https://gspe121-ssl.ls.apple.com",supportsMultipathTCP:!1},bcxDispatcherURL:{url:"https://gsp57-ssl-bcx.ls.apple.com/dispatcher.arpc",supportsMultipathTCP:!1}}],muninBucket:[{bucketID:2,bucketURL:"https://gspe72-ssl.ls.apple.com/mnn_us"},{bucketID:6,bucketURL:"https://gspe72-ssl.ls.apple.com/mnn_us"}]}},u={Settings:o,Configs:p},x={Switch:!0,CountryCode:"US",newsPlusUser:!0},c={Settings:x},h={Switch:!0,CountryCode:"US",canUse:!0},d={Settings:h},X={Switch:!0,CountryCode:"SG",Domains:["web","itunes","app_store","movies","restaurants","maps"],Functions:["flightutilities","lookup","mail","messages","news","safari","siri","spotlight","visualintelligence"],Safari_Smart_History:!0},Z={VisualIntelligence:{enabled_domains:["pets","media","books","art","nature","landmarks"],supported_domains:["ART","BOOK","MEDIA","LANDMARK","ANIMALS","BIRDS","FOOD","SIGN_SYMBOL","AUTO_SYMBOL","DOGS","NATURE","NATURAL_LANDMARK","INSECTS","REPTILES","ALBUM","STOREFRONT","LAUNDRY_CARE_SYMBOL","CATS","OBJECT_2D","SCULPTURE","SKYLINE","MAMMALS"]}},Y={Settings:X,Configs:Z},g={Switch:"true",CountryCode:"US",MultiAccount:"false",Universal:"true"},y={Settings:g},v={Switch:!0,"Third-Party":!1,HLSUrl:"play-edge.itunes.apple.com",ServerUrl:"play.itunes.apple.com",Tabs:["WatchNow","Originals","MLS","Sports","Kids","Store","Movies","TV","ChannelsAndApps","Library","Search"],CountryCode:{Configs:"AUTO",Settings:"AUTO",View:["SG","TW"],WatchNow:"AUTO",Channels:"AUTO",Originals:"AUTO",Sports:"US",Kids:"US",Store:"AUTO",Movies:"AUTO",TV:"AUTO",Persons:"SG",Search:"AUTO",Others:"AUTO"}},T={Locale:[["AU","en-AU"],["CA","en-CA"],["GB","en-GB"],["KR","ko-KR"],["HK","yue-Hant"],["JP","ja-JP"],["MO","zh-Hant"],["TW","zh-Hant"],["US","en-US"],["SG","zh-Hans"]],Tabs:[{title:"主页",type:"WatchNow",universalLinks:["https://tv.apple.com/watch-now","https://tv.apple.com/home"],destinationType:"Target",target:{id:"tahoma_watchnow",type:"Root",url:"https://tv.apple.com/watch-now"},isSelected:!0},{title:"Apple TV+",type:"Originals",universalLinks:["https://tv.apple.com/channel/tvs.sbd.4000","https://tv.apple.com/atv"],destinationType:"Target",target:{id:"tvs.sbd.4000",type:"Brand",url:"https://tv.apple.com/us/channel/tvs.sbd.4000"}},{title:"MLS Season Pass",type:"MLS",universalLinks:["https://tv.apple.com/mls"],destinationType:"Target",target:{id:"tvs.sbd.7000",type:"Brand",url:"https://tv.apple.com/us/channel/tvs.sbd.7000"}},{title:"体育节目",type:"Sports",universalLinks:["https://tv.apple.com/sports"],destinationType:"Target",target:{id:"tahoma_sports",type:"Root",url:"https://tv.apple.com/sports"}},{title:"儿童",type:"Kids",universalLinks:["https://tv.apple.com/kids"],destinationType:"Target",target:{id:"tahoma_kids",type:"Root",url:"https://tv.apple.com/kids"}},{title:"电影",type:"Movies",universalLinks:["https://tv.apple.com/movies"],destinationType:"Target",target:{id:"tahoma_movies",type:"Root",url:"https://tv.apple.com/movies"}},{title:"电视节目",type:"TV",universalLinks:["https://tv.apple.com/tv-shows"],destinationType:"Target",target:{id:"tahoma_tvshows",type:"Root",url:"https://tv.apple.com/tv-shows"}},{title:"商店",type:"Store",universalLinks:["https://tv.apple.com/store"],destinationType:"SubTabs",subTabs:[{title:"电影",type:"Movies",universalLinks:["https://tv.apple.com/movies"],destinationType:"Target",target:{id:"tahoma_movies",type:"Root",url:"https://tv.apple.com/movies"}},{title:"电视节目",type:"TV",universalLinks:["https://tv.apple.com/tv-shows"],destinationType:"Target",target:{id:"tahoma_tvshows",type:"Root",url:"https://tv.apple.com/tv-shows"}}]},{title:"频道和 App",destinationType:"SubTabs",subTabsPlacementType:"ExpandedList",type:"ChannelsAndApps",subTabs:[]},{title:"资料库",type:"Library",destinationType:"Client"},{title:"搜索",type:"Search",universalLinks:["https://tv.apple.com/search"],destinationType:"Target",target:{id:"tahoma_search",type:"Root",url:"https://tv.apple.com/search"}}],i18n:{WatchNow:[["en","Home"],["zh","主页"],["zh-Hans","主頁"],["zh-Hant","主頁"]],Movies:[["en","Movies"],["zh","电影"],["zh-Hans","电影"],["zh-Hant","電影"]],TV:[["en","TV"],["zh","电视节目"],["zh-Hans","电视节目"],["zh-Hant","電視節目"]],Store:[["en","Store"],["zh","商店"],["zh-Hans","商店"],["zh-Hant","商店"]],Sports:[["en","Sports"],["zh","体育节目"],["zh-Hans","体育节目"],["zh-Hant","體育節目"]],Kids:[["en","Kids"],["zh","儿童"],["zh-Hans","儿童"],["zh-Hant","兒童"]],Library:[["en","Library"],["zh","资料库"],["zh-Hans","资料库"],["zh-Hant","資料庫"]],Search:[["en","Search"],["zh","搜索"],["zh-Hans","搜索"],["zh-Hant","蒐索"]]}},S={Settings:v,Configs:T},f=Database={Default:Object.freeze({__proto__:null,Configs:s,Settings:m,default:n}),Location:Object.freeze({__proto__:null,Settings:l,default:r}),Maps:Object.freeze({__proto__:null,Configs:p,Settings:o,default:u}),News:Object.freeze({__proto__:null,Settings:x,default:c}),PrivateRelay:Object.freeze({__proto__:null,Settings:h,default:d}),Siri:Object.freeze({__proto__:null,Configs:Z,Settings:X,default:Y}),TestFlight:Object.freeze({__proto__:null,Settings:g,default:y}),TV:Object.freeze({__proto__:null,Configs:T,Settings:v,default:S})};function C(a,t,m){console.log("☑️ Set Environment Variables","");let{Settings:s,Caches:n,Configs:l}=function(a,t,m){let s=i.getItem(a,m),n={};if("undefined"!=typeof $argument&&Boolean($argument)){let i=Object.fromEntries($argument.split("&").map((e=>e.split("=").map((e=>e.replace(/\"/g,""))))));for(let a in i)e.set(n,a,i[a])}const l={Settings:m?.Default?.Settings||{},Configs:m?.Default?.Configs||{},Caches:{}};Array.isArray(t)||(t=[t]);for(let e of t)l.Settings={...l.Settings,...m?.[e]?.Settings,...n,...s?.[e]?.Settings},l.Configs={...l.Configs,...m?.[e]?.Configs},s?.[e]?.Caches&&"string"==typeof s?.[e]?.Caches&&(s[e].Caches=JSON.parse(s?.[e]?.Caches)),l.Caches={...l.Caches,...s?.[e]?.Caches};return function e(i,a){for(var t in i){var m=i[t];i[t]="object"==typeof m&&null!==m?e(m,a):a(t,m)}return i}(l.Settings,((e,i)=>("true"===i||"false"===i?i=JSON.parse(i):"string"==typeof i&&(i=i.includes(",")?i.split(",").map((e=>r(e))):r(i)),i))),l;function r(e){return e&&!isNaN(e)&&(e=parseInt(e,10)),e}}(a,t,m);if(s?.Tabs&&!Array.isArray(s?.Tabs)&&e.set(s,"Tabs",s?.Tabs?[s.Tabs.toString()]:[]),s?.Domains&&!Array.isArray(s?.Domains)&&e.set(s,"Domains",s?.Domains?[s.Domains.toString()]:[]),s?.Functions&&!Array.isArray(s?.Functions)&&e.set(s,"Functions",s?.Functions?[s.Functions.toString()]:[]),console.log(`✅ Set Environment Variables, Settings: ${typeof s}, Settings内容: ${JSON.stringify(s)}`,""),l.Storefront=new Map(l.Storefront),l.Locale&&(l.Locale=new Map(l.Locale)),l.i18n)for(let e in l.i18n)l.i18n[e]=new Map(l.i18n[e]);return{Settings:s,Caches:n,Configs:l}}const U=new a(" iRingo: 🔍 Siri v3.1.0(3) request");let b;const L=t.parse($request.url);U.log(`⚠ URL: ${JSON.stringify(L)}`,"");const k=$request.method,R=L.host,M=L.path;L.paths,U.log(`⚠ METHOD: ${k}`,"");const P=($request.headers?.["Content-Type"]??$request.headers?.["content-type"])?.split(";")?.[0];U.log(`⚠ FORMAT: ${P}`,""),(async()=>{const{Settings:i,Caches:a,Configs:m}=C("iRingo","Siri",f);switch(U.log(`⚠ Settings.Switch: ${i?.Switch}`,""),i.Switch){case!0:default:const a=L.query?.locale;switch(U.log(`🚧 LOCALE: ${a}`,""),i.CountryCode="AUTO"==i.CountryCode?a?.match(/[A-Z]{2}$/)?.[0]??i.CountryCode:i.CountryCode,e.set(L,"query.cc",i.CountryCode),k){case"POST":case"PUT":case"PATCH":case"DELETE":case"GET":case"HEAD":case"OPTIONS":case void 0:default:switch(R){case"api.smoot.apple.com":case"api.smoot.apple.cn":case"fbs.smoot.apple.com":case"cdn.smoot.apple.com":break;default:switch(M){case"warm":case"render":case"flight":break;case"search":if("zkw"===L.query?.qtype)switch(i.CountryCode){case"CN":case"HK":case"MO":case"TW":case"SG":e.set(L,"query.locale",`${i.CountryCode}_SG`);break;case"US":case"CA":case"UK":case"AU":break;default:e.set(L,"query.locale",`${i.CountryCode}_US`)}else L.query?.q?.startsWith?.("%E5%A4%A9%E6%B0%94%20")?(console.log("'天气 '开头"),L.query.q=L.query.q.replace(/%E5%A4%A9%E6%B0%94/,"weather"),/^weather%20.*%E5%B8%82$/.test(L.query.q)&&(L.query.q=L.query.q.replace(/$/,"%E5%B8%82"))):L.query?.q?.endsWith?.("%20%E5%A4%A9%E6%B0%94")&&(console.log("' 天气'结尾"),L.query.q=L.query.q.replace(/%E5%A4%A9%E6%B0%94/,"weather"),/.*%E5%B8%82%20weather$/.test(L.query.q)&&(L.query.q=L.query.q.replace(/%20weather$/,"%E5%B8%82%20weather")));break;case"card":switch(e.set(L,"query.card_locale",a),L.query?.include){case"tv":case"movies":switch(L.query?.storefront?.match(/[\d]{6}/g)){case"143463":L.query.q=L.query.q.replace(/%2F[a-z]{2}-[A-Z]{2}/,"%2Fzh-HK");break;case"143470":L.query.q=L.query.q.replace(/%2F[a-z]{2}-[A-Z]{2}/,"%2Fzh-TW");break;case"143464":L.query.q=L.query.q.replace(/%2F[a-z]{2}-[A-Z]{2}/,"%2Fzh-SG")}}}}case"CONNECT":case"TRACE":}$request.headers?.Host&&($request.headers.Host=L.host),$request.url=t.stringify(L),U.log("🚧 调试信息",`$request.url: ${$request.url}`,"");case!1:}})().catch((e=>U.logErr(e))).finally((()=>{if(void 0===b)U.done($request);else U.isQuanX()?(b.status||(b.status="HTTP/1.1 200 OK"),delete b.headers?.["Content-Length"],delete b.headers?.["content-length"],delete b.headers?.["Transfer-Encoding"],U.done(b)):U.done({response:b})})); diff --git a/js/archive/Siri.response.js b/js/archive/Siri.response.js deleted file mode 100644 index d0e689579..000000000 --- a/js/archive/Siri.response.js +++ /dev/null @@ -1 +0,0 @@ -class e{static name="Lodash";static version="1.2.2";static about(){return console.log(`\n🟧 ${this.name} v${this.version}\n`)}static get(e={},i="",a=void 0){Array.isArray(i)||(i=this.toPath(i));const t=i.reduce(((e,i)=>Object(e)[i]),e);return void 0===t?a:t}static set(e={},i="",a){return Array.isArray(i)||(i=this.toPath(i)),i.slice(0,-1).reduce(((e,a,t)=>Object(e[a])===e[a]?e[a]:e[a]=/^\d+$/.test(i[t+1])?[]:{}),e)[i[i.length-1]]=a,e}static unset(e={},i=""){return Array.isArray(i)||(i=this.toPath(i)),i.reduce(((e,a,t)=>t===i.length-1?(delete e[a],!0):Object(e)[a]),e)}static toPath(e){return e.replace(/\[(\d+)\]/g,".$1").split(".").filter(Boolean)}static escape(e){const i={"&":"&","<":"<",">":">",'"':""","'":"'"};return e.replace(/[&<>"']/g,(e=>i[e]))}static unescape(e){const i={"&":"&","<":"<",">":">",""":'"',"'":"'"};return e.replace(/&|<|>|"|'/g,(e=>i[e]))}}class i{static name="$Storage";static version="1.0.9";static about(){return console.log(`\n🟧 ${this.name} v${this.version}\n`)}static data=null;static dataFile="box.dat";static#e=/^@(?[^.]+)(?:\.(?.*))?$/;static#i(){return"undefined"!=typeof $environment&&$environment["surge-version"]?"Surge":"undefined"!=typeof $environment&&$environment["stash-version"]?"Stash":"undefined"!=typeof module&&module.exports?"Node.js":"undefined"!=typeof $task?"Quantumult X":"undefined"!=typeof $loon?"Loon":"undefined"!=typeof $rocket?"Shadowrocket":"undefined"!=typeof Egern?"Egern":void 0}static getItem(i=new String,a=null){let t=a;if(!0===i.startsWith("@")){const{key:a,path:m}=i.match(this.#e)?.groups;i=a;let n=this.getItem(i,{});"object"!=typeof n&&(n={}),t=e.get(n,m);try{t=JSON.parse(t)}catch(e){}}else{switch(this.#i()){case"Surge":case"Loon":case"Stash":case"Egern":case"Shadowrocket":t=$persistentStore.read(i);break;case"Quantumult X":t=$prefs.valueForKey(i);break;case"Node.js":this.data=this.#a(this.dataFile),t=this.data?.[i];break;default:t=this.data?.[i]||null}try{t=JSON.parse(t)}catch(e){}}return t??a}static setItem(i=new String,a=new String){let t=!1;if("object"==typeof a)a=JSON.stringify(a);else a=String(a);if(!0===i.startsWith("@")){const{key:m,path:n}=i.match(this.#e)?.groups;i=m;let s=this.getItem(i,{});"object"!=typeof s&&(s={}),e.set(s,n,a),t=this.setItem(i,s)}else switch(this.#i()){case"Surge":case"Loon":case"Stash":case"Egern":case"Shadowrocket":t=$persistentStore.write(a,i);break;case"Quantumult X":t=$prefs.setValueForKey(a,i);break;case"Node.js":this.data=this.#a(this.dataFile),this.data[i]=a,this.#t(this.dataFile),t=!0;break;default:t=this.data?.[i]||null}return t}static removeItem(i){let a=!1;if(!0===i.startsWith("@")){const{key:t,path:m}=i.match(this.#e)?.groups;i=t;let n=this.getItem(i);"object"!=typeof n&&(n={}),keyValue=e.unset(n,m),a=this.setItem(i,n)}else switch(this.#i()){case"Surge":case"Loon":case"Stash":case"Egern":case"Shadowrocket":case"Node.js":default:a=!1;break;case"Quantumult X":a=$prefs.removeValueForKey(i)}return a}static clear(){let e=!1;switch(this.#i()){case"Surge":case"Loon":case"Stash":case"Egern":case"Shadowrocket":case"Node.js":default:e=!1;break;case"Quantumult X":e=$prefs.removeAllValues()}return e}static#a(e){if(!this.isNode())return{};{this.fs=this.fs?this.fs:require("fs"),this.path=this.path?this.path:require("path");const i=this.path.resolve(e),a=this.path.resolve(process.cwd(),e),t=this.fs.existsSync(i),m=!t&&this.fs.existsSync(a);if(!t&&!m)return{};{const e=t?i:a;try{return JSON.parse(this.fs.readFileSync(e))}catch(e){return{}}}}}static#t(e=this.dataFile){if(this.isNode()){this.fs=this.fs?this.fs:require("fs"),this.path=this.path?this.path:require("path");const i=this.path.resolve(e),a=this.path.resolve(process.cwd(),e),t=this.fs.existsSync(i),m=!t&&this.fs.existsSync(a),n=JSON.stringify(this.data);t?this.fs.writeFileSync(i,n):m?this.fs.writeFileSync(a,n):this.fs.writeFileSync(i,n)}}}class a{static name="ENV";static version="1.8.3";static about(){return console.log(`\n🟧 ${this.name} v${this.version}\n`)}constructor(e,i){console.log(`\n🟧 ${a.name} v${a.version}\n`),this.name=e,this.logs=[],this.isMute=!1,this.isMuteLog=!1,this.logSeparator="\n",this.encoding="utf-8",this.startTime=(new Date).getTime(),Object.assign(this,i),this.log(`\n🚩 开始!\n${e}\n`)}environment(){switch(this.platform()){case"Surge":return $environment.app="Surge",$environment;case"Stash":return $environment.app="Stash",$environment;case"Egern":return $environment.app="Egern",$environment;case"Loon":let e=$loon.split(" ");return{device:e[0],ios:e[1],"loon-version":e[2],app:"Loon"};case"Quantumult X":return{app:"Quantumult X"};case"Node.js":return process.env.app="Node.js",process.env;default:return{}}}platform(){return"undefined"!=typeof $environment&&$environment["surge-version"]?"Surge":"undefined"!=typeof $environment&&$environment["stash-version"]?"Stash":"undefined"!=typeof module&&module.exports?"Node.js":"undefined"!=typeof $task?"Quantumult X":"undefined"!=typeof $loon?"Loon":"undefined"!=typeof $rocket?"Shadowrocket":"undefined"!=typeof Egern?"Egern":void 0}isNode(){return"Node.js"===this.platform()}isQuanX(){return"Quantumult X"===this.platform()}isSurge(){return"Surge"===this.platform()}isLoon(){return"Loon"===this.platform()}isShadowrocket(){return"Shadowrocket"===this.platform()}isStash(){return"Stash"===this.platform()}isEgern(){return"Egern"===this.platform()}async getScript(e){return await this.fetch(e).then((e=>e.body))}async runScript(e,a){let t=i.getItem("@chavy_boxjs_userCfgs.httpapi");t=t?.replace?.(/\n/g,"")?.trim();let m=i.getItem("@chavy_boxjs_userCfgs.httpapi_timeout");m=1*m??20,m=a?.timeout??m;const[n,s]=t.split("@"),l={url:`http://${s}/v1/scripting/evaluate`,body:{script_text:e,mock_type:"cron",timeout:m},headers:{"X-Key":n,Accept:"*/*"},timeout:m};await this.fetch(l).then((e=>e.body),(e=>this.logErr(e)))}initGotEnv(e){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,e&&(e.headers=e.headers?e.headers:{},void 0===e.headers.Cookie&&void 0===e.cookieJar&&(e.cookieJar=this.ckjar))}async fetch(i={}||"",a={}){switch(i.constructor){case Object:i={...a,...i};break;case String:i={...a,url:i}}i.method||(i.method="GET",(i.body??i.bodyBytes)&&(i.method="POST")),delete i.headers?.Host,delete i.headers?.[":authority"],delete i.headers?.["Content-Length"],delete i.headers?.["content-length"];const t=i.method.toLocaleLowerCase();switch(this.platform()){case"Loon":case"Surge":case"Stash":case"Egern":case"Shadowrocket":default:return i.timeout&&(i.timeout=parseInt(i.timeout,10),this.isSurge()||(i.timeout=1e3*i.timeout)),i.policy&&(this.isLoon()&&(i.node=i.policy),this.isStash()&&e.set(i,"headers.X-Stash-Selected-Proxy",encodeURI(i.policy)),this.isShadowrocket()&&e.set(i,"headers.X-Surge-Proxy",i.policy)),"boolean"==typeof i.redirection&&(i["auto-redirect"]=i.redirection),i.bodyBytes&&!i.body&&(i.body=i.bodyBytes,delete i.bodyBytes),await new Promise(((e,a)=>{$httpClient[t](i,((t,m,n)=>{t?a(t):(m.ok=/^2\d\d$/.test(m.status),m.statusCode=m.status,n&&(m.body=n,1==i["binary-mode"]&&(m.bodyBytes=n)),e(m))}))}));case"Quantumult X":return i.policy&&e.set(i,"opts.policy",i.policy),"boolean"==typeof i["auto-redirect"]&&e.set(i,"opts.redirection",i["auto-redirect"]),i.body instanceof ArrayBuffer?(i.bodyBytes=i.body,delete i.body):ArrayBuffer.isView(i.body)?(i.bodyBytes=i.body.buffer.slice(i.body.byteOffset,i.body.byteLength+i.body.byteOffset),delete object.body):i.body&&delete i.bodyBytes,await $task.fetch(i).then((e=>(e.ok=/^2\d\d$/.test(e.statusCode),e.status=e.statusCode,e)),(e=>Promise.reject(e.error)));case"Node.js":let a=require("iconv-lite");this.initGotEnv(i);const{url:m,...n}=i;return await this.got[t](m,n).on("redirect",((e,i)=>{try{if(e.headers["set-cookie"]){const a=e.headers["set-cookie"].map(this.cktough.Cookie.parse).toString();a&&this.ckjar.setCookieSync(a,null),i.cookieJar=this.ckjar}}catch(e){this.logErr(e)}})).then((e=>(e.statusCode=e.status,e.body=a.decode(e.rawBody,this.encoding),e.bodyBytes=e.rawBody,e)),(e=>Promise.reject(e.message)))}}time(e,i=null){const a=i?new Date(i):new Date;let t={"M+":a.getMonth()+1,"d+":a.getDate(),"H+":a.getHours(),"m+":a.getMinutes(),"s+":a.getSeconds(),"q+":Math.floor((a.getMonth()+3)/3),S:a.getMilliseconds()};/(y+)/.test(e)&&(e=e.replace(RegExp.$1,(a.getFullYear()+"").substr(4-RegExp.$1.length)));for(let i in t)new RegExp("("+i+")").test(e)&&(e=e.replace(RegExp.$1,1==RegExp.$1.length?t[i]:("00"+t[i]).substr((""+t[i]).length)));return e}msg(e=name,i="",a="",t){const m=e=>{switch(typeof e){case void 0:return e;case"string":switch(this.platform()){case"Surge":case"Stash":case"Egern":default:return{url:e};case"Loon":case"Shadowrocket":return e;case"Quantumult X":return{"open-url":e};case"Node.js":return}case"object":switch(this.platform()){case"Surge":case"Stash":case"Egern":case"Shadowrocket":default:return{url:e.url||e.openUrl||e["open-url"]};case"Loon":return{openUrl:e.openUrl||e.url||e["open-url"],mediaUrl:e.mediaUrl||e["media-url"]};case"Quantumult X":return{"open-url":e["open-url"]||e.url||e.openUrl,"media-url":e["media-url"]||e.mediaUrl,"update-pasteboard":e["update-pasteboard"]||e.updatePasteboard};case"Node.js":return}default:return}};if(!this.isMute)switch(this.platform()){case"Surge":case"Loon":case"Stash":case"Egern":case"Shadowrocket":default:$notification.post(e,i,a,m(t));break;case"Quantumult X":$notify(e,i,a,m(t));case"Node.js":}if(!this.isMuteLog){let t=["","==============📣系统通知📣=============="];t.push(e),i&&t.push(i),a&&t.push(a),console.log(t.join("\n")),this.logs=this.logs.concat(t)}}log(...e){e.length>0&&(this.logs=[...this.logs,...e]),console.log(e.join(this.logSeparator))}logErr(e){switch(this.platform()){case"Surge":case"Loon":case"Stash":case"Egern":case"Shadowrocket":case"Quantumult X":default:this.log("",`❗️ ${this.name}, 错误!`,e);break;case"Node.js":this.log("",`❗️${this.name}, 错误!`,e.stack)}}wait(e){return new Promise((i=>setTimeout(i,e)))}done(i={}){const a=((new Date).getTime()-this.startTime)/1e3;switch(this.log("",`🚩 ${this.name}, 结束! 🕛 ${a} 秒`,""),this.platform()){case"Surge":i.policy&&e.set(i,"headers.X-Surge-Policy",i.policy),$done(i);break;case"Loon":i.policy&&(i.node=i.policy),$done(i);break;case"Stash":i.policy&&e.set(i,"headers.X-Stash-Selected-Proxy",encodeURI(i.policy)),$done(i);break;case"Egern":case"Shadowrocket":default:$done(i);break;case"Quantumult X":i.policy&&e.set(i,"opts.policy",i.policy),delete i["auto-redirect"],delete i["auto-cookie"],delete i["binary-mode"],delete i.charset,delete i.host,delete i.insecure,delete i.method,delete i.opt,delete i.path,delete i.policy,delete i["policy-descriptor"],delete i.scheme,delete i.sessionIndex,delete i.statusCode,delete i.timeout,i.body instanceof ArrayBuffer?(i.bodyBytes=i.body,delete i.body):ArrayBuffer.isView(i.body)?(i.bodyBytes=i.body.buffer.slice(i.body.byteOffset,i.body.byteLength+i.body.byteOffset),delete i.body):i.body&&delete i.bodyBytes,$done(i);break;case"Node.js":process.exit(1)}}}var t={Switch:!0},m={Storefront:[["AE","143481"],["AF","143610"],["AG","143540"],["AI","143538"],["AL","143575"],["AM","143524"],["AO","143564"],["AR","143505"],["AT","143445"],["AU","143460"],["AZ","143568"],["BA","143612"],["BB","143541"],["BD","143490"],["BE","143446"],["BF","143578"],["BG","143526"],["BH","143559"],["BJ","143576"],["BM","143542"],["BN","143560"],["BO","143556"],["BR","143503"],["BS","143539"],["BT","143577"],["BW","143525"],["BY","143565"],["BZ","143555"],["CA","143455"],["CD","143613"],["CG","143582"],["CH","143459"],["CI","143527"],["CL","143483"],["CM","143574"],["CN","143465"],["CO","143501"],["CR","143495"],["CV","143580"],["CY","143557"],["CZ","143489"],["DE","143443"],["DK","143458"],["DM","143545"],["DO","143508"],["DZ","143563"],["EC","143509"],["EE","143518"],["EG","143516"],["ES","143454"],["FI","143447"],["FJ","143583"],["FM","143591"],["FR","143442"],["GA","143614"],["GB","143444"],["GD","143546"],["GF","143615"],["GH","143573"],["GM","143584"],["GR","143448"],["GT","143504"],["GW","143585"],["GY","143553"],["HK","143463"],["HN","143510"],["HR","143494"],["HU","143482"],["ID","143476"],["IE","143449"],["IL","143491"],["IN","143467"],["IQ","143617"],["IS","143558"],["IT","143450"],["JM","143511"],["JO","143528"],["JP","143462"],["KE","143529"],["KG","143586"],["KH","143579"],["KN","143548"],["KP","143466"],["KR","143466"],["KW","143493"],["KY","143544"],["KZ","143517"],["TC","143552"],["TD","143581"],["TJ","143603"],["TH","143475"],["TM","143604"],["TN","143536"],["TO","143608"],["TR","143480"],["TT","143551"],["TW","143470"],["TZ","143572"],["LA","143587"],["LB","143497"],["LC","143549"],["LI","143522"],["LK","143486"],["LR","143588"],["LT","143520"],["LU","143451"],["LV","143519"],["LY","143567"],["MA","143620"],["MD","143523"],["ME","143619"],["MG","143531"],["MK","143530"],["ML","143532"],["MM","143570"],["MN","143592"],["MO","143515"],["MR","143590"],["MS","143547"],["MT","143521"],["MU","143533"],["MV","143488"],["MW","143589"],["MX","143468"],["MY","143473"],["MZ","143593"],["NA","143594"],["NE","143534"],["NG","143561"],["NI","143512"],["NL","143452"],["NO","143457"],["NP","143484"],["NR","143606"],["NZ","143461"],["OM","143562"],["PA","143485"],["PE","143507"],["PG","143597"],["PH","143474"],["PK","143477"],["PL","143478"],["PT","143453"],["PW","143595"],["PY","143513"],["QA","143498"],["RO","143487"],["RS","143500"],["RU","143469"],["RW","143621"],["SA","143479"],["SB","143601"],["SC","143599"],["SE","143456"],["SG","143464"],["SI","143499"],["SK","143496"],["SL","143600"],["SN","143535"],["SR","143554"],["ST","143598"],["SV","143506"],["SZ","143602"],["UA","143492"],["UG","143537"],["US","143441"],["UY","143514"],["UZ","143566"],["VC","143550"],["VE","143502"],["VG","143543"],["VN","143471"],["VU","143609"],["XK","143624"],["YE","143571"],["ZA","143472"],["ZM","143622"],["ZW","143605"]]},n={Settings:t,Configs:m},s={Switch:!0,PEP:{GCC:"US"}},l={Settings:s},r={Switch:!0,UrlInfoSet:{Dispatcher:"AutoNavi",Directions:"AutoNavi",RAP:"Apple",LocationShift:"AUTO"},TileSet:{Map:"CN",Satellite:"HYBRID",Traffic:"CN",POI:"CN",Flyover:"XX",Munin:"XX"},GeoManifest:{Dynamic:{Config:{CountryCode:{default:"CN",iOS:"AUTO",iPadOS:"AUTO",watchOS:"US",macOS:"AUTO"}}}},Config:{Announcements:{"Environment:":{default:"AUTO",iOS:"AUTO",iPadOS:"AUTO",watchOS:"AUTO",macOS:"AUTO"}}}},o={CN:{tileSet:[{style:1,validVersion:[{identifier:2112,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:1},{minX:0,minY:0,maxX:3,maxY:3,minZ:2,maxZ:2},{minX:0,minY:0,maxX:7,maxY:7,minZ:3,maxZ:3},{minX:0,minY:0,maxX:15,maxY:15,minZ:4,maxZ:4},{minX:0,minY:0,maxX:31,maxY:31,minZ:5,maxZ:5},{minX:0,minY:0,maxX:63,maxY:63,minZ:6,maxZ:6},{minX:0,minY:0,maxX:127,maxY:127,minZ:7,maxZ:7},{minX:0,minY:0,maxX:255,maxY:255,minZ:8,maxZ:8},{minX:0,minY:0,maxX:1023,maxY:1023,minZ:10,maxZ:10},{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12},{minX:0,minY:0,maxX:16383,maxY:16383,minZ:14,maxZ:14}],genericTile:[],supportedLanguagesVersion:1}],scale:0,size:2,supportedLanguage:[{identifier:1,language:["ar","ca","cs","da","de","el","en","en-AU","en-GB","es","es-MX","fi","fr","fr-CA","he","hi","hr","hu","id","it","ja","ko","ms","nb","nl","pl","pt","pt-PT","ro","ru","sk","sv","th","tr","uk","vi","zh-Hans","zh-Hant","zh-HK"]}],countryRegionWhitelist:[{countryCode:"AE",region:"AE"},{countryCode:"AE",region:"SA"},{countryCode:"IN",region:"IN"},{countryCode:"JP",region:"JP"},{countryCode:"KR",region:"KR"},{countryCode:"MA",region:"MA"},{countryCode:"RU",region:"RU"},{countryCode:"SA",region:"AE"},{countryCode:"SA",region:"SA"}],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe19-cn-ssl.ls.apple.com/tiles?flags=8",supportsMultipathTCP:!1},{style:7,validVersion:[{identifier:51,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:7},{minX:179,minY:80,maxX:224,maxY:128,minZ:8,maxZ:8},{minX:359,minY:161,maxX:449,maxY:257,minZ:9,maxZ:9},{minX:719,minY:323,maxX:898,maxY:915,minZ:10,maxZ:10},{minX:1438,minY:646,maxX:1797,maxY:1031,minZ:11,maxZ:11},{minX:2876,minY:1292,maxX:3594,maxY:2062,minZ:12,maxZ:12},{minX:5752,minY:2584,maxX:7188,maxY:4124,minZ:13,maxZ:13},{minX:11504,minY:5168,maxX:14376,maxY:8248,minZ:14,maxZ:14},{minX:23008,minY:10336,maxX:28752,maxY:16496,minZ:15,maxZ:15},{minX:46016,minY:20672,maxX:57504,maxY:32992,minZ:16,maxZ:16},{minX:92032,minY:41344,maxX:115008,maxY:65984,minZ:17,maxZ:17},{minX:184064,minY:82668,maxX:230016,maxY:131976,minZ:18,maxZ:18}],genericTile:[{tileType:2,textureIndex:0,resourceIndex:1971}]}],scale:1,size:1,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe11-2-cn-ssl.ls.apple.com/2/tiles",supportsMultipathTCP:!1},{style:7,validVersion:[{identifier:51,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:7},{minX:179,minY:80,maxX:224,maxY:128,minZ:8,maxZ:8},{minX:359,minY:161,maxX:449,maxY:257,minZ:9,maxZ:9},{minX:719,minY:323,maxX:898,maxY:915,minZ:10,maxZ:10},{minX:1438,minY:646,maxX:1797,maxY:1031,minZ:11,maxZ:11},{minX:2876,minY:1292,maxX:3594,maxY:2062,minZ:12,maxZ:12},{minX:5752,minY:2584,maxX:7188,maxY:4124,minZ:13,maxZ:13},{minX:11504,minY:5168,maxX:14376,maxY:8248,minZ:14,maxZ:14},{minX:23008,minY:10336,maxX:28752,maxY:16496,minZ:15,maxZ:15},{minX:46016,minY:20672,maxX:57504,maxY:32992,minZ:16,maxZ:16},{minX:92032,minY:41344,maxX:115008,maxY:65984,minZ:17,maxZ:17},{minX:184064,minY:82668,maxX:230016,maxY:131976,minZ:18,maxZ:18}],genericTile:[{tileType:2,textureIndex:0,resourceIndex:1971}]}],scale:2,size:1,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe11-2-cn-ssl.ls.apple.com/2/tiles",supportsMultipathTCP:!1},{style:11,validVersion:[{identifier:470,availableTiles:[{minX:0,minY:0,maxX:8191,maxY:8191,minZ:13,maxZ:13},{minX:0,minY:0,maxX:16383,maxY:16383,minZ:14,maxZ:14},{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15}],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe19-cn-ssl.ls.apple.com/tiles?flags=1",supportsMultipathTCP:!1},{style:12,validVersion:[{identifier:2111,availableTiles:[{minX:0,minY:0,maxX:255,maxY:255,minZ:8,maxZ:8},{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12}],genericTile:[],timeToLiveSeconds:120}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe12-cn-ssl.ls.apple.com/traffic",supportsMultipathTCP:!1},{style:13,validVersion:[{identifier:2092,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:2},{minX:0,minY:0,maxX:15,maxY:15,minZ:4,maxZ:4},{minX:0,minY:0,maxX:63,maxY:63,minZ:6,maxZ:10},{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:15},{minX:0,minY:0,maxX:65535,maxY:65535,minZ:16,maxZ:16},{minX:0,minY:0,maxX:131071,maxY:131071,minZ:17,maxZ:17}],genericTile:[],timeToLiveSeconds:604800,supportedLanguagesVersion:1}],scale:0,size:2,supportedLanguage:[{identifier:1,language:["ar","ca","cs","da","de","el","en","en-AU","en-GB","es","es-MX","fi","fr","fr-CA","he","hi","hr","hu","id","it","ja","ko","ms","nb","nl","pl","pt","pt-PT","ro","ru","sk","sv","th","tr","uk","vi","zh-Hans","zh-Hant","zh-HK"]}],countryRegionWhitelist:[],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe19-cn-ssl.ls.apple.com/tiles?flags=2",supportsMultipathTCP:!1},{style:18,validVersion:[{identifier:2112,availableTiles:[{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15}],genericTile:[],supportedLanguagesVersion:1}],scale:0,size:2,supportedLanguage:[{identifier:1,language:["ar","ca","cs","da","de","el","en","en-AU","en-GB","es","es-MX","fi","fr","fr-CA","he","hi","hr","hu","id","it","ja","ko","ms","nb","nl","pl","pt","pt-PT","ro","ru","sk","sv","th","tr","uk","vi","zh-Hans","zh-Hant","zh-HK"]}],countryRegionWhitelist:[],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe19-cn-ssl.ls.apple.com/tiles",supportsMultipathTCP:!1},{style:20,validVersion:[{identifier:2112,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:1},{minX:0,minY:0,maxX:3,maxY:3,minZ:2,maxZ:2},{minX:0,minY:0,maxX:7,maxY:7,minZ:3,maxZ:3},{minX:0,minY:0,maxX:15,maxY:15,minZ:4,maxZ:4},{minX:0,minY:0,maxX:31,maxY:31,minZ:5,maxZ:5},{minX:0,minY:0,maxX:63,maxY:63,minZ:6,maxZ:6},{minX:0,minY:0,maxX:127,maxY:127,minZ:7,maxZ:7},{minX:0,minY:0,maxX:255,maxY:255,minZ:8,maxZ:8},{minX:0,minY:0,maxX:511,maxY:511,minZ:9,maxZ:9},{minX:0,minY:0,maxX:1023,maxY:1023,minZ:10,maxZ:10},{minX:0,minY:0,maxX:2047,maxY:2047,minZ:11,maxZ:11},{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12},{minX:0,minY:0,maxX:8191,maxY:8191,minZ:13,maxZ:13},{minX:0,minY:0,maxX:16383,maxY:16383,minZ:14,maxZ:14},{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15}],genericTile:[],supportedLanguagesVersion:1}],scale:0,size:2,supportedLanguage:[{identifier:1,language:["ar","ca","cs","da","de","el","en","en-AU","en-GB","es","es-MX","fi","fr","fr-CA","he","hi","hr","hu","id","it","ja","ko","ms","nb","nl","pl","pt","pt-PT","ro","ru","sk","sv","th","tr","uk","vi","zh-Hans","zh-Hant","zh-HK"]}],countryRegionWhitelist:[{countryCode:"AE",region:"AE"},{countryCode:"AE",region:"SA"},{countryCode:"IN",region:"IN"},{countryCode:"JP",region:"JP"},{countryCode:"KR",region:"KR"},{countryCode:"MA",region:"MA"},{countryCode:"RU",region:"RU"},{countryCode:"SA",region:"AE"},{countryCode:"SA",region:"SA"}],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe19-cn-ssl.ls.apple.com/tiles",supportsMultipathTCP:!1},{style:22,validVersion:[{identifier:2112,availableTiles:[{minX:0,minY:0,maxX:127,maxY:127,minZ:7,maxZ:15}],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe19-cn-ssl.ls.apple.com/tiles",supportsMultipathTCP:!1},{style:30,validVersion:[{identifier:146,availableTiles:[{minX:0,minY:0,maxX:1023,maxY:1023,minZ:10,maxZ:10},{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15},{minX:0,minY:0,maxX:262143,maxY:262143,minZ:18,maxZ:18}],genericTile:[],supportedLanguagesVersion:1}],scale:0,size:2,supportedLanguage:[{identifier:1,language:["ar","ca","cs","da","de","el","en","en-AU","en-GB","es","es-MX","fi","fr","fr-CA","he","hi","hr","hu","id","it","ja","ko","ms","nb","nl","pl","pt","pt-PT","ro","ru","sk","sv","th","tr","uk","vi","zh-Hans","zh-Hant","zh-HK"]}],countryRegionWhitelist:[],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe19-cn-ssl.ls.apple.com/tiles",supportsMultipathTCP:!1},{style:37,validVersion:[{identifier:1904,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:1},{minX:0,minY:0,maxX:7,maxY:7,minZ:3,maxZ:3},{minX:0,minY:0,maxX:31,maxY:31,minZ:5,maxZ:5},{minX:0,minY:0,maxX:127,maxY:127,minZ:7,maxZ:7},{minX:0,minY:0,maxX:511,maxY:511,minZ:9,maxZ:9},{minX:0,minY:0,maxX:1023,maxY:1023,minZ:10,maxZ:10},{minX:0,minY:0,maxX:2047,maxY:2047,minZ:11,maxZ:11},{minX:0,minY:0,maxX:8191,maxY:8191,minZ:13,maxZ:13},{minX:0,minY:0,maxX:16383,maxY:16383,minZ:14,maxZ:14},{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15}],genericTile:[],supportedLanguagesVersion:1}],scale:0,size:2,supportedLanguage:[{identifier:1,language:["ar","ca","cs","da","de","el","en","en-AU","en-GB","es","es-MX","fi","fr","fr-CA","he","hi","hr","hu","id","it","ja","ko","ms","nb","nl","pl","pt","pt-PT","ro","ru","sk","sv","th","tr","uk","vi","zh-Hans","zh-Hant","zh-HK"]}],countryRegionWhitelist:[],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe19-cn-ssl.ls.apple.com/tiles?flags=2",supportsMultipathTCP:!1},{style:47,validVersion:[{identifier:1904,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:1},{minX:0,minY:0,maxX:7,maxY:7,minZ:3,maxZ:3},{minX:0,minY:0,maxX:31,maxY:31,minZ:5,maxZ:5},{minX:0,minY:0,maxX:127,maxY:127,minZ:7,maxZ:7},{minX:0,minY:0,maxX:511,maxY:511,minZ:9,maxZ:9},{minX:0,minY:0,maxX:1023,maxY:1023,minZ:10,maxZ:10},{minX:0,minY:0,maxX:2047,maxY:2047,minZ:11,maxZ:11},{minX:0,minY:0,maxX:8191,maxY:8191,minZ:13,maxZ:13},{minX:0,minY:0,maxX:16383,maxY:16383,minZ:14,maxZ:14},{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15}],genericTile:[],supportedLanguagesVersion:1}],scale:0,size:2,supportedLanguage:[{identifier:1,language:["ar","ca","cs","da","de","el","en","en-AU","en-GB","es","es-MX","fi","fr","fr-CA","he","hi","hr","hu","id","it","ja","ko","ms","nb","nl","pl","pt","pt-PT","ro","ru","sk","sv","th","tr","uk","vi","zh-Hans","zh-Hant","zh-HK"]}],countryRegionWhitelist:[],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe19-cn-ssl.ls.apple.com/tiles",supportsMultipathTCP:!1},{style:48,validVersion:[{identifier:1904,availableTiles:[{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12},{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15}],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe19-cn-ssl.ls.apple.com/tiles",supportsMultipathTCP:!1},{style:53,validVersion:[{identifier:2112,availableTiles:[{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15}],genericTile:[],supportedLanguagesVersion:1}],scale:0,size:2,supportedLanguage:[{identifier:1,language:["ar","ca","cs","da","de","el","en","en-AU","en-GB","es","es-MX","fi","fr","fr-CA","he","hi","hr","hu","id","it","ja","ko","ms","nb","nl","pl","pt","pt-PT","ro","ru","sk","sv","th","tr","uk","vi","zh-Hans","zh-Hant","zh-HK"]}],countryRegionWhitelist:[],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe19-cn-ssl.ls.apple.com/tiles",supportsMultipathTCP:!1},{style:54,validVersion:[{identifier:2112,availableTiles:[{minX:0,minY:0,maxX:8191,maxY:8191,minZ:13,maxZ:13},{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15}],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe19-cn-ssl.ls.apple.com/tiles",supportsMultipathTCP:!1},{style:56,validVersion:[{identifier:16,availableTiles:[{minX:0,minY:0,maxX:131071,maxY:131071,minZ:17,maxZ:17}],genericTile:[],supportedLanguagesVersion:1}],scale:0,size:2,supportedLanguage:[{identifier:1,language:["ar","ca","cs","da","de","el","en","en-AU","en-GB","es","es-MX","fi","fr","fr-CA","he","hi","hr","hu","id","it","ja","ko","ms","nb","nl","pl","pt","pt-PT","ro","ru","sk","sv","th","tr","uk","vi","zh-Hans","zh-Hant","zh-HK"]}],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-cn-ssl.ls.apple.com/tiles",supportsMultipathTCP:!1},{style:57,validVersion:[{identifier:0,availableTiles:[{minX:0,minY:0,maxX:131071,maxY:131071,minZ:17,maxZ:17}],genericTile:[],timeToLiveSeconds:3600}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gsp76-cn-ssl.ls.apple.com/api/tile",supportsMultipathTCP:!1},{style:58,validVersion:[{identifier:137,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:1},{minX:0,minY:0,maxX:3,maxY:3,minZ:2,maxZ:2},{minX:0,minY:0,maxX:7,maxY:7,minZ:3,maxZ:3},{minX:0,minY:0,maxX:15,maxY:15,minZ:4,maxZ:4},{minX:0,minY:0,maxX:31,maxY:31,minZ:5,maxZ:5},{minX:0,minY:0,maxX:63,maxY:63,minZ:6,maxZ:6},{minX:0,minY:0,maxX:127,maxY:127,minZ:7,maxZ:7},{minX:0,minY:0,maxX:255,maxY:255,minZ:8,maxZ:8},{minX:0,minY:0,maxX:511,maxY:511,minZ:9,maxZ:9},{minX:0,minY:0,maxX:1023,maxY:1023,minZ:10,maxZ:10},{minX:0,minY:0,maxX:2047,maxY:2047,minZ:11,maxZ:11},{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12},{minX:0,minY:0,maxX:8191,maxY:8191,minZ:13,maxZ:13},{minX:0,minY:0,maxX:16383,maxY:16383,minZ:14,maxZ:14},{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15},{minX:0,minY:0,maxX:65535,maxY:65535,minZ:16,maxZ:16},{minX:0,minY:0,maxX:131071,maxY:131071,minZ:17,maxZ:17}],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-cn-ssl.ls.apple.com/tiles",supportsMultipathTCP:!1},{style:59,validVersion:[{identifier:80,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:1}],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-cn-ssl.ls.apple.com/asset/v3/model",supportsMultipathTCP:!1},{style:60,validVersion:[{identifier:30,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:1},{minX:0,minY:0,maxX:3,maxY:3,minZ:2,maxZ:2},{minX:0,minY:0,maxX:7,maxY:7,minZ:3,maxZ:3},{minX:0,minY:0,maxX:15,maxY:15,minZ:4,maxZ:4},{minX:0,minY:0,maxX:31,maxY:31,minZ:5,maxZ:5},{minX:0,minY:0,maxX:63,maxY:63,minZ:6,maxZ:6},{minX:0,minY:0,maxX:127,maxY:127,minZ:7,maxZ:7},{minX:0,minY:0,maxX:255,maxY:255,minZ:8,maxZ:8},{minX:0,minY:0,maxX:511,maxY:511,minZ:9,maxZ:9}],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-cn-ssl.ls.apple.com/asset/v3/material",supportsMultipathTCP:!1},{style:61,validVersion:[{identifier:30,availableTiles:[{minX:0,minY:0,maxX:127,maxY:127,minZ:7,maxZ:7}],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-cn-ssl.ls.apple.com/tiles",supportsMultipathTCP:!1},{style:64,validVersion:[{identifier:16,availableTiles:[{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12}],genericTile:[],supportedLanguagesVersion:1}],scale:0,size:2,supportedLanguage:[{identifier:1,language:["ar","ca","cs","da","de","el","en","en-AU","en-GB","es","es-MX","fi","fr","fr-CA","he","hi","hr","hu","id","it","ja","ko","ms","nb","nl","pl","pt","pt-PT","ro","ru","sk","sv","th","tr","uk","vi","zh-Hans","zh-Hant","zh-HK"]}],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-cn-ssl.ls.apple.com/tiles",supportsMultipathTCP:!1},{style:65,validVersion:[{identifier:2,availableTiles:[{minX:0,minY:0,maxX:255,maxY:255,minZ:8,maxZ:8}],genericTile:[],timeToLiveSeconds:3600}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe79-cn-ssl.ls.apple.com/65/v1",supportsMultipathTCP:!1},{style:66,validVersion:[{identifier:2112,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:1},{minX:0,minY:0,maxX:3,maxY:3,minZ:2,maxZ:2},{minX:0,minY:0,maxX:7,maxY:7,minZ:3,maxZ:3},{minX:0,minY:0,maxX:15,maxY:15,minZ:4,maxZ:4},{minX:0,minY:0,maxX:31,maxY:31,minZ:5,maxZ:5},{minX:0,minY:0,maxX:63,maxY:63,minZ:6,maxZ:6},{minX:0,minY:0,maxX:127,maxY:127,minZ:7,maxZ:7},{minX:0,minY:0,maxX:255,maxY:255,minZ:8,maxZ:8},{minX:0,minY:0,maxX:511,maxY:511,minZ:9,maxZ:9},{minX:0,minY:0,maxX:1023,maxY:1023,minZ:10,maxZ:10},{minX:0,minY:0,maxX:2047,maxY:2047,minZ:11,maxZ:11},{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12},{minX:0,minY:0,maxX:8191,maxY:8191,minZ:13,maxZ:13},{minX:0,minY:0,maxX:16383,maxY:16383,minZ:14,maxZ:14},{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15}],genericTile:[],supportedLanguagesVersion:1}],scale:0,size:2,supportedLanguage:[{identifier:1,language:["ar","ca","cs","da","de","el","en","en-AU","en-GB","es","es-MX","fi","fr","fr-CA","he","hi","hr","hu","id","it","ja","ko","ms","nb","nl","pl","pt","pt-PT","ro","ru","sk","sv","th","tr","uk","vi","zh-Hans","zh-Hant","zh-HK"]}],countryRegionWhitelist:[{countryCode:"AE",region:"AE"},{countryCode:"AE",region:"SA"},{countryCode:"IN",region:"IN"},{countryCode:"JP",region:"JP"},{countryCode:"KR",region:"KR"},{countryCode:"MA",region:"MA"},{countryCode:"RU",region:"RU"},{countryCode:"SA",region:"AE"},{countryCode:"SA",region:"SA"}],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-cn-ssl.ls.apple.com/tiles",supportsMultipathTCP:!1},{style:67,validVersion:[{identifier:2112,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:1},{minX:0,minY:0,maxX:3,maxY:3,minZ:2,maxZ:2},{minX:0,minY:0,maxX:7,maxY:7,minZ:3,maxZ:3},{minX:0,minY:0,maxX:15,maxY:15,minZ:4,maxZ:4},{minX:0,minY:0,maxX:31,maxY:31,minZ:5,maxZ:5},{minX:0,minY:0,maxX:63,maxY:63,minZ:6,maxZ:6},{minX:0,minY:0,maxX:127,maxY:127,minZ:7,maxZ:7},{minX:0,minY:0,maxX:255,maxY:255,minZ:8,maxZ:8},{minX:0,minY:0,maxX:1023,maxY:1023,minZ:10,maxZ:10},{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12},{minX:0,minY:0,maxX:16383,maxY:16383,minZ:14,maxZ:14}],genericTile:[],supportedLanguagesVersion:1}],scale:0,size:2,supportedLanguage:[{identifier:1,language:["ar","ca","cs","da","de","el","en","en-AU","en-GB","es","es-MX","fi","fr","fr-CA","he","hi","hr","hu","id","it","ja","ko","ms","nb","nl","pl","pt","pt-PT","ro","ru","sk","sv","th","tr","uk","vi","zh-Hans","zh-Hant","zh-HK"]}],countryRegionWhitelist:[{countryCode:"AE",region:"AE"},{countryCode:"AE",region:"SA"},{countryCode:"IN",region:"IN"},{countryCode:"JP",region:"JP"},{countryCode:"KR",region:"KR"},{countryCode:"MA",region:"MA"},{countryCode:"RU",region:"RU"},{countryCode:"SA",region:"AE"},{countryCode:"SA",region:"SA"}],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-cn-ssl.ls.apple.com/tiles",supportsMultipathTCP:!1},{style:68,validVersion:[{identifier:2092,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:1},{minX:0,minY:0,maxX:3,maxY:3,minZ:2,maxZ:2},{minX:0,minY:0,maxX:15,maxY:15,minZ:4,maxZ:4},{minX:0,minY:0,maxX:63,maxY:63,minZ:6,maxZ:6},{minX:0,minY:0,maxX:127,maxY:127,minZ:7,maxZ:7},{minX:0,minY:0,maxX:255,maxY:255,minZ:8,maxZ:8},{minX:0,minY:0,maxX:511,maxY:511,minZ:9,maxZ:9},{minX:0,minY:0,maxX:1023,maxY:1023,minZ:10,maxZ:10},{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12},{minX:0,minY:0,maxX:8191,maxY:8191,minZ:13,maxZ:13},{minX:0,minY:0,maxX:16383,maxY:16383,minZ:14,maxZ:14},{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15},{minX:0,minY:0,maxX:65535,maxY:65535,minZ:16,maxZ:16},{minX:0,minY:0,maxX:131071,maxY:131071,minZ:17,maxZ:17}],genericTile:[],supportedLanguagesVersion:1}],scale:0,size:2,supportedLanguage:[{identifier:1,language:["ar","ca","cs","da","de","el","en","en-AU","en-GB","es","es-MX","fi","fr","fr-CA","he","hi","hr","hu","id","it","ja","ko","ms","nb","nl","pl","pt","pt-PT","ro","ru","sk","sv","th","tr","uk","vi","zh-Hans","zh-Hant","zh-HK"]}],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-cn-ssl.ls.apple.com/tiles",supportsMultipathTCP:!1},{style:69,validVersion:[{identifier:21,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:1},{minX:0,minY:0,maxX:7,maxY:7,minZ:3,maxZ:3},{minX:0,minY:0,maxX:63,maxY:63,minZ:6,maxZ:6},{minX:0,minY:0,maxX:511,maxY:511,minZ:9,maxZ:9},{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12},{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15}],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe19-cn-ssl.ls.apple.com/tiles",supportsMultipathTCP:!1},{style:72,validVersion:[{identifier:2,availableTiles:[{minX:0,minY:0,maxX:8191,maxY:8191,minZ:13,maxZ:13}],genericTile:[],timeToLiveSeconds:3600}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],supportsMultipathTCP:!1},{style:73,validVersion:[{identifier:470,availableTiles:[{minX:0,minY:0,maxX:8191,maxY:8191,minZ:13,maxZ:13},{minX:0,minY:0,maxX:16383,maxY:16383,minZ:14,maxZ:14},{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15}],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-cn-ssl.ls.apple.com/tiles",supportsMultipathTCP:!1},{style:76,validVersion:[{identifier:0,availableTiles:[{minX:0,minY:0,maxX:8191,maxY:8191,minZ:13,maxZ:13},{minX:0,minY:0,maxX:524287,maxY:524287,minZ:19,maxZ:19}],genericTile:[],timeToLiveSeconds:86400}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe79-cn-ssl.ls.apple.com/sis/v1",supportsMultipathTCP:!1},{style:79,validVersion:[{identifier:29,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:1},{minX:0,minY:0,maxX:3,maxY:3,minZ:2,maxZ:2},{minX:0,minY:0,maxX:7,maxY:7,minZ:3,maxZ:3},{minX:0,minY:0,maxX:15,maxY:15,minZ:4,maxZ:4},{minX:0,minY:0,maxX:31,maxY:31,minZ:5,maxZ:5},{minX:0,minY:0,maxX:63,maxY:63,minZ:6,maxZ:6},{minX:0,minY:0,maxX:127,maxY:127,minZ:7,maxZ:7},{minX:0,minY:0,maxX:255,maxY:255,minZ:8,maxZ:8},{minX:0,minY:0,maxX:511,maxY:511,minZ:9,maxZ:9}],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-cn-ssl.ls.apple.com/tiles",supportsMultipathTCP:!1},{style:84,validVersion:[{identifier:2092,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:1},{minX:0,minY:0,maxX:3,maxY:3,minZ:2,maxZ:2},{minX:0,minY:0,maxX:15,maxY:15,minZ:4,maxZ:4},{minX:0,minY:0,maxX:63,maxY:63,minZ:6,maxZ:6},{minX:0,minY:0,maxX:127,maxY:127,minZ:7,maxZ:7},{minX:0,minY:0,maxX:255,maxY:255,minZ:8,maxZ:8},{minX:0,minY:0,maxX:511,maxY:511,minZ:9,maxZ:9},{minX:0,minY:0,maxX:1023,maxY:1023,minZ:10,maxZ:10},{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12},{minX:0,minY:0,maxX:8191,maxY:8191,minZ:13,maxZ:13},{minX:0,minY:0,maxX:16383,maxY:16383,minZ:14,maxZ:14},{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15},{minX:0,minY:0,maxX:65535,maxY:65535,minZ:16,maxZ:16},{minX:0,minY:0,maxX:131071,maxY:131071,minZ:17,maxZ:17}],genericTile:[],timeToLiveSeconds:1800,supportedLanguagesVersion:1}],scale:0,size:2,supportedLanguage:[{identifier:1,language:["ar","ca","cs","da","de","el","en","en-AU","en-GB","es","es-MX","fi","fr","fr-CA","he","hi","hr","hu","id","it","ja","ko","ms","nb","nl","pl","pt","pt-PT","ro","ru","sk","sv","th","tr","uk","vi","zh-Hans","zh-Hant","zh-HK"]}],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-2-cn-ssl.ls.apple.com/poi_update",supportsMultipathTCP:!1}],attribution:[{name:"AutoNavi",url:"https://gspe21-ssl.ls.apple.com/html/attribution-cn2-66.html",resource:[{resourceType:6,filename:"autonavi-4.png",checksum:{0:61,1:130,2:126,3:203,4:170,5:234,6:91,7:182,8:191,9:120,10:72,11:19,12:46,13:58,14:235,15:55,16:221,17:53,18:252,19:219},region:[],filter:[],validationMethod:0,updateMethod:0},{resourceType:6,filename:"autonavi-4@2x.png",checksum:{0:101,1:191,2:219,3:234,4:178,5:237,6:6,7:231,8:236,9:110,10:3,11:82,12:194,13:129,14:29,15:221,16:225,17:55,18:26,19:203},region:[],filter:[],validationMethod:0,updateMethod:0},{resourceType:6,filename:"autonavi-4@2x.png",checksum:{0:101,1:191,2:219,3:234,4:178,5:237,6:6,7:231,8:236,9:110,10:3,11:82,12:194,13:129,14:29,15:221,16:225,17:55,18:26,19:203},region:[],filter:[],validationMethod:0,updateMethod:0},{resourceType:5,filename:"autonavi-logo-mask-1.png",checksum:{0:247,1:152,2:81,3:90,4:135,5:206,6:171,7:138,8:151,9:37,10:167,11:77,12:112,13:223,14:89,15:164,16:242,17:201,18:164,19:74},region:[],filter:[],validationMethod:0,updateMethod:0},{resourceType:5,filename:"autonavi-logo-mask-1@2x.png",checksum:{0:54,1:203,2:95,3:5,4:82,5:108,6:189,7:170,8:124,9:255,10:39,11:153,12:245,13:47,14:224,15:93,16:202,17:181,18:11,19:127},region:[],filter:[],validationMethod:0,updateMethod:0},{resourceType:5,filename:"autonavi-logo-mask-1@3x.png",checksum:{0:131,1:225,2:158,3:241,4:69,5:218,6:172,7:162,8:166,9:241,10:48,11:174,12:31,13:104,14:225,15:155,16:97,17:143,18:15,19:99},region:[],filter:[],validationMethod:0,updateMethod:0}],region:[],linkDisplayStringIndex:0},{name:"© GeoTechnologies, Inc.",url:"https://gspe21-ssl.ls.apple.com/html/attribution-cn2-66.html",resource:[],region:[{minX:218,minY:102,maxX:225,maxY:104,minZ:8,maxZ:21},{minX:221,minY:98,maxX:228,maxY:101,minZ:8,maxZ:21},{minX:226,minY:91,maxX:231,maxY:97,minZ:8,maxZ:21}],linkDisplayStringIndex:0}],urlInfoSet:[{alternateResourcesURL:[{url:"https://cdn.apple-mapkit.com/rap",supportsMultipathTCP:!1},{url:"https://limit-rule.is.autonavi.com/lpr/rules/download",supportsMultipathTCP:!1}],resourcesURL:{url:"https://gspe21-ssl.ls.apple.com/",supportsMultipathTCP:!1},searchAttributionManifestURL:{url:"https://gspe21-ssl.ls.apple.com/config/search-attribution-1263",supportsMultipathTCP:!1},directionsURL:{url:"https://direction2.is.autonavi.com/direction",supportsMultipathTCP:!1},etaURL:{url:"https://direction2.is.autonavi.com/direction",supportsMultipathTCP:!1},batchReverseGeocoderURL:{url:"https://batch-rgeo.is.autonavi.com/batchRGeo",supportsMultipathTCP:!1},simpleETAURL:{url:"https://direction2.is.autonavi.com/direction",supportsMultipathTCP:!1},polyLocationShiftURL:{url:"https://shift.is.autonavi.com/localshift",supportsMultipathTCP:!1},problemSubmissionURL:{url:"https://rap.is.autonavi.com/rap",supportsMultipathTCP:!1},problemStatusURL:{url:"https://rap.is.autonavi.com/rapstatus",supportsMultipathTCP:!1},reverseGeocoderVersionsURL:{url:"https://gspe21-ssl.ls.apple.com/config/revgeo-version-11.plist",supportsMultipathTCP:!1},problemCategoriesURL:{url:"https://gspe21-ssl.ls.apple.com/config/com.apple.GEO.BusinessLocalizedCategories-424.plist",supportsMultipathTCP:!1},announcementsURL:{url:"https://gspe35-ssl.ls.apple.com/config/announcements?environment=prod-cn",supportsMultipathTCP:!1},dispatcherURL:{url:"https://dispatcher.is.autonavi.com/dispatcher",supportsMultipathTCP:!1},abExperimentURL:{url:"https://gsp-ssl.ls.apple.com/cn/ab.arpc",supportsMultipathTCP:!1},logMessageUsageURL:{url:"https://gsp64-ssl.ls.apple.com/a/v2/use",supportsMultipathTCP:!1},spatialLookupURL:{url:"https://spatialsearch.is.autonavi.com/spatialsearch",supportsMultipathTCP:!1},realtimeTrafficProbeURL:{url:"https://gsp9-ssl.apple.com/hvr/v2/rtloc",supportsMultipathTCP:!1},batchTrafficProbeURL:{url:"https://gsp10-ssl.ls.apple.com/hvr/v2/loc",supportsMultipathTCP:!1},logMessageUsageV3URL:{url:"https://gsp64-ssl.ls.apple.com/hvr/v3/use",supportsMultipathTCP:!1},backgroundDispatcherURL:{url:"https://dispatcher.is.autonavi.com/dispatcher",supportsMultipathTCP:!1},backgroundRevGeoURL:{url:"https://dispatcher.is.autonavi.com/dispatcher",supportsMultipathTCP:!1},wifiConnectionQualityProbeURL:{url:"https://gsp10-ssl-cn.ls.apple.com/hvr/wcq",supportsMultipathTCP:!1},wifiQualityURL:{url:"https://gsp85-cn-ssl.ls.apple.com/wifi_request",supportsMultipathTCP:!1},feedbackSubmissionURL:{url:"https://rap.is.autonavi.com/rap",supportsMultipathTCP:!1},feedbackLookupURL:{url:"https://rap.is.autonavi.com/lookup",supportsMultipathTCP:!1},junctionImageServiceURL:{url:"https://direction2.is.autonavi.com/direction",supportsMultipathTCP:!1},analyticsCohortSessionURL:{url:"https://gsp64-ssl.ls.apple.com/hvr/v3/use",supportsMultipathTCP:!1},analyticsLongSessionURL:{url:"https://gsp64-ssl.ls.apple.com/hvr/v3/use",supportsMultipathTCP:!1},analyticsShortSessionURL:{url:"https://gsp64-ssl.ls.apple.com/hvr/v3/use",supportsMultipathTCP:!1},analyticsSessionlessURL:{url:"https://gsp64-ssl.ls.apple.com/hvr/v3/use",supportsMultipathTCP:!1},webModuleBaseURL:{url:"https://placecard-server-wm.is.autonavi.com",supportsMultipathTCP:!1},wifiQualityTileURL:{url:"https://gspe85-cn-ssl.ls.apple.com/wifi_request_tile",supportsMultipathTCP:!1},batchReverseGeocoderPlaceRequestURL:{url:"https://dispatcher.is.autonavi.com/dispatcher",supportsMultipathTCP:!1},poiBusynessActivityCollectionURL:{url:"https://gsp53-ssl.ls.apple.com/hvr/rt_poi_activity",supportsMultipathTCP:!1},rapWebBundleURL:{url:"https://cdn.apple-mapkit.com/rap",supportsMultipathTCP:!1},offlineDataBatchListURL:{url:"https://ods.is.autonavi.com/api/batchesForRegion",supportsMultipathTCP:!1},offlineDataSizeURL:{url:"https://ods.is.autonavi.com/api/sizeForRegion",supportsMultipathTCP:!1},offlineDataDownloadBaseURL:{url:"https://gspe121-cn-ssl.ls.apple.com",supportsMultipathTCP:!1}}],muninBucket:[{bucketID:2,bucketURL:"https://gspe72-cn-ssl.ls.apple.com/mnn_us"},{bucketID:6,bucketURL:"https://gspe72-cn-ssl.ls.apple.com/mnn_us"}]},XX:{tileSet:[{style:1,validVersion:[{identifier:16034178,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:1},{minX:0,minY:0,maxX:3,maxY:3,minZ:2,maxZ:2},{minX:0,minY:0,maxX:7,maxY:7,minZ:3,maxZ:3},{minX:0,minY:0,maxX:15,maxY:15,minZ:4,maxZ:4},{minX:0,minY:0,maxX:31,maxY:31,minZ:5,maxZ:5},{minX:0,minY:0,maxX:63,maxY:63,minZ:6,maxZ:6},{minX:0,minY:0,maxX:127,maxY:127,minZ:7,maxZ:7},{minX:0,minY:0,maxX:255,maxY:255,minZ:8,maxZ:8},{minX:0,minY:0,maxX:1023,maxY:1023,minZ:10,maxZ:10},{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12},{minX:0,minY:0,maxX:16383,maxY:16383,minZ:14,maxZ:14}],genericTile:[],supportedLanguagesVersion:1}],scale:0,size:2,supportedLanguage:[{identifier:1,language:["ar","ca","cs","da","de","el","en","en-AU","en-GB","es","es-MX","es-US","fi","fr","fr-CA","he","hi","hr","hu","id","it","ja","ko","ms","nb","nl","pl","pt","pt-PT","ro","ru","sk","sv","th","tr","uk","vi","zh-Hans","zh-Hant","zh-HK"]}],countryRegionWhitelist:[{countryCode:"AE",region:"AE"},{countryCode:"AE",region:"SA"},{countryCode:"IN",region:""},{countryCode:"JP",region:"JP"},{countryCode:"KR",region:"KR"},{countryCode:"MA",region:"MA"},{countryCode:"RU",region:"RU"},{countryCode:"SA",region:"AE"},{countryCode:"SA",region:"SA"},{countryCode:"VN",region:"VN"}],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe19-ssl.ls.apple.com/tile.vf?flags=8",dataSet:0,supportsMultipathTCP:!1},{style:1,validVersion:[{identifier:16030619,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:1},{minX:0,minY:0,maxX:3,maxY:3,minZ:2,maxZ:2},{minX:0,minY:0,maxX:7,maxY:7,minZ:3,maxZ:3},{minX:0,minY:0,maxX:15,maxY:15,minZ:4,maxZ:4},{minX:0,minY:0,maxX:31,maxY:31,minZ:5,maxZ:5},{minX:0,minY:0,maxX:63,maxY:63,minZ:6,maxZ:6},{minX:0,minY:0,maxX:127,maxY:127,minZ:7,maxZ:7},{minX:0,minY:0,maxX:255,maxY:255,minZ:8,maxZ:8},{minX:0,minY:0,maxX:1023,maxY:1023,minZ:10,maxZ:10},{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12},{minX:0,minY:0,maxX:16383,maxY:16383,minZ:14,maxZ:14}],genericTile:[],supportedLanguagesVersion:1}],scale:0,size:2,supportedLanguage:[{identifier:1,language:["ar","ca","cs","da","de","el","en","en-AU","en-GB","es","es-MX","es-US","fi","fr","fr-CA","he","hi","hr","hu","id","it","ja","ko","ms","nb","nl","pl","pt","pt-PT","ro","ru","sk","sv","th","tr","uk","vi","zh-Hans","zh-Hant","zh-HK"]}],countryRegionWhitelist:[{countryCode:"AE",region:"AE"},{countryCode:"AE",region:"SA"},{countryCode:"IN",region:""},{countryCode:"JP",region:"JP"},{countryCode:"KR",region:"KR"},{countryCode:"MA",region:"MA"},{countryCode:"RU",region:"RU"},{countryCode:"SA",region:"AE"},{countryCode:"SA",region:"SA"},{countryCode:"VN",region:"VN"}],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe19-kittyhawk-ssl.ls.apple.com/tile.vf?flags=8",dataSet:1,supportsMultipathTCP:!1},{style:7,validVersion:[{identifier:9711,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:22}],genericTile:[{tileType:2,textureIndex:0,resourceIndex:1971}]}],scale:1,size:1,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe11-ssl.ls.apple.com/tile",supportsMultipathTCP:!1},{style:7,validVersion:[{identifier:9711,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:22}],genericTile:[{tileType:2,textureIndex:0,resourceIndex:1971}]}],scale:2,size:1,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe11-ssl.ls.apple.com/tile",supportsMultipathTCP:!1},{style:11,validVersion:[{identifier:16034178,availableTiles:[{minX:0,minY:0,maxX:8191,maxY:8191,minZ:13,maxZ:13},{minX:0,minY:0,maxX:16383,maxY:16383,minZ:14,maxZ:14},{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15}],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe19-ssl.ls.apple.com/tile.vf?flags=1",dataSet:0,supportsMultipathTCP:!1},{style:11,validVersion:[{identifier:16030619,availableTiles:[{minX:0,minY:0,maxX:8191,maxY:8191,minZ:13,maxZ:13},{minX:0,minY:0,maxX:16383,maxY:16383,minZ:14,maxZ:14},{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15}],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe19-kittyhawk-ssl.ls.apple.com/tile.vf?flags=1",dataSet:1,supportsMultipathTCP:!1},{style:12,validVersion:[{identifier:16034178,availableTiles:[{minX:0,minY:0,maxX:255,maxY:255,minZ:8,maxZ:8},{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12}],genericTile:[],timeToLiveSeconds:120}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe12-ssl.ls.apple.com/traffic",dataSet:0,supportsMultipathTCP:!1},{style:12,validVersion:[{identifier:16030619,availableTiles:[{minX:0,minY:0,maxX:255,maxY:255,minZ:8,maxZ:8},{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12}],genericTile:[],timeToLiveSeconds:120}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe12-kittyhawk-ssl.ls.apple.com/traffic",dataSet:1,supportsMultipathTCP:!1},{style:13,validVersion:[{identifier:16034178,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:1},{minX:0,minY:0,maxX:3,maxY:3,minZ:2,maxZ:2},{minX:0,minY:0,maxX:15,maxY:15,minZ:4,maxZ:4},{minX:0,minY:0,maxX:63,maxY:63,minZ:6,maxZ:6},{minX:0,minY:0,maxX:127,maxY:127,minZ:7,maxZ:7},{minX:0,minY:0,maxX:255,maxY:255,minZ:8,maxZ:8},{minX:0,minY:0,maxX:511,maxY:511,minZ:9,maxZ:9},{minX:0,minY:0,maxX:1023,maxY:1023,minZ:10,maxZ:10},{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12},{minX:0,minY:0,maxX:8191,maxY:8191,minZ:13,maxZ:13},{minX:0,minY:0,maxX:16383,maxY:16383,minZ:14,maxZ:14},{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15},{minX:0,minY:0,maxX:65535,maxY:65535,minZ:16,maxZ:16},{minX:0,minY:0,maxX:131071,maxY:131071,minZ:17,maxZ:17}],genericTile:[],supportedLanguagesVersion:1}],scale:0,size:2,supportedLanguage:[{identifier:1,language:["ar","ca","cs","da","de","el","en","en-AU","en-GB","es","es-MX","es-US","fi","fr","fr-CA","he","hi","hr","hu","id","it","ja","ko","ms","nb","nl","pl","pt","pt-PT","ro","ru","sk","sv","th","tr","uk","vi","zh-Hans","zh-Hant","zh-HK"]}],countryRegionWhitelist:[],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe19-ssl.ls.apple.com/tile.vf?flags=2",dataSet:0,supportsMultipathTCP:!1},{style:13,validVersion:[{identifier:16030619,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:1},{minX:0,minY:0,maxX:3,maxY:3,minZ:2,maxZ:2},{minX:0,minY:0,maxX:15,maxY:15,minZ:4,maxZ:4},{minX:0,minY:0,maxX:63,maxY:63,minZ:6,maxZ:6},{minX:0,minY:0,maxX:127,maxY:127,minZ:7,maxZ:7},{minX:0,minY:0,maxX:255,maxY:255,minZ:8,maxZ:8},{minX:0,minY:0,maxX:511,maxY:511,minZ:9,maxZ:9},{minX:0,minY:0,maxX:1023,maxY:1023,minZ:10,maxZ:10},{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12},{minX:0,minY:0,maxX:8191,maxY:8191,minZ:13,maxZ:13},{minX:0,minY:0,maxX:16383,maxY:16383,minZ:14,maxZ:14},{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15},{minX:0,minY:0,maxX:65535,maxY:65535,minZ:16,maxZ:16},{minX:0,minY:0,maxX:131071,maxY:131071,minZ:17,maxZ:17}],genericTile:[],supportedLanguagesVersion:1}],scale:0,size:2,supportedLanguage:[{identifier:1,language:["ar","ca","cs","da","de","el","en","en-AU","en-GB","es","es-MX","es-US","fi","fr","fr-CA","he","hi","hr","hu","id","it","ja","ko","ms","nb","nl","pl","pt","pt-PT","ro","ru","sk","sv","th","tr","uk","vi","zh-Hans","zh-Hant","zh-HK"]}],countryRegionWhitelist:[],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe19-kittyhawk-ssl.ls.apple.com/tile.vf?flags=2",dataSet:1,supportsMultipathTCP:!1},{style:14,validVersion:[{identifier:1,availableTiles:[],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe11-ssl.ls.apple.com/tile",supportsMultipathTCP:!1},{style:15,validVersion:[{identifier:1,availableTiles:[],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe11-ssl.ls.apple.com/tile",supportsMultipathTCP:!1},{style:16,validVersion:[{identifier:1,availableTiles:[],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe11-ssl.ls.apple.com/tile",supportsMultipathTCP:!1},{style:17,validVersion:[{identifier:27,availableTiles:[{minX:0,minY:0,maxX:127,maxY:127,minZ:7,maxZ:7},{minX:0,minY:0,maxX:2047,maxY:2047,minZ:11,maxZ:11},{minX:408,minY:2760,maxX:2583,maxY:3659,minZ:13,maxZ:13},{minX:3848,minY:2332,maxX:4535,maxY:3235,minZ:13,maxZ:13}],genericTile:[]}],scale:1,size:1,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe11-ssl.ls.apple.com/tile",supportsMultipathTCP:!1},{style:18,validVersion:[{identifier:16034178,availableTiles:[{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15}],genericTile:[],supportedLanguagesVersion:1}],scale:0,size:2,supportedLanguage:[{identifier:1,language:["ar","ca","cs","da","de","el","en","en-AU","en-GB","es","es-MX","es-US","fi","fr","fr-CA","he","hi","hr","hu","id","it","ja","ko","ms","nb","nl","pl","pt","pt-PT","ro","ru","sk","sv","th","tr","uk","vi","zh-Hans","zh-Hant","zh-HK"]}],countryRegionWhitelist:[],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe19-ssl.ls.apple.com/tile.vf",dataSet:0,supportsMultipathTCP:!1},{style:18,validVersion:[{identifier:16030619,availableTiles:[{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15}],genericTile:[],supportedLanguagesVersion:1}],scale:0,size:2,supportedLanguage:[{identifier:1,language:["ar","ca","cs","da","de","el","en","en-AU","en-GB","es","es-MX","es-US","fi","fr","fr-CA","he","hi","hr","hu","id","it","ja","ko","ms","nb","nl","pl","pt","pt-PT","ro","ru","sk","sv","th","tr","uk","vi","zh-Hans","zh-Hant","zh-HK"]}],countryRegionWhitelist:[],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe19-kittyhawk-ssl.ls.apple.com/tile.vf",dataSet:1,supportsMultipathTCP:!1},{style:20,validVersion:[{identifier:16034178,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:1},{minX:0,minY:0,maxX:3,maxY:3,minZ:2,maxZ:2},{minX:0,minY:0,maxX:7,maxY:7,minZ:3,maxZ:3},{minX:0,minY:0,maxX:15,maxY:15,minZ:4,maxZ:4},{minX:0,minY:0,maxX:31,maxY:31,minZ:5,maxZ:5},{minX:0,minY:0,maxX:63,maxY:63,minZ:6,maxZ:6},{minX:0,minY:0,maxX:127,maxY:127,minZ:7,maxZ:7},{minX:0,minY:0,maxX:255,maxY:255,minZ:8,maxZ:8},{minX:0,minY:0,maxX:511,maxY:511,minZ:9,maxZ:9},{minX:0,minY:0,maxX:1023,maxY:1023,minZ:10,maxZ:10},{minX:0,minY:0,maxX:2047,maxY:2047,minZ:11,maxZ:11},{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12},{minX:0,minY:0,maxX:8191,maxY:8191,minZ:13,maxZ:13},{minX:0,minY:0,maxX:16383,maxY:16383,minZ:14,maxZ:14},{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15}],genericTile:[],supportedLanguagesVersion:1}],scale:0,size:2,supportedLanguage:[{identifier:1,language:["ar","ca","cs","da","de","el","en","en-AU","en-GB","es","es-MX","es-US","fi","fr","fr-CA","he","hi","hr","hu","id","it","ja","ko","ms","nb","nl","pl","pt","pt-PT","ro","ru","sk","sv","th","tr","uk","vi","zh-Hans","zh-Hant","zh-HK"]}],countryRegionWhitelist:[{countryCode:"AE",region:"AE"},{countryCode:"AE",region:"SA"},{countryCode:"IN",region:""},{countryCode:"JP",region:"JP"},{countryCode:"KR",region:"KR"},{countryCode:"MA",region:"MA"},{countryCode:"RU",region:"RU"},{countryCode:"SA",region:"AE"},{countryCode:"SA",region:"SA"},{countryCode:"VN",region:"VN"}],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe19-ssl.ls.apple.com/tile.vf",dataSet:0,supportsMultipathTCP:!1},{style:20,validVersion:[{identifier:16030619,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:1},{minX:0,minY:0,maxX:3,maxY:3,minZ:2,maxZ:2},{minX:0,minY:0,maxX:7,maxY:7,minZ:3,maxZ:3},{minX:0,minY:0,maxX:15,maxY:15,minZ:4,maxZ:4},{minX:0,minY:0,maxX:31,maxY:31,minZ:5,maxZ:5},{minX:0,minY:0,maxX:63,maxY:63,minZ:6,maxZ:6},{minX:0,minY:0,maxX:127,maxY:127,minZ:7,maxZ:7},{minX:0,minY:0,maxX:255,maxY:255,minZ:8,maxZ:8},{minX:0,minY:0,maxX:511,maxY:511,minZ:9,maxZ:9},{minX:0,minY:0,maxX:1023,maxY:1023,minZ:10,maxZ:10},{minX:0,minY:0,maxX:2047,maxY:2047,minZ:11,maxZ:11},{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12},{minX:0,minY:0,maxX:8191,maxY:8191,minZ:13,maxZ:13},{minX:0,minY:0,maxX:16383,maxY:16383,minZ:14,maxZ:14},{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15}],genericTile:[],supportedLanguagesVersion:1}],scale:0,size:2,supportedLanguage:[{identifier:1,language:["ar","ca","cs","da","de","el","en","en-AU","en-GB","es","es-MX","es-US","fi","fr","fr-CA","he","hi","hr","hu","id","it","ja","ko","ms","nb","nl","pl","pt","pt-PT","ro","ru","sk","sv","th","tr","uk","vi","zh-Hans","zh-Hant","zh-HK"]}],countryRegionWhitelist:[{countryCode:"AE",region:"AE"},{countryCode:"AE",region:"SA"},{countryCode:"IN",region:""},{countryCode:"JP",region:"JP"},{countryCode:"KR",region:"KR"},{countryCode:"MA",region:"MA"},{countryCode:"RU",region:"RU"},{countryCode:"SA",region:"AE"},{countryCode:"SA",region:"SA"},{countryCode:"VN",region:"VN"}],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe19-kittyhawk-ssl.ls.apple.com/tile.vf",dataSet:1,supportsMultipathTCP:!1},{style:22,validVersion:[{identifier:16034178,availableTiles:[{minX:0,minY:0,maxX:127,maxY:127,minZ:7,maxZ:7},{minX:0,minY:0,maxX:255,maxY:255,minZ:8,maxZ:8},{minX:0,minY:0,maxX:511,maxY:511,minZ:9,maxZ:9},{minX:0,minY:0,maxX:1023,maxY:1023,minZ:10,maxZ:10},{minX:0,minY:0,maxX:2047,maxY:2047,minZ:11,maxZ:11},{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12},{minX:0,minY:0,maxX:8191,maxY:8191,minZ:13,maxZ:13},{minX:0,minY:0,maxX:16383,maxY:16383,minZ:14,maxZ:14},{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15}],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe19-ssl.ls.apple.com/tile.vf",dataSet:0,supportsMultipathTCP:!1},{style:22,validVersion:[{identifier:16030619,availableTiles:[{minX:0,minY:0,maxX:127,maxY:127,minZ:7,maxZ:7},{minX:0,minY:0,maxX:255,maxY:255,minZ:8,maxZ:8},{minX:0,minY:0,maxX:511,maxY:511,minZ:9,maxZ:9},{minX:0,minY:0,maxX:1023,maxY:1023,minZ:10,maxZ:10},{minX:0,minY:0,maxX:2047,maxY:2047,minZ:11,maxZ:11},{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12},{minX:0,minY:0,maxX:8191,maxY:8191,minZ:13,maxZ:13},{minX:0,minY:0,maxX:16383,maxY:16383,minZ:14,maxZ:14},{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15}],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe19-kittyhawk-ssl.ls.apple.com/tile.vf",dataSet:1,supportsMultipathTCP:!1},{style:30,validVersion:[{identifier:16034178,availableTiles:[{minX:0,minY:0,maxX:1023,maxY:1023,minZ:10,maxZ:10},{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15},{minX:0,minY:0,maxX:262143,maxY:262143,minZ:18,maxZ:18}],genericTile:[],supportedLanguagesVersion:1}],scale:0,size:2,supportedLanguage:[{identifier:1,language:["ar","ca","cs","da","de","el","en","en-AU","en-GB","es","es-MX","es-US","fi","fr","fr-CA","he","hi","hr","hu","id","it","ja","ko","ms","nb","nl","pl","pt","pt-PT","ro","ru","sk","sv","th","tr","uk","vi","zh-Hans","zh-Hant","zh-HK"]}],countryRegionWhitelist:[],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe19-ssl.ls.apple.com/tile.vf",dataSet:0,supportsMultipathTCP:!1},{style:30,validVersion:[{identifier:16030619,availableTiles:[{minX:0,minY:0,maxX:1023,maxY:1023,minZ:10,maxZ:10},{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15},{minX:0,minY:0,maxX:262143,maxY:262143,minZ:18,maxZ:18}],genericTile:[],supportedLanguagesVersion:1}],scale:0,size:2,supportedLanguage:[{identifier:1,language:["ar","ca","cs","da","de","el","en","en-AU","en-GB","es","es-MX","es-US","fi","fr","fr-CA","he","hi","hr","hu","id","it","ja","ko","ms","nb","nl","pl","pt","pt-PT","ro","ru","sk","sv","th","tr","uk","vi","zh-Hans","zh-Hant","zh-HK"]}],countryRegionWhitelist:[],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe19-kittyhawk-ssl.ls.apple.com/tile.vf",dataSet:1,supportsMultipathTCP:!1},{style:33,validVersion:[{identifier:4,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:7}],genericTile:[]}],scale:1,size:1,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe11-ssl.ls.apple.com/tile",supportsMultipathTCP:!1},{style:37,validVersion:[{identifier:16034178,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:1},{minX:0,minY:0,maxX:3,maxY:3,minZ:2,maxZ:2},{minX:0,minY:0,maxX:7,maxY:7,minZ:3,maxZ:3},{minX:0,minY:0,maxX:31,maxY:31,minZ:5,maxZ:5},{minX:0,minY:0,maxX:127,maxY:127,minZ:7,maxZ:7},{minX:0,minY:0,maxX:511,maxY:511,minZ:9,maxZ:9},{minX:0,minY:0,maxX:1023,maxY:1023,minZ:10,maxZ:10},{minX:0,minY:0,maxX:2047,maxY:2047,minZ:11,maxZ:11},{minX:0,minY:0,maxX:8191,maxY:8191,minZ:13,maxZ:13},{minX:0,minY:0,maxX:16383,maxY:16383,minZ:14,maxZ:14},{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15}],genericTile:[],supportedLanguagesVersion:1}],scale:0,size:2,supportedLanguage:[{identifier:1,language:["ar","ca","cs","da","de","el","en","en-AU","en-GB","es","es-MX","es-US","fi","fr","fr-CA","he","hi","hr","hu","id","it","ja","ko","ms","nb","nl","pl","pt","pt-PT","ro","ru","sk","sv","th","tr","uk","vi","zh-Hans","zh-Hant","zh-HK"]}],countryRegionWhitelist:[],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe19-ssl.ls.apple.com/tile.vf?flags=2",dataSet:0,supportsMultipathTCP:!1},{style:37,validVersion:[{identifier:16030619,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:1},{minX:0,minY:0,maxX:3,maxY:3,minZ:2,maxZ:2},{minX:0,minY:0,maxX:7,maxY:7,minZ:3,maxZ:3},{minX:0,minY:0,maxX:31,maxY:31,minZ:5,maxZ:5},{minX:0,minY:0,maxX:127,maxY:127,minZ:7,maxZ:7},{minX:0,minY:0,maxX:511,maxY:511,minZ:9,maxZ:9},{minX:0,minY:0,maxX:1023,maxY:1023,minZ:10,maxZ:10},{minX:0,minY:0,maxX:2047,maxY:2047,minZ:11,maxZ:11},{minX:0,minY:0,maxX:8191,maxY:8191,minZ:13,maxZ:13},{minX:0,minY:0,maxX:16383,maxY:16383,minZ:14,maxZ:14},{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15}],genericTile:[],supportedLanguagesVersion:1}],scale:0,size:2,supportedLanguage:[{identifier:1,language:["ar","ca","cs","da","de","el","en","en-AU","en-GB","es","es-MX","es-US","fi","fr","fr-CA","he","hi","hr","hu","id","it","ja","ko","ms","nb","nl","pl","pt","pt-PT","ro","ru","sk","sv","th","tr","uk","vi","zh-Hans","zh-Hant","zh-HK"]}],countryRegionWhitelist:[],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe19-kittyhawk-ssl.ls.apple.com/tile.vf?flags=2",dataSet:1,supportsMultipathTCP:!1},{style:42,validVersion:[{identifier:1,availableTiles:[],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe11-ssl.ls.apple.com/tile",supportsMultipathTCP:!1},{style:43,validVersion:[{identifier:1,availableTiles:[],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe11-ssl.ls.apple.com/tile",supportsMultipathTCP:!1},{style:44,validVersion:[{identifier:1,availableTiles:[],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe11-ssl.ls.apple.com/tile",supportsMultipathTCP:!1},{style:47,validVersion:[{identifier:16034178,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:1},{minX:0,minY:0,maxX:3,maxY:3,minZ:2,maxZ:2},{minX:0,minY:0,maxX:7,maxY:7,minZ:3,maxZ:3},{minX:0,minY:0,maxX:31,maxY:31,minZ:5,maxZ:5},{minX:0,minY:0,maxX:127,maxY:127,minZ:7,maxZ:7},{minX:0,minY:0,maxX:511,maxY:511,minZ:9,maxZ:9},{minX:0,minY:0,maxX:1023,maxY:1023,minZ:10,maxZ:10},{minX:0,minY:0,maxX:2047,maxY:2047,minZ:11,maxZ:11},{minX:0,minY:0,maxX:8191,maxY:8191,minZ:13,maxZ:13},{minX:0,minY:0,maxX:16383,maxY:16383,minZ:14,maxZ:14},{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15}],genericTile:[],supportedLanguagesVersion:1}],scale:0,size:2,supportedLanguage:[{identifier:1,language:["ar","ca","cs","da","de","el","en","en-AU","en-GB","es","es-MX","es-US","fi","fr","fr-CA","he","hi","hr","hu","id","it","ja","ko","ms","nb","nl","pl","pt","pt-PT","ro","ru","sk","sv","th","tr","uk","vi","zh-Hans","zh-Hant","zh-HK"]}],countryRegionWhitelist:[],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe19-ssl.ls.apple.com/tile.vf",dataSet:0,supportsMultipathTCP:!1},{style:47,validVersion:[{identifier:16030619,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:1},{minX:0,minY:0,maxX:3,maxY:3,minZ:2,maxZ:2},{minX:0,minY:0,maxX:7,maxY:7,minZ:3,maxZ:3},{minX:0,minY:0,maxX:31,maxY:31,minZ:5,maxZ:5},{minX:0,minY:0,maxX:127,maxY:127,minZ:7,maxZ:7},{minX:0,minY:0,maxX:511,maxY:511,minZ:9,maxZ:9},{minX:0,minY:0,maxX:1023,maxY:1023,minZ:10,maxZ:10},{minX:0,minY:0,maxX:2047,maxY:2047,minZ:11,maxZ:11},{minX:0,minY:0,maxX:8191,maxY:8191,minZ:13,maxZ:13},{minX:0,minY:0,maxX:16383,maxY:16383,minZ:14,maxZ:14},{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15}],genericTile:[],supportedLanguagesVersion:1}],scale:0,size:2,supportedLanguage:[{identifier:1,language:["ar","ca","cs","da","de","el","en","en-AU","en-GB","es","es-MX","es-US","fi","fr","fr-CA","he","hi","hr","hu","id","it","ja","ko","ms","nb","nl","pl","pt","pt-PT","ro","ru","sk","sv","th","tr","uk","vi","zh-Hans","zh-Hant","zh-HK"]}],countryRegionWhitelist:[],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe19-kittyhawk-ssl.ls.apple.com/tile.vf",dataSet:1,supportsMultipathTCP:!1},{style:48,validVersion:[{identifier:11201196,availableTiles:[{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12},{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15}],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe19-ssl.ls.apple.com/tile.vf",dataSet:0,supportsMultipathTCP:!1},{style:48,validVersion:[{identifier:11201196,availableTiles:[{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12},{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15}],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe19-kittyhawk-ssl.ls.apple.com/tile.vf",dataSet:1,supportsMultipathTCP:!1},{style:52,validVersion:[{identifier:1,availableTiles:[],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe11-ssl.ls.apple.com/tile",supportsMultipathTCP:!1},{style:53,validVersion:[{identifier:16034178,availableTiles:[{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15}],genericTile:[],supportedLanguagesVersion:1}],scale:0,size:2,supportedLanguage:[{identifier:1,language:["ar","ca","cs","da","de","el","en","en-AU","en-GB","es","es-MX","es-US","fi","fr","fr-CA","he","hi","hr","hu","id","it","ja","ko","ms","nb","nl","pl","pt","pt-PT","ro","ru","sk","sv","th","tr","uk","vi","zh-Hans","zh-Hant","zh-HK"]}],countryRegionWhitelist:[],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe19-ssl.ls.apple.com/tile.vf",dataSet:0,supportsMultipathTCP:!1},{style:53,validVersion:[{identifier:16030619,availableTiles:[{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15}],genericTile:[],supportedLanguagesVersion:1}],scale:0,size:2,supportedLanguage:[{identifier:1,language:["ar","ca","cs","da","de","el","en","en-AU","en-GB","es","es-MX","es-US","fi","fr","fr-CA","he","hi","hr","hu","id","it","ja","ko","ms","nb","nl","pl","pt","pt-PT","ro","ru","sk","sv","th","tr","uk","vi","zh-Hans","zh-Hant","zh-HK"]}],countryRegionWhitelist:[],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe19-kittyhawk-ssl.ls.apple.com/tile.vf",dataSet:1,supportsMultipathTCP:!1},{style:54,validVersion:[{identifier:13658945,availableTiles:[{minX:0,minY:0,maxX:8191,maxY:8191,minZ:13,maxZ:13},{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15}],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe19-ssl.ls.apple.com/tile.vf",dataSet:0,supportsMultipathTCP:!1},{style:54,validVersion:[{identifier:13659050,availableTiles:[{minX:0,minY:0,maxX:8191,maxY:8191,minZ:13,maxZ:13},{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15}],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe19-kittyhawk-ssl.ls.apple.com/tile.vf",dataSet:1,supportsMultipathTCP:!1},{style:56,validVersion:[{identifier:16034178,availableTiles:[{minX:0,minY:0,maxX:131071,maxY:131071,minZ:17,maxZ:17}],genericTile:[],supportedLanguagesVersion:1}],scale:0,size:2,supportedLanguage:[{identifier:1,language:["ar","ca","cs","da","de","el","en","en-AU","en-GB","es","es-MX","es-US","fi","fr","fr-CA","he","hi","hr","hu","id","it","ja","ko","ms","nb","nl","pl","pt","pt-PT","ro","ru","sk","sv","th","tr","uk","vi","zh-Hans","zh-Hant","zh-HK"]}],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-ssl.ls.apple.com/tile.vf",dataSet:0,supportsMultipathTCP:!1},{style:56,validVersion:[{identifier:16030619,availableTiles:[{minX:0,minY:0,maxX:131071,maxY:131071,minZ:17,maxZ:17}],genericTile:[],supportedLanguagesVersion:1}],scale:0,size:2,supportedLanguage:[{identifier:1,language:["ar","ca","cs","da","de","el","en","en-AU","en-GB","es","es-MX","es-US","fi","fr","fr-CA","he","hi","hr","hu","id","it","ja","ko","ms","nb","nl","pl","pt","pt-PT","ro","ru","sk","sv","th","tr","uk","vi","zh-Hans","zh-Hant","zh-HK"]}],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-kittyhawk-ssl.ls.apple.com/tile.vf",dataSet:1,supportsMultipathTCP:!1},{style:57,validVersion:[{identifier:0,availableTiles:[{minX:0,minY:0,maxX:131071,maxY:131071,minZ:17,maxZ:17}],genericTile:[],timeToLiveSeconds:3600}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe76-ssl.ls.apple.com/api/tile",supportsMultipathTCP:!1},{style:58,validVersion:[{identifier:16034178,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:1},{minX:0,minY:0,maxX:3,maxY:3,minZ:2,maxZ:2},{minX:0,minY:0,maxX:7,maxY:7,minZ:3,maxZ:3},{minX:0,minY:0,maxX:15,maxY:15,minZ:4,maxZ:4},{minX:0,minY:0,maxX:31,maxY:31,minZ:5,maxZ:5},{minX:0,minY:0,maxX:63,maxY:63,minZ:6,maxZ:6},{minX:0,minY:0,maxX:127,maxY:127,minZ:7,maxZ:7},{minX:0,minY:0,maxX:255,maxY:255,minZ:8,maxZ:8},{minX:0,minY:0,maxX:511,maxY:511,minZ:9,maxZ:9},{minX:0,minY:0,maxX:1023,maxY:1023,minZ:10,maxZ:10},{minX:0,minY:0,maxX:2047,maxY:2047,minZ:11,maxZ:11},{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12},{minX:0,minY:0,maxX:8191,maxY:8191,minZ:13,maxZ:13},{minX:0,minY:0,maxX:16383,maxY:16383,minZ:14,maxZ:14},{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15},{minX:0,minY:0,maxX:65535,maxY:65535,minZ:16,maxZ:16},{minX:0,minY:0,maxX:131071,maxY:131071,minZ:17,maxZ:17}],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-ssl.ls.apple.com/tile.vf",dataSet:0,supportsMultipathTCP:!1},{style:58,validVersion:[{identifier:16030619,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:1},{minX:0,minY:0,maxX:3,maxY:3,minZ:2,maxZ:2},{minX:0,minY:0,maxX:7,maxY:7,minZ:3,maxZ:3},{minX:0,minY:0,maxX:15,maxY:15,minZ:4,maxZ:4},{minX:0,minY:0,maxX:31,maxY:31,minZ:5,maxZ:5},{minX:0,minY:0,maxX:63,maxY:63,minZ:6,maxZ:6},{minX:0,minY:0,maxX:127,maxY:127,minZ:7,maxZ:7},{minX:0,minY:0,maxX:255,maxY:255,minZ:8,maxZ:8},{minX:0,minY:0,maxX:511,maxY:511,minZ:9,maxZ:9},{minX:0,minY:0,maxX:1023,maxY:1023,minZ:10,maxZ:10},{minX:0,minY:0,maxX:2047,maxY:2047,minZ:11,maxZ:11},{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12},{minX:0,minY:0,maxX:8191,maxY:8191,minZ:13,maxZ:13},{minX:0,minY:0,maxX:16383,maxY:16383,minZ:14,maxZ:14},{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15},{minX:0,minY:0,maxX:65535,maxY:65535,minZ:16,maxZ:16},{minX:0,minY:0,maxX:131071,maxY:131071,minZ:17,maxZ:17}],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-kittyhawk-ssl.ls.apple.com/tile.vf",dataSet:1,supportsMultipathTCP:!1},{style:59,validVersion:[{identifier:16034178,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:1}],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-ssl.ls.apple.com/asset/v3/model",dataSet:0,supportsMultipathTCP:!1},{style:59,validVersion:[{identifier:16030619,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:1}],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-kittyhawk-ssl.ls.apple.com/asset/v3/model",dataSet:1,supportsMultipathTCP:!1},{style:60,validVersion:[{identifier:16034178,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:1},{minX:0,minY:0,maxX:3,maxY:3,minZ:2,maxZ:2},{minX:0,minY:0,maxX:7,maxY:7,minZ:3,maxZ:3},{minX:0,minY:0,maxX:15,maxY:15,minZ:4,maxZ:4},{minX:0,minY:0,maxX:31,maxY:31,minZ:5,maxZ:5},{minX:0,minY:0,maxX:63,maxY:63,minZ:6,maxZ:6},{minX:0,minY:0,maxX:127,maxY:127,minZ:7,maxZ:7},{minX:0,minY:0,maxX:255,maxY:255,minZ:8,maxZ:8},{minX:0,minY:0,maxX:511,maxY:511,minZ:9,maxZ:9}],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-ssl.ls.apple.com/asset/v3/material",dataSet:0,supportsMultipathTCP:!1},{style:60,validVersion:[{identifier:16030619,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:1},{minX:0,minY:0,maxX:3,maxY:3,minZ:2,maxZ:2},{minX:0,minY:0,maxX:7,maxY:7,minZ:3,maxZ:3},{minX:0,minY:0,maxX:15,maxY:15,minZ:4,maxZ:4},{minX:0,minY:0,maxX:31,maxY:31,minZ:5,maxZ:5},{minX:0,minY:0,maxX:63,maxY:63,minZ:6,maxZ:6},{minX:0,minY:0,maxX:127,maxY:127,minZ:7,maxZ:7},{minX:0,minY:0,maxX:255,maxY:255,minZ:8,maxZ:8},{minX:0,minY:0,maxX:511,maxY:511,minZ:9,maxZ:9}],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-kittyhawk-ssl.ls.apple.com/asset/v3/material",dataSet:1,supportsMultipathTCP:!1},{style:61,validVersion:[{identifier:16034178,availableTiles:[{minX:0,minY:0,maxX:127,maxY:127,minZ:7,maxZ:7}],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-ssl.ls.apple.com/tile.vf",dataSet:0,supportsMultipathTCP:!1},{style:61,validVersion:[{identifier:16030619,availableTiles:[{minX:0,minY:0,maxX:127,maxY:127,minZ:7,maxZ:7}],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-kittyhawk-ssl.ls.apple.com/tile.vf",dataSet:1,supportsMultipathTCP:!1},{style:62,validVersion:[{identifier:16034178,availableTiles:[{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12}],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-ssl.ls.apple.com/tile.vf",dataSet:0,supportsMultipathTCP:!1},{style:62,validVersion:[{identifier:16030619,availableTiles:[{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12}],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-kittyhawk-ssl.ls.apple.com/tile.vf",dataSet:1,supportsMultipathTCP:!1},{style:64,validVersion:[{identifier:16034178,availableTiles:[{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12}],genericTile:[],supportedLanguagesVersion:1}],scale:0,size:2,supportedLanguage:[{identifier:1,language:["ar","ca","cs","da","de","el","en","en-AU","en-GB","es","es-MX","es-US","fi","fr","fr-CA","he","hi","hr","hu","id","it","ja","ko","ms","nb","nl","pl","pt","pt-PT","ro","ru","sk","sv","th","tr","uk","vi","zh-Hans","zh-Hant","zh-HK"]}],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-ssl.ls.apple.com/tile.vf",dataSet:0,supportsMultipathTCP:!1},{style:64,validVersion:[{identifier:16030619,availableTiles:[{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12}],genericTile:[],supportedLanguagesVersion:1}],scale:0,size:2,supportedLanguage:[{identifier:1,language:["ar","ca","cs","da","de","el","en","en-AU","en-GB","es","es-MX","es-US","fi","fr","fr-CA","he","hi","hr","hu","id","it","ja","ko","ms","nb","nl","pl","pt","pt-PT","ro","ru","sk","sv","th","tr","uk","vi","zh-Hans","zh-Hant","zh-HK"]}],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-kittyhawk-ssl.ls.apple.com/tile.vf",dataSet:1,supportsMultipathTCP:!1},{style:65,validVersion:[{identifier:2,availableTiles:[{minX:0,minY:0,maxX:255,maxY:255,minZ:8,maxZ:8}],genericTile:[],timeToLiveSeconds:3600}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe79-ssl.ls.apple.com/65/v1",supportsMultipathTCP:!1},{style:66,validVersion:[{identifier:16034178,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:1},{minX:0,minY:0,maxX:3,maxY:3,minZ:2,maxZ:2},{minX:0,minY:0,maxX:7,maxY:7,minZ:3,maxZ:3},{minX:0,minY:0,maxX:15,maxY:15,minZ:4,maxZ:4},{minX:0,minY:0,maxX:31,maxY:31,minZ:5,maxZ:5},{minX:0,minY:0,maxX:63,maxY:63,minZ:6,maxZ:6},{minX:0,minY:0,maxX:127,maxY:127,minZ:7,maxZ:7},{minX:0,minY:0,maxX:255,maxY:255,minZ:8,maxZ:8},{minX:0,minY:0,maxX:511,maxY:511,minZ:9,maxZ:9},{minX:0,minY:0,maxX:1023,maxY:1023,minZ:10,maxZ:10},{minX:0,minY:0,maxX:2047,maxY:2047,minZ:11,maxZ:11},{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12},{minX:0,minY:0,maxX:8191,maxY:8191,minZ:13,maxZ:13},{minX:0,minY:0,maxX:16383,maxY:16383,minZ:14,maxZ:14},{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15}],genericTile:[],supportedLanguagesVersion:1}],scale:0,size:2,supportedLanguage:[{identifier:1,language:["ar","ca","cs","da","de","el","en","en-AU","en-GB","es","es-MX","es-US","fi","fr","fr-CA","he","hi","hr","hu","id","it","ja","ko","ms","nb","nl","pl","pt","pt-PT","ro","ru","sk","sv","th","tr","uk","vi","zh-Hans","zh-Hant","zh-HK"]}],countryRegionWhitelist:[{countryCode:"AE",region:"AE"},{countryCode:"AE",region:"SA"},{countryCode:"IN",region:""},{countryCode:"JP",region:"JP"},{countryCode:"KR",region:"KR"},{countryCode:"MA",region:"MA"},{countryCode:"RU",region:"RU"},{countryCode:"SA",region:"AE"},{countryCode:"SA",region:"SA"},{countryCode:"VN",region:"VN"}],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-ssl.ls.apple.com/tile.vf",dataSet:0,supportsMultipathTCP:!1},{style:66,validVersion:[{identifier:16030619,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:1},{minX:0,minY:0,maxX:3,maxY:3,minZ:2,maxZ:2},{minX:0,minY:0,maxX:7,maxY:7,minZ:3,maxZ:3},{minX:0,minY:0,maxX:15,maxY:15,minZ:4,maxZ:4},{minX:0,minY:0,maxX:31,maxY:31,minZ:5,maxZ:5},{minX:0,minY:0,maxX:63,maxY:63,minZ:6,maxZ:6},{minX:0,minY:0,maxX:127,maxY:127,minZ:7,maxZ:7},{minX:0,minY:0,maxX:255,maxY:255,minZ:8,maxZ:8},{minX:0,minY:0,maxX:511,maxY:511,minZ:9,maxZ:9},{minX:0,minY:0,maxX:1023,maxY:1023,minZ:10,maxZ:10},{minX:0,minY:0,maxX:2047,maxY:2047,minZ:11,maxZ:11},{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12},{minX:0,minY:0,maxX:8191,maxY:8191,minZ:13,maxZ:13},{minX:0,minY:0,maxX:16383,maxY:16383,minZ:14,maxZ:14},{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15}],genericTile:[],supportedLanguagesVersion:1}],scale:0,size:2,supportedLanguage:[{identifier:1,language:["ar","ca","cs","da","de","el","en","en-AU","en-GB","es","es-MX","es-US","fi","fr","fr-CA","he","hi","hr","hu","id","it","ja","ko","ms","nb","nl","pl","pt","pt-PT","ro","ru","sk","sv","th","tr","uk","vi","zh-Hans","zh-Hant","zh-HK"]}],countryRegionWhitelist:[{countryCode:"AE",region:"AE"},{countryCode:"AE",region:"SA"},{countryCode:"IN",region:""},{countryCode:"JP",region:"JP"},{countryCode:"KR",region:"KR"},{countryCode:"MA",region:"MA"},{countryCode:"RU",region:"RU"},{countryCode:"SA",region:"AE"},{countryCode:"SA",region:"SA"},{countryCode:"VN",region:"VN"}],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-kittyhawk-ssl.ls.apple.com/tile.vf",dataSet:1,supportsMultipathTCP:!1},{style:67,validVersion:[{identifier:16034178,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:1},{minX:0,minY:0,maxX:3,maxY:3,minZ:2,maxZ:2},{minX:0,minY:0,maxX:7,maxY:7,minZ:3,maxZ:3},{minX:0,minY:0,maxX:15,maxY:15,minZ:4,maxZ:4},{minX:0,minY:0,maxX:31,maxY:31,minZ:5,maxZ:5},{minX:0,minY:0,maxX:63,maxY:63,minZ:6,maxZ:6},{minX:0,minY:0,maxX:127,maxY:127,minZ:7,maxZ:7},{minX:0,minY:0,maxX:255,maxY:255,minZ:8,maxZ:8},{minX:0,minY:0,maxX:1023,maxY:1023,minZ:10,maxZ:10},{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12},{minX:0,minY:0,maxX:16383,maxY:16383,minZ:14,maxZ:14}],genericTile:[],supportedLanguagesVersion:1}],scale:0,size:2,supportedLanguage:[{identifier:1,language:["ar","ca","cs","da","de","el","en","en-AU","en-GB","es","es-MX","es-US","fi","fr","fr-CA","he","hi","hr","hu","id","it","ja","ko","ms","nb","nl","pl","pt","pt-PT","ro","ru","sk","sv","th","tr","uk","vi","zh-Hans","zh-Hant","zh-HK"]}],countryRegionWhitelist:[{countryCode:"AE",region:"AE"},{countryCode:"AE",region:"SA"},{countryCode:"IN",region:""},{countryCode:"JP",region:"JP"},{countryCode:"KR",region:"KR"},{countryCode:"MA",region:"MA"},{countryCode:"RU",region:"RU"},{countryCode:"SA",region:"AE"},{countryCode:"SA",region:"SA"},{countryCode:"VN",region:"VN"}],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-ssl.ls.apple.com/tile.vf",dataSet:0,supportsMultipathTCP:!1},{style:67,validVersion:[{identifier:16030619,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:1},{minX:0,minY:0,maxX:3,maxY:3,minZ:2,maxZ:2},{minX:0,minY:0,maxX:7,maxY:7,minZ:3,maxZ:3},{minX:0,minY:0,maxX:15,maxY:15,minZ:4,maxZ:4},{minX:0,minY:0,maxX:31,maxY:31,minZ:5,maxZ:5},{minX:0,minY:0,maxX:63,maxY:63,minZ:6,maxZ:6},{minX:0,minY:0,maxX:127,maxY:127,minZ:7,maxZ:7},{minX:0,minY:0,maxX:255,maxY:255,minZ:8,maxZ:8},{minX:0,minY:0,maxX:1023,maxY:1023,minZ:10,maxZ:10},{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12},{minX:0,minY:0,maxX:16383,maxY:16383,minZ:14,maxZ:14}],genericTile:[],supportedLanguagesVersion:1}],scale:0,size:2,supportedLanguage:[{identifier:1,language:["ar","ca","cs","da","de","el","en","en-AU","en-GB","es","es-MX","es-US","fi","fr","fr-CA","he","hi","hr","hu","id","it","ja","ko","ms","nb","nl","pl","pt","pt-PT","ro","ru","sk","sv","th","tr","uk","vi","zh-Hans","zh-Hant","zh-HK"]}],countryRegionWhitelist:[{countryCode:"AE",region:"AE"},{countryCode:"AE",region:"SA"},{countryCode:"IN",region:""},{countryCode:"JP",region:"JP"},{countryCode:"KR",region:"KR"},{countryCode:"MA",region:"MA"},{countryCode:"RU",region:"RU"},{countryCode:"SA",region:"AE"},{countryCode:"SA",region:"SA"},{countryCode:"VN",region:"VN"}],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-kittyhawk-ssl.ls.apple.com/tile.vf",dataSet:1,supportsMultipathTCP:!1},{style:68,validVersion:[{identifier:16034178,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:1},{minX:0,minY:0,maxX:3,maxY:3,minZ:2,maxZ:2},{minX:0,minY:0,maxX:15,maxY:15,minZ:4,maxZ:4},{minX:0,minY:0,maxX:63,maxY:63,minZ:6,maxZ:6},{minX:0,minY:0,maxX:127,maxY:127,minZ:7,maxZ:7},{minX:0,minY:0,maxX:255,maxY:255,minZ:8,maxZ:8},{minX:0,minY:0,maxX:511,maxY:511,minZ:9,maxZ:9},{minX:0,minY:0,maxX:1023,maxY:1023,minZ:10,maxZ:10},{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12},{minX:0,minY:0,maxX:8191,maxY:8191,minZ:13,maxZ:13},{minX:0,minY:0,maxX:16383,maxY:16383,minZ:14,maxZ:14},{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15},{minX:0,minY:0,maxX:65535,maxY:65535,minZ:16,maxZ:16},{minX:0,minY:0,maxX:131071,maxY:131071,minZ:17,maxZ:17}],genericTile:[],supportedLanguagesVersion:1}],scale:0,size:2,supportedLanguage:[{identifier:1,language:["ar","ca","cs","da","de","el","en","en-AU","en-GB","es","es-MX","es-US","fi","fr","fr-CA","he","hi","hr","hu","id","it","ja","ko","ms","nb","nl","pl","pt","pt-PT","ro","ru","sk","sv","th","tr","uk","vi","zh-Hans","zh-Hant","zh-HK"]}],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-ssl.ls.apple.com/tile.vf",dataSet:0,supportsMultipathTCP:!1},{style:68,validVersion:[{identifier:16030619,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:1},{minX:0,minY:0,maxX:3,maxY:3,minZ:2,maxZ:2},{minX:0,minY:0,maxX:15,maxY:15,minZ:4,maxZ:4},{minX:0,minY:0,maxX:63,maxY:63,minZ:6,maxZ:6},{minX:0,minY:0,maxX:127,maxY:127,minZ:7,maxZ:7},{minX:0,minY:0,maxX:255,maxY:255,minZ:8,maxZ:8},{minX:0,minY:0,maxX:511,maxY:511,minZ:9,maxZ:9},{minX:0,minY:0,maxX:1023,maxY:1023,minZ:10,maxZ:10},{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12},{minX:0,minY:0,maxX:8191,maxY:8191,minZ:13,maxZ:13},{minX:0,minY:0,maxX:16383,maxY:16383,minZ:14,maxZ:14},{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15},{minX:0,minY:0,maxX:65535,maxY:65535,minZ:16,maxZ:16},{minX:0,minY:0,maxX:131071,maxY:131071,minZ:17,maxZ:17}],genericTile:[],supportedLanguagesVersion:1}],scale:0,size:2,supportedLanguage:[{identifier:1,language:["ar","ca","cs","da","de","el","en","en-AU","en-GB","es","es-MX","es-US","fi","fr","fr-CA","he","hi","hr","hu","id","it","ja","ko","ms","nb","nl","pl","pt","pt-PT","ro","ru","sk","sv","th","tr","uk","vi","zh-Hans","zh-Hant","zh-HK"]}],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-kittyhawk-ssl.ls.apple.com/tile.vf",dataSet:1,supportsMultipathTCP:!1},{style:69,validVersion:[{identifier:16034178,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:1},{minX:0,minY:0,maxX:7,maxY:7,minZ:3,maxZ:3},{minX:0,minY:0,maxX:63,maxY:63,minZ:6,maxZ:6},{minX:0,minY:0,maxX:511,maxY:511,minZ:9,maxZ:9},{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12},{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15}],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-ssl.ls.apple.com/tile.vf",dataSet:0,supportsMultipathTCP:!1},{style:69,validVersion:[{identifier:16030619,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:1},{minX:0,minY:0,maxX:7,maxY:7,minZ:3,maxZ:3},{minX:0,minY:0,maxX:63,maxY:63,minZ:6,maxZ:6},{minX:0,minY:0,maxX:511,maxY:511,minZ:9,maxZ:9},{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12},{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15}],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-kittyhawk-ssl.ls.apple.com/tile.vf",dataSet:1,supportsMultipathTCP:!1},{style:70,validVersion:[{identifier:1,availableTiles:[{minX:0,minY:0,maxX:8191,maxY:8191,minZ:13,maxZ:13}],genericTile:[],timeToLiveSeconds:86400}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe76-ssl.ls.apple.com/api/vltile",supportsMultipathTCP:!1},{style:71,validVersion:[{identifier:1,availableTiles:[{minX:0,minY:0,maxX:2097151,maxY:2097151,minZ:21,maxZ:21}],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe92-ssl.ls.apple.com",supportsMultipathTCP:!1},{style:72,validVersion:[{identifier:2,availableTiles:[{minX:0,minY:0,maxX:8191,maxY:8191,minZ:13,maxZ:13}],genericTile:[],timeToLiveSeconds:3600}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe79-ssl.ls.apple.com/72/v2",supportsMultipathTCP:!1},{style:73,validVersion:[{identifier:16034178,availableTiles:[{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12},{minX:0,minY:0,maxX:8191,maxY:8191,minZ:13,maxZ:13},{minX:0,minY:0,maxX:16383,maxY:16383,minZ:14,maxZ:14},{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15}],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-ssl.ls.apple.com/tile.vf",dataSet:0,supportsMultipathTCP:!1},{style:73,validVersion:[{identifier:16030619,availableTiles:[{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12},{minX:0,minY:0,maxX:8191,maxY:8191,minZ:13,maxZ:13},{minX:0,minY:0,maxX:16383,maxY:16383,minZ:14,maxZ:14},{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15}],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-kittyhawk-ssl.ls.apple.com/tile.vf",dataSet:1,supportsMultipathTCP:!1},{style:74,validVersion:[{identifier:0,availableTiles:[{minX:0,minY:0,maxX:1023,maxY:1023,minZ:10,maxZ:10},{minX:0,minY:0,maxX:2097151,maxY:2097151,minZ:21,maxZ:21}],genericTile:[],timeToLiveSeconds:86400}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe79-ssl.ls.apple.com/pbz/v1",supportsMultipathTCP:!1},{style:75,validVersion:[{identifier:0,availableTiles:[{minX:0,minY:0,maxX:1023,maxY:1023,minZ:10,maxZ:10},{minX:0,minY:0,maxX:131071,maxY:131071,minZ:17,maxZ:17}],genericTile:[],timeToLiveSeconds:86400}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe79-ssl.ls.apple.com/pbz/v1",supportsMultipathTCP:!1},{style:76,validVersion:[{identifier:0,availableTiles:[{minX:0,minY:0,maxX:8191,maxY:8191,minZ:13,maxZ:13},{minX:0,minY:0,maxX:524287,maxY:524287,minZ:19,maxZ:19}],genericTile:[],timeToLiveSeconds:86400}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe79-ssl.ls.apple.com/sis/v1",supportsMultipathTCP:!1},{style:78,validVersion:[{identifier:16034178,availableTiles:[{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12}],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-ssl.ls.apple.com/tile.vf",dataSet:0,supportsMultipathTCP:!1},{style:78,validVersion:[{identifier:16030619,availableTiles:[{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12}],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-kittyhawk-ssl.ls.apple.com/tile.vf",dataSet:1,supportsMultipathTCP:!1},{style:79,validVersion:[{identifier:16034178,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:1},{minX:0,minY:0,maxX:3,maxY:3,minZ:2,maxZ:2},{minX:0,minY:0,maxX:7,maxY:7,minZ:3,maxZ:3},{minX:0,minY:0,maxX:15,maxY:15,minZ:4,maxZ:4},{minX:0,minY:0,maxX:31,maxY:31,minZ:5,maxZ:5},{minX:0,minY:0,maxX:63,maxY:63,minZ:6,maxZ:6},{minX:0,minY:0,maxX:127,maxY:127,minZ:7,maxZ:7},{minX:0,minY:0,maxX:255,maxY:255,minZ:8,maxZ:8},{minX:0,minY:0,maxX:511,maxY:511,minZ:9,maxZ:9}],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-ssl.ls.apple.com/tile.vf",dataSet:0,supportsMultipathTCP:!1},{style:79,validVersion:[{identifier:16030619,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:1},{minX:0,minY:0,maxX:3,maxY:3,minZ:2,maxZ:2},{minX:0,minY:0,maxX:7,maxY:7,minZ:3,maxZ:3},{minX:0,minY:0,maxX:15,maxY:15,minZ:4,maxZ:4},{minX:0,minY:0,maxX:31,maxY:31,minZ:5,maxZ:5},{minX:0,minY:0,maxX:63,maxY:63,minZ:6,maxZ:6},{minX:0,minY:0,maxX:127,maxY:127,minZ:7,maxZ:7},{minX:0,minY:0,maxX:255,maxY:255,minZ:8,maxZ:8},{minX:0,minY:0,maxX:511,maxY:511,minZ:9,maxZ:9}],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-kittyhawk-ssl.ls.apple.com/tile.vf",dataSet:1,supportsMultipathTCP:!1},{style:80,validVersion:[{identifier:0,availableTiles:[{minX:0,minY:0,maxX:1023,maxY:1023,minZ:10,maxZ:10},{minX:0,minY:0,maxX:65535,maxY:65535,minZ:16,maxZ:16}],genericTile:[],timeToLiveSeconds:86400}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe79-ssl.ls.apple.com/sdm/v1",supportsMultipathTCP:!1},{style:82,validVersion:[{identifier:16034178,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:1}],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-ssl.ls.apple.com/asset/v3/model-occlusion",dataSet:0,supportsMultipathTCP:!1},{style:82,validVersion:[{identifier:16030619,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:1}],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-kittyhawk-ssl.ls.apple.com/asset/v3/model-occlusion",dataSet:1,supportsMultipathTCP:!1},{style:84,validVersion:[{identifier:16034178,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:1},{minX:0,minY:0,maxX:3,maxY:3,minZ:2,maxZ:2},{minX:0,minY:0,maxX:15,maxY:15,minZ:4,maxZ:4},{minX:0,minY:0,maxX:63,maxY:63,minZ:6,maxZ:6},{minX:0,minY:0,maxX:127,maxY:127,minZ:7,maxZ:7},{minX:0,minY:0,maxX:255,maxY:255,minZ:8,maxZ:8},{minX:0,minY:0,maxX:511,maxY:511,minZ:9,maxZ:9},{minX:0,minY:0,maxX:1023,maxY:1023,minZ:10,maxZ:10},{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12},{minX:0,minY:0,maxX:8191,maxY:8191,minZ:13,maxZ:13},{minX:0,minY:0,maxX:16383,maxY:16383,minZ:14,maxZ:14},{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15},{minX:0,minY:0,maxX:65535,maxY:65535,minZ:16,maxZ:16},{minX:0,minY:0,maxX:131071,maxY:131071,minZ:17,maxZ:17}],genericTile:[],timeToLiveSeconds:1800,supportedLanguagesVersion:1}],scale:0,size:2,supportedLanguage:[{identifier:1,language:["ar","ca","cs","da","de","el","en","en-AU","en-GB","es","es-MX","es-US","fi","fr","fr-CA","he","hi","hr","hu","id","it","ja","ko","ms","nb","nl","pl","pt","pt-PT","ro","ru","sk","sv","th","tr","uk","vi","zh-Hans","zh-Hant","zh-HK"]}],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-2-ssl.ls.apple.com/poi_update",dataSet:0,supportsMultipathTCP:!1},{style:84,validVersion:[{identifier:16030619,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:1},{minX:0,minY:0,maxX:3,maxY:3,minZ:2,maxZ:2},{minX:0,minY:0,maxX:15,maxY:15,minZ:4,maxZ:4},{minX:0,minY:0,maxX:63,maxY:63,minZ:6,maxZ:6},{minX:0,minY:0,maxX:127,maxY:127,minZ:7,maxZ:7},{minX:0,minY:0,maxX:255,maxY:255,minZ:8,maxZ:8},{minX:0,minY:0,maxX:511,maxY:511,minZ:9,maxZ:9},{minX:0,minY:0,maxX:1023,maxY:1023,minZ:10,maxZ:10},{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12},{minX:0,minY:0,maxX:8191,maxY:8191,minZ:13,maxZ:13},{minX:0,minY:0,maxX:16383,maxY:16383,minZ:14,maxZ:14},{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15},{minX:0,minY:0,maxX:65535,maxY:65535,minZ:16,maxZ:16},{minX:0,minY:0,maxX:131071,maxY:131071,minZ:17,maxZ:17}],genericTile:[],timeToLiveSeconds:1800,supportedLanguagesVersion:1}],scale:0,size:2,supportedLanguage:[{identifier:1,language:["ar","ca","cs","da","de","el","en","en-AU","en-GB","es","es-MX","es-US","fi","fr","fr-CA","he","hi","hr","hu","id","it","ja","ko","ms","nb","nl","pl","pt","pt-PT","ro","ru","sk","sv","th","tr","uk","vi","zh-Hans","zh-Hant","zh-HK"]}],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-2-ssl.ls.apple.com/poi_update",dataSet:1,supportsMultipathTCP:!1},{style:85,validVersion:[{identifier:16034178,availableTiles:[{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12}],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-2-ssl.ls.apple.com/live_tile.vf",dataSet:0,supportsMultipathTCP:!1},{style:85,validVersion:[{identifier:16030619,availableTiles:[{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12}],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-2-ssl.ls.apple.com/live_tile.vf",dataSet:1,supportsMultipathTCP:!1},{style:87,validVersion:[{identifier:16034178,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:1},{minX:0,minY:0,maxX:3,maxY:3,minZ:2,maxZ:2},{minX:0,minY:0,maxX:7,maxY:7,minZ:3,maxZ:3},{minX:0,minY:0,maxX:15,maxY:15,minZ:4,maxZ:4},{minX:0,minY:0,maxX:31,maxY:31,minZ:5,maxZ:5},{minX:0,minY:0,maxX:63,maxY:63,minZ:6,maxZ:6},{minX:0,minY:0,maxX:127,maxY:127,minZ:7,maxZ:7},{minX:0,minY:0,maxX:255,maxY:255,minZ:8,maxZ:8},{minX:0,minY:0,maxX:511,maxY:511,minZ:9,maxZ:9},{minX:0,minY:0,maxX:1023,maxY:1023,minZ:10,maxZ:10},{minX:0,minY:0,maxX:2047,maxY:2047,minZ:11,maxZ:11},{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12},{minX:0,minY:0,maxX:8191,maxY:8191,minZ:13,maxZ:13},{minX:0,minY:0,maxX:16383,maxY:16383,minZ:14,maxZ:14},{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15}],genericTile:[],supportedLanguagesVersion:1}],scale:0,size:2,supportedLanguage:[{identifier:1,language:["ar","ca","cs","da","de","el","en","en-AU","en-GB","es","es-MX","es-US","fi","fr","fr-CA","he","hi","hr","hu","id","it","ja","ko","ms","nb","nl","pl","pt","pt-PT","ro","ru","sk","sv","th","tr","uk","vi","zh-Hans","zh-Hant","zh-HK"]}],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-ssl.ls.apple.com/tile.vf",dataSet:0,supportsMultipathTCP:!1},{style:87,validVersion:[{identifier:16030619,availableTiles:[{minX:0,minY:0,maxX:1,maxY:1,minZ:1,maxZ:1},{minX:0,minY:0,maxX:3,maxY:3,minZ:2,maxZ:2},{minX:0,minY:0,maxX:7,maxY:7,minZ:3,maxZ:3},{minX:0,minY:0,maxX:15,maxY:15,minZ:4,maxZ:4},{minX:0,minY:0,maxX:31,maxY:31,minZ:5,maxZ:5},{minX:0,minY:0,maxX:63,maxY:63,minZ:6,maxZ:6},{minX:0,minY:0,maxX:127,maxY:127,minZ:7,maxZ:7},{minX:0,minY:0,maxX:255,maxY:255,minZ:8,maxZ:8},{minX:0,minY:0,maxX:511,maxY:511,minZ:9,maxZ:9},{minX:0,minY:0,maxX:1023,maxY:1023,minZ:10,maxZ:10},{minX:0,minY:0,maxX:2047,maxY:2047,minZ:11,maxZ:11},{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12},{minX:0,minY:0,maxX:8191,maxY:8191,minZ:13,maxZ:13},{minX:0,minY:0,maxX:16383,maxY:16383,minZ:14,maxZ:14},{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15}],genericTile:[],supportedLanguagesVersion:1}],scale:0,size:2,supportedLanguage:[{identifier:1,language:["ar","ca","cs","da","de","el","en","en-AU","en-GB","es","es-MX","es-US","fi","fr","fr-CA","he","hi","hr","hu","id","it","ja","ko","ms","nb","nl","pl","pt","pt-PT","ro","ru","sk","sv","th","tr","uk","vi","zh-Hans","zh-Hant","zh-HK"]}],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-kittyhawk-ssl.ls.apple.com/tile.vf",dataSet:1,supportsMultipathTCP:!1},{style:88,validVersion:[{identifier:16034178,availableTiles:[{minX:0,minY:0,maxX:127,maxY:127,minZ:7,maxZ:7},{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12}],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-ssl.ls.apple.com/tile.vf",dataSet:0,supportsMultipathTCP:!1},{style:88,validVersion:[{identifier:16030619,availableTiles:[{minX:0,minY:0,maxX:127,maxY:127,minZ:7,maxZ:7},{minX:0,minY:0,maxX:4095,maxY:4095,minZ:12,maxZ:12}],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-kittyhawk-ssl.ls.apple.com/tile.vf",dataSet:1,supportsMultipathTCP:!1},{style:89,validVersion:[{identifier:1,availableTiles:[{minX:0,minY:0,maxX:262143,maxY:262143,minZ:18,maxZ:18}],genericTile:[],timeToLiveSeconds:86400}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:1,deviceSKUWhitelist:[],baseURL:"https://gspe79-ssl.ls.apple.com/ray/v1",supportsMultipathTCP:!1},{style:90,validVersion:[{identifier:16034178,availableTiles:[{minX:0,minY:0,maxX:8191,maxY:8191,minZ:13,maxZ:13},{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15}],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-ssl.ls.apple.com/tile.vf",dataSet:0,supportsMultipathTCP:!1},{style:90,validVersion:[{identifier:16030619,availableTiles:[{minX:0,minY:0,maxX:8191,maxY:8191,minZ:13,maxZ:13},{minX:0,minY:0,maxX:32767,maxY:32767,minZ:15,maxZ:15}],genericTile:[]}],scale:0,size:2,supportedLanguage:[],countryRegionWhitelist:[],checksumType:0,requestStyle:0,deviceSKUWhitelist:[],baseURL:"https://gspe19-kittyhawk-ssl.ls.apple.com/tile.vf",dataSet:1,supportsMultipathTCP:!1}],attribution:[{name:"‎",url:"https://gspe21-ssl.ls.apple.com/html/attribution-275.html",resource:[],region:[],linkDisplayStringIndex:0,plainTextURL:"https://gspe21-ssl.ls.apple.com/html/attribution-274.txt",plainTextURLSHA256Checksum:{0:95,1:21,2:102,3:110,4:8,5:247,6:232,7:236,8:45,9:156,10:70,11:137,12:179,13:197,14:80,15:243,16:60,17:246,18:254,19:239,20:198,21:57,22:65,23:219,24:22,25:147,26:180,27:123,28:186,29:78,30:122,31:162}},{name:"MMI",url:"https://gspe21-ssl.ls.apple.com/html/attribution-275.html",resource:[{resourceType:5,filename:"mmi-mask-2.png",checksum:{0:35,1:54,2:2,3:219,4:218,5:184,6:124,7:50,8:35,9:32,10:86,11:20,12:147,13:223,14:7,15:41,16:209,17:238,18:32,19:41},region:[],filter:[],validationMethod:0,updateMethod:0},{resourceType:5,filename:"mmi-mask-2@2x.png",checksum:{0:5,1:160,2:112,3:185,4:3,5:255,6:7,7:75,8:78,9:139,10:52,11:81,12:151,13:231,14:143,15:29,16:187,17:109,18:220,19:80},region:[],filter:[],validationMethod:0,updateMethod:0},{resourceType:5,filename:"mmi-mask-2@3x.png",checksum:{0:240,1:170,2:204,3:91,4:161,5:113,6:81,7:101,8:136,9:205,10:115,11:2,12:192,13:97,14:106,15:34,16:227,17:214,18:74,19:220},region:[],filter:[],validationMethod:0,updateMethod:0}],region:[{minX:176,minY:110,maxX:183,maxY:122,minZ:8,maxZ:21},{minX:178,minY:107,maxX:188,maxY:107,minZ:8,maxZ:21},{minX:178,minY:108,maxX:183,maxY:109,minZ:8,maxZ:21},{minX:180,minY:105,maxX:180,maxY:106,minZ:8,maxZ:21},{minX:181,minY:104,maxX:183,maxY:106,minZ:8,maxZ:21},{minX:182,minY:103,maxX:182,maxY:103,minZ:8,maxZ:21},{minX:184,minY:104,maxX:184,maxY:106,minZ:8,maxZ:21},{minX:184,minY:108,maxX:195,maxY:110,minZ:8,maxZ:21},{minX:184,minY:111,maxX:194,maxY:111,minZ:8,maxZ:21},{minX:184,minY:112,maxX:191,maxY:120,minZ:8,maxZ:21},{minX:184,minY:121,maxX:184,maxY:121,minZ:8,maxZ:21},{minX:185,minY:105,maxX:185,maxY:106,minZ:8,maxZ:21},{minX:190,minY:107,maxX:190,maxY:107,minZ:8,maxZ:21},{minX:193,minY:118,maxX:194,maxY:123,minZ:8,maxZ:21},{minX:195,minY:118,maxX:195,maxY:118,minZ:8,maxZ:21}],linkDisplayStringIndex:0},{name:"© GeoTechnologies, Inc.",url:"https://gspe21-ssl.ls.apple.com/html/attribution-275.html",resource:[],region:[{minX:218,minY:102,maxX:225,maxY:104,minZ:8,maxZ:21},{minX:221,minY:98,maxX:228,maxY:101,minZ:8,maxZ:21},{minX:226,minY:91,maxX:231,maxY:97,minZ:8,maxZ:21}],linkDisplayStringIndex:0}],urlInfoSet:[{alternateResourcesURL:[{url:"https://cdn.apple-mapkit.com/rap",supportsMultipathTCP:!1}],resourcesURL:{url:"https://gspe21-ssl.ls.apple.com/",supportsMultipathTCP:!1},searchAttributionManifestURL:{url:"https://gspe21-ssl.ls.apple.com/config/search-attribution-1262",supportsMultipathTCP:!1},directionsURL:{url:"https://gsp-ssl.ls.apple.com/directions.arpc",supportsMultipathTCP:!0,alternativeMultipathTCPPort:5228},etaURL:{url:"https://gsp-ssl.ls.apple.com/directions.arpc",supportsMultipathTCP:!0,alternativeMultipathTCPPort:5228},batchReverseGeocoderURL:{url:"https://gsp36-ssl.ls.apple.com/revgeo.arpc",supportsMultipathTCP:!1},simpleETAURL:{url:"https://gsp-ssl.ls.apple.com/directions.arpc",supportsMultipathTCP:!0,alternativeMultipathTCPPort:5228},addressCorrectionInitURL:{url:"https://gsp47-ssl.ls.apple.com/ac",supportsMultipathTCP:!1},addressCorrectionUpdateURL:{url:"https://gsp47-ssl.ls.apple.com/ac",supportsMultipathTCP:!1},problemSubmissionURL:{url:"https://sundew.ls.apple.com/v1/feedback/submission.arpc",supportsMultipathTCP:!1},problemStatusURL:{url:"https://sundew.ls.apple.com/grp/st",supportsMultipathTCP:!1},reverseGeocoderVersionsURL:{url:"https://gspe21-ssl.ls.apple.com/config/revgeo-version-11.plist",supportsMultipathTCP:!1},problemCategoriesURL:{url:"https://gspe21-ssl.ls.apple.com/config/com.apple.GEO.BusinessLocalizedCategories-424.plist",supportsMultipathTCP:!1},announcementsURL:{url:"https://gspe35-ssl.ls.apple.com/config/announcements?environment=prod",supportsMultipathTCP:!1},dispatcherURL:{url:"https://gsp-ssl.ls.apple.com/dispatcher.arpc",supportsMultipathTCP:!0,alternativeMultipathTCPPort:5228},problemOptInURL:{url:"https://sundew.ls.apple.com/grp/oi",supportsMultipathTCP:!1},abExperimentURL:{url:"https://gsp-ssl.ls.apple.com/ab.arpc",supportsMultipathTCP:!1},businessPortalBaseURL:{url:"https://mapsconnect.apple.com/business/ui/claimPlace",supportsMultipathTCP:!1},logMessageUsageURL:{url:"https://gsp64-ssl.ls.apple.com/a/v2/use",supportsMultipathTCP:!1},spatialLookupURL:{url:"https://gsp51-ssl.ls.apple.com/api/v1.0/poi/data",supportsMultipathTCP:!1},realtimeTrafficProbeURL:{url:"https://gsp9-ssl.apple.com/hvr/v2/rtloc",supportsMultipathTCP:!1},batchTrafficProbeURL:{url:"https://gsp10-ssl.ls.apple.com/hvr/v2/loc",supportsMultipathTCP:!1},proactiveRoutingURL:{url:"https://gsp-ssl-commute.ls.apple.com/directions.arpc",supportsMultipathTCP:!0,alternativeMultipathTCPPort:5228},logMessageUsageV3URL:{url:"https://gsp64-ssl.ls.apple.com/hvr/v3/use",supportsMultipathTCP:!1},backgroundDispatcherURL:{url:"https://gsp57-ssl-background.ls.apple.com/dispatcher.arpc",supportsMultipathTCP:!0,alternativeMultipathTCPPort:5228},bluePOIDispatcherURL:{url:"https://gsp57-ssl-locus.ls.apple.com/dispatcher.arpc",supportsMultipathTCP:!0,alternativeMultipathTCPPort:5228},backgroundRevGeoURL:{url:"https://gsp57-ssl-revgeo.ls.apple.com/dispatcher.arpc",supportsMultipathTCP:!1},wifiConnectionQualityProbeURL:{url:"https://gsp10-ssl.ls.apple.com/hvr/wcq",supportsMultipathTCP:!1},wifiQualityURL:{url:"https://gsp85-ssl.ls.apple.com/wifi_request",supportsMultipathTCP:!1},feedbackSubmissionURL:{url:"https://sundew.ls.apple.com/v1/feedback/submission.arpc",supportsMultipathTCP:!1},feedbackLookupURL:{url:"https://gsp-ssl.ls.apple.com/feedback.arpc",supportsMultipathTCP:!1},analyticsCohortSessionURL:{url:"https://gsp64-ssl.ls.apple.com/hvr/v3/use",supportsMultipathTCP:!1},analyticsLongSessionURL:{url:"https://gsp64-ssl.ls.apple.com/hvr/v3/use",supportsMultipathTCP:!1},analyticsShortSessionURL:{url:"https://gsp64-ssl.ls.apple.com/hvr/v3/use",supportsMultipathTCP:!1},analyticsSessionlessURL:{url:"https://gsp64-ssl.ls.apple.com/hvr/v3/use",supportsMultipathTCP:!1},webModuleBaseURL:{url:"https://maps.apple.com",supportsMultipathTCP:!1},wifiQualityTileURL:{url:"https://gspe85-ssl.ls.apple.com/wifi_request_tile",supportsMultipathTCP:!1},addressCorrectionTaggedLocationURL:{url:"https://gsp47-ssl.ls.apple.com/ac",supportsMultipathTCP:!1},proactiveAppClipURL:{url:"https://gspe79-ssl.ls.apple.com/72/v2",supportsMultipathTCP:!1},enrichmentSubmissionURL:{url:"https://sundew.ls.apple.com/v1/feedback/submission.arpc",supportsMultipathTCP:!1},ugcLogDiscardURL:{url:"https://sundew.ls.apple.com/v1/log_message",supportsMultipathTCP:!1},batchReverseGeocoderPlaceRequestURL:{url:"https://gsp36-ssl.ls.apple.com/revgeo_pr.arpc",supportsMultipathTCP:!1},pressureProbeDataURL:{url:"https://gsp10-ssl.ls.apple.com/hvr/cpr",supportsMultipathTCP:!1},poiBusynessActivityCollectionURL:{url:"https://gsp53-ssl.ls.apple.com/hvr/rt_poi_activity",supportsMultipathTCP:!1},rapWebBundleURL:{url:"https://cdn.apple-mapkit.com/rap",supportsMultipathTCP:!1},networkSelectionHarvestURL:{url:"https://gsp10-ssl.ls.apple.com/hvr/strn",supportsMultipathTCP:!1},offlineDataBatchListURL:{url:"https://gspe121-ssl.ls.apple.com/api/batchesForRegion",supportsMultipathTCP:!1},offlineDataSizeURL:{url:"https://gspe121-ssl.ls.apple.com/api/sizeForRegion",supportsMultipathTCP:!1},offlineDataDownloadBaseURL:{url:"https://gspe121-ssl.ls.apple.com",supportsMultipathTCP:!1},bcxDispatcherURL:{url:"https://gsp57-ssl-bcx.ls.apple.com/dispatcher.arpc",supportsMultipathTCP:!1}}],muninBucket:[{bucketID:2,bucketURL:"https://gspe72-ssl.ls.apple.com/mnn_us"},{bucketID:6,bucketURL:"https://gspe72-ssl.ls.apple.com/mnn_us"}]}},p={Settings:r,Configs:o},u={Switch:!0,CountryCode:"US",newsPlusUser:!0},x={Settings:u},c={Switch:!0,CountryCode:"US",canUse:!0},h={Settings:c},d={Switch:!0,CountryCode:"SG",Domains:["web","itunes","app_store","movies","restaurants","maps"],Functions:["flightutilities","lookup","mail","messages","news","safari","siri","spotlight","visualintelligence"],Safari_Smart_History:!0},X={VisualIntelligence:{enabled_domains:["pets","media","books","art","nature","landmarks"],supported_domains:["ART","BOOK","MEDIA","LANDMARK","ANIMALS","BIRDS","FOOD","SIGN_SYMBOL","AUTO_SYMBOL","DOGS","NATURE","NATURAL_LANDMARK","INSECTS","REPTILES","ALBUM","STOREFRONT","LAUNDRY_CARE_SYMBOL","CATS","OBJECT_2D","SCULPTURE","SKYLINE","MAMMALS"]}},Y={Settings:d,Configs:X},Z={Switch:"true",CountryCode:"US",MultiAccount:"false",Universal:"true"},g={Settings:Z},y={Switch:!0,"Third-Party":!1,HLSUrl:"play-edge.itunes.apple.com",ServerUrl:"play.itunes.apple.com",Tabs:["WatchNow","Originals","MLS","Sports","Kids","Store","Movies","TV","ChannelsAndApps","Library","Search"],CountryCode:{Configs:"AUTO",Settings:"AUTO",View:["SG","TW"],WatchNow:"AUTO",Channels:"AUTO",Originals:"AUTO",Sports:"US",Kids:"US",Store:"AUTO",Movies:"AUTO",TV:"AUTO",Persons:"SG",Search:"AUTO",Others:"AUTO"}},v={Locale:[["AU","en-AU"],["CA","en-CA"],["GB","en-GB"],["KR","ko-KR"],["HK","yue-Hant"],["JP","ja-JP"],["MO","zh-Hant"],["TW","zh-Hant"],["US","en-US"],["SG","zh-Hans"]],Tabs:[{title:"主页",type:"WatchNow",universalLinks:["https://tv.apple.com/watch-now","https://tv.apple.com/home"],destinationType:"Target",target:{id:"tahoma_watchnow",type:"Root",url:"https://tv.apple.com/watch-now"},isSelected:!0},{title:"Apple TV+",type:"Originals",universalLinks:["https://tv.apple.com/channel/tvs.sbd.4000","https://tv.apple.com/atv"],destinationType:"Target",target:{id:"tvs.sbd.4000",type:"Brand",url:"https://tv.apple.com/us/channel/tvs.sbd.4000"}},{title:"MLS Season Pass",type:"MLS",universalLinks:["https://tv.apple.com/mls"],destinationType:"Target",target:{id:"tvs.sbd.7000",type:"Brand",url:"https://tv.apple.com/us/channel/tvs.sbd.7000"}},{title:"体育节目",type:"Sports",universalLinks:["https://tv.apple.com/sports"],destinationType:"Target",target:{id:"tahoma_sports",type:"Root",url:"https://tv.apple.com/sports"}},{title:"儿童",type:"Kids",universalLinks:["https://tv.apple.com/kids"],destinationType:"Target",target:{id:"tahoma_kids",type:"Root",url:"https://tv.apple.com/kids"}},{title:"电影",type:"Movies",universalLinks:["https://tv.apple.com/movies"],destinationType:"Target",target:{id:"tahoma_movies",type:"Root",url:"https://tv.apple.com/movies"}},{title:"电视节目",type:"TV",universalLinks:["https://tv.apple.com/tv-shows"],destinationType:"Target",target:{id:"tahoma_tvshows",type:"Root",url:"https://tv.apple.com/tv-shows"}},{title:"商店",type:"Store",universalLinks:["https://tv.apple.com/store"],destinationType:"SubTabs",subTabs:[{title:"电影",type:"Movies",universalLinks:["https://tv.apple.com/movies"],destinationType:"Target",target:{id:"tahoma_movies",type:"Root",url:"https://tv.apple.com/movies"}},{title:"电视节目",type:"TV",universalLinks:["https://tv.apple.com/tv-shows"],destinationType:"Target",target:{id:"tahoma_tvshows",type:"Root",url:"https://tv.apple.com/tv-shows"}}]},{title:"频道和 App",destinationType:"SubTabs",subTabsPlacementType:"ExpandedList",type:"ChannelsAndApps",subTabs:[]},{title:"资料库",type:"Library",destinationType:"Client"},{title:"搜索",type:"Search",universalLinks:["https://tv.apple.com/search"],destinationType:"Target",target:{id:"tahoma_search",type:"Root",url:"https://tv.apple.com/search"}}],i18n:{WatchNow:[["en","Home"],["zh","主页"],["zh-Hans","主頁"],["zh-Hant","主頁"]],Movies:[["en","Movies"],["zh","电影"],["zh-Hans","电影"],["zh-Hant","電影"]],TV:[["en","TV"],["zh","电视节目"],["zh-Hans","电视节目"],["zh-Hant","電視節目"]],Store:[["en","Store"],["zh","商店"],["zh-Hans","商店"],["zh-Hant","商店"]],Sports:[["en","Sports"],["zh","体育节目"],["zh-Hans","体育节目"],["zh-Hant","體育節目"]],Kids:[["en","Kids"],["zh","儿童"],["zh-Hans","儿童"],["zh-Hant","兒童"]],Library:[["en","Library"],["zh","资料库"],["zh-Hans","资料库"],["zh-Hant","資料庫"]],Search:[["en","Search"],["zh","搜索"],["zh-Hans","搜索"],["zh-Hant","蒐索"]]}},T={Settings:y,Configs:v},f=Database={Default:Object.freeze({__proto__:null,Configs:m,Settings:t,default:n}),Location:Object.freeze({__proto__:null,Settings:s,default:l}),Maps:Object.freeze({__proto__:null,Configs:o,Settings:r,default:p}),News:Object.freeze({__proto__:null,Settings:u,default:x}),PrivateRelay:Object.freeze({__proto__:null,Settings:c,default:h}),Siri:Object.freeze({__proto__:null,Configs:X,Settings:d,default:Y}),TestFlight:Object.freeze({__proto__:null,Settings:Z,default:g}),TV:Object.freeze({__proto__:null,Configs:v,Settings:y,default:T})};function S(a,t,m){console.log("☑️ Set Environment Variables","");let{Settings:n,Caches:s,Configs:l}=function(a,t,m){let n=i.getItem(a,m),s={};if("undefined"!=typeof $argument&&Boolean($argument)){let i=Object.fromEntries($argument.split("&").map((e=>e.split("=").map((e=>e.replace(/\"/g,""))))));for(let a in i)e.set(s,a,i[a])}const l={Settings:m?.Default?.Settings||{},Configs:m?.Default?.Configs||{},Caches:{}};Array.isArray(t)||(t=[t]);for(let e of t)l.Settings={...l.Settings,...m?.[e]?.Settings,...s,...n?.[e]?.Settings},l.Configs={...l.Configs,...m?.[e]?.Configs},n?.[e]?.Caches&&"string"==typeof n?.[e]?.Caches&&(n[e].Caches=JSON.parse(n?.[e]?.Caches)),l.Caches={...l.Caches,...n?.[e]?.Caches};return function e(i,a){for(var t in i){var m=i[t];i[t]="object"==typeof m&&null!==m?e(m,a):a(t,m)}return i}(l.Settings,((e,i)=>("true"===i||"false"===i?i=JSON.parse(i):"string"==typeof i&&(i=i.includes(",")?i.split(",").map((e=>r(e))):r(i)),i))),l;function r(e){return e&&!isNaN(e)&&(e=parseInt(e,10)),e}}(a,t,m);if(n?.Tabs&&!Array.isArray(n?.Tabs)&&e.set(n,"Tabs",n?.Tabs?[n.Tabs.toString()]:[]),n?.Domains&&!Array.isArray(n?.Domains)&&e.set(n,"Domains",n?.Domains?[n.Domains.toString()]:[]),n?.Functions&&!Array.isArray(n?.Functions)&&e.set(n,"Functions",n?.Functions?[n.Functions.toString()]:[]),console.log(`✅ Set Environment Variables, Settings: ${typeof n}, Settings内容: ${JSON.stringify(n)}`,""),l.Storefront=new Map(l.Storefront),l.Locale&&(l.Locale=new Map(l.Locale)),l.i18n)for(let e in l.i18n)l.i18n[e]=new Map(l.i18n[e]);return{Settings:n,Caches:s,Configs:l}}const b=new a(" iRingo: 🔍 Siri v3.0.4(2) response"),U=class{static name="URI";static version="1.2.7";static about(){return console.log(`\n🟧 ${this.name} v${this.version}\n`)}static#m={scheme:"",host:"",path:"",query:{}};static parse(e){let i=e.match(/(?:(?.+):\/\/(?[^/]+))?\/?(?[^?]+)?\??(?[^?]+)?/)?.groups??null;if(i?.path?i.paths=i.path.split("/"):i.path="",i?.paths){const e=i.paths[i.paths.length-1];if(e?.includes(".")){const a=e.split(".");i.format=a[a.length-1]}}return i?.query&&(i.query=Object.fromEntries(i.query.split("&").map((e=>e.split("="))))),i}static stringify(e=this.#m){let i="";return e?.scheme&&e?.host&&(i+=e.scheme+"://"+e.host),e?.path&&(i+=e?.host?"/"+e.path:e.path),e?.query&&(i+="?"+Object.entries(e.query).map((e=>e.join("="))).join("&")),i}}.parse($request.url);b.log(`⚠ URL: ${JSON.stringify(U)}`,"");const L=$request.method,C=U.host,k=U.path;U.paths,b.log(`⚠ METHOD: ${L}`,"");const R=($response.headers?.["Content-Type"]??$response.headers?.["content-type"])?.split(";")?.[0];b.log(`⚠ FORMAT: ${R}`,""),(async()=>{const{Settings:e,Caches:i,Configs:a}=S("iRingo","Siri",f);switch(b.log(`⚠ Settings.Switch: ${e?.Switch}`,""),e.Switch){case!0:default:let i={};switch(R){case void 0:case"application/x-www-form-urlencoded":case"text/plain":default:case"application/x-mpegURL":case"application/x-mpegurl":case"application/vnd.apple.mpegurl":case"audio/mpegurl":case"text/xml":case"text/html":case"text/plist":case"application/xml":case"application/plist":case"application/x-plist":case"text/vtt":case"application/vtt":break;case"text/json":case"application/json":switch(i=JSON.parse($response.body??"{}"),C){case"api.smoot.apple.com":case"api.smoot.apple.cn":if("bag"===k){i.enabled=!0,i.feedback_enabled=!0,i?.enabled_domains&&(i.enabled_domains=[...new Set([...i?.enabled_domains??[],...e.Domains])],b.log("🎉 领域列表",`enabled_domains: ${JSON.stringify(i.enabled_domains)}`,"")),i?.scene_aware_lookup_enabled_domains&&(i.scene_aware_lookup_enabled_domains=[...new Set([...i?.scene_aware_lookup_enabled_domains??[],...e.Domains])],b.log("🎉 领域列表",`scene_aware_lookup_enabled_domains: ${JSON.stringify(i.scene_aware_lookup_enabled_domains)}`,"")),i.min_query_len=3;let t=i?.overrides;if(t){e.Functions.forEach((e=>{let i=t?.[`${e}`];i?(i.enabled=!0,i.feedback_enabled=!0):i={enabled:!0,feedback_enabled:!0}}));let i=t?.lookup;i&&(i.min_query_len=2);let m=t?.safari;m&&(m.experiments_custom_feedback_enabled=!0);let n=t?.spotlight;n&&(n.use_twolayer_ranking=!0,n.experiments_custom_feedback_enabled=!0,n.min_query_len=2,n.collect_scores=!0,n.collect_anonymous_metadata=!0);let s=t?.visualintelligence;s&&(s.enabled_domains=[...new Set([...s.enabled_domains??[],...a.VisualIntelligence.enabled_domains])],s.supported_domains=[...new Set([...s.supported_domains??[],...a.VisualIntelligence.supported_domains])])}i.safari_smart_history_enabled=!!e.Safari_Smart_History,i.smart_history_feature_feedback_enabled=!!e.Safari_Smart_History}}$response.body=JSON.stringify(i);case"application/protobuf":case"application/x-protobuf":case"application/vnd.google.protobuf":case"application/grpc":case"application/grpc+proto":case"application/octet-stream":}case!1:}})().catch((e=>b.logErr(e))).finally((()=>b.done($response))); diff --git a/js/archive/Weather.Availability.request.beta.js b/js/archive/Weather.Availability.request.beta.js deleted file mode 100644 index 331ecf47f..000000000 --- a/js/archive/Weather.Availability.request.beta.js +++ /dev/null @@ -1,111 +0,0 @@ -/* -README:https://github.com/VirgilClyne/iRingo -*/ - -const $ = new Env("🌤 Weather Availability v1.0.1-request-beta"); -const URL = new URLs(); -const DataBase = { - "Location":{ - "Settings":{"Switch":true,"PEP":{"GCC":"US"},"Services":{"Dispatcher":"AUTO","Directions":"AUTO","Traffic":"AUTO","Tiles":"AUTO"},"Geo_manifest":{"Dynamic":{"Config":{"Country_code":"CN"}}},"Config":{"Announcements":{"Environment:":"prod-cn"},"Defaults":{"LagunaBeach":true,"DrivingMultiWaypointRoutesEnabled":true,"GEOAddressCorrection":true,"LookupMaxParametersCount":true,"LocalitiesAndLandmarks":true,"PedestrianAR":true,"6694982d2b14e95815e44e970235e230":true,"OpticalHeading":true,"UseCLPedestrianMapMatchedLocations":true,"WiFiQualityNetworkDisabled":false,"WiFiQualityTileDisabled":false}}} - }, - "Weather":{ - "Settings":{"Switch":true,"NextHour":{"Switch":true},"AQI":{"Switch":true,"Mode":"WAQI Public","Location":"Station","Auth":null,"Scale":"EPA_NowCast.2207"},"Map":{"AQI":false}}, - "Configs":{"Availability":{"v2":["currentWeather","forecastDaily","forecastHourly","history","weatherChange","forecastNextHour","severeWeather","airQuality"],"v3":["currentWeather","forecastDaily","forecastHourly","weatherChange","forecastNextHour","news","weatherAlerts","weatherAlertNotifications","airQuality"]},"Pollutants":{"co":"CO","no":"NO","no2":"NO2","so2":"SO2","o3":"OZONE","nox":"NOX","pm25":"PM2.5","pm10":"PM10","other":"OTHER"},"Status":{"clear":"clear","sleet":"sleet","drizzle":"rain","rain":"rain","heavy_rain":"rain","flurries":"snow","snow":"snow","heavy_snow":"snow"},"Precipitation":{"Level":{"INVALID":-1,"NO":0,"LIGHT":1,"MODERATE":2,"HEAVY":3,"STORM":4},"Range":{"RADAR":{"NO":[0,0.031],"LIGHT":[0.031,0.25],"MODERATE":[0.25,0.35],"HEAVY":[0.35,0.48],"STORM":[0.48,1]},"MMPERHR":{"NO":[0,0.08],"LIGHT":[0.08,3.44],"MODERATE":[3.44,11.33],"HEAVY":[11.33,51.30],"STORM":[51.30,100]}}}} - }, - "Siri":{ - "Settings":{"Switch":true,"CountryCode":"SG","Domains":["web","itunes","app_store","movies","restaurants","maps"],"Functions":["flightutilities","lookup","mail","messages","news","safari","siri","spotlight","visualintelligence"],"Safari_Smart_History":true} - }, - "TV":{ - "Settings":{"Switch":true,"Third-Party":true,"Configs":{"CountryCode":"AUTO","Tabs":["WatchNow","Originals","Movies","TV","Sports","Kids","Library","Search"]},"View":{"CountryCode":["SG","TW"]},"WatchNow":{"CountryCode":"AUTO"},"Channels":{"CountryCode":"AUTO"},"Originals":{"CountryCode":"TW"},"Movies":{"CountryCode":"AUTO"},"TV":{"CountryCode":"AUTO"},"Sports":{"CountryCode":"US"},"Kids":{"CountryCode":"US"},"Persons":{"CountryCode":"SG"},"Search":{"CountryCode":"TW"},"Others":{"CountryCode":"AUTO"}}, - "Configs":{ - "Locale":{"AU":"en-AU","CA":"en-CA","GB":"en-GB","KR":"ko-KR","HK":"yue-Hant","JP":"ja-JP","MO":"zh-Hant","TW":"zh-Hant","US":"en-US","SG":"zh-Hans"}, - "Tabs":{"zh":{"WatchNow":"立即观看","Originals":"原创内容","Movies":"电影","TV":"电视节目","Store":"商店","Sports":"体育节目","Kids":"儿童","Library":"资料库","Search":"搜索"},"zh-Hans":{"WatchNow":"立即观看","Originals":"原创内容","Movies":"电影","TV":"电视节目","Store":"商店","Sports":"体育节目","Kids":"儿童","Library":"资料库","Search":"搜索"},"zh-Hant":{"WatchNow":"立即觀看","Originals":"原創內容","Movies":"電影","TV":"電視節目","Store":"商店","Sports":"體育節目","Kids":"兒童","Library":"資料庫","Search":"蒐索"},"en":{"WatchNow":"Watch Now","Originals":"Originals","Movies":"Movies","TV":"TV Shows","Store":"Store","Sports":"Sports","Kids":"Kids","Library":"Library","Search":"Search"}} - } - }, - "News":{ - "Settings":{"Switch":true,"CountryCode":"US","newsPlusUser":"AUTO"} - }, - "Default": { - "Settings":{"Switch":true}, - "Configs":{ - "Storefront":{"AF":"143610","AL":"143575","AO":"143564","AI":"143538","AG":"143540","AR":"143505","AM":"143524","AU":"143460","AT":"143445","AZ":"143568","BA":"143612","BS":"143539","BH":"143559","BB":"143541","BD":"143490","BY":"143565","BE":"143446","BZ":"143555","BJ":"143576","BM":"143542","BT":"143577","BO":"143556","BW":"143525","BR":"143503","VG":"143543","BN":"143560","BG":"143526","BF":"143578","CA":"143455","CI":"143527","CM":"143574","CV":"143580","KY":"143544","TD":"143581","CL":"143483","CN":"143465","CO":"143501","CG":"143582","CR":"143495","HR":"143494","CY":"143557","CZ":"143489","DK":"143458","DM":"143545","DO":"143508","DZ":"143563","EC":"143509","EG":"143516","SV":"143506","EE":"143518","FJ":"143583","FI":"143447","FR":"143442","GM":"143584","DE":"143443","GH":"143573","GR":"143448","GD":"143546","GT":"143504","GW":"143585","GY":"143553","HN":"143510","HK":"143463","HU":"143482","IS":"143558","IN":"143467","ID":"143476","IE":"143449","IL":"143491","IT":"143450","JM":"143511","JP":"143462","JO":"143528","KH":"143579","KR":"143466","KZ":"143517","KE":"143529","KW":"143493","KG":"143586","LA":"143587","LV":"143519","LB":"143497","LR":"143588","LT":"143520","LI":"143522","LU":"143451","MO":"143515","MK":"143530","MG":"143531","MW":"143589","MY":"143473","MV":"143488","ML":"143532","MT":"143521","MR":"143590","MU":"143533","MX":"143468","FM":"143591","MD":"143523","MN":"143592","MS":"143547","MZ":"143593","NA":"143594","NP":"143484","NL":"143452","NZ":"143461","NI":"143512","NE":"143534","NG":"143561","NO":"143457","OM":"143562","PK":"143477","PW":"143595","PA":"143485","PG":"143597","PY":"143513","PE":"143507","PH":"143474","PL":"143478","PT":"143453","QA":"143498","RO":"143487","RU":"143469","ST":"143598","SA":"143479","SN":"143535","SC":"143599","SL":"143600","SG":"143464","SK":"143496","SI":"143499","SB":"143601","ZA":"143472","KP":"143466","ES":"143454","LK":"143486","KN":"143548","LC":"143549","VC":"143550","SR":"143554","SZ":"143602","SE":"143456","CH":"143459","TW":"143470","TJ":"143603","TZ":"143572","TH":"143475","TT":"143551","TN":"143536","TR":"143480","TM":"143604","TC":"143552","AE":"143481","UG":"143537","UA":"143492","GB":"143444","US":"143441","UY":"143514","UZ":"143566","VE":"143502","VN":"143471","YE":"143571","ZW":"143605","CD":"143613","GA":"143614","GF":"143615","IQ":"143617","XK":"143624","LY":"143567","ME":"143619","MA":"143620","MM":"143570","NR":"143606","RW":"143621","RS":"143500","TO":"143608","VU":"143609","ZM":"143622"} - } - } -}; - -// headers转小写 -for (const [key, value] of Object.entries($request.headers)) { - delete $request.headers[key] - $request.headers[key.toLowerCase()] = value -}; - -/***************** Processing *****************/ -!(async () => { - const { Settings, Caches, Configs } = await setENV("iRingo", "Weather", DataBase); - if (Settings.Switch) { - await setETag("Availability", Caches); - } -})() - .catch((e) => $.logErr(e)) - .finally(() => { - if ($.isQuanX()) $.done({ headers: $request.headers }) - else $.done($request) - }) - -/***************** Async Function *****************/ -/** - * Get Environment Variables - * @link https://github.com/VirgilClyne/VirgilClyne/blob/main/function/getENV/getENV.min.js - * @author VirgilClyne - * @param {String} t - Persistent Store Key - * @param {String} e - Platform Name - * @param {Object} n - Default Database - * @return {Promise<*>} - */ -async function getENV(t,e,n){let i=$.getjson(t,n),s={};if("undefined"!=typeof $argument&&Boolean($argument)){let t=Object.fromEntries($argument.split("&").map((t=>t.split("="))));for(let e in t)f(s,e,t[e])}let g={...n?.Default?.Settings,...n?.[e]?.Settings,...i?.[e]?.Settings,...s},o={...n?.Default?.Configs,...n?.[e]?.Configs,...i?.[e]?.Configs},a=i?.[e]?.Caches||void 0;return"string"==typeof a&&(a=JSON.parse(a)),{Settings:g,Caches:a,Configs:o};function f(t,e,n){e.split(".").reduce(((t,i,s)=>t[i]=e.split(".").length===++s?n:t[i]||{}),t)}} - -/** - * Set Environment Variables - * @author VirgilClyne - * @param {String} name - Persistent Store Key - * @param {String} platform - Platform Name - * @param {Object} database - Default DataBase - * @return {Promise<*>} - */ -async function setENV(name, platform, database) { - $.log(`⚠ ${$.name}, Set Environment Variables`, ""); - let { Settings, Caches = {}, Configs } = await getENV(name, platform, database); - /***************** Prase *****************/ - Settings.Switch = JSON.parse(Settings.Switch) // BoxJs字符串转Boolean - Settings.NextHour.Switch = JSON.parse(Settings.NextHour.Switch) // BoxJs字符串转Boolean - Settings.AQI.Switch = JSON.parse(Settings.AQI.Switch) // BoxJs字符串转Boolean - Settings.Map.AQI = JSON.parse(Settings.Map.AQI) // BoxJs字符串转Boolean - $.log(`🎉 ${$.name}, Set Environment Variables`, `Settings: ${typeof Settings}`, `Settings内容: ${JSON.stringify(Settings)}`, ""); - return { Settings, Caches, Configs } -}; - -/** - * Set ETag - * @author VirgilClyne - * @param {String} name - Config Name - * @param {Object} caches - Caches - * @return {Promise<*>} - */ - async function setETag(name, caches) { - $.log(`⚠ ${$.name}, Set ETag`, `caches.${name}.ETag = ${caches?.[name]?.ETag}`, ""); - if ($request?.headers?.["if-none-match"] !== caches?.[name]?.ETag) { - let newCaches = caches; - newCaches[name] = { "ETag": $request?.headers?.["if-none-match"] } - $.setjson(newCaches, "@iRingo.Location.Caches"); - $request.headers["if-none-match"] = `\"${$request.headers["if-none-match"].replace(/\"/g, "")}_\"` - } - return $.log(`🎉 ${$.name}, Set ETag`, `if-none-match = ${$request?.headers?.["if-none-match"]}`, ""); -}; - -/***************** Env *****************/ -// prettier-ignore -// https://github.com/chavyleung/scripts/blob/master/Env.min.js -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}isStash(){return"undefined"!=typeof $environment&&$environment["stash-version"]}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,a]=i.split("@"),n={url:`http://${a}/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),a=i?"null"===o?null:o||"{}":"{}";try{const e=JSON.parse(a);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?s.status:s.statusCode,s.status=s.statusCode),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&&t.error||"UndefinedError"));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:a}=t,n=s.decode(a,this.encoding);e(null,{status:i,statusCode:r,headers:o,rawBody:a,body:n},n)},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?s.status:s.statusCode,s.status=s.statusCode),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&&t.error||"UndefinedError"));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:a}=t,n=i.decode(a,this.encoding);e(null,{status:s,statusCode:r,headers:o,rawBody:a,body:n},n)},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,i=t["update-pasteboard"]||t.updatePasteboard;return{"open-url":e,"media-url":s,"update-pasteboard":i}}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):this.isNode()&&process.exit(1)}}(t,e)} - -// https://github.com/VirgilClyne/VirgilClyne/blob/main/function/URL/URLs.embedded.min.js -function URLs(s){return new class{constructor(s=[]){this.name="URL v1.0.1",this.opts=s,this.json={url:{scheme:"",host:"",path:""},params:{}}}parse(s){let t=s.match(/(?.+):\/\/(?[^/]+)\/?(?[^?]+)?\??(?.*)?/)?.groups??null;return t?.path||(t.path=""),t?.params&&(t.params=Object.fromEntries(t.params.split("&").map((s=>s.split("="))))),t}stringify(s=this.json){return s?.params?s.scheme+"://"+s.host+"/"+s.path+"?"+Object.entries(s.params).map((s=>s.join("="))).join("&"):s.scheme+"://"+s.host+"/"+s.path}}(s)} diff --git a/js/archive/Weather.Availability.request.js b/js/archive/Weather.Availability.request.js deleted file mode 100644 index 12f71b09d..000000000 --- a/js/archive/Weather.Availability.request.js +++ /dev/null @@ -1,111 +0,0 @@ -/* -README:https://github.com/VirgilClyne/iRingo -*/ - -const $ = new Env("🌤 Weather Availability v1.0.1-request"); -const URL = new URLs(); -const DataBase = { - "Location":{ - "Settings":{"Switch":true,"PEP":{"GCC":"US"},"Services":{"Dispatcher":"AUTO","Directions":"AUTO","Traffic":"AUTO","Tiles":"AUTO"},"Geo_manifest":{"Dynamic":{"Config":{"Country_code":"CN"}}},"Config":{"Announcements":{"Environment:":"prod-cn"},"Defaults":{"LagunaBeach":true,"DrivingMultiWaypointRoutesEnabled":true,"GEOAddressCorrection":true,"LookupMaxParametersCount":true,"LocalitiesAndLandmarks":true,"PedestrianAR":true,"6694982d2b14e95815e44e970235e230":true,"OpticalHeading":true,"UseCLPedestrianMapMatchedLocations":true,"WiFiQualityNetworkDisabled":false,"WiFiQualityTileDisabled":false}}} - }, - "Weather":{ - "Settings":{"Switch":true,"NextHour":{"Switch":true},"AQI":{"Switch":true,"Mode":"WAQI Public","Location":"Station","Auth":null,"Scale":"EPA_NowCast.2207"},"Map":{"AQI":false}}, - "Configs":{"Availability":{"v2":["currentWeather","forecastDaily","forecastHourly","history","weatherChange","forecastNextHour","severeWeather","airQuality"],"v3":["currentWeather","forecastDaily","forecastHourly","weatherChange","forecastNextHour","news","weatherAlerts","weatherAlertNotifications","airQuality"]},"Pollutants":{"co":"CO","no":"NO","no2":"NO2","so2":"SO2","o3":"OZONE","nox":"NOX","pm25":"PM2.5","pm10":"PM10","other":"OTHER"},"Status":{"clear":"clear","sleet":"sleet","drizzle":"rain","rain":"rain","heavy_rain":"rain","flurries":"snow","snow":"snow","heavy_snow":"snow"},"Precipitation":{"Level":{"INVALID":-1,"NO":0,"LIGHT":1,"MODERATE":2,"HEAVY":3,"STORM":4},"Range":{"RADAR":{"NO":[0,0.031],"LIGHT":[0.031,0.25],"MODERATE":[0.25,0.35],"HEAVY":[0.35,0.48],"STORM":[0.48,1]},"MMPERHR":{"NO":[0,0.08],"LIGHT":[0.08,3.44],"MODERATE":[3.44,11.33],"HEAVY":[11.33,51.30],"STORM":[51.30,100]}}}} - }, - "Siri":{ - "Settings":{"Switch":true,"CountryCode":"SG","Domains":["web","itunes","app_store","movies","restaurants","maps"],"Functions":["flightutilities","lookup","mail","messages","news","safari","siri","spotlight","visualintelligence"],"Safari_Smart_History":true} - }, - "TV":{ - "Settings":{"Switch":true,"Third-Party":true,"Configs":{"CountryCode":"AUTO","Tabs":["WatchNow","Originals","Movies","TV","Sports","Kids","Library","Search"]},"View":{"CountryCode":["SG","TW"]},"WatchNow":{"CountryCode":"AUTO"},"Channels":{"CountryCode":"AUTO"},"Originals":{"CountryCode":"TW"},"Movies":{"CountryCode":"AUTO"},"TV":{"CountryCode":"AUTO"},"Sports":{"CountryCode":"US"},"Kids":{"CountryCode":"US"},"Persons":{"CountryCode":"SG"},"Search":{"CountryCode":"TW"},"Others":{"CountryCode":"AUTO"}}, - "Configs":{ - "Locale":{"AU":"en-AU","CA":"en-CA","GB":"en-GB","KR":"ko-KR","HK":"yue-Hant","JP":"ja-JP","MO":"zh-Hant","TW":"zh-Hant","US":"en-US","SG":"zh-Hans"}, - "Tabs":{"zh":{"WatchNow":"立即观看","Originals":"原创内容","Movies":"电影","TV":"电视节目","Store":"商店","Sports":"体育节目","Kids":"儿童","Library":"资料库","Search":"搜索"},"zh-Hans":{"WatchNow":"立即观看","Originals":"原创内容","Movies":"电影","TV":"电视节目","Store":"商店","Sports":"体育节目","Kids":"儿童","Library":"资料库","Search":"搜索"},"zh-Hant":{"WatchNow":"立即觀看","Originals":"原創內容","Movies":"電影","TV":"電視節目","Store":"商店","Sports":"體育節目","Kids":"兒童","Library":"資料庫","Search":"蒐索"},"en":{"WatchNow":"Watch Now","Originals":"Originals","Movies":"Movies","TV":"TV Shows","Store":"Store","Sports":"Sports","Kids":"Kids","Library":"Library","Search":"Search"}} - } - }, - "News":{ - "Settings":{"Switch":true,"CountryCode":"US","newsPlusUser":"AUTO"} - }, - "Default": { - "Settings":{"Switch":true}, - "Configs":{ - "Storefront":{"AF":"143610","AL":"143575","AO":"143564","AI":"143538","AG":"143540","AR":"143505","AM":"143524","AU":"143460","AT":"143445","AZ":"143568","BA":"143612","BS":"143539","BH":"143559","BB":"143541","BD":"143490","BY":"143565","BE":"143446","BZ":"143555","BJ":"143576","BM":"143542","BT":"143577","BO":"143556","BW":"143525","BR":"143503","VG":"143543","BN":"143560","BG":"143526","BF":"143578","CA":"143455","CI":"143527","CM":"143574","CV":"143580","KY":"143544","TD":"143581","CL":"143483","CN":"143465","CO":"143501","CG":"143582","CR":"143495","HR":"143494","CY":"143557","CZ":"143489","DK":"143458","DM":"143545","DO":"143508","DZ":"143563","EC":"143509","EG":"143516","SV":"143506","EE":"143518","FJ":"143583","FI":"143447","FR":"143442","GM":"143584","DE":"143443","GH":"143573","GR":"143448","GD":"143546","GT":"143504","GW":"143585","GY":"143553","HN":"143510","HK":"143463","HU":"143482","IS":"143558","IN":"143467","ID":"143476","IE":"143449","IL":"143491","IT":"143450","JM":"143511","JP":"143462","JO":"143528","KH":"143579","KR":"143466","KZ":"143517","KE":"143529","KW":"143493","KG":"143586","LA":"143587","LV":"143519","LB":"143497","LR":"143588","LT":"143520","LI":"143522","LU":"143451","MO":"143515","MK":"143530","MG":"143531","MW":"143589","MY":"143473","MV":"143488","ML":"143532","MT":"143521","MR":"143590","MU":"143533","MX":"143468","FM":"143591","MD":"143523","MN":"143592","MS":"143547","MZ":"143593","NA":"143594","NP":"143484","NL":"143452","NZ":"143461","NI":"143512","NE":"143534","NG":"143561","NO":"143457","OM":"143562","PK":"143477","PW":"143595","PA":"143485","PG":"143597","PY":"143513","PE":"143507","PH":"143474","PL":"143478","PT":"143453","QA":"143498","RO":"143487","RU":"143469","ST":"143598","SA":"143479","SN":"143535","SC":"143599","SL":"143600","SG":"143464","SK":"143496","SI":"143499","SB":"143601","ZA":"143472","KP":"143466","ES":"143454","LK":"143486","KN":"143548","LC":"143549","VC":"143550","SR":"143554","SZ":"143602","SE":"143456","CH":"143459","TW":"143470","TJ":"143603","TZ":"143572","TH":"143475","TT":"143551","TN":"143536","TR":"143480","TM":"143604","TC":"143552","AE":"143481","UG":"143537","UA":"143492","GB":"143444","US":"143441","UY":"143514","UZ":"143566","VE":"143502","VN":"143471","YE":"143571","ZW":"143605","CD":"143613","GA":"143614","GF":"143615","IQ":"143617","XK":"143624","LY":"143567","ME":"143619","MA":"143620","MM":"143570","NR":"143606","RW":"143621","RS":"143500","TO":"143608","VU":"143609","ZM":"143622"} - } - } -}; - -// headers转小写 -for (const [key, value] of Object.entries($request.headers)) { - delete $request.headers[key] - $request.headers[key.toLowerCase()] = value -}; - -/***************** Processing *****************/ -!(async () => { - const { Settings, Caches, Configs } = await setENV("iRingo", "Weather", DataBase); - if (Settings.Switch) { - await setETag("Availability", Caches); - } -})() - .catch((e) => $.logErr(e)) - .finally(() => { - if ($.isQuanX()) $.done({ headers: $request.headers }) - else $.done($request) - }) - -/***************** Async Function *****************/ -/** - * Get Environment Variables - * @link https://github.com/VirgilClyne/VirgilClyne/blob/main/function/getENV/getENV.min.js - * @author VirgilClyne - * @param {String} t - Persistent Store Key - * @param {String} e - Platform Name - * @param {Object} n - Default Database - * @return {Promise<*>} - */ -async function getENV(t,e,n){let i=$.getjson(t,n),s={};if("undefined"!=typeof $argument&&Boolean($argument)){let t=Object.fromEntries($argument.split("&").map((t=>t.split("="))));for(let e in t)f(s,e,t[e])}let g={...n?.Default?.Settings,...n?.[e]?.Settings,...i?.[e]?.Settings,...s},o={...n?.Default?.Configs,...n?.[e]?.Configs,...i?.[e]?.Configs},a=i?.[e]?.Caches||void 0;return"string"==typeof a&&(a=JSON.parse(a)),{Settings:g,Caches:a,Configs:o};function f(t,e,n){e.split(".").reduce(((t,i,s)=>t[i]=e.split(".").length===++s?n:t[i]||{}),t)}} - -/** - * Set Environment Variables - * @author VirgilClyne - * @param {String} name - Persistent Store Key - * @param {String} platform - Platform Name - * @param {Object} database - Default DataBase - * @return {Promise<*>} - */ -async function setENV(name, platform, database) { - $.log(`⚠ ${$.name}, Set Environment Variables`, ""); - let { Settings, Caches = {}, Configs } = await getENV(name, platform, database); - /***************** Prase *****************/ - Settings.Switch = JSON.parse(Settings.Switch) // BoxJs字符串转Boolean - Settings.NextHour.Switch = JSON.parse(Settings.NextHour.Switch) // BoxJs字符串转Boolean - Settings.AQI.Switch = JSON.parse(Settings.AQI.Switch) // BoxJs字符串转Boolean - Settings.Map.AQI = JSON.parse(Settings.Map.AQI) // BoxJs字符串转Boolean - $.log(`🎉 ${$.name}, Set Environment Variables`, `Settings: ${typeof Settings}`, `Settings内容: ${JSON.stringify(Settings)}`, ""); - return { Settings, Caches, Configs } -}; - -/** - * Set ETag - * @author VirgilClyne - * @param {String} name - Config Name - * @param {Object} caches - Caches - * @return {Promise<*>} - */ - async function setETag(name, caches) { - $.log(`⚠ ${$.name}, Set ETag`, `caches.${name}.ETag = ${caches?.[name]?.ETag}`, ""); - if ($request?.headers?.["if-none-match"] !== caches?.[name]?.ETag) { - let newCaches = caches; - newCaches[name] = { "ETag": $request?.headers?.["if-none-match"] } - $.setjson(newCaches, "@iRingo.Location.Caches"); - $request.headers["if-none-match"] = `\"${$request.headers["if-none-match"].replace(/\"/g, "")}_\"` - } - return $.log(`🎉 ${$.name}, Set ETag`, `if-none-match = ${$request?.headers?.["if-none-match"]}`, ""); -}; - -/***************** Env *****************/ -// prettier-ignore -// https://github.com/chavyleung/scripts/blob/master/Env.min.js -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}isStash(){return"undefined"!=typeof $environment&&$environment["stash-version"]}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,a]=i.split("@"),n={url:`http://${a}/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),a=i?"null"===o?null:o||"{}":"{}";try{const e=JSON.parse(a);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?s.status:s.statusCode,s.status=s.statusCode),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&&t.error||"UndefinedError"));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:a}=t,n=s.decode(a,this.encoding);e(null,{status:i,statusCode:r,headers:o,rawBody:a,body:n},n)},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?s.status:s.statusCode,s.status=s.statusCode),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&&t.error||"UndefinedError"));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:a}=t,n=i.decode(a,this.encoding);e(null,{status:s,statusCode:r,headers:o,rawBody:a,body:n},n)},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,i=t["update-pasteboard"]||t.updatePasteboard;return{"open-url":e,"media-url":s,"update-pasteboard":i}}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):this.isNode()&&process.exit(1)}}(t,e)} - -// https://github.com/VirgilClyne/VirgilClyne/blob/main/function/URL/URLs.embedded.min.js -function URLs(s){return new class{constructor(s=[]){this.name="URL v1.0.1",this.opts=s,this.json={url:{scheme:"",host:"",path:""},params:{}}}parse(s){let t=s.match(/(?.+):\/\/(?[^/]+)\/?(?[^?]+)?\??(?.*)?/)?.groups??null;return t?.path||(t.path=""),t?.params&&(t.params=Object.fromEntries(t.params.split("&").map((s=>s.split("="))))),t}stringify(s=this.json){return s?.params?s.scheme+"://"+s.host+"/"+s.path+"?"+Object.entries(s.params).map((s=>s.join("="))).join("&"):s.scheme+"://"+s.host+"/"+s.path}}(s)} diff --git a/js/archive/Weather.Availability.response.beta.js b/js/archive/Weather.Availability.response.beta.js deleted file mode 100644 index 4a6eb06e4..000000000 --- a/js/archive/Weather.Availability.response.beta.js +++ /dev/null @@ -1,120 +0,0 @@ -/* -README:https://github.com/VirgilClyne/iRingo -*/ -const $ = new Env("🌤 Weather Availability v1.1.3-response-beta"); -const URL = new URLs(); -const DataBase = { - "Location":{ - "Settings":{"Switch":true,"PEP":{"GCC":"US"},"Services":{"Dispatcher":"AUTO","Directions":"AUTO","Traffic":"AUTO","Tiles":"AUTO"},"Geo_manifest":{"Dynamic":{"Config":{"Country_code":"CN"}}},"Config":{"Announcements":{"Environment:":"prod-cn"},"Defaults":{"LagunaBeach":true,"DrivingMultiWaypointRoutesEnabled":true,"GEOAddressCorrection":true,"LookupMaxParametersCount":true,"LocalitiesAndLandmarks":true,"PedestrianAR":true,"6694982d2b14e95815e44e970235e230":true,"OpticalHeading":true,"UseCLPedestrianMapMatchedLocations":true,"WiFiQualityNetworkDisabled":false,"WiFiQualityTileDisabled":false}}} - }, - "Weather":{ - "Settings":{"Switch":true,"NextHour":{"Switch":true},"AQI":{"Switch":true,"Mode":"WAQI Public","Location":"Station","Auth":null,"Scale":"EPA_NowCast.2207"},"Map":{"AQI":false}}, - "Configs":{"Availability":{"v2":["currentWeather","forecastDaily","forecastHourly","history","weatherChange","forecastNextHour","severeWeather","airQuality","appLocationConfig"],"v3":["currentWeather","forecastDaily","forecastHourly","weatherChange","forecastNextHour","news","weatherAlerts","weatherAlertNotifications","airQuality"]},"Pollutants":{"co":"CO","no":"NO","no2":"NO2","so2":"SO2","o3":"OZONE","nox":"NOX","pm25":"PM2.5","pm10":"PM10","other":"OTHER"},"Status":{"clear":"clear","sleet":"sleet","drizzle":"rain","rain":"rain","heavy_rain":"rain","flurries":"snow","snow":"snow","heavy_snow":"snow"},"Precipitation":{"Level":{"INVALID":-1,"NO":0,"LIGHT":1,"MODERATE":2,"HEAVY":3,"STORM":4},"Range":{"RADAR":{"NO":[0,0.031],"LIGHT":[0.031,0.25],"MODERATE":[0.25,0.35],"HEAVY":[0.35,0.48],"STORM":[0.48,1]},"MMPERHR":{"NO":[0,0.08],"LIGHT":[0.08,3.44],"MODERATE":[3.44,11.33],"HEAVY":[11.33,51.30],"STORM":[51.30,100]}}}} - }, - "Siri":{ - "Settings":{"Switch":true,"CountryCode":"SG","Domains":["web","itunes","app_store","movies","restaurants","maps"],"Functions":["flightutilities","lookup","mail","messages","news","safari","siri","spotlight","visualintelligence"],"Safari_Smart_History":true} - }, - "TV":{ - "Settings":{"Switch":true,"Third-Party":true,"Configs":{"CountryCode":"AUTO","Tabs":["WatchNow","Originals","Movies","TV","Sports","Kids","Library","Search"]},"View":{"CountryCode":["SG","TW"]},"WatchNow":{"CountryCode":"AUTO"},"Channels":{"CountryCode":"AUTO"},"Originals":{"CountryCode":"TW"},"Movies":{"CountryCode":"AUTO"},"TV":{"CountryCode":"AUTO"},"Sports":{"CountryCode":"US"},"Kids":{"CountryCode":"US"},"Persons":{"CountryCode":"SG"},"Search":{"CountryCode":"TW"},"Others":{"CountryCode":"AUTO"}}, - "Configs":{ - "Locale":{"AU":"en-AU","CA":"en-CA","GB":"en-GB","KR":"ko-KR","HK":"yue-Hant","JP":"ja-JP","MO":"zh-Hant","TW":"zh-Hant","US":"en-US","SG":"zh-Hans"}, - "Tabs":{"zh":{"WatchNow":"立即观看","Originals":"原创内容","Movies":"电影","TV":"电视节目","Store":"商店","Sports":"体育节目","Kids":"儿童","Library":"资料库","Search":"搜索"},"zh-Hans":{"WatchNow":"立即观看","Originals":"原创内容","Movies":"电影","TV":"电视节目","Store":"商店","Sports":"体育节目","Kids":"儿童","Library":"资料库","Search":"搜索"},"zh-Hant":{"WatchNow":"立即觀看","Originals":"原創內容","Movies":"電影","TV":"電視節目","Store":"商店","Sports":"體育節目","Kids":"兒童","Library":"資料庫","Search":"蒐索"},"en":{"WatchNow":"Watch Now","Originals":"Originals","Movies":"Movies","TV":"TV Shows","Store":"Store","Sports":"Sports","Kids":"Kids","Library":"Library","Search":"Search"}} - } - }, - "News":{ - "Settings":{"Switch":true,"CountryCode":"US","newsPlusUser":"AUTO"} - }, - "Default": { - "Settings":{"Switch":true}, - "Configs":{ - "Storefront":{"AF":"143610","AL":"143575","AO":"143564","AI":"143538","AG":"143540","AR":"143505","AM":"143524","AU":"143460","AT":"143445","AZ":"143568","BA":"143612","BS":"143539","BH":"143559","BB":"143541","BD":"143490","BY":"143565","BE":"143446","BZ":"143555","BJ":"143576","BM":"143542","BT":"143577","BO":"143556","BW":"143525","BR":"143503","VG":"143543","BN":"143560","BG":"143526","BF":"143578","CA":"143455","CI":"143527","CM":"143574","CV":"143580","KY":"143544","TD":"143581","CL":"143483","CN":"143465","CO":"143501","CG":"143582","CR":"143495","HR":"143494","CY":"143557","CZ":"143489","DK":"143458","DM":"143545","DO":"143508","DZ":"143563","EC":"143509","EG":"143516","SV":"143506","EE":"143518","FJ":"143583","FI":"143447","FR":"143442","GM":"143584","DE":"143443","GH":"143573","GR":"143448","GD":"143546","GT":"143504","GW":"143585","GY":"143553","HN":"143510","HK":"143463","HU":"143482","IS":"143558","IN":"143467","ID":"143476","IE":"143449","IL":"143491","IT":"143450","JM":"143511","JP":"143462","JO":"143528","KH":"143579","KR":"143466","KZ":"143517","KE":"143529","KW":"143493","KG":"143586","LA":"143587","LV":"143519","LB":"143497","LR":"143588","LT":"143520","LI":"143522","LU":"143451","MO":"143515","MK":"143530","MG":"143531","MW":"143589","MY":"143473","MV":"143488","ML":"143532","MT":"143521","MR":"143590","MU":"143533","MX":"143468","FM":"143591","MD":"143523","MN":"143592","MS":"143547","MZ":"143593","NA":"143594","NP":"143484","NL":"143452","NZ":"143461","NI":"143512","NE":"143534","NG":"143561","NO":"143457","OM":"143562","PK":"143477","PW":"143595","PA":"143485","PG":"143597","PY":"143513","PE":"143507","PH":"143474","PL":"143478","PT":"143453","QA":"143498","RO":"143487","RU":"143469","ST":"143598","SA":"143479","SN":"143535","SC":"143599","SL":"143600","SG":"143464","SK":"143496","SI":"143499","SB":"143601","ZA":"143472","KP":"143466","ES":"143454","LK":"143486","KN":"143548","LC":"143549","VC":"143550","SR":"143554","SZ":"143602","SE":"143456","CH":"143459","TW":"143470","TJ":"143603","TZ":"143572","TH":"143475","TT":"143551","TN":"143536","TR":"143480","TM":"143604","TC":"143552","AE":"143481","UG":"143537","UA":"143492","GB":"143444","US":"143441","UY":"143514","UZ":"143566","VE":"143502","VN":"143471","YE":"143571","ZW":"143605","CD":"143613","GA":"143614","GF":"143615","IQ":"143617","XK":"143624","LY":"143567","ME":"143619","MA":"143620","MM":"143570","NR":"143606","RW":"143621","RS":"143500","TO":"143608","VU":"143609","ZM":"143622"} - } - } -}; - -/***************** Processing *****************/ -!(async () => { - const { Settings, Caches, Configs } = await setENV("iRingo", "Weather", DataBase); - if (Settings.Switch) { - $.log(`🎉 ${$.name}, 可用性检查`, ""); - if ($response.statusCode === 200 || $response.status === 200) { - $.log($response.statusCode || $response.status); - const url = URL.parse($request.url); - const parameters = getParams(url?.path); - let data = JSON.parse($response.body); - switch (parameters?.ver) { - case "v1": - case "v2": - data = Array.from(new Set([...data, ...Configs.Availability.v2])); - break; - case "v3": - data = Array.from(new Set([...data, ...Configs.Availability.v3])); - break; - default: - $.log(`❗️ ${$.name}:不支持${appleApiVersionString}版本的Apple API,您可能需要更新模块`, ''); - break; - } - $.log(`🎉 ${$.name}, 功能列表`, JSON.stringify(data), ""); - $response.body = JSON.stringify(data); - } - } -})() - .catch((e) => $.logErr(e)) - .finally(() => { - if ($.isQuanX()) $.done({ body: $response.body }) - else $.done($response) - }) - -/***************** Async Function *****************/ -/** - * Get Environment Variables - * @link https://github.com/VirgilClyne/VirgilClyne/blob/main/function/getENV/getENV.min.js - * @author VirgilClyne - * @param {String} t - Persistent Store Key - * @param {String} e - Platform Name - * @param {Object} n - Default Database - * @return {Promise<*>} - */ -async function getENV(t,e,n){let i=$.getjson(t,n),s={};if("undefined"!=typeof $argument&&Boolean($argument)){let t=Object.fromEntries($argument.split("&").map((t=>t.split("="))));for(let e in t)f(s,e,t[e])}let g={...n?.Default?.Settings,...n?.[e]?.Settings,...i?.[e]?.Settings,...s},o={...n?.Default?.Configs,...n?.[e]?.Configs,...i?.[e]?.Configs},a=i?.[e]?.Caches||void 0;return"string"==typeof a&&(a=JSON.parse(a)),{Settings:g,Caches:a,Configs:o};function f(t,e,n){e.split(".").reduce(((t,i,s)=>t[i]=e.split(".").length===++s?n:t[i]||{}),t)}} - -/** - * Set Environment Variables - * @author VirgilClyne - * @param {String} name - Persistent Store Key - * @param {String} platform - Platform Name - * @param {Object} database - Default DataBase - * @return {Promise<*>} - */ -async function setENV(name, platform, database) { - $.log(`⚠ ${$.name}, Set Environment Variables`, ""); - let { Settings, Caches = {}, Configs } = await getENV(name, platform, database); - /***************** Prase *****************/ - Settings.Switch = JSON.parse(Settings.Switch) // BoxJs字符串转Boolean - Settings.NextHour.Switch = JSON.parse(Settings.NextHour.Switch) // BoxJs字符串转Boolean - Settings.AQI.Switch = JSON.parse(Settings.AQI.Switch) // BoxJs字符串转Boolean - Settings.Map.AQI = JSON.parse(Settings.Map.AQI) // BoxJs字符串转Boolean - $.log(`🎉 ${$.name}, Set Environment Variables`, `Settings: ${typeof Settings}`, `Settings内容: ${JSON.stringify(Settings)}`, ""); - return { Settings, Caches, Configs } -}; - -/** - * Get Origin Parameters - * @author VirgilClyne - * @author WordlessEcho - * @param {String} path - Path of URL - * @return {Object.<'ver'|'language'|'lat'|'lng'|'countryCode', string>|{}} - - * `version`, `language`, `latitude`, `longitude` and `regionCode` from path. - * Empty object will be returned if type of path is invalid. - */ -function getParams(path = "") { - const regExp = /^(?v1|v2|v3)\/availability\/(?-?\d+\.\d+)\/(?-?\d+\.\d+).*/i; - return path?.match(regExp)?.groups; -}; - -/***************** Env *****************/ -// prettier-ignore -// https://github.com/chavyleung/scripts/blob/master/Env.min.js -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}isStash(){return"undefined"!=typeof $environment&&$environment["stash-version"]}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,a]=i.split("@"),n={url:`http://${a}/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),a=i?"null"===o?null:o||"{}":"{}";try{const e=JSON.parse(a);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?s.status:s.statusCode,s.status=s.statusCode),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&&t.error||"UndefinedError"));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:a}=t,n=s.decode(a,this.encoding);e(null,{status:i,statusCode:r,headers:o,rawBody:a,body:n},n)},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?s.status:s.statusCode,s.status=s.statusCode),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&&t.error||"UndefinedError"));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:a}=t,n=i.decode(a,this.encoding);e(null,{status:s,statusCode:r,headers:o,rawBody:a,body:n},n)},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,i=t["update-pasteboard"]||t.updatePasteboard;return{"open-url":e,"media-url":s,"update-pasteboard":i}}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):this.isNode()&&process.exit(1)}}(t,e)} - -// https://github.com/VirgilClyne/VirgilClyne/blob/main/function/URL/URLs.embedded.min.js -function URLs(s){return new class{constructor(s=[]){this.name="URL v1.0.1",this.opts=s,this.json={url:{scheme:"",host:"",path:""},params:{}}}parse(s){let t=s.match(/(?.+):\/\/(?[^/]+)\/?(?[^?]+)?\??(?.*)?/)?.groups??null;return t?.path||(t.path=""),t?.params&&(t.params=Object.fromEntries(t.params.split("&").map((s=>s.split("="))))),t}stringify(s=this.json){return s?.params?s.scheme+"://"+s.host+"/"+s.path+"?"+Object.entries(s.params).map((s=>s.join("="))).join("&"):s.scheme+"://"+s.host+"/"+s.path}}(s)} diff --git a/js/archive/Weather.Availability.response.js b/js/archive/Weather.Availability.response.js deleted file mode 100644 index 6ad049a20..000000000 --- a/js/archive/Weather.Availability.response.js +++ /dev/null @@ -1,120 +0,0 @@ -/* -README:https://github.com/VirgilClyne/iRingo -*/ -const $ = new Env("🌤 Weather Availability v1.1.3-response"); -const URL = new URLs(); -const DataBase = { - "Location":{ - "Settings":{"Switch":true,"PEP":{"GCC":"US"},"Services":{"Dispatcher":"AUTO","Directions":"AUTO","Traffic":"AUTO","Tiles":"AUTO"},"Geo_manifest":{"Dynamic":{"Config":{"Country_code":"CN"}}},"Config":{"Announcements":{"Environment:":"prod-cn"},"Defaults":{"LagunaBeach":true,"DrivingMultiWaypointRoutesEnabled":true,"GEOAddressCorrection":true,"LookupMaxParametersCount":true,"LocalitiesAndLandmarks":true,"PedestrianAR":true,"6694982d2b14e95815e44e970235e230":true,"OpticalHeading":true,"UseCLPedestrianMapMatchedLocations":true,"WiFiQualityNetworkDisabled":false,"WiFiQualityTileDisabled":false}}} - }, - "Weather":{ - "Settings":{"Switch":true,"NextHour":{"Switch":true},"AQI":{"Switch":true,"Mode":"WAQI Public","Location":"Station","Auth":null,"Scale":"EPA_NowCast.2207"},"Map":{"AQI":false}}, - "Configs":{"Availability":{"v2":["currentWeather","forecastDaily","forecastHourly","history","weatherChange","forecastNextHour","severeWeather","airQuality","appLocationConfig"],"v3":["currentWeather","forecastDaily","forecastHourly","weatherChange","forecastNextHour","news","weatherAlerts","weatherAlertNotifications","airQuality"]},"Pollutants":{"co":"CO","no":"NO","no2":"NO2","so2":"SO2","o3":"OZONE","nox":"NOX","pm25":"PM2.5","pm10":"PM10","other":"OTHER"},"Status":{"clear":"clear","sleet":"sleet","drizzle":"rain","rain":"rain","heavy_rain":"rain","flurries":"snow","snow":"snow","heavy_snow":"snow"},"Precipitation":{"Level":{"INVALID":-1,"NO":0,"LIGHT":1,"MODERATE":2,"HEAVY":3,"STORM":4},"Range":{"RADAR":{"NO":[0,0.031],"LIGHT":[0.031,0.25],"MODERATE":[0.25,0.35],"HEAVY":[0.35,0.48],"STORM":[0.48,1]},"MMPERHR":{"NO":[0,0.08],"LIGHT":[0.08,3.44],"MODERATE":[3.44,11.33],"HEAVY":[11.33,51.30],"STORM":[51.30,100]}}}} - }, - "Siri":{ - "Settings":{"Switch":true,"CountryCode":"SG","Domains":["web","itunes","app_store","movies","restaurants","maps"],"Functions":["flightutilities","lookup","mail","messages","news","safari","siri","spotlight","visualintelligence"],"Safari_Smart_History":true} - }, - "TV":{ - "Settings":{"Switch":true,"Third-Party":true,"Configs":{"CountryCode":"AUTO","Tabs":["WatchNow","Originals","Movies","TV","Sports","Kids","Library","Search"]},"View":{"CountryCode":["SG","TW"]},"WatchNow":{"CountryCode":"AUTO"},"Channels":{"CountryCode":"AUTO"},"Originals":{"CountryCode":"TW"},"Movies":{"CountryCode":"AUTO"},"TV":{"CountryCode":"AUTO"},"Sports":{"CountryCode":"US"},"Kids":{"CountryCode":"US"},"Persons":{"CountryCode":"SG"},"Search":{"CountryCode":"TW"},"Others":{"CountryCode":"AUTO"}}, - "Configs":{ - "Locale":{"AU":"en-AU","CA":"en-CA","GB":"en-GB","KR":"ko-KR","HK":"yue-Hant","JP":"ja-JP","MO":"zh-Hant","TW":"zh-Hant","US":"en-US","SG":"zh-Hans"}, - "Tabs":{"zh":{"WatchNow":"立即观看","Originals":"原创内容","Movies":"电影","TV":"电视节目","Store":"商店","Sports":"体育节目","Kids":"儿童","Library":"资料库","Search":"搜索"},"zh-Hans":{"WatchNow":"立即观看","Originals":"原创内容","Movies":"电影","TV":"电视节目","Store":"商店","Sports":"体育节目","Kids":"儿童","Library":"资料库","Search":"搜索"},"zh-Hant":{"WatchNow":"立即觀看","Originals":"原創內容","Movies":"電影","TV":"電視節目","Store":"商店","Sports":"體育節目","Kids":"兒童","Library":"資料庫","Search":"蒐索"},"en":{"WatchNow":"Watch Now","Originals":"Originals","Movies":"Movies","TV":"TV Shows","Store":"Store","Sports":"Sports","Kids":"Kids","Library":"Library","Search":"Search"}} - } - }, - "News":{ - "Settings":{"Switch":true,"CountryCode":"US","newsPlusUser":"AUTO"} - }, - "Default": { - "Settings":{"Switch":true}, - "Configs":{ - "Storefront":{"AF":"143610","AL":"143575","AO":"143564","AI":"143538","AG":"143540","AR":"143505","AM":"143524","AU":"143460","AT":"143445","AZ":"143568","BA":"143612","BS":"143539","BH":"143559","BB":"143541","BD":"143490","BY":"143565","BE":"143446","BZ":"143555","BJ":"143576","BM":"143542","BT":"143577","BO":"143556","BW":"143525","BR":"143503","VG":"143543","BN":"143560","BG":"143526","BF":"143578","CA":"143455","CI":"143527","CM":"143574","CV":"143580","KY":"143544","TD":"143581","CL":"143483","CN":"143465","CO":"143501","CG":"143582","CR":"143495","HR":"143494","CY":"143557","CZ":"143489","DK":"143458","DM":"143545","DO":"143508","DZ":"143563","EC":"143509","EG":"143516","SV":"143506","EE":"143518","FJ":"143583","FI":"143447","FR":"143442","GM":"143584","DE":"143443","GH":"143573","GR":"143448","GD":"143546","GT":"143504","GW":"143585","GY":"143553","HN":"143510","HK":"143463","HU":"143482","IS":"143558","IN":"143467","ID":"143476","IE":"143449","IL":"143491","IT":"143450","JM":"143511","JP":"143462","JO":"143528","KH":"143579","KR":"143466","KZ":"143517","KE":"143529","KW":"143493","KG":"143586","LA":"143587","LV":"143519","LB":"143497","LR":"143588","LT":"143520","LI":"143522","LU":"143451","MO":"143515","MK":"143530","MG":"143531","MW":"143589","MY":"143473","MV":"143488","ML":"143532","MT":"143521","MR":"143590","MU":"143533","MX":"143468","FM":"143591","MD":"143523","MN":"143592","MS":"143547","MZ":"143593","NA":"143594","NP":"143484","NL":"143452","NZ":"143461","NI":"143512","NE":"143534","NG":"143561","NO":"143457","OM":"143562","PK":"143477","PW":"143595","PA":"143485","PG":"143597","PY":"143513","PE":"143507","PH":"143474","PL":"143478","PT":"143453","QA":"143498","RO":"143487","RU":"143469","ST":"143598","SA":"143479","SN":"143535","SC":"143599","SL":"143600","SG":"143464","SK":"143496","SI":"143499","SB":"143601","ZA":"143472","KP":"143466","ES":"143454","LK":"143486","KN":"143548","LC":"143549","VC":"143550","SR":"143554","SZ":"143602","SE":"143456","CH":"143459","TW":"143470","TJ":"143603","TZ":"143572","TH":"143475","TT":"143551","TN":"143536","TR":"143480","TM":"143604","TC":"143552","AE":"143481","UG":"143537","UA":"143492","GB":"143444","US":"143441","UY":"143514","UZ":"143566","VE":"143502","VN":"143471","YE":"143571","ZW":"143605","CD":"143613","GA":"143614","GF":"143615","IQ":"143617","XK":"143624","LY":"143567","ME":"143619","MA":"143620","MM":"143570","NR":"143606","RW":"143621","RS":"143500","TO":"143608","VU":"143609","ZM":"143622"} - } - } -}; - -/***************** Processing *****************/ -!(async () => { - const { Settings, Caches, Configs } = await setENV("iRingo", "Weather", DataBase); - if (Settings.Switch) { - $.log(`🎉 ${$.name}, 可用性检查`, ""); - if ($response.statusCode === 200 || $response.status === 200) { - $.log($response.statusCode || $response.status); - const url = URL.parse($request.url); - const parameters = getParams(url?.path); - let data = JSON.parse($response.body); - switch (parameters?.ver) { - case "v1": - case "v2": - data = Array.from(new Set([...data, ...Configs.Availability.v2])); - break; - case "v3": - data = Array.from(new Set([...data, ...Configs.Availability.v3])); - break; - default: - $.log(`❗️ ${$.name}:不支持${appleApiVersionString}版本的Apple API,您可能需要更新模块`, ''); - break; - } - $.log(`🎉 ${$.name}, 功能列表`, JSON.stringify(data), ""); - $response.body = JSON.stringify(data); - } - } -})() - .catch((e) => $.logErr(e)) - .finally(() => { - if ($.isQuanX()) $.done({ body: $response.body }) - else $.done($response) - }) - -/***************** Async Function *****************/ -/** - * Get Environment Variables - * @link https://github.com/VirgilClyne/VirgilClyne/blob/main/function/getENV/getENV.min.js - * @author VirgilClyne - * @param {String} t - Persistent Store Key - * @param {String} e - Platform Name - * @param {Object} n - Default Database - * @return {Promise<*>} - */ -async function getENV(t,e,n){let i=$.getjson(t,n),s={};if("undefined"!=typeof $argument&&Boolean($argument)){let t=Object.fromEntries($argument.split("&").map((t=>t.split("="))));for(let e in t)f(s,e,t[e])}let g={...n?.Default?.Settings,...n?.[e]?.Settings,...i?.[e]?.Settings,...s},o={...n?.Default?.Configs,...n?.[e]?.Configs,...i?.[e]?.Configs},a=i?.[e]?.Caches||void 0;return"string"==typeof a&&(a=JSON.parse(a)),{Settings:g,Caches:a,Configs:o};function f(t,e,n){e.split(".").reduce(((t,i,s)=>t[i]=e.split(".").length===++s?n:t[i]||{}),t)}} - -/** - * Set Environment Variables - * @author VirgilClyne - * @param {String} name - Persistent Store Key - * @param {String} platform - Platform Name - * @param {Object} database - Default DataBase - * @return {Promise<*>} - */ -async function setENV(name, platform, database) { - $.log(`⚠ ${$.name}, Set Environment Variables`, ""); - let { Settings, Caches = {}, Configs } = await getENV(name, platform, database); - /***************** Prase *****************/ - Settings.Switch = JSON.parse(Settings.Switch) // BoxJs字符串转Boolean - Settings.NextHour.Switch = JSON.parse(Settings.NextHour.Switch) // BoxJs字符串转Boolean - Settings.AQI.Switch = JSON.parse(Settings.AQI.Switch) // BoxJs字符串转Boolean - Settings.Map.AQI = JSON.parse(Settings.Map.AQI) // BoxJs字符串转Boolean - $.log(`🎉 ${$.name}, Set Environment Variables`, `Settings: ${typeof Settings}`, `Settings内容: ${JSON.stringify(Settings)}`, ""); - return { Settings, Caches, Configs } -}; - -/** - * Get Origin Parameters - * @author VirgilClyne - * @author WordlessEcho - * @param {String} path - Path of URL - * @return {Object.<'ver'|'language'|'lat'|'lng'|'countryCode', string>|{}} - - * `version`, `language`, `latitude`, `longitude` and `regionCode` from path. - * Empty object will be returned if type of path is invalid. - */ -function getParams(path = "") { - const regExp = /^(?v1|v2|v3)\/availability\/(?-?\d+\.\d+)\/(?-?\d+\.\d+).*/i; - return path?.match(regExp)?.groups; -}; - -/***************** Env *****************/ -// prettier-ignore -// https://github.com/chavyleung/scripts/blob/master/Env.min.js -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}isStash(){return"undefined"!=typeof $environment&&$environment["stash-version"]}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,a]=i.split("@"),n={url:`http://${a}/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),a=i?"null"===o?null:o||"{}":"{}";try{const e=JSON.parse(a);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?s.status:s.statusCode,s.status=s.statusCode),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&&t.error||"UndefinedError"));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:a}=t,n=s.decode(a,this.encoding);e(null,{status:i,statusCode:r,headers:o,rawBody:a,body:n},n)},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?s.status:s.statusCode,s.status=s.statusCode),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&&t.error||"UndefinedError"));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:a}=t,n=i.decode(a,this.encoding);e(null,{status:s,statusCode:r,headers:o,rawBody:a,body:n},n)},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,i=t["update-pasteboard"]||t.updatePasteboard;return{"open-url":e,"media-url":s,"update-pasteboard":i}}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):this.isNode()&&process.exit(1)}}(t,e)} - -// https://github.com/VirgilClyne/VirgilClyne/blob/main/function/URL/URLs.embedded.min.js -function URLs(s){return new class{constructor(s=[]){this.name="URL v1.0.1",this.opts=s,this.json={url:{scheme:"",host:"",path:""},params:{}}}parse(s){let t=s.match(/(?.+):\/\/(?[^/]+)\/?(?[^?]+)?\??(?.*)?/)?.groups??null;return t?.path||(t.path=""),t?.params&&(t.params=Object.fromEntries(t.params.split("&").map((s=>s.split("="))))),t}stringify(s=this.json){return s?.params?s.scheme+"://"+s.host+"/"+s.path+"?"+Object.entries(s.params).map((s=>s.join("="))).join("&"):s.scheme+"://"+s.host+"/"+s.path}}(s)} diff --git a/js/archive/Weather.Map.request.beta.js b/js/archive/Weather.Map.request.beta.js deleted file mode 100644 index 1775122a8..000000000 --- a/js/archive/Weather.Map.request.beta.js +++ /dev/null @@ -1,107 +0,0 @@ -/* -README:https://github.com/VirgilClyne/iRingo -*/ - -const $ = new Env("🌤 Weather Map v1.2.3-request-beta"); -const URL = new URLs(); -const DataBase = { - "Location":{ - "Settings":{"Switch":true,"PEP":{"GCC":"US"},"Services":{"Dispatcher":"AUTO","Directions":"AUTO","Traffic":"AUTO","Tiles":"AUTO"},"Geo_manifest":{"Dynamic":{"Config":{"Country_code":"CN"}}},"Config":{"Announcements":{"Environment:":"prod-cn"},"Defaults":{"LagunaBeach":true,"DrivingMultiWaypointRoutesEnabled":true,"GEOAddressCorrection":true,"LookupMaxParametersCount":true,"LocalitiesAndLandmarks":true,"PedestrianAR":true,"6694982d2b14e95815e44e970235e230":true,"OpticalHeading":true,"UseCLPedestrianMapMatchedLocations":true,"WiFiQualityNetworkDisabled":false,"WiFiQualityTileDisabled":false}}} - }, - "Weather":{ - "Settings":{"Switch":true,"NextHour":{"Switch":true},"AQI":{"Switch":true,"Mode":"WAQI Public","Location":"Station","Auth":null,"Scale":"EPA_NowCast.2207"},"Map":{"AQI":false}}, - "Configs":{"Availability":{"v2":["currentWeather","forecastDaily","forecastHourly","history","weatherChange","forecastNextHour","severeWeather","airQuality"],"v3":["currentWeather","forecastDaily","forecastHourly","weatherChange","forecastNextHour","news","weatherAlerts","weatherAlertNotifications","airQuality"]},"Pollutants":{"co":"CO","no":"NO","no2":"NO2","so2":"SO2","o3":"OZONE","nox":"NOX","pm25":"PM2.5","pm10":"PM10","other":"OTHER"},"Status":{"clear":"clear","sleet":"sleet","drizzle":"rain","rain":"rain","heavy_rain":"rain","flurries":"snow","snow":"snow","heavy_snow":"snow"},"Precipitation":{"Level":{"INVALID":-1,"NO":0,"LIGHT":1,"MODERATE":2,"HEAVY":3,"STORM":4},"Range":{"RADAR":{"NO":[0,0.031],"LIGHT":[0.031,0.25],"MODERATE":[0.25,0.35],"HEAVY":[0.35,0.48],"STORM":[0.48,1]},"MMPERHR":{"NO":[0,0.08],"LIGHT":[0.08,3.44],"MODERATE":[3.44,11.33],"HEAVY":[11.33,51.30],"STORM":[51.30,100]}}}} - }, - "Siri":{ - "Settings":{"Switch":true,"CountryCode":"SG","Domains":["web","itunes","app_store","movies","restaurants","maps"],"Functions":["flightutilities","lookup","mail","messages","news","safari","siri","spotlight","visualintelligence"],"Safari_Smart_History":true} - }, - "TV":{ - "Settings":{"Switch":true,"Third-Party":true,"Configs":{"CountryCode":"AUTO","Tabs":["WatchNow","Originals","Movies","TV","Sports","Kids","Library","Search"]},"View":{"CountryCode":["SG","TW"]},"WatchNow":{"CountryCode":"AUTO"},"Channels":{"CountryCode":"AUTO"},"Originals":{"CountryCode":"TW"},"Movies":{"CountryCode":"AUTO"},"TV":{"CountryCode":"AUTO"},"Sports":{"CountryCode":"US"},"Kids":{"CountryCode":"US"},"Persons":{"CountryCode":"SG"},"Search":{"CountryCode":"TW"},"Others":{"CountryCode":"AUTO"}}, - "Configs":{ - "Locale":{"AU":"en-AU","CA":"en-CA","GB":"en-GB","KR":"ko-KR","HK":"yue-Hant","JP":"ja-JP","MO":"zh-Hant","TW":"zh-Hant","US":"en-US","SG":"zh-Hans"}, - "Tabs":{"zh":{"WatchNow":"立即观看","Originals":"原创内容","Movies":"电影","TV":"电视节目","Store":"商店","Sports":"体育节目","Kids":"儿童","Library":"资料库","Search":"搜索"},"zh-Hans":{"WatchNow":"立即观看","Originals":"原创内容","Movies":"电影","TV":"电视节目","Store":"商店","Sports":"体育节目","Kids":"儿童","Library":"资料库","Search":"搜索"},"zh-Hant":{"WatchNow":"立即觀看","Originals":"原創內容","Movies":"電影","TV":"電視節目","Store":"商店","Sports":"體育節目","Kids":"兒童","Library":"資料庫","Search":"蒐索"},"en":{"WatchNow":"Watch Now","Originals":"Originals","Movies":"Movies","TV":"TV Shows","Store":"Store","Sports":"Sports","Kids":"Kids","Library":"Library","Search":"Search"}} - } - }, - "News":{ - "Settings":{"Switch":true,"CountryCode":"US","newsPlusUser":"AUTO"} - }, - "Default": { - "Settings":{"Switch":true}, - "Configs":{ - "Storefront":{"AF":"143610","AL":"143575","AO":"143564","AI":"143538","AG":"143540","AR":"143505","AM":"143524","AU":"143460","AT":"143445","AZ":"143568","BA":"143612","BS":"143539","BH":"143559","BB":"143541","BD":"143490","BY":"143565","BE":"143446","BZ":"143555","BJ":"143576","BM":"143542","BT":"143577","BO":"143556","BW":"143525","BR":"143503","VG":"143543","BN":"143560","BG":"143526","BF":"143578","CA":"143455","CI":"143527","CM":"143574","CV":"143580","KY":"143544","TD":"143581","CL":"143483","CN":"143465","CO":"143501","CG":"143582","CR":"143495","HR":"143494","CY":"143557","CZ":"143489","DK":"143458","DM":"143545","DO":"143508","DZ":"143563","EC":"143509","EG":"143516","SV":"143506","EE":"143518","FJ":"143583","FI":"143447","FR":"143442","GM":"143584","DE":"143443","GH":"143573","GR":"143448","GD":"143546","GT":"143504","GW":"143585","GY":"143553","HN":"143510","HK":"143463","HU":"143482","IS":"143558","IN":"143467","ID":"143476","IE":"143449","IL":"143491","IT":"143450","JM":"143511","JP":"143462","JO":"143528","KH":"143579","KR":"143466","KZ":"143517","KE":"143529","KW":"143493","KG":"143586","LA":"143587","LV":"143519","LB":"143497","LR":"143588","LT":"143520","LI":"143522","LU":"143451","MO":"143515","MK":"143530","MG":"143531","MW":"143589","MY":"143473","MV":"143488","ML":"143532","MT":"143521","MR":"143590","MU":"143533","MX":"143468","FM":"143591","MD":"143523","MN":"143592","MS":"143547","MZ":"143593","NA":"143594","NP":"143484","NL":"143452","NZ":"143461","NI":"143512","NE":"143534","NG":"143561","NO":"143457","OM":"143562","PK":"143477","PW":"143595","PA":"143485","PG":"143597","PY":"143513","PE":"143507","PH":"143474","PL":"143478","PT":"143453","QA":"143498","RO":"143487","RU":"143469","ST":"143598","SA":"143479","SN":"143535","SC":"143599","SL":"143600","SG":"143464","SK":"143496","SI":"143499","SB":"143601","ZA":"143472","KP":"143466","ES":"143454","LK":"143486","KN":"143548","LC":"143549","VC":"143550","SR":"143554","SZ":"143602","SE":"143456","CH":"143459","TW":"143470","TJ":"143603","TZ":"143572","TH":"143475","TT":"143551","TN":"143536","TR":"143480","TM":"143604","TC":"143552","AE":"143481","UG":"143537","UA":"143492","GB":"143444","US":"143441","UY":"143514","UZ":"143566","VE":"143502","VN":"143471","YE":"143571","ZW":"143605","CD":"143613","GA":"143614","GF":"143615","IQ":"143617","XK":"143624","LY":"143567","ME":"143619","MA":"143620","MM":"143570","NR":"143606","RW":"143621","RS":"143500","TO":"143608","VU":"143609","ZM":"143622"} - } - } -}; - -// headers转小写 -for (const [key, value] of Object.entries($request.headers)) { - delete $request.headers[key] - $request.headers[key.toLowerCase()] = value -}; - -/***************** Processing *****************/ -!(async () => { - const { Settings, Caches, Configs } = await setENV("iRingo", "Weather", DataBase); - if (Settings.Switch) { - if (Settings.Map.AQI) { - let url = URL.parse($request.url); - $.log(url.path); - switch (url.path) { - case "v1/mapOverlay/airQuality": - url.host = "tiles.waqi.info" - url.path = `tiles/usepa-aqi/${url.params?.z}/${url.params?.x}/${url.params?.y}.png` - delete url.params - break; - default: - break; - } - if ($request?.headers?.host) $request.headers.host = url.host; - $request.url = URL.stringify(url); - } - } -})() - .catch((e) => $.logErr(e)) - .finally(() => { - if ($.isQuanX()) $.done({ url: $request.url, headers: $request.headers }) - else $.done($request) - }) - -/***************** Async Function *****************/ -/** - * Get Environment Variables - * @link https://github.com/VirgilClyne/VirgilClyne/blob/main/function/getENV/getENV.min.js - * @author VirgilClyne - * @param {String} t - Persistent Store Key - * @param {String} e - Platform Name - * @param {Object} n - Default Database - * @return {Promise<*>} - */ -async function getENV(t,e,n){let i=$.getjson(t,n),s={};if("undefined"!=typeof $argument&&Boolean($argument)){let t=Object.fromEntries($argument.split("&").map((t=>t.split("="))));for(let e in t)f(s,e,t[e])}let g={...n?.Default?.Settings,...n?.[e]?.Settings,...i?.[e]?.Settings,...s},o={...n?.Default?.Configs,...n?.[e]?.Configs,...i?.[e]?.Configs},a=i?.[e]?.Caches||void 0;return"string"==typeof a&&(a=JSON.parse(a)),{Settings:g,Caches:a,Configs:o};function f(t,e,n){e.split(".").reduce(((t,i,s)=>t[i]=e.split(".").length===++s?n:t[i]||{}),t)}} - -/** - * Set Environment Variables - * @author VirgilClyne - * @param {String} name - Persistent Store Key - * @param {String} platform - Platform Name - * @param {Object} database - Default DataBase - * @return {Promise<*>} - */ -async function setENV(name, platform, database) { - $.log(`⚠ ${$.name}, Set Environment Variables`, ""); - let { Settings, Caches = {}, Configs } = await getENV(name, platform, database); - /***************** Prase *****************/ - Settings.Switch = JSON.parse(Settings.Switch) // BoxJs字符串转Boolean - Settings.NextHour.Switch = JSON.parse(Settings.NextHour.Switch) // BoxJs字符串转Boolean - Settings.AQI.Switch = JSON.parse(Settings.AQI.Switch) // BoxJs字符串转Boolean - Settings.Map.AQI = JSON.parse(Settings.Map.AQI) // BoxJs字符串转Boolean - $.log(`🎉 ${$.name}, Set Environment Variables`, `Settings: ${typeof Settings}`, `Settings内容: ${JSON.stringify(Settings)}`, ""); - return { Settings, Caches, Configs } -}; - -/***************** Env *****************/ -// prettier-ignore -// https://github.com/chavyleung/scripts/blob/master/Env.min.js -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}isStash(){return"undefined"!=typeof $environment&&$environment["stash-version"]}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,a]=i.split("@"),n={url:`http://${a}/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),a=i?"null"===o?null:o||"{}":"{}";try{const e=JSON.parse(a);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?s.status:s.statusCode,s.status=s.statusCode),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&&t.error||"UndefinedError"));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:a}=t,n=s.decode(a,this.encoding);e(null,{status:i,statusCode:r,headers:o,rawBody:a,body:n},n)},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?s.status:s.statusCode,s.status=s.statusCode),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&&t.error||"UndefinedError"));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:a}=t,n=i.decode(a,this.encoding);e(null,{status:s,statusCode:r,headers:o,rawBody:a,body:n},n)},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,i=t["update-pasteboard"]||t.updatePasteboard;return{"open-url":e,"media-url":s,"update-pasteboard":i}}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):this.isNode()&&process.exit(1)}}(t,e)} - -// https://github.com/VirgilClyne/VirgilClyne/blob/main/function/URL/URLs.embedded.min.js -function URLs(s){return new class{constructor(s=[]){this.name="URL v1.0.1",this.opts=s,this.json={url:{scheme:"",host:"",path:""},params:{}}}parse(s){let t=s.match(/(?.+):\/\/(?[^/]+)\/?(?[^?]+)?\??(?.*)?/)?.groups??null;return t?.path||(t.path=""),t?.params&&(t.params=Object.fromEntries(t.params.split("&").map((s=>s.split("="))))),t}stringify(s=this.json){return s?.params?s.scheme+"://"+s.host+"/"+s.path+"?"+Object.entries(s.params).map((s=>s.join("="))).join("&"):s.scheme+"://"+s.host+"/"+s.path}}(s)} diff --git a/js/archive/Weather.Map.request.js b/js/archive/Weather.Map.request.js deleted file mode 100644 index b5d6db5bc..000000000 --- a/js/archive/Weather.Map.request.js +++ /dev/null @@ -1,107 +0,0 @@ -/* -README:https://github.com/VirgilClyne/iRingo -*/ - -const $ = new Env("🌤 Weather Map v1.2.3-request"); -const URL = new URLs(); -const DataBase = { - "Location":{ - "Settings":{"Switch":true,"PEP":{"GCC":"US"},"Services":{"Dispatcher":"AUTO","Directions":"AUTO","Traffic":"AUTO","Tiles":"AUTO"},"Geo_manifest":{"Dynamic":{"Config":{"Country_code":"CN"}}},"Config":{"Announcements":{"Environment:":"prod-cn"},"Defaults":{"LagunaBeach":true,"DrivingMultiWaypointRoutesEnabled":true,"GEOAddressCorrection":true,"LookupMaxParametersCount":true,"LocalitiesAndLandmarks":true,"PedestrianAR":true,"6694982d2b14e95815e44e970235e230":true,"OpticalHeading":true,"UseCLPedestrianMapMatchedLocations":true,"WiFiQualityNetworkDisabled":false,"WiFiQualityTileDisabled":false}}} - }, - "Weather":{ - "Settings":{"Switch":true,"NextHour":{"Switch":true},"AQI":{"Switch":true,"Mode":"WAQI Public","Location":"Station","Auth":null,"Scale":"EPA_NowCast.2207"},"Map":{"AQI":false}}, - "Configs":{"Availability":{"v2":["currentWeather","forecastDaily","forecastHourly","history","weatherChange","forecastNextHour","severeWeather","airQuality"],"v3":["currentWeather","forecastDaily","forecastHourly","weatherChange","forecastNextHour","news","weatherAlerts","weatherAlertNotifications","airQuality"]},"Pollutants":{"co":"CO","no":"NO","no2":"NO2","so2":"SO2","o3":"OZONE","nox":"NOX","pm25":"PM2.5","pm10":"PM10","other":"OTHER"},"Status":{"clear":"clear","sleet":"sleet","drizzle":"rain","rain":"rain","heavy_rain":"rain","flurries":"snow","snow":"snow","heavy_snow":"snow"},"Precipitation":{"Level":{"INVALID":-1,"NO":0,"LIGHT":1,"MODERATE":2,"HEAVY":3,"STORM":4},"Range":{"RADAR":{"NO":[0,0.031],"LIGHT":[0.031,0.25],"MODERATE":[0.25,0.35],"HEAVY":[0.35,0.48],"STORM":[0.48,1]},"MMPERHR":{"NO":[0,0.08],"LIGHT":[0.08,3.44],"MODERATE":[3.44,11.33],"HEAVY":[11.33,51.30],"STORM":[51.30,100]}}}} - }, - "Siri":{ - "Settings":{"Switch":true,"CountryCode":"SG","Domains":["web","itunes","app_store","movies","restaurants","maps"],"Functions":["flightutilities","lookup","mail","messages","news","safari","siri","spotlight","visualintelligence"],"Safari_Smart_History":true} - }, - "TV":{ - "Settings":{"Switch":true,"Third-Party":true,"Configs":{"CountryCode":"AUTO","Tabs":["WatchNow","Originals","Movies","TV","Sports","Kids","Library","Search"]},"View":{"CountryCode":["SG","TW"]},"WatchNow":{"CountryCode":"AUTO"},"Channels":{"CountryCode":"AUTO"},"Originals":{"CountryCode":"TW"},"Movies":{"CountryCode":"AUTO"},"TV":{"CountryCode":"AUTO"},"Sports":{"CountryCode":"US"},"Kids":{"CountryCode":"US"},"Persons":{"CountryCode":"SG"},"Search":{"CountryCode":"TW"},"Others":{"CountryCode":"AUTO"}}, - "Configs":{ - "Locale":{"AU":"en-AU","CA":"en-CA","GB":"en-GB","KR":"ko-KR","HK":"yue-Hant","JP":"ja-JP","MO":"zh-Hant","TW":"zh-Hant","US":"en-US","SG":"zh-Hans"}, - "Tabs":{"zh":{"WatchNow":"立即观看","Originals":"原创内容","Movies":"电影","TV":"电视节目","Store":"商店","Sports":"体育节目","Kids":"儿童","Library":"资料库","Search":"搜索"},"zh-Hans":{"WatchNow":"立即观看","Originals":"原创内容","Movies":"电影","TV":"电视节目","Store":"商店","Sports":"体育节目","Kids":"儿童","Library":"资料库","Search":"搜索"},"zh-Hant":{"WatchNow":"立即觀看","Originals":"原創內容","Movies":"電影","TV":"電視節目","Store":"商店","Sports":"體育節目","Kids":"兒童","Library":"資料庫","Search":"蒐索"},"en":{"WatchNow":"Watch Now","Originals":"Originals","Movies":"Movies","TV":"TV Shows","Store":"Store","Sports":"Sports","Kids":"Kids","Library":"Library","Search":"Search"}} - } - }, - "News":{ - "Settings":{"Switch":true,"CountryCode":"US","newsPlusUser":"AUTO"} - }, - "Default": { - "Settings":{"Switch":true}, - "Configs":{ - "Storefront":{"AF":"143610","AL":"143575","AO":"143564","AI":"143538","AG":"143540","AR":"143505","AM":"143524","AU":"143460","AT":"143445","AZ":"143568","BA":"143612","BS":"143539","BH":"143559","BB":"143541","BD":"143490","BY":"143565","BE":"143446","BZ":"143555","BJ":"143576","BM":"143542","BT":"143577","BO":"143556","BW":"143525","BR":"143503","VG":"143543","BN":"143560","BG":"143526","BF":"143578","CA":"143455","CI":"143527","CM":"143574","CV":"143580","KY":"143544","TD":"143581","CL":"143483","CN":"143465","CO":"143501","CG":"143582","CR":"143495","HR":"143494","CY":"143557","CZ":"143489","DK":"143458","DM":"143545","DO":"143508","DZ":"143563","EC":"143509","EG":"143516","SV":"143506","EE":"143518","FJ":"143583","FI":"143447","FR":"143442","GM":"143584","DE":"143443","GH":"143573","GR":"143448","GD":"143546","GT":"143504","GW":"143585","GY":"143553","HN":"143510","HK":"143463","HU":"143482","IS":"143558","IN":"143467","ID":"143476","IE":"143449","IL":"143491","IT":"143450","JM":"143511","JP":"143462","JO":"143528","KH":"143579","KR":"143466","KZ":"143517","KE":"143529","KW":"143493","KG":"143586","LA":"143587","LV":"143519","LB":"143497","LR":"143588","LT":"143520","LI":"143522","LU":"143451","MO":"143515","MK":"143530","MG":"143531","MW":"143589","MY":"143473","MV":"143488","ML":"143532","MT":"143521","MR":"143590","MU":"143533","MX":"143468","FM":"143591","MD":"143523","MN":"143592","MS":"143547","MZ":"143593","NA":"143594","NP":"143484","NL":"143452","NZ":"143461","NI":"143512","NE":"143534","NG":"143561","NO":"143457","OM":"143562","PK":"143477","PW":"143595","PA":"143485","PG":"143597","PY":"143513","PE":"143507","PH":"143474","PL":"143478","PT":"143453","QA":"143498","RO":"143487","RU":"143469","ST":"143598","SA":"143479","SN":"143535","SC":"143599","SL":"143600","SG":"143464","SK":"143496","SI":"143499","SB":"143601","ZA":"143472","KP":"143466","ES":"143454","LK":"143486","KN":"143548","LC":"143549","VC":"143550","SR":"143554","SZ":"143602","SE":"143456","CH":"143459","TW":"143470","TJ":"143603","TZ":"143572","TH":"143475","TT":"143551","TN":"143536","TR":"143480","TM":"143604","TC":"143552","AE":"143481","UG":"143537","UA":"143492","GB":"143444","US":"143441","UY":"143514","UZ":"143566","VE":"143502","VN":"143471","YE":"143571","ZW":"143605","CD":"143613","GA":"143614","GF":"143615","IQ":"143617","XK":"143624","LY":"143567","ME":"143619","MA":"143620","MM":"143570","NR":"143606","RW":"143621","RS":"143500","TO":"143608","VU":"143609","ZM":"143622"} - } - } -}; - -// headers转小写 -for (const [key, value] of Object.entries($request.headers)) { - delete $request.headers[key] - $request.headers[key.toLowerCase()] = value -}; - -/***************** Processing *****************/ -!(async () => { - const { Settings, Caches, Configs } = await setENV("iRingo", "Weather", DataBase); - if (Settings.Switch) { - if (Settings.Map.AQI) { - let url = URL.parse($request.url); - $.log(url.path); - switch (url.path) { - case "v1/mapOverlay/airQuality": - url.host = "tiles.waqi.info" - url.path = `tiles/usepa-aqi/${url.params?.z}/${url.params?.x}/${url.params?.y}.png` - delete url.params - break; - default: - break; - } - if ($request?.headers?.host) $request.headers.host = url.host; - $request.url = URL.stringify(url); - } - } -})() - .catch((e) => $.logErr(e)) - .finally(() => { - if ($.isQuanX()) $.done({ url: $request.url, headers: $request.headers }) - else $.done($request) - }) - -/***************** Async Function *****************/ -/** - * Get Environment Variables - * @link https://github.com/VirgilClyne/VirgilClyne/blob/main/function/getENV/getENV.min.js - * @author VirgilClyne - * @param {String} t - Persistent Store Key - * @param {String} e - Platform Name - * @param {Object} n - Default Database - * @return {Promise<*>} - */ -async function getENV(t,e,n){let i=$.getjson(t,n),s={};if("undefined"!=typeof $argument&&Boolean($argument)){let t=Object.fromEntries($argument.split("&").map((t=>t.split("="))));for(let e in t)f(s,e,t[e])}let g={...n?.Default?.Settings,...n?.[e]?.Settings,...i?.[e]?.Settings,...s},o={...n?.Default?.Configs,...n?.[e]?.Configs,...i?.[e]?.Configs},a=i?.[e]?.Caches||void 0;return"string"==typeof a&&(a=JSON.parse(a)),{Settings:g,Caches:a,Configs:o};function f(t,e,n){e.split(".").reduce(((t,i,s)=>t[i]=e.split(".").length===++s?n:t[i]||{}),t)}} - -/** - * Set Environment Variables - * @author VirgilClyne - * @param {String} name - Persistent Store Key - * @param {String} platform - Platform Name - * @param {Object} database - Default DataBase - * @return {Promise<*>} - */ -async function setENV(name, platform, database) { - $.log(`⚠ ${$.name}, Set Environment Variables`, ""); - let { Settings, Caches = {}, Configs } = await getENV(name, platform, database); - /***************** Prase *****************/ - Settings.Switch = JSON.parse(Settings.Switch) // BoxJs字符串转Boolean - Settings.NextHour.Switch = JSON.parse(Settings.NextHour.Switch) // BoxJs字符串转Boolean - Settings.AQI.Switch = JSON.parse(Settings.AQI.Switch) // BoxJs字符串转Boolean - Settings.Map.AQI = JSON.parse(Settings.Map.AQI) // BoxJs字符串转Boolean - $.log(`🎉 ${$.name}, Set Environment Variables`, `Settings: ${typeof Settings}`, `Settings内容: ${JSON.stringify(Settings)}`, ""); - return { Settings, Caches, Configs } -}; - -/***************** Env *****************/ -// prettier-ignore -// https://github.com/chavyleung/scripts/blob/master/Env.min.js -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}isStash(){return"undefined"!=typeof $environment&&$environment["stash-version"]}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,a]=i.split("@"),n={url:`http://${a}/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),a=i?"null"===o?null:o||"{}":"{}";try{const e=JSON.parse(a);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?s.status:s.statusCode,s.status=s.statusCode),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&&t.error||"UndefinedError"));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:a}=t,n=s.decode(a,this.encoding);e(null,{status:i,statusCode:r,headers:o,rawBody:a,body:n},n)},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?s.status:s.statusCode,s.status=s.statusCode),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&&t.error||"UndefinedError"));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:a}=t,n=i.decode(a,this.encoding);e(null,{status:s,statusCode:r,headers:o,rawBody:a,body:n},n)},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,i=t["update-pasteboard"]||t.updatePasteboard;return{"open-url":e,"media-url":s,"update-pasteboard":i}}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):this.isNode()&&process.exit(1)}}(t,e)} - -// https://github.com/VirgilClyne/VirgilClyne/blob/main/function/URL/URLs.embedded.min.js -function URLs(s){return new class{constructor(s=[]){this.name="URL v1.0.1",this.opts=s,this.json={url:{scheme:"",host:"",path:""},params:{}}}parse(s){let t=s.match(/(?.+):\/\/(?[^/]+)\/?(?[^?]+)?\??(?.*)?/)?.groups??null;return t?.path||(t.path=""),t?.params&&(t.params=Object.fromEntries(t.params.split("&").map((s=>s.split("="))))),t}stringify(s=this.json){return s?.params?s.scheme+"://"+s.host+"/"+s.path+"?"+Object.entries(s.params).map((s=>s.join("="))).join("&"):s.scheme+"://"+s.host+"/"+s.path}}(s)} diff --git a/js/archive/Weather.response.beta.js b/js/archive/Weather.response.beta.js deleted file mode 100644 index 29da46c39..000000000 --- a/js/archive/Weather.response.beta.js +++ /dev/null @@ -1,6005 +0,0 @@ -// README: https://github.com/VirgilClyne/iRingo - -const $ = new Env('🌤 Weather v4.0.0-response-beta'); - -/** - * Get Environment Variables - * @author VirgilClyne - * @param {String} t - Persistent Store Key - * @param {String} e - Platform Name - * @param {Object} n - Default DataBase - * @return {{Settings: Object, Caches: Object, Configs: Object}} - */ -// noinspection -// eslint-disable-next-line -function getENV(t, e, n) { const i = $.getjson(t, n); const s = i?.[e]?.Settings || n?.[e]?.Settings || n?.Default?.Settings; const g = i?.[e]?.Configs || n?.[e]?.Configs || n?.Default?.Configs; let f = i?.[e]?.Caches || void 0; if (typeof f === 'string' && (f = JSON.parse(f)), typeof $argument !== 'undefined') { if ($argument) { const t = Object.fromEntries($argument.split('&').map(((t) => t.split('=')))); const e = {}; for (const a in t)o(e, a, t[a]); Object.assign(s, e); } function o(t, e, n) { e.split('.').reduce(((t, i, s) => t[i] = e.split('.').length === ++s ? n : t[i] || {}), t); } } return { Settings: s, Caches: f, Configs: g }; } - -const database = { - Location: { - Settings: { - Switch: true, - CountryCode: 'US', - Config: { - GEOAddressCorrection: true, LookupMaxParametersCount: true, LocalitiesAndLandmarks: true, PedestrianAR: true, '6694982d2b14e95815e44e970235e230': true, OpticalHeading: true, UseCLPedestrianMapMatchedLocations: true, - }, - }, - }, - Weather: { - Settings: { - Switch: true, - NextHour: { Switch: true, Source: 'www.weatherol.cn' }, - AQI: { - Switch: true, Targets: ['HJ6332012'], Local: { Switch: true, Standard: 'WAQI_InstantCast' }, Source: 'www.weatherol.cn', Comparison: { Switch: true, Source: 'Local' }, - }, - Map: { AQI: false }, - APIs: { - WeatherOL: { HTTPHeaders: { 'Content-Type': 'application/json' } }, - ColorfulClouds: { - HTTPHeaders: { 'Content-Type': 'application/json' }, - Token: '', - ForceCNForAQI: true, - ForceCNForComparison: false, - }, - WAQI: { HTTPHeaders: { 'Content-Type': 'application/json' }, Token: '' }, - }, - Log: { - Level: 'info', - Location: false, - }, - }, - Configs: { - Pollutants: { - co: 'CO', no: 'NO', no2: 'NO2', so2: 'SO2', o3: 'OZONE', nox: 'NOX', pm25: 'PM2.5', pm10: 'PM10', other: 'OTHER', - }, - }, - Cache: { - aqis: {}, - }, - }, - Siri: { - Settings: { - Switch: true, CountryCode: 'SG', Domains: ['web', 'itunes', 'app_store', 'movies', 'restaurants', 'maps'], Functions: ['flightutilities', 'lookup', 'mail', 'messages', 'news', 'safari', 'siri', 'spotlight', 'visualintelligence'], Safari_Smart_History: true, - }, - }, -}; - -/** @typedef {1 | 2 | 3} supportedAppleApi */ -/** @typedef {{latitude: number, longitude: number}} coordinate */ -/** @typedef {{forV2: string, forV1: string}} providerLogo */ - -/** - * Scale names in 2207 version - * @typedef { - * "CA.AQHI" | "FR.ATMO" | "UBA" | "NAQI" | "EU.EAQI" | "ICARS" | "NL.LKI" | "SG.NEA" | "KR.CAI" - * | "DAQI" | "EPA_NowCast" | "HJ6332012" - * } scaleNames2207 - */ -/** @typedef {"ES.MITECO"} scaleName2208 */ -/** @typedef {scaleNames2207|scaleName2208} scaleNames */ -/** @typedef {`${scaleNames2207}.2207` | `${scaleName2208}.2208`} appleAqiScales */ - -/** - * AQI info in cache - * @typedef {Object} cachedAqi - * - * @property {coordinate} location - Coordinate of AQI - * @property {string} [stationName] - `AirQuality.source` from QWeather - * @property {scaleNames} scaleName - Part before the '.' in iOS `AirQuality.scale` - * @property {number} aqi - Air quality index - */ - -/** - * Supported VOCs by {@link toEpaAqis} - * @typedef {"NO2" | "NO" | "SO2" | "OZONE" | "CO"} supportedEpaVocs - */ -/** - * Supported pollutants by EPA - * @typedef {supportedEpaVocs | "NOX" | "PM2.5" | "PM10"} supportedEpaPollutantNames - */ -/** - * Pollutant names used in Apple weather. - * `C6H6` is in Germany, India, Italia and Spain, `NH3` is in India, `NMHC` in Japan. - * @typedef { - * supportedEpaPollutantNames | "C6H6" | "NH3" | "NMHC" - * } applePollutantNames - */ - -/** @typedef {"ppb" | "µg/m3"} pollutantUnitsV1 */ -/** @typedef {"ppb" | "microgramsPerM3"} pollutantUnitsV2 */ -/** @typedef {pollutantUnitsV1 | "ppm" | "mg/m3"} pollutantUnitsSlash */ -/** @typedef {pollutantUnitsV2 | "ppm" | "milligramsPerM3"} pollutantUnitsText */ - -/** - * Base pollutant type for `AirQuality.pollutants` - * @typedef {Object} pollutantBase - * - * @property {string} name - Name of pollutant - * @property {number} amount - Amount of pollutant - */ -/** - * Pollutant for `air_quality.pollutants` in Apple APIv1 - * @typedef {pollutantBase} pollutantV1 - * - * @property {pollutantUnitsV1} unit - Unit of pollutant - */ -/** - * Pollutant for `airQuality.pollutants` in Apple APIv2 and APIv3 - * @typedef {pollutantBase} pollutantV2 - * - * @property {pollutantUnitsV2} unit - Unit of pollutant - */ - -/** - * AQI info generated by {@link toEpaAqis} - * @typedef {Object} aqiInfo - * - * @property {number} index - Air quality index - * @property {{name: string, aqi: number}[] | []} pollutants - Aqi of each pollutant - * @property {supportedEpaPollutantNames} [primary] - Primary pollutant - */ - -/** @typedef {"unknown" | "worse" | "same" | "better"} aqiComparison */ -/** @typedef {"station" | "modeled"} sourceType */ -/** @typedef {0 | 1} dataSource */ - -/** - * Object for {@link toMetadata} - * @typedef {Object} metadataObject - * - * @property {string} [language] - Language in ISO 639 style (example: en-US) - * @property {coordinate} [location] - Coordinate of location of data - * @property {number} [expireTimestamp] - UNIX timestamp of expire time - * @property {providerLogo} [providerLogo] - URL to the provider logo - * @property {string} [providerName] - Name of the provider - * @property {number} [readTimestamp] - UNIX timestamp of read time - * @property {number} [reportedTimestamp] - UNIX timestamp of reported time - * @property {dataSource} [dataSource] - Type of data source - * @property {string} [unit] - Unit of the data - * @property {string} [url] - URL to the data source - */ - -/** - * Metadata of next hour object for Apple Weather APIv1 - * @typedef {Object} appleMetadataBaseV1 - * - * @property {string} [read_time] - Time in seconds - * @property {string} [expire_time] - Time in seconds - * @property {1} version - 1 in APIv1 - * @property {number} [latitude] - * @property {number} [longitude] - * @property {string} [language] - Language in ISO 639 style (example: en-US) - * @property {string} [provider_name] - */ -/** - * Metadata of next hour object for Apple Weather APIv2 - * @typedef {Object} appleMetadataBaseV2 - * - * @property {string} [expireTime] - Time in "YYYY-MM-DDTHH:MM:SSZ" format - * @property {string} [language] - Language in ISO 639 style (example: en-US) - * @property {number} [latitude] - * @property {number} [longitude] - * @property {string} [providerName] - * @property {string} [readTime] - Time in "YYYY-MM-DDTHH:MM:SSZ" format - * @property {2} version - 2 in APIv2 - */ -/** - * Metadata of next hour object for Apple Weather APIv3 - * @typedef {Object} appleMetadataBaseV3 - * - * @property {string} [attributionURL] - * @property {string} [expireTime] - Time in "YYYY-MM-DDTHH:MM:SSZ" format - * @property {number} [latitude] - * @property {number} [longitude] - * @property {string} [readTime] - Time in "YYYY-MM-DDTHH:MM:SSZ" format - * @property {1} version - 1 in APIv3 - */ -/** - * Metadata of air quality for Apple Weather APIv1 - * @typedef {appleMetadataBaseV1} appleAirQualityMetadataV1 - * - * @property {string} [reported_time] - Time in seconds - * @property {string} [provider_logo] - URL to the provider logo - * @property {0 | 1} [data_source] - Type of data source. 0 means station. 1 means modeled. - */ -/** - * Metadata of next hour for Apple Weather APIv1 - * @typedef {appleMetadataBaseV1} appleNextHourMetadataV1 - * - * @property {0 | 1} [data_source] - Type of data source. 0 means station. 1 means modeled. - */ -/** - * Metadata of air quality for Apple Weather APIv2 - * @typedef {appleMetadataBaseV2} appleAirQualityMetadataV2 - * - * @property {string} [providerLogo] - URL to the provider logo - * @property {string} [reportedTime] - Time in "YYYY-MM-DDTHH:MM:SSZ" format - * @property {"m"} [units] - Units of data - */ -/** - * Metadata of next hour for Apple Weather APIv2 - * @typedef {appleMetadataBaseV2} appleNextHourMetadataV2 - * - * @property {"m"} [units] - Units of data - */ -/** - * Metadata of air quality for Apple Weather APIv3 - * @typedef {appleMetadataBaseV3} appleAirQualityMetadataV3 - * - * @property {string} [language] - Language in ISO 639 style (example: en-US) - * @property {string} [providerLogo] - URL to the provider logo - * @property {string} [providerName] - * @property {string} [reportedTime] - Time in "YYYY-MM-DDTHH:MM:SSZ" format - * @property {"m"} [units] - Units of data - */ -/** - * Metadata of next hour for Apple Weather APIv3 - * @typedef {appleMetadataBaseV3} appleNextHourMetadataV3 - * - * @property {string} [language] - Language in ISO 639 style (example: en-US) - * @property {string} [providerName] - * @property {"m"} [units] - Units of data - */ - -/** - * Object for {@link toAirQuality} - * @typedef {Object} airQualityObject - * - * @property {pollutantV2[]} [pollutants] - Array of pollutant - * @property {boolean} [isSignificant] - Importance of AQI info, set true will pin AQI info to top. - * AQI info of China will always been pinned after APIv2 - * @property {string} [url] - URL to the website of AQI details - * @property {applePollutantNames} [primary] - Name of the primary pollutant - * @property {string} [sourceName] - Name of the source - * @property {number} [categoryIndex] - AQI level starts from 1 - * @property {number} [aqi] - Air quality index - * @property {appleAqiScales} [scale] - `name.version` of the AQI scale - * @property {aqiComparison} [previousDayComparison] - (APIv2+) Comparison to the previous day - * @property {sourceType} [sourceType] - (APIv2+) Type of the info - */ - -/** - * Air quality object for Apple Weather APIv1 - * @typedef {Object} appleAqiWithoutMetadataV1 - * - * @property {"AirQuality"} name - * @property {string} [source] - Station name if provider is QWeather - * @property {string} [learnMoreURL] - * @property {number} [airQualityIndex] - * @property {number} [airQualityCategoryIndex] - * @property {appleAqiScales} [airQualityScale] - * @property {applePollutantNames} [primaryPollutant] - * @property {boolean} [isSignificant] - * @property {Object.} [pollutants] - */ -/** - * Air quality object for Apple Weather APIv3 - * @typedef {Object} appleAqiWithoutMetadataV3 - * - * @property {"AirQuality"} [name] - * @property {number} [categoryIndex] - * @property {number} [index] - * @property {boolean} [isSignificant] - * @property {string} [learnMoreURL] - * @property {Object.} [pollutants] - * @property {aqiComparison} [previousDayComparison] - * @property {applePollutantNames} [primaryPollutant] - * @property {appleAqiScales} [scale] - * @property {sourceType} [sourceType] - */ -/** - * Air quality object for Apple Weather APIv2 - * @typedef {appleAqiWithoutMetadataV3} appleAqiWithoutMetadataV2 - * - * @property {string} [source] - Station name if provider is QWeather - */ -/** - * Air quality object for Apple Weather APIv1 - * @typedef {appleAqiWithoutMetadataV1} appleAqiV1 - * - * @property {appleAirQualityMetadataV1} metadata - Metadata of air quality - */ -/** - * Air quality object for Apple Weather APIv2 - * @typedef {appleAqiWithoutMetadataV2} appleAqiV2 - * - * @property {appleAirQualityMetadataV2} metadata - Metadata of air quality - */ -/** - * Air quality object for Apple Weather APIv3 - * @typedef {appleAqiWithoutMetadataV3} appleAqiV3 - * - * @property {appleAirQualityMetadataV3} metadata - Metadata of air quality - */ - -/** - * Precipitation types for `nextHour.summary[].condition`. - * [PrecipitationType | Apple Developer Documentation]{@link https://developer.apple.com/documentation/weatherkitrestapi/precipitationtype} - * @typedef { - * "clear" | "precipitation" | "rain" | "snow" | "sleet" | "hail" | "mixed" - * } precipitationTypes - */ -/** - * Weather statuses for `nextHour.condition[].token` - * @typedef { - * "clear" | "precipitation" | "drizzle" | "flurries" | "rain" | "snow" | "heavy-rain" - * | "heavy-snow" | "sleet" | "hail" | "mixed" - * } weatherStatuses - */ - -/** - * Minute precipitation data - * @typedef {Object} minute - * - * @property {weatherStatuses} weatherStatus - Weather status of this minute - * @property {number} precipitation - Precipitation of this minute - * @property {number} precipitationIntensityPerceived - Precipitation from 0 to 3, - * can be generated by {@link toPerceived} - * @property {number} chance - Integer from 0 to 100, percentage of chance to rain or snow - * @property {string} shortDescription - Description that will be display in city list - * @property {string} longDescription - Description with `firstAt`, `secondAt`, etc... - * to present `X minutes later` dynamically base on current time. - * @property {Object. | {}} parameters - Object that works with `longDescription`. - * `firstAt`, `secondAt`, etc... as keys, minutes after startTime as values. - */ -/** - * Object for {@link toNextHour} - * @typedef {Object} nextHourObject - * - * @property {minute[]} minutes - Array of minute data - * @property {number} startTimestamp - Timestamp of precipitation started - */ - -/** - * Base minute type for `NextHourForecast.minutes` - * @typedef {Object} nextHourMinuteBase - * - * @property {number} precipChance - Integer from 0 to 100, percentage of chance - * @property {number} precipIntensity - 2 decimal places from Apple. Actually could be any number. - */ -/** - * Minute for `next_hour.minutes` in Apple APIv1 - * @typedef {nextHourMinuteBase} nextHourMinuteV1 - * - * @property {number} startAt - Time of the start time (in seconds) - * @property {number} perceivedIntensity - 3 decimal places from 0 to 3, - * can be generated by `toApplePrecipitation()` - */ -/** - * Minute for `forecastNextHour.minutes` in Apple APIv2 - * @typedef {nextHourMinuteBase} nextHourMinuteV2 - * - * @property {string} startTime - Time of the start time (in YYYY-MM-DDTHH:MM:SSZ format) - * @property {number} precipIntensityPerceived - 3 decimal places from 0 to 3, - * can be generated by `toApplePrecipitation()` - */ -/** - * Minute for `forecastNextHour.minutes` in Apple APIv3 - * @typedef {Object} nextHourMinuteV3 - * - * @property {string} startTime - Time of the start time (in YYYY-MM-DDTHH:MM:SSZ format) - * @property {number} precipitationChance - Number from 0 to 1 with 2 decimal places, - * percentage of chance - * @property {number} precipitationIntensity - 2 decimal places from Apple. - * Actually could be any number. - * @property {number} precipitationIntensityPerceived - 3 decimal places from 0 to 3, - * can be generated by `toApplePrecipitation()` - */ - -/** - * Base condition type for `NextHourForecast` - * @typedef {Object} nextHourConditionBase - * - * @property {string} token - A tag to describe possibility, weather status and time status - * @property {string} longTemplate - A description for current weather. Time is dynamic by - * replacing to `{firstAt}`, `{secondAt}`, etc for `parameters` - * @property {string} shortTemplate - As same as `longTemplate`, but no dynamic time support - * and will be display in city list - */ -/** - * Condition for `next_hour.condition` in Apple APIv1 - * @typedef {nextHourConditionBase} nextHourConditionV1 - * - * @property {number} [validUntil] - Time of the end time (in seconds) - * @property {Object.} parameters - Key should be `firstAt`, `secondAt`, etc. - * Value is time in seconds - */ -/** - * Condition for `forecastNextHour.condition` in Apple APIv2 - * @typedef {nextHourConditionBase} nextHourConditionV2 - * - * @property {string} startTime - Time of the start time (in YYYY-MM-DDTHH:MM:SSZ format) - * @property {string} [endTime] - Time of the end time (in YYYY-MM-DDTHH:MM:SSZ format) - * @property {Object.} parameters - Key should be `firstAt`, `secondAt`, etc. - * Value is time in YYYY-MM-DDTHH:MM:SSZ format - */ -/** - * Condition for `forecastNextHour.condition` in Apple APIv3 - * @typedef {Object} nextHourConditionV3 - * - * @property {string} startTime - Time of the start time (in YYYY-MM-DDTHH:MM:SSZ format) - * @property {string} [endTime] - Time of the end time (in YYYY-MM-DDTHH:MM:SSZ format) - * @property {string} token - A tag to describe possibility, weather status and time status - * @property {Object.} parameters - Key should be `firstAt`, `secondAt`, etc. - * Value is time in YYYY-MM-DDTHH:MM:SSZ format. Effective to the description in Apple Weather. - */ - -/** - * Base summary type for `NextHourForecast` - * @typedef {Object} nextHourSummaryBase - * - * @property {precipitationTypes} condition - Weather condition - */ -/** - * Condition for `next_hour.summary` in Apple APIv1 - * @typedef {nextHourSummaryBase} nextHourSummaryV1 - * - * @property {number} [validUntil] - Time of the end time (in seconds) - * @property {number} [probability] - Precipitation chance of this minute if condition is not clear - * @property {number} [maxIntensity] - Maximum value of {@link nextHourMinuteV1#perceivedIntensity} - * in this range of minutes if condition is not clear - * @property {number} [minIntensity] - Minimum value of {@link nextHourMinuteV1#perceivedIntensity} - * in this range of minutes if condition is not clear - */ -/** - * Condition for `forecastNextHour.summary` in Apple APIv2 - * @typedef {nextHourSummaryBase} nextHourSummaryV2 - * - * @property {string} startTime - Time of the start time (in YYYY-MM-DDTHH:MM:SSZ format) - * @property {string} [endTime] - Time of the end time (in YYYY-MM-DDTHH:MM:SSZ format) - * @property {number} [precipChance] - Precipitation chance of this minute if condition is not clear - * @property {number} [precipIntensity] - Maximum value of `precipIntensityPerceived` in - * {@link nextHourMinuteV2} in this range of minutes if condition is not clear - */ -/** - * Condition for `forecastNextHour.summary` in Apple APIv3 - * @typedef {nextHourSummaryBase} nextHourSummaryV3 - * - * @property {string} startTime - Time of the start time (in YYYY-MM-DDTHH:MM:SSZ format) - * @property {string} [endTime] - Time of the end time (in YYYY-MM-DDTHH:MM:SSZ format) - * @property {number} precipitationChance - Maximum precipitation chance of this summary - * @property {number} precipitationIntensity - Maximum value of `precipitationIntensityPerceived` in - * {@link nextHourMinuteV3} in this range of minutes if condition is not clear - */ - -/** - * Next hour object for Apple Weather APIv1 - * @typedef {Object} appleNextHourWithoutMetadataV1 - * - * @property {"NextHourForecast"} name - * @property {nextHourConditionV1[]} [condition] - * @property {nextHourSummaryV1[]} [summary] - * @property {number} [startTime] - Time in seconds - * @property {nextHourMinuteV1[]} [minutes] - */ -/** - * Base next hour object for Apple Weather APIv2 - * @typedef {Object} appleNextHourWithoutMetadataV2 - * - * @property {"NextHourForecast"} name - * @property {nextHourConditionV2[]} [condition] - * @property {nextHourSummaryV2[]} [summary] - * @property {string} [startTime] - Time in "YYYY-MM-DDTHH:MM:SSZ" format - * @property {nextHourMinuteV2[]} [minutes] - */ -/** - * Base next hour object for Apple Weather APIv3 - * @typedef {Object} appleNextHourWithoutMetadataV3 - * - * @property {"NextHourForecast"} name - * @property {nextHourConditionV3[]} [condition] - * @property {nextHourSummaryV3[]} [summary] - * @property {string} [forecastStart] - Time in "YYYY-MM-DDTHH:MM:SSZ" format - * @property {string} [forecastEnd] - Time in "YYYY-MM-DDTHH:MM:SSZ" format - * @property {nextHourMinuteV3[]} [minutes] - */ -/** - * Next hour object for Apple Weather APIv1 - * @typedef {appleNextHourWithoutMetadataV1} appleNextHourV1 - * - * @property {appleNextHourMetadataV1} metadata - Metadata of air quality - */ -/** - * Next hour object for Apple Weather APIv2 - * @typedef {appleNextHourWithoutMetadataV2} appleNextHourV2 - * - * @property {appleNextHourMetadataV2} metadata - Metadata of air quality - */ -/** - * Next hour object for Apple Weather APIv3 - * @typedef {appleNextHourWithoutMetadataV3} appleNextHourV3 - * - * @property {appleNextHourMetadataV3} metadata - Metadata of air quality - */ - -/** - * @typedef {Object} range - * - * @property {Number} LOWER - lower of range - * @property {Number} UPPER - upper of range - */ - -/** - * Precipitation level and its range - * @typedef {Object} precipitationLevel - * - * @property {number} VALUE - Value of precipitation level for comparing - * @property {range} RANGE - Precipitation range of level - */ -/** @typedef {"NO" | "LIGHT" | "MODERATE" | "HEAVY" | "STORM"} precipitationLevelNames */ -/** @typedef {Object.} precipitationLevels */ - -/** - * AQI level and its range - * @typedef {Object} aqiLevel - * - * @property {number} VALUE - AQI level value for `airQuality.categoryIndex`. Should be start at 1. - * @property {range} RANGE - Range of AQI level - */ - -/** - * Concentration range for pollutant - * @typedef {Object} concentrationRange - * - * @property {range} AMOUNT - Range of concentration amount - * @property {range} AQI - AQI range for its concentration amount range - */ - -/** - * @typedef {Object} concentration - * - * @property {pollutantUnitsText} UNIT - Unit of concentration - * @property {Object.} RANGES - Concentration range for pollutant - */ - -/** - * AQI standard - * @typedef {Object} aqiStandard - * - * @property {appleAqiScales} APPLE_SCALE - Value of `airQuality.scale` - * @property {Object.} AQI_LEVELS - Value and ranges of AQI levels - * @property {number} SIGNIFICANT_LEVEL - AQI will be pinned if `airQuality.categoryIndex` - * greater or equals than this - * @property {Object.} CONCENTRATIONS - Key should be the pollutant name. - */ - -/** - * Version 1 of next hour settings - * @typedef {Object} nextHourSettingsV1 - * - * @property {boolean} switch - Switch of the next hour - * @property {string} source - API source of next hour - */ -/** - * Version 1 settings of local converter of AQI - * @typedef {Object} aqiLocalSettingsV1 - * - * @property {boolean} switch - Switch of the AQI local converter - * @property {string} standard - Name of the converter - */ -/** - * Version 1 of AQI comparison settings - * @typedef {Object} aqiComparisonSettingsV1 - * - * @property {boolean} switch - Switch of the AQI comparison - * @property {string} source - API source of AQI comparison - */ -/** - * Version 1 of AQI settings - * @typedef {Object} aqiSettingsV1 - * - * @property {boolean} switch - Switch of the AQI - * @property {string[]} targets - AQI with matched scales will be edited by module - * @property {aqiLocalSettingsV1} local - Settings of local converter of AQI - * @property {aqiComparisonSettingsV1} comparison - AQI comparison settings - * @property {string} source - API source of AQI - */ -/** - * Base API settings - * @typedef {Object} baseApiSettingsV1 - * - * @property {Object} httpHeaders - HTTP headers for requesting API - */ -/** - * Base API with token settings - * @typedef {baseApiSettingsV1} baseApiTokenSettingsV1 - * - * @property {string} token - Token for the API - */ -/** - * Version 1 of WeatherOL API settings - * @typedef {baseApiSettingsV1} weatherOlSettingsV1 - */ -/** - * Version 1 of ColorfulClouds API settings - * @typedef {baseApiTokenSettingsV1} colorfulCloudsSettingsV1 - * - * @property {boolean} forceCnForAqi - Force to use China standard in AQI data - * @property {boolean} forceCnForComparison - Force to use China standard in AQI comparison data - */ -/** - * Version 1 of The World Air Quality Project API settings - * @typedef {baseApiTokenSettingsV1} waqiSettingsV1 - */ -/** - * Version 1 of APIs settings - * @typedef {Object} apisSettingsV1 - * - * @property {weatherOlSettingsV1} weatherOl - API settings of WeatherOL - * @property {colorfulCloudsSettingsV1} colorfulClouds - API settings of ColorfulClouds - * @property {waqiSettingsV1} waqi - API settings of WAQI - */ -/** - * Version 1 of log settings - * @typedef {Object} logSettingsV1 - * - * @property {"debug" | "info" | "warn" | "error"} level - Log level - * @property {boolean} location - Print location info to logs - */ -/** - * Version 1 of settings - * @typedef {Object} settingsV1 - * - * @property {boolean} switch - Switch of the module - * @property {nextHourSettingsV1} nextHour - Settings of next hour - * @property {aqiSettingsV1} aqi - Settings of AQI - * @property {apisSettingsV1} apis - Settings of APIs - * @property {logSettingsV1} log - Settings of log - */ - -/** - * Version 1 of AQI cache - * @typedef {Object} aqiCacheV1 - * - * @property {coordinate} location - Location of AQI info - * @property {scaleNames} scaleName - Scale of AQI info - * @property {number} aqi - AQI index - */ -/** - * Version 1 of caches - * @typedef {Object} cachesV1 - * - * @property {Object.} aqis - AQI caches - * @property {{tokens: Object.}} waqi - WAQI caches - */ - -/** - * WAQI error - * @typedef {Object} waqiError - * - * @property {"error"} status - * @property {string} data - Error message - */ -/** - * WAQI pollutant names - * @typedef {"co" | "no2" | "o3" | "pm10" | "pm25" | "so2"} waqiPollutantNames - */ -/** - * Data in WAQI mapq - * @typedef {Object} waqiMapqD - * - * @property {string} nlo - Station name in English - * @property {string} nna - Station name in local language - * @property {number} t - Report time (in seconds) - * @property {waqiPollutantNames} pol - Primary pollutant - * @property {string} x - Station ID - * @property {string} v - AQI - * @property {string} u - Country/Province/Station Name - * @property {string} key - * @property {number} d - Distance to the location - * @property {[number, number]} geo - Station coordinate - * @property {string} cca2 - Country code in ISO 3166-1 alpha-2 - */ -/** - * WAQI mapq - * @typedef {Object} waqiMapq - * - * @property {waqiMapqD[]} d - Data in WAQI mapq - * @property {string} dt - * @property {null} g - */ - -/** - * Station in WAQI mapq2 - * @typedef {Object} waqiMapq2Station - * - * @property {string} idx - Station ID - * @property {string} aqi - Air quality index - * @property {string} utime - Report time (in `YYYY-MM-DDTHH:MM:SS+/-timezone` format) - * @property {[number, number]} geo - Station coordinate - * @property {string} name - Station name in "English (local language)" - * @property {string} country - Country code in ISO 3166-1 alpha-2 - * @property {number} distance - Distance to the location - */ -/** - * Data in WAQI mapq2 - * @typedef {Object} waqiMapq2Data - * - * @property {null} location - * @property {waqiMapq2Station[]} stations - Stations in mapq2 - */ -/** - * WAQI mapq2 - * @typedef {Object} waqiMapq2 - * - * @property {waqiMapq2Data} data - Data in WAQI mapq2 - * @property {string} dt - * @property {"ok" | "error"} status - Status of API - */ - -/** - * Attribution in WAQI feed - * @typedef {Object} waqiFeedAttribution - * - * @property {string} url - URL to the source - * @property {string} name - Name of the source - */ -/** - * City data in WAQI feed - * @typedef {Object} waqiFeedCity - * - * @property {[number, number]} geo - Station coordinate - * @property {string} name - Station name in "English (local language)" - * @property {string} url - URL to the data of station in WAQI - * @property {""} location - */ -/** - * Time data in WAQI feed - * @typedef {Object} waqiFeedTime - * - * @property {string} s - Time in "YYYY-MM-DD HH:MM:SS" format - * @property {string} tz - Timezone in "+/-HH:MM" - * @property {number} v - Time in seconds - * @property {string} iso - Time in "YYYY-MM-DDTHH:MM:SS+/-Timezone" format - */ -/** - * Forecast data in WAQI feed - * @typedef {Object} waqiFeedForecast - * - * @property {Object.} daily - */ -/** - * Data in WAQI feed - * @typedef {Object} waqiFeedData - * - * @property {number} aqi - Air quality index - * @property {number} idx - Station ID - * @property {waqiFeedAttribution[]} attributions - Sources of the data - * @property {waqiFeedCity} city - Station info - * @property {waqiPollutantNames} dominentpol - Primary pollutant - * @property {Object.} iaqi - * @property {waqiFeedTime} time - Report time - * @property {waqiFeedForecast} forecast - Country/Province/Station Name - * @property {{sync: string}} debug - */ -/** - * WAQI feed - * @typedef {Object} waqiFeed - * - * @property {"ok" | "error"} status - Status of API - * @property {waqiFeedData} data - Data in WAQI feed - */ - -/** - * msg data of `rxs.obs[]` of WAQI AQI feed - * @typedef {Object} waqiAqiFeedRxsObsMsg - * - * @property {number} aqi - Air quality index - * @property {number} idx - Station ID - * @property {waqiFeedAttribution[]} attributions - Sources of the data - * @property {waqiFeedCity} city - Station info - * @property {waqiPollutantNames} dominentpol - Primary pollutant - * @property {Object.} iaqi - - * t is time in "YYYY-MM-DD HH:MM:SS" format - * @property {waqiFeedTime} time - Report time - * @property {Object.< - * waqiPollutantNames|string, {e: number, s: string, d: number, m: number, v: Array} - * >} obs - * @property {{daily: Object.< - * waqiPollutantNames|string, {avg: number, day: string, max: number, min: number}[] - * >, hourly: {}}} forecast - * @property {gen: number} xsync - Time in seconds - */ -/** - * obs data of rxs of WAQI AQI feed - * @typedef {Object} waqiAqiFeedRxsObs - * - * @property {waqiAqiFeedRxsObsMsg[]} msg - Data from station - * @property {"ok" | "error"} status - Status of API - * @property {string} cached - */ -/** - * rxs data of WAQI AQI feed - * @typedef {Object} waqiAqiFeedRxs - * - * @property {waqiAqiFeedRxsObs[]} obs - Data from stations - * @property {"ok" | "error"} status - Status of API - * @property {string} ver - */ -/** - * WAQI AQI feed - * @typedef {Object} waqiAqiFeed - * - * @property {string} dt - * @property {waqiAqiFeedRxs} rxs - */ - -/** - * msg data of `rxs.obs[]` of WAQI AQI feed - * @typedef {Object} waqiNowFeedRxsObsMsg - * - * @property {number} aqi - Air quality index - * @property {number} idx - Station ID - * @property {waqiFeedCity} city - Station info - * @property {waqiPollutantNames} dominentpol - Primary pollutant - * @property {waqiFeedTime} time - Report time - * @property {sync: string} debug - Time in YYYY-MM-DDTHH:MM:SS+/-timezone format - */ -/** - * obs data of rxs of WAQI AQI feed - * @typedef {Object} waqiNowFeedRxsObs - * - * @property {waqiNowFeedRxsObsMsg[]} msg - Data from station - * @property {"ok" | "error"} status - Status of API - * @property {string} cached - */ -/** - * rxs data of WAQI AQI feed - * @typedef {Object} waqiNowFeedRxs - * - * @property {waqiNowFeedRxsObs[]} obs - Data from stations - * @property {"ok" | "error"} status - Status of API - * @property {string} ver - */ -/** - * WAQI now feed - * @typedef {Object} waqiNowFeed - * - * @property {string} dt - * @property {waqiNowFeedRxs} rxs - */ - -/** - * Pollutants and AQI from ColorfulClouds handled by {@link getCcAirQuality} - * @typedef {Object} ccAirQuality - * - * @property {number | -1} PM2.5 - Amount in micrograms/m3 - * @property {number | -1} PM10 - Amount in micrograms/m3 - * @property {number | -1} OZONE - Amount in micrograms/m3 - * @property {number | -1} NO2 - Amount in micrograms/m3 - * @property {number | -1} SO2 - Amount in micrograms/m3 - * @property {number | -1} CO - Amount in milligrams/M3 - * @property {{chn: number | -1, usa: number | -1}} aqi - Air quality index - */ - -/** - * Air quality descriptions (zh_CN) in ColorfulClouds - * @typedef {"缺数据" | "优" | "良" | "轻度污染" | "中度污染" | "重度污染" | "严重污染"} ccAirQualityDescriptionsZhCn - */ -/** - * Air quality data of realtime of ColorfulClouds v2.4+ - * @typedef {Object} ccV2d4pRealtimeAirQuality - * - * @property {number} pm25 - Amount in micrograms/m3 - * @property {number} pm10 - Amount in micrograms/m3 - * @property {number} o3 - Amount in micrograms/m3 - * @property {number} no2 - Amount in micrograms/m3 - * @property {number} so2 - Amount in micrograms/m3 - * @property {number} co - Amount in milligrams/M3 - * @property {{chn: number, usa: number}} aqi - Air quality index - * @property {{ - * chn: ccAirQualityDescriptionsZhCn|string, usa: ccAirQualityDescriptionsZhCn|string | "" - * }} description - Air quality description - */ -/** - * ColorfulClouds v2 base - * @typedef {Object} colorfulCloudsV2Base - * - * @property {"ok" | "error"} status - Status of API - * @property {string} api_version - API version starting with "v" - * @property {"alpha" | "active"} api_status - Channel of current API version - * @property {"zh_CN" | "zh_TW" | "en_US" | "en_GB"} lang - Affective to the descriptions - * @property {"SI" | "imperial" | "metric:v1" | "metric:v2" | "metric"} unit - Unit of the data. [单位制 | 彩云天气 API](https://docs.caiyunapp.com/docs/tables/unit/) - * @property {number} tzshift - Timezone in seconds - * @property {string} timezone - Timezone in "Continent/Region" format - * @property {number} server_time - Time in seconds - * @property {[number, number]} location - Coordinate of current location - */ -/** - * Result of ColorfulClouds v2 - * @typedef {Object} colorfulCloudsV2Result - * - * @property {Object} [alert] - * @property {Object} [realtime] - * @property {Object} [minutely] - * @property {Object} [hourly] - * @property {Object} [daily] - * @property {number} primary - * @property {string} forecast_keypoint - Description of current weather - */ -/** - * ColorfulClouds v2 - * @typedef {colorfulCloudsV2Base} colorfulCloudsV2 - * - * @property {colorfulCloudsV2Result} result - */ -/** - * ColorfulClouds v2 error - * @typedef {Object} colorfulCloudsV2Error - * - * @property {"ok" | "error"} status - Status of API - * @property {string} error - Error message - * @property {string} api_version - API version starting with "v" - */ - -const EPA_454_AQI_LEVELS = { - INVALID: { VALUE: -1, RANGE: { LOWER: Number.MIN_VALUE, UPPER: -1 } }, - GOOD: { VALUE: 1, RANGE: { LOWER: 0, UPPER: 50 } }, - MODERATE: { VALUE: 2, RANGE: { LOWER: 51, UPPER: 100 } }, - UNHEALTHY_FOR_SENSITIVE: { VALUE: 3, RANGE: { LOWER: 101, UPPER: 150 } }, - UNHEALTHY: { VALUE: 4, RANGE: { LOWER: 151, UPPER: 200 } }, - VERY_UNHEALTHY: { VALUE: 5, RANGE: { LOWER: 201, UPPER: 300 } }, - HAZARDOUS: { VALUE: 6, RANGE: { LOWER: 301, UPPER: 400 } }, - // Used in calculation - VERY_HAZARDOUS: { VALUE: 6, RANGE: { LOWER: 401, UPPER: 500 } }, - OVER_RANGE: { VALUE: 7, RANGE: { LOWER: 500, UPPER: Number.MAX_VALUE } }, -}; - -/** - * US AQI standard, not equal to NowCast. - * [EPA 454/B-18-007]{@link https://www.airnow.gov/sites/default/files/2020-05/aqi-technical-assistance-document-sept2018.pdf} - * @type aqiStandard - */ -const EPA_454 = { - APPLE_SCALE: 'EPA_NowCast.2207', - AQI_LEVELS: EPA_454_AQI_LEVELS, - // Unhealthy for sensitive groups - SIGNIFICANT_LEVEL: 3, - - CONCENTRATIONS: { - OZONE: { - UNIT: 'ppm', - RANGES: { - UNHEALTHY_FOR_SENSITIVE: { - AMOUNT: { LOWER: 0.125, UPPER: 0.164 }, - AQI: EPA_454_AQI_LEVELS.UNHEALTHY_FOR_SENSITIVE.RANGE, - }, - UNHEALTHY: { - AMOUNT: { LOWER: 0.165, UPPER: 0.204 }, - AQI: EPA_454_AQI_LEVELS.UNHEALTHY.RANGE, - }, - VERY_UNHEALTHY: { - AMOUNT: { LOWER: 0.205, UPPER: 0.404 }, - AQI: EPA_454_AQI_LEVELS.VERY_UNHEALTHY.RANGE, - }, - HAZARDOUS: { - AMOUNT: { LOWER: 0.405, UPPER: 0.504 }, - AQI: EPA_454_AQI_LEVELS.HAZARDOUS.RANGE, - }, - VERY_HAZARDOUS: { - AMOUNT: { LOWER: 0.505, UPPER: 0.604 }, - AQI: EPA_454_AQI_LEVELS.VERY_HAZARDOUS.RANGE, - }, - }, - }, - OZONE_8H: { - UNIT: 'ppm', - RANGES: { - GOOD: { - AMOUNT: { LOWER: 0.000, UPPER: 0.054 }, - AQI: EPA_454_AQI_LEVELS.GOOD.RANGE, - }, - MODERATE: { - AMOUNT: { LOWER: 0.055, UPPER: 0.070 }, - AQI: EPA_454_AQI_LEVELS.MODERATE.RANGE, - }, - UNHEALTHY_FOR_SENSITIVE: { - AMOUNT: { LOWER: 0.071, UPPER: 0.085 }, - AQI: EPA_454_AQI_LEVELS.UNHEALTHY_FOR_SENSITIVE.RANGE, - }, - UNHEALTHY: { - AMOUNT: { LOWER: 0.086, UPPER: 0.105 }, - AQI: EPA_454_AQI_LEVELS.UNHEALTHY.RANGE, - }, - VERY_UNHEALTHY: { - AMOUNT: { LOWER: 0.106, UPPER: 0.200 }, - AQI: EPA_454_AQI_LEVELS.VERY_UNHEALTHY.RANGE, - }, - }, - }, - 'PM2.5_24H': { - UNIT: 'microgramsPerM3', - RANGES: { - GOOD: { - AMOUNT: { LOWER: 0.0, UPPER: 12.0 }, - AQI: EPA_454_AQI_LEVELS.GOOD.RANGE, - }, - MODERATE: { - AMOUNT: { LOWER: 12.1, UPPER: 35.4 }, - AQI: EPA_454_AQI_LEVELS.MODERATE.RANGE, - }, - // If a different SHL for PM2.5 is promulgated, - // the following numbers will change accordingly. - UNHEALTHY_FOR_SENSITIVE: { - AMOUNT: { LOWER: 35.5, UPPER: 55.4 }, - AQI: EPA_454_AQI_LEVELS.UNHEALTHY_FOR_SENSITIVE.RANGE, - }, - UNHEALTHY: { - AMOUNT: { LOWER: 55.5, UPPER: 150.4 }, - AQI: EPA_454_AQI_LEVELS.UNHEALTHY.RANGE, - }, - VERY_UNHEALTHY: { - AMOUNT: { LOWER: 150.5, UPPER: 250.4 }, - AQI: EPA_454_AQI_LEVELS.VERY_UNHEALTHY.RANGE, - }, - HAZARDOUS: { - AMOUNT: { LOWER: 250.5, UPPER: 350.4 }, - AQI: EPA_454_AQI_LEVELS.HAZARDOUS.RANGE, - }, - VERY_HAZARDOUS: { - AMOUNT: { LOWER: 350.5, UPPER: 500.4 }, - AQI: EPA_454_AQI_LEVELS.VERY_HAZARDOUS.RANGE, - }, - }, - }, - PM10_24H: { - UNIT: 'microgramsPerM3', - RANGES: { - GOOD: { - AMOUNT: { LOWER: 0, UPPER: 54 }, - AQI: EPA_454_AQI_LEVELS.GOOD.RANGE, - }, - MODERATE: { - AMOUNT: { LOWER: 55, UPPER: 154 }, - AQI: EPA_454_AQI_LEVELS.MODERATE.RANGE, - }, - UNHEALTHY_FOR_SENSITIVE: { - AMOUNT: { LOWER: 155, UPPER: 254 }, - AQI: EPA_454_AQI_LEVELS.UNHEALTHY_FOR_SENSITIVE.RANGE, - }, - UNHEALTHY: { - AMOUNT: { LOWER: 255, UPPER: 354 }, - AQI: EPA_454_AQI_LEVELS.UNHEALTHY.RANGE, - }, - VERY_UNHEALTHY: { - AMOUNT: { LOWER: 355, UPPER: 424 }, - AQI: EPA_454_AQI_LEVELS.VERY_UNHEALTHY.RANGE, - }, - HAZARDOUS: { - AMOUNT: { LOWER: 425, UPPER: 504 }, - AQI: EPA_454_AQI_LEVELS.HAZARDOUS.RANGE, - }, - VERY_HAZARDOUS: { - AMOUNT: { LOWER: 505, UPPER: 604 }, - AQI: EPA_454_AQI_LEVELS.VERY_HAZARDOUS.RANGE, - }, - }, - }, - CO_8H: { - UNIT: 'ppm', - RANGES: { - GOOD: { - AMOUNT: { LOWER: 0.0, UPPER: 4.4 }, - AQI: EPA_454_AQI_LEVELS.GOOD.RANGE, - }, - MODERATE: { - AMOUNT: { LOWER: 4.5, UPPER: 9.4 }, - AQI: EPA_454_AQI_LEVELS.MODERATE.RANGE, - }, - UNHEALTHY_FOR_SENSITIVE: { - AMOUNT: { LOWER: 9.5, UPPER: 12.4 }, - AQI: EPA_454_AQI_LEVELS.UNHEALTHY_FOR_SENSITIVE.RANGE, - }, - UNHEALTHY: { - AMOUNT: { LOWER: 12.5, UPPER: 15.4 }, - AQI: EPA_454_AQI_LEVELS.UNHEALTHY.RANGE, - }, - VERY_UNHEALTHY: { - AMOUNT: { LOWER: 15.5, UPPER: 30.4 }, - AQI: EPA_454_AQI_LEVELS.VERY_UNHEALTHY.RANGE, - }, - HAZARDOUS: { - AMOUNT: { LOWER: 30.5, UPPER: 40.4 }, - AQI: EPA_454_AQI_LEVELS.HAZARDOUS.RANGE, - }, - VERY_HAZARDOUS: { - AMOUNT: { LOWER: 40.5, UPPER: 50.4 }, - AQI: EPA_454_AQI_LEVELS.VERY_HAZARDOUS.RANGE, - }, - }, - }, - SO2: { - UNIT: 'ppb', - RANGES: { - GOOD: { - AMOUNT: { LOWER: 0, UPPER: 35 }, - AQI: EPA_454_AQI_LEVELS.GOOD.RANGE, - }, - MODERATE: { - AMOUNT: { LOWER: 36, UPPER: 75 }, - AQI: EPA_454_AQI_LEVELS.MODERATE.RANGE, - }, - UNHEALTHY_FOR_SENSITIVE: { - AMOUNT: { LOWER: 76, UPPER: 185 }, - AQI: EPA_454_AQI_LEVELS.UNHEALTHY_FOR_SENSITIVE.RANGE, - }, - }, - // 1-hour SO2 values do not define higher AQI values (≥ 200). - // AQI values of 200 or greater are calculated with 24-hour SO2 concentrations. - }, - SO2_24H: { - UNIT: 'ppb', - RANGES: { - UNHEALTHY: { - AMOUNT: { LOWER: 186, UPPER: 304 }, - AQI: EPA_454_AQI_LEVELS.UNHEALTHY.RANGE, - }, - VERY_UNHEALTHY: { - AMOUNT: { LOWER: 305, UPPER: 604 }, - AQI: EPA_454_AQI_LEVELS.VERY_UNHEALTHY.RANGE, - }, - HAZARDOUS: { - AMOUNT: { LOWER: 605, UPPER: 804 }, - AQI: EPA_454_AQI_LEVELS.HAZARDOUS.RANGE, - }, - VERY_HAZARDOUS: { - AMOUNT: { LOWER: 805, UPPER: 1004 }, - AQI: EPA_454_AQI_LEVELS.VERY_HAZARDOUS.RANGE, - }, - }, - }, - NO2: { - UNIT: 'ppb', - RANGES: { - GOOD: { - AMOUNT: { LOWER: 0, UPPER: 53 }, - AQI: EPA_454_AQI_LEVELS.GOOD.RANGE, - }, - MODERATE: { - AMOUNT: { LOWER: 54, UPPER: 100 }, - AQI: EPA_454_AQI_LEVELS.MODERATE.RANGE, - }, - UNHEALTHY_FOR_SENSITIVE: { - AMOUNT: { LOWER: 101, UPPER: 360 }, - AQI: EPA_454_AQI_LEVELS.UNHEALTHY_FOR_SENSITIVE.RANGE, - }, - UNHEALTHY: { - AMOUNT: { LOWER: 361, UPPER: 649 }, - AQI: EPA_454_AQI_LEVELS.UNHEALTHY.RANGE, - }, - VERY_UNHEALTHY: { - AMOUNT: { LOWER: 650, UPPER: 1249 }, - AQI: EPA_454_AQI_LEVELS.VERY_UNHEALTHY.RANGE, - }, - HAZARDOUS: { - AMOUNT: { LOWER: 1250, UPPER: 1649 }, - AQI: EPA_454_AQI_LEVELS.HAZARDOUS.RANGE, - }, - VERY_HAZARDOUS: { - AMOUNT: { LOWER: 1650, UPPER: 2049 }, - AQI: EPA_454_AQI_LEVELS.VERY_HAZARDOUS.RANGE, - }, - }, - }, - }, -}; - -/** - * China AQI standard. - * [环境空气质量指数(AQI)技术规定(试行)]{@link https://www.mee.gov.cn/ywgz/fgbz/bz/bzwb/jcffbz/201203/W020120410332725219541.pdf} - * @type aqiStandard - */ -const HJ_633 = { - APPLE_SCALE: 'HJ6332012.2207', - AQI_LEVELS: EPA_454.AQI_LEVELS, - SIGNIFICANT_LEVEL: EPA_454.SIGNIFICANT_LEVEL, - - CONCENTRATIONS: { - SO2_24H: { - UNIT: 'microgramsPerM3', - RANGES: { - GOOD: { - AMOUNT: { LOWER: 0, UPPER: 50 }, - AQI: EPA_454.AQI_LEVELS.GOOD.RANGE, - }, - MODERATE: { - AMOUNT: { LOWER: 51, UPPER: 150 }, - AQI: EPA_454.AQI_LEVELS.MODERATE.RANGE, - }, - UNHEALTHY_FOR_SENSITIVE: { - AMOUNT: { LOWER: 151, UPPER: 475 }, - AQI: EPA_454.AQI_LEVELS.UNHEALTHY_FOR_SENSITIVE.RANGE, - }, - UNHEALTHY: { - AMOUNT: { LOWER: 476, UPPER: 800 }, - AQI: EPA_454.AQI_LEVELS.UNHEALTHY.RANGE, - }, - VERY_UNHEALTHY: { - AMOUNT: { LOWER: 801, UPPER: 1600 }, - AQI: EPA_454.AQI_LEVELS.VERY_UNHEALTHY.RANGE, - }, - HAZARDOUS: { - AMOUNT: { LOWER: 1601, UPPER: 2100 }, - AQI: EPA_454.AQI_LEVELS.HAZARDOUS.RANGE, - }, - VERY_HAZARDOUS: { - AMOUNT: { LOWER: 2101, UPPER: 2602 }, - AQI: EPA_454.AQI_LEVELS.VERY_HAZARDOUS.RANGE, - }, - }, - }, - SO2: { - UNIT: 'microgramsPerM3', - RANGES: { - GOOD: { - AMOUNT: { LOWER: 0, UPPER: 150 }, - AQI: EPA_454.AQI_LEVELS.GOOD.RANGE, - }, - MODERATE: { - AMOUNT: { LOWER: 151, UPPER: 500 }, - AQI: EPA_454.AQI_LEVELS.MODERATE.RANGE, - }, - UNHEALTHY_FOR_SENSITIVE: { - AMOUNT: { LOWER: 501, UPPER: 650 }, - AQI: EPA_454.AQI_LEVELS.UNHEALTHY_FOR_SENSITIVE.RANGE, - }, - UNHEALTHY: { - AMOUNT: { LOWER: 651, UPPER: 800 }, - AQI: EPA_454.AQI_LEVELS.UNHEALTHY.RANGE, - }, - // 二氧化硫(SO2)1小时平均浓度高于800 ug/m3的,不再进行其空气质量分指数计算,二氧化硫(SO2)空气质量分指数按24小时平均浓度计算的分指数报告。 - }, - }, - NO2_24H: { - UNIT: 'microgramsPerM3', - RANGES: { - GOOD: { - AMOUNT: { LOWER: 0, UPPER: 40 }, - AQI: EPA_454.AQI_LEVELS.GOOD.RANGE, - }, - MODERATE: { - AMOUNT: { LOWER: 41, UPPER: 80 }, - AQI: EPA_454.AQI_LEVELS.MODERATE.RANGE, - }, - UNHEALTHY_FOR_SENSITIVE: { - AMOUNT: { LOWER: 81, UPPER: 180 }, - AQI: EPA_454.AQI_LEVELS.UNHEALTHY_FOR_SENSITIVE.RANGE, - }, - UNHEALTHY: { - AMOUNT: { LOWER: 181, UPPER: 280 }, - AQI: EPA_454.AQI_LEVELS.UNHEALTHY.RANGE, - }, - VERY_UNHEALTHY: { - AMOUNT: { LOWER: 281, UPPER: 565 }, - AQI: EPA_454.AQI_LEVELS.VERY_UNHEALTHY.RANGE, - }, - HAZARDOUS: { - AMOUNT: { LOWER: 566, UPPER: 750 }, - AQI: EPA_454.AQI_LEVELS.HAZARDOUS.RANGE, - }, - VERY_HAZARDOUS: { - AMOUNT: { LOWER: 751, UPPER: 940 }, - AQI: EPA_454.AQI_LEVELS.VERY_HAZARDOUS.RANGE, - }, - }, - }, - NO2: { - UNIT: 'microgramsPerM3', - RANGES: { - GOOD: { - AMOUNT: { LOWER: 0, UPPER: 100 }, - AQI: EPA_454.AQI_LEVELS.GOOD.RANGE, - }, - MODERATE: { - AMOUNT: { LOWER: 101, UPPER: 200 }, - AQI: EPA_454.AQI_LEVELS.MODERATE.RANGE, - }, - UNHEALTHY_FOR_SENSITIVE: { - AMOUNT: { LOWER: 201, UPPER: 700 }, - AQI: EPA_454.AQI_LEVELS.UNHEALTHY_FOR_SENSITIVE.RANGE, - }, - UNHEALTHY: { - AMOUNT: { LOWER: 701, UPPER: 1200 }, - AQI: EPA_454.AQI_LEVELS.UNHEALTHY.RANGE, - }, - VERY_UNHEALTHY: { - AMOUNT: { LOWER: 1201, UPPER: 2340 }, - AQI: EPA_454.AQI_LEVELS.VERY_UNHEALTHY.RANGE, - }, - HAZARDOUS: { - AMOUNT: { LOWER: 2341, UPPER: 3090 }, - AQI: EPA_454.AQI_LEVELS.HAZARDOUS.RANGE, - }, - VERY_HAZARDOUS: { - AMOUNT: { LOWER: 3091, UPPER: 3840 }, - AQI: EPA_454.AQI_LEVELS.VERY_HAZARDOUS.RANGE, - }, - }, - }, - PM10_24H: { - UNIT: 'microgramsPerM3', - RANGES: { - GOOD: { - AMOUNT: { LOWER: 0, UPPER: 50 }, - AQI: EPA_454.AQI_LEVELS.GOOD.RANGE, - }, - MODERATE: { - AMOUNT: { LOWER: 51, UPPER: 150 }, - AQI: EPA_454.AQI_LEVELS.MODERATE.RANGE, - }, - UNHEALTHY_FOR_SENSITIVE: { - AMOUNT: { LOWER: 151, UPPER: 250 }, - AQI: EPA_454.AQI_LEVELS.UNHEALTHY_FOR_SENSITIVE.RANGE, - }, - UNHEALTHY: { - AMOUNT: { LOWER: 251, UPPER: 350 }, - AQI: EPA_454.AQI_LEVELS.UNHEALTHY.RANGE, - }, - VERY_UNHEALTHY: { - AMOUNT: { LOWER: 351, UPPER: 420 }, - AQI: EPA_454.AQI_LEVELS.VERY_UNHEALTHY.RANGE, - }, - HAZARDOUS: { - AMOUNT: { LOWER: 421, UPPER: 500 }, - AQI: EPA_454.AQI_LEVELS.HAZARDOUS.RANGE, - }, - VERY_HAZARDOUS: { - AMOUNT: { LOWER: 501, UPPER: 600 }, - AQI: EPA_454.AQI_LEVELS.VERY_HAZARDOUS.RANGE, - }, - }, - }, - CO_24H: { - UNIT: 'milligramsPerM3', - RANGES: { - GOOD: { - AMOUNT: { LOWER: 0, UPPER: 2 }, - AQI: EPA_454.AQI_LEVELS.GOOD.RANGE, - }, - MODERATE: { - AMOUNT: { LOWER: 3, UPPER: 4 }, - AQI: EPA_454.AQI_LEVELS.MODERATE.RANGE, - }, - UNHEALTHY_FOR_SENSITIVE: { - AMOUNT: { LOWER: 5, UPPER: 14 }, - AQI: EPA_454.AQI_LEVELS.UNHEALTHY_FOR_SENSITIVE.RANGE, - }, - UNHEALTHY: { - AMOUNT: { LOWER: 15, UPPER: 24 }, - AQI: EPA_454.AQI_LEVELS.UNHEALTHY.RANGE, - }, - VERY_UNHEALTHY: { - AMOUNT: { LOWER: 25, UPPER: 36 }, - AQI: EPA_454.AQI_LEVELS.VERY_UNHEALTHY.RANGE, - }, - HAZARDOUS: { - AMOUNT: { LOWER: 37, UPPER: 48 }, - AQI: EPA_454.AQI_LEVELS.HAZARDOUS.RANGE, - }, - VERY_HAZARDOUS: { - AMOUNT: { LOWER: 49, UPPER: 60 }, - AQI: EPA_454.AQI_LEVELS.VERY_HAZARDOUS.RANGE, - }, - }, - }, - CO: { - UNIT: 'milligramsPerM3', - RANGES: { - GOOD: { - AMOUNT: { LOWER: 0, UPPER: 5 }, - AQI: EPA_454.AQI_LEVELS.GOOD.RANGE, - }, - MODERATE: { - AMOUNT: { LOWER: 6, UPPER: 10 }, - AQI: EPA_454.AQI_LEVELS.MODERATE.RANGE, - }, - UNHEALTHY_FOR_SENSITIVE: { - AMOUNT: { LOWER: 11, UPPER: 35 }, - AQI: EPA_454.AQI_LEVELS.UNHEALTHY_FOR_SENSITIVE.RANGE, - }, - UNHEALTHY: { - AMOUNT: { LOWER: 36, UPPER: 60 }, - AQI: EPA_454.AQI_LEVELS.UNHEALTHY.RANGE, - }, - VERY_UNHEALTHY: { - AMOUNT: { LOWER: 61, UPPER: 90 }, - AQI: EPA_454.AQI_LEVELS.VERY_UNHEALTHY.RANGE, - }, - HAZARDOUS: { - AMOUNT: { LOWER: 91, UPPER: 120 }, - AQI: EPA_454.AQI_LEVELS.HAZARDOUS.RANGE, - }, - VERY_HAZARDOUS: { - AMOUNT: { LOWER: 121, UPPER: 150 }, - AQI: EPA_454.AQI_LEVELS.VERY_HAZARDOUS.RANGE, - }, - }, - }, - OZONE: { - UNIT: 'microgramsPerM3', - RANGES: { - GOOD: { - AMOUNT: { LOWER: 0, UPPER: 160 }, - AQI: EPA_454.AQI_LEVELS.GOOD.RANGE, - }, - MODERATE: { - AMOUNT: { LOWER: 161, UPPER: 200 }, - AQI: EPA_454.AQI_LEVELS.MODERATE.RANGE, - }, - UNHEALTHY_FOR_SENSITIVE: { - AMOUNT: { LOWER: 201, UPPER: 300 }, - AQI: EPA_454.AQI_LEVELS.UNHEALTHY_FOR_SENSITIVE.RANGE, - }, - UNHEALTHY: { - AMOUNT: { LOWER: 301, UPPER: 400 }, - AQI: EPA_454.AQI_LEVELS.UNHEALTHY.RANGE, - }, - VERY_UNHEALTHY: { - AMOUNT: { LOWER: 401, UPPER: 800 }, - AQI: EPA_454.AQI_LEVELS.VERY_UNHEALTHY.RANGE, - }, - HAZARDOUS: { - AMOUNT: { LOWER: 801, UPPER: 1000 }, - AQI: EPA_454.AQI_LEVELS.HAZARDOUS.RANGE, - }, - VERY_HAZARDOUS: { - AMOUNT: { LOWER: 1001, UPPER: 1200 }, - AQI: EPA_454.AQI_LEVELS.VERY_HAZARDOUS.RANGE, - }, - }, - }, - OZONE_8H: { - UNIT: 'microgramsPerM3', - RANGES: { - GOOD: { - AMOUNT: { LOWER: 0, UPPER: 100 }, - AQI: EPA_454.AQI_LEVELS.GOOD.RANGE, - }, - MODERATE: { - AMOUNT: { LOWER: 101, UPPER: 160 }, - AQI: EPA_454.AQI_LEVELS.MODERATE.RANGE, - }, - UNHEALTHY_FOR_SENSITIVE: { - AMOUNT: { LOWER: 161, UPPER: 215 }, - AQI: EPA_454.AQI_LEVELS.UNHEALTHY_FOR_SENSITIVE.RANGE, - }, - UNHEALTHY: { - AMOUNT: { LOWER: 216, UPPER: 265 }, - AQI: EPA_454.AQI_LEVELS.UNHEALTHY.RANGE, - }, - VERY_UNHEALTHY: { - AMOUNT: { LOWER: 266, UPPER: 800 }, - AQI: EPA_454.AQI_LEVELS.VERY_UNHEALTHY.RANGE, - }, - // 臭氧(O3)8小时平均浓度值高于800 ug/m3的,不再进行其空气质量分指数计算,臭氧(O3)空气质量分指数按1小时平均浓度计算的分指数报告。 - }, - }, - 'PM2.5_24H': { - UNIT: 'microgramsPerM3', - RANGES: { - GOOD: { - AMOUNT: { LOWER: 0, UPPER: 35 }, - AQI: EPA_454.AQI_LEVELS.GOOD.RANGE, - }, - MODERATE: { - AMOUNT: { LOWER: 36, UPPER: 75 }, - AQI: EPA_454.AQI_LEVELS.MODERATE.RANGE, - }, - UNHEALTHY_FOR_SENSITIVE: { - AMOUNT: { LOWER: 76, UPPER: 115 }, - AQI: EPA_454.AQI_LEVELS.UNHEALTHY_FOR_SENSITIVE.RANGE, - }, - UNHEALTHY: { - AMOUNT: { LOWER: 116, UPPER: 150 }, - AQI: EPA_454.AQI_LEVELS.UNHEALTHY.RANGE, - }, - VERY_UNHEALTHY: { - AMOUNT: { LOWER: 151, UPPER: 250 }, - AQI: EPA_454.AQI_LEVELS.VERY_UNHEALTHY.RANGE, - }, - HAZARDOUS: { - AMOUNT: { LOWER: 251, UPPER: 350 }, - AQI: EPA_454.AQI_LEVELS.HAZARDOUS.RANGE, - }, - VERY_HAZARDOUS: { - AMOUNT: { LOWER: 351, UPPER: 500 }, - AQI: EPA_454.AQI_LEVELS.VERY_HAZARDOUS.RANGE, - }, - }, - }, - }, -}; - -/** - * WAQI InstantCast. - * [A Beginner's Guide to Air Quality Instant-Cast and Now-Cast.]{@link https://aqicn.org/faq/2015-03-15/air-quality-nowcast-a-beginners-guide/} - * [Ozone AQI Scale update]{@link https://aqicn.org/faq/2016-08-10/ozone-aqi-scale-update/} - * @type aqiStandard - */ -const WAQI_INSTANT_CAST = { - APPLE_SCALE: EPA_454.APPLE_SCALE, - AQI_LEVELS: EPA_454.AQI_LEVELS, - SIGNIFICANT_LEVEL: EPA_454.SIGNIFICANT_LEVEL, - - CONCENTRATIONS: { - ...EPA_454.CONCENTRATIONS, - OZONE: { - UNIT: 'ppb', - RANGES: { - GOOD: { - AMOUNT: { LOWER: 0, UPPER: 61.5 }, - AQI: EPA_454.AQI_LEVELS.GOOD.RANGE, - }, - MODERATE: { - AMOUNT: { LOWER: 62.5, UPPER: 100.5 }, - AQI: EPA_454.AQI_LEVELS.MODERATE.RANGE, - }, - UNHEALTHY_FOR_SENSITIVE: { - AMOUNT: { LOWER: 101.5, UPPER: 151.5 }, - AQI: EPA_454.AQI_LEVELS.UNHEALTHY_FOR_SENSITIVE.RANGE, - }, - UNHEALTHY: { - AMOUNT: { LOWER: 152.5, UPPER: 204 }, - AQI: EPA_454.AQI_LEVELS.UNHEALTHY.RANGE, - }, - VERY_UNHEALTHY: { - AMOUNT: { LOWER: 205, UPPER: 404 }, - AQI: EPA_454.AQI_LEVELS.VERY_UNHEALTHY.RANGE, - }, - HAZARDOUS: { - AMOUNT: { LOWER: 405, UPPER: 504 }, - AQI: EPA_454.AQI_LEVELS.HAZARDOUS.RANGE, - }, - VERY_HAZARDOUS: { - AMOUNT: { LOWER: 505, UPPER: 604 }, - AQI: EPA_454.AQI_LEVELS.VERY_HAZARDOUS.RANGE, - }, - }, - }, - 'PM2.5': EPA_454.CONCENTRATIONS['PM2.5_24H'], - PM10: EPA_454.CONCENTRATIONS.PM10_24H, - CO: EPA_454.CONCENTRATIONS.CO_8H, - OZONE_8H: undefined, - 'PM2.5_24H': undefined, - PM10_24H: undefined, - CO_8H: undefined, - SO2_24H: undefined, - }, -}; - -/** - * Check passed parameter is or not an object - * @param {any} object - Value you wish to check - * @return {boolean} - Return `true` if passed parameter is an object - */ -const isObject = (object) => typeof object === 'object' && !Array.isArray(object) && object !== null; - -/** - * Check passed parameter is or not a non-empty string - * @author WordlessEcho - * @param {any} string - Value you wish to check - * @return {boolean} - Return `true` if passed parameter is a non-empty string - */ -const isNonEmptyString = (string) => typeof string === 'string' && string.length > 0; - -/** - * Check passed parameter is or not a non-NaN number - * @author WordlessEcho - * @param {any} number - Value you wish to check - * @return {boolean} - Return `true` if passed parameter is a non-NaN number - */ -const isNonNanNumber = (number) => typeof number === 'number' && !Number.isNaN(number); - -/** - * Check passed parameter is or not a valid latitude - * @author WordlessEcho - * @param {number} latitude - Latitude you wish to check - * @return {boolean} - Return `true` if passed parameter is a valid latitude - */ -const isLatitude = (latitude) => isNonNanNumber(latitude) && latitude >= -90 && latitude <= 90; - -/** - * Check passed parameter is or not a valid longitude - * @author WordlessEcho - * @param {number} longitude - Longitude you wish to check - * @return {boolean} - Return `true` if passed parameter is a valid longitude - */ -const isLongitude = (longitude) => ( - isNonNanNumber(longitude) && longitude >= -180 && longitude <= 180 -); - -/** - * Check passed parameter is or not a valid location - * @author WordlessEcho - * @param {coordinate} location - Location with latitude and longitude - * @return {boolean} - Return `true` if passed parameter is a valid location - */ -const isLocation = (location) => isLatitude(location?.latitude) && isLongitude(location?.longitude); - -/** - * Parse JSON with exception handler - * @author WordlessEcho - * @param {string} stringJson - String to be parsed - * @param {(Error) => any} catcher - * @return {Object|any} - Parsed JSON or returned from `catcher` - */ -const parseJson = (stringJson, catcher) => { - // eslint-disable-next-line functional/no-try-statement - try { - return JSON.parse(stringJson); - } catch (e) { - return catcher(e); - } -}; -/** - * Parse JSON with default value - * @author WordlessEcho - * @param {string} stringJson - String to be parsed - * @param {any} defaultValue - Value to be returned if failed to parse `stringJson` - * @return {Object|any} - Parsed JSON or `defaultValue` if failed - */ -const parseJsonWithDefault = (stringJson, defaultValue) => { - // eslint-disable-next-line functional/no-try-statement - try { - return JSON.parse(stringJson); - } catch (e) { - return defaultValue; - } -}; - -/** - * Check passed parameter is or not a valid range - * @author WordlessEcho - * @param {any} range - Range to be checked - * @return {boolean} - Return `true` if passed parameter is a valid range - */ -const isRange = (range) => isNonNanNumber(range?.LOWER) && isNonNanNumber(range?.UPPER); -/** - * Check passed parameter is or not a positive with zero range - * @author WordlessEcho - * @param {any} range - Range to be checked - * @return {boolean} - Return `true` if passed parameter is a positive with zero range - */ -const isPositiveWithZeroRange = (range) => isRange(range) && range.LOWER >= 0 && range.UPPER >= 0; -/** - * Check passed parameter is or not a positive range - * @author WordlessEcho - * @param {any} range - Range to be checked - * @return {boolean} - Return `true` if passed parameter is a positive range - */ -const isPositiveRange = (range) => ( - isPositiveWithZeroRange(range) && range.LOWER !== 0 && range.UPPER !== 0 -); - -/** - * Get settings from Box.js - * @author WordlessEcho - * @author VirgilClyne - * @param {Object} envs - `envs` from {@link getENV} - * @return {settingsV1} - Valid settings - */ -const toSettings = (envs) => { - const settings = database.Weather.Settings; - // eslint-disable-next-line functional/no-conditional-statement - if (parseJsonWithDefault(envs?.Switch, false)) { - const wikiLink = 'https://github.com/VirgilClyne/iRingo/wiki/%F0%9F%8C%A4%E5%A4%A9%E6%B0%94#%E4%BD%BF%E7%94%A8%E8%AF%B4%E6%98%8E'; - // eslint-disable-next-line functional/no-expression-statement - $.log(`⚠️${toSettings.name}:您好像刚刚升级到${$.name},不妨看看新功能并重新设置一次模块? ${wikiLink}`, ''); - } - - return { - switch: parseJsonWithDefault(envs?.Settings?.Switch, settings.Switch), - nextHour: { - switch: parseJsonWithDefault( - envs?.Settings?.NextHour?.Switch, - settings.NextHour.Switch, - ), - source: isNonEmptyString(envs?.Settings?.NextHour?.Source) ? envs.Settings.NextHour.Source - : settings.NextHour.Source, - }, - aqi: { - switch: parseJsonWithDefault(envs?.Settings?.AQI?.Switch, settings.AQI.Switch), - targets: parseJsonWithDefault(`[${envs?.Settings?.AQI?.Targets}]`, settings.AQI.Targets), - local: { - switch: parseJsonWithDefault( - envs?.Settings?.AQI?.Local?.Switch, - settings.AQI.Local.Switch, - ), - standard: isNonEmptyString(envs?.Settings?.AQI?.Local?.Standard) - ? envs.Settings.AQI.Local.Standard : settings.AQI.Local.Standard, - }, - source: isNonEmptyString(envs?.Settings?.AQI?.Source) ? envs.Settings.AQI.Source - : settings.AQI.Source, - comparison: { - switch: parseJsonWithDefault( - envs?.Settings?.AQI?.Comparison.Switch, - settings.AQI.Comparison.Switch, - ), - source: isNonEmptyString(envs?.Settings?.AQI?.Comparison?.Source) - ? envs.Settings.AQI.Comparison.Source : settings.AQI.Comparison.Source, - }, - }, - map: { - aqi: parseJsonWithDefault(envs?.Settings?.Map?.AQI, settings.Map.AQI), - }, - apis: { - weatherOl: { - httpHeaders: parseJsonWithDefault( - envs?.Settings?.APIs?.WeatherOl?.HTTPHeaders, - settings.APIs.WeatherOL.HTTPHeaders, - ), - }, - colorfulClouds: { - httpHeaders: parseJsonWithDefault( - envs?.Settings?.APIs?.ColorfulClouds?.HTTPHeaders, - settings.APIs.ColorfulClouds.HTTPHeaders, - ), - token: envs?.Settings?.APIs?.ColorfulClouds?.Token, - forceCnForAqi: parseJsonWithDefault( - envs?.Settings?.APIs?.ColorfulClouds?.ForceCNForAQI, - settings.APIs.ColorfulClouds.ForceCNForAQI, - ), - forceCnForComparison: parseJsonWithDefault( - envs?.Settings?.APIs?.ColorfulClouds?.ForceCNForComparison, - settings.APIs.ColorfulClouds.ForceCNForComparison, - ), - }, - waqi: { - httpHeaders: parseJsonWithDefault( - envs?.Settings?.APIs?.WAQI?.HTTPHeaders, - settings.APIs.WAQI.HTTPHeaders, - ), - token: envs?.Settings?.APIs?.WAQI?.Token, - }, - }, - log: { - level: isNonEmptyString(envs?.Settings?.Log?.Level) ? envs.Settings.Log.Level - : settings.Log.Level, - Location: parseJsonWithDefault(envs?.Settings?.Log?.Location, settings.Log.Location), - }, - }; -}; - -/** - * Get caches from Box.js - * @author WordlessEcho - * @param {Object} envs - `envs` from {@link getENV} - * @return {cachesV1} - Valid caches - */ -const toCaches = (envs) => ({ - aqis: { - ...(isObject(envs?.Caches?.aqis) && envs.Caches.aqis), - }, - waqi: { - ...(isObject(envs?.Caches?.waqi) && envs.Caches.waqi), - tokens: { - ...(isObject(envs.Caches?.waqi?.tokens) && envs.Caches.waqi.tokens), - }, - }, -}); - -const settings = toSettings(getENV('iRingo', 'Weather', database)); - -/** - * Log helper - * @param {"debug" | "info" | "warn" | "error"} level - Log level - * @param {string} message - Log message - */ -const logger = (level, message) => { - /** - * Get emoji by log level - * @param {"debug" | "info" | "warn" | "error"} l - Log level - * @return {string} - Emoji for log level - */ - const toEmoji = (l) => { - switch (l) { - case 'error': - return '❗️'; - case 'warn': - return '⚠️'; - case 'info': - return 'ℹ️'; - case 'debug': - default: - return '🚧'; - } - }; - - /** - * Get required tags - * @param {"debug" | "info" | "warn" | "error"} l - Log level - * @return {string[]} - Required tags - */ - const matchedTags = (l) => { - const tags = ['debug', 'info', 'warn', 'error']; - switch (l) { - case 'debug': - return tags; - case 'warn': - return tags.slice(2); - case 'error': - return tags.slice(3); - case 'info': - default: - return tags.slice(1); - } - }; - - // eslint-disable-next-line functional/no-conditional-statement - if (matchedTags(settings.log.level).includes(level)) { - // eslint-disable-next-line functional/no-expression-statement - $.log(`${toEmoji(level)} ${message}`, ''); - } -}; - -/** - * Get AQI from cache - * @author WordlessEcho - * @param {Object.} cachedAqis - Caches of AQIs - * @param {number} timestamp - UNIX timestamp of cached time - * @param {coordinate} location - Coordinate of AQI info - * @param {?string} stationName - `AirQuality.source` from QWeather - * @param {appleAqiScales} scaleName - Part before the '.' in iOS `AirQuality.scale` - * @return {cachedAqi | {aqi: -1}} - Matched AQI info - */ -const getCachedAqi = (cachedAqis, timestamp, location, stationName, scaleName) => { - const pythagoreanTheorem = (a, b) => Math.sqrt(a * a + b * b); - - if ( - isObject(cachedAqis) && isNonNanNumber(timestamp) && timestamp > 0 - && isLocation(location) && isNonEmptyString(scaleName) - ) { - const cacheTimestampString = Object.keys(cachedAqis).find((timestampString) => { - const cachedTimestamp = parseInt(timestampString, 10); - return isNonNanNumber(cachedTimestamp) && cachedTimestamp >= timestamp - && cachedTimestamp < timestamp + 1000 * 60 * 60; - }); - const cacheTimestamp = parseInt(cacheTimestampString, 10); - - const cache = isNonNanNumber(cacheTimestamp) - ? cachedAqis[cacheTimestamp].find((aqiInfo) => ( - isNonEmptyString(stationName) - ? aqiInfo?.stationName === stationName && aqiInfo?.scaleName === scaleName - // Cannot get station name - : pythagoreanTheorem( - Math.abs(aqiInfo.location.longitude - location.longitude), - Math.abs(aqiInfo.location.latitude - location.latitude), - // 0.085 is an approximation by observing air quality map from Apple Weather - ) < 0.085 && aqiInfo?.scaleName === scaleName - )) - : { aqi: -1 }; - - if (isNonNanNumber(cache?.aqi) && cache.aqi >= 0) { - logger('info', `${getCachedAqi.name}:找到了已缓存的AQI信息:AQI值为${cache.aqi}`); - return cache; - } - } - - return { aqi: -1 }; -}; - -/** - * Get token from cache - * @author WordlessEcho - * @param {Object.} cachedTokens - Caches of WAQI tokens - * @param {number} stationId - Station ID - * @return {string} - Matched token - */ -const getCachedWaqiToken = (cachedTokens, stationId) => { - if (isObject(cachedTokens) && isNonNanNumber(stationId)) { - // 1 hour - const cacheLimit = (+(new Date())) - 1000 * 60 * 60; - const cachedToken = cachedTokens?.[stationId]; - - if (isNonNanNumber(cachedToken?.timestamp) && cachedToken.timestamp > cacheLimit - && isNonEmptyString(cachedToken?.token)) { - logger('info', `${getCachedWaqiToken.name}:找到了监测站ID ${stationId}的token缓存`); - return cachedToken.token; - } - } - - return ''; -}; - -/** - * Return caches for `setjson` - * @author WordlessEcho - * @param {cachesV1} caches - Caches of iRingo.Weather.Caches - * @param {number} timestamp - UNIX timestamp of cached time - * @param {coordinate} location - Coordinate of AQI info - * @param {?string} stationName - `AirQuality.source` from QWeather - * @param {scaleNames} scaleName - Part before the '.' in iOS `AirQuality.scale` - * @param {number} aqi - Air quality index - * @return {cachesV1} - Cache will not be edited if any parameter is invalid. - */ -const cacheAqi = (caches, timestamp, location, stationName, scaleName, aqi) => { - // Remove caches before 36 hours ago - const cacheLimit = (+new Date()) - 1000 * 60 * 60 * 36; - - const validAqis = isObject(caches?.aqis) ? Object.fromEntries(Object.entries(caches.aqis) - .filter(([timestampString, aqisInfo]) => { - const cachedTimestamp = parseInt(timestampString, 10); - return isNonNanNumber(cachedTimestamp) && cachedTimestamp > cacheLimit - && Array.isArray(aqisInfo); - }) - .map(([timestampString, aqisInfo]) => [ - timestampString, - aqisInfo.filter((aqiInfo) => ( - isNonNanNumber(aqiInfo?.aqi) && aqiInfo.aqi >= 0 && isLocation(aqiInfo?.location) - && isNonEmptyString(aqiInfo?.scaleName) - )), - ])) : {}; - - if ( - isNonNanNumber(timestamp) && timestamp > cacheLimit && isLocation(location) - && isNonEmptyString(scaleName) && isNonNanNumber(aqi) && aqi >= 0 - ) { - const cacheTimestampString = Object.keys(validAqis).find((timestampString) => { - const cachedTimestamp = parseInt(timestampString, 10); - return isNonNanNumber(cachedTimestamp) && cachedTimestamp >= timestamp - && cachedTimestamp < timestamp + 1000 * 60 * 60; - }); - const cacheTimestamp = parseInt(cacheTimestampString, 10); - - const existedCache = isNonNanNumber(cacheTimestamp) - ? validAqis[cacheTimestamp].find((aqiInfo) => ( - isNonEmptyString(stationName) - ? aqiInfo?.stationName === stationName && aqiInfo?.scaleName === scaleName - // Cannot get station name - // https://www.mee.gov.cn/gkml/hbb/bwj/201204/W020140904493567314967.pdf - : Math.abs(aqiInfo.location.longitude - location.longitude) < 0.045 - && Math.abs(aqiInfo.location.latitude - location.latitude) < 0.045 - && aqiInfo?.scaleName === scaleName - )) - : { aqi: -1 }; - - if (!isNonNanNumber(existedCache?.aqi) || existedCache.aqi < 0) { - logger( - 'debug', - `${cacheAqi.name}:已将当前AQI信息缓存,AQI信息:\n` - + `时间:${new Date(timestamp)}\n` - + `${settings.log.location ? `经度:${location.longitude},纬度:${location.latitude}\n` : ''}` - + `${isNonEmptyString(stationName) ? `监测站:${stationName}\n` : ''}` - + `AQI标准:${scaleName}\nAQI:${aqi}`, - ); - - return { - ...(isObject(caches) && caches), - aqis: { - ...validAqis, - [isNonNanNumber(cacheTimestamp) ? cacheTimestamp : timestamp]: [ - ...(Array.isArray(validAqis?.[cacheTimestamp]) ? validAqis[cacheTimestamp] : []), - { - location, - ...(isNonEmptyString(stationName) && { stationName }), - scaleName, - aqi, - }, - ], - }, - }; - } - } - - return { - ...(isObject(caches) && caches), - aqis: { ...validAqis }, - }; -}; - -/** - * Return caches for `setjson` - * @author WordlessEcho - * @param {cachesV1} caches - Caches of iRingo.Weather.Caches - * @param {number} stationId - Station ID - * @param {string} token - Token of station - * @return {cachesV1} - Cache will not be edited if any parameter is invalid. - */ -const cacheWaqiToken = (caches, stationId, token) => { - // Remove caches before 1 hour ago - const cacheLimit = (+(new Date())) - 1000 * 60 * 60; - - const validTokens = isObject(caches?.waqi?.tokens) - ? Object.fromEntries(Object.entries(caches.waqi.tokens) - .filter(([stationIdString, tokenInfo]) => ( - isNonNanNumber(parseInt(stationIdString, 10)) && isNonNanNumber(tokenInfo?.timestamp) - && tokenInfo.timestamp > cacheLimit && isNonEmptyString(tokenInfo?.token) - ))) : {}; - - return { - ...(isObject(caches) && caches), - waqi: { - ...(isObject(caches?.waqi) && caches?.waqi), - tokens: { - ...validTokens, - ...(isNonNanNumber(stationId) && isNonEmptyString(token) - && !logger('info', `${cacheWaqiToken.name}:已缓存监测站ID ${stationId}的token`) && { - [stationId]: { timestamp: (+(new Date())), token }, - }), - }, - }, - }; -}; - -// TODO: I am too afraid about regex to rewrite this -/** - * Get Origin Parameters - * @author VirgilClyne - * @author WordlessEcho - * @param {String} path - Path of URL - * @return {Object.<'ver'|'language'|'lat'|'lng'|'countryCode', string>|{}} - - * `version`, `language`, `latitude`, `longitude` and `regionCode` from path. - * Empty object will be returned if type of path is invalid. - */ -const getParams = (path) => { - if (!isNonEmptyString(path)) { - return {}; - } - - const regExp = /^(?v1|v2|v3)\/weather\/(?[\w-_]+)\/(?-?\d+\.\d+)\/(?-?\d+\.\d+).*(?country=[A-Z]{2})?.*/i; - const result = path.match(regExp); - - return isObject(result?.groups) ? result.groups : {}; -}; - -/** - * Get the nearest station info from WAQI - * @author WordlessEcho - * @param {coordinate} location - Location for finding the nearest station - * @param {"mapq" | "mapq2"} mapqVersion - Version of mapq. Using 1 (mapq) if invalid. - * @param {Object} [headers] - HTTP headers - * @return {Promise} - Result from WAQI in mapq2 format - */ -const waqiNearest = ( - location, - mapqVersion, - headers = { 'Content-Type': 'application/json' }, -) => new Promise((resolve) => { - if (!isLocation(location)) { - // eslint-disable-next-line functional/no-expression-statement - resolve({ - status: 'error', - data: `${waqiNearest.name}: Invalid location` - + `Latitude: ${location?.latitude}` - + `Longitude: ${location?.longitude}`, - }); - return; - } - - // eslint-disable-next-line functional/no-expression-statement - $.get( - { - headers, - // n is stations to return - url: `https://api.waqi.info/${mapqVersion}/nearest?n=1&geo=1/${location.latitude}/${location.longitude}`, - }, - (error, _response, data) => { - if (error) { - // eslint-disable-next-line functional/no-expression-statement - resolve({ - status: 'error', - data: `${waqiNearest.name}: Error: ${error}\n` - + `Data: ${data}`, - }); - return; - } - - const result = parseJson(data, (e) => ({ - status: 'error', - data: `${waqiNearest.name}: Data from WAQI is not a valid JSON\n` - + `Error: ${e}\n` - + `Data: ${data}`, - })); - - if (isNonEmptyString(result.status) && result.status !== 'ok') { - // eslint-disable-next-line functional/no-expression-statement - resolve({ - status: 'error', - data: result?.data ?? result?.message ?? result?.reason - ?? `${waqiNearest.name}: WAQI return a unknown error\nData: ${data}`, - }); - return; - } - - // eslint-disable-next-line functional/no-expression-statement - resolve(result); - }, - ); -}); - -/** - * Get token for public from WAQI - * @author WordlessEcho - * @param {number} [stationId] - ID of station - * @param {Object} [headers] - HTTP headers - * @return {Promise<{status: "ok" | "error", data: string}>} - - * Token in `data` if ok. Error message in `data` if failed. - */ -// eslint-disable-next-line no-unused-vars -const waqiToken = (stationId, headers = { 'Content-Type': 'application/json' }) => new Promise((resolve) => { - // eslint-disable-next-line functional/no-expression-statement - $.get( - { headers, url: `https://api.waqi.info/api/token/${isNonNanNumber(stationId) ? stationId : ''}` }, - (error, _response, data) => { - if (error) { - // eslint-disable-next-line functional/no-expression-statement - resolve({ - status: 'error', - data: `${waqiToken.name}: Error: ${error}\n` - + `Data: ${data}`, - }); - return; - } - - const result = parseJson(data, (e) => ({ - status: 'error', - data: `${waqiToken.name}: Data from WAQI is not a valid JSON\n` - + `Error: ${e}\n` - + `Data: ${data}`, - })); - - if (result.status === 'error') { - // eslint-disable-next-line functional/no-expression-statement - resolve(result); - return; - } - - if (result?.rxs?.status !== 'ok') { - // eslint-disable-next-line functional/no-expression-statement - resolve({ - status: 'error', - data: `${waqiToken.name}: WAQI returned an unexpected status\n` - + `rxs.status: ${result?.rxs?.status}` - + `Data: ${data}`, - }); - return; - } - - if (!Array.isArray(result?.rxs?.obs)) { - // eslint-disable-next-line functional/no-expression-statement - resolve({ - status: 'error', - data: `${waqiToken.name}: rxs.obs is not an array.\n` - + `rxs.obs type: ${typeof result?.rxs?.obs}` - + `Data: ${data}`, - }); - return; - } - - const token = result.rxs.obs.find((obs) => (isNonEmptyString(obs?.msg?.token)))?.msg?.token; - if (!isNonEmptyString(token)) { - // eslint-disable-next-line functional/no-expression-statement - resolve({ - status: 'error', - data: `${waqiToken.name}: No valid token found\n` - + `Token type: ${typeof token}\n` - + `Token length: ${token?.length}\n` - + `Data: ${data}`, - }); - return; - } - - // eslint-disable-next-line functional/no-expression-statement - resolve({ status: 'ok', data: token }); - }, - ); -}); - -/** - * Get data by using token from The World Air Quality Project. - * [API - Air Quality Programmatic APIs]{@link https://aqicn.org/api/} - * @author VirgilClyne - * @author WordlessEcho - * @param {?coordinate} location - Required in feed based on location - * @param {?number} stationId - Required in feed based on station ID - * @param {string} token - Token for WAQI API. - * [Air Quality Open Data Platform]{@link https://aqicn.org/data-platform/token/} - * @param {Object} [headers] - HTTP headers - * @return {Promise} - Feed data from WAQI - */ -const waqiV2 = ( - location, - stationId, - token, - headers = { 'Content-Type': 'application/json' }, -) => new Promise((resolve) => { - if (!isNonEmptyString(token)) { - // eslint-disable-next-line functional/no-expression-statement - resolve({ - status: 'error', - data: `${waqiV2.name}: Invalid token\n` - + `Token type: ${typeof token}\n` - + `Token length: ${token?.length}`, - }); - return; - } - - /** - * Handle errors into WAQI format - * @author WordlessEcho - * @param {Error} err - Error from get of `Env.js` - * @param {string} d - Data from get of `Env.js` - * @return {waqiFeed|waqiError} - Data in WAQI format - */ - const getResult = (err, d) => { - if (err) { - return { - status: 'error', - data: `${waqiV2.name}: Error: ${err}\n` - + `Data: ${d}`, - }; - } - - const result = parseJson(d, (e) => ({ - status: 'error', - data: 'Data from WAQI is not a valid JSON\n' - + `Error: ${e}\n` - + `Data: ${d}`, - })); - - if (!isNonEmptyString(result?.status)) { - return { - status: 'error', - data: 'WAQI returned an unknown status\n' - + `Data: ${d}`, - }; - } - - return result; - }; - - const baseUrl = 'https://api.waqi.info'; - if (isLocation(location)) { - // eslint-disable-next-line functional/no-expression-statement - $.get( - { - headers, - url: `${baseUrl}/feed/geo:${location.latitude};${location.longitude}/?token=${token}`, - }, - (error, _response, data) => { - // eslint-disable-next-line functional/no-expression-statement - resolve(getResult(error, data)); - }, - ); - return; - } - - if (isNonNanNumber(stationId)) { - // eslint-disable-next-line functional/no-expression-statement - $.get( - { - headers, - url: `${baseUrl}/feed/@${stationId}/?token=${token}`, - }, - (error, _response, data) => { - // eslint-disable-next-line functional/no-expression-statement - resolve(getResult(error, data)); - }, - ); - return; - } - - // eslint-disable-next-line functional/no-expression-statement - resolve({ - status: 'error', - data: `${waqiV2.name}: Invalid parameters\n` - + `Location: ${JSON.stringify(location)}` - + `Station ID: ${stationId}`, - }); -}); - -/** - * Get data from WAQI old API - * @author WordlessEcho - * @param {"now" | "aqi"} type - Type of API - * @param {number} stationId - ID of station - * @param {?string} body - HTTP body - * @param {Object} [headers] - HTTP headers - * @return {Promise} - Data from WAQI - */ -// eslint-disable-next-line no-unused-vars -const waqiV1 = ( - type, - stationId, - body, - headers = { 'Content-Type': 'application/json' }, -) => new Promise((resolve) => { - if (!isNonNanNumber(stationId)) { - // eslint-disable-next-line functional/no-expression-statement - resolve({ - status: 'error', - data: `${waqiV1.name}: Invalid station ID\n` - + `Station ID: ${stationId}`, - }); - return; - } - - const baseUrl = 'https://api.waqi.info'; - - // eslint-disable-next-line functional/no-expression-statement - $.post( - { - headers, - url: `${baseUrl}/api/feed/@${stationId}/${type}.json`, - ...(isNonEmptyString(body) && { body }), - }, - (error, response, data) => { - if (error) { - // eslint-disable-next-line functional/no-expression-statement - resolve({ - status: 'error', - data: `${waqiV1.name}: Error: ${error}\n` - + `Data: ${data}`, - }); - return; - } - - const result = parseJson(data, (e) => ({ - status: 'error', - data: 'Data from WAQI is not a valid JSON\n' - + `Error: ${e}\n` - + `Data: ${data}`, - })); - - if (!isNonEmptyString(result?.rxs?.status)) { - // eslint-disable-next-line functional/no-expression-statement - resolve({ - status: 'error', - data: 'WAQI returned an unknown status\n' - + `Data: ${data}`, - }); - return; - } - - // eslint-disable-next-line functional/no-expression-statement - resolve(result); - }, - ); -}); - -/** - * Convert data from {@link waqiNearest} to feed format - * @author WordlessEcho - * @param {"mapq" | "mapq2"} version - Version of mapq - * @param {waqiMapq|waqiMapq2|waqiError} nearestData - Data from {@link waqiNearest} - * @return {(waqiFeed|waqiError)[]} - Data in feed format - */ -const waqiNearestToFeed = (version, nearestData) => { - /** - * Get error message from WAQI - * @author WordlessEcho - * @param {"mapq" | "mapq2"} mapqVersion - WAQI mapq version - * @param {waqiError} data - WAQI mapq data - * @return {string} - Error message - */ - const toErrorMessage = (mapqVersion, data) => { - const forUnknown = `${waqiNearestToFeed.name}: Unknown error from WAQI.\n` - + `Data: ${JSON.stringify(nearestData)}`; - - if (isNonEmptyString(data?.data)) { - return data.data; - } - - switch (mapqVersion) { - case 'mapq2': - return isNonEmptyString(data?.reason) ? data.reason : forUnknown; - case 'mapq': - return isNonEmptyString(data?.message) ? data.message : forUnknown; - default: - return forUnknown; - } - }; - - if (nearestData.status === 'error') { - return [{ - status: 'error', - data: toErrorMessage(version, nearestData), - }]; - } - - /** - * Convert mapq(1) time to YYYY-MM-DDTHH:MM:SS+/-timezone - * @author WordlessEcho - * @param {{t: number} | {utime: string}} data - Data with `t` or `utime` - * @return {string} - YYYY-MM-DDTHH:MM:SS+/-timezone format ISO time - */ - const serverTimeToIsoString = (data) => { - if (isNonNanNumber(data?.t) && data.t > 0) { - return `${new Date(data.t * 1000).toISOString().slice(0, -5)}+00:00`; - } - - if (!Number.isNaN(Date.parse(data?.utime))) { - return data.utime; - } - - return `${(new Date((new Date()).setMinutes(0, 0, 0))).toISOString().slice(0, -5)}+00:00`; - }; - - switch (version) { - case 'mapq': - if (!Array.isArray(nearestData?.d)) { - return [{ - status: 'error', - data: `${waqiNearestToFeed.name}: \`d\` is not an array\n` - + `Data: ${JSON.stringify(nearestData)}`, - }]; - } - - return nearestData.d.map((station) => { - const aqi = parseInt(station?.v, 10); - const stationId = parseInt(station?.x, 10); - // nna: Local language. nlo: English. - const stationName = station?.nna ?? station?.nlo; - - if (!isNonNanNumber(aqi) || aqi < 0) { - return { - status: 'error', - data: `${waqiNearestToFeed.name}: Invalid AQI\n` - + `AQI: ${station?.v}\n` - + `Station data: ${JSON.stringify(station)}`, - }; - } - - const isoTime = serverTimeToIsoString(station); - - return { - status: 'ok', - data: { - aqi, - ...(!Number.isNaN(stationId) && { idx: stationId }), - attributions: [{ - url: 'https://waqi.info/', - name: 'The World Air Quality Project', - }], - city: { - geo: station.geo, - ...(isNonEmptyString(stationName) && { name: stationName }), - url: 'https://aqicn.org', - location: '', - }, - ...(isNonEmptyString(station?.pol) && { dominentpol: station.pol }), - iaqi: {}, - time: { - s: isoTime.slice(0, -6), - tz: isoTime.slice(-6), - v: Date.parse(isoTime) / 1000, - iso: isoTime, - }, - forecast: {}, - debug: {}, - }, - }; - }); - case 'mapq2': - if (!Array.isArray(nearestData?.data?.stations)) { - return [{ - status: 'error', - data: `${waqiNearestToFeed.name}: \`data.stations\` is not an array\n` - + `Data: ${JSON.stringify(nearestData)}`, - }]; - } - - return nearestData.data.stations.map((station) => { - const aqi = parseInt(station?.aqi, 10); - const stationId = parseInt(station?.idx, 10); - - if (!isNonNanNumber(aqi) || aqi < 0) { - return { - status: 'error', - data: `${waqiNearestToFeed.name}: Invalid AQI\n` - + `AQI: ${station?.aqi}\n` - + `Station data: ${JSON.stringify(station)}`, - }; - } - - const isoTime = serverTimeToIsoString(station); - - return { - status: 'ok', - data: { - aqi, - ...(!Number.isNaN(stationId) && { idx: stationId }), - attributions: [{ - url: 'https://waqi.info/', - name: 'The World Air Quality Project', - }], - city: { - geo: station.geo, - ...(isNonEmptyString(station?.name) && { name: station.name }), - url: 'https://aqicn.org', - location: '', - }, - iaqi: {}, - time: { - s: isoTime.slice(0, -6), - tz: isoTime.slice(-6), - v: Date.parse(isoTime) / 1000, - iso: isoTime, - }, - forecast: {}, - debug: {}, - }, - }; - }); - default: - return [{ - status: 'error', - data: `${waqiNearestToFeed.name}: Unsupported mapq version.`, - }]; - } -}; - -/** - * Convert data from {@link waqiV1} to feed format - * @author WordlessEcho - * @param {waqiNowFeed|waqiAqiFeed|waqiError} v1Data - Data from {@link waqiV1} - * @return {(waqiFeed|waqiError)[]} - Data in feed format - */ -// eslint-disable-next-line no-unused-vars -const waqiV1ToFeed = (v1Data) => { - const unknownError = `${waqiV1ToFeed.name}: Unknown error from WAQI\n` - + `Data: ${JSON.stringify(v1Data)}`; - - if (v1Data?.status === 'error') { - return [{ - status: 'error', - data: v1Data?.data ?? unknownError, - }]; - } - - if (v1Data?.rxs?.status !== 'ok') { - return [{ - status: 'error', - data: unknownError, - }]; - } - - if (!Array.isArray(v1Data?.rxs?.obs)) { - return [{ - status: 'error', - data: `${waqiV1ToFeed.name}: \`d\` is not an array\n` - + `Data: ${JSON.stringify(v1Data)}`, - }]; - } - - /** - * Make sure time in data is in YYYY-MM-DDTHH:MM:SS+/-timezone format - * @author WordlessEcho - * @param {{msg: {time: {iso: string}}}} data - Data with `msg.time.iso` - * @return {string} - YYYY-MM-DDTHH:MM:SS+/-timezone format ISO time - */ - const serverTimeToIsoString = (data) => { - if (!Number.isNaN(Date.parse(data?.msg?.time?.iso))) { - return data.msg.time.iso; - } - - return `${(new Date((new Date()).setMinutes(0, 0, 0))).toISOString().slice(0, -5)}+00:00`; - }; - - /** - * Try to convert debug time in YYYY-MM-DDTHH:MM:SS+/-timezone format - * @author WordlessEcho - * @param {{msg: {xsync: {gen: number}}} | {msg: {debug: {sync: string}}}} data - - * Data with `msg.xsync.gen` or `msg.debug.sync` - * @return {string} - Return empty string if data is invalid. - */ - const getDebug = (data) => { - if (isNonNanNumber(data?.msg?.xsync?.gen) && data.msg.xsync.gen > 0) { - return `${(new Date(data.msg.xsync.gen * 1000)).toISOString().slice(0, -5)}+00:00`; - } - - if (!Number.isNaN(Date.parse(data.msg?.debug?.sync))) { - return data.msg.debug.sync; - } - - return ''; - }; - - return v1Data.rxs.obs.map((station) => { - if (!isNonEmptyString(station?.status) || (station.status !== 'ok' && station.status !== 'error')) { - return { - status: 'error', - data: `${waqiV1ToFeed.name}: Unknown status from WAQI\n` - + `Station data: ${JSON.stringify(station)}`, - }; - } - - if (!isNonNanNumber(station?.msg?.aqi) || station.msg.aqi < 0) { - return { - status: 'error', - data: `${waqiV1ToFeed.name}: Invalid AQI\n` - + `Station data: ${JSON.stringify(station)}`, - }; - } - - const debugTime = getDebug(station); - return { - status: station.status, - data: { - ...station.msg, - attributions: Array.isArray(station.msg?.attributions) - && station.msg.attributions.length > 0 ? station.msg.attributions - : [{ - url: 'https://waqi.info/', - name: 'The World Air Quality Project', - }], - city: { - url: 'https://aqicn.org', - location: '', - ...station.msg?.city, - }, - iaqi: { ...station.msg?.iaqi }, - time: { - ...station.msg?.time, - iso: serverTimeToIsoString(station), - }, - forecast: { ...station.msg?.forecast }, - }, - debug: { ...(debugTime.length > 0 ? { sync: debugTime } : {}) }, - }; - }); -}; - -/** - * Get data from "气象在线". This API could be considered as unconfigurable ColorfulClouds API. - * [简介 | 彩云天气 API]{@link https://docs.caiyunapp.com/docs/v2.2/intro} - * [通用预报接口/v2.2 - CaiyunWiki]{@link https://open.caiyunapp.com/%E9%80%9A%E7%94%A8%E9%A2%84%E6%8A%A5%E6%8E%A5%E5%8F%A3/v2.2} - * @author VirgilClyne - * @author WordlessEcho - * @param {"forecast" | "realtime"} type - `forecast` or `realtime` - * @param {coordinate} location - { latitude, longitude } - * @param {Object} headers - HTTP headers - * @return {Promise} data from "气象在线" - */ -const weatherOl = ( - type, - location, - headers = { 'Content-Type': 'application/json' }, -) => new Promise((resolve) => { - const apiVersion = 'v2.2'; - if (!isLocation(location)) { - // eslint-disable-next-line functional/no-expression-statement - resolve({ - status: 'failed', - error: `${weatherOl.name}: Invalid location: ${JSON.stringify(location)}`, - api_version: apiVersion, - }); - return; - } - - const request = { - headers, - url: 'https://www.weatherol.cn/api/minute/getPrecipitation' - + `?type=${type}` - + `&ll=${location.longitude},${location.latitude}`, - }; - - // eslint-disable-next-line functional/no-expression-statement - $.get(request, (error, response, data) => { - if (error || data === 'error') { - // eslint-disable-next-line functional/no-expression-statement - resolve({ - status: 'failed', - error: `${weatherOl.name}: ${error && `Error: ${error}\n`}Data: ${data}`, - api_version: apiVersion, - }); - return; - } - - const result = parseJson(data, (e) => ({ - status: 'failed', - error: `${weatherOl.name}: Data from WeatherOL is not a valid JSON\n` - + `Error: ${e}` - + `Data: ${data}`, - })); - - if (result?.status !== 'ok') { - const version = isNonNanNumber(result?.api_version) ? `v${result.api_version}` : apiVersion; - - // eslint-disable-next-line functional/no-expression-statement - resolve(isNonEmptyString(result?.status) - // The type of api_version during error will be number - ? { - ...result, - api_version: version, - } - : { - status: 'failed', - error: result?.message ?? `${weatherOl.name}: WeatherOL returned an unknown status\n` - + `Data: ${data}`, - api_version: version, - }); - return; - } - - // eslint-disable-next-line functional/no-expression-statement - resolve(result); - }); -}); - -/** - * Get data from ColorfulClouds. [简介 | 彩云天气 API]{@link https://docs.caiyunapp.com/docs/intro/} - * @author WordlessEcho - * @author shindgewongxj - * @param {string} token - Token for ColorfulClouds API - * @param {coordinate} location - Coordinate of location - * @param {string} language - Language from Apple Weather - * @param {Object} [headers] - HTTP headers - * @param {string} [apiVersion] - ColorfulClouds API version - * @param {"realtime" | "minutely" | "hourly" | "daily" | "weather"} [path] - - * @param {Object} [parameters] - Parameters pass to URL - * @return {Promise} Data from ColorfulClouds - */ -const colorfulClouds = ( - token, - location, - language, - headers = { 'Content-Type': 'application/json' }, - path = 'weather', - parameters = { unit: 'metric:v2' }, - apiVersion = 'v2.6', -) => { - /** - * Convert iOS-style language into the language supported by ColorfulClouds API. - * [语言 | 彩云天气 API]{@link https://docs.caiyunapp.com/docs/tables/lang} - * @author shindgewongxj - * @author WordlessEcho - * @param {string} languageWithReigon - "zh-Hans-CA", "en-US", "ja-CA" from Apple URL - * @returns {string} - `en_US` will be returned if language is not supported - */ - const toColorfulCloudsLang = (languageWithReigon) => { - if (isNonEmptyString(languageWithReigon)) { - if (/zh-(Hans|CN)/.test(languageWithReigon)) { - return 'zh_CN'; - } - if (/zh-(Hant|HK|TW)/.test(languageWithReigon)) { - return 'zh_TW'; - } - if (languageWithReigon.includes('en-GB')) { - return 'en_GB'; - } - if (languageWithReigon.includes('ja')) { - return 'ja'; - } - } - - return 'en_US'; - }; - - /** - * Return a valid API version for ColorfulClouds. - * @author WordlessEcho - * @param {string} version - API version to be checked - * @return {string} - API version for ColorfulClouds. - * `v2.6` will be returned if passed version is invalid. - */ - const checkCcApiVersion = (version) => { - if (isNonEmptyString(version) && version.startsWith('v')) { - const versionCode = parseFloat(version.slice(1)); - - if (!Number.isNaN(versionCode)) { - return version; - } - } - - return 'v2.6'; - }; - - /** - * Check the type of parameters - * @author WordlessEcho - * @param {string} uncheckedToken - Token of ColorfulClouds - * @param {coordinate} uncheckedLocation - Coordinate of location - * @return {string} - Error message to be returned. - * Empty string will be returned if all types of parameter are correct. - */ - const getError = (uncheckedToken, uncheckedLocation) => { - if (!isNonEmptyString(uncheckedToken)) { - return `${colorfulClouds.name}: Invalid token\n` - + `Token type: ${typeof uncheckedToken}\n` - + `Token length: ${uncheckedToken?.length}`; - } - - if (!isLocation(uncheckedLocation)) { - return `${colorfulClouds.name}: Invalid location: ${JSON.stringify(uncheckedLocation)}`; - } - - return ''; - }; - - return new Promise((resolve) => { - const validApiVersion = checkCcApiVersion(apiVersion); - const errorMessage = getError(token, location); - if (errorMessage.length > 0) { - // eslint-disable-next-line functional/no-expression-statement - resolve({ - status: 'failed', - error: errorMessage, - api_version: validApiVersion, - }); - return; - } - - const parametersString = isObject(parameters) - ? Object.entries({ lang: toColorfulCloudsLang(language), ...parameters }) - .map(([key, value]) => `${key}=${value}`).join('&') - : ''; - - const request = { - headers, - url: `https://api.caiyunapp.com/${apiVersion}/${token}/` - + `${location.longitude},${location.latitude}/` - // https://docs.caiyunapp.com/docs/weather/ - + `${path}?${parametersString}`, - }; - - // eslint-disable-next-line functional/no-expression-statement - $.get(request, (error, response, data) => { - if (error) { - // eslint-disable-next-line functional/no-expression-statement - resolve({ - status: 'failed', - error: `${colorfulClouds.name}: Error: ${error}\n` - + `Data: ${data}`, - api_version: validApiVersion, - }); - return; - } - - const result = parseJson(data, (e) => ({ - status: 'failed', - error: `${colorfulClouds.name}: Data from ColorfulClouds is not a valid JSON\n` - + `Error: ${e}\n` - + `Data: ${data}`, - })); - - if (result?.status !== 'ok') { - const version = isNonNanNumber(result?.api_version) ? `v${result.api_version}` : apiVersion; - - // eslint-disable-next-line functional/no-expression-statement - resolve(isNonEmptyString(result?.status) - // The type of api_version during error will be number - ? { - ...result, - api_version: version, - } - : { - status: 'failed', - error: `${colorfulClouds.name}: ColorfulClouds returned an unknown status\n` - + `Data: ${data}`, - api_version: version, - }); - return; - } - - // eslint-disable-next-line functional/no-expression-statement - resolve(result); - }); - }); -}; - -/** - * Convert timestamp to time in Apple Weather - * @author VirgilClyne - * @author WordlessEcho - * @param {supportedAppleApi} apiVersion - Apple Weather API Version - * @param {number} timestamp - UNIX timestamp - * @returns {number|string | ""} - UNIX time in seconds for APIv1, - * `YYYY-MM-DDTHH:MM:SSZ` format time for APIv2. Return empty string if api version is not valid. - */ -const toAppleTime = (apiVersion, timestamp) => { - const timeDate = isNonNanNumber(timestamp) && timestamp > 0 - ? (new Date(timestamp)) : (new Date()); - - switch (apiVersion) { - case 1: - return Math.trunc((+timeDate) / 1000); - case 2: - case 3: - return `${timeDate.toISOString().split('.')[0]}Z`; - default: - return ''; - } -}; - -/** - * Convert pollutant amount to another unit - * @author WordlessEcho - * @param {pollutantUnitsText} unit - Unit of amount - * @param {pollutantUnitsText} unitToConvert - Unit to convert - * @param {number} amount - Amount of pollutant - * @param {?supportedEpaVocs} pollutantName - For converting ppm or ppb to mg/m3 or ug/m3 - * @returns {number | -1} - - * Converted amount or -1 if converting unsupported VOCs or unsupported units - */ -const pollutantUnitConverterUs = (unit, unitToConvert, amount, pollutantName) => { - if (!isNonNanNumber(amount) || amount < 0) { - return -1; - } - - /** - * Calculated by - * ([Ozone AQI: Using concentrations in milligrams or ppb?]{@link https://aqicn.org/faq/2015-09-06/ozone-aqi-using-concentrations-in-milligrams-or-ppb/}, - * [Understanding Units of Measurement - Terrie K. Boguski, P.E. (CHSR)]{@link https://cfpub.epa.gov/ncer_abstracts/index.cfm/fuseaction/display.files/fileid/14285}): - * - * (amount * 12.187 * molecularWeight) / (temperatureInCelsius + 273.15) - * - * - 12.187 is the inverse of gas constant. - * - 273.15 is the 0 celsius in kelvin. - * - In US EPA, temperatureInCelsius is 25. In EU is 20. - * - * @type {Object.} - */ - const US_PPX_TO_XGM3 = { - NO2: 1.88, OZONE: 1.97, NO: 1.23, SO2: 2.62, CO: 1.14, - }; - - /** - * Check unit is ppm or ppb - * @author WordlessEcho - * @param {pollutantUnitsText} unitToCheck - Unit to be checked - * @returns {boolean} - True if unit is `ppm` or `ppb` - */ - const isPpx = (unitToCheck) => unitToCheck === 'ppm' || unitToCheck === 'ppb'; - - /** - * Check unit is mg/m3 or ug/m3 - * @author WordlessEcho - * @param {pollutantUnitsText} unitToCheck - Unit to be checked - * @returns {boolean} - True if unit is `milligramsPerM3` or `microgramsPerM3` - */ - const isXgM3 = (unitToCheck) => ( - unitToCheck === 'milligramsPerM3' || unitToCheck === 'microgramsPerM3' - ); - - if ((isPpx(unit) && isXgM3(unitToConvert)) || (isXgM3(unit) && isPpx(unitToConvert))) { - if (!Object.keys(US_PPX_TO_XGM3).includes(pollutantName)) { - return -1; - } - } - - switch (unit) { - case 'ppm': - switch (unitToConvert) { - case 'ppm': - return amount; - case 'ppb': - return amount * 1000; - case 'milligramsPerM3': - return amount * US_PPX_TO_XGM3[pollutantName]; - case 'microgramsPerM3': { - const inPpb = pollutantUnitConverterUs(unit, 'ppb', amount, pollutantName); - return inPpb * US_PPX_TO_XGM3[pollutantName]; - } - default: - return -1; - } - case 'ppb': - switch (unitToConvert) { - case 'ppb': - return amount; - case 'ppm': - return amount * 0.001; - case 'milligramsPerM3': { - const inPpm = pollutantUnitConverterUs(unit, 'ppm', amount, pollutantName); - return inPpm * US_PPX_TO_XGM3[pollutantName]; - } - case 'microgramsPerM3': - return amount * US_PPX_TO_XGM3[pollutantName]; - default: - return -1; - } - case 'milligramsPerM3': - switch (unitToConvert) { - case 'milligramsPerM3': - return amount; - case 'microgramsPerM3': - return amount * 1000; - case 'ppm': - return amount / US_PPX_TO_XGM3[pollutantName]; - case 'ppb': { - const inUgM3 = pollutantUnitConverterUs(unit, 'microgramsPerM3', amount, pollutantName); - return inUgM3 / US_PPX_TO_XGM3[pollutantName]; - } - default: - return -1; - } - case 'microgramsPerM3': - switch (unitToConvert) { - case 'microgramsPerM3': - return amount; - case 'milligramsPerM3': - return amount * 0.001; - case 'ppm': { - const inMgM3 = pollutantUnitConverterUs(unit, 'milligramsPerM3', amount, pollutantName); - return inMgM3 / US_PPX_TO_XGM3[pollutantName]; - } - case 'ppb': - return amount / US_PPX_TO_XGM3[pollutantName]; - default: - return -1; - } - default: - return -1; - } -}; - -/** - * Calculate AQI by AQI range and concentration breakpoints. - * [Technical Assistance Document for the Reporting of Daily Air Quality – the Air Quality Index (AQI)]{@link https://www.airnow.gov/sites/default/files/2020-05/aqi-technical-assistance-document-sept2018.pdf} - * [环境空气质量指数(AQI)技术规定(试行)]{@link https://www.mee.gov.cn/ywgz/fgbz/bz/bzwb/jcffbz/201203/W020120410332725219541.pdf} - * @author WordlessEcho - * @param {concentrationRange[]} concentrationRanges - concentrationBreakpoints - * @param {number} amount - Amount of pollutant - * @returns {number | -1} - Air quality index, -1 if amount is not a valid number - */ -const toEpaAqi = (concentrationRanges, amount) => { - if (Array.isArray(concentrationRanges) && isNonNanNumber(amount) && amount >= 0) { - const ranges = concentrationRanges.filter((r) => ( - isPositiveWithZeroRange(r?.AMOUNT) && isPositiveWithZeroRange(r?.AQI) - )); - - if (ranges.length > 0) { - const range = ranges.find(({ AMOUNT }) => amount >= AMOUNT.LOWER && amount <= AMOUNT.UPPER); - - if (isPositiveWithZeroRange(range?.AQI) && isPositiveWithZeroRange(range?.AMOUNT)) { - const { AQI, AMOUNT } = range; - return Math.round( - ((AQI.UPPER - AQI.LOWER) * (amount - AMOUNT.LOWER)) - / (AMOUNT.UPPER - AMOUNT.LOWER) + AQI.LOWER, - ); - } - - // Over range! - const topRange = ranges.reduce((previous, current) => ( - current.AMOUNT.UPPER > previous.AMOUNT.UPPER ? current : previous - )); - - // Or we just return `topRange.AQI.UPPER`? - if ( - isPositiveWithZeroRange(topRange?.AMOUNT) && isPositiveWithZeroRange(topRange?.AQI) - && amount > topRange.AMOUNT.UPPER - ) { - return Math.round(amount - topRange.AMOUNT.UPPER + topRange.AQI.UPPER); - } - } - } - - return -1; -}; - -/** - * Calculate amount of pollutants to AQIs - * @author WordlessEcho - * @param {Object.} concentrationsInfo - - * Amount breakpoints, AQI breakpoints and unit info of concentrations - * @param {pollutantV2[]} pollutants - Name, amount and unit info of pollutants - * @returns {aqiInfo} - */ -const toEpaAqis = (concentrationsInfo, pollutants) => { - if (!isObject(concentrationsInfo) || !Array.isArray(pollutants)) { - return { index: -1, pollutants: [] }; - } - - const concentrations = Object.fromEntries(Object.entries(concentrationsInfo) - .filter(([key, value]) => ( - isNonEmptyString(key) && isNonEmptyString(value?.UNIT) && isObject(value?.RANGES) - && !Object.values(value.RANGES).some((rangesForLevel) => ( - !isPositiveWithZeroRange(rangesForLevel?.AMOUNT) - || !isPositiveWithZeroRange(rangesForLevel?.AQI) - )) - ))); - - const pollutantAqis = pollutants - .filter((pollutant) => isNonEmptyString(pollutant?.name)) - .map((pollutant) => { - if ( - Object.keys(concentrations).includes(pollutant.name) && isNonEmptyString(pollutant?.unit) - && isNonNanNumber(pollutant?.amount) && pollutant.amount >= 0 - ) { - const { name, unit, amount } = pollutant; - const concentration = concentrations[name]; - const convertedAmount = unit === concentration.UNIT ? amount - : pollutantUnitConverterUs(unit, concentration.UNIT, amount, name); - - return { name, aqi: toEpaAqi(Object.values(concentration.RANGES), convertedAmount) }; - } - - return { name: pollutant.name, aqi: -1 }; - }); - - const validAqis = pollutantAqis?.filter(({ aqi }) => aqi !== -1); - const primary = Array.isArray(validAqis) && validAqis.length > 0 ? validAqis.reduce( - (previous, current) => (current.aqi > previous.aqi ? current : previous), - ) : { aqi: -1 }; - - return { - index: primary.aqi, - ...(isNonEmptyString(primary?.name) && { primary: primary.name }), - pollutants: pollutantAqis, - }; -}; - -/** - * Calculate Air Quality Level - * @author WordlessEcho - * @author VirgilClyne - * @param {aqiLevel[]} aqiLevels - Breakpoints of AQI - * @param {number} aqi - Air quality index - * @returns {number | -1} - -1 if AQI or aqiLevels is invalid. - * `topLevel.VALUE` + 1 will be returned if no matched ranges. - */ -const toAqiLevel = (aqiLevels, aqi) => { - if (Array.isArray(aqiLevels) && isNonNanNumber(aqi) && aqi >= 0) { - const levels = aqiLevels.filter((level) => ( - isPositiveWithZeroRange(level?.RANGE) && isNonNanNumber(level?.VALUE) && level.VALUE > 0 - )); - - const level = levels.find(({ RANGE }) => (aqi >= RANGE.LOWER && aqi <= RANGE.UPPER)); - - if (isNonNanNumber(level?.VALUE)) { - return level.VALUE; - } - - const topLevel = levels.length > 0 && levels.reduce((previous, current) => ( - current.VALUE > previous.VALUE ? current : previous - )); - - if (isNonNanNumber(topLevel?.VALUE) && aqi > topLevel.RANGE.UPPER) { - return topLevel.VALUE + 1; - } - } - - return -1; -}; - -/** - * Compare Air Quality Levels - * @author WordlessEcho - * @param {number} aqiLevelA - Value from {@link toAqiLevel} to compare - * @param {number} aqiLevelB - Value from {@link toAqiLevel} to be compared - * @returns {aqiComparison} - Value for `AirQuality.previousDayComparison`. - * `unknown` will be returned if aqiLevel is invalid. - */ -const compareAqi = (aqiLevelA, aqiLevelB) => { - if ( - !isNonNanNumber(aqiLevelA) || !isNonNanNumber(aqiLevelB) || aqiLevelA <= 0 || aqiLevelB <= 0 - ) { - return 'unknown'; - } - - if (aqiLevelA > aqiLevelB) { - return 'worse'; - } if (aqiLevelA < aqiLevelB) { - return 'better'; - } - - return 'same'; -}; - -/** - * Fix unit of CO from QWeather - * @author WordlessEcho - * @param {pollutantUnitsV2|pollutantUnitsV1} unit - Unit of CO - * @param {number} amount - Amount of CO - * @return {number | -1} - Converted CO amount. -1 will be returned if amount is invalid. - * Amount will not be converted if unit is not `microgramsPerM3`. - */ -const fixQweatherCo = (unit, amount) => { - if (!isNonNanNumber(amount) || amount < 0) { - return -1; - } - - if (unit === 'µg/m3' || unit === 'microgramsPerM3') { - const mgAmount = pollutantUnitConverterUs( - 'microgramsPerM3', - HJ_633.CONCENTRATIONS.CO.UNIT, - amount, - 'CO', - ); - - if (mgAmount < 0.1) { - const fixedAmount = pollutantUnitConverterUs( - HJ_633.CONCENTRATIONS.CO.UNIT, - 'microgramsPerM3', - amount, - 'CO', - ); - - logger( - 'debug', - `${fixQweatherCo.name}:已修复一氧化碳含量,原始值:${amount}ug/m3,修复值:${fixedAmount}ug/m3`, - ); - return fixedAmount; - } - } - - return amount; -}; - -/** - * Convert pollutant unit into Apple APIv2 style - * @author WordlessEcho - * @param {pollutantV1[]} pollutants - Pollutants in Apple APIv1 format - * @return {pollutantV2[]} - Pollutants in Apple APIv2 format - */ -const convertV1Pollutants = (pollutants) => { - const units = ['ppb', 'µg/m3']; - const validPollutants = Array.isArray(pollutants) ? pollutants.filter( - (pollutant) => ( - units.includes(pollutant?.unit) && isNonEmptyString(pollutant?.name) - && isNonNanNumber(pollutant?.amount) && pollutant.amount >= 0 - ), - ) : []; - - return validPollutants.map((pollutant) => ({ - ...pollutant, unit: pollutant.unit === 'µg/m3' ? 'microgramsPerM3' : pollutant.unit, - })); -}; - -/** - * Convert pollutants from Apple to specific EPA standard - * @author WordlessEcho - * @param {aqiStandard} standard - EPA standard to convert - * @param {pollutantV2[]} pollutants - Pollutants in Apple APIv2 format - * @return {airQualityObject} - Object for {@link toAirQuality} - */ -const appleToEpaAirQuality = (standard, pollutants) => { - if ( - !isObject(standard) || !Array.isArray(pollutants) || !isObject(standard?.CONCENTRATIONS) - || !isObject(standard?.AQI_LEVELS) - ) { - return {}; - } - - const validConcentrations = Object.fromEntries(Object.entries(standard.CONCENTRATIONS).filter( - ([, value]) => ( - isNonEmptyString(value?.UNIT) && isObject(value?.RANGES) - && !Object.values(value.RANGES).includes((v) => ( - !isPositiveRange(v?.AMOUNT) || !isPositiveWithZeroRange(v?.AQI) - )) - ), - )); - - if (Object.keys(validConcentrations) <= 0) { - return {}; - } - - const units = ['ppb', 'microgramsPerM3']; - const validPollutants = pollutants.filter((pollutant) => ( - Object.keys(validConcentrations).includes(pollutant?.name) && units.includes(pollutant?.unit) - && isNonNanNumber(pollutant?.amount) && pollutant.amount >= 0 - )); - - const aqis = toEpaAqis(validConcentrations, validPollutants); - if (!isNonNanNumber(aqis.index) || aqis.index < 0) { - return {}; - } - - const validAqiLevelValues = Object.values(standard.AQI_LEVELS).filter((level) => ( - isNonNanNumber(level.VALUE) && isPositiveWithZeroRange(level.RANGE) - )); - - const topAqiLevelValue = validAqiLevelValues.length > 0 - ? Math.max(...validAqiLevelValues.map(({ VALUE }) => VALUE)) : -1; - const aqiLevel = toAqiLevel(validAqiLevelValues, aqis.index); - const categoryIndex = aqiLevel > 0 && aqiLevel > topAqiLevelValue ? topAqiLevelValue : aqiLevel; - - return { - isSignificant: categoryIndex >= standard.SIGNIFICANT_LEVEL, - ...(isNonEmptyString(aqis?.primary) && { primary: aqis.primary }), - categoryIndex, - aqi: aqis.index, - scale: standard.APPLE_SCALE, - }; -}; - -/** - * Get air quality from ColorfulClouds - * @author WordlessEcho - * @param {colorfulCloudsV2} dataWithRealtime - Data from ColorfulClouds with air quality info - * @return {ccAirQuality | {aqi: {usa: -1, chn: -1}}} - Air quality data in v2.4+ format - */ -const getCcAirQuality = (dataWithRealtime) => { - const toColorfulCloudsNames = { - NO2: 'no2', 'PM2.5': 'pm25', SO2: 'so2', OZONE: 'o3', PM10: 'pm10', CO: 'co', aqi: 'aqi', - }; - - const apiVersion = dataWithRealtime?.api_version; - const versionCode = isNonEmptyString(apiVersion) && apiVersion.startsWith('v') - && parseFloat(apiVersion.slice(1)); - const validVersionCode = isNonNanNumber(versionCode) ? versionCode : -1; - - // https://open.caiyunapp.com/%E5%BD%A9%E4%BA%91%E5%A4%A9%E6%B0%94_API/v2.5#.E6.A0.BC.E5.BC.8F.E5.8F.98.E6.9B.B4 - // https://docs.caiyunapp.com/docs/v2.4/intro#%E4%B8%8D%E5%85%BC%E5%AE%B9%E7%9A%84%E6%9B%B4%E6%96%B0 - if (validVersionCode >= 2.2 && validVersionCode < 3) { - const airQuality = validVersionCode >= 2.4 - ? dataWithRealtime?.result?.realtime?.air_quality - : dataWithRealtime?.result; - - if (isObject(airQuality)) { - const result = Object.fromEntries(Object.keys(toColorfulCloudsNames).map((key) => { - const value = airQuality?.[toColorfulCloudsNames[key]]; - - if (key === 'aqi') { - const chnAqi = validVersionCode >= 2.4 ? value?.chn : value; - const usaAqi = validVersionCode >= 2.4 ? value?.usa : -1; - - return [key, { - usa: isNonNanNumber(usaAqi) && usaAqi >= 0 ? usaAqi : -1, - chn: isNonNanNumber(chnAqi) && chnAqi >= 0 ? chnAqi : -1, - }]; - } - - return [ - key, - isNonNanNumber(value) && value >= 0 ? value : -1, - ]; - })); - - // Detect the support of air quality - if ( - Object.values(result).filter((value) => isNonNanNumber(value) && value <= 0).length <= 1 - ) { - logger('debug', `${getCcAirQuality.name}:美标:${result.aqi.usa},国标:${result.aqi.chn}`); - return result; - } - } - - logger('error', `${getCcAirQuality.name}:缺少空气质量数据`); - // eslint-disable-next-line functional/no-conditional-statement - } else { - logger('error', `${getCcAirQuality.name}:不支持${apiVersion}版本的API`); - } - - return { aqi: { usa: -1, chn: -1 } }; -}; - -/** - * Convert seconds to timezone - * @author WordlessEcho - * @param {number} minutes - Timezone in minutes - * @return {string} - Timezone in "+/-HH:MM" format - */ -const minutesToIsoTimezone = (minutes) => { - const validOffset = isNonNanNumber(minutes) ? minutes : (new Date()).getTimezoneOffset(); - return `${validOffset < 0 ? '-' : '+'}` - + `${Math.floor(Math.abs(validOffset / 60)).toString().padStart(2, '0')}` - + `:${(Math.abs(validOffset % 60)).toString().padStart(2, '0')}`; -}; - -/** - * Get history AQI from ColorfulClouds - * @author WordlessEcho - * @param {colorfulCloudsV2} historyData - Data with history from ColorfulClouds - * @param {number} timestamp - Timestamp of data to get - * @return {{usa: number | -1, chn: number | -1}} - Air quality index - */ -const colorfulCloudsHistoryAqi = (historyData, timestamp) => { - if (!isNonNanNumber(timestamp) || timestamp <= 0) { - return { usa: -1, chn: -1 }; - } - - const apiVersion = historyData?.api_version; - const versionCode = isNonEmptyString(apiVersion) && apiVersion.startsWith('v') - && parseFloat(apiVersion.slice(1)); - const validVersionCode = isNonNanNumber(versionCode) ? versionCode : -1; - - const hourTimestamp = (new Date(timestamp)).setMinutes(0, 0, 0); - - const historyAqis = validVersionCode >= 2.4 - ? historyData?.result?.hourly?.air_quality?.aqi - : historyData?.result?.hourly?.aqi; - - // An hour as range - const aqis = historyAqis?.find((aqi) => { - if (!isNonEmptyString(aqi?.datetime)) { - return false; - } - - // https://docs.caiyunapp.com/docs/v2.3/intro#%E4%B8%8D%E5%85%BC%E5%AE%B9%E7%9A%84%E6%9B%B4%E6%96%B0 - if ( - validVersionCode < 2.3 && (!isNonNanNumber(historyData?.tzshift) - || historyData.tzshift % 60 !== 0) - ) { - return false; - } - const ts = Date.parse(validVersionCode < 2.3 - ? `${aqi.datetime.replace(' ', 'T')}:00.000${minutesToIsoTimezone(historyData.tzshift / 60)}` - : aqi.datetime.split('+').join(':00.000+')); - - return isNonNanNumber(ts) && ts > 0 - && ts >= hourTimestamp && ts < hourTimestamp + 1000 * 60 * 60; - }); - - const usaAqi = validVersionCode >= 2.4 ? aqis?.value?.usa : -1; - const chnAqi = validVersionCode >= 2.4 ? aqis?.value?.chn : aqis?.value; - - return { - usa: isNonNanNumber(usaAqi) && usaAqi >= 0 ? usaAqi : -1, - chn: isNonNanNumber(chnAqi) && chnAqi >= 0 ? chnAqi : -1, - }; -}; - -/** - * Compare AQI to the yesterday from ColorfulClouds - * @author WordlessEcho - * @param {colorfulCloudsV2} realtimeAndHistoryData - - * Data with realtime and history from ColorfulClouds - * @param {boolean} forceChn - Use `aqi.chn` by force - * @return {aqiComparison} - Result of comparison for `previousDayComparison` - */ -const colorfulCloudsToAqiComparison = (realtimeAndHistoryData, forceChn) => { - const airQuality = getCcAirQuality(realtimeAndHistoryData); - - const serverTime = realtimeAndHistoryData?.server_time; - const serverTimestamp = isNonNanNumber(serverTime) && serverTime > 0 - ? serverTime * 1000 : (+new Date()); - const reportedTimestamp = (new Date(serverTimestamp)).setMinutes(0, 0, 0); - const yesterdayTimestamp = reportedTimestamp - 1000 * 60 * 60 * 24; - - const todayAqi = airQuality.aqi; - const yesterdayAqi = colorfulCloudsHistoryAqi(realtimeAndHistoryData, yesterdayTimestamp); - - if ((typeof forceChn !== 'boolean' || !forceChn) && todayAqi.usa >= 0 && yesterdayAqi.usa >= 0) { - const todayAqiLevel = toAqiLevel(Object.values(EPA_454.AQI_LEVELS), todayAqi.usa); - const yesterdayAqiLevel = toAqiLevel(Object.values(EPA_454.AQI_LEVELS), yesterdayAqi.usa); - - return compareAqi(todayAqiLevel, yesterdayAqiLevel); - } - - if (todayAqi.chn >= 0 && yesterdayAqi.chn >= 0) { - const todayAqiLevel = toAqiLevel(Object.values(HJ_633.AQI_LEVELS), todayAqi.chn); - const yesterdayAqiLevel = toAqiLevel(Object.values(HJ_633.AQI_LEVELS), yesterdayAqi.chn); - - return compareAqi(todayAqiLevel, yesterdayAqiLevel); - } - - logger( - 'error', - `${colorfulCloudsToAqiComparison.name}:无法找到AQI,今日:${JSON.stringify(todayAqi)}` - + `,昨日:${JSON.stringify(yesterdayAqi)}`, - ); - return 'unknown'; -}; - -/** - * Get AQI comparison from WAQI AQI feed - * @param {waqiAqiFeedRxsObsMsg} aqiFeedMsg - * @return {aqiComparison} - */ -const waqiV1AqiToAqiComparison = (aqiFeedMsg) => { - const obsData = aqiFeedMsg?.obs; - if (!obsData) { - return 'unknown'; - } - - const pollutantNames = ['co', 'no2', 'o3', 'pm10', 'pm25', 'so2']; - - const parsedHistoryData = Object.fromEntries( - Object.entries(obsData).map(([pollutantName, value]) => { - const getTimestamp = (array, index) => { - const isoTime = value.s.replace(' ', 'T'); - const baseTime = (+(new Date(isoTime))); - if (index === 0) { - return baseTime; - } - - return array.slice(0, index + 1).map((v) => (isNonNanNumber(v) ? baseTime : v[1] * 1000)) - .reduce((previous, current) => previous + current); - }; - - const getAqi = (array, index) => { - const relative = array[index]; - const relativeValidValue = isNonNanNumber(relative) ? relative : relative[0]; - const decimal = value.m; - if (index === 0) { - return relativeValidValue / decimal; - } - - return array.slice(0, index + 1).map((v) => (isNonNanNumber(v) ? v : v[0])) - .reduce((previous, current) => previous + current) / decimal; - }; - - return [pollutantName, value.v.map((v, index, array) => ({ - timestamp: getTimestamp(array, index), - aqi: getAqi(array, index), - }))]; - }), - ); - - const isoTime = aqiFeedMsg?.time?.iso; - const timestamp = isNonEmptyString(isoTime) - ? Date.parse(`${isoTime.slice(0, -6)}.000${isoTime.slice(-6)}`) : (+(new Date())); - const nowHourTimestamp = isNonNanNumber(timestamp) ? (new Date(timestamp)).setMinutes(0, 0, 0) - : (new Date()).setMinutes(0, 0, 0); - const yesterdayTimestamp = nowHourTimestamp - 1000 * 60 * 60 * 24; - const yesterdayAqis = pollutantNames.map((name) => { - const historyData = parsedHistoryData?.[name]; - if (Array.isArray(historyData)) { - const aqi = historyData.find((history) => ( - isNonNanNumber(history?.timestamp) && history.timestamp >= yesterdayTimestamp - && history.timestamp < yesterdayTimestamp + 1000 * 60 * 60 - ))?.aqi; - - if (isNonNanNumber(aqi)) { - return aqi; - } - } - - logger('warn', `${waqiV1AqiToAqiComparison.name}:缺失${name}的历史数据`); - return -1; - }); - const yesterdayAqi = Math.max(...yesterdayAqis); - const todayAqi = aqiFeedMsg?.aqi; - - if ( - !isNonNanNumber(yesterdayAqi) || yesterdayAqi < 0 || !isNonNanNumber(todayAqi) || todayAqi < 0 - ) { - logger('error', `${waqiV1AqiToAqiComparison.name}:无法找到AQI,今日AQI = ${todayAqi},昨日AQI = ${yesterdayAqi}`); - - return 'unknown'; - } - - return compareAqi( - toAqiLevel(Object.values(WAQI_INSTANT_CAST.AQI_LEVELS), todayAqi), - toAqiLevel(Object.values(WAQI_INSTANT_CAST.AQI_LEVELS), yesterdayAqi), - ); -}; - -/** - * Convert data from ColorfulClouds to air quality metadata - * @author WordlessEcho - * @param {providerLogo} providerLogo - URL to the provider logo - * @param {string} providerName - Name of the provider - * @param {string} url - URL to the provider - * @param {colorfulCloudsV2} data - Data from ColorfulClouds - * @return {metadataObject | {}} - Object for {@link toMetadata} - */ -const colorfulCloudsToAqiMetadata = (providerLogo, providerName, url, data) => { - const language = data?.lang; - const location = { latitude: data?.location?.[0], longitude: data?.location?.[1] }; - // the unit of server_time is second - const serverTime = data?.server_time; - const serverTimestamp = isNonNanNumber(serverTime) && serverTime > 0 - ? serverTime * 1000 : (+(new Date())); - - const reportedTimestamp = (new Date(serverTimestamp)).setMinutes(0, 0, 0); - const expireTimestamp = reportedTimestamp + 1000 * 60 * 60; - const validExpireTimestamp = expireTimestamp > (+(new Date())) - ? expireTimestamp : (+(new Date())) + 1000 * 60 * 15; - - const validProviderLogo = { - ...(isNonEmptyString(providerLogo?.forV1) && { forV1: providerLogo.forV1 }), - ...(isNonEmptyString(providerLogo?.forV2) && { forV2: providerLogo.forV2 }), - }; - - const variableMetadata = { - ...(isNonEmptyString(language) && { language: language.replace('_', '-') }), - ...(isLocation(location) && { location }), - ...(Object.keys(validProviderLogo).length > 0 && { providerLogo: validProviderLogo }), - ...(isNonEmptyString(providerName) && { providerName }), - url, - }; - - return Object.keys(variableMetadata).length > 0 ? { - ...variableMetadata, - expireTimestamp: validExpireTimestamp, - readTimestamp: serverTimestamp, - reportedTimestamp, - dataSource: 1, - // https://developer.apple.com/documentation/weatherkitrestapi/unitssystem - unit: 'm', - } : {}; -}; - -/** - * Convert data from ColorfulClouds to object for {@link toAirQuality} - * @author WordlessEcho - * @param {colorfulCloudsV2} realtimeAndHistoryData - - * Data with realtime and history from ColorfulClouds - * @param {string} url - Link to AQI info - * @param {string} providerName - Name of the provider for `source` of Apple Weather - * @param {boolean} aqiForceChn - Use `aqi.chn` by force for AQI - * @param {boolean} comparisonForceChn - Use `aqi.chn` by force for comparison - * @return {airQualityObject | {}} - Object for {@link toAirQuality} - */ -const colorfulCloudsToAqi = ( - realtimeAndHistoryData, - url, - providerName, - aqiForceChn, - comparisonForceChn, -) => { - /** - * Get AQI standard based on existed data - * @author WordlessEcho - * @param {boolean} hasUsa - Existence of aqi.usa - * @param {boolean} forceChn - Use aqi.chn by force - * @return {aqiStandard} - AQI standard - */ - const getCcStandard = (hasUsa, forceChn) => ( - typeof hasUsa !== 'boolean' || !hasUsa || (typeof forceChn === 'boolean' && forceChn) - ? { - ...HJ_633, - CONCENTRATIONS: { - ...HJ_633.CONCENTRATIONS, - PM10: HJ_633.CONCENTRATIONS.PM10_24H, - 'PM2.5': HJ_633.CONCENTRATIONS['PM2.5_24H'], - }, - } // TODO: EPA NowCast - : WAQI_INSTANT_CAST - ); - - /** - * Convert data from {@link getCcAirQuality} to {@link pollutantV2} object for Apple Weather - * @author WordlessEcho - * @param {ccAirQuality} airQuality - Pollutants data from {@link getCcAirQuality} - * @return {pollutantV2[] | []} - - * Object for `airQuality.pollutants` of Apple Weather - */ - const toPollutants = (airQuality) => (isObject(airQuality) - ? Object.entries(airQuality) - .filter(([key]) => key !== 'aqi') - .map(([pollutantName, amount]) => ({ - name: pollutantName, - amount: pollutantName === 'CO' ? pollutantUnitConverterUs( - 'milligramsPerM3', - 'microgramsPerM3', - amount, - null, - ) : amount, - unit: 'microgramsPerM3', - })) - : []); - - const airQuality = getCcAirQuality(realtimeAndHistoryData); - const standard = getCcStandard(airQuality.aqi.usa >= 0, aqiForceChn); - - const aqi = standard.APPLE_SCALE === EPA_454.APPLE_SCALE - ? airQuality.aqi.usa : airQuality.aqi.chn; - if (!isNonNanNumber(aqi) || aqi < 0) { - return {}; - } - - const categoryIndex = toAqiLevel(Object.values(standard.AQI_LEVELS), aqi); - const pollutants = toPollutants(airQuality); - const primaryPollutant = toEpaAqis(standard.CONCENTRATIONS, pollutants)?.primary; - - return { - isSignificant: categoryIndex >= (isNonNanNumber(standard.SIGNIFICANT_LEVEL) - ? standard.SIGNIFICANT_LEVEL : Number.MAX_VALUE), - url: isNonEmptyString(url) ? url : 'https://caiyunapp.com/weather/', - pollutants, - // Primary pollutant must be included in pollutants - ...(isNonEmptyString(primaryPollutant) && { primaryPollutant }), - sourceName: isNonEmptyString(providerName) ? providerName : 'ColorfulClouds', - categoryIndex, - aqi, - scale: standard.APPLE_SCALE, - previousDayComparison: - colorfulCloudsToAqiComparison(realtimeAndHistoryData, comparisonForceChn), - sourceType: 'modeled', - }; -}; - -/** - * Convert data from WAQI to air quality metadata - * @author WordlessEcho - * @param {waqiFeed} feedData - Feed data from WAQI - * @return {metadataObject | {}} - Object for {@link toMetadata} - */ -const waqiToAqiMetadata = (feedData) => { - const location = { - latitude: feedData?.data?.city?.geo?.[0], - longitude: feedData?.data?.city?.geo?.[1], - }; - if (!isLocation(location)) { - return {}; - } - - const serverTimestamp = Date.parse(feedData?.data?.time?.iso); - const validServerTimestamp = isNonNanNumber(serverTimestamp) && serverTimestamp > 0 - ? serverTimestamp : (+(new Date())); - - const reportedTimestamp = (new Date(validServerTimestamp)).setMinutes(0, 0, 0); - const expireTimestamp = reportedTimestamp + 1000 * 60 * 60; - const validExpireTimestamp = expireTimestamp > (+(new Date())) - ? expireTimestamp : (+(new Date())) + 1000 * 60 * 15; - - return { - language: 'en-US', - location, - expireTimestamp: validExpireTimestamp, - providerLogo: { - forV1: 'https://waqi.info/images/logo.png', - forV2: 'https://raw.githubusercontent.com/VirgilClyne/iRingo/main/image/waqi.info.logo.png', - }, - providerName: isNonEmptyString(feedData?.data?.city?.name) - ? `${feedData.data.city.name} (The World Air Quality Project)` - : 'The World Air Quality Project', - readTimestamp: serverTimestamp, - reportedTimestamp, - dataSource: 0, - // https://developer.apple.com/documentation/weatherkitrestapi/unitssystem - unit: 'm', - url: 'https://waqi.info', - }; -}; - -/** - * Covert data from WAQI to object for {@link toAirQuality} - * @author WordlessEcho - * @param {waqiFeed} feedData - Data with AQI from WAQI feed - * @return {airQualityObject | {}} - Object for {@link toAirQuality} - */ -const waqiToAqi = (feedData) => { - // TODO: Find other pollutant names for WAQI - const toApplePollutantName = { - no2: 'NO2', no: 'NO', nox: 'NOX', pm25: 'PM2.5', so2: 'SO2', o3: 'OZONE', pm10: 'PM10', co: 'CO', other: 'OTHER', - }; - - const aqi = feedData?.data?.aqi; - if (!isNonNanNumber(aqi) || aqi < 0) { - return {}; - } - - const validAqi = typeof aqi === 'number' && !Number.isNaN(aqi) && aqi >= 0 ? aqi : -1; - const categoryIndex = toAqiLevel(Object.values(WAQI_INSTANT_CAST.AQI_LEVELS), validAqi); - - return isNonNanNumber(aqi) && aqi >= 0 ? { - isSignificant: categoryIndex >= (isNonNanNumber(WAQI_INSTANT_CAST.SIGNIFICANT_LEVEL) - ? WAQI_INSTANT_CAST.SIGNIFICANT_LEVEL : Number.MAX_VALUE), - url: isNonEmptyString(feedData?.data?.city?.url) ? feedData.data.city.url : 'https://aqicn.org/', - // Pollutant data from WAQI is AQI not amount - pollutants: [], - ...(Object.keys(toApplePollutantName).includes(feedData?.data?.dominentpol) - && { primary: toApplePollutantName[feedData.data.dominentpol] }), - sourceName: isNonEmptyString(feedData?.data?.city?.name) ? feedData.data.city.name : '', - categoryIndex, - aqi: validAqi, - scale: WAQI_INSTANT_CAST.APPLE_SCALE, - previousDayComparison: 'unknown', - sourceType: 'station', - } : {}; -}; - -/** - * Mapping the precipitation level ranges to 3 level of ranges of Apple - * @author WordlessEcho - * @param {precipitationLevels} precipitationLevels - Range of each precipitation level - * @param {number} precipitation - Value of precipitation - * @return {number} - Value for `forecastNextHour.minutes[].precipIntensityPerceived`. - * 0 will be returned if precipitation levels or precipitation is invalid. - */ -const toPerceived = (precipitationLevels, precipitation) => { - const levels = isObject(precipitationLevels) - ? Object.values(precipitationLevels).filter((level) => ( - isPositiveWithZeroRange(level.RANGE) && isNonNanNumber(level.VALUE) - && level.VALUE >= 0 && level.VALUE <= 3 - )) - : []; - - if (levels.length > 0 && isNonNanNumber(precipitation) && precipitation >= 0) { - const topLevel = levels.reduce((previous, current) => ( - current.VALUE > previous.VALUE ? current : previous - )); - - if (precipitation > topLevel.RANGE.UPPER) { - return topLevel.VALUE; - } - - const currentLevel = levels.find(({ RANGE }) => ( - precipitation >= RANGE.LOWER && precipitation < RANGE.UPPER - )); - const lastLevel = levels.find(({ VALUE }) => VALUE === currentLevel.VALUE - 1); - - if ( - isPositiveWithZeroRange(currentLevel?.RANGE) && isPositiveWithZeroRange(lastLevel?.RANGE) - && isNonNanNumber(currentLevel?.VALUE) && isNonNanNumber(lastLevel?.VALUE) - ) { - return currentLevel.VALUE > 0 - ? lastLevel.VALUE + (((precipitation - lastLevel.RANGE.UPPER) * 1000) - / ((currentLevel.RANGE.UPPER - currentLevel.RANGE.LOWER) * 1000)) - : 0; - } - } - - return 0; -}; - -/** - * Get weather status for `NextHourForecast.condition[].token` - * @author WordlessEcho - * @param {precipitationTypes} precipitationType - Type of precipitation - * @param {number} precipitationIntensityPerceived - Apple precipitation. - * Can be generated from {@link toPerceived} - * @returns {weatherStatuses} - Weather status of current type and precipitation - */ -const perceivedToStatus = (precipitationType, precipitationIntensityPerceived) => { - if (precipitationType === 'clear' || precipitationIntensityPerceived <= 0) { - return 'clear'; - } - - if (precipitationType === 'rain' || precipitationType === 'snow') { - if (precipitationIntensityPerceived <= 1) { - return precipitationType === 'rain' ? 'drizzle' : 'flurries'; - } - if (precipitationIntensityPerceived <= 2) { - return precipitationType === 'rain' ? 'rain' : 'snow'; - } - - return precipitationType === 'rain' ? 'heavy-rain' : 'heavy-snow'; - } - - return precipitationType; -}; - -/** - * Convert weather status to precipitation type - * @author WordlessEcho - * @param {weatherStatuses} weatherStatus - Weather status to be converted - * @returns {precipitationTypes} - `precipitation` will be returned if precipitation is invalid. - */ -const weatherStatusToType = (weatherStatus) => { - switch (weatherStatus) { - case 'clear': - case 'sleet': - case 'hail': - case 'mixed': - return weatherStatus; - case 'flurries': - case 'snow': - case 'heavy-snow': - return 'snow'; - case 'drizzle': - case 'rain': - case 'heavy-rain': - return 'rain'; - default: - return 'precipitation'; - } -}; - -/** - * Transfer numbers into ordinal numerals. [Source code]{@link https://stackoverflow.com/a/20426113} - * @author WordlessEcho - * @param {number} number - Number to transfer - * @return {string} - Ordinal numeral of given number. - * Empty string will be returned if given number is invalid. - */ -const stringifyNumber = (number) => { - const special = [ - 'zeroth', 'first', 'second', 'third', 'fourth', 'fifth', 'sixth', 'seventh', 'eighth', - 'ninth', 'tenth', 'eleventh', 'twelfth', 'thirteenth', 'fourteenth', 'fifteenth', - 'sixteenth', 'seventeenth', 'eighteenth', 'nineteenth', - ]; - const deca = ['twent', 'thirt', 'fort', 'fift', 'sixt', 'sevent', 'eight', 'ninet']; - - if (!isNonNanNumber(number) || number < 0) { - return ''; - } - - if (number < 20) { - return special[number]; - } - - if (number % 10 === 0) { - return `${deca[Math.floor(number / 10) - 2]}ieth`; - } - - return `${deca[Math.floor(number / 10) - 2]}y-${special[number % 10]}`; -}; - -/** - * Convert data from ColorfulClouds to next hour metadata - * @author WordlessEcho - * @param {string} providerName - Name of the provider - * @param {string} url - URL to the data - * @param {colorfulCloudsV2} data - Data from ColorfulClouds - * @return { - * appleNextHourWithoutMetadataV1|appleNextHourWithoutMetadataV2|appleNextHourWithoutMetadataV3 | {} - * } - Object for {@link toMetadata} - */ -const colorfulCloudsToNextHourMetadata = (providerName, url, data) => { - const language = data?.lang; - const location = { latitude: data?.location?.[0], longitude: data?.location?.[1] }; - const nowTimestamp = (+(new Date())); - // the unit of server_time is second - const serverTime = data?.server_time; - const serverTimestamp = isNonNanNumber(serverTime) && serverTime > 0 - ? serverTime * 1000 : (+(new Date())); - const expireTimestamp = serverTimestamp + 1000 * 60 * 15; - - return { - language: isNonNanNumber(language) ? language.replace('_', '-') : 'zh-CN', - ...(isLocation(location) && { location }), - expireTimestamp: expireTimestamp < nowTimestamp - ? nowTimestamp + 1000 * 60 * 5 : expireTimestamp, - providerName: isNonEmptyString(providerName) ? providerName : 'ColorfulClouds', - readTimestamp: serverTimestamp, - dataSource: 1, - // https://developer.apple.com/documentation/weatherkitrestapi/unitssystem - unit: 'm', - url, - }; -}; - -/** - * Covert data from ColorfulClouds to minutes for {@link toNextHour} - * @author WordlessEcho - * @param {string} providerName - Name of the provider. Will be used as placeholder. - * @param {colorfulCloudsV2} dataWithMinutely - Data from ColorfulClouds with minutely - * @return {nextHourObject | {}} nextHourObject for {@link toNextHour} - */ -const colorfulCloudsToNextHour = (providerName, dataWithMinutely) => { - const supportedCcApis = [2]; - const supportedUnits = ['metric:v2', 'metric:v1', 'metric']; - - // [降水强度 | 彩云天气 API]{@link https://docs.caiyunapp.com/docs/tables/precip} (v2.6) - const radarLevels = { - NO: { VALUE: 0, RANGE: { LOWER: 0, UPPER: 0.031 } }, - LIGHT: { VALUE: 1, RANGE: { LOWER: 0.031, UPPER: 0.25 } }, - MODERATE: { VALUE: 2, RANGE: { LOWER: 0.25, UPPER: 0.35 } }, - HEAVY: { VALUE: 3, RANGE: { LOWER: 0.35, UPPER: 0.48 } }, - STORM: { VALUE: 4, RANGE: { LOWER: 0.48, UPPER: Number.MAX_VALUE } }, - }; - - // [降水强度 | 彩云天气 API]{@link https://docs.caiyunapp.com/docs/tables/precip} (v2.6) - const mmPerHourLevels = { - NO: { VALUE: 0, RANGE: { LOWER: 0, UPPER: 0.08 } }, - LIGHT: { VALUE: 1, RANGE: { LOWER: 0.08, UPPER: 3.44 } }, - MODERATE: { VALUE: 2, RANGE: { LOWER: 3.44, UPPER: 11.33 } }, - HEAVY: { VALUE: 3, RANGE: { LOWER: 11.33, UPPER: 51.30 } }, - STORM: { VALUE: 4, RANGE: { LOWER: 51.30, UPPER: Number.MAX_VALUE } }, - }; - - const KM = { - zh_CN: '公里', - zh_TW: '公里', - // kilometers - ja: 'キロメートル', - en_US: 'km', - en_GB: 'km', - }; - - /** - * Get precipitation type from ColorfulClouds hourly skycons. - * [天气现象 | 彩云天气API]{@link https://docs.caiyunapp.com/docs/tables/skycon/} - * @author WordlessEcho - * @param {number} timestamp - UNIX timestamp of server time - * @param {colorfulCloudsV2} dataWithHourlySkycons - Date with hourly.skycon[] from ColorfulClouds - * @return {precipitationTypes} - Weather type or empty string if no valid sky condition - */ - const getPrecipitationType = (timestamp, dataWithHourlySkycons) => { - const skycons = dataWithHourlySkycons?.result?.hourly?.skycon; - if (!Array.isArray(skycons)) { - return 'clear'; - } - - const apiVersion = dataWithHourlySkycons?.api_version; - const versionCode = isNonEmptyString(apiVersion) && apiVersion.startsWith('v') && parseFloat(apiVersion.slice(1)); - const validVersionCode = isNonNanNumber(versionCode) ? versionCode : -1; - - const serverTime = dataWithHourlySkycons?.server_time; - const serverTimestamp = isNonNanNumber(serverTime) && serverTime > 0 - ? serverTime * 1000 : (+(new Date())); - const hourTimestamp = (new Date(serverTimestamp)).setMinutes(0, 0, 0); - const currentHourTimestamp = (new Date(timestamp)).setMinutes(0, 0, 0); - - const skyConditions = skycons.filter((skycon) => { - if (!isNonEmptyString(skycon?.datetime)) { - return false; - } - - // https://docs.caiyunapp.com/docs/v2.3/intro#%E4%B8%8D%E5%85%BC%E5%AE%B9%E7%9A%84%E6%9B%B4%E6%96%B0 - if ( - validVersionCode < 2.3 && (!isNonNanNumber(dataWithHourlySkycons?.tzshift) - || dataWithHourlySkycons.tzshift % 60 !== 0) - ) { - return false; - } - const ts = Date.parse(validVersionCode < 2.3 - ? `${skycon.datetime.replace(' ', 'T')}:00.000${minutesToIsoTimezone(dataWithHourlySkycons.tzshift / 60)}` - : skycon.datetime.split('+').join(':00.000+')); - - return isNonNanNumber(ts) && ts > 0 - // Limit to 3 hours since ColorfulClouds provide two hours report - && ts >= hourTimestamp && ts <= hourTimestamp + 1000 * 60 * 60 * 2; - }); - - const skyCondition = skyConditions.concat().sort((a, b) => { - const aTimestamp = Date.parse(validVersionCode < 2.3 - ? `${a.datetime.replace(' ', 'T')}:00.000${minutesToIsoTimezone(dataWithHourlySkycons.tzshift / 60)}` - : a.datetime.split('+').join(':00.000+')); - const bTimestamp = Date.parse(validVersionCode < 2.3 - ? `${b.datetime.replace(' ', 'T')}:00.000${minutesToIsoTimezone(dataWithHourlySkycons.tzshift / 60)}` - : b.datetime.split('+').join(':00.000+')); - - return currentHourTimestamp - aTimestamp - (currentHourTimestamp - bTimestamp); - }).find((skycon) => ( - isNonEmptyString(skycon?.value) && ( - skycon.value.includes('RAIN') || skycon.value.includes('SNOW') - )))?.value; - - if (!isNonEmptyString(skyCondition)) { - return 'precipitation'; - } - - if (skyCondition.includes('SNOW')) { - return 'snow'; - } - - if (skyCondition.includes('RAIN')) { - return 'rain'; - } - - return 'precipitation'; - }; - - /** - * Convert precipitation type to weather status - * @param {{start: number, end: number, isDrizzleOrFlurries: boolean}[]} precipitationInfo - - * Ranges of precipitations with bounds and drizzle or flurries detection - * @param {number} timeInMinute - Time for weather status to get - * @param {precipitationTypes} precipitationType - Type of precipitation - * @param {number} perceived - Precipitation intensity perceived, - * can be generated by {@link toPerceived} - * @return {weatherStatuses} - Weather status to specified minute - */ - const toWeatherStatus = (precipitationInfo, timeInMinute, precipitationType, perceived) => { - if ( - !Array.isArray(precipitationInfo) || !isNonNanNumber(timeInMinute) || timeInMinute < 0 - || precipitationInfo.some((info) => ( - !isNonNanNumber(info?.start) || !isNonNanNumber(info?.end) - )) - ) { - return 'precipitation'; - } - - const status = perceivedToStatus(precipitationType, perceived); - const rainStatus = ['rain', 'drizzle']; - const snowStatus = ['snow', 'flurries']; - const targets = [...rainStatus, ...snowStatus]; - if (targets.includes(status)) { - const infoOfMinute = precipitationInfo.find((info) => ( - timeInMinute >= info.start && timeInMinute <= info.end - )); - - if (typeof infoOfMinute?.isDrizzleOrFlurries === 'boolean') { - if (rainStatus.includes(status)) { - return infoOfMinute.isDrizzleOrFlurries ? 'drizzle' : 'rain'; - } - - if (snowStatus.includes(status)) { - return infoOfMinute.isDrizzleOrFlurries ? 'flurries' : 'snow'; - } - } - } - - return status; - }; - - /** - * Assign chance for each minute - * since ColorfulClouds only provider chances for periods of 30 minutes - * @author WordlessEcho - * @param {number[]} probabilities - `result.minutely.probability` from ColorfulClouds - * @param {number} timeInMinute - Minutes from start time of precipitation - * @return {number | -1} - 0 to 100 integer, -1 will be returned if probability is invalid - */ - const getChance = (probabilities, timeInMinute) => { - if (Array.isArray(probabilities) && isNonNanNumber(timeInMinute) && timeInMinute >= 0) { - // Calculate order, 1 as first index. - // Index here is relative to bound, plus bound for real index in precipitations. - // We have only 4 chances per half hour from API. - const chance = probabilities?.[Math.floor(timeInMinute / 30)]; - - if (isNonNanNumber(chance) && chance >= 0) { - return chance * 100; - } - } - - return -1; - }; - - /** - * Mapping times to 'variable' that helpful for Apple to use cached description - * @author WordlessEcho - * @param {string} description - Description for next two hours from ColorfulClouds - * @param {string} ccLanguage - Language code from ColorfulClouds - * @param {number} timeInMinute - Minutes from start time of precipitaion - * @param {number} timeShift - Number of minutes that has expired - * @return {{longDescription: string | "", parameters: Object. | {}}} - - * Short description and parameters for Apple Weather - */ - const toDescription = (description, ccLanguage, timeInMinute, timeShift) => { - if (!isNonEmptyString(description)) { - return { - longDescription: '', - parameters: {}, - }; - } - - /** - * Map times in description to `{firstAt}`, `{secondAt}`, etc... - * @author WordlessEcho - * @param {string} rawDescription - Description with times - * @return {{longDescription: string | "", parameters: Object. | {}}} - - * Short description and parameters for Apple Weather - */ - const modifyDescription = (rawDescription) => { - if (!isNonEmptyString(rawDescription)) { - return { - longDescription: '', - parameters: {}, - }; - } - - /** - * Insert 'after that' for description. - * Times in description in Apple Weather after `{firstAt}` will be display as period. - * @author WordlessEcho - * @param {string} language - Language from ColorfulClouds for description - * @param {string} modifiedDescription - Description with '{firstAt}' - * @return {string | ""} - Description after inserted. - * Return empty string if description is invalid. - */ - const insertAfterToDescription = (language, modifiedDescription) => { - if (!isNonEmptyString(modifiedDescription)) { - return ''; - } - - const FIRST_AT = '{firstAt}'; - // Words that used to insert into description - const AFTER = { - zh_CN: '再过', - zh_TW: '再過', - ja: 'その後', - en_US: 'after that', - // ColorfulClouds seems not prefer to display multiple times in en_GB - en_GB: 'after that', - }; - - // Split description into two part at `{firstAt}` - const splitDescriptions = modifiedDescription.split(FIRST_AT); - if (splitDescriptions.length < 2) { - return modifiedDescription; - } - - switch (language) { - case 'en_GB': - return [ - ...splitDescriptions.slice(0, splitDescriptions.length - 1), - splitDescriptions[splitDescriptions.length - 1] - // Append `after that` to description. - .replaceAll('} min later', `} min later ${AFTER.en_GB}`), - ].join(FIRST_AT); - case 'zh_CN': - return [ - ...splitDescriptions.slice(0, splitDescriptions.length - 1), - splitDescriptions[splitDescriptions.length - 1] - .replaceAll('直到{', `${AFTER.zh_CN}{`), - ].join(FIRST_AT); - case 'zh_TW': - return [ - ...splitDescriptions.slice(0, splitDescriptions.length - 1), - splitDescriptions[splitDescriptions.length - 1] - .replaceAll('直到{', `${AFTER.zh_TW}{`), - ].join(FIRST_AT); - case 'ja': - // Japanese support from ColorfulClouds is broken for sometime. - // https://lolic.at/notice/AJNH316TTSy1fRlOka - - // TODO: I am not familiar for Japanese, contributions welcome - return [ - ...splitDescriptions.slice(0, splitDescriptions.length - 1), - splitDescriptions[splitDescriptions.length - 1] - .replaceAll('{', `${AFTER.ja} {`), - ].join(FIRST_AT); - case 'en_US': - return [ - ...splitDescriptions.slice(0, splitDescriptions.length - 1), - splitDescriptions[splitDescriptions.length - 1] - .replaceAll('} min later', `} min later ${AFTER.en_US}`), - ].join(FIRST_AT); - default: - return modifiedDescription; - } - }; - - const times = rawDescription - .match(/\d+/g).map((timeInString) => parseInt(timeInString, 10)) - .filter((time) => isNonNanNumber(time) && time > 0); - - const descriptionWithParameters = times.reduce( - ({ longDescription, parameters }, time, index) => { - const key = `${stringifyNumber(index + 1)}At`; - return { - longDescription: longDescription.replace(`${time}`, `{${key}}`), - parameters: { ...parameters, [key]: time - timeShift }, - }; - }, - { longDescription: rawDescription, parameters: {} }, - ); - - const longDescription = insertAfterToDescription( - ccLanguage, - descriptionWithParameters.longDescription, - ); - - return { - longDescription, - parameters: descriptionWithParameters.parameters, - }; - }; - - const SPLITTERS = { - en_US: ['but ', 'and '], - en_GB: ['but ', 'and '], - zh_CN: [','], - zh_TW: [','], - ja: ['、'], - }; - - const allTimesString = description.match(/\d+/g); - if (!Array.isArray(allTimesString)) { - return { longDescription: '', parameters: {} }; - } - - // Split sentence by time - const allTimes = allTimesString.map((timeInString) => parseInt(timeInString, 10)) - .filter((time) => !Number.isNaN(time) && time > 0); - - const expiredTimes = allTimes.filter((time) => time <= timeInMinute); - if (expiredTimes.length <= 0) { - return modifyDescription(description); - } - - const maxExpiredTime = Math.max(...expiredTimes); - if (maxExpiredTime === allTimes[allTimes.length - 1]) { - return { - longDescription: '', - parameters: {}, - }; - } - - const startIndex = description.indexOf(`${maxExpiredTime}`) + `${maxExpiredTime}`.length; - - const splitters = SPLITTERS[ccLanguage]; - const splitIndexes = splitters.map((splitter) => ( - description.indexOf(splitter, startIndex) + splitter.length - )).filter((index) => index !== -1); - - return modifyDescription(description.slice(Math.min(...splitIndexes))); - }; - - const provider = isNonEmptyString(providerName) ? providerName : $.name; - - const apiVersion = dataWithMinutely?.api_version; - const versionCode = isNonEmptyString(apiVersion) && apiVersion.startsWith('v') && parseFloat(apiVersion.slice(1)); - const validVersionCode = isNonNanNumber(versionCode) ? versionCode : -1; - const majorVersion = Math.trunc(validVersionCode); - if ( - dataWithMinutely?.status !== 'ok' - || !supportedCcApis.includes(majorVersion) - || !supportedUnits.includes(dataWithMinutely?.unit) - // ColorfulClouds: This might be deprecated in future - || dataWithMinutely?.result?.minutely?.datasource !== 'radar' - || !Array.isArray(dataWithMinutely.result.minutely?.precipitation_2h) - || dataWithMinutely.result.minutely.precipitation_2h.some((p) => !isNonNanNumber(p)) - || !Array.isArray(dataWithMinutely.result.minutely?.probability) - || dataWithMinutely.result.minutely.probability.some((p) => !isNonNanNumber(p)) - ) { - // eslint-disable-next-line functional/no-conditional-statement - if (dataWithMinutely?.result?.minutely?.datasource !== 'radar') { - logger('error', `${colorfulCloudsToNextHour.name}:缺少此地的短临降水数据`); - logger( - 'debug', - `${colorfulCloudsToNextHour.name}:数据源:${dataWithMinutely?.result?.minutely?.datasource}`, - ); - } - - return {}; - } - - // `server_time` is in seconds - const serverTime = dataWithMinutely?.server_time; - const serverTimestamp = isNonNanNumber(serverTime) && serverTime > 0 - ? serverTime * 1000 : (+(new Date())); - const startTimestamp = (new Date()).setSeconds(0, 0) + 1000 * 60; - const startIndex = (startTimestamp - (new Date(serverTimestamp).setSeconds(0, 0) + 1000 * 60)) - / 1000 / 60; - const validStartIndex = startIndex >= 0 ? startIndex : 0; - - const maxPrecipitation = Math.max(...dataWithMinutely.result.minutely.precipitation_2h); - const levels = dataWithMinutely?.unit === 'metric:v2' ? mmPerHourLevels : radarLevels; - - const precipitations = dataWithMinutely.result.minutely.precipitation_2h.slice(validStartIndex); - const precipitationBounds = precipitations.flatMap((current, index, array) => { - const previous = array[index - 1]; - - if (index === 0 || previous < levels.NO.RANGE.UPPER) { - if (current >= levels.NO.RANGE.UPPER) { - return [index]; - } - } else if (previous >= levels.NO.RANGE.UPPER) { - if (current < levels.NO.RANGE.UPPER) { - return [index]; - } - } - - return []; - }); - const precipitationInfo = precipitationBounds.flatMap((value, index, array) => { - if (index % 2 > 0) { - return []; - } - - const start = value; - const end = array?.[index + 1]; - const validEnd = isNonNanNumber(end) && end >= 0 ? end : precipitations.length; - - const slicedPrecipitations = precipitations.slice(start, validEnd); - const sum = slicedPrecipitations.reduce((p, c) => p + c, 0); - const average = sum / slicedPrecipitations.length; - - return [{ - start, - end: validEnd, - isDrizzleOrFlurries: isNonNanNumber(sum) - && Math.max(...slicedPrecipitations) < levels.HEAVY.RANGE.LOWER - && average < levels.MODERATE.RANGE.LOWER, - }]; - }); - - const minutes = precipitations.map((precipitation, index) => { - const validPrecipitation = isNonNanNumber(precipitation) && precipitation >= 0 - ? precipitation : 0; - - const timeInMinute = validStartIndex + index + 1; - - const hourlyPrecipitationType = getPrecipitationType( - serverTimestamp + 1000 * 60 * timeInMinute, - dataWithMinutely, - ); - const precipitationType = maxPrecipitation >= Object.values(levels) - .find(({ VALUE }) => VALUE === 0).RANGE.UPPER ? hourlyPrecipitationType : 'clear'; - - const precipitationIntensityPerceived = toPerceived(levels, validPrecipitation); - - const isClear = validPrecipitation < levels.NO.RANGE.UPPER; - const chance = getChance(dataWithMinutely.result.minutely.probability, timeInMinute); - const validChance = chance >= 0 ? chance : 100; - - const ccDescription = dataWithMinutely.result.minutely?.description; - // ColorfulClouds may report no rain even if precipitation > no rain - const descriptionWithParameters = !isNonEmptyString(ccDescription) - || ccDescription.includes(KM[dataWithMinutely?.lang]) - ? { - longDescription: provider, - parameters: {}, - } - : toDescription( - ccDescription, - dataWithMinutely?.lang, - timeInMinute, - validStartIndex, - ); - - const validDescriptionWithParameters = descriptionWithParameters.longDescription.length > 0 - ? descriptionWithParameters : { longDescription: provider, parameters: {} }; - - return { - weatherStatus: toWeatherStatus( - precipitationInfo, - timeInMinute, - precipitationType, - precipitationIntensityPerceived, - ), - precipitation: validPrecipitation, - precipitationIntensityPerceived, - // Set chance to zero if clear - chance: isClear ? 0 : validChance, - shortDescription: isNonEmptyString(dataWithMinutely.result?.forecast_keypoint) - ? dataWithMinutely.result?.forecast_keypoint : provider, - ...validDescriptionWithParameters, - }; - }); - - return { - startTimestamp, - minutes, - }; -}; - -/** - * Append station name from QWeather to provider name - * @author WordlessEcho - * @param {string} providerName - Provider name from metadata - * @param {string} source - Station name in source - * @return {string | ""} - Appended string - */ -const appendQweatherSourceToProviderName = (providerName, source) => { - if (!isNonEmptyString(source)) { - return isNonEmptyString(providerName) ? providerName : $.name; - } - - switch (providerName) { - case '和风天气': - return `${source}(${providerName})`; - case 'QWeather': - return `${source} (${providerName})`; - default: - return providerName; - } -}; - -/** - * Create metadata - * @author VirgilClyne - * @author WordlessEcho - * @param {supportedAppleApi} appleApiVersion - Apple API version - * @param {metadataObject} metadataObject - Object of the metadata info - * @return { - * appleNextHourMetadataV1|appleNextHourMetadataV2|appleNextHourMetadataV3|appleAirQualityMetadataV1 - * |appleAirQualityMetadataV2|appleAirQualityMetadataV3 | {} - * } - Metadata for air quality or next hour in Apple Weather format - */ -const toMetadata = (appleApiVersion, metadataObject) => { - const supportedApis = [1, 2, 3]; - if (!supportedApis.includes(appleApiVersion)) { - return {}; - } - - const sharedMetadata = { - ...(isNonEmptyString(metadataObject?.language) && { language: metadataObject.language }), - ...(isLatitude(metadataObject?.location?.latitude) - && { latitude: metadataObject.location.latitude }), - ...(isLongitude(metadataObject?.location?.longitude) - && { longitude: metadataObject.location.longitude }), - }; - - const getSharedMetadataV2Plus = (apiVersion, metadataObj) => ({ - ...(isNonNanNumber(metadataObj?.expireTimestamp) && metadataObj.expireTimestamp > 0 - && { expireTime: toAppleTime(apiVersion, metadataObj.expireTimestamp) }), - ...(isNonEmptyString(metadataObj?.providerLogo?.forV2) - && { providerLogo: metadataObj.providerLogo.forV2 }), - ...(isNonEmptyString(metadataObj?.providerName) - && { providerName: metadataObj.providerName }), - ...(isNonNanNumber(metadataObj?.readTimestamp) && metadataObj.readTimestamp > 0 - && { readTime: toAppleTime(apiVersion, metadataObj.readTimestamp) }), - ...(isNonNanNumber(metadataObj?.reportedTimestamp) && metadataObj.reportedTimestamp > 0 - && { reportedTime: toAppleTime(apiVersion, metadataObj.reportedTimestamp) }), - ...(isNonEmptyString(metadataObj?.unit) && { units: metadataObj.unit }), - }); - - /** - * Merge metadata for Apple Weather by API version - * @param {supportedAppleApi} apiVersion - Apple API version - * @param {metadataObject} metadataObj - Metadata object - * @return { - * appleNextHourMetadataV1|appleNextHourMetadataV2|appleNextHourMetadataV3 - * |appleAirQualityMetadataV1|appleAirQualityMetadataV2|appleAirQualityMetadataV3 | {} - * } - Metadata for air quality or next hour in Apple Weather format (without `name` property) - */ - const getMetadata = (apiVersion, metadataObj) => { - switch (apiVersion) { - case 1: - // no units for APIv1 - return { - ...sharedMetadata, - ...(isNonNanNumber(metadataObj?.expireTimestamp) && metadataObj.expireTimestamp > 0 - && { expire_time: toAppleTime(apiVersion, metadataObj.expireTimestamp) }), - ...(isNonEmptyString(metadataObj?.providerLogo?.forV1) - && { provider_logo: metadataObj.providerLogo.forV1 }), - ...(isNonEmptyString(metadataObj?.providerName) - && { provider_name: metadataObj.providerName }), - ...(isNonNanNumber(metadataObj?.readTimestamp) && metadataObj.readTimestamp > 0 - && { read_time: toAppleTime(apiVersion, metadataObj.readTimestamp) }), - ...(isNonNanNumber(metadataObj?.reportedTimestamp) && metadataObj.reportedTimestamp > 0 - && { reported_time: toAppleTime(apiVersion, metadataObj.reportedTimestamp) }), - ...((metadataObj?.dataSource === 0 || metadataObj?.dataSource === 1) - && { data_source: metadataObj.dataSource }), - }; - case 2: - // no data source for APIv2 - return { - ...sharedMetadata, - ...getSharedMetadataV2Plus(apiVersion, metadataObj), - }; - case 3: - return { - ...(isNonEmptyString(metadataObj?.url) && { attributionURL: metadataObj.url }), - ...sharedMetadata, - ...getSharedMetadataV2Plus(apiVersion, metadataObj), - }; - default: - return {}; - } - }; - - const metadata = getMetadata(appleApiVersion, metadataObject); - return Object.keys(sharedMetadata).length > 0 || Object.keys(metadata).length > 0 - ? { - ...(isNonNanNumber(appleApiVersion) && appleApiVersion > 0 - && { version: appleApiVersion > 2 ? appleApiVersion - 2 : appleApiVersion }), - ...sharedMetadata, - ...metadata, - } : {}; -}; - -/** - * Output pollutant info - * @param {supportedAppleApi} appleApiVersion - Apple Weather API version - * @param {applePollutantNames} name - Name of the pollutant - * @param {number} amount - Amount of pollutant - * @param {pollutantUnitsV2} unit - Unit of pollutant in Apple Weather types - * @return {pollutantV1|pollutantV2 | {}} - Pollutant info for Apple Weather - */ -const toPollutant = (appleApiVersion, name, amount, unit) => { - if (!isNonEmptyString(name) || !['ppb', 'microgramsPerM3'].includes(unit) || !isNonNanNumber(amount)) { - return {}; - } - - switch (appleApiVersion) { - case 1: - return { - name, - amount: amount > 0 ? amount : -1, - unit: unit === 'microgramsPerM3' ? 'µg/m3' : unit, - }; - case 2: - case 3: - return { - name, - amount: amount > 0 ? amount : -1, - unit, - }; - default: - return {}; - } -}; - -/** - * Output Air Quality Data - * @author VirgilClyne - * @author WordlessEcho - * @param {supportedAppleApi} appleApiVersion - Apple Weather API Version - * @param {airQualityObject} aqiObject - Object of the AQI info - * @return { - * appleAqiWithoutMetadataV1|appleAqiWithoutMetadataV2|appleAqiWithoutMetadataV3 | {} - * } - Air quality data in Apple Weather style without metadata - */ -const toAirQuality = (appleApiVersion, aqiObject) => { - const units = ['ppb', 'microgramsPerM3']; - const comparisonValues = ['better', 'same', 'worse', 'unknown']; - const sourceTypes = ['station', 'modeled']; - - const validPollutants = Array.isArray(aqiObject?.pollutants) ? aqiObject.pollutants.filter( - (pollutant) => ( - units.includes(pollutant?.unit) && isNonEmptyString(pollutant?.name) - && isNonNanNumber(pollutant?.amount) && pollutant.amount >= 0 - ), - ) : []; - - const sharedAirQuality = { - ...(typeof aqiObject?.isSignificant === 'boolean' && { isSignificant: aqiObject.isSignificant }), - ...(isNonEmptyString(aqiObject?.url) && { learnMoreURL: aqiObject.url }), - ...(isNonEmptyString(aqiObject?.primary) && { primaryPollutant: aqiObject.primary }), - ...(validPollutants.length > 0 && { - pollutants: Object.fromEntries(validPollutants.map(({ name, amount, unit }) => ([ - name, toPollutant(appleApiVersion, name, amount, unit), - ]))), - }), - // Source was removed in APIv3 - ...(isNonEmptyString(aqiObject?.sourceName) && { source: aqiObject.sourceName }), - }; - - /** - * Merge air quality for Apple Weather by API version - * @param {supportedAppleApi} apiVersion - Apple API version - * @param {airQualityObject} aqiObj - Air quality object - * @return {appleAqiWithoutMetadataV1|appleAqiWithoutMetadataV2|appleAqiWithoutMetadataV3 | {}} - - * Air quality object for Apple Weather (without `name` property) - */ - const getAirQuality = (apiVersion, aqiObj) => { - switch (apiVersion) { - case 1: - return { - ...sharedAirQuality, - ...(isNonNanNumber(aqiObj?.categoryIndex) && aqiObj.categoryIndex > 0 - && { airQualityCategoryIndex: aqiObj.categoryIndex }), - ...(isNonNanNumber(aqiObj?.aqi) && aqiObj.aqi >= 0 - && { airQualityIndex: aqiObj.aqi }), - ...(isNonEmptyString(aqiObj?.scale) && { airQualityScale: aqiObj.scale }), - }; - case 2: - case 3: - return { - ...sharedAirQuality, - ...(isNonNanNumber(aqiObj?.categoryIndex) && aqiObj.categoryIndex > 0 - && { categoryIndex: aqiObj.categoryIndex }), - ...(isNonNanNumber(aqiObj?.aqi) && aqiObj.aqi >= 0 - && { index: aqiObj.aqi }), - ...(comparisonValues.includes(aqiObj?.previousDayComparison) - && { previousDayComparison: aqiObj.previousDayComparison }), - ...(isNonEmptyString(aqiObj?.scale) && { scale: aqiObj.scale }), - ...(sourceTypes.includes(aqiObj?.sourceType) && { sourceType: aqiObj.sourceType }), - }; - default: - return {}; - } - }; - - const airQuality = getAirQuality(appleApiVersion, aqiObject); - return Object.keys(sharedAirQuality).length > 0 || Object.keys(airQuality).length > 0 - ? { name: 'AirQuality', ...sharedAirQuality, ...airQuality } : {}; -}; - -/** - * Output object for `NextHourForecast` of Apple Weather - * @author WordlessEcho - * @author VirgilClyne - * @param {supportedAppleApi} appleApiVersion - Apple Weather API Version - * @param {nextHourObject} nextHourObject - Object of the precipitation info - * @return { - * appleNextHourWithoutMetadataV1|appleNextHourWithoutMetadataV2|appleNextHourWithoutMetadataV3 | {} - * } - Apple weather style data without metadata - */ -const toNextHour = (appleApiVersion, nextHourObject) => { - if ( - !Array.isArray(nextHourObject?.minutes) || !isNonNanNumber(nextHourObject?.startTimestamp) - || nextHourObject.startTimestamp <= 0 - ) { - return {}; - } - - /** - * Check type of weather status of minute - * @param {weatherStatuses} weatherStatus - Weather status to be checked - * @param {number} precipitation - Precipitation of the weather status - * @return {weatherStatuses} - `weatherStatus` if valid, - * or "precipitation"/"clear" based on precipitation - */ - const checkWeatherStatus = (weatherStatus, precipitation) => { - const weatherStatuses = [ - 'clear', 'precipitation', 'drizzle', 'flurries', 'rain', 'snow', 'heavy-rain', 'heavy-snow', - 'sleet', 'hail', 'mixed', - ]; - - if (!weatherStatuses.includes(weatherStatus)) { - if (isNonNanNumber(precipitation) && precipitation > 0) { - return 'precipitation'; - } - - return 'clear'; - } - - return weatherStatus; - }; - - const minutesData = nextHourObject.minutes.map((minute) => { - const precipitationIntensityPerceived = isNonNanNumber(minute?.precipitationIntensityPerceived) - && minute.precipitationIntensityPerceived >= 0 ? minute.precipitationIntensityPerceived : 0; - const fallbackChance = precipitationIntensityPerceived <= 0 ? 0 : 100; - const chance = isNonNanNumber(minute?.chance) && minute.chance >= 0 && minute.chance <= 100 - ? minute.chance : fallbackChance; - - const validShortDescription = isNonEmptyString(minute?.shortDescription) - ? minute.shortDescription : $.name; - const validLongDescription = isNonEmptyString(minute?.longDescription) - ? minute.longDescription : ''; - - return { - weatherStatus: checkWeatherStatus(minute?.weatherStatus, minute?.precipitation), - precipitation: isNonNanNumber(minute?.precipitation) && minute.precipitation >= 0 - ? minute.precipitation : 0, - precipitationIntensityPerceived, - chance: Math.round(chance), - shortDescription: validShortDescription, - longDescription: validLongDescription, - ...({ parameters: isObject(minute?.parameters) ? minute.parameters : {} }), - }; - }); - - /** - * Get array of condition for `condition` in `NextHourForecast` - * @author WordlessEcho - * @param {supportedAppleApi} apiVersion - Apple Weather API Version - * @param {minute[]} minutes - Array of minute precipitation data - * @param {number} startTimestamp - UNIX timestamp at condition start - * @return {nextHourConditionV1[]|nextHourConditionV2[]|nextHourConditionV3[]|[]} - - * Conditions for Apple Weather - */ - const toConditions = (apiVersion, minutes, startTimestamp) => { - const slicedMinutes = minutes.slice(0, 60); - - /** - * Merge possibility, weather status and time status for `forecastNextHour.condition.token` - * @author WordlessEcho - * @param {number} bound - Bound of current weather status - * @return {string} - Token for Apple Weather - */ - const toToken = (bound) => { - if (!isNonNanNumber(bound) || bound < 0 || bound >= slicedMinutes.length) { - return 'clear'; - } - - const firstStatus = slicedMinutes[bound].weatherStatus; - const secondStatusRelatedIndex = slicedMinutes.slice(bound).findIndex((minute) => ( - minute.weatherStatus !== firstStatus)); - const secondStatusIndex = secondStatusRelatedIndex === -1 - ? -1 : secondStatusRelatedIndex + bound; - const secondStatus = secondStatusIndex === -1 ? null - : slicedMinutes[secondStatusIndex].weatherStatus; - - if (firstStatus === 'clear') { - if (secondStatusIndex === -1) { - return firstStatus; - } - - const nextClearIndex = slicedMinutes.slice(secondStatusIndex) - .findIndex((minute) => minute.weatherStatus === 'clear'); - if (nextClearIndex === -1) { - const maxChance = Math.max(...slicedMinutes - .slice(secondStatusIndex).map((minute) => minute.chance)); - // https://developer.apple.com/documentation/weatherkitrestapi/certainty - return `${maxChance < 50 ? 'possible-' : ''}${secondStatus}.start`; - } - - const maxChance = Math.max(...slicedMinutes - .slice(secondStatusIndex, nextClearIndex).map((minute) => minute.chance)); - return `${maxChance < 50 ? 'possible-' : ''}${secondStatus}.start-stop`; - } - - // If current weather is not clear - if (secondStatus === 'clear') { - const nextNotClearIndex = slicedMinutes.slice(secondStatusIndex) - .findIndex((minute) => minute.weatherStatus !== 'clear'); - const maxChance = Math.max(...slicedMinutes - .slice(bound, secondStatusIndex).map((minute) => minute.chance)); - - if (nextNotClearIndex !== -1) { - return `${maxChance < 50 ? 'possible-' : ''}${firstStatus}.stop-start`; - } - - return `${maxChance < 50 ? 'possible-' : ''}${firstStatus}.stop`; - } - - const maxChance = Math.max(...slicedMinutes.map((minute) => minute.chance)); - return firstStatus.startsWith('heavy-') && secondStatusIndex !== -1 - ? `${maxChance < 50 ? 'possible-' : ''}${firstStatus}-to-${secondStatus}.constant` - : `${maxChance < 50 ? 'possible-' : ''}${firstStatus}.constant`; - }; - - const toParameters = (bounds, indexInBound, timestamp) => bounds - .slice(indexInBound).reduce((parameters, currentBound, index) => { - const lastStatus = slicedMinutes[ - indexInBound + index - 1 < 0 ? 0 : bounds[indexInBound + index - 1] - ].weatherStatus; - const currentStatus = slicedMinutes[currentBound].weatherStatus; - - if ( - currentBound + 1 === slicedMinutes.length && (currentBound <= 0 - || currentStatus === slicedMinutes[currentBound - 1].weatherStatus) - ) { - return parameters; - } - - return { - ...parameters, - ...((lastStatus !== 'clear' || currentStatus !== 'clear') && { - [`${stringifyNumber(Object.keys(parameters).length + 1)}At`]: toAppleTime( - apiVersion, - timestamp + currentBound * 60 * 1000, - ), - }), - }; - }, {}); - - /** @type {number[]} */ - const bounds = slicedMinutes.flatMap((current, index, array) => { - const previous = array[index - 1]; - - if ( - index === 0 - || (index + 1 !== array.length && current.weatherStatus === previous.weatherStatus) - ) { - return []; - } - - return [index]; - }); - - const timestamp = isNonNanNumber(startTimestamp) && startTimestamp > 0 - ? startTimestamp : (+(new Date())); - - // TODO: (FIX) Infinite loop while length of minutesData is 1 - return bounds.map((bound, index, array) => { - const minute = slicedMinutes[bound]; - const lastBound = index === 0 ? 0 : array[index - 1]; - - const token = toToken(lastBound); - const needEndTime = !( - index + 1 === array.length && minute.weatherStatus === minutes[bound + 1].weatherStatus - ); - - const haveLongDescription = isNonEmptyString(minute.longDescription); - const longDescription = haveLongDescription ? minute.longDescription : $.name; - - switch (apiVersion) { - case 1: - return { - ...(needEndTime && { - validUntil: toAppleTime(apiVersion, timestamp + bound * 60 * 1000), - }), - token, - longTemplate: !haveLongDescription && isNonEmptyString(minute.shortDescription) - ? minute.shortDescription : longDescription, - shortTemplate: minute.shortDescription, - parameters: haveLongDescription && Object.keys(minute.parameters).length > 0 - ? Object.fromEntries(Object.entries(minute.parameters).map(([key, value]) => [ - key, toAppleTime(apiVersion, timestamp + value * 60 * 1000), - ])) : toParameters(array, index, slicedMinutes, timestamp), - }; - case 2: - return { - startTime: toAppleTime(apiVersion, timestamp + lastBound * 60 * 1000), - ...(needEndTime && { - endTime: toAppleTime(apiVersion, timestamp + bound * 60 * 1000), - }), - token, - longTemplate: !haveLongDescription && isNonEmptyString(minute.shortDescription) - ? minute.shortDescription : longDescription, - shortTemplate: minute.shortDescription, - parameters: haveLongDescription && Object.keys(minute.parameters).length > 0 - ? Object.fromEntries(Object.entries(minute.parameters).map(([key, value]) => [ - key, toAppleTime(apiVersion, timestamp + value * 60 * 1000), - ])) : toParameters(array, index, timestamp), - }; - case 3: - return { - startTime: toAppleTime(apiVersion, timestamp + lastBound * 60 * 1000), - ...(needEndTime && { - endTime: toAppleTime(apiVersion, timestamp + bound * 60 * 1000), - }), - token, - parameters: toParameters(array, index, timestamp), - }; - default: - return {}; - } - }); - }; - - /** - * Get array of summary for `summary` in `NextHourForecast` - * @author WordlessEcho - * @param {supportedAppleApi} apiVersions - Apple Weather API Version - * @param {minute[]} minutes - Array of minute precipitation data - * @param {number} startTimestamp - UNIX timestamp when minutes start - * @return {nextHourSummaryV1[]|nextHourSummaryV2[]|nextHourSummaryV3[]|[]} - - * Summaries for Apple Weather - */ - const toSummaries = (apiVersions, minutes, startTimestamp) => { - /** @type number[] */ - const bounds = minutes.slice(0, 59).flatMap((current, index, array) => { - const previous = array[index - 1]; - - if ( - index === 0 || ( - index + 1 !== array.length && weatherStatusToType(current.weatherStatus) - === weatherStatusToType(previous.weatherStatus) - ) - ) { - return []; - } - - return [index]; - }); - - const timestamp = isNonNanNumber(startTimestamp) && startTimestamp > 0 - ? startTimestamp : (+(new Date())); - - return bounds.map((bound, index, array) => { - const lastBound = index === 0 ? 0 : array[index - 1]; - const minutesInSummary = minutes.slice(lastBound, bound); - - const needEndTime = !( - index + 1 === array.length - && minutes[bound].weatherStatus === minutes[bound + 1].weatherStatus - ); - const condition = weatherStatusToType(minutes[lastBound].weatherStatus); - - const isNotClear = condition !== 'clear'; - const maxChance = Math.max(...minutesInSummary.map(({ chance }) => chance)); - const precipitations = minutesInSummary.map(({ precipitation }) => precipitation); - - switch (apiVersions) { - case 1: - return { - ...(needEndTime && { - validUntil: toAppleTime(apiVersions, timestamp + bound * 60 * 1000), - }), - condition, - ...(isNotClear ? { - probability: maxChance, - maxIntensity: Math.max(...precipitations), - minIntensity: Math.min(...precipitations), - } : null), - }; - // to make ESLint and JSDoc happy - case 2: - return { - startTime: toAppleTime(apiVersions, timestamp + lastBound * 60 * 1000), - ...(needEndTime && { - endTime: toAppleTime(apiVersions, timestamp + bound * 60 * 1000), - }), - condition, - ...(isNotClear && { - precipChance: maxChance, - precipIntensity: Math.max(...precipitations), - }), - }; - case 3: - return { - startTime: toAppleTime(apiVersions, timestamp + lastBound * 60 * 1000), - ...(needEndTime && { - endTime: toAppleTime(apiVersions, timestamp + bound * 60 * 1000), - }), - condition, - precipitationChance: maxChance / 100, - precipitationIntensity: Math.max(...precipitations), - }; - default: - return {}; - } - }); - }; - - /** - * Get array of minutes for `minutes` in `NextHourForecast` - * @author WordlessEcho - * @param {supportedAppleApi} apiVersions - Apple Weather API Version - * @param {minute[]} minutes - Array of minute precipitation data - * @param {number} startTimestamp - UNIX timestamp when minutes start - * @return {nextHourMinuteV1[]|nextHourMinuteV2[]|nextHourMinuteV3[]|[]} - - * Minutes for Apple Weather - */ - const toMinutes = (apiVersions, minutes, startTimestamp) => { - const timestamp = isNonNanNumber(startTimestamp) && startTimestamp > 0 - ? startTimestamp : (+(new Date())); - - const getSharedMinuteV2Below = (precipIntensity, precipChance) => ({ - precipIntensity, - precipChance, - }); - - return minutes.map( - ({ precipitation, chance, precipitationIntensityPerceived }, index) => { - switch (apiVersions) { - case 1: - return { - startAt: toAppleTime(apiVersions, timestamp + index * 60 * 1000), - ...getSharedMinuteV2Below(precipitation, chance), - perceivedIntensity: precipitationIntensityPerceived, - }; - case 2: - return { - startTime: toAppleTime(apiVersions, timestamp + index * 60 * 1000), - ...getSharedMinuteV2Below(precipitation, chance), - precipIntensityPerceived: precipitationIntensityPerceived, - }; - case 3: - return { - startTime: toAppleTime(apiVersions, timestamp + index * 60 * 1000), - precipitationChance: chance / 100, - precipitationIntensity: precipitation, - precipitationIntensityPerceived, - }; - default: - return {}; - } - }, - ); - }; - - /** - * Merge next hour object - * @author WordlessEcho - * @param {supportedAppleApi} apiVersion - Apple API version - * @param {?(nextHourConditionV1[]|nextHourConditionV2[]|nextHourConditionV3[])} condition - - * Condition for Apple Weather - * @param {?(nextHourSummaryV1[]|nextHourSummaryV2[]|nextHourSummaryV3[])} summary - - * Summary for Apple Weather - * @param {?(nextHourMinuteV1[]|nextHourMinuteV2[]|nextHourMinuteV3[])} minutes - - * Minutes for Apple Weather - * @param {number} timestamp - UNIX timestamp at next hour start - * @return { - * appleNextHourWithoutMetadataV1|appleNextHourWithoutMetadataV2|appleNextHourWithoutMetadataV3 - * | {} - * } - Next hour object for Apple Weather (without `name` property) - */ - const getNextHour = (apiVersion, condition, summary, minutes, timestamp) => { - const sharedNextHour = { - ...(Array.isArray(condition) && condition.length > 0 && { condition }), - ...(Array.isArray(summary) && summary.length > 0 && { summary }), - ...(Array.isArray(minutes) && minutes.length > 0 && { minutes }), - }; - - switch (apiVersion) { - case 1: - case 2: - return { - ...sharedNextHour, - ...(isNonNanNumber(timestamp) && timestamp > 0 - && { startTime: toAppleTime(appleApiVersion, timestamp) }), - }; - case 3: - return { - ...sharedNextHour, - ...(isNonNanNumber(timestamp) && timestamp > 0 - && { forecastStart: toAppleTime(appleApiVersion, timestamp) }), - ...(Array.isArray(minutes) && minutes.length > 0 && { - forecastEnd: toAppleTime(appleApiVersion, timestamp + 1000 * 60 * minutes.length), - }), - }; - default: - return {}; - } - }; - - const nextHour = getNextHour( - appleApiVersion, - toConditions(appleApiVersion, minutesData, nextHourObject.startTimestamp), - toSummaries(appleApiVersion, minutesData, nextHourObject.startTimestamp), - toMinutes(appleApiVersion, minutesData, nextHourObject.startTimestamp), - nextHourObject.startTimestamp, - ); - - if (Object.keys(nextHour).length > 0) { - // eslint-disable-next-line functional/no-conditional-statement - if (!nextHour.summary.some((s) => s.condition !== 'clear')) { - logger( - 'info', - `${toNextHour.name}:API报告此地未来一小时无降水,Apple天气上将不会显示下小时降水强度信息`, - ); - // eslint-disable-next-line functional/no-conditional-statement - } else if (nextHour.summary.some((s) => s.condition === 'precipitation')) { - logger('warn', `${toNextHour.name}:缺失部分雨雪类型信息`); - } - - logger('debug', `${toNextHour.name}:condition = ${JSON.stringify(nextHour.condition)}`); - logger('debug', `${toNextHour.name}:summary = ${JSON.stringify(nextHour.summary)}`); - - return { name: 'NextHourForecast', ...nextHour }; - } - - return {}; -}; - -/** - * Get name of the key for Apple Weather by API version - * @author WordlessEcho - * @param {supportedAppleApi} apiVersion - Apple API version - * @return {Object. | {}} - Name of the key - */ -const getKeywords = (apiVersion) => { - switch (apiVersion) { - case 1: - return { - METADATA: 'metadata', - AIR_QUALITY: 'air_quality', - REQUIRE_NEXT_HOUR: 'next_hour_forecast', - NEXT_HOUR: 'next_hour', - NEXT_HOUR_SUMMARY: 'summary', - NEXT_HOUR_SUMMARY_CONDITION: 'condition', - PROVIDER_NAME: 'provider_name', - REPORTED_TIME: 'reported_time', - AQI_INDEX: 'airQualityIndex', - AQI_SCALE: 'airQualityScale', - POLLUTANTS: 'pollutants', - PRIMARY_POLLUTANT: 'primaryPollutant', - UNIT: 'unit', - AMOUNT: 'amount', - SOURCE: 'source', - AQI_COMPARISON: '', - TEMPORARILY_UNAVAILABLE: 'temporarily_unavailable', - }; - case 2: - case 3: - return { - METADATA: 'metadata', - AIR_QUALITY: 'airQuality', - REQUIRE_NEXT_HOUR: 'forecastNextHour', - NEXT_HOUR: 'forecastNextHour', - NEXT_HOUR_SUMMARY: 'summary', - NEXT_HOUR_SUMMARY_CONDITION: 'condition', - PROVIDER_NAME: 'providerName', - REPORTED_TIME: 'reportedTime', - AQI_INDEX: 'index', - AQI_SCALE: 'scale', - POLLUTANTS: 'pollutants', - PRIMARY_POLLUTANT: 'primaryPollutant', - UNIT: 'unit', - AMOUNT: 'amount', - SOURCE: 'source', - AQI_COMPARISON: 'previousDayComparison', - TEMPORARILY_UNAVAILABLE: 'temporarilyUnavailable', - }; - default: - return {}; - } -}; - -/** - * Convert apple time to UNIX timestamp - * @author WordlessEcho - * @param {supportedAppleApi} apiVersion - Apple API version - * @param {number|string} time - Time in Apple format - * @param {number} fallbackTimestamp - Time for fallback if convert failed - * @return {number} - UNIX timestamp of Apple time - */ -const appleTimeToTimestamp = (apiVersion, time, fallbackTimestamp) => { - const fallback = isNonNanNumber(fallbackTimestamp) && fallbackTimestamp > 0 - ? fallbackTimestamp : (+(new Date())); - - switch (apiVersion) { - case 1: - return isNonNanNumber(time) && time > 0 ? time * 1000 : fallback; - case 2: { - const timestamp = Date.parse(time); - return isNonNanNumber(timestamp) && timestamp > 0 ? timestamp : fallback; - } - default: - return fallback; - } -}; - -/** - * Set response - * @param {Object} response - Response to set - */ -const setResponse = (response) => { - // eslint-disable-next-line functional/no-conditional-statement - if (isObject(response)) { - // eslint-disable-next-line functional/no-expression-statement - $.done(!$.isQuanX() ? response : { body: isNonEmptyString(response?.body) ? response?.body : '' }); - // eslint-disable-next-line functional/no-conditional-statement - } else { - // eslint-disable-next-line functional/no-expression-statement - $.done({ body: '' }); - } -}; - -// eslint-disable-next-line functional/no-conditional-statement -if (settings.switch) { - const supportedAppleApis = [1, 2, 3]; - - // eslint-disable-next-line no-undef - if (isNonEmptyString($request?.url)) { - // eslint-disable-next-line no-undef - const url = (new URLs()).parse($request.url); - - if (url) { - const parameters = getParams(url.path); - const appleApiVersionString = parameters?.ver; - const appleApiVersion = isNonEmptyString(appleApiVersionString) - ? parseInt(appleApiVersionString.slice(1), 10) : -1; - - // eslint-disable-next-line functional/no-conditional-statement - if (!supportedAppleApis.includes(appleApiVersion)) { - logger('error', `${$.name}:不支持${appleApiVersionString}版本的Apple API,您可能需要更新模块`); - // eslint-disable-next-line functional/no-expression-statement,no-undef - setResponse($response); - // eslint-disable-next-line functional/no-conditional-statement,no-undef - } else if ($response?.statusCode !== 200 && $response?.status !== 200) { - logger( - 'warn', - // eslint-disable-next-line no-undef - `${$.name}:服务器返回非200状态码,statusCode = ${$response?.statusCode}, status = ${$response?.status}`, - ); - // eslint-disable-next-line functional/no-expression-statement,no-undef - setResponse($response); - // eslint-disable-next-line functional/no-conditional-statement - } else { - logger('info', `${$.name}:模块开始运行`); - - // eslint-disable-next-line no-undef - const dataFromApple = parseJsonWithDefault($response?.body, null); - // eslint-disable-next-line functional/no-conditional-statement - if (isObject(dataFromApple)) { - const latitude = parseFloat(parameters?.lat); - const longitude = parseFloat(parameters?.lng); - - // eslint-disable-next-line functional/no-conditional-statement - if (isLatitude(latitude) && isLongitude(longitude)) { - const languageWithRegion = parameters?.language; - - // eslint-disable-next-line functional/no-conditional-statement - if (!isNonEmptyString(languageWithRegion)) { - logger('warn', `${$.name}:无法获取语言信息,语言:${parameters?.language}`); - } - - const { - METADATA, AIR_QUALITY, REQUIRE_NEXT_HOUR, NEXT_HOUR, PROVIDER_NAME, REPORTED_TIME, - AQI_INDEX, AQI_SCALE, POLLUTANTS, PRIMARY_POLLUTANT, UNIT, AMOUNT, SOURCE, - AQI_COMPARISON, TEMPORARILY_UNAVAILABLE, - } = getKeywords(appleApiVersion); - - const settingsToAqiStandard = { WAQI_InstantCast: WAQI_INSTANT_CAST }; - const scaleToAqiStandard = { - [EPA_454.APPLE_SCALE]: EPA_454, [HJ_633.APPLE_SCALE]: HJ_633, - }; - const supportedApis = ['www.weatherol.cn', 'api.caiyunapp.com', 'api.waqi.info']; - - /** - * Get data requirements from Apple Weather - * @param {supportedAppleApis} apiVersion - Apple Weather API version - * @param {Object} parsedUrl - URL parsed by {@link URLs} - * @return {string[]} - Data requirements - */ - const getRequireData = (apiVersion, parsedUrl) => { - switch (apiVersion) { - case 1: { - const requirement = parsedUrl.params?.include; - return isNonEmptyString(requirement) ? requirement.split(',') : []; - } - case 2: - case 3: { - const requirement = parsedUrl.params?.dataSets; - return isNonEmptyString(requirement) ? requirement.split(',') : []; - } - default: - return []; - } - }; - - /** - * Get scale of modified air quality before network requesting - * @param {settingsV1} projectSettings - Settings of module - * @param {appleAqiScales} appleScale - Current Apple Weather scale - * @return {string} - Target scale of modified air quality - */ - const getTargetScale = (projectSettings, appleScale) => { - if (projectSettings.aqi.local.switch) { - const scale = settingsToAqiStandard[projectSettings.aqi.local.standard] - ?.APPLE_SCALE; - if (!isNonEmptyString(scale)) { - return ''; - } - - return settingsToAqiStandard[projectSettings.aqi.local.standard].APPLE_SCALE; - } - - if (!projectSettings.aqi.switch) { - return isNonEmptyString(appleScale) ? appleScale : ''; - } - - switch (projectSettings.aqi.source) { - case 'www.weatherol.cn': - return HJ_633.APPLE_SCALE; - case 'api.caiyunapp.com': - return projectSettings.apis.colorfulClouds.forceCnForAqi - ? HJ_633.APPLE_SCALE : EPA_454.APPLE_SCALE; - case 'api.waqi.info': - return WAQI_INSTANT_CAST.APPLE_SCALE; - default: - return ''; - } - }; - - /** - * Missions for APIs from {@link toMissions} - * @typedef {"aqi" | "forCompareAqi" | "nextHour"} missions - */ - /** - * Get missions for creating promises - * @param {string} aqi - AQI source - * @param {string} forCompareAqi - AQI comparison source - * @param {string} nextHour - Next hour source - * @return {{api: string, missions: missions[]}[]} - - * Missions for APIs - */ - const toMissions = (aqi, forCompareAqi, nextHour) => supportedApis - .map((api) => ({ - api, - missions: [ - ...(aqi === api ? ['aqi'] : []), - ...(forCompareAqi === api ? ['forCompareAqi'] : []), - ...(nextHour === api ? ['nextHour'] : []), - ], - })); - - /** - * Get path by missions for ColorfulClouds - * @param {missions[]} missions - Mission list - * @return {"weather" | "realtime" | "minutely" | "hourly" | "daily"} - - * URL Path for ColofulClouds - */ - const missionsToCcPath = (missions) => { - if (!Array.isArray(missions) || missions.length <= 0 || missions.length > 1) { - return 'weather'; - } - - switch (missions[0]) { - case 'aqi': - return 'realtime'; - // We need hourly.skycons to detect rain or snow - case 'nextHour': - case 'aqiForComparison': - default: - return 'weather'; - } - }; - - /** - * Get ColorfulClouds by language - * @param {string} language - Language from Apple Weather - * @return {string} - Name of the ColorfulClouds - */ - const getColorfulCloudsName = (language) => { - // No official name for Japanese - if (isNonEmptyString(language)) { - if (/zh-(Hans|CN)/.test(language)) { - return '彩云天气'; - } - if (/zh-(Hant|HK|TW)/.test(language)) { - return '彩雲天氣'; - } - } - - return 'ColorfulClouds'; - }; - - /** - * Handle data from API to air quality - * @param {supportedAppleApi} apiVersion - Apple Weather API version - * @param {{ - * api: string, missions: missions[], returnedData: Object, types: string[] - * }} promiseData - Data from promises - * @param {string} appleLanguage - Language from Apple Weather - * @return {appleAqiV1|appleAqiV2|appleAqiV3 | {}} - - * Air quality object for Apple Weather - */ - const getAirQuality = (apiVersion, promiseData, appleLanguage) => { - if (!Array.isArray(promiseData?.missions) || !promiseData.missions.includes('aqi')) { - return {}; - } - - switch (promiseData?.api) { - case 'www.weatherol.cn': - return { - [METADATA]: toMetadata(apiVersion, colorfulCloudsToAqiMetadata( - { - forV1: 'https://raw.githubusercontent.com/VirgilClyne/iRingo/main/image/weatherol-logo-colorful.png', - forV2: 'https://raw.githubusercontent.com/VirgilClyne/iRingo/main/image/weatherol-logo.png', - }, - '气象在线', - 'https://www.weatherol.cn/', - promiseData?.returnedData, - )), - ...toAirQuality(apiVersion, colorfulCloudsToAqi( - promiseData?.returnedData, - 'https://www.weatherol.cn/', - '气象在线', - true, - true, - )), - }; - case 'api.caiyunapp.com': - return { - [METADATA]: toMetadata(apiVersion, colorfulCloudsToAqiMetadata( - { - forV1: 'https://raw.githubusercontent.com/VirgilClyne/iRingo/main/image/colorfulclouds-logo-colorful.png', - forV2: 'https://raw.githubusercontent.com/VirgilClyne/iRingo/main/image/colorfulclouds-logo.png', - }, - getColorfulCloudsName(appleLanguage), - 'https://caiyunapp.com/weather/', - promiseData?.returnedData, - )), - ...toAirQuality(apiVersion, colorfulCloudsToAqi( - promiseData?.returnedData, - 'https://caiyunapp.com/weather/', - getColorfulCloudsName(appleLanguage), - settings.apis.colorfulClouds.forceCnForAqi, - settings.apis.colorfulClouds.forceCnForComparison, - )), - }; - case 'api.waqi.info': - if (Array.isArray(promiseData?.types)) { - if (promiseData.types.includes('locationFeed')) { - return { - [METADATA]: toMetadata( - apiVersion, - waqiToAqiMetadata(promiseData?.returnedData), - ), - ...toAirQuality(apiVersion, waqiToAqi(promiseData?.returnedData)), - }; - } - - if (promiseData.types.includes('mapq')) { - const dataInFeed = waqiNearestToFeed('mapq', promiseData?.returnedData).find((data) => data.status === 'ok'); - return dataInFeed ? { - [METADATA]: toMetadata( - apiVersion, - waqiToAqiMetadata(dataInFeed), - ), - ...toAirQuality(apiVersion, waqiToAqi(dataInFeed)), - } : {}; - } - - if (promiseData.types.includes('v1Aqi')) { - const dataInFeed = waqiV1ToFeed(promiseData?.returnedData).find((data) => data.status === 'ok'); - return dataInFeed ? { - [METADATA]: toMetadata( - apiVersion, - waqiToAqiMetadata(dataInFeed), - ), - ...toAirQuality(apiVersion, waqiToAqi(dataInFeed)), - } : {}; - } - } - - return {}; - default: - return {}; - } - }; - - /** - * Handle data from API to air quality - * @param {{ - * api: string, missions: missions[], returnedData: Object, types: string[] - * }} promiseData - Data from promises - * @return {aqiComparison} - AQI comparison for Apple Weather - */ - const getAqiComparison = (promiseData) => { - if (!Array.isArray(promiseData?.missions) || !promiseData.missions.includes('forCompareAqi')) { - return 'unknown'; - } - - switch (promiseData?.api) { - case 'api.caiyunapp.com': - return colorfulCloudsToAqiComparison( - promiseData?.returnedData, - settings.apis.colorfulClouds.forceCnForComparison, - ); - case 'api.waqi.info': { - if (Array.isArray(promiseData?.returnedData?.rxs?.obs)) { - const aqiFeedObs = promiseData?.returnedData.rxs.obs.find((station) => station?.status === 'ok'); - if (aqiFeedObs?.msg) { - return waqiV1AqiToAqiComparison(aqiFeedObs.msg); - } - } - - return 'unknown'; - } - default: - return 'unknown'; - } - }; - - /** - * Handle data from API to air quality - * @param {supportedAppleApi} apiVersion - Apple Weather API version - * @param {{ - * api: string, missions: missions[], returnedData: Object, types: string[] - * }} promiseData - Data from promises - * @param {string} appleLanguage - Language from Apple Weather - * @return {appleNextHourV1|appleNextHourV2|appleNextHourV3 | {}} - - * Air quality object for Apple Weather - */ - const getNextHour = (apiVersion, promiseData, appleLanguage) => { - if (!Array.isArray(promiseData?.missions) || !promiseData.missions.includes('nextHour')) { - return {}; - } - - switch (promiseData?.api) { - case 'www.weatherol.cn': - return { - [METADATA]: toMetadata(apiVersion, colorfulCloudsToNextHourMetadata( - '气象在线', - 'https://www.weatherol.cn/', - promiseData?.returnedData, - )), - ...toNextHour(apiVersion, colorfulCloudsToNextHour('气象在线', promiseData?.returnedData)), - }; - case 'api.caiyunapp.com': - return { - [METADATA]: toMetadata(apiVersion, colorfulCloudsToNextHourMetadata( - getColorfulCloudsName(appleLanguage), - 'https://caiyunapp.com/weather/', - promiseData?.returnedData, - )), - ...toNextHour(apiVersion, colorfulCloudsToNextHour( - getColorfulCloudsName(appleLanguage), - promiseData?.returnedData, - )), - }; - default: - return {}; - } - }; - - const location = { latitude, longitude }; - // eslint-disable-next-line functional/no-conditional-statement - if (settings.log.location) { - logger('debug', `${$.name}:经度:${longitude},纬度:${latitude}`); - } - const requireData = getRequireData(appleApiVersion, url); - - const qweatherNames = ['和风天气', 'QWeather']; - const airQuality = { - ...dataFromApple?.[AIR_QUALITY], - ...(qweatherNames.includes(dataFromApple?.[AIR_QUALITY]?.[METADATA]?.[PROVIDER_NAME]) - && { - [METADATA]: { - ...dataFromApple[AIR_QUALITY][METADATA], - [PROVIDER_NAME]: appendQweatherSourceToProviderName( - dataFromApple[AIR_QUALITY][METADATA][PROVIDER_NAME], - dataFromApple[AIR_QUALITY]?.[SOURCE], - ), - }, - ...(isObject(dataFromApple[AIR_QUALITY]?.[POLLUTANTS]) && { - [POLLUTANTS]: Object.fromEntries( - Object.entries(dataFromApple[AIR_QUALITY][POLLUTANTS]).map(([key, value]) => { - if (key === 'CO') { - const fixedAmount = fixQweatherCo(value?.[UNIT], value?.[AMOUNT]); - return [key, { - ...value, - ...(fixedAmount >= 0 && { [AMOUNT]: fixedAmount }), - }]; - } - return [key, value]; - }), - ), - }), - }), - }; - const nextHour = dataFromApple?.[NEXT_HOUR]; - - const aqiProvider = airQuality?.[METADATA]?.[PROVIDER_NAME]; - const aqiScale = airQuality?.[AQI_SCALE]; - const needAqi = requireData.includes(AIR_QUALITY) && settings.aqi.switch - && (!isNonEmptyString(aqiScale) || (!settings.aqi.local.switch - && settings.aqi.targets.includes( - aqiScale.slice(0, aqiScale.lastIndexOf('.')), - ))); - - const needCompareAqi = requireData.includes(AIR_QUALITY) - && settings.aqi.comparison.switch && AQI_COMPARISON.length > 0 - && (airQuality?.[AQI_COMPARISON] === 'unknown' || needAqi); - const nowHourTimestamp = (new Date()).setMinutes(0, 0, 0); - const yesterdayHourTimestamp = nowHourTimestamp - 1000 * 60 * 60 * 24; - const yesterdayReportTimestamp = needAqi ? yesterdayHourTimestamp - : appleTimeToTimestamp( - appleApiVersion, - airQuality?.[METADATA]?.[REPORTED_TIME], - nowHourTimestamp, - ) - 1000 * 60 * 60 * 24; - const cachedAqi = needCompareAqi ? getCachedAqi( - toCaches(getENV('iRingo', 'Weather', database)).aqis, - yesterdayReportTimestamp, - location, - qweatherNames.includes(aqiProvider) ? airQuality?.source : null, - getTargetScale(settings, aqiScale), - ) : { aqi: -1 }; - - const nextHourProvider = nextHour?.[METADATA]?.[PROVIDER_NAME]; - const needNextHour = requireData.includes(REQUIRE_NEXT_HOUR) - && settings.nextHour.switch && !isNonEmptyString(nextHourProvider); - - const missionList = toMissions( - needAqi ? settings.aqi.source : null, - needCompareAqi && cachedAqi.aqi < 0 ? settings.aqi.comparison.source : null, - needNextHour ? settings.nextHour.source : null, - ); - logger('info', `${$.name}:任务列表:${JSON.stringify(missionList)}`); - - const promises = Array.isArray(missionList) ? missionList - .filter((missionObject) => ( - supportedApis.includes(missionObject?.api) && Array.isArray(missionObject?.missions) - )) - .flatMap(({ api, missions }) => { - if (missions.length <= 0) { - return []; - } - - switch (api) { - case 'www.weatherol.cn': - return missions.flatMap((mission) => { - switch (mission) { - case 'aqi': - return [ - weatherOl('realtime', location, settings.apis.weatherOl.httpHeaders) - .then((returnedData) => ({ - missions: [mission], - api, - types: ['realtime'], - returnedData, - })), - ]; - case 'nextHour': - return [ - weatherOl('forecast', location, settings.apis.weatherOl.httpHeaders) - .then((returnedData) => ({ - missions: [mission], - api, - types: ['forecast'], - returnedData, - })), - ]; - default: - return []; - } - }); - case 'api.caiyunapp.com': { - const path = missionsToCcPath(missions); - const needHistory = missions.includes('forCompareAqi'); - - return [colorfulClouds( - settings.apis.colorfulClouds.token, - location, - languageWithRegion, - settings.apis.colorfulClouds.httpHeaders, - path, - { - unit: 'metric:v2', - ...(needHistory && { begin: yesterdayHourTimestamp / 1000 }), - }, - ).then((returnedData) => ({ - missions, - api, - types: [path, ...(needHistory ? ['history'] : [])], - returnedData, - }))]; - } - case 'api.waqi.info': { - const getHistoryAqis = (stationId, fallbackData) => { - const getAqiData = (id, token) => waqiV1('aqi', id, `token=${token}&id=${id}`) - .then((returnedData) => ({ - missions: ['aqi', 'forCompareAqi'], - api, - types: ['v1Aqi'], - returnedData, - })); - const cachedToken = getCachedWaqiToken( - toCaches(getENV('iRingo', 'Weather', database)).waqi.tokens, - stationId, - ); - - return isNonEmptyString(cachedToken) - ? getAqiData(stationId, cachedToken) - : waqiToken(stationId).then((tokenData) => { - if (tokenData.status === 'ok') { - const newCaches = cacheWaqiToken( - toCaches(getENV('iRingo', 'Weather', database)), - stationId, - tokenData.data, - ); - // eslint-disable-next-line - $.setjson( - newCaches, - '@iRingo.Weather.Caches', - ); - return getAqiData(stationId, tokenData.data); - } - - logger('error', `${$.name}:无法获取WAQI token`); - return Promise.resolve({ - missions: ['aqi'], - api, - types: ['mapq'], - returnedData: fallbackData, - }); - }); - }; - - if (missions.includes('aqi') || missions.includes('forCompareAqi')) { - const tokenFromUser = settings.apis.waqi.token; - - if (isNonEmptyString(tokenFromUser)) { - return [ - waqiV2(location, null, tokenFromUser, settings.apis.waqi.httpHeaders) - .then((v2Data) => { - if (missions.includes('forCompareAqi')) { - if (isNonNanNumber(v2Data?.data?.idx)) { - return getHistoryAqis(v2Data.data.idx, v2Data); - } - - logger('error', `${$.name}:无法获取WAQI监测站ID`); - } - - return { - missions: ['aqi'], - api, - types: ['locationFeed'], - returnedData: v2Data, - }; - }), - ]; - } - - return [ - waqiNearest(location, 'mapq', settings.apis.waqi.httpHeaders) - .then((nearestData) => { - if (missions.includes('forCompareAqi')) { - if (Array.isArray(nearestData?.d)) { - const station = nearestData.d.find((s) => ( - isNonNanNumber(parseInt(s?.x, 10)) - )); - const stationId = parseInt(station?.x, 10); - - if (isNonNanNumber(stationId)) { - return getHistoryAqis(stationId, nearestData); - } - - logger('error', `${$.name}:无法获取WAQI监测站ID`); - // eslint-disable-next-line functional/no-conditional-statement - } else { - logger('error', `${$.name}:无法获取WAQI监测站列表`); - } - } - - return { - missions: ['aqi'], - api, - types: ['mapq'], - returnedData: nearestData, - }; - }), - ]; - } - - return []; - } - default: - return []; - } - }) : []; - - // eslint-disable-next-line functional/no-expression-statement - Promise.all(promises).then((dataArray) => { - if (!Array.isArray(dataArray)) { - return dataFromApple; - } - - const dataForAqi = dataArray.find((data) => ( - Array.isArray(data?.missions) && data.missions.includes('aqi') - )); - const dataForAqiComparison = dataArray.find((data) => ( - Array.isArray(data?.missions) && data.missions.includes('forCompareAqi') - )); - const dataForNextHour = dataArray.find((data) => ( - Array.isArray(data?.missions) && data.missions.includes('nextHour') - )); - - const modifiedAirQuality = getAirQuality( - appleApiVersion, - dataForAqi, - languageWithRegion, - ); - if ( - dataForAqi && Object.keys(modifiedAirQuality) - .filter((key) => key !== METADATA && key !== AQI_COMPARISON).length <= 0 - // eslint-disable-next-line functional/no-conditional-statement - ) { - logger('error', `${$.name}:无法处理${dataForAqi?.api}的空气质量数据`); - logger('debug', `API返回数据:${JSON.stringify(dataForAqi)}`); - } - const mergedAirQuality = { - ...airQuality, - ...modifiedAirQuality, - [METADATA]: { ...airQuality?.[METADATA], ...modifiedAirQuality?.[METADATA] }, - }; - const mergedScale = mergedAirQuality?.[AQI_SCALE]; - const pollutants = isObject(mergedAirQuality?.[POLLUTANTS]) - ? Object.values(mergedAirQuality[POLLUTANTS]) : []; - const localConvertedAirQuality = { - ...mergedAirQuality, - ...(settings.aqi.local.switch && pollutants.length > 0 - && settings.aqi.targets.includes( - mergedScale.slice(0, mergedScale.lastIndexOf('.')), - ) - // Little trick for logging - && !logger( - 'info', - `${$.name}:已转换AQI,原始标准:${mergedAirQuality[AQI_SCALE]},` - + `原始AQI:${mergedAirQuality[AQI_INDEX]}`, - ) - && toAirQuality(appleApiVersion, appleToEpaAirQuality( - settingsToAqiStandard[settings.aqi.local.standard], - appleApiVersion === 1 ? convertV1Pollutants(pollutants) : pollutants, - ))), - }; - const aqiLevels = scaleToAqiStandard[localConvertedAirQuality?.[AQI_SCALE]] - ?.AQI_LEVELS; - const modifiedCompareAqi = localConvertedAirQuality?.[AQI_INDEX] >= 0 - && cachedAqi.aqi >= 0 && isObject(aqiLevels) ? compareAqi( - toAqiLevel(Object.values(aqiLevels), localConvertedAirQuality[AQI_INDEX]), - toAqiLevel(Object.values(aqiLevels), cachedAqi.aqi), - ) - : getAqiComparison(dataForAqiComparison); - if ( - dataForAqiComparison && modifiedCompareAqi === 'unknown' - // eslint-disable-next-line functional/no-conditional-statement - ) { - logger('error', `${$.name}:无法处理${dataForAqiComparison?.api}的对比昨日空气质量数据`); - logger('debug', `API返回数据:${JSON.stringify(dataForAqiComparison)}`); - } - const modifiedNextHour = getNextHour( - appleApiVersion, - dataForNextHour, - languageWithRegion, - ); - if ( - dataForNextHour && Object.keys(modifiedNextHour) - .filter((key) => key !== METADATA).length <= 0 - // eslint-disable-next-line functional/no-conditional-statement - ) { - logger('error', `${$.name}:无法处理${dataForNextHour?.api}的下小时降水强度数据`); - logger('debug', `API返回数据:${JSON.stringify(dataForNextHour)}`); - } - - const primaryPollutant = localConvertedAirQuality?.[PRIMARY_POLLUTANT]; - - return { - ...dataFromApple, - ...(requireData.includes(AIR_QUALITY) && { - [AIR_QUALITY]: { - ...localConvertedAirQuality, - ...(needCompareAqi && { [AQI_COMPARISON]: modifiedCompareAqi }), - ...( - isNonEmptyString(primaryPollutant) && ( - !isObject(localConvertedAirQuality?.[POLLUTANTS]) - || !Object.keys(localConvertedAirQuality[POLLUTANTS]) - .some((key) => key === primaryPollutant)) - && { - [POLLUTANTS]: { - ...localConvertedAirQuality?.[POLLUTANTS], - [primaryPollutant]: toPollutant(appleApiVersion, primaryPollutant, -1, 'microgramsPerM3'), - }, - } - ), - }, - }), - ...(requireData.includes(REQUIRE_NEXT_HOUR) && { - [NEXT_HOUR]: { - ...nextHour, - ...modifiedNextHour, - [METADATA]: { ...nextHour?.[METADATA], ...modifiedNextHour?.[METADATA] }, - }, - }), - }; - }).then((responseBody) => { - const time = responseBody?.[AIR_QUALITY]?.[METADATA]?.[REPORTED_TIME]; - const timestamp = appleTimeToTimestamp(appleApiVersion, time, nowHourTimestamp); - - const airQualityProvider = responseBody?.[AIR_QUALITY]?.[METADATA]?.[PROVIDER_NAME]; - const airQualityScale = responseBody?.[AIR_QUALITY]?.[AQI_SCALE]; - - // eslint-disable-next-line functional/no-conditional-statement - if (isNonEmptyString(airQualityScale)) { - // eslint-disable-next-line functional/no-expression-statement - $.setjson(cacheAqi( - toCaches(getENV('iRingo', 'Weather', database)), - timestamp, - location, - qweatherNames.includes(airQualityProvider) - ? responseBody?.[AIR_QUALITY]?.[SOURCE] : null, - airQualityScale.slice(0, airQualityScale.indexOf('.')), - responseBody?.[AIR_QUALITY]?.[AQI_INDEX], - ), '@iRingo.Weather.Caches'); - } - - const nextHourProviderName = responseBody?.[NEXT_HOUR]?.[METADATA]?.[PROVIDER_NAME]; - - return { - ...responseBody, - ...(isNonEmptyString(airQualityProvider) && { - [AIR_QUALITY]: { - ...responseBody[AIR_QUALITY], - [METADATA]: { - ...responseBody[AIR_QUALITY][METADATA], - ...( - Object.keys(responseBody[AIR_QUALITY]) - .filter((key) => key !== METADATA).length <= 1 - && { [TEMPORARILY_UNAVAILABLE]: true } - ), - }, - }, - }), - ...(isNonEmptyString(nextHourProviderName) && { - [NEXT_HOUR]: { - ...responseBody[NEXT_HOUR], - [METADATA]: { - ...responseBody[NEXT_HOUR][METADATA], - ...( - Object.keys(responseBody[NEXT_HOUR]) - .filter((key) => key !== METADATA && key !== AQI_COMPARISON).length <= 0 - && { [TEMPORARILY_UNAVAILABLE]: true } - ), - }, - }, - }), - }; - }).then((responseBody) => { - // eslint-disable-next-line functional/no-conditional-statement - if (responseBody?.[AIR_QUALITY]?.[METADATA]?.[TEMPORARILY_UNAVAILABLE]) { - logger( - 'error', - `${$.name}:检测到未能成功获取空气质量数据,` - + `数据源:${responseBody?.[AIR_QUALITY]?.[METADATA]?.[PROVIDER_NAME]}` - + `${settings.log.location ? `,经度:${longitude},纬度:${latitude}` : ''}`, - ); - } - // eslint-disable-next-line functional/no-conditional-statement - if (responseBody?.[NEXT_HOUR]?.[METADATA]?.[TEMPORARILY_UNAVAILABLE]) { - logger( - 'error', - `${$.name}:检测到未能成功获取下小时降水数据,` - + `数据源:${responseBody?.[NEXT_HOUR]?.[METADATA]?.[PROVIDER_NAME]}` - + `${settings.log.location ? `,经度:${longitude},纬度:${latitude}` : ''}`, - ); - } - - // eslint-disable-next-line functional/no-expression-statement,no-undef - setResponse({ ...$response, body: JSON.stringify(responseBody) }); - }); - // eslint-disable-next-line functional/no-conditional-statement - } else { - // eslint-disable-next-line functional/no-expression-statement - logger('error', `${$.name}:缺失经纬度信息。经度:${parameters?.lng},纬度:${parameters?.lat}`); - // eslint-disable-next-line functional/no-expression-statement - logger('debug', `${$.name}:URL参数:${JSON.stringify(parameters)}`); - // eslint-disable-next-line functional/no-expression-statement,no-undef - setResponse($response); - } - // eslint-disable-next-line functional/no-conditional-statement - } else { - // eslint-disable-next-line functional/no-expression-statement,no-undef - logger('error', `${$.name}:数据解析失败,HTTP body = ${$response?.body}`); - // eslint-disable-next-line functional/no-expression-statement,no-undef - logger('debug', `${$.name}:响应信息:${JSON.stringify($response)}`); - // eslint-disable-next-line functional/no-expression-statement,no-undef - setResponse($response); - } - } - // eslint-disable-next-line functional/no-conditional-statement - } else { - // eslint-disable-next-line functional/no-expression-statement,no-undef - logger('error', `${$.name}:无法解析URL,url = ${url}, $response.url = ${$request?.url}`); - // eslint-disable-next-line functional/no-expression-statement,no-undef - logger('debug', `${$.name}:请求信息:${JSON.stringify($request)}`); - // eslint-disable-next-line functional/no-expression-statement,no-undef - setResponse($response); - } - // eslint-disable-next-line functional/no-conditional-statement - } else { - // eslint-disable-next-line functional/no-expression-statement,no-undef - logger('error', `${$.name}:无法获取URL信息,$response.url = ${$request?.url}`); - // eslint-disable-next-line functional/no-expression-statement,no-undef - logger('debug', `${$.name}:请求信息:${JSON.stringify($request)}`); - // eslint-disable-next-line functional/no-expression-statement,no-undef - setResponse($response); - } -// eslint-disable-next-line functional/no-conditional-statement -} else { - // eslint-disable-next-line functional/no-expression-statement - logger('warn', `${$.name}:Box.js设置内或参数内已禁用模块`); - // eslint-disable-next-line functional/no-expression-statement,no-undef - setResponse($response); -} - -/***************** Env *****************/ -// prettier-ignore -// noinspection -// eslint-disable-next-line -// https://github.com/chavyleung/scripts/blob/master/Env.min.js -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}isStash(){return"undefined"!=typeof $environment&&$environment["stash-version"]}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,i=t["update-pasteboard"]||t.updatePasteboard;return{"open-url":e,"media-url":s,"update-pasteboard":i}}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)} - -// noinspection -// eslint-disable-next-line -// https://github.com/VirgilClyne/VirgilClyne/blob/main/function/URL/URLs.embedded.min.js -function URLs(s){return new class{constructor(s=[]){this.name="URL v1.0.0",this.opts=s,this.json={url:{scheme:"",host:"",path:""},params:{}}}parse(s){let t=s.match(/(?.+):\/\/(?[^/]+)\/?(?[^?]+)?\??(?.*)?/)?.groups??null;return t?.params&&(t.params=Object.fromEntries(t.params.split("&").map((s=>s.split("="))))),t}stringify(s=this.json){return s?.params?s.scheme+"://"+s.host+"/"+s.path+"?"+Object.entries(s.params).map((s=>s.join("="))).join("&"):s.scheme+"://"+s.host+"/"+s.path}}(s)} diff --git a/js/archive/Weather.response.js b/js/archive/Weather.response.js deleted file mode 100644 index 6e8b65ab6..000000000 --- a/js/archive/Weather.response.js +++ /dev/null @@ -1,6005 +0,0 @@ -// README: https://github.com/VirgilClyne/iRingo - -const $ = new Env('🌤 Weather v4.0.0-response'); - -/** - * Get Environment Variables - * @author VirgilClyne - * @param {String} t - Persistent Store Key - * @param {String} e - Platform Name - * @param {Object} n - Default DataBase - * @return {{Settings: Object, Caches: Object, Configs: Object}} - */ -// noinspection -// eslint-disable-next-line -function getENV(t, e, n) { const i = $.getjson(t, n); const s = i?.[e]?.Settings || n?.[e]?.Settings || n?.Default?.Settings; const g = i?.[e]?.Configs || n?.[e]?.Configs || n?.Default?.Configs; let f = i?.[e]?.Caches || void 0; if (typeof f === 'string' && (f = JSON.parse(f)), typeof $argument !== 'undefined') { if ($argument) { const t = Object.fromEntries($argument.split('&').map(((t) => t.split('=')))); const e = {}; for (const a in t)o(e, a, t[a]); Object.assign(s, e); } function o(t, e, n) { e.split('.').reduce(((t, i, s) => t[i] = e.split('.').length === ++s ? n : t[i] || {}), t); } } return { Settings: s, Caches: f, Configs: g }; } - -const database = { - Location: { - Settings: { - Switch: true, - CountryCode: 'US', - Config: { - GEOAddressCorrection: true, LookupMaxParametersCount: true, LocalitiesAndLandmarks: true, PedestrianAR: true, '6694982d2b14e95815e44e970235e230': true, OpticalHeading: true, UseCLPedestrianMapMatchedLocations: true, - }, - }, - }, - Weather: { - Settings: { - Switch: true, - NextHour: { Switch: true, Source: 'www.weatherol.cn' }, - AQI: { - Switch: true, Targets: ['HJ6332012'], Local: { Switch: true, Standard: 'WAQI_InstantCast' }, Source: 'www.weatherol.cn', Comparison: { Switch: true, Source: 'Local' }, - }, - Map: { AQI: false }, - APIs: { - WeatherOL: { HTTPHeaders: { 'Content-Type': 'application/json' } }, - ColorfulClouds: { - HTTPHeaders: { 'Content-Type': 'application/json' }, - Token: '', - ForceCNForAQI: true, - ForceCNForComparison: false, - }, - WAQI: { HTTPHeaders: { 'Content-Type': 'application/json' }, Token: '' }, - }, - Log: { - Level: 'info', - Location: false, - }, - }, - Configs: { - Pollutants: { - co: 'CO', no: 'NO', no2: 'NO2', so2: 'SO2', o3: 'OZONE', nox: 'NOX', pm25: 'PM2.5', pm10: 'PM10', other: 'OTHER', - }, - }, - Cache: { - aqis: {}, - }, - }, - Siri: { - Settings: { - Switch: true, CountryCode: 'SG', Domains: ['web', 'itunes', 'app_store', 'movies', 'restaurants', 'maps'], Functions: ['flightutilities', 'lookup', 'mail', 'messages', 'news', 'safari', 'siri', 'spotlight', 'visualintelligence'], Safari_Smart_History: true, - }, - }, -}; - -/** @typedef {1 | 2 | 3} supportedAppleApi */ -/** @typedef {{latitude: number, longitude: number}} coordinate */ -/** @typedef {{forV2: string, forV1: string}} providerLogo */ - -/** - * Scale names in 2207 version - * @typedef { - * "CA.AQHI" | "FR.ATMO" | "UBA" | "NAQI" | "EU.EAQI" | "ICARS" | "NL.LKI" | "SG.NEA" | "KR.CAI" - * | "DAQI" | "EPA_NowCast" | "HJ6332012" - * } scaleNames2207 - */ -/** @typedef {"ES.MITECO"} scaleName2208 */ -/** @typedef {scaleNames2207|scaleName2208} scaleNames */ -/** @typedef {`${scaleNames2207}.2207` | `${scaleName2208}.2208`} appleAqiScales */ - -/** - * AQI info in cache - * @typedef {Object} cachedAqi - * - * @property {coordinate} location - Coordinate of AQI - * @property {string} [stationName] - `AirQuality.source` from QWeather - * @property {scaleNames} scaleName - Part before the '.' in iOS `AirQuality.scale` - * @property {number} aqi - Air quality index - */ - -/** - * Supported VOCs by {@link toEpaAqis} - * @typedef {"NO2" | "NO" | "SO2" | "OZONE" | "CO"} supportedEpaVocs - */ -/** - * Supported pollutants by EPA - * @typedef {supportedEpaVocs | "NOX" | "PM2.5" | "PM10"} supportedEpaPollutantNames - */ -/** - * Pollutant names used in Apple weather. - * `C6H6` is in Germany, India, Italia and Spain, `NH3` is in India, `NMHC` in Japan. - * @typedef { - * supportedEpaPollutantNames | "C6H6" | "NH3" | "NMHC" - * } applePollutantNames - */ - -/** @typedef {"ppb" | "µg/m3"} pollutantUnitsV1 */ -/** @typedef {"ppb" | "microgramsPerM3"} pollutantUnitsV2 */ -/** @typedef {pollutantUnitsV1 | "ppm" | "mg/m3"} pollutantUnitsSlash */ -/** @typedef {pollutantUnitsV2 | "ppm" | "milligramsPerM3"} pollutantUnitsText */ - -/** - * Base pollutant type for `AirQuality.pollutants` - * @typedef {Object} pollutantBase - * - * @property {string} name - Name of pollutant - * @property {number} amount - Amount of pollutant - */ -/** - * Pollutant for `air_quality.pollutants` in Apple APIv1 - * @typedef {pollutantBase} pollutantV1 - * - * @property {pollutantUnitsV1} unit - Unit of pollutant - */ -/** - * Pollutant for `airQuality.pollutants` in Apple APIv2 and APIv3 - * @typedef {pollutantBase} pollutantV2 - * - * @property {pollutantUnitsV2} unit - Unit of pollutant - */ - -/** - * AQI info generated by {@link toEpaAqis} - * @typedef {Object} aqiInfo - * - * @property {number} index - Air quality index - * @property {{name: string, aqi: number}[] | []} pollutants - Aqi of each pollutant - * @property {supportedEpaPollutantNames} [primary] - Primary pollutant - */ - -/** @typedef {"unknown" | "worse" | "same" | "better"} aqiComparison */ -/** @typedef {"station" | "modeled"} sourceType */ -/** @typedef {0 | 1} dataSource */ - -/** - * Object for {@link toMetadata} - * @typedef {Object} metadataObject - * - * @property {string} [language] - Language in ISO 639 style (example: en-US) - * @property {coordinate} [location] - Coordinate of location of data - * @property {number} [expireTimestamp] - UNIX timestamp of expire time - * @property {providerLogo} [providerLogo] - URL to the provider logo - * @property {string} [providerName] - Name of the provider - * @property {number} [readTimestamp] - UNIX timestamp of read time - * @property {number} [reportedTimestamp] - UNIX timestamp of reported time - * @property {dataSource} [dataSource] - Type of data source - * @property {string} [unit] - Unit of the data - * @property {string} [url] - URL to the data source - */ - -/** - * Metadata of next hour object for Apple Weather APIv1 - * @typedef {Object} appleMetadataBaseV1 - * - * @property {string} [read_time] - Time in seconds - * @property {string} [expire_time] - Time in seconds - * @property {1} version - 1 in APIv1 - * @property {number} [latitude] - * @property {number} [longitude] - * @property {string} [language] - Language in ISO 639 style (example: en-US) - * @property {string} [provider_name] - */ -/** - * Metadata of next hour object for Apple Weather APIv2 - * @typedef {Object} appleMetadataBaseV2 - * - * @property {string} [expireTime] - Time in "YYYY-MM-DDTHH:MM:SSZ" format - * @property {string} [language] - Language in ISO 639 style (example: en-US) - * @property {number} [latitude] - * @property {number} [longitude] - * @property {string} [providerName] - * @property {string} [readTime] - Time in "YYYY-MM-DDTHH:MM:SSZ" format - * @property {2} version - 2 in APIv2 - */ -/** - * Metadata of next hour object for Apple Weather APIv3 - * @typedef {Object} appleMetadataBaseV3 - * - * @property {string} [attributionURL] - * @property {string} [expireTime] - Time in "YYYY-MM-DDTHH:MM:SSZ" format - * @property {number} [latitude] - * @property {number} [longitude] - * @property {string} [readTime] - Time in "YYYY-MM-DDTHH:MM:SSZ" format - * @property {1} version - 1 in APIv3 - */ -/** - * Metadata of air quality for Apple Weather APIv1 - * @typedef {appleMetadataBaseV1} appleAirQualityMetadataV1 - * - * @property {string} [reported_time] - Time in seconds - * @property {string} [provider_logo] - URL to the provider logo - * @property {0 | 1} [data_source] - Type of data source. 0 means station. 1 means modeled. - */ -/** - * Metadata of next hour for Apple Weather APIv1 - * @typedef {appleMetadataBaseV1} appleNextHourMetadataV1 - * - * @property {0 | 1} [data_source] - Type of data source. 0 means station. 1 means modeled. - */ -/** - * Metadata of air quality for Apple Weather APIv2 - * @typedef {appleMetadataBaseV2} appleAirQualityMetadataV2 - * - * @property {string} [providerLogo] - URL to the provider logo - * @property {string} [reportedTime] - Time in "YYYY-MM-DDTHH:MM:SSZ" format - * @property {"m"} [units] - Units of data - */ -/** - * Metadata of next hour for Apple Weather APIv2 - * @typedef {appleMetadataBaseV2} appleNextHourMetadataV2 - * - * @property {"m"} [units] - Units of data - */ -/** - * Metadata of air quality for Apple Weather APIv3 - * @typedef {appleMetadataBaseV3} appleAirQualityMetadataV3 - * - * @property {string} [language] - Language in ISO 639 style (example: en-US) - * @property {string} [providerLogo] - URL to the provider logo - * @property {string} [providerName] - * @property {string} [reportedTime] - Time in "YYYY-MM-DDTHH:MM:SSZ" format - * @property {"m"} [units] - Units of data - */ -/** - * Metadata of next hour for Apple Weather APIv3 - * @typedef {appleMetadataBaseV3} appleNextHourMetadataV3 - * - * @property {string} [language] - Language in ISO 639 style (example: en-US) - * @property {string} [providerName] - * @property {"m"} [units] - Units of data - */ - -/** - * Object for {@link toAirQuality} - * @typedef {Object} airQualityObject - * - * @property {pollutantV2[]} [pollutants] - Array of pollutant - * @property {boolean} [isSignificant] - Importance of AQI info, set true will pin AQI info to top. - * AQI info of China will always been pinned after APIv2 - * @property {string} [url] - URL to the website of AQI details - * @property {applePollutantNames} [primary] - Name of the primary pollutant - * @property {string} [sourceName] - Name of the source - * @property {number} [categoryIndex] - AQI level starts from 1 - * @property {number} [aqi] - Air quality index - * @property {appleAqiScales} [scale] - `name.version` of the AQI scale - * @property {aqiComparison} [previousDayComparison] - (APIv2+) Comparison to the previous day - * @property {sourceType} [sourceType] - (APIv2+) Type of the info - */ - -/** - * Air quality object for Apple Weather APIv1 - * @typedef {Object} appleAqiWithoutMetadataV1 - * - * @property {"AirQuality"} name - * @property {string} [source] - Station name if provider is QWeather - * @property {string} [learnMoreURL] - * @property {number} [airQualityIndex] - * @property {number} [airQualityCategoryIndex] - * @property {appleAqiScales} [airQualityScale] - * @property {applePollutantNames} [primaryPollutant] - * @property {boolean} [isSignificant] - * @property {Object.} [pollutants] - */ -/** - * Air quality object for Apple Weather APIv3 - * @typedef {Object} appleAqiWithoutMetadataV3 - * - * @property {"AirQuality"} [name] - * @property {number} [categoryIndex] - * @property {number} [index] - * @property {boolean} [isSignificant] - * @property {string} [learnMoreURL] - * @property {Object.} [pollutants] - * @property {aqiComparison} [previousDayComparison] - * @property {applePollutantNames} [primaryPollutant] - * @property {appleAqiScales} [scale] - * @property {sourceType} [sourceType] - */ -/** - * Air quality object for Apple Weather APIv2 - * @typedef {appleAqiWithoutMetadataV3} appleAqiWithoutMetadataV2 - * - * @property {string} [source] - Station name if provider is QWeather - */ -/** - * Air quality object for Apple Weather APIv1 - * @typedef {appleAqiWithoutMetadataV1} appleAqiV1 - * - * @property {appleAirQualityMetadataV1} metadata - Metadata of air quality - */ -/** - * Air quality object for Apple Weather APIv2 - * @typedef {appleAqiWithoutMetadataV2} appleAqiV2 - * - * @property {appleAirQualityMetadataV2} metadata - Metadata of air quality - */ -/** - * Air quality object for Apple Weather APIv3 - * @typedef {appleAqiWithoutMetadataV3} appleAqiV3 - * - * @property {appleAirQualityMetadataV3} metadata - Metadata of air quality - */ - -/** - * Precipitation types for `nextHour.summary[].condition`. - * [PrecipitationType | Apple Developer Documentation]{@link https://developer.apple.com/documentation/weatherkitrestapi/precipitationtype} - * @typedef { - * "clear" | "precipitation" | "rain" | "snow" | "sleet" | "hail" | "mixed" - * } precipitationTypes - */ -/** - * Weather statuses for `nextHour.condition[].token` - * @typedef { - * "clear" | "precipitation" | "drizzle" | "flurries" | "rain" | "snow" | "heavy-rain" - * | "heavy-snow" | "sleet" | "hail" | "mixed" - * } weatherStatuses - */ - -/** - * Minute precipitation data - * @typedef {Object} minute - * - * @property {weatherStatuses} weatherStatus - Weather status of this minute - * @property {number} precipitation - Precipitation of this minute - * @property {number} precipitationIntensityPerceived - Precipitation from 0 to 3, - * can be generated by {@link toPerceived} - * @property {number} chance - Integer from 0 to 100, percentage of chance to rain or snow - * @property {string} shortDescription - Description that will be display in city list - * @property {string} longDescription - Description with `firstAt`, `secondAt`, etc... - * to present `X minutes later` dynamically base on current time. - * @property {Object. | {}} parameters - Object that works with `longDescription`. - * `firstAt`, `secondAt`, etc... as keys, minutes after startTime as values. - */ -/** - * Object for {@link toNextHour} - * @typedef {Object} nextHourObject - * - * @property {minute[]} minutes - Array of minute data - * @property {number} startTimestamp - Timestamp of precipitation started - */ - -/** - * Base minute type for `NextHourForecast.minutes` - * @typedef {Object} nextHourMinuteBase - * - * @property {number} precipChance - Integer from 0 to 100, percentage of chance - * @property {number} precipIntensity - 2 decimal places from Apple. Actually could be any number. - */ -/** - * Minute for `next_hour.minutes` in Apple APIv1 - * @typedef {nextHourMinuteBase} nextHourMinuteV1 - * - * @property {number} startAt - Time of the start time (in seconds) - * @property {number} perceivedIntensity - 3 decimal places from 0 to 3, - * can be generated by `toApplePrecipitation()` - */ -/** - * Minute for `forecastNextHour.minutes` in Apple APIv2 - * @typedef {nextHourMinuteBase} nextHourMinuteV2 - * - * @property {string} startTime - Time of the start time (in YYYY-MM-DDTHH:MM:SSZ format) - * @property {number} precipIntensityPerceived - 3 decimal places from 0 to 3, - * can be generated by `toApplePrecipitation()` - */ -/** - * Minute for `forecastNextHour.minutes` in Apple APIv3 - * @typedef {Object} nextHourMinuteV3 - * - * @property {string} startTime - Time of the start time (in YYYY-MM-DDTHH:MM:SSZ format) - * @property {number} precipitationChance - Number from 0 to 1 with 2 decimal places, - * percentage of chance - * @property {number} precipitationIntensity - 2 decimal places from Apple. - * Actually could be any number. - * @property {number} precipitationIntensityPerceived - 3 decimal places from 0 to 3, - * can be generated by `toApplePrecipitation()` - */ - -/** - * Base condition type for `NextHourForecast` - * @typedef {Object} nextHourConditionBase - * - * @property {string} token - A tag to describe possibility, weather status and time status - * @property {string} longTemplate - A description for current weather. Time is dynamic by - * replacing to `{firstAt}`, `{secondAt}`, etc for `parameters` - * @property {string} shortTemplate - As same as `longTemplate`, but no dynamic time support - * and will be display in city list - */ -/** - * Condition for `next_hour.condition` in Apple APIv1 - * @typedef {nextHourConditionBase} nextHourConditionV1 - * - * @property {number} [validUntil] - Time of the end time (in seconds) - * @property {Object.} parameters - Key should be `firstAt`, `secondAt`, etc. - * Value is time in seconds - */ -/** - * Condition for `forecastNextHour.condition` in Apple APIv2 - * @typedef {nextHourConditionBase} nextHourConditionV2 - * - * @property {string} startTime - Time of the start time (in YYYY-MM-DDTHH:MM:SSZ format) - * @property {string} [endTime] - Time of the end time (in YYYY-MM-DDTHH:MM:SSZ format) - * @property {Object.} parameters - Key should be `firstAt`, `secondAt`, etc. - * Value is time in YYYY-MM-DDTHH:MM:SSZ format - */ -/** - * Condition for `forecastNextHour.condition` in Apple APIv3 - * @typedef {Object} nextHourConditionV3 - * - * @property {string} startTime - Time of the start time (in YYYY-MM-DDTHH:MM:SSZ format) - * @property {string} [endTime] - Time of the end time (in YYYY-MM-DDTHH:MM:SSZ format) - * @property {string} token - A tag to describe possibility, weather status and time status - * @property {Object.} parameters - Key should be `firstAt`, `secondAt`, etc. - * Value is time in YYYY-MM-DDTHH:MM:SSZ format. Effective to the description in Apple Weather. - */ - -/** - * Base summary type for `NextHourForecast` - * @typedef {Object} nextHourSummaryBase - * - * @property {precipitationTypes} condition - Weather condition - */ -/** - * Condition for `next_hour.summary` in Apple APIv1 - * @typedef {nextHourSummaryBase} nextHourSummaryV1 - * - * @property {number} [validUntil] - Time of the end time (in seconds) - * @property {number} [probability] - Precipitation chance of this minute if condition is not clear - * @property {number} [maxIntensity] - Maximum value of {@link nextHourMinuteV1#perceivedIntensity} - * in this range of minutes if condition is not clear - * @property {number} [minIntensity] - Minimum value of {@link nextHourMinuteV1#perceivedIntensity} - * in this range of minutes if condition is not clear - */ -/** - * Condition for `forecastNextHour.summary` in Apple APIv2 - * @typedef {nextHourSummaryBase} nextHourSummaryV2 - * - * @property {string} startTime - Time of the start time (in YYYY-MM-DDTHH:MM:SSZ format) - * @property {string} [endTime] - Time of the end time (in YYYY-MM-DDTHH:MM:SSZ format) - * @property {number} [precipChance] - Precipitation chance of this minute if condition is not clear - * @property {number} [precipIntensity] - Maximum value of `precipIntensityPerceived` in - * {@link nextHourMinuteV2} in this range of minutes if condition is not clear - */ -/** - * Condition for `forecastNextHour.summary` in Apple APIv3 - * @typedef {nextHourSummaryBase} nextHourSummaryV3 - * - * @property {string} startTime - Time of the start time (in YYYY-MM-DDTHH:MM:SSZ format) - * @property {string} [endTime] - Time of the end time (in YYYY-MM-DDTHH:MM:SSZ format) - * @property {number} precipitationChance - Maximum precipitation chance of this summary - * @property {number} precipitationIntensity - Maximum value of `precipitationIntensityPerceived` in - * {@link nextHourMinuteV3} in this range of minutes if condition is not clear - */ - -/** - * Next hour object for Apple Weather APIv1 - * @typedef {Object} appleNextHourWithoutMetadataV1 - * - * @property {"NextHourForecast"} name - * @property {nextHourConditionV1[]} [condition] - * @property {nextHourSummaryV1[]} [summary] - * @property {number} [startTime] - Time in seconds - * @property {nextHourMinuteV1[]} [minutes] - */ -/** - * Base next hour object for Apple Weather APIv2 - * @typedef {Object} appleNextHourWithoutMetadataV2 - * - * @property {"NextHourForecast"} name - * @property {nextHourConditionV2[]} [condition] - * @property {nextHourSummaryV2[]} [summary] - * @property {string} [startTime] - Time in "YYYY-MM-DDTHH:MM:SSZ" format - * @property {nextHourMinuteV2[]} [minutes] - */ -/** - * Base next hour object for Apple Weather APIv3 - * @typedef {Object} appleNextHourWithoutMetadataV3 - * - * @property {"NextHourForecast"} name - * @property {nextHourConditionV3[]} [condition] - * @property {nextHourSummaryV3[]} [summary] - * @property {string} [forecastStart] - Time in "YYYY-MM-DDTHH:MM:SSZ" format - * @property {string} [forecastEnd] - Time in "YYYY-MM-DDTHH:MM:SSZ" format - * @property {nextHourMinuteV3[]} [minutes] - */ -/** - * Next hour object for Apple Weather APIv1 - * @typedef {appleNextHourWithoutMetadataV1} appleNextHourV1 - * - * @property {appleNextHourMetadataV1} metadata - Metadata of air quality - */ -/** - * Next hour object for Apple Weather APIv2 - * @typedef {appleNextHourWithoutMetadataV2} appleNextHourV2 - * - * @property {appleNextHourMetadataV2} metadata - Metadata of air quality - */ -/** - * Next hour object for Apple Weather APIv3 - * @typedef {appleNextHourWithoutMetadataV3} appleNextHourV3 - * - * @property {appleNextHourMetadataV3} metadata - Metadata of air quality - */ - -/** - * @typedef {Object} range - * - * @property {Number} LOWER - lower of range - * @property {Number} UPPER - upper of range - */ - -/** - * Precipitation level and its range - * @typedef {Object} precipitationLevel - * - * @property {number} VALUE - Value of precipitation level for comparing - * @property {range} RANGE - Precipitation range of level - */ -/** @typedef {"NO" | "LIGHT" | "MODERATE" | "HEAVY" | "STORM"} precipitationLevelNames */ -/** @typedef {Object.} precipitationLevels */ - -/** - * AQI level and its range - * @typedef {Object} aqiLevel - * - * @property {number} VALUE - AQI level value for `airQuality.categoryIndex`. Should be start at 1. - * @property {range} RANGE - Range of AQI level - */ - -/** - * Concentration range for pollutant - * @typedef {Object} concentrationRange - * - * @property {range} AMOUNT - Range of concentration amount - * @property {range} AQI - AQI range for its concentration amount range - */ - -/** - * @typedef {Object} concentration - * - * @property {pollutantUnitsText} UNIT - Unit of concentration - * @property {Object.} RANGES - Concentration range for pollutant - */ - -/** - * AQI standard - * @typedef {Object} aqiStandard - * - * @property {appleAqiScales} APPLE_SCALE - Value of `airQuality.scale` - * @property {Object.} AQI_LEVELS - Value and ranges of AQI levels - * @property {number} SIGNIFICANT_LEVEL - AQI will be pinned if `airQuality.categoryIndex` - * greater or equals than this - * @property {Object.} CONCENTRATIONS - Key should be the pollutant name. - */ - -/** - * Version 1 of next hour settings - * @typedef {Object} nextHourSettingsV1 - * - * @property {boolean} switch - Switch of the next hour - * @property {string} source - API source of next hour - */ -/** - * Version 1 settings of local converter of AQI - * @typedef {Object} aqiLocalSettingsV1 - * - * @property {boolean} switch - Switch of the AQI local converter - * @property {string} standard - Name of the converter - */ -/** - * Version 1 of AQI comparison settings - * @typedef {Object} aqiComparisonSettingsV1 - * - * @property {boolean} switch - Switch of the AQI comparison - * @property {string} source - API source of AQI comparison - */ -/** - * Version 1 of AQI settings - * @typedef {Object} aqiSettingsV1 - * - * @property {boolean} switch - Switch of the AQI - * @property {string[]} targets - AQI with matched scales will be edited by module - * @property {aqiLocalSettingsV1} local - Settings of local converter of AQI - * @property {aqiComparisonSettingsV1} comparison - AQI comparison settings - * @property {string} source - API source of AQI - */ -/** - * Base API settings - * @typedef {Object} baseApiSettingsV1 - * - * @property {Object} httpHeaders - HTTP headers for requesting API - */ -/** - * Base API with token settings - * @typedef {baseApiSettingsV1} baseApiTokenSettingsV1 - * - * @property {string} token - Token for the API - */ -/** - * Version 1 of WeatherOL API settings - * @typedef {baseApiSettingsV1} weatherOlSettingsV1 - */ -/** - * Version 1 of ColorfulClouds API settings - * @typedef {baseApiTokenSettingsV1} colorfulCloudsSettingsV1 - * - * @property {boolean} forceCnForAqi - Force to use China standard in AQI data - * @property {boolean} forceCnForComparison - Force to use China standard in AQI comparison data - */ -/** - * Version 1 of The World Air Quality Project API settings - * @typedef {baseApiTokenSettingsV1} waqiSettingsV1 - */ -/** - * Version 1 of APIs settings - * @typedef {Object} apisSettingsV1 - * - * @property {weatherOlSettingsV1} weatherOl - API settings of WeatherOL - * @property {colorfulCloudsSettingsV1} colorfulClouds - API settings of ColorfulClouds - * @property {waqiSettingsV1} waqi - API settings of WAQI - */ -/** - * Version 1 of log settings - * @typedef {Object} logSettingsV1 - * - * @property {"debug" | "info" | "warn" | "error"} level - Log level - * @property {boolean} location - Print location info to logs - */ -/** - * Version 1 of settings - * @typedef {Object} settingsV1 - * - * @property {boolean} switch - Switch of the module - * @property {nextHourSettingsV1} nextHour - Settings of next hour - * @property {aqiSettingsV1} aqi - Settings of AQI - * @property {apisSettingsV1} apis - Settings of APIs - * @property {logSettingsV1} log - Settings of log - */ - -/** - * Version 1 of AQI cache - * @typedef {Object} aqiCacheV1 - * - * @property {coordinate} location - Location of AQI info - * @property {scaleNames} scaleName - Scale of AQI info - * @property {number} aqi - AQI index - */ -/** - * Version 1 of caches - * @typedef {Object} cachesV1 - * - * @property {Object.} aqis - AQI caches - * @property {{tokens: Object.}} waqi - WAQI caches - */ - -/** - * WAQI error - * @typedef {Object} waqiError - * - * @property {"error"} status - * @property {string} data - Error message - */ -/** - * WAQI pollutant names - * @typedef {"co" | "no2" | "o3" | "pm10" | "pm25" | "so2"} waqiPollutantNames - */ -/** - * Data in WAQI mapq - * @typedef {Object} waqiMapqD - * - * @property {string} nlo - Station name in English - * @property {string} nna - Station name in local language - * @property {number} t - Report time (in seconds) - * @property {waqiPollutantNames} pol - Primary pollutant - * @property {string} x - Station ID - * @property {string} v - AQI - * @property {string} u - Country/Province/Station Name - * @property {string} key - * @property {number} d - Distance to the location - * @property {[number, number]} geo - Station coordinate - * @property {string} cca2 - Country code in ISO 3166-1 alpha-2 - */ -/** - * WAQI mapq - * @typedef {Object} waqiMapq - * - * @property {waqiMapqD[]} d - Data in WAQI mapq - * @property {string} dt - * @property {null} g - */ - -/** - * Station in WAQI mapq2 - * @typedef {Object} waqiMapq2Station - * - * @property {string} idx - Station ID - * @property {string} aqi - Air quality index - * @property {string} utime - Report time (in `YYYY-MM-DDTHH:MM:SS+/-timezone` format) - * @property {[number, number]} geo - Station coordinate - * @property {string} name - Station name in "English (local language)" - * @property {string} country - Country code in ISO 3166-1 alpha-2 - * @property {number} distance - Distance to the location - */ -/** - * Data in WAQI mapq2 - * @typedef {Object} waqiMapq2Data - * - * @property {null} location - * @property {waqiMapq2Station[]} stations - Stations in mapq2 - */ -/** - * WAQI mapq2 - * @typedef {Object} waqiMapq2 - * - * @property {waqiMapq2Data} data - Data in WAQI mapq2 - * @property {string} dt - * @property {"ok" | "error"} status - Status of API - */ - -/** - * Attribution in WAQI feed - * @typedef {Object} waqiFeedAttribution - * - * @property {string} url - URL to the source - * @property {string} name - Name of the source - */ -/** - * City data in WAQI feed - * @typedef {Object} waqiFeedCity - * - * @property {[number, number]} geo - Station coordinate - * @property {string} name - Station name in "English (local language)" - * @property {string} url - URL to the data of station in WAQI - * @property {""} location - */ -/** - * Time data in WAQI feed - * @typedef {Object} waqiFeedTime - * - * @property {string} s - Time in "YYYY-MM-DD HH:MM:SS" format - * @property {string} tz - Timezone in "+/-HH:MM" - * @property {number} v - Time in seconds - * @property {string} iso - Time in "YYYY-MM-DDTHH:MM:SS+/-Timezone" format - */ -/** - * Forecast data in WAQI feed - * @typedef {Object} waqiFeedForecast - * - * @property {Object.} daily - */ -/** - * Data in WAQI feed - * @typedef {Object} waqiFeedData - * - * @property {number} aqi - Air quality index - * @property {number} idx - Station ID - * @property {waqiFeedAttribution[]} attributions - Sources of the data - * @property {waqiFeedCity} city - Station info - * @property {waqiPollutantNames} dominentpol - Primary pollutant - * @property {Object.} iaqi - * @property {waqiFeedTime} time - Report time - * @property {waqiFeedForecast} forecast - Country/Province/Station Name - * @property {{sync: string}} debug - */ -/** - * WAQI feed - * @typedef {Object} waqiFeed - * - * @property {"ok" | "error"} status - Status of API - * @property {waqiFeedData} data - Data in WAQI feed - */ - -/** - * msg data of `rxs.obs[]` of WAQI AQI feed - * @typedef {Object} waqiAqiFeedRxsObsMsg - * - * @property {number} aqi - Air quality index - * @property {number} idx - Station ID - * @property {waqiFeedAttribution[]} attributions - Sources of the data - * @property {waqiFeedCity} city - Station info - * @property {waqiPollutantNames} dominentpol - Primary pollutant - * @property {Object.} iaqi - - * t is time in "YYYY-MM-DD HH:MM:SS" format - * @property {waqiFeedTime} time - Report time - * @property {Object.< - * waqiPollutantNames|string, {e: number, s: string, d: number, m: number, v: Array} - * >} obs - * @property {{daily: Object.< - * waqiPollutantNames|string, {avg: number, day: string, max: number, min: number}[] - * >, hourly: {}}} forecast - * @property {gen: number} xsync - Time in seconds - */ -/** - * obs data of rxs of WAQI AQI feed - * @typedef {Object} waqiAqiFeedRxsObs - * - * @property {waqiAqiFeedRxsObsMsg[]} msg - Data from station - * @property {"ok" | "error"} status - Status of API - * @property {string} cached - */ -/** - * rxs data of WAQI AQI feed - * @typedef {Object} waqiAqiFeedRxs - * - * @property {waqiAqiFeedRxsObs[]} obs - Data from stations - * @property {"ok" | "error"} status - Status of API - * @property {string} ver - */ -/** - * WAQI AQI feed - * @typedef {Object} waqiAqiFeed - * - * @property {string} dt - * @property {waqiAqiFeedRxs} rxs - */ - -/** - * msg data of `rxs.obs[]` of WAQI AQI feed - * @typedef {Object} waqiNowFeedRxsObsMsg - * - * @property {number} aqi - Air quality index - * @property {number} idx - Station ID - * @property {waqiFeedCity} city - Station info - * @property {waqiPollutantNames} dominentpol - Primary pollutant - * @property {waqiFeedTime} time - Report time - * @property {sync: string} debug - Time in YYYY-MM-DDTHH:MM:SS+/-timezone format - */ -/** - * obs data of rxs of WAQI AQI feed - * @typedef {Object} waqiNowFeedRxsObs - * - * @property {waqiNowFeedRxsObsMsg[]} msg - Data from station - * @property {"ok" | "error"} status - Status of API - * @property {string} cached - */ -/** - * rxs data of WAQI AQI feed - * @typedef {Object} waqiNowFeedRxs - * - * @property {waqiNowFeedRxsObs[]} obs - Data from stations - * @property {"ok" | "error"} status - Status of API - * @property {string} ver - */ -/** - * WAQI now feed - * @typedef {Object} waqiNowFeed - * - * @property {string} dt - * @property {waqiNowFeedRxs} rxs - */ - -/** - * Pollutants and AQI from ColorfulClouds handled by {@link getCcAirQuality} - * @typedef {Object} ccAirQuality - * - * @property {number | -1} PM2.5 - Amount in micrograms/m3 - * @property {number | -1} PM10 - Amount in micrograms/m3 - * @property {number | -1} OZONE - Amount in micrograms/m3 - * @property {number | -1} NO2 - Amount in micrograms/m3 - * @property {number | -1} SO2 - Amount in micrograms/m3 - * @property {number | -1} CO - Amount in milligrams/M3 - * @property {{chn: number | -1, usa: number | -1}} aqi - Air quality index - */ - -/** - * Air quality descriptions (zh_CN) in ColorfulClouds - * @typedef {"缺数据" | "优" | "良" | "轻度污染" | "中度污染" | "重度污染" | "严重污染"} ccAirQualityDescriptionsZhCn - */ -/** - * Air quality data of realtime of ColorfulClouds v2.4+ - * @typedef {Object} ccV2d4pRealtimeAirQuality - * - * @property {number} pm25 - Amount in micrograms/m3 - * @property {number} pm10 - Amount in micrograms/m3 - * @property {number} o3 - Amount in micrograms/m3 - * @property {number} no2 - Amount in micrograms/m3 - * @property {number} so2 - Amount in micrograms/m3 - * @property {number} co - Amount in milligrams/M3 - * @property {{chn: number, usa: number}} aqi - Air quality index - * @property {{ - * chn: ccAirQualityDescriptionsZhCn|string, usa: ccAirQualityDescriptionsZhCn|string | "" - * }} description - Air quality description - */ -/** - * ColorfulClouds v2 base - * @typedef {Object} colorfulCloudsV2Base - * - * @property {"ok" | "error"} status - Status of API - * @property {string} api_version - API version starting with "v" - * @property {"alpha" | "active"} api_status - Channel of current API version - * @property {"zh_CN" | "zh_TW" | "en_US" | "en_GB"} lang - Affective to the descriptions - * @property {"SI" | "imperial" | "metric:v1" | "metric:v2" | "metric"} unit - Unit of the data. [单位制 | 彩云天气 API](https://docs.caiyunapp.com/docs/tables/unit/) - * @property {number} tzshift - Timezone in seconds - * @property {string} timezone - Timezone in "Continent/Region" format - * @property {number} server_time - Time in seconds - * @property {[number, number]} location - Coordinate of current location - */ -/** - * Result of ColorfulClouds v2 - * @typedef {Object} colorfulCloudsV2Result - * - * @property {Object} [alert] - * @property {Object} [realtime] - * @property {Object} [minutely] - * @property {Object} [hourly] - * @property {Object} [daily] - * @property {number} primary - * @property {string} forecast_keypoint - Description of current weather - */ -/** - * ColorfulClouds v2 - * @typedef {colorfulCloudsV2Base} colorfulCloudsV2 - * - * @property {colorfulCloudsV2Result} result - */ -/** - * ColorfulClouds v2 error - * @typedef {Object} colorfulCloudsV2Error - * - * @property {"ok" | "error"} status - Status of API - * @property {string} error - Error message - * @property {string} api_version - API version starting with "v" - */ - -const EPA_454_AQI_LEVELS = { - INVALID: { VALUE: -1, RANGE: { LOWER: Number.MIN_VALUE, UPPER: -1 } }, - GOOD: { VALUE: 1, RANGE: { LOWER: 0, UPPER: 50 } }, - MODERATE: { VALUE: 2, RANGE: { LOWER: 51, UPPER: 100 } }, - UNHEALTHY_FOR_SENSITIVE: { VALUE: 3, RANGE: { LOWER: 101, UPPER: 150 } }, - UNHEALTHY: { VALUE: 4, RANGE: { LOWER: 151, UPPER: 200 } }, - VERY_UNHEALTHY: { VALUE: 5, RANGE: { LOWER: 201, UPPER: 300 } }, - HAZARDOUS: { VALUE: 6, RANGE: { LOWER: 301, UPPER: 400 } }, - // Used in calculation - VERY_HAZARDOUS: { VALUE: 6, RANGE: { LOWER: 401, UPPER: 500 } }, - OVER_RANGE: { VALUE: 7, RANGE: { LOWER: 500, UPPER: Number.MAX_VALUE } }, -}; - -/** - * US AQI standard, not equal to NowCast. - * [EPA 454/B-18-007]{@link https://www.airnow.gov/sites/default/files/2020-05/aqi-technical-assistance-document-sept2018.pdf} - * @type aqiStandard - */ -const EPA_454 = { - APPLE_SCALE: 'EPA_NowCast.2207', - AQI_LEVELS: EPA_454_AQI_LEVELS, - // Unhealthy for sensitive groups - SIGNIFICANT_LEVEL: 3, - - CONCENTRATIONS: { - OZONE: { - UNIT: 'ppm', - RANGES: { - UNHEALTHY_FOR_SENSITIVE: { - AMOUNT: { LOWER: 0.125, UPPER: 0.164 }, - AQI: EPA_454_AQI_LEVELS.UNHEALTHY_FOR_SENSITIVE.RANGE, - }, - UNHEALTHY: { - AMOUNT: { LOWER: 0.165, UPPER: 0.204 }, - AQI: EPA_454_AQI_LEVELS.UNHEALTHY.RANGE, - }, - VERY_UNHEALTHY: { - AMOUNT: { LOWER: 0.205, UPPER: 0.404 }, - AQI: EPA_454_AQI_LEVELS.VERY_UNHEALTHY.RANGE, - }, - HAZARDOUS: { - AMOUNT: { LOWER: 0.405, UPPER: 0.504 }, - AQI: EPA_454_AQI_LEVELS.HAZARDOUS.RANGE, - }, - VERY_HAZARDOUS: { - AMOUNT: { LOWER: 0.505, UPPER: 0.604 }, - AQI: EPA_454_AQI_LEVELS.VERY_HAZARDOUS.RANGE, - }, - }, - }, - OZONE_8H: { - UNIT: 'ppm', - RANGES: { - GOOD: { - AMOUNT: { LOWER: 0.000, UPPER: 0.054 }, - AQI: EPA_454_AQI_LEVELS.GOOD.RANGE, - }, - MODERATE: { - AMOUNT: { LOWER: 0.055, UPPER: 0.070 }, - AQI: EPA_454_AQI_LEVELS.MODERATE.RANGE, - }, - UNHEALTHY_FOR_SENSITIVE: { - AMOUNT: { LOWER: 0.071, UPPER: 0.085 }, - AQI: EPA_454_AQI_LEVELS.UNHEALTHY_FOR_SENSITIVE.RANGE, - }, - UNHEALTHY: { - AMOUNT: { LOWER: 0.086, UPPER: 0.105 }, - AQI: EPA_454_AQI_LEVELS.UNHEALTHY.RANGE, - }, - VERY_UNHEALTHY: { - AMOUNT: { LOWER: 0.106, UPPER: 0.200 }, - AQI: EPA_454_AQI_LEVELS.VERY_UNHEALTHY.RANGE, - }, - }, - }, - 'PM2.5_24H': { - UNIT: 'microgramsPerM3', - RANGES: { - GOOD: { - AMOUNT: { LOWER: 0.0, UPPER: 12.0 }, - AQI: EPA_454_AQI_LEVELS.GOOD.RANGE, - }, - MODERATE: { - AMOUNT: { LOWER: 12.1, UPPER: 35.4 }, - AQI: EPA_454_AQI_LEVELS.MODERATE.RANGE, - }, - // If a different SHL for PM2.5 is promulgated, - // the following numbers will change accordingly. - UNHEALTHY_FOR_SENSITIVE: { - AMOUNT: { LOWER: 35.5, UPPER: 55.4 }, - AQI: EPA_454_AQI_LEVELS.UNHEALTHY_FOR_SENSITIVE.RANGE, - }, - UNHEALTHY: { - AMOUNT: { LOWER: 55.5, UPPER: 150.4 }, - AQI: EPA_454_AQI_LEVELS.UNHEALTHY.RANGE, - }, - VERY_UNHEALTHY: { - AMOUNT: { LOWER: 150.5, UPPER: 250.4 }, - AQI: EPA_454_AQI_LEVELS.VERY_UNHEALTHY.RANGE, - }, - HAZARDOUS: { - AMOUNT: { LOWER: 250.5, UPPER: 350.4 }, - AQI: EPA_454_AQI_LEVELS.HAZARDOUS.RANGE, - }, - VERY_HAZARDOUS: { - AMOUNT: { LOWER: 350.5, UPPER: 500.4 }, - AQI: EPA_454_AQI_LEVELS.VERY_HAZARDOUS.RANGE, - }, - }, - }, - PM10_24H: { - UNIT: 'microgramsPerM3', - RANGES: { - GOOD: { - AMOUNT: { LOWER: 0, UPPER: 54 }, - AQI: EPA_454_AQI_LEVELS.GOOD.RANGE, - }, - MODERATE: { - AMOUNT: { LOWER: 55, UPPER: 154 }, - AQI: EPA_454_AQI_LEVELS.MODERATE.RANGE, - }, - UNHEALTHY_FOR_SENSITIVE: { - AMOUNT: { LOWER: 155, UPPER: 254 }, - AQI: EPA_454_AQI_LEVELS.UNHEALTHY_FOR_SENSITIVE.RANGE, - }, - UNHEALTHY: { - AMOUNT: { LOWER: 255, UPPER: 354 }, - AQI: EPA_454_AQI_LEVELS.UNHEALTHY.RANGE, - }, - VERY_UNHEALTHY: { - AMOUNT: { LOWER: 355, UPPER: 424 }, - AQI: EPA_454_AQI_LEVELS.VERY_UNHEALTHY.RANGE, - }, - HAZARDOUS: { - AMOUNT: { LOWER: 425, UPPER: 504 }, - AQI: EPA_454_AQI_LEVELS.HAZARDOUS.RANGE, - }, - VERY_HAZARDOUS: { - AMOUNT: { LOWER: 505, UPPER: 604 }, - AQI: EPA_454_AQI_LEVELS.VERY_HAZARDOUS.RANGE, - }, - }, - }, - CO_8H: { - UNIT: 'ppm', - RANGES: { - GOOD: { - AMOUNT: { LOWER: 0.0, UPPER: 4.4 }, - AQI: EPA_454_AQI_LEVELS.GOOD.RANGE, - }, - MODERATE: { - AMOUNT: { LOWER: 4.5, UPPER: 9.4 }, - AQI: EPA_454_AQI_LEVELS.MODERATE.RANGE, - }, - UNHEALTHY_FOR_SENSITIVE: { - AMOUNT: { LOWER: 9.5, UPPER: 12.4 }, - AQI: EPA_454_AQI_LEVELS.UNHEALTHY_FOR_SENSITIVE.RANGE, - }, - UNHEALTHY: { - AMOUNT: { LOWER: 12.5, UPPER: 15.4 }, - AQI: EPA_454_AQI_LEVELS.UNHEALTHY.RANGE, - }, - VERY_UNHEALTHY: { - AMOUNT: { LOWER: 15.5, UPPER: 30.4 }, - AQI: EPA_454_AQI_LEVELS.VERY_UNHEALTHY.RANGE, - }, - HAZARDOUS: { - AMOUNT: { LOWER: 30.5, UPPER: 40.4 }, - AQI: EPA_454_AQI_LEVELS.HAZARDOUS.RANGE, - }, - VERY_HAZARDOUS: { - AMOUNT: { LOWER: 40.5, UPPER: 50.4 }, - AQI: EPA_454_AQI_LEVELS.VERY_HAZARDOUS.RANGE, - }, - }, - }, - SO2: { - UNIT: 'ppb', - RANGES: { - GOOD: { - AMOUNT: { LOWER: 0, UPPER: 35 }, - AQI: EPA_454_AQI_LEVELS.GOOD.RANGE, - }, - MODERATE: { - AMOUNT: { LOWER: 36, UPPER: 75 }, - AQI: EPA_454_AQI_LEVELS.MODERATE.RANGE, - }, - UNHEALTHY_FOR_SENSITIVE: { - AMOUNT: { LOWER: 76, UPPER: 185 }, - AQI: EPA_454_AQI_LEVELS.UNHEALTHY_FOR_SENSITIVE.RANGE, - }, - }, - // 1-hour SO2 values do not define higher AQI values (≥ 200). - // AQI values of 200 or greater are calculated with 24-hour SO2 concentrations. - }, - SO2_24H: { - UNIT: 'ppb', - RANGES: { - UNHEALTHY: { - AMOUNT: { LOWER: 186, UPPER: 304 }, - AQI: EPA_454_AQI_LEVELS.UNHEALTHY.RANGE, - }, - VERY_UNHEALTHY: { - AMOUNT: { LOWER: 305, UPPER: 604 }, - AQI: EPA_454_AQI_LEVELS.VERY_UNHEALTHY.RANGE, - }, - HAZARDOUS: { - AMOUNT: { LOWER: 605, UPPER: 804 }, - AQI: EPA_454_AQI_LEVELS.HAZARDOUS.RANGE, - }, - VERY_HAZARDOUS: { - AMOUNT: { LOWER: 805, UPPER: 1004 }, - AQI: EPA_454_AQI_LEVELS.VERY_HAZARDOUS.RANGE, - }, - }, - }, - NO2: { - UNIT: 'ppb', - RANGES: { - GOOD: { - AMOUNT: { LOWER: 0, UPPER: 53 }, - AQI: EPA_454_AQI_LEVELS.GOOD.RANGE, - }, - MODERATE: { - AMOUNT: { LOWER: 54, UPPER: 100 }, - AQI: EPA_454_AQI_LEVELS.MODERATE.RANGE, - }, - UNHEALTHY_FOR_SENSITIVE: { - AMOUNT: { LOWER: 101, UPPER: 360 }, - AQI: EPA_454_AQI_LEVELS.UNHEALTHY_FOR_SENSITIVE.RANGE, - }, - UNHEALTHY: { - AMOUNT: { LOWER: 361, UPPER: 649 }, - AQI: EPA_454_AQI_LEVELS.UNHEALTHY.RANGE, - }, - VERY_UNHEALTHY: { - AMOUNT: { LOWER: 650, UPPER: 1249 }, - AQI: EPA_454_AQI_LEVELS.VERY_UNHEALTHY.RANGE, - }, - HAZARDOUS: { - AMOUNT: { LOWER: 1250, UPPER: 1649 }, - AQI: EPA_454_AQI_LEVELS.HAZARDOUS.RANGE, - }, - VERY_HAZARDOUS: { - AMOUNT: { LOWER: 1650, UPPER: 2049 }, - AQI: EPA_454_AQI_LEVELS.VERY_HAZARDOUS.RANGE, - }, - }, - }, - }, -}; - -/** - * China AQI standard. - * [环境空气质量指数(AQI)技术规定(试行)]{@link https://www.mee.gov.cn/ywgz/fgbz/bz/bzwb/jcffbz/201203/W020120410332725219541.pdf} - * @type aqiStandard - */ -const HJ_633 = { - APPLE_SCALE: 'HJ6332012.2207', - AQI_LEVELS: EPA_454.AQI_LEVELS, - SIGNIFICANT_LEVEL: EPA_454.SIGNIFICANT_LEVEL, - - CONCENTRATIONS: { - SO2_24H: { - UNIT: 'microgramsPerM3', - RANGES: { - GOOD: { - AMOUNT: { LOWER: 0, UPPER: 50 }, - AQI: EPA_454.AQI_LEVELS.GOOD.RANGE, - }, - MODERATE: { - AMOUNT: { LOWER: 51, UPPER: 150 }, - AQI: EPA_454.AQI_LEVELS.MODERATE.RANGE, - }, - UNHEALTHY_FOR_SENSITIVE: { - AMOUNT: { LOWER: 151, UPPER: 475 }, - AQI: EPA_454.AQI_LEVELS.UNHEALTHY_FOR_SENSITIVE.RANGE, - }, - UNHEALTHY: { - AMOUNT: { LOWER: 476, UPPER: 800 }, - AQI: EPA_454.AQI_LEVELS.UNHEALTHY.RANGE, - }, - VERY_UNHEALTHY: { - AMOUNT: { LOWER: 801, UPPER: 1600 }, - AQI: EPA_454.AQI_LEVELS.VERY_UNHEALTHY.RANGE, - }, - HAZARDOUS: { - AMOUNT: { LOWER: 1601, UPPER: 2100 }, - AQI: EPA_454.AQI_LEVELS.HAZARDOUS.RANGE, - }, - VERY_HAZARDOUS: { - AMOUNT: { LOWER: 2101, UPPER: 2602 }, - AQI: EPA_454.AQI_LEVELS.VERY_HAZARDOUS.RANGE, - }, - }, - }, - SO2: { - UNIT: 'microgramsPerM3', - RANGES: { - GOOD: { - AMOUNT: { LOWER: 0, UPPER: 150 }, - AQI: EPA_454.AQI_LEVELS.GOOD.RANGE, - }, - MODERATE: { - AMOUNT: { LOWER: 151, UPPER: 500 }, - AQI: EPA_454.AQI_LEVELS.MODERATE.RANGE, - }, - UNHEALTHY_FOR_SENSITIVE: { - AMOUNT: { LOWER: 501, UPPER: 650 }, - AQI: EPA_454.AQI_LEVELS.UNHEALTHY_FOR_SENSITIVE.RANGE, - }, - UNHEALTHY: { - AMOUNT: { LOWER: 651, UPPER: 800 }, - AQI: EPA_454.AQI_LEVELS.UNHEALTHY.RANGE, - }, - // 二氧化硫(SO2)1小时平均浓度高于800 ug/m3的,不再进行其空气质量分指数计算,二氧化硫(SO2)空气质量分指数按24小时平均浓度计算的分指数报告。 - }, - }, - NO2_24H: { - UNIT: 'microgramsPerM3', - RANGES: { - GOOD: { - AMOUNT: { LOWER: 0, UPPER: 40 }, - AQI: EPA_454.AQI_LEVELS.GOOD.RANGE, - }, - MODERATE: { - AMOUNT: { LOWER: 41, UPPER: 80 }, - AQI: EPA_454.AQI_LEVELS.MODERATE.RANGE, - }, - UNHEALTHY_FOR_SENSITIVE: { - AMOUNT: { LOWER: 81, UPPER: 180 }, - AQI: EPA_454.AQI_LEVELS.UNHEALTHY_FOR_SENSITIVE.RANGE, - }, - UNHEALTHY: { - AMOUNT: { LOWER: 181, UPPER: 280 }, - AQI: EPA_454.AQI_LEVELS.UNHEALTHY.RANGE, - }, - VERY_UNHEALTHY: { - AMOUNT: { LOWER: 281, UPPER: 565 }, - AQI: EPA_454.AQI_LEVELS.VERY_UNHEALTHY.RANGE, - }, - HAZARDOUS: { - AMOUNT: { LOWER: 566, UPPER: 750 }, - AQI: EPA_454.AQI_LEVELS.HAZARDOUS.RANGE, - }, - VERY_HAZARDOUS: { - AMOUNT: { LOWER: 751, UPPER: 940 }, - AQI: EPA_454.AQI_LEVELS.VERY_HAZARDOUS.RANGE, - }, - }, - }, - NO2: { - UNIT: 'microgramsPerM3', - RANGES: { - GOOD: { - AMOUNT: { LOWER: 0, UPPER: 100 }, - AQI: EPA_454.AQI_LEVELS.GOOD.RANGE, - }, - MODERATE: { - AMOUNT: { LOWER: 101, UPPER: 200 }, - AQI: EPA_454.AQI_LEVELS.MODERATE.RANGE, - }, - UNHEALTHY_FOR_SENSITIVE: { - AMOUNT: { LOWER: 201, UPPER: 700 }, - AQI: EPA_454.AQI_LEVELS.UNHEALTHY_FOR_SENSITIVE.RANGE, - }, - UNHEALTHY: { - AMOUNT: { LOWER: 701, UPPER: 1200 }, - AQI: EPA_454.AQI_LEVELS.UNHEALTHY.RANGE, - }, - VERY_UNHEALTHY: { - AMOUNT: { LOWER: 1201, UPPER: 2340 }, - AQI: EPA_454.AQI_LEVELS.VERY_UNHEALTHY.RANGE, - }, - HAZARDOUS: { - AMOUNT: { LOWER: 2341, UPPER: 3090 }, - AQI: EPA_454.AQI_LEVELS.HAZARDOUS.RANGE, - }, - VERY_HAZARDOUS: { - AMOUNT: { LOWER: 3091, UPPER: 3840 }, - AQI: EPA_454.AQI_LEVELS.VERY_HAZARDOUS.RANGE, - }, - }, - }, - PM10_24H: { - UNIT: 'microgramsPerM3', - RANGES: { - GOOD: { - AMOUNT: { LOWER: 0, UPPER: 50 }, - AQI: EPA_454.AQI_LEVELS.GOOD.RANGE, - }, - MODERATE: { - AMOUNT: { LOWER: 51, UPPER: 150 }, - AQI: EPA_454.AQI_LEVELS.MODERATE.RANGE, - }, - UNHEALTHY_FOR_SENSITIVE: { - AMOUNT: { LOWER: 151, UPPER: 250 }, - AQI: EPA_454.AQI_LEVELS.UNHEALTHY_FOR_SENSITIVE.RANGE, - }, - UNHEALTHY: { - AMOUNT: { LOWER: 251, UPPER: 350 }, - AQI: EPA_454.AQI_LEVELS.UNHEALTHY.RANGE, - }, - VERY_UNHEALTHY: { - AMOUNT: { LOWER: 351, UPPER: 420 }, - AQI: EPA_454.AQI_LEVELS.VERY_UNHEALTHY.RANGE, - }, - HAZARDOUS: { - AMOUNT: { LOWER: 421, UPPER: 500 }, - AQI: EPA_454.AQI_LEVELS.HAZARDOUS.RANGE, - }, - VERY_HAZARDOUS: { - AMOUNT: { LOWER: 501, UPPER: 600 }, - AQI: EPA_454.AQI_LEVELS.VERY_HAZARDOUS.RANGE, - }, - }, - }, - CO_24H: { - UNIT: 'milligramsPerM3', - RANGES: { - GOOD: { - AMOUNT: { LOWER: 0, UPPER: 2 }, - AQI: EPA_454.AQI_LEVELS.GOOD.RANGE, - }, - MODERATE: { - AMOUNT: { LOWER: 3, UPPER: 4 }, - AQI: EPA_454.AQI_LEVELS.MODERATE.RANGE, - }, - UNHEALTHY_FOR_SENSITIVE: { - AMOUNT: { LOWER: 5, UPPER: 14 }, - AQI: EPA_454.AQI_LEVELS.UNHEALTHY_FOR_SENSITIVE.RANGE, - }, - UNHEALTHY: { - AMOUNT: { LOWER: 15, UPPER: 24 }, - AQI: EPA_454.AQI_LEVELS.UNHEALTHY.RANGE, - }, - VERY_UNHEALTHY: { - AMOUNT: { LOWER: 25, UPPER: 36 }, - AQI: EPA_454.AQI_LEVELS.VERY_UNHEALTHY.RANGE, - }, - HAZARDOUS: { - AMOUNT: { LOWER: 37, UPPER: 48 }, - AQI: EPA_454.AQI_LEVELS.HAZARDOUS.RANGE, - }, - VERY_HAZARDOUS: { - AMOUNT: { LOWER: 49, UPPER: 60 }, - AQI: EPA_454.AQI_LEVELS.VERY_HAZARDOUS.RANGE, - }, - }, - }, - CO: { - UNIT: 'milligramsPerM3', - RANGES: { - GOOD: { - AMOUNT: { LOWER: 0, UPPER: 5 }, - AQI: EPA_454.AQI_LEVELS.GOOD.RANGE, - }, - MODERATE: { - AMOUNT: { LOWER: 6, UPPER: 10 }, - AQI: EPA_454.AQI_LEVELS.MODERATE.RANGE, - }, - UNHEALTHY_FOR_SENSITIVE: { - AMOUNT: { LOWER: 11, UPPER: 35 }, - AQI: EPA_454.AQI_LEVELS.UNHEALTHY_FOR_SENSITIVE.RANGE, - }, - UNHEALTHY: { - AMOUNT: { LOWER: 36, UPPER: 60 }, - AQI: EPA_454.AQI_LEVELS.UNHEALTHY.RANGE, - }, - VERY_UNHEALTHY: { - AMOUNT: { LOWER: 61, UPPER: 90 }, - AQI: EPA_454.AQI_LEVELS.VERY_UNHEALTHY.RANGE, - }, - HAZARDOUS: { - AMOUNT: { LOWER: 91, UPPER: 120 }, - AQI: EPA_454.AQI_LEVELS.HAZARDOUS.RANGE, - }, - VERY_HAZARDOUS: { - AMOUNT: { LOWER: 121, UPPER: 150 }, - AQI: EPA_454.AQI_LEVELS.VERY_HAZARDOUS.RANGE, - }, - }, - }, - OZONE: { - UNIT: 'microgramsPerM3', - RANGES: { - GOOD: { - AMOUNT: { LOWER: 0, UPPER: 160 }, - AQI: EPA_454.AQI_LEVELS.GOOD.RANGE, - }, - MODERATE: { - AMOUNT: { LOWER: 161, UPPER: 200 }, - AQI: EPA_454.AQI_LEVELS.MODERATE.RANGE, - }, - UNHEALTHY_FOR_SENSITIVE: { - AMOUNT: { LOWER: 201, UPPER: 300 }, - AQI: EPA_454.AQI_LEVELS.UNHEALTHY_FOR_SENSITIVE.RANGE, - }, - UNHEALTHY: { - AMOUNT: { LOWER: 301, UPPER: 400 }, - AQI: EPA_454.AQI_LEVELS.UNHEALTHY.RANGE, - }, - VERY_UNHEALTHY: { - AMOUNT: { LOWER: 401, UPPER: 800 }, - AQI: EPA_454.AQI_LEVELS.VERY_UNHEALTHY.RANGE, - }, - HAZARDOUS: { - AMOUNT: { LOWER: 801, UPPER: 1000 }, - AQI: EPA_454.AQI_LEVELS.HAZARDOUS.RANGE, - }, - VERY_HAZARDOUS: { - AMOUNT: { LOWER: 1001, UPPER: 1200 }, - AQI: EPA_454.AQI_LEVELS.VERY_HAZARDOUS.RANGE, - }, - }, - }, - OZONE_8H: { - UNIT: 'microgramsPerM3', - RANGES: { - GOOD: { - AMOUNT: { LOWER: 0, UPPER: 100 }, - AQI: EPA_454.AQI_LEVELS.GOOD.RANGE, - }, - MODERATE: { - AMOUNT: { LOWER: 101, UPPER: 160 }, - AQI: EPA_454.AQI_LEVELS.MODERATE.RANGE, - }, - UNHEALTHY_FOR_SENSITIVE: { - AMOUNT: { LOWER: 161, UPPER: 215 }, - AQI: EPA_454.AQI_LEVELS.UNHEALTHY_FOR_SENSITIVE.RANGE, - }, - UNHEALTHY: { - AMOUNT: { LOWER: 216, UPPER: 265 }, - AQI: EPA_454.AQI_LEVELS.UNHEALTHY.RANGE, - }, - VERY_UNHEALTHY: { - AMOUNT: { LOWER: 266, UPPER: 800 }, - AQI: EPA_454.AQI_LEVELS.VERY_UNHEALTHY.RANGE, - }, - // 臭氧(O3)8小时平均浓度值高于800 ug/m3的,不再进行其空气质量分指数计算,臭氧(O3)空气质量分指数按1小时平均浓度计算的分指数报告。 - }, - }, - 'PM2.5_24H': { - UNIT: 'microgramsPerM3', - RANGES: { - GOOD: { - AMOUNT: { LOWER: 0, UPPER: 35 }, - AQI: EPA_454.AQI_LEVELS.GOOD.RANGE, - }, - MODERATE: { - AMOUNT: { LOWER: 36, UPPER: 75 }, - AQI: EPA_454.AQI_LEVELS.MODERATE.RANGE, - }, - UNHEALTHY_FOR_SENSITIVE: { - AMOUNT: { LOWER: 76, UPPER: 115 }, - AQI: EPA_454.AQI_LEVELS.UNHEALTHY_FOR_SENSITIVE.RANGE, - }, - UNHEALTHY: { - AMOUNT: { LOWER: 116, UPPER: 150 }, - AQI: EPA_454.AQI_LEVELS.UNHEALTHY.RANGE, - }, - VERY_UNHEALTHY: { - AMOUNT: { LOWER: 151, UPPER: 250 }, - AQI: EPA_454.AQI_LEVELS.VERY_UNHEALTHY.RANGE, - }, - HAZARDOUS: { - AMOUNT: { LOWER: 251, UPPER: 350 }, - AQI: EPA_454.AQI_LEVELS.HAZARDOUS.RANGE, - }, - VERY_HAZARDOUS: { - AMOUNT: { LOWER: 351, UPPER: 500 }, - AQI: EPA_454.AQI_LEVELS.VERY_HAZARDOUS.RANGE, - }, - }, - }, - }, -}; - -/** - * WAQI InstantCast. - * [A Beginner's Guide to Air Quality Instant-Cast and Now-Cast.]{@link https://aqicn.org/faq/2015-03-15/air-quality-nowcast-a-beginners-guide/} - * [Ozone AQI Scale update]{@link https://aqicn.org/faq/2016-08-10/ozone-aqi-scale-update/} - * @type aqiStandard - */ -const WAQI_INSTANT_CAST = { - APPLE_SCALE: EPA_454.APPLE_SCALE, - AQI_LEVELS: EPA_454.AQI_LEVELS, - SIGNIFICANT_LEVEL: EPA_454.SIGNIFICANT_LEVEL, - - CONCENTRATIONS: { - ...EPA_454.CONCENTRATIONS, - OZONE: { - UNIT: 'ppb', - RANGES: { - GOOD: { - AMOUNT: { LOWER: 0, UPPER: 61.5 }, - AQI: EPA_454.AQI_LEVELS.GOOD.RANGE, - }, - MODERATE: { - AMOUNT: { LOWER: 62.5, UPPER: 100.5 }, - AQI: EPA_454.AQI_LEVELS.MODERATE.RANGE, - }, - UNHEALTHY_FOR_SENSITIVE: { - AMOUNT: { LOWER: 101.5, UPPER: 151.5 }, - AQI: EPA_454.AQI_LEVELS.UNHEALTHY_FOR_SENSITIVE.RANGE, - }, - UNHEALTHY: { - AMOUNT: { LOWER: 152.5, UPPER: 204 }, - AQI: EPA_454.AQI_LEVELS.UNHEALTHY.RANGE, - }, - VERY_UNHEALTHY: { - AMOUNT: { LOWER: 205, UPPER: 404 }, - AQI: EPA_454.AQI_LEVELS.VERY_UNHEALTHY.RANGE, - }, - HAZARDOUS: { - AMOUNT: { LOWER: 405, UPPER: 504 }, - AQI: EPA_454.AQI_LEVELS.HAZARDOUS.RANGE, - }, - VERY_HAZARDOUS: { - AMOUNT: { LOWER: 505, UPPER: 604 }, - AQI: EPA_454.AQI_LEVELS.VERY_HAZARDOUS.RANGE, - }, - }, - }, - 'PM2.5': EPA_454.CONCENTRATIONS['PM2.5_24H'], - PM10: EPA_454.CONCENTRATIONS.PM10_24H, - CO: EPA_454.CONCENTRATIONS.CO_8H, - OZONE_8H: undefined, - 'PM2.5_24H': undefined, - PM10_24H: undefined, - CO_8H: undefined, - SO2_24H: undefined, - }, -}; - -/** - * Check passed parameter is or not an object - * @param {any} object - Value you wish to check - * @return {boolean} - Return `true` if passed parameter is an object - */ -const isObject = (object) => typeof object === 'object' && !Array.isArray(object) && object !== null; - -/** - * Check passed parameter is or not a non-empty string - * @author WordlessEcho - * @param {any} string - Value you wish to check - * @return {boolean} - Return `true` if passed parameter is a non-empty string - */ -const isNonEmptyString = (string) => typeof string === 'string' && string.length > 0; - -/** - * Check passed parameter is or not a non-NaN number - * @author WordlessEcho - * @param {any} number - Value you wish to check - * @return {boolean} - Return `true` if passed parameter is a non-NaN number - */ -const isNonNanNumber = (number) => typeof number === 'number' && !Number.isNaN(number); - -/** - * Check passed parameter is or not a valid latitude - * @author WordlessEcho - * @param {number} latitude - Latitude you wish to check - * @return {boolean} - Return `true` if passed parameter is a valid latitude - */ -const isLatitude = (latitude) => isNonNanNumber(latitude) && latitude >= -90 && latitude <= 90; - -/** - * Check passed parameter is or not a valid longitude - * @author WordlessEcho - * @param {number} longitude - Longitude you wish to check - * @return {boolean} - Return `true` if passed parameter is a valid longitude - */ -const isLongitude = (longitude) => ( - isNonNanNumber(longitude) && longitude >= -180 && longitude <= 180 -); - -/** - * Check passed parameter is or not a valid location - * @author WordlessEcho - * @param {coordinate} location - Location with latitude and longitude - * @return {boolean} - Return `true` if passed parameter is a valid location - */ -const isLocation = (location) => isLatitude(location?.latitude) && isLongitude(location?.longitude); - -/** - * Parse JSON with exception handler - * @author WordlessEcho - * @param {string} stringJson - String to be parsed - * @param {(Error) => any} catcher - * @return {Object|any} - Parsed JSON or returned from `catcher` - */ -const parseJson = (stringJson, catcher) => { - // eslint-disable-next-line functional/no-try-statement - try { - return JSON.parse(stringJson); - } catch (e) { - return catcher(e); - } -}; -/** - * Parse JSON with default value - * @author WordlessEcho - * @param {string} stringJson - String to be parsed - * @param {any} defaultValue - Value to be returned if failed to parse `stringJson` - * @return {Object|any} - Parsed JSON or `defaultValue` if failed - */ -const parseJsonWithDefault = (stringJson, defaultValue) => { - // eslint-disable-next-line functional/no-try-statement - try { - return JSON.parse(stringJson); - } catch (e) { - return defaultValue; - } -}; - -/** - * Check passed parameter is or not a valid range - * @author WordlessEcho - * @param {any} range - Range to be checked - * @return {boolean} - Return `true` if passed parameter is a valid range - */ -const isRange = (range) => isNonNanNumber(range?.LOWER) && isNonNanNumber(range?.UPPER); -/** - * Check passed parameter is or not a positive with zero range - * @author WordlessEcho - * @param {any} range - Range to be checked - * @return {boolean} - Return `true` if passed parameter is a positive with zero range - */ -const isPositiveWithZeroRange = (range) => isRange(range) && range.LOWER >= 0 && range.UPPER >= 0; -/** - * Check passed parameter is or not a positive range - * @author WordlessEcho - * @param {any} range - Range to be checked - * @return {boolean} - Return `true` if passed parameter is a positive range - */ -const isPositiveRange = (range) => ( - isPositiveWithZeroRange(range) && range.LOWER !== 0 && range.UPPER !== 0 -); - -/** - * Get settings from Box.js - * @author WordlessEcho - * @author VirgilClyne - * @param {Object} envs - `envs` from {@link getENV} - * @return {settingsV1} - Valid settings - */ -const toSettings = (envs) => { - const settings = database.Weather.Settings; - // eslint-disable-next-line functional/no-conditional-statement - if (parseJsonWithDefault(envs?.Switch, false)) { - const wikiLink = 'https://github.com/VirgilClyne/iRingo/wiki/%F0%9F%8C%A4%E5%A4%A9%E6%B0%94#%E4%BD%BF%E7%94%A8%E8%AF%B4%E6%98%8E'; - // eslint-disable-next-line functional/no-expression-statement - $.log(`⚠️${toSettings.name}:您好像刚刚升级到${$.name},不妨看看新功能并重新设置一次模块? ${wikiLink}`, ''); - } - - return { - switch: parseJsonWithDefault(envs?.Settings?.Switch, settings.Switch), - nextHour: { - switch: parseJsonWithDefault( - envs?.Settings?.NextHour?.Switch, - settings.NextHour.Switch, - ), - source: isNonEmptyString(envs?.Settings?.NextHour?.Source) ? envs.Settings.NextHour.Source - : settings.NextHour.Source, - }, - aqi: { - switch: parseJsonWithDefault(envs?.Settings?.AQI?.Switch, settings.AQI.Switch), - targets: parseJsonWithDefault(`[${envs?.Settings?.AQI?.Targets}]`, settings.AQI.Targets), - local: { - switch: parseJsonWithDefault( - envs?.Settings?.AQI?.Local?.Switch, - settings.AQI.Local.Switch, - ), - standard: isNonEmptyString(envs?.Settings?.AQI?.Local?.Standard) - ? envs.Settings.AQI.Local.Standard : settings.AQI.Local.Standard, - }, - source: isNonEmptyString(envs?.Settings?.AQI?.Source) ? envs.Settings.AQI.Source - : settings.AQI.Source, - comparison: { - switch: parseJsonWithDefault( - envs?.Settings?.AQI?.Comparison.Switch, - settings.AQI.Comparison.Switch, - ), - source: isNonEmptyString(envs?.Settings?.AQI?.Comparison?.Source) - ? envs.Settings.AQI.Comparison.Source : settings.AQI.Comparison.Source, - }, - }, - map: { - aqi: parseJsonWithDefault(envs?.Settings?.Map?.AQI, settings.Map.AQI), - }, - apis: { - weatherOl: { - httpHeaders: parseJsonWithDefault( - envs?.Settings?.APIs?.WeatherOl?.HTTPHeaders, - settings.APIs.WeatherOL.HTTPHeaders, - ), - }, - colorfulClouds: { - httpHeaders: parseJsonWithDefault( - envs?.Settings?.APIs?.ColorfulClouds?.HTTPHeaders, - settings.APIs.ColorfulClouds.HTTPHeaders, - ), - token: envs?.Settings?.APIs?.ColorfulClouds?.Token, - forceCnForAqi: parseJsonWithDefault( - envs?.Settings?.APIs?.ColorfulClouds?.ForceCNForAQI, - settings.APIs.ColorfulClouds.ForceCNForAQI, - ), - forceCnForComparison: parseJsonWithDefault( - envs?.Settings?.APIs?.ColorfulClouds?.ForceCNForComparison, - settings.APIs.ColorfulClouds.ForceCNForComparison, - ), - }, - waqi: { - httpHeaders: parseJsonWithDefault( - envs?.Settings?.APIs?.WAQI?.HTTPHeaders, - settings.APIs.WAQI.HTTPHeaders, - ), - token: envs?.Settings?.APIs?.WAQI?.Token, - }, - }, - log: { - level: isNonEmptyString(envs?.Settings?.Log?.Level) ? envs.Settings.Log.Level - : settings.Log.Level, - Location: parseJsonWithDefault(envs?.Settings?.Log?.Location, settings.Log.Location), - }, - }; -}; - -/** - * Get caches from Box.js - * @author WordlessEcho - * @param {Object} envs - `envs` from {@link getENV} - * @return {cachesV1} - Valid caches - */ -const toCaches = (envs) => ({ - aqis: { - ...(isObject(envs?.Caches?.aqis) && envs.Caches.aqis), - }, - waqi: { - ...(isObject(envs?.Caches?.waqi) && envs.Caches.waqi), - tokens: { - ...(isObject(envs.Caches?.waqi?.tokens) && envs.Caches.waqi.tokens), - }, - }, -}); - -const settings = toSettings(getENV('iRingo', 'Weather', database)); - -/** - * Log helper - * @param {"debug" | "info" | "warn" | "error"} level - Log level - * @param {string} message - Log message - */ -const logger = (level, message) => { - /** - * Get emoji by log level - * @param {"debug" | "info" | "warn" | "error"} l - Log level - * @return {string} - Emoji for log level - */ - const toEmoji = (l) => { - switch (l) { - case 'error': - return '❗️'; - case 'warn': - return '⚠️'; - case 'info': - return 'ℹ️'; - case 'debug': - default: - return '🚧'; - } - }; - - /** - * Get required tags - * @param {"debug" | "info" | "warn" | "error"} l - Log level - * @return {string[]} - Required tags - */ - const matchedTags = (l) => { - const tags = ['debug', 'info', 'warn', 'error']; - switch (l) { - case 'debug': - return tags; - case 'warn': - return tags.slice(2); - case 'error': - return tags.slice(3); - case 'info': - default: - return tags.slice(1); - } - }; - - // eslint-disable-next-line functional/no-conditional-statement - if (matchedTags(settings.log.level).includes(level)) { - // eslint-disable-next-line functional/no-expression-statement - $.log(`${toEmoji(level)} ${message}`, ''); - } -}; - -/** - * Get AQI from cache - * @author WordlessEcho - * @param {Object.} cachedAqis - Caches of AQIs - * @param {number} timestamp - UNIX timestamp of cached time - * @param {coordinate} location - Coordinate of AQI info - * @param {?string} stationName - `AirQuality.source` from QWeather - * @param {appleAqiScales} scaleName - Part before the '.' in iOS `AirQuality.scale` - * @return {cachedAqi | {aqi: -1}} - Matched AQI info - */ -const getCachedAqi = (cachedAqis, timestamp, location, stationName, scaleName) => { - const pythagoreanTheorem = (a, b) => Math.sqrt(a * a + b * b); - - if ( - isObject(cachedAqis) && isNonNanNumber(timestamp) && timestamp > 0 - && isLocation(location) && isNonEmptyString(scaleName) - ) { - const cacheTimestampString = Object.keys(cachedAqis).find((timestampString) => { - const cachedTimestamp = parseInt(timestampString, 10); - return isNonNanNumber(cachedTimestamp) && cachedTimestamp >= timestamp - && cachedTimestamp < timestamp + 1000 * 60 * 60; - }); - const cacheTimestamp = parseInt(cacheTimestampString, 10); - - const cache = isNonNanNumber(cacheTimestamp) - ? cachedAqis[cacheTimestamp].find((aqiInfo) => ( - isNonEmptyString(stationName) - ? aqiInfo?.stationName === stationName && aqiInfo?.scaleName === scaleName - // Cannot get station name - : pythagoreanTheorem( - Math.abs(aqiInfo.location.longitude - location.longitude), - Math.abs(aqiInfo.location.latitude - location.latitude), - // 0.085 is an approximation by observing air quality map from Apple Weather - ) < 0.085 && aqiInfo?.scaleName === scaleName - )) - : { aqi: -1 }; - - if (isNonNanNumber(cache?.aqi) && cache.aqi >= 0) { - logger('info', `${getCachedAqi.name}:找到了已缓存的AQI信息:AQI值为${cache.aqi}`); - return cache; - } - } - - return { aqi: -1 }; -}; - -/** - * Get token from cache - * @author WordlessEcho - * @param {Object.} cachedTokens - Caches of WAQI tokens - * @param {number} stationId - Station ID - * @return {string} - Matched token - */ -const getCachedWaqiToken = (cachedTokens, stationId) => { - if (isObject(cachedTokens) && isNonNanNumber(stationId)) { - // 1 hour - const cacheLimit = (+(new Date())) - 1000 * 60 * 60; - const cachedToken = cachedTokens?.[stationId]; - - if (isNonNanNumber(cachedToken?.timestamp) && cachedToken.timestamp > cacheLimit - && isNonEmptyString(cachedToken?.token)) { - logger('info', `${getCachedWaqiToken.name}:找到了监测站ID ${stationId}的token缓存`); - return cachedToken.token; - } - } - - return ''; -}; - -/** - * Return caches for `setjson` - * @author WordlessEcho - * @param {cachesV1} caches - Caches of iRingo.Weather.Caches - * @param {number} timestamp - UNIX timestamp of cached time - * @param {coordinate} location - Coordinate of AQI info - * @param {?string} stationName - `AirQuality.source` from QWeather - * @param {scaleNames} scaleName - Part before the '.' in iOS `AirQuality.scale` - * @param {number} aqi - Air quality index - * @return {cachesV1} - Cache will not be edited if any parameter is invalid. - */ -const cacheAqi = (caches, timestamp, location, stationName, scaleName, aqi) => { - // Remove caches before 36 hours ago - const cacheLimit = (+new Date()) - 1000 * 60 * 60 * 36; - - const validAqis = isObject(caches?.aqis) ? Object.fromEntries(Object.entries(caches.aqis) - .filter(([timestampString, aqisInfo]) => { - const cachedTimestamp = parseInt(timestampString, 10); - return isNonNanNumber(cachedTimestamp) && cachedTimestamp > cacheLimit - && Array.isArray(aqisInfo); - }) - .map(([timestampString, aqisInfo]) => [ - timestampString, - aqisInfo.filter((aqiInfo) => ( - isNonNanNumber(aqiInfo?.aqi) && aqiInfo.aqi >= 0 && isLocation(aqiInfo?.location) - && isNonEmptyString(aqiInfo?.scaleName) - )), - ])) : {}; - - if ( - isNonNanNumber(timestamp) && timestamp > cacheLimit && isLocation(location) - && isNonEmptyString(scaleName) && isNonNanNumber(aqi) && aqi >= 0 - ) { - const cacheTimestampString = Object.keys(validAqis).find((timestampString) => { - const cachedTimestamp = parseInt(timestampString, 10); - return isNonNanNumber(cachedTimestamp) && cachedTimestamp >= timestamp - && cachedTimestamp < timestamp + 1000 * 60 * 60; - }); - const cacheTimestamp = parseInt(cacheTimestampString, 10); - - const existedCache = isNonNanNumber(cacheTimestamp) - ? validAqis[cacheTimestamp].find((aqiInfo) => ( - isNonEmptyString(stationName) - ? aqiInfo?.stationName === stationName && aqiInfo?.scaleName === scaleName - // Cannot get station name - // https://www.mee.gov.cn/gkml/hbb/bwj/201204/W020140904493567314967.pdf - : Math.abs(aqiInfo.location.longitude - location.longitude) < 0.045 - && Math.abs(aqiInfo.location.latitude - location.latitude) < 0.045 - && aqiInfo?.scaleName === scaleName - )) - : { aqi: -1 }; - - if (!isNonNanNumber(existedCache?.aqi) || existedCache.aqi < 0) { - logger( - 'debug', - `${cacheAqi.name}:已将当前AQI信息缓存,AQI信息:\n` - + `时间:${new Date(timestamp)}\n` - + `${settings.log.location ? `经度:${location.longitude},纬度:${location.latitude}\n` : ''}` - + `${isNonEmptyString(stationName) ? `监测站:${stationName}\n` : ''}` - + `AQI标准:${scaleName}\nAQI:${aqi}`, - ); - - return { - ...(isObject(caches) && caches), - aqis: { - ...validAqis, - [isNonNanNumber(cacheTimestamp) ? cacheTimestamp : timestamp]: [ - ...(Array.isArray(validAqis?.[cacheTimestamp]) ? validAqis[cacheTimestamp] : []), - { - location, - ...(isNonEmptyString(stationName) && { stationName }), - scaleName, - aqi, - }, - ], - }, - }; - } - } - - return { - ...(isObject(caches) && caches), - aqis: { ...validAqis }, - }; -}; - -/** - * Return caches for `setjson` - * @author WordlessEcho - * @param {cachesV1} caches - Caches of iRingo.Weather.Caches - * @param {number} stationId - Station ID - * @param {string} token - Token of station - * @return {cachesV1} - Cache will not be edited if any parameter is invalid. - */ -const cacheWaqiToken = (caches, stationId, token) => { - // Remove caches before 1 hour ago - const cacheLimit = (+(new Date())) - 1000 * 60 * 60; - - const validTokens = isObject(caches?.waqi?.tokens) - ? Object.fromEntries(Object.entries(caches.waqi.tokens) - .filter(([stationIdString, tokenInfo]) => ( - isNonNanNumber(parseInt(stationIdString, 10)) && isNonNanNumber(tokenInfo?.timestamp) - && tokenInfo.timestamp > cacheLimit && isNonEmptyString(tokenInfo?.token) - ))) : {}; - - return { - ...(isObject(caches) && caches), - waqi: { - ...(isObject(caches?.waqi) && caches?.waqi), - tokens: { - ...validTokens, - ...(isNonNanNumber(stationId) && isNonEmptyString(token) - && !logger('info', `${cacheWaqiToken.name}:已缓存监测站ID ${stationId}的token`) && { - [stationId]: { timestamp: (+(new Date())), token }, - }), - }, - }, - }; -}; - -// TODO: I am too afraid about regex to rewrite this -/** - * Get Origin Parameters - * @author VirgilClyne - * @author WordlessEcho - * @param {String} path - Path of URL - * @return {Object.<'ver'|'language'|'lat'|'lng'|'countryCode', string>|{}} - - * `version`, `language`, `latitude`, `longitude` and `regionCode` from path. - * Empty object will be returned if type of path is invalid. - */ -const getParams = (path) => { - if (!isNonEmptyString(path)) { - return {}; - } - - const regExp = /^(?v1|v2|v3)\/weather\/(?[\w-_]+)\/(?-?\d+\.\d+)\/(?-?\d+\.\d+).*(?country=[A-Z]{2})?.*/i; - const result = path.match(regExp); - - return isObject(result?.groups) ? result.groups : {}; -}; - -/** - * Get the nearest station info from WAQI - * @author WordlessEcho - * @param {coordinate} location - Location for finding the nearest station - * @param {"mapq" | "mapq2"} mapqVersion - Version of mapq. Using 1 (mapq) if invalid. - * @param {Object} [headers] - HTTP headers - * @return {Promise} - Result from WAQI in mapq2 format - */ -const waqiNearest = ( - location, - mapqVersion, - headers = { 'Content-Type': 'application/json' }, -) => new Promise((resolve) => { - if (!isLocation(location)) { - // eslint-disable-next-line functional/no-expression-statement - resolve({ - status: 'error', - data: `${waqiNearest.name}: Invalid location` - + `Latitude: ${location?.latitude}` - + `Longitude: ${location?.longitude}`, - }); - return; - } - - // eslint-disable-next-line functional/no-expression-statement - $.get( - { - headers, - // n is stations to return - url: `https://api.waqi.info/${mapqVersion}/nearest?n=1&geo=1/${location.latitude}/${location.longitude}`, - }, - (error, _response, data) => { - if (error) { - // eslint-disable-next-line functional/no-expression-statement - resolve({ - status: 'error', - data: `${waqiNearest.name}: Error: ${error}\n` - + `Data: ${data}`, - }); - return; - } - - const result = parseJson(data, (e) => ({ - status: 'error', - data: `${waqiNearest.name}: Data from WAQI is not a valid JSON\n` - + `Error: ${e}\n` - + `Data: ${data}`, - })); - - if (isNonEmptyString(result.status) && result.status !== 'ok') { - // eslint-disable-next-line functional/no-expression-statement - resolve({ - status: 'error', - data: result?.data ?? result?.message ?? result?.reason - ?? `${waqiNearest.name}: WAQI return a unknown error\nData: ${data}`, - }); - return; - } - - // eslint-disable-next-line functional/no-expression-statement - resolve(result); - }, - ); -}); - -/** - * Get token for public from WAQI - * @author WordlessEcho - * @param {number} [stationId] - ID of station - * @param {Object} [headers] - HTTP headers - * @return {Promise<{status: "ok" | "error", data: string}>} - - * Token in `data` if ok. Error message in `data` if failed. - */ -// eslint-disable-next-line no-unused-vars -const waqiToken = (stationId, headers = { 'Content-Type': 'application/json' }) => new Promise((resolve) => { - // eslint-disable-next-line functional/no-expression-statement - $.get( - { headers, url: `https://api.waqi.info/api/token/${isNonNanNumber(stationId) ? stationId : ''}` }, - (error, _response, data) => { - if (error) { - // eslint-disable-next-line functional/no-expression-statement - resolve({ - status: 'error', - data: `${waqiToken.name}: Error: ${error}\n` - + `Data: ${data}`, - }); - return; - } - - const result = parseJson(data, (e) => ({ - status: 'error', - data: `${waqiToken.name}: Data from WAQI is not a valid JSON\n` - + `Error: ${e}\n` - + `Data: ${data}`, - })); - - if (result.status === 'error') { - // eslint-disable-next-line functional/no-expression-statement - resolve(result); - return; - } - - if (result?.rxs?.status !== 'ok') { - // eslint-disable-next-line functional/no-expression-statement - resolve({ - status: 'error', - data: `${waqiToken.name}: WAQI returned an unexpected status\n` - + `rxs.status: ${result?.rxs?.status}` - + `Data: ${data}`, - }); - return; - } - - if (!Array.isArray(result?.rxs?.obs)) { - // eslint-disable-next-line functional/no-expression-statement - resolve({ - status: 'error', - data: `${waqiToken.name}: rxs.obs is not an array.\n` - + `rxs.obs type: ${typeof result?.rxs?.obs}` - + `Data: ${data}`, - }); - return; - } - - const token = result.rxs.obs.find((obs) => (isNonEmptyString(obs?.msg?.token)))?.msg?.token; - if (!isNonEmptyString(token)) { - // eslint-disable-next-line functional/no-expression-statement - resolve({ - status: 'error', - data: `${waqiToken.name}: No valid token found\n` - + `Token type: ${typeof token}\n` - + `Token length: ${token?.length}\n` - + `Data: ${data}`, - }); - return; - } - - // eslint-disable-next-line functional/no-expression-statement - resolve({ status: 'ok', data: token }); - }, - ); -}); - -/** - * Get data by using token from The World Air Quality Project. - * [API - Air Quality Programmatic APIs]{@link https://aqicn.org/api/} - * @author VirgilClyne - * @author WordlessEcho - * @param {?coordinate} location - Required in feed based on location - * @param {?number} stationId - Required in feed based on station ID - * @param {string} token - Token for WAQI API. - * [Air Quality Open Data Platform]{@link https://aqicn.org/data-platform/token/} - * @param {Object} [headers] - HTTP headers - * @return {Promise} - Feed data from WAQI - */ -const waqiV2 = ( - location, - stationId, - token, - headers = { 'Content-Type': 'application/json' }, -) => new Promise((resolve) => { - if (!isNonEmptyString(token)) { - // eslint-disable-next-line functional/no-expression-statement - resolve({ - status: 'error', - data: `${waqiV2.name}: Invalid token\n` - + `Token type: ${typeof token}\n` - + `Token length: ${token?.length}`, - }); - return; - } - - /** - * Handle errors into WAQI format - * @author WordlessEcho - * @param {Error} err - Error from get of `Env.js` - * @param {string} d - Data from get of `Env.js` - * @return {waqiFeed|waqiError} - Data in WAQI format - */ - const getResult = (err, d) => { - if (err) { - return { - status: 'error', - data: `${waqiV2.name}: Error: ${err}\n` - + `Data: ${d}`, - }; - } - - const result = parseJson(d, (e) => ({ - status: 'error', - data: 'Data from WAQI is not a valid JSON\n' - + `Error: ${e}\n` - + `Data: ${d}`, - })); - - if (!isNonEmptyString(result?.status)) { - return { - status: 'error', - data: 'WAQI returned an unknown status\n' - + `Data: ${d}`, - }; - } - - return result; - }; - - const baseUrl = 'https://api.waqi.info'; - if (isLocation(location)) { - // eslint-disable-next-line functional/no-expression-statement - $.get( - { - headers, - url: `${baseUrl}/feed/geo:${location.latitude};${location.longitude}/?token=${token}`, - }, - (error, _response, data) => { - // eslint-disable-next-line functional/no-expression-statement - resolve(getResult(error, data)); - }, - ); - return; - } - - if (isNonNanNumber(stationId)) { - // eslint-disable-next-line functional/no-expression-statement - $.get( - { - headers, - url: `${baseUrl}/feed/@${stationId}/?token=${token}`, - }, - (error, _response, data) => { - // eslint-disable-next-line functional/no-expression-statement - resolve(getResult(error, data)); - }, - ); - return; - } - - // eslint-disable-next-line functional/no-expression-statement - resolve({ - status: 'error', - data: `${waqiV2.name}: Invalid parameters\n` - + `Location: ${JSON.stringify(location)}` - + `Station ID: ${stationId}`, - }); -}); - -/** - * Get data from WAQI old API - * @author WordlessEcho - * @param {"now" | "aqi"} type - Type of API - * @param {number} stationId - ID of station - * @param {?string} body - HTTP body - * @param {Object} [headers] - HTTP headers - * @return {Promise} - Data from WAQI - */ -// eslint-disable-next-line no-unused-vars -const waqiV1 = ( - type, - stationId, - body, - headers = { 'Content-Type': 'application/json' }, -) => new Promise((resolve) => { - if (!isNonNanNumber(stationId)) { - // eslint-disable-next-line functional/no-expression-statement - resolve({ - status: 'error', - data: `${waqiV1.name}: Invalid station ID\n` - + `Station ID: ${stationId}`, - }); - return; - } - - const baseUrl = 'https://api.waqi.info'; - - // eslint-disable-next-line functional/no-expression-statement - $.post( - { - headers, - url: `${baseUrl}/api/feed/@${stationId}/${type}.json`, - ...(isNonEmptyString(body) && { body }), - }, - (error, response, data) => { - if (error) { - // eslint-disable-next-line functional/no-expression-statement - resolve({ - status: 'error', - data: `${waqiV1.name}: Error: ${error}\n` - + `Data: ${data}`, - }); - return; - } - - const result = parseJson(data, (e) => ({ - status: 'error', - data: 'Data from WAQI is not a valid JSON\n' - + `Error: ${e}\n` - + `Data: ${data}`, - })); - - if (!isNonEmptyString(result?.rxs?.status)) { - // eslint-disable-next-line functional/no-expression-statement - resolve({ - status: 'error', - data: 'WAQI returned an unknown status\n' - + `Data: ${data}`, - }); - return; - } - - // eslint-disable-next-line functional/no-expression-statement - resolve(result); - }, - ); -}); - -/** - * Convert data from {@link waqiNearest} to feed format - * @author WordlessEcho - * @param {"mapq" | "mapq2"} version - Version of mapq - * @param {waqiMapq|waqiMapq2|waqiError} nearestData - Data from {@link waqiNearest} - * @return {(waqiFeed|waqiError)[]} - Data in feed format - */ -const waqiNearestToFeed = (version, nearestData) => { - /** - * Get error message from WAQI - * @author WordlessEcho - * @param {"mapq" | "mapq2"} mapqVersion - WAQI mapq version - * @param {waqiError} data - WAQI mapq data - * @return {string} - Error message - */ - const toErrorMessage = (mapqVersion, data) => { - const forUnknown = `${waqiNearestToFeed.name}: Unknown error from WAQI.\n` - + `Data: ${JSON.stringify(nearestData)}`; - - if (isNonEmptyString(data?.data)) { - return data.data; - } - - switch (mapqVersion) { - case 'mapq2': - return isNonEmptyString(data?.reason) ? data.reason : forUnknown; - case 'mapq': - return isNonEmptyString(data?.message) ? data.message : forUnknown; - default: - return forUnknown; - } - }; - - if (nearestData.status === 'error') { - return [{ - status: 'error', - data: toErrorMessage(version, nearestData), - }]; - } - - /** - * Convert mapq(1) time to YYYY-MM-DDTHH:MM:SS+/-timezone - * @author WordlessEcho - * @param {{t: number} | {utime: string}} data - Data with `t` or `utime` - * @return {string} - YYYY-MM-DDTHH:MM:SS+/-timezone format ISO time - */ - const serverTimeToIsoString = (data) => { - if (isNonNanNumber(data?.t) && data.t > 0) { - return `${new Date(data.t * 1000).toISOString().slice(0, -5)}+00:00`; - } - - if (!Number.isNaN(Date.parse(data?.utime))) { - return data.utime; - } - - return `${(new Date((new Date()).setMinutes(0, 0, 0))).toISOString().slice(0, -5)}+00:00`; - }; - - switch (version) { - case 'mapq': - if (!Array.isArray(nearestData?.d)) { - return [{ - status: 'error', - data: `${waqiNearestToFeed.name}: \`d\` is not an array\n` - + `Data: ${JSON.stringify(nearestData)}`, - }]; - } - - return nearestData.d.map((station) => { - const aqi = parseInt(station?.v, 10); - const stationId = parseInt(station?.x, 10); - // nna: Local language. nlo: English. - const stationName = station?.nna ?? station?.nlo; - - if (!isNonNanNumber(aqi) || aqi < 0) { - return { - status: 'error', - data: `${waqiNearestToFeed.name}: Invalid AQI\n` - + `AQI: ${station?.v}\n` - + `Station data: ${JSON.stringify(station)}`, - }; - } - - const isoTime = serverTimeToIsoString(station); - - return { - status: 'ok', - data: { - aqi, - ...(!Number.isNaN(stationId) && { idx: stationId }), - attributions: [{ - url: 'https://waqi.info/', - name: 'The World Air Quality Project', - }], - city: { - geo: station.geo, - ...(isNonEmptyString(stationName) && { name: stationName }), - url: 'https://aqicn.org', - location: '', - }, - ...(isNonEmptyString(station?.pol) && { dominentpol: station.pol }), - iaqi: {}, - time: { - s: isoTime.slice(0, -6), - tz: isoTime.slice(-6), - v: Date.parse(isoTime) / 1000, - iso: isoTime, - }, - forecast: {}, - debug: {}, - }, - }; - }); - case 'mapq2': - if (!Array.isArray(nearestData?.data?.stations)) { - return [{ - status: 'error', - data: `${waqiNearestToFeed.name}: \`data.stations\` is not an array\n` - + `Data: ${JSON.stringify(nearestData)}`, - }]; - } - - return nearestData.data.stations.map((station) => { - const aqi = parseInt(station?.aqi, 10); - const stationId = parseInt(station?.idx, 10); - - if (!isNonNanNumber(aqi) || aqi < 0) { - return { - status: 'error', - data: `${waqiNearestToFeed.name}: Invalid AQI\n` - + `AQI: ${station?.aqi}\n` - + `Station data: ${JSON.stringify(station)}`, - }; - } - - const isoTime = serverTimeToIsoString(station); - - return { - status: 'ok', - data: { - aqi, - ...(!Number.isNaN(stationId) && { idx: stationId }), - attributions: [{ - url: 'https://waqi.info/', - name: 'The World Air Quality Project', - }], - city: { - geo: station.geo, - ...(isNonEmptyString(station?.name) && { name: station.name }), - url: 'https://aqicn.org', - location: '', - }, - iaqi: {}, - time: { - s: isoTime.slice(0, -6), - tz: isoTime.slice(-6), - v: Date.parse(isoTime) / 1000, - iso: isoTime, - }, - forecast: {}, - debug: {}, - }, - }; - }); - default: - return [{ - status: 'error', - data: `${waqiNearestToFeed.name}: Unsupported mapq version.`, - }]; - } -}; - -/** - * Convert data from {@link waqiV1} to feed format - * @author WordlessEcho - * @param {waqiNowFeed|waqiAqiFeed|waqiError} v1Data - Data from {@link waqiV1} - * @return {(waqiFeed|waqiError)[]} - Data in feed format - */ -// eslint-disable-next-line no-unused-vars -const waqiV1ToFeed = (v1Data) => { - const unknownError = `${waqiV1ToFeed.name}: Unknown error from WAQI\n` - + `Data: ${JSON.stringify(v1Data)}`; - - if (v1Data?.status === 'error') { - return [{ - status: 'error', - data: v1Data?.data ?? unknownError, - }]; - } - - if (v1Data?.rxs?.status !== 'ok') { - return [{ - status: 'error', - data: unknownError, - }]; - } - - if (!Array.isArray(v1Data?.rxs?.obs)) { - return [{ - status: 'error', - data: `${waqiV1ToFeed.name}: \`d\` is not an array\n` - + `Data: ${JSON.stringify(v1Data)}`, - }]; - } - - /** - * Make sure time in data is in YYYY-MM-DDTHH:MM:SS+/-timezone format - * @author WordlessEcho - * @param {{msg: {time: {iso: string}}}} data - Data with `msg.time.iso` - * @return {string} - YYYY-MM-DDTHH:MM:SS+/-timezone format ISO time - */ - const serverTimeToIsoString = (data) => { - if (!Number.isNaN(Date.parse(data?.msg?.time?.iso))) { - return data.msg.time.iso; - } - - return `${(new Date((new Date()).setMinutes(0, 0, 0))).toISOString().slice(0, -5)}+00:00`; - }; - - /** - * Try to convert debug time in YYYY-MM-DDTHH:MM:SS+/-timezone format - * @author WordlessEcho - * @param {{msg: {xsync: {gen: number}}} | {msg: {debug: {sync: string}}}} data - - * Data with `msg.xsync.gen` or `msg.debug.sync` - * @return {string} - Return empty string if data is invalid. - */ - const getDebug = (data) => { - if (isNonNanNumber(data?.msg?.xsync?.gen) && data.msg.xsync.gen > 0) { - return `${(new Date(data.msg.xsync.gen * 1000)).toISOString().slice(0, -5)}+00:00`; - } - - if (!Number.isNaN(Date.parse(data.msg?.debug?.sync))) { - return data.msg.debug.sync; - } - - return ''; - }; - - return v1Data.rxs.obs.map((station) => { - if (!isNonEmptyString(station?.status) || (station.status !== 'ok' && station.status !== 'error')) { - return { - status: 'error', - data: `${waqiV1ToFeed.name}: Unknown status from WAQI\n` - + `Station data: ${JSON.stringify(station)}`, - }; - } - - if (!isNonNanNumber(station?.msg?.aqi) || station.msg.aqi < 0) { - return { - status: 'error', - data: `${waqiV1ToFeed.name}: Invalid AQI\n` - + `Station data: ${JSON.stringify(station)}`, - }; - } - - const debugTime = getDebug(station); - return { - status: station.status, - data: { - ...station.msg, - attributions: Array.isArray(station.msg?.attributions) - && station.msg.attributions.length > 0 ? station.msg.attributions - : [{ - url: 'https://waqi.info/', - name: 'The World Air Quality Project', - }], - city: { - url: 'https://aqicn.org', - location: '', - ...station.msg?.city, - }, - iaqi: { ...station.msg?.iaqi }, - time: { - ...station.msg?.time, - iso: serverTimeToIsoString(station), - }, - forecast: { ...station.msg?.forecast }, - }, - debug: { ...(debugTime.length > 0 ? { sync: debugTime } : {}) }, - }; - }); -}; - -/** - * Get data from "气象在线". This API could be considered as unconfigurable ColorfulClouds API. - * [简介 | 彩云天气 API]{@link https://docs.caiyunapp.com/docs/v2.2/intro} - * [通用预报接口/v2.2 - CaiyunWiki]{@link https://open.caiyunapp.com/%E9%80%9A%E7%94%A8%E9%A2%84%E6%8A%A5%E6%8E%A5%E5%8F%A3/v2.2} - * @author VirgilClyne - * @author WordlessEcho - * @param {"forecast" | "realtime"} type - `forecast` or `realtime` - * @param {coordinate} location - { latitude, longitude } - * @param {Object} headers - HTTP headers - * @return {Promise} data from "气象在线" - */ -const weatherOl = ( - type, - location, - headers = { 'Content-Type': 'application/json' }, -) => new Promise((resolve) => { - const apiVersion = 'v2.2'; - if (!isLocation(location)) { - // eslint-disable-next-line functional/no-expression-statement - resolve({ - status: 'failed', - error: `${weatherOl.name}: Invalid location: ${JSON.stringify(location)}`, - api_version: apiVersion, - }); - return; - } - - const request = { - headers, - url: 'https://www.weatherol.cn/api/minute/getPrecipitation' - + `?type=${type}` - + `&ll=${location.longitude},${location.latitude}`, - }; - - // eslint-disable-next-line functional/no-expression-statement - $.get(request, (error, response, data) => { - if (error || data === 'error') { - // eslint-disable-next-line functional/no-expression-statement - resolve({ - status: 'failed', - error: `${weatherOl.name}: ${error && `Error: ${error}\n`}Data: ${data}`, - api_version: apiVersion, - }); - return; - } - - const result = parseJson(data, (e) => ({ - status: 'failed', - error: `${weatherOl.name}: Data from WeatherOL is not a valid JSON\n` - + `Error: ${e}` - + `Data: ${data}`, - })); - - if (result?.status !== 'ok') { - const version = isNonNanNumber(result?.api_version) ? `v${result.api_version}` : apiVersion; - - // eslint-disable-next-line functional/no-expression-statement - resolve(isNonEmptyString(result?.status) - // The type of api_version during error will be number - ? { - ...result, - api_version: version, - } - : { - status: 'failed', - error: result?.message ?? `${weatherOl.name}: WeatherOL returned an unknown status\n` - + `Data: ${data}`, - api_version: version, - }); - return; - } - - // eslint-disable-next-line functional/no-expression-statement - resolve(result); - }); -}); - -/** - * Get data from ColorfulClouds. [简介 | 彩云天气 API]{@link https://docs.caiyunapp.com/docs/intro/} - * @author WordlessEcho - * @author shindgewongxj - * @param {string} token - Token for ColorfulClouds API - * @param {coordinate} location - Coordinate of location - * @param {string} language - Language from Apple Weather - * @param {Object} [headers] - HTTP headers - * @param {string} [apiVersion] - ColorfulClouds API version - * @param {"realtime" | "minutely" | "hourly" | "daily" | "weather"} [path] - - * @param {Object} [parameters] - Parameters pass to URL - * @return {Promise} Data from ColorfulClouds - */ -const colorfulClouds = ( - token, - location, - language, - headers = { 'Content-Type': 'application/json' }, - path = 'weather', - parameters = { unit: 'metric:v2' }, - apiVersion = 'v2.6', -) => { - /** - * Convert iOS-style language into the language supported by ColorfulClouds API. - * [语言 | 彩云天气 API]{@link https://docs.caiyunapp.com/docs/tables/lang} - * @author shindgewongxj - * @author WordlessEcho - * @param {string} languageWithReigon - "zh-Hans-CA", "en-US", "ja-CA" from Apple URL - * @returns {string} - `en_US` will be returned if language is not supported - */ - const toColorfulCloudsLang = (languageWithReigon) => { - if (isNonEmptyString(languageWithReigon)) { - if (/zh-(Hans|CN)/.test(languageWithReigon)) { - return 'zh_CN'; - } - if (/zh-(Hant|HK|TW)/.test(languageWithReigon)) { - return 'zh_TW'; - } - if (languageWithReigon.includes('en-GB')) { - return 'en_GB'; - } - if (languageWithReigon.includes('ja')) { - return 'ja'; - } - } - - return 'en_US'; - }; - - /** - * Return a valid API version for ColorfulClouds. - * @author WordlessEcho - * @param {string} version - API version to be checked - * @return {string} - API version for ColorfulClouds. - * `v2.6` will be returned if passed version is invalid. - */ - const checkCcApiVersion = (version) => { - if (isNonEmptyString(version) && version.startsWith('v')) { - const versionCode = parseFloat(version.slice(1)); - - if (!Number.isNaN(versionCode)) { - return version; - } - } - - return 'v2.6'; - }; - - /** - * Check the type of parameters - * @author WordlessEcho - * @param {string} uncheckedToken - Token of ColorfulClouds - * @param {coordinate} uncheckedLocation - Coordinate of location - * @return {string} - Error message to be returned. - * Empty string will be returned if all types of parameter are correct. - */ - const getError = (uncheckedToken, uncheckedLocation) => { - if (!isNonEmptyString(uncheckedToken)) { - return `${colorfulClouds.name}: Invalid token\n` - + `Token type: ${typeof uncheckedToken}\n` - + `Token length: ${uncheckedToken?.length}`; - } - - if (!isLocation(uncheckedLocation)) { - return `${colorfulClouds.name}: Invalid location: ${JSON.stringify(uncheckedLocation)}`; - } - - return ''; - }; - - return new Promise((resolve) => { - const validApiVersion = checkCcApiVersion(apiVersion); - const errorMessage = getError(token, location); - if (errorMessage.length > 0) { - // eslint-disable-next-line functional/no-expression-statement - resolve({ - status: 'failed', - error: errorMessage, - api_version: validApiVersion, - }); - return; - } - - const parametersString = isObject(parameters) - ? Object.entries({ lang: toColorfulCloudsLang(language), ...parameters }) - .map(([key, value]) => `${key}=${value}`).join('&') - : ''; - - const request = { - headers, - url: `https://api.caiyunapp.com/${apiVersion}/${token}/` - + `${location.longitude},${location.latitude}/` - // https://docs.caiyunapp.com/docs/weather/ - + `${path}?${parametersString}`, - }; - - // eslint-disable-next-line functional/no-expression-statement - $.get(request, (error, response, data) => { - if (error) { - // eslint-disable-next-line functional/no-expression-statement - resolve({ - status: 'failed', - error: `${colorfulClouds.name}: Error: ${error}\n` - + `Data: ${data}`, - api_version: validApiVersion, - }); - return; - } - - const result = parseJson(data, (e) => ({ - status: 'failed', - error: `${colorfulClouds.name}: Data from ColorfulClouds is not a valid JSON\n` - + `Error: ${e}\n` - + `Data: ${data}`, - })); - - if (result?.status !== 'ok') { - const version = isNonNanNumber(result?.api_version) ? `v${result.api_version}` : apiVersion; - - // eslint-disable-next-line functional/no-expression-statement - resolve(isNonEmptyString(result?.status) - // The type of api_version during error will be number - ? { - ...result, - api_version: version, - } - : { - status: 'failed', - error: `${colorfulClouds.name}: ColorfulClouds returned an unknown status\n` - + `Data: ${data}`, - api_version: version, - }); - return; - } - - // eslint-disable-next-line functional/no-expression-statement - resolve(result); - }); - }); -}; - -/** - * Convert timestamp to time in Apple Weather - * @author VirgilClyne - * @author WordlessEcho - * @param {supportedAppleApi} apiVersion - Apple Weather API Version - * @param {number} timestamp - UNIX timestamp - * @returns {number|string | ""} - UNIX time in seconds for APIv1, - * `YYYY-MM-DDTHH:MM:SSZ` format time for APIv2. Return empty string if api version is not valid. - */ -const toAppleTime = (apiVersion, timestamp) => { - const timeDate = isNonNanNumber(timestamp) && timestamp > 0 - ? (new Date(timestamp)) : (new Date()); - - switch (apiVersion) { - case 1: - return Math.trunc((+timeDate) / 1000); - case 2: - case 3: - return `${timeDate.toISOString().split('.')[0]}Z`; - default: - return ''; - } -}; - -/** - * Convert pollutant amount to another unit - * @author WordlessEcho - * @param {pollutantUnitsText} unit - Unit of amount - * @param {pollutantUnitsText} unitToConvert - Unit to convert - * @param {number} amount - Amount of pollutant - * @param {?supportedEpaVocs} pollutantName - For converting ppm or ppb to mg/m3 or ug/m3 - * @returns {number | -1} - - * Converted amount or -1 if converting unsupported VOCs or unsupported units - */ -const pollutantUnitConverterUs = (unit, unitToConvert, amount, pollutantName) => { - if (!isNonNanNumber(amount) || amount < 0) { - return -1; - } - - /** - * Calculated by - * ([Ozone AQI: Using concentrations in milligrams or ppb?]{@link https://aqicn.org/faq/2015-09-06/ozone-aqi-using-concentrations-in-milligrams-or-ppb/}, - * [Understanding Units of Measurement - Terrie K. Boguski, P.E. (CHSR)]{@link https://cfpub.epa.gov/ncer_abstracts/index.cfm/fuseaction/display.files/fileid/14285}): - * - * (amount * 12.187 * molecularWeight) / (temperatureInCelsius + 273.15) - * - * - 12.187 is the inverse of gas constant. - * - 273.15 is the 0 celsius in kelvin. - * - In US EPA, temperatureInCelsius is 25. In EU is 20. - * - * @type {Object.} - */ - const US_PPX_TO_XGM3 = { - NO2: 1.88, OZONE: 1.97, NO: 1.23, SO2: 2.62, CO: 1.14, - }; - - /** - * Check unit is ppm or ppb - * @author WordlessEcho - * @param {pollutantUnitsText} unitToCheck - Unit to be checked - * @returns {boolean} - True if unit is `ppm` or `ppb` - */ - const isPpx = (unitToCheck) => unitToCheck === 'ppm' || unitToCheck === 'ppb'; - - /** - * Check unit is mg/m3 or ug/m3 - * @author WordlessEcho - * @param {pollutantUnitsText} unitToCheck - Unit to be checked - * @returns {boolean} - True if unit is `milligramsPerM3` or `microgramsPerM3` - */ - const isXgM3 = (unitToCheck) => ( - unitToCheck === 'milligramsPerM3' || unitToCheck === 'microgramsPerM3' - ); - - if ((isPpx(unit) && isXgM3(unitToConvert)) || (isXgM3(unit) && isPpx(unitToConvert))) { - if (!Object.keys(US_PPX_TO_XGM3).includes(pollutantName)) { - return -1; - } - } - - switch (unit) { - case 'ppm': - switch (unitToConvert) { - case 'ppm': - return amount; - case 'ppb': - return amount * 1000; - case 'milligramsPerM3': - return amount * US_PPX_TO_XGM3[pollutantName]; - case 'microgramsPerM3': { - const inPpb = pollutantUnitConverterUs(unit, 'ppb', amount, pollutantName); - return inPpb * US_PPX_TO_XGM3[pollutantName]; - } - default: - return -1; - } - case 'ppb': - switch (unitToConvert) { - case 'ppb': - return amount; - case 'ppm': - return amount * 0.001; - case 'milligramsPerM3': { - const inPpm = pollutantUnitConverterUs(unit, 'ppm', amount, pollutantName); - return inPpm * US_PPX_TO_XGM3[pollutantName]; - } - case 'microgramsPerM3': - return amount * US_PPX_TO_XGM3[pollutantName]; - default: - return -1; - } - case 'milligramsPerM3': - switch (unitToConvert) { - case 'milligramsPerM3': - return amount; - case 'microgramsPerM3': - return amount * 1000; - case 'ppm': - return amount / US_PPX_TO_XGM3[pollutantName]; - case 'ppb': { - const inUgM3 = pollutantUnitConverterUs(unit, 'microgramsPerM3', amount, pollutantName); - return inUgM3 / US_PPX_TO_XGM3[pollutantName]; - } - default: - return -1; - } - case 'microgramsPerM3': - switch (unitToConvert) { - case 'microgramsPerM3': - return amount; - case 'milligramsPerM3': - return amount * 0.001; - case 'ppm': { - const inMgM3 = pollutantUnitConverterUs(unit, 'milligramsPerM3', amount, pollutantName); - return inMgM3 / US_PPX_TO_XGM3[pollutantName]; - } - case 'ppb': - return amount / US_PPX_TO_XGM3[pollutantName]; - default: - return -1; - } - default: - return -1; - } -}; - -/** - * Calculate AQI by AQI range and concentration breakpoints. - * [Technical Assistance Document for the Reporting of Daily Air Quality – the Air Quality Index (AQI)]{@link https://www.airnow.gov/sites/default/files/2020-05/aqi-technical-assistance-document-sept2018.pdf} - * [环境空气质量指数(AQI)技术规定(试行)]{@link https://www.mee.gov.cn/ywgz/fgbz/bz/bzwb/jcffbz/201203/W020120410332725219541.pdf} - * @author WordlessEcho - * @param {concentrationRange[]} concentrationRanges - concentrationBreakpoints - * @param {number} amount - Amount of pollutant - * @returns {number | -1} - Air quality index, -1 if amount is not a valid number - */ -const toEpaAqi = (concentrationRanges, amount) => { - if (Array.isArray(concentrationRanges) && isNonNanNumber(amount) && amount >= 0) { - const ranges = concentrationRanges.filter((r) => ( - isPositiveWithZeroRange(r?.AMOUNT) && isPositiveWithZeroRange(r?.AQI) - )); - - if (ranges.length > 0) { - const range = ranges.find(({ AMOUNT }) => amount >= AMOUNT.LOWER && amount <= AMOUNT.UPPER); - - if (isPositiveWithZeroRange(range?.AQI) && isPositiveWithZeroRange(range?.AMOUNT)) { - const { AQI, AMOUNT } = range; - return Math.round( - ((AQI.UPPER - AQI.LOWER) * (amount - AMOUNT.LOWER)) - / (AMOUNT.UPPER - AMOUNT.LOWER) + AQI.LOWER, - ); - } - - // Over range! - const topRange = ranges.reduce((previous, current) => ( - current.AMOUNT.UPPER > previous.AMOUNT.UPPER ? current : previous - )); - - // Or we just return `topRange.AQI.UPPER`? - if ( - isPositiveWithZeroRange(topRange?.AMOUNT) && isPositiveWithZeroRange(topRange?.AQI) - && amount > topRange.AMOUNT.UPPER - ) { - return Math.round(amount - topRange.AMOUNT.UPPER + topRange.AQI.UPPER); - } - } - } - - return -1; -}; - -/** - * Calculate amount of pollutants to AQIs - * @author WordlessEcho - * @param {Object.} concentrationsInfo - - * Amount breakpoints, AQI breakpoints and unit info of concentrations - * @param {pollutantV2[]} pollutants - Name, amount and unit info of pollutants - * @returns {aqiInfo} - */ -const toEpaAqis = (concentrationsInfo, pollutants) => { - if (!isObject(concentrationsInfo) || !Array.isArray(pollutants)) { - return { index: -1, pollutants: [] }; - } - - const concentrations = Object.fromEntries(Object.entries(concentrationsInfo) - .filter(([key, value]) => ( - isNonEmptyString(key) && isNonEmptyString(value?.UNIT) && isObject(value?.RANGES) - && !Object.values(value.RANGES).some((rangesForLevel) => ( - !isPositiveWithZeroRange(rangesForLevel?.AMOUNT) - || !isPositiveWithZeroRange(rangesForLevel?.AQI) - )) - ))); - - const pollutantAqis = pollutants - .filter((pollutant) => isNonEmptyString(pollutant?.name)) - .map((pollutant) => { - if ( - Object.keys(concentrations).includes(pollutant.name) && isNonEmptyString(pollutant?.unit) - && isNonNanNumber(pollutant?.amount) && pollutant.amount >= 0 - ) { - const { name, unit, amount } = pollutant; - const concentration = concentrations[name]; - const convertedAmount = unit === concentration.UNIT ? amount - : pollutantUnitConverterUs(unit, concentration.UNIT, amount, name); - - return { name, aqi: toEpaAqi(Object.values(concentration.RANGES), convertedAmount) }; - } - - return { name: pollutant.name, aqi: -1 }; - }); - - const validAqis = pollutantAqis?.filter(({ aqi }) => aqi !== -1); - const primary = Array.isArray(validAqis) && validAqis.length > 0 ? validAqis.reduce( - (previous, current) => (current.aqi > previous.aqi ? current : previous), - ) : { aqi: -1 }; - - return { - index: primary.aqi, - ...(isNonEmptyString(primary?.name) && { primary: primary.name }), - pollutants: pollutantAqis, - }; -}; - -/** - * Calculate Air Quality Level - * @author WordlessEcho - * @author VirgilClyne - * @param {aqiLevel[]} aqiLevels - Breakpoints of AQI - * @param {number} aqi - Air quality index - * @returns {number | -1} - -1 if AQI or aqiLevels is invalid. - * `topLevel.VALUE` + 1 will be returned if no matched ranges. - */ -const toAqiLevel = (aqiLevels, aqi) => { - if (Array.isArray(aqiLevels) && isNonNanNumber(aqi) && aqi >= 0) { - const levels = aqiLevels.filter((level) => ( - isPositiveWithZeroRange(level?.RANGE) && isNonNanNumber(level?.VALUE) && level.VALUE > 0 - )); - - const level = levels.find(({ RANGE }) => (aqi >= RANGE.LOWER && aqi <= RANGE.UPPER)); - - if (isNonNanNumber(level?.VALUE)) { - return level.VALUE; - } - - const topLevel = levels.length > 0 && levels.reduce((previous, current) => ( - current.VALUE > previous.VALUE ? current : previous - )); - - if (isNonNanNumber(topLevel?.VALUE) && aqi > topLevel.RANGE.UPPER) { - return topLevel.VALUE + 1; - } - } - - return -1; -}; - -/** - * Compare Air Quality Levels - * @author WordlessEcho - * @param {number} aqiLevelA - Value from {@link toAqiLevel} to compare - * @param {number} aqiLevelB - Value from {@link toAqiLevel} to be compared - * @returns {aqiComparison} - Value for `AirQuality.previousDayComparison`. - * `unknown` will be returned if aqiLevel is invalid. - */ -const compareAqi = (aqiLevelA, aqiLevelB) => { - if ( - !isNonNanNumber(aqiLevelA) || !isNonNanNumber(aqiLevelB) || aqiLevelA <= 0 || aqiLevelB <= 0 - ) { - return 'unknown'; - } - - if (aqiLevelA > aqiLevelB) { - return 'worse'; - } if (aqiLevelA < aqiLevelB) { - return 'better'; - } - - return 'same'; -}; - -/** - * Fix unit of CO from QWeather - * @author WordlessEcho - * @param {pollutantUnitsV2|pollutantUnitsV1} unit - Unit of CO - * @param {number} amount - Amount of CO - * @return {number | -1} - Converted CO amount. -1 will be returned if amount is invalid. - * Amount will not be converted if unit is not `microgramsPerM3`. - */ -const fixQweatherCo = (unit, amount) => { - if (!isNonNanNumber(amount) || amount < 0) { - return -1; - } - - if (unit === 'µg/m3' || unit === 'microgramsPerM3') { - const mgAmount = pollutantUnitConverterUs( - 'microgramsPerM3', - HJ_633.CONCENTRATIONS.CO.UNIT, - amount, - 'CO', - ); - - if (mgAmount < 0.1) { - const fixedAmount = pollutantUnitConverterUs( - HJ_633.CONCENTRATIONS.CO.UNIT, - 'microgramsPerM3', - amount, - 'CO', - ); - - logger( - 'debug', - `${fixQweatherCo.name}:已修复一氧化碳含量,原始值:${amount}ug/m3,修复值:${fixedAmount}ug/m3`, - ); - return fixedAmount; - } - } - - return amount; -}; - -/** - * Convert pollutant unit into Apple APIv2 style - * @author WordlessEcho - * @param {pollutantV1[]} pollutants - Pollutants in Apple APIv1 format - * @return {pollutantV2[]} - Pollutants in Apple APIv2 format - */ -const convertV1Pollutants = (pollutants) => { - const units = ['ppb', 'µg/m3']; - const validPollutants = Array.isArray(pollutants) ? pollutants.filter( - (pollutant) => ( - units.includes(pollutant?.unit) && isNonEmptyString(pollutant?.name) - && isNonNanNumber(pollutant?.amount) && pollutant.amount >= 0 - ), - ) : []; - - return validPollutants.map((pollutant) => ({ - ...pollutant, unit: pollutant.unit === 'µg/m3' ? 'microgramsPerM3' : pollutant.unit, - })); -}; - -/** - * Convert pollutants from Apple to specific EPA standard - * @author WordlessEcho - * @param {aqiStandard} standard - EPA standard to convert - * @param {pollutantV2[]} pollutants - Pollutants in Apple APIv2 format - * @return {airQualityObject} - Object for {@link toAirQuality} - */ -const appleToEpaAirQuality = (standard, pollutants) => { - if ( - !isObject(standard) || !Array.isArray(pollutants) || !isObject(standard?.CONCENTRATIONS) - || !isObject(standard?.AQI_LEVELS) - ) { - return {}; - } - - const validConcentrations = Object.fromEntries(Object.entries(standard.CONCENTRATIONS).filter( - ([, value]) => ( - isNonEmptyString(value?.UNIT) && isObject(value?.RANGES) - && !Object.values(value.RANGES).includes((v) => ( - !isPositiveRange(v?.AMOUNT) || !isPositiveWithZeroRange(v?.AQI) - )) - ), - )); - - if (Object.keys(validConcentrations) <= 0) { - return {}; - } - - const units = ['ppb', 'microgramsPerM3']; - const validPollutants = pollutants.filter((pollutant) => ( - Object.keys(validConcentrations).includes(pollutant?.name) && units.includes(pollutant?.unit) - && isNonNanNumber(pollutant?.amount) && pollutant.amount >= 0 - )); - - const aqis = toEpaAqis(validConcentrations, validPollutants); - if (!isNonNanNumber(aqis.index) || aqis.index < 0) { - return {}; - } - - const validAqiLevelValues = Object.values(standard.AQI_LEVELS).filter((level) => ( - isNonNanNumber(level.VALUE) && isPositiveWithZeroRange(level.RANGE) - )); - - const topAqiLevelValue = validAqiLevelValues.length > 0 - ? Math.max(...validAqiLevelValues.map(({ VALUE }) => VALUE)) : -1; - const aqiLevel = toAqiLevel(validAqiLevelValues, aqis.index); - const categoryIndex = aqiLevel > 0 && aqiLevel > topAqiLevelValue ? topAqiLevelValue : aqiLevel; - - return { - isSignificant: categoryIndex >= standard.SIGNIFICANT_LEVEL, - ...(isNonEmptyString(aqis?.primary) && { primary: aqis.primary }), - categoryIndex, - aqi: aqis.index, - scale: standard.APPLE_SCALE, - }; -}; - -/** - * Get air quality from ColorfulClouds - * @author WordlessEcho - * @param {colorfulCloudsV2} dataWithRealtime - Data from ColorfulClouds with air quality info - * @return {ccAirQuality | {aqi: {usa: -1, chn: -1}}} - Air quality data in v2.4+ format - */ -const getCcAirQuality = (dataWithRealtime) => { - const toColorfulCloudsNames = { - NO2: 'no2', 'PM2.5': 'pm25', SO2: 'so2', OZONE: 'o3', PM10: 'pm10', CO: 'co', aqi: 'aqi', - }; - - const apiVersion = dataWithRealtime?.api_version; - const versionCode = isNonEmptyString(apiVersion) && apiVersion.startsWith('v') - && parseFloat(apiVersion.slice(1)); - const validVersionCode = isNonNanNumber(versionCode) ? versionCode : -1; - - // https://open.caiyunapp.com/%E5%BD%A9%E4%BA%91%E5%A4%A9%E6%B0%94_API/v2.5#.E6.A0.BC.E5.BC.8F.E5.8F.98.E6.9B.B4 - // https://docs.caiyunapp.com/docs/v2.4/intro#%E4%B8%8D%E5%85%BC%E5%AE%B9%E7%9A%84%E6%9B%B4%E6%96%B0 - if (validVersionCode >= 2.2 && validVersionCode < 3) { - const airQuality = validVersionCode >= 2.4 - ? dataWithRealtime?.result?.realtime?.air_quality - : dataWithRealtime?.result; - - if (isObject(airQuality)) { - const result = Object.fromEntries(Object.keys(toColorfulCloudsNames).map((key) => { - const value = airQuality?.[toColorfulCloudsNames[key]]; - - if (key === 'aqi') { - const chnAqi = validVersionCode >= 2.4 ? value?.chn : value; - const usaAqi = validVersionCode >= 2.4 ? value?.usa : -1; - - return [key, { - usa: isNonNanNumber(usaAqi) && usaAqi >= 0 ? usaAqi : -1, - chn: isNonNanNumber(chnAqi) && chnAqi >= 0 ? chnAqi : -1, - }]; - } - - return [ - key, - isNonNanNumber(value) && value >= 0 ? value : -1, - ]; - })); - - // Detect the support of air quality - if ( - Object.values(result).filter((value) => isNonNanNumber(value) && value <= 0).length <= 1 - ) { - logger('debug', `${getCcAirQuality.name}:美标:${result.aqi.usa},国标:${result.aqi.chn}`); - return result; - } - } - - logger('error', `${getCcAirQuality.name}:缺少空气质量数据`); - // eslint-disable-next-line functional/no-conditional-statement - } else { - logger('error', `${getCcAirQuality.name}:不支持${apiVersion}版本的API`); - } - - return { aqi: { usa: -1, chn: -1 } }; -}; - -/** - * Convert seconds to timezone - * @author WordlessEcho - * @param {number} minutes - Timezone in minutes - * @return {string} - Timezone in "+/-HH:MM" format - */ -const minutesToIsoTimezone = (minutes) => { - const validOffset = isNonNanNumber(minutes) ? minutes : (new Date()).getTimezoneOffset(); - return `${validOffset < 0 ? '-' : '+'}` - + `${Math.floor(Math.abs(validOffset / 60)).toString().padStart(2, '0')}` - + `:${(Math.abs(validOffset % 60)).toString().padStart(2, '0')}`; -}; - -/** - * Get history AQI from ColorfulClouds - * @author WordlessEcho - * @param {colorfulCloudsV2} historyData - Data with history from ColorfulClouds - * @param {number} timestamp - Timestamp of data to get - * @return {{usa: number | -1, chn: number | -1}} - Air quality index - */ -const colorfulCloudsHistoryAqi = (historyData, timestamp) => { - if (!isNonNanNumber(timestamp) || timestamp <= 0) { - return { usa: -1, chn: -1 }; - } - - const apiVersion = historyData?.api_version; - const versionCode = isNonEmptyString(apiVersion) && apiVersion.startsWith('v') - && parseFloat(apiVersion.slice(1)); - const validVersionCode = isNonNanNumber(versionCode) ? versionCode : -1; - - const hourTimestamp = (new Date(timestamp)).setMinutes(0, 0, 0); - - const historyAqis = validVersionCode >= 2.4 - ? historyData?.result?.hourly?.air_quality?.aqi - : historyData?.result?.hourly?.aqi; - - // An hour as range - const aqis = historyAqis?.find((aqi) => { - if (!isNonEmptyString(aqi?.datetime)) { - return false; - } - - // https://docs.caiyunapp.com/docs/v2.3/intro#%E4%B8%8D%E5%85%BC%E5%AE%B9%E7%9A%84%E6%9B%B4%E6%96%B0 - if ( - validVersionCode < 2.3 && (!isNonNanNumber(historyData?.tzshift) - || historyData.tzshift % 60 !== 0) - ) { - return false; - } - const ts = Date.parse(validVersionCode < 2.3 - ? `${aqi.datetime.replace(' ', 'T')}:00.000${minutesToIsoTimezone(historyData.tzshift / 60)}` - : aqi.datetime.split('+').join(':00.000+')); - - return isNonNanNumber(ts) && ts > 0 - && ts >= hourTimestamp && ts < hourTimestamp + 1000 * 60 * 60; - }); - - const usaAqi = validVersionCode >= 2.4 ? aqis?.value?.usa : -1; - const chnAqi = validVersionCode >= 2.4 ? aqis?.value?.chn : aqis?.value; - - return { - usa: isNonNanNumber(usaAqi) && usaAqi >= 0 ? usaAqi : -1, - chn: isNonNanNumber(chnAqi) && chnAqi >= 0 ? chnAqi : -1, - }; -}; - -/** - * Compare AQI to the yesterday from ColorfulClouds - * @author WordlessEcho - * @param {colorfulCloudsV2} realtimeAndHistoryData - - * Data with realtime and history from ColorfulClouds - * @param {boolean} forceChn - Use `aqi.chn` by force - * @return {aqiComparison} - Result of comparison for `previousDayComparison` - */ -const colorfulCloudsToAqiComparison = (realtimeAndHistoryData, forceChn) => { - const airQuality = getCcAirQuality(realtimeAndHistoryData); - - const serverTime = realtimeAndHistoryData?.server_time; - const serverTimestamp = isNonNanNumber(serverTime) && serverTime > 0 - ? serverTime * 1000 : (+new Date()); - const reportedTimestamp = (new Date(serverTimestamp)).setMinutes(0, 0, 0); - const yesterdayTimestamp = reportedTimestamp - 1000 * 60 * 60 * 24; - - const todayAqi = airQuality.aqi; - const yesterdayAqi = colorfulCloudsHistoryAqi(realtimeAndHistoryData, yesterdayTimestamp); - - if ((typeof forceChn !== 'boolean' || !forceChn) && todayAqi.usa >= 0 && yesterdayAqi.usa >= 0) { - const todayAqiLevel = toAqiLevel(Object.values(EPA_454.AQI_LEVELS), todayAqi.usa); - const yesterdayAqiLevel = toAqiLevel(Object.values(EPA_454.AQI_LEVELS), yesterdayAqi.usa); - - return compareAqi(todayAqiLevel, yesterdayAqiLevel); - } - - if (todayAqi.chn >= 0 && yesterdayAqi.chn >= 0) { - const todayAqiLevel = toAqiLevel(Object.values(HJ_633.AQI_LEVELS), todayAqi.chn); - const yesterdayAqiLevel = toAqiLevel(Object.values(HJ_633.AQI_LEVELS), yesterdayAqi.chn); - - return compareAqi(todayAqiLevel, yesterdayAqiLevel); - } - - logger( - 'error', - `${colorfulCloudsToAqiComparison.name}:无法找到AQI,今日:${JSON.stringify(todayAqi)}` - + `,昨日:${JSON.stringify(yesterdayAqi)}`, - ); - return 'unknown'; -}; - -/** - * Get AQI comparison from WAQI AQI feed - * @param {waqiAqiFeedRxsObsMsg} aqiFeedMsg - * @return {aqiComparison} - */ -const waqiV1AqiToAqiComparison = (aqiFeedMsg) => { - const obsData = aqiFeedMsg?.obs; - if (!obsData) { - return 'unknown'; - } - - const pollutantNames = ['co', 'no2', 'o3', 'pm10', 'pm25', 'so2']; - - const parsedHistoryData = Object.fromEntries( - Object.entries(obsData).map(([pollutantName, value]) => { - const getTimestamp = (array, index) => { - const isoTime = value.s.replace(' ', 'T'); - const baseTime = (+(new Date(isoTime))); - if (index === 0) { - return baseTime; - } - - return array.slice(0, index + 1).map((v) => (isNonNanNumber(v) ? baseTime : v[1] * 1000)) - .reduce((previous, current) => previous + current); - }; - - const getAqi = (array, index) => { - const relative = array[index]; - const relativeValidValue = isNonNanNumber(relative) ? relative : relative[0]; - const decimal = value.m; - if (index === 0) { - return relativeValidValue / decimal; - } - - return array.slice(0, index + 1).map((v) => (isNonNanNumber(v) ? v : v[0])) - .reduce((previous, current) => previous + current) / decimal; - }; - - return [pollutantName, value.v.map((v, index, array) => ({ - timestamp: getTimestamp(array, index), - aqi: getAqi(array, index), - }))]; - }), - ); - - const isoTime = aqiFeedMsg?.time?.iso; - const timestamp = isNonEmptyString(isoTime) - ? Date.parse(`${isoTime.slice(0, -6)}.000${isoTime.slice(-6)}`) : (+(new Date())); - const nowHourTimestamp = isNonNanNumber(timestamp) ? (new Date(timestamp)).setMinutes(0, 0, 0) - : (new Date()).setMinutes(0, 0, 0); - const yesterdayTimestamp = nowHourTimestamp - 1000 * 60 * 60 * 24; - const yesterdayAqis = pollutantNames.map((name) => { - const historyData = parsedHistoryData?.[name]; - if (Array.isArray(historyData)) { - const aqi = historyData.find((history) => ( - isNonNanNumber(history?.timestamp) && history.timestamp >= yesterdayTimestamp - && history.timestamp < yesterdayTimestamp + 1000 * 60 * 60 - ))?.aqi; - - if (isNonNanNumber(aqi)) { - return aqi; - } - } - - logger('warn', `${waqiV1AqiToAqiComparison.name}:缺失${name}的历史数据`); - return -1; - }); - const yesterdayAqi = Math.max(...yesterdayAqis); - const todayAqi = aqiFeedMsg?.aqi; - - if ( - !isNonNanNumber(yesterdayAqi) || yesterdayAqi < 0 || !isNonNanNumber(todayAqi) || todayAqi < 0 - ) { - logger('error', `${waqiV1AqiToAqiComparison.name}:无法找到AQI,今日AQI = ${todayAqi},昨日AQI = ${yesterdayAqi}`); - - return 'unknown'; - } - - return compareAqi( - toAqiLevel(Object.values(WAQI_INSTANT_CAST.AQI_LEVELS), todayAqi), - toAqiLevel(Object.values(WAQI_INSTANT_CAST.AQI_LEVELS), yesterdayAqi), - ); -}; - -/** - * Convert data from ColorfulClouds to air quality metadata - * @author WordlessEcho - * @param {providerLogo} providerLogo - URL to the provider logo - * @param {string} providerName - Name of the provider - * @param {string} url - URL to the provider - * @param {colorfulCloudsV2} data - Data from ColorfulClouds - * @return {metadataObject | {}} - Object for {@link toMetadata} - */ -const colorfulCloudsToAqiMetadata = (providerLogo, providerName, url, data) => { - const language = data?.lang; - const location = { latitude: data?.location?.[0], longitude: data?.location?.[1] }; - // the unit of server_time is second - const serverTime = data?.server_time; - const serverTimestamp = isNonNanNumber(serverTime) && serverTime > 0 - ? serverTime * 1000 : (+(new Date())); - - const reportedTimestamp = (new Date(serverTimestamp)).setMinutes(0, 0, 0); - const expireTimestamp = reportedTimestamp + 1000 * 60 * 60; - const validExpireTimestamp = expireTimestamp > (+(new Date())) - ? expireTimestamp : (+(new Date())) + 1000 * 60 * 15; - - const validProviderLogo = { - ...(isNonEmptyString(providerLogo?.forV1) && { forV1: providerLogo.forV1 }), - ...(isNonEmptyString(providerLogo?.forV2) && { forV2: providerLogo.forV2 }), - }; - - const variableMetadata = { - ...(isNonEmptyString(language) && { language: language.replace('_', '-') }), - ...(isLocation(location) && { location }), - ...(Object.keys(validProviderLogo).length > 0 && { providerLogo: validProviderLogo }), - ...(isNonEmptyString(providerName) && { providerName }), - url, - }; - - return Object.keys(variableMetadata).length > 0 ? { - ...variableMetadata, - expireTimestamp: validExpireTimestamp, - readTimestamp: serverTimestamp, - reportedTimestamp, - dataSource: 1, - // https://developer.apple.com/documentation/weatherkitrestapi/unitssystem - unit: 'm', - } : {}; -}; - -/** - * Convert data from ColorfulClouds to object for {@link toAirQuality} - * @author WordlessEcho - * @param {colorfulCloudsV2} realtimeAndHistoryData - - * Data with realtime and history from ColorfulClouds - * @param {string} url - Link to AQI info - * @param {string} providerName - Name of the provider for `source` of Apple Weather - * @param {boolean} aqiForceChn - Use `aqi.chn` by force for AQI - * @param {boolean} comparisonForceChn - Use `aqi.chn` by force for comparison - * @return {airQualityObject | {}} - Object for {@link toAirQuality} - */ -const colorfulCloudsToAqi = ( - realtimeAndHistoryData, - url, - providerName, - aqiForceChn, - comparisonForceChn, -) => { - /** - * Get AQI standard based on existed data - * @author WordlessEcho - * @param {boolean} hasUsa - Existence of aqi.usa - * @param {boolean} forceChn - Use aqi.chn by force - * @return {aqiStandard} - AQI standard - */ - const getCcStandard = (hasUsa, forceChn) => ( - typeof hasUsa !== 'boolean' || !hasUsa || (typeof forceChn === 'boolean' && forceChn) - ? { - ...HJ_633, - CONCENTRATIONS: { - ...HJ_633.CONCENTRATIONS, - PM10: HJ_633.CONCENTRATIONS.PM10_24H, - 'PM2.5': HJ_633.CONCENTRATIONS['PM2.5_24H'], - }, - } // TODO: EPA NowCast - : WAQI_INSTANT_CAST - ); - - /** - * Convert data from {@link getCcAirQuality} to {@link pollutantV2} object for Apple Weather - * @author WordlessEcho - * @param {ccAirQuality} airQuality - Pollutants data from {@link getCcAirQuality} - * @return {pollutantV2[] | []} - - * Object for `airQuality.pollutants` of Apple Weather - */ - const toPollutants = (airQuality) => (isObject(airQuality) - ? Object.entries(airQuality) - .filter(([key]) => key !== 'aqi') - .map(([pollutantName, amount]) => ({ - name: pollutantName, - amount: pollutantName === 'CO' ? pollutantUnitConverterUs( - 'milligramsPerM3', - 'microgramsPerM3', - amount, - null, - ) : amount, - unit: 'microgramsPerM3', - })) - : []); - - const airQuality = getCcAirQuality(realtimeAndHistoryData); - const standard = getCcStandard(airQuality.aqi.usa >= 0, aqiForceChn); - - const aqi = standard.APPLE_SCALE === EPA_454.APPLE_SCALE - ? airQuality.aqi.usa : airQuality.aqi.chn; - if (!isNonNanNumber(aqi) || aqi < 0) { - return {}; - } - - const categoryIndex = toAqiLevel(Object.values(standard.AQI_LEVELS), aqi); - const pollutants = toPollutants(airQuality); - const primaryPollutant = toEpaAqis(standard.CONCENTRATIONS, pollutants)?.primary; - - return { - isSignificant: categoryIndex >= (isNonNanNumber(standard.SIGNIFICANT_LEVEL) - ? standard.SIGNIFICANT_LEVEL : Number.MAX_VALUE), - url: isNonEmptyString(url) ? url : 'https://caiyunapp.com/weather/', - pollutants, - // Primary pollutant must be included in pollutants - ...(isNonEmptyString(primaryPollutant) && { primaryPollutant }), - sourceName: isNonEmptyString(providerName) ? providerName : 'ColorfulClouds', - categoryIndex, - aqi, - scale: standard.APPLE_SCALE, - previousDayComparison: - colorfulCloudsToAqiComparison(realtimeAndHistoryData, comparisonForceChn), - sourceType: 'modeled', - }; -}; - -/** - * Convert data from WAQI to air quality metadata - * @author WordlessEcho - * @param {waqiFeed} feedData - Feed data from WAQI - * @return {metadataObject | {}} - Object for {@link toMetadata} - */ -const waqiToAqiMetadata = (feedData) => { - const location = { - latitude: feedData?.data?.city?.geo?.[0], - longitude: feedData?.data?.city?.geo?.[1], - }; - if (!isLocation(location)) { - return {}; - } - - const serverTimestamp = Date.parse(feedData?.data?.time?.iso); - const validServerTimestamp = isNonNanNumber(serverTimestamp) && serverTimestamp > 0 - ? serverTimestamp : (+(new Date())); - - const reportedTimestamp = (new Date(validServerTimestamp)).setMinutes(0, 0, 0); - const expireTimestamp = reportedTimestamp + 1000 * 60 * 60; - const validExpireTimestamp = expireTimestamp > (+(new Date())) - ? expireTimestamp : (+(new Date())) + 1000 * 60 * 15; - - return { - language: 'en-US', - location, - expireTimestamp: validExpireTimestamp, - providerLogo: { - forV1: 'https://waqi.info/images/logo.png', - forV2: 'https://raw.githubusercontent.com/VirgilClyne/iRingo/main/image/waqi.info.logo.png', - }, - providerName: isNonEmptyString(feedData?.data?.city?.name) - ? `${feedData.data.city.name} (The World Air Quality Project)` - : 'The World Air Quality Project', - readTimestamp: serverTimestamp, - reportedTimestamp, - dataSource: 0, - // https://developer.apple.com/documentation/weatherkitrestapi/unitssystem - unit: 'm', - url: 'https://waqi.info', - }; -}; - -/** - * Covert data from WAQI to object for {@link toAirQuality} - * @author WordlessEcho - * @param {waqiFeed} feedData - Data with AQI from WAQI feed - * @return {airQualityObject | {}} - Object for {@link toAirQuality} - */ -const waqiToAqi = (feedData) => { - // TODO: Find other pollutant names for WAQI - const toApplePollutantName = { - no2: 'NO2', no: 'NO', nox: 'NOX', pm25: 'PM2.5', so2: 'SO2', o3: 'OZONE', pm10: 'PM10', co: 'CO', other: 'OTHER', - }; - - const aqi = feedData?.data?.aqi; - if (!isNonNanNumber(aqi) || aqi < 0) { - return {}; - } - - const validAqi = typeof aqi === 'number' && !Number.isNaN(aqi) && aqi >= 0 ? aqi : -1; - const categoryIndex = toAqiLevel(Object.values(WAQI_INSTANT_CAST.AQI_LEVELS), validAqi); - - return isNonNanNumber(aqi) && aqi >= 0 ? { - isSignificant: categoryIndex >= (isNonNanNumber(WAQI_INSTANT_CAST.SIGNIFICANT_LEVEL) - ? WAQI_INSTANT_CAST.SIGNIFICANT_LEVEL : Number.MAX_VALUE), - url: isNonEmptyString(feedData?.data?.city?.url) ? feedData.data.city.url : 'https://aqicn.org/', - // Pollutant data from WAQI is AQI not amount - pollutants: [], - ...(Object.keys(toApplePollutantName).includes(feedData?.data?.dominentpol) - && { primary: toApplePollutantName[feedData.data.dominentpol] }), - sourceName: isNonEmptyString(feedData?.data?.city?.name) ? feedData.data.city.name : '', - categoryIndex, - aqi: validAqi, - scale: WAQI_INSTANT_CAST.APPLE_SCALE, - previousDayComparison: 'unknown', - sourceType: 'station', - } : {}; -}; - -/** - * Mapping the precipitation level ranges to 3 level of ranges of Apple - * @author WordlessEcho - * @param {precipitationLevels} precipitationLevels - Range of each precipitation level - * @param {number} precipitation - Value of precipitation - * @return {number} - Value for `forecastNextHour.minutes[].precipIntensityPerceived`. - * 0 will be returned if precipitation levels or precipitation is invalid. - */ -const toPerceived = (precipitationLevels, precipitation) => { - const levels = isObject(precipitationLevels) - ? Object.values(precipitationLevels).filter((level) => ( - isPositiveWithZeroRange(level.RANGE) && isNonNanNumber(level.VALUE) - && level.VALUE >= 0 && level.VALUE <= 3 - )) - : []; - - if (levels.length > 0 && isNonNanNumber(precipitation) && precipitation >= 0) { - const topLevel = levels.reduce((previous, current) => ( - current.VALUE > previous.VALUE ? current : previous - )); - - if (precipitation > topLevel.RANGE.UPPER) { - return topLevel.VALUE; - } - - const currentLevel = levels.find(({ RANGE }) => ( - precipitation >= RANGE.LOWER && precipitation < RANGE.UPPER - )); - const lastLevel = levels.find(({ VALUE }) => VALUE === currentLevel.VALUE - 1); - - if ( - isPositiveWithZeroRange(currentLevel?.RANGE) && isPositiveWithZeroRange(lastLevel?.RANGE) - && isNonNanNumber(currentLevel?.VALUE) && isNonNanNumber(lastLevel?.VALUE) - ) { - return currentLevel.VALUE > 0 - ? lastLevel.VALUE + (((precipitation - lastLevel.RANGE.UPPER) * 1000) - / ((currentLevel.RANGE.UPPER - currentLevel.RANGE.LOWER) * 1000)) - : 0; - } - } - - return 0; -}; - -/** - * Get weather status for `NextHourForecast.condition[].token` - * @author WordlessEcho - * @param {precipitationTypes} precipitationType - Type of precipitation - * @param {number} precipitationIntensityPerceived - Apple precipitation. - * Can be generated from {@link toPerceived} - * @returns {weatherStatuses} - Weather status of current type and precipitation - */ -const perceivedToStatus = (precipitationType, precipitationIntensityPerceived) => { - if (precipitationType === 'clear' || precipitationIntensityPerceived <= 0) { - return 'clear'; - } - - if (precipitationType === 'rain' || precipitationType === 'snow') { - if (precipitationIntensityPerceived <= 1) { - return precipitationType === 'rain' ? 'drizzle' : 'flurries'; - } - if (precipitationIntensityPerceived <= 2) { - return precipitationType === 'rain' ? 'rain' : 'snow'; - } - - return precipitationType === 'rain' ? 'heavy-rain' : 'heavy-snow'; - } - - return precipitationType; -}; - -/** - * Convert weather status to precipitation type - * @author WordlessEcho - * @param {weatherStatuses} weatherStatus - Weather status to be converted - * @returns {precipitationTypes} - `precipitation` will be returned if precipitation is invalid. - */ -const weatherStatusToType = (weatherStatus) => { - switch (weatherStatus) { - case 'clear': - case 'sleet': - case 'hail': - case 'mixed': - return weatherStatus; - case 'flurries': - case 'snow': - case 'heavy-snow': - return 'snow'; - case 'drizzle': - case 'rain': - case 'heavy-rain': - return 'rain'; - default: - return 'precipitation'; - } -}; - -/** - * Transfer numbers into ordinal numerals. [Source code]{@link https://stackoverflow.com/a/20426113} - * @author WordlessEcho - * @param {number} number - Number to transfer - * @return {string} - Ordinal numeral of given number. - * Empty string will be returned if given number is invalid. - */ -const stringifyNumber = (number) => { - const special = [ - 'zeroth', 'first', 'second', 'third', 'fourth', 'fifth', 'sixth', 'seventh', 'eighth', - 'ninth', 'tenth', 'eleventh', 'twelfth', 'thirteenth', 'fourteenth', 'fifteenth', - 'sixteenth', 'seventeenth', 'eighteenth', 'nineteenth', - ]; - const deca = ['twent', 'thirt', 'fort', 'fift', 'sixt', 'sevent', 'eight', 'ninet']; - - if (!isNonNanNumber(number) || number < 0) { - return ''; - } - - if (number < 20) { - return special[number]; - } - - if (number % 10 === 0) { - return `${deca[Math.floor(number / 10) - 2]}ieth`; - } - - return `${deca[Math.floor(number / 10) - 2]}y-${special[number % 10]}`; -}; - -/** - * Convert data from ColorfulClouds to next hour metadata - * @author WordlessEcho - * @param {string} providerName - Name of the provider - * @param {string} url - URL to the data - * @param {colorfulCloudsV2} data - Data from ColorfulClouds - * @return { - * appleNextHourWithoutMetadataV1|appleNextHourWithoutMetadataV2|appleNextHourWithoutMetadataV3 | {} - * } - Object for {@link toMetadata} - */ -const colorfulCloudsToNextHourMetadata = (providerName, url, data) => { - const language = data?.lang; - const location = { latitude: data?.location?.[0], longitude: data?.location?.[1] }; - const nowTimestamp = (+(new Date())); - // the unit of server_time is second - const serverTime = data?.server_time; - const serverTimestamp = isNonNanNumber(serverTime) && serverTime > 0 - ? serverTime * 1000 : (+(new Date())); - const expireTimestamp = serverTimestamp + 1000 * 60 * 15; - - return { - language: isNonNanNumber(language) ? language.replace('_', '-') : 'zh-CN', - ...(isLocation(location) && { location }), - expireTimestamp: expireTimestamp < nowTimestamp - ? nowTimestamp + 1000 * 60 * 5 : expireTimestamp, - providerName: isNonEmptyString(providerName) ? providerName : 'ColorfulClouds', - readTimestamp: serverTimestamp, - dataSource: 1, - // https://developer.apple.com/documentation/weatherkitrestapi/unitssystem - unit: 'm', - url, - }; -}; - -/** - * Covert data from ColorfulClouds to minutes for {@link toNextHour} - * @author WordlessEcho - * @param {string} providerName - Name of the provider. Will be used as placeholder. - * @param {colorfulCloudsV2} dataWithMinutely - Data from ColorfulClouds with minutely - * @return {nextHourObject | {}} nextHourObject for {@link toNextHour} - */ -const colorfulCloudsToNextHour = (providerName, dataWithMinutely) => { - const supportedCcApis = [2]; - const supportedUnits = ['metric:v2', 'metric:v1', 'metric']; - - // [降水强度 | 彩云天气 API]{@link https://docs.caiyunapp.com/docs/tables/precip} (v2.6) - const radarLevels = { - NO: { VALUE: 0, RANGE: { LOWER: 0, UPPER: 0.031 } }, - LIGHT: { VALUE: 1, RANGE: { LOWER: 0.031, UPPER: 0.25 } }, - MODERATE: { VALUE: 2, RANGE: { LOWER: 0.25, UPPER: 0.35 } }, - HEAVY: { VALUE: 3, RANGE: { LOWER: 0.35, UPPER: 0.48 } }, - STORM: { VALUE: 4, RANGE: { LOWER: 0.48, UPPER: Number.MAX_VALUE } }, - }; - - // [降水强度 | 彩云天气 API]{@link https://docs.caiyunapp.com/docs/tables/precip} (v2.6) - const mmPerHourLevels = { - NO: { VALUE: 0, RANGE: { LOWER: 0, UPPER: 0.08 } }, - LIGHT: { VALUE: 1, RANGE: { LOWER: 0.08, UPPER: 3.44 } }, - MODERATE: { VALUE: 2, RANGE: { LOWER: 3.44, UPPER: 11.33 } }, - HEAVY: { VALUE: 3, RANGE: { LOWER: 11.33, UPPER: 51.30 } }, - STORM: { VALUE: 4, RANGE: { LOWER: 51.30, UPPER: Number.MAX_VALUE } }, - }; - - const KM = { - zh_CN: '公里', - zh_TW: '公里', - // kilometers - ja: 'キロメートル', - en_US: 'km', - en_GB: 'km', - }; - - /** - * Get precipitation type from ColorfulClouds hourly skycons. - * [天气现象 | 彩云天气API]{@link https://docs.caiyunapp.com/docs/tables/skycon/} - * @author WordlessEcho - * @param {number} timestamp - UNIX timestamp of server time - * @param {colorfulCloudsV2} dataWithHourlySkycons - Date with hourly.skycon[] from ColorfulClouds - * @return {precipitationTypes} - Weather type or empty string if no valid sky condition - */ - const getPrecipitationType = (timestamp, dataWithHourlySkycons) => { - const skycons = dataWithHourlySkycons?.result?.hourly?.skycon; - if (!Array.isArray(skycons)) { - return 'clear'; - } - - const apiVersion = dataWithHourlySkycons?.api_version; - const versionCode = isNonEmptyString(apiVersion) && apiVersion.startsWith('v') && parseFloat(apiVersion.slice(1)); - const validVersionCode = isNonNanNumber(versionCode) ? versionCode : -1; - - const serverTime = dataWithHourlySkycons?.server_time; - const serverTimestamp = isNonNanNumber(serverTime) && serverTime > 0 - ? serverTime * 1000 : (+(new Date())); - const hourTimestamp = (new Date(serverTimestamp)).setMinutes(0, 0, 0); - const currentHourTimestamp = (new Date(timestamp)).setMinutes(0, 0, 0); - - const skyConditions = skycons.filter((skycon) => { - if (!isNonEmptyString(skycon?.datetime)) { - return false; - } - - // https://docs.caiyunapp.com/docs/v2.3/intro#%E4%B8%8D%E5%85%BC%E5%AE%B9%E7%9A%84%E6%9B%B4%E6%96%B0 - if ( - validVersionCode < 2.3 && (!isNonNanNumber(dataWithHourlySkycons?.tzshift) - || dataWithHourlySkycons.tzshift % 60 !== 0) - ) { - return false; - } - const ts = Date.parse(validVersionCode < 2.3 - ? `${skycon.datetime.replace(' ', 'T')}:00.000${minutesToIsoTimezone(dataWithHourlySkycons.tzshift / 60)}` - : skycon.datetime.split('+').join(':00.000+')); - - return isNonNanNumber(ts) && ts > 0 - // Limit to 3 hours since ColorfulClouds provide two hours report - && ts >= hourTimestamp && ts <= hourTimestamp + 1000 * 60 * 60 * 2; - }); - - const skyCondition = skyConditions.concat().sort((a, b) => { - const aTimestamp = Date.parse(validVersionCode < 2.3 - ? `${a.datetime.replace(' ', 'T')}:00.000${minutesToIsoTimezone(dataWithHourlySkycons.tzshift / 60)}` - : a.datetime.split('+').join(':00.000+')); - const bTimestamp = Date.parse(validVersionCode < 2.3 - ? `${b.datetime.replace(' ', 'T')}:00.000${minutesToIsoTimezone(dataWithHourlySkycons.tzshift / 60)}` - : b.datetime.split('+').join(':00.000+')); - - return currentHourTimestamp - aTimestamp - (currentHourTimestamp - bTimestamp); - }).find((skycon) => ( - isNonEmptyString(skycon?.value) && ( - skycon.value.includes('RAIN') || skycon.value.includes('SNOW') - )))?.value; - - if (!isNonEmptyString(skyCondition)) { - return 'precipitation'; - } - - if (skyCondition.includes('SNOW')) { - return 'snow'; - } - - if (skyCondition.includes('RAIN')) { - return 'rain'; - } - - return 'precipitation'; - }; - - /** - * Convert precipitation type to weather status - * @param {{start: number, end: number, isDrizzleOrFlurries: boolean}[]} precipitationInfo - - * Ranges of precipitations with bounds and drizzle or flurries detection - * @param {number} timeInMinute - Time for weather status to get - * @param {precipitationTypes} precipitationType - Type of precipitation - * @param {number} perceived - Precipitation intensity perceived, - * can be generated by {@link toPerceived} - * @return {weatherStatuses} - Weather status to specified minute - */ - const toWeatherStatus = (precipitationInfo, timeInMinute, precipitationType, perceived) => { - if ( - !Array.isArray(precipitationInfo) || !isNonNanNumber(timeInMinute) || timeInMinute < 0 - || precipitationInfo.some((info) => ( - !isNonNanNumber(info?.start) || !isNonNanNumber(info?.end) - )) - ) { - return 'precipitation'; - } - - const status = perceivedToStatus(precipitationType, perceived); - const rainStatus = ['rain', 'drizzle']; - const snowStatus = ['snow', 'flurries']; - const targets = [...rainStatus, ...snowStatus]; - if (targets.includes(status)) { - const infoOfMinute = precipitationInfo.find((info) => ( - timeInMinute >= info.start && timeInMinute <= info.end - )); - - if (typeof infoOfMinute?.isDrizzleOrFlurries === 'boolean') { - if (rainStatus.includes(status)) { - return infoOfMinute.isDrizzleOrFlurries ? 'drizzle' : 'rain'; - } - - if (snowStatus.includes(status)) { - return infoOfMinute.isDrizzleOrFlurries ? 'flurries' : 'snow'; - } - } - } - - return status; - }; - - /** - * Assign chance for each minute - * since ColorfulClouds only provider chances for periods of 30 minutes - * @author WordlessEcho - * @param {number[]} probabilities - `result.minutely.probability` from ColorfulClouds - * @param {number} timeInMinute - Minutes from start time of precipitation - * @return {number | -1} - 0 to 100 integer, -1 will be returned if probability is invalid - */ - const getChance = (probabilities, timeInMinute) => { - if (Array.isArray(probabilities) && isNonNanNumber(timeInMinute) && timeInMinute >= 0) { - // Calculate order, 1 as first index. - // Index here is relative to bound, plus bound for real index in precipitations. - // We have only 4 chances per half hour from API. - const chance = probabilities?.[Math.floor(timeInMinute / 30)]; - - if (isNonNanNumber(chance) && chance >= 0) { - return chance * 100; - } - } - - return -1; - }; - - /** - * Mapping times to 'variable' that helpful for Apple to use cached description - * @author WordlessEcho - * @param {string} description - Description for next two hours from ColorfulClouds - * @param {string} ccLanguage - Language code from ColorfulClouds - * @param {number} timeInMinute - Minutes from start time of precipitaion - * @param {number} timeShift - Number of minutes that has expired - * @return {{longDescription: string | "", parameters: Object. | {}}} - - * Short description and parameters for Apple Weather - */ - const toDescription = (description, ccLanguage, timeInMinute, timeShift) => { - if (!isNonEmptyString(description)) { - return { - longDescription: '', - parameters: {}, - }; - } - - /** - * Map times in description to `{firstAt}`, `{secondAt}`, etc... - * @author WordlessEcho - * @param {string} rawDescription - Description with times - * @return {{longDescription: string | "", parameters: Object. | {}}} - - * Short description and parameters for Apple Weather - */ - const modifyDescription = (rawDescription) => { - if (!isNonEmptyString(rawDescription)) { - return { - longDescription: '', - parameters: {}, - }; - } - - /** - * Insert 'after that' for description. - * Times in description in Apple Weather after `{firstAt}` will be display as period. - * @author WordlessEcho - * @param {string} language - Language from ColorfulClouds for description - * @param {string} modifiedDescription - Description with '{firstAt}' - * @return {string | ""} - Description after inserted. - * Return empty string if description is invalid. - */ - const insertAfterToDescription = (language, modifiedDescription) => { - if (!isNonEmptyString(modifiedDescription)) { - return ''; - } - - const FIRST_AT = '{firstAt}'; - // Words that used to insert into description - const AFTER = { - zh_CN: '再过', - zh_TW: '再過', - ja: 'その後', - en_US: 'after that', - // ColorfulClouds seems not prefer to display multiple times in en_GB - en_GB: 'after that', - }; - - // Split description into two part at `{firstAt}` - const splitDescriptions = modifiedDescription.split(FIRST_AT); - if (splitDescriptions.length < 2) { - return modifiedDescription; - } - - switch (language) { - case 'en_GB': - return [ - ...splitDescriptions.slice(0, splitDescriptions.length - 1), - splitDescriptions[splitDescriptions.length - 1] - // Append `after that` to description. - .replaceAll('} min later', `} min later ${AFTER.en_GB}`), - ].join(FIRST_AT); - case 'zh_CN': - return [ - ...splitDescriptions.slice(0, splitDescriptions.length - 1), - splitDescriptions[splitDescriptions.length - 1] - .replaceAll('直到{', `${AFTER.zh_CN}{`), - ].join(FIRST_AT); - case 'zh_TW': - return [ - ...splitDescriptions.slice(0, splitDescriptions.length - 1), - splitDescriptions[splitDescriptions.length - 1] - .replaceAll('直到{', `${AFTER.zh_TW}{`), - ].join(FIRST_AT); - case 'ja': - // Japanese support from ColorfulClouds is broken for sometime. - // https://lolic.at/notice/AJNH316TTSy1fRlOka - - // TODO: I am not familiar for Japanese, contributions welcome - return [ - ...splitDescriptions.slice(0, splitDescriptions.length - 1), - splitDescriptions[splitDescriptions.length - 1] - .replaceAll('{', `${AFTER.ja} {`), - ].join(FIRST_AT); - case 'en_US': - return [ - ...splitDescriptions.slice(0, splitDescriptions.length - 1), - splitDescriptions[splitDescriptions.length - 1] - .replaceAll('} min later', `} min later ${AFTER.en_US}`), - ].join(FIRST_AT); - default: - return modifiedDescription; - } - }; - - const times = rawDescription - .match(/\d+/g).map((timeInString) => parseInt(timeInString, 10)) - .filter((time) => isNonNanNumber(time) && time > 0); - - const descriptionWithParameters = times.reduce( - ({ longDescription, parameters }, time, index) => { - const key = `${stringifyNumber(index + 1)}At`; - return { - longDescription: longDescription.replace(`${time}`, `{${key}}`), - parameters: { ...parameters, [key]: time - timeShift }, - }; - }, - { longDescription: rawDescription, parameters: {} }, - ); - - const longDescription = insertAfterToDescription( - ccLanguage, - descriptionWithParameters.longDescription, - ); - - return { - longDescription, - parameters: descriptionWithParameters.parameters, - }; - }; - - const SPLITTERS = { - en_US: ['but ', 'and '], - en_GB: ['but ', 'and '], - zh_CN: [','], - zh_TW: [','], - ja: ['、'], - }; - - const allTimesString = description.match(/\d+/g); - if (!Array.isArray(allTimesString)) { - return { longDescription: '', parameters: {} }; - } - - // Split sentence by time - const allTimes = allTimesString.map((timeInString) => parseInt(timeInString, 10)) - .filter((time) => !Number.isNaN(time) && time > 0); - - const expiredTimes = allTimes.filter((time) => time <= timeInMinute); - if (expiredTimes.length <= 0) { - return modifyDescription(description); - } - - const maxExpiredTime = Math.max(...expiredTimes); - if (maxExpiredTime === allTimes[allTimes.length - 1]) { - return { - longDescription: '', - parameters: {}, - }; - } - - const startIndex = description.indexOf(`${maxExpiredTime}`) + `${maxExpiredTime}`.length; - - const splitters = SPLITTERS[ccLanguage]; - const splitIndexes = splitters.map((splitter) => ( - description.indexOf(splitter, startIndex) + splitter.length - )).filter((index) => index !== -1); - - return modifyDescription(description.slice(Math.min(...splitIndexes))); - }; - - const provider = isNonEmptyString(providerName) ? providerName : $.name; - - const apiVersion = dataWithMinutely?.api_version; - const versionCode = isNonEmptyString(apiVersion) && apiVersion.startsWith('v') && parseFloat(apiVersion.slice(1)); - const validVersionCode = isNonNanNumber(versionCode) ? versionCode : -1; - const majorVersion = Math.trunc(validVersionCode); - if ( - dataWithMinutely?.status !== 'ok' - || !supportedCcApis.includes(majorVersion) - || !supportedUnits.includes(dataWithMinutely?.unit) - // ColorfulClouds: This might be deprecated in future - || dataWithMinutely?.result?.minutely?.datasource !== 'radar' - || !Array.isArray(dataWithMinutely.result.minutely?.precipitation_2h) - || dataWithMinutely.result.minutely.precipitation_2h.some((p) => !isNonNanNumber(p)) - || !Array.isArray(dataWithMinutely.result.minutely?.probability) - || dataWithMinutely.result.minutely.probability.some((p) => !isNonNanNumber(p)) - ) { - // eslint-disable-next-line functional/no-conditional-statement - if (dataWithMinutely?.result?.minutely?.datasource !== 'radar') { - logger('error', `${colorfulCloudsToNextHour.name}:缺少此地的短临降水数据`); - logger( - 'debug', - `${colorfulCloudsToNextHour.name}:数据源:${dataWithMinutely?.result?.minutely?.datasource}`, - ); - } - - return {}; - } - - // `server_time` is in seconds - const serverTime = dataWithMinutely?.server_time; - const serverTimestamp = isNonNanNumber(serverTime) && serverTime > 0 - ? serverTime * 1000 : (+(new Date())); - const startTimestamp = (new Date()).setSeconds(0, 0) + 1000 * 60; - const startIndex = (startTimestamp - (new Date(serverTimestamp).setSeconds(0, 0) + 1000 * 60)) - / 1000 / 60; - const validStartIndex = startIndex >= 0 ? startIndex : 0; - - const maxPrecipitation = Math.max(...dataWithMinutely.result.minutely.precipitation_2h); - const levels = dataWithMinutely?.unit === 'metric:v2' ? mmPerHourLevels : radarLevels; - - const precipitations = dataWithMinutely.result.minutely.precipitation_2h.slice(validStartIndex); - const precipitationBounds = precipitations.flatMap((current, index, array) => { - const previous = array[index - 1]; - - if (index === 0 || previous < levels.NO.RANGE.UPPER) { - if (current >= levels.NO.RANGE.UPPER) { - return [index]; - } - } else if (previous >= levels.NO.RANGE.UPPER) { - if (current < levels.NO.RANGE.UPPER) { - return [index]; - } - } - - return []; - }); - const precipitationInfo = precipitationBounds.flatMap((value, index, array) => { - if (index % 2 > 0) { - return []; - } - - const start = value; - const end = array?.[index + 1]; - const validEnd = isNonNanNumber(end) && end >= 0 ? end : precipitations.length; - - const slicedPrecipitations = precipitations.slice(start, validEnd); - const sum = slicedPrecipitations.reduce((p, c) => p + c, 0); - const average = sum / slicedPrecipitations.length; - - return [{ - start, - end: validEnd, - isDrizzleOrFlurries: isNonNanNumber(sum) - && Math.max(...slicedPrecipitations) < levels.HEAVY.RANGE.LOWER - && average < levels.MODERATE.RANGE.LOWER, - }]; - }); - - const minutes = precipitations.map((precipitation, index) => { - const validPrecipitation = isNonNanNumber(precipitation) && precipitation >= 0 - ? precipitation : 0; - - const timeInMinute = validStartIndex + index + 1; - - const hourlyPrecipitationType = getPrecipitationType( - serverTimestamp + 1000 * 60 * timeInMinute, - dataWithMinutely, - ); - const precipitationType = maxPrecipitation >= Object.values(levels) - .find(({ VALUE }) => VALUE === 0).RANGE.UPPER ? hourlyPrecipitationType : 'clear'; - - const precipitationIntensityPerceived = toPerceived(levels, validPrecipitation); - - const isClear = validPrecipitation < levels.NO.RANGE.UPPER; - const chance = getChance(dataWithMinutely.result.minutely.probability, timeInMinute); - const validChance = chance >= 0 ? chance : 100; - - const ccDescription = dataWithMinutely.result.minutely?.description; - // ColorfulClouds may report no rain even if precipitation > no rain - const descriptionWithParameters = !isNonEmptyString(ccDescription) - || ccDescription.includes(KM[dataWithMinutely?.lang]) - ? { - longDescription: provider, - parameters: {}, - } - : toDescription( - ccDescription, - dataWithMinutely?.lang, - timeInMinute, - validStartIndex, - ); - - const validDescriptionWithParameters = descriptionWithParameters.longDescription.length > 0 - ? descriptionWithParameters : { longDescription: provider, parameters: {} }; - - return { - weatherStatus: toWeatherStatus( - precipitationInfo, - timeInMinute, - precipitationType, - precipitationIntensityPerceived, - ), - precipitation: validPrecipitation, - precipitationIntensityPerceived, - // Set chance to zero if clear - chance: isClear ? 0 : validChance, - shortDescription: isNonEmptyString(dataWithMinutely.result?.forecast_keypoint) - ? dataWithMinutely.result?.forecast_keypoint : provider, - ...validDescriptionWithParameters, - }; - }); - - return { - startTimestamp, - minutes, - }; -}; - -/** - * Append station name from QWeather to provider name - * @author WordlessEcho - * @param {string} providerName - Provider name from metadata - * @param {string} source - Station name in source - * @return {string | ""} - Appended string - */ -const appendQweatherSourceToProviderName = (providerName, source) => { - if (!isNonEmptyString(source)) { - return isNonEmptyString(providerName) ? providerName : $.name; - } - - switch (providerName) { - case '和风天气': - return `${source}(${providerName})`; - case 'QWeather': - return `${source} (${providerName})`; - default: - return providerName; - } -}; - -/** - * Create metadata - * @author VirgilClyne - * @author WordlessEcho - * @param {supportedAppleApi} appleApiVersion - Apple API version - * @param {metadataObject} metadataObject - Object of the metadata info - * @return { - * appleNextHourMetadataV1|appleNextHourMetadataV2|appleNextHourMetadataV3|appleAirQualityMetadataV1 - * |appleAirQualityMetadataV2|appleAirQualityMetadataV3 | {} - * } - Metadata for air quality or next hour in Apple Weather format - */ -const toMetadata = (appleApiVersion, metadataObject) => { - const supportedApis = [1, 2, 3]; - if (!supportedApis.includes(appleApiVersion)) { - return {}; - } - - const sharedMetadata = { - ...(isNonEmptyString(metadataObject?.language) && { language: metadataObject.language }), - ...(isLatitude(metadataObject?.location?.latitude) - && { latitude: metadataObject.location.latitude }), - ...(isLongitude(metadataObject?.location?.longitude) - && { longitude: metadataObject.location.longitude }), - }; - - const getSharedMetadataV2Plus = (apiVersion, metadataObj) => ({ - ...(isNonNanNumber(metadataObj?.expireTimestamp) && metadataObj.expireTimestamp > 0 - && { expireTime: toAppleTime(apiVersion, metadataObj.expireTimestamp) }), - ...(isNonEmptyString(metadataObj?.providerLogo?.forV2) - && { providerLogo: metadataObj.providerLogo.forV2 }), - ...(isNonEmptyString(metadataObj?.providerName) - && { providerName: metadataObj.providerName }), - ...(isNonNanNumber(metadataObj?.readTimestamp) && metadataObj.readTimestamp > 0 - && { readTime: toAppleTime(apiVersion, metadataObj.readTimestamp) }), - ...(isNonNanNumber(metadataObj?.reportedTimestamp) && metadataObj.reportedTimestamp > 0 - && { reportedTime: toAppleTime(apiVersion, metadataObj.reportedTimestamp) }), - ...(isNonEmptyString(metadataObj?.unit) && { units: metadataObj.unit }), - }); - - /** - * Merge metadata for Apple Weather by API version - * @param {supportedAppleApi} apiVersion - Apple API version - * @param {metadataObject} metadataObj - Metadata object - * @return { - * appleNextHourMetadataV1|appleNextHourMetadataV2|appleNextHourMetadataV3 - * |appleAirQualityMetadataV1|appleAirQualityMetadataV2|appleAirQualityMetadataV3 | {} - * } - Metadata for air quality or next hour in Apple Weather format (without `name` property) - */ - const getMetadata = (apiVersion, metadataObj) => { - switch (apiVersion) { - case 1: - // no units for APIv1 - return { - ...sharedMetadata, - ...(isNonNanNumber(metadataObj?.expireTimestamp) && metadataObj.expireTimestamp > 0 - && { expire_time: toAppleTime(apiVersion, metadataObj.expireTimestamp) }), - ...(isNonEmptyString(metadataObj?.providerLogo?.forV1) - && { provider_logo: metadataObj.providerLogo.forV1 }), - ...(isNonEmptyString(metadataObj?.providerName) - && { provider_name: metadataObj.providerName }), - ...(isNonNanNumber(metadataObj?.readTimestamp) && metadataObj.readTimestamp > 0 - && { read_time: toAppleTime(apiVersion, metadataObj.readTimestamp) }), - ...(isNonNanNumber(metadataObj?.reportedTimestamp) && metadataObj.reportedTimestamp > 0 - && { reported_time: toAppleTime(apiVersion, metadataObj.reportedTimestamp) }), - ...((metadataObj?.dataSource === 0 || metadataObj?.dataSource === 1) - && { data_source: metadataObj.dataSource }), - }; - case 2: - // no data source for APIv2 - return { - ...sharedMetadata, - ...getSharedMetadataV2Plus(apiVersion, metadataObj), - }; - case 3: - return { - ...(isNonEmptyString(metadataObj?.url) && { attributionURL: metadataObj.url }), - ...sharedMetadata, - ...getSharedMetadataV2Plus(apiVersion, metadataObj), - }; - default: - return {}; - } - }; - - const metadata = getMetadata(appleApiVersion, metadataObject); - return Object.keys(sharedMetadata).length > 0 || Object.keys(metadata).length > 0 - ? { - ...(isNonNanNumber(appleApiVersion) && appleApiVersion > 0 - && { version: appleApiVersion > 2 ? appleApiVersion - 2 : appleApiVersion }), - ...sharedMetadata, - ...metadata, - } : {}; -}; - -/** - * Output pollutant info - * @param {supportedAppleApi} appleApiVersion - Apple Weather API version - * @param {applePollutantNames} name - Name of the pollutant - * @param {number} amount - Amount of pollutant - * @param {pollutantUnitsV2} unit - Unit of pollutant in Apple Weather types - * @return {pollutantV1|pollutantV2 | {}} - Pollutant info for Apple Weather - */ -const toPollutant = (appleApiVersion, name, amount, unit) => { - if (!isNonEmptyString(name) || !['ppb', 'microgramsPerM3'].includes(unit) || !isNonNanNumber(amount)) { - return {}; - } - - switch (appleApiVersion) { - case 1: - return { - name, - amount: amount > 0 ? amount : -1, - unit: unit === 'microgramsPerM3' ? 'µg/m3' : unit, - }; - case 2: - case 3: - return { - name, - amount: amount > 0 ? amount : -1, - unit, - }; - default: - return {}; - } -}; - -/** - * Output Air Quality Data - * @author VirgilClyne - * @author WordlessEcho - * @param {supportedAppleApi} appleApiVersion - Apple Weather API Version - * @param {airQualityObject} aqiObject - Object of the AQI info - * @return { - * appleAqiWithoutMetadataV1|appleAqiWithoutMetadataV2|appleAqiWithoutMetadataV3 | {} - * } - Air quality data in Apple Weather style without metadata - */ -const toAirQuality = (appleApiVersion, aqiObject) => { - const units = ['ppb', 'microgramsPerM3']; - const comparisonValues = ['better', 'same', 'worse', 'unknown']; - const sourceTypes = ['station', 'modeled']; - - const validPollutants = Array.isArray(aqiObject?.pollutants) ? aqiObject.pollutants.filter( - (pollutant) => ( - units.includes(pollutant?.unit) && isNonEmptyString(pollutant?.name) - && isNonNanNumber(pollutant?.amount) && pollutant.amount >= 0 - ), - ) : []; - - const sharedAirQuality = { - ...(typeof aqiObject?.isSignificant === 'boolean' && { isSignificant: aqiObject.isSignificant }), - ...(isNonEmptyString(aqiObject?.url) && { learnMoreURL: aqiObject.url }), - ...(isNonEmptyString(aqiObject?.primary) && { primaryPollutant: aqiObject.primary }), - ...(validPollutants.length > 0 && { - pollutants: Object.fromEntries(validPollutants.map(({ name, amount, unit }) => ([ - name, toPollutant(appleApiVersion, name, amount, unit), - ]))), - }), - // Source was removed in APIv3 - ...(isNonEmptyString(aqiObject?.sourceName) && { source: aqiObject.sourceName }), - }; - - /** - * Merge air quality for Apple Weather by API version - * @param {supportedAppleApi} apiVersion - Apple API version - * @param {airQualityObject} aqiObj - Air quality object - * @return {appleAqiWithoutMetadataV1|appleAqiWithoutMetadataV2|appleAqiWithoutMetadataV3 | {}} - - * Air quality object for Apple Weather (without `name` property) - */ - const getAirQuality = (apiVersion, aqiObj) => { - switch (apiVersion) { - case 1: - return { - ...sharedAirQuality, - ...(isNonNanNumber(aqiObj?.categoryIndex) && aqiObj.categoryIndex > 0 - && { airQualityCategoryIndex: aqiObj.categoryIndex }), - ...(isNonNanNumber(aqiObj?.aqi) && aqiObj.aqi >= 0 - && { airQualityIndex: aqiObj.aqi }), - ...(isNonEmptyString(aqiObj?.scale) && { airQualityScale: aqiObj.scale }), - }; - case 2: - case 3: - return { - ...sharedAirQuality, - ...(isNonNanNumber(aqiObj?.categoryIndex) && aqiObj.categoryIndex > 0 - && { categoryIndex: aqiObj.categoryIndex }), - ...(isNonNanNumber(aqiObj?.aqi) && aqiObj.aqi >= 0 - && { index: aqiObj.aqi }), - ...(comparisonValues.includes(aqiObj?.previousDayComparison) - && { previousDayComparison: aqiObj.previousDayComparison }), - ...(isNonEmptyString(aqiObj?.scale) && { scale: aqiObj.scale }), - ...(sourceTypes.includes(aqiObj?.sourceType) && { sourceType: aqiObj.sourceType }), - }; - default: - return {}; - } - }; - - const airQuality = getAirQuality(appleApiVersion, aqiObject); - return Object.keys(sharedAirQuality).length > 0 || Object.keys(airQuality).length > 0 - ? { name: 'AirQuality', ...sharedAirQuality, ...airQuality } : {}; -}; - -/** - * Output object for `NextHourForecast` of Apple Weather - * @author WordlessEcho - * @author VirgilClyne - * @param {supportedAppleApi} appleApiVersion - Apple Weather API Version - * @param {nextHourObject} nextHourObject - Object of the precipitation info - * @return { - * appleNextHourWithoutMetadataV1|appleNextHourWithoutMetadataV2|appleNextHourWithoutMetadataV3 | {} - * } - Apple weather style data without metadata - */ -const toNextHour = (appleApiVersion, nextHourObject) => { - if ( - !Array.isArray(nextHourObject?.minutes) || !isNonNanNumber(nextHourObject?.startTimestamp) - || nextHourObject.startTimestamp <= 0 - ) { - return {}; - } - - /** - * Check type of weather status of minute - * @param {weatherStatuses} weatherStatus - Weather status to be checked - * @param {number} precipitation - Precipitation of the weather status - * @return {weatherStatuses} - `weatherStatus` if valid, - * or "precipitation"/"clear" based on precipitation - */ - const checkWeatherStatus = (weatherStatus, precipitation) => { - const weatherStatuses = [ - 'clear', 'precipitation', 'drizzle', 'flurries', 'rain', 'snow', 'heavy-rain', 'heavy-snow', - 'sleet', 'hail', 'mixed', - ]; - - if (!weatherStatuses.includes(weatherStatus)) { - if (isNonNanNumber(precipitation) && precipitation > 0) { - return 'precipitation'; - } - - return 'clear'; - } - - return weatherStatus; - }; - - const minutesData = nextHourObject.minutes.map((minute) => { - const precipitationIntensityPerceived = isNonNanNumber(minute?.precipitationIntensityPerceived) - && minute.precipitationIntensityPerceived >= 0 ? minute.precipitationIntensityPerceived : 0; - const fallbackChance = precipitationIntensityPerceived <= 0 ? 0 : 100; - const chance = isNonNanNumber(minute?.chance) && minute.chance >= 0 && minute.chance <= 100 - ? minute.chance : fallbackChance; - - const validShortDescription = isNonEmptyString(minute?.shortDescription) - ? minute.shortDescription : $.name; - const validLongDescription = isNonEmptyString(minute?.longDescription) - ? minute.longDescription : ''; - - return { - weatherStatus: checkWeatherStatus(minute?.weatherStatus, minute?.precipitation), - precipitation: isNonNanNumber(minute?.precipitation) && minute.precipitation >= 0 - ? minute.precipitation : 0, - precipitationIntensityPerceived, - chance: Math.round(chance), - shortDescription: validShortDescription, - longDescription: validLongDescription, - ...({ parameters: isObject(minute?.parameters) ? minute.parameters : {} }), - }; - }); - - /** - * Get array of condition for `condition` in `NextHourForecast` - * @author WordlessEcho - * @param {supportedAppleApi} apiVersion - Apple Weather API Version - * @param {minute[]} minutes - Array of minute precipitation data - * @param {number} startTimestamp - UNIX timestamp at condition start - * @return {nextHourConditionV1[]|nextHourConditionV2[]|nextHourConditionV3[]|[]} - - * Conditions for Apple Weather - */ - const toConditions = (apiVersion, minutes, startTimestamp) => { - const slicedMinutes = minutes.slice(0, 60); - - /** - * Merge possibility, weather status and time status for `forecastNextHour.condition.token` - * @author WordlessEcho - * @param {number} bound - Bound of current weather status - * @return {string} - Token for Apple Weather - */ - const toToken = (bound) => { - if (!isNonNanNumber(bound) || bound < 0 || bound >= slicedMinutes.length) { - return 'clear'; - } - - const firstStatus = slicedMinutes[bound].weatherStatus; - const secondStatusRelatedIndex = slicedMinutes.slice(bound).findIndex((minute) => ( - minute.weatherStatus !== firstStatus)); - const secondStatusIndex = secondStatusRelatedIndex === -1 - ? -1 : secondStatusRelatedIndex + bound; - const secondStatus = secondStatusIndex === -1 ? null - : slicedMinutes[secondStatusIndex].weatherStatus; - - if (firstStatus === 'clear') { - if (secondStatusIndex === -1) { - return firstStatus; - } - - const nextClearIndex = slicedMinutes.slice(secondStatusIndex) - .findIndex((minute) => minute.weatherStatus === 'clear'); - if (nextClearIndex === -1) { - const maxChance = Math.max(...slicedMinutes - .slice(secondStatusIndex).map((minute) => minute.chance)); - // https://developer.apple.com/documentation/weatherkitrestapi/certainty - return `${maxChance < 50 ? 'possible-' : ''}${secondStatus}.start`; - } - - const maxChance = Math.max(...slicedMinutes - .slice(secondStatusIndex, nextClearIndex).map((minute) => minute.chance)); - return `${maxChance < 50 ? 'possible-' : ''}${secondStatus}.start-stop`; - } - - // If current weather is not clear - if (secondStatus === 'clear') { - const nextNotClearIndex = slicedMinutes.slice(secondStatusIndex) - .findIndex((minute) => minute.weatherStatus !== 'clear'); - const maxChance = Math.max(...slicedMinutes - .slice(bound, secondStatusIndex).map((minute) => minute.chance)); - - if (nextNotClearIndex !== -1) { - return `${maxChance < 50 ? 'possible-' : ''}${firstStatus}.stop-start`; - } - - return `${maxChance < 50 ? 'possible-' : ''}${firstStatus}.stop`; - } - - const maxChance = Math.max(...slicedMinutes.map((minute) => minute.chance)); - return firstStatus.startsWith('heavy-') && secondStatusIndex !== -1 - ? `${maxChance < 50 ? 'possible-' : ''}${firstStatus}-to-${secondStatus}.constant` - : `${maxChance < 50 ? 'possible-' : ''}${firstStatus}.constant`; - }; - - const toParameters = (bounds, indexInBound, timestamp) => bounds - .slice(indexInBound).reduce((parameters, currentBound, index) => { - const lastStatus = slicedMinutes[ - indexInBound + index - 1 < 0 ? 0 : bounds[indexInBound + index - 1] - ].weatherStatus; - const currentStatus = slicedMinutes[currentBound].weatherStatus; - - if ( - currentBound + 1 === slicedMinutes.length && (currentBound <= 0 - || currentStatus === slicedMinutes[currentBound - 1].weatherStatus) - ) { - return parameters; - } - - return { - ...parameters, - ...((lastStatus !== 'clear' || currentStatus !== 'clear') && { - [`${stringifyNumber(Object.keys(parameters).length + 1)}At`]: toAppleTime( - apiVersion, - timestamp + currentBound * 60 * 1000, - ), - }), - }; - }, {}); - - /** @type {number[]} */ - const bounds = slicedMinutes.flatMap((current, index, array) => { - const previous = array[index - 1]; - - if ( - index === 0 - || (index + 1 !== array.length && current.weatherStatus === previous.weatherStatus) - ) { - return []; - } - - return [index]; - }); - - const timestamp = isNonNanNumber(startTimestamp) && startTimestamp > 0 - ? startTimestamp : (+(new Date())); - - // TODO: (FIX) Infinite loop while length of minutesData is 1 - return bounds.map((bound, index, array) => { - const minute = slicedMinutes[bound]; - const lastBound = index === 0 ? 0 : array[index - 1]; - - const token = toToken(lastBound); - const needEndTime = !( - index + 1 === array.length && minute.weatherStatus === minutes[bound + 1].weatherStatus - ); - - const haveLongDescription = isNonEmptyString(minute.longDescription); - const longDescription = haveLongDescription ? minute.longDescription : $.name; - - switch (apiVersion) { - case 1: - return { - ...(needEndTime && { - validUntil: toAppleTime(apiVersion, timestamp + bound * 60 * 1000), - }), - token, - longTemplate: !haveLongDescription && isNonEmptyString(minute.shortDescription) - ? minute.shortDescription : longDescription, - shortTemplate: minute.shortDescription, - parameters: haveLongDescription && Object.keys(minute.parameters).length > 0 - ? Object.fromEntries(Object.entries(minute.parameters).map(([key, value]) => [ - key, toAppleTime(apiVersion, timestamp + value * 60 * 1000), - ])) : toParameters(array, index, slicedMinutes, timestamp), - }; - case 2: - return { - startTime: toAppleTime(apiVersion, timestamp + lastBound * 60 * 1000), - ...(needEndTime && { - endTime: toAppleTime(apiVersion, timestamp + bound * 60 * 1000), - }), - token, - longTemplate: !haveLongDescription && isNonEmptyString(minute.shortDescription) - ? minute.shortDescription : longDescription, - shortTemplate: minute.shortDescription, - parameters: haveLongDescription && Object.keys(minute.parameters).length > 0 - ? Object.fromEntries(Object.entries(minute.parameters).map(([key, value]) => [ - key, toAppleTime(apiVersion, timestamp + value * 60 * 1000), - ])) : toParameters(array, index, timestamp), - }; - case 3: - return { - startTime: toAppleTime(apiVersion, timestamp + lastBound * 60 * 1000), - ...(needEndTime && { - endTime: toAppleTime(apiVersion, timestamp + bound * 60 * 1000), - }), - token, - parameters: toParameters(array, index, timestamp), - }; - default: - return {}; - } - }); - }; - - /** - * Get array of summary for `summary` in `NextHourForecast` - * @author WordlessEcho - * @param {supportedAppleApi} apiVersions - Apple Weather API Version - * @param {minute[]} minutes - Array of minute precipitation data - * @param {number} startTimestamp - UNIX timestamp when minutes start - * @return {nextHourSummaryV1[]|nextHourSummaryV2[]|nextHourSummaryV3[]|[]} - - * Summaries for Apple Weather - */ - const toSummaries = (apiVersions, minutes, startTimestamp) => { - /** @type number[] */ - const bounds = minutes.slice(0, 59).flatMap((current, index, array) => { - const previous = array[index - 1]; - - if ( - index === 0 || ( - index + 1 !== array.length && weatherStatusToType(current.weatherStatus) - === weatherStatusToType(previous.weatherStatus) - ) - ) { - return []; - } - - return [index]; - }); - - const timestamp = isNonNanNumber(startTimestamp) && startTimestamp > 0 - ? startTimestamp : (+(new Date())); - - return bounds.map((bound, index, array) => { - const lastBound = index === 0 ? 0 : array[index - 1]; - const minutesInSummary = minutes.slice(lastBound, bound); - - const needEndTime = !( - index + 1 === array.length - && minutes[bound].weatherStatus === minutes[bound + 1].weatherStatus - ); - const condition = weatherStatusToType(minutes[lastBound].weatherStatus); - - const isNotClear = condition !== 'clear'; - const maxChance = Math.max(...minutesInSummary.map(({ chance }) => chance)); - const precipitations = minutesInSummary.map(({ precipitation }) => precipitation); - - switch (apiVersions) { - case 1: - return { - ...(needEndTime && { - validUntil: toAppleTime(apiVersions, timestamp + bound * 60 * 1000), - }), - condition, - ...(isNotClear ? { - probability: maxChance, - maxIntensity: Math.max(...precipitations), - minIntensity: Math.min(...precipitations), - } : null), - }; - // to make ESLint and JSDoc happy - case 2: - return { - startTime: toAppleTime(apiVersions, timestamp + lastBound * 60 * 1000), - ...(needEndTime && { - endTime: toAppleTime(apiVersions, timestamp + bound * 60 * 1000), - }), - condition, - ...(isNotClear && { - precipChance: maxChance, - precipIntensity: Math.max(...precipitations), - }), - }; - case 3: - return { - startTime: toAppleTime(apiVersions, timestamp + lastBound * 60 * 1000), - ...(needEndTime && { - endTime: toAppleTime(apiVersions, timestamp + bound * 60 * 1000), - }), - condition, - precipitationChance: maxChance / 100, - precipitationIntensity: Math.max(...precipitations), - }; - default: - return {}; - } - }); - }; - - /** - * Get array of minutes for `minutes` in `NextHourForecast` - * @author WordlessEcho - * @param {supportedAppleApi} apiVersions - Apple Weather API Version - * @param {minute[]} minutes - Array of minute precipitation data - * @param {number} startTimestamp - UNIX timestamp when minutes start - * @return {nextHourMinuteV1[]|nextHourMinuteV2[]|nextHourMinuteV3[]|[]} - - * Minutes for Apple Weather - */ - const toMinutes = (apiVersions, minutes, startTimestamp) => { - const timestamp = isNonNanNumber(startTimestamp) && startTimestamp > 0 - ? startTimestamp : (+(new Date())); - - const getSharedMinuteV2Below = (precipIntensity, precipChance) => ({ - precipIntensity, - precipChance, - }); - - return minutes.map( - ({ precipitation, chance, precipitationIntensityPerceived }, index) => { - switch (apiVersions) { - case 1: - return { - startAt: toAppleTime(apiVersions, timestamp + index * 60 * 1000), - ...getSharedMinuteV2Below(precipitation, chance), - perceivedIntensity: precipitationIntensityPerceived, - }; - case 2: - return { - startTime: toAppleTime(apiVersions, timestamp + index * 60 * 1000), - ...getSharedMinuteV2Below(precipitation, chance), - precipIntensityPerceived: precipitationIntensityPerceived, - }; - case 3: - return { - startTime: toAppleTime(apiVersions, timestamp + index * 60 * 1000), - precipitationChance: chance / 100, - precipitationIntensity: precipitation, - precipitationIntensityPerceived, - }; - default: - return {}; - } - }, - ); - }; - - /** - * Merge next hour object - * @author WordlessEcho - * @param {supportedAppleApi} apiVersion - Apple API version - * @param {?(nextHourConditionV1[]|nextHourConditionV2[]|nextHourConditionV3[])} condition - - * Condition for Apple Weather - * @param {?(nextHourSummaryV1[]|nextHourSummaryV2[]|nextHourSummaryV3[])} summary - - * Summary for Apple Weather - * @param {?(nextHourMinuteV1[]|nextHourMinuteV2[]|nextHourMinuteV3[])} minutes - - * Minutes for Apple Weather - * @param {number} timestamp - UNIX timestamp at next hour start - * @return { - * appleNextHourWithoutMetadataV1|appleNextHourWithoutMetadataV2|appleNextHourWithoutMetadataV3 - * | {} - * } - Next hour object for Apple Weather (without `name` property) - */ - const getNextHour = (apiVersion, condition, summary, minutes, timestamp) => { - const sharedNextHour = { - ...(Array.isArray(condition) && condition.length > 0 && { condition }), - ...(Array.isArray(summary) && summary.length > 0 && { summary }), - ...(Array.isArray(minutes) && minutes.length > 0 && { minutes }), - }; - - switch (apiVersion) { - case 1: - case 2: - return { - ...sharedNextHour, - ...(isNonNanNumber(timestamp) && timestamp > 0 - && { startTime: toAppleTime(appleApiVersion, timestamp) }), - }; - case 3: - return { - ...sharedNextHour, - ...(isNonNanNumber(timestamp) && timestamp > 0 - && { forecastStart: toAppleTime(appleApiVersion, timestamp) }), - ...(Array.isArray(minutes) && minutes.length > 0 && { - forecastEnd: toAppleTime(appleApiVersion, timestamp + 1000 * 60 * minutes.length), - }), - }; - default: - return {}; - } - }; - - const nextHour = getNextHour( - appleApiVersion, - toConditions(appleApiVersion, minutesData, nextHourObject.startTimestamp), - toSummaries(appleApiVersion, minutesData, nextHourObject.startTimestamp), - toMinutes(appleApiVersion, minutesData, nextHourObject.startTimestamp), - nextHourObject.startTimestamp, - ); - - if (Object.keys(nextHour).length > 0) { - // eslint-disable-next-line functional/no-conditional-statement - if (!nextHour.summary.some((s) => s.condition !== 'clear')) { - logger( - 'info', - `${toNextHour.name}:API报告此地未来一小时无降水,Apple天气上将不会显示下小时降水强度信息`, - ); - // eslint-disable-next-line functional/no-conditional-statement - } else if (nextHour.summary.some((s) => s.condition === 'precipitation')) { - logger('warn', `${toNextHour.name}:缺失部分雨雪类型信息`); - } - - logger('debug', `${toNextHour.name}:condition = ${JSON.stringify(nextHour.condition)}`); - logger('debug', `${toNextHour.name}:summary = ${JSON.stringify(nextHour.summary)}`); - - return { name: 'NextHourForecast', ...nextHour }; - } - - return {}; -}; - -/** - * Get name of the key for Apple Weather by API version - * @author WordlessEcho - * @param {supportedAppleApi} apiVersion - Apple API version - * @return {Object. | {}} - Name of the key - */ -const getKeywords = (apiVersion) => { - switch (apiVersion) { - case 1: - return { - METADATA: 'metadata', - AIR_QUALITY: 'air_quality', - REQUIRE_NEXT_HOUR: 'next_hour_forecast', - NEXT_HOUR: 'next_hour', - NEXT_HOUR_SUMMARY: 'summary', - NEXT_HOUR_SUMMARY_CONDITION: 'condition', - PROVIDER_NAME: 'provider_name', - REPORTED_TIME: 'reported_time', - AQI_INDEX: 'airQualityIndex', - AQI_SCALE: 'airQualityScale', - POLLUTANTS: 'pollutants', - PRIMARY_POLLUTANT: 'primaryPollutant', - UNIT: 'unit', - AMOUNT: 'amount', - SOURCE: 'source', - AQI_COMPARISON: '', - TEMPORARILY_UNAVAILABLE: 'temporarily_unavailable', - }; - case 2: - case 3: - return { - METADATA: 'metadata', - AIR_QUALITY: 'airQuality', - REQUIRE_NEXT_HOUR: 'forecastNextHour', - NEXT_HOUR: 'forecastNextHour', - NEXT_HOUR_SUMMARY: 'summary', - NEXT_HOUR_SUMMARY_CONDITION: 'condition', - PROVIDER_NAME: 'providerName', - REPORTED_TIME: 'reportedTime', - AQI_INDEX: 'index', - AQI_SCALE: 'scale', - POLLUTANTS: 'pollutants', - PRIMARY_POLLUTANT: 'primaryPollutant', - UNIT: 'unit', - AMOUNT: 'amount', - SOURCE: 'source', - AQI_COMPARISON: 'previousDayComparison', - TEMPORARILY_UNAVAILABLE: 'temporarilyUnavailable', - }; - default: - return {}; - } -}; - -/** - * Convert apple time to UNIX timestamp - * @author WordlessEcho - * @param {supportedAppleApi} apiVersion - Apple API version - * @param {number|string} time - Time in Apple format - * @param {number} fallbackTimestamp - Time for fallback if convert failed - * @return {number} - UNIX timestamp of Apple time - */ -const appleTimeToTimestamp = (apiVersion, time, fallbackTimestamp) => { - const fallback = isNonNanNumber(fallbackTimestamp) && fallbackTimestamp > 0 - ? fallbackTimestamp : (+(new Date())); - - switch (apiVersion) { - case 1: - return isNonNanNumber(time) && time > 0 ? time * 1000 : fallback; - case 2: { - const timestamp = Date.parse(time); - return isNonNanNumber(timestamp) && timestamp > 0 ? timestamp : fallback; - } - default: - return fallback; - } -}; - -/** - * Set response - * @param {Object} response - Response to set - */ -const setResponse = (response) => { - // eslint-disable-next-line functional/no-conditional-statement - if (isObject(response)) { - // eslint-disable-next-line functional/no-expression-statement - $.done(!$.isQuanX() ? response : { body: isNonEmptyString(response?.body) ? response?.body : '' }); - // eslint-disable-next-line functional/no-conditional-statement - } else { - // eslint-disable-next-line functional/no-expression-statement - $.done({ body: '' }); - } -}; - -// eslint-disable-next-line functional/no-conditional-statement -if (settings.switch) { - const supportedAppleApis = [1, 2, 3]; - - // eslint-disable-next-line no-undef - if (isNonEmptyString($request?.url)) { - // eslint-disable-next-line no-undef - const url = (new URLs()).parse($request.url); - - if (url) { - const parameters = getParams(url.path); - const appleApiVersionString = parameters?.ver; - const appleApiVersion = isNonEmptyString(appleApiVersionString) - ? parseInt(appleApiVersionString.slice(1), 10) : -1; - - // eslint-disable-next-line functional/no-conditional-statement - if (!supportedAppleApis.includes(appleApiVersion)) { - logger('error', `${$.name}:不支持${appleApiVersionString}版本的Apple API,您可能需要更新模块`); - // eslint-disable-next-line functional/no-expression-statement,no-undef - setResponse($response); - // eslint-disable-next-line functional/no-conditional-statement,no-undef - } else if ($response?.statusCode !== 200 && $response?.status !== 200) { - logger( - 'warn', - // eslint-disable-next-line no-undef - `${$.name}:服务器返回非200状态码,statusCode = ${$response?.statusCode}, status = ${$response?.status}`, - ); - // eslint-disable-next-line functional/no-expression-statement,no-undef - setResponse($response); - // eslint-disable-next-line functional/no-conditional-statement - } else { - logger('info', `${$.name}:模块开始运行`); - - // eslint-disable-next-line no-undef - const dataFromApple = parseJsonWithDefault($response?.body, null); - // eslint-disable-next-line functional/no-conditional-statement - if (isObject(dataFromApple)) { - const latitude = parseFloat(parameters?.lat); - const longitude = parseFloat(parameters?.lng); - - // eslint-disable-next-line functional/no-conditional-statement - if (isLatitude(latitude) && isLongitude(longitude)) { - const languageWithRegion = parameters?.language; - - // eslint-disable-next-line functional/no-conditional-statement - if (!isNonEmptyString(languageWithRegion)) { - logger('warn', `${$.name}:无法获取语言信息,语言:${parameters?.language}`); - } - - const { - METADATA, AIR_QUALITY, REQUIRE_NEXT_HOUR, NEXT_HOUR, PROVIDER_NAME, REPORTED_TIME, - AQI_INDEX, AQI_SCALE, POLLUTANTS, PRIMARY_POLLUTANT, UNIT, AMOUNT, SOURCE, - AQI_COMPARISON, TEMPORARILY_UNAVAILABLE, - } = getKeywords(appleApiVersion); - - const settingsToAqiStandard = { WAQI_InstantCast: WAQI_INSTANT_CAST }; - const scaleToAqiStandard = { - [EPA_454.APPLE_SCALE]: EPA_454, [HJ_633.APPLE_SCALE]: HJ_633, - }; - const supportedApis = ['www.weatherol.cn', 'api.caiyunapp.com', 'api.waqi.info']; - - /** - * Get data requirements from Apple Weather - * @param {supportedAppleApis} apiVersion - Apple Weather API version - * @param {Object} parsedUrl - URL parsed by {@link URLs} - * @return {string[]} - Data requirements - */ - const getRequireData = (apiVersion, parsedUrl) => { - switch (apiVersion) { - case 1: { - const requirement = parsedUrl.params?.include; - return isNonEmptyString(requirement) ? requirement.split(',') : []; - } - case 2: - case 3: { - const requirement = parsedUrl.params?.dataSets; - return isNonEmptyString(requirement) ? requirement.split(',') : []; - } - default: - return []; - } - }; - - /** - * Get scale of modified air quality before network requesting - * @param {settingsV1} projectSettings - Settings of module - * @param {appleAqiScales} appleScale - Current Apple Weather scale - * @return {string} - Target scale of modified air quality - */ - const getTargetScale = (projectSettings, appleScale) => { - if (projectSettings.aqi.local.switch) { - const scale = settingsToAqiStandard[projectSettings.aqi.local.standard] - ?.APPLE_SCALE; - if (!isNonEmptyString(scale)) { - return ''; - } - - return settingsToAqiStandard[projectSettings.aqi.local.standard].APPLE_SCALE; - } - - if (!projectSettings.aqi.switch) { - return isNonEmptyString(appleScale) ? appleScale : ''; - } - - switch (projectSettings.aqi.source) { - case 'www.weatherol.cn': - return HJ_633.APPLE_SCALE; - case 'api.caiyunapp.com': - return projectSettings.apis.colorfulClouds.forceCnForAqi - ? HJ_633.APPLE_SCALE : EPA_454.APPLE_SCALE; - case 'api.waqi.info': - return WAQI_INSTANT_CAST.APPLE_SCALE; - default: - return ''; - } - }; - - /** - * Missions for APIs from {@link toMissions} - * @typedef {"aqi" | "forCompareAqi" | "nextHour"} missions - */ - /** - * Get missions for creating promises - * @param {string} aqi - AQI source - * @param {string} forCompareAqi - AQI comparison source - * @param {string} nextHour - Next hour source - * @return {{api: string, missions: missions[]}[]} - - * Missions for APIs - */ - const toMissions = (aqi, forCompareAqi, nextHour) => supportedApis - .map((api) => ({ - api, - missions: [ - ...(aqi === api ? ['aqi'] : []), - ...(forCompareAqi === api ? ['forCompareAqi'] : []), - ...(nextHour === api ? ['nextHour'] : []), - ], - })); - - /** - * Get path by missions for ColorfulClouds - * @param {missions[]} missions - Mission list - * @return {"weather" | "realtime" | "minutely" | "hourly" | "daily"} - - * URL Path for ColofulClouds - */ - const missionsToCcPath = (missions) => { - if (!Array.isArray(missions) || missions.length <= 0 || missions.length > 1) { - return 'weather'; - } - - switch (missions[0]) { - case 'aqi': - return 'realtime'; - // We need hourly.skycons to detect rain or snow - case 'nextHour': - case 'aqiForComparison': - default: - return 'weather'; - } - }; - - /** - * Get ColorfulClouds by language - * @param {string} language - Language from Apple Weather - * @return {string} - Name of the ColorfulClouds - */ - const getColorfulCloudsName = (language) => { - // No official name for Japanese - if (isNonEmptyString(language)) { - if (/zh-(Hans|CN)/.test(language)) { - return '彩云天气'; - } - if (/zh-(Hant|HK|TW)/.test(language)) { - return '彩雲天氣'; - } - } - - return 'ColorfulClouds'; - }; - - /** - * Handle data from API to air quality - * @param {supportedAppleApi} apiVersion - Apple Weather API version - * @param {{ - * api: string, missions: missions[], returnedData: Object, types: string[] - * }} promiseData - Data from promises - * @param {string} appleLanguage - Language from Apple Weather - * @return {appleAqiV1|appleAqiV2|appleAqiV3 | {}} - - * Air quality object for Apple Weather - */ - const getAirQuality = (apiVersion, promiseData, appleLanguage) => { - if (!Array.isArray(promiseData?.missions) || !promiseData.missions.includes('aqi')) { - return {}; - } - - switch (promiseData?.api) { - case 'www.weatherol.cn': - return { - [METADATA]: toMetadata(apiVersion, colorfulCloudsToAqiMetadata( - { - forV1: 'https://raw.githubusercontent.com/VirgilClyne/iRingo/main/image/weatherol-logo-colorful.png', - forV2: 'https://raw.githubusercontent.com/VirgilClyne/iRingo/main/image/weatherol-logo.png', - }, - '气象在线', - 'https://www.weatherol.cn/', - promiseData?.returnedData, - )), - ...toAirQuality(apiVersion, colorfulCloudsToAqi( - promiseData?.returnedData, - 'https://www.weatherol.cn/', - '气象在线', - true, - true, - )), - }; - case 'api.caiyunapp.com': - return { - [METADATA]: toMetadata(apiVersion, colorfulCloudsToAqiMetadata( - { - forV1: 'https://raw.githubusercontent.com/VirgilClyne/iRingo/main/image/colorfulclouds-logo-colorful.png', - forV2: 'https://raw.githubusercontent.com/VirgilClyne/iRingo/main/image/colorfulclouds-logo.png', - }, - getColorfulCloudsName(appleLanguage), - 'https://caiyunapp.com/weather/', - promiseData?.returnedData, - )), - ...toAirQuality(apiVersion, colorfulCloudsToAqi( - promiseData?.returnedData, - 'https://caiyunapp.com/weather/', - getColorfulCloudsName(appleLanguage), - settings.apis.colorfulClouds.forceCnForAqi, - settings.apis.colorfulClouds.forceCnForComparison, - )), - }; - case 'api.waqi.info': - if (Array.isArray(promiseData?.types)) { - if (promiseData.types.includes('locationFeed')) { - return { - [METADATA]: toMetadata( - apiVersion, - waqiToAqiMetadata(promiseData?.returnedData), - ), - ...toAirQuality(apiVersion, waqiToAqi(promiseData?.returnedData)), - }; - } - - if (promiseData.types.includes('mapq')) { - const dataInFeed = waqiNearestToFeed('mapq', promiseData?.returnedData).find((data) => data.status === 'ok'); - return dataInFeed ? { - [METADATA]: toMetadata( - apiVersion, - waqiToAqiMetadata(dataInFeed), - ), - ...toAirQuality(apiVersion, waqiToAqi(dataInFeed)), - } : {}; - } - - if (promiseData.types.includes('v1Aqi')) { - const dataInFeed = waqiV1ToFeed(promiseData?.returnedData).find((data) => data.status === 'ok'); - return dataInFeed ? { - [METADATA]: toMetadata( - apiVersion, - waqiToAqiMetadata(dataInFeed), - ), - ...toAirQuality(apiVersion, waqiToAqi(dataInFeed)), - } : {}; - } - } - - return {}; - default: - return {}; - } - }; - - /** - * Handle data from API to air quality - * @param {{ - * api: string, missions: missions[], returnedData: Object, types: string[] - * }} promiseData - Data from promises - * @return {aqiComparison} - AQI comparison for Apple Weather - */ - const getAqiComparison = (promiseData) => { - if (!Array.isArray(promiseData?.missions) || !promiseData.missions.includes('forCompareAqi')) { - return 'unknown'; - } - - switch (promiseData?.api) { - case 'api.caiyunapp.com': - return colorfulCloudsToAqiComparison( - promiseData?.returnedData, - settings.apis.colorfulClouds.forceCnForComparison, - ); - case 'api.waqi.info': { - if (Array.isArray(promiseData?.returnedData?.rxs?.obs)) { - const aqiFeedObs = promiseData?.returnedData.rxs.obs.find((station) => station?.status === 'ok'); - if (aqiFeedObs?.msg) { - return waqiV1AqiToAqiComparison(aqiFeedObs.msg); - } - } - - return 'unknown'; - } - default: - return 'unknown'; - } - }; - - /** - * Handle data from API to air quality - * @param {supportedAppleApi} apiVersion - Apple Weather API version - * @param {{ - * api: string, missions: missions[], returnedData: Object, types: string[] - * }} promiseData - Data from promises - * @param {string} appleLanguage - Language from Apple Weather - * @return {appleNextHourV1|appleNextHourV2|appleNextHourV3 | {}} - - * Air quality object for Apple Weather - */ - const getNextHour = (apiVersion, promiseData, appleLanguage) => { - if (!Array.isArray(promiseData?.missions) || !promiseData.missions.includes('nextHour')) { - return {}; - } - - switch (promiseData?.api) { - case 'www.weatherol.cn': - return { - [METADATA]: toMetadata(apiVersion, colorfulCloudsToNextHourMetadata( - '气象在线', - 'https://www.weatherol.cn/', - promiseData?.returnedData, - )), - ...toNextHour(apiVersion, colorfulCloudsToNextHour('气象在线', promiseData?.returnedData)), - }; - case 'api.caiyunapp.com': - return { - [METADATA]: toMetadata(apiVersion, colorfulCloudsToNextHourMetadata( - getColorfulCloudsName(appleLanguage), - 'https://caiyunapp.com/weather/', - promiseData?.returnedData, - )), - ...toNextHour(apiVersion, colorfulCloudsToNextHour( - getColorfulCloudsName(appleLanguage), - promiseData?.returnedData, - )), - }; - default: - return {}; - } - }; - - const location = { latitude, longitude }; - // eslint-disable-next-line functional/no-conditional-statement - if (settings.log.location) { - logger('debug', `${$.name}:经度:${longitude},纬度:${latitude}`); - } - const requireData = getRequireData(appleApiVersion, url); - - const qweatherNames = ['和风天气', 'QWeather']; - const airQuality = { - ...dataFromApple?.[AIR_QUALITY], - ...(qweatherNames.includes(dataFromApple?.[AIR_QUALITY]?.[METADATA]?.[PROVIDER_NAME]) - && { - [METADATA]: { - ...dataFromApple[AIR_QUALITY][METADATA], - [PROVIDER_NAME]: appendQweatherSourceToProviderName( - dataFromApple[AIR_QUALITY][METADATA][PROVIDER_NAME], - dataFromApple[AIR_QUALITY]?.[SOURCE], - ), - }, - ...(isObject(dataFromApple[AIR_QUALITY]?.[POLLUTANTS]) && { - [POLLUTANTS]: Object.fromEntries( - Object.entries(dataFromApple[AIR_QUALITY][POLLUTANTS]).map(([key, value]) => { - if (key === 'CO') { - const fixedAmount = fixQweatherCo(value?.[UNIT], value?.[AMOUNT]); - return [key, { - ...value, - ...(fixedAmount >= 0 && { [AMOUNT]: fixedAmount }), - }]; - } - return [key, value]; - }), - ), - }), - }), - }; - const nextHour = dataFromApple?.[NEXT_HOUR]; - - const aqiProvider = airQuality?.[METADATA]?.[PROVIDER_NAME]; - const aqiScale = airQuality?.[AQI_SCALE]; - const needAqi = requireData.includes(AIR_QUALITY) && settings.aqi.switch - && (!isNonEmptyString(aqiScale) || (!settings.aqi.local.switch - && settings.aqi.targets.includes( - aqiScale.slice(0, aqiScale.lastIndexOf('.')), - ))); - - const needCompareAqi = requireData.includes(AIR_QUALITY) - && settings.aqi.comparison.switch && AQI_COMPARISON.length > 0 - && (airQuality?.[AQI_COMPARISON] === 'unknown' || needAqi); - const nowHourTimestamp = (new Date()).setMinutes(0, 0, 0); - const yesterdayHourTimestamp = nowHourTimestamp - 1000 * 60 * 60 * 24; - const yesterdayReportTimestamp = needAqi ? yesterdayHourTimestamp - : appleTimeToTimestamp( - appleApiVersion, - airQuality?.[METADATA]?.[REPORTED_TIME], - nowHourTimestamp, - ) - 1000 * 60 * 60 * 24; - const cachedAqi = needCompareAqi ? getCachedAqi( - toCaches(getENV('iRingo', 'Weather', database)).aqis, - yesterdayReportTimestamp, - location, - qweatherNames.includes(aqiProvider) ? airQuality?.source : null, - getTargetScale(settings, aqiScale), - ) : { aqi: -1 }; - - const nextHourProvider = nextHour?.[METADATA]?.[PROVIDER_NAME]; - const needNextHour = requireData.includes(REQUIRE_NEXT_HOUR) - && settings.nextHour.switch && !isNonEmptyString(nextHourProvider); - - const missionList = toMissions( - needAqi ? settings.aqi.source : null, - needCompareAqi && cachedAqi.aqi < 0 ? settings.aqi.comparison.source : null, - needNextHour ? settings.nextHour.source : null, - ); - logger('info', `${$.name}:任务列表:${JSON.stringify(missionList)}`); - - const promises = Array.isArray(missionList) ? missionList - .filter((missionObject) => ( - supportedApis.includes(missionObject?.api) && Array.isArray(missionObject?.missions) - )) - .flatMap(({ api, missions }) => { - if (missions.length <= 0) { - return []; - } - - switch (api) { - case 'www.weatherol.cn': - return missions.flatMap((mission) => { - switch (mission) { - case 'aqi': - return [ - weatherOl('realtime', location, settings.apis.weatherOl.httpHeaders) - .then((returnedData) => ({ - missions: [mission], - api, - types: ['realtime'], - returnedData, - })), - ]; - case 'nextHour': - return [ - weatherOl('forecast', location, settings.apis.weatherOl.httpHeaders) - .then((returnedData) => ({ - missions: [mission], - api, - types: ['forecast'], - returnedData, - })), - ]; - default: - return []; - } - }); - case 'api.caiyunapp.com': { - const path = missionsToCcPath(missions); - const needHistory = missions.includes('forCompareAqi'); - - return [colorfulClouds( - settings.apis.colorfulClouds.token, - location, - languageWithRegion, - settings.apis.colorfulClouds.httpHeaders, - path, - { - unit: 'metric:v2', - ...(needHistory && { begin: yesterdayHourTimestamp / 1000 }), - }, - ).then((returnedData) => ({ - missions, - api, - types: [path, ...(needHistory ? ['history'] : [])], - returnedData, - }))]; - } - case 'api.waqi.info': { - const getHistoryAqis = (stationId, fallbackData) => { - const getAqiData = (id, token) => waqiV1('aqi', id, `token=${token}&id=${id}`) - .then((returnedData) => ({ - missions: ['aqi', 'forCompareAqi'], - api, - types: ['v1Aqi'], - returnedData, - })); - const cachedToken = getCachedWaqiToken( - toCaches(getENV('iRingo', 'Weather', database)).waqi.tokens, - stationId, - ); - - return isNonEmptyString(cachedToken) - ? getAqiData(stationId, cachedToken) - : waqiToken(stationId).then((tokenData) => { - if (tokenData.status === 'ok') { - const newCaches = cacheWaqiToken( - toCaches(getENV('iRingo', 'Weather', database)), - stationId, - tokenData.data, - ); - // eslint-disable-next-line - $.setjson( - newCaches, - '@iRingo.Weather.Caches', - ); - return getAqiData(stationId, tokenData.data); - } - - logger('error', `${$.name}:无法获取WAQI token`); - return Promise.resolve({ - missions: ['aqi'], - api, - types: ['mapq'], - returnedData: fallbackData, - }); - }); - }; - - if (missions.includes('aqi') || missions.includes('forCompareAqi')) { - const tokenFromUser = settings.apis.waqi.token; - - if (isNonEmptyString(tokenFromUser)) { - return [ - waqiV2(location, null, tokenFromUser, settings.apis.waqi.httpHeaders) - .then((v2Data) => { - if (missions.includes('forCompareAqi')) { - if (isNonNanNumber(v2Data?.data?.idx)) { - return getHistoryAqis(v2Data.data.idx, v2Data); - } - - logger('error', `${$.name}:无法获取WAQI监测站ID`); - } - - return { - missions: ['aqi'], - api, - types: ['locationFeed'], - returnedData: v2Data, - }; - }), - ]; - } - - return [ - waqiNearest(location, 'mapq', settings.apis.waqi.httpHeaders) - .then((nearestData) => { - if (missions.includes('forCompareAqi')) { - if (Array.isArray(nearestData?.d)) { - const station = nearestData.d.find((s) => ( - isNonNanNumber(parseInt(s?.x, 10)) - )); - const stationId = parseInt(station?.x, 10); - - if (isNonNanNumber(stationId)) { - return getHistoryAqis(stationId, nearestData); - } - - logger('error', `${$.name}:无法获取WAQI监测站ID`); - // eslint-disable-next-line functional/no-conditional-statement - } else { - logger('error', `${$.name}:无法获取WAQI监测站列表`); - } - } - - return { - missions: ['aqi'], - api, - types: ['mapq'], - returnedData: nearestData, - }; - }), - ]; - } - - return []; - } - default: - return []; - } - }) : []; - - // eslint-disable-next-line functional/no-expression-statement - Promise.all(promises).then((dataArray) => { - if (!Array.isArray(dataArray)) { - return dataFromApple; - } - - const dataForAqi = dataArray.find((data) => ( - Array.isArray(data?.missions) && data.missions.includes('aqi') - )); - const dataForAqiComparison = dataArray.find((data) => ( - Array.isArray(data?.missions) && data.missions.includes('forCompareAqi') - )); - const dataForNextHour = dataArray.find((data) => ( - Array.isArray(data?.missions) && data.missions.includes('nextHour') - )); - - const modifiedAirQuality = getAirQuality( - appleApiVersion, - dataForAqi, - languageWithRegion, - ); - if ( - dataForAqi && Object.keys(modifiedAirQuality) - .filter((key) => key !== METADATA && key !== AQI_COMPARISON).length <= 0 - // eslint-disable-next-line functional/no-conditional-statement - ) { - logger('error', `${$.name}:无法处理${dataForAqi?.api}的空气质量数据`); - logger('debug', `API返回数据:${JSON.stringify(dataForAqi)}`); - } - const mergedAirQuality = { - ...airQuality, - ...modifiedAirQuality, - [METADATA]: { ...airQuality?.[METADATA], ...modifiedAirQuality?.[METADATA] }, - }; - const mergedScale = mergedAirQuality?.[AQI_SCALE]; - const pollutants = isObject(mergedAirQuality?.[POLLUTANTS]) - ? Object.values(mergedAirQuality[POLLUTANTS]) : []; - const localConvertedAirQuality = { - ...mergedAirQuality, - ...(settings.aqi.local.switch && pollutants.length > 0 - && settings.aqi.targets.includes( - mergedScale.slice(0, mergedScale.lastIndexOf('.')), - ) - // Little trick for logging - && !logger( - 'info', - `${$.name}:已转换AQI,原始标准:${mergedAirQuality[AQI_SCALE]},` - + `原始AQI:${mergedAirQuality[AQI_INDEX]}`, - ) - && toAirQuality(appleApiVersion, appleToEpaAirQuality( - settingsToAqiStandard[settings.aqi.local.standard], - appleApiVersion === 1 ? convertV1Pollutants(pollutants) : pollutants, - ))), - }; - const aqiLevels = scaleToAqiStandard[localConvertedAirQuality?.[AQI_SCALE]] - ?.AQI_LEVELS; - const modifiedCompareAqi = localConvertedAirQuality?.[AQI_INDEX] >= 0 - && cachedAqi.aqi >= 0 && isObject(aqiLevels) ? compareAqi( - toAqiLevel(Object.values(aqiLevels), localConvertedAirQuality[AQI_INDEX]), - toAqiLevel(Object.values(aqiLevels), cachedAqi.aqi), - ) - : getAqiComparison(dataForAqiComparison); - if ( - dataForAqiComparison && modifiedCompareAqi === 'unknown' - // eslint-disable-next-line functional/no-conditional-statement - ) { - logger('error', `${$.name}:无法处理${dataForAqiComparison?.api}的对比昨日空气质量数据`); - logger('debug', `API返回数据:${JSON.stringify(dataForAqiComparison)}`); - } - const modifiedNextHour = getNextHour( - appleApiVersion, - dataForNextHour, - languageWithRegion, - ); - if ( - dataForNextHour && Object.keys(modifiedNextHour) - .filter((key) => key !== METADATA).length <= 0 - // eslint-disable-next-line functional/no-conditional-statement - ) { - logger('error', `${$.name}:无法处理${dataForNextHour?.api}的下小时降水强度数据`); - logger('debug', `API返回数据:${JSON.stringify(dataForNextHour)}`); - } - - const primaryPollutant = localConvertedAirQuality?.[PRIMARY_POLLUTANT]; - - return { - ...dataFromApple, - ...(requireData.includes(AIR_QUALITY) && { - [AIR_QUALITY]: { - ...localConvertedAirQuality, - ...(needCompareAqi && { [AQI_COMPARISON]: modifiedCompareAqi }), - ...( - isNonEmptyString(primaryPollutant) && ( - !isObject(localConvertedAirQuality?.[POLLUTANTS]) - || !Object.keys(localConvertedAirQuality[POLLUTANTS]) - .some((key) => key === primaryPollutant)) - && { - [POLLUTANTS]: { - ...localConvertedAirQuality?.[POLLUTANTS], - [primaryPollutant]: toPollutant(appleApiVersion, primaryPollutant, -1, 'microgramsPerM3'), - }, - } - ), - }, - }), - ...(requireData.includes(REQUIRE_NEXT_HOUR) && { - [NEXT_HOUR]: { - ...nextHour, - ...modifiedNextHour, - [METADATA]: { ...nextHour?.[METADATA], ...modifiedNextHour?.[METADATA] }, - }, - }), - }; - }).then((responseBody) => { - const time = responseBody?.[AIR_QUALITY]?.[METADATA]?.[REPORTED_TIME]; - const timestamp = appleTimeToTimestamp(appleApiVersion, time, nowHourTimestamp); - - const airQualityProvider = responseBody?.[AIR_QUALITY]?.[METADATA]?.[PROVIDER_NAME]; - const airQualityScale = responseBody?.[AIR_QUALITY]?.[AQI_SCALE]; - - // eslint-disable-next-line functional/no-conditional-statement - if (isNonEmptyString(airQualityScale)) { - // eslint-disable-next-line functional/no-expression-statement - $.setjson(cacheAqi( - toCaches(getENV('iRingo', 'Weather', database)), - timestamp, - location, - qweatherNames.includes(airQualityProvider) - ? responseBody?.[AIR_QUALITY]?.[SOURCE] : null, - airQualityScale.slice(0, airQualityScale.indexOf('.')), - responseBody?.[AIR_QUALITY]?.[AQI_INDEX], - ), '@iRingo.Weather.Caches'); - } - - const nextHourProviderName = responseBody?.[NEXT_HOUR]?.[METADATA]?.[PROVIDER_NAME]; - - return { - ...responseBody, - ...(isNonEmptyString(airQualityProvider) && { - [AIR_QUALITY]: { - ...responseBody[AIR_QUALITY], - [METADATA]: { - ...responseBody[AIR_QUALITY][METADATA], - ...( - Object.keys(responseBody[AIR_QUALITY]) - .filter((key) => key !== METADATA).length <= 1 - && { [TEMPORARILY_UNAVAILABLE]: true } - ), - }, - }, - }), - ...(isNonEmptyString(nextHourProviderName) && { - [NEXT_HOUR]: { - ...responseBody[NEXT_HOUR], - [METADATA]: { - ...responseBody[NEXT_HOUR][METADATA], - ...( - Object.keys(responseBody[NEXT_HOUR]) - .filter((key) => key !== METADATA && key !== AQI_COMPARISON).length <= 0 - && { [TEMPORARILY_UNAVAILABLE]: true } - ), - }, - }, - }), - }; - }).then((responseBody) => { - // eslint-disable-next-line functional/no-conditional-statement - if (responseBody?.[AIR_QUALITY]?.[METADATA]?.[TEMPORARILY_UNAVAILABLE]) { - logger( - 'error', - `${$.name}:检测到未能成功获取空气质量数据,` - + `数据源:${responseBody?.[AIR_QUALITY]?.[METADATA]?.[PROVIDER_NAME]}` - + `${settings.log.location ? `,经度:${longitude},纬度:${latitude}` : ''}`, - ); - } - // eslint-disable-next-line functional/no-conditional-statement - if (responseBody?.[NEXT_HOUR]?.[METADATA]?.[TEMPORARILY_UNAVAILABLE]) { - logger( - 'error', - `${$.name}:检测到未能成功获取下小时降水数据,` - + `数据源:${responseBody?.[NEXT_HOUR]?.[METADATA]?.[PROVIDER_NAME]}` - + `${settings.log.location ? `,经度:${longitude},纬度:${latitude}` : ''}`, - ); - } - - // eslint-disable-next-line functional/no-expression-statement,no-undef - setResponse({ ...$response, body: JSON.stringify(responseBody) }); - }); - // eslint-disable-next-line functional/no-conditional-statement - } else { - // eslint-disable-next-line functional/no-expression-statement - logger('error', `${$.name}:缺失经纬度信息。经度:${parameters?.lng},纬度:${parameters?.lat}`); - // eslint-disable-next-line functional/no-expression-statement - logger('debug', `${$.name}:URL参数:${JSON.stringify(parameters)}`); - // eslint-disable-next-line functional/no-expression-statement,no-undef - setResponse($response); - } - // eslint-disable-next-line functional/no-conditional-statement - } else { - // eslint-disable-next-line functional/no-expression-statement,no-undef - logger('error', `${$.name}:数据解析失败,HTTP body = ${$response?.body}`); - // eslint-disable-next-line functional/no-expression-statement,no-undef - logger('debug', `${$.name}:响应信息:${JSON.stringify($response)}`); - // eslint-disable-next-line functional/no-expression-statement,no-undef - setResponse($response); - } - } - // eslint-disable-next-line functional/no-conditional-statement - } else { - // eslint-disable-next-line functional/no-expression-statement,no-undef - logger('error', `${$.name}:无法解析URL,url = ${url}, $response.url = ${$request?.url}`); - // eslint-disable-next-line functional/no-expression-statement,no-undef - logger('debug', `${$.name}:请求信息:${JSON.stringify($request)}`); - // eslint-disable-next-line functional/no-expression-statement,no-undef - setResponse($response); - } - // eslint-disable-next-line functional/no-conditional-statement - } else { - // eslint-disable-next-line functional/no-expression-statement,no-undef - logger('error', `${$.name}:无法获取URL信息,$response.url = ${$request?.url}`); - // eslint-disable-next-line functional/no-expression-statement,no-undef - logger('debug', `${$.name}:请求信息:${JSON.stringify($request)}`); - // eslint-disable-next-line functional/no-expression-statement,no-undef - setResponse($response); - } -// eslint-disable-next-line functional/no-conditional-statement -} else { - // eslint-disable-next-line functional/no-expression-statement - logger('warn', `${$.name}:Box.js设置内或参数内已禁用模块`); - // eslint-disable-next-line functional/no-expression-statement,no-undef - setResponse($response); -} - -/***************** Env *****************/ -// prettier-ignore -// noinspection -// eslint-disable-next-line -// https://github.com/chavyleung/scripts/blob/master/Env.min.js -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}isStash(){return"undefined"!=typeof $environment&&$environment["stash-version"]}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,i=t["update-pasteboard"]||t.updatePasteboard;return{"open-url":e,"media-url":s,"update-pasteboard":i}}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)} - -// noinspection -// eslint-disable-next-line -// https://github.com/VirgilClyne/VirgilClyne/blob/main/function/URL/URLs.embedded.min.js -function URLs(s){return new class{constructor(s=[]){this.name="URL v1.0.0",this.opts=s,this.json={url:{scheme:"",host:"",path:""},params:{}}}parse(s){let t=s.match(/(?.+):\/\/(?[^/]+)\/?(?[^?]+)?\??(?.*)?/)?.groups??null;return t?.params&&(t.params=Object.fromEntries(t.params.split("&").map((s=>s.split("="))))),t}stringify(s=this.json){return s?.params?s.scheme+"://"+s.host+"/"+s.path+"?"+Object.entries(s.params).map((s=>s.join("="))).join("&"):s.scheme+"://"+s.host+"/"+s.path}}(s)} diff --git a/modules/Siri.srmodule b/modules/Siri.srmodule index 97c470ab2..46b49092c 100644 --- a/modules/Siri.srmodule +++ b/modules/Siri.srmodule @@ -1,24 +1,24 @@ #!name= iRingo: ⭕ Siri & Search #!desc=V1️⃣ & iOS 17.6⬇️\n全面自定义「Siri与搜索」中的「Siri 建议」功能。\n⚠️本模块不含自定义「Siri与搜索」中的「询问 Siri」功能。 #!openUrl=http://boxjs.com/#/app/iRingo.Siri -#!author=VirgilClyne -#!homepage=https://github.com/VirgilClyne -#!manual=https://github.com/VirgilClyne/iRingo/wiki/⭕-Siri与搜索 -#!icon=https://raw.githubusercontent.com/Koolson/Qure/master/IconSet/Color/Siri.png +#!author=VirgilClyne[https://github.com/VirgilClyne] +#!homepage=https://github.com/NSRingo +#!manual=https://github.com/NSRingo/Siri/wiki/⭕-Siri与搜索 +#!icon=https://raw.githubusercontent.com/NSRingo/Siri/main/images/icon/v1/Siri%20-%20Icon.png #!category= iRingo [Script] # Bag (iOS/macOS) -⭕ Siri.Bag.request = type=http-request, pattern=^https?:\/\/api2?\.smoot\.apple\.(com|cn)\/bag, requires-body=0, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/main/js/archive/Siri.request.js, argument= -⭕ Siri.Bag.response = type=http-response, pattern=^https?:\/\/api2?\.smoot\.apple\.(com|cn)\/bag, requires-body=1, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/main/js/archive/Siri.response.js, argument= +⭕ Siri.Bag.request = type=http-request, pattern=^https?:\/\/api2?\.smoot\.apple\.(com|cn)\/bag, requires-body=0, script-path=https://github.com/NSRingo/Siri/releases/latest/download/request.js, argument= +⭕ Siri.Bag.response = type=http-response, pattern=^https?:\/\/api2?\.smoot\.apple\.(com|cn)\/bag, requires-body=1, script-path=https://github.com/NSRingo/Siri/releases/latest/download/response.js, argument= # Siri Suggestions & Look Up Search (iOS/macOS) -🔍 Siri.Search.request = type=http-request, pattern=^https?:\/\/api(2|-.+)\.smoot\.apple\.com\/search, requires-body=0, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/main/js/archive/Siri.request.js, argument= -🔍 Siri.Search.response = type=http-response, pattern=^https?:\/\/api(2|-.+)\.smoot\.apple\.com\/search, requires-body=1, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/main/js/archive/Siri.response.js, argument= +🔍 Siri.Search.request = type=http-request, pattern=^https?:\/\/api(2|-.+)\.smoot\.apple\.com\/search, requires-body=0, script-path=https://github.com/NSRingo/Siri/releases/latest/download/request.js, argument= +🔍 Siri.Search.response = type=http-response, pattern=^https?:\/\/api(2|-.+)\.smoot\.apple\.com\/search, requires-body=1, script-path=https://github.com/NSRingo/Siri/releases/latest/download/response.js, argument= # Siri Infomation Card (macOS) -📇 Siri.Card.request = type=http-request, pattern=^https?:\/\/api(2|-.+)\.smoot\.apple\.com\/card, requires-body=0, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/main/js/archive/Siri.request.js, argument= -📇 Siri.Card.response = type=http-response, pattern=^https?:\/\/api(2|-.+)\.smoot\.apple\.com\/card, requires-body=1, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/main/js/archive/Siri.response.js, argument= +📇 Siri.Card.request = type=http-request, pattern=^https?:\/\/api(2|-.+)\.smoot\.apple\.com\/card, requires-body=0, script-path=https://github.com/NSRingo/Siri/releases/latest/download/request.js, argument= +📇 Siri.Card.response = type=http-response, pattern=^https?:\/\/api(2|-.+)\.smoot\.apple\.com\/card, requires-body=1, script-path=https://github.com/NSRingo/Siri/releases/latest/download/response.js, argument= # Flight Search (iOS/macOS) -🛫 Siri.Flight.request = type=http-request, pattern=^https?:\/\/api(2|-.+)\.smoot\.apple\.com\/flight, requires-body=0, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/main/js/archive/Siri.request.js, argument= +🛫 Siri.Flight.request = type=http-request, pattern=^https?:\/\/api(2|-.+)\.smoot\.apple\.com\/flight, requires-body=0, script-path=https://github.com/NSRingo/Siri/releases/latest/download/request.js, argument= [MITM] hostname = %APPEND% api*.smoot.apple.com, api.smoot.apple.cn diff --git a/plugin/News.plugin b/plugin/News.plugin index 5112c2efa..1d5ec01ff 100644 --- a/plugin/News.plugin +++ b/plugin/News.plugin @@ -2,8 +2,8 @@ #!desc=1.自定义解锁News的地区 2.Coming Soon… 注:该插件包含代理规则,安装过程中需要匹配代理策略组为🇺🇸🇬🇧🇨🇦🇦🇺线路,使用过程中gateway.icloud.com会走与News相同的代理线路。 #!openUrl=http://boxjs.com/#/app/iRingo.News #!author=VirgilClyne -#!homepage=https://github.com/VirgilClyne -#!manual=https://github.com/VirgilClyne/iRingo/wiki/📰News +#!homepage=https://github.com/NSRingo +#!manual=https://github.com/NSRingo/Siri/wiki/📰News #!icon=https://www.apple.com/v/apple-news/i/images/shared/icon_news_plus__c9r2qvl6xfqu_large.png #!category= iRingo diff --git a/plugin/Siri.macOS.plugin b/plugin/Siri.macOS.plugin index 59ff26ce9..57da7d1b9 100644 --- a/plugin/Siri.macOS.plugin +++ b/plugin/Siri.macOS.plugin @@ -1,10 +1,10 @@ #!name= iRingo: ⭕ Siri & Spotlight #!desc=V1️⃣ & iOS 17.6⬇️\n全面自定义「Siri与聚焦」中的「Siri 建议」功能。\n⚠️本模块不含自定义「Siri与聚焦」中的「询问 Siri」功能。 #!openUrl=http://boxjs.com/#/app/iRingo.Siri -#!author=VirgilClyne -#!homepage=https://github.com/VirgilClyne -#!manual=https://github.com/VirgilClyne/iRingo/wiki/⭕-Siri与搜索 -#!icon=https://raw.githubusercontent.com/Koolson/Qure/master/IconSet/Color/Siri.png +#!author=VirgilClyne[https://github.com/VirgilClyne] +#!homepage=https://github.com/NSRingo +#!manual=https://github.com/NSRingo/Siri/wiki/⭕-Siri与搜索 +#!icon=https://raw.githubusercontent.com/NSRingo/Siri/main/images/icon/v1/Siri%20-%20Icon.png #!tag= iRingo #!system=macOS @@ -17,16 +17,16 @@ DOMAIN,e16991.b.akamaiedge.net [Script] # Bag (iOS/macOS) -http-request ^https?:\/\/api2?\.smoot\.apple\.(com|cn)\/bag script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/main/js/Siri.request.js, tag=⭕ Siri.Bag.request -http-response ^https?:\/\/api2?\.smoot\.apple\.(com|cn)\/bag script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/main/js/Siri.response.js, requires-body=1, tag=⭕ Siri.Bag.response +http-request ^https?:\/\/api2?\.smoot\.apple\.(com|cn)\/bag script-path=https://github.com/NSRingo/Siri/releases/latest/download/request.js, tag=⭕ Siri.Bag.request +http-response ^https?:\/\/api2?\.smoot\.apple\.(com|cn)\/bag script-path=https://github.com/NSRingo/Siri/releases/latest/download/response.js, requires-body=1, tag=⭕ Siri.Bag.response # Siri Suggestions & Look Up Search (iOS/macOS) -http-request ^https?:\/\/api(2|-.+)\.smoot\.apple\.(com|cn)\/search script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/main/js/Siri.request.js, tag=🔍 Siri.Search.request -http-response ^https?:\/\/api(2|-.+)\.smoot\.apple\.(com|cn)\/search script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/main/js/Siri.response.js, requires-body=1, tag=🔍 Siri.Search.response +http-request ^https?:\/\/api(2|-.+)\.smoot\.apple\.(com|cn)\/search script-path=https://github.com/NSRingo/Siri/releases/latest/download/request.js, tag=🔍 Siri.Search.request +http-response ^https?:\/\/api(2|-.+)\.smoot\.apple\.(com|cn)\/search script-path=https://github.com/NSRingo/Siri/releases/latest/download/response.js, requires-body=1, tag=🔍 Siri.Search.response # Siri Infomation Card (macOS) -http-request ^https?:\/\/api(2|-.+)\.smoot\.apple\.(com|cn)\/card script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/main/js/Siri.request.js, tag=📇 Siri.Card.request -http-response ^https?:\/\/api(2|-.+)\.smoot\.apple\.(com|cn)\/card script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/main/js/Siri.response.js, requires-body=1, tag=📇 Siri.Card.response +http-request ^https?:\/\/api(2|-.+)\.smoot\.apple\.(com|cn)\/card script-path=https://github.com/NSRingo/Siri/releases/latest/download/request.js, tag=📇 Siri.Card.request +http-response ^https?:\/\/api(2|-.+)\.smoot\.apple\.(com|cn)\/card script-path=https://github.com/NSRingo/Siri/releases/latest/download/response.js, requires-body=1, tag=📇 Siri.Card.response # Flight Search (iOS/macOS) -http-request ^https?:\/\/api(2|-.+)\.smoot\.apple\.(com|cn)\/flight script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/main/js/Siri.request.js, tag=🛫 Siri.Flight.request +http-request ^https?:\/\/api(2|-.+)\.smoot\.apple\.(com|cn)\/flight script-path=https://github.com/NSRingo/Siri/releases/latest/download/request.js, tag=🛫 Siri.Flight.request [MITM] hostname = api*.smoot.apple.com, api.smoot.apple.cn diff --git a/plugin/Siri.plugin b/plugin/Siri.plugin index 631dfefdd..496b78749 100644 --- a/plugin/Siri.plugin +++ b/plugin/Siri.plugin @@ -1,22 +1,22 @@ #!name= iRingo: ⭕ Siri & Search #!desc=V1️⃣ & iOS 17.6⬇️\n全面自定义「Siri与搜索」中的「Siri 建议」功能。\n⚠️本模块不含自定义「Siri与搜索」中的「询问 Siri」功能。 #!openUrl=http://boxjs.com/#/app/iRingo.Siri -#!author=VirgilClyne -#!homepage=https://github.com/VirgilClyne -#!manual=https://github.com/VirgilClyne/iRingo/wiki/⭕-Siri与搜索 -#!icon=https://raw.githubusercontent.com/Koolson/Qure/master/IconSet/Color/Siri.png +#!author=VirgilClyne[https://github.com/VirgilClyne] +#!homepage=https://github.com/NSRingo +#!manual=https://github.com/NSRingo/Siri/wiki/⭕-Siri与搜索 +#!icon=https://raw.githubusercontent.com/NSRingo/Siri/main/images/icon/v1/Siri%20-%20Icon.png #!tag= iRingo #!system=iOS,iPadOS,watchOS [Script] # Bag (iOS/macOS) -http-request ^https?:\/\/api2?\.smoot\.apple\.(com|cn)\/bag script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/main/js/Siri.request.js, tag=⭕ Siri.Bag.request -http-response ^https?:\/\/api2?\.smoot\.apple\.(com|cn)\/bag script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/main/js/Siri.response.js, requires-body=1, tag=⭕ Siri.Bag.response +http-request ^https?:\/\/api2?\.smoot\.apple\.(com|cn)\/bag script-path=https://github.com/NSRingo/Siri/releases/latest/download/request.js, tag=⭕ Siri.Bag.request +http-response ^https?:\/\/api2?\.smoot\.apple\.(com|cn)\/bag script-path=https://github.com/NSRingo/Siri/releases/latest/download/response.js, requires-body=1, tag=⭕ Siri.Bag.response # Siri Suggestions & Look Up Search (iOS/macOS) -http-request ^https?:\/\/api(2|-.+)\.smoot\.apple\.(com|cn)\/search script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/main/js/Siri.request.js, tag=🔍 Siri.Search.request -http-response ^https?:\/\/api(2|-.+)\.smoot\.apple\.(com|cn)\/search script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/main/js/Siri.response.js, requires-body=1, tag=🔍 Siri.Search.response +http-request ^https?:\/\/api(2|-.+)\.smoot\.apple\.(com|cn)\/search script-path=https://github.com/NSRingo/Siri/releases/latest/download/request.js, tag=🔍 Siri.Search.request +http-response ^https?:\/\/api(2|-.+)\.smoot\.apple\.(com|cn)\/search script-path=https://github.com/NSRingo/Siri/releases/latest/download/response.js, requires-body=1, tag=🔍 Siri.Search.response # Flight Search (iOS/macOS) -http-request ^https?:\/\/api(2|-.+)\.smoot\.apple\.(com|cn)\/flight script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/main/js/Siri.request.js, tag=🛫 Siri.Flight.request +http-request ^https?:\/\/api(2|-.+)\.smoot\.apple\.(com|cn)\/flight script-path=https://github.com/NSRingo/Siri/releases/latest/download/request.js, tag=🛫 Siri.Flight.request [MITM] hostname = api*.smoot.apple.com, api.smoot.apple.cn diff --git a/plugin/Weather.plugin b/plugin/Weather.plugin index 3a8ce1850..bdfb005b2 100644 --- a/plugin/Weather.plugin +++ b/plugin/Weather.plugin @@ -1,21 +1,20 @@ -#!name= iRingo for 🌤 Weather -#!desc=(V4) 1.解锁全部天气功能 2.替换空气质量数据 3.添加下一小时降水数据 4.替换空气质量地图 +#!name= iRingo: 🌤 Weather +#!desc=V4️⃣ & iOS 16⬇️\n1.解锁全部天气功能\n2.替换空气质量数据\n3.添加下一小时降水数据\n4.替换空气质量地图 #!openUrl=http://boxjs.com/#/app/iRingo.Weather -#!author=Wordless Echo -#!homepage=https://github.com/WordlessEcho -#!manual=https://github.com/VirgilClyne/iRingo/wiki/🌤天气 -#!icon=https://is4-ssl.mzstatic.com/image/thumb/Purple125/v4/c8/76/87/c8768792-b7ab-7de4-9c70-1888096b7ae9/AppIcon-0-0-1x_U007emarketing-0-0-0-10-0-0-sRGB-0-0-0-GLES2_U002c0-512MB-85-220-0-0.png/108x0w.webp +#!author=WordlessEcho[https://github.com/WordlessEcho] +#!homepage=https://github.com/NSRingo +#!manual=https://github.com/NSRingo/Weather/wiki +#!icon=https://developer.apple.com/assets/elements/icons/weather/weather-128x128.png #!category= iRingo [Rule] -DOMAIN,weather-analytics-events.apple.com,REJECT -DOMAIN-SUFFIX,waqi.info +DOMAIN,weather-analytics-events.apple.com,REJECT-DROP [Script] -http-request ^https?:\/\/weather-data\.apple\.com\/(v1|v2|v3)\/availability\/ script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/main/js/Weather.Availability.request.js, requires-body=0, tag=Weather Availability Request -http-response ^https?:\/\/weather-data\.apple\.com\/(v1|v2|v3)\/availability\/ script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/main/js/Weather.Availability.response.js, requires-body=1, tag=Weather Availability Response -http-response ^https?:\/\/weather-data\.apple\.com\/(v1|v2|v3)\/weather\/ script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/main/js/Weather.response.js, requires-body=1, timeout=20, tag=Weather Response -http-request ^https?:\/\/weather-map2?\.apple\.com\/(v1|v2)\/mapOverlay\/\w+(\?.*country=CN.*)?$ script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/main/js/Weather.Map.request.js, requires-body=0, tag=Weather Map Request +Weather Availability Request = type=http-request, pattern=^https?:\/\/weather-data\.apple\.com\/(v1|v2|v3)\/availability\/, requires-body=0, script-path=https://raw.githubusercontent.com/NSRingo/Weather/main/js/Availability.request.js, argument= +Weather Availability Response = type=http-response, pattern=^https?:\/\/weather-data\.apple\.com\/(v1|v2|v3)\/availability\/, requires-body=1, script-path=https://raw.githubusercontent.com/NSRingo/Weather/main/js/Availability.response.js, argument= +Weather Response = type=http-response, pattern=^https?:\/\/weather-data\.apple\.com\/(v1|v2|v3)\/weather\/, requires-body=1, timeout=20, script-path=https://raw.githubusercontent.com/NSRingo/Weather/main/js/response.js, argument= +Weather Map Request = type=http-request, pattern=^https?:\/\/weather-map2?\.apple\.com\/(v1|v2)\/mapOverlay\/\w+(\?.*country=CN.*)?$, requires-body=0, script-path=https://raw.githubusercontent.com/NSRingo/Weather/main/js/Map.request.js, argument= [MITM] -hostname = weather-edge.apple.com, weather-data.apple.com, weather-map.apple.com, weather-map2.apple.com +hostname = %APPEND% weather-edge.apple.com, weather-data.apple.com, weather-data-origin.apple.com, weather-map.apple.com, weather-map2.apple.com diff --git a/sgmodule/Siri.V2.beta.sgmodule b/sgmodule/Siri.V2.beta.sgmodule deleted file mode 100644 index 8193ad312..000000000 --- a/sgmodule/Siri.V2.beta.sgmodule +++ /dev/null @@ -1,30 +0,0 @@ -#!name= iRingo: ⭕ Siri β -#!desc=BETA: V2️⃣ & iOS 18⬆️ & watchOS 11⬆️\n全面自定义「Siri」中的「Siri 请求」和「Siri 建议」功能。 -#!openUrl=http://boxjs.com/#/app/iRingo.Siri.Beta -#!author=VirgilClyne -#!homepage=https://github.com/VirgilClyne -#!manual=https://github.com/VirgilClyne/iRingo/wiki/⭕-Siri -#!icon=https://upload.wikimedia.org/wikipedia/commons/d/de/Logo_Apple_Siri_iOS_2024.svg -#!category= iRingo -#!arguments=CountryCode:US -#!arguments-desc=CountryCode: 国家或地区代码 - -[Script] -# Apple.Parsec.Siri.V2Alpha.SiriSearch -⭕ Siri.SiriSearch.request = type=http-request, pattern=^https?:\/\/(guzzoni|api2|api-.*)\.smoot\.apple\.com\/apple\.parsec\.siri\.v2alpha\.SiriSearch\/SiriSearch, requires-body=1, binary-body-mode=1, engine=webview, debug=1, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/beta/js/Siri.request.beta.js, argument=CountryCode={{{CountryCode}}} -⭕ Siri.SiriSearch.response = type=http-response, pattern=^https?:\/\/(guzzoni|api2|api-.*)\.smoot\.apple\.com\/apple\.parsec\.siri\.v2alpha\.SiriSearch\/SiriSearch, requires-body=1, binary-body-mode=1, engine=webview, debug=1, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/beta/js/Siri.response.beta.js, argument=CountryCode={{{CountryCode}}} -# Apple.Parsec.Lookup.V1Alpha.LookupSearch -🔍 Lookup.LookupSearch.request = type=http-request, pattern=^https?:\/\/(api-lookup|api2|api-.*)\.smoot\.apple\.com\/apple\.parsec\.lookup\.v1alpha\.LookupSearch\/LookupSearch, requires-body=1, binary-body-mode=1, engine=webview, debug=1, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/beta/js/Siri.request.beta.js, argument=CountryCode={{{CountryCode}}} -🔍 Lookup.LookupSearch.response = type=http-response, pattern=^https?:\/\/(api-lookup|api2|api-.*)\.smoot\.apple\.com\/apple\.parsec\.lookup\.v1alpha\.LookupSearch\/LookupSearch, requires-body=1, binary-body-mode=1, engine=webview, debug=1, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/beta/js/Siri.response.beta.js, argument=CountryCode={{{CountryCode}}} -# Apple.Parsec.Visualsearch.V2.VisualSearch -🔍 Visualsearch.VisualSearch.request = type=http-request, pattern=^https?:\/\/(api2|api-.*)\.smoot\.apple\.com\/apple\.parsec\.visualsearch\.v2\.VisualSearch\/VisualSearch, requires-body=1, binary-body-mode=1, engine=webview, debug=1, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/beta/js/Siri.request.beta.js, argument=CountryCode={{{CountryCode}}} -🔍 Visualsearch.VisualSearch.response = type=http-response, pattern=^https?:\/\/(api2|api-.*)\.smoot\.apple\.com\/apple\.parsec\.visualsearch\.v2\.VisualSearch\/VisualSearch, requires-body=1, binary-body-mode=1, engine=webview, debug=1, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/beta/js/Siri.response.beta.js, argument=CountryCode={{{CountryCode}}} -# Apple.Parsec.ResponseFramework.Engagement.V1Alpha.EngagementSearch -🔍 ResponseFramework.Engagement.EngagementSearch.request = type=http-request, pattern=^https?:\/\/(api-.*)\.smoot\.apple\.com\/apple\.parsec\.responseframework\.engagement\.v1alpha\.EngagementSearch\/EngagementSearch, requires-body=1, binary-body-mode=1, engine=webview, debug=1, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/beta/js/Siri.request.beta.js, argument=CountryCode={{{CountryCode}}} -🔍 ResponseFramework.Engagement.EngagementSearch.response = type=http-response, pattern=^https?:\/\/(api-.*)\.smoot\.apple\.com\/apple\.parsec\.responseframework\.engagement\.v1alpha\.EngagementSearch\/EngagementSearch, requires-body=1, binary-body-mode=1, engine=webview, debug=1, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/beta/js/Siri.response.beta.js, argument=CountryCode={{{CountryCode}}} -# Apple.Parsec.Spotlight.V1Alpha.ZkwSuggestService -🔍 Spotlight.ZkwSuggestService.request = type=http-request, pattern=^https?:\/\/(api-.*)\.smoot\.apple\.com\/apple\.parsec\.spotlight\.v1alpha\.ZkwSuggestService\/Suggest, requires-body=1, binary-body-mode=1, engine=webview, debug=1, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/beta/js/Siri.request.beta.js, argument=CountryCode={{{CountryCode}}} -🔍 Spotlight.ZkwSuggestService.response = type=http-response, pattern=^https?:\/\/(api-.*)\.smoot\.apple\.com\/apple\.parsec\.spotlight\.v1alpha\.ZkwSuggestService\/Suggest, requires-body=1, binary-body-mode=1, engine=webview, debug=1, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/beta/js/Siri.response.beta.js, argument=CountryCode={{{CountryCode}}} - -[MITM] -hostname = %APPEND% guzzoni.smoot.apple.com, api2.smoot.apple.com, api-*.smoot.apple.com diff --git a/sgmodule/Siri.V2.macOS.beta.sgmodule b/sgmodule/Siri.V2.macOS.beta.sgmodule deleted file mode 100644 index 574aa4f07..000000000 --- a/sgmodule/Siri.V2.macOS.beta.sgmodule +++ /dev/null @@ -1,35 +0,0 @@ -#!name= iRingo: ⭕ Siri β -#!desc=BETA: V2️⃣ & macOS 15⬆️\n全面自定义「Siri」中的「Siri 请求」和「聚焦」中的「Siri 建议」功能 -#!openUrl=http://boxjs.com/#/app/iRingo.Siri.Beta -#!author=VirgilClyne -#!homepage=https://github.com/VirgilClyne -#!manual=https://github.com/VirgilClyne/iRingo/wiki/⭕-Siri -#!icon=https://upload.wikimedia.org/wikipedia/commons/d/de/Logo_Apple_Siri_iOS_2024.svg -#!category= iRingo -#!system=mac -#!arguments=CountryCode: US,Proxy: 🌑Proxy -#!arguments-desc=CountryCode: 国家或地区代码\nProxy: macOS‘查询’代理策略 - -[Rule] -# > Look Up (Only for Wikipedia@macOS, Surge Enhance Mode enabled required) -RULE-SET,https://raw.githubusercontent.com/VirgilClyne/iRingo/main/RuleSet/LookUp.Wikipedia.list,{{{Proxy}}} - -[Script] -# Apple.Parsec.Siri.V2Alpha.SiriSearch -⭕ Siri.SiriSearch.request = type=http-request, pattern=^https?:\/\/(guzzoni|api2|api-.*)\.smoot\.apple\.com\/apple\.parsec\.siri\.v2alpha\.SiriSearch\/SiriSearch, requires-body=1, binary-body-mode=1, engine=webview, debug=1, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/beta/js/Siri.request.beta.js, argument=CountryCode={{{CountryCode}}} -⭕ Siri.SiriSearch.response = type=http-response, pattern=^https?:\/\/(guzzoni|api2|api-.*)\.smoot\.apple\.com\/apple\.parsec\.siri\.v2alpha\.SiriSearch\/SiriSearch, requires-body=1, binary-body-mode=1, engine=webview, debug=1, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/beta/js/Siri.response.beta.js, argument=CountryCode={{{CountryCode}}} -# Apple.Parsec.Lookup.V1Alpha.LookupSearch -🔍 Lookup.LookupSearch.request = type=http-request, pattern=^https?:\/\/(api-lookup|api2|api-.*)\.smoot\.apple\.com\/apple\.parsec\.lookup\.v1alpha\.LookupSearch\/LookupSearch, requires-body=1, binary-body-mode=1, engine=webview, debug=1, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/beta/js/Siri.request.beta.js, argument=CountryCode={{{CountryCode}}} -🔍 Lookup.LookupSearch.response = type=http-response, pattern=^https?:\/\/(api-lookup|api2|api-.*)\.smoot\.apple\.com\/apple\.parsec\.lookup\.v1alpha\.LookupSearch\/LookupSearch, requires-body=1, binary-body-mode=1, engine=webview, debug=1, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/beta/js/Siri.response.beta.js, argument=CountryCode={{{CountryCode}}} -# Apple.Parsec.Visualsearch.V2.VisualSearch -🔍 Visualsearch.VisualSearch.request = type=http-request, pattern=^https?:\/\/(api2|api-.*)\.smoot\.apple\.com\/apple\.parsec\.visualsearch\.v2\.VisualSearch\/VisualSearch, requires-body=1, binary-body-mode=1, engine=webview, debug=1, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/beta/js/Siri.request.beta.js, argument=CountryCode={{{CountryCode}}} -🔍 Visualsearch.VisualSearch.response = type=http-response, pattern=^https?:\/\/(api2|api-.*)\.smoot\.apple\.com\/apple\.parsec\.visualsearch\.v2\.VisualSearch\/VisualSearch, requires-body=1, binary-body-mode=1, engine=webview, debug=1, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/beta/js/Siri.response.beta.js, argument=CountryCode={{{CountryCode}}} -# Apple.Parsec.ResponseFramework.Engagement.V1Alpha.EngagementSearch -🔍 ResponseFramework.Engagement.EngagementSearch.request = type=http-request, pattern=^https?:\/\/(api2|api-.*)\.smoot\.apple\.com\/apple\.parsec\.responseframework\.engagement\.v1alpha\.EngagementSearch\/EngagementSearch, requires-body=1, binary-body-mode=1, engine=webview, debug=1, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/beta/js/Siri.request.beta.js, argument=CountryCode={{{CountryCode}}} -🔍 ResponseFramework.Engagement.EngagementSearch.response = type=http-response, pattern=^https?:\/\/(api2|api-.*)\.smoot\.apple\.com\/apple\.parsec\.responseframework\.engagement\.v1alpha\.EngagementSearch\/EngagementSearch, requires-body=1, binary-body-mode=1, engine=webview, debug=1, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/beta/js/Siri.response.beta.js, argument=CountryCode={{{CountryCode}}} -# Apple.Parsec.Spotlight.V1Alpha.ZkwSuggestService -🔍 Spotlight.ZkwSuggestService.request = type=http-request, pattern=^https?:\/\/(api2|api-.*)\.smoot\.apple\.com\/apple\.parsec\.spotlight\.v1alpha\.ZkwSuggestService\/Suggest, requires-body=1, binary-body-mode=1, engine=webview, debug=1, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/beta/js/Siri.request.beta.js, argument=CountryCode={{{CountryCode}}} -🔍 Spotlight.ZkwSuggestService.response = type=http-response, pattern=^https?:\/\/(api2|api-.*)\.smoot\.apple\.com\/apple\.parsec\.spotlight\.v1alpha\.ZkwSuggestService\/Suggest, requires-body=1, binary-body-mode=1, engine=webview, debug=1, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/beta/js/Siri.response.beta.js, argument=CountryCode={{{CountryCode}}} - -[MITM] -hostname = %APPEND% guzzoni.smoot.apple.com, api2.smoot.apple.com, api-*.smoot.apple.com diff --git a/sgmodule/Siri.beta.sgmodule b/sgmodule/Siri.beta.sgmodule deleted file mode 100644 index 81f07e9b4..000000000 --- a/sgmodule/Siri.beta.sgmodule +++ /dev/null @@ -1,31 +0,0 @@ -#!name= iRingo: ⭕ Siri & Search β -#!desc=BETA: V1️⃣ & iOS 17.6⬇️\n全面自定义「Siri与搜索」中的「Siri 建议」功能。\n⚠️本模块不含自定义「Siri与搜索」中的「询问 Siri」功能。 -#!openUrl=http://boxjs.com/#/app/iRingo.Siri.Beta -#!author=VirgilClyne -#!homepage=https://github.com/VirgilClyne -#!manual=https://github.com/VirgilClyne/iRingo/wiki/⭕-Siri与搜索 -#!icon=https://raw.githubusercontent.com/Koolson/Qure/master/IconSet/Color/Siri.png -#!category= iRingo -#!system=ios -#!arguments=CountryCode:SG -#!arguments-desc=CountryCode: 国家或地区代码 - -[Script] -# Bag (iOS/macOS) -⭕ Siri.Bag.request = type=http-request, pattern=^https?:\/\/api2?\.smoot\.apple\.(com|cn)\/bag, requires-body=0, engine=webview, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/beta/js/Siri.request.beta.js, argument=CountryCode={{{CountryCode}}} -⭕ Siri.Bag.response = type=http-response, pattern=^https?:\/\/api2?\.smoot\.apple\.(com|cn)\/bag, requires-body=1, engine=webview, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/beta/js/Siri.response.beta.js, argument=CountryCode={{{CountryCode}}} -# Siri Suggestions & Look Up Search (iOS/macOS) -🔍 Siri.Search.request = type=http-request, pattern=^https?:\/\/api(2|-.+)\.smoot\.apple\.com\/search, requires-body=0, engine=webview, debug=1, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/beta/js/Siri.request.beta.js, argument=CountryCode={{{CountryCode}}} -🔍 Siri.Search.response = type=http-response, pattern=^https?:\/\/api(2|-.+)\.smoot\.apple\.com\/search, requires-body=1, engine=webview, debug=1, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/beta/js/Siri.response.beta.js, argument=CountryCode={{{CountryCode}}} -# Flight Search (iOS/macOS) -🛫 Siri.Flight.request = type=http-request, pattern=^https?:\/\/api(2|-.+)\.smoot\.apple\.com\/flight, requires-body=0, engine=webview, debug=1, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/beta/js/Siri.request.beta.js, argument=CountryCode={{{CountryCode}}} -# warm (unknown) -⭕ Siri.Warm.request = type=http-request, pattern=^https?:\/\/api(2|-.+)\.smoot\.apple\.com\/warm, requires-body=0, engine=webview, debug=1, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/beta/js/Siri.request.beta.js, argument=CountryCode={{{CountryCode}}} -# render (unknown) -⭕ Siri.Render.request = type=http-request, pattern=^https?:\/\/api(2|-.+)\.smoot\.apple\.com\/render, requires-body=0, engine=webview, debug=1, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/beta/js/Siri.request.beta.js, argument=CountryCode={{{CountryCode}}} -# Apple.Parsec.Visualsearch.V2.VisualSearch -🔍 Visualsearch.VisualSearch.request = type=http-request, pattern=^https?:\/\/(api2|api-.*)\.smoot\.apple\.com\/apple\.parsec\.visualsearch\.v2\.VisualSearch\/VisualSearch, requires-body=1, binary-body-mode=1, engine=webview, debug=1, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/beta/js/Siri.request.beta.js, argument=CountryCode={{{CountryCode}}} -🔍 Visualsearch.VisualSearch.response = type=http-response, pattern=^https?:\/\/(api2|api-.*)\.smoot\.apple\.com\/apple\.parsec\.visualsearch\.v2\.VisualSearch\/VisualSearch, requires-body=1, binary-body-mode=1, engine=webview, debug=1, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/beta/js/Siri.response.beta.js, argument=CountryCode={{{CountryCode}}} - -[MITM] -hostname = %APPEND% api*.smoot.apple.com, api.smoot.apple.cn diff --git a/sgmodule/Siri.macOS.beta.sgmodule b/sgmodule/Siri.macOS.beta.sgmodule deleted file mode 100644 index c8cf78f1e..000000000 --- a/sgmodule/Siri.macOS.beta.sgmodule +++ /dev/null @@ -1,38 +0,0 @@ -#!name= iRingo: ⭕ Siri & Spotlight β -#!desc=BETA: V1️⃣ & macOS 14.5⬇️\n全面自定义「Siri与聚焦」中的「Siri 建议」功能。\n⚠️本模块不含自定义「Siri与聚焦」中的「询问 Siri」功能。 -#!openUrl=http://boxjs.com/#/app/iRingo.Siri.Beta -#!author=VirgilClyne -#!homepage=https://github.com/VirgilClyne -#!manual=https://github.com/VirgilClyne/iRingo/wiki/⭕-Siri与搜索 -#!icon=https://raw.githubusercontent.com/Koolson/Qure/master/IconSet/Color/Siri.png -#!category= iRingo -#!system=mac -#!arguments=CountryCode:SG,Proxy:🌑Proxy -#!arguments-desc=CountryCode: 国家或地区代码\nProxy: macOS‘查询’代理策略 - -[Rule] -# > Look Up (Only for Wikipedia@macOS, Surge Enhance Mode enabled required) -RULE-SET,https://raw.githubusercontent.com/VirgilClyne/iRingo/main/RuleSet/LookUp.Wikipedia.list,{{{Proxy}}} - -[Script] -# Bag (iOS/macOS) -⭕ Siri.Bag.request = type=http-request, pattern=^https?:\/\/api2?\.smoot\.apple\.(com|cn)\/bag, requires-body=0, engine=webview, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/beta/js/Siri.request.beta.js, argument=CountryCode={{{CountryCode}}} -⭕ Siri.Bag.response = type=http-response, pattern=^https?:\/\/api2?\.smoot\.apple\.(com|cn)\/bag, requires-body=1, engine=webview, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/beta/js/Siri.response.beta.js, argument=CountryCode={{{CountryCode}}} -# Siri Suggestions & Look Up Search (iOS/macOS) -🔍 Siri.Search.request = type=http-request, pattern=^https?:\/\/api(2|-.+)\.smoot\.apple\.com\/search, requires-body=0, engine=webview, debug=1, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/beta/js/Siri.request.beta.js, argument=CountryCode={{{CountryCode}}} -🔍 Siri.Search.response = type=http-response, pattern=^https?:\/\/api(2|-.+)\.smoot\.apple\.com\/search, requires-body=1, engine=webview, debug=1, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/beta/js/Siri.response.beta.js, argument=CountryCode={{{CountryCode}}} -# Siri Infomation Card (macOS) -📇 Siri.Card.request = type=http-request, pattern=^https?:\/\/api(2|-.+)\.smoot\.apple\.com\/card, requires-body=0, engine=webview, debug=1, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/beta/js/Siri.request.beta.js, argument=CountryCode={{{CountryCode}}} -📇 Siri.Card.response = type=http-response, pattern=^https?:\/\/api(2|-.+)\.smoot\.apple\.com\/card, requires-body=1, engine=webview, debug=1, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/beta/js/Siri.response.beta.js, argument=CountryCode={{{CountryCode}}} -# Flight Search (iOS/macOS) -🛫 Siri.Flight.request = type=http-request, pattern=^https?:\/\/api(2|-.+)\.smoot\.apple\.com\/flight, requires-body=0, engine=webview, debug=1, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/beta/js/Siri.request.beta.js, argument=CountryCode={{{CountryCode}}} -# warm (unknown) -⭕ Siri.Warm.request = type=http-request, pattern=^https?:\/\/api(2|-.+)\.smoot\.apple\.com\/warm, requires-body=0, engine=webview, debug=1, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/beta/js/Siri.request.beta.js, argument=CountryCode={{{CountryCode}}} -# render (unknown) -⭕ Siri.Render.request = type=http-request, pattern=^https?:\/\/api(2|-.+)\.smoot\.apple\.com\/render, requires-body=0, engine=webview, debug=1, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/beta/js/Siri.request.beta.js, argument=CountryCode={{{CountryCode}}} -# Apple.Parsec.Visualsearch.V2.VisualSearch -🔍 Visualsearch.VisualSearch.request = type=http-request, pattern=^https?:\/\/(api2|api-.*)\.smoot\.apple\.com\/apple\.parsec\.visualsearch\.v2\.VisualSearch\/VisualSearch, requires-body=1, binary-body-mode=1, engine=webview, debug=1, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/beta/js/Siri.request.beta.js, argument=CountryCode={{{CountryCode}}} -🔍 Visualsearch.VisualSearch.response = type=http-response, pattern=^https?:\/\/(api2|api-.*)\.smoot\.apple\.com\/apple\.parsec\.visualsearch\.v2\.VisualSearch\/VisualSearch, requires-body=1, binary-body-mode=1, engine=webview, debug=1, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/beta/js/Siri.response.beta.js, argument=CountryCode={{{CountryCode}}} - -[MITM] -hostname = %APPEND% api*.smoot.apple.com, api.smoot.apple.cn diff --git a/sgmodule/Siri.macOS.sgmodule b/sgmodule/Siri.macOS.sgmodule index 0a2fbd9f7..f55145c0d 100644 --- a/sgmodule/Siri.macOS.sgmodule +++ b/sgmodule/Siri.macOS.sgmodule @@ -1,10 +1,10 @@ #!name= iRingo: ⭕ Siri & Spotlight #!desc=V1️⃣ & macOS 14.5⬇️\n全面自定义「Siri与聚焦」中的「Siri 建议」功能。\n⚠️本模块不含自定义「Siri与聚焦」中的「询问 Siri」功能。 #!openUrl=http://boxjs.com/#/app/iRingo.Siri -#!author=VirgilClyne -#!homepage=https://github.com/VirgilClyne -#!manual=https://github.com/VirgilClyne/iRingo/wiki/⭕-Siri与搜索 -#!icon=https://raw.githubusercontent.com/Koolson/Qure/master/IconSet/Color/Siri.png +#!author=VirgilClyne[https://github.com/VirgilClyne] +#!homepage=https://github.com/NSRingo +#!manual=https://github.com/NSRingo/Siri/wiki/⭕-Siri与搜索 +#!icon=https://raw.githubusercontent.com/NSRingo/Siri/main/images/icon/v1/Siri%20-%20Icon.png #!category= iRingo #!system=mac #!arguments=CountryCode:SG,Proxy:🌑Proxy @@ -12,20 +12,20 @@ [Rule] # > Look Up (Only for Wikipedia@macOS, Surge Enhance Mode enabled required) -RULE-SET,https://raw.githubusercontent.com/VirgilClyne/iRingo/main/RuleSet/LookUp.Wikipedia.list,{{{Proxy}}} +RULE-SET,https://github.com/NSRingo/Siri/releases/latest/download/LookUp.Wikipedia.list,{{{Proxy}}} [Script] # Bag (iOS/macOS) -⭕ Siri.Bag.request = type=http-request, pattern=^https?:\/\/api2?\.smoot\.apple\.(com|cn)\/bag, requires-body=0, engine=webview, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/main/js/Siri.request.js, argument=CountryCode={{{CountryCode}}} -⭕ Siri.Bag.response = type=http-response, pattern=^https?:\/\/api2?\.smoot\.apple\.(com|cn)\/bag, requires-body=1, engine=webview, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/main/js/Siri.response.js, argument=CountryCode={{{CountryCode}}} +⭕ Siri.Bag.request = type=http-request, pattern=^https?:\/\/api2?\.smoot\.apple\.(com|cn)\/bag, requires-body=0, engine=webview, script-path=https://github.com/NSRingo/Siri/releases/latest/download/request.js, argument=CountryCode={{{CountryCode}}} +⭕ Siri.Bag.response = type=http-response, pattern=^https?:\/\/api2?\.smoot\.apple\.(com|cn)\/bag, requires-body=1, engine=webview, script-path=https://github.com/NSRingo/Siri/releases/latest/download/response.js, argument=CountryCode={{{CountryCode}}} # Siri Suggestions & Look Up Search (iOS/macOS) -🔍 Siri.Search.request = type=http-request, pattern=^https?:\/\/api(2|-.+)\.smoot\.apple\.com\/search, requires-body=0, engine=webview, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/main/js/Siri.request.js, argument=CountryCode={{{CountryCode}}} -🔍 Siri.Search.response = type=http-response, pattern=^https?:\/\/api(2|-.+)\.smoot\.apple\.com\/search, requires-body=1, engine=webview, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/main/js/Siri.response.js, argument=CountryCode={{{CountryCode}}} +🔍 Siri.Search.request = type=http-request, pattern=^https?:\/\/api(2|-.+)\.smoot\.apple\.com\/search, requires-body=0, engine=webview, script-path=https://github.com/NSRingo/Siri/releases/latest/download/request.js, argument=CountryCode={{{CountryCode}}} +🔍 Siri.Search.response = type=http-response, pattern=^https?:\/\/api(2|-.+)\.smoot\.apple\.com\/search, requires-body=1, engine=webview, script-path=https://github.com/NSRingo/Siri/releases/latest/download/response.js, argument=CountryCode={{{CountryCode}}} # Siri Infomation Card (macOS) -📇 Siri.Card.request = type=http-request, pattern=^https?:\/\/api(2|-.+)\.smoot\.apple\.com\/card, requires-body=0, engine=webview, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/main/js/Siri.request.js, argument=CountryCode={{{CountryCode}}} -📇 Siri.Card.response = type=http-response, pattern=^https?:\/\/api(2|-.+)\.smoot\.apple\.com\/card, requires-body=1, engine=webview, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/main/js/Siri.response.js, argument=CountryCode={{{CountryCode}}} +📇 Siri.Card.request = type=http-request, pattern=^https?:\/\/api(2|-.+)\.smoot\.apple\.com\/card, requires-body=0, engine=webview, script-path=https://github.com/NSRingo/Siri/releases/latest/download/request.js, argument=CountryCode={{{CountryCode}}} +📇 Siri.Card.response = type=http-response, pattern=^https?:\/\/api(2|-.+)\.smoot\.apple\.com\/card, requires-body=1, engine=webview, script-path=https://github.com/NSRingo/Siri/releases/latest/download/response.js, argument=CountryCode={{{CountryCode}}} # Flight Search (iOS/macOS) -🛫 Siri.Flight.request = type=http-request, pattern=^https?:\/\/api(2|-.+)\.smoot\.apple\.com\/flight, requires-body=0, engine=webview, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/main/js/Siri.request.js, argument=CountryCode={{{CountryCode}}} +🛫 Siri.Flight.request = type=http-request, pattern=^https?:\/\/api(2|-.+)\.smoot\.apple\.com\/flight, requires-body=0, engine=webview, script-path=https://github.com/NSRingo/Siri/releases/latest/download/request.js, argument=CountryCode={{{CountryCode}}} [MITM] hostname = %APPEND% api*.smoot.apple.com, api.smoot.apple.cn diff --git a/sgmodule/Siri.sgmodule b/sgmodule/Siri.sgmodule index c48c59fda..0b6f5cc2a 100644 --- a/sgmodule/Siri.sgmodule +++ b/sgmodule/Siri.sgmodule @@ -1,10 +1,10 @@ #!name= iRingo: ⭕ Siri & Search #!desc=V1️⃣ & iOS 17.6⬇️\n全面自定义「Siri与搜索」中的「Siri 建议」功能。\n⚠️本模块不含自定义「Siri与搜索」中的「询问 Siri」功能。 #!openUrl=http://boxjs.com/#/app/iRingo.Siri -#!author=VirgilClyne -#!homepage=https://github.com/VirgilClyne -#!manual=https://github.com/VirgilClyne/iRingo/wiki/⭕-Siri与搜索 -#!icon=https://raw.githubusercontent.com/Koolson/Qure/master/IconSet/Color/Siri.png +#!author=VirgilClyne[https://github.com/VirgilClyne] +#!homepage=https://github.com/NSRingo +#!manual=https://github.com/NSRingo/Siri/wiki/⭕-Siri与搜索 +#!icon=https://raw.githubusercontent.com/NSRingo/Siri/main/images/icon/v1/Siri%20-%20Icon.png #!category= iRingo #!system=ios #!arguments=CountryCode:SG @@ -12,13 +12,13 @@ [Script] # Bag (iOS/macOS) -⭕ Siri.Bag.request = type=http-request, pattern=^https?:\/\/api2?\.smoot\.apple\.(com|cn)\/bag, requires-body=0, engine=webview, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/main/js/Siri.request.js, argument=CountryCode={{{CountryCode}}} -⭕ Siri.Bag.response = type=http-response, pattern=^https?:\/\/api2?\.smoot\.apple\.(com|cn)\/bag, requires-body=1, engine=webview, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/main/js/Siri.response.js, argument=CountryCode={{{CountryCode}}} +⭕ Siri.Bag.request = type=http-request, pattern=^https?:\/\/api2?\.smoot\.apple\.(com|cn)\/bag, requires-body=0, engine=webview, script-path=https://github.com/NSRingo/Siri/releases/latest/download/request.js, argument=CountryCode={{{CountryCode}}} +⭕ Siri.Bag.response = type=http-response, pattern=^https?:\/\/api2?\.smoot\.apple\.(com|cn)\/bag, requires-body=1, engine=webview, script-path=https://github.com/NSRingo/Siri/releases/latest/download/response.js, argument=CountryCode={{{CountryCode}}} # Siri Suggestions & Look Up Search (iOS/macOS) -🔍 Siri.Search.request = type=http-request, pattern=^https?:\/\/api(2|-.+)\.smoot\.apple\.com\/search, requires-body=0, engine=webview, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/main/js/Siri.request.js, argument=CountryCode={{{CountryCode}}} -🔍 Siri.Search.response = type=http-response, pattern=^https?:\/\/api(2|-.+)\.smoot\.apple\.com\/search, requires-body=1, engine=webview, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/main/js/Siri.response.js, argument=CountryCode={{{CountryCode}}} +🔍 Siri.Search.request = type=http-request, pattern=^https?:\/\/api(2|-.+)\.smoot\.apple\.com\/search, requires-body=0, engine=webview, script-path=https://github.com/NSRingo/Siri/releases/latest/download/request.js, argument=CountryCode={{{CountryCode}}} +🔍 Siri.Search.response = type=http-response, pattern=^https?:\/\/api(2|-.+)\.smoot\.apple\.com\/search, requires-body=1, engine=webview, script-path=https://github.com/NSRingo/Siri/releases/latest/download/response.js, argument=CountryCode={{{CountryCode}}} # Flight Search (iOS/macOS) -🛫 Siri.Flight.request = type=http-request, pattern=^https?:\/\/api(2|-.+)\.smoot\.apple\.com\/flight, requires-body=0, engine=webview, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/main/js/Siri.request.js, argument=CountryCode={{{CountryCode}}} +🛫 Siri.Flight.request = type=http-request, pattern=^https?:\/\/api(2|-.+)\.smoot\.apple\.com\/flight, requires-body=0, engine=webview, script-path=https://github.com/NSRingo/Siri/releases/latest/download/request.js, argument=CountryCode={{{CountryCode}}} [MITM] hostname = %APPEND% api*.smoot.apple.com, api.smoot.apple.cn diff --git a/sgmodule/TV.beta.sgmodule b/sgmodule/TV.beta.sgmodule index a24f3b1df..4f9c3b1bb 100644 --- a/sgmodule/TV.beta.sgmodule +++ b/sgmodule/TV.beta.sgmodule @@ -10,12 +10,12 @@ #!arguments-desc=Third-Party: 启用第三方App与TV app关联功能\n * 是否将桌面版/macOS版/app版等平台的TV app转换至iPad版\n * 以启用第三方App与TV app关联功能(如: Disney+,Prime Video等)\nHLSUrl: 播放服务地址\n ├ play-edge.itunes.apple.com: 默认\n ├ play.itunes.apple.com: 不推荐,与播放服务域名重叠\n └ 不填写(留空): 不修改\nServerUrl: 密钥服务地址\n ├ play.itunes.apple.com: 默认\n ├ play-edge.itunes.apple.com: 不推荐,与密钥服务域名重叠\n └ 不填写(留空): 不修改 [Script] -📺 TV.uts.request = type=http-request, pattern=^https?:\/\/uts-api\.itunes\.apple\.com\/uts\/(v1|v2|v3)\/, requires-body=1, debug=1, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/beta/js/TV.request.beta.js, argument=Third-Party={{{Third-Party}}}, argument=Third-Party={{{Third-Party}}}&HLSUrl={{{HLSUrl}}}&ServerUrl={{{ServerUrl}}} -📺 TV.uts.response = type=http-response, pattern=^https?:\/\/uts-api\.itunes\.apple\.com\/uts\/(v1|v2|v3)\/, requires-body=1, debug=1, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/beta/js/TV.response.beta.js, argument=Third-Party={{{Third-Party}}}, argument=Third-Party={{{Third-Party}}}&HLSUrl={{{HLSUrl}}}&ServerUrl={{{ServerUrl}}} -📺 TV.umc.request = type=http-request, pattern=^https?:\/\/umc-tempo-api\.apple\.com\/(v1|v2|v3)\/, requires-body=1, debug=1, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/beta/js/TV.request.beta.js, argument= +📺 TV.uts.request = type=http-request, pattern=^https?:\/\/uts-api\.itunes\.apple\.com\/uts\/(v1|v2|v3)\/, requires-body=1, debug=1, script-path=https://raw.githubusercontent.com/AnLingzhi/iRingo/beta/js/TV.request.beta.js, argument=Third-Party={{{Third-Party}}}, argument=Third-Party={{{Third-Party}}}&HLSUrl={{{HLSUrl}}}&ServerUrl={{{ServerUrl}}} +📺 TV.uts.response = type=http-response, pattern=^https?:\/\/uts-api\.itunes\.apple\.com\/uts\/(v1|v2|v3)\/, requires-body=1, debug=1, script-path=https://raw.githubusercontent.com/AnLingzhi/iRingo/beta/js/TV.response.beta.js, argument=Third-Party={{{Third-Party}}}, argument=Third-Party={{{Third-Party}}}&HLSUrl={{{HLSUrl}}}&ServerUrl={{{ServerUrl}}} +📺 TV.umc.request = type=http-request, pattern=^https?:\/\/umc-tempo-api\.apple\.com\/(v1|v2|v3)\/, requires-body=1, debug=1, script-path=https://raw.githubusercontent.com/AnLingzhi/iRingo/beta/js/TV.request.beta.js, argument= -📺 TV.hls.request = type=http-request, pattern=^https?:\/\/play(-edge)?\.itunes\.apple\.com\/WebObjects\/MZPlay(Local)?\.woa\/hls(\/(subscription|workout))?\/playlist\.m3u8, debug=1, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/beta/js/TV.request.beta.js, argument= -📺 TV.wa.request = type=http-request, pattern=^https?:\/\/play(-edge)?\.itunes\.apple\.com\/WebObjects\/MZPlay(Local)?\.woa\/wa\/(fpsRequest|checkInNonceRequest), debug=1, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/beta/js/TV.request.beta.js, argument= +📺 TV.hls.request = type=http-request, pattern=^https?:\/\/play(-edge)?\.itunes\.apple\.com\/WebObjects\/MZPlay(Local)?\.woa\/hls(\/(subscription|workout))?\/playlist\.m3u8, debug=1, script-path=https://raw.githubusercontent.com/AnLingzhi/iRingo/beta/js/TV.request.beta.js, argument= +📺 TV.wa.request = type=http-request, pattern=^https?:\/\/play(-edge)?\.itunes\.apple\.com\/WebObjects\/MZPlay(Local)?\.woa\/wa\/(fpsRequest|checkInNonceRequest), debug=1, script-path=https://raw.githubusercontent.com/AnLingzhi/iRingo/beta/js/TV.request.beta.js, argument= [MITM] hostname = %APPEND% uts-api.itunes.apple.com, umc-tempo-api.apple.com diff --git a/sgmodule/Weather.beta.sgmodule b/sgmodule/Weather.beta.sgmodule deleted file mode 100644 index 936924bec..000000000 --- a/sgmodule/Weather.beta.sgmodule +++ /dev/null @@ -1,20 +0,0 @@ -#!name= iRingo: 🌤 Weather β -#!desc=(BETA)\n1.解锁全部天气功能\n2.替换空气质量数据\n3.添加下一小时降水数据\n4.替换空气质量地图 -#!openUrl=http://boxjs.com/#/app/iRingo.Weather.beta -#!author=Wordless Echo -#!homepage=https://github.com/WordlessEcho -#!manual=https://github.com/VirgilClyne/iRingo/wiki/🌤天气 -#!icon=https://is4-ssl.mzstatic.com/image/thumb/Purple125/v4/c8/76/87/c8768792-b7ab-7de4-9c70-1888096b7ae9/AppIcon-0-0-1x_U007emarketing-0-0-0-10-0-0-sRGB-0-0-0-GLES2_U002c0-512MB-85-220-0-0.png/108x0w.webp -#!category= iRingo - -[Rule] -DOMAIN,weather-analytics-events.apple.com,REJECT - -[Script] -Weather Availability Request = type=http-request, pattern=^https?:\/\/weather-data\.apple\.com\/(v1|v2|v3)\/availability\/, requires-body=0, debug=1, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/beta/js/Weather.Availability.request.beta.js, argument= -Weather Availability Response = type=http-response, pattern=^https?:\/\/weather-data\.apple\.com\/(v1|v2|v3)\/availability\/, requires-body=1, debug=1, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/beta/js/Weather.Availability.response.beta.js, argument= -Weather Response = type=http-response, pattern=^https?:\/\/weather-data\.apple\.com\/(v1|v2|v3)\/weather\/, requires-body=1, timeout=20, debug=1, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/beta/js/Weather.response.beta.js, argument= -Weather Map Request = type=http-request, pattern=^https?:\/\/weather-map2?\.apple\.com\/(v1|v2)\/mapOverlay\/\w+(\?.*country=CN.*)?$, requires-body=0, debug=1, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/beta/js/Weather.Map.request.beta.js, argument= - -[MITM] -hostname = %APPEND% weather-edge.apple.com, weather-data.apple.com, weather-data-origin.apple.com, weather-map.apple.com, weather-map2.apple.com, api.waqi.info, tiles.waqi.info, www.weatherol.cn, api.caiyunapp.com diff --git a/sgmodule/Weather.sgmodule b/sgmodule/Weather.sgmodule index 8c35ab5a1..9cbf6bbaa 100644 --- a/sgmodule/Weather.sgmodule +++ b/sgmodule/Weather.sgmodule @@ -1,20 +1,20 @@ #!name= iRingo: 🌤 Weather -#!desc=1.解锁全部天气功能\n2.替换空气质量数据\n3.添加下一小时降水数据\n4.替换空气质量地图 +#!desc=V4️⃣ & iOS 16⬇️\n1.解锁全部天气功能\n2.替换空气质量数据\n3.添加下一小时降水数据\n4.替换空气质量地图 #!openUrl=http://boxjs.com/#/app/iRingo.Weather -#!author=Wordless Echo -#!homepage=https://github.com/WordlessEcho -#!manual=https://github.com/VirgilClyne/iRingo/wiki/🌤天气 -#!icon=https://is4-ssl.mzstatic.com/image/thumb/Purple125/v4/c8/76/87/c8768792-b7ab-7de4-9c70-1888096b7ae9/AppIcon-0-0-1x_U007emarketing-0-0-0-10-0-0-sRGB-0-0-0-GLES2_U002c0-512MB-85-220-0-0.png/108x0w.webp +#!author=WordlessEcho[https://github.com/WordlessEcho] +#!homepage=https://github.com/NSRingo +#!manual=https://github.com/NSRingo/Weather/wiki +#!icon=https://developer.apple.com/assets/elements/icons/weather/weather-128x128.png #!category= iRingo [Rule] -DOMAIN,weather-analytics-events.apple.com,REJECT +DOMAIN,weather-analytics-events.apple.com,REJECT-DROP [Script] -Weather Availability Request = type=http-request, pattern=^https?:\/\/weather-data\.apple\.com\/(v1|v2|v3)\/availability\/, requires-body=0, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/main/js/Weather.Availability.request.js, argument= -Weather Availability Response = type=http-response, pattern=^https?:\/\/weather-data\.apple\.com\/(v1|v2|v3)\/availability\/, requires-body=1, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/main/js/Weather.Availability.response.js, argument= -Weather Response = type=http-response, pattern=^https?:\/\/weather-data\.apple\.com\/(v1|v2|v3)\/weather\/, requires-body=1, timeout=20, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/main/js/Weather.response.js, argument= -Weather Map Request = type=http-request, pattern=^https?:\/\/weather-map2?\.apple\.com\/(v1|v2)\/mapOverlay\/\w+(\?.*country=CN.*)?$, requires-body=0, script-path=https://raw.githubusercontent.com/VirgilClyne/iRingo/main/js/Weather.Map.request.js, argument= +Weather Availability Request = type=http-request, pattern=^https?:\/\/weather-data\.apple\.com\/(v1|v2|v3)\/availability\/, script-path=https://raw.githubusercontent.com/NSRingo/Weather/main/js/Availability.request.js, argument= +Weather Availability Response = type=http-response, pattern=^https?:\/\/weather-data\.apple\.com\/(v1|v2|v3)\/availability\/, requires-body=1, script-path=https://raw.githubusercontent.com/NSRingo/Weather/main/js/Availability.response.js, argument= +Weather Response = type=http-response, pattern=^https?:\/\/weather-data\.apple\.com\/(v1|v2|v3)\/weather\/, requires-body=1, script-path=https://raw.githubusercontent.com/NSRingo/Weather/main/js/response.js, argument= +Weather Map Request = type=http-request, pattern=^https?:\/\/weather-map2?\.apple\.com\/(v1|v2)\/mapOverlay\/\w+(\?.*country=CN.*)?$, script-path=https://raw.githubusercontent.com/NSRingo/Weather/main/js/Map.request.js, argument= [MITM] hostname = %APPEND% weather-edge.apple.com, weather-data.apple.com, weather-data-origin.apple.com, weather-map.apple.com, weather-map2.apple.com diff --git a/snippet/Siri.snippet b/snippet/Siri.snippet index 41437990d..c33329907 100644 --- a/snippet/Siri.snippet +++ b/snippet/Siri.snippet @@ -1,24 +1,24 @@ #!name= iRingo: ⭕ Siri & Search #!desc=V1️⃣ & iOS 17.6⬇️\n全面自定义「Siri与搜索」中的「Siri 建议」功能。\n⚠️本模块不含自定义「Siri与搜索」中的「询问 Siri」功能。 #!openUrl=http://boxjs.com/#/app/iRingo.Siri -#!author=VirgilClyne -#!homepage=https://github.com/VirgilClyne -#!manual=https://github.com/VirgilClyne/iRingo/wiki/⭕-Siri与搜索 -#!icon=https://raw.githubusercontent.com/Koolson/Qure/master/IconSet/Color/Siri.png +#!author=VirgilClyne[https://github.com/VirgilClyne] +#!homepage=https://github.com/NSRingo +#!manual=https://github.com/NSRingo/Siri/wiki/⭕-Siri与搜索 +#!icon=https://raw.githubusercontent.com/NSRingo/Siri/main/images/icon/v1/Siri%20-%20Icon.png #!category= iRingo #[rewrite_local] # ⭕ Siri Bag (iOS/macOS) -^https?:\/\/api2?\.smoot\.apple\.(com|cn)\/bag url script-request-header https://raw.githubusercontent.com/VirgilClyne/iRingo/main/js/Siri.request.js -^https?:\/\/api2?\.smoot\.apple\.(com|cn)\/bag url script-response-body https://raw.githubusercontent.com/VirgilClyne/iRingo/main/js/Siri.response.js +^https?:\/\/api2?\.smoot\.apple\.(com|cn)\/bag url script-request-header https://github.com/NSRingo/Siri/releases/latest/download/request.js +^https?:\/\/api2?\.smoot\.apple\.(com|cn)\/bag url script-response-body https://github.com/NSRingo/Siri/releases/latest/download/response.js # 🔍 Siri Suggestions & Look Up Search (iOS/macOS) -^https?:\/\/api(2|-.+)\.smoot\.apple\.com\/search url script-request-header https://raw.githubusercontent.com/VirgilClyne/iRingo/main/js/Siri.request.js -^https?:\/\/api(2|-.+)\.smoot\.apple\.com\/search url script-response-body https://raw.githubusercontent.com/VirgilClyne/iRingo/main/js/Siri.response.js +^https?:\/\/api(2|-.+)\.smoot\.apple\.com\/search url script-request-header https://github.com/NSRingo/Siri/releases/latest/download/request.js +^https?:\/\/api(2|-.+)\.smoot\.apple\.com\/search url script-response-body https://github.com/NSRingo/Siri/releases/latest/download/response.js # 📇 Siri Infomation Card (macOS) -^https?:\/\/api(2|-.+)\.smoot\.apple\.com\/card url script-request-header https://raw.githubusercontent.com/VirgilClyne/iRingo/main/js/Siri.request.js -^https?:\/\/api(2|-.+)\.smoot\.apple\.com\/card url script-response-body https://raw.githubusercontent.com/VirgilClyne/iRingo/main/js/Siri.response.js +^https?:\/\/api(2|-.+)\.smoot\.apple\.com\/card url script-request-header https://github.com/NSRingo/Siri/releases/latest/download/request.js +^https?:\/\/api(2|-.+)\.smoot\.apple\.com\/card url script-response-body https://github.com/NSRingo/Siri/releases/latest/download/response.js # 🛫 Siri Flight Search (iOS/macOS) -^https?:\/\/api(2|-.+)\.smoot\.apple\.com\/flight url script-request-header https://raw.githubusercontent.com/VirgilClyne/iRingo/main/js/Siri.request.js +^https?:\/\/api(2|-.+)\.smoot\.apple\.com\/flight url script-request-header https://github.com/NSRingo/Siri/releases/latest/download/request.js #[mitm] hostname = api*.smoot.apple.com, api.smoot.apple.cn diff --git a/snippet/Weather.snippet b/snippet/Weather.snippet index 2e3460236..3b9c3fb96 100644 --- a/snippet/Weather.snippet +++ b/snippet/Weather.snippet @@ -1,15 +1,21 @@ -#!name= iRingo for 🌤 Weather -#!desc=(V4) 1.解锁全部天气功能 2.替换空气质量数据 3.添加下一小时降水数据 4.替换空气质量地图 +#!name= iRingo: 🌤 Weather +#!desc=V4️⃣ & iOS 16⬇️\n1.解锁全部天气功能\n2.替换空气质量数据\n3.添加下一小时降水数据\n4.替换空气质量地图 #!openUrl=http://boxjs.com/#/app/iRingo.Weather -#!author=Wordless Echo -#!homepage=https://github.com/WordlessEcho -#!manual=https://github.com/VirgilClyne/iRingo/wiki/🌤天气 -#!icon=https://is4-ssl.mzstatic.com/image/thumb/Purple125/v4/c8/76/87/c8768792-b7ab-7de4-9c70-1888096b7ae9/AppIcon-0-0-1x_U007emarketing-0-0-0-10-0-0-sRGB-0-0-0-GLES2_U002c0-512MB-85-220-0-0.png/108x0w.webp +#!author=WordlessEcho[https://github.com/WordlessEcho] +#!homepage=https://github.com/NSRingo +#!manual=https://github.com/NSRingo/Weather/wiki +#!icon=https://developer.apple.com/assets/elements/icons/weather/weather-128x128.png #!category= iRingo -^https?:\/\/weather-data\.apple\.com\/(v1|v2|v3)\/availability\/ url script-request-header https://raw.githubusercontent.com/VirgilClyne/iRingo/main/js/Weather.Availability.request.js -^https?:\/\/weather-data\.apple\.com\/(v1|v2|v3)\/availability\/ url script-response-body https://raw.githubusercontent.com/VirgilClyne/iRingo/main/js/Weather.Availability.response.js -^https?:\/\/weather-data\.apple\.com\/(v1|v2|v3)\/weather\/ url script-response-body https://raw.githubusercontent.com/VirgilClyne/iRingo/main/js/Weather.response.js -^https?:\/\/weather-map2?\.apple\.com\/(v1|v2)\/mapOverlay\/\w+(\?.*country=CN.*)?$ url script-request-header https://raw.githubusercontent.com/VirgilClyne/iRingo/main/js/Weather.Map.request.js +# 🌤 Weather Availability +^https?:\/\/weather-data\.apple\.com\/(v1|v2|v3)\/availability\/ url script-request-header https://raw.githubusercontent.com/NSRingo/Weather/main/js/Availability.request.js +^https?:\/\/weather-data\.apple\.com\/(v1|v2|v3)\/availability\/ url script-response-body https://raw.githubusercontent.com/NSRingo/Weather/main/js/Availability.response.js +# 🌤 Weather Response +^https?:\/\/weather-data\.apple\.com\/(v1|v2|v3)\/weather\/ url script-response-body https://raw.githubusercontent.com/NSRingo/Weather/main/js/response.js +# 🌤 Weather Map Request +^https?:\/\/weather-map2?\.apple\.com\/(v1|v2)\/mapOverlay\/\w+(\?.*country=CN.*)?$ url script-request-header https://raw.githubusercontent.com/NSRingo/Weather/main/js/Map.request.js +# 🌤 Weather Analytics +^https?:\/\/weather-analytics-events\.apple\.com\/ url reject -hostname = weather-edge.apple.com, weather-data.apple.com, weather-map.apple.com, weather-map2.apple.com +#[mitm] +hostname = weather-edge.apple.com, weather-data.apple.com, weather-map.apple.com, weather-map2.apple.com, weather-analytics-events.apple.com diff --git a/src/Siri.request.beta.js b/src/Siri.request.beta.js deleted file mode 100644 index 581f86c31..000000000 --- a/src/Siri.request.beta.js +++ /dev/null @@ -1,368 +0,0 @@ -import { $platform, URL, _, Storage, fetch, notification, log, logError, wait, done, getScript, runScript } from "./utils/utils.mjs"; -import GRPC from "./utils/GRPC.mjs"; -import Database from "./database/index.mjs"; -import setENV from "./function/setENV.mjs"; -import modifyPegasusQueryContext from "./function/modifyPegasusQueryContext.mjs"; -import { MESSAGE_TYPE, reflectionMergePartial, BinaryReader, WireType, UnknownFieldHandler, isJsonObject, typeofJsonValue, jsonWriteOptions, MessageType } from "@protobuf-ts/runtime"; -import { SiriPegasusRequest } from "./protobuf/apple.parsec.siri.v2alpha.SiriPegasusRequest"; -import { LookupSearchRequest } from "./protobuf/apple.parsec.lookup.v1alpha.LookupSearchRequest"; -import { VisualSearchRequest } from "./protobuf/apple.parsec.visualsearch.v2.VisualSearchRequest"; -import { PegasusQueryContext } from "./protobuf/apple.parsec.search.PegasusQueryContext"; -log("v4.2.3(4049)"); -// 构造回复数据 -let $response = undefined; -/***************** Processing *****************/ -// 解构URL -const url = new URL($request.url); -log(`⚠ url: ${url.toJSON()}`, ""); -// 获取连接参数 -const METHOD = $request.method, HOST = url.hostname, PATH = url.pathname, PATHs = url.pathname.split("/").filter(Boolean); -log(`⚠ METHOD: ${METHOD}, HOST: ${HOST}, PATH: ${PATH}`, ""); -// 解析格式 -const FORMAT = ($request.headers?.["Content-Type"] ?? $request.headers?.["content-type"])?.split(";")?.[0]; -log(`⚠ FORMAT: ${FORMAT}`, ""); -!(async () => { - const { Settings, Caches, Configs } = setENV("iRingo", "Siri", Database); - log(`⚠ Settings.Switch: ${Settings?.Switch}`, ""); - switch (Settings.Switch) { - case true: - default: - // 创建空数据 - let Locale, Language, CountryCode; - let body = {}; - // 方法判断 - switch (METHOD) { - case "POST": - case "PUT": - case "PATCH": - case "DELETE": - // 格式判断 - switch (FORMAT) { - case undefined: // 视为无body - break; - case "application/x-www-form-urlencoded": - case "text/plain": - default: - //log(`🚧 body: ${body}`, ""); - break; - case "application/x-mpegURL": - case "application/x-mpegurl": - case "application/vnd.apple.mpegurl": - case "audio/mpegurl": - //body = M3U8.parse($request.body); - //log(`🚧 body: ${JSON.stringify(body)}`, ""); - //$request.body = M3U8.stringify(body); - break; - case "text/xml": - case "text/html": - case "text/plist": - case "application/xml": - case "application/plist": - case "application/x-plist": - //body = XML.parse($request.body); - //log(`🚧 body: ${JSON.stringify(body)}`, ""); - //$request.body = XML.stringify(body); - break; - case "text/vtt": - case "application/vtt": - //body = VTT.parse($request.body); - //log(`🚧 body: ${JSON.stringify(body)}`, ""); - //$request.body = VTT.stringify(body); - break; - case "text/json": - case "application/json": - //body = JSON.parse($request.body ?? "{}"); - //log(`🚧 body: ${JSON.stringify(body)}`, ""); - //$request.body = JSON.stringify(body); - break; - case "application/protobuf": - case "application/x-protobuf": - case "application/vnd.google.protobuf": - case "application/grpc": - case "application/grpc+proto": - case "applecation/octet-stream": - //log(`🚧 $request.body: ${JSON.stringify($request.body)}`, ""); - let rawBody = ($platform === "Quantumult X") ? new Uint8Array($request.bodyBytes ?? []) : $request.body ?? new Uint8Array(); - //log(`🚧 isBuffer? ${ArrayBuffer.isView(rawBody)}: ${JSON.stringify(rawBody)}`, ""); - switch (FORMAT) { - case "application/protobuf": - case "application/x-protobuf": - case "application/vnd.google.protobuf": - break; - case "application/grpc": - case "application/grpc+proto": - rawBody = GRPC.decode(rawBody); - // 解析链接并处理protobuf数据 - // 主机判断 - switch (HOST) { - case "guzzoni.smoot.apple.com": - case "api-siri.smoot.apple.com": - case "api2.smoot.apple.com": - default: - //$request.headers["content-type"] = "application/grpc+proto"; - if ($request.headers["user-agent"]?.includes("grpc-node-js")) $request.headers["user-agent"] = "PegasusKit/1 (iPhone14,3; iPhone OS 18.1 22B5054e) siri/1"; - // 路径判断 - switch (PATH) { - case "/apple.parsec.siri.v2alpha.SiriSearch/SiriSearch": // Siri搜索 - body = SiriPegasusRequest.fromBinary(rawBody); - log(`🚧 body: ${JSON.stringify(body)}`, ""); - body.queryContext = modifyPegasusQueryContext(body.queryContext, Settings); - let fixLocation = true; - body?.queries?.[0]?.unknown2002.forEach((unknown2002, index) => { - switch (unknown2002?.n2?.supplement?.typeUrl) { - case "type.googleapis.com/apple.parsec.siri.v2alpha.AppInfo": - /****************** initialization start *******************/ - class ApplicationInfomationRequest$Type extends MessageType { - constructor() { - super("ApplicationInfomationRequest", [ - { no: 2, name: "bundleID", kind: "scalar", T: 9 /*ScalarType.STRING*/ }, - { no: 4, name: "launchIntent", kind: "scalar", T: 9 /*ScalarType.STRING*/ } - ]); - } - } - const ApplicationInfomationRequest = new ApplicationInfomationRequest$Type(); - /****************** initialization finish *******************/ - const AppInfo = ApplicationInfomationRequest.fromBinary(unknown2002?.n2?.supplement?.value); - log(`🚧 AppInfo: ${JSON.stringify(AppInfo)}`, ""); - switch (AppInfo?.bundleID) { - case "com.apple.weather": - case "com.heweather.weatherapp": - fixLocation = false; - break; - case "com.apple.store.Jolly": - fixLocation = false; - break; - case "com.apple.Music": - case "com.apple.AppStore": - fixLocation = false; - break; - default: - break; - }; - break; - case "type.googleapis.com/apple.parsec.siri.v2alpha.SiriKitAppInfo": - break; - case "type.googleapis.com/apple.parsec.siri.v2alpha.AmpUserState": - break; - case "type.googleapis.com/apple.parsec.siri.v2alpha.AudioQueueStateInfo": - break; - }; - }); - if (fixLocation) delete body?.queryContext?.location; - log(`🚧 body: ${JSON.stringify(body)}`, ""); - rawBody = SiriPegasusRequest.toBinary(body); - break; - case "/apple.parsec.lookup.v1alpha.LookupSearch/LookupSearch": // 查询搜索 - body = LookupSearchRequest.fromBinary(rawBody); - log(`🚧 body: ${JSON.stringify(body)}`, ""); - body.queryContext = modifyPegasusQueryContext(body.queryContext, Settings); - log(`🚧 body: ${JSON.stringify(body)}`, ""); - rawBody = LookupSearchRequest.toBinary(body); - break; - case "/apple.parsec.visualsearch.v2.VisualSearch/VisualSearch": { // 视觉搜索 - body = VisualSearchRequest.fromBinary(rawBody); - log(`🚧 body: ${JSON.stringify(body)}`, ""); - body.queryContext = modifyPegasusQueryContext(body.queryContext, Settings); - log(`🚧 body: ${JSON.stringify(body)}`, ""); - rawBody = VisualSearchRequest.toBinary(body); - break; - }; - case "/apple.parsec.responseframework.engagement.v1alpha.EngagementSearch/EngagementSearch": // - /****************** initialization start *******************/ - class EngagementRequest$Type extends MessageType { - constructor() { - super("EngagementRequest", [ - { no: 1, name: "queryContext", kind: "message", T: () => PegasusQueryContext } - ]); - } - } - const EngagementRequest = new EngagementRequest$Type(); - /****************** initialization finish *******************/ - body = EngagementRequest.fromBinary(rawBody); - log(`🚧 body: ${JSON.stringify(body)}`, ""); - body.queryContext = modifyPegasusQueryContext(body.queryContext, Settings); - log(`🚧 body: ${JSON.stringify(body)}`, ""); - rawBody = EngagementRequest.toBinary(body); - break; - case "/apple.parsec.spotlight.v1alpha.ZkwSuggestService/Suggest": // 新闻建议 - /****************** initialization start *******************/ - class ZkwSuggestRequest$Type extends MessageType { - constructor() { - super("ZkwSuggestRequest", [ - //{ no: 1, name: "queries", kind: "message", repeat: 1 /*RepeatType.PACKED*/, T: () => Query }, - { no: 2, name: "queryContext", kind: "message", T: () => PegasusQueryContext } - ]); - } - } - const ZkwSuggestRequest = new ZkwSuggestRequest$Type(); - /****************** initialization finish *******************/ - body = ZkwSuggestRequest.fromBinary(rawBody); - log(`🚧 body: ${JSON.stringify(body)}`, ""); - body.queryContext = modifyPegasusQueryContext(body.queryContext, Settings); - log(`🚧 body: ${JSON.stringify(body)}`, ""); - rawBody = ZkwSuggestRequest.toBinary(body); - break; - }; - break; - }; - rawBody = GRPC.encode(rawBody); - break; - }; - // 写入二进制数据 - $request.body = rawBody; - break; - }; - //break; // 不中断,继续处理URL - case "GET": - case "HEAD": - case "OPTIONS": - default: - Locale = Locale ?? url.searchParams.get("locale"); - [Language, CountryCode] = Locale?.split("_") ?? []; - log(`🚧 Locale: ${Locale}, Language: ${Language}, CountryCode: ${CountryCode}`, ""); - switch (Settings.CountryCode) { - case "AUTO": - Settings.CountryCode = CountryCode; - break; - default: - if (url.searchParams.has("cc")) url.searchParams.set("cc", Settings.CountryCode); - break; - }; - // 主机判断 - switch (HOST) { - case "api.smoot.apple.cn": - case "api.smoot.apple.com": - case "api2.smoot.apple.com": - case "api-siri.smoot.apple.com": - default: // 其他主机 - let q = url.searchParams.get("q"); - // 路径判断 - switch (PATH) { - case "/bag": // 配置 - break; - case "/search": // 搜索 - switch (url.searchParams.get("qtype")) { - case "zkw": // 处理"新闻"小组件 - switch (Settings.CountryCode) { - case "CN": - case "HK": - case "MO": - case "TW": - case "SG": - url.searchParams.set("locale", `${Language}_SG`); - break; - case "US": - case "CA": - case "UK": - case "AU": - // 不做修正 - break; - default: - url.searchParams.set("locale", `${Language}_US`); - break; - }; - break; - default: // 其他搜索 - if (q?.startsWith?.("%E5%A4%A9%E6%B0%94%20")) { // 处理"天气"搜索,搜索词"天气 "开头 - console.log("'天气 '开头"); - url.searchParams.set("q", q.replace(/%E5%A4%A9%E6%B0%94/, "weather")); // "天气"替换为"weather" - if (/^weather%20.*%E5%B8%82$/.test(q)) url.searchParams.set("q", q.replace(/$/, "%E5%B8%82")); - } else if (q?.endsWith?.("%20%E5%A4%A9%E6%B0%94")) {// 处理"天气"搜索,搜索词" 天气"结尾 - console.log("' 天气'结尾"); - url.searchParams.set("q", q.replace(/%E5%A4%A9%E6%B0%94/, "weather")); // "天气"替换为"weather" - if (/.*%E5%B8%82%20weather$/.test(q)) url.searchParams.set("q", q.replace(/%20weather$/, "%E5%B8%82%20weather")); - }; - break; - }; - break; - case "/card": // 卡片 - switch (url.searchParams.get("include")) { - case "tv": - case "movies": - url.searchParams.set("card_locale", `${Language}_${Settings.CountryCode}`); - const storefront = url.searchParams.get("storefront")?.match(/[\d]{6}/g); - switch (storefront) { //StoreFront ID, from App Store Region - case "143463": // HK - url.searchParams.set("q", q.replace(/%2F[a-z]{2}-[A-Z]{2}/, "%2Fzh-HK")); - break; - case "143464": // SG - url.searchParams.set("q", q.replace(/%2F[a-z]{2}-[A-Z]{2}/, "%2Fzh-SG")); - break; - case "143465": // CN - url.searchParams.set("q", q.replace(/%2F[a-z]{2}-[A-Z]{2}/, "%2Fzh-HK")); - break; - case "143470": // TW - url.searchParams.set("q", q.replace(/%2F[a-z]{2}-[A-Z]{2}/, "%2Fzh-TW")); - break; - }; - break; - case "apps": - case "music": - url.searchParams.set("card_locale", `${Language}_${Settings.CountryCode}`); - break; - case "dictionary": - switch (Language) { - case "zh-Hans": - case "zh-Hant": - url.searchParams.set("card_locale", `en_${Settings.CountryCode}`); - break; - }; - break; - default: - url.searchParams.set("card_locale", `${Language}_${Settings.CountryCode}`); - break; - }; - break; - case "/warm": - break; - case "/render": - break; - case "/flight": // 航班 - break; - }; - break; - case "guzzoni.smoot.apple.com": - break; - case "fbs.smoot.apple.com": - break; - case "cdn.smoot.apple.com": - break; - }; - break; - case "CONNECT": - case "TRACE": - break; - }; - $request.url = url.toString(); - log(`🚧 调试信息`, `$request.url: ${$request.url}`, ""); - break; - case false: - break; - }; -})() - .catch((e) => logError(e)) - .finally(() => { - switch ($response) { - default: // 有构造回复数据,返回构造的回复数据 - //log(`🚧 finally`, `echo $response: ${JSON.stringify($response, null, 2)}`, ""); - if ($response.headers?.["Content-Encoding"]) $response.headers["Content-Encoding"] = "identity"; - if ($response.headers?.["content-encoding"]) $response.headers["content-encoding"] = "identity"; - switch ($platform) { - default: - done({ response: $response }); - break; - case "Quantumult X": - if (!$response.status) $response.status = "HTTP/1.1 200 OK"; - delete $response.headers?.["Content-Length"]; - delete $response.headers?.["content-length"]; - delete $response.headers?.["Transfer-Encoding"]; - done($response); - break; - }; - break; - case undefined: // 无构造回复数据,发送修改的请求数据 - //log(`🚧 finally`, `$request: ${JSON.stringify($request, null, 2)}`, ""); - done($request); - break; - }; - }) diff --git a/src/Siri.request.js b/src/Siri.request.js deleted file mode 100644 index 240ca0c4c..000000000 --- a/src/Siri.request.js +++ /dev/null @@ -1,224 +0,0 @@ -import { $platform, URL, _, Storage, fetch, notification, log, logError, wait, done, getScript, runScript } from "./utils/utils.mjs"; -import Database from "./database/index.mjs"; -import setENV from "./function/setENV.mjs"; -log("v4.0.0(4010)"); -// 构造回复数据 -let $response = undefined; -/***************** Processing *****************/ -// 解构URL -const url = new URL($request.url); -log(`⚠ url: ${url.toJSON()}`, ""); -// 获取连接参数 -const METHOD = $request.method, HOST = url.hostname, PATH = url.pathname, PATHs = url.pathname.split("/").filter(Boolean); -log(`⚠ METHOD: ${METHOD}, HOST: ${HOST}, PATH: ${PATH}`, ""); -// 解析格式 -const FORMAT = ($request.headers?.["Content-Type"] ?? $request.headers?.["content-type"])?.split(";")?.[0]; -log(`⚠ FORMAT: ${FORMAT}`, ""); -!(async () => { - const { Settings, Caches, Configs } = setENV("iRingo", "Siri", Database); - log(`⚠ Settings.Switch: ${Settings?.Switch}`, ""); - switch (Settings.Switch) { - case true: - default: - // 创建空数据 - let Locale, Language, CountryCode; - let body = {}; - // 方法判断 - switch (METHOD) { - case "POST": - case "PUT": - case "PATCH": - case "DELETE": - // 格式判断 - switch (FORMAT) { - case undefined: // 视为无body - break; - case "application/x-www-form-urlencoded": - case "text/plain": - default: - break; - case "application/x-mpegURL": - case "application/x-mpegurl": - case "application/vnd.apple.mpegurl": - case "audio/mpegurl": - break; - case "text/xml": - case "text/html": - case "text/plist": - case "application/xml": - case "application/plist": - case "application/x-plist": - break; - case "text/vtt": - case "application/vtt": - break; - case "text/json": - case "application/json": - break; - case "application/protobuf": - case "application/x-protobuf": - case "application/vnd.google.protobuf": - case "application/grpc": - case "application/grpc+proto": - case "applecation/octet-stream": - // 路径判断 - switch (PATH) { - case "/apple.parsec.siri.v2alpha.SiriSearch/SiriSearch": // Siri搜索 - break; - case "/apple.parsec.spotlight.v1alpha.ZkwSuggestService/Suggest": // 新闻建议 - break; - }; - break; - }; - //break; // 不中断,继续处理URL - case "GET": - case "HEAD": - case "OPTIONS": - default: - Locale = Locale ?? url.searchParams.get("locale"); - [Language, CountryCode] = Locale?.split("_") ?? []; - log(`🚧 Locale: ${Locale}, Language: ${Language}, CountryCode: ${CountryCode}`, ""); - switch (Settings.CountryCode) { - case "AUTO": - Settings.CountryCode = CountryCode; - break; - default: - if (url.searchParams.has("cc")) url.searchParams.set("cc", Settings.CountryCode); - break; - }; - // 主机判断 - switch (HOST) { - case "api.smoot.apple.cn": - case "api.smoot.apple.com": - case "api2.smoot.apple.com": - case "api-siri.smoot.apple.com": - default: // 其他主机 - let q = url.searchParams.get("q"); - // 路径判断 - switch (PATH) { - case "/bag": // 配置 - break; - case "/search": // 搜索 - switch (url.searchParams.get("qtype")) { - case "zkw": // 处理"新闻"小组件 - switch (Settings.CountryCode) { - case "CN": - case "HK": - case "MO": - case "TW": - case "SG": - url.searchParams.set("locale", `${Language}_SG`); - break; - case "US": - case "CA": - case "UK": - case "AU": - // 不做修正 - break; - default: - url.searchParams.set("locale", `${Language}_US`); - break; - }; - break; - default: // 其他搜索 - if (q?.startsWith?.("%E5%A4%A9%E6%B0%94%20")) { // 处理"天气"搜索,搜索词"天气 "开头 - console.log("'天气 '开头"); - url.searchParams.set("q", q.replace(/%E5%A4%A9%E6%B0%94/, "weather")); // "天气"替换为"weather" - if (/^weather%20.*%E5%B8%82$/.test(q)) url.searchParams.set("q", q.replace(/$/, "%E5%B8%82")); - } else if (q?.endsWith?.("%20%E5%A4%A9%E6%B0%94")) {// 处理"天气"搜索,搜索词" 天气"结尾 - console.log("' 天气'结尾"); - url.searchParams.set("q", q.replace(/%E5%A4%A9%E6%B0%94/, "weather")); // "天气"替换为"weather" - if (/.*%E5%B8%82%20weather$/.test(q)) url.searchParams.set("q", q.replace(/%20weather$/, "%E5%B8%82%20weather")); - }; - break; - }; - break; - case "/card": // 卡片 - switch (url.searchParams.get("include")) { - case "tv": - case "movies": - url.searchParams.set("card_locale", `${Language}_${Settings.CountryCode}`); - const storefront = url.searchParams.get("storefront")?.match(/[\d]{6}/g); - switch (storefront) { //StoreFront ID, from App Store Region - case "143463": // HK - url.searchParams.set("q", q.replace(/%2F[a-z]{2}-[A-Z]{2}/, "%2Fzh-HK")); - break; - case "143464": // SG - url.searchParams.set("q", q.replace(/%2F[a-z]{2}-[A-Z]{2}/, "%2Fzh-SG")); - break; - case "143465": // CN - url.searchParams.set("q", q.replace(/%2F[a-z]{2}-[A-Z]{2}/, "%2Fzh-HK")); - break; - case "143470": // TW - url.searchParams.set("q", q.replace(/%2F[a-z]{2}-[A-Z]{2}/, "%2Fzh-TW")); - break; - }; - break; - case "apps": - case "music": - url.searchParams.set("card_locale", `${Language}_${Settings.CountryCode}`); - break; - case "dictionary": - switch (Language) { - case "zh-Hans": - case "zh-Hant": - url.searchParams.set("card_locale", `en_${Settings.CountryCode}`); - break; - }; - break; - default: - url.searchParams.set("card_locale", `${Language}_${Settings.CountryCode}`); - break; - }; - break; - case "/warm": - break; - case "/render": - break; - case "/flight": // 航班 - break; - }; - break; - case "guzzoni.smoot.apple.com": - break; - case "fbs.smoot.apple.com": - break; - case "cdn.smoot.apple.com": - break; - }; - break; - case "CONNECT": - case "TRACE": - break; - }; - $request.url = url.toString(); - log(`🚧 调试信息`, `$request.url: ${$request.url}`, ""); - break; - case false: - break; - }; -})() - .catch((e) => logError(e)) - .finally(() => { - switch ($response) { - default: // 有构造回复数据,返回构造的回复数据 - if ($response.headers?.["Content-Encoding"]) $response.headers["Content-Encoding"] = "identity"; - if ($response.headers?.["content-encoding"]) $response.headers["content-encoding"] = "identity"; - switch ($platform) { - default: - done({ response: $response }); - break; - case "Quantumult X": - if (!$response.status) $response.status = "HTTP/1.1 200 OK"; - delete $response.headers?.["Content-Length"]; - delete $response.headers?.["content-length"]; - delete $response.headers?.["Transfer-Encoding"]; - done($response); - break; - }; - break; - case undefined: // 无构造回复数据,发送修改的请求数据 - done($request); - break; - }; - }) diff --git a/src/Siri.response.beta.js b/src/Siri.response.beta.js deleted file mode 100644 index 6d6e685b1..000000000 --- a/src/Siri.response.beta.js +++ /dev/null @@ -1,224 +0,0 @@ -import { $platform, URL, _, Storage, fetch, notification, log, logError, wait, done, getScript, runScript } from "./utils/utils.mjs"; -import GRPC from "./utils/GRPC.mjs"; -import Database from "./database/index.mjs"; -import setENV from "./function/setENV.mjs"; -log("v4.0.2(4003)"); -/***************** Processing *****************/ -// 解构URL -const url = new URL($request.url); -log(`⚠ url: ${url.toJSON()}`, ""); -// 获取连接参数 -const METHOD = $request.method, HOST = url.hostname, PATH = url.pathname, PATHs = url.pathname.split("/").filter(Boolean); -log(`⚠ METHOD: ${METHOD}, HOST: ${HOST}, PATH: ${PATH}`, ""); -// 解析格式 -const FORMAT = ($response.headers?.["Content-Type"] ?? $response.headers?.["content-type"])?.split(";")?.[0]; -log(`⚠ FORMAT: ${FORMAT}`, ""); -!(async () => { - const { Settings, Caches, Configs } = setENV("iRingo", "Siri", Database); - log(`⚠ Settings.Switch: ${Settings?.Switch}`, ""); - switch (Settings.Switch) { - case true: - default: - // 创建空数据 - let body = {}; - // 格式判断 - switch (FORMAT) { - case undefined: // 视为无body - break; - case "application/x-www-form-urlencoded": - case "text/plain": - default: - break; - case "application/x-mpegURL": - case "application/x-mpegurl": - case "application/vnd.apple.mpegurl": - case "audio/mpegurl": - //body = M3U8.parse($response.body); - //log(`🚧 body: ${JSON.stringify(body)}`, ""); - //$response.body = M3U8.stringify(body); - break; - case "text/xml": - case "text/html": - case "text/plist": - case "application/xml": - case "application/plist": - case "application/x-plist": - //body = XML.parse($response.body); - //log(`🚧 body: ${JSON.stringify(body)}`, ""); - break; - case "text/vtt": - case "application/vtt": - //body = VTT.parse($response.body); - //log(`🚧 body: ${JSON.stringify(body)}`, ""); - //$response.body = VTT.stringify(body); - break; - case "text/json": - case "application/json": - body = JSON.parse($response.body ?? "{}"); - // 主机判断 - switch (HOST) { - case "api.smoot.apple.com": - case "api.smoot.apple.cn": - // 路径判断 - switch (PATH) { - case "/bag": // 配置 - body.enabled = true; - body.feedback_enabled = true; - //body.search_url = body?.search_url || "https:\/\/api-glb-apne1c.smoot.apple.com\/search"; - //body.feedback_url = body?.feedback_url || "https:\/\/fbs.smoot.apple.com\/fb"; - if (body?.enabled_domains) { - body.enabled_domains = [...new Set([...body?.enabled_domains ?? [], ...Settings.Domains])]; - log(`🎉 领域列表`, `enabled_domains: ${JSON.stringify(body.enabled_domains)}`, ""); - } - if (body?.scene_aware_lookup_enabled_domains) { - body.scene_aware_lookup_enabled_domains = [...new Set([...body?.scene_aware_lookup_enabled_domains ?? [], ...Settings.Domains])]; - log(`🎉 领域列表`, `scene_aware_lookup_enabled_domains: ${JSON.stringify(body.scene_aware_lookup_enabled_domains)}`, ""); - } - body.min_query_len = 3; - let Overrides = body?.overrides; - if (Overrides) [...new Set([...Object.keys(Overrides), ...Settings.Functions])].forEach(Function => { - log(`🎉 覆盖列表`, `Function: ${Function}`, ""); - //_.set(Overrides, `${Function}.enabled`, true); - //_.set(Overrides, `${Function}.feedback_enabled`, true); - switch (Function) { - case "flightutilities": - _.set(Overrides, "flightutilities.enabled", true); - _.set(Overrides, "flightutilities.feedback_enabled", true); - //_.set(Overrides, "flightutilities.flight_url", "https:\/\/api-glb-aps1b.smoot.apple.com\/flight"); - //_.set(Overrides, "flightutilities.fallback_flight_url", "https:\/\/api-glb-apse1c.smoot.apple.com\/flight"); - break; - case "lookup": - _.set(Overrides, "lookup.enabled", true); - _.set(Overrides, "lookup.feedback_enabled", true); - //_.set(Overrides, "lookup.min_query_len", 2); - //_.set(Overrides, "lookup.search_render_timeout", 2000); - break; - case "mail": - _.set(Overrides, "mail.enabled", true); - _.set(Overrides, "mail.feedback_enabled", true); - break; - case "messages": - _.set(Overrides, "messages.enabled", true); - _.set(Overrides, "messages.feedback_enabled", true); - break; - case "news": - _.set(Overrides, "news.enabled", true); - _.set(Overrides, "news.feedback_enabled", true); - break; - case "safari": - _.set(Overrides, "safari.enabled", true); - _.set(Overrides, "safari.feedback_enabled", true); - _.set(Overrides, "safari.experiments_custom_feedback_enabled", true); - break; - case "spotlight": - _.set(Overrides, "spotlight.enabled", true); - _.set(Overrides, "spotlight.feedback_enabled", true); - //_.set(Overrides, "spotlight.use_twolayer_ranking", true); - //_.set(Overrides, "spotlight.experiments_custom_feedback_enabled", true); - //_.set(Overrides, "spotlight.min_query_len", 2); - //_.set(Overrides, "spotlight.collect_scores", true); - //_.set(Overrides, "spotlight.collect_anonymous_metadata", true); - break; - case "visualintelligence": - _.set(Overrides, "visualintelligence.enabled", true); - _.set(Overrides, "visualintelligence.feedback_enabled", true); - _.set(Overrides, "visualintelligence.enabled_domains", [...new Set([...Overrides.visualIntelligence?.enabled_domains ?? [], ...Configs.VisualIntelligence.enabled_domains])]); - _.set(Overrides, "visualintelligence.supported_domains", [...new Set([...Overrides.visualIntelligence?.supported_domains ?? [], ...Configs.VisualIntelligence.supported_domains])]); - break; - } - }); - // Safari Smart History - body.safari_smart_history_enabled = (Settings.Safari_Smart_History) ? true : false; - body.smart_history_feature_feedback_enabled = (Settings.Safari_Smart_History) ? true : false; - /* - if (body?.mescal_enabled) { - body.mescal_enabled = true; - body.mescal_version = 200; - body.mescal_cert_url = "https://init.itunes.apple.com/WebObjects/MZInit.woa/wa/signSapSetupCert"; - body.mescal_setup_url = "https://play.itunes.apple.com/WebObjects/MZPlay.woa/wa/signSapSetup"; - } - let smart_search_v2 = body?.smart_search_v2_parameters; - if (smart_search_v2) { - smart_search_v2.smart_history_score_v2_enabled = true; - smart_search_v2.smart_history_score_v2_enable_count = true; - }; - body.session_experiment_metadata_enabled = true; - //body.sample_features = true; - //body.use_ledbelly = true; - */ - break; - }; - break; - case "fbs.smoot.apple.com": - break; - case "cdn.smoot.apple.com": - break; - default: // 其他主机 - // 路径判断 - switch (PATH) { - case "/warm": - case "/render": - case "/flight": // 航班 - break; - case "/search": // 搜索 - break; - case "/card": // 卡片 - break; - }; - break; - }; - $response.body = JSON.stringify(body); - break; - case "application/protobuf": - case "application/x-protobuf": - case "application/vnd.google.protobuf": - case "application/grpc": - case "application/grpc+proto": - case "application/octet-stream": - //log(`🚧 $response.body: ${JSON.stringify($response.body)}`, ""); - let rawBody = ($platform === "Quantumult X") ? new Uint8Array($response.bodyBytes ?? []) : $response.body ?? new Uint8Array(); - //log(`🚧 isBuffer? ${ArrayBuffer.isView(rawBody)}: ${JSON.stringify(rawBody)}`, ""); - switch (FORMAT) { - case "application/protobuf": - case "application/x-protobuf": - case "application/vnd.google.protobuf": - break; - case "application/grpc": - case "application/grpc+proto": - rawBody = GRPC.decode(rawBody); - // 解析链接并处理protobuf数据 - // 主机判断 - switch (HOST) { - case "guzzoni.smoot.apple.com": - // 路径判断 - switch (PATH) { - case "/apple.parsec.siri.v2alpha.SiriSearch/SiriSearch": // Siri搜索 - /****************** initialization start *******************/ - /****************** initialization finish *******************/ - break; - }; - break; - default: - // 路径判断 - switch (PATH) { - case "/apple.parsec.spotlight.v1alpha.ZkwSuggestService/Suggest": // 新闻建议 - /****************** initialization start *******************/ - /****************** initialization finish *******************/ - break; - }; - break; - }; - rawBody = GRPC.encode(rawBody); - break; - }; - // 写入二进制数据 - $response.body = rawBody; - break; - }; - break; - case false: - break; - }; -})() - .catch((e) => logError(e)) - .finally(() => done($response)) diff --git a/src/Siri.response.js b/src/Siri.response.js deleted file mode 100644 index 0869b4462..000000000 --- a/src/Siri.response.js +++ /dev/null @@ -1,159 +0,0 @@ -import { $platform, URL, _, Storage, fetch, notification, log, logError, wait, done, getScript, runScript } from "./utils/utils.mjs"; -import Database from "./database/index.mjs"; -import setENV from "./function/setENV.mjs"; -log("v4.0.2(4003)"); -/***************** Processing *****************/ -// 解构URL -const url = new URL($request.url); -log(`⚠ url: ${url.toJSON()}`, ""); -// 获取连接参数 -const METHOD = $request.method, HOST = url.hostname, PATH = url.pathname, PATHs = url.pathname.split("/").filter(Boolean); -log(`⚠ METHOD: ${METHOD}, HOST: ${HOST}, PATH: ${PATH}`, ""); -// 解析格式 -const FORMAT = ($response.headers?.["Content-Type"] ?? $response.headers?.["content-type"])?.split(";")?.[0]; -log(`⚠ FORMAT: ${FORMAT}`, ""); -!(async () => { - const { Settings, Caches, Configs } = setENV("iRingo", "Siri", Database); - log(`⚠ Settings.Switch: ${Settings?.Switch}`, ""); - switch (Settings.Switch) { - case true: - default: - // 创建空数据 - let body = {}; - // 格式判断 - switch (FORMAT) { - case undefined: // 视为无body - break; - case "application/x-www-form-urlencoded": - case "text/plain": - default: - break; - case "application/x-mpegURL": - case "application/x-mpegurl": - case "application/vnd.apple.mpegurl": - case "audio/mpegurl": - break; - case "text/xml": - case "text/html": - case "text/plist": - case "application/xml": - case "application/plist": - case "application/x-plist": - break; - case "text/vtt": - case "application/vtt": - break; - case "text/json": - case "application/json": - body = JSON.parse($response.body ?? "{}"); - // 主机判断 - switch (HOST) { - case "api.smoot.apple.com": - case "api.smoot.apple.cn": - // 路径判断 - switch (PATH) { - case "/bag": // 配置 - body.enabled = true; - body.feedback_enabled = true; - if (body?.enabled_domains) { - body.enabled_domains = [...new Set([...body?.enabled_domains ?? [], ...Settings.Domains])]; - log(`🎉 领域列表`, `enabled_domains: ${JSON.stringify(body.enabled_domains)}`, ""); - } - if (body?.scene_aware_lookup_enabled_domains) { - body.scene_aware_lookup_enabled_domains = [...new Set([...body?.scene_aware_lookup_enabled_domains ?? [], ...Settings.Domains])]; - log(`🎉 领域列表`, `scene_aware_lookup_enabled_domains: ${JSON.stringify(body.scene_aware_lookup_enabled_domains)}`, ""); - } - body.min_query_len = 3; - let Overrides = body?.overrides; - if (Overrides) [...new Set([...Object.keys(Overrides), ...Settings.Functions])].forEach(Function => { - log(`🎉 覆盖列表`, `Function: ${Function}`, ""); - //_.set(Overrides, `${Function}.enabled`, true); - //_.set(Overrides, `${Function}.feedback_enabled`, true); - switch (Function) { - case "flightutilities": - _.set(Overrides, "flightutilities.enabled", true); - _.set(Overrides, "flightutilities.feedback_enabled", true); - //_.set(Overrides, "flightutilities.flight_url", "https:\/\/api-glb-aps1b.smoot.apple.com\/flight"); - //_.set(Overrides, "flightutilities.fallback_flight_url", "https:\/\/api-glb-apse1c.smoot.apple.com\/flight"); - break; - case "lookup": - _.set(Overrides, "lookup.enabled", true); - _.set(Overrides, "lookup.feedback_enabled", true); - //_.set(Overrides, "lookup.min_query_len", 2); - //_.set(Overrides, "lookup.search_render_timeout", 2000); - break; - case "mail": - _.set(Overrides, "mail.enabled", true); - _.set(Overrides, "mail.feedback_enabled", true); - break; - case "messages": - _.set(Overrides, "messages.enabled", true); - _.set(Overrides, "messages.feedback_enabled", true); - break; - case "news": - _.set(Overrides, "news.enabled", true); - _.set(Overrides, "news.feedback_enabled", true); - break; - case "safari": - _.set(Overrides, "safari.enabled", true); - _.set(Overrides, "safari.feedback_enabled", true); - _.set(Overrides, "safari.experiments_custom_feedback_enabled", true); - break; - case "spotlight": - _.set(Overrides, "spotlight.enabled", true); - _.set(Overrides, "spotlight.feedback_enabled", true); - //_.set(Overrides, "spotlight.use_twolayer_ranking", true); - //_.set(Overrides, "spotlight.experiments_custom_feedback_enabled", true); - //_.set(Overrides, "spotlight.min_query_len", 2); - //_.set(Overrides, "spotlight.collect_scores", true); - //_.set(Overrides, "spotlight.collect_anonymous_metadata", true); - break; - case "visualintelligence": - _.set(Overrides, "visualintelligence.enabled", true); - _.set(Overrides, "visualintelligence.feedback_enabled", true); - _.set(Overrides, "visualintelligence.enabled_domains", [...new Set([...Overrides.visualIntelligence?.enabled_domains ?? [], ...Configs.VisualIntelligence.enabled_domains])]); - _.set(Overrides, "visualintelligence.supported_domains", [...new Set([...Overrides.visualIntelligence?.supported_domains ?? [], ...Configs.VisualIntelligence.supported_domains])]); - break; - } - }); - // Safari Smart History - body.safari_smart_history_enabled = (Settings.Safari_Smart_History) ? true : false; - body.smart_history_feature_feedback_enabled = (Settings.Safari_Smart_History) ? true : false; - break; - }; - break; - case "fbs.smoot.apple.com": - break; - case "cdn.smoot.apple.com": - break; - default: // 其他主机 - // 路径判断 - switch (PATH) { - case "/warm": - case "/render": - case "/flight": // 航班 - break; - case "/search": // 搜索 - break; - case "/card": // 卡片 - break; - }; - break; - }; - $response.body = JSON.stringify(body); - break; - case "application/protobuf": - case "application/x-protobuf": - case "application/vnd.google.protobuf": - case "application/grpc": - case "application/grpc+proto": - case "application/octet-stream": - break; - }; - break; - case false: - break; - }; -})() - .catch((e) => logError(e)) - .finally(() => done($response)) diff --git a/src/archive/Siri.request.js b/src/archive/Siri.request.js deleted file mode 100644 index 62564ba04..000000000 --- a/src/archive/Siri.request.js +++ /dev/null @@ -1,203 +0,0 @@ -import _ from './ENV/Lodash.mjs' -import $Storage from './ENV/$Storage.mjs' -import ENV from "./ENV/ENV.mjs"; -import URI from "./URI/URI.mjs"; - -import Database from "./database/index.mjs"; -import setENV from "./function/setENV.mjs"; - -const $ = new ENV(" iRingo: 🔍 Siri v3.1.0(3) request"); - -// 构造回复数据 -let $response = undefined; - -/***************** Processing *****************/ -// 解构URL -const URL = URI.parse($request.url); -$.log(`⚠ URL: ${JSON.stringify(URL)}`, ""); -// 获取连接参数 -const METHOD = $request.method, HOST = URL.host, PATH = URL.path, PATHs = URL.paths; -$.log(`⚠ METHOD: ${METHOD}`, ""); -// 解析格式 -const FORMAT = ($request.headers?.["Content-Type"] ?? $request.headers?.["content-type"])?.split(";")?.[0]; -$.log(`⚠ FORMAT: ${FORMAT}`, ""); -(async () => { - const { Settings, Caches, Configs } = setENV("iRingo", "Siri", Database); - $.log(`⚠ Settings.Switch: ${Settings?.Switch}`, ""); - switch (Settings.Switch) { - case true: - default: - const LOCALE = URL.query?.locale; - $.log(`🚧 LOCALE: ${LOCALE}`, ""); - Settings.CountryCode = (Settings.CountryCode == "AUTO") ? LOCALE?.match(/[A-Z]{2}$/)?.[0] ?? Settings.CountryCode : Settings.CountryCode; - _.set(URL, "query.cc", Settings.CountryCode); - // 创建空数据 - let body = {}; - // 方法判断 - switch (METHOD) { - case "POST": - case "PUT": - case "PATCH": - case "DELETE": - // 格式判断 - switch (FORMAT) { - case undefined: // 视为无body - break; - case "application/x-www-form-urlencoded": - case "text/plain": - default: - break; - case "application/x-mpegURL": - case "application/x-mpegurl": - case "application/vnd.apple.mpegurl": - case "audio/mpegurl": - break; - case "text/xml": - case "text/html": - case "text/plist": - case "application/xml": - case "application/plist": - case "application/x-plist": - break; - case "text/vtt": - case "application/vtt": - break; - case "text/json": - case "application/json": - break; - case "application/protobuf": - case "application/x-protobuf": - case "application/vnd.google.protobuf": - case "application/grpc": - case "application/grpc+proto": - case "applecation/octet-stream": - // 路径判断 - switch (PATH) { - case "apple.parsec.spotlight.v1alpha.ZkwSuggestService/Suggest": // 新闻建议 - break; - }; - break; - }; - //break; // 不中断,继续处理URL - case "GET": - case "HEAD": - case "OPTIONS": - case undefined: // QX牛逼,script-echo-response不返回method - default: - // 主机判断 - switch (HOST) { - case "api.smoot.apple.com": - case "api.smoot.apple.cn": - // 路径判断 - switch (PATH) { - case "bag": // 配置 - break; - }; - break; - case "fbs.smoot.apple.com": - break; - case "cdn.smoot.apple.com": - break; - default: // 其他主机 - // 路径判断 - switch (PATH) { - case "warm": - case "render": - case "flight": // 航班 - break; - case "search": // 搜索 - switch (URL.query?.qtype) { - case "zkw": // 处理"新闻"小组件 - switch (Settings.CountryCode) { - case "CN": - case "HK": - case "MO": - case "TW": - case "SG": - _.set(URL, "query.locale", `${Settings.CountryCode}_SG`); - break; - case "US": - case "CA": - case "UK": - case "AU": - // 不做修正 - break; - default: - _.set(URL, "query.locale", `${Settings.CountryCode}_US`); - break; - }; - break; - default: // 其他搜索 - if (URL.query?.q?.startsWith?.("%E5%A4%A9%E6%B0%94%20")) { // 处理"天气"搜索,搜索词"天气 "开头 - console.log("'天气 '开头"); - URL.query.q = URL.query.q.replace(/%E5%A4%A9%E6%B0%94/, "weather"); // "天气"替换为"weather" - if (/^weather%20.*%E5%B8%82$/.test(URL.query.q)) URL.query.q = URL.query.q.replace(/$/, "%E5%B8%82"); - } else if (URL.query?.q?.endsWith?.("%20%E5%A4%A9%E6%B0%94")) {// 处理"天气"搜索,搜索词" 天气"结尾 - console.log("' 天气'结尾"); - URL.query.q = URL.query.q.replace(/%E5%A4%A9%E6%B0%94/, "weather"); // "天气"替换为"weather" - if (/.*%E5%B8%82%20weather$/.test(URL.query.q)) URL.query.q = URL.query.q.replace(/%20weather$/, "%E5%B8%82%20weather"); - }; - break; - }; - break; - case "card": // 卡片 - _.set(URL, "query.card_locale", LOCALE); - switch (URL.query?.include) { - case "tv": - case "movies": - switch (URL.query?.storefront?.match(/[\d]{6}/g)) { //StoreFront ID, from App Store Region - case "143463": // CN - URL.query.q = URL.query.q.replace(/%2F[a-z]{2}-[A-Z]{2}/, "%2Fzh-HK") - break; - case "143470": // TW - URL.query.q = URL.query.q.replace(/%2F[a-z]{2}-[A-Z]{2}/, "%2Fzh-TW") - break; - case "143464": // SG - URL.query.q = URL.query.q.replace(/%2F[a-z]{2}-[A-Z]{2}/, "%2Fzh-SG") - break; - }; - break; - case "apps": - case "music": - break; - case "dictionary": - break; - default: - break; - }; - break; - }; - break; - }; - break; - case "CONNECT": - case "TRACE": - break; - }; - if ($request.headers?.Host) $request.headers.Host = URL.host; - $request.url = URI.stringify(URL); - $.log(`🚧 调试信息`, `$request.url: ${$request.url}`, ""); - break; - case false: - break; - }; -})() - .catch((e) => $.logErr(e)) - .finally(() => { - switch ($response) { - default: // 有构造回复数据,返回构造的回复数据 - if ($response.headers?.["Content-Encoding"]) $response.headers["Content-Encoding"] = "identity"; - if ($response.headers?.["content-encoding"]) $response.headers["content-encoding"] = "identity"; - if ($.isQuanX()) { - if (!$response.status) $response.status = "HTTP/1.1 200 OK"; - delete $response.headers?.["Content-Length"]; - delete $response.headers?.["content-length"]; - delete $response.headers?.["Transfer-Encoding"]; - $.done($response); - } else $.done({ response: $response }); - break; - case undefined: // 无构造回复数据,发送修改的请求数据 - $.done($request); - break; - }; - }) diff --git a/src/archive/Siri.response.js b/src/archive/Siri.response.js deleted file mode 100644 index b1145035e..000000000 --- a/src/archive/Siri.response.js +++ /dev/null @@ -1,150 +0,0 @@ -import _ from './ENV/Lodash.mjs' -import $Storage from './ENV/$Storage.mjs' -import ENV from "./ENV/ENV.mjs"; -import URI from "./URI/URI.mjs"; - -import Database from "./database/index.mjs"; -import setENV from "./function/setENV.mjs"; - -const $ = new ENV(" iRingo: 🔍 Siri v3.0.4(2) response"); - -/***************** Processing *****************/ -// 解构URL -const URL = URI.parse($request.url); -$.log(`⚠ URL: ${JSON.stringify(URL)}`, ""); -// 获取连接参数 -const METHOD = $request.method, HOST = URL.host, PATH = URL.path, PATHs = URL.paths; -$.log(`⚠ METHOD: ${METHOD}`, ""); -// 解析格式 -const FORMAT = ($response.headers?.["Content-Type"] ?? $response.headers?.["content-type"])?.split(";")?.[0]; -$.log(`⚠ FORMAT: ${FORMAT}`, ""); -(async () => { - const { Settings, Caches, Configs } = setENV("iRingo", "Siri", Database); - $.log(`⚠ Settings.Switch: ${Settings?.Switch}`, ""); - switch (Settings.Switch) { - case true: - default: - // 创建空数据 - let body = {}; - // 格式判断 - switch (FORMAT) { - case undefined: // 视为无body - break; - case "application/x-www-form-urlencoded": - case "text/plain": - default: - break; - case "application/x-mpegURL": - case "application/x-mpegurl": - case "application/vnd.apple.mpegurl": - case "audio/mpegurl": - break; - case "text/xml": - case "text/html": - case "text/plist": - case "application/xml": - case "application/plist": - case "application/x-plist": - break; - case "text/vtt": - case "application/vtt": - break; - case "text/json": - case "application/json": - body = JSON.parse($response.body ?? "{}"); - // 主机判断 - switch (HOST) { - case "api.smoot.apple.com": - case "api.smoot.apple.cn": - // 路径判断 - switch (PATH) { - case "bag": // 配置 - body.enabled = true; - body.feedback_enabled = true; - if (body?.enabled_domains) { - body.enabled_domains = [...new Set([...body?.enabled_domains ?? [], ...Settings.Domains])]; - $.log(`🎉 领域列表`, `enabled_domains: ${JSON.stringify(body.enabled_domains)}`, ""); - } - if (body?.scene_aware_lookup_enabled_domains) { - body.scene_aware_lookup_enabled_domains = [...new Set([...body?.scene_aware_lookup_enabled_domains ?? [], ...Settings.Domains])]; - $.log(`🎉 领域列表`, `scene_aware_lookup_enabled_domains: ${JSON.stringify(body.scene_aware_lookup_enabled_domains)}`, ""); - } - body.min_query_len = 3; - let Overrides = body?.overrides; - if (Overrides) { - Settings.Functions.forEach(app => { - let APP = Overrides?.[`${app}`]; - if (APP) { - APP.enabled = true; - APP.feedback_enabled = true; - } else APP = { enabled: true, feedback_enabled: true }; - }); - let FlightUtilities = Overrides?.flightutilities; - if (FlightUtilities) { - }; - let Lookup = Overrides?.lookup; - if (Lookup) { - Lookup.min_query_len = 2; - }; - let Mail = Overrides?.mail; - let Messages = Overrides?.messages; - let News = Overrides?.news; - let Safari = Overrides?.safari; - if (Safari) { - Safari.experiments_custom_feedback_enabled = true; - }; - let Spotlight = Overrides?.spotlight; - if (Spotlight) { - Spotlight.use_twolayer_ranking = true; - Spotlight.experiments_custom_feedback_enabled = true; - Spotlight.min_query_len = 2; - Spotlight.collect_scores = true; - Spotlight.collect_anonymous_metadata = true; - }; - let VisualIntelligence = Overrides?.visualintelligence; - if (VisualIntelligence) { - VisualIntelligence.enabled_domains = [...new Set([...VisualIntelligence.enabled_domains ?? [], ...Configs.VisualIntelligence.enabled_domains])]; - VisualIntelligence.supported_domains = [...new Set([...VisualIntelligence.supported_domains ?? [], ...Configs.VisualIntelligence.supported_domains])]; - }; - }; - // Safari Smart History - body.safari_smart_history_enabled = (Settings.Safari_Smart_History) ? true : false; - body.smart_history_feature_feedback_enabled = (Settings.Safari_Smart_History) ? true : false; - break; - }; - break; - case "fbs.smoot.apple.com": - break; - case "cdn.smoot.apple.com": - break; - default: // 其他主机 - // 路径判断 - switch (PATH) { - case "warm": - case "render": - case "flight": // 航班 - break; - case "search": // 搜索 - break; - case "card": // 卡片 - break; - }; - break; - }; - $response.body = JSON.stringify(body); - break; - case "application/protobuf": - case "application/x-protobuf": - case "application/vnd.google.protobuf": - case "application/grpc": - case "application/grpc+proto": - case "application/octet-stream": - break; - }; - break; - case false: - break; - }; -})() - .catch((e) => $.logErr(e)) - .finally(() => $.done($response)) diff --git a/src/class/PegasusAPI.mjs b/src/class/PegasusAPI.mjs deleted file mode 100644 index 5fced3807..000000000 --- a/src/class/PegasusAPI.mjs +++ /dev/null @@ -1,48 +0,0 @@ -import { log } from "../utils/utils.mjs"; -//import { MESSAGE_TYPE, reflectionMergePartial, BinaryReader, WireType, UnknownFieldHandler, isJsonObject, typeofJsonValue, jsonWriteOptions, MessageType } from "@protobuf-ts/runtime"; -import { SiriPegasusRequest } from "../protobuf/apple.parsec.siri.v2alpha.SiriPegasusRequest"; -import { SiriPegasusContext } from "../protobuf/apple.parsec.siri.v2alpha.SiriPegasusContext"; -import { PegasusQueryContext } from "../protobuf/apple.parsec.search.PegasusQueryContext"; -export default class PegasusAPI { - static Name = "PegasusAPI"; - static Version = "1.0.0"; - static Author = "VirgilClyne"; - static decode(rawBody = new Uint8Array([]), message = "SiriPegasusRequest") { - log("☑️ PegasusAPI.decode", ""); - let body = {}; - switch (message) { - case "SiriPegasusRequest": - body = SiriPegasusRequest.fromBinary(rawBody); - break; - case "SiriPegasusContext": - body = SiriPegasusContext.fromBinary(rawBody); - break; - case "PegasusQueryContext": - body = PegasusQueryContext.fromBinary(rawBody); - break; - default: - throw new Error(`Unknown message type: ${message}`); - }; - log("✅ PegasusAPI.decode", ""); - return body; - }; - static encode(body = {}, message = "SiriPegasusRequest") { - log("☑️ PegasusAPI.encode", ""); - let rawBody = new Uint8Array([]); - switch (message) { - case "SiriPegasusRequest": - rawBody = SiriPegasusRequest.toBinary(body); - break; - case "SiriPegasusContext": - rawBody = SiriPegasusContext.toBinary(body); - break - case "PegasusQueryContext": - rawBody = PegasusQueryContext.toBinary(body); - break; - default: - throw new Error(`Unknown message type: ${message}`); - }; - log("✅ PegasusAPI.encode", ""); - return rawBody; - }; -} diff --git a/src/database/Siri.json b/src/database/Siri.json deleted file mode 100644 index d67040c25..000000000 --- a/src/database/Siri.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "Settings": { - "Switch": true, - "CountryCode": "SG", - "Region": "AUTO", - "Domains": ["web", "itunes", "app_store", "movies", "restaurants", "maps"], - "Functions": ["flightutilities", "lookup", "mail", "messages", "news", "safari", "siri", "spotlight", "visualintelligence"], - "Safari_Smart_History": true - }, - "Configs": { - "VisualIntelligence": { - "enabled_domains": ["pets", "media", "books", "art", "nature", "landmarks"], - "supported_domains": ["ART", "BOOK", "MEDIA", "LANDMARK", "ANIMALS", "BIRDS", "FOOD", "SIGN_SYMBOL", "AUTO_SYMBOL", "DOGS", "NATURE", "NATURAL_LANDMARK", "INSECTS", "REPTILES", "ALBUM", "STOREFRONT", "LAUNDRY_CARE_SYMBOL", "CATS", "OBJECT_2D", "SCULPTURE", "SKYLINE", "MAMMALS"] - } - } -} diff --git a/src/database/index.mjs b/src/database/index.mjs index 84c78dc70..9fb748e49 100644 --- a/src/database/index.mjs +++ b/src/database/index.mjs @@ -3,7 +3,6 @@ import * as Location from "./Location.json"; import * as Maps from "./Maps.json"; import * as News from "./News.json"; import * as PrivateRelay from "./PrivateRelay.json"; -import * as Siri from "./Siri.json"; import * as TestFlight from "./TestFlight.json"; import * as TV from "./TV.json"; @@ -13,7 +12,6 @@ export default Database = { "Maps": Maps, "News": News, "PrivateRelay": PrivateRelay, - "Siri": Siri, "TestFlight": TestFlight, "TV": TV }; diff --git a/src/function/modifyPegasusQueryContext.mjs b/src/function/modifyPegasusQueryContext.mjs deleted file mode 100644 index e7b5e4f99..000000000 --- a/src/function/modifyPegasusQueryContext.mjs +++ /dev/null @@ -1,26 +0,0 @@ -export default function modifyPegasusQueryContext(queryContext, Settings) { - console.log(`☑️ modify PegasusQueryContext`, ""); - Locale = queryContext.locale; - [Language, CountryCode] = Locale?.split("_") ?? []; - console.log(`🚧 Locale: ${Locale}, Language: ${Language}, CountryCode: ${CountryCode}`); - switch (Settings.CountryCode) { - case "AUTO": - Settings.CountryCode = CountryCode; - //break; - default: - if (queryContext?.countryCode) queryContext.countryCode = Settings.CountryCode; - //if (data?.siriPegasusContext?.conversationContext?.cc) data.siriPegasusContext.conversationContext.cc = Settings.CountryCode; - break; - }; - switch (Settings.Region) { - case "AUTO": - break; - default: - if (queryContext?.region) queryContext.region = Settings.Region; - break; - }; - if (queryContext?.skuRegion === "CH") queryContext.skuRegion = "LL"; - //delete queryContext?.location; - console.log(`✅ modify PegasusQueryContext`, ""); - return queryContext; -}; diff --git a/src/protobuf/apple.parsec.lookup.v1alpha.LookupSearchRequest.d.ts b/src/protobuf/apple.parsec.lookup.v1alpha.LookupSearchRequest.d.ts deleted file mode 100644 index 88c30fead..000000000 --- a/src/protobuf/apple.parsec.lookup.v1alpha.LookupSearchRequest.d.ts +++ /dev/null @@ -1,22 +0,0 @@ -// @generated by protobuf-ts 2.9.4 with parameter generate_dependencies,long_type_number,keep_enum_prefix,output_javascript -// @generated from protobuf file "apple.parsec.lookup.v1alpha.LookupSearchRequest.proto" (syntax proto3) -// tslint:disable -import { MessageType } from "@protobuf-ts/runtime"; -import { PegasusQueryContext } from "./apple.parsec.search.PegasusQueryContext"; -/** - * @generated from protobuf message LookupSearchRequest - */ -export interface LookupSearchRequest { - /** - * @generated from protobuf field: PegasusQueryContext queryContext = 2; - */ - queryContext?: PegasusQueryContext; -} -declare class LookupSearchRequest$Type extends MessageType { - constructor(); -} -/** - * @generated MessageType for protobuf message LookupSearchRequest - */ -export declare const LookupSearchRequest: LookupSearchRequest$Type; -export {}; diff --git a/src/protobuf/apple.parsec.lookup.v1alpha.LookupSearchRequest.js b/src/protobuf/apple.parsec.lookup.v1alpha.LookupSearchRequest.js deleted file mode 100644 index 9b572634f..000000000 --- a/src/protobuf/apple.parsec.lookup.v1alpha.LookupSearchRequest.js +++ /dev/null @@ -1,20 +0,0 @@ -// @generated by protobuf-ts 2.9.4 with parameter generate_dependencies,long_type_number,keep_enum_prefix,output_javascript -// @generated from protobuf file "apple.parsec.lookup.v1alpha.LookupSearchRequest.proto" (syntax proto3) -// tslint:disable -// @generated by protobuf-ts 2.9.4 with parameter generate_dependencies,long_type_number,keep_enum_prefix,output_javascript -// @generated from protobuf file "apple.parsec.lookup.v1alpha.LookupSearchRequest.proto" (syntax proto3) -// tslint:disable -import { MessageType } from "@protobuf-ts/runtime"; -import { PegasusQueryContext } from "./apple.parsec.search.PegasusQueryContext"; -// @generated message type with reflection information, may provide speed optimized methods -class LookupSearchRequest$Type extends MessageType { - constructor() { - super("LookupSearchRequest", [ - { no: 2, name: "queryContext", kind: "message", T: () => PegasusQueryContext } - ]); - } -} -/** - * @generated MessageType for protobuf message LookupSearchRequest - */ -export const LookupSearchRequest = new LookupSearchRequest$Type(); diff --git a/src/protobuf/apple.parsec.search.PegasusQueryContext.d.ts b/src/protobuf/apple.parsec.search.PegasusQueryContext.d.ts deleted file mode 100644 index 87ec77d5b..000000000 --- a/src/protobuf/apple.parsec.search.PegasusQueryContext.d.ts +++ /dev/null @@ -1,243 +0,0 @@ -// @generated by protobuf-ts 2.9.4 with parameter generate_dependencies,long_type_number,keep_enum_prefix,output_javascript -// @generated from protobuf file "apple.parsec.search.PegasusQueryContext.proto" (syntax proto3) -// tslint:disable -import { MessageType } from "@protobuf-ts/runtime"; -/** - * @generated from protobuf message PegasusQueryContext - */ -export interface PegasusQueryContext { - /** - * @generated from protobuf field: string secretKey = 1; - */ - secretKey: string; - /** - * @generated from protobuf field: string countryCode = 2; - */ - countryCode: string; - /** - * @generated from protobuf field: string locale = 3; - */ - locale: string; - /** - * @generated from protobuf field: string effectiveSystemLanguage = 4; - */ - effectiveSystemLanguage: string; - /** - * @generated from protobuf field: repeated string preferredLanguages = 5; - */ - preferredLanguages: string[]; - /** - * @generated from protobuf field: string storeFront = 6; - */ - storeFront: string; - /** - * userGuid = 7; - * - * @generated from protobuf field: string timeZone = 8; - */ - timeZone: string; - /** - * @generated from protobuf field: optional string skuRegion = 9; - */ - skuRegion?: string; - /** - * calendar = 10; - * - * @generated from protobuf field: TemperatureUnit temperatureUnit = 11; - */ - temperatureUnit: TemperatureUnit; - /** - * @generated from protobuf field: MeasurementSystem measurementSystem = 12; - */ - measurementSystem: MeasurementSystem; - /** - * @generated from protobuf field: HourFormat hourFormat = 13; - */ - hourFormat: HourFormat; - /** - * @generated from protobuf field: Location location = 14; - */ - location?: Location; - /** - * mapsSession = 15; - * subscriptions = 16; - * transportationMode = 17; - * - * @generated from protobuf field: optional string installedAppsSignature = 18; - */ - installedAppsSignature?: string; - /** - * @generated from protobuf field: optional float uiScale = 19; - */ - uiScale?: number; - /** - * internalBuild = 20; //? - * - * @generated from protobuf field: optional int32 seedBuild = 21; - */ - seedBuild?: number; - /** - * @generated from protobuf field: bool hsEnabled = 22; - */ - hsEnabled: boolean; - /** - * bool managedDevice = 23; //? - * string abTestSeed = 24; //? - * string siriLocale = 25; //? - * - * @generated from protobuf field: optional string region = 26; - */ - region?: string; - /** - * string sil = 27; //? - * - * @generated from protobuf field: optional string trialIdentifiers = 28; - */ - trialIdentifiers?: string; -} -/** - * @generated from protobuf message Location - */ -export interface Location { - /** - * @generated from protobuf field: optional float latitude = 1; - */ - latitude?: number; - /** - * @generated from protobuf field: optional float longitude = 2; - */ - longitude?: number; - /** - * @generated from protobuf field: optional Source source = 3; - */ - source?: Source; - /** - * @generated from protobuf field: optional float horizontalAccuracy = 4; - */ - horizontalAccuracy?: number; -} -/** - * @generated from protobuf enum TemperatureUnit - */ -export declare enum TemperatureUnit { - /** - * @generated from protobuf enum value: TemperatureUnitUnknown = 0; - */ - TemperatureUnitUnknown = 0, - /** - * celsius - * - * @generated from protobuf enum value: TemperatureUnitCelsius = 1; - */ - TemperatureUnitCelsius = 1, - /** - * fahrenheit - * - * @generated from protobuf enum value: TemperatureUnitFahrenheit = 2; - */ - TemperatureUnitFahrenheit = 2 -} -/** - * @generated from protobuf enum MeasurementSystem - */ -export declare enum MeasurementSystem { - /** - * @generated from protobuf enum value: MeasurementSystemUnknown = 0; - */ - MeasurementSystemUnknown = 0, - /** - * @generated from protobuf enum value: MeasurementSystemSI = 1; - */ - MeasurementSystemSI = 1, - /** - * @generated from protobuf enum value: MeasurementSystemUS = 2; - */ - MeasurementSystemUS = 2, - /** - * @generated from protobuf enum value: MeasurementSystemUK = 3; - */ - MeasurementSystemUK = 3 -} -/** - * @generated from protobuf enum HourFormat - */ -export declare enum HourFormat { - /** - * @generated from protobuf enum value: HourFormatUnknown = 0; - */ - HourFormatUnknown = 0, - /** - * twelve - * - * @generated from protobuf enum value: HourFormatTwelve = 1; - */ - HourFormatTwelve = 1, - /** - * twentyFour - * - * @generated from protobuf enum value: HourFormatTwentyFour = 2; - */ - HourFormatTwentyFour = 2 -} -/** - * @generated from protobuf enum Source - */ -export declare enum Source { - /** - * @generated from protobuf enum value: SourceUnknown = 0; - */ - SourceUnknown = 0, - /** - * disabledByUser - * - * @generated from protobuf enum value: SourceDisabledByUser = 1; - */ - SourceDisabledByUser = 1, - /** - * disabledByBag - * - * @generated from protobuf enum value: SourceDisabledByBag = 2; - */ - SourceDisabledByBag = 2, - /** - * tooSlow - * - * @generated from protobuf enum value: SourceTooSlow = 3; - */ - SourceTooSlow = 3, - /** - * @generated from protobuf enum value: SourceError = 4; - */ - SourceError = 4, - /** - * @generated from protobuf enum value: SourceGPS = 5; - */ - SourceGPS = 5, - /** - * cellular - * - * @generated from protobuf enum value: SourceCellular = 6; - */ - SourceCellular = 6, - /** - * wiFi - * - * @generated from protobuf enum value: SourceWiFi = 7; - */ - SourceWiFi = 7 -} -declare class PegasusQueryContext$Type extends MessageType { - constructor(); -} -/** - * @generated MessageType for protobuf message PegasusQueryContext - */ -export declare const PegasusQueryContext: PegasusQueryContext$Type; -declare class Location$Type extends MessageType { - constructor(); -} -/** - * @generated MessageType for protobuf message Location - */ -export declare const Location: Location$Type; -export {}; diff --git a/src/protobuf/apple.parsec.search.PegasusQueryContext.js b/src/protobuf/apple.parsec.search.PegasusQueryContext.js deleted file mode 100644 index 659c61eed..000000000 --- a/src/protobuf/apple.parsec.search.PegasusQueryContext.js +++ /dev/null @@ -1,171 +0,0 @@ -// @generated by protobuf-ts 2.9.4 with parameter generate_dependencies,long_type_number,output_javascript -// @generated from protobuf file "apple.parsec.search.PegasusQueryContext.proto" (syntax proto3) -// tslint:disable -// @generated by protobuf-ts 2.9.4 with parameter generate_dependencies,long_type_number,output_javascript -// @generated from protobuf file "apple.parsec.search.PegasusQueryContext.proto" (syntax proto3) -// tslint:disable -import { MessageType } from "@protobuf-ts/runtime"; -/** - * @generated from protobuf enum TemperatureUnit - */ -export var TemperatureUnit; -(function (TemperatureUnit) { - /** - * @generated from protobuf enum value: TemperatureUnitUnknown = 0; - */ - TemperatureUnit[TemperatureUnit["TemperatureUnitUnknown"] = 0] = "TemperatureUnitUnknown"; - /** - * celsius - * - * @generated from protobuf enum value: TemperatureUnitCelsius = 1; - */ - TemperatureUnit[TemperatureUnit["TemperatureUnitCelsius"] = 1] = "TemperatureUnitCelsius"; - /** - * fahrenheit - * - * @generated from protobuf enum value: TemperatureUnitFahrenheit = 2; - */ - TemperatureUnit[TemperatureUnit["TemperatureUnitFahrenheit"] = 2] = "TemperatureUnitFahrenheit"; -})(TemperatureUnit || (TemperatureUnit = {})); -/** - * @generated from protobuf enum MeasurementSystem - */ -export var MeasurementSystem; -(function (MeasurementSystem) { - /** - * @generated from protobuf enum value: MeasurementSystemUnknown = 0; - */ - MeasurementSystem[MeasurementSystem["MeasurementSystemUnknown"] = 0] = "MeasurementSystemUnknown"; - /** - * @generated from protobuf enum value: MeasurementSystemSI = 1; - */ - MeasurementSystem[MeasurementSystem["MeasurementSystemSI"] = 1] = "MeasurementSystemSI"; - /** - * @generated from protobuf enum value: MeasurementSystemUS = 2; - */ - MeasurementSystem[MeasurementSystem["MeasurementSystemUS"] = 2] = "MeasurementSystemUS"; - /** - * @generated from protobuf enum value: MeasurementSystemUK = 3; - */ - MeasurementSystem[MeasurementSystem["MeasurementSystemUK"] = 3] = "MeasurementSystemUK"; -})(MeasurementSystem || (MeasurementSystem = {})); -/** - * @generated from protobuf enum HourFormat - */ -export var HourFormat; -(function (HourFormat) { - /** - * @generated from protobuf enum value: HourFormatUnknown = 0; - */ - HourFormat[HourFormat["HourFormatUnknown"] = 0] = "HourFormatUnknown"; - /** - * twelve - * - * @generated from protobuf enum value: HourFormatTwelve = 1; - */ - HourFormat[HourFormat["HourFormatTwelve"] = 1] = "HourFormatTwelve"; - /** - * twentyFour - * - * @generated from protobuf enum value: HourFormatTwentyFour = 2; - */ - HourFormat[HourFormat["HourFormatTwentyFour"] = 2] = "HourFormatTwentyFour"; -})(HourFormat || (HourFormat = {})); -/** - * @generated from protobuf enum Source - */ -export var Source; -(function (Source) { - /** - * @generated from protobuf enum value: SourceUnknown = 0; - */ - Source[Source["SourceUnknown"] = 0] = "SourceUnknown"; - /** - * disabledByUser - * - * @generated from protobuf enum value: SourceDisabledByUser = 1; - */ - Source[Source["SourceDisabledByUser"] = 1] = "SourceDisabledByUser"; - /** - * disabledByBag - * - * @generated from protobuf enum value: SourceDisabledByBag = 2; - */ - Source[Source["SourceDisabledByBag"] = 2] = "SourceDisabledByBag"; - /** - * tooSlow - * - * @generated from protobuf enum value: SourceTooSlow = 3; - */ - Source[Source["SourceTooSlow"] = 3] = "SourceTooSlow"; - /** - * @generated from protobuf enum value: SourceError = 4; - */ - Source[Source["SourceError"] = 4] = "SourceError"; - /** - * @generated from protobuf enum value: SourceGPS = 5; - */ - Source[Source["SourceGPS"] = 5] = "SourceGPS"; - /** - * cellular - * - * @generated from protobuf enum value: SourceCellular = 6; - */ - Source[Source["SourceCellular"] = 6] = "SourceCellular"; - /** - * wiFi - * - * @generated from protobuf enum value: SourceWiFi = 7; - */ - Source[Source["SourceWiFi"] = 7] = "SourceWiFi"; -})(Source || (Source = {})); -// @generated message type with reflection information, may provide speed optimized methods -class PegasusQueryContext$Type extends MessageType { - constructor() { - super("PegasusQueryContext", [ - { no: 1, name: "secretKey", kind: "scalar", T: 9 /*ScalarType.STRING*/ }, - { no: 2, name: "countryCode", kind: "scalar", T: 9 /*ScalarType.STRING*/ }, - { no: 3, name: "locale", kind: "scalar", T: 9 /*ScalarType.STRING*/ }, - { no: 4, name: "effectiveSystemLanguage", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ }, - { no: 5, name: "preferredLanguages", kind: "scalar", repeat: 2 /*RepeatType.UNPACKED*/, T: 9 /*ScalarType.STRING*/ }, - { no: 6, name: "storeFront", kind: "scalar", T: 9 /*ScalarType.STRING*/ }, - { no: 7, name: "userGuid", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ }, - { no: 8, name: "timeZone", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ }, - { no: 9, name: "skuRegion", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ }, - { no: 10, name: "calendar", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ }, - { no: 11, name: "temperatureUnit", kind: "enum", T: () => ["TemperatureUnit", TemperatureUnit] }, - { no: 12, name: "measurementSystem", kind: "enum", T: () => ["MeasurementSystem", MeasurementSystem] }, - { no: 13, name: "hourFormat", kind: "enum", T: () => ["HourFormat", HourFormat] }, - { no: 14, name: "location", kind: "message", T: () => Location }, - { no: 18, name: "installedAppsSignature", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ }, - { no: 19, name: "uiScale", kind: "scalar", opt: true, T: 2 /*ScalarType.FLOAT*/ }, - { no: 20, name: "internalBuild", kind: "scalar", opt: true, T: 13 /*ScalarType.UINT32*/ }, - { no: 21, name: "seedBuild", kind: "scalar", opt: true, T: 13 /*ScalarType.UINT32*/ }, - { no: 22, name: "hsEnabled", kind: "scalar", opt: true, T: 8 /*ScalarType.BOOL*/ }, - { no: 26, name: "region", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ }, - { no: 28, name: "trialIdentifiers", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ }, - { no: 33, name: "deviceModel", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ }, - { no: 34, name: "isGenerativeModelDevice", kind: "scalar", opt: true, T: 8 /*ScalarType.BOOL*/ }, - { no: 38, name: "longSecretKey", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ } - ]); - } -} -/** - * @generated MessageType for protobuf message PegasusQueryContext - */ -export const PegasusQueryContext = new PegasusQueryContext$Type(); -// @generated message type with reflection information, may provide speed optimized methods -class Location$Type extends MessageType { - constructor() { - super("Location", [ - { no: 1, name: "latitude", kind: "scalar", opt: true, T: 2 /*ScalarType.FLOAT*/ }, - { no: 2, name: "longitude", kind: "scalar", opt: true, T: 2 /*ScalarType.FLOAT*/ }, - { no: 3, name: "source", kind: "enum", opt: true, T: () => ["Source", Source] }, - { no: 4, name: "horizontalAccuracy", kind: "scalar", opt: true, T: 2 /*ScalarType.FLOAT*/ } - ]); - } -} -/** - * @generated MessageType for protobuf message Location - */ -export const Location = new Location$Type(); diff --git a/src/protobuf/apple.parsec.siri.context.ClientConversationContextInfo.d.ts b/src/protobuf/apple.parsec.siri.context.ClientConversationContextInfo.d.ts deleted file mode 100644 index 8744f912c..000000000 --- a/src/protobuf/apple.parsec.siri.context.ClientConversationContextInfo.d.ts +++ /dev/null @@ -1,22 +0,0 @@ -// @generated by protobuf-ts 2.9.4 with parameter generate_dependencies,long_type_number,keep_enum_prefix,output_javascript -// @generated from protobuf file "apple.parsec.siri.context.ClientConversationContextInfo.proto" (syntax proto3) -// tslint:disable -import { MessageType } from "@protobuf-ts/runtime"; -/** - * clientDisplayHints = 1; - * clientApplicationContext = 2; - * onDeviceContext = 3; - * promptContext = 4; - * - * @generated from protobuf message ClientConversationContextInfo - */ -export interface ClientConversationContextInfo { -} -declare class ClientConversationContextInfo$Type extends MessageType { - constructor(); -} -/** - * @generated MessageType for protobuf message ClientConversationContextInfo - */ -export declare const ClientConversationContextInfo: ClientConversationContextInfo$Type; -export {}; diff --git a/src/protobuf/apple.parsec.siri.context.ClientConversationContextInfo.js b/src/protobuf/apple.parsec.siri.context.ClientConversationContextInfo.js deleted file mode 100644 index 0a03cf63e..000000000 --- a/src/protobuf/apple.parsec.siri.context.ClientConversationContextInfo.js +++ /dev/null @@ -1,17 +0,0 @@ -// @generated by protobuf-ts 2.9.4 with parameter generate_dependencies,long_type_number,keep_enum_prefix,output_javascript -// @generated from protobuf file "apple.parsec.siri.context.ClientConversationContextInfo.proto" (syntax proto3) -// tslint:disable -// @generated by protobuf-ts 2.9.4 with parameter generate_dependencies,long_type_number,keep_enum_prefix,output_javascript -// @generated from protobuf file "apple.parsec.siri.context.ClientConversationContextInfo.proto" (syntax proto3) -// tslint:disable -import { MessageType } from "@protobuf-ts/runtime"; -// @generated message type with reflection information, may provide speed optimized methods -class ClientConversationContextInfo$Type extends MessageType { - constructor() { - super("ClientConversationContextInfo", []); - } -} -/** - * @generated MessageType for protobuf message ClientConversationContextInfo - */ -export const ClientConversationContextInfo = new ClientConversationContextInfo$Type(); diff --git a/src/protobuf/apple.parsec.siri.v2alpha.DeviceState.d.ts b/src/protobuf/apple.parsec.siri.v2alpha.DeviceState.d.ts deleted file mode 100644 index 8f7e77f66..000000000 --- a/src/protobuf/apple.parsec.siri.v2alpha.DeviceState.d.ts +++ /dev/null @@ -1,105 +0,0 @@ -// @generated by protobuf-ts 2.9.4 with parameter generate_dependencies,long_type_number,output_javascript -// @generated from protobuf file "apple.parsec.siri.v2alpha.DeviceState.proto" (syntax proto3) -// tslint:disable -import { MessageType } from "@protobuf-ts/runtime"; -/** - * @generated from protobuf message DeviceState - */ -export interface DeviceState { - /** - * @generated from protobuf field: optional string companionName = 1; - */ - companionName?: string; - /** - * @generated from protobuf field: optional string deviceName = 2; - */ - deviceName?: string; - /** - * @generated from protobuf field: optional string inputOrigin = 3; - */ - inputOrigin?: string; - /** - * @generated from protobuf field: optional bool isAppleTv = 6; - */ - isAppleTv?: boolean; - /** - * @generated from protobuf field: optional bool isCarDnd = 7; - */ - isCarDnd?: boolean; - /** - * @generated from protobuf field: optional bool isCarPlay = 8; - */ - isCarPlay?: boolean; - /** - * @generated from protobuf field: optional bool isEyesFree = 9; - */ - isEyesFree?: boolean; - /** - * @generated from protobuf field: optional bool isHomePod = 10; - */ - isHomePod?: boolean; - /** - * @generated from protobuf field: optional bool isLockedWithPasscode = 11; - */ - isLockedWithPasscode?: boolean; - /** - * @generated from protobuf field: optional bool isMac = 12; - */ - isMac?: boolean; - /** - * @generated from protobuf field: optional bool isMultiUser = 13; - */ - isMultiUser?: boolean; - /** - * @generated from protobuf field: optional bool isPad = 14; - */ - isPad?: boolean; - /** - * @generated from protobuf field: optional bool isPhone = 15; - */ - isPhone?: boolean; - /** - * @generated from protobuf field: optional bool isTextToSpeechEnabled = 16; - */ - isTextToSpeechEnabled?: boolean; - /** - * @generated from protobuf field: optional bool isVox = 17; - */ - isVox?: boolean; - /** - * @generated from protobuf field: optional bool isVoiceGenderFemale = 18; - */ - isVoiceGenderFemale?: boolean; - /** - * @generated from protobuf field: optional bool isVoiceGenderMale = 19; - */ - isVoiceGenderMale?: boolean; - /** - * @generated from protobuf field: optional bool isVoiceGenderUnknown = 20; - */ - isVoiceGenderUnknown?: boolean; - /** - * @generated from protobuf field: optional bool isVoiceTriggerEnabled = 21; - */ - isVoiceTriggerEnabled?: boolean; - /** - * @generated from protobuf field: optional bool isWatch = 22; - */ - isWatch?: boolean; - /** - * @generated from protobuf field: optional string siriLocale = 23; - */ - siriLocale?: string; - /** - * @generated from protobuf field: optional string userAssignedDeviceName = 24; - */ - userAssignedDeviceName?: string; -} -declare class DeviceState$Type extends MessageType { - constructor(); -} -/** - * @generated MessageType for protobuf message DeviceState - */ -export declare const DeviceState: DeviceState$Type; -export {}; diff --git a/src/protobuf/apple.parsec.siri.v2alpha.DeviceState.js b/src/protobuf/apple.parsec.siri.v2alpha.DeviceState.js deleted file mode 100644 index ab1c607b2..000000000 --- a/src/protobuf/apple.parsec.siri.v2alpha.DeviceState.js +++ /dev/null @@ -1,40 +0,0 @@ -// @generated by protobuf-ts 2.9.4 with parameter generate_dependencies,long_type_number,output_javascript -// @generated from protobuf file "apple.parsec.siri.v2alpha.DeviceState.proto" (syntax proto3) -// tslint:disable -// @generated by protobuf-ts 2.9.4 with parameter generate_dependencies,long_type_number,output_javascript -// @generated from protobuf file "apple.parsec.siri.v2alpha.DeviceState.proto" (syntax proto3) -// tslint:disable -import { MessageType } from "@protobuf-ts/runtime"; -// @generated message type with reflection information, may provide speed optimized methods -class DeviceState$Type extends MessageType { - constructor() { - super("DeviceState", [ - { no: 1, name: "companionName", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ }, - { no: 2, name: "deviceName", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ }, - { no: 3, name: "inputOrigin", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ }, - { no: 6, name: "isAppleTv", kind: "scalar", opt: true, T: 8 /*ScalarType.BOOL*/ }, - { no: 7, name: "isCarDnd", kind: "scalar", opt: true, T: 8 /*ScalarType.BOOL*/ }, - { no: 8, name: "isCarPlay", kind: "scalar", opt: true, T: 8 /*ScalarType.BOOL*/ }, - { no: 9, name: "isEyesFree", kind: "scalar", opt: true, T: 8 /*ScalarType.BOOL*/ }, - { no: 10, name: "isHomePod", kind: "scalar", opt: true, T: 8 /*ScalarType.BOOL*/ }, - { no: 11, name: "isLockedWithPasscode", kind: "scalar", opt: true, T: 8 /*ScalarType.BOOL*/ }, - { no: 12, name: "isMac", kind: "scalar", opt: true, T: 8 /*ScalarType.BOOL*/ }, - { no: 13, name: "isMultiUser", kind: "scalar", opt: true, T: 8 /*ScalarType.BOOL*/ }, - { no: 14, name: "isPad", kind: "scalar", opt: true, T: 8 /*ScalarType.BOOL*/ }, - { no: 15, name: "isPhone", kind: "scalar", opt: true, T: 8 /*ScalarType.BOOL*/ }, - { no: 16, name: "isTextToSpeechEnabled", kind: "scalar", opt: true, T: 8 /*ScalarType.BOOL*/ }, - { no: 17, name: "isVox", kind: "scalar", opt: true, T: 8 /*ScalarType.BOOL*/ }, - { no: 18, name: "isVoiceGenderFemale", kind: "scalar", opt: true, T: 8 /*ScalarType.BOOL*/ }, - { no: 19, name: "isVoiceGenderMale", kind: "scalar", opt: true, T: 8 /*ScalarType.BOOL*/ }, - { no: 20, name: "isVoiceGenderUnknown", kind: "scalar", opt: true, T: 8 /*ScalarType.BOOL*/ }, - { no: 21, name: "isVoiceTriggerEnabled", kind: "scalar", opt: true, T: 8 /*ScalarType.BOOL*/ }, - { no: 22, name: "isWatch", kind: "scalar", opt: true, T: 8 /*ScalarType.BOOL*/ }, - { no: 23, name: "siriLocale", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ }, - { no: 24, name: "userAssignedDeviceName", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ } - ]); - } -} -/** - * @generated MessageType for protobuf message DeviceState - */ -export const DeviceState = new DeviceState$Type(); diff --git a/src/protobuf/apple.parsec.siri.v2alpha.PegasusSearchQuery.d.ts b/src/protobuf/apple.parsec.siri.v2alpha.PegasusSearchQuery.d.ts deleted file mode 100644 index 9275efb37..000000000 --- a/src/protobuf/apple.parsec.siri.v2alpha.PegasusSearchQuery.d.ts +++ /dev/null @@ -1,79 +0,0 @@ -// @generated by protobuf-ts 2.9.4 with parameter generate_dependencies,long_type_number,output_javascript -// @generated from protobuf file "apple.parsec.siri.v2alpha.PegasusSearchQuery.proto" (syntax proto3) -// tslint:disable -import { MessageType } from "@protobuf-ts/runtime"; -import { Any } from "./google/protobuf/any"; -/** - * @interface POMMESSchemaPOMMESPegasusSearchQuery : SISchemaInstrumentationMessage - * - * @generated from protobuf message PegasusSearchQuery - */ -export interface PegasusSearchQuery { - /** - * @generated from protobuf field: string queryString = 1; - */ - queryString: string; - /** - * @generated from protobuf field: string queryID = 2; - */ - queryID: string; - /** - * _searchStatus - * _error - * _queryConfidenceScore - * repeated SiriInstruction siriInstruction = 6; - * - * @generated from protobuf field: repeated Unknown2002 unknown2002 = 2002; - */ - unknown2002: Unknown2002[]; -} -/** - * @generated from protobuf message Unknown2002 - */ -export interface Unknown2002 { - /** - * @generated from protobuf field: U2002N2 n2 = 2; - */ - n2?: U2002N2; -} -/** - * @generated from protobuf message U2002N2 - */ -export interface U2002N2 { - /** - * @generated from protobuf field: int32 unknown1 = 1; - */ - unknown1: number; - /** - * @generated from protobuf field: google.protobuf.Any supplement = 2; - */ - supplement?: Any; - /** - * Supplement supplement = 2; - * - * @generated from protobuf field: int32 unknown3 = 3; - */ - unknown3: number; -} -declare class PegasusSearchQuery$Type extends MessageType { - constructor(); -} -/** - * @generated MessageType for protobuf message PegasusSearchQuery - */ -export declare const PegasusSearchQuery: PegasusSearchQuery$Type; -declare class Unknown2002$Type extends MessageType { - constructor(); -} -/** - * @generated MessageType for protobuf message Unknown2002 - */ -export declare const Unknown2002: Unknown2002$Type; -declare class U2002N2$Type extends MessageType { - constructor(); -} -/** - * @generated MessageType for protobuf message U2002N2 - */ -export declare const U2002N2: U2002N2$Type; -export {}; diff --git a/src/protobuf/apple.parsec.siri.v2alpha.PegasusSearchQuery.js b/src/protobuf/apple.parsec.siri.v2alpha.PegasusSearchQuery.js deleted file mode 100644 index b0606aee0..000000000 --- a/src/protobuf/apple.parsec.siri.v2alpha.PegasusSearchQuery.js +++ /dev/null @@ -1,48 +0,0 @@ -// @generated by protobuf-ts 2.9.4 with parameter generate_dependencies,long_type_number,output_javascript -// @generated from protobuf file "apple.parsec.siri.v2alpha.PegasusSearchQuery.proto" (syntax proto3) -// tslint:disable -// @generated by protobuf-ts 2.9.4 with parameter generate_dependencies,long_type_number,output_javascript -// @generated from protobuf file "apple.parsec.siri.v2alpha.PegasusSearchQuery.proto" (syntax proto3) -// tslint:disable -import { MessageType } from "@protobuf-ts/runtime"; -import { Any } from "./google/protobuf/any"; -// @generated message type with reflection information, may provide speed optimized methods -class PegasusSearchQuery$Type extends MessageType { - constructor() { - super("PegasusSearchQuery", [ - { no: 1, name: "queryString", kind: "scalar", T: 9 /*ScalarType.STRING*/ }, - { no: 2, name: "queryID", kind: "scalar", T: 9 /*ScalarType.STRING*/ }, - { no: 2002, name: "unknown2002", kind: "message", repeat: 1 /*RepeatType.PACKED*/, T: () => Unknown2002 } - ]); - } -} -/** - * @generated MessageType for protobuf message PegasusSearchQuery - */ -export const PegasusSearchQuery = new PegasusSearchQuery$Type(); -// @generated message type with reflection information, may provide speed optimized methods -class Unknown2002$Type extends MessageType { - constructor() { - super("Unknown2002", [ - { no: 2, name: "n2", kind: "message", T: () => U2002N2 } - ]); - } -} -/** - * @generated MessageType for protobuf message Unknown2002 - */ -export const Unknown2002 = new Unknown2002$Type(); -// @generated message type with reflection information, may provide speed optimized methods -class U2002N2$Type extends MessageType { - constructor() { - super("U2002N2", [ - { no: 1, name: "unknown1", kind: "scalar", T: 5 /*ScalarType.INT32*/ }, - { no: 2, name: "supplement", kind: "message", T: () => Any }, - { no: 3, name: "unknown3", kind: "scalar", T: 5 /*ScalarType.INT32*/ } - ]); - } -} -/** - * @generated MessageType for protobuf message U2002N2 - */ -export const U2002N2 = new U2002N2$Type(); diff --git a/src/protobuf/apple.parsec.siri.v2alpha.SiriPegasusContext.d.ts b/src/protobuf/apple.parsec.siri.v2alpha.SiriPegasusContext.d.ts deleted file mode 100644 index bab950836..000000000 --- a/src/protobuf/apple.parsec.siri.v2alpha.SiriPegasusContext.d.ts +++ /dev/null @@ -1,38 +0,0 @@ -// @generated by protobuf-ts 2.9.4 with parameter generate_dependencies,long_type_number,output_javascript -// @generated from protobuf file "apple.parsec.siri.v2alpha.SiriPegasusContext.proto" (syntax proto3) -// tslint:disable -import { MessageType } from "@protobuf-ts/runtime"; -import { DeviceState } from "./apple.parsec.siri.v2alpha.DeviceState"; -/** - * @interface POMMESSchemaPOMMESSiriPegasusMetadata : SISchemaInstrumentationMessage - * - * @generated from protobuf message SiriPegasusContext - */ -export interface SiriPegasusContext { - /** - * @generated from protobuf field: DeviceState deviceState = 1; - */ - deviceState?: DeviceState; - /** - * int32 siriInteractionType = 2; - * bool _isNavigationMode = 3; - * _treatRandomAsFirst = 4; - * - * @generated from protobuf field: string assistantID = 8; - */ - assistantID: string; - /** - * _person = 9; - * - * @generated from protobuf field: string interactionID = 10; - */ - interactionID: string; -} -declare class SiriPegasusContext$Type extends MessageType { - constructor(); -} -/** - * @generated MessageType for protobuf message SiriPegasusContext - */ -export declare const SiriPegasusContext: SiriPegasusContext$Type; -export {}; diff --git a/src/protobuf/apple.parsec.siri.v2alpha.SiriPegasusContext.js b/src/protobuf/apple.parsec.siri.v2alpha.SiriPegasusContext.js deleted file mode 100644 index 96a2c16ea..000000000 --- a/src/protobuf/apple.parsec.siri.v2alpha.SiriPegasusContext.js +++ /dev/null @@ -1,22 +0,0 @@ -// @generated by protobuf-ts 2.9.4 with parameter generate_dependencies,long_type_number,output_javascript -// @generated from protobuf file "apple.parsec.siri.v2alpha.SiriPegasusContext.proto" (syntax proto3) -// tslint:disable -// @generated by protobuf-ts 2.9.4 with parameter generate_dependencies,long_type_number,output_javascript -// @generated from protobuf file "apple.parsec.siri.v2alpha.SiriPegasusContext.proto" (syntax proto3) -// tslint:disable -import { MessageType } from "@protobuf-ts/runtime"; -import { DeviceState } from "./apple.parsec.siri.v2alpha.DeviceState"; -// @generated message type with reflection information, may provide speed optimized methods -class SiriPegasusContext$Type extends MessageType { - constructor() { - super("SiriPegasusContext", [ - { no: 1, name: "deviceState", kind: "message", T: () => DeviceState }, - { no: 8, name: "assistantID", kind: "scalar", T: 9 /*ScalarType.STRING*/ }, - { no: 10, name: "interactionID", kind: "scalar", T: 9 /*ScalarType.STRING*/ } - ]); - } -} -/** - * @generated MessageType for protobuf message SiriPegasusContext - */ -export const SiriPegasusContext = new SiriPegasusContext$Type(); diff --git a/src/protobuf/apple.parsec.siri.v2alpha.SiriPegasusRequest.d.ts b/src/protobuf/apple.parsec.siri.v2alpha.SiriPegasusRequest.d.ts deleted file mode 100644 index 2c1866daa..000000000 --- a/src/protobuf/apple.parsec.siri.v2alpha.SiriPegasusRequest.d.ts +++ /dev/null @@ -1,51 +0,0 @@ -// @generated by protobuf-ts 2.9.4 with parameter generate_dependencies,long_type_number,output_javascript -// @generated from protobuf file "apple.parsec.siri.v2alpha.SiriPegasusRequest.proto" (syntax proto3) -// tslint:disable -import { MessageType } from "@protobuf-ts/runtime"; -import { SiriPegasusContext } from "./apple.parsec.siri.v2alpha.SiriPegasusContext"; -import { PegasusQueryContext } from "./apple.parsec.search.PegasusQueryContext"; -import { PegasusSearchQuery } from "./apple.parsec.siri.v2alpha.PegasusSearchQuery"; -/** - * @interface POMMESSchemaPOMMESPegasusRequestArguments : SISchemaInstrumentationMessage - * - * @generated from protobuf message SiriPegasusRequest - */ -export interface SiriPegasusRequest { - /** - * @generated from protobuf field: repeated PegasusSearchQuery queries = 1; - */ - queries: PegasusSearchQuery[]; - /** - * PegasusSearchQuery query = 1; - * - * @generated from protobuf field: PegasusQueryContext queryContext = 2; - */ - queryContext?: PegasusQueryContext; - /** - * @generated from protobuf field: optional bool userDataShareOptIn = 3; - */ - userDataShareOptIn?: boolean; - /** - * @generated from protobuf field: optional string featureFlag = 4; - */ - featureFlag?: string; - /** - * @generated from protobuf field: optional SiriPegasusContext siriPegasusContext = 5; - */ - siriPegasusContext?: SiriPegasusContext; - /** - * optional SiriEnvironment siriEnvironment = 6; - * optional bool isSiriInternalRequest = 7; //? - * - * @generated from protobuf field: optional bool isDataOnlyRequest = 8; - */ - isDataOnlyRequest?: boolean; -} -declare class SiriPegasusRequest$Type extends MessageType { - constructor(); -} -/** - * @generated MessageType for protobuf message SiriPegasusRequest - */ -export declare const SiriPegasusRequest: SiriPegasusRequest$Type; -export {}; diff --git a/src/protobuf/apple.parsec.siri.v2alpha.SiriPegasusRequest.js b/src/protobuf/apple.parsec.siri.v2alpha.SiriPegasusRequest.js deleted file mode 100644 index f826c8f8c..000000000 --- a/src/protobuf/apple.parsec.siri.v2alpha.SiriPegasusRequest.js +++ /dev/null @@ -1,27 +0,0 @@ -// @generated by protobuf-ts 2.9.4 with parameter generate_dependencies,long_type_number,output_javascript -// @generated from protobuf file "apple.parsec.siri.v2alpha.SiriPegasusRequest.proto" (syntax proto3) -// tslint:disable -// @generated by protobuf-ts 2.9.4 with parameter generate_dependencies,long_type_number,output_javascript -// @generated from protobuf file "apple.parsec.siri.v2alpha.SiriPegasusRequest.proto" (syntax proto3) -// tslint:disable -import { MessageType } from "@protobuf-ts/runtime"; -import { SiriPegasusContext } from "./apple.parsec.siri.v2alpha.SiriPegasusContext"; -import { PegasusQueryContext } from "./apple.parsec.search.PegasusQueryContext"; -import { PegasusSearchQuery } from "./apple.parsec.siri.v2alpha.PegasusSearchQuery"; -// @generated message type with reflection information, may provide speed optimized methods -class SiriPegasusRequest$Type extends MessageType { - constructor() { - super("SiriPegasusRequest", [ - { no: 1, name: "queries", kind: "message", repeat: 1 /*RepeatType.PACKED*/, T: () => PegasusSearchQuery }, - { no: 2, name: "queryContext", kind: "message", T: () => PegasusQueryContext }, - { no: 3, name: "userDataShareOptIn", kind: "scalar", opt: true, T: 8 /*ScalarType.BOOL*/ }, - { no: 4, name: "featureFlag", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ }, - { no: 5, name: "siriPegasusContext", kind: "message", T: () => SiriPegasusContext }, - { no: 8, name: "isDataOnlyRequest", kind: "scalar", opt: true, T: 8 /*ScalarType.BOOL*/ } - ]); - } -} -/** - * @generated MessageType for protobuf message SiriPegasusRequest - */ -export const SiriPegasusRequest = new SiriPegasusRequest$Type(); diff --git a/src/protobuf/apple.parsec.visualsearch.v2.VisualSearchRequest.d.ts b/src/protobuf/apple.parsec.visualsearch.v2.VisualSearchRequest.d.ts deleted file mode 100644 index 0ecf7f415..000000000 --- a/src/protobuf/apple.parsec.visualsearch.v2.VisualSearchRequest.d.ts +++ /dev/null @@ -1,25 +0,0 @@ -// @generated by protobuf-ts 2.9.4 with parameter generate_dependencies,long_type_number,output_javascript -// @generated from protobuf file "apple.parsec.visualsearch.v2.VisualSearchRequest.proto" (syntax proto3) -// tslint:disable -import { MessageType } from "@protobuf-ts/runtime"; -import { PegasusQueryContext } from "./apple.parsec.search.PegasusQueryContext"; -/** - * @generated from protobuf message VisualSearchRequest - */ -export interface VisualSearchRequest { - /** - * MISSING_TYPE *_visualQueryLite = 1 - * MISSING_TYPE *_includeDomains = 2; - * - * @generated from protobuf field: PegasusQueryContext queryContext = 3; - */ - queryContext?: PegasusQueryContext; -} -declare class VisualSearchRequest$Type extends MessageType { - constructor(); -} -/** - * @generated MessageType for protobuf message VisualSearchRequest - */ -export declare const VisualSearchRequest: VisualSearchRequest$Type; -export {}; diff --git a/src/protobuf/apple.parsec.visualsearch.v2.VisualSearchRequest.js b/src/protobuf/apple.parsec.visualsearch.v2.VisualSearchRequest.js deleted file mode 100644 index 203d788c6..000000000 --- a/src/protobuf/apple.parsec.visualsearch.v2.VisualSearchRequest.js +++ /dev/null @@ -1,20 +0,0 @@ -// @generated by protobuf-ts 2.9.4 with parameter generate_dependencies,long_type_number,output_javascript -// @generated from protobuf file "apple.parsec.visualsearch.v2.VisualSearchRequest.proto" (syntax proto3) -// tslint:disable -// @generated by protobuf-ts 2.9.4 with parameter generate_dependencies,long_type_number,output_javascript -// @generated from protobuf file "apple.parsec.visualsearch.v2.VisualSearchRequest.proto" (syntax proto3) -// tslint:disable -import { MessageType } from "@protobuf-ts/runtime"; -import { PegasusQueryContext } from "./apple.parsec.search.PegasusQueryContext"; -// @generated message type with reflection information, may provide speed optimized methods -class VisualSearchRequest$Type extends MessageType { - constructor() { - super("VisualSearchRequest", [ - { no: 3, name: "queryContext", kind: "message", T: () => PegasusQueryContext } - ]); - } -} -/** - * @generated MessageType for protobuf message VisualSearchRequest - */ -export const VisualSearchRequest = new VisualSearchRequest$Type(); diff --git a/stoverride/Siri.beta.stoverride b/stoverride/Siri.macOS.stoverride similarity index 55% rename from stoverride/Siri.beta.stoverride rename to stoverride/Siri.macOS.stoverride index 9910e523b..95edee9e3 100644 --- a/stoverride/Siri.beta.stoverride +++ b/stoverride/Siri.macOS.stoverride @@ -1,14 +1,14 @@ -name: " iRingo: ⭕ Siri & Search β" +name: " iRingo: ⭕ Siri & Spotlight" desc: |- - V1️⃣ & iOS 17.6⬇️ & BETA - 全面自定义「Siri与搜索」中的「Siri 建议」功能。 - 本模块不含自定义「Siri与搜索」中的「询问 Siri」功能。 - 注:该覆写包含代理规则。 -openUrl: "http://boxjs.com/#/app/iRingo.Siri.beta" -author: "VirgilClyne" -homepage: "https://github.com/VirgilClyne" -manual: "https://github.com/VirgilClyne/iRingo/wiki/⭕-Siri与搜索" -icon: "https://raw.githubusercontent.com/Koolson/Qure/master/IconSet/Color/Siri.png" + V1️⃣ & iOS 17.6⬇️ + 全面自定义「Siri与聚焦」中的「Siri 建议」功能。 + 本模块不含自定义「Siri与聚焦」中的「询问 Siri」功能。 + 注:该覆写包含代理规则。" +openUrl: "http://boxjs.com/#/app/iRingo.Siri" +author: "VirgilClyne[https://github.com/VirgilClyne]" +homepage: "https://github.com/NSRingo" +manual: "https://github.com/NSRingo/Siri/wiki/⭕-Siri与搜索" +icon: "https://raw.githubusercontent.com/NSRingo/Siri/main/images/icon/v1/Siri%20-%20Icon.png" category: " iRingo" rules: @@ -21,13 +21,13 @@ http: - "api*.smoot.apple.com" - "api.smoot.apple.cn" script: - - match: ^https?:\/\/api\.smoot\.apple\.(com|cn)\/bag + - match: ^https?:\/\/api2?\.smoot\.apple\.(com|cn)\/bag name: Siri.request type: request require-body: false timeout: 5 argument: - - match: ^https?:\/\/api\.smoot\.apple\.(com|cn)\/bag + - match: ^https?:\/\/api2?\.smoot\.apple\.(com|cn)\/bag name: Siri.response type: response require-body: true @@ -57,18 +57,6 @@ http: require-body: true timeout: 10 argument: - - match: ^https?:\/\/api(2|-.+)\.smoot\.apple\.com\/warm - name: Siri.request - type: request - require-body: false - timeout: 5 - argument: - - match: ^https?:\/\/api(2|-.+)\.smoot\.apple\.com\/render - name: Siri.request - type: request - require-body: false - timeout: 5 - argument: - match: ^https?:\/\/api(2|-.+)\.smoot\.apple\.com\/flight name: Siri.request type: request @@ -78,8 +66,8 @@ http: script-providers: Siri.request: - url: https://raw.githubusercontent.com/VirgilClyne/iRingo/beta/js/Siri.request.beta.js + url: https://github.com/NSRingo/Siri/releases/latest/download/request.js interval: 86400 Siri.response: - url: https://raw.githubusercontent.com/VirgilClyne/iRingo/beta/js/Siri.response.beta.js + url: https://github.com/NSRingo/Siri/releases/latest/download/response.js interval: 86400 diff --git a/stoverride/Siri.stoverride b/stoverride/Siri.stoverride index e059003b1..fee020d9a 100644 --- a/stoverride/Siri.stoverride +++ b/stoverride/Siri.stoverride @@ -3,19 +3,13 @@ desc: |- V1️⃣ & iOS 17.6⬇️ 全面自定义「Siri与搜索」中的「Siri 建议」功能。 本模块不含自定义「Siri与搜索」中的「询问 Siri」功能。 - 注:该覆写包含代理规则。" openUrl: "http://boxjs.com/#/app/iRingo.Siri" -author: "VirgilClyne" -homepage: "https://github.com/VirgilClyne" -manual: "https://github.com/VirgilClyne/iRingo/wiki/⭕-Siri与搜索" -icon: "https://raw.githubusercontent.com/Koolson/Qure/master/IconSet/Color/Siri.png" +author: "VirgilClyne[https://github.com/VirgilClyne]" +homepage: "https://github.com/NSRingo" +manual: "https://github.com/NSRingo/Siri/wiki/⭕-Siri与搜索" +icon: "https://raw.githubusercontent.com/NSRingo/Siri/main/images/icon/v1/Siri%20-%20Icon.png" category: " iRingo" -rules: - - DOMAIN,lookup-api.apple.com,PROXY - - DOMAIN,lookup-api.apple.com.edgekey.net,PROXY - - DOMAIN,e16991.b.akamaiedge.net,PROXY - http: mitm: - "api*.smoot.apple.com" @@ -66,8 +60,8 @@ http: script-providers: Siri.request: - url: https://raw.githubusercontent.com/VirgilClyne/iRingo/main/js/Siri.request.js + url: https://github.com/NSRingo/Siri/releases/latest/download/request.js interval: 86400 Siri.response: - url: https://raw.githubusercontent.com/VirgilClyne/iRingo/main/js/Siri.response.js + url: https://github.com/NSRingo/Siri/releases/latest/download/response.js interval: 86400 diff --git a/stoverride/Weather.stoverride b/stoverride/Weather.stoverride index e31a9dd36..de4027307 100644 --- a/stoverride/Weather.stoverride +++ b/stoverride/Weather.stoverride @@ -1,14 +1,19 @@ -name:  iRingo for 🌤 Weather -desc: (V4) 1.解锁全部天气功能 2.替换空气质量数据 3.添加下一小时降水数据 4.替换空气质量地图 +name:  iRingo: 🌤 Weather +desc: |- + V4️⃣ & iOS 16⬇️ + 1.解锁全部天气功能 + 2.替换空气质量数据 + 3.添加下一小时降水数据 + 4.替换空气质量地图 openUrl: http://boxjs.com/#/app/iRingo.Weather -author: Wordless Echo -homepage: https://github.com/WordlessEcho -manual: https://github.com/VirgilClyne/iRingo/wiki/🌤天气 -icon: https://is4-ssl.mzstatic.com/image/thumb/Purple125/v4/c8/76/87/c8768792-b7ab-7de4-9c70-1888096b7ae9/AppIcon-0-0-1x_U007emarketing-0-0-0-10-0-0-sRGB-0-0-0-GLES2_U002c0-512MB-85-220-0-0.png/108x0w.webp +author: WordlessEcho[https://github.com/WordlessEcho] +homepage: https://github.com/NSRingo +manual: "https://github.com/NSRingo/Weather/wiki" +icon: "https://developer.apple.com/assets/elements/icons/weather/weather-128x128.png" category: " iRingo" rules: - - DOMAIN,weather-analytics-events.apple.com,REJECT + - DOMAIN,weather-analytics-events.apple.com,REJECT-DROP http: mitm: @@ -42,14 +47,14 @@ http: script-providers: Weather.Availability.request: - url: https://raw.githubusercontent.com/VirgilClyne/iRingo/main/js/Weather.Availability.request.js + url: https://raw.githubusercontent.com/NSRingo/Weather/main/js/Availability.request.js interval: 86400 Weather.Availability.response: - url: https://raw.githubusercontent.com/VirgilClyne/iRingo/main/js/Weather.Availability.response.js + url: https://raw.githubusercontent.com/NSRingo/Weather/main/js/Availability.response.js interval: 86400 Weather.response: - url: https://raw.githubusercontent.com/VirgilClyne/iRingo/main/js/Weather.response.js + url: https://raw.githubusercontent.com/NSRingo/Weather/main/js/response.js interval: 86400 Weather.Map.request: - url: https://raw.githubusercontent.com/VirgilClyne/iRingo/main/js/Weather.Map.request.js + url: https://raw.githubusercontent.com/NSRingo/Weather/main/js/Map.request.js interval: 86400