Skip to content

Commit c396478

Browse files
committed
feat: support fabric 1.20.2
1 parent 7c9aa68 commit c396478

14 files changed

Lines changed: 503 additions & 1 deletion

File tree

fabric-1.20/build.gradle.kts

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
plugins {
2+
id("fabric-loom") version "1.0-SNAPSHOT"
3+
}
4+
5+
java.toolchain.languageVersion.set(JavaLanguageVersion.of(17))
6+
7+
val minecraftVersion = "1.20.2"
8+
val yarnMappings = "1.20.2+build.4"
9+
val loaderVersion = "0.14.23"
10+
val fabricVersion = "0.90.0+1.20.2"
11+
val archivesBaseName = "InterChatMod-${project.name}"
12+
13+
repositories {
14+
// Add repositories to retrieve artifacts from in here.
15+
// You should only use this when depending on other mods because
16+
// Loom adds the essential maven repositories to download Minecraft and libraries from automatically.
17+
// See https://docs.gradle.org/current/userguide/declaring_repositories.html
18+
// for more information about repositories.
19+
maven { url = uri("https://maven.wispforest.io") }
20+
}
21+
22+
dependencies {
23+
// To change the versions see the gradle.properties file
24+
minecraft("com.mojang:minecraft:$minecraftVersion")
25+
mappings("net.fabricmc:yarn:$yarnMappings:v2")
26+
modImplementation("net.fabricmc:fabric-loader:$loaderVersion")
27+
28+
// Fabric API. This is technically optional, but you probably want it anyway.
29+
modImplementation("net.fabricmc.fabric-api:fabric-api:$fabricVersion")
30+
31+
modImplementation("io.wispforest:owo-lib:0.11.3+1.20.2")
32+
annotationProcessor("io.wispforest:owo-lib:0.11.3+1.20.2")
33+
34+
// Uncomment the following line to enable the deprecated Fabric API modules.
35+
// These are included in the Fabric API production distribution and allow you to update your mod to the latest modules at a later more convenient time.
36+
37+
// modImplementation "net.fabricmc.fabric-api:fabric-api-deprecated:${project.fabric_version}"
38+
implementation("org.java-websocket:Java-WebSocket:1.5.4")
39+
include("org.java-websocket:Java-WebSocket:1.5.4")
40+
}
41+
42+
tasks {
43+
processResources {
44+
inputs.property("version", project.version)
45+
46+
filesMatching("fabric.mod.json") {
47+
expand(mapOf("version" to project.version))
48+
}
49+
}
50+
51+
compileJava {
52+
options.encoding = "UTF-8"
53+
}
54+
55+
remapJar {
56+
archiveFileName.set("$archivesBaseName-${project.version}.jar")
57+
from("LICENSE") {
58+
rename { "${it}_${archivesBaseName}"}
59+
}
60+
}
61+
62+
remapSourcesJar {
63+
archiveFileName.set("$archivesBaseName-${project.version}-sources.jar")
64+
from("LICENSE") {
65+
rename { "${it}_${archivesBaseName}"}
66+
}
67+
}
68+
69+
shadowJar {
70+
archiveBaseName.set("$archivesBaseName-DO-NOT-USE")
71+
}
72+
}
73+
74+
java {
75+
// Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task
76+
// if it is present.
77+
// If you remove this line, sources will not be generated.
78+
withSourcesJar()
79+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package net.azisaba.interchatmod.fabric;
2+
3+
import com.mojang.brigadier.arguments.StringArgumentType;
4+
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
5+
import net.azisaba.interchatmod.fabric.model.Guild;
6+
import net.fabricmc.fabric.api.client.command.v2.ClientCommandManager;
7+
import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
8+
import net.minecraft.command.CommandSource;
9+
import net.minecraft.text.Text;
10+
11+
public class Commands {
12+
public static LiteralArgumentBuilder<FabricClientCommandSource> builderGS() {
13+
return ClientCommandManager.literal("cgs")
14+
.then(ClientCommandManager.argument("guild", StringArgumentType.word())
15+
.suggests((ctx, builder) -> CommandSource.suggestMatching(Mod.GUILDS.stream().map(Guild::name), builder))
16+
.executes(ctx -> executeFocus(ctx.getSource(), StringArgumentType.getString(ctx, "guild")))
17+
.then(ClientCommandManager.argument("message", StringArgumentType.greedyString())
18+
.executes(ctx -> executeChat(ctx.getSource(), StringArgumentType.getString(ctx, "guild"), StringArgumentType.getString(ctx, "message")))
19+
)
20+
);
21+
}
22+
23+
public static LiteralArgumentBuilder<FabricClientCommandSource> builderG() {
24+
return ClientCommandManager.literal("cg")
25+
.then(ClientCommandManager.argument("message", StringArgumentType.greedyString())
26+
.executes(ctx -> executeChat(ctx.getSource(), null, StringArgumentType.getString(ctx, "message")))
27+
);
28+
}
29+
30+
public static LiteralArgumentBuilder<FabricClientCommandSource> builderReconnectInterChat() {
31+
return ClientCommandManager.literal("reconnectinterchat")
32+
.executes(ctx -> {
33+
Mod.reconnect();
34+
return 0;
35+
})
36+
.then(ClientCommandManager.argument("apikey", StringArgumentType.greedyString())
37+
.executes(ctx -> {
38+
Mod.CONFIG.apiKey(StringArgumentType.getString(ctx, "apikey"));
39+
Mod.reconnect();
40+
return 0;
41+
})
42+
);
43+
}
44+
45+
private static int executeFocus(FabricClientCommandSource source, String guildName) {
46+
Guild guild = Mod.GUILDS.stream().filter(g -> g.name().equalsIgnoreCase(guildName)).findAny().orElse(null);
47+
if (guild == null) {
48+
source.sendError(Text.literal("そんなぎるどないよ " + guildName));
49+
return 0;
50+
}
51+
Mod.client.selectGuild(guild.id());
52+
source.sendFeedback(Text.literal(guild.name() + " にちゃっとするようにしたよ(/cg <めっせーじ>でできるよ)"));
53+
return 1;
54+
}
55+
56+
private static int executeChat(FabricClientCommandSource source, String guildName, String message) {
57+
if (guildName != null) {
58+
Guild guild = Mod.GUILDS.stream().filter(g -> g.name().equalsIgnoreCase(guildName)).findAny().orElse(null);
59+
if (guild == null) {
60+
source.sendError(Text.literal("そんなぎるどないよ " + guildName));
61+
return 0;
62+
}
63+
Mod.client.sendMessageToGuild(guild.id(), message);
64+
} else {
65+
Mod.client.sendMessageToGuild(null, message);
66+
}
67+
return 1;
68+
}
69+
}
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
package net.azisaba.interchatmod.fabric;
2+
3+
import com.google.gson.Gson;
4+
import com.google.gson.JsonArray;
5+
import com.google.gson.JsonElement;
6+
import com.google.gson.JsonObject;
7+
import net.azisaba.interchatmod.fabric.model.Guild;
8+
import net.fabricmc.api.ModInitializer;
9+
import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback;
10+
import net.minecraft.client.MinecraftClient;
11+
import net.minecraft.client.network.ServerInfo;
12+
import net.minecraft.server.integrated.IntegratedServer;
13+
import org.jetbrains.annotations.NotNull;
14+
import org.slf4j.Logger;
15+
import org.slf4j.LoggerFactory;
16+
17+
import javax.net.ssl.SSLContext;
18+
import javax.net.ssl.SSLSocketFactory;
19+
import java.net.HttpURLConnection;
20+
import java.net.URI;
21+
import java.net.URL;
22+
import java.nio.charset.StandardCharsets;
23+
import java.util.*;
24+
25+
public class Mod implements ModInitializer {
26+
public static final Logger LOGGER = LoggerFactory.getLogger("InterChatMod");
27+
public static final net.azisaba.interchatmod.fabric.ModConfig CONFIG = net.azisaba.interchatmod.fabric.ModConfig.createAndLoad();
28+
public static final Timer TIMER = new Timer(true);
29+
public static final Set<Guild> GUILDS = Collections.synchronizedSet(new HashSet<>());
30+
public static WebSocketChatClient client;
31+
32+
@Override
33+
public void onInitialize() {
34+
ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> {
35+
dispatcher.register(Commands.builderGS());
36+
dispatcher.register(Commands.builderG());
37+
dispatcher.register(Commands.builderReconnectInterChat());
38+
});
39+
40+
TIMER.schedule(new TimerTask() {
41+
@Override
42+
public void run() {
43+
try {
44+
String url = "https://api-ktor.azisaba.net/interchat/guilds/list";
45+
HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
46+
connection.addRequestProperty("Authorization", "Bearer " + CONFIG.apiKey());
47+
String response = new String(connection.getInputStream().readAllBytes(), StandardCharsets.UTF_8);
48+
JsonArray arr = new Gson().fromJson(response, JsonArray.class);
49+
Set<Guild> localGuilds = getGuildsFromArray(arr);
50+
GUILDS.clear();
51+
GUILDS.addAll(localGuilds);
52+
} catch (Exception e) {
53+
LOGGER.warn("Failed to fetch guild list", e);
54+
}
55+
}
56+
}, 1000 * 30, 1000 * 30);
57+
58+
reconnect();
59+
}
60+
61+
public static void reconnect() {
62+
try {
63+
if (client != null) {
64+
client.close();
65+
}
66+
LOGGER.info("Attempting to connect to the server");
67+
URI uri = new URI("wss://api-ktor.azisaba.net/interchat/stream?server=dummy");
68+
client = new WebSocketChatClient(uri);
69+
if (uri.getScheme().startsWith("wss")) {
70+
SSLContext sslContext = SSLContext.getInstance("TLS");
71+
sslContext.init(null, null, null);
72+
SSLSocketFactory factory = sslContext.getSocketFactory();
73+
client.setSocketFactory(factory);
74+
}
75+
client.connect();
76+
} catch (Exception e) {
77+
LOGGER.error("Failed to establish WebSocket session", e);
78+
}
79+
}
80+
81+
@NotNull
82+
private static Set<Guild> getGuildsFromArray(JsonArray arr) {
83+
Set<Guild> localGuilds = new HashSet<>();
84+
for (JsonElement element : arr) {
85+
JsonObject obj = element.getAsJsonObject();
86+
localGuilds.add(
87+
new Guild(
88+
obj.get("id").getAsLong(),
89+
obj.get("name").getAsString(),
90+
obj.get("format").getAsString(),
91+
obj.get("capacity").getAsInt(),
92+
obj.get("open").getAsBoolean(),
93+
obj.get("deleted").getAsBoolean()
94+
)
95+
);
96+
}
97+
return localGuilds;
98+
}
99+
100+
public static boolean isInAzisaba() {
101+
ServerInfo serverInfo = MinecraftClient.getInstance().getCurrentServerEntry();
102+
if (serverInfo == null) return false;
103+
return serverInfo.address.endsWith(".azisaba.net") || serverInfo.address.equals("azisaba.net");
104+
}
105+
106+
public static void trySwitch() {
107+
if (client == null) return;
108+
ServerInfo serverData = MinecraftClient.getInstance().getCurrentServerEntry();
109+
if (serverData != null) {
110+
client.switchServer(serverData.address);
111+
}
112+
IntegratedServer singleServer = MinecraftClient.getInstance().getServer();
113+
if (singleServer != null) {
114+
client.switchServer(singleServer.getSaveProperties().getLevelName());
115+
}
116+
}
117+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package net.azisaba.interchatmod.fabric;
2+
3+
import io.wispforest.owo.config.annotation.Config;
4+
import io.wispforest.owo.config.annotation.Modmenu;
5+
6+
@Modmenu(modId = "interchatmod")
7+
@Config(name = "interchat-config", wrapperName = "ModConfig")
8+
public class ModConfigModel {
9+
public String apiKey = "";
10+
public boolean hideEverything = false;
11+
public boolean chatWithoutCommand = false;
12+
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
package net.azisaba.interchatmod.fabric;
2+
3+
import com.google.gson.Gson;
4+
import com.google.gson.JsonNull;
5+
import com.google.gson.JsonObject;
6+
import com.google.gson.JsonPrimitive;
7+
import net.minecraft.client.MinecraftClient;
8+
import net.minecraft.client.network.ClientPlayerEntity;
9+
import net.minecraft.text.Text;
10+
import org.java_websocket.client.WebSocketClient;
11+
import org.java_websocket.handshake.ServerHandshake;
12+
import org.jetbrains.annotations.Nullable;
13+
14+
import java.net.URI;
15+
import java.util.TimerTask;
16+
17+
public class WebSocketChatClient extends WebSocketClient {
18+
private final Gson gson = new Gson();
19+
private long openAt;
20+
21+
public WebSocketChatClient(URI uri) {
22+
super(uri);
23+
}
24+
25+
private void sendMessage(Text text) {
26+
Mod.LOGGER.info("[WS] {}", text.getString());
27+
if (Mod.CONFIG.hideEverything()) return;
28+
ClientPlayerEntity player = MinecraftClient.getInstance().player;
29+
if (player == null) return;
30+
player.sendMessage(text);
31+
}
32+
33+
@Override
34+
public void onOpen(ServerHandshake handshakedata) {
35+
openAt = System.currentTimeMillis();
36+
auth(Mod.CONFIG.apiKey());
37+
Mod.trySwitch();
38+
sendMessage(Text.literal("Connected to guild chat."));
39+
}
40+
41+
@Override
42+
public void onMessage(String message) {
43+
JsonObject obj = gson.fromJson(message, JsonObject.class);
44+
if (obj.has("message") && !Mod.isInAzisaba()) {
45+
sendMessage(Text.literal(obj.get("message").getAsString()));
46+
}
47+
}
48+
49+
@Override
50+
public void onClose(int code, String reason, boolean remote) {
51+
Mod.LOGGER.info("Disconnected from server");
52+
if (System.currentTimeMillis() - openAt < 1000) {
53+
sendMessage(Text.literal("ギルドチャットから切断されました。APIキーを確認して、/reconnectinterchat [apikey]を実行してください。"));
54+
return;
55+
}
56+
if (remote) {
57+
sendMessage(Text.literal("ギルドチャットから切断されました。15秒後に再接続を試みます。"));
58+
Mod.TIMER.schedule(new TimerTask() {
59+
@Override
60+
public void run() {
61+
Mod.reconnect();
62+
}
63+
}, 1000 * 15);
64+
}
65+
}
66+
67+
@Override
68+
public void onError(Exception ex) {
69+
Mod.LOGGER.error("WebSocket error", ex);
70+
}
71+
72+
public void auth(String key) {
73+
JsonObject obj = new JsonObject();
74+
obj.addProperty("type", "auth");
75+
obj.addProperty("key", key);
76+
send(gson.toJson(obj));
77+
}
78+
79+
public void switchServer(String server) {
80+
JsonObject obj = new JsonObject();
81+
obj.addProperty("type", "switch_server");
82+
obj.addProperty("server", server);
83+
send(gson.toJson(obj));
84+
}
85+
86+
public void selectGuild(long id) {
87+
JsonObject obj = new JsonObject();
88+
obj.addProperty("type", "select");
89+
obj.addProperty("guildId", id);
90+
send(gson.toJson(obj));
91+
}
92+
93+
public void sendMessageToGuild(@Nullable Long guildId, String message) {
94+
JsonObject obj = new JsonObject();
95+
obj.addProperty("type", "message");
96+
obj.add("guildId", guildId == null ? JsonNull.INSTANCE : new JsonPrimitive(guildId));
97+
obj.addProperty("message", message);
98+
send(gson.toJson(obj));
99+
}
100+
}

0 commit comments

Comments
 (0)