├── .gitignore ├── .prettierrc ├── 2YaInstall.js ├── JDDou ├── README.md ├── jd_img.gif ├── jdd.png ├── jddnew.png ├── yea.gif └── yesterday.png ├── JDWuLiu ├── README.md ├── cart.png └── none.jpg ├── README.md ├── Scripts ├── 12123.js ├── BiliBili.js ├── BiliBiliUp.js ├── BiliBiliWatch.js ├── Calendar.js ├── CalendarFnc.js ├── CarWidget.js ├── ChinaMobile.js ├── ChinaTelecom.js ├── ChinaUnicom.js ├── Contact.js ├── DmYY.js ├── Ftms.js ├── Health.js ├── HistoryToday.js ├── JD-all-one-v2.js ├── JDDou.js ├── JDDouK.js ├── JDWuLiu.js ├── Oild.js ├── PoisonCalendar.js ├── PoisonCalendarText.js ├── PriceWidgets.js ├── Spotify.js ├── Telegram.js ├── VPNBoardPress.js ├── VPNSubscription.js ├── VpnBoard.js ├── YouTube.js ├── ZXTrains.js ├── birthday.js ├── crypto-js.min.js ├── webo.js └── wsgw.js ├── birthdayCountDown ├── 2Ya.jpg ├── Components │ └── Calendar.js ├── README.md ├── birthday.png ├── img.GIF ├── index.js └── thumb1.jpg ├── extra_install.json ├── images ├── count.png ├── ftms.png ├── gas-night.png ├── jdk.jpg ├── large.png ├── medium.png ├── more.png └── small.png ├── install.json ├── package.json ├── widget.Install.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | # production 3 | /build 4 | /.vscode 5 | /.idea 6 | /.history 7 | test.js 8 | Env.js 9 | .DS_Store 10 | .DS_Store 11 | /.output 12 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "singleQuote": true, 4 | "tabWidth": 2, 5 | "trailingComma": "es5" 6 | } -------------------------------------------------------------------------------- /2YaInstall.js: -------------------------------------------------------------------------------- 1 | // Variables used by Scriptable. 2 | // These must be at the very top of the file. Do not edit. 3 | // icon-color: brown; icon-glyph: download; 4 | 5 | //订阅地址 6 | const subscriptionURL = 'https://raw.githubusercontent.com/dompling/Scriptable/master/install.json'; 7 | 8 | class YaYaInstall { 9 | constructor() { 10 | this.request = new Request(''); 11 | this.files = FileManager.iCloud(); 12 | this.rootPath = this.files.documentsDirectory(); 13 | this.defaultHeaders = { 14 | Accept: '*/*', 15 | 'Content-Type': 'application/json', 16 | }; 17 | } 18 | 19 | saveFileName = (fileName) => { 20 | const hasSuffix = fileName.lastIndexOf('.') + 1; 21 | return !hasSuffix ? `${fileName}.js` : fileName; 22 | }; 23 | 24 | write = (fileName, content) => { 25 | let file = this.saveFileName(fileName); 26 | const filePath = `${this.rootPath}/${file}`; 27 | FileManager.iCloud().writeString(filePath, content); 28 | return true; 29 | }; 30 | 31 | fetchUrlString = async ({url, headers = {}}, callback = () => {}) => { 32 | this.request.url = url; 33 | this.request.method = 'GET'; 34 | this.request.headers = { 35 | ...headers, 36 | ...this.defaultHeaders, 37 | }; 38 | const data = await this.request.loadString(); 39 | callback(this.request.response, data); 40 | return data; 41 | }; 42 | 43 | saveFile = async ({moduleName, url}) => { 44 | const header = `// Variables used by Scriptable. 45 | // These must be at the very top of the file. Do not edit. 46 | // icon-color: deep-gray; icon-glyph: file-code;\n`; 47 | const content = await this.fetchUrlString({url}); 48 | const fileHeader = content.includes('icon-color') ? `` : header; 49 | this.write(`${moduleName}`, `${fileHeader}${content}`); 50 | }; 51 | 52 | install = async () => { 53 | console.log('🤖更新开始!'); 54 | const req = new Request(subscriptionURL); 55 | const subscription = await req.loadJSON(); 56 | const apps = subscription.apps; 57 | for (const script of apps) { 58 | await this.saveFile({moduleName: script.name, url: script.scriptURL}); 59 | if (script.depend) { 60 | for (const item of script.depend) { 61 | await this.saveFile({moduleName: item.name, url: item.scriptURL}); 62 | } 63 | } 64 | // console.log(script.moduleName + ':更新成功'); 65 | } 66 | console.log('🤖更新结束!'); 67 | }; 68 | } 69 | 70 | await new YaYaInstall().install(); 71 | -------------------------------------------------------------------------------- /JDDou/README.md: -------------------------------------------------------------------------------- 1 | # 京东豆收入明细小组件 2 | 3 | > scriptable 中号组件 参数填写 boxjs 的 cookie 下标:0,1,2,3.... 4 | > 项目地址:[京东豆收](https://github.com/dompling/Scriptable/blob/master/Scripts/JDDou.js) 5 | 6 | # 使用方式 7 | 8 | 1. 该组件配合 boxjs 使用,请自行网上搜索配置 boxjs 的教程。 9 | 2. 使用专用的获取 JD cookie 的地址 [京东 Cookie 获取](https://raw.githubusercontent.com/dompling/Script/master/jd/JD_extra_cookie.js) 10 | 3. 引入[DmYY.js] 11 | 4. 获取到 Cookie 之后,配置提示的下标请根据下标自行减一填写到参数里面 12 | 13 | ![](https://raw.githubusercontent.com/dompling/Scriptable/master/JD/jd_img.gif) 14 | 15 | ## 特别感谢 16 | 17 | 1. [@lxk0301](https://github.com/lxk0301) 18 | 2. [@NobyDa](https://github.com/NobyDa) 19 | -------------------------------------------------------------------------------- /JDDou/jd_img.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dompling/Scriptable/c00be22b941a22cfdade52cf7851df69214f7595/JDDou/jd_img.gif -------------------------------------------------------------------------------- /JDDou/jdd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dompling/Scriptable/c00be22b941a22cfdade52cf7851df69214f7595/JDDou/jdd.png -------------------------------------------------------------------------------- /JDDou/jddnew.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dompling/Scriptable/c00be22b941a22cfdade52cf7851df69214f7595/JDDou/jddnew.png -------------------------------------------------------------------------------- /JDDou/yea.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dompling/Scriptable/c00be22b941a22cfdade52cf7851df69214f7595/JDDou/yea.gif -------------------------------------------------------------------------------- /JDDou/yesterday.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dompling/Scriptable/c00be22b941a22cfdade52cf7851df69214f7595/JDDou/yesterday.png -------------------------------------------------------------------------------- /JDWuLiu/README.md: -------------------------------------------------------------------------------- 1 | # 京东物流小组件 2 | 3 | > 项目地址:[京东物流](https://github.com/dompling/Scriptable/blob/master/Scripts/JDWuLiu.js) 4 | 5 | 征集一个 暂无物流信息的背景图片,谢谢大佬们了。 6 | 7 | # 使用方式 8 | 9 | 1. 该组件配合 boxjs 使用,请自行网上搜索配置 boxjs 的教程。 10 | 2. 使用专用的获取 JD cookie 的地址 [京东 Cookie 获取](https://raw.githubusercontent.com/dompling/Script/master/jd/JD_extra_cookie.js) 11 | 3. 引入 小件件 组件 12 | 4. 获取 cookie 后进入 scriptable 运行一下,到弹出内容中选择京东 CK ,进入选取需要 展示的 ck 物流信息 13 | 14 | ## 特别感谢 15 | 16 | 1. [@lxk0301](https://github.com/lxk0301) 17 | 2. [@NobyDa](https://github.com/NobyDa) 18 | -------------------------------------------------------------------------------- /JDWuLiu/cart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dompling/Scriptable/c00be22b941a22cfdade52cf7851df69214f7595/JDWuLiu/cart.png -------------------------------------------------------------------------------- /JDWuLiu/none.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dompling/Scriptable/c00be22b941a22cfdade52cf7851df69214f7595/JDWuLiu/none.jpg -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # scriptable [Tg电报群](https://t.me/Scriptable_JS) 2 | 3 | >首先安装 TF版的 [scriptable](https://testflight.apple.com/join/uN1vTqxk) 4 | 5 | > 网页安装:(推荐:⭐️⭐️⭐️⭐️⭐️)[WebStore](https://dompling.github.io/store/#/menu) 6 | > 安装教程:[网页安装步骤](https://t.me/Scriptable_JS/101536) 7 | 8 | - 作者:@2Ya - 订阅安装包 9 | 10 | - 作者: @2214962083 - 订阅安装包 11 | 12 | - 作者: @anker1209 - 订阅安装包 13 | 14 | 15 | ##### BoxJs 简单说明可看作者[BoxJs 仓库地址](https://github.com/chavyleung/scripts/) 16 | 17 | BoxJS 数据,是根据 圈 X,Loon , Surge 做的代理功能,教程如下地址: 18 | 19 | - [BoxJs 使用教程](https://chavyleung.gitbook.io/boxjs/) 20 | 21 | - [BoxJs 教程视频](https://youtu.be/eIpBrRxiy0w) 22 | 23 | # 组件列表 24 | 25 | > 特别说明:关于京东的小组件都支持多账号,但是必须用代理脚本去抓取 ck,通过选择设置下标的方式来切换多个账号。[京东 Cookie](https://raw.githubusercontent.com/dompling/Script/master/jd/JD_extra_cookie.js) 26 | 27 | | 名称 | 说明 | 备注 | 28 | | ----------------------------- | ---------- | --------------------------------------------------------------------------------------------------- | 29 | | [破壳日](Scripts/Birthday.js) | BoxJs 数据 | | 30 | | [历史上的今天](Scripts/HistoryToday.js) | | | 31 | | [京东豆收支明细](Scripts/JDDou.js) | BoxJs 数据 | parameter: 0 京东 ck 下标 ,也可根据 APP 提示自行选择 | 32 | | [京东豆收支 K 线图](Scripts/JDDouK.js) | BoxJs 数据 | parameter: 0 京东 ck 下标 ,也可根据 APP 提示自行选择 | 33 | | [京东商品物流](Scripts/JDWuLiu.js) | BoxJs 数据 | parameter: 0 京东 ck 下标 ,也可根据 APP 提示自行选择 | 34 | | [哔哩哔哩今日番剧](Scripts/BiliBili.js) | | | 35 | | [哔哩哔哩关注消息](Scripts/BiliBiliWatch.js) | BoxJs 数据 | [哔哩哔哩 CK](https://raw.githubusercontent.com/dompling/Script/master/BiliBili/bilibili.cookie.js) | 36 | | [哔哩哔哩 UP 主](Scripts/BiliBiliUp.js) | | parameter:50952087 ,(mid:50952087)获取方式,打开 B 站Up主个人空间,复制地址的数字。如[https://space.bilibili.com/50952087/](https://space.bilibili.com/50952087/)的 mid 为 '50952087' | 37 | | [智联火车票提醒](Scripts/ZXTrains.js) | BoxJs 数据,请根据教程来使用 | [购买火车票重写教程](https://raw.githubusercontent.com/dompling/Script/master/ZXTrians/ZXTrains.js) | 38 | | [健康步数](Scripts/Health.js) | 需要配合[健康数据](https://www.icloud.com/shortcuts/beb65db5ea0a474abe7ff080410b9ddf)捷径进行使用 | 捷径运行之后自动生成数据 | 39 | | [桌面联系人](Scripts/Contact.js) | 显示桌面联系人 | parameter:YaYa (输入相应的手机联系人的名字,会自动关联搜索通讯录,可以是姓,可以是名字,可以是姓+名字 ) | 40 | | [Telegram](Scripts/Telegram.js) | 显示桌面订阅 | parameter: Durov (输入相应的电报名) | 41 | | [YouTube](Scripts/YouTube.js) | 显示桌面订阅 | parameter: Durov (输入相应的油管作者名,若不显示输入作者订阅地址再试) | 42 | | [中国电信](Scripts/ChinaTelecom.js) | BoxJs 数据 | 本组件读取 boxjs 缓存,其他用户请自行抓取 authToken,Cookie | 43 | | [中国移动](Scripts/ChinaMobile.js) | BoxJs 数据 | 本组件读取 boxjs 缓存,其他用户请自行抓取 | 44 | | [机场+签到](Scripts/VpnBoard.js) | 手动新增账号 | parameter:下标 | 45 | | [机场+签到 第二版](Scripts/VPNBoardPress.js) | 手动新增账号 | parameter:下标 | 46 | | [机场订阅流量](Scripts/VPNSubscription.js) | 手动新增账号 | parameter:下标 | 47 | | [v2board机场模板](Scripts/VPNV2Bord.js) | 手动新增账号 | parameter:下标 | 48 | | [毒汤日历](Scripts/PoisonCalendar.js) | BoxJS数据 |[cookie 获取方式](https://raw.githubusercontent.com/dompling/Script/master/DJT/djt.cookie.js) | 49 | | [疫情日报](https://raw.githubusercontent.com/dompling/scriptableTsx/master/scripts/COVID-19.js) | 地区拼音,cd|xian 般城市直接首字母,可能不显示的时候试试全部拼音 | 50 | | [今日油价](https://raw.githubusercontent.com/dompling/scriptableTsx/master/scripts/TodayOilPrice.js) | BoxJS数据 | 自行申请腾讯地图的 token,以显示附近加油站 | 51 | 52 | # 赞赏码 53 | 54 | 55 | ## 特别感谢 56 | - [@GideonSenku](https://github.com/GideonSenku) 57 | - [@NobyDa](https://github.com/NobyDa) 58 | - [@chavyleung](https://github.com/chavyleung) 59 | - [@lxk0301](https://github.com/lxk0301) 60 | - [@『Hell Cell』](https://t.me/HellCellZC123) 61 | - [@xinian](https://github.com/58xinian) 提供 UI 帮助 62 | -------------------------------------------------------------------------------- /Scripts/12123.js: -------------------------------------------------------------------------------- 1 | // Variables used by Scriptable. 2 | // These must be at the very top of the file. Do not edit. 3 | // icon-color: deep-gray; icon-glyph: car; 4 | 5 | // 添加require,是为了vscode中可以正确引入包,以获得自动补全等功能 6 | if (typeof require === 'undefined') require = importModule; 7 | const { DmYY, Runing } = require('./DmYY'); 8 | 9 | const API_PARAMS = { 10 | api4: 'biz.vio.detail.query', 11 | infoURL: 'https://miniappcsfw.122.gov.cn:8443/openapi/invokeApi/business/biz', 12 | api1: 'biz.vio.unhandledVioCount.query', 13 | productId: 'p10000000000000000001', 14 | alipay: 'alipays://platformapi/startapp?appId=2019050964403523', 15 | api2: 'biz.vio.peccancyChannelList.query', 16 | status: 17 | 'alipays://platformapi/startapp?appId=2019050964403523&page=pages%2Flicense%2Flicense', 18 | update: 'https://gitcode.net/4qiao/scriptable/raw/master/api/violation.js', 19 | api3: 'biz.vio.peccancyUnhandleInfoList.query', 20 | Ver: 'Version 1.2\n\nverifyToken过期需打开Quantumult-X', 21 | }; 22 | 23 | // @组件代码开始 24 | class Widget extends DmYY { 25 | constructor(arg) { 26 | super(arg, { 27 | lightBgColor: '#2581f2', 28 | darkBgColor: '#2581f2', 29 | darkColor: '#fff', 30 | lightColor: '#fff', 31 | }); 32 | this.en = '12123'; 33 | this.name = '交管 12123'; 34 | config.runsInApp && 35 | this.registerAction({ 36 | icon: { name: 'paperplane', color: '#722ed1' }, 37 | type: 'input', 38 | title: 'Token', 39 | desc: '微信小程序交管12123获取', 40 | val: 'Token', 41 | onClick: async () => { 42 | const token = this.settings.token; 43 | this.settings.token = 44 | (await this.getCache('wx_12123', false)) || token; 45 | if (this.settings.token) this.saveSettings(false); 46 | return this.setAlertInput('Token', '设置 token', { 47 | token: '微信小程序交管12123获取', 48 | }); 49 | }, 50 | }); 51 | 52 | config.runsInApp && this.registerAction('基础设置', this.setWidgetConfig); 53 | } 54 | 55 | format = (str) => { 56 | return parseInt(str) >= 10 ? str : `0${str}`; 57 | }; 58 | 59 | date = new Date(); 60 | arrUpdateTime = [ 61 | this.format(this.date.getMonth() + 1), 62 | this.format(this.date.getDate()), 63 | this.format(this.date.getHours()), 64 | this.format(this.date.getMinutes()), 65 | ]; 66 | 67 | dataSource = { 68 | left: { 69 | title: '川 G88888', 70 | icon: 'car.fill', 71 | listItem: [ 72 | { label: '未处违法', value: `0`, unit: '条' }, 73 | { label: '车辆状态', value: '正常' }, 74 | { label: '上次更新', value: '00:00' }, 75 | ], 76 | }, 77 | right: { 78 | title: '驾驶证', 79 | icon: 'creditcard.fill', 80 | listItem: [ 81 | { label: '证件状态', value: '正常' }, 82 | { label: '累计扣分', value: `0`, unit: '分' }, 83 | { label: '重置日期', value: '—' }, 84 | ], 85 | }, 86 | }; 87 | 88 | init = async () => { 89 | this.settings.token = 90 | (await this.getCache('wx_12123', false)) || this.settings.token; 91 | if (this.settings.dataSource) { 92 | this.dataSource = this.settings.dataSource; 93 | } else { 94 | await this.cacheData(); 95 | } 96 | this.cacheData(); 97 | }; 98 | 99 | cacheData = async () => { 100 | try { 101 | const token = this.settings.token.replace('params=', ''); 102 | const body = JSON.parse(decodeURIComponent(token)); 103 | const params = { 104 | sign: body.sign, 105 | // businessId: body.businessId, 106 | verifyToken: body.verifyToken, 107 | // businessPrincipalId: body.businessPrincipalId, 108 | }; 109 | 110 | const response = await this.$request.post(API_PARAMS.infoURL, { 111 | body: `params=${JSON.stringify({ 112 | api: API_PARAMS.api1, 113 | productId: API_PARAMS.productId, 114 | ...params, 115 | })}`, 116 | }); 117 | console.log(response); 118 | if (response.success) { 119 | const illegal = response.data.list[0] || {}; 120 | this.dataSource.left.listItem[0].value = illegal.count || 0; 121 | 122 | const details = await this.$request.post(API_PARAMS.infoURL, { 123 | body: `params=${encodeURIComponent( 124 | JSON.stringify({ 125 | api: 'biz.user.integration.query', 126 | productId: API_PARAMS.productId, 127 | ...params, 128 | }) 129 | )}`, 130 | }); 131 | 132 | console.log(details); 133 | 134 | if (details.success) { 135 | const { drivingLicense, vehicles } = details.data; 136 | const reaccDate = drivingLicense.reaccDate.split('-'); 137 | this.dataSource.right.title = `驾驶证 ${drivingLicense.allowToDrive}`; 138 | this.dataSource.right.listItem[1].value = 139 | drivingLicense.cumulativePoint; 140 | this.dataSource.right.listItem[2].value = `${reaccDate[1]}-${reaccDate[2]}`; 141 | 142 | if (vehicles.length) { 143 | this.dataSource.left.title = vehicles[0].plateNumber; 144 | } 145 | } 146 | 147 | this.dataSource.left.listItem[2].value = `${this.arrUpdateTime[2]}:${this.arrUpdateTime[3]}`; 148 | 149 | this.settings.dataSource = this.dataSource; 150 | this.saveSettings(false); 151 | } else { 152 | this.notify( 153 | `verifyToken已过期 ⚠️`, 154 | '点击通知框自动跳转到支付宝小程序交管12123页面获取最新的Token ( 请确保已打开辅助工具 )', 155 | API_PARAMS.alipay 156 | ); 157 | } 158 | } catch (e) { 159 | console.log(e); 160 | } 161 | }; 162 | 163 | renderImage = async (uri) => { 164 | return this.$request.get(uri, 'IMG'); 165 | }; 166 | 167 | notSupport(w) { 168 | const stack = w.addStack(); 169 | stack.addText('暂不支持'); 170 | return w; 171 | } 172 | 173 | renderSmall = async (w) => { 174 | this.notSupport(w); 175 | return w; 176 | }; 177 | 178 | renderLarge = async (w) => { 179 | return this.notSupport(w); 180 | }; 181 | 182 | renderCard = (w, data) => { 183 | w.borderColor = this.widgetColor; 184 | w.borderWidth = 2; 185 | w.cornerRadius = 8; 186 | 187 | w.layoutVertically(); 188 | w.setPadding(10, 10, 10, 10); 189 | const topStack = w.addStack(); 190 | topStack.layoutHorizontally(); 191 | topStack.centerAlignContent(); 192 | const iconImage = SFSymbol.named(data.icon).image; 193 | const iconImageStack = topStack.addImage(iconImage); 194 | iconImageStack.tintColor = this.widgetColor; 195 | iconImageStack.imageSize = new Size(30, 30); 196 | 197 | topStack.addSpacer(10); 198 | 199 | const licensePlateText = topStack.addText(data.title); 200 | licensePlateText.textColor = this.widgetColor; 201 | licensePlateText.font = this.provideFont('bold', 14); 202 | 203 | w.addSpacer(); 204 | 205 | data.listItem.forEach((item, index) => { 206 | const listItemStack = w.addStack(); 207 | listItemStack.centerAlignContent(); 208 | const labelText = listItemStack.addText(item.label); 209 | labelText.textColor = this.widgetColor; 210 | labelText.font = this.provideFont('medium', 14); 211 | 212 | listItemStack.addSpacer(); 213 | if (index !== data.listItem.length - 1) w.addSpacer(); 214 | 215 | const valueText = listItemStack.addText(`${item.value}`); 216 | valueText.textColor = this.widgetColor; 217 | valueText.font = this.provideFont('medium', 14); 218 | 219 | if (item.unit) { 220 | const unitText = listItemStack.addText(item.unit); 221 | unitText.textColor = this.widgetColor; 222 | unitText.font = this.provideFont('medium', 14); 223 | } 224 | }); 225 | }; 226 | 227 | renderMedium = async (w) => { 228 | const containerStack = w.addStack(); 229 | containerStack.layoutHorizontally(); 230 | containerStack.centerAlignContent(); 231 | 232 | const leftStack = containerStack.addStack(); 233 | this.renderCard(leftStack, this.dataSource.left); 234 | 235 | containerStack.addSpacer(10); 236 | 237 | const rightStack = containerStack.addStack(); 238 | this.renderCard(rightStack, this.dataSource.right); 239 | return w; 240 | }; 241 | 242 | /** 243 | * 渲染函数,函数名固定 244 | * 可以根据 this.widgetFamily 来判断小组件尺寸,以返回不同大小的内容 245 | */ 246 | async render() { 247 | await this.init(); 248 | const widget = new ListWidget(); 249 | await this.getWidgetBackgroundImage(widget); 250 | if (this.widgetFamily === 'medium') { 251 | return await this.renderMedium(widget); 252 | } else if (this.widgetFamily === 'large') { 253 | return await this.renderLarge(widget); 254 | } else { 255 | return await this.renderSmall(widget); 256 | } 257 | } 258 | } 259 | 260 | // @组件代码结束 261 | await Runing(Widget, '', false); //远程开发环境 262 | -------------------------------------------------------------------------------- /Scripts/BiliBili.js: -------------------------------------------------------------------------------- 1 | // Variables used by Scriptable. 2 | // These must be at the very top of the file. Do not edit. 3 | // icon-color: deep-gray; icon-glyph: tv; 4 | 5 | // 添加require,是为了vscode中可以正确引入包,以获得自动补全等功能 6 | if (typeof require === 'undefined') require = importModule; 7 | const { DmYY, Runing } = require('./DmYY'); 8 | 9 | // @组件代码开始 10 | class Widget extends DmYY { 11 | constructor(arg) { 12 | super(arg); 13 | this.name = '哔哩哔哩今日番剧'; 14 | this.en = 'BiliBiliMonitor'; 15 | this.logo = 16 | 'https://raw.githubusercontent.com/Orz-3/mini/master/Color/bilibili.png'; 17 | config.runsInApp && this.registerAction('基础设置', this.setWidgetConfig); 18 | this.cacheName = this.md5(`dataSouce_${this.en}`); 19 | } 20 | 21 | useBoxJS = false; 22 | today = ''; 23 | dataSource = []; 24 | 25 | init = async () => { 26 | try { 27 | const today = new Date(); 28 | const month = today.getMonth() + 1; 29 | const day = today.getDate(); 30 | this.today = `${month}-${day}`; 31 | if (Keychain.contains(this.cacheName)) { 32 | const dataSource = JSON.parse(Keychain.get(this.cacheName)); 33 | if (dataSource[this.today]) { 34 | this.dataSource = dataSource[this.today].seasons; 35 | } else { 36 | this.dataSource = await this.getDramaList(); 37 | } 38 | } else { 39 | this.dataSource = await this.getDramaList(); 40 | } 41 | } catch (e) { 42 | console.log(e); 43 | } 44 | }; 45 | 46 | getDramaList = async () => { 47 | const url = `https://bangumi.bilibili.com/web_api/timeline_global`; 48 | const response = await this.$request.get(url); 49 | try { 50 | if (response.code === 0 && response.result.length > 0) { 51 | const dataSource = response.result; 52 | const result = dataSource.find((item) => item.date === this.today); 53 | if (result) { 54 | Keychain.set( 55 | this.cacheName, 56 | JSON.stringify({ [this.today]: result }), 57 | ); 58 | return result.seasons; 59 | } 60 | } 61 | return false; 62 | } catch (e) { 63 | return false; 64 | } 65 | }; 66 | 67 | setListCell = async (body, data) => { 68 | let { 69 | cover, 70 | url, 71 | title, 72 | pub_time, 73 | pub_index, 74 | delay, 75 | delay_reason, 76 | delay_index, 77 | } = data; 78 | body.url = url; 79 | const imageView = body.addStack(); 80 | imageView.size = new Size(89, 105); 81 | imageView.cornerRadius = 5; 82 | const image = await this.$request.get(cover, 'IMG'); 83 | imageView.backgroundImage = image; 84 | imageView.borderWidth = 1; 85 | imageView.borderColor = new Color(this.widgetColor.hex, 0.7); 86 | const stackDesc = imageView.addStack(); 87 | 88 | stackDesc.layoutVertically(); 89 | const stackDescTop = stackDesc.addStack(); 90 | stackDescTop.setPadding(5, 0, 0, 0); 91 | const textColor = new Color('#fff'); 92 | if (delay) pub_index = `${delay_index}「${delay_reason}」`; 93 | stackDescTop.addSpacer(); 94 | const stackTopText = stackDescTop.addStack(); 95 | stackTopText.setPadding(0, 2, 0, 2); 96 | stackTopText.backgroundColor = new Color('#000', 0.3); 97 | stackTopText.cornerRadius = 4; 98 | const subContent = stackTopText.addText(pub_index); 99 | subContent.font = Font.boldSystemFont(10); 100 | subContent.textColor = textColor; 101 | subContent.lineLimit = 1; 102 | 103 | stackDesc.addSpacer(); 104 | const stackDescBottom = stackDesc.addStack(); 105 | stackDescBottom.addSpacer(); 106 | stackDescBottom.backgroundColor = new Color('#000', 0.3); 107 | 108 | const textView = stackDescBottom.addStack(); 109 | textView.setPadding(0, 10, 0, 10); 110 | textView.size = new Size(89, 30); 111 | textView.layoutVertically(); 112 | const descText = textView.addText(title); 113 | descText.font = Font.boldSystemFont(10); 114 | descText.textColor = textColor; 115 | descText.lineLimit = 1; 116 | 117 | const timerText = textView.addText(`更新:${pub_time}`); 118 | timerText.font = Font.lightSystemFont(10); 119 | timerText.textColor = textColor; 120 | timerText.lineLimit = 1; 121 | stackDescBottom.addSpacer(); 122 | 123 | return body; 124 | }; 125 | 126 | fillRect(drawing, rect, color) { 127 | const path = new Path(); 128 | path.addRoundedRect(rect, 0, 0); 129 | drawing.addPath(path); 130 | drawing.setFillColor(new Color(color, 1)); 131 | drawing.fillPath(); 132 | } 133 | 134 | drawLine(drawing, rect, color, scale) { 135 | const x1 = Math.round(rect.x + scale * 1.5); 136 | const y1 = rect.y - scale; 137 | const x2 = Math.round(rect.width + scale * 1.5); 138 | const point1 = new Point(x1, y1); 139 | const point2 = new Point(x2, y1); 140 | const path = new Path(); 141 | path.move(point1); 142 | path.addLine(point2); 143 | drawing.addPath(path); 144 | drawing.setStrokeColor(new Color(color, 1)); 145 | drawing.setLineWidth(60 / (40 + 15 * scale)); 146 | drawing.strokePath(); 147 | } 148 | 149 | setLine = (stack, color) => { 150 | try { 151 | const topDrawing = new DrawContext(); 152 | topDrawing.size = new Size(642, 100); 153 | topDrawing.opaque = false; 154 | topDrawing.respectScreenScale = true; 155 | 156 | const rectLine = new Rect(0, 70, 610, 26); 157 | this.fillRect(topDrawing, rectLine, color); 158 | for (let i = 0; i < 40; i++) { 159 | this.drawLine(topDrawing, rectLine, color, i); 160 | } 161 | 162 | const stackLine = stack.addStack(); 163 | stack.backgroundImage = topDrawing.getImage(); 164 | stackLine.addSpacer(); 165 | const line = stackLine.addStack(); 166 | line.size = new Size(1, 10); 167 | stackLine.addSpacer(); 168 | } catch (e) { 169 | console.log(e); 170 | } 171 | }; 172 | 173 | setWidget = async (body, data) => { 174 | const d = body.addStack(); 175 | d.addSpacer(); 176 | const container = d.addStack(); 177 | container.layoutVertically(); 178 | const dataSource = data.length > 3 ? [data.splice(0, 3), data] : [data]; 179 | let itemIndex = 0; 180 | for (const item of dataSource) { 181 | let listItem = container.addStack(); 182 | let index = 0; 183 | for (const video of item) { 184 | const stackItem = listItem.addStack(); 185 | await this.setListCell(stackItem, video); 186 | index++; 187 | if (item.length !== index) listItem.addSpacer(13); 188 | } 189 | itemIndex++; 190 | if (dataSource.length !== itemIndex) container.addSpacer(13); 191 | } 192 | if (this.widgetFamily === 'large') { 193 | container.addSpacer(); 194 | this.setLine(container, '#e8e8e8'); 195 | const timerColor = new Color(this.widgetColor.hex, 0.7); 196 | const fontSize = 10; 197 | container.addSpacer(); 198 | const stackFooter = container.addStack(); 199 | stackFooter.addSpacer(); 200 | const now = new Date(); 201 | const stackDate = stackFooter.addDate( 202 | new Date(`${now.getFullYear()}/${now.getMonth() + 1}/${now.getDate()}`), 203 | ); 204 | stackDate.textColor = timerColor; 205 | stackDate.fontSize = fontSize; 206 | stackDate.rightAlignText(); 207 | stackDate.applyTimerStyle(); 208 | } 209 | d.addSpacer(); 210 | return body; 211 | }; 212 | 213 | renderSmall = async (w) => { 214 | const stack = w.addStack(); 215 | stack.addText('暂不支持'); 216 | return w; 217 | }; 218 | 219 | renderLarge = async (w) => { 220 | const dataSource = this.getRandomArrayElements(this.dataSource, 6); 221 | return await this.setWidget(w, dataSource); 222 | }; 223 | 224 | renderMedium = async (w) => { 225 | const dataSource = this.getRandomArrayElements(this.dataSource, 3); 226 | return await this.setWidget(w, dataSource); 227 | }; 228 | 229 | /** 230 | * 渲染函数,函数名固定 231 | * 可以根据 this.widgetFamily 来判断小组件尺寸,以返回不同大小的内容 232 | */ 233 | async render() { 234 | await this.init(); 235 | const widget = new ListWidget(); 236 | await this.getWidgetBackgroundImage(widget); 237 | const header = widget.addStack(); 238 | if (this.widgetFamily !== 'small') { 239 | await this.renderJDHeader(header); 240 | } else { 241 | await this.renderHeader(header, this.logo, this.name, this.widgetColor); 242 | } 243 | widget.addSpacer(10); 244 | if (this.widgetFamily === 'medium') { 245 | return await this.renderMedium(widget); 246 | } else if (this.widgetFamily === 'large') { 247 | return await this.renderLarge(widget); 248 | } else { 249 | return await this.renderSmall(widget); 250 | } 251 | } 252 | 253 | renderJDHeader = async (header) => { 254 | header.centerAlignContent(); 255 | await this.renderHeader(header, this.logo, this.name, this.widgetColor); 256 | header.addSpacer(); 257 | const headerMore = header.addStack(); 258 | headerMore.url = ''; 259 | headerMore.setPadding(1, 10, 1, 10); 260 | headerMore.cornerRadius = 10; 261 | headerMore.backgroundColor = new Color('#fff', 0.5); 262 | const textItem = headerMore.addText(this.today); 263 | textItem.font = Font.boldSystemFont(12); 264 | textItem.textColor = this.widgetColor; 265 | textItem.lineLimit = 1; 266 | textItem.rightAlignText(); 267 | return header; 268 | }; 269 | } 270 | 271 | // @组件代码结束 272 | // await Runing(Widget, "", false); // 正式环境 273 | await Runing(Widget, '', false); //远程开发环境 274 | -------------------------------------------------------------------------------- /Scripts/BiliBiliUp.js: -------------------------------------------------------------------------------- 1 | // Variables used by Scriptable. 2 | // These must be at the very top of the file. Do not edit. 3 | // icon-color: gray; icon-glyph: chalkboard; 4 | 5 | // 添加require,是为了vscode中可以正确引入包,以获得自动补全等功能 6 | if (typeof require === 'undefined') require = importModule; 7 | const {DmYY, Runing} = require('./DmYY'); 8 | 9 | // @组件代码开始 10 | class Widget extends DmYY { 11 | constructor(arg) { 12 | super(arg); 13 | this.name = 'B站 UP 主'; 14 | this.en = 'BiliBiliUp'; 15 | this.inputValue = arg || '50952087'; 16 | this.Run(); 17 | } 18 | 19 | useBoxJS = false; 20 | ytInitialData = {}; 21 | videos = []; 22 | baseUrl = 'https://api.bilibili.com/x'; 23 | url; 24 | 25 | numberFormat(value) { 26 | try { 27 | const param = {}; 28 | let k = 10000; 29 | const size = ['', '万', '亿', '万亿']; 30 | let i; 31 | if (value < k) { 32 | param.value = value; 33 | param.unit = ''; 34 | } else { 35 | i = Math.floor(Math.log(value) / Math.log(k)); 36 | param.value = ((value / Math.pow(k, i))).toFixed(2); 37 | param.unit = size[i]; 38 | } 39 | return `${param.value}${param.unit}`; 40 | } catch (e) { 41 | console.log(e); 42 | } 43 | } 44 | 45 | init = async () => { 46 | try { 47 | await this.getData(); 48 | await this.getRelationStat(); 49 | await this.getVideoList(); 50 | } catch (e) { 51 | console.log('❌错误信息:' + e); 52 | } 53 | }; 54 | 55 | getData = async () => { 56 | const url = `${this.baseUrl}/space/acc/info?mid=${this.inputValue}&jsonp=jsonp`; 57 | const response = await this.$request.get(url); 58 | if (response.code === 0) { 59 | this.ytInitialData = response.data; 60 | } 61 | }; 62 | 63 | getRelationStat = async () => { 64 | const url = `${this.baseUrl}/relation/stat?vmid=${this.inputValue}&jsonp=jsonp`; 65 | const response = await this.$request.get(url); 66 | if (response.code === 0) { 67 | this.ytInitialData = {...this.ytInitialData, relation: response.data}; 68 | } 69 | }; 70 | 71 | getVideoList = async () => { 72 | const url = `${this.baseUrl}/space/arc/search?mid=${this.inputValue}&pn=1&ps=25&index=1&jsonp=jsonp`; 73 | const response = await this.$request.get(url); 74 | if (response.code === 0) { 75 | this.ytInitialData = { 76 | ...this.ytInitialData, 77 | videos: response.data.list.vlist.map(item => ({ 78 | thumb: item.pic, 79 | title: item.title, 80 | view: `${this.numberFormat(item.play)}次`, 81 | url: item.bvid, 82 | })), 83 | }; 84 | } 85 | }; 86 | 87 | setAvatar = async (stack) => { 88 | stack.size = new Size(50, 50); 89 | stack.cornerRadius = 5; 90 | const {face} = this.ytInitialData; 91 | const imgLogo = await this.$request.get(face, 'IMG'); 92 | const imgLogoItem = stack.addImage(imgLogo); 93 | imgLogoItem.imageSize = new Size(50, 50); 94 | return stack; 95 | }; 96 | 97 | setTitleStack = (stack) => { 98 | const textFormatNumber = this.textFormat.title; 99 | textFormatNumber.color = this.backGroundColor; 100 | const {name} = this.ytInitialData; 101 | const title = name; 102 | textFormatNumber.size = 103 | title.length > 20 || this.widgetFamily === 'small' ? 16 : 20; 104 | const titleItem = this.provideText(title, stack, textFormatNumber); 105 | titleItem.lineLimit = 1; 106 | }; 107 | 108 | setPathStack = (stack) => { 109 | const textFormatNumber = this.textFormat.defaultText; 110 | textFormatNumber.color = this.backGroundColor; 111 | textFormatNumber.size = 12; 112 | const {relation} = this.ytInitialData; 113 | let simpleText = `关注数:${this.numberFormat(relation.follower)}`; 114 | const titleItem = this.provideText( 115 | simpleText, 116 | stack, 117 | textFormatNumber, 118 | ); 119 | titleItem.lineLimit = 1; 120 | titleItem.textOpacity = 0.9; 121 | }; 122 | 123 | setFooterCell = async (stack) => { 124 | const datas = this.getRandomArrayElements(this.ytInitialData.videos, 3); 125 | for (let i = 0; i < datas.length; i++) { 126 | if (i === 1) stack.addSpacer(); 127 | const video = datas[i]; 128 | const stackVideo = stack.addStack(); 129 | stackVideo.setPadding(10, 10, 10, 10); 130 | stackVideo.url = 'https://www.bilibili.com/video/' + video.url; 131 | stackVideo.backgroundColor = this.widgetColor; 132 | stackVideo.centerAlignContent(); 133 | stackVideo.layoutVertically(); 134 | const img = await this.$request.get(video.thumb, 'IMG'); 135 | stackVideo.backgroundImage = await this.shadowImage(img, '#000', 0.3); 136 | const title = {...this.textFormat.defaultText}; 137 | title.size = 8; 138 | title.color = new Color('#fff'); 139 | this.provideText(video.title, stackVideo, title); 140 | stackVideo.addSpacer(); 141 | title.color = new Color('#fff', 0.7); 142 | this.provideText(video.view, stackVideo, title); 143 | stackVideo.size = new Size(87, 56); 144 | stackVideo.cornerRadius = 4; 145 | if (i === 1) stack.addSpacer(); 146 | } 147 | }; 148 | 149 | renderSmall = async (w) => { 150 | return w; 151 | }; 152 | 153 | renderMedium = async (w) => { 154 | const stackBody = w.addStack(); 155 | stackBody.url = `https://space.bilibili.com/${this.inputValue}/`; 156 | stackBody.layoutVertically(); 157 | const stackHeader = stackBody.addStack(); 158 | stackHeader.setPadding(5, 10, 5, 10); 159 | stackHeader.cornerRadius = 10; 160 | stackHeader.backgroundColor = this.widgetColor; 161 | 162 | stackHeader.centerAlignContent(); 163 | const stackLeft = stackHeader.addStack(); 164 | await this.setAvatar(stackLeft); 165 | stackHeader.addSpacer(20); 166 | 167 | const stackRight = stackHeader.addStack(); 168 | stackRight.layoutVertically(); 169 | this.setTitleStack(stackRight); 170 | stackRight.addSpacer(5); 171 | this.setPathStack(stackRight); 172 | stackHeader.addSpacer(); 173 | stackBody.addSpacer(); 174 | 175 | const stackFooter = stackBody.addStack(); 176 | stackFooter.setPadding(10, 0, 10, 0); 177 | stackFooter.cornerRadius = 10; 178 | stackFooter.backgroundColor = this.widgetColor; 179 | stackFooter.addSpacer(); 180 | await this.setFooterCell(stackFooter); 181 | stackFooter.addSpacer(); 182 | return w; 183 | }; 184 | 185 | renderLarge = async (w) => { 186 | return w; 187 | }; 188 | 189 | Run() { 190 | if (config.runsInApp) { 191 | this.registerAction('基础设置', this.setWidgetConfig); 192 | } 193 | } 194 | 195 | /** 196 | * 渲染函数,函数名固定 197 | * 可以根据 this.widgetFamily 来判断小组件尺寸,以返回不同大小的内容 198 | */ 199 | async render() { 200 | await this.init(); 201 | const widget = new ListWidget(); 202 | await this.getWidgetBackgroundImage(widget); 203 | if (this.widgetFamily === 'medium') { 204 | return await this.renderMedium(widget); 205 | } else if (this.widgetFamily === 'large') { 206 | return await this.renderLarge(widget); 207 | } else { 208 | return await this.renderSmall(widget); 209 | } 210 | } 211 | } 212 | 213 | // @组件代码结束 214 | // await Runing(Widget, "", true); // 正式环境 215 | await Runing(Widget, args.widgetParameter, false); //远程开发环境 216 | -------------------------------------------------------------------------------- /Scripts/BiliBiliWatch.js: -------------------------------------------------------------------------------- 1 | // Variables used by Scriptable. 2 | // These must be at the very top of the file. Do not edit. 3 | // icon-color: gray; icon-glyph: chalkboard; 4 | 5 | // 添加require,是为了vscode中可以正确引入包,以获得自动补全等功能 6 | if (typeof require === 'undefined') require = importModule; 7 | const { DmYY, Runing } = require('./DmYY'); 8 | 9 | // @组件代码开始 10 | class Widget extends DmYY { 11 | constructor(arg) { 12 | super(arg); 13 | this.name = '哔哩哔哩关注'; 14 | this.en = 'BiliBiliWatch'; 15 | this.logo = 16 | 'https://raw.githubusercontent.com/Orz-3/mini/master/Color/bilibili.png'; 17 | this.Run(module.filename); 18 | } 19 | 20 | cookie = ''; 21 | dataSource = []; 22 | 23 | init = async () => { 24 | try { 25 | await this.getDramaList(); 26 | } catch (e) { 27 | console.log(e); 28 | } 29 | }; 30 | 31 | getDramaList = async () => { 32 | const url = `https://api.vc.bilibili.com/dynamic_svr/v1/dynamic_svr/dynamic_new?type_list=268435455`; 33 | const method = `GET`; 34 | const headers = { 35 | Cookie: this.cookie, 36 | 'User-Agent': `bili-universal/10320 CFNetwork/1206 Darwin/20.1.0 os/ios model/iPhone XR mobi_app/iphone build/10320 osVer/14.2 network/2 channel/AppStore`, 37 | }; 38 | const response = await this.$request.get(url, { 39 | method, 40 | headers, 41 | }); 42 | try { 43 | const { code, data } = response; 44 | if (code === 0 && data.cards.length > 0) { 45 | let dataSource = data.cards; 46 | dataSource.forEach((item) => { 47 | const card = JSON.parse(item.card); 48 | let temp = false; 49 | if (card.apiSeasonInfo) { 50 | temp = {}; 51 | temp.title = card.apiSeasonInfo.title; 52 | temp.url = card.url; 53 | temp.reply = card.reply_count; 54 | temp.play = card.play_count; 55 | temp.img = card.cover; 56 | temp.desc = card.new_desc; 57 | temp.timestamp = item.desc.timestamp; 58 | } else if (card.videos === 1) { 59 | temp = {}; 60 | temp.title = card.title; 61 | temp.url = card.jump_url; 62 | temp.reply = card.stat.reply; 63 | temp.play = card.stat.view; 64 | temp.desc = card.desc; 65 | temp.img = card.pic; 66 | temp.timestamp = item.desc.timestamp; 67 | } 68 | if (temp) this.dataSource.push(temp); 69 | }); 70 | return this.dataSource; 71 | } else { 72 | throw 'cookie 失效,请重新获取'; 73 | } 74 | } catch (e) { 75 | console.log('❌错误信息:' + e); 76 | return false; 77 | } 78 | }; 79 | 80 | setListCell = async (cell, data) => { 81 | const { title, url, reply, play, desc, img, timestamp } = data; 82 | let body = cell.addStack(); 83 | body.url = url; 84 | if (this.widgetFamily !== 'small') { 85 | const imageView = body.addStack(); 86 | imageView.size = new Size(43, 43); 87 | imageView.cornerRadius = 5; 88 | const image = await this.$request.get(img, 'IMG'); 89 | imageView.backgroundImage = image; 90 | body.addSpacer(10); 91 | } 92 | 93 | const textView = body.addStack(); 94 | textView.layoutVertically(); 95 | 96 | const descText = textView.addText(`${title} ${desc}`); 97 | descText.font = Font.boldSystemFont(14); 98 | descText.textColor = this.widgetColor; 99 | descText.lineLimit = 1; 100 | 101 | textView.addSpacer(3); 102 | 103 | const date = new Date(); 104 | date.setTime(timestamp * 1000); //注意,这行是关键代码 105 | let month = date.getMonth() + 1; 106 | let day = date.getDate(); 107 | 108 | const timerText1 = textView.addText(`${month}-${day} 更新了`); 109 | timerText1.font = Font.lightSystemFont(10); 110 | timerText1.textColor = this.widgetColor; 111 | timerText1.lineLimit = 1; 112 | 113 | const descView = textView.addStack(); 114 | 115 | const icon1 = descView.addText('浏览:'); 116 | icon1.font = Font.lightSystemFont(10); 117 | icon1.textColor = this.widgetColor; 118 | descView.addSpacer(3); 119 | const timerText = descView.addText(`${play}`); 120 | timerText.font = Font.lightSystemFont(10); 121 | timerText.textColor = this.widgetColor; 122 | descView.addSpacer(5); 123 | 124 | const icon2 = descView.addText('评论:'); 125 | icon2.font = Font.lightSystemFont(10); 126 | icon2.textColor = this.widgetColor; 127 | 128 | descView.addSpacer(3); 129 | const timerText2 = descView.addText(`${reply}`); 130 | timerText2.font = Font.lightSystemFont(10); 131 | timerText2.textColor = this.widgetColor; 132 | return cell; 133 | }; 134 | 135 | setWidget = async (body, size) => { 136 | const container = body.addStack(); 137 | container.layoutVertically(); 138 | const dataSource = this.getRandomArrayElements(this.dataSource, size); 139 | for (let index = 0; index < dataSource.length; index++) { 140 | const data = dataSource[index]; 141 | let listItem = container.addStack(); 142 | await this.setListCell(listItem, data); 143 | container.addSpacer(10); 144 | } 145 | body.addSpacer(); 146 | return body; 147 | }; 148 | 149 | renderSmall = async (w) => { 150 | return await this.setWidget(w, 2); 151 | }; 152 | 153 | renderLarge = async (w) => { 154 | return await this.setWidget(w, 6); 155 | }; 156 | 157 | renderMedium = async (w) => { 158 | return await this.setWidget(w, 2); 159 | }; 160 | 161 | /** 162 | * 渲染函数,函数名固定 163 | * 可以根据 this.widgetFamily 来判断小组件尺寸,以返回不同大小的内容 164 | */ 165 | async render() { 166 | await this.init(); 167 | const widget = new ListWidget(); 168 | await this.getWidgetBackgroundImage(widget); 169 | const header = widget.addStack(); 170 | if (this.widgetFamily !== 'small') { 171 | await this.renderJDHeader(header); 172 | } else { 173 | await this.renderHeader(header, this.logo, this.name, this.widgetColor); 174 | } 175 | widget.addSpacer(10); 176 | if (this.widgetFamily === 'medium') { 177 | return await this.renderMedium(widget); 178 | } else if (this.widgetFamily === 'large') { 179 | return await this.renderLarge(widget); 180 | } else { 181 | return await this.renderSmall(widget); 182 | } 183 | } 184 | 185 | renderJDHeader = async (header) => { 186 | header.centerAlignContent(); 187 | await this.renderHeader(header, this.logo, this.name, this.widgetColor); 188 | header.addSpacer(); 189 | const headerMore = header.addStack(); 190 | headerMore.url = ''; 191 | headerMore.setPadding(1, 10, 1, 10); 192 | headerMore.cornerRadius = 10; 193 | headerMore.backgroundColor = new Color('#fff', 0.5); 194 | const textItem = headerMore.addText('个人中心'); 195 | textItem.font = Font.boldSystemFont(12); 196 | textItem.textColor = this.widgetColor; 197 | textItem.lineLimit = 1; 198 | textItem.rightAlignText(); 199 | return header; 200 | }; 201 | 202 | Run = (filename) => { 203 | if (config.runsInApp) { 204 | this.registerAction('基础设置', this.setWidgetConfig); 205 | this.registerAction('账号设置', this.inputCk); 206 | this.registerAction('代理缓存', this._loadCk); 207 | } 208 | let _md5 = this.md5(filename + this.en); 209 | this.CACHE_KEY = `cache_${_md5}`; 210 | try { 211 | this.cookie = this.settings[this.en]; 212 | if (!this.cookie) { 213 | throw 'CK 获取失败'; 214 | } 215 | return true; 216 | } catch (e) { 217 | this.notify('错误提示', e); 218 | return false; 219 | } 220 | }; 221 | 222 | _loadCk = async () => { 223 | try { 224 | const cookie = await this.getCache('@bilibili.cookie'); 225 | if (cookie) { 226 | this.cookie = cookie; 227 | this.settings[this.en] = this.cookie; 228 | this.saveSettings(); 229 | } else { 230 | throw 'ck 获取失败'; 231 | } 232 | return true; 233 | } catch (e) { 234 | console.log(e); 235 | this.cookie = ''; 236 | return false; 237 | } 238 | }; 239 | 240 | async inputCk() { 241 | const a = new Alert(); 242 | a.title = '账号设置'; 243 | a.message = '手动输入 Ck'; 244 | a.addTextField('Cookie', this.cookie); 245 | a.addAction('确定'); 246 | a.addCancelAction('取消'); 247 | const id = await a.presentAlert(); 248 | if (id === -1) return; 249 | this.cookie = a.textFieldValue(0); 250 | // 保存到本地 251 | this.settings[this.en] = this.cookie; 252 | this.saveSettings(); 253 | } 254 | } 255 | 256 | // @组件代码结束 257 | // await Runing(Widget, "", false); // 正式环境 258 | await Runing(Widget, '', false); //远程开发环境 259 | -------------------------------------------------------------------------------- /Scripts/CarWidget.js: -------------------------------------------------------------------------------- 1 | // Variables used by Scriptable. 2 | // These must be at the very top of the file. Do not edit. 3 | // icon-color: deep-gray; icon-glyph: car; 4 | 5 | // 添加require,是为了vscode中可以正确引入包,以获得自动补全等功能 6 | if (typeof require === 'undefined') require = importModule; 7 | const { DmYY, Runing } = require('./DmYY'); 8 | 9 | // @组件代码开始 10 | class Widget extends DmYY { 11 | constructor(arg) { 12 | super(arg); 13 | if (config.runsInApp) { 14 | this.registerAction({ 15 | icon: { name: 'paperplane', color: '#722ed1' }, 16 | type: 'input', 17 | title: '油价设置', 18 | desc: '89|92|95|0', 19 | val: 'oilNumber', 20 | }); 21 | 22 | this.registerAction({ 23 | icon: { name: 'car', color: '#f5222d' }, 24 | type: 'input', 25 | title: '依赖插件', 26 | desc: '汽车的依赖插件例如 Ftms', 27 | val: 'filePath', 28 | }); 29 | 30 | this.registerAction({ 31 | icon: { name: 'plus.viewfinder', color: '#fa8c16' }, 32 | type: 'input', 33 | title: '缩放比例', 34 | desc: '比例越大进度条越长', 35 | placeholder: '取值 0~1', 36 | val: 'scale', 37 | }); 38 | } 39 | 40 | config.runsInApp && this.registerAction('基础设置', this.setWidgetConfig); 41 | this.cacheName = this.md5(`dataSouce_${this.en}`); 42 | const filePath = this.settings.filePath || 'Ftms'; 43 | const carModule = require(`./${filePath}`); 44 | const carService = new carModule(this); 45 | this.scale = parseFloat(this.settings.scale) || 1; // 柱状图比例高度,值越大,柱状范围越广 46 | this.init = carService.init; 47 | this.name = carService.name; 48 | this.logo = carService.logo; 49 | this.viewColor = Color.dynamic( 50 | new Color('#d9d9d9', parseFloat(this.settings.lightOpacity || 1)), 51 | new Color('#8c8c8c', parseFloat(this.settings.darkOpacity || 1)) 52 | ); 53 | } 54 | 55 | widgetHeight = 145; 56 | 57 | serveInfo = { 58 | carNumber: '', 59 | }; 60 | 61 | dataSource = { 62 | remoteInfo: { 63 | datatime: '', 64 | list: [], 65 | userId: '', 66 | carNumber: '', 67 | }, 68 | monitorInfo: { 69 | km: '0', 70 | oilRate: '0', 71 | oilWaste: '0', 72 | oilWasteText: '', 73 | }, 74 | safeText: '', 75 | oilPriceText: '', 76 | oilZDE: 0, 77 | }; 78 | 79 | createProgressBar( 80 | soFar, 81 | total = 100, 82 | width = 400, 83 | height = 40, 84 | showPercentage = false 85 | ) { 86 | const context = new DrawContext(); 87 | context.size = new Size(width, height); 88 | context.opaque = false; 89 | context.respectScreenScale = true; 90 | 91 | // bar background 92 | context.setFillColor(new Color('#48484b')); 93 | const bgPath = new Path(); 94 | bgPath.addRoundedRect( 95 | new Rect(0, 0, width, height), 96 | height / 2, 97 | height / 2 - 1 98 | ); 99 | context.addPath(bgPath); 100 | context.fillPath(); 101 | 102 | // bar foreground 103 | context.setFillColor(new Color('#e8e8e8')); 104 | const fgPath = new Path(); 105 | fgPath.addRoundedRect( 106 | new Rect(0, 0, (width * soFar) / total, height), 107 | height / 2, 108 | height / 2 - 1 109 | ); 110 | context.addPath(fgPath); 111 | context.fillPath(); 112 | 113 | if (showPercentage) { 114 | const percentage = ((soFar / total) * 100).toFixed(2); 115 | let xPos = (width * soFar) / total + 5; 116 | // if over 70%, show in foreground area 117 | // to ensure that it doesn't overflow the display 118 | if (percentage > 70) { 119 | xPos = (width * soFar) / total - 55; 120 | } 121 | 122 | context.setFont(Font.semiboldRoundedSystemFont(14)); 123 | context.setTextColor(primaryTextColor); 124 | context.drawText(`${percentage}%`, new Point(xPos, height / 14)); 125 | } 126 | 127 | return context.getImage(); 128 | } 129 | 130 | renderBorder = (stack) => { 131 | stack.borderWidth = 1; 132 | }; 133 | 134 | renderImage = async (uri) => { 135 | return this.$request.get(uri, 'IMG'); 136 | }; 137 | 138 | notSupport(w) { 139 | const stack = w.addStack(); 140 | stack.addText('暂不支持'); 141 | return w; 142 | } 143 | 144 | renderSmall = async (w) => { 145 | w.addSpacer(); 146 | 147 | const stack = w.addStack(); 148 | stack.layoutVertically(); 149 | const headerStack = stack.addStack(); 150 | headerStack.centerAlignContent(); 151 | headerStack.addSpacer(10); 152 | const gasImg = SFSymbol.named('fuelpump').image; 153 | 154 | const gasIcon = headerStack.addImage(gasImg); 155 | gasIcon.imageSize = new Size(16, 16); 156 | gasIcon.tintColor = this.widgetColor; 157 | headerStack.addSpacer(5); 158 | 159 | const oilRateStackText = headerStack.addText( 160 | `${this.dataSource.monitorInfo.oilRate}%` 161 | ); 162 | oilRateStackText.textColor = this.widgetColor; 163 | oilRateStackText.font = Font.boldSystemFont(14); 164 | 165 | headerStack.addSpacer(); 166 | const logImg = await this.renderImage(this.logo); 167 | const logImgStack = headerStack.addImage(logImg); 168 | logImgStack.imageSize = new Size(20, 20); 169 | headerStack.addSpacer(10); 170 | 171 | const bodyStack = stack.addStack(); 172 | bodyStack.centerAlignContent(); 173 | bodyStack.addSpacer(); 174 | const progressImg = this.createProgressBar( 175 | this.dataSource.monitorInfo.oilRate 176 | ); 177 | const progressBar = bodyStack.addImage(progressImg); 178 | progressBar.imageSize = new Size(this.widgetHeight * this.scale, 28); 179 | bodyStack.addSpacer(); 180 | 181 | stack.addSpacer(); 182 | 183 | const oilWasteStack = stack.addStack(); 184 | oilWasteStack.centerAlignContent(); 185 | oilWasteStack.addSpacer(); 186 | const oilWasteStackText = oilWasteStack.addText( 187 | this.dataSource.monitorInfo.oilWasteText 188 | ); 189 | oilWasteStackText.textColor = this.widgetColor; 190 | oilWasteStackText.font = Font.boldSystemFont(10); 191 | oilWasteStack.addSpacer(5); 192 | const oilPriceStackText = oilWasteStack.addText( 193 | this.dataSource.oilPriceText 194 | ); 195 | oilPriceStackText.textColor = this.widgetColor; 196 | oilPriceStackText.font = Font.boldSystemFont(10); 197 | oilWasteStack.addSpacer(2); 198 | const oilStatus = this.dataSource.oilZDE > 0; 199 | const oilZdeImage = SFSymbol.named( 200 | oilStatus ? 'arrow.up' : 'arrow.down' 201 | ).image; 202 | 203 | const oilZdeWidgetImg = oilWasteStack.addImage(oilZdeImage); 204 | oilZdeWidgetImg.tintColor = new Color(oilStatus ? '#f5222d' : '#a0d911'); 205 | oilZdeWidgetImg.imageSize = new Size(10, 10); 206 | 207 | oilWasteStack.addSpacer(); 208 | 209 | const kilometerStack = stack.addStack(); 210 | 211 | kilometerStack.centerAlignContent(); 212 | kilometerStack.addSpacer(); 213 | const panoImg = SFSymbol.named('speedometer').image; 214 | 215 | const panoImgStack = kilometerStack.addStack(); 216 | panoImgStack.setPadding(5, 0, 0, 0); 217 | const panoStack = panoImgStack.addImage(panoImg); 218 | panoStack.tintColor = this.widgetColor; 219 | panoStack.imageSize = new Size(20, 20); 220 | kilometerStack.addSpacer(5); 221 | 222 | const oilWasteText = kilometerStack.addText(this.dataSource.monitorInfo.km); 223 | oilWasteText.font = Font.boldSystemFont(28); 224 | oilWasteText.textColor = this.widgetColor; 225 | kilometerStack.addSpacer(5); 226 | const unitStack = kilometerStack.addStack(); 227 | unitStack.setPadding(5, 0, 0, 0); 228 | const oilWasteUnit = unitStack.addText('km'); 229 | oilWasteUnit.font = Font.boldSystemFont(14); 230 | oilWasteUnit.textColor = this.widgetColor; 231 | kilometerStack.addSpacer(); 232 | 233 | stack.addSpacer(); 234 | 235 | const btBodyStack = stack.addStack(); 236 | btBodyStack.addSpacer(); 237 | const bottomStack = btBodyStack.addStack(); 238 | bottomStack.setPadding(10, 0, 10, 0); 239 | bottomStack.centerAlignContent(); 240 | bottomStack.addSpacer(); 241 | bottomStack.cornerRadius = 15; 242 | bottomStack.backgroundColor = this.viewColor; 243 | const dataTime = this.dataSource.remoteInfo.datatime; 244 | const countKmText = bottomStack.addText(`上传:${dataTime || '-'}`); 245 | countKmText.textColor = this.widgetColor; 246 | countKmText.font = Font.boldSystemFont(12); 247 | countKmText.centerAlignText(); 248 | bottomStack.addSpacer(); 249 | w.addSpacer(); 250 | return w; 251 | }; 252 | 253 | renderLarge = async (w) => { 254 | return this.renderSmall(w); 255 | }; 256 | 257 | renderMedium = async (w) => { 258 | const containerStack = w.addStack(); 259 | containerStack.centerAlignContent(); 260 | const carStack = containerStack.addStack(); 261 | carStack.addSpacer(); 262 | carStack.backgroundColor = this.viewColor; 263 | 264 | carStack.layoutVertically(); 265 | 266 | carStack.centerAlignContent(); 267 | carStack.size = new Size(this.widgetHeight, this.widgetHeight); 268 | carStack.cornerRadius = 20; 269 | const carImg = await this.renderImage(this.serveInfo.picUrl); 270 | 271 | const carImgStack = carStack.addStack(); 272 | const carResStack = carImgStack.addImage(carImg); 273 | carResStack.imageSize = new Size(137.5, 70); 274 | 275 | carStack.addSpacer(); 276 | 277 | const carNumberStack = carStack.addStack(); 278 | carNumberStack.addSpacer(); 279 | carNumberStack.centerAlignContent(); 280 | const carNumberText = carNumberStack.addText(this.serveInfo.carNumber); 281 | carNumberText.font = Font.boldSystemFont(24); 282 | carNumberText.textColor = this.widgetColor; 283 | carNumberText.centerAlignText(); 284 | carNumberStack.addSpacer(); 285 | 286 | carStack.addSpacer(); 287 | 288 | const carSafeStack = carStack.addStack(); 289 | carSafeStack.addSpacer(); 290 | carSafeStack.centerAlignContent(); 291 | 292 | let safeIconImg; 293 | if (this.dataSource.safeText) { 294 | safeIconImg = carSafeStack.addImage(SFSymbol.named('lock.open').image); 295 | } else { 296 | safeIconImg = carSafeStack.addImage(SFSymbol.named('lock').image); 297 | } 298 | 299 | carSafeStack.addSpacer(5); 300 | const statusText = carSafeStack.addText( 301 | this.dataSource.safeText || '已上锁' 302 | ); 303 | statusText.centerAlignText(); 304 | statusText.font = Font.systemFont(12); 305 | statusText.textColor = this.dataSource.safeText 306 | ? new Color('#f5222d') 307 | : this.widgetColor; 308 | 309 | safeIconImg.tintColor = statusText.textColor; 310 | safeIconImg.imageSize = new Size(10, 14); 311 | 312 | carSafeStack.addSpacer(); 313 | 314 | carStack.addSpacer(); 315 | 316 | containerStack.addSpacer(); 317 | const rightStack = containerStack.addStack(); 318 | rightStack.layoutVertically(); 319 | await this.renderSmall(rightStack); 320 | 321 | return w; 322 | }; 323 | 324 | /** 325 | * 渲染函数,函数名固定 326 | * 可以根据 this.widgetFamily 来判断小组件尺寸,以返回不同大小的内容 327 | */ 328 | async render() { 329 | await this.init(); 330 | const widget = new ListWidget(); 331 | widget.setPadding(10, 10, 10, 10); 332 | await this.getWidgetBackgroundImage(widget); 333 | if (this.widgetFamily === 'medium') { 334 | return await this.renderMedium(widget); 335 | } else { 336 | return await this.notSupport(widget); 337 | } 338 | } 339 | } 340 | 341 | // @组件代码结束 342 | await Runing(Widget, '', false); //远程开发环境 343 | -------------------------------------------------------------------------------- /Scripts/ChinaUnicom.js: -------------------------------------------------------------------------------- 1 | // Variables used by Scriptable. 2 | // These must be at the very top of the file. Do not edit. 3 | // icon-color: pink; icon-glyph: paper-plane; 4 | 5 | // 添加require,是为了vscode中可以正确引入包,以获得自动补全等功能 6 | if (typeof require === 'undefined') require = importModule; 7 | const {DmYY, Runing} = require('./DmYY'); 8 | 9 | // @组件代码开始 10 | class Widget extends DmYY { 11 | constructor(arg) { 12 | super(arg); 13 | this.name = '中国联通'; 14 | this.en = 'ChinaUnicom'; 15 | this.Run(); 16 | } 17 | 18 | loginheader = {}; 19 | 20 | fgCircleColor = Color.dynamic(new Color('#dddef3'), new Color('#fff')); 21 | percentColor = this.widgetColor; 22 | textColor1 = Color.dynamic(new Color('#333'), new Color('#fff')); 23 | textColor2 = this.widgetColor; 24 | 25 | circleColor1 = new Color('#ffbb73'); 26 | circleColor2 = new Color('#ff0029'); 27 | circleColor3 = new Color('#00b800'); 28 | circleColor4 = new Color('#8376f9'); 29 | iconColor = new Color('#827af1'); 30 | 31 | format = (str) => { 32 | return parseInt(str) >= 10 ? str : `0${str}`; 33 | }; 34 | 35 | date = new Date(); 36 | arrUpdateTime = [ 37 | this.format(this.date.getMonth() + 1), 38 | this.format(this.date.getDate()), 39 | this.format(this.date.getHours()), 40 | this.format(this.date.getMinutes()), 41 | ]; 42 | 43 | maxFee = 100; 44 | // percent 的计算方式,剩余/总量 * 100 = 百分比| 百分比 * 3.6 ,为显示进度。 45 | phoneBill = { 46 | percent: 0, 47 | label: '话费剩余', 48 | count: 0, 49 | unit: '元', 50 | icon: 'yensign.circle', 51 | circleColor: this.circleColor1, 52 | }; 53 | 54 | flow = { 55 | percent: 0, 56 | label: '已用流量', 57 | count: 0, 58 | unit: 'M', 59 | icon: 'waveform.path.badge.minus', 60 | circleColor: this.circleColor2, 61 | }; 62 | 63 | voice = { 64 | percent: 0, 65 | label: '语音剩余', 66 | count: 0, 67 | unit: '分钟', 68 | icon: 'mic', 69 | circleColor: this.circleColor3, 70 | }; 71 | 72 | updateTime = { 73 | percent: 0, 74 | label: '联通更新', 75 | count: `${this.arrUpdateTime[2]}:${this.arrUpdateTime[3]}`, 76 | unit: '', 77 | urlIcon: 'https://raw.githubusercontent.com/Orz-3/mini/master/10010.png', 78 | circleColor: this.circleColor4, 79 | }; 80 | 81 | canvSize = 100; 82 | canvWidth = 5; // circle thickness 83 | canvRadius = 100; // circle radius 84 | dayRadiusOffset = 60; 85 | canvTextSize = 40; 86 | 87 | init = async () => { 88 | try { 89 | const nowHours = this.date.getHours(); 90 | const updateHours = nowHours > 12 ? 24 : 12; 91 | this.updateTime.percent = Math.floor((nowHours / updateHours) * 100); 92 | await this.getinfo(); 93 | } catch (e) { 94 | console.log(e); 95 | } 96 | }; 97 | 98 | async getinfo() { 99 | try { 100 | const telNum = this.gettel(); 101 | const url = { 102 | url: `https://m.client.10010.com/mobileService/home/queryUserInfoSeven.htm?version=iphone_c@7.0403&desmobiel=${telNum}&showType=3`, 103 | headers: { 104 | Cookie: this.loginheader.Cookie, 105 | }, 106 | }; 107 | const signinfo = await this.$request.get(url); 108 | if (signinfo.code === 'Y') { 109 | console.log('✅获取信息成功'); 110 | console.log(signinfo.data); 111 | signinfo.data.dataList.forEach((item) => { 112 | let percent = 0; 113 | if (item.usedTitle.includes('剩余')) 114 | percent = item.usedTitle.replace('剩余', '').replace('%'); 115 | if (item.usedTitle.includes('已用')) 116 | percent = ( 117 | 100 - parseFloat(item.usedTitle.replace('已用', '').replace('%')) 118 | ).toFixed(2); 119 | 120 | if (item.type === 'flow') { 121 | this.flow.count = item.number; 122 | this.flow.unit = item.unit; 123 | this.flow.percent = percent; 124 | this.flow.label = item.remainTitle; 125 | } 126 | if (item.type === 'fee') { 127 | this.phoneBill.count = item.number; 128 | this.phoneBill.unit = item.unit; 129 | this.phoneBill.percent = 130 | Math.floor((item.number / this.maxFee).toFixed(2) * 100); 131 | this.phoneBill.label = item.remainTitle; 132 | } 133 | if (item.type === 'voice') { 134 | this.voice.count = item.number; 135 | this.voice.unit = item.unit; 136 | this.voice.percent = percent; 137 | this.voice.label = item.remainTitle; 138 | } 139 | }); 140 | } else { 141 | throw 'cookie错误'; 142 | } 143 | } catch (e) { 144 | console.log('❌获取信息失败:' + e); 145 | } 146 | } 147 | 148 | gettel() { 149 | const reqCookie = this.loginheader.Cookie; 150 | let tel = ''; 151 | if (tel === '' && reqCookie.indexOf(`u_account=`) >= 0) 152 | tel = reqCookie.match(/u_account=(.*?);/)[1]; 153 | if (tel === '' && reqCookie.indexOf(`c_mobile=`) >= 0) { 154 | tel = reqCookie.match(/c_mobile=(.*?);/)[1]; 155 | } 156 | return tel; 157 | } 158 | 159 | makeCanvas() { 160 | const canvas = new DrawContext(); 161 | canvas.opaque = false; 162 | canvas.respectScreenScale = true; 163 | canvas.size = new Size(this.canvSize, this.canvSize); 164 | return canvas; 165 | } 166 | 167 | makeCircle(canvas, radiusOffset, degree, color) { 168 | let ctr = new Point(this.canvSize / 2, this.canvSize / 2); 169 | // Outer circle 170 | const bgx = ctr.x - (this.canvRadius - radiusOffset); 171 | const bgy = ctr.y - (this.canvRadius - radiusOffset); 172 | const bgd = 2 * (this.canvRadius - radiusOffset); 173 | const bgr = new Rect(bgx, bgy, bgd, bgd); 174 | canvas.setStrokeColor(this.fgCircleColor); 175 | canvas.setLineWidth(2); 176 | canvas.strokeEllipse(bgr); 177 | // Inner circle 178 | canvas.setFillColor(color); 179 | for (let t = 0; t < degree; t++) { 180 | const rect_x = 181 | ctr.x + 182 | (this.canvRadius - radiusOffset) * this.sinDeg(t) - 183 | this.canvWidth / 2; 184 | const rect_y = 185 | ctr.y - 186 | (this.canvRadius - radiusOffset) * this.cosDeg(t) - 187 | this.canvWidth / 2; 188 | const rect_r = new Rect(rect_x, rect_y, this.canvWidth, this.canvWidth); 189 | canvas.fillEllipse(rect_r); 190 | } 191 | } 192 | 193 | drawText(txt, canvas, txtOffset, fontSize) { 194 | const txtRect = new Rect( 195 | this.canvTextSize / 2 - 20, 196 | txtOffset - this.canvTextSize / 2, 197 | this.canvSize, 198 | this.canvTextSize, 199 | ); 200 | canvas.setTextColor(this.percentColor); 201 | canvas.setFont(Font.boldSystemFont(fontSize)); 202 | canvas.setTextAlignedCenter(); 203 | canvas.drawTextInRect(`${txt}`, txtRect); 204 | } 205 | 206 | drawPointText(txt, canvas, txtPoint, fontSize) { 207 | canvas.setTextColor(this.percentColor); 208 | canvas.setFont(Font.boldSystemFont(fontSize)); 209 | canvas.drawText(txt, txtPoint); 210 | } 211 | 212 | sinDeg(deg) { 213 | return Math.sin((deg * Math.PI) / 180); 214 | } 215 | 216 | cosDeg(deg) { 217 | return Math.cos((deg * Math.PI) / 180); 218 | } 219 | 220 | setCircleText = (stack, data) => { 221 | const stackCircle = stack.addStack(); 222 | const canvas = this.makeCanvas(); 223 | stackCircle.size = new Size(70, 70); 224 | this.makeCircle( 225 | canvas, 226 | this.dayRadiusOffset, 227 | data.percent * 3.6, 228 | data.circleColor, 229 | ); 230 | 231 | this.drawText(data.percent, canvas, 75, 18); 232 | this.drawPointText(`%`, canvas, new Point(65, 50), 14); 233 | stackCircle.backgroundImage = canvas.getImage(); 234 | 235 | stackCircle.setPadding(20, 0, 0, 0); 236 | stackCircle.addSpacer(); 237 | const icon = data.urlIcon 238 | ? {image: data.icon} 239 | : SFSymbol.named(data.icon); 240 | const imageIcon = stackCircle.addImage(icon.image); 241 | imageIcon.tintColor = this.iconColor; 242 | imageIcon.imageSize = new Size(15, 15); 243 | // canvas.drawImageInRect(icon.image, new Rect(110, 80, 60, 60)); 244 | stackCircle.addSpacer(); 245 | 246 | stack.addSpacer(5); 247 | const stackDesc = stack.addStack(); 248 | stackDesc.size = new Size(70, 60); 249 | stackDesc.centerAlignContent(); 250 | stackDesc.layoutVertically(); 251 | stackDesc.addSpacer(10); 252 | const textLabel = this.textFormat.defaultText; 253 | textLabel.size = 12; 254 | textLabel.color = this.textColor2; 255 | this.provideText(data.label, stackDesc, textLabel); 256 | stackDesc.addSpacer(10); 257 | 258 | const stackDescFooter = stackDesc.addStack(); 259 | stackDescFooter.centerAlignContent(); 260 | const textCount = this.textFormat.title; 261 | textCount.size = 16; 262 | textCount.color = this.textColor1; 263 | this.provideText(`${data.count}`, stackDescFooter, textCount); 264 | stackDescFooter.addSpacer(2); 265 | this.provideText(data.unit, stackDescFooter, textLabel); 266 | }; 267 | 268 | renderSmall = async (w) => { 269 | w.setPadding(5, 5, 5, 5); 270 | const stackBody = w.addStack(); 271 | stackBody.layoutVertically(); 272 | const stackTop = stackBody.addStack(); 273 | this.setCircleText(stackTop, this.phoneBill); 274 | const stackBottom = stackBody.addStack(); 275 | this.setCircleText(stackBottom, this.flow); 276 | 277 | const stackFooter = stackBody.addStack(); 278 | stackFooter.addSpacer(); 279 | const text = this.textFormat.defaultText; 280 | text.color = new Color('#aaa'); 281 | this.provideText( 282 | `联通更新:${this.arrUpdateTime[2]}:${this.arrUpdateTime[3]}`, 283 | stackFooter, 284 | text, 285 | ); 286 | stackFooter.addSpacer(); 287 | return w; 288 | }; 289 | 290 | renderMedium = async (w) => { 291 | const stackBody = w.addStack(); 292 | stackBody.layoutVertically(); 293 | const stackTop = stackBody.addStack(); 294 | this.setCircleText(stackTop, this.phoneBill); 295 | this.setCircleText(stackTop, this.flow); 296 | const stackBottom = stackBody.addStack(); 297 | this.setCircleText(stackBottom, this.voice); 298 | this.updateTime.icon = await this.$request.get( 299 | this.updateTime.urlIcon, 300 | 'IMG', 301 | ); 302 | this.setCircleText(stackBottom, this.updateTime); 303 | return w; 304 | }; 305 | 306 | renderLarge = async (w) => { 307 | return await this.renderMedium(w); 308 | }; 309 | 310 | Run() { 311 | if (config.runsInApp) { 312 | this.registerAction('费用进度', async () => { 313 | await this.setAlertInput(`${this.name}`, '预计当月费用使用值', { 314 | maxFee: '默认 100 元', 315 | }); 316 | }); 317 | const widgetInitConfig = { 318 | loginheader: 'chavy_tokenheader_10010', 319 | }; 320 | this.registerAction('颜色配置', async () => { 321 | await this.setAlertInput( 322 | `${this.name}颜色配置`, 323 | '进度条颜色|底圈颜色\n图标颜色|比值颜色|值颜色', 324 | { 325 | step1: '进度颜色 1', 326 | step2: '进度颜色 2', 327 | step3: '进度颜色 3', 328 | step4: '进度颜色 4', 329 | inner: '底圈颜色', 330 | icon: '图标颜色', 331 | percent: '比值颜色', 332 | value: '值颜色', 333 | }, 334 | ); 335 | }); 336 | this.registerAction('账号设置', async () => { 337 | await this.setAlertInput( 338 | `${this.name}账号`, 339 | '读取 BoxJS 缓存信息', 340 | widgetInitConfig, 341 | ); 342 | }); 343 | this.registerAction('代理缓存', async () => { 344 | await this.setCacheBoxJSData(widgetInitConfig); 345 | }); 346 | this.registerAction('基础设置', this.setWidgetConfig); 347 | } 348 | 349 | try { 350 | const { 351 | loginheader, 352 | step1, 353 | step2, 354 | step3, 355 | step4, 356 | inner, 357 | icon, 358 | percent, 359 | value, 360 | maxFee, 361 | } = this.settings; 362 | this.fgCircleColor = inner ? new Color(inner) : this.fgCircleColor; 363 | this.textColor1 = value ? new Color(value) : this.textColor1; 364 | 365 | this.phoneBill.circleColor = step1 ? new Color(step1) : this.circleColor1; 366 | this.flow.circleColor = step2 ? new Color(step2) : this.circleColor2; 367 | this.voice.circleColor = step3 ? new Color(step3) : this.circleColor3; 368 | this.updateTime.circleColor = step4 369 | ? new Color(step4) 370 | : this.circleColor4; 371 | 372 | this.iconColor = icon ? new Color(icon) : this.iconColor; 373 | this.percentColor = percent ? new Color(percent) : this.percentColor; 374 | this.loginheader = loginheader ? JSON.parse(loginheader) : {}; 375 | this.maxFee = parseFloat(maxFee) || this.maxFee; 376 | } catch (e) { 377 | console.log(e); 378 | } 379 | } 380 | 381 | /** 382 | * 渲染函数,函数名固定 383 | * 可以根据 this.widgetFamily 来判断小组件尺寸,以返回不同大小的内容 384 | */ 385 | async render() { 386 | await this.init(); 387 | const widget = new ListWidget(); 388 | widget.setPadding(0, 0, 0, 0); 389 | await this.getWidgetBackgroundImage(widget); 390 | if (this.widgetFamily === 'medium') { 391 | return await this.renderMedium(widget); 392 | } else if (this.widgetFamily === 'large') { 393 | return await this.renderLarge(widget); 394 | } else { 395 | return await this.renderSmall(widget); 396 | } 397 | } 398 | } 399 | 400 | // @组件代码结束 401 | // await Runing(Widget, "", false); // 正式环境 402 | await Runing(Widget, args.widgetParameter, false); //远程开发环境 403 | -------------------------------------------------------------------------------- /Scripts/Contact.js: -------------------------------------------------------------------------------- 1 | // Variables used by Scriptable. 2 | // These must be at the very top of the file. Do not edit. 3 | // icon-color: deep-green; icon-glyph: mobile-alt; 4 | 5 | // 添加require,是为了vscode中可以正确引入包,以获得自动补全等功能 6 | if (typeof require === 'undefined') require = importModule; 7 | const { DmYY, Runing } = require('./DmYY'); 8 | 9 | // @组件代码开始 10 | class Widget extends DmYY { 11 | constructor(arg) { 12 | super(arg); 13 | this.name = '桌面联系人'; 14 | this.en = 'ContactTable'; 15 | this.userName = arg || 'Ya'; 16 | this.Run(); 17 | } 18 | 19 | today = ''; 20 | useBoxJS = false; 21 | dataSource = {}; 22 | phoneNumber = {}; 23 | size1 = new Size(15, 15); 24 | size2 = new Size(30, 30); 25 | 26 | init = async () => { 27 | try { 28 | const cardAll = await ContactsContainer.all(); 29 | const data = await Contact.all(cardAll); 30 | if (!this.userName) { 31 | this.dataSource = data[0]; 32 | } else { 33 | this.dataSource = data.find((item) => { 34 | return ( 35 | item.familyName === this.userName || 36 | item.givenName === this.userName || 37 | item.nickname === this.userName || 38 | `${item.familyName}${item.givenName}` === this.userName 39 | ); 40 | }); 41 | } 42 | if (!this.dataSource) 43 | return this.notify(this.name, '未找到通讯录相关联系人,请重新设置'); 44 | this.userName = `${this.dataSource.familyName}${this.dataSource.givenName}`; 45 | const phoneNumbers = this.dataSource.phoneNumbers; 46 | if (phoneNumbers.length) { 47 | this.phoneNumber = phoneNumbers[0]; 48 | this.phoneNumber.value = this.phoneNumber.value.replaceAll(' ', ''); 49 | } 50 | } catch (e) { 51 | console.log(e); 52 | } 53 | }; 54 | 55 | setAvatar = (w) => { 56 | const stackBody = w.addStack(); 57 | const stackLeft = stackBody.addStack(); 58 | stackLeft.setPadding(10, 10, 10, 0); 59 | stackLeft.layoutVertically(); 60 | stackLeft.addSpacer(); 61 | const stackAvatar = stackLeft.addStack(); 62 | stackAvatar.centerAlignContent(); 63 | stackAvatar.size = new Size(80, 80); 64 | stackAvatar.borderWidth = 7; 65 | stackAvatar.borderColor = new Color('#222', 0.7); 66 | stackAvatar.cornerRadius = 40; 67 | if (this.dataSource.image) { 68 | const imgAvatar = stackAvatar.addImage(this.dataSource.image); 69 | imgAvatar.imageSize = new Size(80, 80); 70 | } else { 71 | let textFormat = this.textFormat.title; 72 | textFormat.color = this.widgetColor; 73 | textFormat.size = 42; 74 | this.provideText( 75 | this.userName.substr(0, 1) || '', 76 | stackAvatar, 77 | textFormat 78 | ); 79 | } 80 | stackLeft.addSpacer(); 81 | stackBody.addSpacer(5); 82 | return stackBody; 83 | }; 84 | 85 | setContentCenter = (stackBody) => { 86 | const stackCenter = stackBody.addStack(); 87 | stackCenter.setPadding(10, 0, 10, 10); 88 | stackCenter.layoutVertically(); 89 | stackCenter.addSpacer(); 90 | const stackUsername = stackCenter.addStack(); 91 | stackUsername.centerAlignContent(); 92 | stackCenter.addSpacer(15); 93 | const stackPhoneNumber = stackCenter.addStack(); 94 | stackPhoneNumber.centerAlignContent(); 95 | stackCenter.addSpacer(15); 96 | const stackNote = stackCenter.addStack(); 97 | stackNote.centerAlignContent(); 98 | stackCenter.addSpacer(); 99 | 100 | let textFormat = this.textFormat.defaultText; 101 | textFormat.color = this.widgetColor; 102 | textFormat.size = 18; 103 | const phoneNumber = this.phoneNumber.value || ''; 104 | 105 | const iconPerson = SFSymbol.named('person'); 106 | const imgPerson = stackUsername.addImage(iconPerson.image); 107 | imgPerson.tintColor = this.widgetColor; 108 | imgPerson.imageSize = this.size1; 109 | stackUsername.addSpacer(5); 110 | 111 | const iconPhone = SFSymbol.named('iphone'); 112 | const imgIphone = stackPhoneNumber.addImage(iconPhone.image); 113 | imgIphone.tintColor = this.widgetColor; 114 | imgIphone.imageSize = this.size1; 115 | stackPhoneNumber.addSpacer(5); 116 | 117 | const iconNote = SFSymbol.named('envelope'); 118 | const imgNote = stackNote.addImage(iconNote.image); 119 | imgNote.tintColor = this.widgetColor; 120 | imgNote.imageSize = this.size1; 121 | stackNote.addSpacer(5); 122 | 123 | const data = this.dataSource.emailAddresses; 124 | const email = data.length ? data[0] : {}; 125 | this.provideText(this.userName || '', stackUsername, textFormat); 126 | this.provideText(phoneNumber || '', stackPhoneNumber, textFormat); 127 | const mailTextItem = this.provideText( 128 | email.value || '', 129 | stackNote, 130 | textFormat 131 | ); 132 | mailTextItem.lineLimit = 1; 133 | 134 | stackBody.addSpacer(); 135 | return stackBody; 136 | }; 137 | 138 | stepActionRight = (stackBody) => { 139 | const stackRight = stackBody.addStack(); 140 | stackRight.setPadding(10, 20, 10, 20); 141 | stackRight.layoutVertically(); 142 | stackRight.backgroundColor = this.widgetOpacityColor; 143 | 144 | stackRight.addSpacer(); 145 | const stackCallPhone = stackRight.addStack(); 146 | stackRight.addSpacer(); 147 | const stackSendMessage = stackRight.addStack(); 148 | stackRight.addSpacer(); 149 | const stackDetail = stackRight.addStack(); 150 | stackRight.addSpacer(); 151 | 152 | const phone = this.phoneNumber.value || ''; 153 | const data = this.dataSource.emailAddresses; 154 | const email = data.length ? data[0] : {}; 155 | 156 | stackCallPhone.url = `tel:${phone}`; 157 | stackSendMessage.url = `sms:${phone}`; 158 | stackDetail.url = `mailto:${email.value || ''}`; 159 | 160 | const iconVideo = SFSymbol.named('video'); 161 | const imgVideo = stackCallPhone.addImage(iconVideo.image); 162 | imgVideo.tintColor = this.backGroundColor; 163 | imgVideo.imageSize = this.size2; 164 | 165 | const iconMessage = SFSymbol.named('message'); 166 | const imgMessage = stackSendMessage.addImage(iconMessage.image); 167 | imgMessage.tintColor = this.backGroundColor; 168 | imgMessage.imageSize = this.size2; 169 | 170 | const iconEnvelope = SFSymbol.named('envelope.open'); 171 | const imgEnvelope = stackDetail.addImage(iconEnvelope.image); 172 | imgEnvelope.tintColor = this.backGroundColor; 173 | imgEnvelope.imageSize = this.size2; 174 | 175 | return stackBody; 176 | }; 177 | 178 | renderSmall = (w) => { 179 | this.setContentCenter(stackBody); 180 | this.stepActionRight(stackBody); 181 | return w; 182 | }; 183 | 184 | renderLarge = (w) => { 185 | const stackBody = this.setAvatar(w); 186 | this.setContentCenter(stackBody); 187 | this.stepActionRight(stackBody); 188 | return w; 189 | }; 190 | 191 | renderMedium = (w) => { 192 | const stackBody = this.setAvatar(w); 193 | this.setContentCenter(stackBody); 194 | this.stepActionRight(stackBody); 195 | return w; 196 | }; 197 | 198 | Run() { 199 | if (config.runsInApp) { 200 | this.registerAction({ 201 | icon: { name: 'phone', color: '#722ed1' }, 202 | type: 'input', 203 | title: '右侧透明', 204 | desc: '若不需要右侧背景设置透明度 0 即可', 205 | placeholder: '透明度 0~1', 206 | val: 'rightOpacity', 207 | }); 208 | 209 | this.registerAction('基础设置', this.setWidgetConfig); 210 | } 211 | const light = new Color( 212 | this.settings.lightColor, 213 | parseInt(this.settings.rightOpacity || 1) 214 | ); 215 | const dark = new Color( 216 | this.settings.darkColor, 217 | parseInt(this.settings.rightOpacity || 1) 218 | ); 219 | this.widgetOpacityColor = Color.dynamic(light, dark); 220 | } 221 | 222 | /** 223 | * 渲染函数,函数名固定 224 | * 可以根据 this.widgetFamily 来判断小组件尺寸,以返回不同大小的内容 225 | */ 226 | async render() { 227 | await this.init(); 228 | const widget = new ListWidget(); 229 | widget.setPadding(0, 0, 0, 0); 230 | await this.getWidgetBackgroundImage(widget); 231 | if (this.widgetFamily === 'medium') { 232 | return await this.renderMedium(widget); 233 | } else if (this.widgetFamily === 'large') { 234 | return await this.renderLarge(widget); 235 | } else { 236 | return await this.renderSmall(widget); 237 | } 238 | } 239 | } 240 | 241 | // @组件代码结束 242 | // await Runing(Widget, "", false); // 正式环境 243 | await Runing(Widget, args.widgetParameter, false); //远程开发环境 244 | -------------------------------------------------------------------------------- /Scripts/Ftms.js: -------------------------------------------------------------------------------- 1 | class Ftms { 2 | constructor(carWidget) { 3 | this.$ = carWidget; 4 | } 5 | 6 | name = '一汽丰田'; 7 | en = 'ftms'; 8 | logo = 'https://www.toyota.com.cn/favicon.ico'; 9 | 10 | baseOpt = { 11 | headers: { 12 | Connection: `keep-alive`, 13 | Host: `appiov.ftms.com.cn`, 14 | 'Content-Type': `application/json`, 15 | }, 16 | body: ``, 17 | }; 18 | 19 | init = async () => { 20 | if (this.$.settings.dataSource) { 21 | this.$.serveInfo = this.$.settings.serveInfo; 22 | this.$.dataSource = this.$.settings.dataSource; 23 | } else { 24 | await this.cacheData(); 25 | } 26 | this.cacheData(); 27 | }; 28 | 29 | cacheData = async () => { 30 | try { 31 | await this.getOilPrice(); 32 | await this.getBmuServeHicleInfo(); 33 | await this.getRemoteInfoDetail(); 34 | } catch (e) { 35 | console.log(e); 36 | } 37 | }; 38 | 39 | getBaseOptions(api) { 40 | const baseURL = `https://appiov.ftms.com.cn`; 41 | console.log({ url: `${baseURL}/${api}`, ...this.baseOpt }); 42 | return { url: `${baseURL}/${api}`, ...this.baseOpt }; 43 | } 44 | 45 | getRemoteInfoDetail = async () => { 46 | const options = this.getBaseOptions( 47 | 'ftms-iov-app-gbook/api/gbook/getRemoteInfoDetail' 48 | ); 49 | const response = await this.$.$request.post(options); 50 | if (response.msg === 'success') { 51 | this.$.dataSource.remoteInfo = response.result; 52 | const safeData = 53 | response.result.list.filter((item) => item.security !== 'safe') || []; 54 | if (safeData.length > 0) { 55 | this.$.dataSource.safeText = `${safeData[0].typeName}:${safeData[0].dataName}`; 56 | } else { 57 | this.$.dataSource.safeText = ``; 58 | } 59 | const dataTime = this.$.dataSource.remoteInfo.datatime.split('-'); 60 | this.$.dataSource.remoteInfo.datatime = `${dataTime[1] || ''}-${ 61 | dataTime[2] || '' 62 | }`; 63 | } else { 64 | this.$.notify(this.name, response.msg); 65 | } 66 | await this.getDrivingMonitorInfo(); 67 | }; 68 | 69 | getDrivingMonitorInfo = async () => { 70 | const options = this.getBaseOptions( 71 | 'ftms-iov-app-gbook/api/gbook/getDrivingMonitorInfo' 72 | ); 73 | const response = await this.$.$request.post(options); 74 | console.log(response); 75 | if (response.msg === 'success') { 76 | this.$.dataSource.monitorInfo = response.result; 77 | } 78 | this.$.dataSource.monitorInfo.oilWasteText = `油耗:${this.$.dataSource.monitorInfo.oilWaste}L/100km`; 79 | this.$.settings.dataSource = this.$.dataSource; 80 | this.$.saveSettings(false); 81 | }; 82 | 83 | getBmuServeHicleInfo = async () => { 84 | let headers = await this.$.getCache('@ftms.headers'); 85 | headers = JSON.parse(headers || '{}'); 86 | this.baseOpt.headers = { 87 | token: headers.token, 88 | 'User-Agent': headers['User-Agent'], 89 | ...this.baseOpt.headers, 90 | }; 91 | const options = { 92 | url: `https://superapp.ftms.com.cn/superapp/users/wt/getbmuservehicleinfo?scriptable=1`, 93 | headers, 94 | }; 95 | if (!this.$.settings.serveInfo) { 96 | const response = await this.$.$request.post(options); 97 | console.log(response); 98 | if (response.code === '200') { 99 | this.$.settings.serveInfo = response.data; 100 | this.$.serveInfo = response.data; 101 | this.$.saveSettings(false); 102 | } else { 103 | this.$.notify(this.name, response.msg); 104 | } 105 | } else { 106 | this.$.serveInfo = this.$.settings.serveInfo || {}; 107 | } 108 | 109 | this.baseOpt.headers.userId = this.$.serveInfo.userId; 110 | this.baseOpt.headers['USER-ID'] = this.$.serveInfo.userId; 111 | this.baseOpt.body = JSON.stringify({ vin: this.$.serveInfo.vin }); 112 | this.baseOpt.headers.Authorization = `Bearer ${this.baseOpt.headers.token}`; 113 | this.baseOpt.headers.accessToken = this.baseOpt.headers.token; 114 | this.baseOpt.headers['ACCESS-TOKEN'] = this.baseOpt.headers.token; 115 | }; 116 | 117 | getOilPrice = async () => { 118 | const location = await Location.current(); 119 | const locationText = await Location.reverseGeocode( 120 | location.latitude, 121 | location.longitude 122 | ); 123 | const { administrativeArea = '' } = locationText[0] || {}; 124 | 125 | const oilNumber = `${this.$.settings.oilNumber || '92'}`; 126 | 127 | const filter = `(CITYNAME="${administrativeArea.replace('省', '')}")`; 128 | const time = Date.now(); 129 | const url = `https://datacenter-web.eastmoney.com/api/data/v1/get?reportName=RPTA_WEB_YJ_JH&columns=ALL&filter=${encodeURIComponent( 130 | filter 131 | )}&sortColumns=DIM_DATE&sortTypes=-1&pageNumber=1&pageSize=1&source=WEB&_=${time}`; 132 | 133 | const options = { url }; 134 | const response = await this.$.$request.post(options); 135 | console.log(response); 136 | if (response.result) { 137 | this.$.dataSource.oilPrice = response.result.data[0]; 138 | this.$.dataSource.oilZDE = response.result.data[0][`ZDE${oilNumber}`]; 139 | this.$.dataSource.oilPriceText = `油价:${ 140 | response.result.data[0][`V${oilNumber}`] 141 | }`; 142 | } 143 | }; 144 | } 145 | 146 | module.exports = Ftms; 147 | -------------------------------------------------------------------------------- /Scripts/Health.js: -------------------------------------------------------------------------------- 1 | // letiables used by Scriptable. 2 | // These must be at the very top of the file. Do not edit. 3 | // icon-color: orange; icon-glyph: user-plus; 4 | 5 | /** 6 | * https://www.icloud.com/shortcuts/2be502d8e9694068ae982cd3a70dea89:快捷指令 7 | * 组件必须配合快捷指令使用,运行快捷指令时,保存的路径是 Scriptable 下 8 | */ 9 | 10 | // 添加require,是为了vscode中可以正确引入包,以获得自动补全等功能 11 | 12 | if (typeof require === 'undefined') require = importModule 13 | const { DmYY, Runing } = require('./DmYY') 14 | 15 | // @组件代码开始 16 | class Widget extends DmYY { 17 | constructor(arg) { 18 | super(arg) 19 | this.name = '健康行走步数' 20 | this.en = 'healthCenter' 21 | this.maxMonthDist = parseInt(this.settings.maxMonthDist) || 5 // 柱状图比例高度,值越大,柱状范围越广 22 | this.Run() 23 | } 24 | 25 | widgetFamily = 'medium' 26 | maxYearDist = 1500 27 | 28 | color1 = Color.orange() 29 | lineColor = new Color('#48484b') 30 | useBoxJS = false 31 | 32 | running = {} 33 | stepsCount = 0 34 | stepsToday = 0 35 | 36 | init = async () => { 37 | try { 38 | await this.getData() 39 | } catch (e) { 40 | console.log(e) 41 | } 42 | } 43 | 44 | numberFormat(value) { 45 | try { 46 | const param = {} 47 | let k = 10000 48 | const size = ['', '万', '亿', '万亿'] 49 | let i 50 | if (value < k) { 51 | param.value = value 52 | param.unit = '' 53 | } else { 54 | i = Math.floor(Math.log(value) / Math.log(k)) 55 | param.value = (value / Math.pow(k, i)).toFixed(2) 56 | param.unit = size[i] 57 | } 58 | return param 59 | } catch (e) { 60 | console.log(e) 61 | } 62 | } 63 | 64 | getData = async () => { 65 | try { 66 | const fileICloud = FileManager.iCloud() 67 | const dir = fileICloud.documentsDirectory() 68 | const path = fileICloud.joinPath(dir, 'health.txt') 69 | const response = fileICloud.readString(path) 70 | let data = JSON.parse(response) 71 | const dateToday = new Date() 72 | const year = dateToday.getFullYear() 73 | let month = dateToday.getMonth() + 1 74 | let day = dateToday.getDate() 75 | month = month >= 10 ? month : `0${month}` 76 | day = day >= 10 ? day : `0${day}` 77 | const today = `${year}-${month}-${day}` 78 | 79 | data.forEach((item) => { 80 | if (item.health_type === 'Walking + Running Distance') { 81 | item.samples.forEach((run, index) => { 82 | if (item.samples.length - 1 === index) return 83 | const date = run.date 84 | if (!this.running[date]) this.running[date] = 0 85 | this.running[date] += parseFloat(run.value) 86 | }) 87 | } 88 | if (item.health_type === 'Steps') { 89 | item.samples.forEach((step) => { 90 | if (step.date === today) this.stepsToday = step.value 91 | this.stepsCount += parseInt(step.value) 92 | }) 93 | } 94 | }) 95 | Object.keys(this.running).forEach((key) => { 96 | this.running[key] = Math.floor(this.running[key] * 100) / 100 97 | }) 98 | } catch (e) { 99 | this.notify( 100 | this.name, 101 | '健康数据读取失败,请点击使用健康数据快捷指令更新步数', 102 | 'https://www.icloud.com/shortcuts/beb65db5ea0a474abe7ff080410b9ddf' 103 | ) 104 | return false 105 | } 106 | } 107 | 108 | /*------------------------------------------------------------------------------ 109 | 50 km Linien 110 | ------------------------------------------------------------------------------*/ 111 | createLines(stack) { 112 | let canvas, path 113 | // 50km Linien 114 | canvas = new DrawContext() 115 | canvas.size = new Size(292, 82) 116 | canvas.opaque = false 117 | canvas.respectScreenScale = true 118 | canvas.setFillColor(this.lineColor) 119 | path = new Path() 120 | path.addRect(new Rect(0, 0, 292, 1)) 121 | canvas.addPath(path) 122 | canvas.fillPath() 123 | path = new Path() 124 | path.addRect(new Rect(0, 15, 292, 1)) 125 | canvas.addPath(path) 126 | canvas.fillPath() 127 | path = new Path() 128 | path.addRect(new Rect(0, 30, 292, 1)) 129 | canvas.addPath(path) 130 | canvas.fillPath() 131 | path = new Path() 132 | path.addRect(new Rect(0, 45, 292, 1)) 133 | canvas.addPath(path) 134 | canvas.fillPath() 135 | stack.backgroundImage = canvas.getImage() 136 | } 137 | 138 | async buildWidget(widget) { 139 | // // Stacks definieren 140 | let stackYear = widget.addStack() 141 | widget.addSpacer() 142 | let stackMonth = widget.addStack() 143 | // Stacks für Symbol und Jahresauswertung aufbereiten 144 | let stackYear1 = stackYear.addStack() 145 | stackYear.addSpacer(10) 146 | let stackYear2 = stackYear.addStack() 147 | let sym = SFSymbol.named('figure.walk') 148 | let img = stackYear1.addImage(sym.image) 149 | img.tintColor = this.color1 150 | img.imageSize = new Size(25, 25) 151 | stackYear2.layoutVertically() 152 | let stackYearCurr = stackYear2.addStack() 153 | let stackThemItem = stackYear2.addStack() 154 | let stackToday = stackYear2.addStack() 155 | 156 | let data = 0 157 | const runningData = Object.keys(this.running) 158 | if (runningData.length > 12) runningData.splice(0, runningData.length - 12) 159 | runningData.forEach((date) => { 160 | const [_, month, day] = date.split('-') 161 | const stackDay = stackMonth.addStack() 162 | const value = this.running[date] 163 | this.createProgressMonth(stackDay, `${month}.${day}`, value) 164 | stackMonth.addSpacer(2) 165 | data += value 166 | }) 167 | this.createProgressYear(stackYearCurr, '运动', data, this.color1) 168 | 169 | const count = (18 * this.stepsCount) / ((20000 * this.maxMonthDist) / 4) 170 | this.createProgressSteps( 171 | stackThemItem, 172 | '步数', 173 | this.stepsCount, 174 | this.color1, 175 | count 176 | ) 177 | const today = (18 * this.stepsToday) / ((2000 * this.maxMonthDist) / 4) 178 | this.createProgressSteps( 179 | stackToday, 180 | '今日', 181 | this.stepsToday, 182 | this.color1, 183 | today 184 | ) 185 | 186 | // 50km Linie 187 | this.createLines(stackMonth) 188 | return widget 189 | } 190 | 191 | createProgressYear(stack, year, dist, color) { 192 | let stackDesc, stackPBar, stackDist, canvas, path, txt, img 193 | 194 | // Initialisierung 195 | stack.centerAlignContent() 196 | 197 | // Stacks definieren 198 | stackDesc = stack.addStack() 199 | stackPBar = stack.addStack() 200 | stackDist = stack.addStack() 201 | 202 | // Beschreibung 203 | stackDesc.size = new Size(30, 0) 204 | txt = stackDesc.addText(year) 205 | txt.font = Font.systemFont(7) 206 | txt.textColor = this.widgetColor 207 | stackDesc.addSpacer() 208 | 209 | // Progress-Bar 210 | canvas = new DrawContext() 211 | canvas.size = new Size(180, 7) 212 | canvas.opaque = false 213 | canvas.respectScreenScale = true 214 | canvas.setFillColor(new Color('#48484b')) 215 | path = new Path() 216 | path.addRoundedRect(new Rect(0, 0, 180, 5), 3, 2) 217 | canvas.addPath(path) 218 | canvas.fillPath() 219 | canvas.setFillColor(color) 220 | path = new Path() 221 | path.addRoundedRect( 222 | new Rect(0, 0, (180 * dist) / ((200 * this.maxMonthDist) / 4), 5), 223 | 3, 224 | 2 225 | ) 226 | canvas.addPath(path) 227 | canvas.fillPath() 228 | img = stackPBar.addImage(canvas.getImage()) 229 | img.imageSize = new Size(180, 7) 230 | 231 | // Distanz 232 | stackDist.addSpacer(10) 233 | txt = stackDist.addText(Math.round(dist).toString() + ' km') 234 | txt.font = Font.systemFont(7) 235 | txt.textColor = this.widgetColor 236 | } 237 | 238 | createProgressSteps(stack, year, dist, color, rectScale) { 239 | let stackDesc, stackPBar, stackDist, canvas, path, txt, img 240 | 241 | // Initialisierung 242 | stack.centerAlignContent() 243 | 244 | // Stacks definieren 245 | stackDesc = stack.addStack() 246 | stackPBar = stack.addStack() 247 | stackDist = stack.addStack() 248 | 249 | // Beschreibung 250 | stackDesc.size = new Size(30, 0) 251 | txt = stackDesc.addText(year) 252 | txt.font = Font.systemFont(7) 253 | txt.textColor = this.widgetColor 254 | stackDesc.addSpacer() 255 | 256 | // Progress-Bar 257 | canvas = new DrawContext() 258 | canvas.size = new Size(180, 7) 259 | canvas.opaque = false 260 | canvas.respectScreenScale = true 261 | canvas.setFillColor(new Color('#48484b')) 262 | path = new Path() 263 | path.addRoundedRect(new Rect(0, 0, 180, 5), 3, 2) 264 | canvas.addPath(path) 265 | canvas.fillPath() 266 | canvas.setFillColor(color) 267 | path = new Path() 268 | const numberText = this.numberFormat(dist) 269 | 270 | path.addRoundedRect(new Rect(0, 0, rectScale, 5), 3, 2) 271 | canvas.addPath(path) 272 | canvas.fillPath() 273 | img = stackPBar.addImage(canvas.getImage()) 274 | img.imageSize = new Size(180, 7) 275 | 276 | // Distanz 277 | stackDist.addSpacer(10) 278 | 279 | txt = stackDist.addText(numberText.value + ` ${numberText.unit}步`) 280 | txt.font = Font.systemFont(7) 281 | txt.textColor = this.widgetColor 282 | } 283 | 284 | createTemplateItem(stack, desc) { 285 | // Stacks 286 | const txt = stack.addText(desc) 287 | txt.font = Font.systemFont(7) 288 | txt.textColor = this.widgetColor 289 | } 290 | 291 | /*------------------------------------------------------------------------------ 292 | Balkenanzeige für Monatsauswertung aufbereiten 293 | ------------------------------------------------------------------------------*/ 294 | createProgressMonth(stack, month, dist3) { 295 | let stackDist, stackPBar, stackDesc, canvas, path, s, img, txt 296 | 297 | // Stacks definieren 298 | stack.layoutVertically() 299 | stackPBar = stack.addStack() 300 | stack.addSpacer(5) 301 | stackDesc = stack.addStack() 302 | stackDist = stack.addStack() 303 | 304 | // Progress-Bar 305 | canvas = new DrawContext() 306 | canvas.size = new Size(17, 60) 307 | canvas.opaque = false 308 | canvas.respectScreenScale = true 309 | 310 | canvas.setFillColor(this.color1) 311 | path = new Path() 312 | s = (50 * dist3) / this.maxMonthDist 313 | path.addRect(new Rect(6, 60 - s, 8, s)) 314 | canvas.addPath(path) 315 | canvas.fillPath() 316 | img = stackPBar.addImage(canvas.getImage()) 317 | img.imageSize = new Size(17, 60) 318 | 319 | // Monat 320 | stackDesc.size = new Size(23, 10) 321 | txt = stackDesc.addText(month) 322 | txt.font = Font.systemFont(7) 323 | txt.textColor = this.widgetColor 324 | txt.centerAlignText() 325 | 326 | // Distanz aktuelle Jahr 327 | stackDist.size = new Size(20, 8) 328 | txt = stackDist.addText(Math.round(dist3).toString()) 329 | txt.font = Font.systemFont(6) 330 | txt.textColor = this.widgetColor 331 | txt.centerAlignText() 332 | } 333 | 334 | /** 335 | * 渲染函数,函数名固定 336 | * 可以根据 this.widgetFamily 来判断小组件尺寸,以返回不同大小的内容 337 | */ 338 | async render() { 339 | await this.init() 340 | const widget = new ListWidget() 341 | await this.getWidgetBackgroundImage(widget) 342 | await this.buildWidget(widget) 343 | await widget.presentMedium() 344 | if (config.runsFromHomeScreen) return widget 345 | } 346 | 347 | Run = () => { 348 | if (config.runsInApp) { 349 | this.registerAction('捷径安装', async () => { 350 | await this.notify( 351 | this.name, 352 | '点击安装捷径', 353 | 'https://www.icloud.com/shortcuts/beb65db5ea0a474abe7ff080410b9ddf' 354 | ) 355 | }) 356 | this.registerAction('柱状比例', async () => { 357 | await this.setAlertInput( 358 | '设置柱状比例', 359 | ' 柱状图比例高度,值越大,柱状范围越广', 360 | { maxMonthDist: '比例默认值,5' } 361 | ) 362 | }) 363 | this.registerAction('皮肤颜色', this.setWidgetSkin) 364 | this.registerAction('刻度颜色', this.setWidgetScale) 365 | this.registerAction('基础设置', this.setWidgetConfig) 366 | } 367 | const skinColor = !this.isNight 368 | ? this.settings.lightSkinColor 369 | : this.settings.darkSkinColor 370 | this.color1 = skinColor ? new Color(skinColor) : this.color1 371 | const scaleColor = !this.isNight 372 | ? this.settings.lightScaleColor 373 | : this.settings.darkScaleColor 374 | this.lineColor = scaleColor ? new Color(scaleColor) : this.lineColor 375 | } 376 | 377 | setWidgetSkin = async () => { 378 | await this.setLightAndDark( 379 | '柱状颜色', 380 | false, 381 | 'lightSkinColor', 382 | 'darkSkinColor' 383 | ) 384 | } 385 | 386 | setWidgetScale = async () => { 387 | await this.setLightAndDark( 388 | '刻度颜色', 389 | false, 390 | 'lightScaleColor', 391 | 'darkScaleColor' 392 | ) 393 | } 394 | } 395 | let params = args.shortcutParameter 396 | if (params) { 397 | const fileICloud = FileManager.iCloud() 398 | const path = fileICloud.documentsDirectory() 399 | fileICloud.writeString(path + '/health.txt', JSON.stringify(params)) 400 | Script.complete() 401 | } else { 402 | await Runing(Widget, '', false) 403 | } 404 | // @组件代码结束 405 | -------------------------------------------------------------------------------- /Scripts/HistoryToday.js: -------------------------------------------------------------------------------- 1 | // Variables used by Scriptable. 2 | // These must be at the very top of the file. Do not edit. 3 | // icon-color: green; icon-glyph: calendar-minus; 4 | 5 | // 添加require,是为了vscode中可以正确引入包,以获得自动补全等功能 6 | if (typeof require === 'undefined') require = importModule; 7 | const { DmYY, Runing } = require('./DmYY'); 8 | 9 | // @组件代码开始 10 | class Widget extends DmYY { 11 | constructor(arg) { 12 | super(arg); 13 | this.name = '历史上的今天'; 14 | this.en = 'historyToday'; 15 | this.logo = 16 | 'https://raw.githubusercontent.com/Orz-3/mini/master/Color/historyToday.png'; 17 | config.runsInApp && this.registerAction('基础设置', this.setWidgetConfig); 18 | this.cacheName = this.md5(`dataSouce_${this.en}`); 19 | } 20 | 21 | useBoxJS = false; 22 | today = ''; 23 | dataSource = []; 24 | 25 | init = async () => { 26 | try { 27 | const today = new Date(); 28 | const month = today.getMonth() + 1; 29 | const day = today.getDate(); 30 | this.today = `${month}.${day}`; 31 | await this.getHistoryList(); 32 | } catch (e) { 33 | console.log(e); 34 | } 35 | }; 36 | 37 | getHistoryList = async () => { 38 | const url = `http://api.sodion.net/api_v1/grap/todayinhistory`; 39 | const response = await this.$request.get(url); 40 | if (!response || !response.length) 41 | console.log('接口数据异常,请稍后再试!'); 42 | this.dataSource = response; 43 | }; 44 | 45 | setListCell = async (cell, data) => { 46 | let { year, title, href, img } = data; 47 | let body = cell.addStack(); 48 | body.url = href; 49 | 50 | const box = body.addStack(); 51 | box.addSpacer(); 52 | box.setPadding(10, 10, 10, 10); 53 | box.backgroundColor = new Color('#000', 0.1); 54 | box.cornerRadius = 20; 55 | box.layoutVertically(); 56 | 57 | const boxTopStack = box.addStack(); 58 | boxTopStack.addSpacer(); 59 | const avatarStack = boxTopStack.addStack(); 60 | avatarStack.size = new Size(50, 50); 61 | avatarStack.cornerRadius = 25; 62 | const image = await this.$request.get(img, 'IMG'); 63 | const imageItem = avatarStack.addImage(image); 64 | imageItem.centerAlignImage(); 65 | boxTopStack.addSpacer(); 66 | 67 | box.addSpacer(); 68 | const titleStack = box.addStack(); 69 | titleStack.size = new Size(0, 30); 70 | const descText = titleStack.addText(title); 71 | descText.font = Font.boldSystemFont(8); 72 | descText.textColor = this.widgetColor; 73 | descText.lineLimit = 3; 74 | 75 | box.addSpacer(5); 76 | const yearStack = box.addStack(); 77 | yearStack.addSpacer(); 78 | const yearText = yearStack.addText(year); 79 | yearText.font = Font.boldSystemFont(10); 80 | yearText.textColor = this.widgetColor; 81 | yearStack.addSpacer(); 82 | 83 | return cell; 84 | }; 85 | 86 | group(array, subNum) { 87 | let index = 0; 88 | let newArray = []; 89 | while (index < array.length) { 90 | newArray.push(array.slice(index, (index += subNum))); 91 | } 92 | return newArray; 93 | } 94 | 95 | setWidget = async (body, size) => { 96 | const container = body.addStack(); 97 | container.setPadding(10, 10, 10, 10); 98 | const data = this.getRandomArrayElements(this.dataSource, size); 99 | if (size === 6) { 100 | const source = this.group(data, 3); 101 | container.layoutVertically(); 102 | for (const item of source) { 103 | const boxStack = container.addStack(); 104 | container.addSpacer(); 105 | for (let index = 0; index < item.length; index++) { 106 | const data = item[index]; 107 | let listItem = boxStack.addStack(); 108 | await this.setListCell(listItem, data); 109 | if (index !== item.length - 1) boxStack.addSpacer(); 110 | } 111 | } 112 | } else { 113 | const dataSource = data; 114 | for (let index = 0; index < dataSource.length; index++) { 115 | const data = dataSource[index]; 116 | let listItem = container.addStack(); 117 | await this.setListCell(listItem, data); 118 | if (index !== dataSource.length - 1) container.addSpacer(); 119 | } 120 | } 121 | return body; 122 | }; 123 | 124 | renderSmall = async (w) => { 125 | return await this.setWidget(w, 1); 126 | }; 127 | 128 | renderLarge = async (w) => { 129 | return await this.setWidget(w, 6); 130 | }; 131 | 132 | renderMedium = async (w) => { 133 | return await this.setWidget(w, 3); 134 | }; 135 | 136 | /** 137 | * 渲染函数,函数名固定 138 | * 可以根据 this.widgetFamily 来判断小组件尺寸,以返回不同大小的内容 139 | */ 140 | async render() { 141 | await this.init(); 142 | const widget = new ListWidget(); 143 | widget.setPadding(0, 0, 0, 0); 144 | await this.getWidgetBackgroundImage(widget); 145 | if (this.widgetFamily === 'medium') { 146 | return await this.renderMedium(widget); 147 | } else if (this.widgetFamily === 'large') { 148 | return await this.renderLarge(widget); 149 | } else { 150 | return await this.renderSmall(widget); 151 | } 152 | } 153 | } 154 | 155 | // @组件代码结束 156 | // await Runing(Widget, "", false); // 正式环境 157 | await Runing(Widget, '', false); //远程开发环境 158 | -------------------------------------------------------------------------------- /Scripts/JDDou.js: -------------------------------------------------------------------------------- 1 | // Variables used by Scriptable. 2 | // These must be at the very top of the file. Do not edit. 3 | // icon-color: cyan; icon-glyph: yen-sign; 4 | 5 | // 添加require,是为了vscode中可以正确引入包,以获得自动补全等功能 6 | if (typeof require === 'undefined') require = importModule; 7 | const { DmYY, Runing } = require('./DmYY'); 8 | 9 | // @组件代码开始 10 | class Widget extends DmYY { 11 | constructor(arg) { 12 | super(arg); 13 | this.name = '京东豆'; 14 | this.en = 'JDDou'; 15 | this.JDRun(module.filename, args); 16 | } 17 | 18 | beanCount = 0; 19 | incomeBean = 0; 20 | expenseBean = 0; 21 | timerKeys = []; 22 | 23 | JDCookie = { 24 | cookie: '', 25 | userName: '', 26 | }; 27 | CookiesData = []; 28 | 29 | init = async () => { 30 | try { 31 | await this.TotalBean(); 32 | this.timerKeys = this.getDay(1); 33 | await this.getAmountData(); 34 | } catch (e) { 35 | console.log(e); 36 | } 37 | }; 38 | 39 | getAmountData = async () => { 40 | let i = 0, 41 | page = 1; 42 | do { 43 | const response = await this.getJingBeanBalanceDetail(page); 44 | console.log( 45 | `第${page}页:${response.code === '0' ? '请求成功' : '请求失败'}`, 46 | ); 47 | if (response.code === '3') { 48 | i = 1; 49 | console.log(response); 50 | } 51 | if (response && response.code === '0') { 52 | page++; 53 | let detailList = response.jingDetailList; 54 | if (detailList && detailList.length > 0) { 55 | for (let item of detailList) { 56 | const dates = item.date.split(' '); 57 | if (this.timerKeys.indexOf(dates[0]) > -1) { 58 | if (this.timerKeys[0] === dates[0]) { 59 | const amount = Number(item.amount); 60 | if (amount > 0) this.incomeBean += amount; 61 | if (amount < 0) this.expenseBean += amount; 62 | } 63 | } else { 64 | i = 1; 65 | break; 66 | } 67 | } 68 | } 69 | } 70 | } while (i === 0); 71 | }; 72 | 73 | getDay(dayNumber) { 74 | let data = []; 75 | let i = dayNumber; 76 | do { 77 | const today = new Date(); 78 | const year = today.getFullYear(); 79 | const targetday_milliseconds = today.getTime() - 1000 * 60 * 60 * 24 * i; 80 | today.setTime(targetday_milliseconds); //注意,这行是关键代码 81 | let month = today.getMonth() + 1; 82 | month = month >= 10 ? month : `0${month}`; 83 | let day = today.getDate(); 84 | day = day >= 10 ? day : `0${day}`; 85 | data.push(`${year}-${month}-${day}`); 86 | i--; 87 | } while (i >= 0); 88 | return data; 89 | } 90 | 91 | TotalBean = async () => { 92 | const options = { 93 | headers: { 94 | Accept: 'application/json,text/plain, */*', 95 | 'Content-Type': 'application/x-www-form-urlencoded', 96 | 'Accept-Encoding': 'gzip, deflate, br', 97 | 'Accept-Language': 'zh-cn', 98 | Connection: 'keep-alive', 99 | Cookie: this.JDCookie.cookie, 100 | Referer: 'https://wqs.jd.com/my/jingdou/my.shtml?sceneval=2', 101 | 'User-Agent': 102 | 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_0_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0 Mobile/15E148 Safari/604.1', 103 | }, 104 | }; 105 | const url = 'https://wq.jd.com/user/info/QueryJDUserInfo?sceneval=2'; 106 | const request = new Request(url, { method: 'POST' }); 107 | request.body = options.body; 108 | request.headers = options.headers; 109 | 110 | const response = await request.loadJSON(); 111 | if (response.retcode === 0) { 112 | this.beanCount = response.base.jdNum; 113 | } else { 114 | console.log('京东服务器返回空数据'); 115 | } 116 | return response; 117 | }; 118 | 119 | getJingBeanBalanceDetail = async (page) => { 120 | try { 121 | const options = { 122 | url: `https://bean.m.jd.com/beanDetail/detail.json`, 123 | body: `page=${page}`, 124 | headers: { 125 | 'X-Requested-With': `XMLHttpRequest`, 126 | Connection: `keep-alive`, 127 | 'Accept-Encoding': `gzip, deflate, br`, 128 | 'Content-Type': `application/x-www-form-urlencoded; charset=UTF-8`, 129 | Origin: `https://bean.m.jd.com`, 130 | 'User-Agent': `Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.1 Safari/605.1.15`, 131 | Cookie: this.JDCookie.cookie, 132 | Host: `bean.m.jd.com`, 133 | Referer: `https://bean.m.jd.com/beanDetail/index.action?resourceValue=bean`, 134 | 'Accept-Language': `zh-cn`, 135 | Accept: `application/json, text/javascript, */*; q=0.01`, 136 | }, 137 | }; 138 | return await this.$request.post(options.url, options); 139 | } catch (e) { 140 | console.log(e); 141 | } 142 | }; 143 | 144 | transforJSON = (str) => { 145 | if (typeof str == 'string') { 146 | try { 147 | return JSON.parse(str); 148 | } catch (e) { 149 | console.log(e); 150 | return []; 151 | } 152 | } 153 | console.log('It is not a string!'); 154 | }; 155 | 156 | setContainer = async (container, { icon, text, desc }) => { 157 | container.layoutVertically(); 158 | container.centerAlignContent(); 159 | 160 | const viewer = container.addStack(); 161 | viewer.size = new Size(90, 25); 162 | const jdD_icon = await this.$request.get(icon, 'IMG'); 163 | const imageElemView = viewer.addImage(jdD_icon); 164 | imageElemView.centerAlignImage(); 165 | imageElemView.imageSize = new Size(25, 25); 166 | container.addSpacer(10); 167 | 168 | const textview = container.addStack(); 169 | textview.size = new Size(90, 30); 170 | const titleTextItem = textview.addText(text); 171 | titleTextItem.font = Font.boldSystemFont(22); 172 | titleTextItem.textColor = new Color('#ffef03'); 173 | titleTextItem.centerAlignText(); 174 | 175 | const descView = container.addStack(); 176 | descView.size = new Size(90, 30); 177 | const descTextItem = descView.addText(desc); 178 | descTextItem.textColor = this.widgetColor; 179 | descTextItem.font = Font.lightSystemFont(16); 180 | descTextItem.centerAlignText(); 181 | 182 | return container; 183 | }; 184 | 185 | setWidget = async (widget) => { 186 | const body = widget.addStack(); 187 | body.centerAlignContent(); 188 | body.url = 'https://bean.m.jd.com/'; 189 | const letfContainer = body.addStack(); 190 | await this.setContainer(letfContainer, { 191 | icon: 'https://gitee.com/scriptableJS/Scriptable/raw/master/JDDou/jdd.png', 192 | text: `${this.beanCount}`, 193 | desc: '当前京豆', 194 | }); 195 | body.addSpacer(); 196 | const centerContainer = body.addStack(); 197 | await this.setContainer(centerContainer, { 198 | icon: 'https://gitee.com/scriptableJS/Scriptable/raw/master/JDDou/jdd.png', 199 | text: `+${this.incomeBean}`, 200 | desc: '昨日收入', 201 | }); 202 | body.addSpacer(); 203 | const rightContainer = body.addStack(); 204 | await this.setContainer(rightContainer, { 205 | icon: 'https://gitee.com/scriptableJS/Scriptable/raw/master/JDDou/jdd.png', 206 | text: `${this.expenseBean}`, 207 | desc: '昨日支出', 208 | }); 209 | return widget; 210 | }; 211 | 212 | renderSmall = async (w) => { 213 | return await this.renderLarge(w); 214 | }; 215 | 216 | renderLarge = async (w) => { 217 | const text = w.addText('暂不支持'); 218 | text.font = Font.boldSystemFont(20); 219 | text.textColor = this.widgetColor; 220 | return w; 221 | }; 222 | 223 | renderMedium = async (w) => { 224 | return await this.setWidget(w); 225 | }; 226 | 227 | /** 228 | * 渲染函数,函数名固定 229 | * 可以根据 this.widgetFamily 来判断小组件尺寸,以返回不同大小的内容 230 | */ 231 | async render() { 232 | await this.init(); 233 | const widget = new ListWidget(); 234 | await this.getWidgetBackgroundImage(widget); 235 | const header = widget.addStack(); 236 | if (this.widgetFamily !== 'small') { 237 | await this.renderJDHeader(header); 238 | } else { 239 | await this.renderHeader(header, this.logo, this.name); 240 | } 241 | widget.addSpacer(20); 242 | if (this.widgetFamily === 'medium') { 243 | widget.url = 244 | 'https://bean.m.jd.com/beanDetail/index.action?resourceValue=bean'; 245 | await this.renderMedium(widget); 246 | } else if (this.widgetFamily === 'large') { 247 | await this.renderLarge(widget); 248 | } else { 249 | await this.renderSmall(widget); 250 | } 251 | return widget; 252 | } 253 | 254 | renderJDHeader = async (header) => { 255 | header.centerAlignContent(); 256 | await this.renderHeader(header, this.logo, this.name, this.widgetColor); 257 | header.addSpacer(); 258 | const headerMore = header.addStack(); 259 | headerMore.url = 'https://home.m.jd.com/myJd/home.action'; 260 | headerMore.setPadding(1, 10, 1, 10); 261 | headerMore.cornerRadius = 10; 262 | headerMore.backgroundColor = new Color('#fff', 0.5); 263 | const textItem = headerMore.addText(this.JDCookie.userName); 264 | textItem.font = Font.boldSystemFont(12); 265 | textItem.textColor = this.widgetColor; 266 | textItem.lineLimit = 1; 267 | textItem.rightAlignText(); 268 | return header; 269 | }; 270 | 271 | jdWebView = async () => { 272 | const webView = new WebView(); 273 | const url = 274 | 'https://mcr.jd.com/credit_home/pages/index.html?btPageType=BT&channelName=024'; 275 | await webView.loadURL(url); 276 | await webView.present(false); 277 | const req = new Request( 278 | 'https://ms.jr.jd.com/gw/generic/bt/h5/m/firstScreenNew', 279 | ); 280 | req.method = 'POST'; 281 | req.body = 282 | 'reqData={"clientType":"ios","clientVersion":"13.2.3","deviceId":"","environment":"3"}'; 283 | await req.loadJSON(); 284 | const cookies = req.response.cookies; 285 | const account = { username: '', cookie: '' }; 286 | const cookie = []; 287 | cookies.forEach((item) => { 288 | const value = `${item.name}=${item.value}`; 289 | if (item.name === 'pt_key') cookie.push(value); 290 | if (item.name === 'pt_pin') { 291 | account.username = item.value; 292 | cookie.push(value); 293 | } 294 | }); 295 | account.cookie = cookie.join('; '); 296 | console.log(account); 297 | 298 | if (account.cookie) { 299 | this.settings = { ...this.settings, ...account }; 300 | this.saveSettings(false); 301 | console.log(`${this.name}: cookie获取成功,请关闭窗口!`); 302 | this.notify(this.name, 'cookie获取成功,请关闭窗口!'); 303 | } 304 | }; 305 | 306 | JDRun = (filename, args) => { 307 | if (config.runsInApp) { 308 | this.registerAction('基础设置', this.setWidgetConfig); 309 | this.registerAction('账号设置', async () => { 310 | const index = await this.generateAlert('设置账号信息', [ 311 | '网站登录', 312 | '手动输入', 313 | ]); 314 | if (index === 0) { 315 | await this.jdWebView(); 316 | } else { 317 | await this.setAlertInput('账号设置', '京东账号 Ck', { 318 | username: '昵称', 319 | cookie: 'Cookie', 320 | }); 321 | } 322 | }); 323 | this.registerAction('代理缓存', this.actionSettings); 324 | } 325 | let _md5 = this.md5(filename + this.en); 326 | this.CACHE_KEY = `cache_${_md5}`; 327 | this.JDindex = 328 | typeof args.widgetParameter === 'string' 329 | ? parseInt(args.widgetParameter) 330 | : false; 331 | this.logo = 332 | 'https://raw.githubusercontent.com/Orz-3/mini/master/Color/jd.png'; 333 | try { 334 | const cookieData = this.settings.cookieData; 335 | if (this.JDindex !== false && cookieData[this.JDindex]) { 336 | this.JDCookie = cookieData[this.JDindex]; 337 | } else { 338 | this.JDCookie.userName = this.settings.username; 339 | this.JDCookie.cookie = this.settings.cookie; 340 | } 341 | if (!this.JDCookie.cookie) throw '京东 CK 获取失败'; 342 | this.JDCookie.userName = decodeURI(this.JDCookie.userName); 343 | return true; 344 | } catch (e) { 345 | this.notify('错误提示', e); 346 | return false; 347 | } 348 | }; 349 | 350 | // 加载京东 Ck 节点列表 351 | _loadJDCk = async () => { 352 | try { 353 | const CookiesData = await this.getCache('CookiesJD'); 354 | this.CookiesData = []; 355 | if (CookiesData) { 356 | this.CookiesData = this.transforJSON(CookiesData); 357 | } 358 | const CookieJD = await this.getCache('CookieJD'); 359 | if (CookieJD) { 360 | const userName = CookieJD.match(/pt_pin=(.+?);/)[1]; 361 | const ck1 = { 362 | cookie: CookieJD, 363 | userName, 364 | }; 365 | this.CookiesData.push(ck1); 366 | } 367 | const Cookie2JD = await this.getCache('CookieJD2'); 368 | if (Cookie2JD) { 369 | const userName = Cookie2JD.match(/pt_pin=(.+?);/)[1]; 370 | const ck2 = { 371 | cookie: Cookie2JD, 372 | userName, 373 | }; 374 | this.CookiesData.push(ck2); 375 | } 376 | return true; 377 | } catch (e) { 378 | console.log(e); 379 | this.CookiesData = []; 380 | return false; 381 | } 382 | }; 383 | 384 | async actionSettings() { 385 | try { 386 | const table = new UITable(); 387 | if (!(await this._loadJDCk())) throw 'BoxJS 数据读取失败'; 388 | // 如果是节点,则先远程获取 389 | this.settings.cookieData = this.CookiesData; 390 | this.saveSettings(false); 391 | this.CookiesData.map((t, index) => { 392 | const r = new UITableRow(); 393 | r.addText(`parameter:${index} ${t.userName}`); 394 | r.onSelect = (n) => { 395 | this.settings.username = t.userName; 396 | this.settings.cookie = t.cookie; 397 | this.saveSettings(); 398 | }; 399 | table.addRow(r); 400 | }); 401 | let body = '京东 Ck 缓存成功,根据下标选择相应的 Ck'; 402 | if (this.settings.cookie) { 403 | body += ',或者使用当前选中Ck:' + this.settings.username; 404 | } 405 | this.notify(this.name, body); 406 | table.present(false); 407 | } catch (e) { 408 | console.log(e); 409 | } 410 | } 411 | } 412 | 413 | // @组件代码结束 414 | // await Runing(Widget, "", false); // 正式环境 415 | await Runing(Widget, '', false); //远程开发环境 416 | -------------------------------------------------------------------------------- /Scripts/JDWuLiu.js: -------------------------------------------------------------------------------- 1 | // Variables used by Scriptable. 2 | // These must be at the very top of the file. Do not edit. 3 | // icon-color: teal; icon-glyph: truck; 4 | 5 | // 添加require,是为了vscode中可以正确引入包,以获得自动补全等功能 6 | if (typeof require === 'undefined') require = importModule; 7 | const { DmYY, Runing } = require('./DmYY'); 8 | 9 | // @组件代码开始 10 | class Widget extends DmYY { 11 | constructor(arg) { 12 | super(arg); 13 | this.name = '京东物流'; 14 | this.en = 'JDWuLiu'; 15 | this.JDRun(module.filename, args); 16 | } 17 | 18 | JDCookie = { 19 | cookie: '', 20 | userName: '', 21 | }; 22 | CookiesData = []; 23 | orderList = []; 24 | 25 | opts = { 26 | headers: { 27 | Accept: `*/*`, 28 | Connection: `keep-alive`, 29 | Host: `wq.jd.com`, 30 | 'Accept-Language': 'zh-cn', 31 | 'Accept-Encoding': 'gzip, deflate, br', 32 | 'User-Agent': `Mozilla/5.0 (iPhone; CPU iPhone OS 14_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.1 Mobile/15E148 Safari/604.1`, 33 | }, 34 | }; 35 | 36 | init = async () => { 37 | try { 38 | this.opts.headers.Cookie = this.JDCookie.cookie; 39 | this.orderList = await this.getOrderList(); 40 | } catch (e) { 41 | console.log(e); 42 | } 43 | }; 44 | 45 | getOrderList = async () => { 46 | const url = 47 | 'https://wq.jd.com/bases/orderlist/list?order_type=2&start_page=1&page_size=10'; 48 | const request = new Request(''); 49 | request.url = url; 50 | request.headers = { 51 | ...this.opts.headers, 52 | Referer: `https://wqs.jd.com/order/orderlist_merge.shtml?sceneval=2&orderType=waitReceipt`, 53 | }; 54 | const response = await request.loadJSON(); 55 | let data = []; 56 | try { 57 | data = response.orderList.filter((item) => { 58 | return ( 59 | item.stateInfo.stateCode === '15' || item.stateInfo.stateCode === '9' 60 | ); 61 | }); 62 | } catch (e) { 63 | console.log(e); 64 | } 65 | // 判断数据是否为空(加载失败) 66 | if (!data) { 67 | // 判断是否有缓存 68 | if (Keychain.contains(this.CACHE_KEY)) { 69 | let cache = Keychain.get(this.CACHE_KEY); 70 | return JSON.parse(cache); 71 | } else { 72 | // 刷新 73 | return []; 74 | } 75 | } 76 | // 存储缓存 77 | Keychain.set(this.CACHE_KEY, JSON.stringify(data)); 78 | return data; 79 | }; 80 | 81 | setListCell = async (cell, data) => { 82 | const { 83 | productList = [], 84 | orderDetailLink = '', 85 | progressInfo = {}, 86 | stateInfo, 87 | } = data; 88 | const product = productList[0]; 89 | let body = cell.addStack(); 90 | body.url = orderDetailLink; 91 | if (this.widgetFamily !== 'small') { 92 | const imageView = body.addStack(); 93 | imageView.size = new Size(75, 75); 94 | imageView.cornerRadius = 5; 95 | imageView.url = product.skuLink; 96 | imageView.backgroundImage = await this.$request.get(product.image, 'IMG'); 97 | body.addSpacer(10); 98 | } 99 | 100 | const textView = body.addStack(); 101 | textView.url = orderDetailLink; 102 | textView.layoutVertically(); 103 | 104 | const descText = textView.addText(progressInfo.content); 105 | descText.font = Font.boldSystemFont(14); 106 | descText.textColor = this.widgetColor; 107 | descText.lineLimit = 2; 108 | 109 | textView.addSpacer(); 110 | 111 | const stackDesc = textView.addStack(); 112 | const timerText = stackDesc.addText(progressInfo.tip); 113 | timerText.font = Font.lightSystemFont(12); 114 | timerText.textColor = this.widgetColor; 115 | // timerText.lineLimit = 1; 116 | stackDesc.addSpacer(); 117 | const statusText = stackDesc.addText(stateInfo.stateName); 118 | statusText.font = Font.lightSystemFont(12); 119 | timerText.textColor = this.widgetColor; 120 | timerText.lineLimit = 1; 121 | 122 | textView.addSpacer(10); 123 | 124 | cell.addSpacer(10); 125 | return cell; 126 | }; 127 | 128 | setWidget = async (body) => { 129 | body.url = 130 | 'https://wqs.jd.com/order/orderlist_merge.shtml?sceneval=2&orderType=waitReceipt'; 131 | const container = body.addStack(); 132 | container.layoutVertically(); 133 | if (!this.orderList.length) { 134 | if (this.widgetFamily !== 'small') { 135 | const bg = await this.$request.get( 136 | 'https://gitee.com/scriptableJS/Scriptable/raw/master/JDWuLiu/cart.png', 137 | 'IMG', 138 | ); 139 | const cartView = container.addStack(); 140 | if (this.widgetFamily === 'large') { 141 | cartView.size = new Size(285, 150); 142 | } else { 143 | cartView.size = new Size(285, 50); 144 | } 145 | bg.imageSize = new Size(75, 50); 146 | cartView.addImage(bg); 147 | } 148 | let textItem = container.addStack(); 149 | if (this.widgetFamily !== 'small') textItem.size = new Size(300, 20); 150 | textItem.addText('空空如也'); 151 | textItem.textColor = this.widgetColor; 152 | textItem.font = Font.boldSystemFont(15); 153 | textItem.lineLimit = 1; 154 | body.addSpacer(); 155 | return body; 156 | } 157 | for (let index = 0; index < this.orderList.length; index++) { 158 | if (this.widgetFamily !== 'large' && index === 1) { 159 | return body; 160 | } 161 | if (index === 3) { 162 | return body; 163 | } 164 | const data = this.orderList[index]; 165 | let listItem = container.addStack(); 166 | await this.setListCell(listItem, data); 167 | container.addSpacer(10); 168 | } 169 | body.addSpacer(); 170 | return body; 171 | }; 172 | 173 | renderSmall = async (w) => { 174 | return await this.setWidget(w); 175 | }; 176 | 177 | renderLarge = async (w) => { 178 | return await this.setWidget(w); 179 | }; 180 | 181 | renderMedium = async (w) => { 182 | return await this.setWidget(w); 183 | }; 184 | 185 | /** 186 | * 渲染函数,函数名固定 187 | * 可以根据 this.widgetFamily 来判断小组件尺寸,以返回不同大小的内容 188 | */ 189 | async render() { 190 | await this.init(); 191 | const widget = new ListWidget(); 192 | await this.getWidgetBackgroundImage(widget); 193 | const header = widget.addStack(); 194 | if (this.widgetFamily !== 'small') { 195 | await this.renderJDHeader(header); 196 | } else { 197 | await this.renderHeader(header, this.logo, this.name, this.widgetColor); 198 | } 199 | widget.addSpacer(20); 200 | if (this.widgetFamily === 'medium') { 201 | return await this.renderMedium(widget); 202 | } else if (this.widgetFamily === 'large') { 203 | return await this.renderLarge(widget); 204 | } else { 205 | return await this.renderSmall(widget); 206 | } 207 | } 208 | 209 | renderJDHeader = async (header) => { 210 | header.centerAlignContent(); 211 | await this.renderHeader(header, this.logo, this.name, this.widgetColor); 212 | header.addSpacer(); 213 | const headerMore = header.addStack(); 214 | headerMore.url = 'https://home.m.jd.com/myJd/home.action'; 215 | headerMore.setPadding(1, 10, 1, 10); 216 | headerMore.cornerRadius = 10; 217 | headerMore.backgroundColor = new Color('#fff', 0.5); 218 | const textItem = headerMore.addText(this.JDCookie.userName); 219 | textItem.font = Font.boldSystemFont(12); 220 | textItem.textColor = this.widgetColor; 221 | textItem.lineLimit = 1; 222 | textItem.rightAlignText(); 223 | return header; 224 | }; 225 | 226 | jdWebView = async () => { 227 | const webView = new WebView(); 228 | const url = 229 | 'https://mcr.jd.com/credit_home/pages/index.html?btPageType=BT&channelName=024'; 230 | await webView.loadURL(url); 231 | await webView.present(false); 232 | const req = new Request( 233 | 'https://ms.jr.jd.com/gw/generic/bt/h5/m/firstScreenNew', 234 | ); 235 | req.method = 'POST'; 236 | req.body = 237 | 'reqData={"clientType":"ios","clientVersion":"13.2.3","deviceId":"","environment":"3"}'; 238 | await req.loadJSON(); 239 | const cookies = req.response.cookies; 240 | const account = { username: '', cookie: '' }; 241 | const cookie = []; 242 | cookies.forEach((item) => { 243 | const value = `${item.name}=${item.value}`; 244 | if (item.name === 'pt_key') cookie.push(value); 245 | if (item.name === 'pt_pin') { 246 | account.username = item.value; 247 | cookie.push(value); 248 | } 249 | }); 250 | account.cookie = cookie.join('; '); 251 | console.log(account); 252 | 253 | if (account.cookie) { 254 | this.settings = { ...this.settings, ...account }; 255 | this.saveSettings(false); 256 | console.log(`${this.name}: cookie获取成功,请关闭窗口!`); 257 | this.notify(this.name, 'cookie获取成功,请关闭窗口!'); 258 | } 259 | }; 260 | 261 | JDRun = (filename, args) => { 262 | if (config.runsInApp) { 263 | this.registerAction('基础设置', this.setWidgetConfig); 264 | this.registerAction('账号设置', async () => { 265 | const index = await this.generateAlert('设置账号信息', [ 266 | '网站登录', 267 | '手动输入', 268 | ]); 269 | if (index === 0) { 270 | await this.jdWebView(); 271 | } else { 272 | await this.setAlertInput('账号设置', '京东账号 Ck', { 273 | username: '昵称', 274 | cookie: 'Cookie', 275 | }); 276 | } 277 | }); 278 | this.registerAction('代理缓存', this.actionSettings); 279 | } 280 | let _md5 = this.md5(filename + this.en); 281 | this.CACHE_KEY = `cache_${_md5}`; 282 | this.JDindex = 283 | typeof args.widgetParameter === 'string' 284 | ? parseInt(args.widgetParameter) 285 | : false; 286 | this.logo = 287 | 'https://raw.githubusercontent.com/Orz-3/mini/master/Color/jd.png'; 288 | try { 289 | const cookieData = this.settings.cookieData; 290 | if (this.JDindex !== false && cookieData[this.JDindex]) { 291 | this.JDCookie = cookieData[this.JDindex]; 292 | } else { 293 | this.JDCookie.userName = this.settings.username; 294 | this.JDCookie.cookie = this.settings.cookie; 295 | } 296 | if (!this.JDCookie.cookie) throw '京东 CK 获取失败'; 297 | this.JDCookie.userName = decodeURI(this.JDCookie.userName); 298 | return true; 299 | } catch (e) { 300 | this.notify('错误提示', e); 301 | return false; 302 | } 303 | }; 304 | 305 | // 加载京东 Ck 节点列表 306 | _loadJDCk = async () => { 307 | try { 308 | const CookiesData = await this.getCache('CookiesJD'); 309 | this.CookiesData = []; 310 | if (CookiesData) { 311 | this.CookiesData = this.transforJSON(CookiesData); 312 | } 313 | const CookieJD = await this.getCache('CookieJD'); 314 | if (CookieJD) { 315 | const userName = CookieJD.match(/pt_pin=(.+?);/)[1]; 316 | const ck1 = { 317 | cookie: CookieJD, 318 | userName, 319 | }; 320 | this.CookiesData.push(ck1); 321 | } 322 | const Cookie2JD = await this.getCache('CookieJD2'); 323 | if (Cookie2JD) { 324 | const userName = Cookie2JD.match(/pt_pin=(.+?);/)[1]; 325 | const ck2 = { 326 | cookie: Cookie2JD, 327 | userName, 328 | }; 329 | this.CookiesData.push(ck2); 330 | } 331 | return true; 332 | } catch (e) { 333 | console.log(e); 334 | this.CookiesData = []; 335 | return false; 336 | } 337 | }; 338 | 339 | async actionSettings() { 340 | try { 341 | const table = new UITable(); 342 | if (!(await this._loadJDCk())) throw 'BoxJS 数据读取失败'; 343 | // 如果是节点,则先远程获取 344 | this.settings.cookieData = this.CookiesData; 345 | this.saveSettings(false); 346 | this.CookiesData.map((t, index) => { 347 | const r = new UITableRow(); 348 | r.addText(`parameter:${index} ${t.userName}`); 349 | r.onSelect = (n) => { 350 | this.settings.username = t.userName; 351 | this.settings.cookie = t.cookie; 352 | this.saveSettings(); 353 | }; 354 | table.addRow(r); 355 | }); 356 | let body = '京东 Ck 缓存成功,根据下标选择相应的 Ck'; 357 | if (this.settings.cookie) { 358 | body += ',或者使用当前选中Ck:' + this.settings.username; 359 | } 360 | this.notify(this.name, body); 361 | table.present(false); 362 | } catch (e) { 363 | console.log(e); 364 | } 365 | } 366 | } 367 | 368 | // @组件代码结束 369 | // await Runing(Widget, "", false); // 正式环境 370 | await Runing(Widget, '', false); //远程开发环境 371 | -------------------------------------------------------------------------------- /Scripts/Oild.js: -------------------------------------------------------------------------------- 1 | // Variables used by Scriptable. 2 | // These must be at the very top of the file. Do not edit. 3 | // icon-color: deep-gray; icon-glyph: oil; 4 | 5 | // 添加require,是为了vscode中可以正确引入包,以获得自动补全等功能 6 | if (typeof require === 'undefined') require = importModule; 7 | const { DmYY, Runing } = require('./DmYY'); 8 | 9 | const enumConfig = { 10 | 89: '汽油', 11 | 92: '汽油', 12 | 95: '汽油', 13 | 98: '汽油', 14 | 0: '柴油', 15 | }; 16 | 17 | const provinces = [ 18 | '北京', 19 | '天津', 20 | '上海', 21 | '重庆', 22 | '河北', 23 | '山西', 24 | '辽宁', 25 | '吉林', 26 | '黑龙江', 27 | '江苏', 28 | '浙江', 29 | '安徽', 30 | '福建', 31 | '江西', 32 | '山东', 33 | '河南', 34 | '湖北', 35 | '湖南', 36 | '广东', 37 | '海南', 38 | '四川', 39 | '贵州', 40 | '云南', 41 | '陕西', 42 | '甘肃', 43 | '青海', 44 | '台湾', 45 | '内蒙古', 46 | '广西', 47 | '西藏', 48 | '宁夏', 49 | '新疆', 50 | '香港', 51 | '澳门', 52 | ]; 53 | 54 | const squareColor = '#8165AC'; 55 | const processColor = [`#7517F8`, `#E323FF`]; 56 | const processBarColor = [`#4da1ff`, `#4dffdf`]; 57 | const processBarBgColor = '#5A5A89'; 58 | 59 | // @组件代码开始 60 | class Widget extends DmYY { 61 | constructor(arg) { 62 | super(arg); 63 | this.en = 'oilWidget'; 64 | this.name = '油价'; 65 | if (config.runsInApp) { 66 | this.registerAction( 67 | '油价设置', 68 | () => { 69 | return this.setAlertInput('油价设置', '设置类型,多个英文逗号分割', { 70 | oilNumber: '92,95,89,0', 71 | }); 72 | }, 73 | { name: 'oilcan', color: '#E64C57' } 74 | ); 75 | 76 | this.registerAction({ 77 | icon: { name: 'location.circle', color: '#8BE39D' }, 78 | type: 'switch', 79 | title: '自动定位', 80 | val: 'location', 81 | }); 82 | 83 | this.registerAction({ 84 | icon: { name: 'mappin.and.ellipse.circle', color: '#5BBFF6' }, 85 | type: 'select', 86 | title: '油价省份', 87 | val: 'province', 88 | placeholder: '地区', 89 | options: provinces, 90 | }); 91 | this.registerAction('基础设置', this.setWidgetConfig); 92 | } 93 | } 94 | 95 | dataSource = { 96 | DIM_ID: '', 97 | DIM_DATE: '', 98 | CITYNAME: '', 99 | V0: 0, 100 | V95: 0, 101 | V92: 0, 102 | V89: 0, 103 | ZDE0: 0, 104 | ZDE92: 0, 105 | ZDE95: 0, 106 | ZDE89: 0, 107 | QE0: 0, 108 | QE92: 0, 109 | QE95: 0, 110 | QE89: 0, 111 | }; 112 | 113 | init = async () => { 114 | try { 115 | this.oilNumber = `${this.settings.oilNumber || '92'}`.split(','); 116 | const oilNumber = []; 117 | this.oilNumber.forEach((item) => { 118 | if (item && ['92', '95', '89', '0'].includes(item)) { 119 | oilNumber.push(item); 120 | } 121 | }); 122 | this.oilNumber = oilNumber; 123 | if (!this.oilNumber.length) 124 | return this.notify(this.name, '请设置油价型号:92,95,89,0'); 125 | } catch (error) { 126 | return console.log('请设置正确的油价'); 127 | } 128 | this.province = this.settings.province; 129 | this.location = this.settings.location; 130 | 131 | if (this.settings.dataSource) { 132 | this.dataSource = this.settings.dataSource[0]; 133 | } else { 134 | await this.cacheData(); 135 | } 136 | this.cacheData(); 137 | }; 138 | 139 | cacheData = async () => { 140 | try { 141 | await this.getOilPrice(); 142 | } catch (e) { 143 | console.log(e); 144 | } 145 | }; 146 | 147 | getOilPrice = async () => { 148 | if (this.location) { 149 | const location = await Location.current(); 150 | const locationText = await Location.reverseGeocode( 151 | location.latitude, 152 | location.longitude 153 | ); 154 | const { administrativeArea = '' } = locationText[0] || {}; 155 | this.province = administrativeArea.replace('省', ''); 156 | this.settings.province = this.province; 157 | this.saveSettings(false); 158 | } 159 | 160 | if (!this.province) return this.notify(this.name, '请设置油价省份!!'); 161 | console.log(`当前省份:${this.province}`); 162 | this.province = `(CITYNAME="${this.province}")`; 163 | const time = Date.now(); 164 | const url = `https://datacenter-web.eastmoney.com/api/data/v1/get?reportName=RPTA_WEB_YJ_JH&columns=ALL&filter=${encodeURIComponent( 165 | this.province 166 | )}&sortColumns=DIM_DATE&sortTypes=-1&pageNumber=1&pageSize=6&source=WEB&_=${time}`; 167 | 168 | const options = { url }; 169 | const response = await this.$request.post(options); 170 | console.log(response); 171 | if (response.result) { 172 | this.dataSource = response.result.data[0]; 173 | this.settings.dataSource = response.result.data; 174 | this.saveSettings(false); 175 | } 176 | }; 177 | 178 | renderImage = async (uri) => { 179 | return this.$request.get(uri, 'IMG'); 180 | }; 181 | 182 | notSupport(w) { 183 | const stack = w.addStack(); 184 | stack.addText('暂不支持'); 185 | return w; 186 | } 187 | 188 | renderSmall = async (w) => { 189 | const headerStack = w.addStack(); 190 | const dollarImage = SFSymbol.named(`yensign.circle`).image; 191 | headerStack.centerAlignContent(); 192 | const dollarWidgetImg = headerStack.addImage(dollarImage); 193 | dollarWidgetImg.tintColor = new Color('#f5222d'); 194 | dollarWidgetImg.imageSize = new Size(24, 24); 195 | 196 | headerStack.addSpacer(); 197 | 198 | w.addSpacer(); 199 | const topStack = w.addStack(); 200 | const topLStack = topStack.addStack(); 201 | topLStack.layoutVertically(); 202 | topLStack.addSpacer(); 203 | topLStack.bottomAlignContent(); 204 | const oilPrice = (this.dataSource[`V${this.oilNumber[0]}`] || '').toFixed( 205 | 2 206 | ); 207 | const timer = (this.dataSource.DIM_DATE.split(' ')[0] || '').split('-'); 208 | const oilNumText = topLStack.addText(`${oilPrice}`); 209 | oilNumText.textColor = this.widgetColor; 210 | oilNumText.minimumScaleFactor = 0.6; 211 | oilNumText.font = Font.boldSystemFont(38); 212 | topLStack.addSpacer(); 213 | 214 | const oilStatus = this.dataSource[`ZDE${this.oilNumber[0]}`] > 0; 215 | const oilZdeImage = SFSymbol.named( 216 | oilStatus ? 'arrow.up' : 'arrow.up' 217 | ).image; 218 | topStack.addSpacer(); 219 | const topRStack = topStack.addStack(); 220 | topRStack.addSpacer(); 221 | topRStack.layoutVertically(); 222 | topRStack.bottomAlignContent(); 223 | const zdeStack = topRStack.addStack(); 224 | zdeStack.setPadding(2, 6, 0, 0); 225 | const oilZdeWidgetImg = zdeStack.addImage(oilZdeImage); 226 | oilZdeWidgetImg.tintColor = new Color(oilStatus ? '#f5222d' : '#a0d911'); 227 | oilZdeWidgetImg.imageSize = new Size(16, 16); 228 | 229 | const timerText = topRStack.addText(`${timer[2]}/${timer[1]}`); 230 | timerText.textColor = this.widgetColor; 231 | timerText.font = Font.systemFont(12); 232 | topRStack.addSpacer(); 233 | 234 | w.addSpacer(); 235 | 236 | const bottomStack = w.addStack(); 237 | bottomStack.addSpacer(); 238 | const rightText = bottomStack.addText( 239 | `${this.oilNumber[0]}#${enumConfig[this.oilNumber[0]]}` 240 | ); 241 | oilNumText.textColor = this.widgetColor; 242 | rightText.font = Font.boldSystemFont(18); 243 | rightText.textOpacity = 0.3; 244 | rightText.rightAlignText(); 245 | bottomStack.addSpacer(5); 246 | return w; 247 | }; 248 | 249 | rowData = (w, oilNumber) => { 250 | const oilPrice = (this.dataSource[`V${oilNumber}`] || '').toFixed(2); 251 | const oilZde = (this.dataSource[`ZDE${oilNumber}`] || '').toFixed(2); 252 | const oilType = enumConfig[oilNumber] || ''; 253 | 254 | const colStack = w.addStack(); 255 | 256 | const oilNumberStack = colStack.addStack(); 257 | const colSize = new Size(40, 40); 258 | oilNumberStack.size = colSize; 259 | oilNumberStack.cornerRadius = 8; 260 | oilNumberStack.borderWidth = 4; 261 | oilNumberStack.borderColor = new Color(squareColor); 262 | oilNumberStack.centerAlignContent(); 263 | this.provideText(`${oilNumber}`, oilNumberStack, { 264 | font: 'bold', 265 | size: 26, 266 | color: new Color(squareColor), 267 | }); 268 | 269 | colStack.addSpacer(7); 270 | 271 | const oilInfoStack = colStack.addStack(); 272 | oilInfoStack.size = new Size(65, colSize.height); 273 | oilInfoStack.layoutVertically(); 274 | oilInfoStack.addSpacer(); 275 | this.provideText(`#${oilType}`, oilInfoStack, { 276 | font: 'light', 277 | size: 12, 278 | color: this.widgetColor, 279 | opacity: 0.5, 280 | }); 281 | 282 | oilInfoStack.addSpacer(2); 283 | 284 | this.provideText(`${oilPrice}`, oilInfoStack, { 285 | font: 'medium', 286 | size: 18, 287 | color: this.widgetColor, 288 | }); 289 | oilInfoStack.addSpacer(); 290 | 291 | const processStack = colStack.addStack(); 292 | processStack.centerAlignContent(); 293 | const processVerWidth = 10; 294 | 295 | let maxCount = 0; 296 | const oilHistory = this.settings.dataSource.map((item) => { 297 | const value = 298 | (item[`ZDE${oilNumber}`] / item[`QE${oilNumber}`]).toFixed(2) * 100; 299 | if (maxCount < value) maxCount = value; 300 | return value; 301 | }); 302 | 303 | maxCount = maxCount * 1.5; 304 | 305 | oilHistory.forEach((item) => { 306 | const processItemStack = processStack.addStack(); 307 | processItemStack.size = new Size(processVerWidth, colSize.height); 308 | processItemStack.cornerRadius = processVerWidth / 2; 309 | processItemStack.backgroundColor = new Color(processBarBgColor); 310 | if (item > 0) processItemStack.addSpacer(); 311 | processItemStack.layoutVertically(); 312 | 313 | const itemBarStack = processItemStack.addStack(); 314 | itemBarStack.cornerRadius = processItemStack.cornerRadius; 315 | itemBarStack.size = new Size( 316 | processVerWidth, 317 | colSize.height * (Math.abs(item) / maxCount) 318 | ); 319 | itemBarStack.backgroundGradient = this.gradient(processColor); 320 | if (item < 0) processItemStack.addSpacer(); 321 | processStack.addSpacer(); 322 | }); 323 | 324 | colStack.addSpacer(); 325 | 326 | const oilZdeStack = colStack.addStack(); 327 | 328 | const oilZdeSize = new Size(80, 10); 329 | 330 | oilZdeStack.layoutVertically(); 331 | oilZdeStack.size = new Size(oilZdeSize.width, colSize.height); 332 | oilZdeStack.centerAlignContent(); 333 | 334 | const oilZdeValueStack = oilZdeStack.addStack(); 335 | oilZdeValueStack.centerAlignContent(); 336 | oilZdeValueStack.addSpacer(); 337 | this.provideText(`${oilZde > 0 ? '+' : ''} ${oilZde}`, oilZdeValueStack, { 338 | font: 'light', 339 | size: 14, 340 | color: this.widgetColor, 341 | }); 342 | 343 | const oilZdeImage = SFSymbol.named( 344 | oilZde > 0 ? 'arrow.up' : 'arrow.down' 345 | ).image; 346 | 347 | oilZdeValueStack.addSpacer(10); 348 | 349 | const oilZdeWidgetImg = oilZdeValueStack.addImage(oilZdeImage); 350 | oilZdeWidgetImg.tintColor = new Color(oilZde > 0 ? '#f5222d' : '#a0d911'); 351 | oilZdeWidgetImg.imageSize = new Size(10, 10); 352 | 353 | oilZdeValueStack.addSpacer(); 354 | 355 | oilZdeStack.addSpacer(5); 356 | 357 | const oilZdeValue = Math.abs(parseFloat(oilZde)); 358 | const processBarBgStack = oilZdeStack.addStack(); 359 | 360 | processBarBgStack.cornerRadius = 5; 361 | processBarBgStack.size = oilZdeSize; 362 | processBarBgStack.backgroundColor = new Color(processBarBgColor); 363 | 364 | if (oilZde < 0) processBarBgStack.addSpacer(); 365 | 366 | const processBarStack = processBarBgStack.addStack(); 367 | 368 | const linear = new LinearGradient(); 369 | linear.colors = processBarColor.map((item) => new Color(item)); 370 | linear.locations = [0, 0.5]; 371 | linear.startPoint = new Point(0, 0); 372 | linear.endPoint = new Point(1, 1); 373 | 374 | processBarStack.backgroundGradient = linear; 375 | processBarStack.cornerRadius = oilZdeSize.height / 2; 376 | processBarStack.size = new Size( 377 | parseInt(oilZdeSize.width * oilZdeValue), 378 | oilZdeSize.height 379 | ); 380 | 381 | if (oilZde > 0) processBarBgStack.addSpacer(); 382 | }; 383 | 384 | renderLarge = async (w) => { 385 | return this.notSupport(w); 386 | }; 387 | 388 | renderBorder = (stack) => { 389 | stack.borderWidth = 1; 390 | }; 391 | 392 | gradient = (color, config = { locations: [0, 0.5] }) => { 393 | const linear = new LinearGradient(); 394 | linear.colors = color.map((item) => new Color(item)); 395 | linear.locations = config.locations; 396 | return linear; 397 | }; 398 | 399 | renderMedium = async (w) => { 400 | w.addSpacer(); 401 | this.oilNumber.forEach((oilNumber, index) => { 402 | if (index > 2) return; 403 | this.rowData(w, oilNumber); 404 | w.addSpacer(); 405 | }); 406 | 407 | return w; 408 | }; 409 | 410 | /** 411 | * 渲染函数,函数名固定 412 | * 可以根据 this.widgetFamily 来判断小组件尺寸,以返回不同大小的内容 413 | */ 414 | async render() { 415 | await this.init(); 416 | const widget = new ListWidget(); 417 | await this.getWidgetBackgroundImage(widget); 418 | if (this.widgetFamily === 'medium') { 419 | return await this.renderMedium(widget); 420 | } else if (this.widgetFamily === 'large') { 421 | return await this.renderLarge(widget); 422 | } else { 423 | return await this.renderSmall(widget); 424 | } 425 | } 426 | } 427 | 428 | // @组件代码结束 429 | await Runing(Widget, '', false); //远程开发环境 430 | -------------------------------------------------------------------------------- /Scripts/PoisonCalendar.js: -------------------------------------------------------------------------------- 1 | // Variables used by Scriptable. 2 | // These must be at the very top of the file. Do not edit. 3 | // icon-color: yellow; icon-glyph: calendar-alt; 4 | 5 | // 添加require,是为了vscode中可以正确引入包,以获得自动补全等功能 6 | if (typeof require === 'undefined') require = importModule; 7 | const { DmYY, Runing } = require('./DmYY'); 8 | const { Calendar } = require('./Calendar'); 9 | const $calendar = new Calendar(); 10 | 11 | // @组件代码开始 12 | class Widget extends DmYY { 13 | constructor(arg) { 14 | super(arg); 15 | this.name = '毒汤日历'; 16 | this.en = 'PoisonCalendar'; 17 | this.Run(); 18 | } 19 | 20 | cookie = ''; 21 | date = new Date(); 22 | format = new DateFormatter(); 23 | 24 | userInfo = {}; 25 | dataSource = []; 26 | baseUrl = 'http://www.dutangapp.cn'; 27 | 28 | init = async () => { 29 | try { 30 | await this.getUserInfo(); 31 | await this.getDaysInfo(); 32 | } catch (e) { 33 | console.log(e); 34 | } 35 | }; 36 | 37 | getUserInfo = async () => { 38 | try { 39 | const url = `${this.baseUrl}/u/wx_login?code=&os=iOS&unid=${this.cookie}&version=3.5.2`; 40 | const response = await this.$request.get(url); 41 | if (response.code === 0) { 42 | console.log('✅用户信息获取成功'); 43 | const { data } = response; 44 | this.userInfo = data; 45 | } else { 46 | console.log('❌用户信息获取失败'); 47 | } 48 | } catch (e) { 49 | console.log('❌用户信息获取失败' + e); 50 | } 51 | }; 52 | 53 | getDaysInfo = async () => { 54 | try { 55 | const today = `${this.date.getFullYear()}-${ 56 | this.date.getMonth() + 1 57 | }-${this.date.getDate()}`; 58 | const url = `${this.baseUrl}/u/v2/days_info?days=${today}`; 59 | console.log(url); 60 | const response = await this.$request.get(url); 61 | if (response.code === 0) { 62 | console.log(`✅今日${this.name}获取成功`); 63 | const { data } = response; 64 | this.format.dateFormat = 'YYYY-MM-dd'; 65 | const key = this.format.string(this.date); 66 | this.dataSource = data[key].toxicList; 67 | } else { 68 | console.log(`❌今日${this.name}获取成功`); 69 | } 70 | } catch (e) { 71 | console.log(`❌今日${this.name}获取成功` + e); 72 | } 73 | }; 74 | 75 | setAvatar = async (stack) => { 76 | stack.size = new Size(50, 50); 77 | stack.cornerRadius = 5; 78 | const imgLogo = await this.$request.get(this.userInfo.avatar, 'IMG'); 79 | const imgLogoItem = stack.addImage(imgLogo); 80 | imgLogoItem.imageSize = new Size(50, 50); 81 | return stack; 82 | }; 83 | 84 | setTitleStack = (stack) => { 85 | const textFormatNumber = this.textFormat.title; 86 | textFormatNumber.color = this.backGroundColor; 87 | const title = this.userInfo.nick; 88 | textFormatNumber.size = 89 | title.length > 20 || this.widgetFamily === 'small' ? 16 : 20; 90 | const titleItem = this.provideText(title, stack, textFormatNumber); 91 | titleItem.lineLimit = 1; 92 | }; 93 | 94 | setPathStack = (stack) => { 95 | const textFormatNumber = this.textFormat.defaultText; 96 | textFormatNumber.color = new Color('#2481cc'); 97 | textFormatNumber.size = 12; 98 | this.format.dateFormat = 'HH:mm'; 99 | let simpleText = '更新:' + this.format.string(this.date); 100 | this.format.dateFormat = 'YYYY-MM-dd'; 101 | const titleItem = this.provideText(simpleText, stack, textFormatNumber); 102 | titleItem.lineLimit = 1; 103 | }; 104 | 105 | setFooterCell = async (stack) => { 106 | const dayInfos = this.getRandomArrayElements(this.dataSource || [], 1); 107 | const info = dayInfos[0] || {}; 108 | const title = this.textFormat.title; 109 | title.color = this.backGroundColor; 110 | title.size = 12; 111 | const stackContent = stack.addStack(); 112 | stackContent.layoutVertically(); 113 | stackContent.centerAlignContent(); 114 | stackContent.addSpacer(); 115 | this.provideText(info.data, stackContent, title); 116 | stackContent.addSpacer(); 117 | }; 118 | 119 | setCalendar(stack) { 120 | const today = this.format.string(this.date); 121 | const todays = today.split('-'); 122 | const response = $calendar.solar2lunar(todays[0], todays[1], todays[2]); 123 | stack.layoutVertically(); 124 | const stackCalendar = stack.addStack(); 125 | stackCalendar.setPadding(5, 0, 5, 0); 126 | stackCalendar.centerAlignContent(); 127 | stackCalendar.cornerRadius = 4; 128 | stackCalendar.backgroundColor = this.backGroundColor; 129 | const title = this.textFormat.title; 130 | title.color = this.widgetColor; 131 | title.size = 24; 132 | this.provideText(todays[2], stackCalendar, title); 133 | const stackYear = stackCalendar.addStack(); 134 | stackYear.layoutVertically(); 135 | 136 | const text = this.textFormat.defaultText; 137 | text.color = this.widgetColor; 138 | text.size = 8; 139 | 140 | const animal = $calendar.getAnimalZodiacToEmoji(response.Animal); 141 | this.provideText(response.Animal + animal, stackYear, text); 142 | stackYear.addSpacer(2); 143 | this.provideText(response.IMonthCn, stackYear, text); 144 | stackYear.addSpacer(2); 145 | this.provideText(response.IDayCn, stackYear, text); 146 | } 147 | 148 | renderSmall = async (w) => { 149 | return w; 150 | }; 151 | 152 | renderMedium = async (w) => { 153 | const stackBody = w.addStack(); 154 | stackBody.layoutVertically(); 155 | const stackHeader = stackBody.addStack(); 156 | stackHeader.setPadding(5, 10, 5, 10); 157 | stackHeader.cornerRadius = 10; 158 | stackHeader.backgroundColor = this.widgetColor; 159 | 160 | stackHeader.centerAlignContent(); 161 | const stackLeft = stackHeader.addStack(); 162 | await this.setAvatar(stackLeft); 163 | stackHeader.addSpacer(20); 164 | 165 | const stackRight = stackHeader.addStack(); 166 | stackRight.layoutVertically(); 167 | this.setTitleStack(stackRight); 168 | stackRight.addSpacer(5); 169 | this.setPathStack(stackRight); 170 | stackHeader.addSpacer(); 171 | 172 | const stackDay = stackHeader.addStack(); 173 | this.setCalendar(stackDay); 174 | 175 | stackBody.addSpacer(); 176 | 177 | const stackFooter = stackBody.addStack(); 178 | stackFooter.setPadding(10, 0, 10, 0); 179 | stackFooter.cornerRadius = 10; 180 | stackFooter.backgroundColor = this.widgetColor; 181 | stackFooter.addSpacer(); 182 | await this.setFooterCell(stackFooter); 183 | stackFooter.addSpacer(); 184 | return w; 185 | }; 186 | 187 | renderLarge = async (w) => { 188 | return w; 189 | }; 190 | 191 | Run() { 192 | if (config.runsInApp) { 193 | const widgetInitConfig = { 194 | cookie: '@DJT.unid', 195 | }; 196 | this.registerAction('账号设置', async () => { 197 | await this.setAlertInput( 198 | `${this.name}账号`, 199 | '读取 BoxJS 缓存信息', 200 | widgetInitConfig, 201 | ); 202 | }); 203 | this.registerAction('代理缓存', async () => { 204 | await this.setCacheBoxJSData(widgetInitConfig); 205 | }); 206 | this.registerAction('基础设置', this.setWidgetConfig); 207 | } 208 | this.cookie = this.settings.cookie || this.cookie; 209 | } 210 | 211 | /** 212 | * 渲染函数,函数名固定 213 | * 可以根据 this.widgetFamily 来判断小组件尺寸,以返回不同大小的内容 214 | */ 215 | async render() { 216 | await this.init(); 217 | const widget = new ListWidget(); 218 | await this.getWidgetBackgroundImage(widget); 219 | if (this.widgetFamily === 'medium') { 220 | return await this.renderMedium(widget); 221 | } else if (this.widgetFamily === 'large') { 222 | return await this.renderLarge(widget); 223 | } else { 224 | return await this.renderSmall(widget); 225 | } 226 | } 227 | } 228 | 229 | // @组件代码结束 230 | // await Runing(Widget, "", true); // 正式环境 231 | await Runing(Widget, args.widgetParameter, false); //远程开发环境 232 | -------------------------------------------------------------------------------- /Scripts/PoisonCalendarText.js: -------------------------------------------------------------------------------- 1 | // Variables used by Scriptable. 2 | // These must be at the very top of the file. Do not edit. 3 | // icon-color: yellow; icon-glyph: calendar-alt; 4 | 5 | // 添加require,是为了vscode中可以正确引入包,以获得自动补全等功能 6 | if (typeof require === 'undefined') require = importModule; 7 | const { DmYY, Runing } = require('./DmYY'); 8 | const { Calendar } = require('./Calendar'); 9 | const $calendar = new Calendar(); 10 | 11 | // @组件代码开始 12 | class Widget extends DmYY { 13 | constructor(arg) { 14 | super(arg); 15 | this.name = '毒汤日历'; 16 | this.en = 'PoisonCalendar'; 17 | this.Run(); 18 | } 19 | 20 | cookie = ''; 21 | date = new Date(); 22 | format = new DateFormatter(); 23 | 24 | userInfo = {}; 25 | dataSource = []; 26 | baseUrl = 'http://www.dutangapp.cn'; 27 | 28 | init = async () => { 29 | try { 30 | await this.getUserInfo(); 31 | await this.getDaysInfo(); 32 | } catch (e) { 33 | console.log(e); 34 | } 35 | }; 36 | 37 | getUserInfo = async () => { 38 | try { 39 | const url = `${this.baseUrl}/u/wx_login?code=&os=iOS&unid=${this.cookie}&version=3.5.2`; 40 | const response = await this.$request.get(url); 41 | if (response.code === 0) { 42 | console.log('✅用户信息获取成功'); 43 | const { data } = response; 44 | this.userInfo = data; 45 | } else { 46 | console.log('❌用户信息获取失败'); 47 | } 48 | } catch (e) { 49 | console.log('❌用户信息获取失败' + e); 50 | } 51 | }; 52 | 53 | getDaysInfo = async () => { 54 | try { 55 | // this.format.dateFormat = 'YYYY-MM-dd'; 56 | const today = `${this.date.getFullYear()}-${ 57 | this.date.getMonth() + 1 58 | }-${this.date.getDate()}`; 59 | console.log(today); 60 | const url = `${this.baseUrl}/u/v2/days_info?days=${today}`; 61 | const response = await this.$request.get(url); 62 | if (response.code === 0) { 63 | console.log(`✅今日${this.name}获取成功`); 64 | const { data } = response; 65 | this.format.dateFormat = 'YYYY-MM-dd'; 66 | const key = this.format.string(this.date); 67 | this.dataSource = data[key].toxicList; 68 | } else { 69 | console.log(`❌今日${this.name}获取成功`); 70 | } 71 | } catch (e) { 72 | console.log(`❌今日${this.name}获取成功` + e); 73 | } 74 | }; 75 | 76 | setAvatar = async (stack) => { 77 | stack.size = new Size(50, 50); 78 | stack.cornerRadius = 5; 79 | const imgLogo = await this.$request.get(this.userInfo.avatar, 'IMG'); 80 | const imgLogoItem = stack.addImage(imgLogo); 81 | imgLogoItem.imageSize = new Size(50, 50); 82 | return stack; 83 | }; 84 | 85 | setTitleStack = (stack) => { 86 | const textFormatNumber = this.textFormat.title; 87 | textFormatNumber.color = this.backGroundColor; 88 | const title = this.userInfo.nick; 89 | textFormatNumber.size = 90 | title.length > 20 || this.widgetFamily === 'small' ? 16 : 20; 91 | const titleItem = this.provideText(title, stack, textFormatNumber); 92 | titleItem.lineLimit = 1; 93 | }; 94 | 95 | setPathStack = (stack) => { 96 | const textFormatNumber = this.textFormat.defaultText; 97 | textFormatNumber.color = new Color('#2481cc'); 98 | textFormatNumber.size = 12; 99 | this.format.dateFormat = 'HH:mm'; 100 | let simpleText = '更新:' + this.format.string(this.date); 101 | this.format.dateFormat = 'YYYY-MM-dd'; 102 | const titleItem = this.provideText(simpleText, stack, textFormatNumber); 103 | titleItem.lineLimit = 1; 104 | }; 105 | 106 | setFooterCell = async (stack) => { 107 | const dayInfos = this.getRandomArrayElements(this.dataSource || [], 1); 108 | const info = dayInfos[0] || {}; 109 | const title = this.textFormat.title; 110 | title.color = this.backGroundColor; 111 | title.size = 12; 112 | const stackContent = stack.addStack(); 113 | stackContent.layoutVertically(); 114 | stackContent.centerAlignContent(); 115 | stackContent.addSpacer(); 116 | this.provideText(info.data, stackContent, title); 117 | stackContent.addSpacer(); 118 | }; 119 | 120 | setCalendar(stack) { 121 | const today = this.format.string(this.date); 122 | const todays = today.split('-'); 123 | const response = $calendar.solar2lunar(todays[0], todays[1], todays[2]); 124 | stack.layoutVertically(); 125 | const stackCalendar = stack.addStack(); 126 | stackCalendar.setPadding(5, 0, 5, 0); 127 | stackCalendar.centerAlignContent(); 128 | stackCalendar.cornerRadius = 4; 129 | stackCalendar.backgroundColor = this.backGroundColor; 130 | const title = this.textFormat.title; 131 | title.color = this.widgetColor; 132 | title.size = 24; 133 | this.provideText(todays[2], stackCalendar, title); 134 | const stackYear = stackCalendar.addStack(); 135 | stackYear.layoutVertically(); 136 | 137 | const text = this.textFormat.defaultText; 138 | text.color = this.widgetColor; 139 | text.size = 8; 140 | 141 | const animal = $calendar.getAnimalZodiacToEmoji(response.Animal); 142 | this.provideText(response.Animal + animal, stackYear, text); 143 | stackYear.addSpacer(2); 144 | this.provideText(response.IMonthCn, stackYear, text); 145 | stackYear.addSpacer(2); 146 | this.provideText(response.IDayCn, stackYear, text); 147 | } 148 | 149 | renderSmall = async (w) => { 150 | return w; 151 | }; 152 | 153 | renderMedium = async (w) => { 154 | const stackBody = w.addStack(); 155 | 156 | const stackFooter = stackBody.addStack(); 157 | stackFooter.setPadding(10, 0, 10, 0); 158 | stackFooter.cornerRadius = 10; 159 | stackFooter.backgroundColor = this.widgetColor; 160 | stackFooter.addSpacer(); 161 | await this.setFooterCell(stackFooter); 162 | stackFooter.addSpacer(); 163 | return w; 164 | }; 165 | 166 | renderLarge = async (w) => { 167 | return w; 168 | }; 169 | 170 | Run() { 171 | if (config.runsInApp) { 172 | this.registerAction('基础设置', this.setWidgetConfig); 173 | } 174 | } 175 | 176 | /** 177 | * 渲染函数,函数名固定 178 | * 可以根据 this.widgetFamily 来判断小组件尺寸,以返回不同大小的内容 179 | */ 180 | async render() { 181 | await this.init(); 182 | const widget = new ListWidget(); 183 | await this.getWidgetBackgroundImage(widget); 184 | if (this.widgetFamily === 'medium') { 185 | return await this.renderMedium(widget); 186 | } else if (this.widgetFamily === 'large') { 187 | return await this.renderLarge(widget); 188 | } else { 189 | return await this.renderSmall(widget); 190 | } 191 | } 192 | } 193 | 194 | // @组件代码结束 195 | // await Runing(Widget, "", true); // 正式环境 196 | await Runing(Widget, args.widgetParameter, false); //远程开发环境 197 | -------------------------------------------------------------------------------- /Scripts/PriceWidgets.js: -------------------------------------------------------------------------------- 1 | // Variables used by Scriptable. 2 | // These must be at the very top of the file. Do not edit. 3 | // icon-color: deep-blue; icon-glyph: dollar; 4 | 5 | // 添加require,是为了vscode中可以正确引入包,以获得自动补全等功能 6 | if (typeof require === 'undefined') require = importModule; 7 | const { DmYY, Runing } = require('./DmYY'); 8 | 9 | // @组件代码开始 10 | class Widget extends DmYY { 11 | constructor(arg) { 12 | super(arg); 13 | this.en = ' btc'; 14 | this.name = '比特币'; 15 | 16 | if (config.runsInApp) { 17 | this.registerAction({ 18 | icon: { name: 'centsign.circle', color: '#feda31' }, 19 | type: 'input', 20 | title: '比特币种类', 21 | desc: '设置关注种类', 22 | placeholder: 'BTC,ETH,BNB', 23 | val: 'btcType', 24 | }); 25 | this.registerAction('基础设置', this.setWidgetConfig); 26 | } 27 | } 28 | 29 | format = (str) => { 30 | return parseInt(str) >= 10 ? str : `0${str}`; 31 | }; 32 | 33 | endpoint = 'https://api.coingecko.com/api/v3'; 34 | nomicsEndpoint = 'https://api.nomics.com/v1'; 35 | 36 | dataSource = []; 37 | 38 | init = async () => { 39 | if (this.settings.dataSource && !config.runsInApp) { 40 | this.dataSource = this.settings.dataSource; 41 | } else { 42 | await this.cacheData(this.settings.btcType); 43 | } 44 | this.cacheData(this.settings.btcType); 45 | }; 46 | 47 | cacheData = async (params) => { 48 | try { 49 | const ids = await this.transforBtcType(params); 50 | let response = await this.$request.get( 51 | `${this.endpoint}/coins/markets?vs_currency=usd&ids=${ids}`, 52 | 'STRING' 53 | ); 54 | this.dataSource = []; 55 | response = JSON.parse(response); 56 | if (!response.length) response = await this.getAllJson(); 57 | if (ids) { 58 | const idsData = ids.split(','); 59 | idsData.forEach((id) => { 60 | const it = response.find((item) => item.id === id); 61 | if (it && this.dataSource.length < 6) { 62 | this.dataSource.push({ 63 | id: it.id, 64 | name: it.name, 65 | image: it.image, 66 | symbol: it.symbol.toUpperCase(), 67 | current_price: '' + it.current_price, 68 | high_24h: it.high_24h, 69 | low_24h: it.low_24h, 70 | price_change_percentage_24h: it.price_change_percentage_24h, 71 | last_updated: it.last_updated, 72 | }); 73 | } 74 | }); 75 | } else { 76 | response.forEach((it, index) => { 77 | if (index > 5) return; 78 | this.dataSource.push({ 79 | id: it.id, 80 | name: it.name, 81 | image: it.image, 82 | symbol: it.symbol.toUpperCase(), 83 | current_price: '' + it.current_price, 84 | high_24h: it.high_24h, 85 | low_24h: it.low_24h, 86 | price_change_percentage_24h: it.price_change_percentage_24h, 87 | last_updated: it.last_updated, 88 | }); 89 | }); 90 | } 91 | 92 | this.settings.dataSource = this.dataSource; 93 | this.saveSettings(false); 94 | } catch (e) { 95 | console.log(e); 96 | return []; 97 | } 98 | }; 99 | 100 | transforBtcType = async (params) => { 101 | let btcType; 102 | if (params) btcType = params.split(','); 103 | 104 | const btcAll = await this.getAllJson(); 105 | 106 | if (!btcType) 107 | return btcAll 108 | .filter((item, index) => index < 6) 109 | .map((item) => item.id) 110 | .join(','); 111 | 112 | return btcType 113 | .map((item) => { 114 | const result = 115 | btcAll.find((btc) => btc.symbol.toUpperCase() === item) || {}; 116 | return result.id; 117 | }) 118 | .filter((item) => !!item) 119 | .join(','); 120 | }; 121 | 122 | getAllJson = async () => { 123 | const cachePath = this.FILE_MGR.joinPath( 124 | this.FILE_MGR.libraryDirectory(), 125 | `${Script.name()}/datas` 126 | ); 127 | const filename = `${cachePath}/BTC.json`; 128 | if (!this.FILE_MGR.fileExists(cachePath)) 129 | this.FILE_MGR.createDirectory(cachePath, true); 130 | 131 | if (this.FILE_MGR.fileExists(filename)) { 132 | const data = Data.fromFile(`${cachePath}/BTC.json`).toRawString(); 133 | return JSON.parse(data); 134 | } else { 135 | const response = await this.$request.get( 136 | `${this.endpoint}/coins/markets?vs_currency=usd&ids=` 137 | ); 138 | const data = Data.fromString(JSON.stringify(response)); 139 | this.FILE_MGR.write(filename, data); 140 | return response; 141 | } 142 | }; 143 | 144 | renderImage = async (uri) => { 145 | return this.$request.get(uri, 'IMG'); 146 | }; 147 | 148 | notSupport(w) { 149 | const stack = w.addStack(); 150 | stack.addText('暂不支持'); 151 | return w; 152 | } 153 | 154 | getSmallBg = async (url) => { 155 | const webview = new WebView(); 156 | let js = `const canvas = document.createElement('canvas'); 157 | const ctx = canvas.getContext('2d'); 158 | const img = new Image(); 159 | img.crossOrigin = 'anonymous'; 160 | img.onload = () => { 161 | const { width, height } = img 162 | canvas.width = width 163 | canvas.height = height 164 | ctx.globalAlpha = 0.3 165 | ctx.drawImage( 166 | img, 167 | -width / 2 + 50, 168 | -height / 2 + 50, 169 | width, 170 | height 171 | ) 172 | const uri = canvas.toDataURL() 173 | completion(uri); 174 | }; 175 | img.src = 'data:image/png;base64,${Data.fromPNG(url).toBase64String()}'`; 176 | let image = await webview.evaluateJavaScript(js, true); 177 | image = image.replace(/^data\:image\/\w+;base64,/, ''); 178 | return Image.fromData(Data.fromBase64String(image)); 179 | }; 180 | 181 | renderSmall = async (widget) => { 182 | const market = this.dataSource[0] || {}; 183 | 184 | widget.url = `https://www.coingecko.com/en/coins/${market.id}`; 185 | 186 | const image = await this.renderImage(market.image); 187 | const backgroundImg = await this.getSmallBg(image); 188 | widget.backgroundColor = this.backGroundColor; 189 | widget.backgroundImage = backgroundImg; 190 | widget.setPadding(12, 12, 12, 12); 191 | const coin = widget.addText(market.symbol.toUpperCase()); 192 | coin.font = Font.heavySystemFont(24); 193 | coin.textColor = this.widgetColor; 194 | 195 | coin.rightAlignText(); 196 | const name = widget.addText(market.name); 197 | name.font = Font.systemFont(10); 198 | name.textColor = Color.gray(); 199 | name.rightAlignText(); 200 | widget.addSpacer(); 201 | 202 | const trend = widget.addText( 203 | `${market.price_change_percentage_24h.toFixed(2)}%` 204 | ); 205 | trend.font = Font.semiboldSystemFont(16); 206 | trend.textColor = 207 | market.price_change_percentage_24h >= 0 ? Color.green() : Color.red(); 208 | 209 | trend.rightAlignText(); 210 | const price = widget.addText(`$ ${market.current_price}`); 211 | price.font = Font.boldSystemFont(28); 212 | price.textColor = this.widgetColor; 213 | price.rightAlignText(); 214 | price.lineLimit = 1; 215 | price.minimumScaleFactor = 0.1; 216 | const history = widget.addText( 217 | `H: ${market.high_24h}, L: ${market.low_24h}` 218 | ); 219 | history.font = Font.systemFont(10); 220 | history.textColor = Color.gray(); 221 | history.rightAlignText(); 222 | history.lineLimit = 1; 223 | history.minimumScaleFactor = 0.1; 224 | return widget; 225 | }; 226 | 227 | rowCell = async (rowStack, market) => { 228 | rowStack.url = `https://www.coingecko.com/zh/coins/${market.id}`; 229 | rowStack.layoutHorizontally(); 230 | const image = await this.renderImage(market.image); 231 | const iconImage = rowStack.addImage(image); 232 | iconImage.imageSize = new Size(28, 28); 233 | iconImage.cornerRadius = 14; 234 | 235 | rowStack.addSpacer(10); 236 | 237 | const centerStack = rowStack.addStack(); 238 | centerStack.layoutVertically(); 239 | 240 | const topCenterStack = centerStack.addStack(); 241 | topCenterStack.layoutHorizontally(); 242 | 243 | const titleText = topCenterStack.addText(market.symbol); 244 | titleText.textColor = this.widgetColor; 245 | titleText.font = this.provideFont('semibold', 16); 246 | 247 | topCenterStack.addSpacer(); 248 | 249 | const priceText = topCenterStack.addText(`$ ${market.current_price}`); 250 | priceText.textColor = this.widgetColor; 251 | priceText.font = this.provideFont('semibold', 15); 252 | priceText.rightAlignText(); 253 | 254 | const bottomCenterStack = centerStack.addStack(); 255 | bottomCenterStack.layoutHorizontally(); 256 | 257 | const subText = bottomCenterStack.addText(market.name); 258 | subText.textColor = Color.gray(); 259 | subText.font = this.provideFont('semibold', 10); 260 | 261 | bottomCenterStack.addSpacer(); 262 | 263 | const historyText = bottomCenterStack.addText( 264 | `H: ${market.high_24h}, L: ${market.low_24h}` 265 | ); 266 | historyText.textColor = Color.gray(); 267 | historyText.font = this.provideFont('semibold', 10); 268 | historyText.rightAlignText(); 269 | 270 | rowStack.addSpacer(8); 271 | 272 | const rateStack = rowStack.addStack(); 273 | rateStack.size = new Size(72, 28); 274 | rateStack.centerAlignContent(); 275 | rateStack.cornerRadius = 4; 276 | rateStack.backgroundColor = 277 | market.price_change_percentage_24h >= 0 ? Color.green() : Color.red(); 278 | const rateText = rateStack.addText( 279 | (market.price_change_percentage_24h >= 0 ? '+' : '') + 280 | market.price_change_percentage_24h.toFixed(2) + 281 | '%' 282 | ); 283 | rateText.textColor = new Color('#fff', 0.9); 284 | rateText.font = this.provideFont('semibold', 14); 285 | rateText.minimumScaleFactor = 0.01; 286 | rateText.lineLimit = 1; 287 | }; 288 | 289 | renderLarge = async (widget) => { 290 | widget.setPadding(12, 12, 12, 12); 291 | const containerStack = widget.addStack(); 292 | containerStack.layoutVertically(); 293 | for (let index = 0; index < this.dataSource.length; index++) { 294 | const item = this.dataSource[index]; 295 | const rowCellStack = containerStack.addStack(); 296 | await this.rowCell(rowCellStack, item); 297 | if (index !== this.dataSource.length - 1) containerStack.addSpacer(); 298 | } 299 | return widget; 300 | }; 301 | 302 | renderMedium = async (widget) => { 303 | widget.setPadding(12, 12, 12, 12); 304 | const containerStack = widget.addStack(); 305 | containerStack.layoutVertically(); 306 | for (let index = 0; index < this.dataSource.length; index++) { 307 | if (index > 2) return; 308 | const item = this.dataSource[index]; 309 | const rowCellStack = containerStack.addStack(); 310 | await this.rowCell(rowCellStack, item); 311 | if (index !== 2) containerStack.addSpacer(); 312 | } 313 | return widget; 314 | }; 315 | 316 | /** 317 | * 渲染函数,函数名固定 318 | * 可以根据 this.widgetFamily 来判断小组件尺寸,以返回不同大小的内容 319 | */ 320 | async render() { 321 | await this.init(); 322 | const widget = new ListWidget(); 323 | if (this.widgetFamily === 'small') await this.renderSmall(widget); 324 | await this.getWidgetBackgroundImage(widget); 325 | if (this.widgetFamily === 'medium') await this.renderMedium(widget); 326 | if (this.widgetFamily === 'large') await this.renderLarge(widget); 327 | return widget; 328 | } 329 | } 330 | 331 | // @组件代码结束 332 | await Runing(Widget, '', false); //远程开发环境 333 | -------------------------------------------------------------------------------- /Scripts/Telegram.js: -------------------------------------------------------------------------------- 1 | // Variables used by Scriptable. 2 | // These must be at the very top of the file. Do not edit. 3 | // icon-color: pink; icon-glyph: paper-plane; 4 | 5 | // 添加require,是为了vscode中可以正确引入包,以获得自动补全等功能 6 | if (typeof require === 'undefined') require = importModule; 7 | const {DmYY, Runing} = require('./DmYY'); 8 | 9 | // @组件代码开始 10 | class Widget extends DmYY { 11 | constructor(arg) { 12 | super(arg); 13 | this.name = 'Telegram'; 14 | this.en = 'Telegram'; 15 | this.inputValue = arg || 'Durov'; 16 | let _md5 = this.md5(module.filename + this.en); 17 | this.CACHE_KEY = `cache_${_md5}_` + this.inputValue; 18 | this.Run(); 19 | } 20 | 21 | CACHE_KEY; 22 | useBoxJS = false; 23 | dataSource = { 24 | footer: {}, 25 | }; 26 | 27 | init = async () => { 28 | try { 29 | await this.getData(); 30 | } catch (e) { 31 | console.log(e); 32 | } 33 | }; 34 | 35 | getData = async () => { 36 | try { 37 | let data = await this.$request.get( 38 | 'https://t.me/s/' + this.inputValue, 39 | 'STRING', 40 | ); 41 | data = data.match( 42 | /tgme_channel_info_header">(.|\n)+tgme_channel_download_telegram"/, 43 | )[0]; 44 | this.dataSource.logo = data.match(/https.+jpg/)[0]; 45 | this.dataSource.title = data.match( 46 | /header_title">(.+)<\/span>/, 47 | )[1]; 48 | let entities = this.dataSource.title.match(/&#\d{2,3};/g); 49 | if (entities) { 50 | for (let k in entities) { 51 | let rExp = new RegExp(entities[k], 'g'); 52 | this.dataSource.title = this.dataSource.title.replace( 53 | rExp, 54 | this.entityToString(entities[k]), 55 | ); 56 | } 57 | } 58 | let counters = data.match(/counter_value">.+?<\/span>/g); 59 | let type = data.match(/counter_type">.+?<\/span>/g); 60 | counters.forEach((item, index) => { 61 | const value = item.match(/counter_value">(.+?)<\/span>/)[1]; 62 | const key = type[index].match(/counter_type">(.+?)<\/span>/)[1]; 63 | this.dataSource.footer[key] = value; 64 | }); 65 | Keychain.set(this.CACHE_KEY, JSON.stringify(this.dataSource)); 66 | } catch (e) { 67 | if (Keychain.contains(this.CACHE_KEY)) { 68 | this.dataSource = Keychain.get(this.CACHE_KEY); 69 | } 70 | } 71 | }; 72 | 73 | entityToString(entity) { 74 | let entities = entity.split(';'); 75 | entities.pop(); 76 | return entities.map((item) => 77 | String.fromCharCode( 78 | item[2] === 'x' 79 | ? parseInt(item.slice(3), 16) 80 | : parseInt(item.slice(2)), 81 | ), 82 | ).join(''); 83 | } 84 | 85 | setAvatar = async (stack) => { 86 | stack.size = new Size(60, 60); 87 | stack.backgroundColor = this.widgetColor; 88 | stack.cornerRadius = 10; 89 | try { 90 | const {logo} = this.dataSource; 91 | const imgLogo = await this.$request.get(logo, 'IMG'); 92 | const imgLogoItem = stack.addImage(imgLogo); 93 | imgLogoItem.imageSize = new Size(60, 60); 94 | } catch (e) { 95 | console.log(e); 96 | } 97 | return stack; 98 | }; 99 | 100 | setNumberStack = (stack, data) => { 101 | stack.layoutVertically(); 102 | stack.addSpacer(); 103 | const textFormatNumber = this.textFormat.title; 104 | textFormatNumber.color = this.widgetColor; 105 | textFormatNumber.size = 18; 106 | const stackTitle = stack.addStack(); 107 | stackTitle.addSpacer(); 108 | const valueItem = this.provideText( 109 | data.value, 110 | stackTitle, 111 | textFormatNumber, 112 | ); 113 | valueItem.lineLimit = 1; 114 | stackTitle.addSpacer(); 115 | 116 | stack.addSpacer(5); 117 | 118 | const textFormatDesc = this.textFormat.defaultText; 119 | textFormatDesc.color = new Color('#aaaaaa'); 120 | textFormatDesc.size = 10; 121 | const stackDesc = stack.addStack(); 122 | stackDesc.addSpacer(); 123 | const descItem = this.provideText(data.key, stackDesc, textFormatDesc); 124 | descItem.lineLimit = 1; 125 | stackDesc.addSpacer(); 126 | stack.addSpacer(); 127 | 128 | return stack; 129 | }; 130 | 131 | setTitleStack = (stack) => { 132 | const textFormatNumber = this.textFormat.title; 133 | textFormatNumber.color = this.widgetColor; 134 | const title = this.dataSource.title; 135 | textFormatNumber.size = 136 | title.length > 20 || this.widgetFamily === 'small' ? 16 : 20; 137 | const titleItem = this.provideText(title, stack, textFormatNumber); 138 | titleItem.lineLimit = 1; 139 | }; 140 | 141 | setPathStack = (stack) => { 142 | const textFormatNumber = this.textFormat.defaultText; 143 | textFormatNumber.color = new Color('#2481cc'); 144 | textFormatNumber.size = 12; 145 | const titleItem = this.provideText( 146 | `@${this.inputValue}`, 147 | stack, 148 | textFormatNumber, 149 | ); 150 | titleItem.lineLimit = 1; 151 | }; 152 | 153 | renderSmall = async (w) => { 154 | const stackBody = w.addStack(); 155 | stackBody.url = `tg://resolve?domain=${this.inputValue}`; 156 | stackBody.layoutVertically(); 157 | stackBody.addSpacer(); 158 | const stackHeader = stackBody.addStack(); 159 | const stackLeft = stackHeader.addStack(); 160 | await this.setAvatar(stackLeft); 161 | stackHeader.addSpacer(5); 162 | 163 | const stackRight = stackHeader.addStack(); 164 | Object.keys(this.dataSource.footer).forEach((key, index) => { 165 | if (index === 0) { 166 | const value = this.dataSource.footer[key]; 167 | this.setNumberStack(stackRight, {key, value}); 168 | } 169 | }); 170 | 171 | stackBody.addSpacer(); 172 | const stackFooter = stackBody.addStack(); 173 | stackFooter.layoutVertically(); 174 | this.setTitleStack(stackFooter); 175 | stackFooter.addSpacer(5); 176 | this.setPathStack(stackFooter); 177 | stackBody.addSpacer(); 178 | return w; 179 | }; 180 | 181 | renderMedium = async (w) => { 182 | const stackBody = w.addStack(); 183 | stackBody.url = `https://t.me/${this.inputValue}`; 184 | stackBody.layoutVertically(); 185 | stackBody.addSpacer(); 186 | const stackHeader = stackBody.addStack(); 187 | const stackLeft = stackHeader.addStack(); 188 | await this.setAvatar(stackLeft); 189 | stackHeader.addSpacer(20); 190 | 191 | const stackRight = stackHeader.addStack(); 192 | stackRight.layoutVertically(); 193 | stackRight.addSpacer(5); 194 | this.setTitleStack(stackRight); 195 | stackRight.addSpacer(5); 196 | this.setPathStack(stackRight); 197 | 198 | stackBody.addSpacer(); 199 | 200 | const stackFooter = stackBody.addStack(); 201 | Object.keys(this.dataSource.footer).forEach((key) => { 202 | const value = this.dataSource.footer[key]; 203 | const stack = stackFooter.addStack(); 204 | this.setNumberStack(stack, {key, value}); 205 | }); 206 | stackBody.addSpacer(); 207 | return w; 208 | }; 209 | 210 | renderLarge = async (w) => { 211 | return await this.renderMedium(w); 212 | }; 213 | 214 | Run() { 215 | if (config.runsInApp) { 216 | this.registerAction('基础设置', this.setWidgetConfig); 217 | } 218 | } 219 | 220 | /** 221 | * 渲染函数,函数名固定 222 | * 可以根据 this.widgetFamily 来判断小组件尺寸,以返回不同大小的内容 223 | */ 224 | async render() { 225 | await this.init(); 226 | const widget = new ListWidget(); 227 | await this.getWidgetBackgroundImage(widget); 228 | if (this.widgetFamily === 'medium') { 229 | return await this.renderMedium(widget); 230 | } else if (this.widgetFamily === 'large') { 231 | return await this.renderLarge(widget); 232 | } else { 233 | return await this.renderSmall(widget); 234 | } 235 | } 236 | } 237 | 238 | // @组件代码结束 239 | // await Runing(Widget, "", true); // 正式环境 240 | await Runing(Widget, args.widgetParameter); //远程开发环境 241 | -------------------------------------------------------------------------------- /Scripts/YouTube.js: -------------------------------------------------------------------------------- 1 | // Variables used by Scriptable. 2 | // These must be at the very top of the file. Do not edit. 3 | // icon-color: deep-gray; icon-glyph: magnet; 4 | 5 | // 添加require,是为了vscode中可以正确引入包,以获得自动补全等功能 6 | if (typeof require === "undefined") require = importModule; 7 | const { DmYY, Runing } = require("./DmYY"); 8 | 9 | // @组件代码开始 10 | class Widget extends DmYY { 11 | constructor(arg) { 12 | super(arg); 13 | this.name = "YouTube"; 14 | this.en = "YouTube"; 15 | this.inputValue = 16 | arg || "https://m.youtube.com/channel/UCZVThl_MRppEdGUPGjXSSdg"; 17 | this.url = `${this.baseUrl}/c/${this.inputValue}`; 18 | if (this.inputValue.includes("m.youtube.com")) { 19 | this.url = this.inputValue; 20 | } 21 | this.Run(); 22 | } 23 | 24 | useBoxJS = false; 25 | ytInitialData = {}; 26 | videos = []; 27 | baseUrl = "https://m.youtube.com"; 28 | url; 29 | 30 | init = async () => { 31 | try { 32 | await this.getData(); 33 | } catch (e) { 34 | console.log(e); 35 | } 36 | }; 37 | 38 | getData = async () => { 39 | const url = this.url; 40 | 41 | const webView = new WebView(); 42 | await webView.loadURL(url); 43 | const javascript = `completion(ytInitialData||window['ytInitialData']);`; 44 | this.ytInitialData = await webView 45 | .evaluateJavaScript(javascript, true) 46 | .then(async (e) => { 47 | return typeof e === "string" ? JSON.parse(e) : e; 48 | }); 49 | this.getCell(); 50 | }; 51 | 52 | getCell() { 53 | const { contents } = this.ytInitialData; 54 | let videos = []; 55 | Object.keys(contents).forEach((key) => { 56 | const result = contents[key]; 57 | const homeContent = 58 | result.tabs[0].tabRenderer.content.sectionListRenderer.contents; 59 | homeContent.forEach((item) => { 60 | let contents; 61 | if (item.itemSectionRenderer) { 62 | contents = item.itemSectionRenderer.contents; 63 | contents.forEach((video) => { 64 | if (!video.shelfRenderer) return; 65 | const cateVideo = video.shelfRenderer.content; 66 | if (cateVideo) { 67 | const data = []; 68 | Object.keys(cateVideo).forEach((cv) => { 69 | cateVideo[cv].items.forEach((cell) => { 70 | const cellVideo = this.getCellVideo(cell); 71 | if (cellVideo) data.push(cellVideo); 72 | }); 73 | videos = [...videos, ...data]; 74 | }); 75 | } 76 | }); 77 | } else if (item.shelfRenderer) { 78 | contents = item.shelfRenderer; 79 | contents = contents.content.verticalListRenderer.items; 80 | contents.forEach((compactVideoRenderer) => { 81 | const data = this.getCellVideo2(compactVideoRenderer); 82 | if (data) videos.push(data); 83 | }); 84 | } 85 | }); 86 | }); 87 | this.videos = videos; 88 | } 89 | 90 | getCellVideo(data) { 91 | try { 92 | const { gridVideoRenderer } = data; 93 | return { 94 | thumb: gridVideoRenderer.thumbnail.thumbnails[0].url, 95 | title: gridVideoRenderer.title.simpleText, 96 | view: gridVideoRenderer.viewCountText.simpleText, 97 | url: 98 | gridVideoRenderer.navigationEndpoint.commandMetadata 99 | .webCommandMetadata.url, 100 | }; 101 | } catch (e) { 102 | console.log(e); 103 | } 104 | } 105 | 106 | getCellVideo2(data) { 107 | try { 108 | const { compactVideoRenderer } = data; 109 | return { 110 | thumb: compactVideoRenderer.thumbnail.thumbnails[0].url, 111 | title: compactVideoRenderer.title.runs[0].text, 112 | view: compactVideoRenderer.viewCountText.runs[0].text, 113 | url: 114 | compactVideoRenderer.navigationEndpoint.commandMetadata 115 | .webCommandMetadata.url, 116 | }; 117 | } catch (e) { 118 | console.log(e); 119 | } 120 | } 121 | 122 | setAvatar = async (stack) => { 123 | stack.size = new Size(50, 50); 124 | stack.cornerRadius = 5; 125 | const { 126 | metadata: { channelMetadataRenderer }, 127 | } = this.ytInitialData; 128 | const avatar = channelMetadataRenderer.avatar.thumbnails.find( 129 | (item) => item.url 130 | ); 131 | const imgLogo = await this.$request.get(avatar, "IMG"); 132 | const imgLogoItem = stack.addImage(imgLogo); 133 | imgLogoItem.imageSize = new Size(50, 50); 134 | return stack; 135 | }; 136 | 137 | setTitleStack = (stack) => { 138 | const textFormatNumber = this.textFormat.title; 139 | textFormatNumber.color = this.backGroundColor; 140 | const { 141 | metadata: { channelMetadataRenderer }, 142 | } = this.ytInitialData; 143 | const title = channelMetadataRenderer.title; 144 | textFormatNumber.size = 145 | title.length > 20 || this.widgetFamily === "small" ? 16 : 20; 146 | const titleItem = this.provideText(title, stack, textFormatNumber); 147 | titleItem.lineLimit = 1; 148 | }; 149 | 150 | setPathStack = (stack) => { 151 | const textFormatNumber = this.textFormat.defaultText; 152 | textFormatNumber.color = new Color("#2481cc"); 153 | textFormatNumber.size = 12; 154 | const { header } = this.ytInitialData; 155 | let simpleText = ""; 156 | Object.keys(header).forEach((key) => { 157 | const item = header[key]; 158 | if (item.subscriberCountText && !simpleText) { 159 | simpleText = item.subscriberCountText.simpleText; 160 | } 161 | if (!simpleText && item.subscribeButton) { 162 | simpleText = item.subscriberCountText.runs[0].text; 163 | } 164 | }); 165 | const titleItem = this.provideText(simpleText, stack, textFormatNumber); 166 | titleItem.lineLimit = 1; 167 | }; 168 | 169 | setFooterCell = async (stack) => { 170 | const datas = this.getRandomArrayElements(this.videos, 3); 171 | for (let i = 0; i < datas.length; i++) { 172 | if (i === 1) stack.addSpacer(); 173 | const video = datas[i]; 174 | const stackVideo = stack.addStack(); 175 | stackVideo.setPadding(10, 10, 10, 10); 176 | stackVideo.url = this.baseUrl + video.url; 177 | stackVideo.backgroundColor = this.widgetColor; 178 | stackVideo.centerAlignContent(); 179 | stackVideo.layoutVertically(); 180 | const img = await this.$request.get(video.thumb, "IMG"); 181 | stackVideo.backgroundImage = await this.shadowImage(img, "#000", 0.3); 182 | const title = { ...this.textFormat.defaultText }; 183 | title.size = 8; 184 | title.color = new Color("#fff"); 185 | this.provideText(video.title, stackVideo, title); 186 | stackVideo.addSpacer(); 187 | title.color = new Color("#fff", 0.7); 188 | this.provideText(video.view, stackVideo, title); 189 | stackVideo.size = new Size(87, 56); 190 | stackVideo.cornerRadius = 4; 191 | if (i === 1) stack.addSpacer(); 192 | } 193 | }; 194 | 195 | renderSmall = async (w) => { 196 | return w; 197 | }; 198 | 199 | renderMedium = async (w) => { 200 | const stackBody = w.addStack(); 201 | stackBody.url = this.url; 202 | stackBody.layoutVertically(); 203 | const stackHeader = stackBody.addStack(); 204 | stackHeader.setPadding(5, 10, 5, 10); 205 | stackHeader.cornerRadius = 10; 206 | stackHeader.backgroundColor = this.widgetColor; 207 | 208 | stackHeader.centerAlignContent(); 209 | const stackLeft = stackHeader.addStack(); 210 | await this.setAvatar(stackLeft); 211 | stackHeader.addSpacer(20); 212 | 213 | const stackRight = stackHeader.addStack(); 214 | stackRight.layoutVertically(); 215 | this.setTitleStack(stackRight); 216 | stackRight.addSpacer(5); 217 | this.setPathStack(stackRight); 218 | stackHeader.addSpacer(); 219 | stackBody.addSpacer(); 220 | 221 | const stackFooter = stackBody.addStack(); 222 | stackFooter.setPadding(10, 0, 10, 0); 223 | stackFooter.cornerRadius = 10; 224 | stackFooter.backgroundColor = this.widgetColor; 225 | stackFooter.addSpacer(); 226 | await this.setFooterCell(stackFooter); 227 | stackFooter.addSpacer(); 228 | return w; 229 | }; 230 | 231 | renderLarge = async (w) => { 232 | return w; 233 | }; 234 | 235 | Run() { 236 | if (config.runsInApp) { 237 | this.registerAction("基础设置", this.setWidgetConfig); 238 | } 239 | } 240 | 241 | /** 242 | * 渲染函数,函数名固定 243 | * 可以根据 this.widgetFamily 来判断小组件尺寸,以返回不同大小的内容 244 | */ 245 | async render() { 246 | await this.init(); 247 | const widget = new ListWidget(); 248 | await this.getWidgetBackgroundImage(widget); 249 | if (this.widgetFamily === "medium") { 250 | return await this.renderMedium(widget); 251 | } else if (this.widgetFamily === "large") { 252 | return await this.renderLarge(widget); 253 | } else { 254 | return await this.renderSmall(widget); 255 | } 256 | } 257 | } 258 | 259 | // @组件代码结束 260 | // await Runing(Widget, "", true); // 正式环境 261 | await Runing(Widget, args.widgetParameter, false); //远程开发环境 262 | -------------------------------------------------------------------------------- /Scripts/ZXTrains.js: -------------------------------------------------------------------------------- 1 | // Variables used by Scriptable. 2 | // These must be at the very top of the file. Do not edit. 3 | // icon-color: blue; icon-glyph: subway; 4 | 5 | // 说明:只面向qx,loon,surge 用户,请自行添加cookie脚本 6 | // 1.获取 cookie:https://github.com/chavyleung/scripts/tree/master/zxhc 7 | // 2.自动获取待出行列表:https://raw.githubusercontent.com/dompling/Script/master/ZXTrians/ZXTrains.js 按照脚本内容配置 8 | 9 | // 添加require,是为了vscode中可以正确引入包,以获得自动补全等功能 10 | if (typeof require === 'undefined') require = importModule; 11 | const { DmYY, Runing } = require('./DmYY'); 12 | 13 | // @组件代码开始 14 | class Widget extends DmYY { 15 | constructor(arg) { 16 | super(arg); 17 | this.name = '智行火车票'; 18 | this.en = 'ZXTrains'; 19 | this.logo = 20 | 'https://raw.githubusercontent.com/Orz-3/mini/master/Color/zxhc.png'; 21 | this.cacheName = this.md5(`dataSouce_${this.en}`); 22 | if (config.runsInApp) { 23 | this.registerAction('基础设置', this.setWidgetConfig); 24 | } 25 | } 26 | 27 | dataSource = []; 28 | 29 | init = async () => { 30 | try { 31 | this.dataSource = await this.getTrainsList(); 32 | } catch (e) { 33 | console.log(e); 34 | } 35 | }; 36 | 37 | dateToUnixTimestamp(str) { 38 | const dates = new Date(str.replace(/-/g, '/')); 39 | return parseInt(dates.getTime()); 40 | } 41 | 42 | timeAgo(o) { 43 | var n = new Date().getTime(); 44 | var f = n - o; 45 | var bs = f >= 0 ? '前' : '后'; //判断时间点是在当前时间的 之前 还是 之后 46 | f = Math.abs(f); 47 | if (f < 6e4) { 48 | return '刚刚'; 49 | } //小于60秒,刚刚 50 | if (f < 36e5) { 51 | return parseInt(f / 6e4) + '分钟' + bs; 52 | } //小于1小时,按分钟 53 | if (f < 864e5) { 54 | return parseInt(f / 36e5) + '小时' + bs; 55 | } //小于1天按小时 56 | if (f < 2592e6) { 57 | return parseInt(f / 864e5) + '天' + bs; 58 | } //小于1个月(30天),按天数 59 | if (f < 31536e6) { 60 | return parseInt(f / 2592e6) + '个月' + bs; 61 | } //小于1年(365天),按月数 62 | return parseInt(f / 31536e6) + '年' + bs; //大于365天,按年算 63 | } 64 | 65 | async getTrainsList() { 66 | try { 67 | const travels = await this.getCache('@ZXTrains.travels'); 68 | console.log(travels); 69 | if (travels) return travels; 70 | } catch (e) { 71 | console.log('未找到火车票缓存:' + e); 72 | } 73 | return false; 74 | } 75 | 76 | setWidget = async (body) => { 77 | let isNone = true; 78 | try { 79 | for (const item of this.dataSource) { 80 | let { trainFlights, timeDesc } = item.orders[0]; 81 | const data = trainFlights[0]; 82 | const passengerInfos = data.passengerInfos[0]; 83 | const fromDate = this.dateToUnixTimestamp(data.fromTime); 84 | const toDate = this.dateToUnixTimestamp(data.toTime); 85 | const nowDate = parseInt(new Date().getTime()); 86 | if (fromDate - nowDate < 1000 * 60 * 60 * 24 && nowDate < toDate) { 87 | const header = body.addStack(); 88 | this.name = data.title; 89 | await this.renderHeader( 90 | header, 91 | this.logo, 92 | this.name, 93 | this.widgetColor, 94 | ); 95 | body.addSpacer(); 96 | 97 | const container = body.addStack(); 98 | container.url = 'suanya://'; 99 | container.layoutVertically(); 100 | const timeView = container.addStack(); 101 | timeView.setPadding(10, 10, 10, 10); 102 | timeView.backgroundColor = new Color('#1890ff'); 103 | timeView.cornerRadius = 5; 104 | 105 | const left = timeView.addStack(); 106 | left.layoutVertically(); 107 | left.addSpacer(); 108 | const leftTimer = left.addText(data.showFromTime); 109 | leftTimer.font = Font.boldSystemFont(16); 110 | leftTimer.textColor = Color.white(); 111 | left.addSpacer(); 112 | const leftDesc = left.addText(data.fromCityName); 113 | leftDesc.font = Font.lightSystemFont(12); 114 | leftDesc.textColor = Color.white(); 115 | left.addSpacer(); 116 | 117 | timeView.addSpacer(); 118 | 119 | const center = timeView.addStack(); 120 | center.addSpacer(); 121 | center.layoutVertically(); 122 | const image = await this.$request.get(data.trafficIcon, 'IMG'); 123 | const imageView = center.addImage(image); 124 | imageView.imageSize = new Size(40, 40); 125 | center.addSpacer(); 126 | timeView.addSpacer(); 127 | 128 | const right = timeView.addStack(); 129 | right.layoutVertically(); 130 | right.addSpacer(); 131 | const rightTimer = right.addText(data.showToTime); 132 | rightTimer.font = Font.boldSystemFont(16); 133 | rightTimer.textColor = Color.white(); 134 | right.addSpacer(); 135 | const rightDesc = right.addText(data.toCityName); 136 | rightDesc.font = Font.lightSystemFont(12); 137 | rightDesc.textColor = Color.white(); 138 | right.addSpacer(); 139 | 140 | const footerView = container.addStack(); 141 | footerView.setPadding(10, 10, 10, 10); 142 | timeDesc = 143 | nowDate < fromDate 144 | ? `距离发车还有${this.timeAgo(fromDate)}` 145 | : `距离到达还有${this.timeAgo(toDate)}`; 146 | const footerLeftText = footerView.addText(timeDesc); 147 | footerLeftText.font = Font.boldSystemFont(12); 148 | footerLeftText.textColor = this.widgetColor; 149 | footerLeftText.textOpacity = 0.8; 150 | 151 | footerView.addSpacer(); 152 | const footerRightText = footerView.addText( 153 | `${passengerInfos.seatCategory} ${passengerInfos.carriageNo} ${passengerInfos.seatNo} `, 154 | ); 155 | footerRightText.font = Font.boldSystemFont(12); 156 | footerRightText.textColor = this.widgetColor; 157 | footerRightText.textOpacity = 0.8; 158 | 159 | isNone = false; 160 | break; 161 | } 162 | } 163 | } catch (e) { 164 | console.log(e); 165 | } 166 | if (isNone) await this.renderNone(body); 167 | body.addStack(); 168 | return body; 169 | }; 170 | 171 | renderSmall = async (w) => { 172 | return this.renderLarge(w); 173 | }; 174 | 175 | renderLarge = async (w) => { 176 | const header = w.addStack(); 177 | await this.renderHeader(header, this.logo, this.name, this.widgetColor); 178 | w.addSpacer(20); 179 | const text = w.addText('暂不支持'); 180 | text.font = Font.boldSystemFont(20); 181 | text.textColor = this.widgetColor; 182 | w.addSpacer(); 183 | return w; 184 | }; 185 | 186 | renderMedium = async (w) => { 187 | return await this.setWidget(w); 188 | }; 189 | 190 | renderNone = async (widget) => { 191 | const header = widget.addStack(); 192 | await this.renderHeader(header, this.logo, this.name, this.widgetColor); 193 | widget.addSpacer(); 194 | const bodyIcon = await this.$request.get( 195 | 'https://images3.c-ctrip.com/ztrip/img/dcx_HUOCHE.png', 196 | 'IMG', 197 | ); 198 | 199 | const body = widget.addStack(); 200 | body.url = 'suanya://'; 201 | 202 | const container = body.addStack(); 203 | container.layoutVertically(); 204 | const bodyIconView = container.addStack(); 205 | bodyIconView.cornerRadius = 5; 206 | 207 | bodyIconView.addSpacer(); 208 | bodyIconView.backgroundColor = new Color('#1890ff'); 209 | const bodyIconItem = bodyIconView.addImage(bodyIcon); 210 | bodyIconItem.imageSize = new Size(90, 60); 211 | bodyIconView.addSpacer(); 212 | 213 | container.addSpacer(20); 214 | const noneView = container.addStack(); 215 | 216 | noneView.addSpacer(); 217 | const noneText = noneView.addText('暂无未出行行程'); 218 | noneText.font = Font.boldSystemFont(14); 219 | noneText.textColor = this.widgetColor; 220 | noneView.addSpacer(); 221 | 222 | return widget; 223 | }; 224 | 225 | /** 226 | * 渲染函数,函数名固定 227 | * 可以根据 this.widgetFamily 来判断小组件尺寸,以返回不同大小的内容 228 | */ 229 | async render() { 230 | await this.init(); 231 | const widget = new ListWidget(); 232 | await this.getWidgetBackgroundImage(widget); 233 | if (this.widgetFamily === 'medium') { 234 | await this.renderMedium(widget); 235 | } else if (this.widgetFamily === 'large') { 236 | await this.renderLarge(widget); 237 | } else { 238 | await this.renderSmall(widget); 239 | } 240 | return widget; 241 | } 242 | } 243 | 244 | // @组件代码结束 245 | // await Runing(Widget, "", false); // 正式环境 246 | await Runing(Widget, '', false); //远程开发环境 247 | -------------------------------------------------------------------------------- /Scripts/webo.js: -------------------------------------------------------------------------------- 1 | // 2 | // iOS 桌面组件脚本 @「小件件」 3 | // 开发说明:请从 Widget 类开始编写,注释请勿修改 4 | // https://x.im3x.cn 5 | // 6 | 7 | // 添加require,是为了vscode中可以正确引入包,以获得自动补全等功能 8 | 9 | // @组件代码开始 10 | let w = new ListWidget(); 11 | 12 | if (typeof require === 'undefined') require = importModule; // 13 | const { DmYY, Runing } = require('./DmYY'); 14 | 15 | class Widget extends DmYY { 16 | constructor(arg) { 17 | super(arg); 18 | this.name = '微博热搜'; 19 | this.en = 'weiboresou'; 20 | if (config.runsInApp) { 21 | this.registerAction('基础设置', this.setWidgetConfig); 22 | this.registerAction('插件设置', this.actionSetting); 23 | } 24 | } 25 | 26 | actionUrl(name = '', data = '') { 27 | let u = URLScheme.forRunningScript(); 28 | let q = `act=${encodeURIComponent(name)}&data=${encodeURIComponent( 29 | data, 30 | )}&__arg=${encodeURIComponent(this.arg)}&__size=${this.widgetFamily}`; 31 | let result = ''; 32 | if (u.includes('run?')) { 33 | result = `${u}&${q}`; 34 | } else { 35 | result = `${u}?${q}`; 36 | } 37 | return result; 38 | } 39 | 40 | /** 41 | * 渲染小尺寸组件 42 | */ 43 | async renderSmall() { 44 | let res = await this.$request.get( 45 | 'https://m.weibo.cn/api/container/getIndex?containerid=106003%26filter_type%3Drealtimehot', 46 | ); 47 | let data = res['data']['cards'][0]['card_group']; 48 | // 去除第一条 49 | data.shift(); 50 | let topic = data[0]; 51 | console.log(topic); 52 | // 显示数据 53 | 54 | w = await this.renderHeader( 55 | w, 56 | 'https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=2225458401,2104443747&fm=26&gp=0.jpg', 57 | '微博热搜', 58 | ); 59 | let body = w.addStack(); 60 | let txt = body.addText(topic['desc']); 61 | body.addSpacer(); 62 | txt.leftAlignText(); 63 | txt.font = Font.lightSystemFont(14); 64 | txt.textColor = this.widgetColor; 65 | w.addSpacer(); 66 | let footer = w.addStack(); 67 | footer.centerAlignContent(); 68 | let img = footer.addImage(await this.$request.get(topic['pic'], 'IMG')); 69 | img.imageSize = new Size(18, 18); 70 | footer.addSpacer(5); 71 | if (topic['icon']) { 72 | let hot = footer.addImage(await this.$request.get(topic['icon'], 'IMG')); 73 | hot.imageSize = new Size(18, 18); 74 | footer.addSpacer(5); 75 | } 76 | let num = footer.addText(String(topic['desc_extr'])); 77 | num.font = Font.lightSystemFont(10); 78 | num.textOpacity = 0.5; 79 | num.textColor = this.widgetColor; 80 | body.url = topic['scheme']; 81 | return w; 82 | } 83 | /** 84 | * 渲染中尺寸组件 85 | */ 86 | 87 | async renderMedium(count = 4) { 88 | let res = await this.$request.get( 89 | 'https://m.weibo.cn/api/container/getIndex?containerid=106003%26filter_type%3Drealtimehot', 90 | ); 91 | let data = res['data']['cards'][0]['card_group']; 92 | // 去除第一条 93 | data.shift(); 94 | // 显示数据 95 | 96 | w = await this.renderHeader( 97 | w, 98 | 'https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=2225458401,2104443747&fm=26&gp=0.jpg', 99 | '微博热搜', 100 | ); 101 | 102 | // 布局:一行一个,左边顺序排序,中间标题,后边热/新 103 | const body = w.addStack(); 104 | const bodyLeft = body.addStack(); 105 | bodyLeft.layoutVertically(); 106 | for (let i = 0; i < count; i++) { 107 | let topic = data[i]; 108 | let dom = bodyLeft.addStack(); 109 | dom.centerAlignContent(); 110 | let pic = dom.addImage(await this.$request.get(topic['pic'], 'IMG')); 111 | pic.imageSize = new Size(18, 18); 112 | dom.addSpacer(5); 113 | let title = dom.addText(topic['desc']); 114 | title.lineLimit = 1; 115 | title.font = Font.lightSystemFont(14); 116 | title.textColor = this.widgetColor; 117 | dom.addSpacer(5); 118 | if (topic['icon']) { 119 | let iconDom = dom.addStack(); 120 | let icon = iconDom.addImage( 121 | await this.$request.get(topic['icon'], 'IMG'), 122 | ); 123 | icon.imageSize = new Size(18, 18); 124 | } 125 | dom.addSpacer(); 126 | let extr = dom.addText(String(topic['desc_extr'])); 127 | extr.font = Font.lightSystemFont(12); 128 | extr.textColor = this.widgetColor; 129 | extr.textOpacity = 0.6; 130 | dom.url = topic['scheme']; 131 | bodyLeft.addSpacer(5); 132 | } 133 | body.addSpacer(); 134 | w.url = this.actionUrl('setting'); 135 | return w; 136 | } 137 | /** 138 | * 渲染大尺寸组件 139 | */ 140 | async renderLarge() { 141 | return await this.renderMedium(11); 142 | } 143 | 144 | async actionSetting() { 145 | const settings = this.getSettings(); 146 | const arg = settings['type'] || '1'; 147 | let a = new Alert(); 148 | a.title = '打开方式'; 149 | a.message = '点击小组件浏览热点的方式'; 150 | a.addAction((arg === '0' ? '✅ ' : '') + '微博客户端'); 151 | a.addAction((arg === '1' ? '✅ ' : '') + 'Vvebo'); 152 | a.addCancelAction('取消设置'); 153 | let i = await a.presentSheet(); 154 | if (i === -1) return; 155 | this.settings['type'] = String(i); 156 | this.saveSettings(); 157 | } 158 | 159 | async actionOpenUrl(url) { 160 | const settings = this.getSettings(); 161 | if (settings['type'] === '1') { 162 | Safari.openInApp(url, false); 163 | } else { 164 | let k = decodeURIComponent(url).split('q=')[1].split('&')[0]; 165 | Safari.open('vvebo://search?q=' + encodeURIComponent(k)); 166 | } 167 | } 168 | 169 | useBoxJS = false; 170 | 171 | async render() { 172 | await this.getWidgetBackgroundImage(w); 173 | if (this.widgetFamily === 'medium') { 174 | return this.renderMedium(); 175 | } else if (this.widgetFamily === 'large') { 176 | return this.renderLarge(); 177 | } else { 178 | return this.renderSmall(); 179 | } 180 | } 181 | } 182 | 183 | await Runing(Widget); 184 | -------------------------------------------------------------------------------- /Scripts/wsgw.js: -------------------------------------------------------------------------------- 1 | // Variables used by Scriptable. 2 | // These must be at the very top of the file. Do not edit. 3 | // icon-color: deep-gray; icon-glyph: setting; 4 | 5 | // 添加require,是为了vscode中可以正确引入包,以获得自动补全等功能 6 | if (typeof require === 'undefined') require = importModule; 7 | const { DmYY, Runing } = require('./DmYY'); 8 | 9 | /** 10 | * 重写修改自作者 11 | * @channel https://t.me/yqc_123/ 12 | * @feedback https://t.me/NobyDa_Chat 13 | * @author 小白脸|𝐎𝐍𝐙𝟑𝐕 14 | * 15 | * 添加重写:https://raw.githubusercontent.com/dompling/Script/master/wsgw/index.js 16 | * 17 | */ 18 | 19 | const defaultData = { 20 | user: '**', 21 | left: { 22 | dayElePq: [], 23 | balance: 0, 24 | arrearsOfFees: false, 25 | }, 26 | right: { 27 | previousBill: 0, 28 | previousBillRate: 0, 29 | thisYear: 0, 30 | thisYearRate: 0, 31 | }, 32 | update: '', 33 | }; 34 | 35 | // @组件代码开始 36 | class Widget extends DmYY { 37 | constructor(arg) { 38 | super(arg); 39 | this.en = 'wsgw'; 40 | this.name = '网上国网'; 41 | this.userNum = args.widgetParameter || 0; 42 | if (config.runsInApp) { 43 | this.registerAction({ 44 | icon: { name: 'photo.tv', color: '#5A74EF' }, 45 | type: 'color', 46 | title: '左侧背景', 47 | desc: '左侧背景色', 48 | val: 'leftColor', 49 | }); 50 | this.registerAction({ 51 | icon: { name: 'arrow.clockwise', color: '#1890ff' }, 52 | type: 'input', 53 | title: '缓存时间', 54 | desc: '默认3小时 (单位小时),填写方式数字', 55 | placeholder: '3', 56 | val: 'cacheTime', 57 | }); 58 | 59 | this.registerAction({ 60 | icon: { name: 'character.cursor.ibeam', color: '#EC6240' }, 61 | type: 'input', 62 | title: '文字缩放', 63 | desc: '文字缩放比例,值为 0-1', 64 | placeholder: '1', 65 | val: 'scale', 66 | }); 67 | 68 | this.registerAction('基础设置', this.setWidgetConfig); 69 | } 70 | } 71 | 72 | date = new Date(); 73 | day = this.date.getTime(); 74 | 75 | dataSource = { ...defaultData }; 76 | 77 | init = async () => { 78 | console.log(`当前用户下标:${this.userNum}`); 79 | this.cacheTime = (this.settings.cacheTime || 3) * 3600000; 80 | this.scale = parseFloat(this.settings.scale || '1'); 81 | if ( 82 | !this.settings.data || 83 | this.settings.cacheDay + this.cacheTime < this.day 84 | ) { 85 | console.log(`缓存失效,重新获取`); 86 | await this.cacheData(); 87 | } else { 88 | console.log( 89 | `最后更新时间:${new Date(parseInt(this.settings.cacheDay)).toLocaleString()}` 90 | ); 91 | console.log( 92 | `缓存失效时间:${new Date(parseInt(this.settings.cacheDay) + this.cacheTime).toLocaleString()}` 93 | ); 94 | this.dataSource = { ...this.settings.data[this.userNum] }; 95 | if (!this.dataSource.user) await this.cacheData(); 96 | console.log(this.dataSource); 97 | } 98 | }; 99 | 100 | cacheData = async () => { 101 | try { 102 | const response = await this.$request.get( 103 | 'https://api.wsgw-rewrite.com/electricity/bill/all', 104 | { timeoutInterval: 60 } 105 | ); 106 | console.log(response); 107 | this.settings.data = []; 108 | response?.forEach((dataInfo) => { 109 | const dataSource = { 110 | user: '**', 111 | left: { 112 | dayElePq: [], 113 | balance: 0, 114 | arrearsOfFees: false, 115 | }, 116 | right: { 117 | previousBill: 0, 118 | previousBillRate: 0, 119 | thisYear: 0, 120 | thisYearRate: 0, 121 | }, 122 | update: '', 123 | }; 124 | 125 | dataSource.user = dataInfo.userInfo.consName_dst.replaceAll('*', ''); 126 | dataSource.left.balance = parseFloat(dataInfo.eleBill.sumMoney); 127 | dataSource.left.dayElePq = dataInfo.dayElecQuantity.sevenEleList 128 | .filter((item) => item.dayElePq !== '-') 129 | .map((item) => ({ 130 | label: item.day, 131 | value: parseFloat(item.dayElePq), 132 | })); 133 | 134 | dataSource.left.arrearsOfFees = dataInfo.arrearsOfFees; 135 | 136 | dataSource.right.previousBill = parseFloat( 137 | this.last(dataInfo.monthElecQuantity?.mothEleList || []) 138 | ?.monthEleCost || 0 139 | ); 140 | 141 | const oldVal = 142 | this.last(dataInfo.monthElecQuantity?.mothEleList || [], 2) 143 | ?.monthEleCost || 1; 144 | 145 | dataSource.right.previousBillRate = 146 | ((dataSource.right.previousBill - oldVal) / oldVal) * 100; 147 | 148 | dataSource.right.previousBillRate = parseFloat( 149 | dataSource.right.previousBillRate.toFixed(2) 150 | ); 151 | 152 | dataSource.right.thisYear = parseFloat( 153 | dataInfo.monthElecQuantity?.dataInfo?.totalEleCost || 0 154 | ); 155 | 156 | const lastYearVal = dataInfo.lastYearElecQuantity.dataInfo.totalEleCost; 157 | 158 | dataSource.right.thisYearRate = 159 | ((dataSource.right.thisYear - lastYearVal) / lastYearVal) * 100; 160 | 161 | dataSource.right.thisYearRate = parseFloat( 162 | dataSource.right.thisYearRate.toFixed(2) 163 | ); 164 | 165 | dataSource.update = dataInfo.eleBill.date; 166 | this.settings.data.push({ ...dataSource }); 167 | }); 168 | console.log(this.settings.data); 169 | this.dataSource = { ...this.settings.data[this.userNum] }; 170 | this.settings.cacheDay = this.day; 171 | this.saveSettings(false); 172 | } catch (e) { 173 | console.log(`接口数据异常:请检查 BoxJS 重写`); 174 | console.log(e); 175 | } 176 | }; 177 | 178 | last = (data = [], index = 1) => { 179 | return data[data.length - index]; 180 | }; 181 | 182 | renderImage = async (uri) => { 183 | return this.$request.get(uri, 'IMG'); 184 | }; 185 | 186 | notSupport(w) { 187 | const stack = w.addStack(); 188 | stack.addText('暂不支持'); 189 | return w; 190 | } 191 | 192 | barChart() { 193 | return ` 194 | { 195 | "type": "bar", 196 | "data": { 197 | "labels": ${JSON.stringify(this.dataSource.left.dayElePq.map((item) => item.label).reverse())}, 198 | "datasets": [ 199 | { 200 | "label": "Sales", 201 | "data": ${JSON.stringify(this.dataSource.left.dayElePq.map((item) => parseFloat(item.value)).reverse())}, 202 | "backgroundColor": "#fff", 203 | "borderColor": "#fff", 204 | "borderWidth": 1, 205 | "borderRadius": { 206 | "topLeft": 30, 207 | "topRight": 30, 208 | "bottomLeft": 30, // 只为柱状图底部设置圆角 209 | "bottomRight": 30 210 | }, 211 | "barPercentage": 0.8, // 控制柱子的宽度 212 | "categoryPercentage": 0.4, 213 | "borderSkipped": false // 应用自定义的圆角设置 214 | } 215 | ] 216 | }, 217 | "options": { 218 | "plugins": { 219 | "legend": { 220 | "display": false // 隐藏图例 221 | }, 222 | "title": { 223 | "display": false // 隐藏标题 224 | } 225 | }, 226 | "scales": { 227 | "x": { 228 | "display": false // 完全隐藏 X 轴 229 | }, 230 | "y": { 231 | "display": false // 完全隐藏 Y 轴 232 | } 233 | }, 234 | "layout": { 235 | "padding": 0 // 移除图表周围的内边距 236 | } 237 | } 238 | } 239 | `; 240 | } 241 | 242 | createLeft = async (widget) => { 243 | const fontStyle = { 244 | color: new Color('#fff'), 245 | size: 20 * this.scale, 246 | opacity: 0.8, 247 | }; 248 | const leftStack = widget.addStack(); 249 | leftStack.cornerRadius = 10; 250 | leftStack.layoutVertically(); 251 | leftStack.backgroundColor = new Color( 252 | this.settings.leftColor || '#5A74EF', 253 | 0.8 254 | ); 255 | leftStack.setPadding(10, 10, 10, 10); 256 | 257 | const chartStack = leftStack.addStack(); 258 | 259 | const chartImage = await this.renderImage( 260 | `https://quickchart.io/chart?v=4&w=800&h=400&f=png&c=${encodeURIComponent(this.barChart())}` 261 | ); 262 | const chartImageStack = chartStack.addImage(chartImage); 263 | chartImageStack.imageSize = new Size(120, 60); 264 | 265 | leftStack.addSpacer(); 266 | 267 | this.provideText('余额', leftStack, fontStyle); 268 | 269 | const todayStack = leftStack.addStack(); 270 | todayStack.centerAlignContent(); 271 | if (this.dataSource.left.arrearsOfFees) 272 | fontStyle.color = new Color('#f65755'); 273 | 274 | fontStyle.size = 20 * this.scale; 275 | this.provideText('¥ ', todayStack, fontStyle); 276 | 277 | fontStyle.opacity = 1; 278 | const todayUse = this.dataSource.left.balance; 279 | 280 | this.provideText(` ${todayUse.toLocaleString()}`, todayStack, fontStyle); 281 | }; 282 | 283 | createDot = (stack, color) => { 284 | const dotStack = stack.addStack(); 285 | dotStack.setPadding(0, 0, 2, 0); 286 | const dot = dotStack.addStack(); 287 | 288 | dot.size = new Size(10, 10); 289 | dot.backgroundColor = new Color(color); 290 | dot.cornerRadius = 10; 291 | }; 292 | 293 | createCell = (widget, data = { title: '', num: 0, radio: 0 }) => { 294 | const cellStack = widget.addStack(); 295 | cellStack.backgroundColor = new Color('#404045'); 296 | cellStack.setPadding(10, 10, 10, 10); 297 | cellStack.cornerRadius = 10; 298 | cellStack.layoutVertically(); 299 | 300 | const fontStyle = { 301 | color: new Color('#fff'), 302 | size: 14 * this.scale, 303 | opacity: 0.6, 304 | }; 305 | this.provideText(data.title, cellStack, fontStyle); 306 | 307 | const dataStack = cellStack.addStack(); 308 | dataStack.bottomAlignContent(); 309 | 310 | fontStyle.size = 12 * this.scale; 311 | this.provideText('¥ ', dataStack, fontStyle); 312 | 313 | fontStyle.opacity = 1; 314 | fontStyle.size = 20 * this.scale; 315 | this.provideText(` ${data.num.toLocaleString()}`, dataStack, fontStyle); 316 | dataStack.addSpacer(); 317 | 318 | const dotStack = dataStack.addStack(); 319 | this.createDot(dotStack, data.radio > 0 ? '#7EEF8F' : '#ED86A5'); 320 | 321 | fontStyle.size = 12 * this.scale; 322 | this.provideText( 323 | data.radio > 0 ? ` +${data.radio}%` : ` -${Math.abs(data.radio)}%`, 324 | dataStack, 325 | fontStyle 326 | ); 327 | }; 328 | 329 | createRight = async (widget) => { 330 | const rightStack = widget.addStack(); 331 | rightStack.layoutVertically(); 332 | this.createCell(rightStack, { 333 | title: '上期费用', 334 | num: this.dataSource.right.previousBill, 335 | radio: this.dataSource.right.previousBillRate, 336 | }); 337 | rightStack.addSpacer(); 338 | this.createCell(rightStack, { 339 | title: '今年费用', 340 | num: this.dataSource.right.thisYear, 341 | radio: this.dataSource.right.thisYearRate, 342 | }); 343 | }; 344 | 345 | renderSmall = async (w) => { 346 | w.setPadding(10, 10, 10, 10); 347 | await this.createLeft(w); 348 | return w; 349 | }; 350 | 351 | renderMedium = async (w) => { 352 | w.setPadding(10, 10, 10, 10); 353 | const containerStack = w.addStack(); 354 | containerStack.layoutHorizontally(); 355 | await this.createLeft(containerStack); 356 | containerStack.addSpacer(10); 357 | await this.createRight(containerStack); 358 | return w; 359 | }; 360 | 361 | /** 362 | * 渲染函数,函数名固定 363 | * 可以根据 this.widgetFamily 来判断小组件尺寸,以返回不同大小的内容 364 | */ 365 | async render() { 366 | await this.init(); 367 | const widget = new ListWidget(); 368 | widget.url = `com.wsgw.e.zsdl://platformapi/`; 369 | await this.getWidgetBackgroundImage(widget); 370 | if (this.widgetFamily === 'medium') { 371 | return await this.renderMedium(widget); 372 | } else if (this.widgetFamily === 'large') { 373 | return await this.notSupport(widget); 374 | } else { 375 | return await this.renderSmall(widget); 376 | } 377 | } 378 | } 379 | 380 | // @组件代码结束 381 | await Runing(Widget, '0', false); //远程开发环境 382 | -------------------------------------------------------------------------------- /birthdayCountDown/2Ya.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dompling/Scriptable/c00be22b941a22cfdade52cf7851df69214f7595/birthdayCountDown/2Ya.jpg -------------------------------------------------------------------------------- /birthdayCountDown/README.md: -------------------------------------------------------------------------------- 1 | # 生日倒计时 2 | 3 | > 结合 BoxJs 的 🐣 破壳日 🐣 数据使用 4 | 5 | > 项目地址:[https://github.com/dompling/Scriptable/tree/master/birthdayCountDown](https://github.com/dompling/Scriptable/tree/master/birthdayCountDown) 6 | 7 | ## 如何使用 8 | 9 | 1. 引入 Components 中的[Calendar.scriptable](https://raw.githubusercontent.com/dompling/Scriptable/master/birthdayCountDown/Components/Calendar.scriptable) 10 | 2. 引入 [Birthday.scriptable](https://raw.githubusercontent.com/dompling/Scriptable/master/birthdayCountDown/Birthday.scriptable) 11 | 3. 引入 G 大 仓库的[Env.js](https://raw.githubusercontent.com/GideonSenku/Scriptable/master/Env.scriptable) 12 | ![](https://raw.githubusercontent.com/dompling/Scriptable/master/birthdayCountDown/thumb1.jpg) 13 | 14 | ## 特别感谢 15 | 16 | 1. [@GideonSenku](https://github.com/GideonSenku) 提供帮助 17 | -------------------------------------------------------------------------------- /birthdayCountDown/birthday.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dompling/Scriptable/c00be22b941a22cfdade52cf7851df69214f7595/birthdayCountDown/birthday.png -------------------------------------------------------------------------------- /birthdayCountDown/img.GIF: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dompling/Scriptable/c00be22b941a22cfdade52cf7851df69214f7595/birthdayCountDown/img.GIF -------------------------------------------------------------------------------- /birthdayCountDown/thumb1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dompling/Scriptable/c00be22b941a22cfdade52cf7851df69214f7595/birthdayCountDown/thumb1.jpg -------------------------------------------------------------------------------- /extra_install.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "YaYa推荐", 3 | "scriptable": true, 4 | "icon": "https://avatars3.githubusercontent.com/u/23498579?s=460&u=1e87605e4abc4e6ecf3edd8b1d730227f54db4d4&v=4", 5 | "repo": "https://github.com/dompling/Scriptable", 6 | "apps": [ 7 | { 8 | "version": "1.0.0", 9 | "author": "KeiKinn", 10 | "description": "透明贴纸,随机换磁贴图片", 11 | "scriptURL": "https://raw.githubusercontent.com/KeiKinn/StickerOnScreen/main/src/StickerOnScreen.js", 12 | "thumb": "https://img.icons8.com/clouds/344/apple-app-store.png", 13 | "name": "StickerOnScreen", 14 | "title": "透明贴纸", 15 | "html": [ 16 | "作者:@KeiKinn" 17 | ], 18 | "images": [ 19 | "https://raw.githubusercontent.com/KeiKinn/StickerOnScreen/main/img/IMG_3804.JPEG" 20 | ] 21 | }, 22 | { 23 | "version": "1.0.0", 24 | "author": "@Michael Lee", 25 | "description": "根据脚本注释,自行填写 API, 和频道ID", 26 | "scriptURL": "https://raw.githubusercontent.com/zc-nju-med/own_JS/master/YouTube%20Plus.js", 27 | "thumb": "https://www.gstatic.com/youtube/img/branding/favicon/favicon_144x144.png", 28 | "name": "YouTubePlus", 29 | "title": "油管订阅", 30 | "html": [ 31 | "

