-
Notifications
You must be signed in to change notification settings - Fork 88
Expand file tree
/
Copy pathDmYY.scriptable
More file actions
12 lines (11 loc) · 64.3 KB
/
Copy pathDmYY.scriptable
File metadata and controls
12 lines (11 loc) · 64.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
{
"always_run_in_app" : false,
"icon" : {
"color" : "teal",
"glyph" : "cogs"
},
"name" : "DmYY",
"script" : "\n\/*\n * Author: 2Ya\n * Github: https:\/\/github.com\/dompling\n * UI 配置升级 感谢 @LSP 大佬提供代码\n *\/\nclass DmYY {\n constructor(arg, defaultSettings) {\n this.arg = arg;\n this.defaultSettings = defaultSettings || {};\n this._init();\n this.isNight = Device.isUsingDarkAppearance();\n }\n\n BaseCacheKey = 'DmYY';\n _actions = [];\n _menuActions = [];\n widgetColor;\n backGroundColor;\n isNight;\n\n userConfigKey = ['avatar', 'nickname', 'homePageDesc'];\n\n \/\/ 获取 Request 对象\n getRequest = (url = '') => {\n return new Request(url);\n };\n\n \/\/ 发起请求\n http = async (\n options = { headers: {}, url: '' },\n type = 'JSON',\n onError = () => {\n return SFSymbol.named('photo').image;\n }\n ) => {\n let request;\n try {\n if (type === 'IMG') {\n const fileName = `${this.cacheImage}\/${this.md5(options.url)}`;\n request = this.getRequest(options.url);\n let response;\n if (this.FILE_MGR.fileExists(fileName)) {\n request.loadImage().then((res) => {\n this.FILE_MGR.writeImage(fileName, res);\n });\n return Image.fromFile(fileName);\n } else {\n response = await request.loadImage();\n this.FILE_MGR.writeImage(fileName, response);\n }\n return response;\n }\n request = this.getRequest();\n Object.keys(options).forEach((key) => {\n request[key] = options[key];\n });\n request.headers = { ...this.defaultHeaders, ...options.headers };\n\n if (type === 'JSON') {\n return await request.loadJSON();\n }\n if (type === 'STRING') {\n return await request.loadString();\n }\n return await request.loadJSON();\n } catch (e) {\n console.log('error:' + e);\n if (type === 'IMG') return onError?.();\n }\n };\n\n \/\/request 接口请求\n $request = {\n get: (url = '', options = {}, type = 'JSON') => {\n let params = { ...options, method: 'GET' };\n if (typeof url === 'object') {\n params = { ...params, ...url };\n } else {\n params.url = url;\n }\n let _type = type;\n if (typeof options === 'string') _type = options;\n return this.http(params, _type);\n },\n post: (url = '', options = {}, type = 'JSON') => {\n let params = { ...options, method: 'POST' };\n if (typeof url === 'object') {\n params = { ...params, ...url };\n } else {\n params.url = url;\n }\n let _type = type;\n if (typeof options === 'string') _type = options;\n return this.http(params, _type);\n },\n };\n\n \/\/ 获取 boxJS 缓存\n getCache = async (key = '', notify = true) => {\n try {\n let url = 'http:\/\/' + this.prefix + '\/query\/boxdata';\n if (key) url = 'http:\/\/' + this.prefix + '\/query\/data\/' + key;\n const boxdata = await this.$request.get(\n url,\n key ? { timeoutInterval: 1 } : {}\n );\n if (key) {\n this.settings.BoxJSData = {\n ...this.settings.BoxJSData,\n [key]: boxdata.val,\n };\n this.saveSettings(false);\n }\n if (boxdata.val) return boxdata.val;\n\n return boxdata.datas;\n } catch (e) {\n if (key && this.settings.BoxJSData[key]) {\n return this.settings.BoxJSData[key];\n }\n if (notify)\n await this.notify(\n `${this.name} - BoxJS 数据读取失败`,\n '请检查 BoxJS 域名是否为代理复写的域名,如(boxjs.net 或 boxjs.com)。\\n若没有配置 BoxJS 相关模块,请点击通知查看教程',\n 'https:\/\/chavyleung.gitbook.io\/boxjs\/awesome\/videos'\n );\n return false;\n }\n };\n\n transforJSON = (str) => {\n if (typeof str == 'string') {\n try {\n return JSON.parse(str);\n } catch (e) {\n console.log(e);\n return str;\n }\n }\n console.log('It is not a string!');\n };\n\n \/\/ 选择图片并缓存\n chooseImg = async () => {\n return Photos.fromLibrary()\n .then(async (response) => {\n const bool = this.verifyImage(response);\n if (bool) return response;\n throw new Error('图片超过限制');\n })\n .catch((err) => {\n console.log('图片选择异常:' + err);\n });\n };\n\n \/\/ 设置 widget 背景图片\n getWidgetBackgroundImage = async (widget) => {\n const backgroundImage = this.getBackgroundImage();\n if (backgroundImage) {\n const opacity = Device.isUsingDarkAppearance()\n ? Number(this.settings.darkOpacity)\n : Number(this.settings.lightOpacity);\n widget.backgroundImage = await this.shadowImage(\n backgroundImage,\n '#000',\n opacity\n );\n return true;\n } else {\n if (this.backGroundColor.colors) {\n widget.backgroundGradient = this.backGroundColor;\n } else {\n widget.backgroundColor = this.backGroundColor;\n }\n return false;\n }\n };\n\n \/**\n * 验证图片尺寸: 图片像素超过 1000 左右的时候会导致背景无法加载\n * @param img Image\n *\/\n verifyImage = async (img) => {\n try {\n const { width, height } = img.size;\n const direct = true;\n if (width > 1000) {\n const options = ['取消', '打开图像处理'];\n const message =\n '您的图片像素为' +\n width +\n ' x ' +\n height +\n '\\n' +\n '请将图片' +\n (direct ? '宽度' : '高度') +\n '调整到 1000 以下\\n' +\n (!direct ? '宽度' : '高度') +\n '自动适应';\n const index = await this.generateAlert(message, options);\n if (index === 1)\n Safari.openInApp('https:\/\/www.sojson.com\/image\/change.html', false);\n return false;\n }\n return true;\n } catch (e) {\n return false;\n }\n };\n\n \/**\n * 获取截图中的组件剪裁图\n * 可用作透明背景\n * 返回图片image对象\n * 代码改自:https:\/\/gist.github.com\/mzeryck\/3a97ccd1e059b3afa3c6666d27a496c9\n * @param {string} title 开始处理前提示用户截图的信息,可选(适合用在组件自定义透明背景时提示)\n *\/\n async getWidgetScreenShot(title = null) {\n \/\/ Crop an image into the specified rect.\n function cropImage(img, rect) {\n let draw = new DrawContext();\n draw.size = new Size(rect.width, rect.height);\n\n draw.drawImageAtPoint(img, new Point(-rect.x, -rect.y));\n return draw.getImage();\n }\n\n \/\/ Pixel sizes and positions for widgets on all supported phones.\n function phoneSizes() {\n return {\n \/\/ 12 Pro Max\n 2778: {\n small: 510,\n medium: 1092,\n large: 1146,\n left: 96,\n right: 678,\n top: 246,\n middle: 882,\n bottom: 1518,\n },\n\n \/\/ 12 and 12 Pro\n 2532: {\n small: 474,\n medium: 1014,\n large: 1062,\n left: 78,\n right: 618,\n top: 231,\n middle: 819,\n bottom: 1407,\n },\n\n \/\/ 11 Pro Max, XS Max\n 2688: {\n small: 507,\n medium: 1080,\n large: 1137,\n left: 81,\n right: 654,\n top: 228,\n middle: 858,\n bottom: 1488,\n },\n\n \/\/ 11, XR\n 1792: {\n small: 338,\n medium: 720,\n large: 758,\n left: 54,\n right: 436,\n top: 160,\n middle: 580,\n bottom: 1000,\n },\n\n \/\/ 11 Pro, XS, X, 12 mini\n 2436: {\n x: {\n small: 465,\n medium: 987,\n large: 1035,\n left: 69,\n right: 591,\n top: 213,\n middle: 783,\n bottom: 1353,\n },\n\n mini: {\n small: 465,\n medium: 987,\n large: 1035,\n left: 69,\n right: 591,\n top: 231,\n middle: 801,\n bottom: 1371,\n },\n },\n\n \/\/ Plus phones\n 2208: {\n small: 471,\n medium: 1044,\n large: 1071,\n left: 99,\n right: 672,\n top: 114,\n middle: 696,\n bottom: 1278,\n },\n\n \/\/ SE2 and 6\/6S\/7\/8\n 1334: {\n small: 296,\n medium: 642,\n large: 648,\n left: 54,\n right: 400,\n top: 60,\n middle: 412,\n bottom: 764,\n },\n\n \/\/ SE1\n 1136: {\n small: 282,\n medium: 584,\n large: 622,\n left: 30,\n right: 332,\n top: 59,\n middle: 399,\n bottom: 399,\n },\n\n \/\/ 11 and XR in Display Zoom mode\n 1624: {\n small: 310,\n medium: 658,\n large: 690,\n left: 46,\n right: 394,\n top: 142,\n middle: 522,\n bottom: 902,\n },\n\n \/\/ Plus in Display Zoom mode\n 2001: {\n small: 444,\n medium: 963,\n large: 972,\n left: 81,\n right: 600,\n top: 90,\n middle: 618,\n bottom: 1146,\n },\n };\n }\n\n let message =\n title || '开始之前,请先前往桌面,截取空白界面的截图。然后回来继续';\n let exitOptions = ['我已截图', '前去截图 >'];\n let shouldExit = await this.generateAlert(message, exitOptions);\n if (shouldExit) return;\n\n \/\/ Get screenshot and determine phone size.\n let img = await Photos.fromLibrary();\n let height = img.size.height;\n let phone = phoneSizes()[height];\n if (!phone) {\n message = '好像您选择的照片不是正确的截图,请先前往桌面';\n await this.generateAlert(message, ['我已知晓']);\n return;\n }\n\n \/\/ Extra setup needed for 2436-sized phones.\n if (height === 2436) {\n const files = this.FILE_MGR_LOCAL;\n let cacheName = 'mz-phone-type';\n let cachePath = files.joinPath(files.libraryDirectory(), cacheName);\n\n \/\/ If we already cached the phone size, load it.\n if (files.fileExists(cachePath)) {\n let typeString = files.readString(cachePath);\n phone = phone[typeString];\n \/\/ Otherwise, prompt the user.\n } else {\n message = '您的📱型号是?';\n let types = ['iPhone 12 mini', 'iPhone 11 Pro, XS, or X'];\n let typeIndex = await this.generateAlert(message, types);\n let type = typeIndex === 0 ? 'mini' : 'x';\n phone = phone[type];\n files.writeString(cachePath, type);\n }\n }\n\n \/\/ Prompt for widget size and position.\n message = '截图中要设置透明背景组件的尺寸类型是?';\n let sizes = ['小尺寸', '中尺寸', '大尺寸'];\n let size = await this.generateAlert(message, sizes);\n let widgetSize = sizes[size];\n\n message = '要设置透明背景的小组件在哪个位置?';\n message +=\n height === 1136\n ? ' (备注:当前设备只支持两行小组件,所以下边选项中的「中间」和「底部」的选项是一致的)'\n : '';\n\n \/\/ Determine image crop based on phone size.\n let crop = { w: '', h: '', x: '', y: '' };\n if (widgetSize === '小尺寸') {\n crop.w = phone.small;\n crop.h = phone.small;\n let positions = [\n '左上角',\n '右上角',\n '中间左',\n '中间右',\n '左下角',\n '右下角',\n ];\n let _posotions = [\n 'Top left',\n 'Top right',\n 'Middle left',\n 'Middle right',\n 'Bottom left',\n 'Bottom right',\n ];\n let position = await this.generateAlert(message, positions);\n\n \/\/ Convert the two words into two keys for the phone size dictionary.\n let keys = _posotions[position].toLowerCase().split(' ');\n crop.y = phone[keys[0]];\n crop.x = phone[keys[1]];\n } else if (widgetSize === '中尺寸') {\n crop.w = phone.medium;\n crop.h = phone.small;\n\n \/\/ Medium and large widgets have a fixed x-value.\n crop.x = phone.left;\n let positions = ['顶部', '中间', '底部'];\n let _positions = ['Top', 'Middle', 'Bottom'];\n let position = await this.generateAlert(message, positions);\n let key = _positions[position].toLowerCase();\n crop.y = phone[key];\n } else if (widgetSize === '大尺寸') {\n crop.w = phone.medium;\n crop.h = phone.large;\n crop.x = phone.left;\n let positions = ['顶部', '底部'];\n let position = await this.generateAlert(message, positions);\n\n \/\/ Large widgets at the bottom have the \"middle\" y-value.\n crop.y = position ? phone.middle : phone.top;\n }\n\n \/\/ Crop image and finalize the widget.\n return cropImage(img, new Rect(crop.x, crop.y, crop.w, crop.h));\n }\n\n setLightAndDark = async (title, desc, val, placeholder = '') => {\n try {\n const a = new Alert();\n a.title = title;\n a.message = desc;\n a.addTextField(placeholder, `${this.settings[val] || ''}`);\n a.addAction('确定');\n a.addCancelAction('取消');\n const id = await a.presentAlert();\n if (id === -1) return false;\n this.settings[val] = a.textFieldValue(0) || '';\n this.saveSettings();\n return true;\n } catch (e) {\n console.log(e);\n }\n };\n\n \/**\n * 弹出输入框\n * @param title 标题\n * @param desc 描述\n * @param opt 属性\n * @returns {Promise<void>}\n *\/\n setAlertInput = async (title, desc, opt = {}, isSave = true) => {\n const a = new Alert();\n a.title = title;\n a.message = !desc ? '' : desc;\n Object.keys(opt).forEach((key) => {\n a.addTextField(opt[key], this.settings[key]);\n });\n a.addAction('确定');\n a.addCancelAction('取消');\n const id = await a.presentAlert();\n if (id === -1) return;\n const data = {};\n Object.keys(opt).forEach((key, index) => {\n data[key] = a.textFieldValue(index) || '';\n });\n \/\/ 保存到本地\n if (isSave) {\n this.settings = { ...this.settings, ...data };\n return this.saveSettings();\n }\n return data;\n };\n\n setBaseAlertInput = async (title, desc, opt = {}, isSave = true) => {\n const a = new Alert();\n a.title = title;\n a.message = !desc ? '' : desc;\n Object.keys(opt).forEach((key) => {\n a.addTextField(opt[key], this.baseSettings[key] || '');\n });\n a.addAction('确定');\n a.addCancelAction('取消');\n const id = await a.presentAlert();\n if (id === -1) return;\n const data = {};\n Object.keys(opt).forEach((key, index) => {\n data[key] = a.textFieldValue(index) || '';\n });\n \/\/ 保存到本地\n if (isSave) return this.saveBaseSettings(data);\n return data;\n };\n\n \/**\n * 设置当前项目的 boxJS 缓存\n * @param opt key value\n * @returns {Promise<void>}\n *\/\n setCacheBoxJSData = async (opt = {}) => {\n const options = ['取消', '确定'];\n const message = '代理缓存仅支持 BoxJS 相关的代理!';\n const index = await this.generateAlert(message, options);\n if (index === 0) return;\n try {\n const boxJSData = await this.getCache();\n Object.keys(opt).forEach((key) => {\n this.settings[key] = boxJSData[opt[key]] || '';\n });\n \/\/ 保存到本地\n this.saveSettings();\n } catch (e) {\n console.log(e);\n this.notify(\n this.name,\n 'BoxJS 缓存读取失败!点击查看相关教程',\n 'https:\/\/chavyleung.gitbook.io\/boxjs\/awesome\/videos'\n );\n }\n };\n\n \/**\n * 设置组件内容\n * @returns {Promise<void>}\n *\/\n setWidgetConfig = async () => {\n const basic = [\n {\n icon: { name: 'arrow.clockwise', color: '#1890ff' },\n type: 'input',\n title: '刷新时间',\n desc: '刷新时间仅供参考,具体刷新时间由系统判断,单位:分钟',\n val: 'refreshAfterDate',\n },\n {\n icon: { name: 'sun.max.fill', color: '#d48806' },\n type: 'color',\n title: '白天字体颜色',\n desc: '请自行去网站上搜寻颜色(Hex 颜色)',\n val: 'lightColor',\n },\n {\n icon: { name: 'moon.stars.fill', color: '#d4b106' },\n type: 'color',\n title: '晚上字体颜色',\n desc: '请自行去网站上搜寻颜色(Hex 颜色)',\n val: 'darkColor',\n },\n ];\n\n return this.renderAppView([\n { title: '基础设置', menu: basic },\n {\n title: '背景设置',\n menu: [\n {\n icon: { name: 'photo', color: '#13c2c2' },\n type: 'color',\n title: '白天背景颜色',\n desc: '请自行去网站上搜寻颜色(Hex 颜色)\\n支持渐变色,各颜色之间以英文逗号分隔',\n val: 'lightBgColor',\n },\n {\n icon: { name: 'photo.fill', color: '#52c41a' },\n type: 'color',\n title: '晚上背景颜色',\n desc: '请自行去网站上搜寻颜色(Hex 颜色)\\n支持渐变色,各颜色之间以英文逗号分隔',\n val: 'darkBgColor',\n },\n ],\n },\n {\n menu: [\n {\n icon: { name: 'photo.on.rectangle', color: '#fa8c16' },\n name: 'dayBg',\n type: 'img',\n title: '日间背景',\n val: this.cacheImage,\n },\n {\n icon: { name: 'photo.fill.on.rectangle.fill', color: '#fa541c' },\n name: 'nightBg',\n type: 'img',\n title: '夜间背景',\n val: this.cacheImage,\n },\n {\n icon: { name: 'text.below.photo', color: '#faad14' },\n type: 'img',\n name: 'transparentBg',\n title: '透明背景',\n val: this.cacheImage,\n onClick: async (item, __, previewWebView) => {\n const backImage = await this.getWidgetScreenShot();\n if (!backImage || !(await this.verifyImage(backImage))) return;\n const cachePath = `${item.val}\/${item.name}`;\n const base64Img = await this.setBackgroundImage(\n backImage,\n cachePath\n );\n this.insertTextByElementId(\n previewWebView,\n item.name,\n `<img src=\"${base64Img}\" \/>`\n );\n },\n },\n ],\n },\n {\n menu: [\n {\n icon: { name: 'record.circle', color: '#722ed1' },\n type: 'input',\n title: '日间蒙层',\n desc: '完全透明请设置为0',\n val: 'lightOpacity',\n },\n {\n icon: { name: 'record.circle.fill', color: '#eb2f96' },\n type: 'input',\n title: '夜间蒙层',\n desc: '完全透明请设置为0',\n val: 'darkOpacity',\n },\n ],\n },\n {\n menu: [\n {\n icon: { name: 'clear', color: '#f5222d' },\n name: 'removeBackground',\n title: '清空背景图片',\n val: `${this.cacheImage}\/`,\n onClick: async (_, __, previewWebView) => {\n const options = [\n '清空日间',\n '清空夜间',\n '清空透明',\n `清空全部`,\n '取消',\n ];\n const message = '该操作不可逆,会清空背景图片!';\n const index = await this.generateAlert(message, options);\n if (index === 4) return;\n switch (index) {\n case 0:\n await this.setBackgroundImage(false, _.val + 'dayBg');\n this.insertTextByElementId(previewWebView, 'dayBg', ``);\n return;\n case 1:\n await this.setBackgroundImage(false, _.val + 'nightBg');\n this.insertTextByElementId(previewWebView, 'nightBg', ``);\n return;\n case 2:\n await this.setBackgroundImage(false, _.val + 'transparentBg');\n this.insertTextByElementId(\n previewWebView,\n 'transparentBg',\n ``\n );\n return;\n default:\n await this.setBackgroundImage(false, _.val + 'dayBg', false);\n await this.setBackgroundImage(\n false,\n _.val + 'nightBg',\n false\n );\n await this.setBackgroundImage(false, _.val + 'transparentBg');\n this.insertTextByElementId(previewWebView, 'dayBg', ``);\n this.insertTextByElementId(previewWebView, 'nightBg', ``);\n this.insertTextByElementId(\n previewWebView,\n 'transparentBg',\n ``\n );\n break;\n }\n },\n },\n ],\n },\n ]).catch((e) => {\n console.log(e);\n });\n };\n\n drawTableIcon = async (\n icon = 'square.grid.2x2',\n color = '#504ED5',\n cornerWidth = 42\n ) => {\n const sfi = SFSymbol.named(icon);\n sfi.applyFont(Font.mediumSystemFont(30));\n const imgData = Data.fromPNG(sfi.image).toBase64String();\n const html = `\n <img id=\"sourceImg\" src=\"data:image\/png;base64,${imgData}\" \/>\n <img id=\"silhouetteImg\" src=\"\" \/>\n <canvas id=\"mainCanvas\" \/>\n `;\n const js = `\n var canvas = document.createElement(\"canvas\");\n var sourceImg = document.getElementById(\"sourceImg\");\n var silhouetteImg = document.getElementById(\"silhouetteImg\");\n var ctx = canvas.getContext('2d');\n var size = sourceImg.width > sourceImg.height ? sourceImg.width : sourceImg.height;\n canvas.width = size;\n canvas.height = size;\n ctx.drawImage(sourceImg, (canvas.width - sourceImg.width) \/ 2, (canvas.height - sourceImg.height) \/ 2);\n var imgData = ctx.getImageData(0, 0, canvas.width, canvas.height);\n var pix = imgData.data;\n \/\/convert the image into a silhouette\n for (var i=0, n = pix.length; i < n; i+= 4){\n \/\/set red to 0\n pix[i] = 255;\n \/\/set green to 0\n pix[i+1] = 255;\n \/\/set blue to 0\n pix[i+2] = 255;\n \/\/retain the alpha value\n pix[i+3] = pix[i+3];\n }\n ctx.putImageData(imgData,0,0);\n silhouetteImg.src = canvas.toDataURL();\n output=canvas.toDataURL()\n `;\n\n let wv = new WebView();\n await wv.loadHTML(html);\n const base64Image = await wv.evaluateJavaScript(js);\n const iconImage = await new Request(base64Image).loadImage();\n const size = new Size(160, 160);\n const ctx = new DrawContext();\n ctx.opaque = false;\n ctx.respectScreenScale = true;\n ctx.size = size;\n const path = new Path();\n const rect = new Rect(0, 0, size.width, size.width);\n\n path.addRoundedRect(rect, cornerWidth, cornerWidth);\n path.closeSubpath();\n ctx.setFillColor(new Color(color));\n ctx.addPath(path);\n ctx.fillPath();\n const rate = 36;\n const iw = size.width - rate;\n const x = (size.width - iw) \/ 2;\n ctx.drawImageInRect(iconImage, new Rect(x, x, iw, iw));\n return ctx.getImage();\n };\n\n dismissLoading = (webView) => {\n webView.evaluateJavaScript(\n \"window.dispatchEvent(new CustomEvent('JWeb', { detail: { code: 'finishLoading' } }))\",\n false\n );\n };\n\n insertTextByElementId = (webView, elementId, text) => {\n const scripts = `document.getElementById(\"${elementId}_val\").innerHTML=\\`${text}\\`;`;\n webView.evaluateJavaScript(scripts, false);\n };\n\n loadSF2B64 = async (\n icon = 'square.grid.2x2',\n color = '#56A8D6',\n cornerWidth = 42\n ) => {\n const sfImg = await this.drawTableIcon(icon, color, cornerWidth);\n return `data:image\/png;base64,${Data.fromPNG(sfImg).toBase64String()}`;\n };\n\n setUserInfo = async () => {\n const baseOnClick = async (item, _, previewWebView) => {\n const data = await this.setBaseAlertInput(item.title, item.desc, {\n [item.val]: item.placeholder,\n });\n if (!data) return;\n this.insertTextByElementId(previewWebView, item.name, data[item.val]);\n };\n\n return this.renderAppView([\n {\n title: '个性设置',\n menu: [\n {\n icon: { name: 'person', color: '#fa541c' },\n name: this.userConfigKey[0],\n title: '首页头像',\n type: 'img',\n val: this.baseImage,\n onClick: async (_, __, previewWebView) => {\n const options = ['相册选择', '在线链接', '取消'];\n const message = '设置个性化头像';\n const index = await this.generateAlert(message, options);\n if (index === 2) return;\n const cachePath = `${_.val}\/${_.name}`;\n switch (index) {\n case 0:\n const albumOptions = ['选择图片', '清空图片', '取消'];\n\n const albumIndex = await this.generateAlert('', albumOptions);\n if (albumIndex === 2) return;\n if (albumIndex === 1) {\n await this.setBackgroundImage(false, _.name, false);\n this.insertTextByElementId(previewWebView, _.name, ``);\n return;\n }\n\n const backImage = await this.chooseImg();\n if (backImage) {\n const base64Img = await this.setBackgroundImage(\n backImage,\n cachePath\n );\n\n this.insertTextByElementId(\n previewWebView,\n _.name,\n `<img src=\"${base64Img}\"\/>`\n );\n }\n\n break;\n case 1:\n const data = await this.setBaseAlertInput(\n '在线链接',\n '首页头像在线链接',\n {\n avatar: '🔗请输入 URL 图片链接',\n }\n );\n if (!data) return;\n\n if (data[_.name] !== '') {\n const backImage = await this.$request.get(\n data[_.name],\n 'IMG'\n );\n const base64Img = await this.setBackgroundImage(\n backImage,\n cachePath\n );\n\n this.insertTextByElementId(\n previewWebView,\n _.name,\n `<img src=\"${base64Img}\"\/>`\n );\n } else {\n await this.setBackgroundImage(false, cachePath);\n this.insertTextByElementId(previewWebView, 'avatar');\n }\n\n break;\n default:\n break;\n }\n },\n },\n {\n icon: { name: 'pencil', color: '#fa8c16' },\n type: 'input',\n title: '首页昵称',\n desc: '个性化首页昵称',\n placeholder: '👤请输入头像昵称',\n val: this.userConfigKey[1],\n name: this.userConfigKey[1],\n defaultValue: this.baseSettings.nickname,\n onClick: baseOnClick,\n },\n {\n icon: { name: 'lineweight', color: '#a0d911' },\n type: 'input',\n title: '首页昵称描述',\n desc: '个性化首页昵称描述',\n placeholder: '请输入描述',\n val: this.userConfigKey[2],\n name: this.userConfigKey[2],\n defaultValue: this.baseSettings.homePageDesc,\n onClick: baseOnClick,\n },\n ],\n },\n {\n menu: [\n {\n icon: { name: 'shippingbox', color: '#f7bb10' },\n type: 'input',\n title: 'BoxJS 域名',\n desc: '设置BoxJS访问域名,如:boxjs.net 或 boxjs.com',\n val: 'boxjsDomain',\n name: 'boxjsDomain',\n placeholder: 'boxjs.net',\n defaultValue: this.baseSettings.boxjsDomain,\n onClick: baseOnClick,\n },\n {\n icon: { name: 'clear', color: '#f5222d' },\n title: '恢复默认设置',\n name: 'reset',\n onClick: async () => {\n const options = ['取消', '确定'];\n const message = '确定要恢复当前所有配置吗?';\n const index = await this.generateAlert(message, options);\n if (index === 1) {\n this.settings = {};\n this.baseSettings = {};\n for (const item of this.cacheImageBgPath) {\n await this.setBackgroundImage(false, item, false);\n }\n this.saveSettings(false);\n this.saveBaseSettings();\n await this.notify(\n '重置成功',\n '请关闭窗口之后,重新运行当前脚本'\n );\n this.reopenScript();\n }\n },\n },\n ],\n },\n ]);\n };\n\n reopenScript = () => {\n Safari.open(`scriptable:\/\/\/run\/${encodeURIComponent(Script.name())}`);\n };\n\n async renderAppView(\n options = [],\n renderAvatar = false,\n previewWebView = new WebView()\n ) {\n const settingItemFontSize = 14,\n authorNameFontSize = 20,\n authorDescFontSize = 12;\n \/\/ ================== 配置界面样式 ===================\n const style = `\n :root {\n --color-primary: #007aff;\n --divider-color: rgba(60,60,67,0.16);\n --card-background: #fff;\n --card-radius: 8px;\n --list-header-color: rgba(60,60,67,0.6);\n }\n * {\n -webkit-user-select: none;\n user-select: none;\n }\n body {\n margin: 10px 0;\n -webkit-font-smoothing: antialiased;\n font-family: \"SF Pro Display\",\"SF Pro Icons\",\"Helvetica Neue\",\"Helvetica\",\"Arial\",sans-serif;\n accent-color: var(--color-primary);\n background: #f6f6f6;\n }\n .list {\n margin: 15px;\n }\n .list__header {\n margin: 0 18px;\n color: var(--list-header-color);\n font-size: 13px;\n }\n .list__body {\n margin-top: 10px;\n background: var(--card-background);\n border-radius: var(--card-radius);\n overflow: hidden;\n }\n .form-item-auth {\n display: flex;\n align-items: center;\n justify-content: space-between;\n min-height: 4em;\n padding: 0.5em 18px;\n position: relative;\n }\n .form-item-auth-name {\n margin: 0px 12px;\n font-size: ${authorNameFontSize}px;\n font-weight: 430;\n }\n .form-item-auth-desc {\n margin: 0px 12px;\n font-size: ${authorDescFontSize}px;\n font-weight: 400;\n }\n .form-label-author-avatar {\n width: 62px;\n height: 62px;\n border-radius:50%;\n border: 1px solid #F6D377;\n }\n .form-item {\n display: flex;\n align-items: center;\n justify-content: space-between;\n font-size: ${settingItemFontSize}px;\n font-weight: 400;\n min-height: 2.2em;\n padding: 0.5em 18px;\n position: relative;\n }\n .form-label {\n display: flex;\n align-items: center;\n flex-wrap:nowrap\n }\n .form-label-img {\n height: 30px;\n }\n .form-label-title {\n margin-left: 8px;\n white-space: nowrap;\n }\n .bottom-bg {\n margin: 30px 15px 15px 15px;\n }\n .form-item--link .icon-arrow-right {\n color: #86868b;\n }\n\n .form-item-right-desc {\n font-size: 13px;\n color: #86868b;\n margin: 0 4px 0 auto; \n max-width: 100px;\n overflow: hidden;\n text-overflow: ellipsis;\n display:flex;\n align-items: center;\n }\n\n .form-item-right-desc img{\n width:30px;\n height:30px;\n border-radius:3px;\n }\n\n .form-item + .form-item::before {\n content: \"\";\n position: absolute;\n top: 0;\n left: 20px;\n right: 0;\n border-top: 0.5px solid var(--divider-color);\n }\n .form-item input[type=\"checkbox\"] {\n width: 2em;\n height: 2em;\n }\n input[type='input'],select {\n width: 100%;\n height: 2.3em;\n outline-style: none;\n text-align: right;\n padding: 0px 10px;\n border: 1px solid #ddd;\n font-size: 14px;\n color: #86868b;\n border-radius:4px;\n }\n input[type='checkbox'][role='switch'] {\n position: relative;\n display: inline-block;\n appearance: none;\n width: 40px;\n height: 24px;\n border-radius: 24px;\n background: #ccc;\n transition: 0.3s ease-in-out;\n }\n input[type='checkbox'][role='switch']::before {\n content: '';\n position: absolute;\n left: 2px;\n top: 2px;\n width: 20px;\n height: 20px;\n border-radius: 50%;\n background: #fff;\n transition: 0.3s ease-in-out;\n }\n input[type='checkbox'][role='switch']:checked {\n background: var(--color-primary);\n }\n input[type='checkbox'][role='switch']:checked::before {\n transform: translateX(16px);\n }\n .copyright {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin: 15px;\n font-size: 10px;\n color: #86868b;\n }\n .copyright a {\n color: #515154;\n text-decoration: none;\n }\n .preview.loading {\n pointer-events: none;\n }\n .icon-loading {\n display: inline-block;\n animation: 1s linear infinite spin;\n }\n .normal-loading {\n display: inline-block;\n animation: 20s linear infinite spin;\n }\n @keyframes spin {\n 0% {\n transform: rotate(0);\n }\n 100% {\n transform: rotate(1turn);\n }\n }\n @media (prefers-color-scheme: dark) {\n :root {\n --divider-color: rgba(84,84,88,0.65);\n --card-background: #1c1c1e;\n --list-header-color: rgba(235,235,245,0.6);\n }\n body {\n background: #000;\n color: #fff;\n }\n }`;\n\n const js = `\n (() => {\n \n window.invoke = (code, data) => {\n window.dispatchEvent(\n new CustomEvent(\n 'JBridge',\n { detail: { code, data } }\n )\n )\n }\n \n \/\/ 切换ico的loading效果\n const toggleIcoLoading = (e) => {\n try{\n const target = e.currentTarget\n target.classList.add('loading')\n const icon = e.currentTarget.querySelector('.iconfont')\n const className = icon.className\n icon.className = 'iconfont icon-loading'\n const listener = (event) => {\n const { code } = event.detail\n if (code === 'finishLoading') {\n target.classList.remove('loading')\n icon.className = className\n window.removeEventListener('JWeb', listener);\n }\n }\n window.addEventListener('JWeb', listener)\n }catch(e){\n for (const loading of document.querySelectorAll('.icon-loading')) {\n loading.classList.remove('loading');\n loading.className = \"iconfont icon-arrow-right\";\n }\n }\n };\n \n for (const btn of document.querySelectorAll('.label-link')) {\n btn.addEventListener('click', (e) => {\n if(!e.target.id)return;\n toggleIcoLoading(e);\n invoke(e.target.id);\n })\n }\n \n for (const btn of document.querySelectorAll('.form-item__input')) {\n btn.addEventListener('change', (e) => {\n if(!e.target.name)return;\n invoke(e.target.name,e.target.type===\"checkbox\"?\\`\\${e.target.checked}\\`: e.target.value);\n })\n }\n\n if(${renderAvatar}){\n document.querySelectorAll('.form-item-auth')[0].addEventListener('click', (e) => {\n toggleIcoLoading(e);\n invoke(\"userInfo\");\n })\n }\n \n })()`;\n\n let configList = ``;\n let actionsConfig = [];\n\n for (const key in options) {\n const item = options[key];\n actionsConfig = [...item.menu, ...actionsConfig];\n configList += ` \n <div class=\"list\"> \n <div class=\"list__header\">${item.title || ''}<\/div>\n <form id=\"form_${key}\" class=\"list__body\" action=\"javascript:void(0);\">\n `;\n\n for (const menuItem of item.menu) {\n let iconBase64 = ``;\n if (menuItem.children) {\n menuItem.onClick = () => {\n return this.renderAppView(\n typeof menuItem.children === 'function'\n ? menuItem.children()\n : menuItem.children\n );\n };\n }\n if (menuItem.url) {\n const imageIcon = await this.http(\n { url: menuItem.url },\n 'IMG',\n () => {\n return this.drawTableIcon('gear');\n }\n );\n\n if (menuItem.url.indexOf('png') !== -1) {\n iconBase64 = `data:image\/png;base64,${Data.fromPNG(\n imageIcon\n ).toBase64String()}`;\n } else {\n iconBase64 = `data:image\/png;base64,${Data.fromJPEG(\n imageIcon\n ).toBase64String()}`;\n }\n } else {\n const icon = menuItem.icon || {};\n iconBase64 = await this.loadSF2B64(icon.name, icon.color);\n }\n const idName = menuItem.name || menuItem.val;\n\n let defaultHtml = ``;\n if (menuItem.val !== undefined && !menuItem.defaultValue)\n menuItem.defaultValue = this.settings[menuItem.val] || '';\n\n if (menuItem.type === 'input') {\n defaultHtml = menuItem.defaultValue || '';\n } else if (menuItem.type === 'img') {\n const cachePath = `${menuItem.val}\/${menuItem.name}`;\n if (this.FILE_MGR.fileExists(cachePath)) {\n const imageSrc = `data:image\/png;base64,${Data.fromFile(\n cachePath\n ).toBase64String()}`;\n defaultHtml = `<img src=\"${imageSrc}\"\/>`;\n }\n } else if (menuItem.type === 'select') {\n let selectOptions = '';\n\n menuItem.options.forEach((option) => {\n let selected = `selected=\"selected\"`;\n selectOptions += `<option value=\"${option}\" ${\n menuItem.defaultValue === option ? selected : ''\n }>${option}<\/option>`;\n });\n defaultHtml = `<select class=\"form-item__input\" name=\"${idName}\">${selectOptions}<\/select>`;\n } else if (menuItem.type === 'switch') {\n const checked =\n menuItem.defaultValue === 'true' ? `checked=\"checked\"` : '';\n defaultHtml += `<input class=\"form-item__input\" name=\"${idName}\" role=\"switch\" type=\"checkbox\" value=\"true\" ${checked} \/>`;\n } else if (menuItem.type) {\n defaultHtml = `<input class=\"form-item__input\" placeholder=\"${\n menuItem.placeholder || '请输入'\n }\" name=\"${idName}\" type=\"${\n menuItem.type\n }\" enterkeyhint=\"done\" value=\"${menuItem.defaultValue}\">`;\n }\n\n configList += ` \n <label id=\"${idName}\" class=\"form-item form-item--link ${\n !defaultHtml || menuItem.type === 'input' ? 'label-link' : ''\n }\">\n <div class=\"form-label item-none\">\n <img class=\"form-label-img\" class=\"form-label-img\" src=\"${iconBase64}\"\/>\n <div class=\"form-label-title\">${menuItem.title}<\/div>\n <\/div>\n <div id=\"${idName}_val\" class=\"form-item-right-desc\">\n ${defaultHtml}\n <\/div>\n <i id=\"iconfont-${idName}\" class=\"iconfont icon-arrow-right\"><\/i>\n <\/label>\n `;\n }\n configList += `<\/form><\/div>`;\n }\n\n let avatarHtml = '';\n if (renderAvatar) {\n const cachePath = `${this.baseImage}\/${this.userConfigKey[0]}`;\n const avatarConfig = {\n avatar: `https:\/\/avatars.githubusercontent.com\/u\/23498579?v=4`,\n nickname: this.baseSettings[this.userConfigKey[1]] || 'Dompling',\n homPageDesc:\n this.baseSettings[this.userConfigKey[2]] ||\n '18岁,来自九仙山的设计师',\n };\n\n if (this.FILE_MGR.fileExists(cachePath)) {\n avatarConfig.avatar = `data:image\/png;base64,${Data.fromFile(\n cachePath\n ).toBase64String()}`;\n }\n\n avatarHtml = `\n <div class=\"list\">\n <form class=\"list__body\" action=\"javascript:void(0);\">\n <label id=\"userInfo\" class=\"form-item-auth form-item--link\">\n <div class=\"form-label\">\n <img class=\"form-label-author-avatar\" src=\"${avatarConfig.avatar}\"\/>\n <div>\n <div class=\"form-item-auth-name\">${avatarConfig.nickname}<\/div>\n <div class=\"form-item-auth-desc\">${avatarConfig.homPageDesc}<\/div>\n <\/div>\n <\/div>\n <div id=\"userInfo_val\" class=\"form-item-right-desc\">\n 个性化设置\n <\/div>\n <i class=\"iconfont icon-arrow-right\"><\/i>\n <\/label>\n <\/form>\n <\/div>\n `;\n }\n\n const html = `\n <html>\n <head>\n <meta name='viewport' content='width=device-width, user-scalable=no'>\n <link rel=\"stylesheet\" href=\"https:\/\/at.alicdn.com\/t\/c\/font_3791881_bf011w225k4.css\" type=\"text\/css\">\n <style>${style}<\/style>\n <\/head>\n <body>\n ${avatarHtml}\n ${configList} \n <footer>\n <div class=\"copyright\"><div> <\/div><div>© 界面样式修改自 <a href=\"javascript:invoke('safari', 'https:\/\/www.imarkr.com');\">@iMarkr.<\/a><\/div><\/div>\n <\/footer>\n <script>${js}<\/script>\n <\/body>\n <\/html>`;\n\n \n \/\/ 预览web\n await previewWebView.loadHTML(html);\n\n const injectListener = async () => {\n const event = await previewWebView.evaluateJavaScript(\n `(() => {\n try {\n window.addEventListener(\n 'JBridge',\n (e)=>{\n completion(JSON.stringify(e.detail||{}))\n }\n )\n } catch (e) {\n alert(\"预览界面出错:\" + e);\n throw new Error(\"界面处理出错: \" + e);\n return;\n }\n })()`,\n true\n );\n\n const { code, data } = JSON.parse(event);\n try {\n const actionItem = actionsConfig.find(\n (item) => (item.name || item.val) === code\n );\n\n if (code === 'userInfo') await this.setUserInfo();\n\n if (actionItem) {\n const idName = actionItem?.name || actionItem?.val;\n if (actionItem?.onClick) {\n await actionItem?.onClick?.(actionItem, data, previewWebView);\n } else if (actionItem.type == 'input') {\n if (\n await this.setLightAndDark(\n actionItem['title'],\n actionItem['desc'],\n actionItem['val'],\n actionItem['placeholder']\n )\n )\n this.insertTextByElementId(\n previewWebView,\n idName,\n this.settings[actionItem.val] || ''\n );\n } else if (actionItem.type === 'img') {\n const backImage = await this.chooseImg();\n if (backImage) {\n const cachePath = `${actionItem.val}\/${actionItem.name}`;\n const base64Img = await this.setBackgroundImage(\n backImage,\n cachePath,\n false\n );\n this.insertTextByElementId(\n previewWebView,\n idName,\n `<img src=\"${base64Img}\"\/>`\n );\n }\n } else {\n if (data !== undefined) {\n this.settings[actionItem.val] = data;\n this.saveSettings(false);\n }\n }\n }\n } catch (error) {\n console.log('异常操作:' + error);\n }\n this.dismissLoading(previewWebView);\n injectListener();\n };\n\n injectListener().catch((e) => {\n console.error(e);\n this.dismissLoading(previewWebView);\n if (!config.runsInApp) {\n this.notify('主界面', `🚫 ${e}`);\n }\n });\n\n previewWebView.present();\n }\n\n _init(widgetFamily = config.widgetFamily) {\n \/\/ 组件大小:small,medium,large\n this.widgetFamily = widgetFamily;\n this.SETTING_KEY = this.md5(Script.name());\n \/\/用于配置所有的组件相关设置\n\n \/\/ 文件管理器\n \/\/ 提示:缓存数据不要用这个操作,这个是操作源码目录的,缓存建议存放在local temp目录中\n this.FILE_MGR =\n FileManager[\n module.filename.includes('Documents\/iCloud~') ? 'iCloud' : 'local'\n ]();\n\n this.cacheImage = this.FILE_MGR.joinPath(\n this.FILE_MGR.documentsDirectory(),\n `\/images\/${Script.name()}`\n );\n\n this.baseImage = this.FILE_MGR.joinPath(\n this.FILE_MGR.documentsDirectory(),\n `\/images\/`\n );\n\n this.cacheImageBgPath = [\n `${this.cacheImage}\/transparentBg`,\n `${this.cacheImage}\/dayBg`,\n `${this.cacheImage}\/nightBg`,\n `${this.baseImage}\/avatar`,\n ];\n\n if (!this.FILE_MGR.fileExists(this.cacheImage)) {\n this.FILE_MGR.createDirectory(this.cacheImage, true);\n }\n\n \/\/ 本地,用于存储图片等\n this.FILE_MGR_LOCAL = FileManager.local();\n\n this.settings = this.getSettings();\n\n this.baseSettings = this.getBaseSettings();\n\n this.settings = { ...this.defaultSettings, ...this.settings };\n\n this.settings.lightColor = this.settings.lightColor || '#000000';\n this.settings.darkColor = this.settings.darkColor || '#ffffff';\n this.settings.lightBgColor = this.settings.lightBgColor || '#ffffff';\n this.settings.darkBgColor = this.settings.darkBgColor || '#000000';\n this.settings.boxjsDomain = this.baseSettings.boxjsDomain || 'boxjs.net';\n this.settings.refreshAfterDate = this.settings.refreshAfterDate || '30';\n this.settings.lightOpacity = this.settings.lightOpacity || '0.4';\n this.settings.darkOpacity = this.settings.darkOpacity || '0.7';\n\n this.prefix = this.settings.boxjsDomain;\n\n config.runsInApp && this.saveSettings(false);\n\n this.backGroundColor = Color.dynamic(\n new Color(this.settings.lightBgColor),\n new Color(this.settings.darkBgColor)\n );\n\n \/\/ const lightBgColor = this.getColors(this.settings.lightBgColor);\n \/\/ const darkBgColor = this.getColors(this.settings.darkBgColor);\n \/\/ if (lightBgColor.length > 1 || darkBgColor.length > 1) {\n \/\/ this.backGroundColor = !Device.isUsingDarkAppearance()\n \/\/ ? this.getBackgroundColor(lightBgColor)\n \/\/ : this.getBackgroundColor(darkBgColor);\n \/\/ } else if (lightBgColor.length > 0 && darkBgColor.length > 0) {\n \/\/ this.backGroundColor = Color.dynamic(\n \/\/ new Color(this.settings.lightBgColor),\n \/\/ new Color(this.settings.darkBgColor)\n \/\/ );\n \/\/ }\n\n this.widgetColor = Color.dynamic(\n new Color(this.settings.lightColor),\n new Color(this.settings.darkColor)\n );\n }\n\n getColors = (color = '') => {\n const colors = typeof color === 'string' ? color.split(',') : color;\n return colors;\n };\n\n getBackgroundColor = (colors) => {\n const locations = [];\n const linearColor = new LinearGradient();\n const cLen = colors.length;\n linearColor.colors = colors.map((item, index) => {\n locations.push(Math.floor(((index + 1) \/ cLen) * 100) \/ 100);\n return new Color(item, 1);\n });\n linearColor.locations = locations;\n return linearColor;\n };\n\n \/**\n * 注册点击操作菜单\n * @param {string} name 操作函数名\n * @param {func} func 点击后执行的函数\n *\/\n registerAction(name, func, icon = { name: 'gear', color: '#096dd9' }, type) {\n if (typeof name === 'object' && !name.menu) return this._actions.push(name);\n if (typeof name === 'object' && name.menu)\n return this._menuActions.push(name);\n\n const action = {\n name,\n type,\n title: name,\n onClick: func.bind(this),\n };\n\n if (typeof icon === 'string') {\n action.url = icon;\n } else {\n action.icon = icon;\n }\n\n this._actions.push(action);\n }\n\n \/**\n * base64 编码字符串\n * @param {string} str 要编码的字符串\n *\/\n base64Encode(str) {\n const data = Data.fromString(str);\n return data.toBase64String();\n }\n\n \/**\n * base64解码数据 返回字符串\n * @param {string} b64 base64编码的数据\n *\/\n base64Decode(b64) {\n const data = Data.fromBase64String(b64);\n return data.toRawString();\n }\n\n \/**\n * md5 加密字符串\n * @param {string} str 要加密成md5的数据\n *\/\n \/\/ prettier-ignore\n md5(str){function d(n,t){var r=(65535&n)+(65535&t);return(((n>>16)+(t>>16)+(r>>16))<<16)|(65535&r)}function f(n,t,r,e,o,u){return d(((c=d(d(t,n),d(e,u)))<<(f=o))|(c>>>(32-f)),r);var c,f}function l(n,t,r,e,o,u,c){return f((t&r)|(~t&e),n,t,o,u,c)}function v(n,t,r,e,o,u,c){return f((t&e)|(r&~e),n,t,o,u,c)}function g(n,t,r,e,o,u,c){return f(t^r^e,n,t,o,u,c)}function m(n,t,r,e,o,u,c){return f(r^(t|~e),n,t,o,u,c)}function i(n,t){var r,e,o,u;(n[t>>5]|=128<<t%32),(n[14+(((t+64)>>>9)<<4)]=t);for(var c=1732584193,f=-271733879,i=-1732584194,a=271733878,h=0;h<n.length;h+=16)(c=l((r=c),(e=f),(o=i),(u=a),n[h],7,-680876936)),(a=l(a,c,f,i,n[h+1],12,-389564586)),(i=l(i,a,c,f,n[h+2],17,606105819)),(f=l(f,i,a,c,n[h+3],22,-1044525330)),(c=l(c,f,i,a,n[h+4],7,-176418897)),(a=l(a,c,f,i,n[h+5],12,1200080426)),(i=l(i,a,c,f,n[h+6],17,-1473231341)),(f=l(f,i,a,c,n[h+7],22,-45705983)),(c=l(c,f,i,a,n[h+8],7,1770035416)),(a=l(a,c,f,i,n[h+9],12,-1958414417)),(i=l(i,a,c,f,n[h+10],17,-42063)),(f=l(f,i,a,c,n[h+11],22,-1990404162)),(c=l(c,f,i,a,n[h+12],7,1804603682)),(a=l(a,c,f,i,n[h+13],12,-40341101)),(i=l(i,a,c,f,n[h+14],17,-1502002290)),(c=v(c,(f=l(f,i,a,c,n[h+15],22,1236535329)),i,a,n[h+1],5,-165796510)),(a=v(a,c,f,i,n[h+6],9,-1069501632)),(i=v(i,a,c,f,n[h+11],14,643717713)),(f=v(f,i,a,c,n[h],20,-373897302)),(c=v(c,f,i,a,n[h+5],5,-701558691)),(a=v(a,c,f,i,n[h+10],9,38016083)),(i=v(i,a,c,f,n[h+15],14,-660478335)),(f=v(f,i,a,c,n[h+4],20,-405537848)),(c=v(c,f,i,a,n[h+9],5,568446438)),(a=v(a,c,f,i,n[h+14],9,-1019803690)),(i=v(i,a,c,f,n[h+3],14,-187363961)),(f=v(f,i,a,c,n[h+8],20,1163531501)),(c=v(c,f,i,a,n[h+13],5,-1444681467)),(a=v(a,c,f,i,n[h+2],9,-51403784)),(i=v(i,a,c,f,n[h+7],14,1735328473)),(c=g(c,(f=v(f,i,a,c,n[h+12],20,-1926607734)),i,a,n[h+5],4,-378558)),(a=g(a,c,f,i,n[h+8],11,-2022574463)),(i=g(i,a,c,f,n[h+11],16,1839030562)),(f=g(f,i,a,c,n[h+14],23,-35309556)),(c=g(c,f,i,a,n[h+1],4,-1530992060)),(a=g(a,c,f,i,n[h+4],11,1272893353)),(i=g(i,a,c,f,n[h+7],16,-155497632)),(f=g(f,i,a,c,n[h+10],23,-1094730640)),(c=g(c,f,i,a,n[h+13],4,681279174)),(a=g(a,c,f,i,n[h],11,-358537222)),(i=g(i,a,c,f,n[h+3],16,-722521979)),(f=g(f,i,a,c,n[h+6],23,76029189)),(c=g(c,f,i,a,n[h+9],4,-640364487)),(a=g(a,c,f,i,n[h+12],11,-421815835)),(i=g(i,a,c,f,n[h+15],16,530742520)),(c=m(c,(f=g(f,i,a,c,n[h+2],23,-995338651)),i,a,n[h],6,-198630844)),(a=m(a,c,f,i,n[h+7],10,1126891415)),(i=m(i,a,c,f,n[h+14],15,-1416354905)),(f=m(f,i,a,c,n[h+5],21,-57434055)),(c=m(c,f,i,a,n[h+12],6,1700485571)),(a=m(a,c,f,i,n[h+3],10,-1894986606)),(i=m(i,a,c,f,n[h+10],15,-1051523)),(f=m(f,i,a,c,n[h+1],21,-2054922799)),(c=m(c,f,i,a,n[h+8],6,1873313359)),(a=m(a,c,f,i,n[h+15],10,-30611744)),(i=m(i,a,c,f,n[h+6],15,-1560198380)),(f=m(f,i,a,c,n[h+13],21,1309151649)),(c=m(c,f,i,a,n[h+4],6,-145523070)),(a=m(a,c,f,i,n[h+11],10,-1120210379)),(i=m(i,a,c,f,n[h+2],15,718787259)),(f=m(f,i,a,c,n[h+9],21,-343485551)),(c=d(c,r)),(f=d(f,e)),(i=d(i,o)),(a=d(a,u));return[c,f,i,a]}function a(n){for(var t='',r=32*n.length,e=0;e<r;e+=8)t+=String.fromCharCode((n[e>>5]>>>e%32)&255);return t}function h(n){var t=[];for(t[(n.length>>2)-1]=void 0,e=0;e<t.length;e+=1)t[e]=0;for(var r=8*n.length,e=0;e<r;e+=8)t[e>>5]|=(255&n.charCodeAt(e\/8))<<e%32;return t}function e(n){for(var t,r='0123456789abcdef',e='',o=0;o<n.length;o+=1)(t=n.charCodeAt(o)),(e+=r.charAt((t>>>4)&15)+r.charAt(15&t));return e}function r(n){return unescape(encodeURIComponent(n))}function o(n){return a(i(h((t=r(n))),8*t.length));var t}function u(n,t){return(function(n,t){var r,e,o=h(n),u=[],c=[];for(u[15]=c[15]=void 0,16<o.length&&(o=i(o,8*n.length)),r=0;r<16;r+=1)(u[r]=909522486^o[r]),(c[r]=1549556828^o[r]);return((e=i(u.concat(h(t)),512+8*t.length)),a(i(c.concat(e),640)))})(r(n),r(t))}function t(n,t,r){return t?(r?u(t,n):e(u(t,n))):r?o(n):e(o(n))}return t(str)}\n\n \/**\n * 渲染标题内容\n * @param {object} widget 组件对象\n * @param {string} icon 图标地址\n * @param {string} title 标题内容\n * @param {bool|color} color 字体的颜色(自定义背景时使用,默认系统)\n *\/\n async renderHeader(widget, icon, title, color = false) {\n let header = widget.addStack();\n header.centerAlignContent();\n try {\n const image = await this.$request.get(icon, 'IMG');\n let _icon = header.addImage(image);\n _icon.imageSize = new Size(14, 14);\n _icon.cornerRadius = 4;\n } catch (e) {\n console.log(e);\n }\n header.addSpacer(10);\n let _title = header.addText(title);\n if (color) _title.textColor = color;\n _title.textOpacity = 0.7;\n _title.font = Font.boldSystemFont(12);\n _title.lineLimit = 1;\n widget.addSpacer(15);\n return widget;\n }\n\n \/**\n * @param message 描述内容\n * @param options 按钮\n * @returns {Promise<number>}\n *\/\n\n async generateAlert(message, options) {\n let alert = new Alert();\n alert.message = message;\n\n for (const option of options) {\n alert.addAction(option);\n }\n return await alert.presentAlert();\n }\n\n \/**\n * 弹出一个通知\n * @param {string} title 通知标题\n * @param {string} body 通知内容\n * @param {string} url 点击后打开的URL\n *\/\n async notify(title, body, url, opts = {}) {\n let n = new Notification();\n n = Object.assign(n, opts);\n n.title = title;\n n.body = body;\n if (url) n.openURL = url;\n return await n.schedule();\n }\n\n \/**\n * 给图片加一层半透明遮罩\n * @param {Image} img 要处理的图片\n * @param {string} color 遮罩背景颜色\n * @param {float} opacity 透明度\n *\/\n async shadowImage(img, color = '#000000', opacity = 0.7) {\n if (!img) return;\n if (opacity === 0) return img;\n let ctx = new DrawContext();\n \/\/ 获取图片的尺寸\n ctx.size = img.size;\n\n ctx.drawImageInRect(\n img,\n new Rect(0, 0, img.size['width'], img.size['height'])\n );\n ctx.setFillColor(new Color(color, opacity));\n ctx.fillRect(new Rect(0, 0, img.size['width'], img.size['height']));\n return await ctx.getImage();\n }\n\n \/**\n * 获取当前插件的设置\n * @param {boolean} json 是否为json格式\n *\/\n getSettings(json = true) {\n let res = json ? {} : '';\n let cache = '';\n if (Keychain.contains(this.SETTING_KEY)) {\n cache = Keychain.get(this.SETTING_KEY);\n }\n\n if (json) {\n try {\n res = JSON.parse(cache);\n } catch (e) {}\n } else {\n res = cache;\n }\n\n return res;\n }\n\n getBaseSettings(json = true) {\n let res = json ? {} : '';\n let cache = '';\n if (Keychain.contains(this.BaseCacheKey)) {\n cache = Keychain.get(this.BaseCacheKey);\n }\n\n if (json) {\n try {\n res = JSON.parse(cache);\n } catch (e) {}\n } else {\n res = cache;\n }\n\n return res;\n }\n\n saveBaseSettings(res = {}, notify = true) {\n const data = { ...(this.baseSettings || {}), ...res };\n this.baseSettings = data;\n Keychain.set(this.BaseCacheKey, JSON.stringify(data));\n if (notify) this.notify('设置成功', '通用设置需重新运行脚本生效');\n return data;\n }\n\n \/**\n * 存储当前设置\n * @param {bool} notify 是否通知提示\n *\/\n saveSettings(notify = true) {\n let res =\n typeof this.settings === 'object'\n ? JSON.stringify(this.settings)\n : String(this.settings);\n Keychain.set(this.SETTING_KEY, res);\n\n if (notify) this.notify('设置成功', '桌面组件稍后将自动刷新');\n\n return res;\n }\n\n \/**\n * 获取当前插件是否有自定义背景图片\n * @reutrn img | false\n *\/\n getBackgroundImage() {\n if (this.FILE_MGR.fileExists(this.cacheImageBgPath[0]))\n return Image.fromFile(this.cacheImageBgPath[0]);\n\n if (!this.isNight)\n return this.FILE_MGR.fileExists(this.cacheImageBgPath[1])\n ? Image.fromFile(this.cacheImageBgPath[1])\n : undefined;\n else\n return this.FILE_MGR.fileExists(this.cacheImageBgPath[2])\n ? Image.fromFile(this.cacheImageBgPath[2])\n : undefined;\n }\n\n \/**\n * 设置当前组件的背景图片\n * @param {Image} img\n *\/\n setBackgroundImage(img, filePath = this.baseImage, notify = true) {\n const cacheKey = filePath;\n if (!img) {\n \/\/ 移除背景\n if (this.FILE_MGR.fileExists(cacheKey)) this.FILE_MGR.remove(cacheKey);\n if (notify) this.notify('移除成功', '背景图片已移除,稍后刷新生效');\n } else {\n \/\/ 设置背景\n this.FILE_MGR.writeImage(cacheKey, img);\n\n if (notify) this.notify('设置成功', '背景图片已设置!稍后刷新生效');\n return `data:image\/png;base64,${Data.fromFile(\n cacheKey\n ).toBase64String()}`;\n }\n }\n\n getRandomArrayElements(arr, count) {\n let shuffled = arr.slice(0),\n i = arr.length,\n min = i - count,\n temp,\n index;\n min = min > 0 ? min : 0;\n while (i-- > min) {\n index = Math.floor((i + 1) * Math.random());\n temp = shuffled[index];\n shuffled[index] = shuffled[i];\n shuffled[i] = temp;\n }\n return shuffled.slice(min);\n }\n\n textFormat = {\n defaultText: { size: 14, font: 'regular', color: this.widgetColor },\n battery: { size: 10, font: 'bold', color: this.widgetColor },\n title: { size: 16, font: 'semibold', color: this.widgetColor },\n SFMono: { size: 12, font: 'SF Mono', color: this.widgetColor },\n };\n\n provideFont = (fontName, fontSize) => {\n const fontGenerator = {\n ultralight: function () {\n return Font.ultraLightSystemFont(fontSize);\n },\n light: function () {\n return Font.lightSystemFont(fontSize);\n },\n regular: function () {\n return Font.regularSystemFont(fontSize);\n },\n medium: function () {\n return Font.mediumSystemFont(fontSize);\n },\n semibold: function () {\n return Font.semiboldSystemFont(fontSize);\n },\n bold: function () {\n return Font.boldSystemFont(fontSize);\n },\n heavy: function () {\n return Font.heavySystemFont(fontSize);\n },\n black: function () {\n return Font.blackSystemFont(fontSize);\n },\n italic: function () {\n return Font.italicSystemFont(fontSize);\n },\n };\n\n const systemFont = fontGenerator[fontName];\n if (systemFont) {\n return systemFont();\n }\n return new Font(fontName, fontSize);\n };\n\n provideText = (\n string,\n container,\n format = {\n font: 'light',\n size: 14,\n color: this.widgetColor,\n opacity: 1,\n minimumScaleFactor: 1,\n }\n ) => {\n const textItem = container.addText(string);\n const textFont = format.font;\n const textSize = format.size;\n const textColor = format.color;\n\n textItem.font = this.provideFont(textFont, textSize);\n textItem.textColor = textColor;\n textItem.textOpacity = format.opacity || 1;\n textItem.minimumScaleFactor = format.minimumScaleFactor || 1;\n return textItem;\n };\n}\n\n\/\/ @base.end\nconst Runing = async (Widget, default_args = '', isDebug = true, extra) => {\n let M = null;\n \/\/ 判断hash是否和当前设备匹配\n if (config.runsInWidget) {\n M = new Widget(args.widgetParameter || '');\n\n if (extra) {\n Object.keys(extra).forEach((key) => {\n M[key] = extra[key];\n });\n }\n const W = await M.render();\n try {\n if (M.settings.refreshAfterDate) {\n const refreshTime = parseInt(M.settings.refreshAfterDate) * 1000 * 60;\n const timeStr = new Date().getTime() + refreshTime;\n W.refreshAfterDate = new Date(timeStr);\n }\n } catch (e) {\n console.log(e);\n }\n if (W) {\n Script.setWidget(W);\n Script.complete();\n }\n } else {\n let { act, __arg, __size } = args.queryParameters;\n M = new Widget(__arg || default_args || '');\n if (extra) {\n Object.keys(extra).forEach((key) => {\n M[key] = extra[key];\n });\n }\n if (__size) M._init(__size);\n if (!act || !M['_actions']) {\n \/\/ 弹出选择菜单\n const actions = M['_actions'];\n const onClick = async (item) => {\n M.widgetFamily = item.val;\n try {\n M._init(item.val);\n } catch (error) {\n console.log('初始化异常:' + error);\n }\n w = await M.render();\n const fnc = item.val\n .toLowerCase()\n .replace(\/( |^)[a-z]\/g, (L) => L.toUpperCase());\n if (w) {\n return w[`present${fnc}`]();\n }\n };\n const preview = [\n {\n url: `https:\/\/raw.githubusercontent.com\/dompling\/Scriptable\/master\/images\/small.png`,\n title: '小尺寸',\n val: 'small',\n name: 'small',\n dismissOnSelect: true,\n onClick,\n },\n {\n url: `https:\/\/raw.githubusercontent.com\/dompling\/Scriptable\/master\/images\/medium.png`,\n title: '中尺寸',\n val: 'medium',\n name: 'medium',\n dismissOnSelect: true,\n onClick,\n },\n {\n url: `https:\/\/raw.githubusercontent.com\/dompling\/Scriptable\/master\/images\/large.png`,\n title: '大尺寸',\n val: 'large',\n name: 'large',\n dismissOnSelect: true,\n onClick,\n },\n ];\n\n const menuConfig = [\n { title: '预览组件', menu: preview },\n { title: '组件配置', menu: actions },\n ...M['_menuActions'],\n ];\n await M.renderAppView(menuConfig, true);\n }\n }\n};\n\/\/ await new DmYY().setWidgetConfig();\nmodule.exports = { DmYY, Runing };\n\n\/\/version:1.1.0",
"share_sheet_inputs" : [
]
}