[fix]页面创建

This commit is contained in:
2022-06-02 17:00:01 +08:00
commit efe5165c9e
20 changed files with 4514 additions and 0 deletions

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,372 @@
// Variables used by Scriptable.
// These must be at the very top of the file. Do not edit.
// icon-color: deep-green; icon-glyph: angle-double-right;
//
// iOS 桌面组件脚本 @「小件件」
// 开发说明:请从 Widget 类开始编写,注释请勿修改
// https://x.im3x.cn
//
// 添加require是为了vscode中可以正确引入包以获得自动补全等功能
if (typeof require === 'undefined') require = importModule
const { Base } = require("./「小件件」开发环境")
// @组件代码开始
class Widget extends Base {
constructor (arg) {
super(arg)
this.logo = "https://www.v2ex.com/static/img/icon_rayps_64.png"
this.name = "V2EX"
this.desc = "创意工作者们的社区"
// 请求数据接口列表(收集整理中)
this.API = [
// api
[
{
id: 'latest',
name: '最新'
}, {
id: 'hot',
name: '最热'
}
],
// tab
[
{
id: 'all',
name: '全部'
}, {
id: 'hot',
name: '最热'
}, {
id: 'tech',
name: '技术'
}, {
id: 'creative',
name: '创意'
}, {
id: 'playplay',
name: '好玩'
}, {
id: 'apple',
name: 'Apple'
}, {
id: 'jobs',
name: '酷工作'
}, {
id: 'deals',
name: '交易'
}, {
id: 'city',
name: '城市'
}, {
id: 'qna',
name: '问与答'
}
],
// go
[],
]
// 当前设置的存储key提示可通过桌面设置不同参数来保存多个设置
let _md5 = this.md5(module.filename)
this.CACHE_KEY = `cache_${_md5}`
// 获取设置
// 格式type@name比如 go@create、api@hot、tab@all
this.SETTINGS = this.settings['node'] || 'tab@all'
// 注册操作菜单
this.registerAction("节点设置", this.actionSettings)
}
// 渲染组件
async render () {
// 加载节点列表
await this._loadNodes()
const data = await this.getData()
console.log(data)
if (this.widgetFamily === 'medium') {
return await this.renderMedium(data)
} else if (this.widgetFamily === 'large') {
return await this.renderLarge(data)
} else {
return await this.renderSmall(data)
}
}
async renderSmall (data) {
let w = new ListWidget()
let topic = data[0]
w.url = this.actionUrl('open-url', topic['url'])
w = await this.renderHeader(w, this.logo, this.name + ' / ' + this.SETTINGS.split('@')[0], Color.white())
let content = w.addText(topic['title'])
content.font = Font.lightSystemFont(16)
content.textColor = Color.white()
content.lineLimit = 3
w.backgroundImage = await this.shadowImage(await this.getImageByUrl(topic['member']['avatar_large'].replace('mini', 'large')))
w.addSpacer()
let footer = w.addText(`@${topic['member']['username']} / ${topic['node']['title']}`)
footer.font = Font.lightSystemFont(10)
footer.textColor = Color.white()
footer.textOpacity = 0.5
footer.lineLimit = 1
return w
}
// 中尺寸组件
async renderMedium (data) {
let w = new ListWidget()
// w.addSpacer(10)
// 设置名称
let tmp = this.SETTINGS.split('@')
let tid = tmp[0] === 'api' ? 0 : (tmp[0] === 'tab' ? 1 : 2)
let current = ''
this.API[tid].map(a => {
if (a['id'] === tmp[1]) current = a['name']
})
await this.renderHeader(w, this.logo, this.name + ' / ' + current)
w.addSpacer()
let body = w.addStack()
let bodyleft= body.addStack()
bodyleft.layoutVertically()
for (let i = 0; i < 2; i ++) {
bodyleft = await this.renderCell(bodyleft, data[i])
bodyleft.addSpacer()
}
// body.addSpacer()
w.url = this.actionUrl("settings")
return w
}
// 大尺寸组件
async renderLarge (data) {
let w = new ListWidget()
// w.addSpacer(10)
// 设置名称
let tmp = this.SETTINGS.split('@')
let tid = tmp[0] === 'api' ? 0 : (tmp[0] === 'tab' ? 1 : 2)
let current = ''
this.API[tid].map(a => {
if (a['id'] === tmp[1]) current = a['name']
})
await this.renderHeader(w, this.logo, this.name + ' / ' + current)
w.addSpacer()
let body = w.addStack()
let bodyleft= body.addStack()
bodyleft.layoutVertically()
for (let i = 0; i < 5; i ++) {
bodyleft = await this.renderCell(bodyleft, data[i])
bodyleft.addSpacer()
}
// body.addSpacer()
// w.addSpacer()
w.url = this.actionUrl("settings")
return w
}
async renderCell (widget, topic) {
let body = widget.addStack()
body.url = this.actionUrl('open-url', topic['url'])
let left = body.addStack()
let avatar = left.addImage(await this.getImageByUrl(topic['member']['avatar_large'].replace('mini', 'large')))
avatar.imageSize = new Size(35, 35)
avatar.cornerRadius = 5
body.addSpacer(10)
let right = body.addStack()
right.layoutVertically()
let content = right.addText(topic['title'])
content.font = Font.lightSystemFont(14)
content.lineLimit = 1
right.addSpacer(5)
let info = right.addText(`@${topic['member']['username']} / ${topic['node']['title']}`)
info.font = Font.lightSystemFont(10)
info.textOpacity = 0.6
info.lineLimit = 2
widget.addSpacer()
return widget
}
async getData () {
// 解析设置,判断类型,获取对应数据
const tmp = this.SETTINGS.split('@')
switch (tmp[0]) {
case 'tab':
return await this.getDataForTab(tmp[1])
case 'go':
return await this.getDataForGo(tmp[1])
case 'api':
return await this.getDataForApi(tmp[1])
}
}
/**
* 获取首页tab数据
* @param {string} tab tab首页名称
*/
async getDataForTab (tab = 'all') {
let url = `https://www.v2ex.com/?tab=${tab}`
let html = await this.fetchAPI(url, false)
// 解析html
let tmp = html.split(`<div id="Wrapper">`)[1].split(`<div class="inner" style="text-align: right;">`)[0]
let arr = tmp.split('<div class="cell item">')
arr.shift()
let datas = []
for (let i = 0; i < arr.length; i ++) {
let t = arr[i]
let title = t.split(`class="topic-link">`)[1].split('</a')[0]
let avatar = t.split(`<img src="`)[1].split('"')[0]
let node = t.split(`<a class="node"`)[1].split('</a>')[0].split('>')[1]
let user = t.split(`<a class="node" href="`)[1].split('</strong>')[0].split('<strong>')[1].split('>')[1].split('</')[0]
let link = t.split(`<span class="item_title">`)[1].split('class="')[0].split('"')[1]
datas.push({
title,
url: `https://www.v2ex.com${link}`,
member: {
username: user,
'avatar_large': avatar
},
node: {
title: node
}
})
}
return datas
}
async getDataForApi (api) {
return await this.httpGet(`https://www.v2ex.com/api/topics/${api}.json`)
}
/**
* 加载数据
* 节点列表
*/
async getDataForGo (arg = 'create') {
let url = `https://www.v2ex.com/go/${arg}`
let html = await this.fetchAPI(url, false)
// 解析html
let tmp = html.split(`<div id="Wrapper">`)[1].split(`<div class="sidebar_units">`)[0]
let arr = tmp.split(`<table cellpadding="0" cellspacing="0" border="0" width="100%">`)
let node_title = html.split('<title>')[1].split('</')[0]
arr.shift()
arr.pop()
let datas = []
for (let i = 0; i < arr.length; i ++) {
let t = arr[i]
let title = t.split(`class="topic-link">`)[1].split('</a')[0]
let avatar = t.split(`<img src="`)[1].split('"')[0]
let user = t.split(`class="small fade"><strong>`)[1].split('</')[0]
let link = t.split(`<span class="item_title"><a href="`)[1].split('"')[0]
datas.push({
title,
url: `https://www.v2ex.com${link}`,
member: {
username: user,
'avatar_large': avatar
},
node: {
title: node_title
}
})
}
return datas
}
// http.get
async fetchAPI (api, json = true) {
let data = null
try {
let req = new Request(api)
req.headers = {
'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1 Edg/85.0.4183.102'
}
data = await (json ? req.loadJSON() : req.loadString())
} catch (e) {}
// 判断数据是否为空(加载失败)
if (!data) {
// 判断是否有缓存
if (Keychain.contains(this.CACHE_KEY)) {
let cache = Keychain.get(this.CACHE_KEY)
return json ? JSON.parse(cache) : cache
} else {
// 刷新
return null
}
}
// 存储缓存
Keychain.set(this.CACHE_KEY, json ? JSON.stringify(data) : data)
return data
}
// 加载节点列表
async _loadNodes () {
let s2 = await this.httpGet("https://www.v2ex.com/api/nodes/s2.json")
// 排序通过topic
let nodes = s2.sort((a,b) => b['topics']-a['topics'])
this.API[2] = nodes.map(n => ({
id: n['id'],
name: n['text'],
topic: n['topic']
}))
return this.API[2]
}
async actionOpenUrl (url) {
Safari.openInApp(url, false)
}
async actionSettings () {
const tmp = this.SETTINGS.split('@')
const a = new Alert()
a.title = "内容设置"
a.message = "设置组件展示的内容来自哪里"
a.addAction((tmp[0]==='api'?'✅ ':'')+"API接口")
a.addAction((tmp[0]==='tab'?'✅ ':'')+"首页目录")
a.addAction((tmp[0]==='go'?'✅ ':'')+"指定节点")
a.addCancelAction("取消设置")
const i = await a.presentSheet()
if (i === -1) return
const table = new UITable()
// 如果是节点,则先远程获取
if (i === 2 && this.API[2].length === 0) {
await this._loadNodes()
}
this.API[i].map(t => {
const r = new UITableRow()
r.addText((tmp[1]===t.id?'✅ ':'')+t['name'])
r.onSelect = (n) => {
// 保存设置
let _t = 'api';
_t = i === 1 ? 'tab' : _t;
_t = i === 2 ? 'go' : _t;
let v = `${_t}@${t['id']}`
this.SETTINGS = v
this.settings['node'] = v
this.saveSettings()
}
table.addRow(r)
})
table.present(false)
}
}
// @组件代码结束
const { Testing } = require("./「小件件」开发环境")
await Testing(Widget)

