2020import org .jetbrains .annotations .NotNull ;
2121
2222import java .awt .Color ;
23+ import java .io .FileInputStream ;
24+ import java .io .FileOutputStream ;
2325import java .io .IOException ;
2426import java .io .InputStream ;
2527import java .nio .file .Files ;
2628import java .nio .file .Path ;
29+ import java .util .Map ;
30+ import java .util .Properties ;
31+ import java .util .concurrent .ConcurrentHashMap ;
32+ import java .util .function .BiConsumer ;
33+ import java .util .function .Consumer ;
2734import java .util .regex .Matcher ;
2835import java .util .regex .Pattern ;
2936import java .util .logging .Level ;
@@ -41,6 +48,8 @@ public final class DiscordBridgePlugin extends JavaPlugin {
4148 private DiscordBotConnection botConnection ;
4249 private boolean serverStartMessageSent ;
4350 private boolean serverStopMessageSent ;
51+ private final Map <String , String > discordUserToAvatarUrl = new ConcurrentHashMap <>();
52+ private final Map <String , String > hytaleUsernameToDiscordUserId = new ConcurrentHashMap <>();
4453
4554 public DiscordBridgePlugin (@ NotNull JavaPluginInit init ) {
4655 super (init );
@@ -58,6 +67,8 @@ protected void setup() {
5867
5968 DiscordBridgeConfig cfg = config .get ();
6069 getLogger ().at (Level .INFO ).log ("Configuration loaded successfully" );
70+ loadMappings ();
71+ getLogger ().at (Level .INFO ).log ("Mappings loaded successfully" );
6172
6273 getEventRegistry ().registerGlobal (EventPriority .NORMAL , PlayerChatEvent .class , this ::onPlayerChat );
6374 getEventRegistry ().registerGlobal (PlayerConnectEvent .class , this ::onPlayerConnect );
@@ -77,7 +88,19 @@ protected void setup() {
7788 return ;
7889 }
7990
80- this .botConnection = new DiscordBotConnection (cfg , getLogger (), this ::relayDiscordMessage );
91+ BiConsumer <String , String > avatarSetter = (userId , url ) -> {
92+ discordUserToAvatarUrl .put (userId , url );
93+ saveMappings ();
94+ };
95+ BiConsumer <String , String > linkSetter = (username , userId ) -> {
96+ hytaleUsernameToDiscordUserId .put (username , userId );
97+ saveMappings ();
98+ };
99+ Consumer <String > unlinkUser = userId -> {
100+ hytaleUsernameToDiscordUserId .entrySet ().removeIf (entry -> entry .getValue ().equals (userId ));
101+ saveMappings ();
102+ };
103+ this .botConnection = new DiscordBotConnection (cfg , getLogger (), this ::relayDiscordMessage , avatarSetter , linkSetter , unlinkUser );
81104 getLogger ().at (Level .INFO ).log ("Discord bot connection initialized" );
82105 }
83106
@@ -136,7 +159,10 @@ private void onPlayerChat(@NotNull PlayerChatEvent event) {
136159 getLogger ().at (Level .WARNING ).log ("Webhook URL not configured; skipping chat message to Discord." );
137160 return ;
138161 }
139- botConnection .sendWebhookMessage (webhookUrl , event .getSender ().getUsername (), cleaned );
162+ String username = event .getSender ().getUsername ();
163+ String discordUserId = hytaleUsernameToDiscordUserId .get (username );
164+ String avatarUrl = discordUserId != null ? discordUserToAvatarUrl .get (discordUserId ) : null ;
165+ botConnection .sendWebhookMessage (webhookUrl , event .getSender ().getUsername (), cleaned , avatarUrl );
140166 }
141167
142168 private void onPlayerConnect (@ NotNull PlayerConnectEvent event ) {
@@ -361,4 +387,43 @@ private void ensureConfigExists(@NotNull Path dataDir) throws IOException {
361387 }
362388 }
363389 }
390+
391+ private void loadMappings () {
392+ Path mappingsFile = getDataDirectory ().resolve ("mappings.properties" );
393+ if (!Files .exists (mappingsFile )) {
394+ return ;
395+ }
396+ Properties props = new Properties ();
397+ try (FileInputStream fis = new FileInputStream (mappingsFile .toFile ())) {
398+ props .load (fis );
399+ for (String key : props .stringPropertyNames ()) {
400+ String value = props .getProperty (key );
401+ if (key .startsWith ("avatar." )) {
402+ String userId = key .substring (7 );
403+ discordUserToAvatarUrl .put (userId , value );
404+ } else if (key .startsWith ("link." )) {
405+ String username = key .substring (5 );
406+ hytaleUsernameToDiscordUserId .put (username , value );
407+ }
408+ }
409+ } catch (IOException e ) {
410+ getLogger ().at (Level .WARNING ).withCause (e ).log ("Failed to load mappings" );
411+ }
412+ }
413+
414+ private void saveMappings () {
415+ Path mappingsFile = getDataDirectory ().resolve ("mappings.properties" );
416+ Properties props = new Properties ();
417+ for (Map .Entry <String , String > entry : discordUserToAvatarUrl .entrySet ()) {
418+ props .setProperty ("avatar." + entry .getKey (), entry .getValue ());
419+ }
420+ for (Map .Entry <String , String > entry : hytaleUsernameToDiscordUserId .entrySet ()) {
421+ props .setProperty ("link." + entry .getKey (), entry .getValue ());
422+ }
423+ try (FileOutputStream fos = new FileOutputStream (mappingsFile .toFile ())) {
424+ props .store (fos , "Discord Chat Bridge Mappings" );
425+ } catch (IOException e ) {
426+ getLogger ().at (Level .WARNING ).withCause (e ).log ("Failed to save mappings" );
427+ }
428+ }
364429}
0 commit comments