代码拉取完成,页面将自动刷新
// Variables used by Scriptable.
// These must be at the very top of the file. Do not edit.
// icon-color: purple; icon-glyph: car;
// ------静态数据------
// 背景图片
const kBackgroundImage = "http://img1.tcdachun.com/180519/330808-1P51Z9454738.jpg"
// 无数据背景图
const kEmptyDataImage = "https://carappvideo.gtmc.com.cn//fs01//IFImage//carnetImage//img-819-PT-ZZB-089.png"
// 丰田Icon logo_black:黑色图标 logo_white:白色图标
const kToyotaIconImage = "https://gitee.com/James2119/pic/raw/master/img/logo_black.png"
// App版本
const appVersion = "5.5.0"
// UserAgent
const kUserAgent = `GTMC_CarOwner_Yonyou/${appVersion} (iPhone; iOS 16.0.2; Scale/3.00)Accept-Language: zh-Hans-CN;q=1, en-CN;q=0.9`
// CookieKey
const kUserInfoKey = "USERINFO_KEY"
// 当前版本号
const kCurrentVersion = "1.0.4"
// ContentType
const kContentTypeUrlencoded = "application/x-www-form-urlencoded"
const kContentTypeJson = "application/json"
const ak = "7kfXOGGAkWlBTygtw1vldR8ws9nI6P8o"
// 警告信息
const kWarningMsg = [
{
"warnType": "V003",
"warnTypeName": "右后门未关"
},
{
"warnType": "V004",
"warnTypeName": "左后门未关"
},
{
"warnType": "V005",
"warnTypeName": "右前门未关"
},
{
"warnType": "V006",
"warnTypeName": "左前门未关",
},
{
"warnType": "V134",
"warnTypeName": "发动机罩未关"
},
{
"warnType": "V143",
"warnTypeName": "左前门未锁",
},
{
"warnType": "V145",
"warnTypeName": "右后车窗未关"
},
{
"warnType": "V146",
"warnTypeName": "左后车窗未关"
},
{
"warnType": "V147",
"warnTypeName": "右前车窗未关"
},
{
"warnType": "V148",
"warnTypeName": "左前车窗未关"
},
{
"warnType": "V149",
"warnTypeName": "天窗未关"
},
{
"warnType": "V172",
"warnTypeName": "左后门未锁"
},
{
"warnType": "V173",
"warnTypeName": "右前门未锁"
},
{
"warnType": "V174",
"warnTypeName": "右后门未锁"
},
{
"warnType": "V229",
"warnTypeName": "闪光灯未关"
},
{
"warnType": "V408",
"warnTypeName": "尾灯未关"
},
{
"warnType": "V409",
"warnTypeName": "大灯未关"
}
]
// 组件尺寸
const kDeviceSize = {
'428x926': {
small: { width: 176, height: 176 },
medium: { width: 374, height: 176 },
large: { width: 374, height: 391 }
},
'390x844': {
small: { width: 161, height: 161 },
medium: { width: 342, height: 161 },
large: { width: 342, height: 359 }
},
'414x896': {
small: { width: 169, height: 169 },
medium: { width: 360, height: 169 },
large: { width: 360, height: 376 }
},
'375x812': {
small: { width: 155, height: 155 },
medium: { width: 329, height: 155 },
large: { width: 329, height: 345 }
},
'414x736': {
small: { width: 159, height: 159 },
medium: { width: 348, height: 159 },
large: { width: 348, height: 357 }
},
'375x667': {
small: { width: 148, height: 148 },
medium: { width: 322, height: 148 },
large: { width: 322, height: 324 }
},
'320x568': {
small: { width: 141, height: 141 },
medium: { width: 291, height: 141 },
large: { width: 291, height: 299 }
}
}
// ------静态数据结束------
// ------请求URL------
// 广汽丰田接口地址
const CarUrlPrefix = "https://carapp.gtmc.com.cn/api/"
// 汽车VIM信息URL
const CarVinInfoURL = "https://carapp.gtmc.com.cn/api/appgtmc/AppRepairRecordAction/loadUserData.json"
// 汽车经纬度URL
const CarPositionInfoURL = "https://carapp.gtmc.com.cn/api/vhcApp/vhcNet/getVhcPositionInfo"
// 经纬度逆编码URL
function CarPositionAddressURL(ak, latitude, longitude) {
return `https://api.map.baidu.com/reverse_geocoding/v3/?ak=${ak}&output=json&coordtype=wgs84ll&location=${latitude},${longitude}&radius=1000&pois=1&coordtype=bd09ll&page_size=1&extensions_poi=1`
}
// 获取当前车辆信息(油耗,里程等)
function CarCurrentInfoURL(phone, userId, vin) {
return `https://carapp.gtmc.com.cn/api/vhcApp/vhcNet/refreshVhcCondition?appVersion=${appVersion}&carCode=&fyxDevice=2&phone=${phone}&type=&userId=${userId}&vin=${vin}`
}
// 获取当前车辆信息(油耗,里程等)新接口
function CarCurrentInfoURLNew(phone, userId, vin) {
return CarUrlPrefix + "/vhcApp/vhcNet/refreshVhcConditionNew"
}
// 获取当前车辆信息(油量、剩余里程等)新接口
const CarConditionInfoURL = CarUrlPrefix + "/vhcApp/vhcNet/getVhcConditionInfo"
// 车辆健康报告数据
const CarHealthReportByMonthUrl = CarUrlPrefix + "/vhcApp/health/getHealthReportByMonth"
// 获取当前车辆信息(名称,图片,车牌号等)
function CarCurrentVHCInfoURL(phone, userId, vin) {
return `https://carapp.gtmc.com.cn/api/vhcApp/vhcNet/vhcInfo?appVersion=${appVersion}&basePage=1&carCode=&fyxDevice=2&phone=${phone}&showPopup=1&userId=${userId}&vin=${vin}`
}
// 获取警告信息
function CarWarningMsgURL(phone, userId, vin) {
return `https://carapp.gtmc.com.cn/api/vhcApp/warning/warningMsg?appVersion=${appVersion}.0&carCode=5&fyxDevice=2&phone=${phone}&userId=${userId}&vin=${vin}`
}
// 百度静态图片
function BaiduStaticPicURL(ak, longitude, latitude, size) {
return `https://api.map.baidu.com/staticimage/v2?ak=${ak}¢er=${longitude},${latitude}&width=${size.width}&height=${size.height}&zoom=15©right=1`
}
// 更新文件
const kUpdateVersionURL = "https://gitee.com/james2119/scriptable-camry/blob/master/version.json"
// ------请求URL结束------
class Base {
constructor(arg = '') {
this.arg = arg
this._actions = {}
this.init()
}
init(widgetFamily = config.widgetFamily) {
// 组件大小:small,medium,large
this.debugLog("组件初始化")
this.widgetFamily = widgetFamily
this.userInfo = this.getUserInfo()
}
// 注册点击操作菜单
registerAction(name, func) {
this._actions[name] = func.bind(this)
}
//--------Cookie--------
// 获取Cookie信息
getUserInfo() {
let userInfo = ""
if (Keychain.contains(kUserInfoKey)) {
userInfo = Keychain.get(kUserInfoKey)
}
return userInfo
}
// 设置Cookie信息
setUserInfo(userInfo) {
Keychain.set(kUserInfoKey, userInfo)
}
// 移除Cookie信息
removeUserInfo() {
Keychain.remove(kUserInfoKey)
}
//--------网络请求--------
// 封装的网络请求
// POST 请求
async PostRequest(url, contentType, body, cookie) {
let header = {
"type": "1",
"User-Agent": kUserAgent,
"Authorization": cookie,
"appVersion": `${appVersion}`,
"fyxDevice": "2",
"Content-Type": contentType
}
log(`开始POST网络请求: ${url}`)
let request = new Request(url)
request.headers = header
request.method = "POST"
request.body = JSON.stringify(body)
return await (contentType == kContentTypeUrlencoded) ? request.loadString() : request.loadJSON()
}
// GET 请求
async GetRequest(url, contentType, cookie) {
let request = new Request(url)
let header = {
"type": "1",
"User-Agent": kUserAgent,
"Authorization": cookie,
"appVersion": `${appVersion}`,
"fyxDevice": "2",
"Content-Type": contentType
}
log(`开始GET网络请求: ${url}`)
request.headers = header
return await request.loadJSON()
}
//获取远程图片内容
async loadImage(url, useCache = true) {
const cacheKey = this.md5(url)
const cacheFile = FileManager.local().joinPath(FileManager.local().temporaryDirectory(), cacheKey)
// 判断是否有缓存
if (useCache && FileManager.local().fileExists(cacheFile)) {
return Image.fromFile(cacheFile)
}
try {
const req = new Request(url)
const img = await req.loadImage()
// 存储到缓存
FileManager.local().writeImage(cacheFile, img)
return img
} catch (e) {
// 没有缓存+失败情况下,返回自定义的绘制图片(红色背景)
let ctx = new DrawContext()
ctx.size = new Size(100, 100)
ctx.setFillColor(this._Color("#ECECEC"))
ctx.fillRect(new Rect(0, 0, 100, 100))
return ctx.getImage();
}
}
//--------工具函数--------
// md5 加密
md5(string) {
const safeAdd = (x, y) => {
let lsw = (x & 0xFFFF) + (y & 0xFFFF)
return (((x >> 16) + (y >> 16) + (lsw >> 16)) << 16) | (lsw & 0xFFFF)
}
const bitRotateLeft = (num, cnt) => (num << cnt) | (num >>> (32 - cnt))
const md5cmn = (q, a, b, x, s, t) => safeAdd(bitRotateLeft(safeAdd(safeAdd(a, q), safeAdd(x, t)), s), b),
md5ff = (a, b, c, d, x, s, t) => md5cmn((b & c) | ((~b) & d), a, b, x, s, t),
md5gg = (a, b, c, d, x, s, t) => md5cmn((b & d) | (c & (~d)), a, b, x, s, t),
md5hh = (a, b, c, d, x, s, t) => md5cmn(b ^ c ^ d, a, b, x, s, t),
md5ii = (a, b, c, d, x, s, t) => md5cmn(c ^ (b | (~d)), a, b, x, s, t)
const firstChunk = (chunks, x, i) => {
let [a, b, c, d] = chunks;
a = md5ff(a, b, c, d, x[i + 0], 7, -680876936)
d = md5ff(d, a, b, c, x[i + 1], 12, -389564586)
c = md5ff(c, d, a, b, x[i + 2], 17, 606105819)
b = md5ff(b, c, d, a, x[i + 3], 22, -1044525330)
a = md5ff(a, b, c, d, x[i + 4], 7, -176418897)
d = md5ff(d, a, b, c, x[i + 5], 12, 1200080426)
c = md5ff(c, d, a, b, x[i + 6], 17, -1473231341)
b = md5ff(b, c, d, a, x[i + 7], 22, -45705983)
a = md5ff(a, b, c, d, x[i + 8], 7, 1770035416)
d = md5ff(d, a, b, c, x[i + 9], 12, -1958414417)
c = md5ff(c, d, a, b, x[i + 10], 17, -42063)
b = md5ff(b, c, d, a, x[i + 11], 22, -1990404162)
a = md5ff(a, b, c, d, x[i + 12], 7, 1804603682)
d = md5ff(d, a, b, c, x[i + 13], 12, -40341101)
c = md5ff(c, d, a, b, x[i + 14], 17, -1502002290)
b = md5ff(b, c, d, a, x[i + 15], 22, 1236535329)
return [a, b, c, d]
},
secondChunk = (chunks, x, i) => {
let [a, b, c, d] = chunks;
a = md5gg(a, b, c, d, x[i + 1], 5, -165796510)
d = md5gg(d, a, b, c, x[i + 6], 9, -1069501632)
c = md5gg(c, d, a, b, x[i + 11], 14, 643717713)
b = md5gg(b, c, d, a, x[i], 20, -373897302)
a = md5gg(a, b, c, d, x[i + 5], 5, -701558691)
d = md5gg(d, a, b, c, x[i + 10], 9, 38016083)
c = md5gg(c, d, a, b, x[i + 15], 14, -660478335)
b = md5gg(b, c, d, a, x[i + 4], 20, -405537848)
a = md5gg(a, b, c, d, x[i + 9], 5, 568446438)
d = md5gg(d, a, b, c, x[i + 14], 9, -1019803690)
c = md5gg(c, d, a, b, x[i + 3], 14, -187363961)
b = md5gg(b, c, d, a, x[i + 8], 20, 1163531501)
a = md5gg(a, b, c, d, x[i + 13], 5, -1444681467)
d = md5gg(d, a, b, c, x[i + 2], 9, -51403784)
c = md5gg(c, d, a, b, x[i + 7], 14, 1735328473)
b = md5gg(b, c, d, a, x[i + 12], 20, -1926607734)
return [a, b, c, d]
},
thirdChunk = (chunks, x, i) => {
let [a, b, c, d] = chunks;
a = md5hh(a, b, c, d, x[i + 5], 4, -378558)
d = md5hh(d, a, b, c, x[i + 8], 11, -2022574463)
c = md5hh(c, d, a, b, x[i + 11], 16, 1839030562)
b = md5hh(b, c, d, a, x[i + 14], 23, -35309556)
a = md5hh(a, b, c, d, x[i + 1], 4, -1530992060)
d = md5hh(d, a, b, c, x[i + 4], 11, 1272893353)
c = md5hh(c, d, a, b, x[i + 7], 16, -155497632)
b = md5hh(b, c, d, a, x[i + 10], 23, -1094730640)
a = md5hh(a, b, c, d, x[i + 13], 4, 681279174)
d = md5hh(d, a, b, c, x[i], 11, -358537222)
c = md5hh(c, d, a, b, x[i + 3], 16, -722521979)
b = md5hh(b, c, d, a, x[i + 6], 23, 76029189)
a = md5hh(a, b, c, d, x[i + 9], 4, -640364487)
d = md5hh(d, a, b, c, x[i + 12], 11, -421815835)
c = md5hh(c, d, a, b, x[i + 15], 16, 530742520)
b = md5hh(b, c, d, a, x[i + 2], 23, -995338651)
return [a, b, c, d]
},
fourthChunk = (chunks, x, i) => {
let [a, b, c, d] = chunks;
a = md5ii(a, b, c, d, x[i], 6, -198630844)
d = md5ii(d, a, b, c, x[i + 7], 10, 1126891415)
c = md5ii(c, d, a, b, x[i + 14], 15, -1416354905)
b = md5ii(b, c, d, a, x[i + 5], 21, -57434055)
a = md5ii(a, b, c, d, x[i + 12], 6, 1700485571)
d = md5ii(d, a, b, c, x[i + 3], 10, -1894986606)
c = md5ii(c, d, a, b, x[i + 10], 15, -1051523)
b = md5ii(b, c, d, a, x[i + 1], 21, -2054922799)
a = md5ii(a, b, c, d, x[i + 8], 6, 1873313359)
d = md5ii(d, a, b, c, x[i + 15], 10, -30611744)
c = md5ii(c, d, a, b, x[i + 6], 15, -1560198380)
b = md5ii(b, c, d, a, x[i + 13], 21, 1309151649)
a = md5ii(a, b, c, d, x[i + 4], 6, -145523070)
d = md5ii(d, a, b, c, x[i + 11], 10, -1120210379)
c = md5ii(c, d, a, b, x[i + 2], 15, 718787259)
b = md5ii(b, c, d, a, x[i + 9], 21, -343485551)
return [a, b, c, d]
}
const binlMD5 = (x, len) => {
/* append padding */
x[len >> 5] |= 0x80 << (len % 32)
x[(((len + 64) >>> 9) << 4) + 14] = len;
let commands = [firstChunk, secondChunk, thirdChunk, fourthChunk],
initialChunks = [
1732584193,
-271733879,
-1732584194,
271733878
];
return Array.from({ length: Math.floor(x.length / 16) + 1 }, (v, i) => i * 16)
.reduce((chunks, i) => commands
.reduce((newChunks, apply) => apply(newChunks, x, i), chunks.slice())
.map((chunk, index) => safeAdd(chunk, chunks[index])), initialChunks)
}
const binl2rstr = input => Array(input.length * 4).fill(8).reduce((output, k, i) => output + String.fromCharCode((input[(i * k) >> 5] >>> ((i * k) % 32)) & 0xFF), '')
const rstr2binl = input => Array.from(input).map(i => i.charCodeAt(0)).reduce((output, cc, i) => {
let resp = output.slice()
resp[(i * 8) >> 5] |= (cc & 0xFF) << ((i * 8) % 32)
return resp
}, [])
const rstrMD5 = string => binl2rstr(binlMD5(rstr2binl(string), string.length * 8))
const rstr2hex = input => {
const hexTab = (pos) => '0123456789abcdef'.charAt(pos);
return Array.from(input).map(c => c.charCodeAt(0)).reduce((output, x, i) => output + hexTab((x >>> 4) & 0x0F) + hexTab(x & 0x0F), '')
}
const str2rstrUTF8 = unicodeString => {
if (typeof unicodeString !== 'string') throw new TypeError('parameter ‘unicodeString’ is not a string');
const cc = c => c.charCodeAt(0);
return unicodeString
.replace(/[\u0080-\u07ff]/g, // U+0080 - U+07FF => 2 bytes 110yyyyy, 10zzzzzz
c => String.fromCharCode(0xc0 | cc(c) >> 6, 0x80 | cc(c) & 0x3f))
.replace(/[\u0800-\uffff]/g, // U+0800 - U+FFFF => 3 bytes 1110xxxx, 10yyyyyy, 10zzzzzz
c => String.fromCharCode(0xe0 | cc(c) >> 12, 0x80 | cc(c) >> 6 & 0x3F, 0x80 | cc(c) & 0x3f))
}
const rawMD5 = s => rstrMD5(str2rstrUTF8(s))
const hexMD5 = s => rstr2hex(rawMD5(s))
return hexMD5(string)
}
// 时间
getRefreshDate() {
var myDate = new Date()
var M = myDate.getMonth(); // 月
M += 1
var mStr = `${M}`
if (M < 10) {
mStr = `0${M}`
}
var D = myDate.getDate(); //日
var dStr = `${D}`
if (D < 10) {
dStr = `0${D}`
}
var H = myDate.getHours(); //时
var hStr = `${H}`
if (H < 10) {
hStr = `0${H}`
}
var mm = myDate.getMinutes(); //分
var mmStr = `${mm}`
if (mm < 10) {
mmStr = `0${mm}`
}
var refreshTime = `${mStr}月${dStr}日 ${hStr}:${mmStr}`
return refreshTime
}
// 日志打印
debugLog(message, isDebug = true) {
if (isDebug) {
log(message)
}
}
//--------UI操作类--------
// 字体
_Font(size) {
var font = new Font("PingFang SC", size)
return font
}
_Color(hex, alpha = 1) {
let _color = new Color(hex, alpha)
return _color
}
// 弹出一个通知
async notify(title, body = "") {
let notif = new Notification()
notif.title = title
notif.body = body
notif.sound = "accept"
return await notif.schedule()
}
// 给图片加一层半透明遮罩
async shadowImage(img, color = '#000000', opacity = 0.7) {
let ctx = new DrawContext()
// 获取图片的尺寸
ctx.size = img.size
ctx.drawImageInRect(img, new Rect(0, 0, img.size['width'], img.size['height']))
ctx.setFillColor(new Color(color, opacity))
ctx.fillRect(new Rect(0, 0, img.size['width'], img.size['height']))
return ctx.getImage()
}
addClearSpace(stack, w, h) {
let spaceStack = stack.addStack()
spaceStack.size = new Size(w, h)
return spaceStack
}
testShowAlert(message) {
const alert = new Alert()
alert.title = "提示"
alert.message = message
alert.addAction("确定")
alert.presentAlert()
}
ifnull(s) {
if (s !== null && s !== 'undefind' && s !== '') {
return s
} else {
return ""
}
}
}
// 运行环境
// @running.start
const Running = async (Widget, default_args = '') => {
let M = null
// 判断hash是否和当前设备匹配
if (config.runsInWidget) {
M = new Widget(args.widgetParameter || '')
const W = await M.render()
Script.setWidget(W)
Script.complete()
} else if (config.runsWithSiri) {
M = new Widget(args.shortcutParameter || '')
const data = await M.siriShortcutData()
Script.setShortcutOutput(data)
} else {
let { act, data, __arg, __size } = args.queryParameters
M = new Widget(__arg || default_args || '')
if (__size) M.init(__size)
if (!act || !M['_actions']) {
// 弹出选择菜单
const actions = M['_actions']
const _actions = []
const alert = new Alert()
alert.title = M.name
alert.message = M.desc
for (let _ in actions) {
alert.addAction(_)
_actions.push(actions[_])
}
alert.addCancelAction('取消操作')
const idx = await alert.presentSheet()
if (_actions[idx]) {
const func = _actions[idx]
await func()
}
return
}
let _tmp = act.split('-').map(_ => _[0].toUpperCase() + _.substr(1)).join('')
let _act = `action${_tmp}`
if (M[_act] && typeof M[_act] === 'function') {
const func = M[_act].bind(M)
await func(data)
}
}
}
class Widget extends Base {
constructor(arg) {
super(arg)
this.styleType = arg
this.name = '凯美瑞专用小组件'
this.desc = '丰田凯美瑞 车辆桌面组件展示'
this.cookie = ''
this.userId = ''
this.phone = ''
this.ak = ''
if (config.runsInApp) {
// 1.获取cookie和userID
if (this.userInfo.length == 0) {
// 2.如果没有获取到,弹出Alert让其输入
this.registerAction('获取用户信息', this.actionUserInfo)
} else {
let _userInfo = this.userInfo.split(" ")
this.cookie = `${_userInfo[0]}`
this.userId = `${_userInfo[1]}`
this.phone = `${_userInfo[2]}`
this.ak = `${_userInfo[3]}`
this.NetworkingAction(this.cookie, this.userId, this.phone, this.ak)
}
this.registerAction('移除用户信息', this.removeUserInfo)
this.registerAction('抓包教程', this.captureData)
this.registerAction("检查更新", this.checkUpdate)
this.registerAction('当前版本: v' + kCurrentVersion, () => {})
} else if (config.runsInWidget) {
let _userInfo = this.userInfo.split(" ")
this.cookie = `${_userInfo[0]}`
this.userId = `${_userInfo[1]}`
this.phone = `${_userInfo[2]}`
this.ak = `${_userInfo[3]}`
if (this.cookie.length > 0 && this.userId.length > 0 && this.phone.length > 0 && this.ak.length > 0) {
this.NetworkingAction(this.cookie, this.userId, this.phone, this.ak)
}
}
}
/**
* 渲染函数,函数名固定
* 可以根据 this.widgetFamily 来判断小组件尺寸,以返回不同大小的内容
*/
async render() {
let data = await this.getData()
const screenSize = Device.screenSize()
const size = kDeviceSize[`${screenSize.width}x${screenSize.height}`] || DEVICE_SIZE['428x926']
if (data) {
if (typeof data === 'object') {
switch (this.widgetFamily) {
case 'large': {
return await this.renderLarge(size.large, data)
}
case 'medium': {
return await this.renderMedium(size.medium, data)
}
default: {
return await this.renderSmall(size.small, data)
}
}
} else {
// 返回组件错误信息
return await this.renderError()
}
} else {
return await this.renderEmpty()
}
}
// -------------组件渲染--------------
/**
* 渲染小尺寸组件
*/
async renderSmall(size, data) {
let widget = new ListWidget()
widget.setPadding(5, 5, 5, 5)
let gradient = new LinearGradient()
gradient.locations = [0, 1]
gradient.colors = [new Color("#f5f8fd"), new Color("#b7d3e3")]
widget.backgroundGradient = gradient
let bgStack = widget.addStack()
bgStack.size = new Size(size.width - 20, size.height - 15)
bgStack.layoutVertically()
// bgStack.backgroundColor = Color.red()
// 标题
var titleStack = bgStack.addStack()
titleStack.layoutHorizontally()
// titleStack.backgroundColor = Color.blue()
let title = titleStack.addText(`CAMRY ${data.vhcGradeCode}`)
title.lineLimit = 1
title.font = Font.italicSystemFont(20)
title.textColor = Color.black()
// 剩余油量
let isLowFuel = data.fuelPro <= 30
var fuelProStr = `油量: ${data.fuelPro}%`
var fuelProTitle = bgStack.addText(fuelProStr)
fuelProTitle.font = isLowFuel ? Font.italicSystemFont(15) : this._Font(15)
fuelProTitle.textColor = isLowFuel ? Color.red() : Color.black()
// 续航
var mileageVehStr = `续航: ${data.mileageVeh}km`
var mileageVehTitle = bgStack.addText(mileageVehStr)
mileageVehTitle.font = this._Font(15)
mileageVehTitle.textColor = Color.black()
// 更新时间
var refreshDate = `更新时间: ${data.refreshDate}`
var refreshDateTie = bgStack.addText(refreshDate)
refreshDateTie.font = this._Font(11)
refreshDateTie.textColor = Color.black()
// 地址信息
var carAddress = `${data.address}`
var carAddressTitle = bgStack.addText(`实时位置: ${carAddress}`)
carAddressTitle.font = this._Font(10)
carAddressTitle.lineLimit = 2
carAddressTitle.textColor = Color.black()
// 门窗信息,采用通知的形式
let warning = data.warningMsg.length > 0
if (warning) {
this.notify("门窗消息提醒", `${data.warningMsg}`)
}
return widget
}
/**
* 渲染中尺寸组件
*/
async renderMedium(size, data) {
var widget = new ListWidget()
// widget.backgroundColor = this._Color("#7dccbf", 0.5)
let nextRefresh = Date.now() + 1000 * 30 // add 30 second to now
widget.refreshAfterDate = new Date(nextRefresh)
// 背景Stack, 用于整体布局
let bgStack = widget.addStack()
bgStack.layoutHorizontally()
bgStack.backgroundColor = this._Color("#7db2cc", 1)
bgStack.size = new Size(size.width, size.height)
// 左
let leftBgStack = bgStack.addStack()
leftBgStack.size = new Size(bgStack.size.width / 2, bgStack.size.height)
leftBgStack.backgroundColor = this._Color("#8acc7d", 1)
let leftSpaceStack = this.addClearSpace(leftBgStack, 10, leftBgStack.size.height)
// 文本
var textContentStack = leftBgStack.addStack()
textContentStack.layoutVertically()
textContentStack.size = new Size(leftBgStack.size.width - leftSpaceStack.size.width, leftBgStack.size.height)
textContentStack.backgroundColor = this._Color("#ececec", 1)
textContentStack.borderWidth = 1
textContentStack.borderColor = Color.black()
// 定制化Title
let titleString = `${data.carNumber}`
let title = textContentStack.addText(titleString)
title.font = Font.italicSystemFont(17)
title.textColor = Color.black()
// 车架号
//let vinString = `${data.vin}`
//let vin = textContentStack.addText(vinString)
//vin.font = Font.italicSystemFont(8)
//vin.textColor = Color.blue()
// todo 油价展示
// 剩余油量
let isLowFuel = data.fuelPro <= 30
var fuelProStr = `⛽️油量:${data.fuelPro}%`
var fuelProTitle = textContentStack.addText(fuelProStr)
fuelProTitle.font = isLowFuel ? Font.italicSystemFont(14) : this._Font(14)
fuelProTitle.textColor = isLowFuel ? Color.red() : Color.black()
// 续航
var mileageVehStr = `🚀续航:${data.mileageVeh}km`
var mileageVehTitle = textContentStack.addText(mileageVehStr)
mileageVehTitle.font = this._Font(14)
mileageVehTitle.textColor = Color.black()
// 油耗
var fuelWearAvgStr = `油耗:${data.fuelWearAvg}(加油后${data.fuelFilledWearAvg})`
var fuelWearAvgTitle = textContentStack.addText(fuelWearAvgStr)
fuelWearAvgTitle.font = this._Font(10)
fuelWearAvgTitle.textColor = this._Color("#aca8ea",1)
// 总里程
var mileageTotalStr = `里程:${data.mileageTotal}km`
var mileageTotalTitle = textContentStack.addText(mileageTotalStr)
mileageTotalTitle.font = this._Font(10)
mileageTotalTitle.textColor = this._Color("#87acce",1)
// 里程计数器
var mileageCounterStr = `计数:A:${data.mileageCounterA}/B:${data.mileageCounterB}`
var mileageCounterTitle = textContentStack.addText(mileageCounterStr)
mileageCounterTitle.font = this._Font(10)
mileageCounterTitle.textColor = this._Color("#62b0b2",1)
// 更新时间
var refreshDate = `时间:${data.refreshDate}`
var refreshDateTie = textContentStack.addText(refreshDate)
refreshDateTie.font = this._Font(10)
refreshDateTie.textColor = this._Color("#3eb496",1)
// 地址信息
var carAddress = `位置:${data.address}`
var carAddressTitle = textContentStack.addText(carAddress)
carAddressTitle.font = this._Font(10)
carAddressTitle.lineLimit = 2
carAddressTitle.textColor = this._Color("#22b781",1)
// 右
let rightContentStack = bgStack.addStack()
rightContentStack.size = new Size(bgStack.size.width - leftBgStack.size.width, bgStack.size.height)
var mapWidth = bgStack.size.width - leftBgStack.size.width + 100
var mapHeight = bgStack.size.height + 100
rightContentStack.backgroundImage = await this.RequestBDStaticPic(this.ak, data.longitude, data.latitude, new Size(mapWidth, mapHeight))
let rightGradientStack = rightContentStack.addStack()
rightGradientStack.layoutVertically()
rightGradientStack.size = rightContentStack.size
let gradient = new LinearGradient()
gradient.startPoint = new Point(1, 0)
gradient.endPoint = new Point(0, 1)
gradient.locations = [1, 0.75, 0.5, 0.25, 0]
gradient.colors = [this._Color("#ECECEC", 1), this._Color("#ECECEC", 1),
this._Color("#ECECEC", 0.5), this._Color("#ECECEC", 0.25),
this._Color("#ECECEC", 0)]
rightGradientStack.backgroundGradient = gradient
// LOGO
let logoContentStack = rightGradientStack.addStack()
logoContentStack.layoutHorizontally()
logoContentStack.size = new Size(rightGradientStack.size.width - 15, 20)
logoContentStack.borderWidth = 1
logoContentStack.borderColor = Color.blue()
let logoData = await this.loadImage(kToyotaIconImage)
// 空白行
this.addClearSpace(logoContentStack, logoContentStack.size.width - 80, 5)
let logoStack = logoContentStack.addStack()
logoStack.size = new Size(logoData.size.width, 20)
let logoImage = logoStack.addImage(logoData)
logoImage.size = logoStack.size
logoImage.rightAlignImage()
logoStack.borderWidth = 1
logoStack.borderColor = Color.red()
// Car
var carContentStack = rightGradientStack.addStack()
carContentStack.size = new Size(rightContentStack.size.width, rightContentStack.size.height - logoContentStack.size.height - 50)
carContentStack.borderWidth = 1
carContentStack.borderColor = Color.black()
// 空白行
this.addClearSpace(carContentStack, carContentStack.size.width - 80, 20)
var carStack = carContentStack.addStack()
carStack.size = new Size(rightContentStack.size.width - 90, rightContentStack.size.height - logoContentStack.size.height - 80)
var carImage = carStack.addImage(await this.loadImage(data.modelImage))
carImage.imageSize = carStack.size
carImage.rightAlignImage()
carStack.borderWidth = 1
carStack.borderColor = Color.black()
// 门窗信息
var infoStack = rightGradientStack.addStack()
infoStack.size = new Size(rightContentStack.size.width - 15, 15)
var doorInfo = data.warningMsg.length > 0 ? `${warningMsg}` : "门窗已关闭"
var warningMsgTitle = infoStack.addText(`${doorInfo}`)
warningMsgTitle.font = this._Font(10)
warningMsgTitle.textColor = Color.black()
infoStack.backgroundColor = this._Color("#a7a7a7", 0.3)
// 健康报告
var healthStack = rightGradientStack.addStack()
healthStack.size = new Size(rightContentStack.size.width - 15, 30)
var health = healthStack.addText(`当月行驶里程:${data.totalMilage} 天数:${data.totalDrivingDays} 次数:${data.totalDrivingTimes} 时间:${data.totalDrivingDuration}`)
health.font = this._Font(10)
health.textColor = Color.black()
healthStack.borderWidth = 1
healthStack.borderColor = Color.black()
// 门窗信息,采用通知的形式
let warning = data.warningMsg.length > 0
if (warning) {
this.notify("门窗消息提醒", `${data.warningMsg}`)
}
return widget
}
/**
* 渲染大尺寸组件
*/
async renderLarge(size, data) {
let widget = new ListWidget()
widget.backgroundImage = await this.shadowImage(await this.loadImage(kEmptyDataImage))
// TODO
let text = widget.addText('大尺寸组件正在开发,敬请期待')
text.font = this._Font(18)
text.textColor = Color.white()
return widget
}
/**
* 渲染空数据组件
*/
async renderEmpty() {
const widget = new ListWidget()
widget.backgroundImage = await this.shadowImage(await this.loadImage(kEmptyDataImage))
const text = widget.addText('欢迎使用 Camry 小组件')
switch (this.widgetFamily) {
case 'large':
text.font = Font.blackSystemFont(18)
break
case 'medium':
text.font = Font.blackSystemFont(18)
break
case 'small':
text.font = Font.blackSystemFont(12)
break
}
text.centerAlignText()
text.textColor = Color.white()
return widget
}
/**
* 渲染错误信息
*/
async renderError() {
const widget = new ListWidget()
widget.backgroundImage = await this.shadowImage(await this.loadImage(kEmptyDataImage))
const text = widget.addText("数据有误,请重新输入的用户信息")
switch (this.widgetFamily) {
case 'large':
text.font = Font.blackSystemFont(18)
break
case 'medium':
text.font = Font.blackSystemFont(18)
break
case 'small':
text.font = Font.blackSystemFont(12)
break
}
text.textColor = Color.red()
text.centerAlignText()
return widget
}
// -------------组件结束--------------
// 获取Cookie和UserID
async actionUserInfo() {
const alert = new Alert()
alert.title = '组件声明'
alert.message = `
1.小组件需要使用到您的丰云行应用的账号,首次使用需要用到丰云行的令牌和用户信息,但是无法无法直接从该应用获取\n\r
2.小组件不会收集您的个人账户信息,所有账号信息将存在 iCloud 或者 iPhone 上但也请您妥善保管自己的账号\n\r
3.小组件是开源、并且完全免费的,由凯美瑞车主开发,所有责任与广汽丰田公司无关\n\r
开发者: GhostClock\n\r
`
alert.addAction('同意')
alert.addCancelAction('不同意')
const id = await alert.presentAlert()
if (id === -1) return
await this._userInfo()
}
async _userInfo() {
log("_userInfo")
var alert = new Alert()
alert.title = "登录数据"
alert.addTextField("请粘贴您抓取的数据", "")
alert.addTextField("请输入百度地图的AK", "")
alert.addAction("确定")
alert.addCancelAction("取消")
const id = await alert.presentAlert()
if (id === -1) return
let userInfo = alert.textFieldValue(0)
let ak = alert.textFieldValue(1)
try {
let jsonData = JSON.parse(userInfo)
// 解析数据
var cookie = jsonData["body"]["jwt"]
var userId = jsonData["body"]["rData"]["userId"]
var phone = jsonData["body"]["rData"]["telPhone"]
log(userId + "-" + phone)
// 本地保存数据
let cacheUserInfo = cookie + " " + userId + " " + phone + " " + ak
this.setUserInfo(cacheUserInfo)
// 开始执行本地逻辑
this.NetworkingAction(cookie, userId, phone, ak)
this.notify('设置成功', '桌面组件稍后将自动刷新')
} catch (error) {
log(error)
this.testShowAlert("请输入正确的数据")
}
}
async getData() {
// 判断用户是否已经登录
return (Keychain.contains(kUserInfoKey) && this.userInfo.length > 0) ? await this.bootstrap() : false
}
async bootstrap() {
return await this.NetworkingAction(this.cookie, this.userId, this.phone, this.ak)
}
async NetworkingAction(cookie, userId, phone, ak) {
var carInfoData = {
address: "", // 地址
registNo: "", // 地址
vhcName: "", // 名称
modelImage: "", // 图片
mileageTotal: "", // 总行程数
mileageVeh: "", // 续航
fuelPro: "", // 剩余油量
vin: "", // 车架号
longitude: "", //经度
latitude: "", // 维度
warningMsg: "", //警告信息
resultCode: "",
vhcGradeCode: "", // 汽车型号
mileageCounterA: "",//里程计数器A
mileageCounterB: "",//里程计数器B
fuelWearAvg: "",//平均油耗
fuelFilledWearAvg: "",//加油后平均油耗
carNickname: "",//昵称
carNumber: "",//车牌
model: "",//型号
maxSpeed: "",//最高车速
totalMilage: "",//当月行驶里程
totalDrivingDays: "",//行驶天数
totalDrivingDuration: "",//行驶时间
totalDrivingTimes: "",//行驶次数
refreshDate: this.getRefreshDate() //刷新时间
}
// 1.必须先获取车架号
let vinInfo = await this.RequestCarVinInfo(cookie, userId)
if (typeof vinInfo != 'object') {
this.removeUserInfo()
this.notify("提示", "所有用户信息都已经清除")
return vinInfo
}
log(vinInfo)
let vin = vinInfo.vin
carInfoData.vin = vin
carInfoData.vhcGradeCode = vinInfo.vhcGradeCode
carInfoData.carNickname = vinInfo.carNickname
carInfoData.carNumber = vinInfo.carNumber
carInfoData.model = vinInfo.model
this.debugLog(`车架号: ${carInfoData.vin}`)
// 2.获取汽车位置
let positionData = await this.RequestCarPosition(cookie, phone, userId, carInfoData.vin, ak)
carInfoData.address = positionData.address
carInfoData.longitude = positionData.longitude
carInfoData.latitude = positionData.latitude
this.debugLog(`汽车地址: ${carInfoData.address} ${carInfoData.longitude}-${carInfoData.latitude}`)
// 3.获取当前车辆信息(油耗,里程等)
let currentInfo = await this.RequestCarCurrentInfoNew(phone, cookie, userId, carInfoData.vin)
carInfoData.mileageTotal = currentInfo.mileageTotal
carInfoData.fuelWearAvg = currentInfo.fuelWearAvg
carInfoData.fuelFilledWearAvg = currentInfo.fuelFilledWearAvg
carInfoData.mileageCounterA = currentInfo.mileageCounterA
carInfoData.mileageCounterB = currentInfo.mileageCounterB
let conditionInfo = await this.RequestCarConditionInfoNew(phone, cookie, userId, carInfoData.vin)
carInfoData.fuelPro = conditionInfo.fuelPro
carInfoData.mileageVeh = conditionInfo.mileageVeh
this.debugLog(`剩余油量: ${carInfoData.fuelPro}`)
// 4.获取当前车辆信息(名称,图片,车牌号等)
let vhcNames = await this.CarCurrentVHCInfo(phone, userId, cookie, carInfoData.vin)
carInfoData.vhcName = vhcNames.vhcName.slice(2)
carInfoData.modelImage = vhcNames.modelImage
carInfoData.registNo = vhcNames.registNo
this.debugLog(`汽车名称: ${carInfoData.vhcName}`)
// 4.1获取车辆健康报告
let health = await this.RequestCarHealthReportByMonth(cookie, phone, userId, vin)
carInfoData.totalMilage = health.totalMilage
carInfoData.maxSpeed = health.maxSpeed
carInfoData.totalDrivingDays = health.totalDrivingDays
carInfoData.totalDrivingDuration = health.totalDrivingDuration
carInfoData.totalDrivingTimes = health.totalDrivingTimes
this.debugLog(`当月里程: ${carInfoData.totalMilage}`)
// 5.获取门窗警告信息
let warningInfo = await this.CarWarningMsg(phone, userId, cookie, carInfoData.vin)
log(warningInfo)
carInfoData.warningMsg = warningInfo.warningMsg
var doorInfo = carInfoData.warningMsg.length > 0 ? `${carInfoData.warningMsg}` : "门窗已关闭"
this.debugLog(`门窗信息: ${doorInfo}`)
return carInfoData
}
//-----网络请求-----
// 获取车架号信息
async RequestCarVinInfo(cookie, userId) {
let body = `appVersion=${appVersion}&fyxDevice=2&userId=${userId}&versionType=1230`
let stringData = await this.PostRequest(CarVinInfoURL, kContentTypeUrlencoded, body, cookie)
let jsonData = JSON.parse(stringData)
let resultCode = jsonData.resultCode
if (resultCode != "1") {
// Cookie过期
log(`Cookie已经过期, errorCode: ${resultCode}`)
return resultCode
}
let row = jsonData.data.rows[0]
let _vin = row.vin
let _vhcGradeCode = row.vhcGradeCode
let _carNickname = row.carNickname
let _carNumber = row.carNumber
let _carId = row.carId
let _model_id = "CMY-8"
let _model = row.model
_vhcGradeCode = _vhcGradeCode.split(" ")[1]
return { vin: _vin, vhcGradeCode: _vhcGradeCode, carNickname: _carNickname, carNumber: _carNumber, carId: _carId, model: _model}
}
// 获取汽车经纬度信息 -> 地理反编码
async RequestCarPosition(cookie, phone, userId, vin, ak) {
var body = {
"phone": phone,
"vin": vin,
"userId": userId,
}
let data = await this.PostRequest(CarPositionInfoURL, kContentTypeJson, body, cookie)
var longitude = data["data"]["longitude"]
var latitude = data["data"]["latitude"]
log(longitude + " " + latitude)
// 地理反编码
var url = CarPositionAddressURL(ak, latitude, longitude)
let result = await this.GetRequest(url, kContentTypeJson, '')
var addressComponent = result["result"]["addressComponent"]
let city = addressComponent.city
var district = addressComponent["district"]
var street = addressComponent["street"]
var _address = street
return { address: `${city}${district}${_address}`, longitude: `${longitude}`, latitude: `${latitude}` }
}
// 请求百度静态图片
async RequestBDStaticPic(ak, longitude, latitude, size) {
let staticPicUrl = BaiduStaticPicURL(ak, longitude, latitude, size)
return await this.loadImage(staticPicUrl)
}
// 获取当前车辆信息(油耗,里程等)
async RequestCarCurrentInfo(phone, cookie, userId, vin) {
var url = CarCurrentInfoURL(phone, userId, vin)
let jsonData = await this.GetRequest(url, kContentTypeJson, cookie)
/*
"fuelPro": 25.0, 剩余油量
"mileageVeh": 123.0, 续航
"mileageTotal": 923.0, 总行程数
"mileageCounterA": 923.8, 行程A
"mileageCounterB": 923.8, 行程B
"fuelWearAvg": 10.0, 加满油油耗
"fuelFilledWearAvg": 9.2, 当前油耗
*/
var data = jsonData["data"]
var _mileageTotal = data["mileageTotal"]
var _mileageVeh = data["mileageVeh"]
var _fuelPro = data["fuelPro"]
return {
mileageTotal: _mileageTotal,
mileageVeh: _mileageVeh,
fuelPro: _fuelPro
}
}
// 获取当前车辆信息(油耗,里程等)
async RequestCarCurrentInfoNew(phone, cookie, userId, vin) {
var url = CarCurrentInfoURLNew(phone, userId, vin)
var body = {
"phone": phone,
"fyxDevice": "2",
"appVersion": this.appVersion,
"vin": vin,
"userId": userId,
"carCode": "5",
"type": ""
}
let jsonData = await this.PostRequest(url, kContentTypeJson, body, cookie)
/*
"fuelPro": 25.0, 剩余油量
"mileageVeh": 123.0, 续航
"mileageTotal": 923.0, 总行程数
"mileageCounterA": 923.8, 行程A
"mileageCounterB": 923.8, 行程B
"fuelWearAvg": 10.0, 加满油油耗
"fuelFilledWearAvg": 9.2, 当前油耗
*/
log(jsonData)
// 剩余油量
var _fuelPro = jsonData["data"]["itemList"][0].configData
// 总行程里数
var _mileageTotal = jsonData["data"]["itemList"][1].configData
// 里程计数器A
var _mileageCounterA = jsonData["data"]["itemList"][2].configData
// 里程计数器B
var _mileageCounterB = jsonData["data"]["itemList"][3].configData
// 平均油耗
var _fuelWearAvg = jsonData["data"]["itemList"][4].configData
// 加油后平均油耗
var _fuelFilledWearAvg = jsonData["data"]["itemList"][5].configData
return {
fuelPro: _fuelPro,
mileageTotal: _mileageTotal,
mileageCounterA: _mileageCounterA,
mileageCounterB: _mileageCounterB,
fuelWearAvg: _fuelWearAvg,
fuelFilledWearAvg: _fuelFilledWearAvg
}
}
async RequestCarConditionInfoNew(phone, cookie, userId, vin) {
var body = {
"phone": phone,
"fyxDevice": "2",
"appVersion": this.appVersion,
"vin": vin,
"userId": userId,
"carCode": "5",
"type": ""
}
let jsonData = await this.PostRequest(CarConditionInfoURL, kContentTypeJson, body, cookie)
log(jsonData)
var fuelPro = jsonData["data"].fuelPro
var mileageVeh = jsonData["data"].mileageVeh
return {
fuelPro: fuelPro,
mileageVeh: mileageVeh
}
}
// 获取当月运行数据
async RequestCarHealthReportByMonth(cookie, phone, userId, vin) {
let now = new Date()
let Year = now.getFullYear()
let Month = now.getMonth()
Month += 1
let yearStr = `${Year}`
let monthStr = `${Month}`
let body = {
"phone": phone,
"fyxDevice": "2",
"appVersion": this.appVersion,
"vin": vin,
"userId": userId,
"carCode": "5",
"type": "",
"year": yearStr,
"month": monthStr
}
log("赶紧先看看这请求对不对啊!"+body)
let jsonData = await this.PostRequest(CarHealthReportByMonthUrl, kContentTypeJson, body, cookie)
log(jsonData)
let totalMilage = this.ifnull(jsonData["data"].totalMilage)
let maxSpeed = this.ifnull(jsonData["data"].maxSpeed)
let totalDrivingDays = this.ifnull(jsonData["data"].totalDrivingDays)
let totalDrivingDuration = this.ifnull(jsonData["data"].totalDrivingDuration)
let totalDrivingTimes = this.ifnull(jsonData["data"].totalDrivingTimes)
return {
totalMilage: totalMilage,
maxSpeed: maxSpeed,
totalDrivingDays: totalDrivingDays,
totalDrivingDuration: totalDrivingDuration,
totalDrivingTimes: totalDrivingTimes
}
}
// 获取当前车辆信息(名称,图片,车牌号等)
async CarCurrentVHCInfo(phone, userId, cookie, vin) {
var url = CarCurrentVHCInfoURL(phone, userId, vin)
var contentType = `${kContentTypeJson};charset=UTF-8`
let jsonData = await this.GetRequest(url, contentType, cookie)
var data = jsonData["data"]
/*
"registNo": "粤Bxxxxx", 车牌号
"vhcName": "全新第八代凯美瑞", 名称
"modelCode": "MXV",
"modelImage": "https://carappvideo.gtmc.com.cn/fs01/IFImage/carnetImage/img-819-PT-ZZB-089.png", // 图片
*/
var _registNo = data["registNo"]
var _vhcName = data["vhcName"]
var _modelImage = data["modelImage"]
log(_modelImage)
return {
registNo: _registNo,
vhcName: _vhcName,
modelImage: _modelImage
}
}
// 获取车辆门窗信息
async CarWarningMsg(phone, userId, cookie, vin) {
let url = CarWarningMsgURL(phone, userId, vin)
let result = await this.GetRequest(url, kContentTypeJson, cookie)
let warnTypes = result.data.warnTypes
let msg = ""
for (let index in warnTypes) {
let localItem = kWarningMsg[index]
let item = warnTypes[index]
if (item.warnType == localItem.warnType && item.existStatus != 1) {
msg += `${localItem.warnTypeName} `
}
}
log(msg)
return {
warningMsg: msg
}
}
//-----网络请求结束-----
// 手机抓包教程
async captureData() {
Safari.open('https://www.cnblogs.com/hong-fithing/p/12562448.html')
}
// 检查更新
async checkUpdateold() {
const request = new Request(kUpdateVersionURL)
const responseString = await request.loadJSON()
log(responseString)
let response = JSON.parse(responseString)
console.log(`远程版本:${response?.version}`)
if (response?.version === kCurrentVersion) return this.notify('无需更新', '远程版本一致,暂无更新')
console.log('发现新的版本')
const log = response?.changelog.join('\n')
const alert = new Alert()
alert.title = '更新提示'
alert.message = `是否需要升级到${response?.version.toString()}版本\n\r${log}`
alert.addAction('更新')
alert.addCancelAction('取消')
const id = await alert.presentAlert()
if (id === -1) return
await this.notify('正在更新中...')
const REMOTE_REQ = new Request(response?.download)
const REMOTE_RES = await REMOTE_REQ.load()
const UPDATE_FILE = 'camry.js'
const FILE_MGR = FileManager[module.filename.includes('Documents/iCloud~') ? 'iCloud' : 'local']()
FILE_MGR.write(FILE_MGR.joinPath(FILE_MGR.documentsDirectory(), UPDATE_FILE), REMOTE_RES)
await this.notify('桌面组件更新完毕!')
}
// 检查更新
async checkUpdate() {
log('检查更新日志!!!!!!!!!!!!!!')
let response = await this.GetRequest(kUpdateVersionURL, kContentTypeJson)
console.log(`远程版本:${response.version}`)
if (response?.version === kCurrentVersion) return this.notify('无需更新', '远程版本一致,暂无更新')
log('发现新的版本')
let logInfo = response.changelog.join('\n')
log(logInfo)
let alert = new Alert()
alert.title = '更新提示'
alert.message = `是否需要升级到${response.version}版本\n\r${logInfo}`
alert.addAction('更新')
alert.addCancelAction('取消')
let id = await alert.presentAlert()
if (id === -1) return
await this.notify('正在更新中...')
const REMOTE_REQ = new Request(response.download)
const REMOTE_RES = await REMOTE_REQ.load()
let UPDATE_FILE = 'camry.js'
let FILE_MGR = FileManager[module.filename.includes('Documents/iCloud~') ? 'iCloud' : 'local']()
FILE_MGR.write(FILE_MGR.joinPath(FILE_MGR.documentsDirectory(), UPDATE_FILE), REMOTE_RES)
await this.notify('桌面组件更新完毕!')
}
}
await Running(Widget)
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。