View File

@@ -0,0 +1,135 @@
// Variables used by Scriptable.
// These must be at the very top of the file. Do not edit.
// icon-color: blue; icon-glyph: apple-alt;
//
// iOS 桌面组件脚本 @「小件件」
// 开发说明:请从 Widget 类开始编写,注释请勿修改
// https://x.im3x.cn
//
// 添加require是为了vscode中可以正确引入包以获得自动补全等功能
if (typeof require === 'undefined') require = importModule
const { Base } = require("./「小件件」开发环境")
// @组件代码开始
class Widget extends Base {
/**
* 传递给组件的参数,可以是桌面 Parameter 数据,也可以是外部如 URLScheme 等传递的数据
* @param {string} arg 自定义参数
*/
constructor (arg) {
super(arg)
this.name = 'iOS 限免'
this.logo = 'https://api.kzddck.com/script/freeapp.png'
this.desc = 'AppStore 每日限免App速递'
this.registerAction("关于插件", this.aboutHandler)
}
/**
* 渲染函数,函数名固定
* 可以根据 this.widgetFamily 来判断小组件尺寸,以返回不同大小的内容
*/
async render () {
const data = await this.getData()
console.log('data=')
console.log(data)
switch (this.widgetFamily) {
case 'large':
return await this.renderLarge(data)
case 'medium':
return await this.renderMedium(data)
default:
return await this.renderSmall(data)
}
}
/**
* 渲染小尺寸组件
*/
async renderSmall (data) {
let w = new ListWidget()
this.renderHeader(w, this.logo, this.name, Color.white())
// w.addSpacer(10)
const name = w.addText(data['name'].trim())
name.font = Font.boldSystemFont(18)
name.lineLimit = 2
w.addSpacer(10)
const c = w.addText(data['class'].trim())
c.font = Font.lightSystemFont(12)
w.addSpacer()
const price = w.addText(data['price'])
price.font = Font.lightSystemFont(12)
price.textOpacity = 0.6
w.backgroundImage = await this.shadowImage(await this.getImageByUrl(data['img']))
w.url = data['url']
name.textColor = c.textColor = price.textColor = Color.white()
return w
}
/**
* 渲染中尺寸组件
*/
async renderMedium (data, lineLimit = 3) {
let w = new ListWidget()
const body = w.addStack()
const leftBox = body.addStack()
leftBox.layoutVertically()
const icon = leftBox.addImage(await this.getImageByUrl(data['img']))
icon.imageSize = new Size(60, 60)
icon.cornerRadius = 5
leftBox.addSpacer(10)
const price = leftBox.addText(data['price'])
price.font = Font.lightSystemFont(10)
price.textOpacity = 0.8
body.addSpacer(10)
const rightBox = body.addStack()
rightBox.layoutVertically()
const title = rightBox.addText(data['name'])
title.font = Font.boldSystemFont(18)
rightBox.addSpacer(5)
const c = rightBox.addText(data['class'])
c.font = Font.lightSystemFont(12)
c.textOpacity = 0.8
rightBox.addSpacer(5)
const desc = rightBox.addText(data['content'])
desc.lineLimit = lineLimit
desc.font = Font.lightSystemFont(14)
w.url = data['url']
return w
}
/**
* 渲染大尺寸组件
*/
async renderLarge (data) {
return await this.renderMedium(data, 15)
}
/**
* 获取数据函数,函数名可不固定
*/
async getData () {
return await this.httpGet("https://api.kzddck.com/script/free.json")
}
async aboutHandler () {
const a = new Alert()
a.title = "关于"
a.message = "本插件数据来自接口https://api.kzddck.com/script/free.json\n感谢群友 @你很闹i 分享"
a.addCancelAction("了解")
return await a.presentAlert()
}
}
// @组件代码结束
const { Testing } = require("./「小件件」开发环境")
await Testing(Widget)