作者:@Michael Lee

", 32 | "

Michael Lee 大佬博客里一篇文章详细的描述了关于这个小组件开发和使用,有一定英语水平的大佬可以自行摸索,了解了解代码

", 33 | "

博客:iOS Scriptable YouTube widget

", 34 | "

谷歌的 API KEY 需要自行申请,地址:YouTube DataAPI

" 35 | ] 36 | }, 37 | { 38 | "version": "1.0.0", 39 | "author": "@Unknown", 40 | "description": "根据脚本注释自行使用", 41 | "scriptURL": "https://raw.githubusercontent.com/zc-nju-med/own_JS/master/NBA.js", 42 | "thumb": "https://img.icons8.com/nolan/344/basketball.png", 43 | "name": "NBA", 44 | "title": "NBA", 45 | "html": ["

喜欢篮球的,可以看看

"] 46 | }, 47 | { 48 | "version": "1.0.0", 49 | "author": "@三行", 50 | "description": "根据脚本注释自行使用", 51 | "scriptURL": "https://raw.githubusercontent.com/dompling/Scriptable/master/Scripts/webo.js", 52 | "thumb": "https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=2225458401,2104443747&fm=26&gp=0.jpg", 53 | "name": "Webo", 54 | "title": "微博热搜", 55 | "html": [ 56 | "

