/**
* TODO: 更新失败的回滚操作
*/
const isAlphaVersion = Plugins.APP_VERSION.includes('Alpha')
const RollingReleasePath = `data/rolling-release`
/* 触发器 手动触发 */
const onRun = async () => {
await Rolling()
}
/* 触发器 启动APP时 */
const onStartup = async () => {
if (Plugin.AutoRollingMode === 'onStartup') {
doCheck()
}
}
/* 触发器 APP就绪后 */
const onReady = async () => {
if (Plugin.AutoRollingMode === 'onReady') {
doCheck()
}
addRollingReleaseTagToTitleBar()
}
const doCheck = async () => {
await Plugins.sleep(3000) // 至少延迟3s,以便GUI完成对核心状态的恢复
const run = () => Rolling(false, Plugin.AutoRollingReload)
const { running } = Plugins.useKernelApiStore()
if (running) {
run()
} else {
// 延迟检测,确保内核已经启动
setTimeout(run, (Plugin.AutoRollingDelay || 10) * 1000)
}
}
/*
* 右键菜单 - 滚动版本
* params: confirm 是否进行交互式确认
*/
const Rolling = async (confirm = true, autoReload = false) => {
await checkRollingReleaseEnabled()
const isLatest = await checkLatestVersion()
if (!isLatest) {
Plugins.useAppStore().showAbout = true
return
}
const GFC_URL = 'https://api.github.com/repos/GUI-for-Cores/GUI.for.Clash/releases/tags/rolling-release'
const GFS_URL = 'https://api.github.com/repos/GUI-for-Cores/GUI.for.SingBox/releases/tags/rolling-release'
const url = Plugins.APP_TITLE.includes('Clash') ? GFC_URL : GFS_URL
const { destroy } = Plugins.message.info(`[${Plugin.name}] 检测中...`, 999999)
const { body } = await Plugins.HttpGet(url, {
Authorization: Plugins.getGitHubApiAuthorization()
})
if (body.message) {
destroy()
if (body.status == '404') {
throw '当前没有可用的滚动更新'
}
throw body.message
}
const ZipFile = `data/.cache/rolling-release.zip`
const rollingReleaseAsset = body.assets.find((v) => v.name === `rolling-release.zip`)
const ZipUrl = rollingReleaseAsset?.browser_download_url
const VersionUrl = body.assets.find((v) => v.name === 'version.txt')?.browser_download_url
if (!ZipUrl || !VersionUrl) {
destroy()
throw '出现一些错误,无法找到更新资源包'
}
let localVersion = ''
let remoteVersion = ''
try {
const { body } = await Plugins.HttpGet(VersionUrl)
remoteVersion = body
const res = await fetch('/version.txt')
localVersion = await res.text()
} catch (err) {}
if (!remoteVersion) {
destroy()
throw '无法获取远程版本信息'
}
if (localVersion === remoteVersion) {
Plugins.message.success(`[${Plugin.name}] 当前版本已是最新`)
destroy()
return
}
destroy()
confirm && (await Plugins.confirm('', await fetchChangeLog(), { type: 'markdown' }))
const { update, destroy: destroy2, error } = Plugins.message.info('正在更新...')
try {
await Plugins.Download(ZipUrl, ZipFile, {}, (progress, total) => {
update('正在更新...' + ((progress / total) * 100).toFixed(2) + '%')
})
await Plugins.UnzipZIPFile(ZipFile, 'data')
await Plugins.RemoveFile(ZipFile)
await Plugins.WriteFile(`${RollingReleasePath}/updated_at.txt`, Plugins.formatDate(rollingReleaseAsset.updated_at, 'YYYY.MM.DD'))
destroy2()
const ok = autoReload || (await Plugins.confirm(Plugin.name, '更新成功,是否立即重载界面?').catch(() => 0))
ok && Plugins.WindowReloadApp()
} catch (err) {
error(err.message || err)
} finally {
Plugins.sleep(1500).then(() => destroy2())
}
}
/**
* 右键菜单 - 恢复版本
*/
const Recovery = async () => {
await checkRollingReleaseEnabled()
if (!(await Plugins.FileExists(RollingReleasePath))) {
Plugins.message.info('无需恢复,此版本已是默认版本。')
return
}
await Plugins.confirm(
Plugin.name,
'滚动发行过程中,相关配置文件会自动升级,因此回滚会造成配置文件不兼容,导致出现意外状况,确定要回滚吗?',
{
type: 'markdown'
}
)
await Plugins.RemoveFile(RollingReleasePath)
await Plugins.confirm(Plugin.name, '回滚成功,即将重载界面!').catch(() => true)
await Plugins.WindowReloadApp()
}
/**
* 右键菜单 - 更新日志
*/
const Changelog = async () => {
Plugins.alert('', await fetchChangeLog(), { type: 'markdown' })
}
/**
* 右键菜单 - 版本统计
*/
const Statistics = async () => {
const clientMap = {
'GUI.for.Clash': 'https://api.github.com/repos/GUI-for-Cores/GUI.for.Clash/releases',
'GUI.for.SingBox': 'https://api.github.com/repos/GUI-for-Cores/GUI.for.SingBox/releases'
}
const url = clientMap[Plugins.APP_TITLE]
const { body } = await Plugins.HttpGet(url, {
Authorization: Plugins.getGitHubApiAuthorization()
})
const { message } = body
if (message) throw message
const records = body
.sort((a, b) => b.name.startsWith('v') - a.name.startsWith('rolling-release'))
.map((r) => {
const count = r.assets.reduce((p, c) => {
if (c.name.includes('darwin')) {
p.darwin = (p.darwin || 0) + c.download_count
} else if (c.name.includes('windows')) {
p.windows = (p.windows || 0) + c.download_count
} else if (c.name.includes('linux')) {
p.linux = (p.linux || 0) + c.download_count
} else {
p.other = (p.other || 0) + c.download_count
}
return p
}, {})
const size = r.assets.reduce((p, c) => p + c.size, 0)
const name = r.name + (r.name === Plugins.APP_VERSION ? '`我的版本`' : '')
const download = count.other ?? `${windows_icon} ${count.windows} / ${darwin_icon} ${count.darwin} / ${linux_icon} ${count.linux}`
const fileSize = Plugins.formatBytes(size / r.assets.filter((v) => v.name.startsWith('GUI') || v.name.startsWith('rolling-')).length)
const uploader = [...new Set(r.assets.map((asset) => asset.uploader.login))].join('、')
const createTime = Plugins.formatRelativeTime(r.assets[0].updated_at)
return `| ${name} | ${download} | ${fileSize} | ${uploader} | ${createTime} |`
})
const table = ['|版本名称|下载次数|文件大小|发布者|更新时间|', '|-|-|-|-|-|', records.join('\n')]
await Plugins.alert('信息统计如下', table.join('\n'), { type: 'markdown'})
}
const checkRollingReleaseEnabled = async () => {
const appSettings = Plugins.useAppSettingsStore()
if (!appSettings.app.rollingRelease) {
throw '请在【设置】中,开启【启用滚动发行】功能。'
}
}
const checkLatestVersion = async () => {
if (isAlphaVersion) {
throw 'v1.9.8-Alpha已转正,请至 设置 - 关于 更新到最新版本!'
}
const GFC_URL = `https://api.github.com/repos/GUI-for-Cores/GUI.for.Clash/releases/latest`
const GFS_URL = `https://api.github.com/repos/GUI-for-Cores/GUI.for.SingBox/releases/latest`
const url = Plugins.APP_TITLE.includes('Clash') ? GFC_URL : GFS_URL
const { body } = await Plugins.HttpGet(url, {
Authorization: Plugins.getGitHubApiAuthorization()
})
const { tag_name, message } = body
if (message) throw message
return tag_name === Plugins.APP_VERSION
}
const fetchChangeLog = async () => {
const { body } = await Plugins.HttpGet(`https://api.github.com/repos/GUI-for-Cores/${Plugins.APP_TITLE}/commits`, {
Authorization: Plugins.getGitHubApiAuthorization()
})
const releaseIndex = body.findIndex((v) => v.commit.message.startsWith('Release v'))
let currentVersion
try {
currentVersion = await (await fetch('/version.txt')).text()
} catch (error) {
console.log(`[${Plugin.name}]`, '当前不是滚动发行版本')
}
const history = body.slice(0, releaseIndex).map((v) => ({
message: v.commit.message
.replaceAll('\n\n', '\n - ')
.replaceAll(/#(\d+)/g, `[#$1](https://github.com/GUI-for-Cores/${Plugins.APP_TITLE}/pull/$1 "#$1")`),
time: Plugins.formatRelativeTime(v.commit.committer.date),
isCurrent: v.sha.slice(0, 7) === currentVersion,
html_url: v.html_url
}))
if (history.length === 0) {
return `暂无滚动发行日志`
}
let tip = ''
if (!currentVersion) {
tip = '\n\n注意:你当前使用的不是滚动发行版本,请执行本插件以获取上述更新特性。'
}
const changelog =
'## 滚动发行日志\n\n' + history.map((v) => ` - ${v.isCurrent ? '`你的版本`' : ''}${v.message} 【[${v.time}](${v.html_url} "${v.time}")】`).join('\n') + tip
return changelog
}
const addRollingReleaseTagToTitleBar = async () => {
let rollingReleaseVersion
const res = await fetch('/version.txt')
const txt = await res.text()
if (txt && txt.length === 7) {
rollingReleaseVersion = `Rolling Release (${txt})`
}
if (!rollingReleaseVersion) return
const res2 = await fetch('/updated_at.txt')
const txt2 = await res2.text()
if (txt2) {
rollingReleaseVersion = `Rolling Release (${txt2})`
}
const kernelApiStore = Plugins.useKernelApiStore()
Plugins.useAppStore().addCustomActions('title_bar', ({ h, ref }) => {
const loading = ref(false)
const check = async () => {
loading.value = true
await Rolling().catch((err) => Plugins.message.error(err))
loading.value = false
}
return {
component: 'div',
componentSlots: {
default: () => {
return h(
'Button',
{
type: kernelApiStore.running ? 'link' : 'text',
style: kernelApiStore.running
? {
color: 'var(--primary-color)'
}
: {},
size: 'small',
loading: loading.value,
onClick: check
},
() => rollingReleaseVersion
)
}
}
}
})
}
const windows_icon = ``
const darwin_icon = ``
const linux_icon = ``