View File

@@ -0,0 +1,163 @@
// Variables used by Scriptable.
// These must be at the very top of the file. Do not edit.
// icon-color: purple; icon-glyph: comment-alt;
//
// iOS 桌面组件脚本 @「小件件」
// 开发说明:请从 Widget 类开始编写,注释请勿修改
// https://x.im3x.cn
//
// 添加require是为了vscode中可以正确引入包以获得自动补全等功能
if (typeof require === 'undefined') require = importModule
const {
Base
} = require("./「小件件」开发环境")
// @组件代码开始
class Widget extends Base {
constructor(arg) {
super(arg)
this.name = '一言'
this.desc = '在茫茫句海中寻找能感动你的句子'
this.settings = this.getSettings(true, true)
if (this.settings && this.settings['arg']) {
this.arg = this.settings['arg']
}
console.log('arg=' + this.arg)
// 注册设置
this.registerAction('插件设置', this.actionSetting)
}
async render() {
let w = new ListWidget()
await this.renderHeader(
w,
'https://txc.gtimg.com/data/285778/2020/1012/f9cf50f08ebb8bd391a7118c8348f5d8.png',
'一言'
)
let data = await this._getData()
let content = w.addText(data['hitokoto'])
content.font = Font.lightSystemFont(16)
w.addSpacer()
let footer = w.addText(data['from'])
footer.font = Font.lightSystemFont(12)
footer.textOpacity = 0.5
footer.rightAlignText()
footer.lineLimit = 1
w.url = this.actionUrl("menus", JSON.stringify(data));
return w
}
async _getData() {
let args = 'abcdefghijk'
const types = this.arg.split('')
.filter(c => args.indexOf(c) > -1)
.map(c => `c=${c}`)
.join('&') || 'c=k'
let api = `https://v1.hitokoto.cn/?${types}&encode=json`
return await this.httpGet(api)
}
async actionSetting() {
let a = new Alert()
a.title = "插件设置"
a.message = "桌面组件的个性化设置"
a.addAction("句子类型")
a.addCancelAction("取消设置")
let id = await a.presentSheet()
if (id === 0) {
return await this.actionSetting1()
}
}
/**
* 句子类型设置
*/
async actionSetting1() {
console.warn('setting--->' + this.arg)
// 设置句子类型
// 1. 获取本地存储(如果有)
let caches = {}
if (this.arg) {
this.arg.split('').map(a => {
caches[a] = true
})
}
let a1 = new Alert()
let keys = [
['a', '动画'],
['b', '漫画'],
['c', '游戏'],
['d', '文学'],
['e', '原创'],
['g', '其他'],
['h', '影视'],
['i', '诗词'],
['k', '哲学'],
['j', '网易云'],
['f', '来自网络'],
]
a1.title = "句子类型"
a1.message = "桌面组件显示的语句内容类型"
keys.map(k => {
let _id = k[0]
let _name = k[1]
if (caches[_id]) {
_name = `${_name}`
}
a1.addAction(_name)
})
a1.addCancelAction("完成设置")
let id1 = await a1.presentSheet()
if (id1 === -1) return this.saveSettings()
console.log(id1)
let arg = keys[id1]
// 本地存储
if (caches[arg[0]]) {
// 已经有了,那么就取消
caches[arg[0]] = false
} else {
caches[arg[0]] = true
}
// 重新获取设置
let _caches = []
for (let k in caches) {
if (caches[k]) {
_caches.push(k)
}
}
this.arg = _caches.join('');
this.settings["arg"] = this.arg;
// this.saveSettings(false);
// console.log('save-setting:' + this.arg)
// Keychain.set(this.SETTING_KEY, this.arg)
return await this.actionSetting1()
}
// 用户点击组件,触发的 action
async actionMenus(content) {
// this.settings = this.getSettings()
const data = JSON.parse(content)
const alert = new Alert()
alert.title = "一言"
alert.message = data['hitokoto']
alert.addAction("复制内容")
alert.addAction("内容设置")
alert.addAction("关于一言")
alert.addCancelAction("取消操作")
const idx = await alert.presentSheet()
if (idx === 0) {
Pasteboard.copyString(data['hitokoto'] + "\n" + "—— " + data['from'])
} else if (idx === 1) {
return await this.actionSetting1()
} else if (idx === 2) {
Safari.openInApp('https://hitokoto.cn/about', false)
}
}
}
// @组件代码结束
const {
Testing
} = require("./「小件件」开发环境")
await Testing(Widget)

View File

