-
Notifications
You must be signed in to change notification settings - Fork 0
/
iOS15_Weather_AQI_US.js
152 lines (132 loc) · 12.8 KB
/
iOS15_Weather_AQI_US.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
// 此脚本仅适用于 iOS 15 的原生天气 App
// Developed by Hackl0us (https://github.com/hackl0us)
const $ = new Env('AQI-US');
$.token = 'hackl0us_aqi_token';
// 前往 https://aqicn.org/data-platform/token/ 注册账户,将申请的 API Token 填入下方引号内或添加boxjs订阅填写 https://raw.githubusercontent.com/githubdulong/Script/master/boxjs.json
const aqicnToken = $.getdata($.token) || '';
/*
[Surge]
空气质量 = type=http-response,pattern=https://weather-data.apple.com/v2/weather/[\w-]+/-?[0-9]+\.[0-9]+/-?[0-9]+\.[0-9]+\?,requires-body=true,max-size=0,script-path=https://raw.githubusercontent.com/githubdulong/Script/master/iOS15_Weather_AQI_US.js
[Quan_x]
^https:\/\/weather-data\.apple\.com\/v2\/weather\/[\w-]+\/-?[0-9]+\.[0-9]+\/-?[0-9]+\.[0-9]+\? url script-response-body https://raw.githubusercontent.com/githubdulong/Script/master/iOS15_Weather_AQI_US.js
[MITM]
hostname = weather-data.apple.com
*/
const AirQualityStandard = {
CN: 'HJ6332012.2115',
US: 'EPA_NowCast.2115'
}
const AirQualityLevel = {
GOOD: 1,
MODERATE: 2,
UNHEALTHY_FOR_SENSITIVE: 3,
UNHEALTHY: 4,
VERY_UNHEALTHY: 5,
HAZARDOUS: 6
}
const coordRegex = /https:\/\/weather-data\.apple\.com\/v2\/weather\/([\w-]+)\/(-?[0-9]+\.[0-9]+)\/(-?[0-9]+\.[0-9]+)\?/
const [_, language, lat, lng] = $request.url.match(coordRegex)
function classifyAirQualityLevel(aqiIndex) {
if (aqiIndex >= 0 && aqiIndex <= 50) {
return AirQualityLevel.GOOD;
} else if (aqiIndex >= 51 && aqiIndex <= 100) {
return AirQualityLevel.MODERATE;
} else if (aqiIndex >= 101 && aqiIndex <= 150) {
return AirQualityLevel.UNHEALTHY_FOR_SENSITIVE;
} else if (aqiIndex >= 151 && aqiIndex <= 200) {
return AirQualityLevel.UNHEALTHY;
} else if (aqiIndex >= 201 && aqiIndex <= 300) {
return AirQualityLevel.VERY_UNHEALTHY;
} else if (aqiIndex >= 301) {
return AirQualityLevel.HAZARDOUS;
}
}
function modifyWeatherResp(weatherRespBody, aqicnRespBody) {
let weatherRespJson = JSON.parse(weatherRespBody)
let aqicnRespJson = JSON.parse(aqicnRespBody).data
weatherRespJson.airQuality = constructAirQuailityNode(aqicnRespJson)
return JSON.stringify(weatherRespJson)
}
function getPrimaryPollutant(pollutant) {
switch (pollutant) {
case 'co':
return 'CO2';
case 'so2':
return 'SO2';
case 'no2':
return 'NO2';
case 'nox':
return 'NOX'
case 'pm25':
return 'PM2.5';
case 'pm10':
return 'PM10';
case 'o3':
return 'OZONE';
default:
return "OTHER";
}
}
function constructAirQuailityNode(aqicnData) {
let airQualityNode = { "isSignificant": true, "learnMoreURL": "", "primaryPollutant": "", "scale": "", "categoryIndex": 0, "source": "", "pollutants": { "CO": { "name": "CO", "amount": 0, "unit": "microgramsPerM3" }, "NO": { "name": "NO", "amount": 0, "unit": "microgramsPerM3" }, "NO2": { "name": "NO2", "amount": 0, "unit": "microgramsPerM3" }, "SO2": { "name": "SO2", "amount": 0, "unit": "microgramsPerM3" }, "NOX": { "name": "NOX", "amount": 0, "unit": "microgramsPerM3" }, "OZONE": { "name": "OZONE", "amount": 0, "unit": "microgramsPerM3" }, "PM10": { "name": "PM10", "amount": 0, "unit": "microgramsPerM3" }, "PM2.5": { "name": "PM2.5", "amount": 0, "unit": "microgramsPerM3" } }, "sourceType": "station", "metadata": { "version": 2, "longitude": 0, "providerName": "aqicn.org", "providerLogo": "https://aqicn.org/mapi/logo.png", "language": "en-US", "latitude": 0, "expireTime": "", "reportedTime": "", "readTime": "", "units": "m" }, "name": "AirQuality", "index": 0 }
const aqicnIndex = aqicnData.aqi
airQualityNode.index = aqicnIndex
airQualityNode.categoryIndex = classifyAirQualityLevel(aqicnIndex)
airQualityNode.learnMoreURL = aqicnData.city.url + '/cn/m'
airQualityNode.scale = AirQualityStandard.US
airQualityNode.source = aqicnData.city.name
airQualityNode.primaryPollutant = getPrimaryPollutant(aqicnData.dominentpol)
airQualityNode.metadata.latitude = aqicnData.city.geo[0]
airQualityNode.metadata.longitude = aqicnData.city.geo[1]
airQualityNode.metadata.readTime = timeConversion(new Date(), 'remain')
airQualityNode.metadata.reportedTime = timeConversion(new Date(aqicnData.time.iso), 'remain')
airQualityNode.metadata.expireTime = timeConversion(new Date(aqicnData.time.iso), 'add-1h-floor')
airQualityNode.metadata.language = language
airQualityNode.pollutants.CO.amount = aqicnData.iaqi.co?.v || -1
airQualityNode.pollutants.SO2.amount = aqicnData.iaqi.so2?.v || -1
airQualityNode.pollutants.NO2.amount = aqicnData.iaqi.no2?.v || -1
airQualityNode.pollutants.NOX.amount = aqicnData.iaqi.nox?.v || -1
airQualityNode.pollutants["PM2.5"].amount = aqicnData.iaqi.pm25?.v || -1
airQualityNode.pollutants.OZONE.amount = aqicnData.iaqi.o3?.v || -1
airQualityNode.pollutants.PM10.amount = aqicnData.iaqi.pm10?.v || -1
return airQualityNode
}
function timeConversion(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:
console.log('Error time converting action.');
}
let timeString = time.toISOString().split('.')[0] + 'Z'
return timeString;
}
$.get(
{
url: `https://api.waqi.info/feed/geo:${lat};${lng}/?token=${aqicnToken}`
},
(err, resp, data) => {
try {
if (err) {
$.logErr(err, resp);
let body = $response.body;
$.done({ body });
} else {
let body = modifyWeatherResp($response.body, resp.body);
$.done({ body });
}
} catch (e) {
$.logErr(e, resp);
let body = $response.body;
$.done({ body });
}
}
);
/***************** Env *****************/
// prettier-ignore
function Env(t,e){class s{constructor(t){this.env=t}send(t,e="GET"){t="string"==typeof t?{url:t}:t;let s=this.get;return"POST"===e&&(s=this.post),new Promise((e,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.startTime=(new Date).getTime(),Object.assign(this,e)}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}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=(()=>{})){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)})):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))):this.isNode()&&(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();this.ckjar.setCookieSync(s,null),e.cookieJar=this.ckjar}}catch(t){this.logErr(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=>{const{message:s,response:i}=t;e(s,i,i&&i.body)}))}post(t,e=(()=>{})){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.post(t,(t,s,i)=>{!t&&s&&(s.body=i,s.statusCode=s.status),e(t,s,i)});else if(this.isQuanX())t.method="POST",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()){this.initGotEnv(t);const{url:s,...i}=t;this.got.post(s,i).then(t=>{const{statusCode:s,statusCode:i,headers:r,body:o}=t;e(null,{status:s,statusCode:i,headers:r,body:o},o)},t=>{const{message:s,response:i}=t;e(s,i,i&&i.body)})}}time(t){let e={"M+":(new Date).getMonth()+1,"d+":(new Date).getDate(),"H+":(new Date).getHours(),"m+":(new Date).getMinutes(),"s+":(new Date).getSeconds(),"q+":Math.floor(((new Date).getMonth()+3)/3),S:(new Date).getMilliseconds()};/(y+)/.test(t)&&(t=t.replace(RegExp.$1,((new Date).getFullYear()+"").substr(4-RegExp.$1.length)));for(let s in e)new RegExp("("+s+")").test(t)&&(t=t.replace(RegExp.$1,1==RegExp.$1.length?e[s]:("00"+e[s]).substr((""+e[s]).length)));return t}msg(e=t,s="",i="",r){const o=t=>{if(!t)return t;if("string"==typeof t)return this.isLoon()?t:this.isQuanX()?{"open-url":t}:this.isSurge()?{url:t}:void 0;if("object"==typeof t){if(this.isLoon()){let e=t.openUrl||t.url||t["open-url"],s=t.mediaUrl||t["media-url"];return{openUrl:e,mediaUrl:s}}if(this.isQuanX()){let e=t["open-url"]||t.url||t.openUrl,s=t["media-url"]||t.mediaUrl;return{"open-url":e,"media-url":s}}if(this.isSurge()){let e=t.url||t.openUrl||t["open-url"];return{url:e}}}};this.isMute||(this.isSurge()||this.isLoon()?$notification.post(e,s,i,o(r)):this.isQuanX()&&$notify(e,s,i,o(r)));let h=["","==============\ud83d\udce3\u7cfb\u7edf\u901a\u77e5\ud83d\udce3=============="];h.push(e),s&&h.push(s),i&&h.push(i),console.log(h.join("\n")),this.logs=this.logs.concat(h)}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.isSurge()||this.isQuanX()||this.isLoon())&&$done(t)}}(t,e)}