修改自三行大佬的脚本@三行

" 57 | ] 58 | } 59 | ] 60 | } 61 | -------------------------------------------------------------------------------- /images/count.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dompling/Scriptable/c00be22b941a22cfdade52cf7851df69214f7595/images/count.png -------------------------------------------------------------------------------- /images/ftms.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dompling/Scriptable/c00be22b941a22cfdade52cf7851df69214f7595/images/ftms.png -------------------------------------------------------------------------------- /images/gas-night.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dompling/Scriptable/c00be22b941a22cfdade52cf7851df69214f7595/images/gas-night.png -------------------------------------------------------------------------------- /images/jdk.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dompling/Scriptable/c00be22b941a22cfdade52cf7851df69214f7595/images/jdk.jpg -------------------------------------------------------------------------------- /images/large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dompling/Scriptable/c00be22b941a22cfdade52cf7851df69214f7595/images/large.png -------------------------------------------------------------------------------- /images/medium.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dompling/Scriptable/c00be22b941a22cfdade52cf7851df69214f7595/images/medium.png -------------------------------------------------------------------------------- /images/more.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dompling/Scriptable/c00be22b941a22cfdade52cf7851df69214f7595/images/more.png -------------------------------------------------------------------------------- /images/small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dompling/Scriptable/c00be22b941a22cfdade52cf7851df69214f7595/images/small.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "scriptable-types", 3 | "version": "1.0.0", 4 | "author": "2Ya", 5 | "description": "", 6 | "main": "index.js", 7 | "keywords": [ 8 | "scriptable", 9 | "ios", 10 | "widget" 11 | ], 12 | "license": "ISC", 13 | "devDependencies": { 14 | "@types/scriptable-ios": "^1.6.1", 15 | "prettier": "^3.3.3" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /widget.Install.js: -------------------------------------------------------------------------------- 1 | // Variables used by Scriptable. 2 | // These must be at the very top of the file. Do not edit. 3 | // icon-color: brown; icon-glyph: download; 4 | 5 | // version:'1.0.0'; 6 | 7 | const Files = FileManager.iCloud(); 8 | const RootPath = Files.documentsDirectory(); 9 | 10 | const saveFileName = (fileName) => { 11 | const hasSuffix = fileName.lastIndexOf(".") + 1; 12 | return !hasSuffix ? `${fileName}.js` : fileName; 13 | }; 14 | 15 | const write = (fileName, content) => { 16 | let file = saveFileName(fileName); 17 | const filePath = Files.joinPath(RootPath, file); 18 | Files.writeString(filePath, content); 19 | return true; 20 | }; 21 | 22 | const saveFile = async ({ moduleName, url }) => { 23 | const req = new Request(encodeURI(url)); 24 | const content = await req.loadString(); 25 | write(`${moduleName}`, content); 26 | return true; 27 | }; 28 | 29 | const notify = async (title, body, url, opts = {}) => { 30 | let n = new Notification(); 31 | n = Object.assign(n, opts); 32 | n.title = title; 33 | n.body = body; 34 | if (url) n.openURL = url; 35 | return await n.schedule(); 36 | }; 37 | 38 | const renderTableList = async (data) => { 39 | try { 40 | const table = new UITable(); 41 | // 如果是节点,则先远程获取 42 | const req = new Request(data.subscription); 43 | const subscription = await req.loadJSON(); 44 | const apps = subscription.apps; 45 | apps.forEach((item) => { 46 | const r = new UITableRow(); 47 | r.height = 75; 48 | const imgCell = UITableCell.imageAtURL(item.thumb); 49 | imgCell.centerAligned(); 50 | r.addCell(imgCell); 51 | 52 | const nameCell = UITableCell.text(item.title); 53 | nameCell.centerAligned(); 54 | r.addCell(nameCell); 55 | 56 | const downloadCell = UITableCell.button("下载"); 57 | downloadCell.centerAligned(); 58 | downloadCell.dismissOnTap = true; 59 | downloadCell.onTap = async () => { 60 | if (item.depend) { 61 | try { 62 | for (let i = 0; i < item.depend.length; i++) { 63 | const relyItem = item.depend[i]; 64 | const _isWrite = await saveFile({ 65 | moduleName: relyItem.name, 66 | url: relyItem.scriptURL, 67 | }); 68 | if (_isWrite) { 69 | notify("下载提示", `依赖插件:${relyItem.name}下载/更新成功`); 70 | } 71 | } 72 | } catch (e) { 73 | console.log(e); 74 | } 75 | } 76 | const isWrite = await saveFile({ 77 | moduleName: item.name, 78 | url: item.scriptURL, 79 | }); 80 | if (isWrite) { 81 | notify("下载提示", `插件:${item.title}下载/更新成功`); 82 | } 83 | }; 84 | r.addCell(downloadCell); 85 | table.addRow(r); 86 | }); 87 | table.present(false); 88 | } catch (e) { 89 | console.log(e); 90 | notify("错误提示", "订阅获取失败"); 91 | } 92 | }; 93 | const Run = async () => { 94 | try { 95 | const mainAlert = new Alert(); 96 | mainAlert.title = "组件下载"; 97 | mainAlert.message = "可以自行添加订阅地址"; 98 | const cacheKey = "subscriptionList"; 99 | const render = async () => { 100 | let subscriptionList = []; 101 | if (Keychain.contains(cacheKey)) { 102 | subscriptionList = JSON.parse(Keychain.get(cacheKey)); 103 | } 104 | const _actions = []; 105 | console.log(subscriptionList); 106 | subscriptionList.forEach((item) => { 107 | const { author } = item; 108 | mainAlert.addAction("作者:" + author); 109 | _actions.push(async () => { 110 | await renderTableList(item); 111 | }); 112 | }); 113 | 114 | _actions.push(async () => { 115 | const a = new Alert(); 116 | a.title = "订阅地址"; 117 | a.addTextField( 118 | "URL", 119 | "https://raw.githubusercontent.com/dompling/Scriptable/master/install.json" 120 | ); 121 | a.addAction("确定"); 122 | a.addCancelAction("取消"); 123 | const id = await a.presentAlert(); 124 | if (id === -1) return; 125 | try { 126 | const url = a.textFieldValue(0); 127 | const response = await new Request(url).loadJSON(); 128 | delete response.apps; 129 | const data = []; 130 | let isPush = true; 131 | for (let i in subscriptionList) { 132 | const item = subscriptionList[i]; 133 | if (response.author === item.author) { 134 | isPush = false; 135 | data.push({ ...response, subscription: url }); 136 | } else { 137 | data.push(item); 138 | } 139 | } 140 | if (isPush) data.push({ author: response.author, subscription: url }); 141 | Keychain.set(cacheKey, JSON.stringify(data)); 142 | notify("更新成功", "请重新运行本脚本"); 143 | } catch (e) { 144 | console.log(e); 145 | notify("错误提示", "订阅地址错误,不是一个 JSON 格式"); 146 | } 147 | }); 148 | 149 | mainAlert.addAction("添加订阅"); 150 | mainAlert.addCancelAction("取消操作"); 151 | const _actionsIndex = await mainAlert.presentSheet(); 152 | if (_actions[_actionsIndex]) { 153 | const func = _actions[_actionsIndex]; 154 | await func(); 155 | } 156 | }; 157 | await render(); 158 | } catch (e) { 159 | console.log("缓存读取错误" + e); 160 | } 161 | }; 162 | (async () => { 163 | try { 164 | console.log("🤖自更新开始"); 165 | const modules = { 166 | moduleName: "widget.Install", 167 | url: 168 | "https://raw.githubusercontent.com/dompling/Scriptable/master/widget.Install.js", 169 | }; 170 | const result = await saveFile(modules); 171 | if (result) console.log("🤖自更新成功"); 172 | } catch (e) { 173 | console.log(e); 174 | } 175 | })(); 176 | await Run(); 177 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@types/scriptable-ios@^1.6.1": 6 | version "1.6.1" 7 | resolved "https://registry.npm.taobao.org/@types/scriptable-ios/download/@types/scriptable-ios-1.6.1.tgz#44766b47a0c0c9f92a3c1bf46214288cf3d926f4" 8 | integrity sha1-RHZrR6DAyfkqPBv0YhQojPPZJvQ= 9 | 10 | prettier@^3.3.3: 11 | version "3.3.3" 12 | resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.3.3.tgz#30c54fe0be0d8d12e6ae61dbb10109ea00d53105" 13 | integrity sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew== 14 | --------------------------------------------------------------------------------