-
Notifications
You must be signed in to change notification settings - Fork 87
Expand file tree
/
Copy pathWeibo_v2.1.3.scriptable
More file actions
12 lines (11 loc) · 26.3 KB
/
Copy pathWeibo_v2.1.3.scriptable
File metadata and controls
12 lines (11 loc) · 26.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
{
"always_run_in_app" : false,
"icon" : {
"color" : "orange",
"glyph" : "rss-square"
},
"name" : "Weibo_v2.1.3",
"script" : "\/\/ Variables used by Scriptable.\n\/\/ These must be at the very top of the file. Do not edit.\n\/\/ icon-color: pink; icon-glyph: fire;\n\/**\n * Top trending searches on Weibo\n *\n * @version 2.1.3\n * @author Honye\n *\/\n\n\/**\n * @param {number} [height] The screen height measured in pixels\n *\/\nconst phoneSize = (height) => {\n const phones = {\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 \/** 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 \/** 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 \/** 11, XR *\/\n 1792: {\n small: 338,\n medium: 720,\n large: 758,\n left: 55,\n right: 437,\n top: 159,\n middle: 579,\n bottom: 999\n },\n \/** 11 Pro, XS, X, 12 mini *\/\n 2436: {\n small: 465,\n medium: 987,\n large: 1035,\n x: {\n left: 69,\n right: 591,\n top: 213,\n middle: 783,\n bottom: 1353\n },\n mini: {\n left: 69,\n right: 591,\n top: 231,\n middle: 801,\n bottom: 1371\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 \/** 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 \/** 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 \/** 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 \/** 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 height = height || Device.screenResolution().height;\n const scale = Device.screenScale();\n\n const phone = phones[height];\n if (phone) {\n return phone\n }\n\n if (config.runsInWidget) {\n const pc = {\n small: 164 * scale,\n medium: 344 * scale,\n large: 354 * scale\n };\n return pc\n }\n\n \/\/ in app screen fixed 375x812 pt\n return {\n small: 155 * scale,\n medium: 329 * scale,\n large: 345 * scale\n }\n};\n\nconst getImage = async (url) => {\n const request = new Request(url);\n const image = await request.loadImage();\n return image\n};\n\nconst useCache$1 = () => {\n const fm = FileManager.local();\n const cacheDirectory = fm.joinPath(fm.documentsDirectory(), `${Script.name()}\/cache`);\n \/**\n * 删除路径末尾所有的 \/\n * @param {string} filePath\n *\/\n const safePath = (filePath) => {\n return fm.joinPath(cacheDirectory, filePath).replace(\/\\\/+$\/, '')\n };\n \/**\n * 如果上级文件夹不存在,则先创建文件夹\n * @param {string} filePath\n *\/\n const preWrite = (filePath) => {\n const i = filePath.lastIndexOf('\/');\n const directory = filePath.substring(0, i);\n if (!fm.fileExists(directory)) {\n fm.createDirectory(directory, true);\n }\n };\n\n const writeString = (filePath, content) => {\n const nextPath = safePath(filePath);\n preWrite(nextPath);\n fm.writeString(nextPath, content);\n };\n\n const writeJSON = (filePath, jsonData) => writeString(filePath, JSON.stringify(jsonData));\n \/**\n * @param {string} filePath\n * @param {Image} image\n *\/\n const writeImage = (filePath, image) => {\n const nextPath = safePath(filePath);\n preWrite(nextPath);\n return fm.writeImage(nextPath, image)\n };\n\n const readString = (filePath) => {\n return fm.readString(\n fm.joinPath(cacheDirectory, filePath)\n )\n };\n\n const readJSON = (filePath) => JSON.parse(readString(filePath));\n \/**\n * @param {string} filePath\n *\/\n const readImage = (filePath) => {\n return fm.readImage(fm.joinPath(cacheDirectory, filePath))\n };\n\n return {\n cacheDirectory,\n writeString,\n writeJSON,\n writeImage,\n readString,\n readJSON,\n readImage\n }\n};\n\n\/**\n * @param {string} data\n *\/\nconst hashCode = (data) => {\n return Array.from(data).reduce((accumulator, currentChar) => Math.imul(31, accumulator) + currentChar.charCodeAt(0), 0)\n};\n\nconst useCache = (useICloud) => {\n const fm = FileManager[useICloud ? 'iCloud' : 'local']();\n const cacheDirectory = fm.joinPath(fm.documentsDirectory(), Script.name());\n\n const writeString = (filePath, content) => {\n const safePath = fm.joinPath(cacheDirectory, filePath).replace(\/\\\/+$\/, '');\n const i = safePath.lastIndexOf('\/');\n const directory = safePath.substring(0, i);\n if (!fm.fileExists(directory)) {\n fm.createDirectory(directory, true);\n }\n fm.writeString(safePath, content);\n };\n\n const writeJSON = (filePath, jsonData) => writeString(filePath, JSON.stringify(jsonData));\n\n const readString = (filePath) => {\n return fm.readString(\n fm.joinPath(cacheDirectory, filePath)\n )\n };\n\n const readJSON = (filePath) => JSON.parse(readString(filePath));\n\n return {\n cacheDirectory,\n writeString,\n writeJSON,\n readString,\n readJSON\n }\n};\n\nconst readSettings = async () => {\n const localFM = useCache();\n let settings = localFM.readJSON('settings.json');\n if (settings) {\n console.log('[info] use local settings');\n return settings\n }\n\n const iCloudFM = useCache(true);\n settings = iCloudFM.readJSON('settings.json');\n if (settings) {\n console.log('[info] use iCloud settings');\n }\n return settings\n};\n\nconst writeSettings = async (data, { useICloud }) => {\n const fm = useCache(useICloud);\n fm.writeJSON('settings.json', data);\n};\n\nconst removeSettings = async (settings) => {\n const cache = useCache(settings.useICloud);\n FileManager.local().remove(\n FileManager.local().joinPath(\n cache.cacheDirectory,\n 'settings.json'\n )\n );\n};\n\nconst moveSettings = (useICloud, data) => {\n const localFM = useCache();\n const iCloudFM = useCache(true);\n const [i, l] = [\n FileManager.local().joinPath(\n iCloudFM.cacheDirectory,\n 'settings.json'\n ),\n FileManager.local().joinPath(\n localFM.cacheDirectory,\n 'settings.json'\n )\n ];\n try {\n writeSettings(data, { useICloud });\n if (useICloud) {\n FileManager.local().remove(l);\n } else {\n FileManager.iCloud().remove(i);\n }\n } catch (e) {\n console.error(e);\n }\n};\n\n\/**\n * @param {object} options\n * @param {{\n * name: string;\n * label: string;\n * type: string;\n * default: unknow;\n * }[]} options.formItems\n * @param {(data: {\n * settings: Record<string, string>;\n * family: string;\n * }) => Promise<ListWidget>} options.render\n * @param {string} [options.homePage]\n * @returns {Promise<ListWidget|undefined>} 在 Widget 中运行时返回 ListWidget,其它无返回\n *\/\nconst withSettings = async (options = {}) => {\n const {\n formItems = [],\n render,\n homePage = 'https:\/\/www.imarkr.com'\n } = options;\n\n let settings = await readSettings() || {};\n\n if (config.runsInWidget) {\n const widget = await render({ settings });\n return widget\n }\n\n \/\/ ====== web start =======\n const style =\n`:root {\n --color-primary: #007aff;\n --divider-color: rgba(60,60,67,0.36);\n --card-background: #fff;\n --card-radius: 10px;\n --list-header-color: rgba(60,60,67,0.6);\n}\n* {\n -webkit-user-select: none;\n user-select: none;\n}\nbody {\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}\ninput {\n -webkit-user-select: auto;\n user-select: auto;\n}\nbody {\n background: #f2f2f7;\n}\nbutton {\n font-size: 16px;\n background: var(--color-primary);\n color: #fff;\n border-radius: 8px;\n border: none;\n padding: 0.24em 0.5em;\n}\nbutton .iconfont {\n margin-right: 6px;\n}\n.list {\n margin: 15px;\n}\n.list__header {\n margin: 0 20px;\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 border-radius: 12px;\n overflow: hidden;\n}\n.form-item {\n display: flex;\n align-items: center;\n justify-content: space-between;\n font-size: 16px;\n min-height: 2em;\n padding: 0.5em 20px;\n position: relative;\n}\n.form-item--link .icon-arrow_right {\n color: #86868b;\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 .iconfont {\n margin-right: 4px;\n}\n.form-item input,\n.form-item select {\n font-size: 14px;\n text-align: right;\n}\n.form-item input[type=\"checkbox\"] {\n width: 1.25em;\n height: 1.25em;\n}\ninput[type=\"number\"] {\n width: 4em;\n}\ninput[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}\ninput[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}\ninput[type='checkbox'][role='switch']:checked {\n background: var(--color-primary);\n}\ninput[type='checkbox'][role='switch']:checked::before {\n transform: translateX(16px);\n}\n.actions {\n margin: 15px;\n}\n.copyright {\n margin: 15px;\n font-size: 12px;\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@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 const settings = JSON.parse('${JSON.stringify(settings)}')\n const formItems = JSON.parse('${JSON.stringify(formItems)}')\n \n window.invoke = (code, data) => {\n window.dispatchEvent(\n new CustomEvent(\n 'JBridge',\n { detail: { code, data } }\n )\n )\n }\n \n const iCloudInput = document.querySelector('input[name=\"useICloud\"]')\n iCloudInput.checked = settings.useICloud\n iCloudInput\n .addEventListener('change', (e) => {\n invoke('moveSettings', e.target.checked)\n })\n \n const formData = {};\n\n const fragment = document.createDocumentFragment()\n for (const item of formItems) {\n const value = settings[item.name] ?? item.default ?? null\n formData[item.name] = value;\n const label = document.createElement(\"label\");\n label.className = \"form-item\";\n const div = document.createElement(\"div\");\n div.innerText = item.label;\n label.appendChild(div);\n if (item.type === 'select') {\n const select = document.createElement('select')\n select.className = 'form-item__input'\n select.name = item.name\n select.value = value\n for (const opt of (item.options || [])) {\n const option = document.createElement('option')\n option.value = opt.value\n option.innerText = opt.label\n option.selected = value === opt.value\n select.appendChild(option)\n }\n select.addEventListener('change', (e) => {\n formData[item.name] = e.target.value\n invoke('changeSettings', formData)\n })\n label.appendChild(select)\n } else {\n const input = document.createElement(\"input\")\n input.className = 'form-item__input'\n input.name = item.name\n input.type = item.type || \"text\";\n input.enterKeyHint = 'done'\n input.value = value\n \/\/ Switch\n if (item.type === 'switch') {\n input.type = 'checkbox'\n input.role = 'switch'\n input.checked = value\n }\n if (item.type === 'number') {\n input.inputMode = 'decimal'\n }\n if (input.type === 'text') {\n input.size = 12\n }\n input.addEventListener(\"change\", (e) => {\n formData[item.name] =\n item.type === 'switch'\n ? e.target.checked\n : item.type === 'number'\n ? Number(e.target.value)\n : e.target.value;\n invoke('changeSettings', formData)\n });\n label.appendChild(input);\n }\n fragment.appendChild(label);\n }\n document.getElementById('form').appendChild(fragment)\n\n for (const btn of document.querySelectorAll('.preview')) {\n btn.addEventListener('click', (e) => {\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 === 'previewStart') {\n target.classList.remove('loading')\n icon.className = className\n window.removeEventListener('JWeb', listener);\n }\n }\n window.addEventListener('JWeb', listener)\n invoke('preview', e.currentTarget.dataset.size)\n })\n }\n\n const reset = () => {\n for (const item of formItems) {\n const el = document.querySelector(\\`.form-item__input[name=\"\\${item.name}\"]\\`)\n formData[item.name] = item.default\n if (item.type === 'switch') {\n el.checked = item.default\n } else {\n el.value = item.default\n }\n }\n invoke('removeSettings', formData)\n }\n document.getElementById('reset').addEventListener('click', () => reset())\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=\"\/\/at.alicdn.com\/t\/c\/font_3772663_kmo790s3yfq.css\" type=\"text\/css\">\n <style>${style}<\/style>\n <\/head>\n <body>\n <div class=\"list\">\n <div class=\"list__header\">一般<\/div>\n <form class=\"list__body\" action=\"javascript:void(0);\">\n <label class=\"form-item\">\n <div>iCloud 同步<\/div>\n <input name=\"useICloud\" type=\"checkbox\" role=\"switch\">\n <\/label>\n <label id='reset' class=\"form-item form-item--link\">\n <div>恢复默认<\/div>\n <i class=\"iconfont icon-arrow_right\"><\/i>\n <\/label>\n <\/form>\n <\/div>\n <div class=\"list\">\n <div class=\"list__header\">设置<\/div>\n <form id=\"form\" class=\"list__body\" action=\"javascript:void(0);\"><\/form>\n <\/div>\n <div class=\"actions\">\n <button class=\"preview\" data-size=\"small\"><i class=\"iconfont icon-yingyongzhongxin\"><\/i>小组件<\/button>\n <button class=\"preview\" data-size=\"medium\"><i class=\"iconfont icon-daliebiao\"><\/i>中组件<\/button>\n <button class=\"preview\" data-size=\"large\"><i class=\"iconfont icon-dantupailie\"><\/i>大组件<\/button>\n <\/div>\n <footer>\n <div class=\"copyright\">Copyright © 2022 <a href=\"javascript:invoke('safari','https:\/\/www.imarkr.com');\">iMarkr<\/a> All rights reserved.<\/div>\n <\/footer>\n <script>${js}<\/script>\n <\/body>\n<\/html>`;\n\n const webView = new WebView();\n await webView.loadHTML(html, homePage);\n\n const injectListener = async () => {\n const event = await webView.evaluateJavaScript(\n `(() => {\n const controller = new AbortController()\n const listener = (e) => {\n completion(e.detail)\n controller.abort()\n }\n window.addEventListener(\n 'JBridge',\n listener,\n { signal: controller.signal }\n )\n })()`,\n true\n ).catch((err) => {\n console.error(err);\n throw err\n });\n const { code, data } = event;\n switch (code) {\n case 'preview': {\n const widget = await render({ settings, family: data });\n webView.evaluateJavaScript(\n 'window.dispatchEvent(new CustomEvent(\\'JWeb\\', { detail: { code: \\'previewStart\\' } }))',\n false\n );\n widget[`present${data.replace(data[0], data[0].toUpperCase())}`]();\n break\n }\n case 'safari':\n Safari.openInApp(data, true);\n break\n case 'changeSettings':\n settings = { ...settings, ...data };\n writeSettings(data, { useICloud: settings.useICloud });\n break\n case 'moveSettings':\n settings.useICloud = data;\n moveSettings(data, settings);\n break\n case 'removeSettings':\n settings = { ...settings, ...data };\n removeSettings(settings);\n break\n }\n injectListener();\n };\n\n injectListener().catch((e) => {\n console.error(e);\n throw e\n });\n webView.present();\n \/\/ ======= web end =========\n};\n\nconst paddingVertical = 10;\nconst themes = {\n light: {\n background: new Color('#ffffff')\n },\n dark: {\n background: new Color('#282828')\n }\n}; \/\/背景颜色\nconst preference = {\n \/** @type {'light'|'dark'|'system'} *\/\n colorScheme: 'system',\n \/** @type {'h5'|'international'} *\/\n client: 'h5',\n fontSize: 14,\n useShadow: false,\n lightColor: '#333333',\n darkColor: '#ffffff',\n timeColor: '#666666',\n logoSize: 30,\n padding: [NaN, 12, NaN, 14],\n gap: 8\n};\n\n\/** 微博国际版页面 *\/\nconst InternationalScheme = {\n hotSearch: () => 'weibointernational:\/\/hotsearch',\n search: (keyword) => `weibointernational:\/\/search?keyword=${encodeURIComponent(keyword)}`\n};\n\n\/** 微博 H5 应用页面 *\/\nconst H5Page = {\n hotSearch: () => `https:\/\/m.weibo.cn\/p\/index?containerid=${encodeURIComponent('106003&filter_type=realtimehot')}`,\n search: (keyword) => `https:\/\/m.weibo.cn\/search?containerid=${encodeURIComponent('100103type=1&t=10&q=' + keyword)}`\n};\n\nconst conf = {};\nconst screen = Device.screenResolution();\nconst scale = Device.screenScale();\nconst phone = phoneSize(screen.height);\nconst cache = useCache$1();\n\nif (config.runsInWidget) {\n const [client, colorScheme] = (args.widgetParameter || '').split(',').map(text => text.trim());\n preference.client = client === '2' ? 'international' : preference.client;\n preference.colorScheme = colorScheme || preference.colorScheme;\n}\n\nconst Pages = () => {\n switch (preference.client) {\n case 'international':\n return InternationalScheme\n case 'h5':\n return H5Page\n }\n};\n\nconst fetchData = async () => {\n const url = 'https:\/\/weibointl.api.weibo.cn\/portal.php?ct=feed&a=search_topic';\n const request = new Request(url);\n try {\n const res = await request.loadJSON();\n const df = new DateFormatter();\n df.dateFormat = 'HH:mm';\n const timeString = df.string(new Date());\n const data = {\n data: res,\n updatedAt: timeString\n };\n cache.writeJSON('trending.json', data);\n return data\n } catch (e) {\n const data = cache.readJSON('trending.json');\n return data\n }\n};\n\nconst getLogoImage = async () => {\n try {\n const image = cache.readImage('logo.png');\n if (!image) {\n throw new Error('no cache')\n }\n return image\n } catch (e) {\n const image = await getImage('https:\/\/www.sinaimg.cn\/blog\/developer\/wiki\/LOGO_64x64.png');\n cache.writeImage('logo.png', image);\n return image\n }\n};\n\nconst createWidget = async ({ data, updatedAt }) => {\n const {\n fontSize,\n timeColor,\n colorScheme,\n logoSize,\n padding,\n gap\n } = preference;\n const { widgetFamily } = config;\n const heightPX = widgetFamily === 'medium' ? phone.small : phone[widgetFamily];\n const height = heightPX \/ scale;\n conf.count = Math.floor((height - paddingVertical * 2 + gap) \/ (fontSize + gap));\n if (widgetFamily === 'small') {\n padding[1] = padding[3] = 6;\n }\n\n let stackBottom;\n let widgetBottom;\n const widget = new ListWidget();\n widget.backgroundColor = colorScheme === 'system'\n ? Color.dynamic(themes.light.background, themes.dark.background)\n : themes[colorScheme].background;\n widget.url = Pages().hotSearch();\n const paddingY = paddingVertical - (gap \/ 2);\n widget.setPadding(paddingY, padding[1], paddingY, padding[3]);\n\n const max = conf.count;\n const logoLines = logoSize ? Math.ceil((logoSize + gap) \/ (fontSize + gap)) : 0;\n for (let i = 0; i < max; ++i) {\n const item = data.data[i];\n if (i === 0) {\n const stack = widget.addStack();\n await addItem(stack, item);\n stack.addSpacer();\n const textTime = stack.addText(`更新于 ${updatedAt}`);\n textTime.font = Font.systemFont(fontSize * 0.7);\n textTime.textColor = new Color(timeColor);\n } else if (i < max - logoLines) {\n await addItem(widget, item);\n } else {\n if (!widgetBottom) {\n stackBottom = widget.addStack();\n stackBottom.bottomAlignContent();\n widgetBottom = stackBottom.addStack();\n widgetBottom.layoutVertically();\n addItem(widgetBottom, item);\n } else {\n await addItem(widgetBottom, item);\n }\n widgetBottom.length = (widgetBottom.length || 0) + 1;\n if (widgetBottom.length === logoLines) {\n stackBottom.addSpacer();\n const imageLogo = stackBottom.addImage(await getLogoImage());\n imageLogo.imageSize = new Size(logoSize, logoSize);\n }\n }\n }\n return widget\n};\n\nconst getIcon = async (src) => {\n const hash = `${hashCode(src)}`;\n try {\n const image = await getImage(src);\n cache.writeImage(hash, image);\n return image\n } catch (e) {\n return cache.readImage(hash)\n }\n};\n\nconst addItem = async (widget, item) => {\n const {\n fontSize,\n useShadow,\n lightColor,\n darkColor,\n colorScheme,\n gap\n } = preference;\n const stack = widget.addStack();\n const [, queryString] = item.scheme.split('?');\n const query = {};\n queryString.split('&').forEach((item) => {\n const [key, value] = item.split('=');\n query[key] = value;\n });\n stack.url = Pages().search(query.keyword);\n stack.centerAlignContent();\n stack.size = new Size(-1, fontSize + gap);\n const stackIndex = stack.addStack();\n stackIndex.size = new Size(fontSize * 1.4, -1);\n const textIndex = stackIndex.addText(String(item.pic_id));\n textIndex.rightAlignText();\n textIndex.textColor = item.pic_id > 3 ? new Color('#FF9400', 0.7) : new Color('#ED402E', 0.7);\/\/排序文字颜色 后+前\n textIndex.font = Font.boldSystemFont(fontSize);\n stack.addSpacer(4);\n const textTitle = stack.addText(item.title);\n textTitle.font = Font.systemFont(fontSize);\n textTitle.textColor = colorScheme === 'system'\n ? Color.dynamic(new Color(lightColor), new Color(darkColor))\n : colorScheme === 'light'\n ? new Color(lightColor)\n : new Color(darkColor);\n textTitle.lineLimit = 1;\n if (useShadow) {\n textTitle.shadowColor = Color.dynamic(\n new Color(lightColor, 0.2),\n new Color(darkColor, 0.2)\n );\n textTitle.shadowOffset = new Point(1, 1);\n textTitle.shadowRadius = 0.5;\n }\n if (item.icon) {\n stack.addSpacer(4);\n const imageIcon = stack.addImage(await getIcon(item.icon));\n imageIcon.imageSize = new Size(12, 12);\n }\n stack.addSpacer();\n};\n\nconst main = async () => {\n const data = await fetchData();\n\n const widget = await withSettings({\n homePage: 'https:\/\/github.com\/Honye\/scriptable-scripts',\n formItems: [\n {\n name: 'client',\n label: '查看方式',\n type: 'select',\n options: [\n { label: 'H5 (微博)', value: 'h5' },\n { label: '微博国际版', value: 'international' }\n ],\n default: preference.client\n },\n {\n name: 'lightColor',\n label: '字体颜色(浅色)',\n type: 'color',\n default: preference.lightColor\n },\n {\n name: 'darkColor',\n label: '字体颜色(暗色)',\n type: 'color',\n default: preference.darkColor\n },\n {\n name: 'useShadow',\n label: '字体阴影',\n type: 'switch',\n default: preference.useShadow\n },\n {\n name: 'fontSize',\n label: '字体大小',\n type: 'number',\n default: preference.fontSize\n },\n {\n name: 'timeColor',\n label: '更新颜色',\n type: 'color',\n default: preference.timeColor\n },\n {\n name: 'logoSize',\n label: 'Logo 大小(0:隐藏)',\n type: 'number',\n default: preference.logoSize\n }\n ],\n render: async ({ family, settings }) => {\n family && (config.widgetFamily = family);\n Object.assign(preference, settings);\n try {\n return await createWidget(data)\n } catch (e) {\n console.error(e);\n }\n }\n });\n if (config.runsInWidget) {\n Script.setWidget(widget);\n }\n \/\/ if (config.runsInApp) {\n \/\/ const res = await presentSheet({\n \/\/ message: 'Preview the widget or update the script. Update will override the whole script.',\n \/\/ options: [\n \/\/ { title: 'Update', value: 'Update' }\n \/\/ ]\n \/\/ })\n \/\/ const value = res.option?.value\n \/\/ switch (value) {\n \/\/ case 'Update':\n \/\/ update()\n \/\/ break\n \/\/ }\n \/\/ }\n\n Script.complete();\n};\n\nawait main();\n",
"share_sheet_inputs" : [
]
}