Scriptables/Scripts/「源码」V2EX 社区.js
2022-06-02 17:00:01 +08:00

372 lines
10 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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)