mirror of
https://github.com/jie65535/mirai-console-jcf-plugin.git
synced 2025-06-02 17:39:15 +08:00
Complete basic search function
- search mods/modPacks/resourcePacks/worlds - Show all files of the addon - Show change log of addon file
This commit is contained in:
parent
40c2bbba1d
commit
a9b0d5900a
@ -7,6 +7,7 @@ import io.ktor.client.request.*
|
|||||||
import io.ktor.http.*
|
import io.ktor.http.*
|
||||||
import kotlinx.serialization.ExperimentalSerializationApi
|
import kotlinx.serialization.ExperimentalSerializationApi
|
||||||
import kotlinx.serialization.decodeFromString
|
import kotlinx.serialization.decodeFromString
|
||||||
|
import kotlinx.serialization.encodeToString
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import me.jie65535.jcf.model.addon.*
|
import me.jie65535.jcf.model.addon.*
|
||||||
import me.jie65535.jcf.model.addon.file.*
|
import me.jie65535.jcf.model.addon.file.*
|
||||||
@ -18,10 +19,9 @@ import me.jie65535.jcf.model.game.*
|
|||||||
import me.jie65535.jcf.model.minecraft.*
|
import me.jie65535.jcf.model.minecraft.*
|
||||||
import me.jie65535.jcf.model.minecraft.modloader.*
|
import me.jie65535.jcf.model.minecraft.modloader.*
|
||||||
import me.jie65535.jcf.util.Date
|
import me.jie65535.jcf.util.Date
|
||||||
import me.jie65535.jcf.util.internal.DateSerializer
|
|
||||||
|
|
||||||
@OptIn(ExperimentalSerializationApi::class)
|
@OptIn(ExperimentalSerializationApi::class)
|
||||||
class CurseClient {
|
object CurseClient {
|
||||||
private val json = Json {
|
private val json = Json {
|
||||||
isLenient = true
|
isLenient = true
|
||||||
ignoreUnknownKeys = true
|
ignoreUnknownKeys = true
|
||||||
@ -53,7 +53,7 @@ class CurseClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getGameDatabaseTimestamp(): Date {
|
suspend fun getGameDatabaseTimestamp(): Date {
|
||||||
return json.decodeFromString(http.get("api/v2/game/timestamp"))
|
return http.get("api/v2/game/timestamp")
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getAddon(projectId: Int): Addon {
|
suspend fun getAddon(projectId: Int): Addon {
|
||||||
@ -64,7 +64,7 @@ class CurseClient {
|
|||||||
return json.decodeFromString(
|
return json.decodeFromString(
|
||||||
http.post("api/v2/addon") {
|
http.post("api/v2/addon") {
|
||||||
contentType(ContentType.Application.Json)
|
contentType(ContentType.Application.Json)
|
||||||
body = projectIds
|
body = json.encodeToString(projectIds)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -72,7 +72,7 @@ class CurseClient {
|
|||||||
suspend fun getAddons(vararg projectIds: Int): List<Addon> = getAddons(projectIds.toList())
|
suspend fun getAddons(vararg projectIds: Int): List<Addon> = getAddons(projectIds.toList())
|
||||||
|
|
||||||
suspend fun getAddonDescription(projectId: Int): String {
|
suspend fun getAddonDescription(projectId: Int): String {
|
||||||
return json.decodeFromString(http.get("api/v2/addon/$projectId/description"))
|
return http.get("api/v2/addon/$projectId/description")
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getAddonFiles(projectId: Int): List<AddonFile> {
|
suspend fun getAddonFiles(projectId: Int): List<AddonFile> {
|
||||||
@ -83,7 +83,7 @@ class CurseClient {
|
|||||||
return json.decodeFromString(
|
return json.decodeFromString(
|
||||||
http.post("api/v2/addon/files") {
|
http.post("api/v2/addon/files") {
|
||||||
contentType(ContentType.Application.Json)
|
contentType(ContentType.Application.Json)
|
||||||
body = keys
|
body = json.encodeToString(keys)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -91,7 +91,7 @@ class CurseClient {
|
|||||||
suspend fun getAddonFiles(vararg keys: Int): Map<Int, List<AddonFile>> = getAddonFiles(keys.toList())
|
suspend fun getAddonFiles(vararg keys: Int): Map<Int, List<AddonFile>> = getAddonFiles(keys.toList())
|
||||||
|
|
||||||
suspend fun getAddonFileDownloadUrl(projectId: Int, fileId: Int): String {
|
suspend fun getAddonFileDownloadUrl(projectId: Int, fileId: Int): String {
|
||||||
return json.decodeFromString(http.get("api/v2/addon/$projectId/file/$fileId/download-url"))
|
return http.get("api/v2/addon/$projectId/file/$fileId/download-url")
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getAddonFile(projectId: Int, fileId: Int): AddonFile {
|
suspend fun getAddonFile(projectId: Int, fileId: Int): AddonFile {
|
||||||
@ -106,7 +106,7 @@ class CurseClient {
|
|||||||
sortDescending: Boolean = true,
|
sortDescending: Boolean = true,
|
||||||
gameVersion: String? = null,
|
gameVersion: String? = null,
|
||||||
index: Int = 0,
|
index: Int = 0,
|
||||||
pageSize: Int = 1000,
|
pageSize: Int = 10,
|
||||||
searchFilter: String? = null
|
searchFilter: String? = null
|
||||||
): List<Addon> {
|
): List<Addon> {
|
||||||
return json.decodeFromString(
|
return json.decodeFromString(
|
||||||
@ -134,7 +134,7 @@ class CurseClient {
|
|||||||
return json.decodeFromString(
|
return json.decodeFromString(
|
||||||
http.post("api/v2/addon/featured") {
|
http.post("api/v2/addon/featured") {
|
||||||
contentType(ContentType.Application.Json)
|
contentType(ContentType.Application.Json)
|
||||||
body = FeaturedAddonsRequest(gameId, featuredCount, popularCount, updatedCount, excludedAddons)
|
body = json.encodeToString(FeaturedAddonsRequest(gameId, featuredCount, popularCount, updatedCount, excludedAddons))
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -168,14 +168,14 @@ class CurseClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getCategoryDatabaseTimestamp(): Date {
|
suspend fun getCategoryDatabaseTimestamp(): Date {
|
||||||
return json.decodeFromString(http.get("api/v2/category/timestamp"))
|
return http.get("api/v2/category/timestamp")
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getFingerprintMatches(fingerprints: Collection<Long>): FingerprintMatchResult {
|
suspend fun getFingerprintMatches(fingerprints: Collection<Long>): FingerprintMatchResult {
|
||||||
return json.decodeFromString(
|
return json.decodeFromString(
|
||||||
http.post("api/v2/fingerprint") {
|
http.post("api/v2/fingerprint") {
|
||||||
contentType(ContentType.Application.Json)
|
contentType(ContentType.Application.Json)
|
||||||
body = fingerprints
|
body = json.encodeToString(fingerprints)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -186,7 +186,7 @@ class CurseClient {
|
|||||||
return json.decodeFromString(
|
return json.decodeFromString(
|
||||||
http.post("api/v2/fingerprint/fuzzy") {
|
http.post("api/v2/fingerprint/fuzzy") {
|
||||||
contentType(ContentType.Application.Json)
|
contentType(ContentType.Application.Json)
|
||||||
body = FuzzyMatchesRequest(gameId, fingerprints)
|
body = json.encodeToString(FuzzyMatchesRequest(gameId, fingerprints))
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -208,7 +208,7 @@ class CurseClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getModloadersDatabaseTimestamp(): Date {
|
suspend fun getModloadersDatabaseTimestamp(): Date {
|
||||||
return json.decodeFromString(http.get("api/v2/minecraft/modloader/timestamp"))
|
return http.get("api/v2/minecraft/modloader/timestamp")
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getMinecraftVersions(): List<MinecraftVersion> {
|
suspend fun getMinecraftVersions(): List<MinecraftVersion> {
|
||||||
@ -220,6 +220,10 @@ class CurseClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getMinecraftVersionsDatabaseTimestamp(): Date {
|
suspend fun getMinecraftVersionsDatabaseTimestamp(): Date {
|
||||||
return json.decodeFromString(http.get("api/v2/minecraft/version/timestamp"))
|
return http.get("api/v2/minecraft/version/timestamp")
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getAddonFileChangeLog(addonId: Int, fileId: Int): String {
|
||||||
|
return http.get("api/v2/addon/${addonId}/file/${fileId}/changelog")
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -13,7 +13,10 @@ object JCurseforge : KotlinPlugin(
|
|||||||
version = "0.1.0",
|
version = "0.1.0",
|
||||||
) {
|
) {
|
||||||
author("jie65535")
|
author("jie65535")
|
||||||
info("""Curseforge Util""")
|
info("""
|
||||||
|
MC Curseforge Util
|
||||||
|
https://github.com/jie65535/mirai-console-jcf-plugin
|
||||||
|
""".trimIndent())
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
override fun onEnable() {
|
override fun onEnable() {
|
||||||
|
@ -2,17 +2,52 @@ package me.jie65535.jcf
|
|||||||
|
|
||||||
import net.mamoe.mirai.console.command.CommandSender
|
import net.mamoe.mirai.console.command.CommandSender
|
||||||
import net.mamoe.mirai.console.command.CompositeCommand
|
import net.mamoe.mirai.console.command.CompositeCommand
|
||||||
|
import net.mamoe.mirai.console.command.UserCommandSender
|
||||||
|
import net.mamoe.mirai.console.plugin.author
|
||||||
|
import net.mamoe.mirai.console.plugin.info
|
||||||
|
import net.mamoe.mirai.console.plugin.name
|
||||||
|
import net.mamoe.mirai.console.plugin.version
|
||||||
|
|
||||||
object JcfCommand : CompositeCommand(
|
object JcfCommand : CompositeCommand(
|
||||||
JCurseforge, "jcf",
|
JCurseforge, "jcf",
|
||||||
description = "Curseforge Util"
|
description = "Curseforge Util"
|
||||||
) {
|
) {
|
||||||
private val curse = CurseClient()
|
|
||||||
|
|
||||||
@SubCommand
|
@SubCommand
|
||||||
@Description("帮助")
|
@Description("帮助")
|
||||||
suspend fun CommandSender.help() {
|
suspend fun CommandSender.help() {
|
||||||
sendMessage(usage)
|
sendMessage("${JCurseforge.name} by ${JCurseforge.author} ${JCurseforge.version}\n" +
|
||||||
|
"${JCurseforge.info}\n" + usage)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SubCommand("ss")
|
||||||
|
@Description("直接搜索")
|
||||||
|
suspend fun UserCommandSender.search(filter: String) {
|
||||||
|
MinecraftService.search(this, MinecraftService.ALL, filter)
|
||||||
|
}
|
||||||
|
|
||||||
|
@SubCommand("ssmod")
|
||||||
|
@Description("搜索模组")
|
||||||
|
suspend fun UserCommandSender.searchMods(filter: String) {
|
||||||
|
MinecraftService.search(this, MinecraftService.SECTION_ID_MODES, filter)
|
||||||
|
}
|
||||||
|
|
||||||
|
@SubCommand("sspack")
|
||||||
|
@Description("搜索整合包")
|
||||||
|
suspend fun UserCommandSender.searchModPacks(filter: String) {
|
||||||
|
MinecraftService.search(this, MinecraftService.SECTION_ID_MODE_PACKS, filter)
|
||||||
|
}
|
||||||
|
|
||||||
|
@SubCommand("ssres")
|
||||||
|
@Description("搜索资源包")
|
||||||
|
suspend fun UserCommandSender.searchResourcePacks(filter: String) {
|
||||||
|
MinecraftService.search(this, MinecraftService.SECTION_ID_RESOURCE_PACKS, filter)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 可能用不上,先注释掉,有需要再说
|
||||||
|
// @SubCommand("ssworld")
|
||||||
|
// @Description("搜索存档")
|
||||||
|
// suspend fun UserCommandSender.searchWorlds(filter: String) {
|
||||||
|
// MinecraftService.search(this, MinecraftService.SECTION_ID_WORLDS, filter)
|
||||||
|
// }
|
||||||
|
|
||||||
}
|
}
|
88
src/main/kotlin/me/jie65535/jcf/MessageHandler.kt
Normal file
88
src/main/kotlin/me/jie65535/jcf/MessageHandler.kt
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
package me.jie65535.jcf
|
||||||
|
|
||||||
|
import me.jie65535.jcf.model.addon.Addon
|
||||||
|
import me.jie65535.jcf.model.addon.file.AddonFile
|
||||||
|
import me.jie65535.jcf.util.HttpUtil
|
||||||
|
import net.mamoe.mirai.contact.Contact
|
||||||
|
import net.mamoe.mirai.message.data.*
|
||||||
|
import net.mamoe.mirai.utils.ExternalResource.Companion.toExternalResource
|
||||||
|
import java.text.DecimalFormat
|
||||||
|
import kotlin.math.min
|
||||||
|
|
||||||
|
object MessageHandler {
|
||||||
|
|
||||||
|
fun parseSearchResult(addons: List<Addon>, builder: ForwardMessageBuilder, contact: Contact) {
|
||||||
|
for ((index, addon) in addons.withIndex()) {
|
||||||
|
builder.add(contact.bot.id, index.toString(),
|
||||||
|
PlainText("""
|
||||||
|
$index | [${addon.name}] by ${addon.authors[0].name}
|
||||||
|
${formatCount(addon.downloadCount)} Downloads Updated ${addon.dateModified.toLocalDate()}
|
||||||
|
${addon.summary}
|
||||||
|
${addon.websiteUrl}
|
||||||
|
""".trimIndent()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("BlockingMethodInNonBlockingContext")
|
||||||
|
suspend fun parseAddon(addon: Addon, contact: Contact): ForwardMessageBuilder {
|
||||||
|
val builder = ForwardMessageBuilder(contact)
|
||||||
|
.add(contact.bot, loadImage(addon.attachments.find{ it.isDefault }!!.thumbnailUrl, contact))
|
||||||
|
.add(contact.bot, PlainText("""
|
||||||
|
${addon.name}
|
||||||
|
作者:${addon.authors[0].name}
|
||||||
|
概括:${addon.summary}
|
||||||
|
项目ID:${addon.id}
|
||||||
|
创建时间:${addon.dateCreated.toLocalDate()}
|
||||||
|
最近更新:${addon.dateModified.toLocalDate()}
|
||||||
|
总下载:${addon.downloadCount.toLong()}
|
||||||
|
主页:${addon.websiteUrl}
|
||||||
|
""".trimIndent()))
|
||||||
|
if (addon.latestFiles.isNotEmpty()) {
|
||||||
|
builder.add(contact.bot, PlainText("最新文件列表:"))
|
||||||
|
parseAddonFiles(addon.latestFiles, builder, contact)
|
||||||
|
}
|
||||||
|
return builder
|
||||||
|
}
|
||||||
|
|
||||||
|
fun parseAddonFiles(addonFiles: Collection<AddonFile>, builder: ForwardMessageBuilder, contact: Contact) {
|
||||||
|
for ((index, file) in addonFiles.withIndex()) {
|
||||||
|
builder.add(contact.bot.id, index.toString(), PlainText("""
|
||||||
|
$index | ${file.displayName} [${file.releaseType}] [${file.fileDate.toLocalDate()}]
|
||||||
|
${file.downloadUrl}
|
||||||
|
""".trimIndent()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private const val ONE_GRP_SIZE = 5000
|
||||||
|
private const val ONE_MSG_SIZE = 500
|
||||||
|
suspend fun sendLargeMessage(contact: Contact, message: String) {
|
||||||
|
for (g in message.indices step ONE_GRP_SIZE) {
|
||||||
|
val builder = ForwardMessageBuilder(contact)
|
||||||
|
for (i in g until g + min(ONE_GRP_SIZE, message.length-g) step ONE_MSG_SIZE) {
|
||||||
|
builder.add(contact.bot, PlainText(message.subSequence(i, i+(min(ONE_MSG_SIZE, message.length-i)))))
|
||||||
|
}
|
||||||
|
contact.sendMessage(builder.build())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val singleDecimalFormat = DecimalFormat("0.#")
|
||||||
|
private fun formatCount(count: Double): String = when {
|
||||||
|
count < 1000000 -> singleDecimalFormat.format(count / 1000) + "K"
|
||||||
|
count < 1000000000 -> singleDecimalFormat.format(count / 1000000) + "M"
|
||||||
|
else -> count.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("BlockingMethodInNonBlockingContext")
|
||||||
|
private suspend fun loadImage(url: String, contact: Contact): Image {
|
||||||
|
val imgFileName = url.substringAfterLast("/")
|
||||||
|
val file = JCurseforge.resolveDataFile("cache/$imgFileName")
|
||||||
|
val res = if (file.exists()) {
|
||||||
|
file.readBytes().toExternalResource()
|
||||||
|
} else {
|
||||||
|
HttpUtil.downloadImage(url, file).toExternalResource()
|
||||||
|
}
|
||||||
|
val image = contact.uploadImage(res)
|
||||||
|
res.close()
|
||||||
|
return image
|
||||||
|
}
|
||||||
|
}
|
157
src/main/kotlin/me/jie65535/jcf/MinecraftService.kt
Normal file
157
src/main/kotlin/me/jie65535/jcf/MinecraftService.kt
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
package me.jie65535.jcf
|
||||||
|
|
||||||
|
import kotlinx.coroutines.TimeoutCancellationException
|
||||||
|
import me.jie65535.jcf.model.addon.Addon
|
||||||
|
import me.jie65535.jcf.model.addon.AddonSortMethod
|
||||||
|
import me.jie65535.jcf.model.addon.file.AddonFile
|
||||||
|
import net.mamoe.mirai.console.command.UserCommandSender
|
||||||
|
import net.mamoe.mirai.event.events.MessageEvent
|
||||||
|
import net.mamoe.mirai.event.nextEventAsync
|
||||||
|
import net.mamoe.mirai.message.data.ForwardMessageBuilder
|
||||||
|
import net.mamoe.mirai.message.data.PlainText
|
||||||
|
import net.mamoe.mirai.message.data.content
|
||||||
|
import net.mamoe.mirai.utils.MiraiExperimentalApi
|
||||||
|
import java.lang.Exception
|
||||||
|
import java.util.regex.Pattern
|
||||||
|
|
||||||
|
@OptIn(MiraiExperimentalApi::class)
|
||||||
|
object MinecraftService {
|
||||||
|
private val curseClient = CurseClient
|
||||||
|
|
||||||
|
private const val GAME_ID_MINECRAFT = 432
|
||||||
|
const val SECTION_ID_MODES = 6
|
||||||
|
const val SECTION_ID_RESOURCE_PACKS = 12
|
||||||
|
const val SECTION_ID_WORLDS = 17
|
||||||
|
const val SECTION_ID_MODE_PACKS = 4471
|
||||||
|
const val ALL = -1
|
||||||
|
|
||||||
|
private const val WAIT_REPLY_TIMEOUT_MS = 60000L
|
||||||
|
private const val PAGE_SIZE = 10
|
||||||
|
private const val NEXT_PAGE_KEYWORD = "n"
|
||||||
|
private const val SHOW_FILES_KEYWORD = "files"
|
||||||
|
|
||||||
|
suspend fun search(sender: UserCommandSender, sectionId: Int, filter: String) {
|
||||||
|
val addon: Addon
|
||||||
|
var pageIndex = 0
|
||||||
|
while (true) {
|
||||||
|
val searchResult = doSearch(sectionId, filter, pageIndex++, PAGE_SIZE)
|
||||||
|
val hasNextPage = searchResult.size == PAGE_SIZE
|
||||||
|
|
||||||
|
if (searchResult.isEmpty()) {
|
||||||
|
sender.sendMessage("未搜索到结果,请更换关键字重试。")
|
||||||
|
return
|
||||||
|
} else if (searchResult.size == 1) {
|
||||||
|
addon = searchResult[0]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
val builder = ForwardMessageBuilder(sender.subject)
|
||||||
|
builder.add(sender.bot, PlainText("${WAIT_REPLY_TIMEOUT_MS/1000}秒内回复编号查看"))
|
||||||
|
MessageHandler.parseSearchResult(searchResult, builder, sender.subject)
|
||||||
|
if (hasNextPage) builder.add(sender.bot, PlainText("回复[$NEXT_PAGE_KEYWORD]下一页"))
|
||||||
|
sender.sendMessage(builder.build())
|
||||||
|
|
||||||
|
try {
|
||||||
|
val nextEvent = sender.nextEventAsync<MessageEvent>(
|
||||||
|
WAIT_REPLY_TIMEOUT_MS,
|
||||||
|
coroutineContext = sender.coroutineContext
|
||||||
|
) { it.sender == sender.user } .await()
|
||||||
|
if (hasNextPage && nextEvent.message.contentEquals(NEXT_PAGE_KEYWORD, true))
|
||||||
|
continue
|
||||||
|
addon = searchResult[nextEvent.message.content.toInt()]
|
||||||
|
break
|
||||||
|
} catch (e: TimeoutCancellationException) {
|
||||||
|
sender.sendMessage("等待回复超时,请重新查询。")
|
||||||
|
} catch (e: NumberFormatException) {
|
||||||
|
sender.sendMessage("请正确回复序号,此次查询已取消,请重新查询。")
|
||||||
|
} catch (e: IndexOutOfBoundsException) {
|
||||||
|
sender.sendMessage("请回复正确的序号,此次查询已取消,请重新查询。")
|
||||||
|
} catch (e: Exception) {
|
||||||
|
sender.sendMessage("内部发生异常,此次查询已取消,请重新查询。")
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// addon handle
|
||||||
|
showAddon(sender, addon)
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun showAddon(sender: UserCommandSender, addon: Addon) {
|
||||||
|
val builder = MessageHandler.parseAddon(addon, sender.subject)
|
||||||
|
if (addon.latestFiles.isNotEmpty())
|
||||||
|
builder.add(sender.bot, PlainText("${WAIT_REPLY_TIMEOUT_MS/1000}秒内回复[${SHOW_FILES_KEYWORD}]查看所有文件,回复文件序号查看更新日志"))
|
||||||
|
sender.sendMessage(builder.build())
|
||||||
|
|
||||||
|
try {
|
||||||
|
val nextEvent = sender.nextEventAsync<MessageEvent>(
|
||||||
|
WAIT_REPLY_TIMEOUT_MS,
|
||||||
|
coroutineContext = sender.coroutineContext
|
||||||
|
) { it.sender == sender.user } .await()
|
||||||
|
if (nextEvent.message.contentEquals(SHOW_FILES_KEYWORD, true)) {
|
||||||
|
showAllFiles(sender, addon)
|
||||||
|
} else {
|
||||||
|
val file = addon.latestFiles[nextEvent.message.content.toInt()]
|
||||||
|
showChangedLog(sender, addon.id, file)
|
||||||
|
}
|
||||||
|
} catch (e: TimeoutCancellationException) {
|
||||||
|
sender.sendMessage("等待回复超时,请重新查询。")
|
||||||
|
} catch (e: NumberFormatException) {
|
||||||
|
sender.sendMessage("请正确回复序号,此次查询已取消,请重新查询。")
|
||||||
|
} catch (e: IndexOutOfBoundsException) {
|
||||||
|
sender.sendMessage("请回复正确的序号,此次查询已取消,请重新查询。")
|
||||||
|
} catch (e: Exception) {
|
||||||
|
sender.sendMessage("内部发生异常,此次查询已取消,请重新查询。")
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun showAllFiles(sender: UserCommandSender, addon: Addon) {
|
||||||
|
val files = CurseClient.getAddonFiles(addon.id).sortedByDescending { f -> f.fileDate }
|
||||||
|
if (files.isEmpty()) {
|
||||||
|
sender.sendMessage("没有任何文件 :(")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val builder = ForwardMessageBuilder(sender.subject)
|
||||||
|
builder.add(sender.bot, PlainText("${WAIT_REPLY_TIMEOUT_MS/1000}秒内回复文件序号查看更新日志"))
|
||||||
|
MessageHandler.parseAddonFiles(files, builder, sender.subject)
|
||||||
|
sender.sendMessage(builder.build())
|
||||||
|
|
||||||
|
try { // cv大法好,回复功能有功夫再封装,先复制粘贴用着 XD
|
||||||
|
val nextEvent = sender.nextEventAsync<MessageEvent>(
|
||||||
|
WAIT_REPLY_TIMEOUT_MS,
|
||||||
|
coroutineContext = sender.coroutineContext
|
||||||
|
) { it.sender == sender.user } .await()
|
||||||
|
val file = files[nextEvent.message.content.toInt()]
|
||||||
|
showChangedLog(sender, addon.id, file)
|
||||||
|
} catch (e: TimeoutCancellationException) {
|
||||||
|
sender.sendMessage("等待回复超时,请重新查询。")
|
||||||
|
} catch (e: NumberFormatException) {
|
||||||
|
sender.sendMessage("请正确回复序号,此次查询已取消,请重新查询。")
|
||||||
|
} catch (e: IndexOutOfBoundsException) {
|
||||||
|
sender.sendMessage("请回复正确的序号,此次查询已取消,请重新查询。")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val HTMLPattern = Pattern.compile("<[^>]+>", Pattern.CASE_INSENSITIVE)
|
||||||
|
private suspend fun showChangedLog(sender: UserCommandSender, addonId: Int, addonFile: AddonFile) {
|
||||||
|
val changeLogHTML = curseClient.getAddonFileChangeLog(addonId, addonFile.id)
|
||||||
|
val changeLog = HTMLPattern.matcher(changeLogHTML).replaceAll("")
|
||||||
|
MessageHandler.sendLargeMessage(sender.subject, changeLog)
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun doSearch(sectionId: Int, filter: String, pageIndex: Int, pageSize: Int): List<Addon> {
|
||||||
|
return curseClient.searchAddons(
|
||||||
|
gameId = GAME_ID_MINECRAFT,
|
||||||
|
sectionId = sectionId,
|
||||||
|
categoryId = -1,
|
||||||
|
sort = AddonSortMethod.POPULARITY,
|
||||||
|
sortDescending = true,
|
||||||
|
gameVersion = null,
|
||||||
|
index = pageIndex,
|
||||||
|
pageSize = pageSize,
|
||||||
|
searchFilter = filter,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -7,14 +7,47 @@
|
|||||||
|
|
||||||
package me.jie65535.jcf.model.addon
|
package me.jie65535.jcf.model.addon
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 附加排序方法
|
||||||
|
*/
|
||||||
enum class AddonSortMethod {
|
enum class AddonSortMethod {
|
||||||
|
/**
|
||||||
|
* 精选
|
||||||
|
*/
|
||||||
FEATURED,
|
FEATURED,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 人气
|
||||||
|
*/
|
||||||
POPULARITY,
|
POPULARITY,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 最后更新时间
|
||||||
|
*/
|
||||||
LAST_UPDATED,
|
LAST_UPDATED,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 名称
|
||||||
|
*/
|
||||||
NAME,
|
NAME,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 作者
|
||||||
|
*/
|
||||||
AUTHOR,
|
AUTHOR,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 总下载数
|
||||||
|
*/
|
||||||
TOTAL_DOWNLOADS,
|
TOTAL_DOWNLOADS,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 类别
|
||||||
|
*/
|
||||||
CATEGORY,
|
CATEGORY,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 游戏版本
|
||||||
|
*/
|
||||||
GAME_VERSION
|
GAME_VERSION
|
||||||
}
|
}
|
29
src/main/kotlin/me/jie65535/jcf/util/HttpUtil.kt
Normal file
29
src/main/kotlin/me/jie65535/jcf/util/HttpUtil.kt
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
package me.jie65535.jcf.util
|
||||||
|
|
||||||
|
//import okhttp3.MediaType
|
||||||
|
//import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import okhttp3.Request
|
||||||
|
import java.io.File
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
object HttpUtil {
|
||||||
|
// private val JSON: MediaType? = "application/json; charset=utf-8".toMediaTypeOrNull()
|
||||||
|
private val okHttpClient: OkHttpClient by lazy {
|
||||||
|
OkHttpClient.Builder()
|
||||||
|
.readTimeout(10, TimeUnit.SECONDS)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ### 下载图片
|
||||||
|
*/
|
||||||
|
fun downloadImage(url: String, file: File): ByteArray {
|
||||||
|
val request = Request.Builder().url(url).build()
|
||||||
|
val imageByte = okHttpClient.newCall(request).execute().body!!.bytes()
|
||||||
|
val fileParent = file.parentFile
|
||||||
|
if (!fileParent.exists()) fileParent.mkdirs()
|
||||||
|
file.writeBytes(imageByte)
|
||||||
|
return imageByte
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user