Clickable chat links
This commit is contained in:
@@ -23,7 +23,7 @@
|
|||||||
- Support for custom texture packs (loads item models from `firmskyblock:<skyblock id>` before the vanilla model gets loaded)
|
- Support for custom texture packs (loads item models from `firmskyblock:<skyblock id>` before the vanilla model gets loaded)
|
||||||
- Fairy soul highlighter
|
- Fairy soul highlighter
|
||||||
- A hud editor powered by [Jarvis](https://github.com/romangraef/jarvis)
|
- A hud editor powered by [Jarvis](https://github.com/romangraef/jarvis)
|
||||||
- Fishing Helper for Fishing particles (currently not working if you sneak because of 1.19 messing up positioning)
|
- Fishing Helper for Fishing particles (currently not working if you sneak because of 1.20 messing up positioning)
|
||||||
- Basic Config Gui (/firm config). Still needs improvement, but for the basics it's enough.
|
- Basic Config Gui (/firm config). Still needs improvement, but for the basics it's enough.
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ package moe.nea.firmament.features
|
|||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.serializer
|
import kotlinx.serialization.serializer
|
||||||
import moe.nea.firmament.Firmament
|
import moe.nea.firmament.Firmament
|
||||||
import moe.nea.firmament.features.chat.ImagePreview
|
import moe.nea.firmament.features.chat.ChatLinks
|
||||||
import moe.nea.firmament.features.debug.DebugView
|
import moe.nea.firmament.features.debug.DebugView
|
||||||
import moe.nea.firmament.features.debug.DeveloperFeatures
|
import moe.nea.firmament.features.debug.DeveloperFeatures
|
||||||
import moe.nea.firmament.features.fishing.FishingWarning
|
import moe.nea.firmament.features.fishing.FishingWarning
|
||||||
@@ -58,7 +58,7 @@ object FeatureManager : DataHolder<FeatureManager.Config>(serializer(), "feature
|
|||||||
loadFeature(SlotLocking)
|
loadFeature(SlotLocking)
|
||||||
loadFeature(StorageOverlay)
|
loadFeature(StorageOverlay)
|
||||||
loadFeature(CraftingOverlay)
|
loadFeature(CraftingOverlay)
|
||||||
loadFeature(ImagePreview)
|
loadFeature(ChatLinks)
|
||||||
loadFeature(SaveCursorPosition)
|
loadFeature(SaveCursorPosition)
|
||||||
loadFeature(CustomSkyBlockTextures)
|
loadFeature(CustomSkyBlockTextures)
|
||||||
loadFeature(Fixes)
|
loadFeature(Fixes)
|
||||||
|
|||||||
@@ -9,11 +9,11 @@ import moe.nea.jarvis.api.Point
|
|||||||
import kotlinx.coroutines.Deferred
|
import kotlinx.coroutines.Deferred
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.async
|
import kotlinx.coroutines.async
|
||||||
import kotlin.math.max
|
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
import net.minecraft.client.gui.screen.ChatScreen
|
import net.minecraft.client.gui.screen.ChatScreen
|
||||||
import net.minecraft.client.texture.NativeImage
|
import net.minecraft.client.texture.NativeImage
|
||||||
import net.minecraft.client.texture.NativeImageBackedTexture
|
import net.minecraft.client.texture.NativeImageBackedTexture
|
||||||
|
import net.minecraft.text.ClickEvent
|
||||||
import net.minecraft.text.HoverEvent
|
import net.minecraft.text.HoverEvent
|
||||||
import net.minecraft.text.Style
|
import net.minecraft.text.Style
|
||||||
import net.minecraft.text.Text
|
import net.minecraft.text.Text
|
||||||
@@ -28,12 +28,13 @@ import moe.nea.firmament.util.MC
|
|||||||
import moe.nea.firmament.util.transformEachRecursively
|
import moe.nea.firmament.util.transformEachRecursively
|
||||||
import moe.nea.firmament.util.unformattedString
|
import moe.nea.firmament.util.unformattedString
|
||||||
|
|
||||||
object ImagePreview : FirmamentFeature {
|
object ChatLinks : FirmamentFeature {
|
||||||
override val identifier: String
|
override val identifier: String
|
||||||
get() = "image-preview"
|
get() = "chat-links"
|
||||||
|
|
||||||
object TConfig : ManagedConfig(identifier) {
|
object TConfig : ManagedConfig(identifier) {
|
||||||
val enabled by toggle("enabled") { true }
|
val enableLinks by toggle("links-enabled") { true }
|
||||||
|
val imageEnabled by toggle("image-enabled") { true }
|
||||||
val allowAllHosts by toggle("allow-all-hosts") { false }
|
val allowAllHosts by toggle("allow-all-hosts") { false }
|
||||||
val allowedHosts by string("allowed-hosts") { "cdn.discordapp.com,media.discordapp.com,media.discordapp.net,i.imgur.com" }
|
val allowedHosts by string("allowed-hosts") { "cdn.discordapp.com,media.discordapp.com,media.discordapp.net,i.imgur.com" }
|
||||||
val actualAllowedHosts get() = allowedHosts.split(",").map { it.trim() }
|
val actualAllowedHosts get() = allowedHosts.split(",").map { it.trim() }
|
||||||
@@ -46,7 +47,7 @@ object ImagePreview : FirmamentFeature {
|
|||||||
private fun isUrlAllowed(url: String) = isHostAllowed(url.removePrefix("https://").substringBefore("/"))
|
private fun isUrlAllowed(url: String) = isHostAllowed(url.removePrefix("https://").substringBefore("/"))
|
||||||
|
|
||||||
override val config get() = TConfig
|
override val config get() = TConfig
|
||||||
val urlRegex = "https://[^. ]+\\.[^ ]+(\\.(png|gif|jpe?g))(\\?[^ ]*)?( |$)".toRegex()
|
val urlRegex = "https://[^. ]+\\.[^ ]+( |$)".toRegex()
|
||||||
|
|
||||||
data class Image(
|
data class Image(
|
||||||
val texture: Identifier,
|
val texture: Identifier,
|
||||||
@@ -84,45 +85,54 @@ object ImagePreview : FirmamentFeature {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val imageExtensions = listOf("jpg", "png", "gif", "jpeg")
|
||||||
|
fun isImageUrl(url: String): Boolean {
|
||||||
|
return (url.substringAfterLast('.').lowercase() in imageExtensions)
|
||||||
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
override fun onLoad() {
|
override fun onLoad() {
|
||||||
ClientChatLineReceivedEvent.subscribe {
|
ClientChatLineReceivedEvent.subscribe {
|
||||||
it.replaceWith = it.text.transformEachRecursively { child ->
|
if (TConfig.enableLinks)
|
||||||
val text = child.string
|
it.replaceWith = it.text.transformEachRecursively { child ->
|
||||||
if ("://" !in text) return@transformEachRecursively child
|
val text = child.string
|
||||||
val s = Text.empty().setStyle(child.style)
|
if ("://" !in text) return@transformEachRecursively child
|
||||||
var index = 0
|
val s = Text.empty().setStyle(child.style)
|
||||||
while (index < text.length) {
|
var index = 0
|
||||||
val nextMatch = urlRegex.find(text, index)
|
while (index < text.length) {
|
||||||
if (nextMatch == null) {
|
val nextMatch = urlRegex.find(text, index)
|
||||||
s.append(Text.literal(text.substring(index, text.length)))
|
if (nextMatch == null) {
|
||||||
break
|
s.append(Text.literal(text.substring(index, text.length)))
|
||||||
}
|
break
|
||||||
val range = nextMatch.groups[0]!!.range
|
}
|
||||||
val url = nextMatch.groupValues[0]
|
val range = nextMatch.groups[0]!!.range
|
||||||
s.append(Text.literal(text.substring(index, range.first)))
|
val url = nextMatch.groupValues[0]
|
||||||
s.append(
|
s.append(Text.literal(text.substring(index, range.first)))
|
||||||
Text.literal(url).setStyle(
|
s.append(
|
||||||
Style.EMPTY.withUnderline(true).withColor(
|
Text.literal(url).setStyle(
|
||||||
Formatting.AQUA
|
Style.EMPTY.withUnderline(true).withColor(
|
||||||
).withHoverEvent(HoverEvent(HoverEvent.Action.SHOW_TEXT, Text.literal(url)))
|
Formatting.AQUA
|
||||||
|
).withHoverEvent(HoverEvent(HoverEvent.Action.SHOW_TEXT, Text.literal(url)))
|
||||||
|
.withClickEvent(ClickEvent(ClickEvent.Action.OPEN_URL, url))
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
if (isImageUrl(url))
|
||||||
tryCacheUrl(url)
|
tryCacheUrl(url)
|
||||||
index = range.last + 1
|
index = range.last + 1
|
||||||
|
}
|
||||||
|
s
|
||||||
}
|
}
|
||||||
s
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ScreenRenderPostEvent.subscribe {
|
ScreenRenderPostEvent.subscribe {
|
||||||
if (!TConfig.enabled) return@subscribe
|
if (!TConfig.imageEnabled) return@subscribe
|
||||||
if (it.screen !is ChatScreen) return@subscribe
|
if (it.screen !is ChatScreen) return@subscribe
|
||||||
val hoveredComponent =
|
val hoveredComponent =
|
||||||
MC.inGameHud.chatHud.getTextStyleAt(it.mouseX.toDouble(), it.mouseY.toDouble()) ?: return@subscribe
|
MC.inGameHud.chatHud.getTextStyleAt(it.mouseX.toDouble(), it.mouseY.toDouble()) ?: return@subscribe
|
||||||
val hoverEvent = hoveredComponent.hoverEvent ?: return@subscribe
|
val hoverEvent = hoveredComponent.hoverEvent ?: return@subscribe
|
||||||
val value = hoverEvent.getValue(HoverEvent.Action.SHOW_TEXT) ?: return@subscribe
|
val value = hoverEvent.getValue(HoverEvent.Action.SHOW_TEXT) ?: return@subscribe
|
||||||
val url = urlRegex.matchEntire(value.unformattedString)?.groupValues?.get(0) ?: return@subscribe
|
val url = urlRegex.matchEntire(value.unformattedString)?.groupValues?.get(0) ?: return@subscribe
|
||||||
|
if (!isImageUrl(url)) return@subscribe
|
||||||
val imageFuture = imageCache[url] ?: return@subscribe
|
val imageFuture = imageCache[url] ?: return@subscribe
|
||||||
if (!imageFuture.isCompleted) return@subscribe
|
if (!imageFuture.isCompleted) return@subscribe
|
||||||
val image = imageFuture.getCompleted() ?: return@subscribe
|
val image = imageFuture.getCompleted() ?: return@subscribe
|
||||||
@@ -69,11 +69,12 @@
|
|||||||
"firmament.config.storage-overlay.scroll-speed": "Scroll Speed",
|
"firmament.config.storage-overlay.scroll-speed": "Scroll Speed",
|
||||||
"firmament.config.storage-overlay.inverse-scroll": "Invert Scroll",
|
"firmament.config.storage-overlay.inverse-scroll": "Invert Scroll",
|
||||||
"firmament.config.storage-overlay.margin": "Margin",
|
"firmament.config.storage-overlay.margin": "Margin",
|
||||||
"firmament.config.image-preview": "Image Preview",
|
"firmament.config.chat-links": "Chat Links",
|
||||||
"firmament.config.image-preview.enabled": "Enable Image Preview",
|
"firmament.config.chat-links.links-enabled": "Enable Clickable Links",
|
||||||
"firmament.config.image-preview.allow-all-hosts": "Allow all Image Hosts",
|
"firmament.config.chat-links.image-enabled": "Enable Image Preview",
|
||||||
"firmament.config.image-preview.allowed-hosts": "Allowed Image Hosts",
|
"firmament.config.chat-links.allow-all-hosts": "Allow all Image Hosts",
|
||||||
"firmament.config.image-preview.position": "Chat Image Preview",
|
"firmament.config.chat-links.allowed-hosts": "Allowed Image Hosts",
|
||||||
|
"firmament.config.chat-links.position": "Chat Image Preview",
|
||||||
"firmament.hud.edit": "Edit %s",
|
"firmament.hud.edit": "Edit %s",
|
||||||
"firmament.config.custom-skyblock-textures": "Custom SkyBlock Item Textures",
|
"firmament.config.custom-skyblock-textures": "Custom SkyBlock Item Textures",
|
||||||
"firmament.config.custom-skyblock-textures.cache-duration": "Model Cache Duration",
|
"firmament.config.custom-skyblock-textures.cache-duration": "Model Cache Duration",
|
||||||
|
|||||||
Reference in New Issue
Block a user