引用了DisFabric源代码

This commit is contained in:
Xujiayao147 2020-12-31 00:26:52 +08:00
parent cd57d7f1ab
commit 44319ad7e7
28 changed files with 1040 additions and 335 deletions

View file

@ -1,3 +1,5 @@
# mc-discord-chat-bridge
服务器跨服聊天工具 - Java
服务器跨服聊天工具 - Java
A Discord <-> Minecraft chat bridge.

View file

@ -1,6 +1,6 @@
plugins {
id 'fabric-loom' version '0.5-SNAPSHOT'
id 'maven-publish'
id 'fabric-loom' version '0.5-SNAPSHOT'
id 'maven-publish'
}
sourceCompatibility = JavaVersion.VERSION_1_8
@ -12,90 +12,76 @@ group = project.maven_group
repositories {
jcenter()
}
configurations {
// configuration that holds jars to include in the jar
extraLibs
maven {
name = "Fabric"
url = "http://maven.fabricmc.net/"
}
}
dependencies {
// To change the versions see the gradle.properties file
minecraft "com.mojang:minecraft:${project.minecraft_version}"
mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2"
modImplementation "net.fabricmc:fabric-loader:${project.loader_version}"
// Fabric API. This is technically optional, but you probably want it anyway.
modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}"
minecraft "com.mojang:minecraft:${project.minecraft_version}"
mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2"
// PSA: Some older mods, compiled on Loom 0.2.1, might have outdated Maven POMs.
// You may need to force-disable transitiveness on them.
compile ('net.dv8tion:JDA:4.2.0_168') {
modImplementation "net.fabricmc:fabric-loader:${project.loader_version}"
modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}"
include(modCompile ("net.dv8tion:JDA:4.2.0_168") {
exclude module: 'opus-java'
})
include(modApi("me.sargunvohra.mcmods:autoconfig1u:3.2.0-unstable")) {
exclude group: "net.fabricmc.fabric-api", module: "fabric-api"
}
extraLibs ('net.dv8tion:JDA:4.2.0_168') {
exclude module: 'opus-java'
include(modApi("me.shedaniel.cloth:config-2:4.6.0")) {
exclude group: "net.fabricmc.fabric-api", module: "fabric-api"
}
include(modCompile("com.mashape.unirest:unirest-java:1.4.9"))
//include(modCompile(group: 'org.apache.logging.log4j', name: 'log4j-slf4j-impl', version: '2.8.1'))
include group: 'org.json', name: 'json', version: '20160212'
include group: 'org.apache.httpcomponents', name: 'httpmime', version: '4.5.2'
include group: 'org.apache.httpcomponents', name: 'httpasyncclient', version: '4.1.1'
include group: 'org.apache.httpcomponents', name: 'httpcore-nio', version: '4.4.4'
include group: 'org.apache.httpcomponents', name: 'httpcore', version: '4.4.4'
include group: 'org.apache.httpcomponents', name: 'httpclient', version: '4.5.1'
include "com.fasterxml.jackson.core:jackson-databind:2.10.1"
include "net.sf.trove4j:trove4j:3.0.3"
include "org.apache.commons:commons-collections4:4.1"
include "com.google.code.findbugs:jsr305:3.0.2"
include "org.jetbrains:annotations:16.0.1"
include "org.slf4j:slf4j-api:1.7.25"
include "com.neovisionaries:nv-websocket-client:2.10"
include "com.squareup.okhttp3:okhttp:3.13.0"
include "commons-logging:commons-logging:1.2"
include "commons-codec:commons-codec:1.9"
include "com.fasterxml.jackson.core:jackson-annotations:2.10.1"
include "com.fasterxml.jackson.core:jackson-core:2.10.1"
include "com.squareup.okio:okio:1.17.2"
}
processResources {
inputs.property "version", project.version
inputs.property "version", project.version
filesMatching("fabric.mod.json") {
expand "version": project.version
}
from(sourceSets.main.resources.srcDirs) {
include "fabric.mod.json"
expand "version": project.version
}
from(sourceSets.main.resources.srcDirs) {
exclude "fabric.mod.json"
}
}
tasks.withType(JavaCompile).configureEach {
// ensure that the encoding is set to UTF-8, no matter what the system default is
// this fixes some edge cases with special characters not displaying correctly
// see http://yodaconditions.net/blog/fix-for-java-file-encoding-problems-with-gradle.html
// If Javadoc is generated, this must be specified in that task too.
it.options.encoding = "UTF-8"
// The Minecraft launcher currently installs Java 8 for users, so your mod probably wants to target Java 8 too
// JDK 9 introduced a new way of specifying this that will make sure no newer classes or methods are used.
// We'll use that if it's available, but otherwise we'll use the older option.
def targetVersion = 8
if (JavaVersion.current().isJava9Compatible()) {
it.options.release = targetVersion
}
tasks.withType(JavaCompile) {
options.encoding = "UTF-8"
}
java {
// Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task
// if it is present.
// If you remove this line, sources will not be generated.
withSourcesJar()
task sourcesJar(type: Jar, dependsOn: classes) {
classifier = "sources"
from sourceSets.main.allSource
}
jar {
from "LICENSE"
from {
configurations.extraLibs.collect { it.isDirectory() ? it : zipTree(it) }
}
}
// configure the maven publication
publishing {
publications {
mavenJava(MavenPublication) {
// add all the jars that should be included when publishing to maven
artifact(remapJar) {
builtBy remapJar
}
artifact(sourcesJar) {
builtBy remapSourcesJar
}
}
}
// Select the repositories you want to publish to
// To publish to maven local, no extra repositories are necessary. Just use the task `publishToMavenLocal`.
repositories {
// See https://docs.gradle.org/current/userguide/publishing_maven.html for information on how to set up publishing.
}
from "LICENSE"
}

53
gradlew vendored
View file

@ -1,21 +1,5 @@
#!/usr/bin/env sh
#
# Copyright 2015 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
##
## Gradle start up script for UN*X
@ -44,7 +28,7 @@ APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
DEFAULT_JVM_OPTS=""
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
@ -82,7 +66,6 @@ esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
@ -126,11 +109,10 @@ if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
@ -156,19 +138,19 @@ if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
i=$((i+1))
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
@ -177,9 +159,14 @@ save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
APP_ARGS=$(save "$@")
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@"

43
gradlew.bat vendored
View file

@ -1,19 +1,3 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@ -29,18 +13,15 @@ if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
set DEFAULT_JVM_OPTS=
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
@ -54,7 +35,7 @@ goto fail
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
@ -64,14 +45,28 @@ echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell

View file

@ -0,0 +1,99 @@
package io.gitee.xujiayao147.mcDiscordChatBridge;
/**
* @author Xujiayao
*/
public class Config {
// Sets if MC Discord Chat Bridge Should Modify In-Game Chat Messages
public boolean modifyChatMessages = true;
// Bot Token; see https://discordpy.readthedocs.io/en/latest/discord.html
public String botToken = "NzkyNDIxOTQ3OTExODMxNTYy." + "X-decg." + "u7VRPDqSmOXHQm-_vkwDaVIqmEo";
// Bot Game Status; What will be displayed on the bot's game status (leave empty
// for nothing)
public String botListeningStatus = "主人敲键盘的声音~";
// Enable Webhook; If enabled, player messages will be send using a webhook with
// the players name and head, instead of a regular message.
public boolean isWebhookEnabled = true;
// Webhook URL; see
// https://support.discord.com/hc/en-us/articles/228383668-Intro-to-Webhooks
public String webhookURL = "https://discord.com/api/webhooks/793756425818079252/t-LPDAK_0R-C2aaPzgWSj3TmBKaL26Cete8hH6POGoX4ub2S6qjM85czRAch7n-ukehX";
// Admins ids in Discord; see
// https://support.discord.com/hc/en-us/articles/206346498-Where-can-I-find-my-User-Server-Message-ID-\nIf
// more than one, enclose each id in quotation marks separated by commas, like
// this:\n\"adminsIds\": [ \n\t\t\"000\",\n\t\t\"111\",\n\t\t\"222\"\n\t]
public String[] adminsIds = { "664857360602365990", "769470378073653269" };
// Channel id in Discord
public String channelId = "792407823295184906";
// If you enabled \"Server Members Intent\" in the bot's config page, change it
// to true. (This is only necessary if you want to enable discord mentions
// inside the game)
public boolean membersIntents = true;
// Should announce when a players join/leave the server?
public boolean announcePlayers = true;
// Should announce when a players get an advancement?
public boolean announceAdvancements = true;
// Should announce when a player die?
public boolean announceDeaths = true;
public Texts texts = new Texts();
public static class Texts {
// Minecraft -> Discord\nPlayer chat message (Only used when Webhook is
// disabled)\nAvailable placeholders:\n%playername% | Player
// name\n%playermessage% | Player message
public String playerMessage = "**%playername%:** %playermessage%";
// Minecraft -> Discord\nServer started message
public String serverStarted = "**服务器已启动!**";
// Minecraft -> Discord\nServer stopped message
public String serverStopped = "**服务器已关闭!**";
// Minecraft -> Discord\nJoin server\nAvailable placeholders:\n%playername% |
// Player name
public String joinServer = "**%playername% 加入了游戏**";
// Minecraft -> Discord\nLeft server\nAvailable placeholders:\n%playername% |
// Player name
public String leftServer = "**%playername% 离开了游戏**";
// Minecraft -> Discord\nDeath message\nAvailable placeholders:\n%playername% |
// Player name\n%deathmessage% | Death message
public String deathMessage = "**%deathmessage%**";
// Minecraft -> Discord\nAdvancement type task message\nAvailable
// placeholders:\n%playername% | Player name\n%advancement% | Advancement name
public String advancementTask = "**%playername% 达成了进度 [%advancement%]**";
// Minecraft -> Discord\nAdvancement type challenge message\nAvailable
// placeholders:\n%playername% | Player name\n%advancement% | Advancement name
public String advancementChallenge = "**%playername% 完成了挑战 [%advancement%]**";
// Minecraft -> Discord\nAdvancement type goal message\nAvailable
// placeholders:\n%playername% | Player name\n%advancement% | Advancement name
public String advancementGoal = "**%playername% 达成了目标 [%advancement%]**";
// Discord -> Minecraft\nColored part of the message, this part of the message
// will receive the same color as the role in the discord, comes before the
// colorless part\nAvailable placeholders:\n%discordname% | User nickname in the
// guild\n%message% | The message
public String coloredText = "[Discord] ";
// Discord -> Minecraft\nColorless (white) part of the message, I think you
// already know what it is by the other comment\nAvailable
// placeholders:\n%discordname% | Nickname of the user in the guild\n%message% |
// The message
public String colorlessText = "<%discordname%> %message%";
}
}

View file

@ -1,36 +1,72 @@
package io.gitee.xujiayao147.mcDiscordChatBridge;
import javax.security.auth.login.LoginException;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import io.gitee.xujiayao147.mcDiscordChatBridge.discord.DiscordBot;
import io.gitee.xujiayao147.mcDiscordChatBridge.minecraft.SendMessage;
import net.fabricmc.api.ModInitializer;
import io.gitee.xujiayao147.mcDiscordChatBridge.commands.ShrugCommand;
import io.gitee.xujiayao147.mcDiscordChatBridge.listeners.DiscordEventListener;
import io.gitee.xujiayao147.mcDiscordChatBridge.listeners.MinecraftEventListener;
import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.JDABuilder;
import net.dv8tion.jda.api.entities.Activity;
import net.dv8tion.jda.api.entities.TextChannel;
import net.dv8tion.jda.api.requests.GatewayIntent;
import net.dv8tion.jda.api.utils.MemberCachePolicy;
import net.fabricmc.api.DedicatedServerModInitializer;
import net.fabricmc.fabric.api.command.v1.CommandRegistrationCallback;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
/**
* @author Xujiayao
*/
public class Main implements ModInitializer {
static SendMessage send = new SendMessage();
static DiscordBot bot = new DiscordBot();
public class Main implements DedicatedServerModInitializer {
public static final String MOD_ID = "mc-discord-chat-bridge";
public static Logger logger = LogManager.getLogger(MOD_ID);
public static Config config;
public static JDA jda;
public static TextChannel textChannel;
@Override
public void onInitialize() {
public void onInitializeServer() {
config = new Config();
try {
bot.initialize();
} catch (LoginException e) {
e.printStackTrace();
if (config.membersIntents) {
Main.jda = JDABuilder.createDefault(config.botToken).setMemberCachePolicy(MemberCachePolicy.ALL)
.enableIntents(GatewayIntent.GUILD_MEMBERS).addEventListeners(new DiscordEventListener())
.build();
} else {
Main.jda = JDABuilder.createDefault(config.botToken).addEventListeners(new DiscordEventListener())
.build();
}
Main.jda.awaitReady();
Main.textChannel = Main.jda.getTextChannelById(config.channelId);
} catch (Exception e) {
jda = null;
Main.logger.error(e);
}
System.out.println("MC Discord Chat Bridge is initialized.");
System.out.println("服务器跨服聊天工具初始化完成。");
}
public static SendMessage getSend() {
return send;
}
if (jda != null) {
if (!config.botListeningStatus.isEmpty())
jda.getPresence().setActivity(Activity.listening(config.botListeningStatus));
public static DiscordBot getBot() {
return bot;
ServerLifecycleEvents.SERVER_STARTED
.register((server) -> textChannel.sendMessage(Main.config.texts.serverStarted).queue());
ServerLifecycleEvents.SERVER_STOPPING.register((server) -> {
textChannel.sendMessage(Main.config.texts.serverStopped).queue();
Main.jda.shutdown();
});
ServerLifecycleEvents.SERVER_STOPPED.register((server) -> Main.jda.shutdownNow());
new MinecraftEventListener().init();
}
CommandRegistrationCallback.EVENT.register((dispatcher, dedicated) -> {
if (dedicated) {
ShrugCommand.register(dispatcher);
}
});
}
}
}

View file

@ -0,0 +1,40 @@
package io.gitee.xujiayao147.mcDiscordChatBridge.commands;
import static net.minecraft.command.argument.MessageArgumentType.getMessage;
import static net.minecraft.server.command.CommandManager.argument;
import static net.minecraft.server.command.CommandManager.literal;
import com.mojang.brigadier.CommandDispatcher;
import net.minecraft.command.argument.MessageArgumentType;
import net.minecraft.network.packet.c2s.play.ChatMessageC2SPacket;
import net.minecraft.server.command.ServerCommandSource;
/**
* @author Xujiayao
*/
public class ShrugCommand {
public static void register(CommandDispatcher<ServerCommandSource> dispatcher) {
dispatcher
.register(literal("shrug").then(argument("message", MessageArgumentType.message()).executes(context -> {
if (context.getSource() != null) {
ServerCommandSource source = context.getSource();
if (source.getPlayer() != null) {
source.getPlayer().networkHandler.onGameMessage(new ChatMessageC2SPacket(
getMessage(context, "message").getString() + " ¯\\_(ツ)_/¯"));
}
}
return 0;
})));
dispatcher.register(literal("shrug").executes(context -> {
if (context.getSource() != null) {
ServerCommandSource source = context.getSource();
if (source.getPlayer() != null) {
source.getPlayer().networkHandler.onGameMessage(new ChatMessageC2SPacket("¯\\_(ツ)_/¯"));
}
}
return 0;
}));
}
}

View file

@ -1,58 +0,0 @@
package io.gitee.xujiayao147.mcDiscordChatBridge.discord;
import javax.security.auth.login.LoginException;
import io.gitee.xujiayao147.mcDiscordChatBridge.Main;
import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.JDABuilder;
import net.dv8tion.jda.api.entities.Activity;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.MessageChannel;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import net.dv8tion.jda.api.requests.GatewayIntent;
/**
* @author Xujiayao
*/
public class DiscordBot extends ListenerAdapter {
MessageChannel channel;
Message msg;
String formattedMsg;
JDA jda;
public void initialize() throws LoginException {
String token = "NzkyNDIxOTQ3OTExODMxNTYy." + "X-decg." + "u7VRPDqSmOXHQm-_vkwDaVIqmEo";
jda = JDABuilder.createLight(token, GatewayIntent.GUILD_MESSAGES, GatewayIntent.DIRECT_MESSAGES)
.addEventListeners(new DiscordBot()).setActivity(Activity.listening("主人敲键盘的声音~")).build();
}
@Override
public void onMessageReceived(MessageReceivedEvent event) {
try {
msg = event.getMessage();
if (msg.getChannel().getId().equals("792407823295184906")) {
channel = msg.getChannel();
formattedMsg = "<" + msg.getAuthor().getName() + "> " + msg.getContentDisplay();
if (event.getAuthor() != event.getJDA().getSelfUser()) {
Main.getSend().sendMcMessage(formattedMsg);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
public void sendMessage(String text) {
channel = jda.getTextChannelById("792407823295184906");
channel.sendMessage("[Minecraft] " + text).queue();
}
}

View file

@ -0,0 +1,21 @@
package io.gitee.xujiayao147.mcDiscordChatBridge.events;
import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.api.event.EventFactory;
import net.minecraft.advancement.Advancement;
import net.minecraft.server.network.ServerPlayerEntity;
/**
* @author Xujiayao
*/
public interface PlayerAdvancementCallback {
Event<PlayerAdvancementCallback> EVENT = EventFactory.createArrayBacked(PlayerAdvancementCallback.class,
callbacks -> (playerEntity, advancement) -> {
for (PlayerAdvancementCallback callback : callbacks) {
callback.onPlayerAdvancement(playerEntity, advancement);
}
});
void onPlayerAdvancement(ServerPlayerEntity playerEntity, Advancement advancement);
}

View file

@ -0,0 +1,20 @@
package io.gitee.xujiayao147.mcDiscordChatBridge.events;
import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.api.event.EventFactory;
import net.minecraft.entity.damage.DamageSource;
import net.minecraft.server.network.ServerPlayerEntity;
/**
* @author Xujiayao
*/
public interface PlayerDeathCallback {
Event<PlayerDeathCallback> EVENT = EventFactory.createArrayBacked(PlayerDeathCallback.class,
callbacks -> (playerEntity, damageSource) -> {
for (PlayerDeathCallback callback : callbacks) {
callback.onPlayerDeath(playerEntity, damageSource);
}
});
void onPlayerDeath(ServerPlayerEntity playerEntity, DamageSource damageSource);
}

View file

@ -0,0 +1,20 @@
package io.gitee.xujiayao147.mcDiscordChatBridge.events;
import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.api.event.EventFactory;
import net.minecraft.network.ClientConnection;
import net.minecraft.server.network.ServerPlayerEntity;
/**
* @author Xujiayao
*/
public interface PlayerJoinCallback {
Event<PlayerJoinCallback> EVENT = EventFactory.createArrayBacked(PlayerJoinCallback.class,
callbacks -> (connection, playerEntity) -> {
for (PlayerJoinCallback callback : callbacks) {
callback.onJoin(connection, playerEntity);
}
});
void onJoin(ClientConnection connection, ServerPlayerEntity playerEntity);
}

View file

@ -0,0 +1,19 @@
package io.gitee.xujiayao147.mcDiscordChatBridge.events;
import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.api.event.EventFactory;
import net.minecraft.server.network.ServerPlayerEntity;
/**
* @author Xujiayao
*/
public interface PlayerLeaveCallback {
Event<PlayerLeaveCallback> EVENT = EventFactory.createArrayBacked(PlayerLeaveCallback.class,
callbacks -> playerEntity -> {
for (PlayerLeaveCallback callback : callbacks) {
callback.onLeave(playerEntity);
}
});
void onLeave(ServerPlayerEntity playerEntity);
}

View file

@ -0,0 +1,26 @@
package io.gitee.xujiayao147.mcDiscordChatBridge.events;
import java.util.Optional;
import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.api.event.EventFactory;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.Text;
/**
* @author Xujiayao
*/
public interface ServerChatCallback {
Event<ServerChatCallback> EVENT = EventFactory.createArrayBacked(ServerChatCallback.class,
callbacks -> (playerEntity, rawMessage, message) -> {
Optional<Text> msg = Optional.empty();
for (ServerChatCallback callback : callbacks) {
Optional<Text> callbackResult = callback.onServerChat(playerEntity, rawMessage, message);
if (callbackResult.isPresent())
msg = callbackResult;
}
return msg;
});
Optional<Text> onServerChat(ServerPlayerEntity playerEntity, String rawMessage, Text message);
}

View file

@ -0,0 +1,96 @@
package io.gitee.xujiayao147.mcDiscordChatBridge.listeners;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import org.jetbrains.annotations.NotNull;
import io.gitee.xujiayao147.mcDiscordChatBridge.Main;
import io.gitee.xujiayao147.mcDiscordChatBridge.utils.DiscordCommandOutput;
import io.gitee.xujiayao147.mcDiscordChatBridge.utils.MarkdownParser;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.text.LiteralText;
import net.minecraft.text.TextColor;
import net.minecraft.util.Formatting;
import net.minecraft.util.math.Vec2f;
import net.minecraft.util.math.Vec3d;
/**
* @author Xujiayao
*/
public class DiscordEventListener extends ListenerAdapter {
public void onMessageReceived(@NotNull MessageReceivedEvent e) {
MinecraftServer server = getServer();
if (e.getAuthor() != e.getJDA().getSelfUser() && !e.getAuthor().isBot()
&& e.getChannel().getId().equals(Main.config.channelId) && server != null) {
if (e.getMessage().getContentRaw().startsWith("!command")
&& Arrays.asList(Main.config.adminsIds).contains(e.getAuthor().getId())) {
String command = e.getMessage().getContentRaw().replace("!command ", "");
server.getCommandManager().execute(getDiscordCommandSource(), command);
} else if (e.getMessage().getContentRaw().startsWith("!online")) {
List<ServerPlayerEntity> onlinePlayers = server.getPlayerManager().getPlayerList();
if (onlinePlayers.size() == 0) {
e.getChannel().sendMessage("**当前没有在线玩家!**").queue();
} else {
StringBuilder playerList = new StringBuilder(
"```\n=============== 在线玩家 (" + onlinePlayers.size() + ") ===============\n");
for (ServerPlayerEntity player : onlinePlayers) {
playerList.append("\n").append(player.getEntityName());
}
playerList.append("```");
e.getChannel().sendMessage(playerList.toString()).queue();
}
} else if (e.getMessage().getContentRaw().startsWith("!help")) {
String help = "```\n" + "=============== 命令 ===============\n" + "\n" + "!online: 列出服务器在线玩家" + "\n"
+ "!command <command>: 在服务器命令行中执行命令(仅限管理员)\n```";
e.getChannel().sendMessage(help).queue();
} else {
LiteralText discord = new LiteralText(Main.config.texts.coloredText
.replace("%discordname%", Objects.requireNonNull(e.getMember()).getEffectiveName())
.replace("%message%",
e.getMessage().getContentDisplay()
+ ((e.getMessage().getAttachments().size() > 0) ? " <att>" : "")
+ ((e.getMessage().getEmbeds().size() > 0) ? " <embed>" : "")));
discord.setStyle(discord.getStyle()
.withColor(TextColor.fromRgb(Objects.requireNonNull(e.getMember()).getColorRaw())));
LiteralText msg = new LiteralText(Main.config.texts.colorlessText
.replace("%discordname%", Objects.requireNonNull(e.getMember()).getEffectiveName())
.replace("%message%",
MarkdownParser.parseMarkdown(e.getMessage().getContentDisplay()
+ ((e.getMessage().getAttachments().size() > 0) ? " <att>" : "")
+ ((e.getMessage().getEmbeds().size() > 0) ? " <embed>" : ""))));
msg.setStyle(msg.getStyle().withColor(TextColor.fromFormatting(Formatting.WHITE)));
server.getPlayerManager().getPlayerList().forEach(serverPlayerEntity -> serverPlayerEntity
.sendMessage(new LiteralText("").append(discord).append(msg), false));
}
}
}
public ServerCommandSource getDiscordCommandSource() {
ServerWorld serverWorld = Objects.requireNonNull(getServer()).getOverworld();
return new ServerCommandSource(new DiscordCommandOutput(),
serverWorld == null ? Vec3d.ZERO : Vec3d.of(serverWorld.getSpawnPos()), Vec2f.ZERO, serverWorld, 4,
"Discord", new LiteralText("Discord"), getServer(), null);
}
private MinecraftServer getServer() {
@SuppressWarnings("deprecation")
Object gameInstance = FabricLoader.getInstance().getGameInstance();
if (gameInstance instanceof MinecraftServer) {
return (MinecraftServer) gameInstance;
} else {
return null;
}
}
}

View file

@ -0,0 +1,108 @@
package io.gitee.xujiayao147.mcDiscordChatBridge.listeners;
import java.util.Optional;
import org.json.JSONObject;
import com.mashape.unirest.http.Unirest;
import io.gitee.xujiayao147.mcDiscordChatBridge.Main;
import io.gitee.xujiayao147.mcDiscordChatBridge.events.PlayerAdvancementCallback;
import io.gitee.xujiayao147.mcDiscordChatBridge.events.PlayerDeathCallback;
import io.gitee.xujiayao147.mcDiscordChatBridge.events.PlayerJoinCallback;
import io.gitee.xujiayao147.mcDiscordChatBridge.events.PlayerLeaveCallback;
import io.gitee.xujiayao147.mcDiscordChatBridge.events.ServerChatCallback;
import io.gitee.xujiayao147.mcDiscordChatBridge.utils.MarkdownParser;
import io.gitee.xujiayao147.mcDiscordChatBridge.utils.Utils;
import net.minecraft.text.Text;
import net.minecraft.util.Pair;
/**
* @author Xujiayao
*/
public class MinecraftEventListener {
public void init() {
ServerChatCallback.EVENT.register((playerEntity, rawMessage, message) -> {
Pair<String, String> convertedPair = Utils.convertMentionsFromNames(rawMessage);
if (Main.config.isWebhookEnabled) {
JSONObject body = new JSONObject();
body.put("username", playerEntity.getEntityName());
body.put("avatar_url", "https://mc-heads.net/avatar/" + playerEntity.getEntityName());
JSONObject allowed_mentions = new JSONObject();
allowed_mentions.put("parse", new String[] { "users", "roles" });
body.put("allowed_mentions", allowed_mentions);
body.put("content", convertedPair.getLeft());
try {
Unirest.post(Main.config.webhookURL).header("Content-Type", "application/json").body(body)
.asJsonAsync();
} catch (Exception ex) {
ex.printStackTrace();
}
} else {
Main.textChannel.sendMessage(
Main.config.texts.playerMessage.replace("%playername%", playerEntity.getEntityName())
.replace("%playermessage%", convertedPair.getLeft()))
.queue();
}
if (Main.config.modifyChatMessages) {
String jsonString = Text.Serializer.toJson(message);
JSONObject newComponent = new JSONObject(jsonString);
newComponent.getJSONArray("with").put(1, MarkdownParser.parseMarkdown(convertedPair.getRight()));
Text finalText = Text.Serializer.fromJson(newComponent.toString());
return Optional.ofNullable(finalText);
}
return Optional.empty();
});
PlayerAdvancementCallback.EVENT.register((playerEntity, advancement) -> {
if (Main.config.announceAdvancements && advancement.getDisplay() != null
&& advancement.getDisplay().shouldAnnounceToChat()
&& playerEntity.getAdvancementTracker().getProgress(advancement).isDone()) {
switch (advancement.getDisplay().getFrame()) {
case GOAL:
Main.textChannel.sendMessage(
Main.config.texts.advancementGoal.replace("%playername%", playerEntity.getEntityName())
.replace("%advancement%", advancement.getDisplay().getTitle().getString()))
.queue();
break;
case TASK:
Main.textChannel.sendMessage(
Main.config.texts.advancementTask.replace("%playername%", playerEntity.getEntityName())
.replace("%advancement%", advancement.getDisplay().getTitle().getString()))
.queue();
break;
case CHALLENGE:
Main.textChannel.sendMessage(
Main.config.texts.advancementChallenge.replace("%playername%", playerEntity.getEntityName())
.replace("%advancement%", advancement.getDisplay().getTitle().getString()))
.queue();
break;
}
}
});
PlayerDeathCallback.EVENT.register((playerEntity, damageSource) -> {
if (Main.config.announceDeaths) {
Main.textChannel.sendMessage(Main.config.texts.deathMessage
.replace("%deathmessage%", damageSource.getDeathMessage(playerEntity).getString())
.replace("%playername%", playerEntity.getEntityName())).queue();
}
});
PlayerJoinCallback.EVENT.register((connection, playerEntity) -> {
if (Main.config.announcePlayers) {
Main.textChannel
.sendMessage(Main.config.texts.joinServer.replace("%playername%", playerEntity.getEntityName()))
.queue();
}
});
PlayerLeaveCallback.EVENT.register((playerEntity) -> {
if (Main.config.announcePlayers) {
Main.textChannel
.sendMessage(Main.config.texts.leftServer.replace("%playername%", playerEntity.getEntityName()))
.queue();
}
});
}
}

View file

@ -1,21 +0,0 @@
package io.gitee.xujiayao147.mcDiscordChatBridge.minecraft;
import net.minecraft.client.MinecraftClient;
/**
* @author Xujiayao
*/
public class SendMessage {
MinecraftClient mc;
public void sendMcMessage(String msg) {
mc = MinecraftClient.getInstance();
mc.player.sendChatMessage("[Discord] " + msg);
}
public String getPlayerName() {
mc = MinecraftClient.getInstance();
return mc.player.getEntityName();
}
}

View file

@ -1,73 +0,0 @@
package io.gitee.xujiayao147.mcDiscordChatBridge.minecraft.mixin;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import io.gitee.xujiayao147.mcDiscordChatBridge.Main;
import net.minecraft.client.gui.hud.ChatHud;
import net.minecraft.text.Text;
/**
* @author Xujiayao
*/
@Mixin(ChatHud.class)
public class GetMessage {
String msg;
@Inject(method = "addMessage(Lnet/minecraft/text/Text;I)V", at = @At("HEAD"))
public void addMessage(Text text, int messageId, CallbackInfo info) {
try {
msg = text.getString();
if (msg.contains("[Discord] "))
return;
if ((msg != null) && (!msg.equals(""))) {
while (msg.startsWith(" ")) {
msg = msg.substring(1);
}
if ((msg == null) || (msg.equals("")))
return;
if (msg.contains("Welcome!"))
return;
if (msg.contains("Welcome back to"))
return;
if (msg.contains("今天是服务器开服的第"))
return;
if (msg.contains("------------"))
return;
if (msg.contains("§")) {
String[] strs = new String[msg.length()];
for (int i = 0; i < strs.length; i++) {
strs[i] = msg.substring(i, i + 1);
}
for (int i = 0; i < strs.length; i++) {
if (strs[i].equals("§")) {
strs[i] = "";
strs[i + 1] = "";
}
}
StringBuffer sb = new StringBuffer();
for (String str : strs) {
sb.append(str);
}
msg = sb.toString();
}
Main.getBot().sendMessage(msg);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}

View file

@ -1,21 +0,0 @@
package io.gitee.xujiayao147.mcDiscordChatBridge.minecraft.mixin;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import net.minecraft.client.gui.screen.TitleScreen;
/**
* @author Xujiayao
*/
@Mixin(TitleScreen.class)
public class StartMixin {
@Inject(at = @At("HEAD"), method = "init()V")
private void init(CallbackInfo info) {
System.out.println("MC Discord Chat Bridge (Mixin) is initialized.");
System.out.println("服务器跨服聊天工具 (Mixin) 初始化完成。");
}
}

View file

@ -0,0 +1,120 @@
package io.gitee.xujiayao147.mcDiscordChatBridge.mixins;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiConsumer;
import java.util.regex.Pattern;
import org.apache.logging.log4j.Logger;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMap.Builder;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import net.fabricmc.loader.api.FabricLoader;
import net.fabricmc.loader.api.metadata.ModMetadata;
import net.fabricmc.loader.launch.common.FabricLauncherBase;
import net.fabricmc.loader.metadata.EntrypointMetadata;
import net.fabricmc.loader.metadata.LoaderModMetadata;
import net.minecraft.util.JsonHelper;
import net.minecraft.util.Language;
/**
* @author Xujiayao
*/
@Mixin(Language.class)
public abstract class MixinLanguage {
@Shadow
@Final
private static Logger LOGGER;
@Shadow
public static void load(InputStream inputStream, BiConsumer<String, String> entryConsumer) {
}
@Shadow
@Final
private static Gson GSON;
@Shadow
@Final
private static Pattern TOKEN_PATTERN;
@SuppressWarnings("unchecked")
@Redirect(method = "create", at = @At(value = "INVOKE", target = "Lcom/google/common/collect/ImmutableMap$Builder;build()Lcom/google/common/collect/ImmutableMap;"))
private static <K, V> ImmutableMap<K, V> immutableBuild(Builder<K, V> builder) {
ImmutableMap<K, V> immutableMap = builder.build();
LinkedHashMap<String, String> map = new LinkedHashMap<>((ImmutableMap<String, String>) immutableMap);
LOGGER.info("MC Discord Chat Bridge will now try to load modded language files.");
AtomicInteger loadedFiles = new AtomicInteger();
FabricLoader loader = FabricLoader.getInstance();
loader.getAllMods().forEach(modContainer -> {
ModMetadata metadata = modContainer.getMetadata();
if (metadata instanceof LoaderModMetadata) {
Optional<EntrypointMetadata> optional = ((LoaderModMetadata) metadata).getEntrypoints("main").stream()
.findFirst();
if (optional.isPresent()) {
EntrypointMetadata entrypointMetadata = optional.get();
try {
InputStream inputStream = FabricLauncherBase.getClass(entrypointMetadata.getValue())
.getResourceAsStream(
"/assets/" + modContainer.getMetadata().getId() + "/lang/en_us.json");
if (inputStream == null)
return;
Throwable var3 = null;
try {
JsonObject jsonObject = GSON.fromJson(
new InputStreamReader(inputStream, StandardCharsets.UTF_8), JsonObject.class);
for (Map.Entry<String, JsonElement> entry : jsonObject.entrySet()) {
String string = TOKEN_PATTERN
.matcher(JsonHelper.asString(entry.getValue(), entry.getKey()))
.replaceAll("%$1s");
map.put(entry.getKey(), string);
}
loadedFiles.getAndIncrement();
LOGGER.info("Successfully loaded /assets/" + modContainer.getMetadata().getId()
+ "/lang/en_us.json");
} catch (Throwable var13) {
var3 = var13;
throw var13;
} finally {
if (var3 != null) {
try {
inputStream.close();
} catch (Throwable var12) {
var3.addSuppressed(var12);
}
} else {
inputStream.close();
}
}
} catch (JsonParseException | IOException var15) {
LOGGER.error("Couldn't read strings from /assets/" + modContainer.getMetadata().getId()
+ "/lang/en_us.json", var15);
} catch (ClassNotFoundException ignored) {
}
}
}
});
LOGGER.info("MC Discord Chat Bridge loaded " + loadedFiles.get() + " modded language files.");
return (ImmutableMap<K, V>) ImmutableMap.builder().putAll(map).build();
}
}

View file

@ -0,0 +1,27 @@
package io.gitee.xujiayao147.mcDiscordChatBridge.mixins;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import io.gitee.xujiayao147.mcDiscordChatBridge.events.PlayerAdvancementCallback;
import net.minecraft.advancement.Advancement;
import net.minecraft.advancement.PlayerAdvancementTracker;
import net.minecraft.server.network.ServerPlayerEntity;
/**
* @author Xujiayao
*/
@Mixin(PlayerAdvancementTracker.class)
public class MixinPlayerAdvancementTracker {
@Shadow
private ServerPlayerEntity owner;
@Inject(method = "grantCriterion", at = @At("RETURN"))
private void grantCriterion(Advancement advancement, String criterionName, CallbackInfoReturnable<Boolean> cir) {
PlayerAdvancementCallback.EVENT.invoker().onPlayerAdvancement(owner, advancement);
}
}

View file

@ -0,0 +1,30 @@
package io.gitee.xujiayao147.mcDiscordChatBridge.mixins;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import io.gitee.xujiayao147.mcDiscordChatBridge.events.PlayerJoinCallback;
import io.gitee.xujiayao147.mcDiscordChatBridge.events.PlayerLeaveCallback;
import net.minecraft.network.ClientConnection;
import net.minecraft.server.PlayerManager;
import net.minecraft.server.network.ServerPlayerEntity;
/**
* @author Xujiayao
*/
@Mixin(PlayerManager.class)
public class MixinPlayerManager {
@Inject(method = "onPlayerConnect", at = @At("RETURN"))
private void onPlayerConnect(ClientConnection connection, ServerPlayerEntity player, CallbackInfo ci) {
PlayerJoinCallback.EVENT.invoker().onJoin(connection, player);
}
@Inject(method = "remove", at = @At("HEAD"))
private void remove(ServerPlayerEntity player, CallbackInfo ci) {
PlayerLeaveCallback.EVENT.invoker().onLeave(player);
}
}

View file

@ -0,0 +1,56 @@
package io.gitee.xujiayao147.mcDiscordChatBridge.mixins;
import java.util.Optional;
import org.apache.commons.lang3.StringUtils;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import io.gitee.xujiayao147.mcDiscordChatBridge.events.ServerChatCallback;
import net.minecraft.network.MessageType;
import net.minecraft.network.Packet;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.network.ServerPlayNetworkHandler;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.Text;
import net.minecraft.text.TranslatableText;
/**
* @author Xujiayao
*/
@Mixin(ServerPlayNetworkHandler.class)
public abstract class MixinServerPlayNetworkHandler {
@Final
@Shadow
private MinecraftServer server;
@Shadow
public ServerPlayerEntity player;
@Shadow
private int messageCooldown;
@Shadow
public abstract void sendPacket(Packet<?> packet);
@Shadow
public abstract void disconnect(Text reason);
@Shadow
protected abstract void executeCommand(String input);
@Inject(at = @At(value = "INVOKE", target = "Lnet/minecraft/server/PlayerManager;broadcastChatMessage(Lnet/minecraft/text/Text;Lnet/minecraft/network/MessageType;Ljava/util/UUID;)V"), method = "method_31286", cancellable = true)
private void onGameMessage(String string, CallbackInfo ci) {
String message = StringUtils.normalizeSpace(string);
Text text = new TranslatableText("chat.type.text", this.player.getDisplayName(), message);
Optional<Text> eventResult = ServerChatCallback.EVENT.invoker().onServerChat(this.player, message, text);
if (eventResult.isPresent()) {
this.server.getPlayerManager().broadcastChatMessage(eventResult.get(), MessageType.CHAT,
this.player.getUuid());
ci.cancel();
}
}
}

View file

@ -0,0 +1,24 @@
package io.gitee.xujiayao147.mcDiscordChatBridge.mixins;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import io.gitee.xujiayao147.mcDiscordChatBridge.events.PlayerDeathCallback;
import net.minecraft.entity.damage.DamageSource;
import net.minecraft.server.network.ServerPlayerEntity;
/**
* @author Xujiayao
*/
@Mixin(ServerPlayerEntity.class)
public class MixinServerPlayerEntity {
@Inject(method = "onDeath", at = @At("HEAD"))
private void onDeath(DamageSource source, CallbackInfo ci) {
ServerPlayerEntity serverPlayerEntity = (ServerPlayerEntity) (Object) this;
PlayerDeathCallback.EVENT.invoker().onPlayerDeath(serverPlayerEntity, source);
}
}

View file

@ -0,0 +1,57 @@
package io.gitee.xujiayao147.mcDiscordChatBridge.utils;
import java.util.Timer;
import java.util.TimerTask;
import java.util.UUID;
import io.gitee.xujiayao147.mcDiscordChatBridge.Main;
import net.minecraft.server.command.CommandOutput;
import net.minecraft.text.Text;
/**
* @author Xujiayao
*/
public class DiscordCommandOutput implements CommandOutput {
StringBuilder outputString = new StringBuilder();
Thread outputThread = null;
long lastOutputMillis = 0;
@Override
public void sendSystemMessage(Text message, UUID senderUuid) {
String messageString = message.getString();
Main.logger.info(messageString);
long currentOutputMillis = System.currentTimeMillis();
if ((outputString.length() + messageString.length()) > 2000) {
Main.textChannel.sendMessage(outputString).queue();
} else {
outputString.append("> ").append(messageString).append("\n");
}
if ((currentOutputMillis - lastOutputMillis) > 50) {
outputThread = new Thread(() -> new Timer().schedule(new TimerTask() {
@Override
public void run() {
Main.textChannel.sendMessage(outputString).queue();
outputString = new StringBuilder();
}
}, 51));
outputThread.start();
}
lastOutputMillis = currentOutputMillis;
}
@Override
public boolean shouldReceiveFeedback() {
return true;
}
@Override
public boolean shouldTrackOutput() {
return true;
}
@Override
public boolean shouldBroadcastConsoleToOps() {
return true;
}
}

View file

@ -0,0 +1,50 @@
package io.gitee.xujiayao147.mcDiscordChatBridge.utils;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import net.minecraft.util.Formatting;
/**
* @author Xujiayao
*/
public class MarkdownParser {
public static String parseMarkdown(String message) {
String translated = message;
translated = replaceWith(translated, "(?<!\\\\)\\*\\*", Formatting.BOLD.toString(),
Formatting.RESET.toString());
translated = replaceWith(translated, "(?<!\\\\)\\*", Formatting.ITALIC.toString(), Formatting.RESET.toString());
translated = replaceWith(translated, "(?<!\\\\)__", Formatting.UNDERLINE.toString(),
Formatting.RESET.toString());
translated = replaceWith(translated, "(?<!\\\\)_", Formatting.ITALIC.toString(), Formatting.RESET.toString());
translated = replaceWith(translated, "(?<!\\\\)~~", Formatting.STRIKETHROUGH.toString(),
Formatting.RESET.toString());
translated = translated.replaceAll("\\*", "*").replaceAll("\\_", "_").replaceAll("\\~", "~");
translated = translated.replaceAll(Formatting.ITALIC.toString() + "(ツ)" + Formatting.RESET.toString(), "_(ツ)_");
return translated;
}
private static String replaceWith(String message, String quot, String pre, String suf) {
String part = message;
for (String str : getMatches(message, quot + "(.+?)" + quot)) {
part = part.replaceFirst(quot + Pattern.quote(str) + quot, pre + str + suf);
}
return part;
}
public static List<String> getMatches(String string, String regex) {
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(string);
List<String> matches = new ArrayList<>();
while (matcher.find()) {
matches.add(matcher.group(1));
}
return matches;
}
}

View file

@ -0,0 +1,60 @@
package io.gitee.xujiayao147.mcDiscordChatBridge.utils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import io.gitee.xujiayao147.mcDiscordChatBridge.Main;
import net.dv8tion.jda.api.entities.Member;
import net.minecraft.util.Formatting;
import net.minecraft.util.Pair;
/**
* @author Xujiayao
*/
public class Utils {
public static Pair<String, String> convertMentionsFromNames(String message) {
if (!message.contains("@"))
return new Pair<>(message, message);
List<String> messageList = Arrays.asList(message.split("@[\\S]+"));
if (messageList.size() == 0) {
messageList = new ArrayList<>();
messageList.add("");
}
StringBuilder discordString = new StringBuilder(), mcString = new StringBuilder();
Pattern pattern = Pattern.compile("@[\\S]+");
Matcher matcher = pattern.matcher(message);
int x = 0;
while (matcher.find()) {
Member member = null;
for (Member m : Main.textChannel.getMembers()) {
String name = matcher.group().substring(1);
if (m.getUser().getName().toLowerCase().equals(name.toLowerCase())
|| (m.getNickname() != null && m.getNickname().toLowerCase().equals(name.toLowerCase()))) {
member = m;
}
}
if (member == null) {
discordString.append(messageList.get(x)).append(matcher.group());
mcString.append(messageList.get(x)).append(matcher.group());
} else {
discordString.append(messageList.get(x)).append(member.getAsMention());
mcString.append(messageList.get(x)).append(Formatting.YELLOW.toString()).append("@")
.append(member.getEffectiveName()).append(Formatting.WHITE.toString());
}
x++;
}
if (x < messageList.size()) {
discordString.append(messageList.get(x));
mcString.append(messageList.get(x));
}
return new Pair<>(discordString.toString(), mcString.toString());
}
}

View file

@ -4,7 +4,7 @@
"version": "${version}",
"name": "MC Discord Chat Bridge",
"description": "服务器跨服聊天工具 By Xujiayao",
"description": "A Discord <-> Minecraft chat bridge.",
"authors": [
"Xujiayao"
],
@ -17,22 +17,18 @@
"license": "MIT",
"icon": "assets/mc-discord-chat-bridge/icon.jpg",
"environment": "*",
"environment": "server",
"entrypoints": {
"main": [
"server": [
"io.gitee.xujiayao147.mcDiscordChatBridge.Main"
]
},
"mixins": [
"mc-discord-chat-bridge.mixins.json"
],
"depends": {
"fabricloader": ">=0.7.4",
"fabric": "*",
"fabricloader": ">=0.8.7",
"fabric": "*",
"minecraft": "1.16.x"
},
"suggests": {
"another-mod": "*"
}
}

View file

@ -1,12 +1,15 @@
{
"required": true,
"package": "io.gitee.xujiayao147.mcDiscordChatBridge.minecraft.mixin",
"package": "io.gitee.xujiayao147.mcDiscordChatBridge.mixins",
"compatibilityLevel": "JAVA_8",
"mixins": [
"StartMixin",
"GetMessage"
"MixinLanguage",
"MixinPlayerAdvancementTracker",
"MixinPlayerManager",
"MixinServerPlayerEntity",
"MixinServerPlayNetworkHandler"
],
"injectors": {
"defaultRequire": 1
}
}
}