@@ -0,0 +1,244 @@
// Variables used by Scriptable.
// These must be at the very top of the file. Do not edit.
// icon-color: red; icon-glyph: hand-holding-usd;
//
// iOS 桌面组件脚本 @「小件件」
// 开发说明:请从 Widget 类开始编写,注释请勿修改
// https://x.im3x.cn
//
// 添加require是为了vscode中可以正确引入包以获得自动补全等功能
if (typeof require === 'undefined') require = importModule
const { Base } = require("./「小件件」开发环境")
// @组件代码开始
class Widget extends Base {
/**
* 传递给组件的参数,可以是桌面 Parameter 数据,也可以是外部如 URLScheme 等传递的数据
* @param {string} arg 自定义参数
*/
constructor (arg) {
super(arg)
this.name = '京东白条'
this.desc = '显示京东白条账号额度和还款数据'
this.logo = 'https://m.jr.jd.com/statics/logo.jpg'
this.registerAction("登录京东", this.actionLogin)
}
/**
* 渲染函数,函数名固定
* 可以根据 this.widgetFamily 来判断小组件尺寸,以返回不同大小的内容
*/
async render () {
const data = await this.getData()
try {
if (data.resultCode !== 0) {
return this.renderFail(data['resultMsg'], true);
}
if (!data.resultData.data['quota'] || !data.resultData.data['bill']) {
return this.renderFail("数据获取失败,请联系反馈更新")
}
} catch (e) {
return this.renderFail("数据解析失败")
}
switch (this.widgetFamily) {
case 'large':
return await this.renderLarge(data['resultData']['data'])
case 'medium':
return await this.renderMedium(data['resultData']['data'])
default:
return await this.renderSmall(data['resultData']['data'])
}
}
async renderFail (msg, login = false) {
const w = new ListWidget()
w.addText("⚠️")
w.addSpacer(10)
const t = w.addText(msg)
t.textColor = Color.red()
t.font = Font.boldSystemFont(14)
w.url = login ? this.actionUrl('login') : this.actionUrl()
return w
}
/**
* 渲染小尺寸组件
*/
async renderSmall (data) {
let w = new ListWidget()
w.url = this.actionUrl('open-url')
await this.renderHeader(w, this.logo, this.name)
const bg = new LinearGradient()
bg.locations = [0, 1]
bg.colors = [
new Color('#f35942', 1),
new Color('#e92d1d', 1)
]
w.backgroundGradient = bg
// 判断参数如果传递1则显示待还否则显示额度
let info = {}
if (this.arg === "1") {
info = {
title: data['bill']['title'],
data: data['bill']['amount'],
desc: data['bill']['buttonName']
}
} else {
info = {
title: '可用额度',
data: data['quota']['quotaLeft'],
desc: '总额度:' + data['quota']['quotaAll']
}
}
const box = w.addStack()
const body = box.addStack()
body.layoutVertically()
const title = body.addText(info.title)
title.font = Font.boldSystemFont(16)
body.addSpacer(10)
const num = body.addText(info.data)
num.font = Font.systemFont(24)
body.addSpacer()
const desc = body.addText(info.desc)
desc.font = Font.lightSystemFont(12)
desc.textOpacity = 0.8
desc.lineLimit = 1
box.addSpacer()
return w
}
/**
* 渲染中尺寸组件
*/
async renderMedium (data) {
let w = new ListWidget()
w.url = this.actionUrl('open-url')
// const bg = new LinearGradient()
// bg.locations = [0, 1]
// bg.colors = [
// new Color('#f35942', 1),
// new Color('#e92d1d', 1)
// ]
// w.backgroundGradient = bg
w.backgroundImage = await this.getImageByUrl('https://txc.gtimg.com/data/287371/2020/1124/30e1524a9288442bec9243c9afa40e90.png')
await this.renderHeader(w, this.logo, this.name, Color.white())
const VIEW_TOP = w.addStack()
VIEW_TOP.addSpacer(24)
const TOP_LEFT = VIEW_TOP.addStack()
TOP_LEFT.layoutVertically()
const t11 = TOP_LEFT.addText("可用额度")
t11.font = Font.boldSystemFont(16)
TOP_LEFT.addSpacer(10)
const t12 = TOP_LEFT.addText(data['quota']['quotaLeft'])
t12.font = Font.systemFont(24)
TOP_LEFT.addSpacer()
const t13 = TOP_LEFT.addText("总额度:" + data['quota']['quotaAll'])
t13.font = Font.lightSystemFont(12)
t13.textOpacity = 0.8
VIEW_TOP.addSpacer()
const TOP_RIGHT = VIEW_TOP.addStack()
TOP_RIGHT.layoutVertically()
const t21 = TOP_RIGHT.addText(data['bill']['title'])
t21.font = Font.boldSystemFont(16)
TOP_RIGHT.addSpacer(10)
const t22 = TOP_RIGHT.addText(data['bill']['amount'])
t22.font = Font.systemFont(24)
TOP_RIGHT.addSpacer()
const t23 = TOP_RIGHT.addText(data['bill']['buttonName'])
t23.font = Font.lightSystemFont(12)
t23.textOpacity = 0.8
;[t11, t12, t13, t21, t22, t23].map(t => t.textColor = Color.white())
VIEW_TOP.addSpacer(20)
return w
}
/**
* 渲染大尺寸组件
*/
async renderLarge (data) {
return await this.renderFail("暂只支持中尺寸小组件")
}
async getData () {
const pt_key = this.settings['pt_key']
const req = new Request("https://ms.jr.jd.com/gw/generic/bt/h5/m/firstScreenNew")
req.method = "POST"
req.body = 'reqData={"clientType":"ios","clientVersion":"13.2.3","deviceId":"","environment":"3"}'
req.headers = {
Cookie: 'pt_key=' + pt_key
}
const res = await req.loadJSON()
return res
}
async actionLogin () {
const webView = new WebView()
webView.loadURL('https://mcr.jd.com/credit_home/pages/index.html?btPageType=BT&channelName=024')
// 循环获取cookie
const tm = new Timer()
tm.timeInterval = 1000
tm.repeats = true
tm.schedule(async () => {
const req = new Request("https://ms.jr.jd.com/gw/generic/bt/h5/m/firstScreenNew")
req.method = "POST"
req.body = 'reqData={"clientType":"ios","clientVersion":"13.2.3","deviceId":"","environment":"3"}'
const res = await req.loadJSON()
const cookies = req.response.cookies
cookies.map(cookie => {
if (cookie['name'] === 'pt_key') {
// 存储,并通知成功
this.notify("登录成功", "登录凭证已保存!可以关闭当前登录页面了!")
tm.invalidate()
this.settings['pt_key'] = cookie['value']
this.saveSettings(false)
return
}
})
})
await webView.present(true)
tm.invalidate()
}
async actionOpenUrl () {
Safari.openInApp('https://mcr.jd.com/credit_home/pages/index.html?btPageType=BT', false)
}
}
// @组件代码结束
const { Testing } = require("./「小件件」开发环境")
await Testing(Widget)

