1+ /*
2+ * Copyright 2021-2023 dsstudio Technologies and contributors.
3+ *
4+ * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
5+ * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
6+ *
7+ * https://github.com/gnuf0rce/github-helper/blob/master/LICENSE
8+ */
9+
10+
11+ package io.github.gnuf0rce.mirai.github
12+
13+ import io.github.gnuf0rce.github.*
14+ import io.ktor.client.request.*
15+ import io.ktor.client.statement.*
16+ import io.ktor.util.cio.*
17+ import io.ktor.utils.io.*
18+ import kotlinx.coroutines.*
19+ import kotlinx.serialization.*
20+ import net.mamoe.mirai.console.plugin.*
21+ import net.mamoe.mirai.console.plugin.jvm.*
22+ import net.mamoe.mirai.console.util.*
23+ import java.io.File
24+
25+ public object GitHubReleasePluginUpdater {
26+
27+ @JvmStatic
28+ public val dict: MutableMap <String , String > = hashMapOf(
29+ " cn.whitrayhb.grasspics" to " NLR-DevTeam/GrassPictures" ,
30+
31+ " com.evolvedghost.mirai.steamhelper.steamhelper" to " EvolvedGhost/Steamhelper" ,
32+ " com.evolvedghost.mutegames" to " EvolvedGhost/MuteGames" ,
33+
34+ " com.happysnaker.HRobot" to " happysnaker/mirai-plugin-HRobot" ,
35+
36+ " com.hcyacg.bilibili" to " Nekoer/mirai-plugins-bilibili" ,
37+ " com.hcyacg.github-notice" to " Nekoer/mirai-github-notice" ,
38+ " com.hcyacg.novelai" to " Nekoer/mirai-plugins-novelai" ,
39+ " com.hcyacg.pixiv" to " Nekoer/mirai-plugins-pixiv" ,
40+
41+ " com.hrs.kloping.AutoReply" to " Kloping/Mirai_Plugins_Auto_Reply" ,
42+
43+ " com.kasukusakura.mlss" to " KasukuSakura/mirai-login-solver-sakura" ,
44+
45+ " com.khjxiaogu.mirai.MiraiSongPlugin" to " khjxiaogu/MiraiSongPlugin" ,
46+
47+ " com.xtex.repeater" to " xtexChooser/mirai-repeater" ,
48+
49+ " io.github.samarium150.mirai.plugin.mirai-console-drift-bottle" to " Samarium150/mirai-console-drift-bottle" ,
50+ " io.github.samarium150.mirai.plugin.mirai-console-loafers-calendar" to " Samarium150/mirai-console-loafers-calendar" ,
51+ " io.github.samarium150.mirai.plugin.mirai-console-lolicon" to " Samarium150/mirai-console-lolicon" ,
52+
53+ " me.jie65535.mirai-console-jnr-plugin" to " jie65535/mirai-console-jnr-plugin" ,
54+
55+ " me.stageguard.obms.OsuMapSuggester" to " StageGuard/OsuMapSuggester" ,
56+ " me.stageguard.sctimetable" to " StageGuard/SuperCourseTimetableBot" ,
57+
58+ " org.laolittle.plugin.SkikoMirai" to " LaoLittle/SkikoMirai" ,
59+ " org.laolittle.plugin.draw.DrawMeme" to " LaoLittle/DrawMeme" ,
60+
61+ " top.colter.bilibili-dynamic-mirai-plugin" to " Colter23/bilibili-dynamic-mirai-plugin" ,
62+ " top.colter.genshin-sign" to " Colter23/genshin-sign-mirai-plugin" ,
63+
64+ " top.cutestar.antiRecall" to " Pmaru-top/AntiRecall" ,
65+
66+ " top.limbang.mcmod" to " limbang/mirai-console-mcmod-plugin" ,
67+ " top.limbang.mcsm" to " limbang/mirai-console-mcsm-plugin" ,
68+ " top.limbang.minecraft" to " limbang/mirai-console-minecraft-plugin" ,
69+
70+ " top.jie65535.mirai.grasscutter-command" to " jie65535/JGrasscutterCommand" ,
71+ " top.jie65535.mail-notify" to " jie65535/JMailNotify" ,
72+ " top.jie65535.mirai-console-jms-plugin" to " jie65535/mirai-console-jms-plugin" ,
73+ " top.jie65535.jcf" to " jie65535/mirai-console-jcf-plugin" ,
74+ " top.jie65535.mirai-console-jcr-plugin" to " jie65535/mirai-console-jcr-plugin" ,
75+ " top.jie65535.mirai-console-jhr-plugin" to " jie65535/mirai-console-jhr-plugin" ,
76+ " top.jie65535.mirai-console-jcab-arg-plugin" to " jie65535/mirai-console-jcab-arg-plugin" ,
77+
78+ " xmmt.dituon.petpet" to " Dituon/petpet" ,
79+
80+ " xyz.cssxsh.mirai.plugin.novelai-helper" to " cssxsh/novelai-helper" ,
81+ " xyz.cssxsh.mirai.plugin.pixiv-helper" to " cssxsh/pixiv-helper"
82+ )
83+
84+ public fun reload (file : File ) {
85+ if (file.exists()) {
86+ dict.clear()
87+ dict.putAll(GitHubJson .decodeFromString(file.readText()))
88+ } else {
89+ file.writeText(GitHubJson .encodeToString(dict))
90+ val last = file.lastModified()
91+ runBlocking {
92+ ConsoleInput .requestInput(hint = " update.dict.json 已生成,你可以打开浏览一下 (输入回车结束等待)" )
93+ }
94+ if (last < file.lastModified()) {
95+ dict.clear()
96+ dict.putAll(GitHubJson .decodeFromString(file.readText()))
97+ }
98+ }
99+ }
100+
101+ public fun update () {
102+ for (plugin in PluginManager .plugins) {
103+ if (plugin !is JvmPlugin ) continue
104+ val id = dict[plugin.description.id]
105+ ? : dict[plugin.description.name]
106+ ? : continue
107+ val classLoader = plugin::class .java.classLoader as ? java.net.URLClassLoader ? : continue
108+ val source = classLoader
109+ .urLs.singleOrNull()
110+ ?.let { File (it.path) }
111+ ? : continue
112+ var needUpdate = false
113+ var target = PluginManager .pluginsFolder.resolve(plugin.description.id)
114+
115+ plugin.launch {
116+ val latest = github.repo(id).releases.latest()
117+ val jar = latest.assets.find { it.name.endsWith(" .mirai2.jar" ) }
118+ ? : latest.assets.find { it.name.endsWith(" .mirai.jar" ) }
119+ ? : latest.assets.find { it.name.endsWith(" .jar" ) }
120+ ? : return @launch
121+ val updated = jar.updatedAt.toInstant().toEpochMilli()
122+ needUpdate = try {
123+ SemVersion (latest.tagName.removeSuffix(" v" )) > plugin.description.version
124+ } catch (_: IllegalArgumentException ) {
125+ updated > source.lastModified()
126+ }
127+
128+ if (needUpdate.not ()) return @launch
129+
130+ plugin.logger.info(" 从 ${latest.htmlUrl} 尝试升级" )
131+ target = PluginManager .pluginsFolder.resolve(jar.name)
132+ github.useHttpClient { http ->
133+ http.get(jar.browserDownloadUrl)
134+ .bodyAsChannel()
135+ .copyAndClose(target.writeChannel())
136+ }
137+ check(target.length() == jar.size) {
138+ target.delete()
139+ " 从 ${jar.browserDownloadUrl} 下载失败(文件大小校验失败 ${target.length()} !=${jar.size} )"
140+ }
141+ target.setLastModified(updated)
142+ source.delete()
143+ plugin.logger.info(" 从 ${latest.htmlUrl} 升级成功" )
144+
145+ }.invokeOnCompletion { cause ->
146+ if (cause != null ) {
147+ plugin.logger.warning(" 从 $id 升级失败" )
148+ target.deleteOnExit()
149+ Runtime .getRuntime().addShutdownHook(Thread {
150+ target.delete()
151+ })
152+ } else if (needUpdate && source.exists()) {
153+ plugin.logger.warning(" 旧版插件 ${source.name} 删除失败,将尝试添加退出时删除,请在下次启动时手动检查" )
154+ source.deleteOnExit()
155+ Runtime .getRuntime().addShutdownHook(Thread {
156+ classLoader.close()
157+ source.delete()
158+ })
159+ }
160+ }
161+ }
162+ }
163+ }
0 commit comments