diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..c8b9c24 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,6 @@ +{ + "semi": true, + "singleQuote": true, + "tabWidth": 2, + "trailingComma": "es5" +} \ No newline at end of file diff --git a/README.md b/README.md index 2eccdc2..c3fe449 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ >首先安装 TF版的 [scriptable](https://testflight.apple.com/join/uN1vTqxk) -> 网页安装:(推荐:⭐️⭐️⭐️⭐️⭐️)[WebStore](https://scriptablejs.gitee.io/store/#/) +> 网页安装:(推荐:⭐️⭐️⭐️⭐️⭐️)[WebStore](https://dompling.github.io/store/#/menu) > 安装教程:[网页安装步骤](https://t.me/Scriptable_JS/101536) - 作者:@2Ya - 订阅安装包 diff --git a/Scripts/12123.js b/Scripts/12123.js new file mode 100644 index 0000000..3714c6c --- /dev/null +++ b/Scripts/12123.js @@ -0,0 +1,261 @@ +// Variables used by Scriptable. +// These must be at the very top of the file. Do not edit. +// icon-color: deep-gray; icon-glyph: car; + +// 添加require,是为了vscode中可以正确引入包,以获得自动补全等功能 +if (typeof require === 'undefined') require = importModule; +const { DmYY, Runing } = require('./DmYY'); + +const API_PARAMS = { + api4: 'biz.vio.detail.query', + infoURL: 'https://miniappcsfw.122.gov.cn:8443/openapi/invokeApi/business/biz', + api1: 'biz.vio.unhandledVioCount.query', + productId: 'p10000000000000000001', + alipay: 'alipays://platformapi/startapp?appId=2019050964403523', + api2: 'biz.vio.peccancyChannelList.query', + status: + 'alipays://platformapi/startapp?appId=2019050964403523&page=pages%2Flicense%2Flicense', + update: 'https://gitcode.net/4qiao/scriptable/raw/master/api/violation.js', + api3: 'biz.vio.peccancyUnhandleInfoList.query', + Ver: 'Version 1.2\n\nverifyToken过期需打开Quantumult-X', +}; + +// @组件代码开始 +class Widget extends DmYY { + constructor(arg) { + super(arg, { + lightBgColor: '#2581f2', + darkBgColor: '#2581f2', + darkColor: '#fff', + lightColor: '#fff', + }); + this.en = '12123'; + this.name = '交管 12123'; + config.runsInApp && + this.registerAction({ + icon: { name: 'paperplane', color: '#722ed1' }, + type: 'input', + title: 'Token', + desc: '微信小程序交管12123获取', + val: 'Token', + onClick: async () => { + const token = this.settings.token; + this.settings.token = + (await this.getCache('wx_12123', false)) || token; + if (this.settings.token) this.saveSettings(false); + return this.setAlertInput('Token', '设置 token', { + token: '微信小程序交管12123获取', + }); + }, + }); + + config.runsInApp && this.registerAction('基础设置', this.setWidgetConfig); + } + + format = (str) => { + return parseInt(str) >= 10 ? str : `0${str}`; + }; + + date = new Date(); + arrUpdateTime = [ + this.format(this.date.getMonth() + 1), + this.format(this.date.getDate()), + this.format(this.date.getHours()), + this.format(this.date.getMinutes()), + ]; + + dataSource = { + left: { + title: '川 G88888', + icon: 'car.fill', + listItem: [ + { label: '未处违法', value: `0`, unit: '条' }, + { label: '车辆状态', value: '正常' }, + { label: '上次更新', value: '00:00' }, + ], + }, + right: { + title: '驾驶证', + icon: 'creditcard.fill', + listItem: [ + { label: '证件状态', value: '正常' }, + { label: '累计扣分', value: `0`, unit: '分' }, + { label: '重置日期', value: '—' }, + ], + }, + }; + + init = async () => { + this.settings.token = + (await this.getCache('wx_12123', false)) || this.settings.token; + if (this.settings.dataSource) { + this.dataSource = this.settings.dataSource; + } else { + await this.cacheData(); + } + this.cacheData(); + }; + + cacheData = async () => { + try { + const token = this.settings.token.replace('params=', ''); + const body = JSON.parse(decodeURIComponent(token)); + const params = { + sign: body.sign, + // businessId: body.businessId, + verifyToken: body.verifyToken, + // businessPrincipalId: body.businessPrincipalId, + }; + + const response = await this.$request.post(API_PARAMS.infoURL, { + body: `params=${JSON.stringify({ + api: API_PARAMS.api1, + productId: API_PARAMS.productId, + ...params, + })}`, + }); + console.log(response); + if (response.success) { + const illegal = response.data.list[0] || {}; + this.dataSource.left.listItem[0].value = illegal.count || 0; + + const details = await this.$request.post(API_PARAMS.infoURL, { + body: `params=${encodeURIComponent( + JSON.stringify({ + api: 'biz.user.integration.query', + productId: API_PARAMS.productId, + ...params, + }) + )}`, + }); + + console.log(details); + + if (details.success) { + const { drivingLicense, vehicles } = details.data; + const reaccDate = drivingLicense.reaccDate.split('-'); + this.dataSource.right.title = `驾驶证 ${drivingLicense.allowToDrive}`; + this.dataSource.right.listItem[1].value = + drivingLicense.cumulativePoint; + this.dataSource.right.listItem[2].value = `${reaccDate[1]}-${reaccDate[2]}`; + + if (vehicles.length) { + this.dataSource.left.title = vehicles[0].plateNumber; + } + } + + this.dataSource.left.listItem[2].value = `${this.arrUpdateTime[2]}:${this.arrUpdateTime[3]}`; + + this.settings.dataSource = this.dataSource; + this.saveSettings(false); + } else { + this.notify( + `verifyToken已过期 ⚠️`, + '点击通知框自动跳转到支付宝小程序交管12123页面获取最新的Token ( 请确保已打开辅助工具 )', + API_PARAMS.alipay + ); + } + } catch (e) { + console.log(e); + } + }; + + renderImage = async (uri) => { + return this.$request.get(uri, 'IMG'); + }; + + notSupport(w) { + const stack = w.addStack(); + stack.addText('暂不支持'); + return w; + } + + renderSmall = async (w) => { + this.notSupport(w); + return w; + }; + + renderLarge = async (w) => { + return this.notSupport(w); + }; + + renderCard = (w, data) => { + w.borderColor = this.widgetColor; + w.borderWidth = 2; + w.cornerRadius = 8; + + w.layoutVertically(); + w.setPadding(10, 10, 10, 10); + const topStack = w.addStack(); + topStack.layoutHorizontally(); + topStack.centerAlignContent(); + const iconImage = SFSymbol.named(data.icon).image; + const iconImageStack = topStack.addImage(iconImage); + iconImageStack.tintColor = this.widgetColor; + iconImageStack.imageSize = new Size(30, 30); + + topStack.addSpacer(10); + + const licensePlateText = topStack.addText(data.title); + licensePlateText.textColor = this.widgetColor; + licensePlateText.font = this.provideFont('bold', 14); + + w.addSpacer(); + + data.listItem.forEach((item, index) => { + const listItemStack = w.addStack(); + listItemStack.centerAlignContent(); + const labelText = listItemStack.addText(item.label); + labelText.textColor = this.widgetColor; + labelText.font = this.provideFont('medium', 14); + + listItemStack.addSpacer(); + if (index !== data.listItem.length - 1) w.addSpacer(); + + const valueText = listItemStack.addText(`${item.value}`); + valueText.textColor = this.widgetColor; + valueText.font = this.provideFont('medium', 14); + + if (item.unit) { + const unitText = listItemStack.addText(item.unit); + unitText.textColor = this.widgetColor; + unitText.font = this.provideFont('medium', 14); + } + }); + }; + + renderMedium = async (w) => { + const containerStack = w.addStack(); + containerStack.layoutHorizontally(); + containerStack.centerAlignContent(); + + const leftStack = containerStack.addStack(); + this.renderCard(leftStack, this.dataSource.left); + + containerStack.addSpacer(10); + + const rightStack = containerStack.addStack(); + this.renderCard(rightStack, this.dataSource.right); + return w; + }; + + /** + * 渲染函数,函数名固定 + * 可以根据 this.widgetFamily 来判断小组件尺寸,以返回不同大小的内容 + */ + async render() { + await this.init(); + const widget = new ListWidget(); + await this.getWidgetBackgroundImage(widget); + if (this.widgetFamily === 'medium') { + return await this.renderMedium(widget); + } else if (this.widgetFamily === 'large') { + return await this.renderLarge(widget); + } else { + return await this.renderSmall(widget); + } + } +} + +// @组件代码结束 +await Runing(Widget, '', false); //远程开发环境 diff --git a/Scripts/Birthday.js b/Scripts/Birthday.js deleted file mode 100644 index a1bf774..0000000 --- a/Scripts/Birthday.js +++ /dev/null @@ -1,303 +0,0 @@ -// Variables used by Scriptable. -// These must be at the very top of the file. Do not edit. -// icon-color: pink; icon-glyph: birthday-cake; - -// 添加require,是为了vscode中可以正确引入包,以获得自动补全等功能 -if (typeof require === 'undefined') require = importModule; -const { DmYY, Runing } = require('./DmYY'); -const { Calendar } = require('./Calendar'); -const $ = new Calendar(); - -class Widget extends DmYY { - constructor(arg) { - super(arg); - this.name = '破壳日'; - this.en = 'birthday'; - this.logo = - 'https://raw.githubusercontent.com/Orz-3/mini/master/Color/birthday.png'; - this.LEFT_IMG_KEY = this.FILE_MGR_LOCAL.joinPath( - this.FILE_MGR_LOCAL.documentsDirectory(), - `left_image_${this.SETTING_KEY}.jpg`, - ); - this.defaultData = { ...this.defaultData, ...this.settings[this.en] }; - if (config.runsInApp) { - this.registerAction('基础设置', this.setWidgetConfig); - this.registerAction('生日配置', this.setWidgetInitConfig); - this.registerAction('头像设置', this.setLeftWidgetImage); - this.registerAction('代理缓存', this.setWidgetBoxJSConfig); - } - } - - defaultData = { - username: '', // 姓名 - time: '', // 生日日期 - nongli: '', // 农历生日 - eday: '', //相识 - isLeapMonth: false, //如果是农历闰月第四个参数赋值true即可 - }; - - contentText = {}; - - init = async () => { - try { - this.getCalendarData(); - } catch (e) { - console.log(e); - } - }; - - getEdayNumber = (date) => { - var initDay = date.split('-'); - var obj = { - cYear: parseInt(initDay[0]), - cMonth: parseInt(initDay[1]), - cDay: parseInt(initDay[2]), - }; - return Math.abs(this.$.daysBetween(obj)); - }; - - getCalendarData = () => { - const { time, nongli, isLeapMonth, eday } = this.defaultData; - const _data = time.split('-'); - const opt = { - year: parseInt(_data[0]), - month: parseInt(_data[1]), - day: parseInt(_data[2]), - nongli, - isLeapMonth, - }; - - const response = {}; - response.birthdayText = this.$.birthday(opt); - response.nextBirthday = response.birthdayText[0]; - - const solarData = - nongli === 'true' - ? this.$.lunar2solar(opt.year, opt.month, opt.day, isLeapMonth) - : this.$.solar2lunar(opt.year, opt.month, opt.day); - response.gregorian = solarData; - response.animal = `${this.$.getAnimalZodiacToEmoji(solarData.Animal)}-${ - solarData.Animal - }`; - response.astro = `${this.$.getAstroToEmoji(solarData.astro)}-${ - solarData.astro - }`; - if (this.$.verifyTime(eday)) { - response.meetDay = this.getEdayNumber(eday); - } - this.contentText = response; - }; - - setRightCell = (text, rowCell) => { - const subContent = rowCell.addText(text); - subContent.font = Font.boldSystemFont(14); - subContent.textColor = this.widgetColor; - subContent.lineLimit = 1; - rowCell.addSpacer(5); - }; - - setLeftView = (w) => { - const leftImg = this.getLeftImage(); - const left = w.addStack(); - left.size = new Size(110, 110); - left.cornerRadius = 5; - left.borderWidth = 2; - left.borderColor = this.widgetColor; - if (leftImg) { - const widgetImg = left.addImage(leftImg); - widgetImg.imageSize = new Size(110, 110); - widgetImg.applyFillingContentMode(); - widgetImg.cornerRadius = 5; - } - return w; - }; - - setRightView = (right) => { - const { animal, astro, gregorian, nextBirthday, meetDay, birthdayText } = - this.contentText; - const { IMonthCn, IDayCn } = gregorian; - right.layoutVertically(); - this.setRightCell(`🐽相:${animal}`, right); // 属相 - this.setRightCell(`🌠座:${astro}`, right); // 属相 - if (meetDay) { - this.setRightCell(`💖遇:${meetDay} 天`, right); - } - const _birth = `🎂生:${nextBirthday.cYear}-${nextBirthday.cMonth}-${nextBirthday.cDay} (${birthdayText[1]}天)`; - this.setRightCell(_birth, right); - this.setRightCell(`📆农:${IMonthCn}${IDayCn}`, right); - return right; - }; - - fetch = async () => { - const response = await this.$request.get( - 'https://api.uomg.com/api/rand.qinghua?format=json', - ); - return response.content; - }; - - renderSmall = async (w) => { - this.setRightView(w.addStack()); - return w; - }; - - renderLarge = async (w) => { - w.addSpacer(20); - const body = w.addStack(); - const left = body.addStack(); - this.setLeftView(left); - body.addSpacer(20); - const right = body.addStack(); - this.setRightView(right); - - w.addSpacer(20); - const footer = w.addStack(); - const text = await this.fetch(); - const subContent = footer.addText(text); - subContent.font = Font.boldSystemFont(16); - subContent.textColor = this.widgetColor; - w.addSpacer(); - return w; - }; - - renderMedium = async (w) => { - const body = w.addStack(); - const left = body.addStack(); - this.setLeftView(left); - body.addSpacer(); - const right = body.addStack(); - this.setRightView(right); - body.addSpacer(); - w.addSpacer(); - return w; - }; - - /** - * 渲染函数,函数名固定 - * 可以根据 this.widgetFamily 来判断小组件尺寸,以返回不同大小的内容 - */ - async render() { - await this.init(); - const widget = new ListWidget(); - await this.getWidgetBackgroundImage(widget); - const header = widget.addStack(); - if (this.widgetFamily !== 'small') { - await this.renderMoreHeader(header); - } else { - await this.renderHeader(header, this.logo, this.name, this.widgetColor); - } - widget.addSpacer(10); - if (this.widgetFamily === 'medium') { - await this.renderMedium(widget); - } else if (this.widgetFamily === 'large') { - await this.renderLarge(widget); - } else { - await this.renderSmall(widget); - } - return widget; - } - - renderMoreHeader = async (header) => { - header.centerAlignContent(); - await this.renderHeader(header, this.logo, this.name, this.widgetColor); - header.addSpacer(); - const headerMore = header.addStack(); - headerMore.setPadding(1, 10, 1, 10); - headerMore.cornerRadius = 10; - headerMore.backgroundColor = new Color('#fff', 0.5); - const textItem = headerMore.addText(this.defaultData.username); - textItem.font = Font.boldSystemFont(12); - textItem.textColor = this.widgetColor; - textItem.lineLimit = 1; - textItem.rightAlignText(); - return header; - }; - - /** - * 获取当前插件是否有自定义背景图片 - * @reutrn img | false - */ - getLeftImage() { - let result = null; - if (this.FILE_MGR_LOCAL.fileExists(this.LEFT_IMG_KEY)) { - result = Image.fromFile(this.LEFT_IMG_KEY); - } - return result; - } - - /** - * 设置当前组件的背景图片 - * @param {image} img - */ - setLeftImage(img, notify = true) { - if (!img) { - // 移除背景 - if (this.FILE_MGR_LOCAL.fileExists(this.LEFT_IMG_KEY)) { - this.FILE_MGR_LOCAL.remove(this.LEFT_IMG_KEY); - } - if (notify) this.notify('移除成功', '小组件图片已移除,稍后刷新生效'); - } else { - // 设置背景 - // 全部设置一遍, - this.FILE_MGR_LOCAL.writeImage(this.LEFT_IMG_KEY, img); - if (notify) this.notify('设置成功', '小组件图片已设置!稍后刷新生效'); - } - } - - setLeftWidgetImage = async () => { - const alert = new Alert(); - alert.title = '设置左侧图'; - alert.message = '显示左侧图片'; - alert.addAction('设置新图'); - alert.addAction('清空图片'); - alert.addCancelAction('取消'); - const actions = [ - async () => { - const backImage = await this.chooseImg(); - if (!(await this.verifyImage(backImage))) return; - await this.setLeftImage(backImage, true); - }, - () => { - this.setLeftImage(false, true); - }, - ]; - const id = await alert.presentAlert(); - if (id === -1) return; - actions[id] && actions[id].call(this); - }; - - setWidgetInitConfig = async () => { - const a = new Alert(); - a.title = '🐣破壳日配置'; - a.message = '配置破壳日的基础信息'; - a.addTextField('昵称', this.defaultData.username); - a.addTextField('生日/ 年-月-日', this.defaultData.time); - a.addTextField('农历/ true | false', `${this.defaultData.nongli || ''}`); - a.addTextField('相识/ 年-月-日', this.defaultData.eday); - a.addAction('确定'); - a.addCancelAction('取消'); - const id = await a.presentAlert(); - if (id === -1) return; - this.defaultData.username = a.textFieldValue(0); - this.defaultData.time = a.textFieldValue(1); - this.defaultData.nongli = a.textFieldValue(2) === 'true'; - this.defaultData.eday = a.textFieldValue(3); - // 保存到本地 - this.settings[this.en] = this.defaultData; - this.saveSettings(); - }; - - setWidgetBoxJSConfig = async () => { - try { - const datas = await this.getCache(); - Object.keys(this.defaultData).forEach((key) => { - this.defaultData[key] = datas[`@${this.en}.${key}`]; - }); - this.settings[this.en] = this.defaultData; - this.saveSettings(); - } catch (e) { - console.log(e); - } - }; -} - -Runing(Widget, '', false, { $ }); diff --git a/Scripts/CalendarFnc.js b/Scripts/CalendarFnc.js new file mode 100644 index 0000000..a8b5f21 --- /dev/null +++ b/Scripts/CalendarFnc.js @@ -0,0 +1 @@ +function calendarFun(){const calendar={lunarInfo:[0x04bd8,0x04ae0,0x0a570,0x054d5,0x0d260,0x0d950,0x16554,0x056a0,0x09ad0,0x055d2,0x04ae0,0x0a5b6,0x0a4d0,0x0d250,0x1d255,0x0b540,0x0d6a0,0x0ada2,0x095b0,0x14977,0x04970,0x0a4b0,0x0b4b5,0x06a50,0x06d40,0x1ab54,0x02b60,0x09570,0x052f2,0x04970,0x06566,0x0d4a0,0x0ea50,0x06e95,0x05ad0,0x02b60,0x186e3,0x092e0,0x1c8d7,0x0c950,0x0d4a0,0x1d8a6,0x0b550,0x056a0,0x1a5b4,0x025d0,0x092d0,0x0d2b2,0x0a950,0x0b557,0x06ca0,0x0b550,0x15355,0x04da0,0x0a5b0,0x14573,0x052b0,0x0a9a8,0x0e950,0x06aa0,0x0aea6,0x0ab50,0x04b60,0x0aae4,0x0a570,0x05260,0x0f263,0x0d950,0x05b57,0x056a0,0x096d0,0x04dd5,0x04ad0,0x0a4d0,0x0d4d4,0x0d250,0x0d558,0x0b540,0x0b6a0,0x195a6,0x095b0,0x049b0,0x0a974,0x0a4b0,0x0b27a,0x06a50,0x06d40,0x0af46,0x0ab60,0x09570,0x04af5,0x04970,0x064b0,0x074a3,0x0ea50,0x06b58,0x055c0,0x0ab60,0x096d5,0x092e0,0x0c960,0x0d954,0x0d4a0,0x0da50,0x07552,0x056a0,0x0abb7,0x025d0,0x092d0,0x0cab5,0x0a950,0x0b4a0,0x0baa4,0x0ad50,0x055d9,0x04ba0,0x0a5b0,0x15176,0x052b0,0x0a930,0x07954,0x06aa0,0x0ad50,0x05b52,0x04b60,0x0a6e6,0x0a4e0,0x0d260,0x0ea65,0x0d530,0x05aa0,0x076a3,0x096d0,0x04afb,0x04ad0,0x0a4d0,0x1d0b6,0x0d250,0x0d520,0x0dd45,0x0b5a0,0x056d0,0x055b2,0x049b0,0x0a577,0x0a4b0,0x0aa50,0x1b255,0x06d20,0x0ada0,0x14b63,0x09370,0x049f8,0x04970,0x064b0,0x168a6,0x0ea50,0x06b20,0x1a6c4,0x0aae0,0x0a2e0,0x0d2e3,0x0c960,0x0d557,0x0d4a0,0x0da50,0x05d55,0x056a0,0x0a6d0,0x055d4,0x052d0,0x0a9b8,0x0a950,0x0b4a0,0x0b6a6,0x0ad50,0x055a0,0x0aba4,0x0a5b0,0x052b0,0x0b273,0x06930,0x07337,0x06aa0,0x0ad50,0x14b55,0x04b60,0x0a570,0x054e4,0x0d160,0x0e968,0x0d520,0x0daa0,0x16aa6,0x056d0,0x04ae0,0x0a9d4,0x0a2d0,0x0d150,0x0f252,0x0d520,],solarMonth:[31,28,31,30,31,30,31,31,30,31,30,31],Gan:["\u7532","\u4e59","\u4e19","\u4e01","\u620a","\u5df1","\u5e9a","\u8f9b","\u58ec","\u7678",],Zhi:["\u5b50","\u4e11","\u5bc5","\u536f","\u8fb0","\u5df3","\u5348","\u672a","\u7533","\u9149","\u620c","\u4ea5",],Animals:["\u9f20","\u725b","\u864e","\u5154","\u9f99","\u86c7","\u9a6c","\u7f8a","\u7334","\u9e21","\u72d7","\u732a",],solarTerm:["\u5c0f\u5bd2","\u5927\u5bd2","\u7acb\u6625","\u96e8\u6c34","\u60ca\u86f0","\u6625\u5206","\u6e05\u660e","\u8c37\u96e8","\u7acb\u590f","\u5c0f\u6ee1","\u8292\u79cd","\u590f\u81f3","\u5c0f\u6691","\u5927\u6691","\u7acb\u79cb","\u5904\u6691","\u767d\u9732","\u79cb\u5206","\u5bd2\u9732","\u971c\u964d","\u7acb\u51ac","\u5c0f\u96ea","\u5927\u96ea","\u51ac\u81f3",],sTermInfo:["9778397bd097c36b0b6fc9274c91aa","97b6b97bd19801ec9210c965cc920e","97bcf97c3598082c95f8c965cc920f","97bd0b06bdb0722c965ce1cfcc920f","b027097bd097c36b0b6fc9274c91aa","97b6b97bd19801ec9210c965cc920e","97bcf97c359801ec95f8c965cc920f","97bd0b06bdb0722c965ce1cfcc920f","b027097bd097c36b0b6fc9274c91aa","97b6b97bd19801ec9210c965cc920e","97bcf97c359801ec95f8c965cc920f","97bd0b06bdb0722c965ce1cfcc920f","b027097bd097c36b0b6fc9274c91aa","9778397bd19801ec9210c965cc920e","97b6b97bd19801ec95f8c965cc920f","97bd09801d98082c95f8e1cfcc920f","97bd097bd097c36b0b6fc9210c8dc2","9778397bd197c36c9210c9274c91aa","97b6b97bd19801ec95f8c965cc920e","97bd09801d98082c95f8e1cfcc920f","97bd097bd097c36b0b6fc9210c8dc2","9778397bd097c36c9210c9274c91aa","97b6b97bd19801ec95f8c965cc920e","97bcf97c3598082c95f8e1cfcc920f","97bd097bd097c36b0b6fc9210c8dc2","9778397bd097c36c9210c9274c91aa","97b6b97bd19801ec9210c965cc920e","97bcf97c3598082c95f8c965cc920f","97bd097bd097c35b0b6fc920fb0722","9778397bd097c36b0b6fc9274c91aa","97b6b97bd19801ec9210c965cc920e","97bcf97c3598082c95f8c965cc920f","97bd097bd097c35b0b6fc920fb0722","9778397bd097c36b0b6fc9274c91aa","97b6b97bd19801ec9210c965cc920e","97bcf97c359801ec95f8c965cc920f","97bd097bd097c35b0b6fc920fb0722","9778397bd097c36b0b6fc9274c91aa","97b6b97bd19801ec9210c965cc920e","97bcf97c359801ec95f8c965cc920f","97bd097bd097c35b0b6fc920fb0722","9778397bd097c36b0b6fc9274c91aa","97b6b97bd19801ec9210c965cc920e","97bcf97c359801ec95f8c965cc920f","97bd097bd07f595b0b6fc920fb0722","9778397bd097c36b0b6fc9210c8dc2","9778397bd19801ec9210c9274c920e","97b6b97bd19801ec95f8c965cc920f","97bd07f5307f595b0b0bc920fb0722","7f0e397bd097c36b0b6fc9210c8dc2","9778397bd097c36c9210c9274c920e","97b6b97bd19801ec95f8c965cc920f","97bd07f5307f595b0b0bc920fb0722","7f0e397bd097c36b0b6fc9210c8dc2","9778397bd097c36c9210c9274c91aa","97b6b97bd19801ec9210c965cc920e","97bd07f1487f595b0b0bc920fb0722","7f0e397bd097c36b0b6fc9210c8dc2","9778397bd097c36b0b6fc9274c91aa","97b6b97bd19801ec9210c965cc920e","97bcf7f1487f595b0b0bb0b6fb0722","7f0e397bd097c35b0b6fc920fb0722","9778397bd097c36b0b6fc9274c91aa","97b6b97bd19801ec9210c965cc920e","97bcf7f1487f595b0b0bb0b6fb0722","7f0e397bd097c35b0b6fc920fb0722","9778397bd097c36b0b6fc9274c91aa","97b6b97bd19801ec9210c965cc920e","97bcf7f1487f531b0b0bb0b6fb0722","7f0e397bd097c35b0b6fc920fb0722","9778397bd097c36b0b6fc9274c91aa","97b6b97bd19801ec9210c965cc920e","97bcf7f1487f531b0b0bb0b6fb0722","7f0e397bd07f595b0b6fc920fb0722","9778397bd097c36b0b6fc9274c91aa","97b6b97bd19801ec9210c9274c920e","97bcf7f0e47f531b0b0bb0b6fb0722","7f0e397bd07f595b0b0bc920fb0722","9778397bd097c36b0b6fc9210c91aa","97b6b97bd197c36c9210c9274c920e","97bcf7f0e47f531b0b0bb0b6fb0722","7f0e397bd07f595b0b0bc920fb0722","9778397bd097c36b0b6fc9210c8dc2","9778397bd097c36c9210c9274c920e","97b6b7f0e47f531b0723b0b6fb0722","7f0e37f5307f595b0b0bc920fb0722","7f0e397bd097c36b0b6fc9210c8dc2","9778397bd097c36b0b70c9274c91aa","97b6b7f0e47f531b0723b0b6fb0721","7f0e37f1487f595b0b0bb0b6fb0722","7f0e397bd097c35b0b6fc9210c8dc2","9778397bd097c36b0b6fc9274c91aa","97b6b7f0e47f531b0723b0b6fb0721","7f0e27f1487f595b0b0bb0b6fb0722","7f0e397bd097c35b0b6fc920fb0722","9778397bd097c36b0b6fc9274c91aa","97b6b7f0e47f531b0723b0b6fb0721","7f0e27f1487f531b0b0bb0b6fb0722","7f0e397bd097c35b0b6fc920fb0722","9778397bd097c36b0b6fc9274c91aa","97b6b7f0e47f531b0723b0b6fb0721","7f0e27f1487f531b0b0bb0b6fb0722","7f0e397bd097c35b0b6fc920fb0722","9778397bd097c36b0b6fc9274c91aa","97b6b7f0e47f531b0723b0b6fb0721","7f0e27f1487f531b0b0bb0b6fb0722","7f0e397bd07f595b0b0bc920fb0722","9778397bd097c36b0b6fc9274c91aa","97b6b7f0e47f531b0723b0787b0721","7f0e27f0e47f531b0b0bb0b6fb0722","7f0e397bd07f595b0b0bc920fb0722","9778397bd097c36b0b6fc9210c91aa","97b6b7f0e47f149b0723b0787b0721","7f0e27f0e47f531b0723b0b6fb0722","7f0e397bd07f595b0b0bc920fb0722","9778397bd097c36b0b6fc9210c8dc2","977837f0e37f149b0723b0787b0721","7f07e7f0e47f531b0723b0b6fb0722","7f0e37f5307f595b0b0bc920fb0722","7f0e397bd097c35b0b6fc9210c8dc2","977837f0e37f14998082b0787b0721","7f07e7f0e47f531b0723b0b6fb0721","7f0e37f1487f595b0b0bb0b6fb0722","7f0e397bd097c35b0b6fc9210c8dc2","977837f0e37f14998082b0787b06bd","7f07e7f0e47f531b0723b0b6fb0721","7f0e27f1487f531b0b0bb0b6fb0722","7f0e397bd097c35b0b6fc920fb0722","977837f0e37f14998082b0787b06bd","7f07e7f0e47f531b0723b0b6fb0721","7f0e27f1487f531b0b0bb0b6fb0722","7f0e397bd097c35b0b6fc920fb0722","977837f0e37f14998082b0787b06bd","7f07e7f0e47f531b0723b0b6fb0721","7f0e27f1487f531b0b0bb0b6fb0722","7f0e397bd07f595b0b0bc920fb0722","977837f0e37f14998082b0787b06bd","7f07e7f0e47f531b0723b0b6fb0721","7f0e27f1487f531b0b0bb0b6fb0722","7f0e397bd07f595b0b0bc920fb0722","977837f0e37f14998082b0787b06bd","7f07e7f0e47f149b0723b0787b0721","7f0e27f0e47f531b0b0bb0b6fb0722","7f0e397bd07f595b0b0bc920fb0722","977837f0e37f14998082b0723b06bd","7f07e7f0e37f149b0723b0787b0721","7f0e27f0e47f531b0723b0b6fb0722","7f0e397bd07f595b0b0bc920fb0722","977837f0e37f14898082b0723b02d5","7ec967f0e37f14998082b0787b0721","7f07e7f0e47f531b0723b0b6fb0722","7f0e37f1487f595b0b0bb0b6fb0722","7f0e37f0e37f14898082b0723b02d5","7ec967f0e37f14998082b0787b0721","7f07e7f0e47f531b0723b0b6fb0722","7f0e37f1487f531b0b0bb0b6fb0722","7f0e37f0e37f14898082b0723b02d5","7ec967f0e37f14998082b0787b06bd","7f07e7f0e47f531b0723b0b6fb0721","7f0e37f1487f531b0b0bb0b6fb0722","7f0e37f0e37f14898082b072297c35","7ec967f0e37f14998082b0787b06bd","7f07e7f0e47f531b0723b0b6fb0721","7f0e27f1487f531b0b0bb0b6fb0722","7f0e37f0e37f14898082b072297c35","7ec967f0e37f14998082b0787b06bd","7f07e7f0e47f531b0723b0b6fb0721","7f0e27f1487f531b0b0bb0b6fb0722","7f0e37f0e366aa89801eb072297c35","7ec967f0e37f14998082b0787b06bd","7f07e7f0e47f149b0723b0787b0721","7f0e27f1487f531b0b0bb0b6fb0722","7f0e37f0e366aa89801eb072297c35","7ec967f0e37f14998082b0723b06bd","7f07e7f0e47f149b0723b0787b0721","7f0e27f0e47f531b0723b0b6fb0722","7f0e37f0e366aa89801eb072297c35","7ec967f0e37f14998082b0723b06bd","7f07e7f0e37f14998083b0787b0721","7f0e27f0e47f531b0723b0b6fb0722","7f0e37f0e366aa89801eb072297c35","7ec967f0e37f14898082b0723b02d5","7f07e7f0e37f14998082b0787b0721","7f07e7f0e47f531b0723b0b6fb0722","7f0e36665b66aa89801e9808297c35","665f67f0e37f14898082b0723b02d5","7ec967f0e37f14998082b0787b0721","7f07e7f0e47f531b0723b0b6fb0722","7f0e36665b66a449801e9808297c35","665f67f0e37f14898082b0723b02d5","7ec967f0e37f14998082b0787b06bd","7f07e7f0e47f531b0723b0b6fb0721","7f0e36665b66a449801e9808297c35","665f67f0e37f14898082b072297c35","7ec967f0e37f14998082b0787b06bd","7f07e7f0e47f531b0723b0b6fb0721","7f0e26665b66a449801e9808297c35","665f67f0e37f1489801eb072297c35","7ec967f0e37f14998082b0787b06bd","7f07e7f0e47f531b0723b0b6fb0721","7f0e27f1487f531b0b0bb0b6fb0722",],nStr1:["\u65e5","\u4e00","\u4e8c","\u4e09","\u56db","\u4e94","\u516d","\u4e03","\u516b","\u4e5d","\u5341",],nStr2:["\u521d","\u5341","\u5eff","\u5345"],nStr3:["\u6b63","\u4e8c","\u4e09","\u56db","\u4e94","\u516d","\u4e03","\u516b","\u4e5d","\u5341","\u51ac","\u814a",],lYearDays:function(y){var i,sum=348;for(i=0x8000;i>0x8;i>>=1){sum+=calendar.lunarInfo[y-1900]&i?1:0}return sum+calendar.leapDays(y)},leapMonth:function(y){return calendar.lunarInfo[y-1900]&0xf},leapDays:function(y){if(calendar.leapMonth(y)){return calendar.lunarInfo[y-1900]&0x10000?30:29}return 0},monthDays:function(y,m){if(m>12||m<1){return-1}return calendar.lunarInfo[y-1900]&(0x10000>>m)?30:29},solarDays:function(y,m){if(m>12||m<1){return-1}var ms=m-1;if(ms==1){return(y%4==0&&y%100!=0)||y%400==0?29:28}else{return calendar.solarMonth[ms]}},toGanZhiYear:function(lYear){var ganKey=(lYear-3)%10;var zhiKey=(lYear-3)%12;if(ganKey==0)ganKey=10;if(zhiKey==0)zhiKey=12;return calendar.Gan[ganKey-1]+calendar.Zhi[zhiKey-1]},toAstro:function(cMonth,cDay){var s="\u9b54\u7faf\u6c34\u74f6\u53cc\u9c7c\u767d\u7f8a\u91d1\u725b\u53cc\u5b50\u5de8\u87f9\u72ee\u5b50\u5904\u5973\u5929\u79e4\u5929\u874e\u5c04\u624b\u9b54\u7faf";var arr=[20,19,21,21,21,22,23,23,23,23,22,22];return(s.substr(cMonth*2-(cDay2100){return-1}if(n<1||n>24){return-1}var _table=calendar.sTermInfo[y-1900];var _info=[parseInt("0x"+_table.substr(0,5)).toString(),parseInt("0x"+_table.substr(5,5)).toString(),parseInt("0x"+_table.substr(10,5)).toString(),parseInt("0x"+_table.substr(15,5)).toString(),parseInt("0x"+_table.substr(20,5)).toString(),parseInt("0x"+_table.substr(25,5)).toString(),];var _calday=[_info[0].substr(0,1),_info[0].substr(1,2),_info[0].substr(3,1),_info[0].substr(4,2),_info[1].substr(0,1),_info[1].substr(1,2),_info[1].substr(3,1),_info[1].substr(4,2),_info[2].substr(0,1),_info[2].substr(1,2),_info[2].substr(3,1),_info[2].substr(4,2),_info[3].substr(0,1),_info[3].substr(1,2),_info[3].substr(3,1),_info[3].substr(4,2),_info[4].substr(0,1),_info[4].substr(1,2),_info[4].substr(3,1),_info[4].substr(4,2),_info[5].substr(0,1),_info[5].substr(1,2),_info[5].substr(3,1),_info[5].substr(4,2),];return parseInt(_calday[n-1])},toChinaMonth:function(m){if(m>12||m<1){return-1}var s=calendar.nStr3[m-1];s+="\u6708";return s},toChinaDay:function(d){var s;switch(d){case 10:s="\u521d\u5341";break;case 20:s="\u4e8c\u5341";break;break;case 30:s="\u4e09\u5341";break;break;default:s=calendar.nStr2[Math.floor(d/10)];s+=calendar.nStr1[d%10]}return s},getAnimal:function(y){return calendar.Animals[(y-4)%12]},solar2lunar:function(y,m,d){if(y<1900||y>2100){return-1}if(y==1900&&m==1&&d<31){return-1}if(!y){var objDate=new Date()}else{var objDate=new Date(y,parseInt(m)-1,d)}var i,leap=0,temp=0;var y=objDate.getFullYear(),m=objDate.getMonth()+1,d=objDate.getDate();var offset=(Date.UTC(objDate.getFullYear(),objDate.getMonth(),objDate.getDate())-Date.UTC(1900,0,31))/86400000;for(i=1900;i<2101&&offset>0;i++){temp=calendar.lYearDays(i);offset-=temp}if(offset<0){offset+=temp;i--}var isTodayObj=new Date(),isToday=false;if(isTodayObj.getFullYear()==y&&isTodayObj.getMonth()+1==m&&isTodayObj.getDate()==d){isToday=true}var nWeek=objDate.getDay(),cWeek=calendar.nStr1[nWeek];if(nWeek==0){nWeek=7}var year=i;var leap=calendar.leapMonth(i);var isLeap=false;for(i=1;i<13&&offset>0;i++){if(leap>0&&i==leap+1&&isLeap==false){--i;isLeap=true;temp=calendar.leapDays(year)}else{temp=calendar.monthDays(year,i)}if(isLeap==true&&i==leap+1){isLeap=false}offset-=temp}if(offset==0&&leap>0&&i==leap+1){if(isLeap){isLeap=false}else{isLeap=true;--i}}if(offset<0){offset+=temp;--i}var month=i;var day=offset+1;var sm=m-1;var gzY=calendar.toGanZhiYear(year);var firstNode=calendar.getTerm(y,m*2-1);var secondNode=calendar.getTerm(y,m*2);var gzM=calendar.toGanZhi((y-1900)*12+m+11);if(d>=firstNode){gzM=calendar.toGanZhi((y-1900)*12+m+12)}var isTerm=false;var Term=null;if(firstNode==d){isTerm=true;Term=calendar.solarTerm[m*2-2]}if(secondNode==d){isTerm=true;Term=calendar.solarTerm[m*2-1]}var dayCyclical=Date.UTC(y,sm,1,0,0,0,0)/86400000+25567+10;var gzD=calendar.toGanZhi(dayCyclical+d-1);var astro=calendar.toAstro(m,d);return{lYear:year,lMonth:month,lDay:day,Animal:calendar.getAnimal(year),IMonthCn:(isLeap?"\u95f0":"")+calendar.toChinaMonth(month),IDayCn:calendar.toChinaDay(day),cYear:y,cMonth:m,cDay:d,gzYear:gzY,gzMonth:gzM,gzDay:gzD,isToday:isToday,isLeap:isLeap,nWeek:nWeek,ncWeek:"\u661f\u671f"+cWeek,isTerm:isTerm,Term:Term,astro:astro,}},lunar2solar:function(y,m,d,isLeapMonth){var isLeapMonth=!!isLeapMonth;var leapOffset=0;var leapMonth=calendar.leapMonth(y);var leapDay=calendar.leapDays(y);if(isLeapMonth&&leapMonth!=m){return-1}if((y==2100&&m==12&&d>1)||(y==1900&&m==1&&d<31)){return-1}var day=calendar.monthDays(y,m);var _day=day;if(isLeapMonth){_day=calendar.leapDays(y,m)}if(y<1900||y>2100||d>_day){return-1}var offset=0;for(var i=1900;i0){offset+=calendar.leapDays(y);isAdd=true}}offset+=calendar.monthDays(y,i)}if(isLeapMonth){offset+=day}var stmap=Date.UTC(1900,1,30,0,0,0);var calObj=new Date((offset+d-31)*86400000+stmap);var cY=calObj.getUTCFullYear();var cM=calObj.getUTCMonth()+1;var cD=calObj.getUTCDate();return calendar.solar2lunar(cY,cM,cD)},birthday:function(y,m,d,nongli,isLeapMonth){var date;var now=new Date();if(nongli){var now_d=this.solar2lunar(now.getFullYear(),now.getMonth()+1,now.getDate());var now_year=now_d.lYear;date=this.birthBylunar(now_year,m,d,isLeapMonth);if(this.daysBetween(date)<=0){now_year++;date=this.birthBylunar(now_year,m,d,isLeapMonth)}}else{var now_year=now.getFullYear();date=this.solar2lunar(now_year,m,d);if(this.daysBetween(date)<=0){now_year++;date=this.solar2lunar(now_year,m,d)}}return[date.cYear,date.cMonth,date.cDay,this.daysBetween(date)]},birthBylunar:function(y,m,d,isLeapMonth){if(isLeapMonth&&this.leapMonth(y)==m){d=this.lunar2solar(y,m,d,isLeapMonth)}else{d=this.lunar2solar(y,m,d,false)}return d},daysBetween:function(d){var now=new Date();var date=new Date(d.cYear,d.cMonth-1,d.cDay);return parseInt((date.getTime()-now.getTime())/(24*3600*1000))},verifyTime(date){var dateFormat=/^(\d{4})-(\d{1,2})-(\d{1,2})$/;return dateFormat.test(date)},getAnimalZodiacToEmoji(zodiac){var data={鼠:"🐭",牛:"🐂",虎:"🐯",兔:"🐇",龙:"🐲",蛇:"🐍",马:"🐴",羊:"🐑",猴:"🐵",鸡:"🐔",狗:"🐶",猪:"🐷",};return data[zodiac]||""},getAstroToEmoji(astro){var data={白羊座:"♈",金牛座:"♉",双子座:"♊",巨蟹座:"♋",狮子座:"♌",处女座:"♍",天秤座:"♎",天蝎座:"♏",射手座:"♐",摩羯座:"♑",水瓶座:"♒",双鱼座:"♓",蛇夫座:"⛎",};return data[astro]||""},};return calendar} \ No newline at end of file diff --git a/Scripts/CarWidget.js b/Scripts/CarWidget.js new file mode 100644 index 0000000..02cb286 --- /dev/null +++ b/Scripts/CarWidget.js @@ -0,0 +1,342 @@ +// Variables used by Scriptable. +// These must be at the very top of the file. Do not edit. +// icon-color: deep-gray; icon-glyph: car; + +// 添加require,是为了vscode中可以正确引入包,以获得自动补全等功能 +if (typeof require === 'undefined') require = importModule; +const { DmYY, Runing } = require('./DmYY'); + +// @组件代码开始 +class Widget extends DmYY { + constructor(arg) { + super(arg); + if (config.runsInApp) { + this.registerAction({ + icon: { name: 'paperplane', color: '#722ed1' }, + type: 'input', + title: '油价设置', + desc: '89|92|95|0', + val: 'oilNumber', + }); + + this.registerAction({ + icon: { name: 'car', color: '#f5222d' }, + type: 'input', + title: '依赖插件', + desc: '汽车的依赖插件例如 Ftms', + val: 'filePath', + }); + + this.registerAction({ + icon: { name: 'plus.viewfinder', color: '#fa8c16' }, + type: 'input', + title: '缩放比例', + desc: '比例越大进度条越长', + placeholder: '取值 0~1', + val: 'scale', + }); + } + + config.runsInApp && this.registerAction('基础设置', this.setWidgetConfig); + this.cacheName = this.md5(`dataSouce_${this.en}`); + const filePath = this.settings.filePath || 'Ftms'; + const carModule = require(`./${filePath}`); + const carService = new carModule(this); + this.scale = parseFloat(this.settings.scale) || 1; // 柱状图比例高度,值越大,柱状范围越广 + this.init = carService.init; + this.name = carService.name; + this.logo = carService.logo; + this.viewColor = Color.dynamic( + new Color('#d9d9d9', parseFloat(this.settings.lightOpacity || 1)), + new Color('#8c8c8c', parseFloat(this.settings.darkOpacity || 1)) + ); + } + + widgetHeight = 145; + + serveInfo = { + carNumber: '', + }; + + dataSource = { + remoteInfo: { + datatime: '', + list: [], + userId: '', + carNumber: '', + }, + monitorInfo: { + km: '0', + oilRate: '0', + oilWaste: '0', + oilWasteText: '', + }, + safeText: '', + oilPriceText: '', + oilZDE: 0, + }; + + createProgressBar( + soFar, + total = 100, + width = 400, + height = 40, + showPercentage = false + ) { + const context = new DrawContext(); + context.size = new Size(width, height); + context.opaque = false; + context.respectScreenScale = true; + + // bar background + context.setFillColor(new Color('#48484b')); + const bgPath = new Path(); + bgPath.addRoundedRect( + new Rect(0, 0, width, height), + height / 2, + height / 2 - 1 + ); + context.addPath(bgPath); + context.fillPath(); + + // bar foreground + context.setFillColor(new Color('#e8e8e8')); + const fgPath = new Path(); + fgPath.addRoundedRect( + new Rect(0, 0, (width * soFar) / total, height), + height / 2, + height / 2 - 1 + ); + context.addPath(fgPath); + context.fillPath(); + + if (showPercentage) { + const percentage = ((soFar / total) * 100).toFixed(2); + let xPos = (width * soFar) / total + 5; + // if over 70%, show in foreground area + // to ensure that it doesn't overflow the display + if (percentage > 70) { + xPos = (width * soFar) / total - 55; + } + + context.setFont(Font.semiboldRoundedSystemFont(14)); + context.setTextColor(primaryTextColor); + context.drawText(`${percentage}%`, new Point(xPos, height / 14)); + } + + return context.getImage(); + } + + renderBorder = (stack) => { + stack.borderWidth = 1; + }; + + renderImage = async (uri) => { + return this.$request.get(uri, 'IMG'); + }; + + notSupport(w) { + const stack = w.addStack(); + stack.addText('暂不支持'); + return w; + } + + renderSmall = async (w) => { + w.addSpacer(); + + const stack = w.addStack(); + stack.layoutVertically(); + const headerStack = stack.addStack(); + headerStack.centerAlignContent(); + headerStack.addSpacer(10); + const gasImg = SFSymbol.named('fuelpump').image; + + const gasIcon = headerStack.addImage(gasImg); + gasIcon.imageSize = new Size(16, 16); + gasIcon.tintColor = this.widgetColor; + headerStack.addSpacer(5); + + const oilRateStackText = headerStack.addText( + `${this.dataSource.monitorInfo.oilRate}%` + ); + oilRateStackText.textColor = this.widgetColor; + oilRateStackText.font = Font.boldSystemFont(14); + + headerStack.addSpacer(); + const logImg = await this.renderImage(this.logo); + const logImgStack = headerStack.addImage(logImg); + logImgStack.imageSize = new Size(20, 20); + headerStack.addSpacer(10); + + const bodyStack = stack.addStack(); + bodyStack.centerAlignContent(); + bodyStack.addSpacer(); + const progressImg = this.createProgressBar( + this.dataSource.monitorInfo.oilRate + ); + const progressBar = bodyStack.addImage(progressImg); + progressBar.imageSize = new Size(this.widgetHeight * this.scale, 28); + bodyStack.addSpacer(); + + stack.addSpacer(); + + const oilWasteStack = stack.addStack(); + oilWasteStack.centerAlignContent(); + oilWasteStack.addSpacer(); + const oilWasteStackText = oilWasteStack.addText( + this.dataSource.monitorInfo.oilWasteText + ); + oilWasteStackText.textColor = this.widgetColor; + oilWasteStackText.font = Font.boldSystemFont(10); + oilWasteStack.addSpacer(5); + const oilPriceStackText = oilWasteStack.addText( + this.dataSource.oilPriceText + ); + oilPriceStackText.textColor = this.widgetColor; + oilPriceStackText.font = Font.boldSystemFont(10); + oilWasteStack.addSpacer(2); + const oilStatus = this.dataSource.oilZDE > 0; + const oilZdeImage = SFSymbol.named( + oilStatus ? 'arrow.up' : 'arrow.down' + ).image; + + const oilZdeWidgetImg = oilWasteStack.addImage(oilZdeImage); + oilZdeWidgetImg.tintColor = new Color(oilStatus ? '#f5222d' : '#a0d911'); + oilZdeWidgetImg.imageSize = new Size(10, 10); + + oilWasteStack.addSpacer(); + + const kilometerStack = stack.addStack(); + + kilometerStack.centerAlignContent(); + kilometerStack.addSpacer(); + const panoImg = SFSymbol.named('speedometer').image; + + const panoImgStack = kilometerStack.addStack(); + panoImgStack.setPadding(5, 0, 0, 0); + const panoStack = panoImgStack.addImage(panoImg); + panoStack.tintColor = this.widgetColor; + panoStack.imageSize = new Size(20, 20); + kilometerStack.addSpacer(5); + + const oilWasteText = kilometerStack.addText(this.dataSource.monitorInfo.km); + oilWasteText.font = Font.boldSystemFont(28); + oilWasteText.textColor = this.widgetColor; + kilometerStack.addSpacer(5); + const unitStack = kilometerStack.addStack(); + unitStack.setPadding(5, 0, 0, 0); + const oilWasteUnit = unitStack.addText('km'); + oilWasteUnit.font = Font.boldSystemFont(14); + oilWasteUnit.textColor = this.widgetColor; + kilometerStack.addSpacer(); + + stack.addSpacer(); + + const btBodyStack = stack.addStack(); + btBodyStack.addSpacer(); + const bottomStack = btBodyStack.addStack(); + bottomStack.setPadding(10, 0, 10, 0); + bottomStack.centerAlignContent(); + bottomStack.addSpacer(); + bottomStack.cornerRadius = 15; + bottomStack.backgroundColor = this.viewColor; + const dataTime = this.dataSource.remoteInfo.datatime; + const countKmText = bottomStack.addText(`上传:${dataTime || '-'}`); + countKmText.textColor = this.widgetColor; + countKmText.font = Font.boldSystemFont(12); + countKmText.centerAlignText(); + bottomStack.addSpacer(); + w.addSpacer(); + return w; + }; + + renderLarge = async (w) => { + return this.renderSmall(w); + }; + + renderMedium = async (w) => { + const containerStack = w.addStack(); + containerStack.centerAlignContent(); + const carStack = containerStack.addStack(); + carStack.addSpacer(); + carStack.backgroundColor = this.viewColor; + + carStack.layoutVertically(); + + carStack.centerAlignContent(); + carStack.size = new Size(this.widgetHeight, this.widgetHeight); + carStack.cornerRadius = 20; + const carImg = await this.renderImage(this.serveInfo.picUrl); + + const carImgStack = carStack.addStack(); + const carResStack = carImgStack.addImage(carImg); + carResStack.imageSize = new Size(137.5, 70); + + carStack.addSpacer(); + + const carNumberStack = carStack.addStack(); + carNumberStack.addSpacer(); + carNumberStack.centerAlignContent(); + const carNumberText = carNumberStack.addText(this.serveInfo.carNumber); + carNumberText.font = Font.boldSystemFont(24); + carNumberText.textColor = this.widgetColor; + carNumberText.centerAlignText(); + carNumberStack.addSpacer(); + + carStack.addSpacer(); + + const carSafeStack = carStack.addStack(); + carSafeStack.addSpacer(); + carSafeStack.centerAlignContent(); + + let safeIconImg; + if (this.dataSource.safeText) { + safeIconImg = carSafeStack.addImage(SFSymbol.named('lock.open').image); + } else { + safeIconImg = carSafeStack.addImage(SFSymbol.named('lock').image); + } + + carSafeStack.addSpacer(5); + const statusText = carSafeStack.addText( + this.dataSource.safeText || '已上锁' + ); + statusText.centerAlignText(); + statusText.font = Font.systemFont(12); + statusText.textColor = this.dataSource.safeText + ? new Color('#f5222d') + : this.widgetColor; + + safeIconImg.tintColor = statusText.textColor; + safeIconImg.imageSize = new Size(10, 14); + + carSafeStack.addSpacer(); + + carStack.addSpacer(); + + containerStack.addSpacer(); + const rightStack = containerStack.addStack(); + rightStack.layoutVertically(); + await this.renderSmall(rightStack); + + return w; + }; + + /** + * 渲染函数,函数名固定 + * 可以根据 this.widgetFamily 来判断小组件尺寸,以返回不同大小的内容 + */ + async render() { + await this.init(); + const widget = new ListWidget(); + widget.setPadding(10, 10, 10, 10); + await this.getWidgetBackgroundImage(widget); + if (this.widgetFamily === 'medium') { + return await this.renderMedium(widget); + } else { + return await this.notSupport(widget); + } + } +} + +// @组件代码结束 +await Runing(Widget, '', false); //远程开发环境 diff --git a/Scripts/ChinaTelecom.js b/Scripts/ChinaTelecom.js index 10a99ed..03fa63f 100644 --- a/Scripts/ChinaTelecom.js +++ b/Scripts/ChinaTelecom.js @@ -3,30 +3,30 @@ // icon-color: pink; icon-glyph: paper-plane; // 添加require,是为了vscode中可以正确引入包,以获得自动补全等功能 -if (typeof require === 'undefined') require = importModule; -const {DmYY, Runing} = require('./DmYY'); +if (typeof require === "undefined") require = importModule; +const { DmYY, Runing } = require("./DmYY"); // @组件代码开始 class Widget extends DmYY { constructor(arg) { super(arg); - this.name = '中国电信'; - this.en = 'ChinaTelecom'; + this.name = "中国电信"; + this.en = "ChinaTelecom"; this.Run(); } - cookie = ''; - authToken = ''; - fgCircleColor = Color.dynamic(new Color('#dddef3'), new Color('#fff')); + cookie = ""; + authToken = ""; + fgCircleColor = Color.dynamic(new Color("#dddef3"), new Color("#fff")); percentColor = this.widgetColor; - textColor1 = Color.dynamic(new Color('#333'), new Color('#fff')); + textColor1 = Color.dynamic(new Color("#333"), new Color("#fff")); textColor2 = this.widgetColor; - circleColor1 = new Color('#ffbb73'); - circleColor2 = new Color('#ff0029'); - circleColor3 = new Color('#00b800'); - circleColor4 = new Color('#8376f9'); - iconColor = new Color('#827af1'); + circleColor1 = new Color("#ffbb73"); + circleColor2 = new Color("#ff0029"); + circleColor3 = new Color("#00b800"); + circleColor4 = new Color("#8376f9"); + iconColor = new Color("#827af1"); format = (str) => { return parseInt(str) >= 10 ? str : `0${str}`; @@ -43,37 +43,37 @@ class Widget extends DmYY { // percent 的计算方式,剩余/总量 * 100 = 百分比| 百分比 * 3.6 ,为显示进度。 phoneBill = { percent: 0, - label: '话费剩余', + label: "话费剩余", count: 0, - unit: '元', - icon: 'yensign.circle', + unit: "元", + icon: "yensign.circle", circleColor: this.circleColor1, }; flow = { percent: 0, - label: '流量剩余', + label: "流量剩余", count: 0, - unit: 'M', - icon: 'waveform.path.badge.minus', + unit: "M", + icon: "waveform.path.badge.minus", circleColor: this.circleColor2, }; voice = { percent: 0, - label: '语音剩余', + label: "语音剩余", count: 0, - unit: '分钟', - icon: 'mic', + unit: "分钟", + icon: "mic", circleColor: this.circleColor3, }; updateTime = { percent: 0, - label: '电信更新', + label: "电信更新", count: `${this.arrUpdateTime[2]}:${this.arrUpdateTime[3]}`, - unit: '', - urlIcon: 'https://raw.githubusercontent.com/Orz-3/mini/master/10000.png', + unit: "", + urlIcon: "https://raw.githubusercontent.com/Orz-3/mini/master/10000.png", circleColor: this.circleColor4, }; @@ -89,12 +89,12 @@ class Widget extends DmYY { // "User-Agent": "TYUserCenter/2.8 (iPhone; iOS 14.0; Scale/3.00)", }, // body: "t=tysuit", - method: 'POST', + method: "POST", }; fetchUri = { - detail: 'https://e.189.cn/store/user/package_detail.do', - balance: 'https://e.189.cn/store/user/balance_new.do', + detail: "https://e.189.cn/store/user/package_detail.do", + balance: "https://e.189.cn/store/user/balance_new.do", }; init = async () => { @@ -112,9 +112,9 @@ class Widget extends DmYY { formatFlow(number) { const n = number / 1024; if (n < 1024) { - return {count: n.toFixed(2), unit: 'M'}; + return { count: n.toFixed(2), unit: "M" }; } - return {count: (n / 1024).toFixed(2), unit: 'G'}; + return { count: (n / 1024).toFixed(2), unit: "G" }; } getData = async () => { @@ -131,7 +131,7 @@ class Widget extends DmYY { if (detail.result === 0) { // 套餐分钟数 this.voice.percent = Math.floor( - (parseInt(detail.voiceBalance) / parseInt(detail.voiceAmount)) * 100, + (parseInt(detail.voiceBalance) / parseInt(detail.voiceAmount)) * 100 ); this.voice.count = detail.voiceBalance; console.log(detail.items); @@ -139,10 +139,10 @@ class Widget extends DmYY { detail.items.forEach((data) => { if (data.offerType !== 19) { data.items.forEach((item) => { - if (item.unitTypeId === '3') { - if (item.usageAmount !== '0' && item.balanceAmount !== '0') { + if (item.unitTypeId === "3") { + if (item.usageAmount !== "0" && item.balanceAmount !== "0") { this.flow.percent = Math.floor( - (item.balanceAmount / (item.ratableAmount || 1)) * 100, + (item.balanceAmount / (item.ratableAmount || 1)) * 100 ); const flow = this.formatFlow(item.balanceAmount); this.flow.count = flow.count; @@ -155,19 +155,19 @@ class Widget extends DmYY { }); } else { this.flow.percent = Math.floor( - (detail.balance / (detail.total || 1)) * 100, + (detail.balance / (detail.total || 1)) * 100 ); const flow = this.formatFlow(detail.balance); this.flow.count = flow.count; this.flow.unit = flow.unit; this.flow.max = detail.total; } - } if (balance.result === 0) { // 余额 this.phoneBill.count = parseFloat( - parseInt(balance.totalBalanceAvailable) / 100).toFixed(2) + parseInt(balance.totalBalanceAvailable) / 100 + ).toFixed(2); } this.phoneBill.percent = Math.floor((this.phoneBill.count / 100) * 100); }; @@ -194,13 +194,13 @@ class Widget extends DmYY { canvas.setFillColor(color); for (let t = 0; t < degree; t++) { const rect_x = - ctr.x + - (this.canvRadius - radiusOffset) * this.sinDeg(t) - - this.canvWidth / 2; + ctr.x + + (this.canvRadius - radiusOffset) * this.sinDeg(t) - + this.canvWidth / 2; const rect_y = - ctr.y - - (this.canvRadius - radiusOffset) * this.cosDeg(t) - - this.canvWidth / 2; + ctr.y - + (this.canvRadius - radiusOffset) * this.cosDeg(t) - + this.canvWidth / 2; const rect_r = new Rect(rect_x, rect_y, this.canvWidth, this.canvWidth); canvas.fillEllipse(rect_r); } @@ -208,10 +208,10 @@ class Widget extends DmYY { drawText(txt, canvas, txtOffset, fontSize) { const txtRect = new Rect( - this.canvTextSize / 2 - 20, - txtOffset - this.canvTextSize / 2, - this.canvSize, - this.canvTextSize, + this.canvTextSize / 2 - 20, + txtOffset - this.canvTextSize / 2, + this.canvSize, + this.canvTextSize ); canvas.setTextColor(this.percentColor); canvas.setFont(Font.boldSystemFont(fontSize)); @@ -238,10 +238,10 @@ class Widget extends DmYY { const canvas = this.makeCanvas(); stackCircle.size = new Size(70, 70); this.makeCircle( - canvas, - this.dayRadiusOffset, - data.percent * 3.6, - data.circleColor, + canvas, + this.dayRadiusOffset, + data.percent * 3.6, + data.circleColor ); this.drawText(data.percent, canvas, 75, 18); @@ -251,8 +251,8 @@ class Widget extends DmYY { stackCircle.setPadding(20, 0, 0, 0); stackCircle.addSpacer(); const icon = data.urlIcon - ? {image: data.icon} - : SFSymbol.named(data.icon); + ? { image: data.icon } + : SFSymbol.named(data.icon); const imageIcon = stackCircle.addImage(icon.image); imageIcon.tintColor = this.iconColor; imageIcon.imageSize = new Size(15, 15); @@ -293,11 +293,11 @@ class Widget extends DmYY { const stackFooter = stackBody.addStack(); stackFooter.addSpacer(); const text = this.textFormat.defaultText; - text.color = new Color('#aaa'); + text.color = new Color("#aaa"); this.provideText( - `电信更新:${this.arrUpdateTime[2]}:${this.arrUpdateTime[3]}`, - stackFooter, - text, + `电信更新:${this.arrUpdateTime[2]}:${this.arrUpdateTime[3]}`, + stackFooter, + text ); stackFooter.addSpacer(); return w; @@ -312,8 +312,8 @@ class Widget extends DmYY { const stackBottom = stackBody.addStack(); this.setCircleText(stackBottom, this.voice); this.updateTime.icon = await this.$request.get( - this.updateTime.urlIcon, - 'IMG', + this.updateTime.urlIcon, + "IMG" ); this.setCircleText(stackBottom, this.updateTime); return w; @@ -325,18 +325,18 @@ class Widget extends DmYY { renderWebView = async () => { const webView = new WebView(); - const url = 'https://e.189.cn/index.do'; + const url = "https://e.189.cn/index.do"; await webView.loadURL(url); await webView.present(false); const request = new Request(this.fetchUri.detail); - request.method = 'POST'; + request.method = "POST"; const response = await request.loadJSON(); console.log(response); if (response.result === -10001) { - const index = await this.generateAlert('未获取到用户信息', [ - '取消', - '重试', + const index = await this.generateAlert("未获取到用户信息", [ + "取消", + "重试", ]); if (index === 0) return; await this.renderWebView(); @@ -344,7 +344,7 @@ class Widget extends DmYY { const cookies = request.response.cookies; let cookie = []; cookie = cookies.map((item) => `${item.name}=${item.value}`); - cookie = cookie.join('; '); + cookie = cookie.join("; "); this.settings.cookie = cookie; this.saveSettings(); } @@ -352,35 +352,45 @@ class Widget extends DmYY { Run() { if (config.runsInApp) { - const widgetInitConfig = {cookie: 'china_telecom_cookie'}; - this.registerAction('颜色配置', async () => { + const widgetInitConfig = { cookie: "china_telecom_cookie" }; + this.registerAction("颜色配置", async () => { await this.setAlertInput( - `${this.name}颜色配置`, - '进度条颜色|底圈颜色\n图标颜色|比值颜色|值颜色', - { - step1: '进度颜色 1', - step2: '进度颜色 2', - step3: '进度颜色 3', - step4: '进度颜色 4', - inner: '底圈颜色', - icon: '图标颜色', - percent: '比值颜色', - value: '值颜色', - }, + `${this.name}颜色配置`, + "进度条颜色|底圈颜色\n图标颜色|比值颜色|值颜色", + { + step1: "进度颜色 1", + step2: "进度颜色 2", + step3: "进度颜色 3", + step4: "进度颜色 4", + inner: "底圈颜色", + icon: "图标颜色", + percent: "比值颜色", + value: "值颜色", + } ); }); - this.registerAction('账号设置', async () => { - const index = await this.generateAlert('设置账号信息', [ - '取消', - '网站登录', + this.registerAction("账号设置", async () => { + const index = await this.generateAlert("设置账号信息", [ + "取消", + "手动输入", + "网站登录", ]); if (index === 0) return; - await this.renderWebView(); + + if (index === 1) + return this.setAlertInput( + `${this.name}账号设置`, + "手动输入电信 COOKIE", + { + cookie: "电信 cookie", + } + ); + return this.renderWebView(); }); - this.registerAction('代理缓存', async () => { + this.registerAction("代理缓存", async () => { await this.setCacheBoxJSData(widgetInitConfig); }); - this.registerAction('基础设置', this.setWidgetConfig); + this.registerAction("基础设置", this.setWidgetConfig); } const { step1, @@ -419,9 +429,9 @@ class Widget extends DmYY { const widget = new ListWidget(); widget.setPadding(0, 0, 0, 0); await this.getWidgetBackgroundImage(widget); - if (this.widgetFamily === 'medium') { + if (this.widgetFamily === "medium") { return await this.renderMedium(widget); - } else if (this.widgetFamily === 'large') { + } else if (this.widgetFamily === "large") { return await this.renderLarge(widget); } else { return await this.renderSmall(widget); diff --git a/Scripts/Contact.js b/Scripts/Contact.js index 9e58caf..9237e34 100644 --- a/Scripts/Contact.js +++ b/Scripts/Contact.js @@ -3,219 +3,239 @@ // icon-color: deep-green; icon-glyph: mobile-alt; // 添加require,是为了vscode中可以正确引入包,以获得自动补全等功能 -if (typeof require === "undefined") require = importModule; -const { DmYY, Runing } = require("./DmYY"); +if (typeof require === 'undefined') require = importModule; +const { DmYY, Runing } = require('./DmYY'); // @组件代码开始 class Widget extends DmYY { - constructor(arg) { - super(arg); - this.name = "桌面联系人"; - this.en = "ContactTable"; - this.userName = arg || "Ya"; - this.Run(); - } - - - today = ""; - useBoxJS = false; - dataSource = {}; - phoneNumber = {}; - size1 = new Size(15, 15); - size2 = new Size(30, 30); - - - init = async () => { - try { - const cardAll = await ContactsContainer.all(); - const data = await Contact.all(cardAll); - if (!this.userName) { - this.dataSource = data[0]; - } else { - this.dataSource = data.find(item => { - return (item.familyName === this.userName || - item.givenName === this.userName || - item.nickname === this.userName || - `${item.familyName}${item.givenName}` === this.userName - ); - }); - } - if (!this.dataSource) return this.notify(this.name, "未找到通讯录相关联系人,请重新设置"); - this.userName = `${this.dataSource.familyName}${this.dataSource.givenName}`; - const phoneNumbers = this.dataSource.phoneNumbers; - if (phoneNumbers.length) { - this.phoneNumber = phoneNumbers[0]; - this.phoneNumber.value = this.phoneNumber.value.replaceAll(" ", ""); - } - } catch (e) { - console.log(e); - } - }; - - setAvatar = (w) => { - const stackBody = w.addStack(); - const stackLeft = stackBody.addStack(); - stackLeft.setPadding(10, 10, 10, 0); - stackLeft.layoutVertically(); - stackLeft.addSpacer(); - const stackAvatar = stackLeft.addStack(); - stackAvatar.centerAlignContent(); - stackAvatar.size = new Size(80, 80); - stackAvatar.borderWidth = 7; - stackAvatar.borderColor = new Color("#222", 0.7); - stackAvatar.cornerRadius = 40; - if (this.dataSource.image) { - const imgAvatar = stackAvatar.addImage(this.dataSource.image); - imgAvatar.imageSize = new Size(80, 80); - } else { - let textFormat = this.textFormat.title; - textFormat.color = this.widgetColor; - textFormat.size = 42; - this.provideText(this.userName.substr(0, 1) || "", stackAvatar, textFormat); - } - stackLeft.addSpacer(); - stackBody.addSpacer(5); - return stackBody; - }; - - setContentCenter = (stackBody) => { - const stackCenter = stackBody.addStack(); - stackCenter.setPadding(10, 0, 10, 10); - stackCenter.layoutVertically(); - stackCenter.addSpacer(); - const stackUsername = stackCenter.addStack(); - stackUsername.centerAlignContent(); - stackCenter.addSpacer(15); - const stackPhoneNumber = stackCenter.addStack(); - stackPhoneNumber.centerAlignContent(); - stackCenter.addSpacer(15); - const stackNote = stackCenter.addStack(); - stackNote.centerAlignContent(); - stackCenter.addSpacer(); - - let textFormat = this.textFormat.defaultText; - textFormat.color = this.widgetColor; - textFormat.size = 18; - const phoneNumber = this.phoneNumber.value || ""; - - const iconPerson = SFSymbol.named("person"); - const imgPerson = stackUsername.addImage(iconPerson.image); - imgPerson.tintColor = this.widgetColor; - imgPerson.imageSize = this.size1; - stackUsername.addSpacer(5); - - const iconPhone = SFSymbol.named("iphone"); - const imgIphone = stackPhoneNumber.addImage(iconPhone.image); - imgIphone.tintColor = this.widgetColor; - imgIphone.imageSize = this.size1; - stackPhoneNumber.addSpacer(5); - - const iconNote = SFSymbol.named("envelope"); - const imgNote = stackNote.addImage(iconNote.image); - imgNote.tintColor = this.widgetColor; - imgNote.imageSize = this.size1; - stackNote.addSpacer(5); - - const data = this.dataSource.emailAddresses; - const email = data.length ? data[0] : {}; - this.provideText(this.userName || "", stackUsername, textFormat); - this.provideText(phoneNumber || "", stackPhoneNumber, textFormat); - const mailTextItem = this.provideText(email.value || "", stackNote, textFormat); - mailTextItem.lineLimit = 1; - - stackBody.addSpacer(); - return stackBody; - }; - - stepActionRight = (stackBody) => { - const stackRight = stackBody.addStack(); - stackRight.setPadding(10, 20, 10, 20); - stackRight.layoutVertically(); - stackRight.backgroundColor = this.widgetOpacityColor; - - stackRight.addSpacer(); - const stackCallPhone = stackRight.addStack(); - stackRight.addSpacer(); - const stackSendMessage = stackRight.addStack(); - stackRight.addSpacer(); - const stackDetail = stackRight.addStack(); - stackRight.addSpacer(); - - const phone = this.phoneNumber.value || ""; - const data = this.dataSource.emailAddresses; - const email = data.length ? data[0] : {}; - - stackCallPhone.url = `tel:${phone}`; - stackSendMessage.url = `sms:${phone}`; - stackDetail.url = `mailto:${email.value || ""}`; - - const iconVideo = SFSymbol.named("video"); - const imgVideo = stackCallPhone.addImage(iconVideo.image); - imgVideo.tintColor = this.backGroundColor; - imgVideo.imageSize = this.size2; - - const iconMessage = SFSymbol.named("message"); - const imgMessage = stackSendMessage.addImage(iconMessage.image); - imgMessage.tintColor = this.backGroundColor; - imgMessage.imageSize = this.size2; - - const iconEnvelope = SFSymbol.named("envelope.open"); - const imgEnvelope = stackDetail.addImage(iconEnvelope.image); - imgEnvelope.tintColor = this.backGroundColor; - imgEnvelope.imageSize = this.size2; - - return stackBody; - }; - - renderSmall = (w) => { - this.setContentCenter(stackBody); - this.stepActionRight(stackBody); - return w; - }; - - renderLarge = (w) => { - const stackBody = this.setAvatar(w); - this.setContentCenter(stackBody); - this.stepActionRight(stackBody); - return w; - }; - - renderMedium = (w) => { - const stackBody = this.setAvatar(w); - this.setContentCenter(stackBody); - this.stepActionRight(stackBody); - return w; - }; - - Run() { - if (config.runsInApp) { - this.registerAction("右侧透明", async () => { - await this.setAlertInput("右侧透明", "若不需要右侧背景设置透明度 0 即可", { rightOpacity: "透明度 0~1" }); - }); - this.registerAction("基础设置", this.setWidgetConfig); - } - const light = new Color(this.settings.lightColor, parseInt(this.settings.rightOpacity || 1)); - const dark = new Color(this.settings.darkColor, parseInt(this.settings.rightOpacity || 1)); - this.widgetOpacityColor = Color.dynamic(light, dark); - } - - /** - * 渲染函数,函数名固定 - * 可以根据 this.widgetFamily 来判断小组件尺寸,以返回不同大小的内容 - */ - async render() { - await this.init(); - const widget = new ListWidget(); - widget.setPadding(0, 0, 0, 0); - await this.getWidgetBackgroundImage(widget); - if (this.widgetFamily === "medium") { - return await this.renderMedium(widget); - } else if (this.widgetFamily === "large") { - return await this.renderLarge(widget); - } else { - return await this.renderSmall(widget); - } - } + constructor(arg) { + super(arg); + this.name = '桌面联系人'; + this.en = 'ContactTable'; + this.userName = arg || 'Ya'; + this.Run(); + } + + today = ''; + useBoxJS = false; + dataSource = {}; + phoneNumber = {}; + size1 = new Size(15, 15); + size2 = new Size(30, 30); + + init = async () => { + try { + const cardAll = await ContactsContainer.all(); + const data = await Contact.all(cardAll); + if (!this.userName) { + this.dataSource = data[0]; + } else { + this.dataSource = data.find((item) => { + return ( + item.familyName === this.userName || + item.givenName === this.userName || + item.nickname === this.userName || + `${item.familyName}${item.givenName}` === this.userName + ); + }); + } + if (!this.dataSource) + return this.notify(this.name, '未找到通讯录相关联系人,请重新设置'); + this.userName = `${this.dataSource.familyName}${this.dataSource.givenName}`; + const phoneNumbers = this.dataSource.phoneNumbers; + if (phoneNumbers.length) { + this.phoneNumber = phoneNumbers[0]; + this.phoneNumber.value = this.phoneNumber.value.replaceAll(' ', ''); + } + } catch (e) { + console.log(e); + } + }; + + setAvatar = (w) => { + const stackBody = w.addStack(); + const stackLeft = stackBody.addStack(); + stackLeft.setPadding(10, 10, 10, 0); + stackLeft.layoutVertically(); + stackLeft.addSpacer(); + const stackAvatar = stackLeft.addStack(); + stackAvatar.centerAlignContent(); + stackAvatar.size = new Size(80, 80); + stackAvatar.borderWidth = 7; + stackAvatar.borderColor = new Color('#222', 0.7); + stackAvatar.cornerRadius = 40; + if (this.dataSource.image) { + const imgAvatar = stackAvatar.addImage(this.dataSource.image); + imgAvatar.imageSize = new Size(80, 80); + } else { + let textFormat = this.textFormat.title; + textFormat.color = this.widgetColor; + textFormat.size = 42; + this.provideText( + this.userName.substr(0, 1) || '', + stackAvatar, + textFormat + ); + } + stackLeft.addSpacer(); + stackBody.addSpacer(5); + return stackBody; + }; + + setContentCenter = (stackBody) => { + const stackCenter = stackBody.addStack(); + stackCenter.setPadding(10, 0, 10, 10); + stackCenter.layoutVertically(); + stackCenter.addSpacer(); + const stackUsername = stackCenter.addStack(); + stackUsername.centerAlignContent(); + stackCenter.addSpacer(15); + const stackPhoneNumber = stackCenter.addStack(); + stackPhoneNumber.centerAlignContent(); + stackCenter.addSpacer(15); + const stackNote = stackCenter.addStack(); + stackNote.centerAlignContent(); + stackCenter.addSpacer(); + + let textFormat = this.textFormat.defaultText; + textFormat.color = this.widgetColor; + textFormat.size = 18; + const phoneNumber = this.phoneNumber.value || ''; + + const iconPerson = SFSymbol.named('person'); + const imgPerson = stackUsername.addImage(iconPerson.image); + imgPerson.tintColor = this.widgetColor; + imgPerson.imageSize = this.size1; + stackUsername.addSpacer(5); + + const iconPhone = SFSymbol.named('iphone'); + const imgIphone = stackPhoneNumber.addImage(iconPhone.image); + imgIphone.tintColor = this.widgetColor; + imgIphone.imageSize = this.size1; + stackPhoneNumber.addSpacer(5); + + const iconNote = SFSymbol.named('envelope'); + const imgNote = stackNote.addImage(iconNote.image); + imgNote.tintColor = this.widgetColor; + imgNote.imageSize = this.size1; + stackNote.addSpacer(5); + + const data = this.dataSource.emailAddresses; + const email = data.length ? data[0] : {}; + this.provideText(this.userName || '', stackUsername, textFormat); + this.provideText(phoneNumber || '', stackPhoneNumber, textFormat); + const mailTextItem = this.provideText( + email.value || '', + stackNote, + textFormat + ); + mailTextItem.lineLimit = 1; + + stackBody.addSpacer(); + return stackBody; + }; + + stepActionRight = (stackBody) => { + const stackRight = stackBody.addStack(); + stackRight.setPadding(10, 20, 10, 20); + stackRight.layoutVertically(); + stackRight.backgroundColor = this.widgetOpacityColor; + + stackRight.addSpacer(); + const stackCallPhone = stackRight.addStack(); + stackRight.addSpacer(); + const stackSendMessage = stackRight.addStack(); + stackRight.addSpacer(); + const stackDetail = stackRight.addStack(); + stackRight.addSpacer(); + + const phone = this.phoneNumber.value || ''; + const data = this.dataSource.emailAddresses; + const email = data.length ? data[0] : {}; + + stackCallPhone.url = `tel:${phone}`; + stackSendMessage.url = `sms:${phone}`; + stackDetail.url = `mailto:${email.value || ''}`; + + const iconVideo = SFSymbol.named('video'); + const imgVideo = stackCallPhone.addImage(iconVideo.image); + imgVideo.tintColor = this.backGroundColor; + imgVideo.imageSize = this.size2; + + const iconMessage = SFSymbol.named('message'); + const imgMessage = stackSendMessage.addImage(iconMessage.image); + imgMessage.tintColor = this.backGroundColor; + imgMessage.imageSize = this.size2; + + const iconEnvelope = SFSymbol.named('envelope.open'); + const imgEnvelope = stackDetail.addImage(iconEnvelope.image); + imgEnvelope.tintColor = this.backGroundColor; + imgEnvelope.imageSize = this.size2; + + return stackBody; + }; + + renderSmall = (w) => { + this.setContentCenter(stackBody); + this.stepActionRight(stackBody); + return w; + }; + + renderLarge = (w) => { + const stackBody = this.setAvatar(w); + this.setContentCenter(stackBody); + this.stepActionRight(stackBody); + return w; + }; + + renderMedium = (w) => { + const stackBody = this.setAvatar(w); + this.setContentCenter(stackBody); + this.stepActionRight(stackBody); + return w; + }; + + Run() { + if (config.runsInApp) { + this.registerAction({ + icon: { name: 'phone', color: '#722ed1' }, + type: 'input', + title: '右侧透明', + desc: '若不需要右侧背景设置透明度 0 即可', + placeholder: '透明度 0~1', + val: 'rightOpacity', + }); + + this.registerAction('基础设置', this.setWidgetConfig); + } + const light = new Color( + this.settings.lightColor, + parseInt(this.settings.rightOpacity || 1) + ); + const dark = new Color( + this.settings.darkColor, + parseInt(this.settings.rightOpacity || 1) + ); + this.widgetOpacityColor = Color.dynamic(light, dark); + } + + /** + * 渲染函数,函数名固定 + * 可以根据 this.widgetFamily 来判断小组件尺寸,以返回不同大小的内容 + */ + async render() { + await this.init(); + const widget = new ListWidget(); + widget.setPadding(0, 0, 0, 0); + await this.getWidgetBackgroundImage(widget); + if (this.widgetFamily === 'medium') { + return await this.renderMedium(widget); + } else if (this.widgetFamily === 'large') { + return await this.renderLarge(widget); + } else { + return await this.renderSmall(widget); + } + } } // @组件代码结束 diff --git a/Scripts/DmYY.js b/Scripts/DmYY.js index 30f5310..04bee30 100644 --- a/Scripts/DmYY.js +++ b/Scripts/DmYY.js @@ -5,175 +5,210 @@ /* * Author: 2Ya * Github: https://github.com/dompling + * UI 配置升级 感谢 @LSP 大佬提供代码 */ class DmYY { - constructor(arg) { - this.arg = arg - try { - this.init() - } catch (error) { - console.log(error) - } - this.isNight = Device.isUsingDarkAppearance() + constructor(arg, defaultSettings) { + this.arg = arg; + this.defaultSettings = defaultSettings || {}; + this.SETTING_KEY = this.md5(Script.name()); + this._init(); + this.isNight = Device.isUsingDarkAppearance(); } - _actions = {} - BACKGROUND_NIGHT_KEY - widgetColor - backGroundColor - useBoxJS = true - isNight - _actionsIcon = {} + BaseCacheKey = 'DmYY'; + _actions = []; + _menuActions = []; + widgetColor; + backGroundColor; + isNight; + + userConfigKey = ['avatar', 'nickname', 'homePageDesc']; // 获取 Request 对象 getRequest = (url = '') => { - return new Request(url) - } + return new Request(url); + }; // 发起请求 - http = async (options = { headers: {}, url: '' }, type = 'JSON') => { + http = async ( + options = { headers: {}, url: '' }, + type = 'JSON', + onError = () => { + return SFSymbol.named('photo').image; + } + ) => { + let request; try { - let request - if (type !== 'IMG') { - request = this.getRequest() - Object.keys(options).forEach((key) => { - request[key] = options[key] - }) - request.headers = { ...this.defaultHeaders, ...options.headers } - } else { - request = this.getRequest(options.url) - return (await request.loadImage()) || SFSymbol.named('photo').image + if (type === 'IMG') { + const fileName = `${this.cacheImage}/${this.md5(options.url)}`; + request = this.getRequest(options.url); + let response; + if (await this.FILE_MGR.fileExistsExtra(fileName)) { + request.loadImage().then((res) => { + this.FILE_MGR.writeImage(fileName, res); + }); + return Image.fromFile(fileName); + } else { + response = await request.loadImage(); + this.FILE_MGR.writeImage(fileName, response); + } + return response; } + request = this.getRequest(); + Object.keys(options).forEach((key) => { + request[key] = options[key]; + }); + request.headers = { ...this.defaultHeaders, ...options.headers }; + if (type === 'JSON') { - return await request.loadJSON() + return await request.loadJSON(); } if (type === 'STRING') { - return await request.loadString() + return await request.loadString(); } - return await request.loadJSON() + return await request.loadJSON(); } catch (e) { - console.log('error:' + e) - if (type === 'IMG') return SFSymbol.named('photo').image + console.log('error:' + e); + if (type === 'IMG') return onError?.(); } - } + }; //request 接口请求 $request = { - get: async (url = '', options = {}, type = 'JSON') => { - let params = { ...options, method: 'GET' } + get: (url = '', options = {}, type = 'JSON') => { + let params = { ...options, method: 'GET' }; if (typeof url === 'object') { - params = { ...params, ...url } + params = { ...params, ...url }; } else { - params.url = url + params.url = url; } - let _type = type - if (typeof options === 'string') _type = options - return await this.http(params, _type) + let _type = type; + if (typeof options === 'string') _type = options; + return this.http(params, _type); }, - post: async (url = '', options = {}, type = 'JSON') => { - let params = { ...options, method: 'POST' } + post: (url = '', options = {}, type = 'JSON') => { + let params = { ...options, method: 'POST' }; if (typeof url === 'object') { - params = { ...params, ...url } + params = { ...params, ...url }; } else { - params.url = url + params.url = url; } - let _type = type - if (typeof options === 'string') _type = options - return await this.http(params, _type) + let _type = type; + if (typeof options === 'string') _type = options; + return this.http(params, _type); }, - } + }; // 获取 boxJS 缓存 - getCache = async (key = '') => { + getCache = async (key = '', notify = true) => { try { - let url = 'http://' + this.prefix + '/query/boxdata' - if (key) url = 'http://' + this.prefix + '/query/data/' + key - const boxdata = await this.$request.get(url) - if (boxdata.val) return boxdata.val - return boxdata.datas + let url = 'http://' + this.prefix + '/query/boxdata'; + if (key) url = 'http://' + this.prefix + '/query/data/' + key; + const boxdata = await this.$request.get( + url, + key ? { timeoutInterval: 1 } : {} + ); + if (key) { + this.settings.BoxJSData = { + ...this.settings.BoxJSData, + [key]: boxdata.val, + }; + this.saveSettings(false); + } + if (boxdata.val) return boxdata.val; + + return boxdata.datas; } catch (e) { - console.log('boxjs 数据读取失败') - await this.notify( - `${this.name} - BoxJS 数据读取失败`, - '请检查 BoxJS 域名是否为代理复写的域名,如(boxjs.net 或 boxjs.com)。\n若没有配置 BoxJS 相关模块,请点击通知查看教程', - 'https://chavyleung.gitbook.io/boxjs/awesome/videos' - ) - return false + if (key && this.settings.BoxJSData[key]) { + return this.settings.BoxJSData[key]; + } + if (notify) + await this.notify( + `${this.name} - BoxJS 数据读取失败`, + '请检查 BoxJS 域名是否为代理复写的域名,如(boxjs.net 或 boxjs.com)。\n若没有配置 BoxJS 相关模块,请点击通知查看教程', + 'https://chavyleung.gitbook.io/boxjs/awesome/videos' + ); + return false; } - } + }; transforJSON = (str) => { if (typeof str == 'string') { try { - return JSON.parse(str) + return JSON.parse(str); } catch (e) { - console.log(e) - return str + console.log(e); + return str; } } - console.log('It is not a string!') - } + console.log('It is not a string!'); + }; // 选择图片并缓存 - chooseImg = async () => { - return await Photos.fromLibrary() - } + chooseImg = async (verify = false) => { + const response = await Photos.fromLibrary().catch((err) => { + console.log('图片选择异常:' + err); + }); + if (verify) { + const bool = await this.verifyImage(response); + if (bool) return response; + return null; + } + return response; + }; // 设置 widget 背景图片 getWidgetBackgroundImage = async (widget) => { - const backgroundImage = this.getBackgroundImage() + const backgroundImage = await this.getBackgroundImage(); if (backgroundImage) { const opacity = Device.isUsingDarkAppearance() ? Number(this.settings.darkOpacity) - : Number(this.settings.lightOpacity) + : Number(this.settings.lightOpacity); widget.backgroundImage = await this.shadowImage( backgroundImage, '#000', opacity - ) - return true + ); + return true; } else { if (this.backGroundColor.colors) { - widget.backgroundGradient = this.backGroundColor + widget.backgroundGradient = this.backGroundColor; } else { - widget.backgroundColor = this.backGroundColor + widget.backgroundColor = this.backGroundColor; } - return false + return false; } - } + }; /** * 验证图片尺寸: 图片像素超过 1000 左右的时候会导致背景无法加载 * @param img Image */ - verifyImage = async (img) => { - try { - const { width, height } = img.size - const direct = true - if (width > 1000) { - const options = ['取消', '打开图像处理'] - const message = - '您的图片像素为' + - width + - ' x ' + - height + - '\n' + - '请将图片' + - (direct ? '宽度' : '高度') + - '调整到 1000 以下\n' + - (!direct ? '宽度' : '高度') + - '自动适应' - const index = await this.generateAlert(message, options) - if (index === 1) - Safari.openInApp('https://www.sojson.com/image/change.html', false) - return false - } - return true - } catch (e) { - return false + verifyImage = async (img = {}) => { + const { width, height } = img.size; + const direct = true; + if (width > 1000) { + const options = ['取消', '打开图像处理']; + const message = + '您的图片像素为' + + width + + ' x ' + + height + + '\n' + + '请将图片' + + (direct ? '宽度' : '高度') + + '调整到 1000 以下\n' + + (!direct ? '宽度' : '高度') + + '自动适应'; + const index = await this.generateAlert(message, options); + if (index === 1) + Safari.openInApp('https://www.sojson.com/image/change.html', false); + return false; } - } + return true; + }; /** * 获取截图中的组件剪裁图 @@ -185,17 +220,153 @@ class DmYY { async getWidgetScreenShot(title = null) { // Crop an image into the specified rect. function cropImage(img, rect) { - let draw = new DrawContext() - draw.size = new Size(rect.width, rect.height) + let draw = new DrawContext(); + draw.size = new Size(rect.width, rect.height); - draw.drawImageAtPoint(img, new Point(-rect.x, -rect.y)) - return draw.getImage() + draw.drawImageAtPoint(img, new Point(-rect.x, -rect.y)); + return draw.getImage(); } - // Pixel sizes and positions for widgets on all supported phones. - function phoneSizes() { + function phoneSizes(inputHeight) { return { - // 12 Pro Max + /* + + Supported devices + ================= + The following device measurements have been confirmed in iOS 18. + + */ + + // 16 Pro Max + 2868: { + text: { + small: 510, + medium: 1092, + large: 1146, + left: 114, + right: 696, + top: 276, + middle: 912, + bottom: 1548, + }, + notext: { + small: 530, + medium: 1138, + large: 1136, + left: 91, + right: 699, + top: 276, + middle: 882, + bottom: 1488, + }, + }, + + // 16 Plus, 15 Plus, 15 Pro Max, 14 Pro Max + 2796: { + text: { + small: 510, + medium: 1092, + large: 1146, + left: 98, + right: 681, + top: 252, + middle: 888, + bottom: 1524, + }, + notext: { + small: 530, + medium: 1139, + large: 1136, + left: 75, + right: 684, + top: 252, + middle: 858, + bottom: 1464, + }, + }, + + // 16 Pro + 2622: { + text: { + small: 486, + medium: 1032, + large: 1098, + left: 87, + right: 633, + top: 261, + middle: 872, + bottom: 1485, + }, + notext: { + small: 495, + medium: 1037, + large: 1035, + left: 84, + right: 626, + top: 270, + middle: 810, + bottom: 1350, + }, + }, + + // 16, 15, 15 Pro, 14 Pro + 2556: { + text: { + small: 474, + medium: 1017, + large: 1062, + left: 81, + right: 624, + top: 240, + middle: 828, + bottom: 1416, + }, + notext: { + small: 495, + medium: 1047, + large: 1047, + left: 66, + right: 618, + top: 243, + middle: 795, + bottom: 1347, + }, + }, + + // SE3, SE2 + 1334: { + text: { + small: 296, + medium: 642, + large: 648, + left: 54, + right: 400, + top: 60, + middle: 412, + bottom: 764, + }, + notext: { + small: 309, + medium: 667, + large: 667, + left: 41, + right: 399, + top: 67, + middle: 425, + bottom: 783, + }, + }, + + /* + + In-limbo devices + ================= + The following device measurements were confirmed in older versions of iOS. + Please comment if you can confirm these for iOS 18. + + */ + + // 14 Plus, 13 Pro Max, 12 Pro Max 2778: { small: 510, medium: 1092, @@ -207,18 +378,6 @@ class DmYY { bottom: 1518, }, - // 12 and 12 Pro - 2532: { - small: 474, - medium: 1014, - large: 1062, - left: 78, - right: 618, - top: 231, - middle: 819, - bottom: 1407, - }, - // 11 Pro Max, XS Max 2688: { small: 507, @@ -231,19 +390,19 @@ class DmYY { bottom: 1488, }, - // 11, XR - 1792: { - small: 338, - medium: 720, - large: 758, - left: 54, - right: 436, - top: 160, - middle: 580, - bottom: 1000, + // 14, 13, 13 Pro, 12, 12 Pro + 2532: { + small: 474, + medium: 1014, + large: 1062, + left: 78, + right: 618, + top: 231, + middle: 819, + bottom: 1407, }, - // 11 Pro, XS, X, 12 mini + // 13 mini, 12 mini / 11 Pro, XS, X 2436: { x: { small: 465, @@ -255,7 +414,6 @@ class DmYY { middle: 783, bottom: 1353, }, - mini: { small: 465, medium: 987, @@ -268,40 +426,16 @@ class DmYY { }, }, - // Plus phones - 2208: { - small: 471, - medium: 1044, - large: 1071, - left: 99, - right: 672, - top: 114, - middle: 696, - bottom: 1278, - }, - - // SE2 and 6/6S/7/8 - 1334: { - small: 296, - medium: 642, - large: 648, - left: 54, - right: 400, - top: 60, - middle: 412, - bottom: 764, - }, - - // SE1 - 1136: { - small: 282, - medium: 584, - large: 622, - left: 30, - right: 332, - top: 59, - middle: 399, - bottom: 399, + // 11, XR + 1792: { + small: 338, + medium: 720, + large: 758, + left: 55, + right: 437, + top: 159, + middle: 579, + bottom: 999, }, // 11 and XR in Display Zoom mode @@ -316,7 +450,27 @@ class DmYY { bottom: 902, }, - // Plus in Display Zoom mode + /* + + Older devices + ================= + The following devices cannot be updated to iOS 18 or later. + + */ + + // Home button Plus phones + 2208: { + small: 471, + medium: 1044, + large: 1071, + left: 99, + right: 672, + top: 114, + middle: 696, + bottom: 1278, + }, + + // Home button Plus in Display Zoom mode 2001: { small: 444, medium: 963, @@ -327,63 +481,83 @@ class DmYY { middle: 618, bottom: 1146, }, - } + + // SE1 + 1136: { + small: 282, + medium: 584, + large: 622, + left: 30, + right: 332, + top: 59, + middle: 399, + bottom: 399, + }, + }[inputHeight]; } let message = - title || '开始之前,请先前往桌面,截取空白界面的截图。然后回来继续' - let exitOptions = ['我已截图', '前去截图 >'] - let shouldExit = await this.generateAlert(message, exitOptions) - if (shouldExit) return + title || '开始之前,请先前往桌面,截取空白界面的截图。然后回来继续'; + let exitOptions = ['我已截图', '前去截图 >']; + let shouldExit = await this.generateAlert(message, exitOptions); + if (shouldExit) return; // Get screenshot and determine phone size. - let img = await Photos.fromLibrary() - let height = img.size.height - let phone = phoneSizes()[height] + let img = await Photos.fromLibrary(); + let height = img.size.height; + let phone = phoneSizes(height); if (!phone) { - message = '好像您选择的照片不是正确的截图,请先前往桌面' - await this.generateAlert(message, ['我已知晓']) - return + message = '好像您选择的照片不是正确的截图,请先前往桌面'; + await this.generateAlert(message, ['我已知晓']); + return; } - // Extra setup needed for 2436-sized phones. if (height === 2436) { - const files = this.FILE_MGR_LOCAL - let cacheName = 'mz-phone-type' - let cachePath = files.joinPath(files.libraryDirectory(), cacheName) + const files = this.FILE_MGR_LOCAL; + let cacheName = 'mz-phone-type'; + let cachePath = files.joinPath(files.libraryDirectory(), cacheName); // If we already cached the phone size, load it. if (files.fileExists(cachePath)) { - let typeString = files.readString(cachePath) - phone = phone[typeString] + let typeString = files.readString(cachePath); + phone = phone[typeString]; // Otherwise, prompt the user. } else { - message = '您的📱型号是?' - let types = ['iPhone 12 mini', 'iPhone 11 Pro, XS, or X'] - let typeIndex = await this.generateAlert(message, types) - let type = typeIndex === 0 ? 'mini' : 'x' - phone = phone[type] - files.writeString(cachePath, type) + message = '您的📱型号是?'; + let types = ['iPhone 12 mini', 'iPhone 11 Pro, XS, or X']; + let typeIndex = await this.generateAlert(message, types); + let type = typeIndex === 0 ? 'mini' : 'x'; + phone = phone[type]; + files.writeString(cachePath, type); } } + // If supported, check whether home screen has text labels or not. + if (phone.text) { + message = '主屏幕是否有文本标签?'; + const textOptions = ['有', '无']; + const _textOptions = ['text', 'notext']; + const textResponse = await this.generateAlert(message, textOptions); + phone = phone[_textOptions[textResponse]]; + } + // Prompt for widget size and position. - message = '截图中要设置透明背景组件的尺寸类型是?' - let sizes = ['小尺寸', '中尺寸', '大尺寸'] - let size = await this.generateAlert(message, sizes) - let widgetSize = sizes[size] + message = '截图中要设置透明背景组件的尺寸类型是?'; + let sizes = ['小尺寸', '中尺寸', '大尺寸']; + let size = await this.generateAlert(message, sizes); + let widgetSize = sizes[size]; - message = '要设置透明背景的小组件在哪个位置?' + message = '要设置透明背景的小组件在哪个位置?'; message += height === 1136 ? ' (备注:当前设备只支持两行小组件,所以下边选项中的「中间」和「底部」的选项是一致的)' - : '' + : ''; // Determine image crop based on phone size. - let crop = { w: '', h: '', x: '', y: '' } + let crop = { w: '', h: '', x: '', y: '' }; if (widgetSize === '小尺寸') { - crop.w = phone.small - crop.h = phone.small + crop.w = phone.small; + crop.h = phone.small; let positions = [ '左上角', '右上角', @@ -391,7 +565,7 @@ class DmYY { '中间右', '左下角', '右下角', - ] + ]; let _posotions = [ 'Top left', 'Top right', @@ -399,55 +573,56 @@ class DmYY { 'Middle right', 'Bottom left', 'Bottom right', - ] - let position = await this.generateAlert(message, positions) + ]; + let position = await this.generateAlert(message, positions); // Convert the two words into two keys for the phone size dictionary. - let keys = _posotions[position].toLowerCase().split(' ') - crop.y = phone[keys[0]] - crop.x = phone[keys[1]] + let keys = _posotions[position].toLowerCase().split(' '); + crop.y = phone[keys[0]]; + crop.x = phone[keys[1]]; } else if (widgetSize === '中尺寸') { - crop.w = phone.medium - crop.h = phone.small + crop.w = phone.medium; + crop.h = phone.small; // Medium and large widgets have a fixed x-value. - crop.x = phone.left - let positions = ['顶部', '中间', '底部'] - let _positions = ['Top', 'Middle', 'Bottom'] - let position = await this.generateAlert(message, positions) - let key = _positions[position].toLowerCase() - crop.y = phone[key] + crop.x = phone.left; + let positions = ['顶部', '中间', '底部']; + let _positions = ['Top', 'Middle', 'Bottom']; + let position = await this.generateAlert(message, positions); + let key = _positions[position].toLowerCase(); + crop.y = phone[key]; } else if (widgetSize === '大尺寸') { - crop.w = phone.medium - crop.h = phone.large - crop.x = phone.left - let positions = ['顶部', '底部'] - let position = await this.generateAlert(message, positions) + crop.w = phone.medium; + crop.h = phone.large; + crop.x = phone.left; + let positions = ['顶部', '底部']; + let position = await this.generateAlert(message, positions); // Large widgets at the bottom have the "middle" y-value. - crop.y = position ? phone.middle : phone.top + crop.y = position ? phone.middle : phone.top; } // Crop image and finalize the widget. - return cropImage(img, new Rect(crop.x, crop.y, crop.w, crop.h)) + return cropImage(img, new Rect(crop.x, crop.y, crop.w, crop.h)); } - setLightAndDark = async (title, desc, val) => { + setLightAndDark = async (title, desc, val, placeholder = '') => { try { - const a = new Alert() - a.title = title - a.message = desc - a.addTextField('', `${this.settings[val]}`) - a.addAction('确定') - a.addCancelAction('取消') - const id = await a.presentAlert() - if (id === -1) return - this.settings[val] = a.textFieldValue(0) - this.saveSettings() + const a = new Alert(); + a.title = title; + a.message = desc; + a.addTextField(placeholder, `${this.settings[val] || ''}`); + a.addAction('确定'); + a.addCancelAction('取消'); + const id = await a.presentAlert(); + if (id === -1) return false; + this.settings[val] = a.textFieldValue(0) || ''; + this.saveSettings(); + return true; } catch (e) { - console.log(e) + console.log(e); } - } + }; /** * 弹出输入框 @@ -457,27 +632,47 @@ class DmYY { * @returns {Promise} */ setAlertInput = async (title, desc, opt = {}, isSave = true) => { - const a = new Alert() - a.title = title - a.message = !desc ? '' : desc + const a = new Alert(); + a.title = title; + a.message = !desc ? '' : desc; Object.keys(opt).forEach((key) => { - a.addTextField(opt[key], this.settings[key]) - }) - a.addAction('确定') - a.addCancelAction('取消') - const id = await a.presentAlert() - if (id === -1) return - const data = {} + a.addTextField(opt[key], this.settings[key]); + }); + a.addAction('确定'); + a.addCancelAction('取消'); + const id = await a.presentAlert(); + if (id === -1) return; + const data = {}; Object.keys(opt).forEach((key, index) => { - data[key] = a.textFieldValue(index) - }) + data[key] = a.textFieldValue(index) || ''; + }); // 保存到本地 if (isSave) { - this.settings = { ...this.settings, ...data } - return this.saveSettings() + this.settings = { ...this.settings, ...data }; + return this.saveSettings(); } - return data - } + return data; + }; + + setBaseAlertInput = async (title, desc, opt = {}, isSave = true) => { + const a = new Alert(); + a.title = title; + a.message = !desc ? '' : desc; + Object.keys(opt).forEach((key) => { + a.addTextField(opt[key], this.baseSettings[key] || ''); + }); + a.addAction('确定'); + a.addCancelAction('取消'); + const id = await a.presentAlert(); + if (id === -1) return; + const data = {}; + Object.keys(opt).forEach((key, index) => { + data[key] = a.textFieldValue(index) || ''; + }); + // 保存到本地 + if (isSave) return this.saveBaseSettings(data); + return data; + }; /** * 设置当前项目的 boxJS 缓存 @@ -485,189 +680,32 @@ class DmYY { * @returns {Promise} */ setCacheBoxJSData = async (opt = {}) => { - const options = ['取消', '确定'] - const message = '代理缓存仅支持 BoxJS 相关的代理!' - const index = await this.generateAlert(message, options) - if (index === 0) return + const options = ['取消', '确定']; + const message = '代理缓存仅支持 BoxJS 相关的代理!'; + const index = await this.generateAlert(message, options); + if (index === 0) return; try { - const boxJSData = await this.getCache() + const boxJSData = await this.getCache(); Object.keys(opt).forEach((key) => { - this.settings[key] = boxJSData[opt[key]] || '' - }) + this.settings[key] = boxJSData[opt[key]] || ''; + }); // 保存到本地 - this.saveSettings() + this.saveSettings(); } catch (e) { - console.log(e) + console.log(e); this.notify( this.name, 'BoxJS 缓存读取失败!点击查看相关教程', 'https://chavyleung.gitbook.io/boxjs/awesome/videos' - ) + ); } - } + }; /** * 设置组件内容 * @returns {Promise} */ setWidgetConfig = async () => { - const table = new UITable() - table.showSeparators = true - await this.renderDmYYTables(table) - await table.present() - } - - async preferences(table, arr, outfit) { - let header = new UITableRow() - let heading = header.addText(outfit) - heading.titleFont = Font.mediumSystemFont(17) - heading.centerAligned() - table.addRow(header) - for (const item of arr) { - const row = new UITableRow() - row.dismissOnSelect = !!item.dismissOnSelect - if (item.url) { - const rowIcon = row.addImageAtURL(item.url) - rowIcon.widthWeight = 100 - } else { - const icon = item.icon || {} - const image = await this.drawTableIcon( - icon.name, - icon.color, - item.cornerWidth - ) - const imageCell = row.addImage(image) - imageCell.widthWeight = 100 - } - let rowTitle = row.addText(item['title']) - rowTitle.widthWeight = 400 - rowTitle.titleFont = Font.systemFont(16) - if (this.settings[item.val] || item.val) { - let valText = row.addText( - `${this.settings[item.val] || item.val}`.toUpperCase() - ) - const fontSize = !item.val ? 26 : 16 - valText.widthWeight = 500 - valText.rightAligned() - valText.titleColor = Color.blue() - valText.titleFont = Font.mediumSystemFont(fontSize) - } else { - const imgCell = UITableCell.imageAtURL( - 'https://gitee.com/scriptableJS/Scriptable/raw/master/images/more.png' - ) - imgCell.rightAligned() - imgCell.widthWeight = 500 - row.addCell(imgCell) - } - - row.onSelect = item.onClick - ? async () => { - try { - await item.onClick(item, table) - } catch (e) { - console.log(e) - } - } - : async () => { - if (item.type == 'input') { - await this.setLightAndDark( - item['title'], - item['desc'], - item['val'] - ) - } else if (item.type == 'setBackground') { - const backImage = await this.getWidgetScreenShot() - if (backImage) { - await this.setBackgroundImage(backImage, true) - await this.setBackgroundNightImage(backImage, true) - } - } else if (item.type == 'removeBackground') { - const options = ['取消', '清空'] - const message = '该操作不可逆,会清空所有背景图片!' - const index = await this.generateAlert(message, options) - if (index === 0) return - await this.setBackgroundImage(false, true) - await this.setBackgroundNightImage(false, true) - } else { - const backImage = await this.chooseImg() - if (!backImage || !(await this.verifyImage(backImage))) return - if (item.type == 'setDayBackground') - await this.setBackgroundImage(backImage, true) - if (item.type == 'setNightBackground') - await this.setBackgroundNightImage(backImage, true) - } - await this.renderDmYYTables(table) - } - table.addRow(row) - } - table.reload() - } - - drawTableIcon = async ( - icon = 'square.grid.2x2', - color = '#e8e8e8', - cornerWidth = 42 - ) => { - const sfi = SFSymbol.named(icon) - sfi.applyFont(Font.mediumSystemFont(30)) - const imgData = Data.fromPNG(sfi.image).toBase64String() - const html = ` - - - - ` - const js = ` - var canvas = document.createElement("canvas"); - var sourceImg = document.getElementById("sourceImg"); - var silhouetteImg = document.getElementById("silhouetteImg"); - var ctx = canvas.getContext('2d'); - var size = sourceImg.width > sourceImg.height ? sourceImg.width : sourceImg.height; - canvas.width = size; - canvas.height = size; - ctx.drawImage(sourceImg, (canvas.width - sourceImg.width) / 2, (canvas.height - sourceImg.height) / 2); - var imgData = ctx.getImageData(0, 0, canvas.width, canvas.height); - var pix = imgData.data; - //convert the image into a silhouette - for (var i=0, n = pix.length; i < n; i+= 4){ - //set red to 0 - pix[i] = 255; - //set green to 0 - pix[i+1] = 255; - //set blue to 0 - pix[i+2] = 255; - //retain the alpha value - pix[i+3] = pix[i+3]; - } - ctx.putImageData(imgData,0,0); - silhouetteImg.src = canvas.toDataURL(); - output=canvas.toDataURL() - ` - - let wv = new WebView() - await wv.loadHTML(html) - const base64Image = await wv.evaluateJavaScript(js) - const iconImage = await new Request(base64Image).loadImage() - const size = new Size(160, 160) - const ctx = new DrawContext() - ctx.opaque = false - ctx.respectScreenScale = true - ctx.size = size - const path = new Path() - const rect = new Rect(0, 0, size.width, size.width) - - path.addRoundedRect(rect, cornerWidth, cornerWidth) - path.closeSubpath() - ctx.setFillColor(new Color(color)) - ctx.addPath(path) - ctx.fillPath() - const rate = 36 - const iw = size.width - rate - const x = (size.width - iw) / 2 - ctx.drawImageInRect(iconImage, new Rect(x, x, iw, iw)) - return ctx.getImage() - } - - async renderDmYYTables(table) { const basic = [ { icon: { name: 'arrow.clockwise', color: '#1890ff' }, @@ -676,117 +714,968 @@ class DmYY { desc: '刷新时间仅供参考,具体刷新时间由系统判断,单位:分钟', val: 'refreshAfterDate', }, - { - icon: { name: 'photo', color: '#13c2c2' }, - type: 'input', - title: '白天背景颜色', - desc: '请自行去网站上搜寻颜色(Hex 颜色)\n支持渐变色,各颜色之间以英文逗号分隔', - val: 'lightBgColor', - }, - { - icon: { name: 'photo.fill', color: '#52c41a' }, - type: 'input', - title: '晚上背景颜色', - desc: '请自行去网站上搜寻颜色(Hex 颜色)\n支持渐变色,各颜色之间以英文逗号分隔', - val: 'darkBgColor', - }, { icon: { name: 'sun.max.fill', color: '#d48806' }, - type: 'input', + type: 'color', title: '白天字体颜色', desc: '请自行去网站上搜寻颜色(Hex 颜色)', val: 'lightColor', }, { icon: { name: 'moon.stars.fill', color: '#d4b106' }, - type: 'input', + type: 'color', title: '晚上字体颜色', desc: '请自行去网站上搜寻颜色(Hex 颜色)', val: 'darkColor', }, - ] - const background = [ + ]; + + return this.renderAppView([ + { title: '基础设置', menu: basic }, { - icon: { name: 'text.below.photo', color: '#faad14' }, - type: 'setBackground', - title: '透明背景设置', + title: '背景设置', + menu: [ + { + icon: { name: 'photo', color: '#13c2c2' }, + type: 'color', + title: '白天背景颜色', + desc: '请自行去网站上搜寻颜色(Hex 颜色)\n支持渐变色,各颜色之间以英文逗号分隔', + val: 'lightBgColor', + }, + { + icon: { name: 'photo.fill', color: '#52c41a' }, + type: 'color', + title: '晚上背景颜色', + desc: '请自行去网站上搜寻颜色(Hex 颜色)\n支持渐变色,各颜色之间以英文逗号分隔', + val: 'darkBgColor', + }, + ], }, { - icon: { name: 'photo.on.rectangle', color: '#fa8c16' }, - type: 'setDayBackground', - title: '白天背景图片', + menu: [ + { + icon: { name: 'photo.on.rectangle', color: '#fa8c16' }, + name: 'dayBg', + type: 'img', + title: '日间背景', + val: this.cacheImage, + verify: true, + }, + { + icon: { name: 'photo.fill.on.rectangle.fill', color: '#fa541c' }, + name: 'nightBg', + type: 'img', + title: '夜间背景', + val: this.cacheImage, + verify: true, + }, + { + icon: { name: 'text.below.photo', color: '#faad14' }, + type: 'img', + name: 'transparentBg', + title: '透明背景', + val: this.cacheImage, + onClick: async (item, __, previewWebView) => { + const backImage = await this.getWidgetScreenShot(); + if (!backImage || !(await this.verifyImage(backImage))) return; + const cachePath = `${item.val}/${item.name}`; + await this.htmlChangeImage(backImage, cachePath, { + previewWebView, + id: item.name, + }); + }, + }, + ], }, { - icon: { name: 'photo.fill.on.rectangle.fill', color: '#fa541c' }, - type: 'setNightBackground', - title: '晚上背景图片', + menu: [ + { + icon: { name: 'record.circle', color: '#722ed1' }, + type: 'input', + title: '日间蒙层', + desc: '完全透明请设置为0', + val: 'lightOpacity', + }, + { + icon: { name: 'record.circle.fill', color: '#eb2f96' }, + type: 'input', + title: '夜间蒙层', + desc: '完全透明请设置为0', + val: 'darkOpacity', + }, + ], }, { - icon: { name: 'record.circle', color: '#722ed1' }, - type: 'input', - title: '白天蒙层透明', - desc: '完全透明请设置为0', - val: 'lightOpacity', + menu: [ + { + icon: { name: 'clear', color: '#f5222d' }, + name: 'removeBackground', + title: '清空背景图片', + val: `${this.cacheImage}/`, + onClick: async (_, __, previewWebView) => { + const ids = ['dayBg', 'nightBg', 'transparentBg']; + const options = [ + '清空日间', + '清空夜间', + '清空透明', + `清空全部`, + '取消', + ]; + const message = '该操作不可逆,会清空背景图片!'; + const index = await this.generateAlert(message, options); + if (index === 4) return; + switch (index) { + case 3: + await this.htmlChangeImage(false, `${_.val}${ids[0]}`, { + previewWebView, + id: ids[0], + }); + await this.htmlChangeImage(false, `${_.val}${ids[1]}`, { + previewWebView, + id: ids[1], + }); + await this.htmlChangeImage(false, `${_.val}${ids[2]}`, { + previewWebView, + id: ids[2], + }); + return; + default: + await this.htmlChangeImage(false, `${_.val}${ids[index]}`, { + previewWebView, + id: ids[index], + }); + break; + } + }, + }, + ], }, { - icon: { name: 'record.circle.fill', color: '#eb2f96' }, - type: 'input', - title: '晚上蒙层透明', - desc: '完全透明请设置为0', - val: 'darkOpacity', + title: '重置组件', + menu: [ + { + icon: { name: 'trash', color: '#D85888' }, + title: '重置', + desc: '重置当前组件配置', + name: 'reset', + val: 'reset', + onClick: () => { + this.settings = {}; + this.saveSettings(); + this.reopenScript(); + }, + }, + ], }, + ]).catch((e) => { + console.log(e); + }); + }; + + drawTableIcon = async ( + icon = 'square.grid.2x2', + color = '#504ED5', + cornerWidth = 42 + ) => { + let sfi = SFSymbol.named('square.grid.2x2'); + try { + sfi = SFSymbol.named(icon); + sfi.applyFont(Font.mediumSystemFont(30)); + } catch (e) { + console.log(`图标(${icon})异常:` + e); + } + const imgData = Data.fromPNG(sfi.image).toBase64String(); + const html = ` + + + + `; + const js = ` + var canvas = document.createElement("canvas"); + var sourceImg = document.getElementById("sourceImg"); + var silhouetteImg = document.getElementById("silhouetteImg"); + var ctx = canvas.getContext('2d'); + var size = sourceImg.width > sourceImg.height ? sourceImg.width : sourceImg.height; + canvas.width = size; + canvas.height = size; + ctx.drawImage(sourceImg, (canvas.width - sourceImg.width) / 2, (canvas.height - sourceImg.height) / 2); + var imgData = ctx.getImageData(0, 0, canvas.width, canvas.height); + var pix = imgData.data; + //convert the image into a silhouette + for (var i=0, n = pix.length; i < n; i+= 4){ + //set red to 0 + pix[i] = 255; + //set green to 0 + pix[i+1] = 255; + //set blue to 0 + pix[i+2] = 255; + //retain the alpha value + pix[i+3] = pix[i+3]; + } + ctx.putImageData(imgData,0,0); + silhouetteImg.src = canvas.toDataURL(); + output=canvas.toDataURL() + `; + + let wv = new WebView(); + await wv.loadHTML(html); + const base64Image = await wv.evaluateJavaScript(js); + const iconImage = await new Request(base64Image).loadImage(); + const size = new Size(160, 160); + const ctx = new DrawContext(); + ctx.opaque = false; + ctx.respectScreenScale = true; + ctx.size = size; + const path = new Path(); + const rect = new Rect(0, 0, size.width, size.width); + + path.addRoundedRect(rect, cornerWidth, cornerWidth); + path.closeSubpath(); + ctx.setFillColor(new Color(color)); + ctx.addPath(path); + ctx.fillPath(); + const rate = 36; + const iw = size.width - rate; + const x = (size.width - iw) / 2; + ctx.drawImageInRect(iconImage, new Rect(x, x, iw, iw)); + return ctx.getImage(); + }; + + dismissLoading = (webView) => { + webView.evaluateJavaScript( + "window.dispatchEvent(new CustomEvent('JWeb', { detail: { code: 'finishLoading' } }))", + false + ); + }; + + insertTextByElementId = (webView, elementId, text) => { + const scripts = `document.getElementById("${elementId}_val").innerHTML=\`${text}\`;`; + webView.evaluateJavaScript(scripts, false); + }; + + loadSF2B64 = async ( + icon = 'square.grid.2x2', + color = '#56A8D6', + cornerWidth = 42 + ) => { + const sfImg = await this.drawTableIcon(icon, color, cornerWidth); + return `data:image/png;base64,${Data.fromPNG(sfImg).toBase64String()}`; + }; + + setUserInfo = async () => { + const baseOnClick = async (item, _, previewWebView) => { + const data = await this.setBaseAlertInput(item.title, item.desc, { + [item.val]: item.placeholder, + }); + if (!data) return; + this.insertTextByElementId(previewWebView, item.name, data[item.val]); + }; + + return this.renderAppView([ { - icon: { name: 'clear', color: '#f5222d' }, - type: 'removeBackground', - title: '清空背景图片', + title: '个性设置', + menu: [ + { + icon: { name: 'person', color: '#fa541c' }, + name: this.userConfigKey[0], + title: '首页头像', + type: 'img', + val: this.baseImage, + onClick: async (_, __, previewWebView) => { + const options = ['相册选择', '在线链接', '取消']; + const message = '设置个性化头像'; + const index = await this.generateAlert(message, options); + if (index === 2) return; + const cachePath = `${_.val}/${_.name}`; + switch (index) { + case 0: + const albumOptions = ['选择图片', '清空图片', '取消']; + + const albumIndex = await this.generateAlert('', albumOptions); + if (albumIndex === 2) return; + if (albumIndex === 1) { + await this.htmlChangeImage(false, cachePath, { + previewWebView, + id: _.name, + }); + return; + } + + const backImage = await this.chooseImg(); + if (backImage) { + await this.htmlChangeImage(backImage, cachePath, { + previewWebView, + id: _.name, + }); + } + + break; + case 1: + const data = await this.setBaseAlertInput( + '在线链接', + '首页头像在线链接', + { + avatar: '🔗请输入 URL 图片链接', + } + ); + if (!data) return; + + if (data[_.name] !== '') { + const backImage = await this.$request.get( + data[_.name], + 'IMG' + ); + await this.htmlChangeImage(backImage, cachePath, { + previewWebView, + id: _.name, + }); + } else { + await this.htmlChangeImage(false, cachePath, { + previewWebView, + id: _.name, + }); + } + + break; + default: + break; + } + }, + }, + { + icon: { name: 'pencil', color: '#fa8c16' }, + type: 'input', + title: '首页昵称', + desc: '个性化首页昵称', + placeholder: '👤请输入头像昵称', + val: this.userConfigKey[1], + name: this.userConfigKey[1], + defaultValue: this.baseSettings.nickname, + onClick: baseOnClick, + }, + { + icon: { name: 'lineweight', color: '#a0d911' }, + type: 'input', + title: '首页昵称描述', + desc: '个性化首页昵称描述', + placeholder: '请输入描述', + val: this.userConfigKey[2], + name: this.userConfigKey[2], + defaultValue: this.baseSettings.homePageDesc, + onClick: baseOnClick, + }, + ], }, - ] - const boxjs = { - icon: { name: 'shippingbox', color: '#f7bb10' }, - type: 'input', - title: 'BoxJS 域名', - desc: '', - val: 'boxjsDomain', - } - if (this.useBoxJS) basic.push(boxjs) - table.removeAllRows() - let topRow = new UITableRow() - topRow.height = 60 - let leftText = topRow.addButton('Github') - leftText.widthWeight = 0.3 - leftText.onTap = async () => { - await Safari.openInApp('https://github.com/dompling/Scriptable') - } - let centerRow = topRow.addImageAtURL( - 'https://s3.ax1x.com/2021/03/16/6y4oJ1.png' - ) - centerRow.widthWeight = 0.4 - centerRow.centerAligned() - centerRow.onTap = async () => { - await Safari.open('https://t.me/Scriptable_JS') + { + menu: [ + { + icon: { name: 'shippingbox', color: '#f7bb10' }, + type: 'input', + title: 'BoxJS 域名', + desc: '设置BoxJS访问域名,如:boxjs.net 或 boxjs.com', + val: 'boxjsDomain', + name: 'boxjsDomain', + placeholder: 'boxjs.net', + defaultValue: this.baseSettings.boxjsDomain, + onClick: baseOnClick, + }, + { + icon: { name: 'clear', color: '#f5222d' }, + title: '恢复默认设置', + name: 'reset', + onClick: async () => { + const options = ['取消', '确定']; + const message = '确定要恢复当前所有配置吗?'; + const index = await this.generateAlert(message, options); + if (index === 1) { + this.settings = {}; + this.baseSettings = {}; + + this.FILE_MGR.remove(this.cacheImage); + + for (const item of this.cacheImageBgPath) { + await this.setBackgroundImage(false, item, false); + } + + this.saveSettings(false); + this.saveBaseSettings(); + await this.notify( + '重置成功', + '请关闭窗口之后,重新运行当前脚本' + ); + this.reopenScript(); + } + }, + }, + ], + }, + ]); + }; + + htmlChangeImage = async (image, path, { previewWebView, id }) => { + const base64Img = await this.setBackgroundImage(image, path, false); + console.log(path); + this.insertTextByElementId( + previewWebView, + id, + base64Img ? `` : '' + ); + }; + + reopenScript = () => { + Safari.open(`scriptable:///run/${encodeURIComponent(Script.name())}`); + }; + + async renderAppView( + options = [], + renderAvatar = false, + previewWebView = new WebView() + ) { + const settingItemFontSize = 14, + authorNameFontSize = 20, + authorDescFontSize = 12; + // ================== 配置界面样式 =================== + const style = ` + :root { + --color-primary: #007aff; + --divider-color: rgba(60,60,67,0.16); + --card-background: #fff; + --card-radius: 8px; + --list-header-color: rgba(60,60,67,0.6); + } + * { + -webkit-user-select: none; + user-select: none; + } + body { + margin: 10px 0; + -webkit-font-smoothing: antialiased; + font-family: "SF Pro Display","SF Pro Icons","Helvetica Neue","Helvetica","Arial",sans-serif; + accent-color: var(--color-primary); + background: #f6f6f6; + } + .list { + margin: 15px; + } + .list__header { + margin: 0 18px; + color: var(--list-header-color); + font-size: 13px; + } + .list__body { + margin-top: 10px; + background: var(--card-background); + border-radius: var(--card-radius); + overflow: hidden; + } + .form-item-auth { + display: flex; + align-items: center; + justify-content: space-between; + min-height: 4em; + padding: 0.5em 18px; + position: relative; + } + .form-item-auth-name { + margin: 0px 12px; + font-size: ${authorNameFontSize}px; + font-weight: 430; + } + .form-item-auth-desc { + margin: 0px 12px; + font-size: ${authorDescFontSize}px; + font-weight: 400; + } + .form-label-author-avatar { + width: 62px; + height: 62px; + border-radius:50%; + border: 1px solid #F6D377; + } + .form-item, .form-item-switch { + display: flex; + align-items: center; + justify-content: space-between; + font-size: ${settingItemFontSize}px; + font-weight: 400; + min-height: 2.2em; + padding: 0.5em 10px; + position: relative; + } + label > * { + pointer-events: none; + } + .form-label { + display: flex; + align-items: center; + flex-wrap:nowrap + } + .form-label-img { + height: 30px; + } + .form-label-title { + margin-left: 8px; + white-space: nowrap; + } + .bottom-bg { + margin: 30px 15px 15px 15px; + } + .form-item--link .icon-arrow-right { + color: #86868b; + } + + .form-item-right-desc { + font-size: 13px; + color: #86868b; + margin: 0 4px 0 auto; + max-width: 130px; + overflow: hidden; + text-overflow: ellipsis; + display:flex; + align-items: center; + white-space: nowrap; + } + + .form-item-right-desc img{ + width:30px; + height:30px; + border-radius:3px; + } + + .form-item + .form-item::before, + .form-item + .form-item-switch::before, + .form-item-switch + .form-item::before, + .form-item-switch + .form-item-switch::before + { + content: ""; + position: absolute; + top: 0; + left: 0; + right: 0; + border-top: 0.5px solid var(--divider-color); + } + + .form-item input[type="checkbox"] { + width: 2em; + height: 2em; + } + input[type='input'],select,input[type='date'] { + width: 100%; + height: 2.3em; + outline-style: none; + text-align: right; + padding: 0px 10px; + border: 1px solid #ddd; + font-size: 14px; + color: #86868b; + border-radius:4px; + } + input[type='checkbox'][role='switch'] { + position: relative; + display: inline-block; + appearance: none; + width: 40px; + height: 24px; + border-radius: 24px; + background: #ccc; + transition: 0.3s ease-in-out; + } + input[type='checkbox'][role='switch']::before { + content: ''; + position: absolute; + left: 2px; + top: 2px; + width: 20px; + height: 20px; + border-radius: 50%; + background: #fff; + transition: 0.3s ease-in-out; + } + input[type='checkbox'][role='switch']:checked { + background: var(--color-primary); + } + input[type='checkbox'][role='switch']:checked::before { + transform: translateX(16px); + } + .copyright { + display: flex; + align-items: center; + justify-content: space-between; + margin: 15px; + font-size: 10px; + color: #86868b; + } + .copyright a { + color: #515154; + text-decoration: none; + } + .preview.loading { + pointer-events: none; + } + .icon-loading { + display: inline-block; + animation: 1s linear infinite spin; + } + .normal-loading { + display: inline-block; + animation: 20s linear infinite spin; + } + @keyframes spin { + 0% { + transform: rotate(0); + } + 100% { + transform: rotate(1turn); + } + } + @media (prefers-color-scheme: dark) { + :root { + --divider-color: rgba(84,84,88,0.65); + --card-background: #1c1c1e; + --list-header-color: rgba(235,235,245,0.6); + } + body { + background: #000; + color: #fff; + } + }`; + + const js = ` + (() => { + + window.invoke = (code, data) => { + window.dispatchEvent( + new CustomEvent( + 'JBridge', + { detail: { code, data } } + ) + ) + } + + // 切换ico的loading效果 + const toggleIcoLoading = (e) => { + try{ + const target = e.currentTarget + target.classList.add('loading') + const icon = e.currentTarget.querySelector('.iconfont') + const className = icon.className + icon.className = 'iconfont icon-loading' + const listener = (event) => { + const { code } = event.detail + if (code === 'finishLoading') { + target.classList.remove('loading') + icon.className = className + window.removeEventListener('JWeb', listener); + } + } + window.addEventListener('JWeb', listener) + }catch(e){ + for (const loading of document.querySelectorAll('.icon-loading')) { + loading.classList.remove('loading'); + loading.className = "iconfont icon-arrow-right"; + } + } + }; + + for (const btn of document.querySelectorAll('.form-item')) { + btn.addEventListener('click', (e) => { + if(!e.target.id)return; + toggleIcoLoading(e); + invoke(e.target.id); + }) + } + + for (const btn of document.querySelectorAll('.form-item__input')) { + btn.addEventListener('change', (e) => { + if(!e.target.name)return; + invoke(e.target.name,e.target.type==="checkbox"?\`\${e.target.checked}\`: e.target.value); + }) + } + + if(${renderAvatar}){ + document.querySelectorAll('.form-item-auth')[0].addEventListener('click', (e) => { + toggleIcoLoading(e); + invoke("userInfo"); + }) + } + + })()`; + + let configList = ``; + let actionsConfig = []; + + for (const key in options) { + const item = options[key]; + actionsConfig = [...item.menu, ...actionsConfig]; + configList += ` +
+
${item.title || ''}
+
+ `; + + for (const menuItem of item.menu) { + let iconBase64 = ``; + if (menuItem.children) { + menuItem.onClick = () => { + return this.renderAppView( + typeof menuItem.children === 'function' + ? menuItem.children() + : menuItem.children + ); + }; + } + if (menuItem.url) { + const imageIcon = await this.http( + { url: menuItem.url }, + 'IMG', + () => { + return this.drawTableIcon('gear'); + } + ); + + if (menuItem.url.indexOf('png') !== -1) { + iconBase64 = `data:image/png;base64,${Data.fromPNG( + imageIcon + ).toBase64String()}`; + } else { + iconBase64 = `data:image/png;base64,${Data.fromJPEG( + imageIcon + ).toBase64String()}`; + } + } else { + const icon = menuItem.icon || {}; + iconBase64 = await this.loadSF2B64(icon.name, icon.color); + } + const idName = menuItem.name || menuItem.val; + + let defaultHtml = ``; + menuItem.defaultValue = + this.settings[idName] || menuItem.defaultValue || ''; + + if (menuItem.type === 'input') { + defaultHtml = menuItem.defaultValue || ''; + } else if (menuItem.type === 'img') { + const cachePath = `${menuItem.val}/${menuItem.name}`; + if (await this.FILE_MGR.fileExistsExtra(cachePath)) { + const imageSrc = `data:image/png;base64,${Data.fromFile( + cachePath + ).toBase64String()}`; + defaultHtml = ``; + } + } else if (menuItem.type === 'select') { + let selectOptions = ''; + menuItem.options.forEach((option) => { + let selected = `selected="selected"`; + selectOptions += ``; + }); + defaultHtml = ``; + } else if (menuItem.type === 'switch') { + const checked = + menuItem.defaultValue == 'true' ? `checked="checked"` : ''; + defaultHtml += ``; + } else if (menuItem.type) { + defaultHtml = ``; + } + + let addLable = ''; + if (menuItem.type === 'switch' || menuItem.type === 'checkbox') { + addLable = `
`; } - let rightText = topRow.addButton('重置所有') - rightText.widthWeight = 0.3 - rightText.rightAligned() - rightText.onTap = async () => { - const options = ['取消', '重置'] - const message = - '该操作不可逆,会清空所有组件配置!重置后请重新打开设置菜单。' - const index = await this.generateAlert(message, options) - if (index === 0) return - this.settings = {} - await this.setBackgroundImage(false, false) - this.saveSettings() + + let avatarHtml = ''; + if (renderAvatar) { + const cachePath = `${this.baseImage}/${this.userConfigKey[0]}`; + const avatarConfig = { + avatar: `https://avatars.githubusercontent.com/u/23498579?v=4`, + nickname: this.baseSettings[this.userConfigKey[1]] || 'Dompling', + homPageDesc: + this.baseSettings[this.userConfigKey[2]] || + '18岁,来自九仙山的设计师', + }; + + if (await this.FILE_MGR.fileExistsExtra(cachePath)) { + avatarConfig.avatar = `data:image/png;base64,${Data.fromFile( + cachePath + ).toBase64String()}`; + } + + avatarHtml = ` +
+
+ +
+
+ `; } - table.addRow(topRow) - await this.preferences(table, basic, '基础设置') - await this.preferences(table, background, '背景图片') + + const html = ` + + + + + + + + ${avatarHtml} + ${configList} + + + + `; + + // 预览web + await previewWebView.loadHTML(html); + + const injectListener = async () => { + const event = await previewWebView.evaluateJavaScript( + `(() => { + try { + window.addEventListener( + 'JBridge', + (e)=>{ + completion(JSON.stringify(e.detail||{})) + } + ) + } catch (e) { + alert("预览界面出错:" + e); + throw new Error("界面处理出错: " + e); + return; + } + })()`, + true + ); + + const { code, data } = JSON.parse(event); + try { + const actionItem = actionsConfig.find( + (item) => (item.name || item.val) === code + ); + + if (code === 'userInfo') await this.setUserInfo(); + + if (actionItem) { + const idName = actionItem?.name || actionItem?.val; + if (actionItem?.onClick) { + await actionItem?.onClick?.(actionItem, data, previewWebView); + } else if (actionItem.type == 'input') { + if ( + await this.setLightAndDark( + actionItem['title'], + actionItem['desc'], + idName, + actionItem['placeholder'] + ) + ) + this.insertTextByElementId( + previewWebView, + idName, + this.settings[idName] || '' + ); + } else if (actionItem.type === 'img') { + const cachePath = `${actionItem.val}/${actionItem.name}`; + const options = ['相册选择', '清空图片', '取消']; + const message = '相册图片选择,请选择合适图片大小'; + const index = await this.generateAlert(message, options); + switch (index) { + case 0: + const backImage = await this.chooseImg(actionItem.verify); + if (backImage) { + const cachePath = `${actionItem.val}/${actionItem.name}`; + await this.htmlChangeImage(backImage, cachePath, { + previewWebView, + id: idName, + }); + } + break; + case 1: + await this.htmlChangeImage(false, cachePath, { + previewWebView, + id: idName, + }); + break; + default: + break; + } + } else { + if (data !== undefined) { + this.settings[idName] = data; + this.saveSettings(false); + } + } + } + } catch (error) { + console.log('异常操作:' + error); + } + this.dismissLoading(previewWebView); + injectListener(); + }; + + injectListener().catch((e) => { + console.error(e); + this.dismissLoading(previewWebView); + if (!config.runsInApp) { + this.notify('主界面', `🚫 ${e}`); + } + }); + + previewWebView.present(); } - init(widgetFamily = config.widgetFamily) { + initSFSymbol() { + const named = SFSymbol.named; + SFSymbol.named = (str) => { + const current = named(str); + if (!current) { + console.log(`图标异常,请在文中搜索并替换图标:${str}`); + return named('photo'); + } + return current; + }; + return SFSymbol; + } + + _init(widgetFamily = config.widgetFamily) { + this.initSFSymbol(); // 组件大小:small,medium,large - this.widgetFamily = widgetFamily - this.SETTING_KEY = this.md5(Script.name()) + this.widgetFamily = widgetFamily; //用于配置所有的组件相关设置 // 文件管理器 @@ -794,72 +1683,122 @@ class DmYY { this.FILE_MGR = FileManager[ module.filename.includes('Documents/iCloud~') ? 'iCloud' : 'local' - ]() - // 本地,用于存储图片等 - this.FILE_MGR_LOCAL = FileManager.local() - this.BACKGROUND_KEY = this.FILE_MGR_LOCAL.joinPath( - this.FILE_MGR_LOCAL.documentsDirectory(), - 'bg_' + this.SETTING_KEY + '.jpg' - ) - - this.BACKGROUND_NIGHT_KEY = this.FILE_MGR_LOCAL.joinPath( - this.FILE_MGR_LOCAL.documentsDirectory(), - 'bg_' + this.SETTING_KEY + 'night.jpg' - ) - - this.settings = this.getSettings() - this.settings.lightColor = this.settings.lightColor || '#000000' - this.settings.darkColor = this.settings.darkColor || '#ffffff' - this.settings.lightBgColor = this.settings.lightBgColor || '#ffffff' - this.settings.darkBgColor = this.settings.darkBgColor || '#000000' - this.settings.boxjsDomain = this.settings.boxjsDomain || 'boxjs.net' - this.settings.refreshAfterDate = this.settings.refreshAfterDate || '30' - this.settings.lightOpacity = this.settings.lightOpacity || '0.4' - this.settings.darkOpacity = this.settings.darkOpacity || '0.7' - this.prefix = this.settings.boxjsDomain - const lightBgColor = this.getColors(this.settings.lightBgColor) - const darkBgColor = this.getColors(this.settings.darkBgColor) - if (lightBgColor.length > 1 || darkBgColor.length > 1) { - this.backGroundColor = !Device.isUsingDarkAppearance() - ? this.getBackgroundColor(lightBgColor) - : this.getBackgroundColor(darkBgColor) - } else if (lightBgColor.length > 0 && darkBgColor.length > 0) { - this.backGroundColor = Color.dynamic( - new Color(this.settings.lightBgColor), - new Color(this.settings.darkBgColor) - ) + ](); + + this.FILE_MGR.fileExistsExtra = async (filePath) => { + const file = this.FILE_MGR.fileExists(filePath); + if (file) await this.FILE_MGR.downloadFileFromiCloud(filePath); + return file; + }; + + this.cacheImage = this.FILE_MGR.joinPath( + this.FILE_MGR.documentsDirectory(), + `/images/${Script.name()}` + ); + + this.baseImage = this.FILE_MGR.joinPath( + this.FILE_MGR.documentsDirectory(), + `/images/` + ); + + this.cacheImageBgPath = [ + `${this.cacheImage}/transparentBg`, + `${this.cacheImage}/dayBg`, + `${this.cacheImage}/nightBg`, + `${this.baseImage}/avatar`, + ]; + + if (!this.FILE_MGR.fileExists(this.cacheImage)) { + this.FILE_MGR.createDirectory(this.cacheImage, true); } + + // 本地,用于存储图片等 + this.FILE_MGR_LOCAL = FileManager.local(); + + this.settings = this.getSettings(); + + this.baseSettings = this.getBaseSettings(); + + this.settings = { ...this.defaultSettings, ...this.settings }; + + this.settings.lightColor = this.settings.lightColor || '#000000'; + this.settings.darkColor = this.settings.darkColor || '#ffffff'; + this.settings.lightBgColor = this.settings.lightBgColor || '#ffffff'; + this.settings.darkBgColor = this.settings.darkBgColor || '#000000'; + this.settings.boxjsDomain = this.baseSettings.boxjsDomain || 'boxjs.net'; + this.settings.refreshAfterDate = this.settings.refreshAfterDate || '30'; + this.settings.lightOpacity = this.settings.lightOpacity || '0.4'; + this.settings.darkOpacity = this.settings.darkOpacity || '0.7'; + + this.prefix = this.settings.boxjsDomain; + + config.runsInApp && this.saveSettings(false); + + this.backGroundColor = Color.dynamic( + new Color(this.settings.lightBgColor), + new Color(this.settings.darkBgColor) + ); + + // const lightBgColor = this.getColors(this.settings.lightBgColor); + // const darkBgColor = this.getColors(this.settings.darkBgColor); + // if (lightBgColor.length > 1 || darkBgColor.length > 1) { + // this.backGroundColor = !Device.isUsingDarkAppearance() + // ? this.getBackgroundColor(lightBgColor) + // : this.getBackgroundColor(darkBgColor); + // } else if (lightBgColor.length > 0 && darkBgColor.length > 0) { + // this.backGroundColor = Color.dynamic( + // new Color(this.settings.lightBgColor), + // new Color(this.settings.darkBgColor) + // ); + // } + this.widgetColor = Color.dynamic( new Color(this.settings.lightColor), new Color(this.settings.darkColor) - ) + ); } getColors = (color = '') => { - const colors = typeof color === 'string' ? color.split(',') : color - return colors - } + const colors = typeof color === 'string' ? color.split(',') : color; + return colors; + }; getBackgroundColor = (colors) => { - const locations = [] - const linearColor = new LinearGradient() - const cLen = colors.length + const locations = []; + const linearColor = new LinearGradient(); + const cLen = colors.length; linearColor.colors = colors.map((item, index) => { - locations.push(Math.floor(((index + 1) / cLen) * 100) / 100) - return new Color(item, 1) - }) - linearColor.locations = locations - return linearColor - } + locations.push(Math.floor(((index + 1) / cLen) * 100) / 100); + return new Color(item, 1); + }); + linearColor.locations = locations; + return linearColor; + }; /** * 注册点击操作菜单 * @param {string} name 操作函数名 * @param {func} func 点击后执行的函数 */ - registerAction(name, func, icon = { name: 'gear', color: '#096dd9' }) { - this._actions[name] = func.bind(this) - this._actionsIcon[name] = icon + registerAction(name, func, icon = { name: 'gear', color: '#096dd9' }, type) { + if (typeof name === 'object' && !name.menu) return this._actions.push(name); + if (typeof name === 'object' && name.menu) + return this._menuActions.push(name); + + const action = { + name, + type, + title: name, + onClick: func?.bind(this), + }; + + if (typeof icon === 'string') { + action.url = icon; + } else { + action.icon = icon; + } + + this._actions.push(action); } /** @@ -867,8 +1806,8 @@ class DmYY { * @param {string} str 要编码的字符串 */ base64Encode(str) { - const data = Data.fromString(str) - return data.toBase64String() + const data = Data.fromString(str); + return data.toBase64String(); } /** @@ -876,203 +1815,16 @@ class DmYY { * @param {string} b64 base64编码的数据 */ base64Decode(b64) { - const data = Data.fromBase64String(b64) - return data.toRawString() + const data = Data.fromBase64String(b64); + return data.toRawString(); } /** * md5 加密字符串 * @param {string} str 要加密成md5的数据 */ - md5(str) { - function d(n, t) { - var r = (65535 & n) + (65535 & t) - return (((n >> 16) + (t >> 16) + (r >> 16)) << 16) | (65535 & r) - } - - function f(n, t, r, e, o, u) { - return d(((c = d(d(t, n), d(e, u))) << (f = o)) | (c >>> (32 - f)), r) - var c, f - } - - function l(n, t, r, e, o, u, c) { - return f((t & r) | (~t & e), n, t, o, u, c) - } - - function v(n, t, r, e, o, u, c) { - return f((t & e) | (r & ~e), n, t, o, u, c) - } - - function g(n, t, r, e, o, u, c) { - return f(t ^ r ^ e, n, t, o, u, c) - } - - function m(n, t, r, e, o, u, c) { - return f(r ^ (t | ~e), n, t, o, u, c) - } - - function i(n, t) { - var r, e, o, u - ;(n[t >> 5] |= 128 << t % 32), (n[14 + (((t + 64) >>> 9) << 4)] = t) - for ( - var c = 1732584193, - f = -271733879, - i = -1732584194, - a = 271733878, - h = 0; - h < n.length; - h += 16 - ) - (c = l((r = c), (e = f), (o = i), (u = a), n[h], 7, -680876936)), - (a = l(a, c, f, i, n[h + 1], 12, -389564586)), - (i = l(i, a, c, f, n[h + 2], 17, 606105819)), - (f = l(f, i, a, c, n[h + 3], 22, -1044525330)), - (c = l(c, f, i, a, n[h + 4], 7, -176418897)), - (a = l(a, c, f, i, n[h + 5], 12, 1200080426)), - (i = l(i, a, c, f, n[h + 6], 17, -1473231341)), - (f = l(f, i, a, c, n[h + 7], 22, -45705983)), - (c = l(c, f, i, a, n[h + 8], 7, 1770035416)), - (a = l(a, c, f, i, n[h + 9], 12, -1958414417)), - (i = l(i, a, c, f, n[h + 10], 17, -42063)), - (f = l(f, i, a, c, n[h + 11], 22, -1990404162)), - (c = l(c, f, i, a, n[h + 12], 7, 1804603682)), - (a = l(a, c, f, i, n[h + 13], 12, -40341101)), - (i = l(i, a, c, f, n[h + 14], 17, -1502002290)), - (c = v( - c, - (f = l(f, i, a, c, n[h + 15], 22, 1236535329)), - i, - a, - n[h + 1], - 5, - -165796510 - )), - (a = v(a, c, f, i, n[h + 6], 9, -1069501632)), - (i = v(i, a, c, f, n[h + 11], 14, 643717713)), - (f = v(f, i, a, c, n[h], 20, -373897302)), - (c = v(c, f, i, a, n[h + 5], 5, -701558691)), - (a = v(a, c, f, i, n[h + 10], 9, 38016083)), - (i = v(i, a, c, f, n[h + 15], 14, -660478335)), - (f = v(f, i, a, c, n[h + 4], 20, -405537848)), - (c = v(c, f, i, a, n[h + 9], 5, 568446438)), - (a = v(a, c, f, i, n[h + 14], 9, -1019803690)), - (i = v(i, a, c, f, n[h + 3], 14, -187363961)), - (f = v(f, i, a, c, n[h + 8], 20, 1163531501)), - (c = v(c, f, i, a, n[h + 13], 5, -1444681467)), - (a = v(a, c, f, i, n[h + 2], 9, -51403784)), - (i = v(i, a, c, f, n[h + 7], 14, 1735328473)), - (c = g( - c, - (f = v(f, i, a, c, n[h + 12], 20, -1926607734)), - i, - a, - n[h + 5], - 4, - -378558 - )), - (a = g(a, c, f, i, n[h + 8], 11, -2022574463)), - (i = g(i, a, c, f, n[h + 11], 16, 1839030562)), - (f = g(f, i, a, c, n[h + 14], 23, -35309556)), - (c = g(c, f, i, a, n[h + 1], 4, -1530992060)), - (a = g(a, c, f, i, n[h + 4], 11, 1272893353)), - (i = g(i, a, c, f, n[h + 7], 16, -155497632)), - (f = g(f, i, a, c, n[h + 10], 23, -1094730640)), - (c = g(c, f, i, a, n[h + 13], 4, 681279174)), - (a = g(a, c, f, i, n[h], 11, -358537222)), - (i = g(i, a, c, f, n[h + 3], 16, -722521979)), - (f = g(f, i, a, c, n[h + 6], 23, 76029189)), - (c = g(c, f, i, a, n[h + 9], 4, -640364487)), - (a = g(a, c, f, i, n[h + 12], 11, -421815835)), - (i = g(i, a, c, f, n[h + 15], 16, 530742520)), - (c = m( - c, - (f = g(f, i, a, c, n[h + 2], 23, -995338651)), - i, - a, - n[h], - 6, - -198630844 - )), - (a = m(a, c, f, i, n[h + 7], 10, 1126891415)), - (i = m(i, a, c, f, n[h + 14], 15, -1416354905)), - (f = m(f, i, a, c, n[h + 5], 21, -57434055)), - (c = m(c, f, i, a, n[h + 12], 6, 1700485571)), - (a = m(a, c, f, i, n[h + 3], 10, -1894986606)), - (i = m(i, a, c, f, n[h + 10], 15, -1051523)), - (f = m(f, i, a, c, n[h + 1], 21, -2054922799)), - (c = m(c, f, i, a, n[h + 8], 6, 1873313359)), - (a = m(a, c, f, i, n[h + 15], 10, -30611744)), - (i = m(i, a, c, f, n[h + 6], 15, -1560198380)), - (f = m(f, i, a, c, n[h + 13], 21, 1309151649)), - (c = m(c, f, i, a, n[h + 4], 6, -145523070)), - (a = m(a, c, f, i, n[h + 11], 10, -1120210379)), - (i = m(i, a, c, f, n[h + 2], 15, 718787259)), - (f = m(f, i, a, c, n[h + 9], 21, -343485551)), - (c = d(c, r)), - (f = d(f, e)), - (i = d(i, o)), - (a = d(a, u)) - return [c, f, i, a] - } - - function a(n) { - for (var t = '', r = 32 * n.length, e = 0; e < r; e += 8) - t += String.fromCharCode((n[e >> 5] >>> e % 32) & 255) - return t - } - - function h(n) { - var t = [] - for (t[(n.length >> 2) - 1] = void 0, e = 0; e < t.length; e += 1) - t[e] = 0 - for (var r = 8 * n.length, e = 0; e < r; e += 8) - t[e >> 5] |= (255 & n.charCodeAt(e / 8)) << e % 32 - return t - } - - function e(n) { - for (var t, r = '0123456789abcdef', e = '', o = 0; o < n.length; o += 1) - (t = n.charCodeAt(o)), - (e += r.charAt((t >>> 4) & 15) + r.charAt(15 & t)) - return e - } - - function r(n) { - return unescape(encodeURIComponent(n)) - } - - function o(n) { - return a(i(h((t = r(n))), 8 * t.length)) - var t - } - - function u(n, t) { - return (function (n, t) { - var r, - e, - o = h(n), - u = [], - c = [] - for ( - u[15] = c[15] = void 0, - 16 < o.length && (o = i(o, 8 * n.length)), - r = 0; - r < 16; - r += 1 - ) - (u[r] = 909522486 ^ o[r]), (c[r] = 1549556828 ^ o[r]) - return ( - (e = i(u.concat(h(t)), 512 + 8 * t.length)), a(i(c.concat(e), 640)) - ) - })(r(n), r(t)) - } - - function t(n, t, r) { - return t ? (r ? u(t, n) : e(u(t, n))) : r ? o(n) : e(o(n)) - } - - return t(str) - } + // prettier-ignore + md5(str){function d(n,t){var r=(65535&n)+(65535&t);return(((n>>16)+(t>>16)+(r>>16))<<16)|(65535&r)}function f(n,t,r,e,o,u){return d(((c=d(d(t,n),d(e,u)))<<(f=o))|(c>>>(32-f)),r);var c,f}function l(n,t,r,e,o,u,c){return f((t&r)|(~t&e),n,t,o,u,c)}function v(n,t,r,e,o,u,c){return f((t&e)|(r&~e),n,t,o,u,c)}function g(n,t,r,e,o,u,c){return f(t^r^e,n,t,o,u,c)}function m(n,t,r,e,o,u,c){return f(r^(t|~e),n,t,o,u,c)}function i(n,t){var r,e,o,u;(n[t>>5]|=128<>>9)<<4)]=t);for(var c=1732584193,f=-271733879,i=-1732584194,a=271733878,h=0;h>5]>>>e%32)&255);return t}function h(n){var t=[];for(t[(n.length>>2)-1]=void 0,e=0;e>5]|=(255&n.charCodeAt(e/8))<>>4)&15)+r.charAt(15&t));return e}function r(n){return unescape(encodeURIComponent(n))}function o(n){return a(i(h((t=r(n))),8*t.length));var t}function u(n,t){return(function(n,t){var r,e,o=h(n),u=[],c=[];for(u[15]=c[15]=void 0,16 0 ? min : 0 + index; + min = min > 0 ? min : 0; while (i-- > min) { - index = Math.floor((i + 1) * Math.random()) - temp = shuffled[index] - shuffled[index] = shuffled[i] - shuffled[i] = temp + index = Math.floor((i + 1) * Math.random()); + temp = shuffled[index]; + shuffled[index] = shuffled[i]; + shuffled[i] = temp; } - return shuffled.slice(min) + return shuffled.slice(min); } textFormat = { @@ -1266,151 +2031,208 @@ class DmYY { battery: { size: 10, font: 'bold', color: this.widgetColor }, title: { size: 16, font: 'semibold', color: this.widgetColor }, SFMono: { size: 12, font: 'SF Mono', color: this.widgetColor }, - } + }; provideFont = (fontName, fontSize) => { const fontGenerator = { ultralight: function () { - return Font.ultraLightSystemFont(fontSize) + return Font.ultraLightSystemFont(fontSize); }, light: function () { - return Font.lightSystemFont(fontSize) + return Font.lightSystemFont(fontSize); }, regular: function () { - return Font.regularSystemFont(fontSize) + return Font.regularSystemFont(fontSize); }, medium: function () { - return Font.mediumSystemFont(fontSize) + return Font.mediumSystemFont(fontSize); }, semibold: function () { - return Font.semiboldSystemFont(fontSize) + return Font.semiboldSystemFont(fontSize); }, bold: function () { - return Font.boldSystemFont(fontSize) + return Font.boldSystemFont(fontSize); }, heavy: function () { - return Font.heavySystemFont(fontSize) + return Font.heavySystemFont(fontSize); }, black: function () { - return Font.blackSystemFont(fontSize) + return Font.blackSystemFont(fontSize); }, italic: function () { - return Font.italicSystemFont(fontSize) + return Font.italicSystemFont(fontSize); }, - } + }; - const systemFont = fontGenerator[fontName] + const systemFont = fontGenerator[fontName]; if (systemFont) { - return systemFont() + return systemFont(); } - return new Font(fontName, fontSize) - } + return new Font(fontName, fontSize); + }; provideText = (string, container, format) => { - const textItem = container.addText(string) - const textFont = format.font - const textSize = format.size - const textColor = format.color - - textItem.font = this.provideFont(textFont, textSize) - textItem.textColor = textColor - return textItem - } + format = { + font: 'light', + size: 14, + color: this.widgetColor, + opacity: 1, + minimumScaleFactor: 1, + ...format, + }; + const textItem = container.addText(string); + const textFont = format.font; + const textSize = format.size; + const textColor = format.color; + + textItem.font = this.provideFont(textFont, textSize); + textItem.textColor = textColor; + textItem.textOpacity = format.opacity || 1; + textItem.minimumScaleFactor = format.minimumScaleFactor || 1; + return textItem; + }; } // @base.end const Runing = async (Widget, default_args = '', isDebug = true, extra) => { - let M = null + let M = null; // 判断hash是否和当前设备匹配 if (config.runsInWidget) { - M = new Widget(args.widgetParameter || '') + M = new Widget(args.widgetParameter || ''); if (extra) { Object.keys(extra).forEach((key) => { - M[key] = extra[key] - }) + M[key] = extra[key]; + }); } - const W = await M.render() + const W = await M.render(); try { if (M.settings.refreshAfterDate) { - W.refreshAfterDate = new Date( - new Date() + 1000 * 60 * parseInt(M.settings.refreshAfterDate) - ) + const refreshTime = parseInt(M.settings.refreshAfterDate) * 1000 * 60; + const timeStr = new Date().getTime() + refreshTime; + W.refreshAfterDate = new Date(timeStr); } } catch (e) { - console.log(e) + console.log(e); } if (W) { - Script.setWidget(W) - Script.complete() + Script.setWidget(W); + Script.complete(); } } else { - let { act, __arg, __size } = args.queryParameters - M = new Widget(__arg || default_args || '') + let { act, __arg, __size } = args.queryParameters; + M = new Widget(__arg || default_args || ''); if (extra) { Object.keys(extra).forEach((key) => { - M[key] = extra[key] - }) + M[key] = extra[key]; + }); } - if (__size) M.init(__size) + if (__size) M._init(__size); if (!act || !M['_actions']) { // 弹出选择菜单 - const actions = M['_actions'] - const table = new UITable() + const actions = M['_actions']; const onClick = async (item) => { - M.widgetFamily = item.val - w = await M.render() + M.widgetFamily = item.val; + try { + M._init(item.val); + } catch (error) { + console.log('初始化异常:' + error); + } + w = await M.render(); const fnc = item.val .toLowerCase() - .replace(/( |^)[a-z]/g, (L) => L.toUpperCase()) - if (w) { - return w[`present${fnc}`]() - } - } - const preview = [ - { - url: 'https://z3.ax1x.com/2021/03/26/6v5wIP.png', + .replace(/( |^)[a-z]/g, (L) => L.toUpperCase()); + if (w) return w[`present${fnc}`](); + }; + + const preview = [], + lockView = []; + if (M.renderSmall) { + preview.push({ + url: `https://raw.githubusercontent.com/dompling/Scriptable/master/images/small.png`, title: '小尺寸', val: 'small', + name: 'small', dismissOnSelect: true, onClick, - }, - { - url: 'https://z3.ax1x.com/2021/03/26/6v5dat.png', + }); + } + + if (M.renderMedium) { + preview.push({ + url: `https://raw.githubusercontent.com/dompling/Scriptable/master/images/medium.png`, title: '中尺寸', val: 'medium', + name: 'medium', dismissOnSelect: true, onClick, - }, - { - url: 'https://z3.ax1x.com/2021/03/26/6v5BPf.png', + }); + } + + if (M.renderLarge) { + preview.push({ + url: `https://raw.githubusercontent.com/dompling/Scriptable/master/images/large.png`, title: '大尺寸', val: 'large', + name: 'large', dismissOnSelect: true, onClick, - }, - ] - await M.preferences(table, preview, '预览组件') - const extra = [] - for (let _ in actions) { - const iconItem = M._actionsIcon[_] - const isUrl = typeof iconItem === 'string' - const actionItem = { - title: _, - onClick: actions[_], - } - if (isUrl) { - actionItem.url = iconItem - } else { - actionItem.icon = iconItem - } - extra.push(actionItem) + }); } - await M.preferences(table, extra, '配置组件') - return table.present() + + if (M.renderAccessoryInline) { + lockView.push({ + icon: { + color: '#4676EE', + name: 'list.triangle', + }, + title: '锁屏列表', + val: 'accessoryInline', + name: 'accessoryInline', + dismissOnSelect: true, + onClick, + }); + } + + if (M.renderAccessoryRectangular) { + lockView.push({ + icon: { + color: '#4676EE', + name: 'arrow.rectanglepath', + }, + title: '锁屏 2x', + val: 'accessoryRectangular', + name: 'accessoryRectangular', + dismissOnSelect: true, + onClick, + }); + } + + if (M.renderAccessoryCircular) { + lockView.push({ + icon: { + color: '#4676EE', + name: 'circle.circle', + }, + title: '锁屏 1x', + val: 'accessoryCircular', + name: 'accessoryCircular', + dismissOnSelect: true, + onClick, + }); + } + + const menuConfig = [ + ...(preview ? [{ title: '预览组件', menu: preview }] : []), + ...(lockView.length ? [{ title: '锁屏组件', menu: lockView }] : []), + ...M['_menuActions'], + ]; + + if (actions.length) menuConfig.push({ title: '组件配置', menu: actions }); + + await M.renderAppView(menuConfig, true); } } -} - +}; // await new DmYY().setWidgetConfig(); -module.exports = { DmYY, Runing } +module.exports = { DmYY, Runing }; diff --git a/Scripts/Ftms.js b/Scripts/Ftms.js new file mode 100644 index 0000000..59acc51 --- /dev/null +++ b/Scripts/Ftms.js @@ -0,0 +1,146 @@ +class Ftms { + constructor(carWidget) { + this.$ = carWidget; + } + + name = '一汽丰田'; + en = 'ftms'; + logo = 'https://www.toyota.com.cn/favicon.ico'; + + baseOpt = { + headers: { + Connection: `keep-alive`, + Host: `appiov.ftms.com.cn`, + 'Content-Type': `application/json`, + }, + body: ``, + }; + + init = async () => { + if (this.$.settings.dataSource) { + this.$.serveInfo = this.$.settings.serveInfo; + this.$.dataSource = this.$.settings.dataSource; + } else { + await this.cacheData(); + } + this.cacheData(); + }; + + cacheData = async () => { + try { + await this.getOilPrice(); + await this.getBmuServeHicleInfo(); + await this.getRemoteInfoDetail(); + } catch (e) { + console.log(e); + } + }; + + getBaseOptions(api) { + const baseURL = `https://appiov.ftms.com.cn`; + console.log({ url: `${baseURL}/${api}`, ...this.baseOpt }); + return { url: `${baseURL}/${api}`, ...this.baseOpt }; + } + + getRemoteInfoDetail = async () => { + const options = this.getBaseOptions( + 'ftms-iov-app-gbook/api/gbook/getRemoteInfoDetail' + ); + const response = await this.$.$request.post(options); + if (response.msg === 'success') { + this.$.dataSource.remoteInfo = response.result; + const safeData = + response.result.list.filter((item) => item.security !== 'safe') || []; + if (safeData.length > 0) { + this.$.dataSource.safeText = `${safeData[0].typeName}:${safeData[0].dataName}`; + } else { + this.$.dataSource.safeText = ``; + } + const dataTime = this.$.dataSource.remoteInfo.datatime.split('-'); + this.$.dataSource.remoteInfo.datatime = `${dataTime[1] || ''}-${ + dataTime[2] || '' + }`; + } else { + this.$.notify(this.name, response.msg); + } + await this.getDrivingMonitorInfo(); + }; + + getDrivingMonitorInfo = async () => { + const options = this.getBaseOptions( + 'ftms-iov-app-gbook/api/gbook/getDrivingMonitorInfo' + ); + const response = await this.$.$request.post(options); + console.log(response); + if (response.msg === 'success') { + this.$.dataSource.monitorInfo = response.result; + } + this.$.dataSource.monitorInfo.oilWasteText = `油耗:${this.$.dataSource.monitorInfo.oilWaste}L/100km`; + this.$.settings.dataSource = this.$.dataSource; + this.$.saveSettings(false); + }; + + getBmuServeHicleInfo = async () => { + let headers = await this.$.getCache('@ftms.headers'); + headers = JSON.parse(headers || '{}'); + this.baseOpt.headers = { + token: headers.token, + 'User-Agent': headers['User-Agent'], + ...this.baseOpt.headers, + }; + const options = { + url: `https://superapp.ftms.com.cn/superapp/users/wt/getbmuservehicleinfo?scriptable=1`, + headers, + }; + if (!this.$.settings.serveInfo) { + const response = await this.$.$request.post(options); + console.log(response); + if (response.code === '200') { + this.$.settings.serveInfo = response.data; + this.$.serveInfo = response.data; + this.$.saveSettings(false); + } else { + this.$.notify(this.name, response.msg); + } + } else { + this.$.serveInfo = this.$.settings.serveInfo || {}; + } + + this.baseOpt.headers.userId = this.$.serveInfo.userId; + this.baseOpt.headers['USER-ID'] = this.$.serveInfo.userId; + this.baseOpt.body = JSON.stringify({ vin: this.$.serveInfo.vin }); + this.baseOpt.headers.Authorization = `Bearer ${this.baseOpt.headers.token}`; + this.baseOpt.headers.accessToken = this.baseOpt.headers.token; + this.baseOpt.headers['ACCESS-TOKEN'] = this.baseOpt.headers.token; + }; + + getOilPrice = async () => { + const location = await Location.current(); + const locationText = await Location.reverseGeocode( + location.latitude, + location.longitude + ); + const { administrativeArea = '' } = locationText[0] || {}; + + const oilNumber = `${this.$.settings.oilNumber || '92'}`; + + const filter = `(CITYNAME="${administrativeArea.replace('省', '')}")`; + const time = Date.now(); + const url = `https://datacenter-web.eastmoney.com/api/data/v1/get?reportName=RPTA_WEB_YJ_JH&columns=ALL&filter=${encodeURIComponent( + filter + )}&sortColumns=DIM_DATE&sortTypes=-1&pageNumber=1&pageSize=1&source=WEB&_=${time}`; + + const options = { url }; + const response = await this.$.$request.post(options); + console.log(response); + if (response.result) { + this.$.dataSource.oilPrice = response.result.data[0]; + this.$.dataSource.oilZDE = response.result.data[0][`ZDE${oilNumber}`]; + this.$.dataSource.oilPriceText = `油价:${ + response.result.data[0][`V${oilNumber}`] + }`; + } + }; +} + +module.exports = Ftms; diff --git a/Scripts/Health.js b/Scripts/Health.js index b6efbc4..582bbbe 100644 --- a/Scripts/Health.js +++ b/Scripts/Health.js @@ -9,326 +9,326 @@ // 添加require,是为了vscode中可以正确引入包,以获得自动补全等功能 -if (typeof require === 'undefined') require = importModule; -const { DmYY, Runing } = require('./DmYY'); +if (typeof require === 'undefined') require = importModule +const { DmYY, Runing } = require('./DmYY') // @组件代码开始 class Widget extends DmYY { constructor(arg) { - super(arg); - this.name = '健康行走步数'; - this.en = 'healthCenter'; - this.maxMonthDist = parseInt(this.settings.maxMonthDist) || 5; // 柱状图比例高度,值越大,柱状范围越广 - this.Run(); + super(arg) + this.name = '健康行走步数' + this.en = 'healthCenter' + this.maxMonthDist = parseInt(this.settings.maxMonthDist) || 5 // 柱状图比例高度,值越大,柱状范围越广 + this.Run() } - widgetFamily = 'medium'; - maxYearDist = 1500; + widgetFamily = 'medium' + maxYearDist = 1500 - color1 = Color.orange(); - lineColor = new Color('#48484b'); - useBoxJS = false; + color1 = Color.orange() + lineColor = new Color('#48484b') + useBoxJS = false - running = {}; - stepsCount = 0; - stepsToday = 0; + running = {} + stepsCount = 0 + stepsToday = 0 init = async () => { try { - await this.getData(); + await this.getData() } catch (e) { - console.log(e); + console.log(e) } - }; + } numberFormat(value) { try { - const param = {}; - let k = 10000; - const size = ['', '万', '亿', '万亿']; - let i; + const param = {} + let k = 10000 + const size = ['', '万', '亿', '万亿'] + let i if (value < k) { - param.value = value; - param.unit = ''; + param.value = value + param.unit = '' } else { - i = Math.floor(Math.log(value) / Math.log(k)); - param.value = (value / Math.pow(k, i)).toFixed(2); - param.unit = size[i]; + i = Math.floor(Math.log(value) / Math.log(k)) + param.value = (value / Math.pow(k, i)).toFixed(2) + param.unit = size[i] } - return param; + return param } catch (e) { - console.log(e); + console.log(e) } } getData = async () => { try { - const fileICloud = FileManager.iCloud(); - const dir = fileICloud.documentsDirectory(); - const path = fileICloud.joinPath(dir, 'health.txt'); - const response = fileICloud.readString(path); - let data = JSON.parse(response); - const dateToday = new Date(); - const year = dateToday.getFullYear(); - let month = dateToday.getMonth() + 1; - let day = dateToday.getDate(); - month = month >= 10 ? month : `0${month}`; - day = day >= 10 ? day : `0${day}`; - const today = `${year}-${month}-${day}`; + const fileICloud = FileManager.iCloud() + const dir = fileICloud.documentsDirectory() + const path = fileICloud.joinPath(dir, 'health.txt') + const response = fileICloud.readString(path) + let data = JSON.parse(response) + const dateToday = new Date() + const year = dateToday.getFullYear() + let month = dateToday.getMonth() + 1 + let day = dateToday.getDate() + month = month >= 10 ? month : `0${month}` + day = day >= 10 ? day : `0${day}` + const today = `${year}-${month}-${day}` data.forEach((item) => { if (item.health_type === 'Walking + Running Distance') { item.samples.forEach((run, index) => { - if (item.samples.length - 1 === index) return; - const date = run.date; - if (!this.running[date]) this.running[date] = 0; - this.running[date] += parseFloat(run.value); - }); + if (item.samples.length - 1 === index) return + const date = run.date + if (!this.running[date]) this.running[date] = 0 + this.running[date] += parseFloat(run.value) + }) } if (item.health_type === 'Steps') { item.samples.forEach((step) => { - if (step.date === today) this.stepsToday = step.value; - this.stepsCount += parseInt(step.value); - }); + if (step.date === today) this.stepsToday = step.value + this.stepsCount += parseInt(step.value) + }) } - }); + }) Object.keys(this.running).forEach((key) => { - this.running[key] = Math.floor(this.running[key] * 100) / 100; - }); + this.running[key] = Math.floor(this.running[key] * 100) / 100 + }) } catch (e) { this.notify( this.name, '健康数据读取失败,请点击使用健康数据快捷指令更新步数', - 'https://www.icloud.com/shortcuts/beb65db5ea0a474abe7ff080410b9ddf', - ); - return false; + 'https://www.icloud.com/shortcuts/beb65db5ea0a474abe7ff080410b9ddf' + ) + return false } - }; + } /*------------------------------------------------------------------------------ 50 km Linien ------------------------------------------------------------------------------*/ createLines(stack) { - let canvas, path; + let canvas, path // 50km Linien - canvas = new DrawContext(); - canvas.size = new Size(292, 82); - canvas.opaque = false; - canvas.respectScreenScale = true; - canvas.setFillColor(this.lineColor); - path = new Path(); - path.addRect(new Rect(0, 0, 292, 1)); - canvas.addPath(path); - canvas.fillPath(); - path = new Path(); - path.addRect(new Rect(0, 15, 292, 1)); - canvas.addPath(path); - canvas.fillPath(); - path = new Path(); - path.addRect(new Rect(0, 30, 292, 1)); - canvas.addPath(path); - canvas.fillPath(); - path = new Path(); - path.addRect(new Rect(0, 45, 292, 1)); - canvas.addPath(path); - canvas.fillPath(); - stack.backgroundImage = canvas.getImage(); + canvas = new DrawContext() + canvas.size = new Size(292, 82) + canvas.opaque = false + canvas.respectScreenScale = true + canvas.setFillColor(this.lineColor) + path = new Path() + path.addRect(new Rect(0, 0, 292, 1)) + canvas.addPath(path) + canvas.fillPath() + path = new Path() + path.addRect(new Rect(0, 15, 292, 1)) + canvas.addPath(path) + canvas.fillPath() + path = new Path() + path.addRect(new Rect(0, 30, 292, 1)) + canvas.addPath(path) + canvas.fillPath() + path = new Path() + path.addRect(new Rect(0, 45, 292, 1)) + canvas.addPath(path) + canvas.fillPath() + stack.backgroundImage = canvas.getImage() } async buildWidget(widget) { // // Stacks definieren - let stackYear = widget.addStack(); - widget.addSpacer(); - let stackMonth = widget.addStack(); + let stackYear = widget.addStack() + widget.addSpacer() + let stackMonth = widget.addStack() // Stacks für Symbol und Jahresauswertung aufbereiten - let stackYear1 = stackYear.addStack(); - stackYear.addSpacer(10); - let stackYear2 = stackYear.addStack(); - let sym = SFSymbol.named('figure.walk'); - let img = stackYear1.addImage(sym.image); - img.tintColor = this.color1; - img.imageSize = new Size(25, 25); - stackYear2.layoutVertically(); - let stackYearCurr = stackYear2.addStack(); - let stackThemItem = stackYear2.addStack(); - let stackToday = stackYear2.addStack(); - - let data = 0; - const runningData = Object.keys(this.running); - if (runningData.length > 12) runningData.splice(0, runningData.length - 12); + let stackYear1 = stackYear.addStack() + stackYear.addSpacer(10) + let stackYear2 = stackYear.addStack() + let sym = SFSymbol.named('figure.walk') + let img = stackYear1.addImage(sym.image) + img.tintColor = this.color1 + img.imageSize = new Size(25, 25) + stackYear2.layoutVertically() + let stackYearCurr = stackYear2.addStack() + let stackThemItem = stackYear2.addStack() + let stackToday = stackYear2.addStack() + + let data = 0 + const runningData = Object.keys(this.running) + if (runningData.length > 12) runningData.splice(0, runningData.length - 12) runningData.forEach((date) => { - const [_, month, day] = date.split('-'); - const stackDay = stackMonth.addStack(); - const value = this.running[date]; - this.createProgressMonth(stackDay, `${month}.${day}`, value); - stackMonth.addSpacer(2); - data += value; - }); - this.createProgressYear(stackYearCurr, '运动', data, this.color1); - - const count = (18 * this.stepsCount) / ((20000 * this.maxMonthDist) / 4); + const [_, month, day] = date.split('-') + const stackDay = stackMonth.addStack() + const value = this.running[date] + this.createProgressMonth(stackDay, `${month}.${day}`, value) + stackMonth.addSpacer(2) + data += value + }) + this.createProgressYear(stackYearCurr, '运动', data, this.color1) + + const count = (18 * this.stepsCount) / ((20000 * this.maxMonthDist) / 4) this.createProgressSteps( stackThemItem, '步数', this.stepsCount, this.color1, - count, - ); - const today = (18 * this.stepsToday) / ((2000 * this.maxMonthDist) / 4); + count + ) + const today = (18 * this.stepsToday) / ((2000 * this.maxMonthDist) / 4) this.createProgressSteps( stackToday, '今日', this.stepsToday, this.color1, - today, - ); + today + ) // 50km Linie - this.createLines(stackMonth); - return widget; + this.createLines(stackMonth) + return widget } createProgressYear(stack, year, dist, color) { - let stackDesc, stackPBar, stackDist, canvas, path, txt, img; + let stackDesc, stackPBar, stackDist, canvas, path, txt, img // Initialisierung - stack.centerAlignContent(); + stack.centerAlignContent() // Stacks definieren - stackDesc = stack.addStack(); - stackPBar = stack.addStack(); - stackDist = stack.addStack(); + stackDesc = stack.addStack() + stackPBar = stack.addStack() + stackDist = stack.addStack() // Beschreibung - stackDesc.size = new Size(30, 0); - txt = stackDesc.addText(year); - txt.font = Font.systemFont(7); - txt.textColor = this.widgetColor; - stackDesc.addSpacer(); + stackDesc.size = new Size(30, 0) + txt = stackDesc.addText(year) + txt.font = Font.systemFont(7) + txt.textColor = this.widgetColor + stackDesc.addSpacer() // Progress-Bar - canvas = new DrawContext(); - canvas.size = new Size(180, 7); - canvas.opaque = false; - canvas.respectScreenScale = true; - canvas.setFillColor(new Color('#48484b')); - path = new Path(); - path.addRoundedRect(new Rect(0, 0, 180, 5), 3, 2); - canvas.addPath(path); - canvas.fillPath(); - canvas.setFillColor(color); - path = new Path(); + canvas = new DrawContext() + canvas.size = new Size(180, 7) + canvas.opaque = false + canvas.respectScreenScale = true + canvas.setFillColor(new Color('#48484b')) + path = new Path() + path.addRoundedRect(new Rect(0, 0, 180, 5), 3, 2) + canvas.addPath(path) + canvas.fillPath() + canvas.setFillColor(color) + path = new Path() path.addRoundedRect( new Rect(0, 0, (180 * dist) / ((200 * this.maxMonthDist) / 4), 5), 3, - 2, - ); - canvas.addPath(path); - canvas.fillPath(); - img = stackPBar.addImage(canvas.getImage()); - img.imageSize = new Size(180, 7); + 2 + ) + canvas.addPath(path) + canvas.fillPath() + img = stackPBar.addImage(canvas.getImage()) + img.imageSize = new Size(180, 7) // Distanz - stackDist.addSpacer(10); - txt = stackDist.addText(Math.round(dist).toString() + ' km'); - txt.font = Font.systemFont(7); - txt.textColor = this.widgetColor; + stackDist.addSpacer(10) + txt = stackDist.addText(Math.round(dist).toString() + ' km') + txt.font = Font.systemFont(7) + txt.textColor = this.widgetColor } createProgressSteps(stack, year, dist, color, rectScale) { - let stackDesc, stackPBar, stackDist, canvas, path, txt, img; + let stackDesc, stackPBar, stackDist, canvas, path, txt, img // Initialisierung - stack.centerAlignContent(); + stack.centerAlignContent() // Stacks definieren - stackDesc = stack.addStack(); - stackPBar = stack.addStack(); - stackDist = stack.addStack(); + stackDesc = stack.addStack() + stackPBar = stack.addStack() + stackDist = stack.addStack() // Beschreibung - stackDesc.size = new Size(30, 0); - txt = stackDesc.addText(year); - txt.font = Font.systemFont(7); - txt.textColor = this.widgetColor; - stackDesc.addSpacer(); + stackDesc.size = new Size(30, 0) + txt = stackDesc.addText(year) + txt.font = Font.systemFont(7) + txt.textColor = this.widgetColor + stackDesc.addSpacer() // Progress-Bar - canvas = new DrawContext(); - canvas.size = new Size(180, 7); - canvas.opaque = false; - canvas.respectScreenScale = true; - canvas.setFillColor(new Color('#48484b')); - path = new Path(); - path.addRoundedRect(new Rect(0, 0, 180, 5), 3, 2); - canvas.addPath(path); - canvas.fillPath(); - canvas.setFillColor(color); - path = new Path(); - const numberText = this.numberFormat(dist); - - path.addRoundedRect(new Rect(0, 0, rectScale, 5), 3, 2); - canvas.addPath(path); - canvas.fillPath(); - img = stackPBar.addImage(canvas.getImage()); - img.imageSize = new Size(180, 7); + canvas = new DrawContext() + canvas.size = new Size(180, 7) + canvas.opaque = false + canvas.respectScreenScale = true + canvas.setFillColor(new Color('#48484b')) + path = new Path() + path.addRoundedRect(new Rect(0, 0, 180, 5), 3, 2) + canvas.addPath(path) + canvas.fillPath() + canvas.setFillColor(color) + path = new Path() + const numberText = this.numberFormat(dist) + + path.addRoundedRect(new Rect(0, 0, rectScale, 5), 3, 2) + canvas.addPath(path) + canvas.fillPath() + img = stackPBar.addImage(canvas.getImage()) + img.imageSize = new Size(180, 7) // Distanz - stackDist.addSpacer(10); + stackDist.addSpacer(10) - txt = stackDist.addText(numberText.value + ` ${numberText.unit}步`); - txt.font = Font.systemFont(7); - txt.textColor = this.widgetColor; + txt = stackDist.addText(numberText.value + ` ${numberText.unit}步`) + txt.font = Font.systemFont(7) + txt.textColor = this.widgetColor } createTemplateItem(stack, desc) { // Stacks - const txt = stack.addText(desc); - txt.font = Font.systemFont(7); - txt.textColor = this.widgetColor; + const txt = stack.addText(desc) + txt.font = Font.systemFont(7) + txt.textColor = this.widgetColor } /*------------------------------------------------------------------------------ Balkenanzeige für Monatsauswertung aufbereiten ------------------------------------------------------------------------------*/ createProgressMonth(stack, month, dist3) { - let stackDist, stackPBar, stackDesc, canvas, path, s, img, txt; + let stackDist, stackPBar, stackDesc, canvas, path, s, img, txt // Stacks definieren - stack.layoutVertically(); - stackPBar = stack.addStack(); - stack.addSpacer(5); - stackDesc = stack.addStack(); - stackDist = stack.addStack(); + stack.layoutVertically() + stackPBar = stack.addStack() + stack.addSpacer(5) + stackDesc = stack.addStack() + stackDist = stack.addStack() // Progress-Bar - canvas = new DrawContext(); - canvas.size = new Size(17, 60); - canvas.opaque = false; - canvas.respectScreenScale = true; - - canvas.setFillColor(this.color1); - path = new Path(); - s = (50 * dist3) / this.maxMonthDist; - path.addRect(new Rect(6, 60 - s, 8, s)); - canvas.addPath(path); - canvas.fillPath(); - img = stackPBar.addImage(canvas.getImage()); - img.imageSize = new Size(17, 60); + canvas = new DrawContext() + canvas.size = new Size(17, 60) + canvas.opaque = false + canvas.respectScreenScale = true + + canvas.setFillColor(this.color1) + path = new Path() + s = (50 * dist3) / this.maxMonthDist + path.addRect(new Rect(6, 60 - s, 8, s)) + canvas.addPath(path) + canvas.fillPath() + img = stackPBar.addImage(canvas.getImage()) + img.imageSize = new Size(17, 60) // Monat - stackDesc.size = new Size(23, 10); - txt = stackDesc.addText(month); - txt.font = Font.systemFont(7); - txt.textColor = this.widgetColor; - txt.centerAlignText(); + stackDesc.size = new Size(23, 10) + txt = stackDesc.addText(month) + txt.font = Font.systemFont(7) + txt.textColor = this.widgetColor + txt.centerAlignText() // Distanz aktuelle Jahr - stackDist.size = new Size(20, 8); - txt = stackDist.addText(Math.round(dist3).toString()); - txt.font = Font.systemFont(6); - txt.textColor = this.widgetColor; - txt.centerAlignText(); + stackDist.size = new Size(20, 8) + txt = stackDist.addText(Math.round(dist3).toString()) + txt.font = Font.systemFont(6) + txt.textColor = this.widgetColor + txt.centerAlignText() } /** @@ -336,12 +336,12 @@ Balkenanzeige für Monatsauswertung aufbereiten * 可以根据 this.widgetFamily 来判断小组件尺寸,以返回不同大小的内容 */ async render() { - await this.init(); - const widget = new ListWidget(); - await this.getWidgetBackgroundImage(widget); - await this.buildWidget(widget); - await widget.presentMedium(); - if (config.runsFromHomeScreen) return widget; + await this.init() + const widget = new ListWidget() + await this.getWidgetBackgroundImage(widget) + await this.buildWidget(widget) + await widget.presentMedium() + if (config.runsFromHomeScreen) return widget } Run = () => { @@ -350,58 +350,55 @@ Balkenanzeige für Monatsauswertung aufbereiten await this.notify( this.name, '点击安装捷径', - 'https://www.icloud.com/shortcuts/beb65db5ea0a474abe7ff080410b9ddf', - ); - }); + 'https://www.icloud.com/shortcuts/beb65db5ea0a474abe7ff080410b9ddf' + ) + }) this.registerAction('柱状比例', async () => { await this.setAlertInput( '设置柱状比例', ' 柱状图比例高度,值越大,柱状范围越广', - { maxMonthDist: '比例默认值,5' }, - ); - }); - this.registerAction('皮肤颜色', this.setWidgetSkin); - this.registerAction('刻度颜色', this.setWidgetScale); - this.registerAction('基础设置', this.setWidgetConfig); + { maxMonthDist: '比例默认值,5' } + ) + }) + this.registerAction('皮肤颜色', this.setWidgetSkin) + this.registerAction('刻度颜色', this.setWidgetScale) + this.registerAction('基础设置', this.setWidgetConfig) } const skinColor = !this.isNight ? this.settings.lightSkinColor - : this.settings.darkSkinColor; - this.color1 = skinColor ? new Color(skinColor) : this.color1; + : this.settings.darkSkinColor + this.color1 = skinColor ? new Color(skinColor) : this.color1 const scaleColor = !this.isNight ? this.settings.lightScaleColor - : this.settings.darkScaleColor; - this.lineColor = scaleColor ? new Color(scaleColor) : this.lineColor; - }; + : this.settings.darkScaleColor + this.lineColor = scaleColor ? new Color(scaleColor) : this.lineColor + } setWidgetSkin = async () => { await this.setLightAndDark( '柱状颜色', false, 'lightSkinColor', - 'darkSkinColor', - ); - }; + 'darkSkinColor' + ) + } setWidgetScale = async () => { await this.setLightAndDark( '刻度颜色', false, 'lightScaleColor', - 'darkScaleColor', - ); - }; + 'darkScaleColor' + ) + } } - -// @组件代码结束 -if (config.runsFromHomeScreen || config.runsInApp) { - Runing(Widget, '', false); +let params = args.shortcutParameter +if (params) { + const fileICloud = FileManager.iCloud() + const path = fileICloud.documentsDirectory() + fileICloud.writeString(path + '/health.txt', JSON.stringify(params)) + Script.complete() } else { - let params = args.shortcutParameter; - if (params) { - const fileICloud = FileManager.iCloud(); - const path = fileICloud.documentsDirectory(); - fileICloud.writeString(path + '/health.txt', JSON.stringify(params)); - Script.complete(); - } + await Runing(Widget, '', false) } +// @组件代码结束 diff --git a/Scripts/Oild.js b/Scripts/Oild.js new file mode 100644 index 0000000..f2303cd --- /dev/null +++ b/Scripts/Oild.js @@ -0,0 +1,429 @@ +// Variables used by Scriptable. +// These must be at the very top of the file. Do not edit. +// icon-color: deep-gray; icon-glyph: oil; + +// 添加require,是为了vscode中可以正确引入包,以获得自动补全等功能 +if (typeof require === 'undefined') require = importModule; +const { DmYY, Runing } = require('./DmYY'); + +const enumConfig = { + 89: '汽油', + 92: '汽油', + 95: '汽油', + 98: '汽油', + 0: '柴油', +}; + +const provinces = [ + '北京', + '天津', + '上海', + '重庆', + '河北', + '山西', + '辽宁', + '吉林', + '黑龙江', + '江苏', + '浙江', + '安徽', + '福建', + '江西', + '山东', + '河南', + '湖北', + '湖南', + '广东', + '海南', + '四川', + '贵州', + '云南', + '陕西', + '甘肃', + '青海', + '台湾', + '内蒙古', + '广西', + '西藏', + '宁夏', + '新疆', + '香港', + '澳门', +]; + +const squareColor = '#8165AC'; +const processColor = [`#7517F8`, `#E323FF`]; +const processBarColor = [`#4da1ff`, `#4dffdf`]; +const processBarBgColor = '#5A5A89'; + +// @组件代码开始 +class Widget extends DmYY { + constructor(arg) { + super(arg); + this.en = 'oilWidget'; + this.name = '油价'; + if (config.runsInApp) { + this.registerAction( + '油价设置', + () => { + return this.setAlertInput('油价设置', '设置类型,多个英文逗号分割', { + oilNumber: '92,95,89,0', + }); + }, + { name: 'oilcan', color: '#E64C57' } + ); + + this.registerAction({ + icon: { name: 'location.circle', color: '#8BE39D' }, + type: 'switch', + title: '自动定位', + val: 'location', + }); + + this.registerAction({ + icon: { name: 'mappin.and.ellipse.circle', color: '#5BBFF6' }, + type: 'select', + title: '油价省份', + val: 'province', + placeholder: '地区', + options: provinces, + }); + this.registerAction('基础设置', this.setWidgetConfig); + } + } + + dataSource = { + DIM_ID: '', + DIM_DATE: '', + CITYNAME: '', + V0: 0, + V95: 0, + V92: 0, + V89: 0, + ZDE0: 0, + ZDE92: 0, + ZDE95: 0, + ZDE89: 0, + QE0: 0, + QE92: 0, + QE95: 0, + QE89: 0, + }; + + init = async () => { + try { + this.oilNumber = `${this.settings.oilNumber || '92'}`.split(','); + const oilNumber = []; + this.oilNumber.forEach((item) => { + if (item && ['92', '95', '89', '0'].includes(item)) { + oilNumber.push(item); + } + }); + this.oilNumber = oilNumber; + if (!this.oilNumber.length) + return this.notify(this.name, '请设置油价型号:92,95,89,0'); + } catch (error) { + return console.log('请设置正确的油价'); + } + this.province = this.settings.province; + this.location = this.settings.location; + + if (this.settings.dataSource) { + this.dataSource = this.settings.dataSource[0]; + } else { + await this.cacheData(); + } + this.cacheData(); + }; + + cacheData = async () => { + try { + await this.getOilPrice(); + } catch (e) { + console.log(e); + } + }; + + getOilPrice = async () => { + if (this.location) { + const location = await Location.current(); + const locationText = await Location.reverseGeocode( + location.latitude, + location.longitude + ); + const { administrativeArea = '' } = locationText[0] || {}; + this.province = administrativeArea.replace('省', ''); + this.settings.province = this.province; + this.saveSettings(false); + } + + if (!this.province) return this.notify(this.name, '请设置油价省份!!'); + console.log(`当前省份:${this.province}`); + this.province = `(CITYNAME="${this.province}")`; + const time = Date.now(); + const url = `https://datacenter-web.eastmoney.com/api/data/v1/get?reportName=RPTA_WEB_YJ_JH&columns=ALL&filter=${encodeURIComponent( + this.province + )}&sortColumns=DIM_DATE&sortTypes=-1&pageNumber=1&pageSize=6&source=WEB&_=${time}`; + + const options = { url }; + const response = await this.$request.post(options); + console.log(response); + if (response.result) { + this.dataSource = response.result.data[0]; + this.settings.dataSource = response.result.data; + this.saveSettings(false); + } + }; + + renderImage = async (uri) => { + return this.$request.get(uri, 'IMG'); + }; + + notSupport(w) { + const stack = w.addStack(); + stack.addText('暂不支持'); + return w; + } + + renderSmall = async (w) => { + const headerStack = w.addStack(); + const dollarImage = SFSymbol.named(`yensign.circle`).image; + headerStack.centerAlignContent(); + const dollarWidgetImg = headerStack.addImage(dollarImage); + dollarWidgetImg.tintColor = new Color('#f5222d'); + dollarWidgetImg.imageSize = new Size(24, 24); + + headerStack.addSpacer(); + + w.addSpacer(); + const topStack = w.addStack(); + const topLStack = topStack.addStack(); + topLStack.layoutVertically(); + topLStack.addSpacer(); + topLStack.bottomAlignContent(); + const oilPrice = (this.dataSource[`V${this.oilNumber[0]}`] || '').toFixed( + 2 + ); + const timer = (this.dataSource.DIM_DATE.split(' ')[0] || '').split('-'); + const oilNumText = topLStack.addText(`${oilPrice}`); + oilNumText.textColor = this.widgetColor; + oilNumText.minimumScaleFactor = 0.6; + oilNumText.font = Font.boldSystemFont(38); + topLStack.addSpacer(); + + const oilStatus = this.dataSource[`ZDE${this.oilNumber[0]}`] > 0; + const oilZdeImage = SFSymbol.named( + oilStatus ? 'arrow.up' : 'arrow.up' + ).image; + topStack.addSpacer(); + const topRStack = topStack.addStack(); + topRStack.addSpacer(); + topRStack.layoutVertically(); + topRStack.bottomAlignContent(); + const zdeStack = topRStack.addStack(); + zdeStack.setPadding(2, 6, 0, 0); + const oilZdeWidgetImg = zdeStack.addImage(oilZdeImage); + oilZdeWidgetImg.tintColor = new Color(oilStatus ? '#f5222d' : '#a0d911'); + oilZdeWidgetImg.imageSize = new Size(16, 16); + + const timerText = topRStack.addText(`${timer[2]}/${timer[1]}`); + timerText.textColor = this.widgetColor; + timerText.font = Font.systemFont(12); + topRStack.addSpacer(); + + w.addSpacer(); + + const bottomStack = w.addStack(); + bottomStack.addSpacer(); + const rightText = bottomStack.addText( + `${this.oilNumber[0]}#${enumConfig[this.oilNumber[0]]}` + ); + oilNumText.textColor = this.widgetColor; + rightText.font = Font.boldSystemFont(18); + rightText.textOpacity = 0.3; + rightText.rightAlignText(); + bottomStack.addSpacer(5); + return w; + }; + + rowData = (w, oilNumber) => { + const oilPrice = (this.dataSource[`V${oilNumber}`] || '').toFixed(2); + const oilZde = (this.dataSource[`ZDE${oilNumber}`] || '').toFixed(2); + const oilType = enumConfig[oilNumber] || ''; + + const colStack = w.addStack(); + + const oilNumberStack = colStack.addStack(); + const colSize = new Size(40, 40); + oilNumberStack.size = colSize; + oilNumberStack.cornerRadius = 8; + oilNumberStack.borderWidth = 4; + oilNumberStack.borderColor = new Color(squareColor); + oilNumberStack.centerAlignContent(); + this.provideText(`${oilNumber}`, oilNumberStack, { + font: 'bold', + size: 26, + color: new Color(squareColor), + }); + + colStack.addSpacer(7); + + const oilInfoStack = colStack.addStack(); + oilInfoStack.size = new Size(65, colSize.height); + oilInfoStack.layoutVertically(); + oilInfoStack.addSpacer(); + this.provideText(`#${oilType}`, oilInfoStack, { + font: 'light', + size: 12, + color: this.widgetColor, + opacity: 0.5, + }); + + oilInfoStack.addSpacer(2); + + this.provideText(`${oilPrice}`, oilInfoStack, { + font: 'medium', + size: 18, + color: this.widgetColor, + }); + oilInfoStack.addSpacer(); + + const processStack = colStack.addStack(); + processStack.centerAlignContent(); + const processVerWidth = 10; + + let maxCount = 0; + const oilHistory = this.settings.dataSource.map((item) => { + const value = + (item[`ZDE${oilNumber}`] / item[`QE${oilNumber}`]).toFixed(2) * 100; + if (maxCount < value) maxCount = value; + return value; + }); + + maxCount = maxCount * 1.5; + + oilHistory.forEach((item) => { + const processItemStack = processStack.addStack(); + processItemStack.size = new Size(processVerWidth, colSize.height); + processItemStack.cornerRadius = processVerWidth / 2; + processItemStack.backgroundColor = new Color(processBarBgColor); + if (item > 0) processItemStack.addSpacer(); + processItemStack.layoutVertically(); + + const itemBarStack = processItemStack.addStack(); + itemBarStack.cornerRadius = processItemStack.cornerRadius; + itemBarStack.size = new Size( + processVerWidth, + colSize.height * (Math.abs(item) / maxCount) + ); + itemBarStack.backgroundGradient = this.gradient(processColor); + if (item < 0) processItemStack.addSpacer(); + processStack.addSpacer(); + }); + + colStack.addSpacer(); + + const oilZdeStack = colStack.addStack(); + + const oilZdeSize = new Size(80, 10); + + oilZdeStack.layoutVertically(); + oilZdeStack.size = new Size(oilZdeSize.width, colSize.height); + oilZdeStack.centerAlignContent(); + + const oilZdeValueStack = oilZdeStack.addStack(); + oilZdeValueStack.centerAlignContent(); + oilZdeValueStack.addSpacer(); + this.provideText(`${oilZde > 0 ? '+' : ''} ${oilZde}`, oilZdeValueStack, { + font: 'light', + size: 14, + color: this.widgetColor, + }); + + const oilZdeImage = SFSymbol.named( + oilZde > 0 ? 'arrow.up' : 'arrow.down' + ).image; + + oilZdeValueStack.addSpacer(10); + + const oilZdeWidgetImg = oilZdeValueStack.addImage(oilZdeImage); + oilZdeWidgetImg.tintColor = new Color(oilZde > 0 ? '#f5222d' : '#a0d911'); + oilZdeWidgetImg.imageSize = new Size(10, 10); + + oilZdeValueStack.addSpacer(); + + oilZdeStack.addSpacer(5); + + const oilZdeValue = Math.abs(parseFloat(oilZde)); + const processBarBgStack = oilZdeStack.addStack(); + + processBarBgStack.cornerRadius = 5; + processBarBgStack.size = oilZdeSize; + processBarBgStack.backgroundColor = new Color(processBarBgColor); + + if (oilZde < 0) processBarBgStack.addSpacer(); + + const processBarStack = processBarBgStack.addStack(); + + const linear = new LinearGradient(); + linear.colors = processBarColor.map((item) => new Color(item)); + linear.locations = [0, 0.5]; + linear.startPoint = new Point(0, 0); + linear.endPoint = new Point(1, 1); + + processBarStack.backgroundGradient = linear; + processBarStack.cornerRadius = oilZdeSize.height / 2; + processBarStack.size = new Size( + parseInt(oilZdeSize.width * oilZdeValue), + oilZdeSize.height + ); + + if (oilZde > 0) processBarBgStack.addSpacer(); + }; + + renderLarge = async (w) => { + return this.notSupport(w); + }; + + renderBorder = (stack) => { + stack.borderWidth = 1; + }; + + gradient = (color, config = { locations: [0, 0.5] }) => { + const linear = new LinearGradient(); + linear.colors = color.map((item) => new Color(item)); + linear.locations = config.locations; + return linear; + }; + + renderMedium = async (w) => { + w.addSpacer(); + this.oilNumber.forEach((oilNumber, index) => { + if (index > 2) return; + this.rowData(w, oilNumber); + w.addSpacer(); + }); + + return w; + }; + + /** + * 渲染函数,函数名固定 + * 可以根据 this.widgetFamily 来判断小组件尺寸,以返回不同大小的内容 + */ + async render() { + await this.init(); + const widget = new ListWidget(); + await this.getWidgetBackgroundImage(widget); + if (this.widgetFamily === 'medium') { + return await this.renderMedium(widget); + } else if (this.widgetFamily === 'large') { + return await this.renderLarge(widget); + } else { + return await this.renderSmall(widget); + } + } +} + +// @组件代码结束 +await Runing(Widget, '', false); //远程开发环境 diff --git a/Scripts/PriceWidgets.js b/Scripts/PriceWidgets.js new file mode 100644 index 0000000..fd60bf6 --- /dev/null +++ b/Scripts/PriceWidgets.js @@ -0,0 +1,332 @@ +// Variables used by Scriptable. +// These must be at the very top of the file. Do not edit. +// icon-color: deep-blue; icon-glyph: dollar; + +// 添加require,是为了vscode中可以正确引入包,以获得自动补全等功能 +if (typeof require === 'undefined') require = importModule; +const { DmYY, Runing } = require('./DmYY'); + +// @组件代码开始 +class Widget extends DmYY { + constructor(arg) { + super(arg); + this.en = ' btc'; + this.name = '比特币'; + + if (config.runsInApp) { + this.registerAction({ + icon: { name: 'centsign.circle', color: '#feda31' }, + type: 'input', + title: '比特币种类', + desc: '设置关注种类', + placeholder: 'BTC,ETH,BNB', + val: 'btcType', + }); + this.registerAction('基础设置', this.setWidgetConfig); + } + } + + format = (str) => { + return parseInt(str) >= 10 ? str : `0${str}`; + }; + + endpoint = 'https://api.coingecko.com/api/v3'; + nomicsEndpoint = 'https://api.nomics.com/v1'; + + dataSource = []; + + init = async () => { + if (this.settings.dataSource && !config.runsInApp) { + this.dataSource = this.settings.dataSource; + } else { + await this.cacheData(this.settings.btcType); + } + this.cacheData(this.settings.btcType); + }; + + cacheData = async (params) => { + try { + const ids = await this.transforBtcType(params); + let response = await this.$request.get( + `${this.endpoint}/coins/markets?vs_currency=usd&ids=${ids}`, + 'STRING' + ); + this.dataSource = []; + response = JSON.parse(response); + if (!response.length) response = await this.getAllJson(); + if (ids) { + const idsData = ids.split(','); + idsData.forEach((id) => { + const it = response.find((item) => item.id === id); + if (it && this.dataSource.length < 6) { + this.dataSource.push({ + id: it.id, + name: it.name, + image: it.image, + symbol: it.symbol.toUpperCase(), + current_price: '' + it.current_price, + high_24h: it.high_24h, + low_24h: it.low_24h, + price_change_percentage_24h: it.price_change_percentage_24h, + last_updated: it.last_updated, + }); + } + }); + } else { + response.forEach((it, index) => { + if (index > 5) return; + this.dataSource.push({ + id: it.id, + name: it.name, + image: it.image, + symbol: it.symbol.toUpperCase(), + current_price: '' + it.current_price, + high_24h: it.high_24h, + low_24h: it.low_24h, + price_change_percentage_24h: it.price_change_percentage_24h, + last_updated: it.last_updated, + }); + }); + } + + this.settings.dataSource = this.dataSource; + this.saveSettings(false); + } catch (e) { + console.log(e); + return []; + } + }; + + transforBtcType = async (params) => { + let btcType; + if (params) btcType = params.split(','); + + const btcAll = await this.getAllJson(); + + if (!btcType) + return btcAll + .filter((item, index) => index < 6) + .map((item) => item.id) + .join(','); + + return btcType + .map((item) => { + const result = + btcAll.find((btc) => btc.symbol.toUpperCase() === item) || {}; + return result.id; + }) + .filter((item) => !!item) + .join(','); + }; + + getAllJson = async () => { + const cachePath = this.FILE_MGR.joinPath( + this.FILE_MGR.libraryDirectory(), + `${Script.name()}/datas` + ); + const filename = `${cachePath}/BTC.json`; + if (!this.FILE_MGR.fileExists(cachePath)) + this.FILE_MGR.createDirectory(cachePath, true); + + if (this.FILE_MGR.fileExists(filename)) { + const data = Data.fromFile(`${cachePath}/BTC.json`).toRawString(); + return JSON.parse(data); + } else { + const response = await this.$request.get( + `${this.endpoint}/coins/markets?vs_currency=usd&ids=` + ); + const data = Data.fromString(JSON.stringify(response)); + this.FILE_MGR.write(filename, data); + return response; + } + }; + + renderImage = async (uri) => { + return this.$request.get(uri, 'IMG'); + }; + + notSupport(w) { + const stack = w.addStack(); + stack.addText('暂不支持'); + return w; + } + + getSmallBg = async (url) => { + const webview = new WebView(); + let js = `const canvas = document.createElement('canvas'); + const ctx = canvas.getContext('2d'); + const img = new Image(); + img.crossOrigin = 'anonymous'; + img.onload = () => { + const { width, height } = img + canvas.width = width + canvas.height = height + ctx.globalAlpha = 0.3 + ctx.drawImage( + img, + -width / 2 + 50, + -height / 2 + 50, + width, + height + ) + const uri = canvas.toDataURL() + completion(uri); + }; + img.src = 'data:image/png;base64,${Data.fromPNG(url).toBase64String()}'`; + let image = await webview.evaluateJavaScript(js, true); + image = image.replace(/^data\:image\/\w+;base64,/, ''); + return Image.fromData(Data.fromBase64String(image)); + }; + + renderSmall = async (widget) => { + const market = this.dataSource[0] || {}; + + widget.url = `https://www.coingecko.com/en/coins/${market.id}`; + + const image = await this.renderImage(market.image); + const backgroundImg = await this.getSmallBg(image); + widget.backgroundColor = this.backGroundColor; + widget.backgroundImage = backgroundImg; + widget.setPadding(12, 12, 12, 12); + const coin = widget.addText(market.symbol.toUpperCase()); + coin.font = Font.heavySystemFont(24); + coin.textColor = this.widgetColor; + + coin.rightAlignText(); + const name = widget.addText(market.name); + name.font = Font.systemFont(10); + name.textColor = Color.gray(); + name.rightAlignText(); + widget.addSpacer(); + + const trend = widget.addText( + `${market.price_change_percentage_24h.toFixed(2)}%` + ); + trend.font = Font.semiboldSystemFont(16); + trend.textColor = + market.price_change_percentage_24h >= 0 ? Color.green() : Color.red(); + + trend.rightAlignText(); + const price = widget.addText(`$ ${market.current_price}`); + price.font = Font.boldSystemFont(28); + price.textColor = this.widgetColor; + price.rightAlignText(); + price.lineLimit = 1; + price.minimumScaleFactor = 0.1; + const history = widget.addText( + `H: ${market.high_24h}, L: ${market.low_24h}` + ); + history.font = Font.systemFont(10); + history.textColor = Color.gray(); + history.rightAlignText(); + history.lineLimit = 1; + history.minimumScaleFactor = 0.1; + return widget; + }; + + rowCell = async (rowStack, market) => { + rowStack.url = `https://www.coingecko.com/zh/coins/${market.id}`; + rowStack.layoutHorizontally(); + const image = await this.renderImage(market.image); + const iconImage = rowStack.addImage(image); + iconImage.imageSize = new Size(28, 28); + iconImage.cornerRadius = 14; + + rowStack.addSpacer(10); + + const centerStack = rowStack.addStack(); + centerStack.layoutVertically(); + + const topCenterStack = centerStack.addStack(); + topCenterStack.layoutHorizontally(); + + const titleText = topCenterStack.addText(market.symbol); + titleText.textColor = this.widgetColor; + titleText.font = this.provideFont('semibold', 16); + + topCenterStack.addSpacer(); + + const priceText = topCenterStack.addText(`$ ${market.current_price}`); + priceText.textColor = this.widgetColor; + priceText.font = this.provideFont('semibold', 15); + priceText.rightAlignText(); + + const bottomCenterStack = centerStack.addStack(); + bottomCenterStack.layoutHorizontally(); + + const subText = bottomCenterStack.addText(market.name); + subText.textColor = Color.gray(); + subText.font = this.provideFont('semibold', 10); + + bottomCenterStack.addSpacer(); + + const historyText = bottomCenterStack.addText( + `H: ${market.high_24h}, L: ${market.low_24h}` + ); + historyText.textColor = Color.gray(); + historyText.font = this.provideFont('semibold', 10); + historyText.rightAlignText(); + + rowStack.addSpacer(8); + + const rateStack = rowStack.addStack(); + rateStack.size = new Size(72, 28); + rateStack.centerAlignContent(); + rateStack.cornerRadius = 4; + rateStack.backgroundColor = + market.price_change_percentage_24h >= 0 ? Color.green() : Color.red(); + const rateText = rateStack.addText( + (market.price_change_percentage_24h >= 0 ? '+' : '') + + market.price_change_percentage_24h.toFixed(2) + + '%' + ); + rateText.textColor = new Color('#fff', 0.9); + rateText.font = this.provideFont('semibold', 14); + rateText.minimumScaleFactor = 0.01; + rateText.lineLimit = 1; + }; + + renderLarge = async (widget) => { + widget.setPadding(12, 12, 12, 12); + const containerStack = widget.addStack(); + containerStack.layoutVertically(); + for (let index = 0; index < this.dataSource.length; index++) { + const item = this.dataSource[index]; + const rowCellStack = containerStack.addStack(); + await this.rowCell(rowCellStack, item); + if (index !== this.dataSource.length - 1) containerStack.addSpacer(); + } + return widget; + }; + + renderMedium = async (widget) => { + widget.setPadding(12, 12, 12, 12); + const containerStack = widget.addStack(); + containerStack.layoutVertically(); + for (let index = 0; index < this.dataSource.length; index++) { + if (index > 2) return; + const item = this.dataSource[index]; + const rowCellStack = containerStack.addStack(); + await this.rowCell(rowCellStack, item); + if (index !== 2) containerStack.addSpacer(); + } + return widget; + }; + + /** + * 渲染函数,函数名固定 + * 可以根据 this.widgetFamily 来判断小组件尺寸,以返回不同大小的内容 + */ + async render() { + await this.init(); + const widget = new ListWidget(); + if (this.widgetFamily === 'small') await this.renderSmall(widget); + await this.getWidgetBackgroundImage(widget); + if (this.widgetFamily === 'medium') await this.renderMedium(widget); + if (this.widgetFamily === 'large') await this.renderLarge(widget); + return widget; + } +} + +// @组件代码结束 +await Runing(Widget, '', false); //远程开发环境 diff --git a/Scripts/Spotify.js b/Scripts/Spotify.js new file mode 100644 index 0000000..f1446b8 --- /dev/null +++ b/Scripts/Spotify.js @@ -0,0 +1,672 @@ +// Variables used by Scriptable. +// These must be at the very top of the file. Do not edit. +// icon-color: deep-blue; icon-glyph: dollar; + +// 添加require,是为了vscode中可以正确引入包,以获得自动补全等功能 +if (typeof require === "undefined") require = importModule; +const { DmYY, Runing } = require("./DmYY"); + +const logo = `https://open.spotifycdn.com/cdn/images/favicon.0f31d2ea.ico`; +const fontFamily = "AmericanTypewriter-Bold"; + +const api = { + me: `https://api.spotify.com/v1/me`, + accounts: `https://accounts.spotify.com/api/token`, + currentlyPlayingTrack: `https://api.spotify.com/v1/me/player/currently-playing`, + getTrack: `https://api.spotify.com/v1/tracks/11dFghVXANMlKmJXsNCbNl`, + palyState: `https://api.spotify.com/v1/me/player`, + playlists: `https://api.spotify.com/v1/me/playlists`, + following: `https://api.spotify.com/v1/me/following?type=artist`, +}; + +const scope = [ + "ugc-image-upload", + "playlist-read-collaborative", + "playlist-modify-private", + "playlist-modify-public", + "playlist-read-private", + "user-read-playback-position", + "user-read-recently-played", + "user-top-read", + "user-modify-playback-state", + "user-read-currently-playing", + "user-read-playback-state", + "user-read-private", + "user-read-email", + "user-library-modify", + "user-library-read", + "user-follow-modify", + "user-follow-read", + "streaming", + "app-remote-control", +]; + +const webUri = `https://accounts.spotify.com/zh-CN/login?continue=${encodeURIComponent( + "https://developer.spotify.com/documentation/web-api/reference/get-current-users-profile" +)}`; + +function generateRandomString(length) { + let text = ""; + let possible = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + + for (let i = 0; i < length; i++) { + text += possible.charAt(Math.floor(Math.random() * possible.length)); + } + return text; +} + +function convertMillisecondsToHMS(milliseconds) { + // 计算总共有多少秒 + let totalSeconds = Math.floor(milliseconds / 1000); + + // 计算分钟数 + let minutes = Math.floor(totalSeconds / 60); + + // 剩余的秒数 + let seconds = totalSeconds - minutes * 60; + + if (minutes < 10) { + minutes = "0" + minutes; + } + if (seconds < 10) { + seconds = "0" + seconds; + } + + // 组合时分秒 + return minutes + ":" + seconds; +} + +// @组件代码开始 +class Widget extends DmYY { + constructor(arg) { + super(arg); + this.en = "Spotify"; + this.name = "声田音乐"; + this.SETTING_KEY = this.md5(this.en); + this._init(); + this.auth2 = args.queryParameters.code + ? args.queryParameters + : this.settings.auth2; + + this.settings.auth2 = this.auth2; + this.saveSettings(false); + + if (config.runsInApp) { + this.registerAction({ + icon: { + name: "airplayaudio.circle.fill", + color: "#65D46E", + }, + title: "登录", + val: "login", + name: "login", + dismissOnSelect: true, + onClick: () => { + return this.getWebToken(); + }, + }); + + this.registerAction({ + icon: { + name: "music.note", + color: "#65D46E", + }, + title: "客户端 ID", + val: "clientId", + name: "clientId", + type: "input", + dismissOnSelect: true, + }); + + this.registerAction("基础设置", this.setWidgetConfig); + } + } + + baseUri = ""; + accessToken = ""; + dataSource = { + currentlyPlayingTrack: {}, + playlists: {}, + me: {}, + following: {}, + }; + + init = async () => { + await this.getAccessToken(); + if (!this.settings.accessToken) return this.notify(this.name, "请登录!!"); + this.accessToken = `${this.settings.accessToken.token_type} ${this.settings.accessToken.access_token}`; + this.dataSource.playlists = await this.getApiRes(api.playlists); + this.dataSource.following = await this.getApiRes(api.following); + this.dataSource.me = await this.getApiRes(api.me); + this.dataSource.currentlyPlayingTrack = await this.getApiRes( + api.currentlyPlayingTrack + ); + }; + + getAccessToken = async () => { + const { accessToken } = this.settings; + if (accessToken) { + const diffTime = (new Date().getTime() - accessToken.time) / 1000; + if (diffTime > accessToken.expires_in) { + return this.refreshToken(); + } + } else if (this.auth2) { + if (!this.auth2.code) + return this.notify( + this.name, + "请登录!!!", + "https://developer.spotify.com" + ); + const { clientId, codeVerifier } = this.settings; + + const options = { + code: this.auth2.code, + client_id: clientId, + code_verifier: codeVerifier, + grant_type: "authorization_code", + redirect_uri: "scriptable:///run/Spotify?openEditor=true", + }; + + const body = Object.entries(options) + .map((item) => `${item[0]}=${item[1]}`) + .join("&"); + + const response = await this.$request.post(api.accounts, { + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, + body: body, + }); + + console.log(response); + + if (!response.error) { + this.auth2 = null; + this.settings.auth2 = null; + this.settings.accessToken = { + ...this.settings.accessToken, + ...response, + time: new Date().getTime(), + }; + console.log( + `获取 Token 成功,有效期 ${response.expires_in / (60 * 10)} 分钟` + ); + this.saveSettings(); + } + } + }; + + refreshToken = async () => { + const { accessToken, clientId } = this.settings; + + const options = { + client_id: clientId, + grant_type: "refresh_token", + refresh_token: accessToken.refresh_token, + }; + + const body = Object.entries(options) + .map((item) => `${item[0]}=${item[1]}`) + .join("&"); + const response = await this.$request.post(api.accounts, { + headers: { + "Content-Type": "application/x-www-form-urlencoded", + Authorization: this.accessToken, + }, + body: body, + }); + console.log("token 过期,更新"); + console.log(response); + if (!response.error) { + this.auth2 = null; + this.settings.auth2 = null; + this.settings.accessToken = { + ...this.settings.accessToken, + ...response, + time: new Date().getTime(), + }; + console.log( + `刷新 Token 成功,有效期 ${response.expires_in / (60 * 10)} 分钟` + ); + this.saveSettings(false); + } + }; + + getWebToken = async () => { + const clientId = this.settings.clientId; + if (!clientId) + this.notify( + this.name, + "请到 Spotify 官网申请开发者账号创建 APP", + "https://developer.spotify.com" + ); + + this.settings.codeVerifier = generateRandomString(128); + this.saveSettings(false); + + return WebView.loadURL( + `https://dompling.github.io/Spotify?clientId=${clientId}&codeVerifier=${ + this.settings.codeVerifier + }&scope=${encodeURIComponent(scope.join(" "))}` + ); + }; + + getApiRes = async (uri) => { + try { + const response = await this.$request.get(uri, { + headers: { Authorization: this.accessToken }, + }); + if (!response || response.error) + throw new Error(JSON.stringify(response)); + return response; + } catch (error) { + console.log(error); + return {}; + } + }; + + renderLarge = async (widget) => { + const { currentlyPlayingTrack, playlists, me, following } = this.dataSource; + if (currentlyPlayingTrack.is_playing) { + } else { + const containerStack = widget.addStack(); + containerStack.layoutVertically(); + const logoStack = containerStack.addStack(); + + logoStack.centerAlignContent(); + logoStack.addSpacer(); + const logoImage = await this.$request.get(logo, "IMG"); + const logoImageWidget = logoStack.addImage(logoImage); + logoImageWidget.imageSize = new Size(20, 20); + logoImageWidget.centerAlignImage(); + + const meStack = containerStack.addStack(); + meStack.centerAlignContent(); + + const avatarImage = await this.$request.get( + me.images[1]?.url || me.images[0]?.url, + "IMG" + ); + + const avatarStack = meStack.addImage(avatarImage); + avatarStack.imageSize = new Size(64, 64); + avatarStack.cornerRadius = avatarStack.imageSize.height / 2; + + meStack.addSpacer(20); + + const infoStack = meStack.addStack(); + infoStack.layoutVertically(); + this.provideText(me.display_name, infoStack, { + size: 16, + font: fontFamily, + }); + infoStack.addSpacer(20); + + this.provideText(me.email, infoStack, { + size: 12, + font: fontFamily, + }); + + containerStack.addSpacer(20); + + const followStack = containerStack.addStack(); + followStack.centerAlignContent(); + this.provideText(`Following`, followStack, { + size: 16, + font: fontFamily, + }); + + followStack.addSpacer(); + + this.provideText(`${following.artists.total || 0}`, followStack, { + size: 16, + font: fontFamily, + }); + + containerStack.addSpacer(10); + + const followingItemStack = containerStack.addStack(); + followingItemStack.addSpacer(); + const itemSize = new Size(48, 48); + for (let index = 0; index < following.artists.items.length; index++) { + if (index === 4) break; + + const item = following.artists.items[index]; + const itemImage = await this.$request.get(item.images[0].url, "IMG"); + const itemImgStack = followingItemStack.addImage(itemImage); + itemImgStack.imageSize = itemSize; + itemImgStack.cornerRadius = itemSize.height / 2; + followingItemStack.addSpacer(); + } + + containerStack.addSpacer(); + + const playListStack = containerStack.addStack(); + + const listItems = playlists.items.reverse(); + const bottomItems = [ + listItems[0], + listItems[1], + listItems[2], + listItems[3], + ].filter((item) => !!item); + + playListStack.addSpacer(); + + for (const index in bottomItems) { + const item = bottomItems[index]; + const itemImage = await this.$request.get(item.images[0].url, "IMG"); + const itemImageStack = playListStack.addImage(itemImage); + itemImageStack.cornerRadius = 6; + itemImageStack.imageSize = new Size(68, 68); + playListStack.addSpacer(); + } + + containerStack.addSpacer(); + } + return widget; + }; + + renderMedium = async (widget) => { + const { currentlyPlayingTrack, playlists } = this.dataSource; + if (currentlyPlayingTrack.is_playing) { + const containerStack = widget.addStack(); + const leftStack = containerStack.addStack(); + leftStack.centerAlignContent(); + + const thumbImage = await this.$request.get( + currentlyPlayingTrack.item.album.images[0].url, + "IMG" + ); + leftStack.addSpacer(); + leftStack.size = new Size(140, 140); + const thumbImgStack = leftStack.addImage(thumbImage); + thumbImgStack.imageSize = leftStack.size; + thumbImgStack.cornerRadius = 12; + leftStack.addSpacer(); + + containerStack.addSpacer(20); + + const rightStack = containerStack.addStack(); + rightStack.layoutVertically(); + rightStack.addSpacer(); + + const logoStack = rightStack.addStack(); + + logoStack.centerAlignContent(); + logoStack.addSpacer(); + const logoImage = await this.$request.get(logo, "IMG"); + const logoImageWidget = logoStack.addImage(logoImage); + logoImageWidget.imageSize = new Size(20, 20); + logoImageWidget.centerAlignImage(); + + rightStack.addSpacer(); + + const nameStack = this.provideText( + currentlyPlayingTrack.item.name, + rightStack, + { size: 16, font: fontFamily } + ); + + nameStack.lineLimit = 1; + nameStack.minimumScaleFactor = 0.5; + + rightStack.addSpacer(); + + const athorStack = this.provideText( + currentlyPlayingTrack.item.artists[0].name, + rightStack, + { size: 12, font: fontFamily } + ); + + athorStack.lineLimit = 1; + athorStack.minimumScaleFactor = 0.5; + + rightStack.addSpacer(); + + const actionsStack = rightStack.addStack(); + + const preImage = SFSymbol.named("backward.end.fill").image; + const playImage = SFSymbol.named("pause.fill").image; + const nextImage = SFSymbol.named("forward.end.fill").image; + + const iconSize = new Size(20, 20); + const preStack = actionsStack.addImage(preImage); + preStack.tintColor = this.widgetColor; + preStack.imageSize = iconSize; + + actionsStack.addSpacer(); + + const playStack = actionsStack.addImage(playImage); + playStack.tintColor = this.widgetColor; + playStack.imageSize = iconSize; + + actionsStack.addSpacer(); + + const nextStack = actionsStack.addImage(nextImage); + nextStack.tintColor = this.widgetColor; + nextStack.imageSize = iconSize; + + rightStack.addSpacer(); + + containerStack.addSpacer(); + } else { + const listItems = playlists.items.reverse(); + const topItem = listItems[0]; + const containerStack = widget.addStack(); + containerStack.layoutVertically(); + const topStack = containerStack.addStack(); + topStack.centerAlignContent(); + + const topImg = await this.$request.get(topItem.images[0].url, "IMG"); + const topImageStack = topStack.addImage(topImg); + topImageStack.cornerRadius = 6; + topImageStack.imageSize = new Size(50, 50); + + topStack.addSpacer(20); + + this.provideText(topItem.name, topStack, { font: fontFamily, size: 16 }); + + topStack.addSpacer(); + + const logoImage = await this.$request.get(logo, "IMG"); + const logoImageWidget = topStack.addImage(logoImage); + logoImageWidget.imageSize = new Size(20, 20); + + containerStack.addSpacer(); + + const bottomStack = containerStack.addStack(); + + const bottomItems = [ + listItems[1], + listItems[2], + listItems[3], + listItems[4], + ].filter((item) => !!item); + for (const index in bottomItems) { + const item = bottomItems[index]; + const itemImage = await this.$request.get(item.images[0].url, "IMG"); + const itemImageStack = bottomStack.addImage(itemImage); + itemImageStack.cornerRadius = 6; + itemImageStack.imageSize = new Size(65, 65); + if (index !== bottomItems.length - 1) bottomStack.addSpacer(); + } + } + + return widget; + }; + + renderSmall = async (widget) => { + widget.setPadding(0, 0, 0, 0); + const { currentlyPlayingTrack, playlists } = this.dataSource; + + if (currentlyPlayingTrack.is_playing) { + const iconSize = new Size(20, 20); + + const containerStack = widget.addStack(); + containerStack.setPadding(12, 12, 12, 12); + containerStack.layoutVertically(); + + const thumbImage = await this.$request.get( + currentlyPlayingTrack.item.album.images[0].url, + "IMG" + ); + const opacityImg = await this.shadowImage(thumbImage, "#000", 0.3); + containerStack.backgroundImage = opacityImg; + + const topStack = containerStack.addStack(); + topStack.centerAlignContent(); + + const music = SFSymbol.named("timer.circle.fill").image; + const musicStack = topStack.addImage(music); + musicStack.tintColor = Color.white(); + musicStack.imageSize = new Size(14, 14); + + topStack.addSpacer(10); + + this.provideText( + convertMillisecondsToHMS(currentlyPlayingTrack.item.duration_ms), + topStack, + { size: 12, font: fontFamily, color: Color.white() } + ); + + topStack.addSpacer(); + const logoStack = topStack.addStack(); + logoStack.centerAlignContent(); + + logoStack.addSpacer(); + const logoImage = await this.$request.get(logo, "IMG"); + const logoImageWidget = logoStack.addImage(logoImage); + logoImageWidget.imageSize = new Size(20, 20); + logoImageWidget.centerAlignImage(); + + containerStack.addSpacer(); + + const nameStack = this.provideText( + currentlyPlayingTrack.item.name, + containerStack, + { size: 16, font: fontFamily, color: Color.white() } + ); + + nameStack.lineLimit = 2; + containerStack.addSpacer(); + + const athorStack = this.provideText( + currentlyPlayingTrack.item.artists[0].name, + containerStack, + { size: 12, font: fontFamily, color: Color.white() } + ); + + athorStack.lineLimit = 1; + athorStack.minimumScaleFactor = 0.5; + + containerStack.addSpacer(); + + const actionsStack = containerStack.addStack(); + + const preImage = SFSymbol.named("backward.end.fill").image; + const playImage = SFSymbol.named("pause.fill").image; + const nextImage = SFSymbol.named("forward.end.fill").image; + + const preStack = actionsStack.addImage(preImage); + preStack.tintColor = Color.white(); + preStack.imageSize = iconSize; + + actionsStack.addSpacer(); + + const playStack = actionsStack.addImage(playImage); + playStack.tintColor = Color.white(); + playStack.imageSize = iconSize; + + actionsStack.addSpacer(); + + const nextStack = actionsStack.addImage(nextImage); + nextStack.tintColor = Color.white(); + nextStack.imageSize = iconSize; + } else { + const listItems = playlists.items.reverse(); + const topItem = listItems[0]; + + const containerStack = widget.addStack(); + containerStack.setPadding(12, 12, 12, 12); + containerStack.layoutVertically(); + + const thumbImage = await this.$request.get(topItem.images[0].url, "IMG"); + const opacityImg = await this.shadowImage(thumbImage, "#000", 0.3); + containerStack.backgroundImage = opacityImg; + + const topStack = containerStack.addStack(); + topStack.addSpacer(); + const logoImage = await this.$request.get(logo, "IMG"); + const logoImageWidget = topStack.addImage(logoImage); + logoImageWidget.imageSize = new Size(20, 20); + + containerStack.addSpacer(); + + this.provideText(topItem.name, containerStack, { + font: fontFamily, + size: 16, + color: Color.white(), + }); + + containerStack.addSpacer(); + + const descStack = this.provideText(topItem.description, containerStack, { + font: fontFamily, + size: 12, + color: Color.white(), + }); + + descStack.lineLimit = 2; + descStack.minimumScaleFactor = 0.5; + + containerStack.addSpacer(); + + const actionsStack = containerStack.addStack(); + actionsStack.addSpacer(); + actionsStack.centerAlignContent(); + + const heartImage = SFSymbol.named("heart.circle.fill").image; + const heartStack = actionsStack.addImage(heartImage); + heartStack.tintColor = Color.white(); + heartStack.imageSize = new Size(20, 20); + + actionsStack.addSpacer(5); + + this.provideText(`${topItem.tracks.total}`, actionsStack, { + size: 14, + font: fontFamily, + color: Color.white(), + }); + } + + return widget; + }; + + renderAccessoryInline = async (widget) => { + return widget; + }; + + /** + * 渲染函数,函数名固定 + * 可以根据 this.widgetFamily 来判断小组件尺寸,以返回不同大小的内容 + */ + async render() { + await this.init(); + const widget = new ListWidget(); + widget.useDefaultPadding(); + + await this.getWidgetBackgroundImage(widget); + if (this.widgetFamily === "small") await this.renderSmall(widget); + if (this.widgetFamily === "medium") await this.renderMedium(widget); + if (this.widgetFamily === "large") await this.renderLarge(widget); + if (this.widgetFamily == "accessoryInline") + await this.renderAccessoryInline(widget); + + return widget; + } +} + +// @组件代码结束 +await Runing(Widget, "", false); //远程开发环境 diff --git a/Scripts/VPNBoardPress.js b/Scripts/VPNBoardPress.js index 13bd03b..f038939 100644 --- a/Scripts/VPNBoardPress.js +++ b/Scripts/VPNBoardPress.js @@ -482,8 +482,10 @@ completion(response); Run = () => { try { if (config.runsInApp) { - this.registerAction('默认账号', this.actionSettings); - this.registerAction('清除账号', this.deletedVpn); + this.registerAction('默认账号', this.actionSettings, { + name: "text.badge.star", + color: "#a0d911", + }); this.registerAction('新增账号', async () => { const account = await this.setAlertInput( '添加账号', '添加账号数据,添加完成之后请去设置默认账号', { @@ -502,6 +504,13 @@ completion(response); this.settings.dataSource = this.settings.dataSource.filter( item => item); this.saveSettings(); + },{ + name: "text.badge.plus", + color: "#fadb14", + }); + this.registerAction('清除账号', this.deletedVpn, { + name: "text.badge.xmark", + color: "#f5222d", }); this.registerAction('基础设置', this.setWidgetConfig); } diff --git a/Scripts/VPNSubscription.js b/Scripts/VPNSubscription.js index 285525b..fec9982 100644 --- a/Scripts/VPNSubscription.js +++ b/Scripts/VPNSubscription.js @@ -3,39 +3,40 @@ // icon-color: deep-gray; icon-glyph: paper-plane; // 添加require,是为了vscode中可以正确引入包,以获得自动补全等功能 -if (typeof require === 'undefined') require = importModule; -const {DmYY, Runing} = require('./DmYY'); +if (typeof require === "undefined") require = importModule; +const { DmYY, Runing } = require("./DmYY"); // @组件代码开始 class Widget extends DmYY { constructor(arg) { super(arg); - this.name = 'VPNSubscription'; - this.en = 'VPNSubscription'; + this.name = "VPNSubscription"; + this.en = "VPNSubscription"; this.CACHE_KEY = this.md5(`dataSouce_${this.en}`); this.Run(); } useBoxJS = false; - today = ''; - logo = 'https://raw.githubusercontent.com/58xinian/icon/master/glados_animation.gif'; + today = ""; + logo = + "https://raw.githubusercontent.com/58xinian/icon/master/glados_animation.gif"; dataSource = { - restData: '0', - usedData: '0', - totalData: '0', - todayData: '0', + restData: "0", + usedData: "0", + totalData: "0", + todayData: "0", isCheckIn: false, }; account = { - title: '', - url: '', + title: "", + url: "", }; - color1 = ['#ef0a6a', '#b6359c']; - color2 = ['#ff54fa', '#fad126']; - color3 = ['#28cfb3', '#72d7cc']; + color1 = ["#ef0a6a", "#b6359c"]; + color2 = ["#ff54fa", "#fad126"]; + color3 = ["#28cfb3", "#72d7cc"]; chartConfig = (data, color, value) => { console.log(data); @@ -49,7 +50,8 @@ class Widget extends DmYY { "data": [${parseFloat(data[0])}], "borderWidth": 0, "backgroundColor": getGradientFillHelper('vertical', ${JSON.stringify( - color[0])}), + color[0] + )}), } ] }, @@ -75,7 +77,8 @@ class Widget extends DmYY { "data": [${parseFloat(data[1])}], "borderWidth": 0, "backgroundColor": getGradientFillHelper('vertical', ${JSON.stringify( - color[1])}), + color[1] + )}), } ] }, @@ -108,7 +111,8 @@ class Widget extends DmYY { "data": [${parseFloat(data[2])}], "borderWidth": 0, "backgroundColor": getGradientFillHelper('vertical', ${JSON.stringify( - color[2])}), + color[2] + )}), } ] }, @@ -140,7 +144,7 @@ class Widget extends DmYY { console.log(template1); console.log(template2); console.log(template3); - return {template1, template2, template3}; + return { template1, template2, template3 }; }; init = async () => { @@ -162,13 +166,13 @@ class Widget extends DmYY { async getdata(url) { const req = new Request(url); - req.method = 'GET'; + req.method = "GET"; await req.load(); - let resp = req.response.headers['subscription-userinfo']; + let resp = req.response.headers["subscription-userinfo"]; resp = [ - (parseInt(resp.match(/upload=([0-9]+);?/)[1])).toFixed(2), - (parseInt(resp.match(/download=([0-9]+);?/)[1])).toFixed(2), - (parseInt(resp.match(/total=([0-9]+);?/)[1])).toFixed(2), + parseInt(resp.match(/upload=([0-9]+);?/)[1]).toFixed(2), + parseInt(resp.match(/download=([0-9]+);?/)[1]).toFixed(2), + parseInt(resp.match(/total=([0-9]+);?/)[1]).toFixed(2), ]; console.log(resp); return resp; @@ -176,35 +180,36 @@ class Widget extends DmYY { gradient = (color) => { const linear = new LinearGradient(); - linear.colors = color.map(item => new Color(item)); + linear.colors = color.map((item) => new Color(item)); linear.locations = [0, 0.5]; return linear; }; formatFileSize(fileSize) { - if (fileSize < (1024 * 1024)) { + if (fileSize < 1024 * 1024) { let temp = fileSize / 1024; temp = temp.toFixed(2); - return temp + 'KB'; - } else if (fileSize < (1024 * 1024 * 1024)) { + return temp + "KB"; + } else if (fileSize < 1024 * 1024 * 1024) { let temp = fileSize / (1024 * 1024); temp = temp.toFixed(2); - return temp + 'MB'; - } else if (fileSize < (1024 * 1024 * 1024 * 1024)) { + return temp + "MB"; + } else if (fileSize < 1024 * 1024 * 1024 * 1024) { let temp = fileSize / (1024 * 1024 * 1024); temp = temp.toFixed(2); - return temp + 'GB'; + return temp + "GB"; } else { let temp = fileSize / (1024 * 1024 * 1024 * 1024); temp = temp.toFixed(2); - return temp + 'TB'; + return temp + "TB"; } } createChart = async (size, chart) => { - const url = `https://quickchart.io/chart?w=${size.w}&h=${size.h}&f=png&c=${encodeURIComponent( - chart)}`; - return await this.$request.get(url, 'IMG'); + const url = `https://quickchart.io/chart?w=${size.w}&h=${ + size.h + }&f=png&c=${encodeURIComponent(chart)}`; + return await this.$request.get(url, "IMG"); }; setContent = async (w, size, viewSize) => { @@ -212,16 +217,14 @@ class Widget extends DmYY { const use = this.dataSource.usedData; const today = this.dataSource.todayData; const total = this.dataSource.totalData; - const data1 = Math.floor(rest / total * 100); - const data2 = Math.floor(use / total * 100); + const data1 = Math.floor((rest / total) * 100); + const data2 = Math.floor((use / total) * 100); const data3 = Math.floor((today / total) * 100); - const data = [ - data1 || 0, data2 || 0, data3 || 0, - ]; - const {template1, template2, template3} = this.chartConfig( - data, - [this.color1, this.color2, this.color3], - this.formatFileSize(this.dataSource.restData), + const data = [data1 || 0, data2 || 0, data3 || 0]; + const { template1, template2, template3 } = this.chartConfig( + data, + [this.color1, this.color2, this.color3], + this.formatFileSize(this.dataSource.restData) ); const stackContent = w.addStack(); @@ -237,7 +240,6 @@ class Widget extends DmYY { const stackContent3 = stackContent2.addStack(); stackContent3.size = stackSize; stackContent3.backgroundImage = await this.createChart(size, template3); - }; setLabelCell = async (stack, data) => { @@ -250,7 +252,7 @@ class Widget extends DmYY { stackIcon.cornerRadius = 8; if (data.isImg) { try { - const icon = await this.$request.get(data.icon, 'IMG'); + const icon = await this.$request.get(data.icon, "IMG"); stackIcon.addImage(icon); } catch (e) { console.log(e); @@ -262,14 +264,14 @@ class Widget extends DmYY { stackCell.addSpacer(5); const stackTitle = stackCell.addStack(); - const title = {...this.textFormat.title}; + const title = { ...this.textFormat.title }; title.color = this.widgetColor; this.provideText(data.title, stackTitle, title); stackCell.addSpacer(5); const stackValue = stackCell.addStack(); - const value = {...this.textFormat.defaultText}; + const value = { ...this.textFormat.defaultText }; value.color = this.widgetColor; this.provideText(data.value, stackValue, title); @@ -277,7 +279,7 @@ class Widget extends DmYY { }; setFooterCell = (stack, data) => { - const title = {...this.textFormat.title}; + const title = { ...this.textFormat.title }; title.color = this.widgetColor; title.size = 10; @@ -298,7 +300,7 @@ class Widget extends DmYY { this.provideText(data.value, stackText, title); stackText.addSpacer(); - const desc = {...this.textFormat.defaultText}; + const desc = { ...this.textFormat.defaultText }; desc.color = this.widgetColor; desc.size = 8; @@ -315,7 +317,9 @@ class Widget extends DmYY { stackLeft.centerAlignContent(); try { const imgIcon = await this.$request.get( - this.account.icon || this.logo, 'IMG'); + this.account.icon || this.logo, + "IMG" + ); const stackImgItem = stackLeft.addImage(imgIcon); stackImgItem.imageSize = new Size(12, 12); stackImgItem.cornerRadius = 4; @@ -323,7 +327,7 @@ class Widget extends DmYY { } catch (e) { console.log(e); } - const title = {...this.textFormat.title}; + const title = { ...this.textFormat.title }; title.color = this.widgetColor; title.size = 12; this.provideText(this.account.title, stackLeft, title); @@ -331,18 +335,21 @@ class Widget extends DmYY { const stackRight = stackHeader.addStack(); stackRight.centerAlignContent(); - const calendar = SFSymbol.named('waveform.path.badge.minus'); + const calendar = SFSymbol.named("waveform.path.badge.minus"); const imgCalendar = stackRight.addImage(calendar.image); imgCalendar.imageSize = new Size(12, 12); - imgCalendar.tintColor = new Color('#00b800'); + imgCalendar.tintColor = new Color("#00b800"); stackRight.addSpacer(5); this.provideText( - this.formatFileSize(this.dataSource.todayData), stackRight, title); + this.formatFileSize(this.dataSource.todayData), + stackRight, + title + ); w.addSpacer(); const stackContent = w.addStack(); stackContent.addSpacer(); - await this.setContent(stackContent, {w: 360, h: 360}, new Size(80, 80)); + await this.setContent(stackContent, { w: 360, h: 360 }, new Size(80, 80)); stackContent.addSpacer(); w.addSpacer(); @@ -352,7 +359,7 @@ class Widget extends DmYY { const stackFooterLeft = stackFooter.addStack(); this.setFooterCell(stackFooterLeft, { value: this.formatFileSize(this.dataSource.restData), - label: '剩余', + label: "剩余", color: this.color1, }); @@ -361,7 +368,7 @@ class Widget extends DmYY { const stackFooterRight = stackFooter.addStack(); this.setFooterCell(stackFooterRight, { value: this.formatFileSize(this.dataSource.usedData), - label: '累计', + label: "累计", color: this.color2, }); @@ -371,48 +378,38 @@ class Widget extends DmYY { renderMedium = async (w) => { const stackBody = w.addStack(); const stackLeft = stackBody.addStack(); - await this.setContent(stackLeft, {w: 360, h: 360}, new Size(140, 140)); + await this.setContent(stackLeft, { w: 360, h: 360 }, new Size(140, 140)); stackBody.addSpacer(10); const stackRight = stackBody.addStack(); stackRight.layoutVertically(); - await this.setLabelCell( - stackRight, { - icon: this.account.icon || this.logo, - title: this.account.title, - value: ``, - isImg: true, - }); - await this.setLabelCell( - stackRight, - { - icon: this.gradient(this.color3), - title: '上传', - value: this.formatFileSize(this.dataSource.todayData), - }, - ); - await this.setLabelCell( - stackRight, - { - icon: this.gradient(this.color2), - title: '累计', - value: this.formatFileSize(this.dataSource.usedData), - }, - ); + await this.setLabelCell(stackRight, { + icon: this.account.icon || this.logo, + title: this.account.title, + value: ``, + isImg: true, + }); + await this.setLabelCell(stackRight, { + icon: this.gradient(this.color3), + title: "上传", + value: this.formatFileSize(this.dataSource.todayData), + }); + await this.setLabelCell(stackRight, { + icon: this.gradient(this.color2), + title: "累计", + value: this.formatFileSize(this.dataSource.usedData), + }); - await this.setLabelCell( - stackRight, - { - icon: this.gradient(this.color1), - title: '剩余', - value: this.formatFileSize(this.dataSource.restData), - }, - ); + await this.setLabelCell(stackRight, { + icon: this.gradient(this.color1), + title: "剩余", + value: this.formatFileSize(this.dataSource.restData), + }); return w; }; renderLarge = async (w) => { - w.addText('暂不支持'); + w.addText("暂不支持"); return w; }; @@ -424,9 +421,9 @@ class Widget extends DmYY { await this.init(); const widget = new ListWidget(); await this.getWidgetBackgroundImage(widget); - if (this.widgetFamily === 'medium') { + if (this.widgetFamily === "medium") { return await this.renderMedium(widget); - } else if (this.widgetFamily === 'large') { + } else if (this.widgetFamily === "large") { return await this.renderLarge(widget); } else { return await this.renderSmall(widget); @@ -436,33 +433,51 @@ class Widget extends DmYY { Run = () => { try { if (config.runsInApp) { - this.registerAction('默认订阅', this.actionSettings); - this.registerAction('清除订阅', this.deletedVpn); - this.registerAction('新增订阅', async () => { + this.registerAction("默认订阅", this.actionSettings, { + name: "text.badge.star", + color: "#a0d911", + }); + this.registerAction("新增订阅", async () => { const account = await this.setAlertInput( - '添加订阅', '添加订阅数据,添加完成之后请去设置默认订阅', { - title: '机场名', - icon: '图标', - url: '订阅地址', - }, false); + "添加订阅", + "添加订阅数据,添加完成之后请去设置默认订阅", + { + title: "机场名", + icon: "图标", + url: "订阅地址", + }, + false + ); if (!this.settings.dataSource) this.settings.dataSource = []; if (!account) return; if (account.title && account.url) { this.settings.dataSource.push(account); } this.settings.dataSource = this.settings.dataSource.filter( - item => item); + (item) => item + ); this.saveSettings(); + },{ + name: "text.badge.plus", + color: "#fadb14", }); - this.registerAction('基础设置', this.setWidgetConfig); + this.registerAction("清除订阅", this.deletedVpn, { + name: "text.badge.xmark", + color: "#f5222d", + }); + this.registerAction("基础设置", this.setWidgetConfig); } this.account = this.settings.account || this.account; - this.CACHE_KEY += '_' + this.account.title; - const index = typeof args.widgetParameter === 'string' ? parseInt( - args.widgetParameter) : false; - if (this.settings.dataSource && this.settings.dataSource[index] && - index !== - false) { + this.CACHE_KEY += "_" + this.account.title; + const index = + typeof args.widgetParameter === "string" + ? parseInt(args.widgetParameter) + : false; + if ( + this.settings.dataSource && + this.settings.dataSource[index] && + index !== false + ) { this.account = this.settings.dataSource[index]; } } catch (e) { @@ -510,9 +525,8 @@ class Widget extends DmYY { console.log(e); } } - } // @组件代码结束 // await Runing(Widget, "", false); // 正式环境 -await Runing(Widget, '', false); //远程开发环境 +await Runing(Widget, "", false); //远程开发环境 diff --git a/Scripts/VpnBoard.js b/Scripts/VpnBoard.js index 47401d3..d1b1afa 100644 --- a/Scripts/VpnBoard.js +++ b/Scripts/VpnBoard.js @@ -3,24 +3,24 @@ // icon-color: deep-gray; icon-glyph: paper-plane; // 添加require,是为了vscode中可以正确引入包,以获得自动补全等功能 -if (typeof require === 'undefined') require = importModule; -const {DmYY, Runing} = require('./DmYY'); +if (typeof require === "undefined") require = importModule; +const { DmYY, Runing } = require("./DmYY"); // @组件代码开始 class Widget extends DmYY { constructor(arg) { super(arg); - this.name = 'Vpn'; - this.en = 'vpnBoard'; + this.name = "Vpn"; + this.en = "vpnBoard"; this.CACHE_KEY = this.md5(`dataSouce_${this.en}`); this.Run(); } useBoxJS = false; dataSource = { - restData: '0', - usedData: '0', - todayUsed: '0', + restData: "0", + usedData: "0", + todayUsed: "0", isCheckIn: false, }; @@ -28,9 +28,9 @@ class Widget extends DmYY { const color = `#${this.widgetColor.hex}`; let template; let path = this.FILE_MGR.documentsDirectory(); - path = path + '/VPNBoardTemplate.js'; + path = path + "/VPNBoardTemplate.js"; if (this.FILE_MGR.fileExists(path)) { - template = require('./VPNBoardTemplate'); + template = require("./VPNBoardTemplate"); } else { template = ` { @@ -118,27 +118,27 @@ const template = \`${template}\`; module.exports = template;`; this.FILE_MGR.writeString(path, content); } - template = template.replaceAll('__COLOR__', `'${color}'`); - template = template.replace('__LABELS__', `${JSON.stringify(labels)}`); - template = template.replace('__TEXT__', `${JSON.stringify(text)}`); - template = template.replace('__DATAS__', `${JSON.stringify(datas)}`); + template = template.replaceAll("__COLOR__", `'${color}'`); + template = template.replace("__LABELS__", `${JSON.stringify(labels)}`); + template = template.replace("__TEXT__", `${JSON.stringify(text)}`); + template = template.replace("__DATAS__", `${JSON.stringify(datas)}`); return template; }; account = { - title: '', - url: '', - email: '', - password: '', + title: "", + url: "", + email: "", + password: "", }; range = { - '11.1': {todayUsed: '30M'}, - '11.2': {todayUsed: '130M'}, - '11.3': {todayUsed: '300M'}, - '11.4': {todayUsed: '30M'}, - '11.5': {todayUsed: '2g'}, - '11.6': {todayUsed: '3G'}, + 11.1: { todayUsed: "30M" }, + 11.2: { todayUsed: "130M" }, + 11.3: { todayUsed: "300M" }, + 11.4: { todayUsed: "30M" }, + 11.5: { todayUsed: "2g" }, + 11.6: { todayUsed: "3G" }, }; max = 6; @@ -153,7 +153,7 @@ module.exports = template;`; } const date = new Date(); const format = new DateFormatter(); - format.dateFormat = 'MM.dd'; + format.dateFormat = "MM.dd"; const dateDay = format.string(date); this.range[dateDay] = this.dataSource; const rangeKey = Object.keys(this.range); @@ -173,25 +173,28 @@ module.exports = template;`; const table = { url: this.account.url, body: `email=${encodeURIComponent( - this.account.email)}&passwd=${encodeURIComponent( - this.account.password)}&remember_me=on&rumber-me=week`, + this.account.email + )}&passwd=${encodeURIComponent( + this.account.password + )}&remember_me=on&rumber-me=week`, }; const request = new Request(table.url); request.body = table.body; - request.method = 'POST'; + request.method = "POST"; const data = await request.loadString(); try { if ( - JSON.parse(data).msg.match( - /邮箱不存在|邮箱或者密码错误|Mail or password is incorrect/, - ) + JSON.parse(data).msg.match( + /邮箱不存在|邮箱或者密码错误|Mail or password is incorrect/ + ) ) { - this.notify(this.name, '邮箱或者密码错误'); - console.log('登陆失败'); - this.cookie = request.response.cookies( - item => `${item.name}=${item.value}`).join('; '); + this.notify(this.name, "邮箱或者密码错误"); + console.log("登陆失败"); + this.cookie = request.response + .cookies((item) => `${item.name}=${item.value}`) + .join("; "); } else { - console.log('登陆成功'); + console.log("登陆成功"); } } catch (e) { console.log(e); @@ -201,28 +204,27 @@ module.exports = template;`; async checkin() { const url = this.account.url; let checkinPath = - url.indexOf('auth/login') !== -1 ? 'user/checkin' : 'user/_checkin.php'; + url.indexOf("auth/login") !== -1 ? "user/checkin" : "user/_checkin.php"; const checkinreqest = { - url: url.replace(/(auth|user)\/login(.php)*/g, '') + checkinPath, + url: url.replace(/(auth|user)\/login(.php)*/g, "") + checkinPath, headers: { cookie: this.cookie, }, }; - const data = await this.$request.post(checkinreqest, 'STRING'); + const data = await this.$request.post(checkinreqest, "STRING"); if (data.match(/\"msg\"\:/)) { - console.log('签到成功'); + console.log("签到成功"); this.dataSource.isCheckIn = true; } else { - console.log('签到失败'); + console.log("签到失败"); } } async dataResults() { let url = this.account.url; - const userPath = url.indexOf('auth/login') !== -1 - ? 'user' - : 'user/index.php'; - url = url.replace(/(auth|user)\/login(.php)*/g, '') + userPath; + const userPath = + url.indexOf("auth/login") !== -1 ? "user" : "user/index.php"; + url = url.replace(/(auth|user)\/login(.php)*/g, "") + userPath; const webView = new WebView(); await webView.loadURL(url); const js = ` @@ -239,21 +241,21 @@ if($('.progressbar').length){ completion(response); `; const response = await webView.evaluateJavaScript(js, true); - this.dataSource = {...this.dataSource, ...response}; + this.dataSource = { ...this.dataSource, ...response }; } translateFlow(value) { const unit = [ - {unit: 'T', value: 1024 * 1024}, - {unit: 'G', value: 1024}, - {unit: 'M', value: 1}, - {unit: 'K', value: 1 / 1024}, + { unit: "T", value: 1024 * 1024 }, + { unit: "G", value: 1024 }, + { unit: "M", value: 1 }, + { unit: "K", value: 1 / 1024 }, ]; - const data = {unit: '', value: parseFloat(value)}; - unit.forEach(item => { + const data = { unit: "", value: parseFloat(value) }; + unit.forEach((item) => { if (value.indexOf(item.unit) > -1) { data.unit = item.unit; - data.value = Math.floor((parseFloat(value) * item.value) * 100) / 100; + data.value = Math.floor(parseFloat(value) * item.value * 100) / 100; } }); @@ -261,7 +263,9 @@ completion(response); } createChart = async (size) => { - let labels = [], data = [], text = []; + let labels = [], + data = [], + text = []; const rangeKey = Object.keys(this.range); rangeKey.forEach((key) => { labels.push(key); @@ -270,31 +274,32 @@ completion(response); data.push(valueUnit.value); text.push(value); }); - if (this.widgetSize === 'small') { + if (this.widgetSize === "small") { labels = labels.slice(-3); data = data.slice(-3); } console.log(data); const chart = this.chartConfig(labels, data, text); console.log(chart); - const url = `https://quickchart.io/chart?w=${size.w}&h=${size.h}&f=png&c=${encodeURIComponent( - chart)}`; - return await this.$request.get(url, 'IMG'); + const url = `https://quickchart.io/chart?w=${size.w}&h=${ + size.h + }&f=png&c=${encodeURIComponent(chart)}`; + return await this.$request.get(url, "IMG"); }; createDivider(w) { const drawContext = new DrawContext(); - drawContext.size = new Size(543, this.widgetSize === 'small' ? 4 : 2); + drawContext.size = new Size(543, this.widgetSize === "small" ? 4 : 2); const path = new Path(); path.addLine(new Point(1000, 20)); drawContext.addPath(path); - drawContext.setStrokeColor(new Color('#000', 1)); + drawContext.setStrokeColor(new Color("#000", 1)); drawContext.setLineWidth(2); drawContext.strokePath(); const stackLine = w.addStack(); stackLine.borderWidth = 1; - stackLine.borderColor = new Color('#000', 0.4); + stackLine.borderColor = new Color("#000", 0.4); stackLine.addImage(drawContext.getImage()); w.addSpacer(5); } @@ -304,14 +309,15 @@ completion(response); header.centerAlignContent(); const left = header.addStack(); left.centerAlignContent(); - let icon = 'https://raw.githubusercontent.com/58xinian/icon/master/glados_animation.gif'; + let icon = + "https://raw.githubusercontent.com/58xinian/icon/master/glados_animation.gif"; if (this.account.icon) icon = this.account.icon; const stackIcon = left.addStack(); try { - const imgIcon = await this.$request.get(icon, 'IMG'); + const imgIcon = await this.$request.get(icon, "IMG"); const imgIconItem = stackIcon.addImage(imgIcon); let iconSize = new Size(16, 16); - if (this.widgetSize === 'small') iconSize = new Size(12, 12); + if (this.widgetSize === "small") iconSize = new Size(12, 12); imgIconItem.imageSize = iconSize; imgIconItem.cornerRadius = 4; left.addSpacer(5); @@ -319,7 +325,7 @@ completion(response); console.log(e); } - const vpnName = {...this.textFormat.title}; + const vpnName = { ...this.textFormat.title }; vpnName.size = size; vpnName.color = this.widgetColor; this.provideText(this.account.title, left, vpnName); @@ -328,17 +334,21 @@ completion(response); const right = header.addStack(); right.bottomAlignContent(); - const vpnFlow = {...this.textFormat.title}; - vpnFlow.color = new Color('#26c5bc'); + const vpnFlow = { ...this.textFormat.title }; + vpnFlow.color = new Color("#26c5bc"); vpnFlow.size = size; - this.provideText(this.dataSource.isCheckIn ? '已签到' : '未签到', right, vpnFlow); + this.provideText( + this.dataSource.isCheckIn ? "已签到" : "未签到", + right, + vpnFlow + ); w.addSpacer(); - }; + } setLabel(w, data, size) { const stackLabel = w.addStack(); - const textBattery = {...this.textFormat.battery}; + const textBattery = { ...this.textFormat.battery }; textBattery.size = size.label; textBattery.color = this.widgetColor; this.provideText(data.label, stackLabel, textBattery); @@ -355,20 +365,26 @@ completion(response); footer.centerAlignContent(); this.setLabel( - footer, { - label: '剩余:', - color: '#ff3e3e', - value: this.dataSource.restData, - unit: 'GB', - }, size); + footer, + { + label: "剩余:", + color: "#ff3e3e", + value: this.dataSource.restData, + unit: "GB", + }, + size + ); footer.addSpacer(); this.setLabel( - footer, { - label: '累计:', - color: '#dc9e28', - value: this.dataSource.usedData, - unit: 'GB', - }, size); + footer, + { + label: "累计:", + color: "#dc9e28", + value: this.dataSource.usedData, + unit: "GB", + }, + size + ); } setContent = async (w, size) => { @@ -379,22 +395,22 @@ completion(response); renderSmall = async (w) => { await this.setHeader(w, 10); - await this.setContent(w, {w: 195, h: 85}); + await this.setContent(w, { w: 195, h: 85 }); this.createDivider(w); - this.setFooter(w, {label: 6, value: 8}); + this.setFooter(w, { label: 6, value: 8 }); return w; }; renderMedium = async (w) => { await this.setHeader(w, 16); - await this.setContent(w, {w: 390, h: 85}); + await this.setContent(w, { w: 390, h: 85 }); this.createDivider(w); - this.setFooter(w, {label: 14, value: 18}); + this.setFooter(w, { label: 14, value: 18 }); return w; }; renderLarge = async (w) => { - w.addText('暂不支持'); + w.addText("暂不支持"); return w; }; @@ -406,9 +422,9 @@ completion(response); await this.init(); const widget = new ListWidget(); await this.getWidgetBackgroundImage(widget); - if (this.widgetFamily === 'medium') { + if (this.widgetFamily === "medium") { return await this.renderMedium(widget); - } else if (this.widgetFamily === 'large') { + } else if (this.widgetFamily === "large") { return await this.renderLarge(widget); } else { return await this.renderSmall(widget); @@ -418,36 +434,62 @@ completion(response); Run = () => { try { if (config.runsInApp) { - this.registerAction('默认账号', this.actionSettings); - this.registerAction('清除账号', this.deletedVpn); - this.registerAction('新增账号', async () => { - const account = await this.setAlertInput( - '添加账号', '添加账号数据,添加完成之后请去设置默认账号', { - title: '机场名', - icon: '图标', - url: '登陆地址', - email: '邮箱账号', - password: '密码', - }, false); - if (!this.settings.dataSource) this.settings.dataSource = []; - if (!account) return; - if (account.title && account.url && account.email && - account.password) { - this.settings.dataSource.push(account); + this.registerAction("默认账号", this.actionSettings, { + name: "text.badge.star", + color: "#a0d911", + }); + this.registerAction( + "新增账号", + async () => { + const account = await this.setAlertInput( + "添加账号", + "添加账号数据,添加完成之后请去设置默认账号", + { + title: "机场名", + icon: "图标", + url: "登陆地址", + email: "邮箱账号", + password: "密码", + }, + false + ); + if (!this.settings.dataSource) this.settings.dataSource = []; + if (!account) return; + if ( + account.title && + account.url && + account.email && + account.password + ) { + this.settings.dataSource.push(account); + } + this.settings.dataSource = this.settings.dataSource.filter( + (item) => item + ); + this.saveSettings(); + }, + { + name: "text.badge.plus", + color: "#fadb14", } - this.settings.dataSource = this.settings.dataSource.filter( - item => item); - this.saveSettings(); + ); + this.registerAction("清除账号", this.deletedVpn, { + name: "text.badge.xmark", + color: "#f5222d", }); - this.registerAction('基础设置', this.setWidgetConfig); + this.registerAction("基础设置", this.setWidgetConfig); } this.account = this.settings.account || this.account; - this.CACHE_KEY += '_' + this.account.title; - const index = typeof args.widgetParameter === 'string' ? parseInt( - args.widgetParameter) : false; - if (this.settings.dataSource && this.settings.dataSource[index] && - index !== - false) { + this.CACHE_KEY += "_" + this.account.title; + const index = + typeof args.widgetParameter === "string" + ? parseInt(args.widgetParameter) + : false; + if ( + this.settings.dataSource && + this.settings.dataSource[index] && + index !== false + ) { this.account = this.settings.dataSource[index]; } } catch (e) { @@ -461,7 +503,9 @@ completion(response); const dataSource = this.settings.dataSource || []; dataSource.map((t, index) => { const r = new UITableRow(); - r.addText(`parameter:${index} 机场名:${t.title} 账号:${t.email}`); + r.addText( + `parameter:${index} 机场名:${t.title} 账号:${t.email}` + ); r.onSelect = (n) => { this.settings.account = t; this.notify(t.title, `默认账号设置成功\n账号:${t.email}`); @@ -495,9 +539,8 @@ completion(response); console.log(e); } } - } // @组件代码结束 // await Runing(Widget, "", false); // 正式环境 -await Runing(Widget, '', false); //远程开发环境 +await Runing(Widget, "", false); //远程开发环境 diff --git a/Scripts/birthday.js b/Scripts/birthday.js new file mode 100644 index 0000000..255acf8 --- /dev/null +++ b/Scripts/birthday.js @@ -0,0 +1,694 @@ +// Variables used by Scriptable. +// These must be at the very top of the file. Do not edit. +// icon-color: deep-gray; icon-glyph: car; + +// 添加require,是为了vscode中可以正确引入包,以获得自动补全等功能 +if (typeof require === "undefined") require = importModule; +const { DmYY, Runing } = require("./DmYY"); +let mainTextSize = 13; // 倒数、农历、生日文字大小 + +let widthMode = 110; // 中号组件图片尺寸 + +let heightMode = 100; // 中号组件图片尺寸 + +// @组件代码开始 +class Widget extends DmYY { + constructor(arg) { + super(arg); + this.en = "birthday"; + this.name = "破壳日"; + this.LEFT_IMG_KEY = `${this.cacheImage}/avatar`; + + if (config.runsInApp) { + this.registerAction({ + title: "头像设置", + menu: [ + { + icon: { name: "person.badge.plus", color: "#52c41a" }, + type: "img", + title: "头像", + name: "avatar", + val: this.cacheImage, + }, + { + icon: { name: "arrow.left.and.right", color: "#13c2c2" }, + type: "input", + title: "头像宽度", + name: "avatarWidth", + }, + { + icon: { name: "arrow.up.and.down", color: "#1890ff" }, + type: "input", + title: "头像高度", + name: "avatarHeight", + }, + ], + }); + + this.registerAction({ + icon: { name: "a.square", color: "#eb2f96" }, + type: "input", + title: "主文字大小", + name: "mainTextSize", + }); + + this.registerAction({ + icon: { + name: "rectangle.and.pencil.and.ellipsis", + color: "#f5222d", + }, + type: "input", + title: "昵称", + placeholder: "用户昵称", + name: "nickname", + }); + + this.registerAction({ + icon: { + name: "rectangle", + color: "#a68585", + }, + type: "color", + title: "昵称阴影", + placeholder: "昵称阴影", + name: "nicknameShadow", + }); + + this.registerAction({ + icon: { + name: "bubble.left", + color: "#faf61c", + }, + type: "input", + title: "寄语", + name: "bless", + }); + + this.registerAction({ + icon: { + name: "25.square.fill", + color: "#fa541c", + }, + type: "switch", + title: "农历", + name: "nongli", + }); + + this.registerAction({ + icon: { + name: "calendar", + color: "#fa8c16", + }, + type: "date", + title: "破壳日", + name: "birthday", + }); + + this.registerAction({ + icon: { + name: "calendar.badge.clock", + color: "#8016fa", + }, + type: "date", + title: "相识", + name: "eday", + }); + + this.registerAction("基础设置", this.setWidgetConfig); + } + } + + getAge = (beginStr) => { + let tmpBirth = {}; + tmpBirth.year = 0; + tmpBirth.month = 0; + tmpBirth.day = 0; + + if (beginStr == null || beginStr == "") { + return; + } + let startDate = new Date(beginStr.replace(/-/g, "/")); + let today = new Date(); + + let startYear = startDate.getFullYear(); + let endYear = today.getFullYear(); + let startMonth = startDate.getMonth() + 1; + let endMonth = today.getMonth() + 1; + let startDay = startDate.getDate(); + let endDay = today.getDate(); + let allDays = 0; + //准确的每个月天数数组,判断闰年平年。 + let allDayArr = []; + //当月计算 + if (startYear == endYear && startMonth == endMonth) { + tmpBirth.day = endDay - startDay; + return tmpBirth; + } + //正常计算 + for (let i = startYear; i <= endYear; i++) { + let currYear = 365; + let yearMonth = 12; + if ((i % 4 == 0 && i % 100 !== 0) || i % 400 == 0) { + allDays += 366; + currYear = 366; + } + let currMonth = 1; + if (i == startYear) { + currMonth = startMonth; + } + if (i == endYear) { + yearMonth = endMonth; + } + //闰年计算 + for (let m = currMonth; m <= yearMonth; m++) { + let fullDays = 30; + + if (m == 1 || m == 3 || m == 8 || m == 10 || m == 12) { + fullDays = 31; + } else if (m == 2) { + if ((i % 4 == 0 && i % 100 !== 0) || i % 400 == 0) { + fullDays = 29; + } else { + fullDays = 28; + } + } + let dayObj = { + fullDays: fullDays, + currDays: fullDays, + }; + if (m == startMonth && i == startYear) { + dayObj = { + fullDays: fullDays, + currDays: fullDays - startDay, + }; + } else if (m == endMonth && i == endYear) { + dayObj = { + fullDays: fullDays, + currDays: endDay, + }; + } + allDayArr.push(dayObj); + } + } + + if (allDayArr.length == 1) { + tmpBirth.day = allDayArr[0].currDays; + } else if (allDayArr.length == 2) { + var d1 = allDayArr[0].currDays; + var d2 = allDayArr[1].currDays; + //月份天数浮动因子决定准确性 + let cfDay = + allDayArr[0].fullDays > allDayArr[allDayArr.length - 1].fullDays + ? allDayArr[allDayArr.length - 1].fullDays + : allDayArr[0].fullDays; + if (d1 + d2 >= cfDay) { + tmpBirth.day = d1 + d2 - cfDay; + tmpBirth.month += 1; + } else { + tmpBirth.day = d1 + d2; + } + } else { + let d1 = allDayArr[0].currDays; + let d2 = allDayArr[allDayArr.length - 1].currDays; + let sumFullDay = 0; + for (let i = 0; i < allDayArr.length; i++) { + sumFullDay += allDayArr[i].fullDays; + } + //月份天数浮动因子决定准确性 + let cfDay = + allDayArr[0].fullDays > allDayArr[allDayArr.length - 1].fullDays + ? allDayArr[allDayArr.length - 1].fullDays + : allDayArr[0].fullDays; + if (d1 + d2 >= cfDay) { + tmpBirth.day = d1 + d2 - cfDay; + tmpBirth.month += 1; + } else { + tmpBirth.day = d1 + d2; + } + tmpBirth.month += allDayArr.length - 2; + + if (tmpBirth.month >= 12) { + tmpBirth.year += Math.floor(tmpBirth.month / 12); + tmpBirth.month = tmpBirth.month - tmpBirth.year * 12; + } + } + return tmpBirth; + }; + + daysBetween = (d) => { + let now = new Date(); + let date = new Date(d.cYear, d.cMonth - 1, d.cDay); + return parseInt((date.getTime() - now.getTime()) / (24 * 3600 * 1000)); + }; + + getAstroToEmoji = (astro) => { + const data = { + 白羊座: "♈", + 金牛座: "♉", + 双子座: "♊", + 巨蟹座: "♋", + 狮子座: "♌", + 处女座: "♍", + 天秤座: "♎", + 天蝎座: "♏", + 射手座: "♐", + 摩羯座: "♑", + 水瓶座: "♒", + 双鱼座: "♓", + 蛇夫座: "⛎", + }; + return data[astro] || ""; + }; + + getAnimalZodiacToEmoji = (zodiac) => { + const data = { + 鼠: "🐭", + 牛: "🐂", + 虎: "🐯", + 兔: "🐇", + 龙: "🐲", + 蛇: "🐍", + 马: "🐴", + 羊: "🐑", + 猴: "🐵", + 鸡: "🐔", + 狗: "🐶", + 猪: "🐷", + }; + return data[zodiac] || ""; + }; + + verifyTime(date) { + let dateFormat = /^(\d{4})-(\d{1,2})-(\d{1,2})$/; + return dateFormat.test(date); + } + + getEdayNumber = (date) => { + var initDay = date.split("-"); + var obj = { + cYear: parseInt(initDay[0]), + cMonth: parseInt(initDay[1]), + cDay: parseInt(initDay[2]), + }; + return Math.abs(this.daysBetween(obj)); + }; + + ajax = async (opt) => { + const type = opt.nongli ? "lunar" : "solar"; + return ( + await this.$request.post(`https://www.iamwawa.cn/home/nongli/ajax`, { + body: `type=${type}&year=${opt.year}&month=${opt.month}&day=${opt.day}`, + }) + ).data; + }; + + init = async () => { + widthMode = Number(this.settings.avatarWidth) || widthMode; + heightMode = Number(this.settings.avatarHeight) || heightMode; + mainTextSize = Number(this.settings.mainTextSize) || mainTextSize; + + await this.FILE_MGR.fileExistsExtra(this.LEFT_IMG_KEY); + this.defaultData = { + username: this.settings.nickname || "", // 姓名 + time: this.settings.birthday || "2022-12-19", // 生日日期 + nongli: this.settings.nongli === "true" || "", // 农历生日 + eday: this.settings.eday || "2022-12-19", //相识 + bless: this.settings.bless || "", + nicknameShadow: this.settings.nicknameShadow || "#e8e8e8", + isLeapMonth: false, //如果是农历闰月第四个参数赋值true即可 + }; + + const { time, nongli, isLeapMonth, eday } = this.defaultData; + const _data = time.split("-"); + const opt = { + year: parseInt(_data[0]), + month: parseInt(_data[1]), + day: parseInt(_data[2]), + nongli, + isLeapMonth, + }; + + if (this.settings.ajax) { + this.ajax(opt).then((res) => { + this.settings.ajax = res; + this.saveSettings(false); + }); + } else { + this.settings.ajax = await this.ajax(opt); + } + this.saveSettings(false); + const response = this.settings.ajax; + + response.animalEmoji = `${this.getAnimalZodiacToEmoji(response.sx)}`; + response.astro = `${this.getAstroToEmoji(response.xz)}`; + + if (this.verifyTime(eday)) { + response.meetDay = this.getEdayNumber(eday); + } + + this.contentText = { ...response, data: {} }; + + this.contentText.this_year_lunar_solar = + this.contentText.this_year_lunar_solar + .replace("年", "-") + .replace("月", "-") + .replace("日", ""); + this.contentText.next_year_lunar_solar = + this.contentText.next_year_lunar_solar + .replace("年", "-") + .replace("月", "-") + .replace("日", ""); + this.contentText.solar = this.contentText.solar + .replace("年", "-") + .replace("月", "-") + .replace("日", ""); + + const tmpBirth = this.getAge(this.defaultData.time); + let ageYear = tmpBirth.year > 0 ? `${tmpBirth.year}岁` : ""; + let ageMonth = tmpBirth.month > 0 ? `${tmpBirth.month}月` : ""; + let ageDay = tmpBirth.day > 0 ? `${tmpBirth.day}天` : "1天"; + const age = ageYear + ageMonth + ageDay; + const dayIcon = tmpBirth.day + ".circle.fill"; + + this.contentText.data = { + tmpBirth, + ageYear, + ageMonth, + ageDay, + age, + dayIcon, + }; + }; + + rowCell = (widget, { icon, color, title, text, dayImage = false }) => { + const subWidget = widget.addStack(); + subWidget.centerAlignContent(); + + const subImg = subWidget.addImage(SFSymbol.named(icon).image); + subImg.tintColor = new Color(color); + subImg.imageSize = new Size(mainTextSize, mainTextSize); + subWidget.addSpacer(4); + const subTitle = subWidget.addText(title || ""); + subTitle.font = Font.systemFont(mainTextSize); + subTitle.textColor = this.widgetColor; + subWidget.addSpacer(); + const subValue = subWidget.addText(text || ""); + subValue.font = Font.systemFont(mainTextSize); + subValue.textColor = this.widgetColor; + subValue.lineLimit = 1; + + if (dayImage) { + subWidget.addSpacer(2); + let dayIcon = subWidget.addImage(SFSymbol.named(dayImage).image); + dayIcon.imageSize = new Size(mainTextSize + 1, mainTextSize + 1); + dayIcon.tintColor = new Color("#1ab6f8"); + } + }; + + animalImg = (text) => { + const { this_year_lunar_solar, solar } = this.contentText; + + const nextBirthday = { + year: this_year_lunar_solar.split("-")[0], + month: this_year_lunar_solar.split("-")[1], + day: this_year_lunar_solar.split("-")[2], + }; + + const preData = { + year: solar.split("-")[0], + month: solar.split("-")[1], + day: solar.split("-")[2], + }; + + const extraTextColor = "fc8ac3"; //环形进度条中心背景颜色及名字、meetDay颜色 + const ringColor = "fc5ead"; //环形进度条颜色 + const canvSize = 172; + const canvTextSize = 45; + const canvas = new DrawContext(); + const canvWidth = 12; + const canvRadius = 80; + const cbgColor = new Color(ringColor, 0.2); + const cfgColor = new Color(ringColor); + const centerColor = new Color(extraTextColor); + const cfontColor = new Color("ffffff"); + canvas.size = new Size(canvSize, canvSize); + canvas.opaque = false; + canvas.respectScreenScale = true; + + const today = new Date(); + const thenDate = new Date( + `${nextBirthday.year}`, + `${nextBirthday.month}` - 1, + `${nextBirthday.day}` + ); + + const passDate = new Date(preData.year, preData.month - 1, preData.day); + + const gap = today.getTime() - passDate.getTime(); + const gap2 = thenDate.getTime() - passDate.getTime(); + const deg = Math.floor((gap / gap2) * 100 * 3.6); + + let ctr = new Point(canvSize / 2, canvSize / 2); + const bgx = ctr.x - canvRadius; + const bgy = ctr.y - canvRadius; + const bgd = 2 * canvRadius; + const bgr = new Rect(bgx, bgy, bgd, bgd); + + canvas.setFillColor(cfgColor); + canvas.setStrokeColor(cbgColor); + canvas.setLineWidth(canvWidth); + canvas.strokeEllipse(bgr); + + for (let t = 0; t < deg; t++) { + const rect_x = + ctr.x + canvRadius * Math.sin((t * Math.PI) / 180) - canvWidth / 2; + const rect_y = + ctr.y - canvRadius * Math.cos((t * Math.PI) / 180) - canvWidth / 2; + const rect_r = new Rect(rect_x, rect_y, canvWidth, canvWidth); + canvas.fillEllipse(rect_r); + } + + const ringBG = new Rect( + bgx + canvWidth / 2 + 8, + bgy + canvWidth / 2 + 8, + canvRadius * 2 - canvWidth - 16, + canvRadius * 2 - canvWidth - 16 + ); + canvas.setFillColor(centerColor); + canvas.setLineWidth(0); + canvas.fillEllipse(ringBG); + + const canvTextRect = new Rect(0, 70 - canvTextSize / 2, canvSize, 80); + canvas.setTextAlignedCenter(); + canvas.setTextColor(cfontColor); + canvas.setFont(Font.mediumRoundedSystemFont(canvTextSize)); + canvas.setFont(this.provideFont("ultralight", 68)); + canvas.drawTextInRect(`${text}`, canvTextRect); + + return canvas.getImage(); + }; + + renderMedium = (widget) => { + const { + this_year_lunar_solar, + lunar_date, + animalEmoji, + meetDay, + data: { tmpBirth, ageYear, ageMonth, age, dayIcon }, + } = this.contentText; + + const phoneSize = Device.screenSize(); + const radio = phoneSize.width / phoneSize.height; + const containerStack = widget.addStack(); + containerStack.layoutHorizontally(); + + const leftStack = containerStack.addStack(); + leftStack.size = new Size(radio * widthMode, radio * heightMode); + let avatarImg = SFSymbol.named(`photo`).image; + if (this.FILE_MGR.fileExists(this.LEFT_IMG_KEY)) { + avatarImg = Image.fromFile(this.LEFT_IMG_KEY); + } + leftStack.backgroundImage = avatarImg; + containerStack.addSpacer(); + const rightStack = containerStack.addStack(); + rightStack.setPadding(0, 0, 0, 10); + rightStack.layoutVertically(); + rightStack.centerAlignContent(); + + rightStack.addSpacer(); + + const userStack = rightStack.addStack(); + userStack.layoutHorizontally(); + userStack.centerAlignContent(); + + const nameStack = userStack.addStack(); + nameStack.layoutVertically(); + + const userWidgetText = nameStack.addText(this.defaultData.username); + userWidgetText.textColor = this.widgetColor; + userWidgetText.font = this.provideFont("italic", mainTextSize + 10); + userWidgetText.shadowColor = new Color(this.defaultData.nicknameShadow); + userWidgetText.shadowOffset = new Point(3, 3); + userWidgetText.shadowRadius = 3; + + nameStack.addSpacer(5); + this.provideText(`相遇${meetDay}天`, nameStack, { + font: "Party Let", + size: mainTextSize, + opacity: 0.8, + }); + + userStack.addSpacer(); + + userStack.addImage(this.animalImg(animalEmoji)); + + rightStack.addSpacer(20); + if (tmpBirth.year > 0 && tmpBirth.month > 0 && tmpBirth.day > 0) { + this.rowCell(rightStack, { + icon: "hourglass", + color: "#1ab6f8", + title: "年龄", + text: `${ageYear + ageMonth} ${tmpBirth.day} 天`, + }); + } else { + this.rowCell(rightStack, { + icon: "hourglass", + color: "#1ab6f8", + title: "年龄", + text: age, + }); + } + rightStack.addSpacer(); + + this.rowCell(rightStack, { + icon: "calendar", + color: "#30d15b", + title: "农历", + text: `${lunar_date}`, + }); + + rightStack.addSpacer(); + + this.rowCell(rightStack, { + icon: "app.gift.fill", + color: "#fc6d6d", + title: "生日", + text: `${this_year_lunar_solar}`, + }); + + rightStack.addSpacer(); + + return widget; + }; + + renderSmall = (widget) => { + const { + this_year_lunar_solar, + lunar_date, + meetDay, + data: { tmpBirth, ageYear, ageMonth, age, dayIcon }, + } = this.contentText; + + const containerStack = widget.addStack(); + containerStack.layoutVertically(); + + containerStack.addSpacer(); + + const topStack = containerStack.addStack(); + topStack.layoutHorizontally(); + topStack.centerAlignContent(); + + const avatarStack = topStack.addStack(); + let avatarImg = SFSymbol.named(`photo`).image; + if (this.FILE_MGR.fileExists(this.LEFT_IMG_KEY)) { + avatarImg = Image.fromFile(this.LEFT_IMG_KEY); + } + + avatarStack.backgroundImage = avatarImg; + avatarStack.size = new Size(42, 42); + avatarStack.cornerRadius = avatarStack.size.width / 2; + avatarStack.borderColor = Color.green(); + avatarStack.borderWidth = 1; + + topStack.addSpacer(20); + + const nameStack = topStack.addStack(); + nameStack.addSpacer(); + nameStack.layoutVertically(); + nameStack.centerAlignContent(); + + const userWidgetText = nameStack.addText(this.defaultData.username); + userWidgetText.textColor = this.widgetColor; + userWidgetText.font = this.provideFont("italic", 16); + userWidgetText.shadowColor = new Color(this.defaultData.nicknameShadow); + userWidgetText.shadowOffset = new Point(3, 3); + userWidgetText.shadowRadius = 3; + + nameStack.addSpacer(5); + this.provideText(`相遇${meetDay}天`, nameStack, { + font: "Party Let", + size: 12, + opacity: 0.8, + }); + + containerStack.addSpacer(); + + if (tmpBirth.year > 0 && tmpBirth.month > 0 && tmpBirth.day > 0) { + this.rowCell(containerStack, { + icon: "hourglass", + color: "#1ab6f8", + title: "年龄", + text: `${ageYear + ageMonth} ${tmpBirth.day} 天`, + }); + } else { + this.rowCell(containerStack, { + icon: "hourglass", + color: "#1ab6f8", + title: "年龄", + text: age, + }); + } + containerStack.addSpacer(); + + this.rowCell(containerStack, { + icon: "calendar", + color: "#30d15b", + title: "农历", + text: `${lunar_date}`, + }); + + containerStack.addSpacer(); + + this.rowCell(containerStack, { + icon: "app.gift.fill", + color: "#fc6d6d", + title: "生日", + text: `${this_year_lunar_solar}`, + }); + + containerStack.addSpacer(); + + return widget; + }; + + /** + * 渲染函数,函数名固定 + * 可以根据 this.widgetFamily 来判断小组件尺寸,以返回不同大小的内容 + */ + async render() { + await this.init(); + const widget = new ListWidget(); + await this.getWidgetBackgroundImage(widget); + if (this.widgetFamily === "medium") { + widget.setPadding(0, 0, 0, 0); + return await this.renderMedium(widget); + } else if (this.widgetFamily === "large") { + return await this.renderLarge(widget); + } else { + return await this.renderSmall(widget); + } + } +} + +// @组件代码结束 +await Runing(Widget); //远程开发环境 diff --git a/Scripts/wsgw.js b/Scripts/wsgw.js new file mode 100644 index 0000000..a8c1007 --- /dev/null +++ b/Scripts/wsgw.js @@ -0,0 +1,381 @@ +// Variables used by Scriptable. +// These must be at the very top of the file. Do not edit. +// icon-color: deep-gray; icon-glyph: setting; + +// 添加require,是为了vscode中可以正确引入包,以获得自动补全等功能 +if (typeof require === 'undefined') require = importModule; +const { DmYY, Runing } = require('./DmYY'); + +/** + * 重写修改自作者 + * @channel https://t.me/yqc_123/ + * @feedback https://t.me/NobyDa_Chat + * @author 小白脸|𝐎𝐍𝐙𝟑𝐕 + * + * 添加重写:https://raw.githubusercontent.com/dompling/Script/master/wsgw/index.js + * + */ + +const defaultData = { + user: '**', + left: { + dayElePq: [], + balance: 0, + arrearsOfFees: false, + }, + right: { + previousBill: 0, + previousBillRate: 0, + thisYear: 0, + thisYearRate: 0, + }, + update: '', +}; + +// @组件代码开始 +class Widget extends DmYY { + constructor(arg) { + super(arg); + this.en = 'wsgw'; + this.name = '网上国网'; + this.userNum = args.widgetParameter || 0; + if (config.runsInApp) { + this.registerAction({ + icon: { name: 'photo.tv', color: '#5A74EF' }, + type: 'color', + title: '左侧背景', + desc: '左侧背景色', + val: 'leftColor', + }); + this.registerAction({ + icon: { name: 'arrow.clockwise', color: '#1890ff' }, + type: 'input', + title: '缓存时间', + desc: '默认3小时 (单位小时),填写方式数字', + placeholder: '3', + val: 'cacheTime', + }); + + this.registerAction({ + icon: { name: 'character.cursor.ibeam', color: '#EC6240' }, + type: 'input', + title: '文字缩放', + desc: '文字缩放比例,值为 0-1', + placeholder: '1', + val: 'scale', + }); + + this.registerAction('基础设置', this.setWidgetConfig); + } + } + + date = new Date(); + day = this.date.getTime(); + + dataSource = { ...defaultData }; + + init = async () => { + console.log(`当前用户下标:${this.userNum}`); + this.cacheTime = (this.settings.cacheTime || 3) * 3600000; + this.scale = parseFloat(this.settings.scale || '1'); + if ( + !this.settings.data || + this.settings.cacheDay + this.cacheTime < this.day + ) { + console.log(`缓存失效,重新获取`); + await this.cacheData(); + } else { + console.log( + `最后更新时间:${new Date(parseInt(this.settings.cacheDay)).toLocaleString()}` + ); + console.log( + `缓存失效时间:${new Date(parseInt(this.settings.cacheDay) + this.cacheTime).toLocaleString()}` + ); + this.dataSource = { ...this.settings.data[this.userNum] }; + if (!this.dataSource.user) await this.cacheData(); + console.log(this.dataSource); + } + }; + + cacheData = async () => { + try { + const response = await this.$request.get( + 'https://api.wsgw-rewrite.com/electricity/bill/all', + { timeoutInterval: 60 } + ); + console.log(response); + this.settings.data = []; + response?.forEach((dataInfo) => { + const dataSource = { + user: '**', + left: { + dayElePq: [], + balance: 0, + arrearsOfFees: false, + }, + right: { + previousBill: 0, + previousBillRate: 0, + thisYear: 0, + thisYearRate: 0, + }, + update: '', + }; + + dataSource.user = dataInfo.userInfo.consName_dst.replaceAll('*', ''); + dataSource.left.balance = parseFloat(dataInfo.eleBill.sumMoney); + dataSource.left.dayElePq = dataInfo.dayElecQuantity.sevenEleList + .filter((item) => item.dayElePq !== '-') + .map((item) => ({ + label: item.day, + value: parseFloat(item.dayElePq), + })); + + dataSource.left.arrearsOfFees = dataInfo.arrearsOfFees; + + dataSource.right.previousBill = parseFloat( + this.last(dataInfo.monthElecQuantity?.mothEleList || []) + ?.monthEleCost || 0 + ); + + const oldVal = + this.last(dataInfo.monthElecQuantity?.mothEleList || [], 2) + ?.monthEleCost || 1; + + dataSource.right.previousBillRate = + ((dataSource.right.previousBill - oldVal) / oldVal) * 100; + + dataSource.right.previousBillRate = parseFloat( + dataSource.right.previousBillRate.toFixed(2) + ); + + dataSource.right.thisYear = parseFloat( + dataInfo.monthElecQuantity?.dataInfo?.totalEleCost || 0 + ); + + const lastYearVal = dataInfo.lastYearElecQuantity.dataInfo.totalEleCost; + + dataSource.right.thisYearRate = + ((dataSource.right.thisYear - lastYearVal) / lastYearVal) * 100; + + dataSource.right.thisYearRate = parseFloat( + dataSource.right.thisYearRate.toFixed(2) + ); + + dataSource.update = dataInfo.eleBill.date; + this.settings.data.push({ ...dataSource }); + }); + console.log(this.settings.data); + this.dataSource = { ...this.settings.data[this.userNum] }; + this.settings.cacheDay = this.day; + this.saveSettings(false); + } catch (e) { + console.log(`接口数据异常:请检查 BoxJS 重写`); + console.log(e); + } + }; + + last = (data = [], index = 1) => { + return data[data.length - index]; + }; + + renderImage = async (uri) => { + return this.$request.get(uri, 'IMG'); + }; + + notSupport(w) { + const stack = w.addStack(); + stack.addText('暂不支持'); + return w; + } + + barChart() { + return ` + { + "type": "bar", + "data": { + "labels": ${JSON.stringify(this.dataSource.left.dayElePq.map((item) => item.label).reverse())}, + "datasets": [ + { + "label": "Sales", + "data": ${JSON.stringify(this.dataSource.left.dayElePq.map((item) => parseFloat(item.value)).reverse())}, + "backgroundColor": "#fff", + "borderColor": "#fff", + "borderWidth": 1, + "borderRadius": { + "topLeft": 30, + "topRight": 30, + "bottomLeft": 30, // 只为柱状图底部设置圆角 + "bottomRight": 30 + }, + "barPercentage": 0.8, // 控制柱子的宽度 + "categoryPercentage": 0.4, + "borderSkipped": false // 应用自定义的圆角设置 + } + ] + }, + "options": { + "plugins": { + "legend": { + "display": false // 隐藏图例 + }, + "title": { + "display": false // 隐藏标题 + } + }, + "scales": { + "x": { + "display": false // 完全隐藏 X 轴 + }, + "y": { + "display": false // 完全隐藏 Y 轴 + } + }, + "layout": { + "padding": 0 // 移除图表周围的内边距 + } + } +} +`; + } + + createLeft = async (widget) => { + const fontStyle = { + color: new Color('#fff'), + size: 20 * this.scale, + opacity: 0.8, + }; + const leftStack = widget.addStack(); + leftStack.cornerRadius = 10; + leftStack.layoutVertically(); + leftStack.backgroundColor = new Color( + this.settings.leftColor || '#5A74EF', + 0.8 + ); + leftStack.setPadding(10, 10, 10, 10); + + const chartStack = leftStack.addStack(); + + const chartImage = await this.renderImage( + `https://quickchart.io/chart?v=4&w=800&h=400&f=png&c=${encodeURIComponent(this.barChart())}` + ); + const chartImageStack = chartStack.addImage(chartImage); + chartImageStack.imageSize = new Size(120, 60); + + leftStack.addSpacer(); + + this.provideText('余额', leftStack, fontStyle); + + const todayStack = leftStack.addStack(); + todayStack.centerAlignContent(); + if (this.dataSource.left.arrearsOfFees) + fontStyle.color = new Color('#f65755'); + + fontStyle.size = 20 * this.scale; + this.provideText('¥ ', todayStack, fontStyle); + + fontStyle.opacity = 1; + const todayUse = this.dataSource.left.balance; + + this.provideText(` ${todayUse.toLocaleString()}`, todayStack, fontStyle); + }; + + createDot = (stack, color) => { + const dotStack = stack.addStack(); + dotStack.setPadding(0, 0, 2, 0); + const dot = dotStack.addStack(); + + dot.size = new Size(10, 10); + dot.backgroundColor = new Color(color); + dot.cornerRadius = 10; + }; + + createCell = (widget, data = { title: '', num: 0, radio: 0 }) => { + const cellStack = widget.addStack(); + cellStack.backgroundColor = new Color('#404045'); + cellStack.setPadding(10, 10, 10, 10); + cellStack.cornerRadius = 10; + cellStack.layoutVertically(); + + const fontStyle = { + color: new Color('#fff'), + size: 14 * this.scale, + opacity: 0.6, + }; + this.provideText(data.title, cellStack, fontStyle); + + const dataStack = cellStack.addStack(); + dataStack.bottomAlignContent(); + + fontStyle.size = 12 * this.scale; + this.provideText('¥ ', dataStack, fontStyle); + + fontStyle.opacity = 1; + fontStyle.size = 20 * this.scale; + this.provideText(` ${data.num.toLocaleString()}`, dataStack, fontStyle); + dataStack.addSpacer(); + + const dotStack = dataStack.addStack(); + this.createDot(dotStack, data.radio > 0 ? '#7EEF8F' : '#ED86A5'); + + fontStyle.size = 12 * this.scale; + this.provideText( + data.radio > 0 ? ` +${data.radio}%` : ` -${Math.abs(data.radio)}%`, + dataStack, + fontStyle + ); + }; + + createRight = async (widget) => { + const rightStack = widget.addStack(); + rightStack.layoutVertically(); + this.createCell(rightStack, { + title: '上期费用', + num: this.dataSource.right.previousBill, + radio: this.dataSource.right.previousBillRate, + }); + rightStack.addSpacer(); + this.createCell(rightStack, { + title: '今年费用', + num: this.dataSource.right.thisYear, + radio: this.dataSource.right.thisYearRate, + }); + }; + + renderSmall = async (w) => { + w.setPadding(10, 10, 10, 10); + await this.createLeft(w); + return w; + }; + + renderMedium = async (w) => { + w.setPadding(10, 10, 10, 10); + const containerStack = w.addStack(); + containerStack.layoutHorizontally(); + await this.createLeft(containerStack); + containerStack.addSpacer(10); + await this.createRight(containerStack); + return w; + }; + + /** + * 渲染函数,函数名固定 + * 可以根据 this.widgetFamily 来判断小组件尺寸,以返回不同大小的内容 + */ + async render() { + await this.init(); + const widget = new ListWidget(); + widget.url = `com.wsgw.e.zsdl://platformapi/`; + await this.getWidgetBackgroundImage(widget); + if (this.widgetFamily === 'medium') { + return await this.renderMedium(widget); + } else if (this.widgetFamily === 'large') { + return await this.notSupport(widget); + } else { + return await this.renderSmall(widget); + } + } +} + +// @组件代码结束 +await Runing(Widget, '0', false); //远程开发环境 diff --git a/images/count.png b/images/count.png new file mode 100644 index 0000000..919c7fb Binary files /dev/null and b/images/count.png differ diff --git a/images/ftms.png b/images/ftms.png new file mode 100644 index 0000000..59c4bfe Binary files /dev/null and b/images/ftms.png differ diff --git a/images/gas-night.png b/images/gas-night.png new file mode 100644 index 0000000..74f0508 Binary files /dev/null and b/images/gas-night.png differ diff --git a/images/large.png b/images/large.png new file mode 100644 index 0000000..31eba02 Binary files /dev/null and b/images/large.png differ diff --git a/images/medium.png b/images/medium.png new file mode 100644 index 0000000..c336dca Binary files /dev/null and b/images/medium.png differ diff --git a/images/small.png b/images/small.png new file mode 100644 index 0000000..b2fe824 Binary files /dev/null and b/images/small.png differ diff --git a/install.json b/install.json index 5840d8c..a8b7d63 100644 --- a/install.json +++ b/install.json @@ -5,7 +5,7 @@ "repo": "https://github.com/dompling/Scriptable", "apps": [ { - "version": "1.1.0", + "version": "1.1.2", "description": "DmYY组件库", "scriptURL": "https://raw.githubusercontent.com/dompling/Scriptable/master/Scripts/DmYY.js", "thumb": "https://img.icons8.com/clouds/344/settings.png", @@ -13,6 +13,10 @@ "title": "DmYY", "html": [ "

更新说明

", + "v1.1.2", + "
  • fix: 输入默认值问题
  • ", + "v1.1.1", + "
  • feat: DmYY 判断有无 actions
  • ", "v1.1.0", "
  • 修复 boxjs 读取问题
  • ", "v1.0.8", @@ -59,6 +63,12 @@ "html": [ "

    京东组件教程

    ", "

    " + ], + "depend": [ + { + "name": "DmYY", + "scriptURL": "https://raw.githubusercontent.com/dompling/Scriptable/master/Scripts/DmYY.js" + } ] }, { @@ -88,7 +98,13 @@ "scriptURL": "https://raw.githubusercontent.com/dompling/scriptableTsx/master/scripts/BiliBili.js", "thumb": "https://raw.githubusercontent.com/Orz-3/mini/master/Color/bilibili.png", "name": "BiliBili", - "title": "今日番剧" + "title": "今日番剧", + "depend": [ + { + "name": "DmYY", + "scriptURL": "https://raw.githubusercontent.com/dompling/Scriptable/master/Scripts/DmYY.js" + } + ] }, { "version": "1.0.0", @@ -124,7 +140,13 @@ "scriptURL": "https://raw.githubusercontent.com/dompling/Scriptable/master/Scripts/Calendar.js", "thumb": "https://img.icons8.com/clouds/344/edit-calendar.png", "name": "Calendar", - "title": "日历函数" + "title": "日历函数", + "depend": [ + { + "name": "DmYY", + "scriptURL": "https://raw.githubusercontent.com/dompling/Scriptable/master/Scripts/DmYY.js" + } + ] }, { "version": "1.0.0", @@ -363,6 +385,12 @@ "
    ", "

    Mobile登陆地址接口:

    ", "
  • 请自行使用代理软件查看搜索即可,操作方式类似 PC 登陆地址的查看步骤
  • " + ], + "depend": [ + { + "name": "DmYY", + "scriptURL": "https://raw.githubusercontent.com/dompling/Scriptable/master/Scripts/DmYY.js" + } ] }, { @@ -398,12 +426,24 @@ ] }, { - "version": "1.0.0", - "description": "显示定位信息的油价使用的情况", - "scriptURL": "https://raw.githubusercontent.com/dompling/scriptableTsx/master/scripts/TodayOilPrice.js", + "version": "1.0.1", + "description": "获取当前省份油价", + "scriptURL": "https://raw.githubusercontent.com/dompling/Scriptable/master/Scripts/Oild.js", "thumb": "https://img.icons8.com/clouds/344/engine-oil-level.png", "name": "TodayOilPrice", - "title": "今日油价" + "title": "今日油价", + "depend": [ + { + "name": "DmYY", + "scriptURL": "https://raw.githubusercontent.com/dompling/Scriptable/master/Scripts/DmYY.js" + } + ], + "html": [ + "

    今日油价

    ", + "
      1.0.1
    ", + "
      增加自动定位设置
    ", + "
      增加省份选择
    " + ] }, { "version": "1.0.0", @@ -411,7 +451,13 @@ "scriptURL": "https://raw.githubusercontent.com/dompling/scriptableTsx/master/scripts/COVID-19.js", "thumb": "https://img.icons8.com/clouds/344/coronavirus.png", "name": "COVID-19", - "title": "疫情日报" + "title": "疫情日报", + "depend": [ + { + "name": "DmYY", + "scriptURL": "https://raw.githubusercontent.com/dompling/Scriptable/master/Scripts/DmYY.js" + } + ] }, { "version": "1.0.1", @@ -425,6 +471,71 @@ "v1.0.1", "
  • 调整大尺寸排列效果
  • ", "
  • 调整下班倒计时默认不开启
  • " + ], + "depend": [ + { + "name": "DmYY", + "scriptURL": "https://raw.githubusercontent.com/dompling/Scriptable/master/Scripts/DmYY.js" + } + ] + }, + { + "version": "1.0.0", + "description": "交管 12123 违章和扣分查询", + "scriptURL": "https://raw.githubusercontent.com/dompling/Scriptable/master/Scripts/12123.js", + "thumb": "https://is5-ssl.mzstatic.com/image/thumb/Purple112/v4/a7/a1/61/a7a16170-e644-079e-a362-9eb273becf9d/AppIcon-1x_U007emarketing-0-4-0-0-85-220.png/492x0w.webp", + "name": "12123", + "title": "交管 12123", + "html": [ + "

    Token获取说明

    ", + "
  • 获取Token重写:Surge 12123重写模块
  • ", + "
  • 使用方法:配置重写规则,手动运行小组件,按提示跳转到 支付宝12123小程序 登录即可自动抓取/更新Token。
  • ", + "
  • 使用前,请确保您的代理APP已配置好BoxJs重写,BoxJs配置方法:BoxJS教程
  • " + ], + "depend": [ + { + "name": "DmYY", + "scriptURL": "https://raw.githubusercontent.com/dompling/Scriptable/master/Scripts/DmYY.js" + } + ] + }, + { + "version": "1.0.0", + "description": "币种涨幅", + "scriptURL": "https://raw.githubusercontent.com/dompling/Scriptable/master/Scripts/PriceWidgets.js", + "thumb": "https://assets.coingecko.com/coins/images/1/large/bitcoin.png", + "name": "PriceWidgets", + "title": "网络货币", + "html": [ + "

    迁移自JSBox脚本

    ", + "
  • 原项目:wuzeyou/PriceWidgets
  • ", + "
  • 特别感谢大佬:@Jackie Xiang
  • " + ], + "depend": [ + { + "name": "DmYY", + "scriptURL": "https://raw.githubusercontent.com/dompling/Scriptable/master/Scripts/DmYY.js" + } + ] + }, + { + "version": "1.0.2", + "description": "网上国网", + "scriptURL": "https://raw.githubusercontent.com/dompling/Scriptable/master/Scripts/wsgw.js", + "thumb": "https://is1-ssl.mzstatic.com/image/thumb/Purple116/v4/83/d8/8a/83d88a92-5a4d-7a2f-118c-80d795e7a9f6/AppIcon-0-0-1x_U007emarketing-0-5-0-0-sRGB-85-220.png/144x144.png", + "name": "wsgw", + "title": "网上国网", + "html": [ + "

    网上国网

    ", + "
  • 1.填写账号密码BoxJS订阅
  • ", + "
  • 2.参考借用【@小白脸】大佬的重写和【@Yuheng0101】大佬的脚本
  • ", + "
  • 3.Surge 添加远程重写模块【网上国网重写】、其他的请自行使用 https://script.hub 转换
  • " + ], + "depend": [ + { + "name": "DmYY", + "scriptURL": "https://raw.githubusercontent.com/dompling/Scriptable/master/Scripts/DmYY.js" + } ] } ] diff --git a/package.json b/package.json index 409499c..7d7494d 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ ], "license": "ISC", "devDependencies": { - "@types/scriptable-ios": "^1.6.1" + "@types/scriptable-ios": "^1.6.1", + "prettier": "^3.3.3" } } diff --git a/yarn.lock b/yarn.lock index ebd5803..789a8f1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6,3 +6,8 @@ version "1.6.1" resolved "https://registry.npm.taobao.org/@types/scriptable-ios/download/@types/scriptable-ios-1.6.1.tgz#44766b47a0c0c9f92a3c1bf46214288cf3d926f4" integrity sha1-RHZrR6DAyfkqPBv0YhQojPPZJvQ= + +prettier@^3.3.3: + version "3.3.3" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.3.3.tgz#30c54fe0be0d8d12e6ae61dbb10109ea00d53105" + integrity sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==