View File

@@ -0,0 +1,342 @@
// Variables used by Scriptable.
// These must be at the very top of the file. Do not edit.
// icon-color: green; icon-glyph: battery-half;
//
// iOS 桌面组件脚本 @「小件件」
// 开发说明:请从 Widget 类开始编写,注释请勿修改
// https://x.im3x.cn
//
// 添加require是为了vscode中可以正确引入包以获得自动补全等功能
if (typeof require === 'undefined') require = importModule
const { Base } = require("./「小件件」开发环境")
// @组件代码开始
class Widget extends Base {
/**
* 传递给组件的参数,可以是桌面 Parameter 数据,也可以是外部如 URLScheme 等传递的数据
* @param {string} arg 自定义参数
*/
constructor (arg) {
super(arg)
this.name = '人生电量'
this.desc = '预计一下余生还剩多少电量'
this.logo = 'https://txc.gtimg.com/data/287371/2020/1105/a8d2e9e19644b244b7a2307bdf2609c0.png'
this.registerAction("设置信息", this.actionSettings)
this.registerAction("透明背景", this.actionSettings3)
this.BG_FILE = this.getBackgroundImage()
if (this.BG_FILE) this.registerAction("移除背景", this.actionSettings4)
}
/**
* 渲染函数,函数名固定
* 可以根据 this.widgetFamily 来判断小组件尺寸,以返回不同大小的内容
*/
async render () {
if (!this.settings || !this.settings['name'] || !this.settings['date'] || !this.settings['gender']) {
return await this.renderConfigure()
}
switch (this.widgetFamily) {
case 'large':
return await this.renderLarge()
case 'medium':
return await this.renderMedium()
default:
return await this.renderSmall()
}
}
/**
* 手工绘制电量图标
* @param {int} num 0-100 电量
*/
async renderBattery (stack, num = 100, size = 'small') {
const SIZES = {
small: {
width: 40,
height: 20,
borderWidth: 3,
cornerRadius: 3,
rightWidth: 2,
rightHeight: 8,
spacer: 3
},
medium: {
width: 80,
height: 40,
borderWidth: 5,
cornerRadius: 10,
rightWidth: 5,
rightHeight: 15,
spacer: 5
},
large: {}
}
const SIZE = SIZES[size]
// 电池颜色
let color = new Color("#CCCCCC", 1)
if (num < 40) color = Color.yellow()
if (num > 80) color = Color.green()
const box = stack.addStack()
box.centerAlignContent()
const boxLeft = box.addStack()
boxLeft.size = new Size(SIZE['width'], SIZE['height'])
boxLeft.borderColor = new Color('#CCCCCC', 0.8)
boxLeft.borderWidth = SIZE['borderWidth']
boxLeft.cornerRadius = SIZE['cornerRadius']
// 中间电量
// 根据电量计算电量矩形的长总长80-边距10
// 算法70/100 * 电量
const BATTERY_WIDTH = parseInt((SIZE['width'] - (SIZE['spacer']*2)) / 100 * num)
boxLeft.addSpacer(SIZE['spacer'])
boxLeft.setPadding(SIZE['spacer'], 0, SIZE['spacer'], 0)
const boxCenter = boxLeft.addStack()
boxCenter.backgroundColor = color
boxCenter.size = new Size(BATTERY_WIDTH, SIZE['height'] - SIZE['spacer']*2)
boxCenter.cornerRadius = SIZE['cornerRadius'] / 2
boxLeft.addSpacer((SIZE['width'] - SIZE['spacer']*2) - BATTERY_WIDTH + SIZE['spacer'])
box.addSpacer(2)
const boxRight = box.addStack()
boxRight.backgroundColor = new Color('#CCCCCC', 0.8)
boxRight.cornerRadius = 5
boxRight.size = new Size(SIZE['rightWidth'], SIZE['rightHeight'])
return box
}
// 提示配置
async renderConfigure () {
const w = new ListWidget()
w.addText("请点击组件进行设置信息")
w.url = this.actionUrl("settings")
return w
}
// 获取电量值
getPricNum () {
// 电量
// 男7578预计寿命
const SM = this.settings['gender'] === '男' ? 75 : 78
// 1. 已经过了多少天
const DAY_TO_NOW = Math.floor((+new Date() - (+new Date(this.settings['date']))) / (24*60*60*1000))
// 2. 百分比
const PRIC_NUM = parseFloat(1-(DAY_TO_NOW / (75*365))).toFixed(2) * 100
return PRIC_NUM
}
/**
* 渲染小尺寸组件
*/
async renderSmall () {
let w = new ListWidget()
// 名称
await this.renderHeader(w, this.logo, this.name, this.BG_FILE ? Color.white() : null)
const PRIC_NUM = this.getPricNum()
const battery = w.addStack()
battery.addSpacer()
await this.renderBattery(battery, PRIC_NUM)
battery.addSpacer()
w.addSpacer(5)
const num = w.addText(` ${PRIC_NUM} %`)
num.centerAlignText()
num.font = Font.systemFont(36)
// 生日
w.addSpacer()
const _date = new DateFormatter()
_date.dateFormat = "yyyy/MM/dd"
const date = w.addText(this.settings['name'] + ' @ ' + _date.string(new Date(this.settings['date'])))
date.font = Font.lightSystemFont(10)
date.textOpacity = 0.8
date.centerAlignText()
if (this.BG_FILE) {
w.backgroundImage = this.BG_FILE
num.textColor = date.textColor = Color.white()
}
w.url = this.actionUrl("settings")
return w
}
/**
* 渲染中尺寸组件
*/
async renderMedium () {
let w = new ListWidget()
await this.renderHeader(w, this.logo, this.name, this.BG_FILE ? Color.white() : null)
w.addSpacer()
const name = w.addText(this.settings['name'])
name.centerAlignText()
name.font = Font.systemFont(14)
name.textOpacity = 0.8
w.addSpacer(10)
const box = w.addStack()
box.centerAlignContent()
box.addSpacer()
// 中间电量
const PRIC_NUM = this.getPricNum()
const num = box.addText(`${PRIC_NUM} %`)
num.font = Font.boldSystemFont(34)
box.addSpacer(10)
await this.renderBattery(box, PRIC_NUM, 'medium')
box.addSpacer()
w.addSpacer()
w.addSpacer(5)
const _date = new DateFormatter()
_date.dateFormat = "yyyy / MM / dd"
const date = w.addText(_date.string(new Date(this.settings['date'])))
date.font = Font.lightSystemFont(12)
date.textOpacity = 0.8
date.rightAlignText()
if (this.BG_FILE) {
w.backgroundImage = this.BG_FILE
name.textColor = num.textColor = date.textColor = Color.white()
}
w.url = this.actionUrl("settings")
return w
}
/**
* 渲染大尺寸组件
*/
async renderLarge () {
return await this.renderMedium()
}
/**
* 获取数据函数,函数名可不固定
*/
async getData () {
return false
}
/**
* 自定义注册点击事件,用 actionUrl 生成一个触发链接,点击后会执行下方对应的 action
* @param {string} url 打开的链接
*/
async actionOpenUrl (url) {
Safari.openInApp(url, false)
}
async actionSettings () {
const a = new Alert()
a.title = "设置信息"
a.message = "配置您的信息,以便小组件进行计算展示"
const menus = ['输入名称', '选择生日', '选择性别'];
;[{
name:'name',
text: '输入名称'
}, {
name: 'date',
text: '选择生日'
}, {
name: 'gender',
text: '选择性别'
}].map(item => {
a.addAction((this.settings[item.name] ? '✅ ' : '❌ ') + item.text)
})
a.addCancelAction('取消设置')
const id = await a.presentSheet()
if (id === -1) return
await this['actionSettings' + id]()
}
// 设置名称
async actionSettings0 () {
const a = new Alert()
a.title = "输入名称"
a.message = "请输入小组件显示的用户名称"
a.addTextField("名称", this.settings['name'])
a.addAction("确定")
a.addCancelAction("取消")
const id = await a.presentAlert()
if (id === -1) return await this.actionSettings()
const n = a.textFieldValue(0)
if (!n) return await this.actionSettings0()
this.settings['name'] = n
this.saveSettings()
return await this.actionSettings()
}
// 选择生日
async actionSettings1 () {
const dp = new DatePicker()
if (this.settings['date']) {
dp.initialDate = new Date(this.settings['date'])
}
let date
try {
date = await dp.pickDate()
} catch (e) {
return await this.actionSettings()
}
this.settings['date'] = date
this.saveSettings()
return await this.actionSettings()
}
// 选择性别
async actionSettings2 () {
const a = new Alert()
a.title = "选择性别"
a.message = "性别可用于预计寿命"
const genders = ['男', '女']
genders.map(n => {
a.addAction((this.settings['gender'] === n ? '✅ ' : '') + n)
})
a.addCancelAction('取消选择')
const i = await a.presentSheet()
if (i !== -1) {
this.settings['gender'] = genders[i]
this.saveSettings()
}
return await this.actionSettings()
}
// 透明背景
async actionSettings3 () {
const img = await this.getWidgetScreenShot()
if (!img) return
this.setBackgroundImage(img)
}
// 移除背景
async actionSettings4 () {
this.setBackgroundImage(null)
}
}
// @组件代码结束
const { Testing } = require("./「小件件」开发环境")
await Testing(Widget)

