-
-
Save gwthompson/1088a32cf5de02b50ef222ce3b37560c to your computer and use it in GitHub Desktop.
英雄联盟 S10 x iOS 14 桌面小组件 | Scriptable
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* S10 桌面小组件 | |
* Author : BlueSky | |
* Version : 2.0-201009 | |
* API : https://tiyu.baidu.com/match/S10 | |
* Gist : https://gist.github.com/BlueSky-07/70bcfedc4e5fe7fcf1b06db48124fffe | |
* Readme : https://sspai.com/post/62980 | |
*/ | |
/** | |
* 日期偏移 | |
* -2: 可能包含昨天,在有昨天数据的情况下 | |
* -1: 不包含昨天 | |
* 0: 不包含今天 | |
*/ | |
const _offset_ = -2 | |
const LPL = ['TES', 'JDG', 'SN', 'LGD'] | |
/** | |
* Logger Level | |
*/ | |
const _logger_level_ = [ | |
'warn', | |
'error', | |
'info', | |
'debug', | |
// 'verbo', | |
] | |
/** | |
* 简易 Logger | |
*/ | |
const logger = { | |
log(level = 'info', ...args) { | |
if (_logger_level_.includes(level)) { | |
const fn = console[level] || console.log | |
fn(`[${level.padStart(5, ' ')}] ` + args.map(this.stringify).join(' ')) | |
} | |
}, | |
stringify(target) { | |
if (target === null) { | |
return '__null__' | |
} else if (target === undefined) { | |
return '__undefined__' | |
} else if (typeof target === 'function') { | |
return 'function:' + target.name | |
} else if (typeof target === 'object') { | |
return JSON.stringify(target) | |
} else { | |
return target.toString() | |
} | |
}, | |
warn(...args) { | |
this.log('warn', ...args) | |
}, | |
error(...args) { | |
this.log('error', ...args) | |
}, | |
info(...args) { | |
this.log('info', ...args) | |
}, | |
debug(...args) { | |
this.log('debug', ...args) | |
}, | |
verbose(...args) { | |
this.log('verbo', ...args) | |
}, | |
} | |
/** | |
* 构造日期字符串 | |
* @param {number} offset 日期偏移量,单位(天) | |
* @return {string} dateString | |
*/ | |
function getDateString(offset = 0) { | |
const date = new Date(new Date().getTime() + offset * 60 * 60 * 24 * 1000) | |
const dateFormatter = new DateFormatter() | |
dateFormatter.dateFormat = 'yyyy-MM-dd' | |
const string = dateFormatter.string(date) | |
logger.verbose('date', string) | |
return string | |
} | |
/** | |
* 构造请求链接 | |
* @param {number} offset 日期偏移量,单位(天) | |
* @return {string} url | |
*/ | |
function getDataSourceUrl(offset = 0) { | |
const url = `https://tiyu.baidu.com/api/match/S10/live/date/${getDateString(offset)}/direction/after` | |
logger.debug('url', url) | |
return url | |
} | |
/** | |
* 请求缓存 | |
* Map<string, any> | |
*/ | |
const request_cache = new Map() | |
/** | |
* 获得 Logo 图片,用于减少重复图片请求 | |
* @param {string} url 图片地址 | |
* @return {Image} image | |
*/ | |
async function getLogoFromUrl(url) { | |
if (request_cache.has(url)) { | |
logger.verbose('<cache>', '[ read]', url) | |
return request_cache.get(url) | |
} else { | |
const request = new Request(url) | |
const image = await request.loadImage() | |
request_cache.set(url, image) | |
logger.verbose('<cache>', '[write]', url) | |
return image | |
} | |
} | |
/** | |
* 主组件 | |
* @param {number} offset 日期偏移量,单位(天) | |
*/ | |
async function renderMainWidget(offset = _offset_) { | |
const url = getDataSourceUrl(offset) | |
const request = new Request(url) | |
const json = await request.loadJSON() | |
logger.verbose('json', json) | |
const data = json.data | |
if (!data || !data.length) { | |
throwError({ | |
message: '请求网络错误', | |
errorcode: 'FETCHED_INVALID_DATA', | |
}) | |
} | |
let reOrderedData = [...data] | |
const todayDateString = getDateString() | |
const todayData = reOrderedData.find((item) => item.time === todayDateString) | |
if (todayData) { | |
const dateFormatter = new DateFormatter() | |
dateFormatter.dateFormat = 'yyyy-MM-dd HH:mm:ss' | |
const startTime = dateFormatter.date(todayData.list[0].startTime) | |
logger.verbose('start time', startTime) | |
if (offset === -2 && new Date().getTime() > startTime.getTime()) { | |
logger.info('re-fetch, today\'s first match started') | |
return renderMainWidget(-1) | |
} | |
reOrderedData = [ | |
todayData, | |
...reOrderedData.filter((item) => item.time !== todayDateString), | |
] | |
logger.verbose('re-order', reOrderedData) | |
} | |
const widget = new ListWidget() | |
widget.backgroundColor = new Color('#555555') | |
const header = widget.addText('LoL S10') | |
header.rightAlignText() | |
header.textColor = Color.gray() | |
header.font = Font.mediumSystemFont(12) | |
for (const item of reOrderedData) { | |
logger.debug('render', item.time) | |
const title = widget.addText(item.time) | |
title.textColor = item.time === todayDateString ? Color.yellow() : new Color('#eeeeee') | |
title.font = Font.heavySystemFont(14) | |
widget.addSpacer(5) | |
for (const i of item.list) { | |
const { time, leftLogo: l, rightLogo: r, status, statusText } = i | |
logger.debug('render', time, statusText, l.name, l.score, r.name, r.score) | |
let winner | |
if (status === '2' || status === '3') { | |
let l_score = Number.parseInt(l.score) | |
let r_score = Number.parseInt(r.score) | |
if (l_score > r_score) winner = 'l' | |
if (r_score > l_score) winner = 'r' | |
logger.verbose('winner', winner) | |
} | |
const stack = widget.addStack() | |
function addText(string, color) { | |
const text = stack.addText(string) | |
text.textColor = color || Color.white() | |
text.font = new Font('menlo', 12) | |
return text | |
} | |
function addImage(img) { | |
const image = stack.addImage(img) | |
image.imageSize = new Size(16, 16) | |
return image | |
} | |
const l_logo = await getLogoFromUrl(l.logo) | |
const r_logo = await getLogoFromUrl(r.logo) | |
addText(' ') | |
addImage(l_logo) | |
addText(' ') | |
addText(l.name.padStart(3, ' '), LPL.includes(l.name) && Color.red()) | |
addText(' ') | |
addText(l.score, winner === 'l' && Color.orange()) | |
addText(' - ') | |
addText(r.score, winner === 'r' && Color.orange()) | |
addText(' ') | |
addImage(r_logo) | |
addText(' ') | |
addText(r.name.padEnd(3, ' '), LPL.includes(r.name) && Color.red()) | |
if (status === '1') { | |
addText(` [${statusText}]`, Color.yellow()) | |
} else if (status === '4') { | |
addText(` [${time}]`) | |
} | |
widget.addSpacer(3) | |
} | |
widget.addSpacer(5) | |
} | |
const footer = widget.addText(new Date().toLocaleString()) | |
footer.rightAlignText() | |
footer.textColor = Color.gray() | |
footer.font = Font.mediumSystemFont(10) | |
render(widget) | |
} | |
/** | |
* 错误信息组件 | |
* @param {object} payload | |
* @param {string} payload.message 错误信息 | |
* @param {string} payload.errorcode 错误代码 | |
*/ | |
function renderErrorWidget(payload = {}) { | |
const widget = new ListWidget() | |
const backgroundGradient = new LinearGradient() | |
backgroundGradient.colors = [ | |
new Color('#f5222d'), | |
new Color('#cf1322'), | |
new Color('#a8071a'), | |
new Color('#820014'), | |
new Color('#5c0011'), | |
] | |
backgroundGradient.locations = [ | |
0.0, 0.25, 0.5, 0.75, 1.0, | |
] | |
widget.backgroundGradient = backgroundGradient | |
const message = payload.message || '出现错误' | |
const error = widget.addText(message) | |
error.centerAlignText() | |
error.textColor = Color.white() | |
error.font = Font.heavySystemFont(18) | |
if (payload.errorcode) { | |
const errorcode = widget.addText(payload.errorcode) | |
errorcode.centerAlignText() | |
errorcode.textColor = Color.yellow() | |
errorcode.font = Font.lightSystemFont(12) | |
} | |
render(widget) | |
} | |
/** | |
* 渲染 | |
* @param {Widget} widget | |
*/ | |
function render(widget) { | |
if (!widget) { | |
throwError({ message: 'widget is required' }) | |
} else { | |
Script.setWidget(widget) | |
widget.presentLarge() | |
// widget.presentMedium() | |
// widget.presentSmall() | |
} | |
} | |
/** | |
* 统一 Error | |
* @param {object} payload | |
* @param {string} payload.message 错误信息 | |
* @param {string} payload.errorcode 错误代码 | |
*/ | |
function throwError(payload = {}) { | |
const error = new Error(payload.message || '出现错误') | |
Object.assign(error, payload) | |
logger.error(payload.message, payload.errorcode) | |
throw error | |
} | |
async function statistics() { | |
const url = 'https://api.ihint.me/statistics.php?site=scriptable_s10' | |
const request = new Request(url) | |
const res = await request.loadString() | |
logger.debug('statistics', res) | |
} | |
/** | |
* Main | |
*/ | |
async function main() { | |
logger.info('bootstrap') | |
const widgetArgs = args.widgetParameter | |
logger.info('widget args', widgetArgs) | |
let offset = _offset_ | |
if (widgetArgs) { | |
try { | |
offset = Number.parseInt(widgetArgs) | |
if (Number.isNaN(offset)) { | |
throwError({ | |
message: '解析参数错误', | |
errorcode: 'INVALID_ARGS', | |
}) | |
} | |
} catch (e) { | |
logger.info('parse args error') | |
renderErrorWidget(e) | |
throw e | |
} | |
} | |
try { | |
await renderMainWidget(offset) | |
logger.info('render done') | |
await statistics() | |
Script.complete() | |
} catch (e) { | |
logger.info('render error') | |
renderErrorWidget(e) | |
throw e | |
} | |
logger.info('done') | |
} | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment