// Variables used by Scriptable. // These must be at the very top of the file. Do not edit. // icon-color: purple; icon-glyph: bug; /** * 作者: 2Ya * 版本: 1.0.0 * 更新时间:10/18/2022 * github: https://github.com/dompling/Scriptable */ // @编译时间 1666063791613 const MODULE = module; let __topLevelAwait__ = () => Promise.resolve(); function EndAwait(promiseFunc) { __topLevelAwait__ = promiseFunc }; // src/lib/constants.ts var URLSchemeFrom; (function(URLSchemeFrom2) { URLSchemeFrom2["WIDGET"] = "widget"; })(URLSchemeFrom || (URLSchemeFrom = {})); // src/lib/help.ts function fm() { return FileManager[MODULE.filename.includes("Documents/iCloud~") ? "iCloud" : "local"](); } function setStorageDirectory(dirPath) { return { setStorage(key, value) { const hashKey = hash(key); const filePath = FileManager.local().joinPath(dirPath, hashKey); if (value instanceof Image) { FileManager.local().writeImage(filePath, value); return; } if (value instanceof Data) { FileManager.local().write(filePath, value); } Keychain.set(hashKey, JSON.stringify(value)); }, getStorage(key) { const hashKey = hash(key); const filePath = FileManager.local().joinPath(FileManager.local().libraryDirectory(), hashKey); if (FileManager.local().fileExists(filePath)) { const image = Image.fromFile(filePath); const file = Data.fromFile(filePath); return image ? image : file ? file : null; } if (Keychain.contains(hashKey)) { return JSON.parse(Keychain.get(hashKey)); } else { return null; } }, removeStorage(key) { const hashKey = hash(key); const filePath = FileManager.local().joinPath(FileManager.local().libraryDirectory(), hashKey); if (FileManager.local().fileExists(filePath)) { FileManager.local().remove(hashKey); } if (Keychain.contains(hashKey)) { Keychain.remove(hashKey); } } }; } var setStorage = setStorageDirectory(fm().libraryDirectory()).setStorage; var getStorage = setStorageDirectory(FileManager.local().libraryDirectory()).getStorage; var removeStorage = setStorageDirectory(FileManager.local().libraryDirectory()).removeStorage; var setCache = setStorageDirectory(FileManager.local().temporaryDirectory()).setStorage; var getCache = setStorageDirectory(FileManager.local().temporaryDirectory()).getStorage; var removeCache = setStorageDirectory(FileManager.local().temporaryDirectory()).removeStorage; function useStorage(nameSpace) { const _nameSpace = nameSpace || `${MODULE.filename}`; return { setStorage(key, value) { setStorage(`${_nameSpace}${key}`, value); }, getStorage(key) { return getStorage(`${_nameSpace}${key}`); }, removeStorage(key) { removeStorage(`${_nameSpace}${key}`); } }; } function useSetting(settingFilename) { const isUseICloud = () => { return MODULE.filename.includes("Documents/iCloud~"); }; const generateSettingFileName = () => { return MODULE.filename.match(/[^\/]+$/)?.[0].replace(".js", "") || hash(`settings:${MODULE.filename}`); }; const fileManager = fm(); const settingsFolderPath = fileManager.joinPath(fileManager.documentsDirectory(), "/settings-json"); const _settingFilename = `${settingFilename || generateSettingFileName()}.json`; const settingsPath = fileManager.joinPath(settingsFolderPath, _settingFilename); const isFileExists = async () => { if (!fileManager.fileExists(settingsFolderPath)) { fileManager.createDirectory(settingsFolderPath, true); } if (!fileManager.fileExists(settingsPath)) { await fileManager.writeString(settingsPath, "{}"); return false; } return true; }; const getSetting = async (key) => { const fileExists = await isFileExists(); if (!fileExists) return null; if (isUseICloud()) await fileManager.downloadFileFromiCloud(settingsPath); const json = fileManager.readString(settingsPath); const settings = JSON.parse(json) || {}; return settings[key]; }; const setSetting = async (key, value, notify = true) => { const fileExists = await isFileExists(); if (!fileExists) { await fileManager.writeString(settingsPath, JSON.stringify({ [key]: value })); return; } if (isUseICloud()) await fileManager.downloadFileFromiCloud(settingsPath); const json = fileManager.readString(settingsPath); const settings = JSON.parse(json) || {}; settings[key] = value; await fileManager.writeString(settingsPath, JSON.stringify(settings)); if (notify) await showNotification({title: "消息提示", body: "设置保存成功,稍后刷新组件"}); return settings; }; const clear = async (notify = true) => { await fileManager.writeString(settingsPath, JSON.stringify({})); if (notify) await showNotification({title: "消息提示", body: "设置保存成功,稍后刷新组件"}); }; return {getSetting, setSetting, clear}; } async function request(args2) { const { url, data, header, dataType = "json", method = "GET", timeout = 60 * 1e3, useCache = false, failReturnCache = true } = args2; const cacheKey = `url:${url}`; const cache = getStorage(cacheKey); if (useCache && cache !== null) return cache; const req = new Request(url); req.method = method; header && (req.headers = header); data && (req.body = data); req.timeoutInterval = timeout / 1e3; req.allowInsecureRequest = true; let res; try { switch (dataType) { case "json": res = await req.loadJSON(); break; case "text": res = await req.loadString(); break; case "image": res = await req.loadImage(); break; case "data": res = await req.load(); break; default: res = await req.loadJSON(); } const result = {...req.response, data: res}; setStorage(cacheKey, result); return result; } catch (err) { if (cache !== null && failReturnCache) return cache; return err; } } async function showActionSheet(args2) { const {title, desc, cancelText = "取消", itemList} = args2; const alert = new Alert(); title && (alert.title = title); desc && (alert.message = desc); for (const item of itemList) { if (typeof item === "string") { alert.addAction(item); } else { switch (item.type) { case "normal": alert.addAction(item.text); break; case "warn": alert.addDestructiveAction(item.text); break; default: alert.addAction(item.text); break; } } } alert.addCancelAction(cancelText); const tapIndex = await alert.presentSheet(); return tapIndex; } async function showModal(args2) { const {title, content, showCancel = true, cancelText = "取消", confirmText = "确定", inputItems = []} = args2; const alert = new Alert(); title && (alert.title = title); content && (alert.message = content); showCancel && cancelText && alert.addCancelAction(cancelText); alert.addAction(confirmText); for (const input of inputItems) { const {type = "text", text = "", placeholder = ""} = input; if (type === "password") { alert.addSecureTextField(placeholder, text); } else { alert.addTextField(placeholder, text); } } const tapIndex = await alert.presentAlert(); const texts = inputItems.map((item, index) => alert.textFieldValue(index)); return tapIndex === -1 ? { cancel: true, confirm: false, texts } : { cancel: false, confirm: true, texts }; } async function showNotification(args2) { try { const {title, subtitle = "", body = "", openURL, sound, ...others} = args2; let notification = new Notification(); notification.title = title; notification.subtitle = subtitle; notification.body = body; openURL && (notification.openURL = openURL); sound && (notification.sound = sound); notification = Object.assign(notification, others); return await notification.schedule(); } catch (e) { console.log(e); } } async function getImage(args2) { const {filepath, url, useCache = true} = args2; const generateDefaultImage = async () => { const ctx = new DrawContext(); ctx.size = new Size(100, 100); ctx.setFillColor(Color.red()); ctx.fillRect(new Rect(0, 0, 100, 100)); return await ctx.getImage(); }; try { if (filepath) { return Image.fromFile(filepath) || await generateDefaultImage(); } if (!url) return await generateDefaultImage(); const cacheKey = `image:${url}`; if (useCache) { const cache = getCache(url); if (cache instanceof Image) { return cache; } else { removeCache(cacheKey); } } const res = await request({url, dataType: "image"}); const image = res && res.data; image && setCache(cacheKey, image); return image || await generateDefaultImage(); } catch (err) { return await generateDefaultImage(); } } function hash(string) { let hash2 = 0, i, chr; for (i = 0; i < string.length; i++) { chr = string.charCodeAt(i); hash2 = (hash2 << 5) - hash2 + chr; hash2 |= 0; } return `hash_${hash2}`; } async function setTransparentBackground(tips) { const phoneSizea = { "2778": { small: 510, medium: 1092, large: 1146, left: 96, right: 678, top: 246, middle: 882, bottom: 1518 }, "2532": { small: 474, medium: 1014, large: 1062, left: 78, right: 618, top: 231, middle: 819, bottom: 1407 }, "2688": { small: 507, medium: 1080, large: 1137, left: 81, right: 654, top: 228, middle: 858, bottom: 1488 }, "1792": { small: 338, medium: 720, large: 758, left: 54, right: 436, top: 160, middle: 580, bottom: 1e3 }, "2436": { small: 465, medium: 987, large: 1035, left: 69, right: 591, top: 213, middle: 783, bottom: 1353 }, "2208": { small: 471, medium: 1044, large: 1071, left: 99, right: 672, top: 114, middle: 696, bottom: 1278 }, "1334": { small: 296, medium: 642, large: 648, left: 54, right: 400, top: 60, middle: 412, bottom: 764 }, "1136": { small: 282, medium: 584, large: 622, left: 30, right: 332, top: 59, middle: 399, bottom: 399 }, "1624": { small: 310, medium: 658, large: 690, left: 46, right: 394, top: 142, middle: 522, bottom: 902 }, "2001": { small: 444, medium: 963, large: 972, left: 81, right: 600, top: 90, middle: 618, bottom: 1146 } }; const cropImage = (img2, rect) => { const draw = new DrawContext(); draw.size = new Size(rect.width, rect.height); draw.drawImageAtPoint(img2, new Point(-rect.x, -rect.y)); return draw.getImage(); }; const shouldExit = await showModal({ content: tips || "开始之前,请先前往桌面,截取空白界面的截图。然后回来继续", cancelText: "我已截图", confirmText: "前去截图 >" }); if (!shouldExit.cancel) return; const img = await Photos.fromLibrary(); const imgHeight = img.size.height; const phone = phoneSizea[imgHeight]; if (!phone) { const help4 = await showModal({ content: "好像您选择的照片不是正确的截图,或者您的机型我们暂时不支持。点击确定前往社区讨论", confirmText: "帮助", cancelText: "取消" }); if (help4.confirm) Safari.openInApp("https://support.qq.com/products/287371", false); return; } const sizes = ["小尺寸", "中尺寸", "大尺寸"]; const sizeIndex = await showActionSheet({ title: "你准备用哪个尺寸", itemList: sizes }); const widgetSize = sizes[sizeIndex]; const selectLocation = (positions2) => showActionSheet({ title: "你准备把组件放桌面哪里?", desc: imgHeight == 1136 ? " (备注:当前设备只支持两行小组件,所以下边选项中的「中间」和「底部」的选项是一致的)" : "", itemList: positions2 }); const crop = {w: 0, h: 0, x: 0, y: 0}; let positions; let _positions; let positionIndex; let keys; let key; switch (widgetSize) { case "小尺寸": crop.w = phone.small; crop.h = phone.small; positions = ["左上角", "右上角", "中间左", "中间右", "左下角", "右下角"]; _positions = ["top left", "top right", "middle left", "middle right", "bottom left", "bottom right"]; positionIndex = await selectLocation(positions); keys = _positions[positionIndex].split(" "); crop.y = phone[keys[0]]; crop.x = phone[keys[1]]; break; case "中尺寸": crop.w = phone.medium; crop.h = phone.small; crop.x = phone.left; positions = ["顶部", "中间", "底部"]; _positions = ["top", "middle", "bottom"]; positionIndex = await selectLocation(positions); key = _positions[positionIndex]; crop.y = phone[key]; break; case "大尺寸": crop.w = phone.medium; crop.h = phone.large; crop.x = phone.left; positions = ["顶部", "底部"]; _positions = ["top", "middle"]; positionIndex = await selectLocation(positions); key = _positions[positionIndex]; crop.y = phone[key]; break; } return cropImage(img, new Rect(crop.x, crop.y, crop.w, crop.h)); } // src/lib/jsx-runtime.ts var GenrateView = class { static setListWidget(listWidget) { this.listWidget = listWidget; } static async wbox(props, ...children) { const {background, spacing, href, updateDate, padding, onClick} = props; try { isDefined(background) && await setBackground(this.listWidget, background); isDefined(spacing) && (this.listWidget.spacing = spacing); isDefined(href) && (this.listWidget.url = href); isDefined(updateDate) && (this.listWidget.refreshAfterDate = updateDate); isDefined(padding) && this.listWidget.setPadding(...padding); isDefined(onClick) && runOnClick(this.listWidget, onClick); await addChildren(this.listWidget, children); } catch (err) { console.error(err); } return this.listWidget; } static wstack(props, ...children) { return async (parentInstance) => { const widgetStack = parentInstance.addStack(); const { background, spacing, padding, width = 0, height = 0, borderRadius, borderWidth, borderColor, href, verticalAlign, flexDirection, onClick } = props; try { isDefined(background) && await setBackground(widgetStack, background); isDefined(spacing) && (widgetStack.spacing = spacing); isDefined(padding) && widgetStack.setPadding(...padding); isDefined(borderRadius) && (widgetStack.cornerRadius = borderRadius); isDefined(borderWidth) && (widgetStack.borderWidth = borderWidth); isDefined(borderColor) && (widgetStack.borderColor = getColor(borderColor)); isDefined(href) && (widgetStack.url = href); widgetStack.size = new Size(width, height); const verticalAlignMap = { bottom: () => widgetStack.bottomAlignContent(), center: () => widgetStack.centerAlignContent(), top: () => widgetStack.topAlignContent() }; isDefined(verticalAlign) && verticalAlignMap[verticalAlign](); const flexDirectionMap = { row: () => widgetStack.layoutHorizontally(), column: () => widgetStack.layoutVertically() }; isDefined(flexDirection) && flexDirectionMap[flexDirection](); isDefined(onClick) && runOnClick(widgetStack, onClick); } catch (err) { console.error(err); } await addChildren(widgetStack, children); }; } static wimage(props) { return async (parentInstance) => { const { src, href, resizable, width = 0, height = 0, opacity, borderRadius, borderWidth, borderColor, containerRelativeShape, filter, imageAlign, mode, onClick } = props; let _image = src; typeof src === "string" && isUrl(src) && (_image = await getImage({url: src})); typeof src === "string" && !isUrl(src) && (_image = SFSymbol.named(src).image); const widgetImage = parentInstance.addImage(_image); widgetImage.image = _image; try { isDefined(href) && (widgetImage.url = href); isDefined(resizable) && (widgetImage.resizable = resizable); widgetImage.imageSize = new Size(width, height); isDefined(opacity) && (widgetImage.imageOpacity = opacity); isDefined(borderRadius) && (widgetImage.cornerRadius = borderRadius); isDefined(borderWidth) && (widgetImage.borderWidth = borderWidth); isDefined(borderColor) && (widgetImage.borderColor = getColor(borderColor)); isDefined(containerRelativeShape) && (widgetImage.containerRelativeShape = containerRelativeShape); isDefined(filter) && (widgetImage.tintColor = getColor(filter)); const imageAlignMap = { left: () => widgetImage.leftAlignImage(), center: () => widgetImage.centerAlignImage(), right: () => widgetImage.rightAlignImage() }; isDefined(imageAlign) && imageAlignMap[imageAlign](); const modeMap = { fit: () => widgetImage.applyFittingContentMode(), fill: () => widgetImage.applyFillingContentMode() }; isDefined(mode) && modeMap[mode](); isDefined(onClick) && runOnClick(widgetImage, onClick); } catch (err) { console.error(err); } }; } static wspacer(props) { return async (parentInstance) => { const widgetSpacer = parentInstance.addSpacer(); const {length} = props; try { isDefined(length) && (widgetSpacer.length = length); } catch (err) { console.error(err); } }; } static wtext(props, ...children) { return async (parentInstance) => { const widgetText = parentInstance.addText(""); const { textColor, font, opacity, maxLine, scale, shadowColor, shadowRadius, shadowOffset, href, textAlign, onClick } = props; if (children && Array.isArray(children)) { widgetText.text = children.join(""); } try { isDefined(textColor) && (widgetText.textColor = getColor(textColor)); isDefined(font) && (widgetText.font = typeof font === "number" ? Font.systemFont(font) : font); isDefined(opacity) && (widgetText.textOpacity = opacity); isDefined(maxLine) && (widgetText.lineLimit = maxLine); isDefined(scale) && (widgetText.minimumScaleFactor = scale); isDefined(shadowColor) && (widgetText.shadowColor = getColor(shadowColor)); isDefined(shadowRadius) && (widgetText.shadowRadius = shadowRadius); isDefined(shadowOffset) && (widgetText.shadowOffset = shadowOffset); isDefined(href) && (widgetText.url = href); const textAlignMap = { left: () => widgetText.leftAlignText(), center: () => widgetText.centerAlignText(), right: () => widgetText.rightAlignText() }; isDefined(textAlign) && textAlignMap[textAlign](); isDefined(onClick) && runOnClick(widgetText, onClick); } catch (err) { console.error(err); } }; } static wdate(props) { return async (parentInstance) => { const widgetDate = parentInstance.addDate(new Date()); const { date, mode, textColor, font, opacity, maxLine, scale, shadowColor, shadowRadius, shadowOffset, href, textAlign, onClick } = props; try { isDefined(date) && (widgetDate.date = date); isDefined(textColor) && (widgetDate.textColor = getColor(textColor)); isDefined(font) && (widgetDate.font = typeof font === "number" ? Font.systemFont(font) : font); isDefined(opacity) && (widgetDate.textOpacity = opacity); isDefined(maxLine) && (widgetDate.lineLimit = maxLine); isDefined(scale) && (widgetDate.minimumScaleFactor = scale); isDefined(shadowColor) && (widgetDate.shadowColor = getColor(shadowColor)); isDefined(shadowRadius) && (widgetDate.shadowRadius = shadowRadius); isDefined(shadowOffset) && (widgetDate.shadowOffset = shadowOffset); isDefined(href) && (widgetDate.url = href); const modeMap = { time: () => widgetDate.applyTimeStyle(), date: () => widgetDate.applyDateStyle(), relative: () => widgetDate.applyRelativeStyle(), offset: () => widgetDate.applyOffsetStyle(), timer: () => widgetDate.applyTimerStyle() }; isDefined(mode) && modeMap[mode](); const textAlignMap = { left: () => widgetDate.leftAlignText(), center: () => widgetDate.centerAlignText(), right: () => widgetDate.rightAlignText() }; isDefined(textAlign) && textAlignMap[textAlign](); isDefined(onClick) && runOnClick(widgetDate, onClick); } catch (err) { console.error(err); } }; } }; function h(type, props, ...children) { props = props || {}; const listWidget = new ListWidget(); GenrateView.setListWidget(listWidget); const _children = flatteningArr(children); switch (type) { case "wbox": return GenrateView.wbox(props, ..._children); case "wdate": return GenrateView.wdate(props); case "wimage": return GenrateView.wimage(props); case "wspacer": return GenrateView.wspacer(props); case "wstack": return GenrateView.wstack(props, ..._children); case "wtext": return GenrateView.wtext(props, ..._children); default: return type instanceof Function ? type({children: _children, ...props}) : null; } } function Fragment({children}) { return children; } function flatteningArr(arr) { return [].concat(...arr.map((item) => { return Array.isArray(item) ? flatteningArr(item) : item; })); } function getColor(color) { return typeof color === "string" ? new Color(color, 1) : color; } async function getBackground(bg) { bg = typeof bg === "string" && !isUrl(bg) || bg instanceof Color ? getColor(bg) : bg; if (typeof bg === "string") { bg = await getImage({url: bg}); } return bg; } async function setBackground(widget, bg) { const _bg = await getBackground(bg); if (_bg instanceof Color) { widget.backgroundColor = _bg; } if (_bg instanceof Image) { widget.backgroundImage = _bg; } if (_bg instanceof LinearGradient) { widget.backgroundGradient = _bg; } } async function addChildren(instance, children) { if (children && Array.isArray(children)) { for (const child of children) { child instanceof Function ? await child(instance) : ""; } } } function isDefined(value) { if (typeof value === "number" && !isNaN(value)) { return true; } return value !== void 0 && value !== null; } function isUrl(value) { const reg = /^(http|https)\:\/\/[\w\W]+/; return reg.test(value); } function runOnClick(instance, onClick) { const _eventId = hash(onClick.toString()); instance.url = `${URLScheme.forRunningScript()}?eventId=${encodeURIComponent(_eventId)}&from=${URLSchemeFrom.WIDGET}`; const {eventId, from} = args.queryParameters; if (eventId && eventId === _eventId && from === URLSchemeFrom.WIDGET) { onClick(); } } // src/Base.tsx var FILE_MGR_LOCAL = fm(); var Base = class { constructor() { this.componentWillMountBefore = async () => { this.backgroundKey = `${this.en}_background`; const {getSetting} = useSetting(this.en); const {getStorage: getStorage2} = useStorage("boxjs"); this.prefix = await getStorage2("prefix") || this.prefix; const fontColorLight = await getSetting("fontColorLight") || this.fontColor; const fontColorDark = await getSetting("fontColorDark") || this.fontColor; this.fontColor = Device.isUsingDarkAppearance() ? fontColorDark : fontColorLight; const backgroundColorLight = await getSetting("backgroundColorLight") || "#fff"; const backgroundColorDark = await getSetting("backgroundColorDark") || "#000"; this.backgroundColor = Device.isUsingDarkAppearance() ? this.getBackgroundColor(backgroundColorDark) : this.getBackgroundColor(backgroundColorLight); const opacityLight = await getSetting("opacityLight") || this.opacity; const opacityDark = await getSetting("opacityDark") || this.opacity; this.opacity = Device.isUsingDarkAppearance() ? opacityDark : opacityLight; typeof this.componentWillMount === "function" && await this.componentWillMount(); }; this.name = "菜单"; this.en = "base"; this.prefix = "boxjs.net"; this.useBoxJS = true; this.BOX_CATCH_KEY = "BoxJSData"; this.backgroundKey = ""; this.render = async () => false; this.componentWillMount = async () => { }; this.componentDidMount = async () => { }; this.fontColor = Device.isUsingDarkAppearance() ? "#fff" : "#000"; this.opacity = Device.isUsingDarkAppearance() ? "0.7" : "0.4"; this.updateInterval = async () => { const {getSetting} = useSetting(this.en); const updateInterval = await getSetting("updateInterval") || "30"; return parseInt(updateInterval) * 1e3 * 60; }; this.previewClick = async (size) => { try { config.widgetFamily = size; const render = async () => { await this.componentDidMount(); return this.render(); }; const w = await render(); const fnc = size.toLowerCase().replace(/( |^)[a-z]/g, (L) => L.toUpperCase()); w && await w[`present${fnc}`](); } catch (e) { console.log(e); } }; this.widgetAction = [ { title: "小尺寸", val: "small", dismissOnSelect: true, onClick: () => this.previewClick("small"), url: "https://z3.ax1x.com/2021/03/26/6v5wIP.png" }, { title: "中尺寸", val: "medium", dismissOnSelect: true, onClick: () => this.previewClick("medium"), url: "https://z3.ax1x.com/2021/03/26/6v5dat.png" }, { title: "大尺寸", val: "large", dismissOnSelect: true, onClick: () => this.previewClick("large"), url: "https://z3.ax1x.com/2021/03/26/6v5BPf.png" } ]; this.baseActions = [ { title: "字体颜色", val: "白天 | 夜间", icon: {name: "sun.max.fill", color: "#d48806"}, onClick: async () => { await this.setLightAndDark("字体颜色", "Hex 颜色", "fontColor"); } }, { title: "背景设置", icon: {name: "photo.on.rectangle", color: "#fa8c16"}, dismissOnSelect: true, onClick: async () => { const actions = [ { title: "白天图", dismissOnSelect: true, icon: {name: "photo.on.rectangle", color: "#fa8c16"}, onClick: async () => { const image = await Photos.fromLibrary(); if (!await this.verifyImage(image)) return; await this.setImage(image, `${this.backgroundKey}_light`); } }, { title: "夜间图", icon: {name: "photo.fill.on.rectangle.fill", color: "#fa541c"}, dismissOnSelect: true, onClick: async () => { const image = await Photos.fromLibrary(); if (!await this.verifyImage(image)) return; await this.setImage(image, `${this.backgroundKey}_night`); } }, { title: "透明度", icon: {name: "record.circle", color: "#722ed1"}, onClick: async () => { return this.setLightAndDark("透明度", false, "opacity"); } }, { title: "背景色", icon: {name: "photo", color: "#13c2c2"}, onClick: async () => { return this.setLightAndDark("背景色", false, "backgroundColor"); } } ]; const table = new UITable(); await this.showActionSheet(table, "背景设置", actions); await table.present(); } }, { title: "透明背景", icon: {name: "text.below.photo", color: "#faad14"}, onClick: async () => { const image = await setTransparentBackground(); image && await this.setImage(image, this.backgroundKey); } }, { title: "清空背景", icon: {name: "clear", color: "#f5222d"}, onClick: async () => { await this.setImage(null, `${this.backgroundKey}_light`); await this.setImage(null, `${this.backgroundKey}_night`); await this.setImage(null, this.backgroundKey); } } ]; this.actions = [ { title: "刷新时间", icon: {name: "arrow.clockwise", color: "#1890ff"}, onClick: async () => { const {getSetting, setSetting} = useSetting(this.en); const updateInterval = await getSetting("updateInterval") || ""; const {texts} = await showModal({ title: "刷新时间", inputItems: [ { placeholder: "刷新时间单位分钟", text: `${updateInterval}` } ] }); await setSetting("updateInterval", texts); } } ]; this.getBackgroundColor = (color) => { const colors = color.split(","); if (colors.length > 0) { const locations = []; const linearColor = new LinearGradient(); const cLen = colors.length; linearColor.colors = colors.map((item, index) => { locations.push(Math.floor((index + 1) / cLen * 100) / 100); return new Color(item, 1); }); linearColor.locations = locations; return linearColor; } return color; }; this.setLightAndDark = async (title, desc, key) => { try { const {getSetting, setSetting} = useSetting(this.en); const light = `${key}Light`, dark = `${key}Dark`; const lightText = await getSetting(light) || ""; const darkText = await getSetting(dark) || ""; const a = new Alert(); a.title = "白天和夜间" + title; a.message = !desc ? "请自行去网站上搜寻颜色(Hex 颜色)" : desc; a.addTextField("白天", lightText); a.addTextField("夜间", darkText); a.addAction("确定"); a.addCancelAction("取消"); const id = await a.presentAlert(); if (id === -1) return; await setSetting(light, a.textFieldValue(0), false); await setSetting(dark, a.textFieldValue(1)); } catch (e) { console.log(e); } }; this.verifyImage = async (img) => { try { const {width, height} = img.size; const direct = true; if (width > 1e3) { const options = ["取消", "打开图像处理"]; const message = "您的图片像素为" + width + " x " + height + "\n请将图片" + (direct ? "宽度" : "高度") + "调整到 1000 以下\n" + (!direct ? "宽度" : "高度") + "自动适应"; const index = await this.generateAlert(message, options); if (index === 1) await Safari.openInApp("https://www.sojson.com/image/change.html", false); return false; } return true; } catch (e) { return false; } }; this.setImage = async (img, key, notify = true) => { const path = FILE_MGR_LOCAL.joinPath(FILE_MGR_LOCAL.documentsDirectory(), "/images"); if (!FILE_MGR_LOCAL.fileExists(path)) FILE_MGR_LOCAL.createDirectory(path, true); const imgPath = FILE_MGR_LOCAL.joinPath(path, `img_${key}.jpg`); if (!img) { if (FILE_MGR_LOCAL.fileExists(imgPath)) FILE_MGR_LOCAL.remove(imgPath); } else { FILE_MGR_LOCAL.writeImage(imgPath, img); } if (notify) await showNotification({title: this.name, body: "设置生效,稍后刷新", sound: "alert"}); }; this.getBackgroundImage = async () => { let result = void 0; const light = `${this.backgroundKey}_light`; const dark = `${this.backgroundKey}_dark`; const isNight = Device.isUsingDarkAppearance(); const path1 = FILE_MGR_LOCAL.joinPath(FILE_MGR_LOCAL.documentsDirectory(), "/images/img_" + light + ".jpg"); const path2 = FILE_MGR_LOCAL.joinPath(FILE_MGR_LOCAL.documentsDirectory(), "/images/img_" + dark + ".jpg"); const path3 = FILE_MGR_LOCAL.joinPath(FILE_MGR_LOCAL.documentsDirectory(), "/images/img_" + this.backgroundKey + ".jpg"); if (!FILE_MGR_LOCAL.fileExists(path3)) { if (isNight) { if (FILE_MGR_LOCAL.fileExists(path1)) { result = Image.fromFile(path1); } else if (FILE_MGR_LOCAL.fileExists(path2)) { result = Image.fromFile(path2); } } else { if (FILE_MGR_LOCAL.fileExists(path2)) { result = Image.fromFile(path2); } else if (FILE_MGR_LOCAL.fileExists(path1)) { result = Image.fromFile(path1); } } } else { result = Image.fromFile(path3); } if (parseFloat(this.opacity) && result) { return this.shadowImage(result, "#000000", parseFloat(this.opacity)); } return result; }; this.registerAction = (title, onClick, icon = { name: "gear", color: "#096dd9" }) => { const url = typeof icon === "string" ? icon : false; const action = {title, onClick}; if (url) { action.url = url; } else { action.icon = icon; } this.actions.splice(1, 0, action); }; this.drawTableIcon = async (icon = "square.grid.2x2", color = "#e8e8e8", cornerWidth = 42) => { const sfi = SFSymbol.named(icon); sfi.applyFont(Font.mediumSystemFont(30)); const imgData = Data.fromPNG(sfi.image).toBase64String(); const html = ` `; const js = ` var canvas = document.createElement("canvas"); var sourceImg = document.getElementById("sourceImg"); var silhouetteImg = document.getElementById("silhouetteImg"); var ctx = canvas.getContext('2d'); var size = sourceImg.width > sourceImg.height ? sourceImg.width : sourceImg.height; canvas.width = size; canvas.height = size; ctx.drawImage(sourceImg, (canvas.width - sourceImg.width) / 2, (canvas.height - sourceImg.height) / 2); var imgData = ctx.getImageData(0, 0, canvas.width, canvas.height); var pix = imgData.data; //convert the image into a silhouette for (var i=0, n = pix.length; i < n; i+= 4){ //set red to 0 pix[i] = 255; //set green to 0 pix[i+1] = 255; //set blue to 0 pix[i+2] = 255; //retain the alpha value pix[i+3] = pix[i+3]; } ctx.putImageData(imgData,0,0); silhouetteImg.src = canvas.toDataURL(); output=canvas.toDataURL() `; const wv = new WebView(); await wv.loadHTML(html); const base64Image = await wv.evaluateJavaScript(js); const iconImage = await new Request(base64Image).loadImage(); const size = new Size(160, 160); const ctx = new DrawContext(); ctx.opaque = false; ctx.respectScreenScale = true; ctx.size = size; const path = new Path(); const rect = new Rect(0, 0, size.width, size.width); path.addRoundedRect(rect, cornerWidth, cornerWidth); path.closeSubpath(); ctx.setFillColor(new Color(color, 1)); ctx.addPath(path); ctx.fillPath(); const rate = 36; const iw = size.width - rate; const x = (size.width - iw) / 2; ctx.drawImageInRect(iconImage, new Rect(x, x, iw, iw)); return ctx.getImage(); }; this.preferences = async (table, arr, outfit) => { const header = new UITableRow(); const heading = header.addText(outfit); heading.titleFont = Font.mediumSystemFont(17); heading.centerAligned(); table.addRow(header); for (const item of arr) { const row = new UITableRow(); row.dismissOnSelect = !!item.dismissOnSelect; if (item.url) { const rowIcon = row.addImageAtURL(item.url); rowIcon.widthWeight = 100; } else { const icon = item.icon || {}; const image = await this.drawTableIcon(icon.name, icon.color, item.cornerWidth); const imageCell = row.addImage(image); imageCell.widthWeight = 100; } const rowTitle = row.addText(item.title); rowTitle.widthWeight = 400; rowTitle.titleFont = Font.systemFont(16); if (item.val) { const valText = row.addText(`${item.val}`.toUpperCase()); valText.widthWeight = 500; valText.rightAligned(); valText.titleColor = Color.blue(); valText.titleFont = Font.mediumSystemFont(16); } else { const imgCell = UITableCell.imageAtURL("https://gitee.com/scriptableJS/Scriptable/raw/master/images/more.png"); imgCell.rightAligned(); imgCell.widthWeight = 500; row.addCell(imgCell); } row.dismissOnSelect = false; if (item.onClick) row.onSelect = () => item.onClick(item, row); table.addRow(row); } table.reload(); }; this.showActionSheet = async (table, title, actions) => { await this.preferences(table, actions, title); }; this.showMenu = async () => { const table = new UITable(); table.showSeparators = true; table.removeAllRows(); const topRow = new UITableRow(); topRow.height = 60; const leftText = topRow.addButton("Github"); leftText.widthWeight = 0.3; leftText.onTap = async () => { await Safari.openInApp("https://github.com/dompling/Scriptable"); }; const centerRow = topRow.addImageAtURL("https://s3.ax1x.com/2021/03/16/6y4oJ1.png"); centerRow.widthWeight = 0.4; centerRow.centerAligned(); centerRow.onTap = async () => { await Safari.open("https://t.me/Scriptable_JS"); }; const rightText = topRow.addButton("重置所有"); rightText.widthWeight = 0.3; rightText.rightAligned(); rightText.onTap = async () => { const options = ["取消", "重置"]; const message = "该操作不可逆,会清空所有组件配置!重置后请重新打开设置菜单。"; const index = await this.generateAlert(message, options); if (index === 0) return; const {clear} = useSetting(this.en); return clear(); }; table.addRow(topRow); await this.preferences(table, this.widgetAction, "组件预览"); await this.preferences(table, this.actions, "组件设置"); await this.preferences(table, this.baseActions, "主题设置"); await table.present(); }; this.getBoxJsCache = async (key) => { try { const url = "http://" + this.prefix + "/query/boxdata"; const boxdata = (await request({url, dataType: "json"})).data; if (key) return boxdata.datas[key]; return boxdata.datas; } catch (e) { console.log(e); return false; } }; this.setCacheBoxJSData = async (opt) => { const options = ["取消", "确定"]; const message = "代理缓存仅支持 BoxJS 相关的代理\nLoon,Qx,Surge"; const index = await this.generateAlert(message, options); if (index === 0) return; try { const boxJSData = await this.getBoxJsCache(); const settings = {}; Object.keys(opt).forEach((key) => { settings[key] = boxJSData[opt[key]] || ""; }); const {setSetting} = useSetting(this.en); await setSetting(this.BOX_CATCH_KEY, settings, false); await showNotification({ title: this.name, body: "缓存读取:" + JSON.stringify(settings), sound: "alert" }); } catch (e) { console.log(e); await showNotification({ title: this.name, body: "BoxJS 缓存读取失败!点击查看相关教程", openURL: "https://chavyleung.gitbook.io/boxjs/awesome/videos", sound: "alert" }); } }; this.showAlertCatchInput = async (title, content, opt, useKey) => { const {getSetting, setSetting} = useSetting(this.en); const catchValue = await getSetting(useKey || this.BOX_CATCH_KEY) || {}; const settings = catchValue; const inputItems = Object.keys(opt).map((key) => { return {placeholder: opt[key], text: catchValue[key]}; }); const {texts, confirm} = await showModal({title, content, inputItems}); Object.keys(opt).map((key, index) => { settings[key] = texts[index]; }); if (confirm) { await setSetting(useKey || this.BOX_CATCH_KEY, settings); return settings; } }; } async init() { await this.componentWillMountBefore(); if (config.runsInApp) { await this.showMenu(); } else { await this.componentDidMount(); const widget = await this.render(); Script.setWidget(widget); Script.complete(); } } async generateAlert(message, options) { const alert = new Alert(); alert.message = message; for (const option of options) { alert.addAction(option); } return await alert.presentAlert(); } shadowImage(img, color = "#000000", opacity) { if (!opacity || opacity === 0) return img; const ctx = new DrawContext(); ctx.size = img.size; ctx.drawImageInRect(img, new Rect(0, 0, img.size["width"], img.size["height"])); ctx.setFillColor(new Color(color, opacity)); ctx.fillRect(new Rect(0, 0, img.size["width"], img.size["height"])); return ctx.getImage(); } }; var Base_default = Base; // src/Component/RowCeneter/index.tsx var RowCenter = ({children, ...props}) => { return /* @__PURE__ */ h("wstack", { ...props }, /* @__PURE__ */ h("wspacer", null), children, /* @__PURE__ */ h("wspacer", null)); }; var RowCeneter_default = RowCenter; // src/pages/COVID-19.tsx var addumFont = 12; var viewColor = "#000"; var Widget = class extends Base_default { constructor() { super(...arguments); this.name = "疫情数据"; this.en = "covid-19"; this.today = ""; this.pinyin = ""; this.baseUrl = `https://api.inews.qq.com/newsqa/v1/query/pubished/daily/list`; this.useBoxJS = false; this.componentWillMount = async () => { this.registerAction("地区拼音", async () => { const options = {py: "尝试首字母或者全拼"}; await this.showAlertCatchInput("地区拼音", "首字母或全部拼音", options, "pinyin"); }); const {getSetting} = useSetting(this.en); this.pinyin = (await getSetting("pinyin") || {}).py || this.pinyin; this.pinyin = this.pinyin.trim(); }; this.componentDidMount = async () => { const dateFormat = new DateFormatter(); dateFormat.dateFormat = "MM.dd"; this.today = dateFormat.string(new Date()); this.location = await this.getLocation(); if (!this.location) return; const {state = ""} = this.location.postalAddress; let {city = ""} = this.location.postalAddress; let province; if (state) { province = state.replace("省", "").replace("市", ""); this.province = await this.getCovid19({province}); } else { province = city.replace("省", "").replace("市", ""); this.province = await this.getCovid19({province}); } if (state && city) { province = state.replace("省", "").replace("市", ""); city = city.replace("省", "").replace("市", ""); this.city = await this.getCovid19({province, city}); } await this.getCovid19ProvinceCompare(); config.widgetFamily === "large" && await this.getCovid19News(); }; this.getLocation = async () => { try { let locationTextValue; const {getSetting, setSetting} = useSetting(this.en); locationTextValue = await getSetting(`location`); console.log(locationTextValue); if (!locationTextValue) { const location = await Location.current(); const locationText = await Location.reverseGeocode(location.latitude, location.longitude); locationTextValue = locationText[0]; if (locationText) await setSetting("location", locationText[0], false); } else { Location.current().then(async (location) => { const locationText = await Location.reverseGeocode(location.latitude, location.longitude); if (locationText) setSetting("location", locationText[0], false); }); } return locationTextValue; } catch (e) { console.log("❌定位失败:" + e); } }; this.getCovid19 = async (params) => { const query = Object.keys(params).map((item) => { return `${item}=${encodeURIComponent(params[item])}`; }); query.push("today=" + this.today); const url = `${this.baseUrl}?${query.join("&")}`; console.log(url); const response = (await request({method: "POST", url, useCache: true})).data; if (response.ret === 0 && response.data && response.data instanceof Array) { const covid_19 = response.data[response.data.length - 1]; if (covid_19) return covid_19; } }; this.tabInfo = (data) => { return /* @__PURE__ */ h("wstack", { flexDirection: "column", background: data.bg }, /* @__PURE__ */ h("wspacer", null), /* @__PURE__ */ h(RowCeneter_default, null, /* @__PURE__ */ h("wtext", { font: addumFont, textColor: viewColor }, "较上日"), /* @__PURE__ */ h("wtext", { font: addumFont, textColor: data.color }, data.addnum)), /* @__PURE__ */ h("wspacer", { length: 2 }), /* @__PURE__ */ h(RowCeneter_default, null, /* @__PURE__ */ h("wtext", { textColor: data.color }, data.value)), /* @__PURE__ */ h("wspacer", { length: 2 }), /* @__PURE__ */ h(RowCeneter_default, null, /* @__PURE__ */ h("wtext", { font: addumFont, textColor: viewColor }, data.tabText)), /* @__PURE__ */ h("wspacer", null)); }; this.render = async () => { const Footer = () => { return /* @__PURE__ */ h("wstack", { verticalAlign: "center", padding: [0, 10, 10, 10] }, /* @__PURE__ */ h("wimage", { src: "https://img.icons8.com/cute-clipart/2x/coronavirus.png", width: 15, height: 15 }), /* @__PURE__ */ h("wspacer", { length: 10 }), /* @__PURE__ */ h("wtext", { opacity: 0.5, font: 14, textColor: this.fontColor }, "疫情日报"), /* @__PURE__ */ h("wspacer", null), /* @__PURE__ */ h("wimage", { src: "arrow.clockwise", width: 10, height: 10, opacity: 0.5, filter: this.fontColor }), /* @__PURE__ */ h("wspacer", { length: 10 }), /* @__PURE__ */ h("wtext", { font: 12, textAlign: "right", opacity: 0.5, textColor: this.fontColor }, this.nowTime())); }; if (config.widgetFamily === "small") { return /* @__PURE__ */ h("wbox", null, /* @__PURE__ */ h("wspacer", null), /* @__PURE__ */ h("wtext", { textAlign: "center" }, "暂不支持"), /* @__PURE__ */ h("wspacer", null)); } return /* @__PURE__ */ h("wbox", { background: await this.getBackgroundImage() || this.backgroundColor, updateDate: new Date(Date.now() + await this.updateInterval()) }, /* @__PURE__ */ h("wspacer", null), /* @__PURE__ */ h("wstack", { borderRadius: 10 }, this.tabInfo({ color: "#f23a3b", bg: "#fff0f1", value: `${(this.province?.confirm || 0) - parseInt(this.province?.heal || "0") || "-"}`, tabText: "现有确诊", addnum: this.formatNumber(this.updateData?.nowConfirm) }), /* @__PURE__ */ h("wspacer", { length: 2 }), this.tabInfo({ color: "#cc1e1e", bg: "#fff0f1", value: `${this.province?.confirm || "-"}`, tabText: "累计确诊", addnum: this.formatNumber(this.updateData?.confirmAdd) }), /* @__PURE__ */ h("wspacer", { length: 2 }), this.tabInfo({ color: "#178b50", bg: "#f0f8f4", value: `${this.province?.heal || "-"}`, tabText: "累计治愈", addnum: this.formatNumber(this.updateData?.heal) }), /* @__PURE__ */ h("wspacer", { length: 2 }), this.tabInfo({ color: "#4e5a65", bg: "#f2f6f7", value: `${this.province?.dead || "-"}`, tabText: "累计死亡", addnum: this.formatNumber(this.updateData?.dead) })), /* @__PURE__ */ h("wspacer", { length: 5 }), /* @__PURE__ */ h("wstack", { borderRadius: 10, padding: [5, 5, 5, 5], flexDirection: "column", background: "#f8f8f8" }, /* @__PURE__ */ h("wstack", { verticalAlign: "center" }, /* @__PURE__ */ h("wimage", { src: "location", filter: "#005dff", width: 12, height: 12 }), /* @__PURE__ */ h("wspacer", { length: 5 }), /* @__PURE__ */ h("wtext", { textColor: "#005dff", font: addumFont }, (this.location?.postalAddress.city || "") + (this.location?.postalAddress.street || "") || "未找到定位"), /* @__PURE__ */ h("wspacer", null)), this.city && /* @__PURE__ */ h(Fragment, null, /* @__PURE__ */ h("wspacer", { length: 5 }), /* @__PURE__ */ h("wstack", { verticalAlign: "center" }, /* @__PURE__ */ h("wspacer", null), /* @__PURE__ */ h("wtext", { font: addumFont, textColor: viewColor }, this.city?.confirm_add || "0", "新增"), /* @__PURE__ */ h("wspacer", null), /* @__PURE__ */ h("wtext", { font: addumFont, textColor: viewColor }, this.city?.confirm || "0", "确诊"), /* @__PURE__ */ h("wspacer", null), /* @__PURE__ */ h("wtext", { font: addumFont, textColor: viewColor }, this.city?.heal || "0", "治愈"), /* @__PURE__ */ h("wspacer", null), /* @__PURE__ */ h("wtext", { font: addumFont, textColor: viewColor }, this.city?.dead || "0", "死亡"), /* @__PURE__ */ h("wspacer", null)))), this.news && /* @__PURE__ */ h(Fragment, null, /* @__PURE__ */ h("wspacer", { length: 5 }), /* @__PURE__ */ h("wstack", { borderRadius: 10, padding: [5, 5, 5, 5], flexDirection: "column" }, this.news.map((item, index) => { return /* @__PURE__ */ h(Fragment, null, /* @__PURE__ */ h("wstack", { href: item.news_url }, /* @__PURE__ */ h("wstack", { flexDirection: "column" }, /* @__PURE__ */ h("wtext", { font: addumFont, maxLine: 1, textColor: this.fontColor }, item.title), /* @__PURE__ */ h("wspacer", { length: 5 }), /* @__PURE__ */ h("wtext", { font: addumFont, textColor: this.fontColor, opacity: 0.5 }, item.srcfrom)), /* @__PURE__ */ h("wspacer", null), /* @__PURE__ */ h("wimage", { src: item.shortcut, width: 40, height: 30, borderRadius: 4 })), (this.news && this.news?.length - 1) !== index && /* @__PURE__ */ h("wspacer", { length: 5 })); }))), /* @__PURE__ */ h("wspacer", null), /* @__PURE__ */ h(Footer, null)); }; } async getCovid19ProvinceCompare() { const url = `https://api.inews.qq.com/newsqa/v1/query/inner/publish/modules/list?modules=provinceCompare&today=${this.today}`; const response = (await request({method: "POST", url})).data; if (response.ret === 0 && response.data) { const data = response.data.provinceCompare; if (!this.location) return; let {state = "", city = ""} = this.location.postalAddress; state = state.replace("省", "").replace("市", ""); city = city.replace("省", "").replace("市", ""); if (data[state]) this.updateData = data[state] || void 0; if (data[city]) this.updateData = data[state] || void 0; } } async getCovid19News() { if (!this.pinyin) return; const url = `https://api.dreamreader.qq.com/news/v1/province/news/list?province_code=${this.pinyin}&page_size=4&today=${this.today}`; console.log(url); const response = (await request({url, method: "GET", useCache: true})).data.data.items; if (response) this.news = response; } formatNumber(number) { if (!number) return `+0`; return number >= 0 ? `+${number}` : `${number}`; } nowTime() { const date = new Date(); return date.toLocaleTimeString("chinese", {hour12: false}); } }; EndAwait(() => new Widget().init()); await __topLevelAwait__();