View File

@@ -0,0 +1,106 @@
// Variables used by Scriptable.
// These must be at the very top of the file. Do not edit.
// icon-color: orange; icon-glyph: comments;
//
// iOS 桌面组件脚本 @「小件件」
// 开发说明:请从 Widget 类开始编写,注释请勿修改
// https://x.im3x.cn
//
// 添加require是为了vscode中可以正确引入包以获得自动补全等功能
if (typeof require === 'undefined') require = importModule
const {
Base
} = require("./「小件件」开发环境")
// @组件代码开始
class Widget extends Base {
/**
* 传递给组件的参数,可以是桌面 Parameter 数据,也可以是外部如 URLScheme 等传递的数据
* @param {string} arg 自定义参数
*/
constructor(arg) {
super(arg)
this.name = '示例小组件'
this.desc = '「小件件」—— 原创精美实用小组件'
}
/**
* 渲染函数,函数名固定
* 可以根据 this.widgetFamily 来判断小组件尺寸,以返回不同大小的内容
*/
async render() {
const data = await this.getData()
switch (this.widgetFamily) {
case 'large':
return await this.renderLarge(data)
case 'medium':
return await this.renderMedium(data)
default:
return await this.renderSmall(data)
}
}
/**
* 渲染小尺寸组件
*/
async renderSmall(data) {
let w = new ListWidget()
await this.renderHeader(w, data['logo'], data['title'])
const t = w.addText(data['content'])
t.font = Font.lightSystemFont(16)
w.addSpacer()
w.url = this.actionUrl('open-url', data['url'])
return w
}
/**
* 渲染中尺寸组件
*/
async renderMedium(data, num = 3) {
let w = new ListWidget()
await this.renderHeader(w, data['logo'], data['title'])
data['data'].slice(0, num).map(d => {
const cell = w.addStack()
cell.centerAlignContent()
const cell_box = cell.addStack()
cell_box.size = new Size(3, 15)
cell_box.backgroundColor = new Color('#ff837a', 0.6)
cell.addSpacer(10)
const cell_text = cell.addText(d['title'])
cell_text.font = Font.lightSystemFont(16)
cell.url = this.actionUrl("open-url", d['url'])
cell.addSpacer()
w.addSpacer(10)
})
w.addSpacer()
return w
}
/**
* 渲染大尺寸组件
*/
async renderLarge(data) {
return await this.renderMedium(data, 10)
}
/**
* 获取数据函数,函数名可不固定
*/
async getData() {
const api = 'https://x.im3x.cn/v1/test-api.json'
return await this.httpGet(api, true, false)
}
/**
* 自定义注册点击事件,用 actionUrl 生成一个触发链接,点击后会执行下方对应的 action
* @param {string} url 打开的链接
*/
async actionOpenUrl(url) {
Safari.openInApp(url, false)
}
}
// @组件代码结束
const {
Testing
} = require("./「小件件」开发环境")
await Testing(Widget)

View File

@@ -0,0 +1,154 @@
// Variables used by Scriptable.
// These must be at the very top of the file. Do not edit.
// icon-color: orange; icon-glyph: comments;
//
// iOS 桌面组件脚本 @「小件件」
// 开发说明:请从 Widget 类开始编写,注释请勿修改
// https://x.im3x.cn
//
// 添加require是为了vscode中可以正确引入包以获得自动补全等功能
if (typeof require === 'undefined') require = importModule
const { Base } = require("./「小件件」开发环境")
// @组件代码开始
class Widget extends Base {
constructor (arg) {
super(arg)
this.name = '微博热榜'
this.desc = '实时刷新微博热搜榜事件'
// 注册设置
this.registerAction('插件设置', this.actionSetting.bind(this))
}
async render () {
if (this.widgetFamily === 'medium') {
return await this.renderMedium()
} else if (this.widgetFamily === 'large') {
return await this.renderLarge()
} else {
return await this.renderSmall()
}
}
/**
* 渲染小尺寸组件
*/
async renderSmall () {
let res = await this.httpGet('https://m.weibo.cn/api/container/getIndex?containerid=106003%26filter_type%3Drealtimehot')
let data = res['data']['cards'][0]['card_group']
// 去除第一条
data.shift()
let topic = data[0]
console.log(topic)
// 显示数据
let w = new ListWidget()
w = await this.renderHeader(w, 'https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=2225458401,2104443747&fm=26&gp=0.jpg', '微博热搜')
let body = w.addStack()
let txt = body.addText(topic['desc'])
body.addSpacer()
txt.leftAlignText()
txt.font = Font.lightSystemFont(14)
w.addSpacer()
let footer = w.addStack()
footer.centerAlignContent()
let img = footer.addImage(await this.getImageByUrl(topic['pic']))
img.imageSize = new Size(18, 18)
footer.addSpacer(5)
if (topic['icon']) {
let hot = footer.addImage(await this.getImageByUrl(topic['icon']))
hot.imageSize = new Size(18, 18)
footer.addSpacer(5)
}
let num = footer.addText(String(topic['desc_extr']))
num.font = Font.lightSystemFont(10)
num.textOpacity = 0.5
w.url = this.actionUrl('open-url', topic['scheme'])
return w
}
/**
* 渲染中尺寸组件
*/
async renderMedium (count = 4) {
let res = await this.httpGet('https://m.weibo.cn/api/container/getIndex?containerid=106003%26filter_type%3Drealtimehot')
let data = res['data']['cards'][0]['card_group']
// 去除第一条
data.shift()
// 显示数据
let w = new ListWidget()
w = await this.renderHeader(w, 'https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=2225458401,2104443747&fm=26&gp=0.jpg', '微博热搜')
// 布局:一行一个,左边顺序排序,中间标题,后边热/新
const body = w.addStack()
const bodyLeft = body.addStack()
bodyLeft.layoutVertically()
for (let i = 0; i < count; i ++) {
let topic = data[i];
let dom = bodyLeft.addStack()
dom.centerAlignContent()
let pic = dom.addImage(await this.getImageByUrl(topic['pic']))
pic.imageSize = new Size(18, 18)
dom.addSpacer(5)
let title = dom.addText(topic['desc'])
title.lineLimit = 1
title.font = Font.lightSystemFont(14)
dom.addSpacer(5)
if (topic['icon']) {
let iconDom = dom.addStack()
let icon = iconDom.addImage(await this.getImageByUrl(topic['icon']))
icon.imageSize = new Size(18, 18)
}
dom.addSpacer()
let extr = dom.addText(String(topic['desc_extr']))
extr.font = Font.lightSystemFont(12)
extr.textOpacity = 0.6
dom.url = this.actionUrl('open-url', topic['scheme'])
bodyLeft.addSpacer(5)
}
body.addSpacer()
w.url = this.actionUrl("setting")
return w
}
/**
* 渲染大尺寸组件
*/
async renderLarge () {
return await this.renderMedium(11)
}
async actionSetting () {
const settings = this.getSettings()
const arg = settings["type"] || "1"
let a = new Alert()
a.title="打开方式"
a.message="点击小组件浏览热点的方式"
a.addAction((arg==="0"?"✅ ":"")+"微博客户端")
a.addAction((arg==="1"?"✅ ":"")+"自带浏览器")
a.addCancelAction("取消设置")
let i = await a.presentSheet()
if (i===-1) return
this.settings["type"] = String(i)
this.saveSettings()
}
async actionOpenUrl (url) {
const settings = this.getSettings()
if (settings['type']==="1"){
Safari.openInApp(url, false)
} else {
let k = decodeURIComponent(url).split('q=')[1].split('&')[0]
Safari.open('sinaweibo://searchall?q=' + encodeURIComponent(k))
}
}
}
// @组件代码结束
const { Testing } = require("./「小件件」开发环境")
await Testing(Widget)

View File

@@ -0,0 +1,138 @@
// Variables used by Scriptable.
// These must be at the very top of the file. Do not edit.
// icon-color: blue; icon-glyph: fire;
//
// iOS 桌面组件脚本 @「小件件」
// 开发说明:请从 Widget 类开始编写,注释请勿修改
// https://x.im3x.cn
//
// 添加require是为了vscode中可以正确引入包以获得自动补全等功能
if (typeof require === 'undefined') require = importModule
const { Base } = require("./「小件件」开发环境")
// @组件代码开始
class Widget extends Base {
/**
* 传递给组件的参数,可以是桌面 Parameter 数据,也可以是外部如 URLScheme 等传递的数据
* @param {string} arg 自定义参数
*/
constructor (arg) {
super(arg)
this.name = '百度热榜'
this.logo = 'https://www.baidu.com/cache/icon/favicon.ico'
this.desc = '百度搜索风云榜,实时更新网络热点'
}
/**
* 渲染函数,函数名固定
* 可以根据 this.widgetFamily 来判断小组件尺寸,以返回不同大小的内容
*/
async render () {
const data = await this.getData()
switch (this.widgetFamily) {
case 'large':
return await this.renderLarge(data)
case 'medium':
return await this.renderMedium(data)
default:
return await this.renderSmall(data)
}
}
/**
* 渲染小尺寸组件
*/
async renderSmall (data) {
let w = new ListWidget()
await this.renderHeader(w, this.logo, this.name)
const t = w.addText(data['hotsearch'][0]['pure_title'])
t.font = Font.lightSystemFont(16)
w.addSpacer()
w.url = this.actionUrl('open-url', decodeURIComponent(data['hotsearch'][0]['linkurl']))
return w
}
/**
* 渲染中尺寸组件
*/
async renderMedium (data, num = 4) {
let w = new ListWidget()
await this.renderHeader(w, this.logo, this.name)
data['hotsearch'].slice(0, num).map((d, i) => {
const cell = w.addStack()
cell.centerAlignContent()
const idx = cell.addText(String(i+1))
idx.font = Font.boldSystemFont(14)
if (i === 0) {
idx.textColor = new Color('#fe2d46', 1)
} else if (i === 1) {
idx.textColor = new Color('#ff6600', 1)
} else if (i === 2) {
idx.textColor = new Color('#faa90e', 1)
} else {
idx.textColor = new Color('#9195a3', 1)
}
cell.addSpacer(10)
let _title = d['pure_title']
_title = _title.replace(/&quot;/g, '"')
const cell_text = cell.addText(_title)
cell_text.font = Font.lightSystemFont(14)
cell_text.lineLimit = 1
let _url = decodeURIComponent(d['linkurl'])
_url = _url.replace("://www.", "://m.")
cell.url = this.actionUrl("open-url", _url)
cell.addSpacer()
w.addSpacer()
})
// w.addSpacer()
// let lbg = new LinearGradient()
// lbg.locations = [0, 1]
// lbg.colors = [
// Color.dynamic(new Color('#cfd9df', 1), new Color('#09203f', 1)),
// Color.dynamic(new Color('#e2ebf0', 1), new Color('#537895', 1))
// ]
// w.backgroundGradient = lbg
return w
}
/**
* 渲染大尺寸组件
*/
async renderLarge (data) {
return await this.renderMedium(data, 11)
}
/**
* 获取数据函数,函数名可不固定
*/
async getData () {
const req = new Request("https://www.baidu.com/")
req.method = "GET"
req.headers = {
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.67 Safari/537.36 Edg/87.0.664.55"
}
const res = await req.loadString()
// console.log(res)
const tmp = res.split(`<textarea id="hotsearch_data" style="display:none;">`)[1].split(`</textarea>`)[0]
console.log(tmp)
const data = eval(`(${tmp})`)
console.log(data)
// const data = JSON.parse(tmp)
// console.log(data['hotsearch'].length)
return data
}
/**
* 自定义注册点击事件,用 actionUrl 生成一个触发链接,点击后会执行下方对应的 action
* @param {string} url 打开的链接
*/
async actionOpenUrl (url) {
Safari.openInApp(url, false)
}
}
// @组件代码结束
const { Testing } = require("./「小件件」开发环境")
await Testing(Widget)

View File

@@ -0,0 +1,120 @@
// Variables used by Scriptable.
// These must be at the very top of the file. Do not edit.
// icon-color: orange; icon-glyph: comments;
//
// iOS 桌面组件脚本 @「小件件」
// 开发说明:请从 Widget 类开始编写,注释请勿修改
// https://x.im3x.cn
//
// 添加require是为了vscode中可以正确引入包以获得自动补全等功能
if (typeof require === 'undefined') require = importModule
const { Base } = require("./「小件件」开发环境")
// @组件代码开始
class Widget extends Base {
url = 'https://www.youxi369.com/news/2254_4.html'
/**
* 传递给组件的参数,可以是桌面 Parameter 数据,也可以是外部如 URLScheme 等传递的数据
* @param {string} arg 自定义参数
*/
constructor (arg) {
super(arg)
this.name = '蚂蚁庄园'
this.desc = '今天的题会做么?'
}
/**
* 渲染函数,函数名固定
* 可以根据 this.widgetFamily 来判断小组件尺寸,以返回不同大小的内容
*/
async render () {
const data = await this.getData()
switch (this.widgetFamily) {
case 'large':
return await this.renderLarge(data)
case 'medium':
return await this.renderMedium(data)
default:
return await this.renderMedium(data)
}
}
/**
* 渲染中尺寸组件
*/
async renderMedium (data, num = 4, title = false) {
let w = new ListWidget()
// await this.renderHeader(w, data['logo'], data['title'])
data.slice(0, num * 2).map((d, idx) => {
if (!title && idx % 2 === 0) return;
const cell = w.addStack()
cell.centerAlignContent()
const cell_text = cell.addText(d)
cell_text.font = Font.lightSystemFont(16)
cell.addSpacer()
w.addSpacer(10)
})
w.url = this.actionUrl(this.url)
w.addSpacer()
return w
}
/**
* 渲染大尺寸组件
*/
async renderLarge (data) {
return await this.renderMedium(data, 5, true)
}
/**
* 获取数据函数,函数名可不固定
*/
async getData () {
const html = await this.fetchAPI(this.url, false);
const tmp = html.split(`<p style="display:none">mryt</p>`)[1].split(`<div class="more-strategy"><i></i></div>`)[0];
const arr = tmp.split(`</span></p>`).slice(0, 20);
const result = [];
for (const answer of arr) {
// console.log(`====answer==`, answer);
if (!answer) continue;
const text = answer.replace(/<p>/, '').replace(/\&nbsp;/gi, ' ').replace(/<span style="[^"]+">/gi, '').replace(/小鸡宝宝考考你[,]?/, '').trim();
const [ title, an ] = text.split('答案:');
if (!title) continue;
const [ day, t] = title.split('月')[1].split('');
result.push(t);
result.push(`答案:${an} (${day})`);
}
return result;
}
// http.get
async fetchAPI (api, json = true) {
let data = null
try {
let req = new Request(api)
req.headers = {
'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1 Edg/85.0.4183.102'
}
data = await (json ? req.loadJSON() : req.loadString())
} catch (e) {}
// 判断数据是否为空(加载失败)
if (!data) {
return null
}
return data
}
/**
* 自定义注册点击事件,用 actionUrl 生成一个触发链接,点击后会执行下方对应的 action
* @param {string} url 打开的链接
*/
async actionOpenUrl (url) {
Safari.openInApp(url, false)
}
}
// @组件代码结束
const { Testing } = require("./「小件件」开发环境")
await Testing(Widget)