diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f12e409 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/MinetestChatBridgeBot/.gradle/ +/MinetestChatBridgeBot/build/ +/MinetestChatBridgeIRCBot/.gradle/ +/MinetestChatBridgeIRCBot/build/ \ No newline at end of file diff --git a/MinetestChatBridgeBot/.classpath b/MinetestChatBridgeBot/.classpath new file mode 100644 index 0000000..9f9cc9f --- /dev/null +++ b/MinetestChatBridgeBot/.classpath @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/MinetestChatBridgeBot/.project b/MinetestChatBridgeBot/.project new file mode 100644 index 0000000..e04c6b6 --- /dev/null +++ b/MinetestChatBridgeBot/.project @@ -0,0 +1,23 @@ + + + MinetestChatBridgeBot + Project MinetestChatBridgeBot created by Buildship. + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.buildship.core.gradleprojectbuilder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.buildship.core.gradleprojectnature + + diff --git a/MinetestChatBridgeBot/.settings/org.eclipse.buildship.core.prefs b/MinetestChatBridgeBot/.settings/org.eclipse.buildship.core.prefs new file mode 100644 index 0000000..7a23d11 --- /dev/null +++ b/MinetestChatBridgeBot/.settings/org.eclipse.buildship.core.prefs @@ -0,0 +1,13 @@ +arguments= +auto.sync=false +build.scans.enabled=false +connection.gradle.distribution=GRADLE_DISTRIBUTION(VERSION(5.6.1)) +connection.project.dir= +eclipse.preferences.version=1 +gradle.user.home= +java.home= +jvm.arguments= +offline.mode=false +override.workspace.settings=true +show.console.view=true +show.executions.view=true diff --git a/MinetestChatBridgeBot/bin/main/appguru/Main.class b/MinetestChatBridgeBot/bin/main/appguru/Main.class new file mode 100644 index 0000000..3250d08 Binary files /dev/null and b/MinetestChatBridgeBot/bin/main/appguru/Main.class differ diff --git a/MinetestChatBridgeBot/bin/main/bridge/FileBridge$1.class b/MinetestChatBridgeBot/bin/main/bridge/FileBridge$1.class new file mode 100644 index 0000000..5fe5e20 Binary files /dev/null and b/MinetestChatBridgeBot/bin/main/bridge/FileBridge$1.class differ diff --git a/MinetestChatBridgeBot/bin/main/bridge/FileBridge.class b/MinetestChatBridgeBot/bin/main/bridge/FileBridge.class new file mode 100644 index 0000000..23b2d79 Binary files /dev/null and b/MinetestChatBridgeBot/bin/main/bridge/FileBridge.class differ diff --git a/MinetestChatBridgeBot/bin/main/bridge/ProcessBridge.class b/MinetestChatBridgeBot/bin/main/bridge/ProcessBridge.class new file mode 100644 index 0000000..13c0ce8 Binary files /dev/null and b/MinetestChatBridgeBot/bin/main/bridge/ProcessBridge.class differ diff --git a/MinetestChatBridgeBot/bin/main/bridge/SocketBridge.class b/MinetestChatBridgeBot/bin/main/bridge/SocketBridge.class new file mode 100644 index 0000000..b41e7df Binary files /dev/null and b/MinetestChatBridgeBot/bin/main/bridge/SocketBridge.class differ diff --git a/MinetestChatBridgeBot/bin/main/chat/Bot.class b/MinetestChatBridgeBot/bin/main/chat/Bot.class new file mode 100644 index 0000000..0fbac22 Binary files /dev/null and b/MinetestChatBridgeBot/bin/main/chat/Bot.class differ diff --git a/MinetestChatBridgeBot/bin/main/commands/Command.class b/MinetestChatBridgeBot/bin/main/commands/Command.class new file mode 100644 index 0000000..25dd751 Binary files /dev/null and b/MinetestChatBridgeBot/bin/main/commands/Command.class differ diff --git a/MinetestChatBridgeBot/bin/main/commands/StatusCommand.class b/MinetestChatBridgeBot/bin/main/commands/StatusCommand.class new file mode 100644 index 0000000..2651dd7 Binary files /dev/null and b/MinetestChatBridgeBot/bin/main/commands/StatusCommand.class differ diff --git a/MinetestChatBridgeBot/bin/main/misc/GarbageCollector.class b/MinetestChatBridgeBot/bin/main/misc/GarbageCollector.class new file mode 100644 index 0000000..cf045ad Binary files /dev/null and b/MinetestChatBridgeBot/bin/main/misc/GarbageCollector.class differ diff --git a/MinetestChatBridgeBot/bin/main/misc/Utils.class b/MinetestChatBridgeBot/bin/main/misc/Utils.class new file mode 100644 index 0000000..7fabb06 Binary files /dev/null and b/MinetestChatBridgeBot/bin/main/misc/Utils.class differ diff --git a/MinetestChatBridgeBot/build.gradle b/MinetestChatBridgeBot/build.gradle new file mode 100644 index 0000000..90bd796 --- /dev/null +++ b/MinetestChatBridgeBot/build.gradle @@ -0,0 +1,37 @@ +buildscript { + repositories { + jcenter() + } + dependencies { + classpath 'com.github.jengelman.gradle.plugins:shadow:2.0.1' + } +} + +apply plugin: 'java' +apply plugin: 'com.github.johnrengelman.shadow' +apply plugin: 'jacoco' +apply plugin: 'application' + + +description = 'Minetest Chat Bridge Discord Bot' +mainClassName = 'appguru.Main' + +repositories { + mavenCentral() + jcenter() +} + +dependencies { + compile "net.dv8tion:JDA:4.0.0_39", 'ch.qos.logback:logback-classic:1.3.0-alpha4' + compile group: 'com.google.guava', name: 'guava', version: '23.5-jre' + testCompile group: 'junit', name: 'junit', version: '4.12' +} + +jar { + manifest { + attributes( + 'Created-By': "Gradle ${gradle.gradleVersion}", + 'Main-Class': "appguru.Main" + ) + } +} \ No newline at end of file diff --git a/MinetestChatBridgeBot/gradle.properties b/MinetestChatBridgeBot/gradle.properties new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/MinetestChatBridgeBot/gradle.properties @@ -0,0 +1 @@ + diff --git a/MinetestChatBridgeBot/settings.gradle b/MinetestChatBridgeBot/settings.gradle new file mode 100644 index 0000000..a9edce5 --- /dev/null +++ b/MinetestChatBridgeBot/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'MinetestChatBridgeBot' diff --git a/minetest-chat-bridge-bot/src/main/java/appguru/Main.java b/MinetestChatBridgeBot/src/main/java/appguru/Main.java similarity index 79% rename from minetest-chat-bridge-bot/src/main/java/appguru/Main.java rename to MinetestChatBridgeBot/src/main/java/appguru/Main.java index f88fccc..4c2cadd 100644 --- a/minetest-chat-bridge-bot/src/main/java/appguru/Main.java +++ b/MinetestChatBridgeBot/src/main/java/appguru/Main.java @@ -1,6 +1,8 @@ package appguru; +import bridge.FileBridge; import bridge.ProcessBridge; +import bridge.SocketBridge; import chat.Bot; import commands.StatusCommand; import misc.GarbageCollector; @@ -15,12 +17,15 @@ import java.util.Date; public class Main { public static long STARTED_AT; public static int GARBAGE_COLLECTION=5000; //5s - public static long PING_WAIT=20000; //20s + public static long PING_WAIT=5000; //5s public static String PREFIX="!"; public static String DISCORD_PREFIX="?"; public static String GUILD_ID=null; public static PrintStream OUT=System.out; + + public static ProcessBridge PROCESS_BRIDGE; + public static void main(String[] args) throws IOException { if (args.length > 4) { @@ -60,16 +65,22 @@ public class Main { OUT.println("INFO: Starting Minetest chat bridge"); String token=args[0]; String channelname=args[1]; - File in=new File(args[2]); - File out=new File(args[3]); - if (!in.isFile() || !out.isFile() || !in.canWrite() || !in.canRead() || !out.canWrite() || !out.canRead()) { - OUT.println("ERR: Input or output files do not exist or can't be read/written."); - OUT.close(); - System.exit(0); + + if (args[2].length() == 0) { + int socket_port=Integer.parseInt(args[3]); + PROCESS_BRIDGE=new SocketBridge("localhost", socket_port); + } else { + File in=new File(args[2]); + File out=new File(args[3]); + if (!in.isFile() || !out.isFile() || !in.canWrite() || !in.canRead() || !out.canWrite() || !out.canRead()) { + OUT.println("ERR: Input or output files do not exist or can't be read/written."); + System.exit(0); + } + PROCESS_BRIDGE=new FileBridge(in, out); } - ProcessBridge pb=new ProcessBridge(in, out); + try { - Bot i=new Bot(token, pb, channelname); + Bot i=new Bot(token, PROCESS_BRIDGE, channelname); i.registerInfo("status", "Status", "", Color.CYAN, null); i.registerCommand("status", new StatusCommand()); diff --git a/minetest-chat-bridge-bot/src/main/java/bridge/ProcessBridge.java b/MinetestChatBridgeBot/src/main/java/bridge/FileBridge.java similarity index 77% rename from minetest-chat-bridge-bot/src/main/java/bridge/ProcessBridge.java rename to MinetestChatBridgeBot/src/main/java/bridge/FileBridge.java index d24ad4d..42ea77e 100644 --- a/minetest-chat-bridge-bot/src/main/java/bridge/ProcessBridge.java +++ b/MinetestChatBridgeBot/src/main/java/bridge/FileBridge.java @@ -5,21 +5,18 @@ import appguru.Main; import java.io.*; import java.util.function.Consumer; -public class ProcessBridge { - public static long PING_WAIT=20000; //20s +public class FileBridge extends ProcessBridge { - public long last_ping; - public void ping() { - last_ping=System.currentTimeMillis(); - } public File out_file; public File in; public PrintWriter out; + private long last_ping_sent; - public ProcessBridge(File in, File out) throws IOException { + public FileBridge(File in, File out) throws IOException { this.out = new PrintWriter(new BufferedWriter(new FileWriter(out, true))); this.out_file=out; this.in=in; + this.last_ping_sent=System.currentTimeMillis(); } public void kill(String reason) { @@ -30,9 +27,11 @@ public class ProcessBridge { fw.write(""); fw.close(); } catch (IOException e) { - e.printStackTrace(); + e.printStackTrace(Main.OUT); } Main.OUT.close(); + out.write("[KIL]"+reason); + out.close(); System.exit(0); } @@ -47,15 +46,18 @@ public class ProcessBridge { try { Thread.sleep(20); } catch (InterruptedException e) { - e.printStackTrace(); + e.printStackTrace(Main.OUT); } - Main.OUT.flush(); + if (System.currentTimeMillis()-last_ping_sent >= 1000) { + out.write("[PIN]"); + } + // Main.OUT.flush(); out.flush(); out.close(); try { out = new PrintWriter(new BufferedWriter(new FileWriter(out_file, true))); } catch (IOException e) { - e.printStackTrace(); + e.printStackTrace(Main.OUT); } } } @@ -71,19 +73,19 @@ public class ProcessBridge { try { Thread.sleep(20); } catch (InterruptedException e) { - e.printStackTrace(); + e.printStackTrace(Main.OUT); } BufferedReader r = null; try { r = new BufferedReader(new FileReader(in)); } catch (FileNotFoundException e) { - e.printStackTrace(); + e.printStackTrace(Main.OUT); } String line = null; try { line = r.readLine(); } catch (IOException e) { - e.printStackTrace(); + e.printStackTrace(Main.OUT); } boolean one_line = line != null; while (line != null) { @@ -99,7 +101,7 @@ public class ProcessBridge { try { line = r.readLine(); } catch (IOException e) { - e.printStackTrace(); + e.printStackTrace(Main.OUT); } } if (System.currentTimeMillis()-last_ping > Main.PING_WAIT) { @@ -109,7 +111,7 @@ public class ProcessBridge { try { FileWriter fw = new FileWriter(in);fw.write("");fw.close(); } catch (IOException e) { - e.printStackTrace(); + e.printStackTrace(Main.OUT); } } } diff --git a/MinetestChatBridgeBot/src/main/java/bridge/ProcessBridge.java b/MinetestChatBridgeBot/src/main/java/bridge/ProcessBridge.java new file mode 100644 index 0000000..48e4a88 --- /dev/null +++ b/MinetestChatBridgeBot/src/main/java/bridge/ProcessBridge.java @@ -0,0 +1,18 @@ +package bridge; + +import java.util.function.Consumer; + +public abstract class ProcessBridge { + public long last_ping; + public void ping() { + last_ping=System.currentTimeMillis(); + } + + public abstract void kill(String reason); + + public abstract void write(String out); + + public abstract void serve(); + + public abstract void listen(Consumer line_consumer); +} \ No newline at end of file diff --git a/MinetestChatBridgeBot/src/main/java/bridge/SocketBridge.java b/MinetestChatBridgeBot/src/main/java/bridge/SocketBridge.java new file mode 100644 index 0000000..602d9b1 --- /dev/null +++ b/MinetestChatBridgeBot/src/main/java/bridge/SocketBridge.java @@ -0,0 +1,112 @@ +package bridge; + +import appguru.Main; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.util.function.Consumer; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * + * @author lars + */ +public class SocketBridge extends ProcessBridge { + private Socket socket; + private final BufferedWriter writer; + private BufferedReader reader; + + public SocketBridge(String host, int port) throws IOException { + socket = new Socket(); + socket.connect(new InetSocketAddress(host, port)); + writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())); + reader = new BufferedReader(new InputStreamReader(socket.getInputStream())); + } + + @Override + public void kill(String reason) { + Main.OUT.println("INFO: "+reason); + if (socket.isConnected()) { + try { + writer.write("[KIL]"+reason); + } catch (IOException ex) { + ex.printStackTrace(Main.OUT); + } + } + try { + writer.close(); + reader.close(); + socket.close(); + } catch (IOException ex) { + ex.printStackTrace(Main.OUT); + } finally { + Main.OUT.close(); + System.exit(1); + } + } + + @Override + public void write(String out) { + synchronized (writer) { + try { + writer.write(out+"\n"); + writer.flush(); + } catch (IOException ex) { + ex.printStackTrace(Main.OUT); + } + } + } + + @Override + public void serve() { + } + + @Override + public void listen(Consumer line_consumer) { + ping(); + new Thread(() -> { + while(true) { + try { + Thread.sleep(20); + } catch (InterruptedException e) { + e.printStackTrace(Main.OUT); + } + String line = null; + try { + line = reader.readLine(); + } catch (IOException e) { + e.printStackTrace(Main.OUT); + if (socket.isClosed() || socket.isInputShutdown() || socket.isOutputShutdown()) { + kill("Socket connection lost"); + } + } + boolean one_line = line != null; + while (line != null) { + + if (line.startsWith("[PIN]")) { // A PING YAY + ping(); + } else if (line.startsWith("[KIL]")) { + kill("Minetest server shutting down; shutting down as well."); + } else { + line_consumer.accept(line); + } + + try { + line = reader.readLine(); + } catch (IOException e) { + e.printStackTrace(Main.OUT); + } + } + if (System.currentTimeMillis()-last_ping > Main.PING_WAIT) { + kill("No ping during the last "+(Main.PING_WAIT/1000)+"s; shutting down."); + } + } + }).start(); + } + +} diff --git a/minetest-chat-bridge-bot/src/main/java/chat/Bot.java b/MinetestChatBridgeBot/src/main/java/chat/Bot.java similarity index 87% rename from minetest-chat-bridge-bot/src/main/java/chat/Bot.java rename to MinetestChatBridgeBot/src/main/java/chat/Bot.java index bfda5f4..a5b0c6a 100644 --- a/minetest-chat-bridge-bot/src/main/java/chat/Bot.java +++ b/MinetestChatBridgeBot/src/main/java/chat/Bot.java @@ -5,44 +5,32 @@ import bridge.ProcessBridge; import com.google.common.collect.HashBiMap; import commands.Command; import misc.Utils; -import net.dv8tion.jda.core.*; -import net.dv8tion.jda.core.entities.*; -import net.dv8tion.jda.core.events.ReadyEvent; -import net.dv8tion.jda.core.events.channel.text.TextChannelCreateEvent; -import net.dv8tion.jda.core.events.channel.text.TextChannelDeleteEvent; -import net.dv8tion.jda.core.events.channel.text.update.*; -import net.dv8tion.jda.core.events.guild.GuildJoinEvent; -import net.dv8tion.jda.core.events.guild.member.*; -import net.dv8tion.jda.core.events.message.MessageReceivedEvent; -import net.dv8tion.jda.core.events.message.priv.PrivateMessageReceivedEvent; -import net.dv8tion.jda.core.events.message.priv.react.PrivateMessageReactionAddEvent; -import net.dv8tion.jda.core.events.role.RoleCreateEvent; -import net.dv8tion.jda.core.events.role.RoleDeleteEvent; -import net.dv8tion.jda.core.events.role.update.RoleUpdateColorEvent; -import net.dv8tion.jda.core.events.role.update.RoleUpdateNameEvent; -import net.dv8tion.jda.core.events.user.update.UserUpdateNameEvent; -import net.dv8tion.jda.core.hooks.ListenerAdapter; -import net.dv8tion.jda.core.utils.cache.MemberCacheView; +import net.dv8tion.jda.api.*; +import net.dv8tion.jda.api.entities.*; +import net.dv8tion.jda.api.events.ReadyEvent; +import net.dv8tion.jda.api.events.channel.text.TextChannelCreateEvent; +import net.dv8tion.jda.api.events.channel.text.TextChannelDeleteEvent; +import net.dv8tion.jda.api.events.channel.text.update.*; +import net.dv8tion.jda.api.events.guild.GuildJoinEvent; +import net.dv8tion.jda.api.events.guild.member.*; +import net.dv8tion.jda.api.events.message.MessageReceivedEvent; +import net.dv8tion.jda.api.events.role.RoleCreateEvent; +import net.dv8tion.jda.api.events.role.RoleDeleteEvent; +import net.dv8tion.jda.api.events.role.update.RoleUpdateColorEvent; +import net.dv8tion.jda.api.events.role.update.RoleUpdateNameEvent; +import net.dv8tion.jda.api.hooks.ListenerAdapter; import javax.security.auth.login.LoginException; -import java.io.*; import java.time.Instant; import java.time.OffsetDateTime; -import java.time.temporal.TemporalAccessor; import java.util.*; import java.awt.Color; -import java.util.function.Consumer; -import java.util.function.Predicate; +import net.dv8tion.jda.api.events.guild.member.update.GuildMemberUpdateNicknameEvent; public class Bot extends ListenerAdapter { public static int DEFAULT_COLOR=Integer.parseInt("7289DA",16); // Discord color - public long last_ping; - public void ping() { - last_ping=System.currentTimeMillis(); - } - public String text_channel; public ProcessBridge bridge; public JDA jda; @@ -88,20 +76,27 @@ public class Bot extends ListenerAdapter { return this.jda.getGuildById(Main.GUILD_ID); } + @Override public void onTextChannelDelete(TextChannelDeleteEvent event) { if (event.getChannel().getIdLong() == global_channel) { System.err.println("Error ! Global channel was deleted !"); System.exit(1); } } + + @Override public void onTextChannelUpdatePermissions(TextChannelUpdatePermissionsEvent event) { if (event.getChannel().getIdLong() == global_channel && !event.getChannel().canTalk()) { System.err.println("Error ! Cannot talk in global channel !"); System.exit(1); } } + + @Override public void onTextChannelUpdateNSFW(TextChannelUpdateNSFWEvent event) {} + @Override public void onTextChannelUpdateParent(TextChannelUpdateParentEvent event) {} + @Override public void onTextChannelCreate(TextChannelCreateEvent event) {} @Override @@ -112,11 +107,15 @@ public class Bot extends ListenerAdapter { }*/ // IDEA: leave } + + public String escapeName(String name) { + return name.replace(" ", "_").replace(",", "_"); + } @Override public void onGuildMemberJoin(GuildMemberJoinEvent e) { Member m=e.getMember(); - String name=m.getEffectiveName().replace(" ", "_"); + String name=escapeName(m.getEffectiveName()); members.put(Utils.getFreeKey(name, members), m.getUser().getIdLong()); bridge.write("[JOI]"+name+" #"+ Utils.getColorString(m.getColorRaw())); } @@ -169,10 +168,10 @@ public class Bot extends ListenerAdapter { if (chosen == null) { List guilds=event.getJDA().getGuilds(); String guild_id=guilds.get(0).getId(); - OffsetDateTime min_join_time=guilds.get(0).getMember(event.getJDA().getSelfUser()).getJoinDate(); + OffsetDateTime min_join_time=guilds.get(0).getMember(event.getJDA().getSelfUser()).getTimeJoined(); for (int i=1; i < guilds.size(); i++) { Guild g=guilds.get(i); - OffsetDateTime join_time=guilds.get(i).getMember(event.getJDA().getSelfUser()).getJoinDate(); + OffsetDateTime join_time=guilds.get(i).getMember(event.getJDA().getSelfUser()).getTimeJoined(); if (join_time.isBefore(min_join_time)) { min_join_time=join_time; guild_id=g.getId(); @@ -182,16 +181,16 @@ public class Bot extends ListenerAdapter { Main.GUILD_ID=guild_id; } setGlobalChannel(getGuild().getTextChannelsByName(text_channel, true).get(0).getIdLong()); - event.getJDA().getPresence().setGame(net.dv8tion.jda.core.entities.Game.playing("Minetest")); + event.getJDA().getPresence().setActivity(Activity.playing("Minetest")); for (Member m:getGuild().getMemberCache()) { - String name=m.getEffectiveName().replace(" ", "_"); + String name=escapeName(m.getEffectiveName()); String finalname=Utils.getFreeKey(name, members); members.put(finalname, m.getUser().getIdLong()); int color=m.getColor() == null ? DEFAULT_COLOR:m.getColorRaw(); bridge.write("[LIS]"+finalname+" #"+ Utils.getColorString(color)); } for (Role r:getGuild().getRoles()) { - String name=r.getName().replace(" ", "_"); + String name=escapeName(r.getName()); String finalname=Utils.getFreeKey(name, roles); String output="[ROL]"+finalname+" #"+Utils.getColorString(r.getColorRaw()); for (Member m:getGuild().getMembersWithRoles(r)) { @@ -229,7 +228,7 @@ public class Bot extends ListenerAdapter { @Override public void onRoleCreate(RoleCreateEvent event) { - String name=event.getRole().getName().replace(" ", "_"); + String name=escapeName(event.getRole().getName()); String output="[ROL]"+Utils.getFreeKey(name, roles)+" #"+Utils.getColorString(event.getRole().getColorRaw()); bridge.write(output); } @@ -238,7 +237,7 @@ public class Bot extends ListenerAdapter { @Override public void onRoleUpdateName(RoleUpdateNameEvent event) { String oldname=roles.inverse().get(event.getRole().getIdLong()); - String name=Utils.getFreeKey(event.getNewName().replace(" ", "_"), roles); + String name=Utils.getFreeKey(escapeName(event.getNewName()), roles); roles.inverse().remove(event.getRole().getIdLong()); roles.put(name, event.getRole().getIdLong()); String output="[NAM]"+oldname+" "+name; @@ -270,21 +269,18 @@ public class Bot extends ListenerAdapter { } } - public void onGuildMemberNickChange(GuildMemberNickChangeEvent event) { - String newnick=(event.getNewNick() != null ? event.getNewNick():event.getUser().getName()).replace(" ", "_"); + @Override + public void onGuildMemberUpdateNickname(GuildMemberUpdateNicknameEvent event) { + String newnick=escapeName((event.getNewNickname() != null ? event.getNewNickname():event.getUser().getName())); if (members.containsKey(newnick)) { - getGuild().getController().setNickname(event.getMember(), event.getPrevNick()).queue(); - event.getMember().getUser().openPrivateChannel().queue(pc -> pc.sendMessage("Your nickname could not be changed to `"+event.getNewNick()+"` as there already is another guild member with a similar nickname.").queue()); + getGuild().modifyNickname(event.getMember(), event.getOldNickname()).queue(); + event.getMember().getUser().openPrivateChannel().queue(pc -> pc.sendMessage("Your nickname could not be changed to `"+event.getNewNickname()+"` as there already is another guild member with a similar nickname.").queue()); } else { members.inverse().remove(event.getMember().getUser().getIdLong()); members.put(newnick, event.getMember().getUser().getIdLong()); } } - public String getIdentifier(Member m) { - return null; // TODO replace spaces by underscores, check for duplicates, if yes use actual username or even discriminator - } - public static String getName(Member m) { return m.getEffectiveName()+(m.getNickname() == null ? "":" aka "+m.getUser().getName())+" #"+m.getUser().getDiscriminator(); } diff --git a/minetest-chat-bridge-bot/src/main/java/commands/Command.java b/MinetestChatBridgeBot/src/main/java/commands/Command.java similarity index 83% rename from minetest-chat-bridge-bot/src/main/java/commands/Command.java rename to MinetestChatBridgeBot/src/main/java/commands/Command.java index 935fb2f..5718d0f 100644 --- a/minetest-chat-bridge-bot/src/main/java/commands/Command.java +++ b/MinetestChatBridgeBot/src/main/java/commands/Command.java @@ -1,7 +1,7 @@ package commands; import chat.Bot; -import net.dv8tion.jda.core.events.message.MessageReceivedEvent; +import net.dv8tion.jda.api.events.message.MessageReceivedEvent; public abstract class Command { diff --git a/minetest-chat-bridge-bot/src/main/java/commands/StatusCommand.java b/MinetestChatBridgeBot/src/main/java/commands/StatusCommand.java similarity index 93% rename from minetest-chat-bridge-bot/src/main/java/commands/StatusCommand.java rename to MinetestChatBridgeBot/src/main/java/commands/StatusCommand.java index 9e937f9..b9ffac7 100644 --- a/minetest-chat-bridge-bot/src/main/java/commands/StatusCommand.java +++ b/MinetestChatBridgeBot/src/main/java/commands/StatusCommand.java @@ -2,17 +2,20 @@ package commands; import appguru.Main; import chat.Bot; -import net.dv8tion.jda.core.EmbedBuilder; -import net.dv8tion.jda.core.events.message.MessageReceivedEvent; +import net.dv8tion.jda.api.EmbedBuilder; +import net.dv8tion.jda.api.events.message.MessageReceivedEvent; import java.util.Locale; import java.util.concurrent.TimeUnit; public class StatusCommand extends Command { + + @Override public int getMinArgs() { return 0; } + @Override public int getMaxArgs() { return 0; } diff --git a/minetest-chat-bridge-bot/src/main/java/misc/GarbageCollector.java b/MinetestChatBridgeBot/src/main/java/misc/GarbageCollector.java similarity index 100% rename from minetest-chat-bridge-bot/src/main/java/misc/GarbageCollector.java rename to MinetestChatBridgeBot/src/main/java/misc/GarbageCollector.java diff --git a/minetest-chat-bridge-bot/src/main/java/misc/Utils.java b/MinetestChatBridgeBot/src/main/java/misc/Utils.java similarity index 100% rename from minetest-chat-bridge-bot/src/main/java/misc/Utils.java rename to MinetestChatBridgeBot/src/main/java/misc/Utils.java diff --git a/MinetestChatBridgeIRCBot/.classpath b/MinetestChatBridgeIRCBot/.classpath new file mode 100644 index 0000000..9f9cc9f --- /dev/null +++ b/MinetestChatBridgeIRCBot/.classpath @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/MinetestChatBridgeIRCBot/.project b/MinetestChatBridgeIRCBot/.project new file mode 100644 index 0000000..df93e45 --- /dev/null +++ b/MinetestChatBridgeIRCBot/.project @@ -0,0 +1,23 @@ + + + MinetestChatBridgeIRCBot + Project MinetestChatBridgeIRCBot created by Buildship. + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.buildship.core.gradleprojectbuilder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.buildship.core.gradleprojectnature + + diff --git a/MinetestChatBridgeIRCBot/.settings/org.eclipse.buildship.core.prefs b/MinetestChatBridgeIRCBot/.settings/org.eclipse.buildship.core.prefs new file mode 100644 index 0000000..7a23d11 --- /dev/null +++ b/MinetestChatBridgeIRCBot/.settings/org.eclipse.buildship.core.prefs @@ -0,0 +1,13 @@ +arguments= +auto.sync=false +build.scans.enabled=false +connection.gradle.distribution=GRADLE_DISTRIBUTION(VERSION(5.6.1)) +connection.project.dir= +eclipse.preferences.version=1 +gradle.user.home= +java.home= +jvm.arguments= +offline.mode=false +override.workspace.settings=true +show.console.view=true +show.executions.view=true diff --git a/MinetestChatBridgeIRCBot/bin/main/appguru/Main$1.class b/MinetestChatBridgeIRCBot/bin/main/appguru/Main$1.class new file mode 100644 index 0000000..5fb14c4 Binary files /dev/null and b/MinetestChatBridgeIRCBot/bin/main/appguru/Main$1.class differ diff --git a/MinetestChatBridgeIRCBot/bin/main/appguru/Main$2.class b/MinetestChatBridgeIRCBot/bin/main/appguru/Main$2.class new file mode 100644 index 0000000..7c55050 Binary files /dev/null and b/MinetestChatBridgeIRCBot/bin/main/appguru/Main$2.class differ diff --git a/MinetestChatBridgeIRCBot/bin/main/appguru/Main$3.class b/MinetestChatBridgeIRCBot/bin/main/appguru/Main$3.class new file mode 100644 index 0000000..8444d8d Binary files /dev/null and b/MinetestChatBridgeIRCBot/bin/main/appguru/Main$3.class differ diff --git a/MinetestChatBridgeIRCBot/bin/main/appguru/Main$4.class b/MinetestChatBridgeIRCBot/bin/main/appguru/Main$4.class new file mode 100644 index 0000000..34c22ef Binary files /dev/null and b/MinetestChatBridgeIRCBot/bin/main/appguru/Main$4.class differ diff --git a/MinetestChatBridgeIRCBot/bin/main/appguru/Main.class b/MinetestChatBridgeIRCBot/bin/main/appguru/Main.class new file mode 100644 index 0000000..c97d341 Binary files /dev/null and b/MinetestChatBridgeIRCBot/bin/main/appguru/Main.class differ diff --git a/MinetestChatBridgeIRCBot/bin/main/bridge/FileBridge$1.class b/MinetestChatBridgeIRCBot/bin/main/bridge/FileBridge$1.class new file mode 100644 index 0000000..5fe5e20 Binary files /dev/null and b/MinetestChatBridgeIRCBot/bin/main/bridge/FileBridge$1.class differ diff --git a/MinetestChatBridgeIRCBot/bin/main/bridge/FileBridge.class b/MinetestChatBridgeIRCBot/bin/main/bridge/FileBridge.class new file mode 100644 index 0000000..23b2d79 Binary files /dev/null and b/MinetestChatBridgeIRCBot/bin/main/bridge/FileBridge.class differ diff --git a/MinetestChatBridgeIRCBot/bin/main/bridge/IRCFileBridge.class b/MinetestChatBridgeIRCBot/bin/main/bridge/IRCFileBridge.class new file mode 100644 index 0000000..c8843cc Binary files /dev/null and b/MinetestChatBridgeIRCBot/bin/main/bridge/IRCFileBridge.class differ diff --git a/MinetestChatBridgeIRCBot/bin/main/bridge/IRCSocketBridge.class b/MinetestChatBridgeIRCBot/bin/main/bridge/IRCSocketBridge.class new file mode 100644 index 0000000..543be69 Binary files /dev/null and b/MinetestChatBridgeIRCBot/bin/main/bridge/IRCSocketBridge.class differ diff --git a/MinetestChatBridgeIRCBot/bin/main/bridge/ProcessBridge.class b/MinetestChatBridgeIRCBot/bin/main/bridge/ProcessBridge.class new file mode 100644 index 0000000..13c0ce8 Binary files /dev/null and b/MinetestChatBridgeIRCBot/bin/main/bridge/ProcessBridge.class differ diff --git a/MinetestChatBridgeIRCBot/bin/main/bridge/SocketBridge.class b/MinetestChatBridgeIRCBot/bin/main/bridge/SocketBridge.class new file mode 100644 index 0000000..b41e7df Binary files /dev/null and b/MinetestChatBridgeIRCBot/bin/main/bridge/SocketBridge.class differ diff --git a/MinetestChatBridgeIRCBot/bin/main/commands/Command.class b/MinetestChatBridgeIRCBot/bin/main/commands/Command.class new file mode 100644 index 0000000..d3185a0 Binary files /dev/null and b/MinetestChatBridgeIRCBot/bin/main/commands/Command.class differ diff --git a/MinetestChatBridgeIRCBot/bin/main/commands/InfoCommand.class b/MinetestChatBridgeIRCBot/bin/main/commands/InfoCommand.class new file mode 100644 index 0000000..7610255 Binary files /dev/null and b/MinetestChatBridgeIRCBot/bin/main/commands/InfoCommand.class differ diff --git a/MinetestChatBridgeIRCBot/bin/main/handlers/NumericHandler.class b/MinetestChatBridgeIRCBot/bin/main/handlers/NumericHandler.class new file mode 100644 index 0000000..bab48b8 Binary files /dev/null and b/MinetestChatBridgeIRCBot/bin/main/handlers/NumericHandler.class differ diff --git a/MinetestChatBridgeIRCBot/bin/main/handlers/NumericResponseHandler.class b/MinetestChatBridgeIRCBot/bin/main/handlers/NumericResponseHandler.class new file mode 100644 index 0000000..5cc5208 Binary files /dev/null and b/MinetestChatBridgeIRCBot/bin/main/handlers/NumericResponseHandler.class differ diff --git a/MinetestChatBridgeIRCBot/bin/main/handlers/NumericTimeoutResponseHandler.class b/MinetestChatBridgeIRCBot/bin/main/handlers/NumericTimeoutResponseHandler.class new file mode 100644 index 0000000..681fac3 Binary files /dev/null and b/MinetestChatBridgeIRCBot/bin/main/handlers/NumericTimeoutResponseHandler.class differ diff --git a/MinetestChatBridgeIRCBot/bin/main/handlers/ResponseHandler.class b/MinetestChatBridgeIRCBot/bin/main/handlers/ResponseHandler.class new file mode 100644 index 0000000..8c1939f Binary files /dev/null and b/MinetestChatBridgeIRCBot/bin/main/handlers/ResponseHandler.class differ diff --git a/MinetestChatBridgeIRCBot/bin/main/handlers/TimeoutResponseHandler.class b/MinetestChatBridgeIRCBot/bin/main/handlers/TimeoutResponseHandler.class new file mode 100644 index 0000000..5228ffb Binary files /dev/null and b/MinetestChatBridgeIRCBot/bin/main/handlers/TimeoutResponseHandler.class differ diff --git a/MinetestChatBridgeIRCBot/bin/main/handlers/TopicResponseHandler.class b/MinetestChatBridgeIRCBot/bin/main/handlers/TopicResponseHandler.class new file mode 100644 index 0000000..ddccc9e Binary files /dev/null and b/MinetestChatBridgeIRCBot/bin/main/handlers/TopicResponseHandler.class differ diff --git a/MinetestChatBridgeIRCBot/bin/main/handlers/TryAgainHandler.class b/MinetestChatBridgeIRCBot/bin/main/handlers/TryAgainHandler.class new file mode 100644 index 0000000..c499652 Binary files /dev/null and b/MinetestChatBridgeIRCBot/bin/main/handlers/TryAgainHandler.class differ diff --git a/MinetestChatBridgeIRCBot/bin/main/irc/Command.class b/MinetestChatBridgeIRCBot/bin/main/irc/Command.class new file mode 100644 index 0000000..094f07e Binary files /dev/null and b/MinetestChatBridgeIRCBot/bin/main/irc/Command.class differ diff --git a/MinetestChatBridgeIRCBot/bin/main/irc/Default.class b/MinetestChatBridgeIRCBot/bin/main/irc/Default.class new file mode 100644 index 0000000..87c63a4 Binary files /dev/null and b/MinetestChatBridgeIRCBot/bin/main/irc/Default.class differ diff --git a/MinetestChatBridgeIRCBot/bin/main/irc/HandledResponse.class b/MinetestChatBridgeIRCBot/bin/main/irc/HandledResponse.class new file mode 100644 index 0000000..dee62d4 Binary files /dev/null and b/MinetestChatBridgeIRCBot/bin/main/irc/HandledResponse.class differ diff --git a/MinetestChatBridgeIRCBot/bin/main/irc/IRCBot.class b/MinetestChatBridgeIRCBot/bin/main/irc/IRCBot.class new file mode 100644 index 0000000..71fc4fd Binary files /dev/null and b/MinetestChatBridgeIRCBot/bin/main/irc/IRCBot.class differ diff --git a/MinetestChatBridgeIRCBot/bin/main/irc/InvalidMessageException.class b/MinetestChatBridgeIRCBot/bin/main/irc/InvalidMessageException.class new file mode 100644 index 0000000..5c6b7c2 Binary files /dev/null and b/MinetestChatBridgeIRCBot/bin/main/irc/InvalidMessageException.class differ diff --git a/MinetestChatBridgeIRCBot/bin/main/irc/PongCommand.class b/MinetestChatBridgeIRCBot/bin/main/irc/PongCommand.class new file mode 100644 index 0000000..b79ddab Binary files /dev/null and b/MinetestChatBridgeIRCBot/bin/main/irc/PongCommand.class differ diff --git a/MinetestChatBridgeIRCBot/bin/main/misc/GarbageCollector.class b/MinetestChatBridgeIRCBot/bin/main/misc/GarbageCollector.class new file mode 100644 index 0000000..7b6c716 Binary files /dev/null and b/MinetestChatBridgeIRCBot/bin/main/misc/GarbageCollector.class differ diff --git a/MinetestChatBridgeIRCBot/bin/main/numeric/Numeric.class b/MinetestChatBridgeIRCBot/bin/main/numeric/Numeric.class new file mode 100644 index 0000000..628bb3c Binary files /dev/null and b/MinetestChatBridgeIRCBot/bin/main/numeric/Numeric.class differ diff --git a/MinetestChatBridgeIRCBot/bin/main/numeric/NumericLookup.class b/MinetestChatBridgeIRCBot/bin/main/numeric/NumericLookup.class new file mode 100644 index 0000000..2346c2d Binary files /dev/null and b/MinetestChatBridgeIRCBot/bin/main/numeric/NumericLookup.class differ diff --git a/MinetestChatBridgeIRCBot/build.gradle b/MinetestChatBridgeIRCBot/build.gradle new file mode 100644 index 0000000..3957464 --- /dev/null +++ b/MinetestChatBridgeIRCBot/build.gradle @@ -0,0 +1,25 @@ +apply plugin: 'java' +apply plugin: 'jacoco' +apply plugin: 'application' + + +description = 'Minetest Chat Bridge IRC Bot' + +mainClassName = 'appguru.Main' + +repositories { + jcenter() +} + +dependencies { + testCompile 'junit:junit:4.12' +} + +jar { + manifest { + attributes( + 'Created-By': "Gradle ${gradle.gradleVersion}", + 'Main-Class': "appguru.Main" + ) + } +} \ No newline at end of file diff --git a/MinetestChatBridgeIRCBot/gradle.properties b/MinetestChatBridgeIRCBot/gradle.properties new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/MinetestChatBridgeIRCBot/gradle.properties @@ -0,0 +1 @@ + diff --git a/MinetestChatBridgeIRCBot/settings.gradle b/MinetestChatBridgeIRCBot/settings.gradle new file mode 100644 index 0000000..bd9ebeb --- /dev/null +++ b/MinetestChatBridgeIRCBot/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'MinetestChatBridgeIRCBot' diff --git a/minetest-chat-bridge-irc-bot/src/appguru/Main.java b/MinetestChatBridgeIRCBot/src/main/java/appguru/Main.java similarity index 81% rename from minetest-chat-bridge-irc-bot/src/appguru/Main.java rename to MinetestChatBridgeIRCBot/src/main/java/appguru/Main.java index 7ae8d57..5092e69 100644 --- a/minetest-chat-bridge-irc-bot/src/appguru/Main.java +++ b/MinetestChatBridgeIRCBot/src/main/java/appguru/Main.java @@ -1,6 +1,8 @@ package appguru; +import bridge.FileBridge; import bridge.ProcessBridge; +import bridge.SocketBridge; import commands.Command; import commands.InfoCommand; import handlers.NumericTimeoutResponseHandler; @@ -24,11 +26,13 @@ public class Main { public static String IRC_PREFIX="?"; public static String PREFIX="!"; - public static long PING_WAIT=20000; //20s + public static long PING_WAIT=5000; //5s public static PrintStream OUT = System.out; - public static IRCBot chat_bridge; + public static IRCBot CHAT_BRIDGE; + + public static ProcessBridge PROCESS_BRIDGE; public static void main(String[] args) throws IOException { String project_url="https://github.com/appgurueu/adv_chat"; @@ -72,20 +76,23 @@ public class Main { String nickname=args[3]; String channelname=args[4]; - File in=new File(args[5]); - File out=new File(args[6]); - if (!in.isFile() || !out.isFile() || !in.canWrite() || !in.canRead() || !out.canWrite() || !out.canRead()) { - OUT.println("ERR: Input or output files do not exist or can't be read/written."); - System.exit(0); + if (args[5].length() == 0) { + int socket_port=Integer.parseInt(args[6]); + PROCESS_BRIDGE=new SocketBridge("localhost", socket_port); + } else { + File in=new File(args[5]); + File out=new File(args[6]); + if (!in.isFile() || !out.isFile() || !in.canWrite() || !in.canRead() || !out.canWrite() || !out.canRead()) { + OUT.println("ERR: Input or output files do not exist or can't be read/written."); + System.exit(0); + } + PROCESS_BRIDGE=new FileBridge(in, out); } - /* Open Process Bridge */ - ProcessBridge pb=new ProcessBridge(in, out); - /* Create IRC Bot */ - chat_bridge=new IRCBot(port, network, ssl.equals("true")); + CHAT_BRIDGE=new IRCBot(port, network, ssl.equals("true")); - chat_bridge.commands.put("PRIVMSG", (bot, tags, source, params) -> { + CHAT_BRIDGE.commands.put("PRIVMSG", (bot, tags, source, params) -> { if (source == null || params.size() < 2) { return; } @@ -130,13 +137,13 @@ public class Main { mentionstring+=",irc"; } } - pb.write((params.get(0).startsWith("#") ? "[CGM]":"[GMS]")+nick+" "+mentionstring+" "+msg_content); + PROCESS_BRIDGE.write((params.get(0).startsWith("#") ? "[CGM]":"[GMS]")+nick+" "+mentionstring+" "+msg_content); } else { String command="PRIVMSG "+nick+" :No message given. Use '@mentions message'."; try { - chat_bridge.send(command, new TryAgainHandler(command)); + CHAT_BRIDGE.send(command, new TryAgainHandler(command)); } catch (IOException e) { - e.printStackTrace(); + e.printStackTrace(Main.OUT); } } return; @@ -146,12 +153,12 @@ public class Main { if (commandname_and_params[0].length() == 0) { String reply="PRIVMSG "+nick+" :No commandname given !"; try { - chat_bridge.send(reply, new TryAgainHandler(command)); + CHAT_BRIDGE.send(reply, new TryAgainHandler(command)); } catch (IOException e) { - e.printStackTrace(); + e.printStackTrace(Main.OUT); } } else { - pb.write("[CMD]"+nick+" "+String.join(" ", commandname_and_params)+(commandname_and_params.length == 1 ? " ":"")); + PROCESS_BRIDGE.write("[CMD]"+nick+" "+String.join(" ", commandname_and_params)+(commandname_and_params.length == 1 ? " ":"")); } return; } else if (params.get(1).startsWith(Main.IRC_PREFIX)) { @@ -180,13 +187,13 @@ public class Main { return; } if (params.get(0).startsWith("#")) { - pb.write("[MSG]"+nick+" "+params.get(1)); + PROCESS_BRIDGE.write("[MSG]"+nick+" "+params.get(1)); } else { bot.sendTryAgain("PRIVMSG "+nick+" :I can only deliver your message if you use '@mentions'."); } }); - chat_bridge.commands.put("JOIN", (bot, tags, source, params) -> { + CHAT_BRIDGE.commands.put("JOIN", (bot, tags, source, params) -> { if (source == null || params.isEmpty()) { return; } @@ -200,42 +207,42 @@ public class Main { for (byte b=0; b < colorstring.length()-6; b++) { colorstring="0"+colorstring; } - pb.write("[JOI]"+nick+" #"+colorstring+" "+channelname); + PROCESS_BRIDGE.write("[JOI]"+nick+" #"+colorstring+" "+channelname); }); - chat_bridge.commands.put("NICK", (bot, tags, source, params) -> { + CHAT_BRIDGE.commands.put("NICK", (bot, tags, source, params) -> { if (source == null || params.isEmpty()) { return; } int indexOf=source.indexOf('!'); String nick=indexOf >= 0 ? source.substring(0, indexOf):source; - pb.write("[NCK]"+nick+" "+params.get(0)); + PROCESS_BRIDGE.write("[NCK]"+nick+" "+params.get(0)); }); - chat_bridge.commands.put("QUIT", (bot, tags, source, params) -> { + CHAT_BRIDGE.commands.put("QUIT", (bot, tags, source, params) -> { if (source == null || params.isEmpty()) { return; } int indexOf=source.indexOf('!'); String nick=indexOf >= 0 ? source.substring(0, indexOf):source; - pb.write("[EXT]"+nick+" "+(params.size() >= 2 ? params.get(1):"no reason")); + PROCESS_BRIDGE.write("[EXT]"+nick+" "+(params.size() >= 2 ? params.get(1):"no reason")); }); - chat_bridge.commands.put("PART", (bot, tags, source, params) -> { + CHAT_BRIDGE.commands.put("PART", (bot, tags, source, params) -> { if (source == null || params.isEmpty()) { return; } int indexOf=source.indexOf('!'); String nick=indexOf >= 0 ? source.substring(0, indexOf):source; - pb.write("[BYE]"+nick+" "+(params.size() >= 2 ? params.get(1):"no reason")); + PROCESS_BRIDGE.write("[BYE]"+nick+" "+(params.size() >= 2 ? params.get(1):"no reason")); }); // TODO Probably Ident & SASL negotiation ? //chat_bridge.send("CAP LS 302"); - chat_bridge.send("NICK "+nickname); - chat_bridge.send("USER Minetest null null :Minetest Chat Bridge"); // 0 * + CHAT_BRIDGE.send("NICK "+nickname); + CHAT_BRIDGE.send("USER Minetest null null :Minetest Chat Bridge"); // 0 * - chat_bridge.send("JOIN "+channelname, new NumericTimeoutResponseHandler(20000) { + CHAT_BRIDGE.send("JOIN "+channelname, new NumericTimeoutResponseHandler(20000) { @Override public HandledResponse handleNumeric(IRCBot bot, Numeric num, List params) { switch (num) { @@ -256,7 +263,7 @@ public class Main { for (byte b=0; b < colorstring.length()-6; b++) { colorstring="0"+colorstring; } - pb.write("[JOI]"+nick+" #"+colorstring+" "+channelname); + PROCESS_BRIDGE.write("[JOI]"+nick+" #"+colorstring+" "+channelname); } return HandledResponse.BREAK; case RPL_ENDOFNAMES: @@ -282,7 +289,7 @@ public class Main { }; } }; - chat_bridge.chatcommands.put("help", help_command); + CHAT_BRIDGE.chatcommands.put("help", help_command); InfoCommand about_command=new InfoCommand() { @Override @@ -292,7 +299,7 @@ public class Main { }; } }; - chat_bridge.chatcommands.put("about", about_command); + CHAT_BRIDGE.chatcommands.put("about", about_command); InfoCommand status_command=new InfoCommand() { @Override @@ -308,31 +315,31 @@ public class Main { }; } }; - chat_bridge.chatcommands.put("status", status_command); + CHAT_BRIDGE.chatcommands.put("status", status_command); OUT.println("INFO: Starting client"); Main.STARTED_AT=System.currentTimeMillis(); - chat_bridge.listen(); + CHAT_BRIDGE.listen(); OUT.println("INFO: Starting server"); - pb.serve(); + PROCESS_BRIDGE.serve(); OUT.println("INFO: Starting listener"); - pb.listen(line -> { + PROCESS_BRIDGE.listen(line -> { if (line.startsWith("[MSG]")) { try { String command="PRIVMSG #mtchatbridgetest :"+line.substring(5); - chat_bridge.send(command, new TryAgainHandler(command)); + CHAT_BRIDGE.send(command, new TryAgainHandler(command)); } catch (IOException e) { - e.printStackTrace(); + e.printStackTrace(Main.OUT); } } else if (line.startsWith("[PMS]")) { // GMS = PMS with comma separated list of targets String line_content=line.substring(5); String[] parts=line_content.split(" ", 2); String command="PRIVMSG "+parts[0]+" :"+parts[1]; try { - chat_bridge.send(command, new TryAgainHandler(command)); + CHAT_BRIDGE.send(command, new TryAgainHandler(command)); } catch (IOException e) { - e.printStackTrace(); + e.printStackTrace(Main.OUT); } } }); diff --git a/MinetestChatBridgeIRCBot/src/main/java/bridge/FileBridge.java b/MinetestChatBridgeIRCBot/src/main/java/bridge/FileBridge.java new file mode 120000 index 0000000..eb955df --- /dev/null +++ b/MinetestChatBridgeIRCBot/src/main/java/bridge/FileBridge.java @@ -0,0 +1 @@ +/home/lars/.minetest/mods/adv_chat/MinetestChatBridgeBot/src/main/java/bridge/FileBridge.java \ No newline at end of file diff --git a/MinetestChatBridgeIRCBot/src/main/java/bridge/IRCFileBridge.java b/MinetestChatBridgeIRCBot/src/main/java/bridge/IRCFileBridge.java new file mode 100644 index 0000000..98e330f --- /dev/null +++ b/MinetestChatBridgeIRCBot/src/main/java/bridge/IRCFileBridge.java @@ -0,0 +1,25 @@ +package bridge; + +import appguru.Main; +import java.io.File; +import java.io.IOException; + +/** + * + * @author lars + */ +public class IRCFileBridge extends FileBridge { + + public IRCFileBridge(File in, File out) throws IOException { + super(in, out); + } + + @Override + public void kill(String reason) { + super.kill(reason); + + Main.CHAT_BRIDGE.shutdown(reason); + + System.exit(0); + } +} diff --git a/MinetestChatBridgeIRCBot/src/main/java/bridge/IRCSocketBridge.java b/MinetestChatBridgeIRCBot/src/main/java/bridge/IRCSocketBridge.java new file mode 100644 index 0000000..bf4c144 --- /dev/null +++ b/MinetestChatBridgeIRCBot/src/main/java/bridge/IRCSocketBridge.java @@ -0,0 +1,23 @@ +package bridge; + +import appguru.Main; +import java.io.IOException; + +/** + * + * @author lars + */ +public class IRCSocketBridge extends SocketBridge { + public IRCSocketBridge(String host, int port) throws IOException { + super(host, port); + } + + @Override + public void kill(String reason) { + super.kill(reason); + + Main.CHAT_BRIDGE.shutdown(reason); + + System.exit(0); + } +} diff --git a/MinetestChatBridgeIRCBot/src/main/java/bridge/ProcessBridge.java b/MinetestChatBridgeIRCBot/src/main/java/bridge/ProcessBridge.java new file mode 120000 index 0000000..4eee6a0 --- /dev/null +++ b/MinetestChatBridgeIRCBot/src/main/java/bridge/ProcessBridge.java @@ -0,0 +1 @@ +/home/lars/.minetest/mods/adv_chat/MinetestChatBridgeBot/src/main/java/bridge/ProcessBridge.java \ No newline at end of file diff --git a/MinetestChatBridgeIRCBot/src/main/java/bridge/SocketBridge.java b/MinetestChatBridgeIRCBot/src/main/java/bridge/SocketBridge.java new file mode 120000 index 0000000..a463d43 --- /dev/null +++ b/MinetestChatBridgeIRCBot/src/main/java/bridge/SocketBridge.java @@ -0,0 +1 @@ +/home/lars/.minetest/mods/adv_chat/MinetestChatBridgeBot/src/main/java/bridge/SocketBridge.java \ No newline at end of file diff --git a/minetest-chat-bridge-irc-bot/src/commands/Command.java b/MinetestChatBridgeIRCBot/src/main/java/commands/Command.java similarity index 100% rename from minetest-chat-bridge-irc-bot/src/commands/Command.java rename to MinetestChatBridgeIRCBot/src/main/java/commands/Command.java diff --git a/minetest-chat-bridge-irc-bot/src/commands/InfoCommand.java b/MinetestChatBridgeIRCBot/src/main/java/commands/InfoCommand.java similarity index 100% rename from minetest-chat-bridge-irc-bot/src/commands/InfoCommand.java rename to MinetestChatBridgeIRCBot/src/main/java/commands/InfoCommand.java diff --git a/minetest-chat-bridge-irc-bot/src/handlers/NumericHandler.java b/MinetestChatBridgeIRCBot/src/main/java/handlers/NumericHandler.java similarity index 100% rename from minetest-chat-bridge-irc-bot/src/handlers/NumericHandler.java rename to MinetestChatBridgeIRCBot/src/main/java/handlers/NumericHandler.java diff --git a/minetest-chat-bridge-irc-bot/src/handlers/NumericResponseHandler.java b/MinetestChatBridgeIRCBot/src/main/java/handlers/NumericResponseHandler.java similarity index 100% rename from minetest-chat-bridge-irc-bot/src/handlers/NumericResponseHandler.java rename to MinetestChatBridgeIRCBot/src/main/java/handlers/NumericResponseHandler.java diff --git a/minetest-chat-bridge-irc-bot/src/handlers/NumericTimeoutResponseHandler.java b/MinetestChatBridgeIRCBot/src/main/java/handlers/NumericTimeoutResponseHandler.java similarity index 100% rename from minetest-chat-bridge-irc-bot/src/handlers/NumericTimeoutResponseHandler.java rename to MinetestChatBridgeIRCBot/src/main/java/handlers/NumericTimeoutResponseHandler.java diff --git a/minetest-chat-bridge-irc-bot/src/handlers/ResponseHandler.java b/MinetestChatBridgeIRCBot/src/main/java/handlers/ResponseHandler.java similarity index 100% rename from minetest-chat-bridge-irc-bot/src/handlers/ResponseHandler.java rename to MinetestChatBridgeIRCBot/src/main/java/handlers/ResponseHandler.java diff --git a/minetest-chat-bridge-irc-bot/src/handlers/TimeoutResponseHandler.java b/MinetestChatBridgeIRCBot/src/main/java/handlers/TimeoutResponseHandler.java similarity index 100% rename from minetest-chat-bridge-irc-bot/src/handlers/TimeoutResponseHandler.java rename to MinetestChatBridgeIRCBot/src/main/java/handlers/TimeoutResponseHandler.java diff --git a/minetest-chat-bridge-irc-bot/src/handlers/TopicResponseHandler.java b/MinetestChatBridgeIRCBot/src/main/java/handlers/TopicResponseHandler.java similarity index 100% rename from minetest-chat-bridge-irc-bot/src/handlers/TopicResponseHandler.java rename to MinetestChatBridgeIRCBot/src/main/java/handlers/TopicResponseHandler.java diff --git a/minetest-chat-bridge-irc-bot/src/handlers/TryAgainHandler.java b/MinetestChatBridgeIRCBot/src/main/java/handlers/TryAgainHandler.java similarity index 100% rename from minetest-chat-bridge-irc-bot/src/handlers/TryAgainHandler.java rename to MinetestChatBridgeIRCBot/src/main/java/handlers/TryAgainHandler.java diff --git a/minetest-chat-bridge-irc-bot/src/irc/Command.java b/MinetestChatBridgeIRCBot/src/main/java/irc/Command.java similarity index 100% rename from minetest-chat-bridge-irc-bot/src/irc/Command.java rename to MinetestChatBridgeIRCBot/src/main/java/irc/Command.java diff --git a/minetest-chat-bridge-irc-bot/src/irc/Default.java b/MinetestChatBridgeIRCBot/src/main/java/irc/Default.java similarity index 100% rename from minetest-chat-bridge-irc-bot/src/irc/Default.java rename to MinetestChatBridgeIRCBot/src/main/java/irc/Default.java diff --git a/minetest-chat-bridge-irc-bot/src/irc/HandledResponse.java b/MinetestChatBridgeIRCBot/src/main/java/irc/HandledResponse.java similarity index 100% rename from minetest-chat-bridge-irc-bot/src/irc/HandledResponse.java rename to MinetestChatBridgeIRCBot/src/main/java/irc/HandledResponse.java diff --git a/minetest-chat-bridge-irc-bot/src/irc/IRCBot.java b/MinetestChatBridgeIRCBot/src/main/java/irc/IRCBot.java similarity index 79% rename from minetest-chat-bridge-irc-bot/src/irc/IRCBot.java rename to MinetestChatBridgeIRCBot/src/main/java/irc/IRCBot.java index 4df0825..0e7073b 100644 --- a/minetest-chat-bridge-irc-bot/src/irc/IRCBot.java +++ b/MinetestChatBridgeIRCBot/src/main/java/irc/IRCBot.java @@ -10,6 +10,8 @@ import java.net.Socket; import java.util.*; import static irc.HandledResponse.*; +import java.io.BufferedReader; +import java.io.InputStreamReader; public class IRCBot { public Map chatcommands; @@ -39,7 +41,7 @@ public class IRCBot { try { socket.close(); } catch (IOException e) { - e.printStackTrace(); + e.printStackTrace(Main.OUT); } } })); @@ -158,6 +160,16 @@ public class IRCBot { } public void listen() { Thread listenerThread = new Thread(() -> { + BufferedReader reader=null; + try { + reader=new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8")); + } catch (IOException e) { + e.printStackTrace(Main.OUT); + if (socket.isClosed() || socket.isInputShutdown() || socket.isOutputShutdown()) { + Main.PROCESS_BRIDGE.kill("Socket connection lost"); + } + System.exit(1); + } while (true) { try { Thread.sleep(20); @@ -165,34 +177,29 @@ public class IRCBot { return; } try { - String message = ""; - int c = socket.getInputStream().read(); - while (c >= 0) { - if (c == '\n' && message.charAt(message.length() - 1) == '\r') { - // message completed ! - message = message.substring(0, message.length() - 1); - try { - processMessage(message); - } catch (InvalidMessageException e) { - e.printStackTrace(); - } - message = ""; - } else { - message += (char) c; + String message; + while ((message = reader.readLine()) != null) { + try { + processMessage(message); + } catch (InvalidMessageException e) { + e.printStackTrace(Main.OUT); } - c = socket.getInputStream().read(); } } catch (IOException e) { - e.printStackTrace(); + e.printStackTrace(Main.OUT); + if (socket.isClosed() || socket.isInputShutdown() || socket.isOutputShutdown()) { + Main.PROCESS_BRIDGE.kill("Socket connection lost"); + } } } }); listenerThread.start(); } public void send(String message) throws IOException { - for (int i = 0; i < message.length(); i++) { - socket.getOutputStream().write(message.charAt(i)); + if (socket.isClosed() || socket.isOutputShutdown()) { + Main.PROCESS_BRIDGE.kill("Socket connection lost"); } + socket.getOutputStream().write(message.getBytes("UTF-8")); socket.getOutputStream().write('\r'); socket.getOutputStream().write('\n'); } @@ -204,7 +211,21 @@ public class IRCBot { try { send(command, new TryAgainHandler(command)); } catch (IOException e) { - e.printStackTrace(); + e.printStackTrace(Main.OUT); + } + } + public void shutdown(String reason) { + try { + this.send("QUIT :" + reason); + } catch (IOException e) { + e.printStackTrace(Main.OUT); + } + if (!this.socket.isClosed()) { + try { + this.socket.close(); + } catch (IOException e) { + e.printStackTrace(Main.OUT); + } } } } diff --git a/minetest-chat-bridge-irc-bot/src/irc/InvalidMessageException.java b/MinetestChatBridgeIRCBot/src/main/java/irc/InvalidMessageException.java similarity index 100% rename from minetest-chat-bridge-irc-bot/src/irc/InvalidMessageException.java rename to MinetestChatBridgeIRCBot/src/main/java/irc/InvalidMessageException.java diff --git a/minetest-chat-bridge-irc-bot/src/irc/PongCommand.java b/MinetestChatBridgeIRCBot/src/main/java/irc/PongCommand.java similarity index 100% rename from minetest-chat-bridge-irc-bot/src/irc/PongCommand.java rename to MinetestChatBridgeIRCBot/src/main/java/irc/PongCommand.java diff --git a/minetest-chat-bridge-irc-bot/src/misc/GarbageCollector.java b/MinetestChatBridgeIRCBot/src/main/java/misc/GarbageCollector.java similarity index 100% rename from minetest-chat-bridge-irc-bot/src/misc/GarbageCollector.java rename to MinetestChatBridgeIRCBot/src/main/java/misc/GarbageCollector.java diff --git a/minetest-chat-bridge-irc-bot/src/numeric/Numeric.java b/MinetestChatBridgeIRCBot/src/main/java/numeric/Numeric.java similarity index 100% rename from minetest-chat-bridge-irc-bot/src/numeric/Numeric.java rename to MinetestChatBridgeIRCBot/src/main/java/numeric/Numeric.java diff --git a/minetest-chat-bridge-irc-bot/src/numeric/NumericLookup.java b/MinetestChatBridgeIRCBot/src/main/java/numeric/NumericLookup.java similarity index 100% rename from minetest-chat-bridge-irc-bot/src/numeric/NumericLookup.java rename to MinetestChatBridgeIRCBot/src/main/java/numeric/NumericLookup.java diff --git a/Readme.md b/Readme.md index 1859871..5dd1db0 100644 --- a/Readme.md +++ b/Readme.md @@ -1,9 +1,9 @@ -# Advanced Chat(`adv_chat`) +# Advanced Chat (`adv_chat`) + > One Mod to rule them all, One Mod to find them, > One Mod to bring them all, and in the darkness bind them \- adapted quote from "Lord of the Rings" -With "all" other chat mods are meant. Adds roles, colors, unicode, hud notifications, and chat bridges (IRC & discord). @@ -15,26 +15,56 @@ Depends on [`modlib`](https://github.com/appgurueu/modlib). Modlib has been upda Code licensed under the GPLv3 (GNU Public License Version 3). Written by Lars Mueller alias LMD or appguru(eu). -## Terminology +## Links -Chatter : Participant in chat, be it a Minetest player, IRC user, or Discord member -Role : "Group" of chatters -Targets/Mentions : Roles or chatters mentioned using `@` +* [GitHub](https://github.com/appgurueu/voxelizer) - sources, issue tracking, contributing +* [Discord](https://discord.gg/ysP74by) - discussion, chatting +* [Minetest Forum](https://forum.minetest.net/viewtopic.php?f=9&t=22845) - (more organized) discussion +* [ContentDB](https://content.minetest.net/packages/LMD/voxelizer/) - releases (downloading from GitHub is recommended) + +## Setup + +In order to properly use `adv_chat`, you'll have to meet the following prerequisites: + +* `modlib` Minetest mod installed and enabled as hard dependency and additionally also `cmdlib` (recommended) +* `adv_chat` needs to be installed, enabled and added to the trusted mods in settings/`minetest.conf` +* [LuaSocket](https://luarocks.org/modules/luasocket/luasocket) should be installed (`sudo luarocks install luasocket` on Ubuntu) +* Complete [Java](https://www.java.com/de/) 8 or ideally newer installation under your system path (accessible from terminal via `java`) + +Then just install it like any other mod and enjoy your greatly improved chat experience! + +## Terminology + +Chatter: Participant in chat, be it a Minetest player, IRC user, or Discord member +Role: "Group" of chatters +Targets/Mentions: Roles or chatters mentioned using `@` ## Features -* Discord & IRC chat bridges +* Discord & IRC chat bridges, login & commands +* Blocking * Colorization +* Style preservation * Unicode * Mentions * HUD channels/notifications * Scheduled messages for offline players +## Changes + +### 🎃 Halloween Update + +* Proper formatting support +* More configuration options +* Remote login for chatcommand execution +* Many under-the-hood changes cleaning up stuff & fixing bugs (improving the code & architecture) +* See `config_help.md` and the sources for all details + ## API ### HUD notifications -See the `hud_channels.lua` for how it works and `test.lua` for a score change demo running with random values. +See `hud_channels.lua` for how it works and `test.lua` for a score change demo running with random values. ### Votes @@ -58,14 +88,14 @@ See the code and `config_help.md`. Feel free to contact me. ### Unicode support -This mod adds unicode support. Simply use the unicode codepoint in hexadecimal format prefixed by `U+`. To get a "slight smile" (🙂), you'd use `U+1F643`. Note that not all fonts fully support Unicode. +This mod adds unicode support. Simply use the unicode codepoint in hexadecimal format prefixed by `U+`. To get a "slight smile" (🙂), you'd use `U+1F642`. Note that not all fonts fully support Unicode. Use the `/chat say` command to open a text entry field to paste text. ### Real-time chat Use `@` at the beginning to message players or roles before your message. There are 3 special mentions : `minetest`, `irc` and `discord`. -Can be separated by comma **&** whitespace. Examples : +Can be separated by comma **&** whitespace. Examples: * `@singleplayer hi, singleplayer !` - message `hi, singleplayer !` is sent to singleplayer * `lol(or whitespaces) @singleplayer hi` - message is just sent in global chat @@ -103,7 +133,7 @@ Summarized, the Discord Chat Bridge works quite similar to the IRC one, with som Making Minetest & IRC chat compatible with Discord required the introduction of restrictions to simplify and reduce confusion. * No double nicknames on Discord. If there are double nicknames, one of them gets an appendix, which is not guaranteed to be the same each time. So better make sure this doesn't happen. -* Spaces (` `) in Discord nicknames are replaced by underscores (`_`) +* Spaces (` `) and commata (`,`) in Discord nicknames are replaced by underscores (`_`) ### Internal process bridge protocol diff --git a/chatcommands.lua b/chatcommands.lua new file mode 100644 index 0000000..07b4cc8 --- /dev/null +++ b/chatcommands.lua @@ -0,0 +1,73 @@ +minetest.original_get_player_privs = minetest.get_player_privs +function minetest.get_player_privs(playername) + if chatters[playername] then + return {chatter=true} + end + return minetest.original_get_player_privs(playername) +end + +if cmd_ext then + function call_chatcommand(chatter, call) + local last_space, next_space = 1, call:find(" ") + local command_trie, command_name = cmd_ext.chatcommands + local cmd, suggestion + local total_command_name = {} + repeat + next_space = next_space or call:len()+1 + command_name = call:sub(last_space, next_space-1) + table.insert(total_command_name, command_name) + local concat = table.concat(total_command_name, " ") + if bridges.command_blacklist[total_command_name] then + return false, "Command only available from Minetest." + end + total_command_name = {concat} + if command_name == "" and cmd and not cmd.params then break end + cmd, suggestion, _ = trie.search(command_trie, command_name) + if not cmd then + return false, "No such chatcommand."..((suggestion and " Did you mean \""..call:sub(0, last_space-1)..suggestion.."\" ?") or "") + elseif cmd.subcommands and not cmd.implicit_call then + command_trie = cmd.subcommands + last_space, next_space = next_space + 1, call:find(" ", next_space+1) + else + last_space = next_space + 1 + break + end + until next_space == call:len() + local params = call:sub(last_space) + if cmd.privs and cmd.privs.chatter then + return cmd.func(chatter, params) + end + return cmd.func((chatters[chatter] and chatters[chatter].login) or chatter, params) + end +else + function call_chatcommand(chatter, call) + local name, params = unpack(string_ext.split(call, " ", 2)) + if bridges.command_blacklist[name] then + return false, "Command only available from Minetest." + end + local command = minetest.registered_chatcommands[name] + if not command then + return false, "No such chatcommand." + end + local privs = minetest.get_player_privs(chatter) + local to_lose, to_gain = {}, {} + for priv, val in pairs(command.privs) do + if val ~= privs[val] then + table.insert((val and to_gain) or to_lose, priv) + end + end + if #to_lose ~= 0 or #to_gain ~= 0 then + if #to_lose == 0 then + return false, "Missing privileges : "..table.concat(to_gain, ", ") + end + if #to_gain == 0 then + return false, "Privileges to be lost : "..table.concat(to_lose, ", ") + end + return false, "Missing privileges : "..table.concat(to_gain, ", ")..", privileges to be lost : "..table.concat(to_lose, ", ") + end + if cmd.privs.chatter then + return cmd.func(chatter, params) + end + return command.func((chatters[chatter] and chatters[chatter].login) or chatter, params) + end +end \ No newline at end of file diff --git a/closest_color.lua b/closest_color.lua new file mode 120000 index 0000000..ebf0867 --- /dev/null +++ b/closest_color.lua @@ -0,0 +1 @@ +/home/lars/.minetest/mods/voxelizer/closest_color.lua \ No newline at end of file diff --git a/colorize_message.lua b/colorize_message.lua index aa5bdad..266279c 100644 --- a/colorize_message.lua +++ b/colorize_message.lua @@ -1,35 +1,39 @@ +-- Converts "#XXXXXX" color codes to colors function colorize_message(message) local rope={} - --IFNDEF bridge + --IFNDEF discord local otherrope={} --ENDIF - local last_index=1 - for i=1, string.len(message) do - local c=string.byte(message:sub(i,i)) - if c == hashtag then + local i=1 + while i <= message:len() do + local c=message:sub(i,i) + if c == string.char(0x1b) and message:sub(i+1, i+4) == "(c@#" and message:sub(i+11, i+11) == ")" then + table.insert(rope, message:sub(i, i+11)) + i=i+11 + goto continue + elseif c == "#" then for j=i+1, i+6 do - local c2=string.byte(string.upper(message:sub(j,j))) - if c2:len() == 0 or not string_ext.is_hexadecimal(c2) then - i=j + local c2=message:sub(j,j):upper() + if c2=="" or not ((c2 >= "0" and c2 <= "9") or (c2 >= "A" and c2 <= "F")) then goto nocolor end end - local colorstring=minetest.get_color_escape_sequence(string.sub(message, i, i+6)) - table.insert(rope, message:sub(last_index, i-1)) - --IFNDEF bridge - table.insert(otherrope, message:sub(last_index, i-1)) - --ENDIF - table.insert(rope, colorstring) - last_index=i+7 - ::nocolor:: + table.insert(rope, minetest.get_color_escape_sequence(message:sub(i, i+6))) + i=i+6 + goto continue end + ::nocolor:: + table.insert(rope, c) + --IFNDEF discord + table.insert(otherrope, c) + --ENDIF + ::continue:: + i=i+1 end - table.insert(rope, message:sub(last_index)) - --IFNDEF bridge - table.insert(otherrope, message:sub(last_index)) + return table.concat(rope) + --IFNDEF discord + , table.concat(otherrope) --ENDIF - return table.concat(rope, "") - --IFNDEF bridge - , table.concat(otherrope, "") - --ENDIF -end \ No newline at end of file +end + +load_schemes() \ No newline at end of file diff --git a/conf.lua b/conf.lua index 67933ce..f72a4b9 100644 --- a/conf.lua +++ b/conf.lua @@ -1,25 +1,36 @@ -local schemedef={mention_prefix={type="string"}, -mention_delim={type="string"}, -delim={type="string"}} +local schemedef={ + message_prefix={type="string"}, + mention_prefix={type="string"}, + mention_delim={type="string"}, + content_prefix={type="string"}, + message_suffix={type="string"}, +} local conf_spec={type="table", children={ - scheme={type="table", required_children={ + schemes={type="table", required_children={ minetest=schemedef }, possible_children={ - other=schemedef + irc=schemedef, + discord=schemedef }}, bridges={ type="table", possible_children={ - irc={type="table", children={ - port={type="number", range={0, 65535}}, - network={type="string"}, - nickname={type="string"}, - channelname={type="string"}, - ssl={type="boolean"}, - prefix={type="string"}, - minetest_prefix={type="string"} - }}, + irc={type="table", required_children={ + port={type="number", range={0, 65535}}, + network={type="string"}, + nickname={type="string"}, + channelname={type="string"}, + ssl={type="boolean"}, + prefix={type="string"}, + minetest_prefix={type="string"} + }, + possible_children={ + bridge={type="string", possible_values={"files", "sockets"}}, + convert_minetest_colors={type="string", possible_values={"disabled", "hex", "safer", "safest"}}, + handle_discord_markdown={type="boolean"}, + handle_minetest_markdown={type="boolean"} + }}, discord={type="table", required_children={ token={type="string"}, channelname={type="string"}, @@ -29,9 +40,15 @@ local conf_spec={type="table", children={ possible_children={ blacklist={type="table", keys={type="string"}}, whitelist={type="table", keys={type="string"}}, - guild_id={type="string"} + guild_id={type="string"}, + bridge={type="string", possible_values={"files", "sockets"}}, + convert_internal_markdown={type="boolean"}, + convert_minetest_markdown={type="boolean"}, + handle_irc_styles={type="string", possible_values={"escape_markdown", "convert", "disabled"}} } - } + }, + command_blacklist={type="table", keys={type="number"}, values={type="string"}}, + command_whitelist={type="table", keys={type="number"}, values={type="string"}} } } }} @@ -39,6 +56,30 @@ local conf_spec={type="table", children={ local config=conf.import("adv_chat", conf_spec) table_ext.add_all(getfenv(1), config) +function load_schemes() + for k, v in pairs(schemes.minetest) do + schemes.minetest[k] = colorize_message(v) + end + + for _,s in pairs({"irc", "discord"}) do + if not schemes[s] then + schemes[s] = {} + for k, v in pairs(schemes.minetest) do + schemes[s][k] = minetest.strip_colors(v) + end + end + end + + load_schemes = nil +end + +if not bridges.irc.style_conversion then + bridges.irc.style_conversion={} + if not bridges.irc.style_conversion.colors then + bridges.irc.style_conversion.colors="disabled" + end +end + if bridges.discord then local blacklist_empty=table_ext.is_empty(bridges.discord.blacklist or {}) @@ -58,4 +99,23 @@ if bridges.discord then end end +end + +if bridges.discord or bridges.irc then + + bridges.command_blacklist = table_ext.set(bridges.command_blacklist or {}) + bridges.command_whitelist = table_ext.set(bridges.command_whitelist or {}) + local blacklist_empty=table_ext.is_empty(bridges.command_blacklist) + local whitelist_empty=table_ext.is_empty(bridges.command_whitelist or {}) + if blacklist_empty then + if not whitelist_empty then + bridges.command_blacklist=setmetatable(bridges.command_blacklist, {__index=function(value) + if bridges.command_whitelist[value] then + return nil + end + return true + end}) + end + end + end \ No newline at end of file diff --git a/config_help.md b/config_help.md index 911a4f4..ad14386 100644 --- a/config_help.md +++ b/config_help.md @@ -11,14 +11,15 @@ Explaining document(this, Markdown) : `/adv_chat/config_help.m Readme : `/adv_chat/Readme.md` ## Default Configuration + Located under `/adv_chat/default_config.json` + ```json { - "scheme" : { - "minetest" : {"mention_prefix":"#FFFF00@", "mention_delim":"#FFFF00, ", "delim":"#FFFF00 : "}, + "schemes" : { + "minetest" : {"message_prefix": "", "mention_prefix": "#FFFF00@", "mention_delim": "#FFFF00, ", "content_prefix": "#FFFF00: #FFFFFF"}, "other" : null }, - "bridges" : { "discord" : null, "irc" : null @@ -27,13 +28,13 @@ Located under `/adv_chat/default_config.json` ``` ## Example Configuration + ```json { - "scheme" : { - "minetest" : {"mention_prefix":"#FFFF00@", "mention_delim":"#FFFF00, ", "delim":"#FFFF00 : "}, + "schemes" : { + "minetest" : {"message_prefix": "Somebody - namely ", "mention_prefix": "#FFFF00 - wrote to ", "mention_delim": "#FFFF00 and ", "content_prefix": "#FFFF00: #FFFFFF", "message_suffix": " :D"}, "other" : null }, - "bridges" : { "discord" : {"channelname":"allgemein", "prefix": "?", "minetest_prefix": "!","token":"S.U.Pxxs.E.R.T.9998OKEN", "blacklist":{"~~new_role~~":true}, "guild_id": 580416319703351296}, "irc" : {"channelname":"#mtchatbridgetest", "prefix": "?", "minetest_prefix": "!", "nickname": "MT_Chat_Bridge", "network": "irc.freenode.net", "port": 7000, "ssl": true} @@ -44,28 +45,40 @@ Located under `/adv_chat/default_config.json` ## Usage -### `scheme` +### `schemes` -Specifies the chat message format, `minetest` is for the one used on the Minetest chat, and `other` is for Discord/IRC. +Specifies the chat message format, `minetest` is for the one used on the Minetest chat, `irc` is IRC, and `discord` for Discord. + +* `message_prefix` - Prefix for the message * `mention_prefix` - Prefix for mentionpart. * `mention_delim` - Mention delimiter. -* `delim` - Message/sendername delimiter. -If you want to use color escape sequences, type `\x1B(c@#66FF00)`, and replace `#66FF00` with your color of choice in hex format. +* `content_prefix` - Message/sendername delimiter. +* `message_suffix` - Suffix for the message -Messages are formatted as `sendername + mention_prefix + {mentions, mention_delim} + delim + message` +If you want to use color escape sequences, type something like `#66FF00 colorized text here`, and replace `#66FF00` with your color of choice in hex format. + +Messages are formatted as `message_prefix + sendername + mention_prefix + {mentions, mention_delim} + delim + message + message_suffix` ### `bridges` + Configuration for IRC/Discord chat bridges. If `irc` or `discord` are set to `false` or `null`, the corresponding chat bridges aren't created. #### `discord` -Table with the following entries : -* `token` : Discord bot token, required -* `channelname` : Name of bridge channel, required as well -* `prefix`, `minetest_prefix` : Prefixes for Discord/Minetest commands, required -* `role_blacklist`/`role_whitelist` : Blacklist/whitelist of Discord roles. If both or none are set, Discord roles are ignored. -* `guild_id` : Guild ID, string. If swines add your bot to other servers, force it to use the server with the specified Guild ID. Optional. If unset, bot will use the guild it joined first. -Example : +Table with the following entries : + +* `token`: Discord bot token, required +* `channelname`: Name of bridge channel, required as well +* `prefix`, `minetest_prefix`: Prefixes for Discord/Minetest commands, required +* `role_blacklist`/`role_whitelist`: Blacklist/whitelist of Discord roles. If both or none are set, Discord roles are ignored. +* `guild_id`: Guild ID, string. If swines add your bot to other servers, force it to use the server with the specified Guild ID. Optional. If unset, bot will use the guild it joined first. +* `bridge`: Optional. Forces type of process bridge to use. Choices are `"file"` and `"socket"`. Sockets are recommended but require `luasocket`. +* `convert_internal_markdown`/`convert_minetest_markdown`: Optional boolean. Whether Markdown sent from Minetest/internal chat messages should be left untouched as if it was Discord Markdown +* `handle_irc_styles`: Optional string. How IRC styles should be converted to Discord Markdown. Possible values: `"disabled"`, `"escape_markdown"` and `"convert"` +* `strip_discord_markdown_in_minetest`: Optional boolean. Whether Discord Markdown should be stripped from Minetest chat. + +Example : + ```json { "discord": { @@ -80,22 +93,33 @@ Example : ``` #### `irc` -Table with fields (all required) : -* `network` : IRC network, for example `irc.freenode.net` -* `port` : Port, on [Freenode](https://freenode.net/kb/answer/chat) it would be `7000` if SSL is used, or else `6667`. Just google "connecting to network" for your IRC network of choice to get detailed information. -* `ssl` : Whether to use encryption (SSL) to communicate with the IRC network. Setting this to `true` is recommended. -* `nickname` : Bot nickname -* `channelname` : IRC channel name, for example `#minetest-server` -* `prefix`, `minetest_prefix` : Prefixes for IRC/Minetest commands, required -Example : +Table with fields. Required are: + +* `network`: IRC network, for example `irc.freenode.net` +* `port`: Port, on [Freenode](https://freenode.net/kb/answer/chat) it would be `7000` if SSL is used, or else `6667`. Just google "connecting to network" for your IRC network of choice to get detailed information. +* `ssl`: Whether to use encryption (SSL) to communicate with the IRC network. Setting this to `true` is recommended. +* `nickname`: Bot nickname +* `channelname`: IRC channel name, for example `#minetest-server` +* `prefix`, `minetest_prefix`: Prefixes for IRC bot/Minetest chatcommands, required + +Optional fields are: + +* `bridge`: Type of process bridge to use can be forced here. Choices are `"file"` and `"socket"`. Sockets are recommended but require `luasocket`. +* `convert_minetest_colors`: How colors from Minetest chat messages should be converted to IRC. Possible values are `"disabled"`, `"safest"`, `"safe"` and `"hex_safe"` and `"hex"` +* `handle_internal_markdown`: How Markdown sent from internal MT should be converted to IRC text styles. Possible values are `"disabled"`, `"strip"` and `"convert"` +* `handle_minetest_markdown`: How Markdown sent from Minetest should be converted to IRC text styles. Possible values are `"disabled"`, `"strip"` and `"convert"` +* `handle_discord_markdown`: How Markdown sent from Discord should be converted to IRC text styles. Possible values are `"disabled"`, `"strip"` and `"convert"` + +Example: + ```json { "irc": { "prefix": "?", "minetest_prefix": "!", - "channelname": "#minetest-server", - "nickname": "SERVERNAME_Chat", + "channelname": "#minetest-server", + "nickname": "SERVERNAME_Chat", "port": 7000, "ssl": true, "network": "irc.freenode.net" @@ -103,21 +127,29 @@ Example : } ``` +#### `chatcommand_whitelist`/`chatcommand_blacklist` + +Whitelist/blacklist of chatcommands which are not available from Discord or IRC. If both or none are set, all chatcommands are blacklisted. + ## Recommendations ### Consistency + It is recommended to **keep consistency**. To do so, channel & chat bot names could be similar across Discord and IRC. The same goes for prefixes. ### Prefixes + You should try to keep prefixes similar and memorable, while ensuring that there are no collisions. I recommend the combination of `?` for Discord/IRC commands and `!` for Minetest commands. Other neat combinations I have thought of are `+` and `-`, or `;` and `:`. Keep in mind that prefixes should be easy to type as well, and that others might have a different keyboard layout. ### Discord Avatar + Pixel-art Minetest skin heads always work well as avatars. For an example look you could look at my [Robby-Head](https://github.com/appgurueu/artwork/blob/master/robbyhead.png). There are tons of skins out there and it's fairly easy to extract the faces (but make sure you don't violate the licenses when using the images). A good starting point is [Addis Open MT-Skin Database](http://minetest.fensta.bplaced.net/). You can, however, of course also design it yourself. Just grab your favorite pixel-art program and draw a 8x8 head. You should also make sure to scale the small image up (to at least 256x256), because else Discord scales it up "for you" which makes it lose it's sharp edges. ### Security -Only two basic hints : Always enable SSL, and don't give your bot token to anyone. -And of course make sure your server isn't hacked. Messages are sent as plain text over the file bridges. \ No newline at end of file + +Only two basic hints : Always enable SSL, and don't give your bot token to anyone. +And of course make sure your server isn't hacked. Messages are sent as plain text over the sockets or file bridges. diff --git a/default_config.json b/default_config.json index 0d98eca..0837519 100644 --- a/default_config.json +++ b/default_config.json @@ -1,6 +1,6 @@ { - "scheme" : { - "minetest" : {"mention_prefix":"#FFFF00@", "mention_delim":"#FFFF00, ", "delim":"#FFFF00 : "}, + "schemes" : { + "minetest" : {"message_prefix": "", "mention_prefix": "#FFFF00@", "mention_delim": "#FFFF00, ", "content_prefix": "#FFFF00: #FFFFFF"}, "other" : null }, diff --git a/discord.lua b/discord.lua index 5353f14..d876ba9 100644 --- a/discord.lua +++ b/discord.lua @@ -17,20 +17,29 @@ function delete_discord_role(linecontent) end end -file_ext.process_bridge_build("discord") +local bridge +if bridges.discord.bridge == "files" then + bridge = build_file_bridge("discord") +else + bridge = build_bridge("discord") +end -file_ext.process_bridge_listen("discord", function(line) +discord_bridge = bridge + +bridge.listen(function(line) local linecontent=line:sub(6) if string_ext.starts_with(line, "[MSG]") then local parts=string_ext.split(linecontent, " ", 2) local src=parts[1].."[discord]" - send_to_all(src, src..scheme.other.delim..parts[2], minetest.get_color_escape_sequence(get_color(src))..src..scheme.minetest.delim..parts[2], "discord") + local adv_msg=message.new(chatters[src], nil, parts[2]) + adv_msg.sent_to="discord" + send_to_all(adv_msg) elseif string_ext.starts_with(line, "[GMS]") or string_ext.starts_with(line, "[CGM]") then -- GMS = group message or CGM = channel group message local parts=string_ext.split(linecontent, " ",3) local source=parts[1] local targets=string_ext.split_without_limit(parts[2], ",") local msg=parts[3] - local sent_to="nobody" + local sent_to if string_ext.starts_with(line, "[CGM]") then sent_to="discord" end @@ -38,39 +47,29 @@ file_ext.process_bridge_listen("discord", function(line) for _, target in ipairs(targets) do targetset[target]=true end - local invalid_targets, msg, mt_msg=build_message(source.."[discord]", targets, msg) - send_to_targets(source.."[discord]", table_ext.set(targets), msg, mt_msg, sent_to) - if (#invalid_targets) == 1 then - file_ext.process_bridge_write("discord", "[PMS]"..source.." The target "..invalid_targets[1].." is inexistant.") - elseif (#invalid_targets) > 1 then - file_ext.process_bridge_write("discord", "[PMS]"..source.." The targets "..table.concat(invalid_targets, ", ").." are inexistant.") + local adv_msg=message.new(chatters[source.."[discord]"], targets, msg) + adv_msg.sent_to=sent_to + message.mentionpart(adv_msg) --force check mentions + send_to_targets(adv_msg) + if (#adv_msg.invalid_mentions) == 1 then + discord_bridge.write("[PMS]#FFFFFF "..source.." The target "..adv_msg.invalid_mentions[1].." is inexistant.") + elseif (#adv_msg.invalid_mentions) > 1 then + discord_bridge.write("[PMS]#FFFFFF "..source.." The targets "..table.concat(adv_msg.invalid_mentions, ", ").." are inexistant.") end elseif string_ext.starts_with(line, "[CMD]") then - local parts=string_ext.split(linecontent, " ", 3) + local parts=string_ext.split(linecontent, " ", 2) local source=parts[1] - local commandname=parts[2] - local params=parts[3] - local command=minetest.registered_chatcommands[commandname] - if command then - if not table_ext.is_empty(command.privs) then - file_ext.process_bridge_write("discord", "[ERR]"..source.." Command requires privs.") - else - local success, retval = command.func(source.."[discord]", params or "") - if success then - file_ext.process_bridge_write("discord", "[SUC]"..source.." "..(retval or "No return value.")) - else - file_ext.process_bridge_write("discord", "[ERR]"..source.." "..(retval or "No return value.")) - end - end - else - file_ext.process_bridge_write("discord", "[ERR]"..source.."`"+commandname+"` : No such command.") - end + local call=parts[2] + local success, retval = call_chatcommand(source.."[discord]", call) + local prefix = "[PMS]#FFFFFF " + if success then prefix = "[SUC]" elseif success == false then prefix = "[ERR]" end + discord_bridge.write(prefix..source.." "..(retval or "No return value.")) elseif string_ext.starts_with(line, "[JOI]") or string_ext.starts_with(line, "[LIS]") then local parts=string_ext.split(linecontent, " ", 2) --nick & roles local chatter=parts[1].."[discord]" join(chatter, {color=parts[2], roles={}, discord=true}) if string_ext.starts_with(line, "[JOI]") then - send_to_all("", get_color(chatter)..chatter..minetest.get_color_escape_sequence("#FFFFFF").." joined.") + minetest.chat_send_all(get_color(chatter)..chatter..minetest.get_color_escape_sequence("#FFFFFF").." joined.") end elseif string_ext.starts_with(line, "[EXT]") then chatters[linecontent.."[discord]"]=nil @@ -134,24 +133,24 @@ end) -- Pinging mt_ext.register_globalstep(1, function() - file_ext.process_bridge_write("discord", "[PIN]") + bridge.write("[PIN]") end) -- Killing on_shutdown minetest.register_on_shutdown(function() - file_ext.process_bridge_write("discord", "[KIL]") + bridge.write("[KIL]") end) -file_ext.process_bridge_serve("discord") +bridge.serve() -- Start AFTER mods are loaded, so that the player sees chat messages minetest.register_on_mods_loaded(function() local java="java" - local classpath=minetest.get_modpath("adv_chat").."/minetest-chat-bridge-bot/out/production/classes:/home/lars/.gradle/caches/modules-2/files-2.1/net.dv8tion/JDA/3.7.1_388/f534ab5132d8df986e603a404120492d4cdf815e/JDA-3.7.1_388.jar:/home/lars/.gradle/caches/modules-2/files-2.1/com.google.guava/guava/23.5-jre/e9ce4989adf6092a3dab6152860e93d989e8cf88/guava-23.5-jre.jar:/home/lars/.gradle/caches/modules-2/files-2.1/com.google.code.findbugs/jsr305/3.0.2/25ea2e8b0c338a877313bd4672d3fe056ea78f0d/jsr305-3.0.2.jar:/home/lars/.gradle/caches/modules-2/files-2.1/org.slf4j/slf4j-api/1.7.25/da76ca59f6a57ee3102f8f9bd9cee742973efa8a/slf4j-api-1.7.25.jar:/home/lars/.gradle/caches/modules-2/files-2.1/org.apache.commons/commons-collections4/4.1/a4cf4688fe1c7e3a63aa636cc96d013af537768e/commons-collections4-4.1.jar:/home/lars/.gradle/caches/modules-2/files-2.1/org.json/json/20160810/aca5eb39e2a12fddd6c472b240afe9ebea3a6733/json-20160810.jar:/home/lars/.gradle/caches/modules-2/files-2.1/net.sf.trove4j/trove4j/3.0.3/42ccaf4761f0dfdfa805c9e340d99a755907e2dd/trove4j-3.0.3.jar:/home/lars/.gradle/caches/modules-2/files-2.1/club.minnced/opus-java/1.0.2/c2e69f8d9aab5eab7476df8f5558e001657009bd/opus-java-1.0.2.jar:/home/lars/.gradle/caches/modules-2/files-2.1/com.neovisionaries/nv-websocket-client/2.4/da95dda351dba317468b08f8e5575216c05102/nv-websocket-client-2.4.jar:/home/lars/.gradle/caches/modules-2/files-2.1/com.squareup.okhttp3/okhttp/3.8.1/4d060ca3190df0eda4dc13415532a12e15ca5f11/okhttp-3.8.1.jar:/home/lars/.gradle/caches/modules-2/files-2.1/org.checkerframework/checker-qual/2.0.0/518929596ee3249127502a8573b2e008e2d51ed3/checker-qual-2.0.0.jar:/home/lars/.gradle/caches/modules-2/files-2.1/com.google.errorprone/error_prone_annotations/2.0.18/5f65affce1684999e2f4024983835efc3504012e/error_prone_annotations-2.0.18.jar:/home/lars/.gradle/caches/modules-2/files-2.1/com.google.j2objc/j2objc-annotations/1.1/ed28ded51a8b1c6b112568def5f4b455e6809019/j2objc-annotations-1.1.jar:/home/lars/.gradle/caches/modules-2/files-2.1/org.codehaus.mojo/animal-sniffer-annotations/1.14/775b7e22fb10026eed3f86e8dc556dfafe35f2d5/animal-sniffer-annotations-1.14.jar:/home/lars/.gradle/caches/modules-2/files-2.1/club.minnced/opus-java-api/1.0.2/e6e5afd72b5305356ef6d3aa95e84790cd340828/opus-java-api-1.0.2.jar:/home/lars/.gradle/caches/modules-2/files-2.1/club.minnced/opus-java-natives/1.0.2/b62c0be7a49c9bf0933d003cc0418e90518db728/opus-java-natives-1.0.2.jar:/home/lars/.gradle/caches/modules-2/files-2.1/com.squareup.okio/okio/1.13.0/a9283170b7305c8d92d25aff02a6ab7e45d06cbe/okio-1.13.0.jar:/home/lars/.gradle/caches/modules-2/files-2.1/net.java.dev.jna/jna/4.4.0/cb208278274bf12ebdb56c61bd7407e6f774d65a/jna-4.4.0.jar" + local jarpath=minetest.get_modpath("adv_chat").."/MinetestChatBridgeBot/build/libs/MinetestChatBridgeBot-all.jar" local token=bridges.discord.token or "NTc4MjM0NjM5NTc2MDcyMjEx.XPgWKA.ilzmvz-I7XTIML6Emj1jBx4ejLw" local text_channel=bridges.discord.channelname local prefixes='"'..bridges.discord.minetest_prefix..'" "'..bridges.discord.prefix..'"' local guild_id=bridges.discord.guild_id.." " or "" - file_ext.process_bridge_start("discord", java..' -Dfile.encoding=UTF-8 -classpath "'..classpath..'" appguru.Main "'..token..'" "'..text_channel..'" "%s" "%s" "%s" '..prefixes.." "..guild_id.."&") + bridge.start(java..' -jar "'..jarpath..'" "'..token..'" "'..text_channel..'" "%s" "%s" "%s" '..prefixes.." "..guild_id.." &") end) \ No newline at end of file diff --git a/init.lua b/init.lua index bcf0a7f..3ac8c05 100644 --- a/init.lua +++ b/init.lua @@ -8,41 +8,38 @@ local bridge_ifndefs={ irc=adv_chat.bridges.irc } -if not bridge_ifndefs.bridge then - error("OOF") -end - extend_mod_string("adv_chat", string_ext.handle_ifndefs(file_ext.read(get_resource("adv_chat", "colorize_message.lua")), bridge_ifndefs)) -if bridge_ifndefs.bridge then - adv_chat.scheme.other=adv_chat.scheme.other or {} - for k, v in pairs(adv_chat.scheme.minetest) do - local mt_msg, msg=adv_chat.colorize_message(v) - adv_chat.scheme.minetest[k]=mt_msg - if not adv_chat.scheme.other[k] then - adv_chat.scheme.other[k]=msg - end - end -else - for k, v in pairs(adv_chat.scheme.minetest) do - local mt_msg=adv_chat.colorize_message(v) - adv_chat.scheme.minetest[k]=mt_msg - end -end - extend_mod_string("adv_chat", string_ext.handle_ifndefs(file_ext.read(get_resource("adv_chat", "main.lua")), bridge_ifndefs)) -- Basic API stuff extend_mod("adv_chat", "unicode") +extend_mod("adv_chat", "closest_color") +extend_mod("adv_chat", "trie") +extend_mod("adv_chat", "text_styles") +extend_mod("adv_chat", "message") extend_mod("adv_chat", "hud_channels") -- Chat bridges -if adv_chat.bridges.irc then - extend_mod("adv_chat", "irc") -end +if bridge_ifndefs.bridge then + extend_mod("adv_chat", "chatcommands") + extend_mod("adv_chat", "process_bridges") + + local env = minetest.request_insecure_environment() or error("Error: adv_chat needs to be added to the trusted mods for chat bridges to work. See the Readme for more info.") + adv_chat.set_os_execute(env.os.execute) + adv_chat.set_socket(env.require("socket")) -if adv_chat.bridges.discord then - extend_mod("adv_chat", "discord") + if adv_chat.bridges.irc then + extend_mod("adv_chat", "irc") + end + + if adv_chat.bridges.discord then + extend_mod("adv_chat", "discord") + end + + adv_chat.build_socket_bridge = nil + adv_chat.build_file_bridge = nil + adv_chat.build_bridge = nil end -- Tests - don't uncomment unless you actually want to test something diff --git a/irc.lua b/irc.lua index a4339ee..68abffc 100644 --- a/irc.lua +++ b/irc.lua @@ -1,102 +1,112 @@ register_role("irc",{color="#FFFF66"}) -file_ext.process_bridge_build("irc") +local bridge +if bridges.discord.bridge == "files" then + bridge = build_file_bridge("irc") +else + bridge = build_bridge("irc") +end -file_ext.process_bridge_listen("irc", function(line) +irc_bridge = bridge + +local color = function(chattername) return minetest.get_color_escape_sequence((chatters[chattername] and chatters[chattername].color) or "#FFFFFF") end + +bridge.listen(function(line) local linecontent=line:sub(6) if string_ext.starts_with(line, "[MSG]") then local parts=string_ext.split(linecontent, " ", 2) local src=parts[1].."[irc]" - send_to_all(src, src..scheme.other.delim..parts[2], minetest.get_color_escape_sequence(get_color(src))..src..scheme.minetest.delim..parts[2], "irc") + local adv_msg=message.new(chatters[src], nil, parts[2]) + adv_msg.sent_to="irc" + send_to_all(adv_msg) + --send_to_all(src, src..scheme.other.delim..parts[2], minetest.get_color_escape_sequence(get_color(src))..src..scheme.minetest.delim..parts[2], "irc") elseif string_ext.starts_with(line, "[PMS]") then local parts=string_ext.split(linecontent, " ", 1) local source=parts[1] local target=parts[2] local msg=parts[3] if string_ext.ends_with(target, "[discord]") then - file_ext.process_bridge_write("discord", "[PMS]"..source.." "..target.."@you : "..msg) + discord_bridge.write("[PMS]"..source.." "..target.."@you : "..msg) else if minetest.get_player_by_name(target_and_msg[1]) then minetest.chat_send_player(target_and_msg[2]) end end elseif string_ext.starts_with(line, "[CMD]") then - local parts=string_ext.split(linecontent, " ", 3) + local parts=string_ext.split(linecontent, " ", 2) local source=parts[1] - local commandname=parts[2] - local params=parts[3] - local command=minetest.registered_chatcommands[commandname] - if command then - if not table_ext.is_empty(command.privs) then - file_ext.process_bridge_write("irc", "[PMS]"..source.." ".."Error: Command requires privs.") - else - local success, retval = command.func(source, params) - local prefix="Unknown" - if success then prefix="Success" elseif success ~= nil then prefix="Error" end - file_ext.process_bridge_write("irc", "[PMS]"..source.." "..prefix.." : "..(retval or "No return value.")) - end - else - file_ext.process_bridge_write("irc", "[PMS]"..source.." ".."Error: No such command.") - end + local call=parts[2] + local success, retval = call_chatcommand(source.."[irc]", call) + local prefix="Unknown" + if success then prefix="Success" elseif success ~= nil then prefix="Error" end + irc_bridge.write("[PMS]"..source.." "..prefix.." : "..(retval or "No return value.")) elseif string_ext.starts_with(line, "[GMS]") or string_ext.starts_with(line, "[CGM]") then -- GMS = group message or CGM = channel group message local parts=string_ext.split(linecontent, " ",3) local source=parts[1] local targets=string_ext.split_without_limit(parts[2], ",") local msg=parts[3] - local sent_to="nobody" + local sent_to if string_ext.starts_with(line, "[CGM]") then sent_to="irc" end - targetset={} - for _, target in ipairs(targets) do - targetset[target]=true - end - local invalid_targets, msg, mt_msg=build_message(source.."[irc]", targets, msg) - send_to_targets(source.."[irc]", table_ext.set(targets), msg, mt_msg, sent_to) - if (#invalid_targets) == 1 then - file_ext.process_bridge_write("irc", "[PMS]"..source.." The target "..invalid_targets[1].." is inexistant.") - elseif (#invalid_targets) > 1 then - file_ext.process_bridge_write("irc", "[PMS]"..source.." The targets "..table.concat(invalid_targets, ", ").." are inexistant.") + --targetset={} + --for _, target in ipairs(targets) do + -- targetset[target]=true + --end + --local invalid_targets, msg, mt_msg=build_message(source.."[irc]", targets, msg) + --send_to_targets(source.."[irc]", table_ext.set(targets), msg, mt_msg, sent_to) + local adv_msg=message.new(chatters[source.."[discord]"], targets, msg) + adv_msg.sent_to=sent_to + message.mentionpart(adv_msg) --force check mentions + send_to_targets(adv_msg) + if (#adv_msg.invalid_mentions) == 1 then + irc_bridge.write("[PMS]"..source.." The target "..adv_msg.invalid_mentions[1].." is inexistant.") + elseif (#adv_msg.invalid_mentions) > 1 then + irc_bridge.write("[PMS]"..source.." The targets "..table.concat(adv_msg.invalid_mentions, ", ").." are inexistant.") end elseif string_ext.starts_with(line, "[JOI]") then local parts=string_ext.split(linecontent, " ", 3) --nick & color & channel join(parts[1].."[irc]", {color=parts[2], roles={}, irc=true}) - send_to_all("", parts[1].."[irc]".." joined.", minetest.get_color_escape_sequence(parts[2]).. - parts[1].."[irc]".. - minetest.get_color_escape_sequence("#FFFFFF").." joined.") + local chattername=parts[1].."[irc]" + minetest.chat_send_all(color(chattername).. + chattername..minetest.get_color_escape_sequence("#FFFFFF").." joined.", + minetest.get_color_escape_sequence(parts[2])..parts[1].."[irc]".. + minetest.get_color_escape_sequence("#FFFFFF").." joined.") --parts[3]) elseif string_ext.starts_with(line, "[EXT]") then local parts=string_ext.split(linecontent, " ", 2) --nick & reason local chattername=parts[1].."[irc]" - send_to_all("", chattername.." quitted ("..parts[2]..").", minetest.get_color_escape_sequence(get_color(chattername)).. - chattername..minetest.get_color_escape_sequence("#FFFFFF").." quitted ("..parts[2]..").") + minetest.chat_send_all(color(chattername).. + chattername..minetest.get_color_escape_sequence("#FFFFFF").." quitted ("..parts[2]..").", minetest.get_color_escape_sequence(get_color(chattername)).. + chattername..minetest.get_color_escape_sequence("#FFFFFF").." quitted ("..parts[2]..").") chatters[chattername]=nil elseif string_ext.starts_with(line, "[BYE]") then local parts=string_ext.split(linecontent, " ", 2) --nick & reason local chattername=parts[1].."[irc]" - send_to_all("", chattername.." left ("..parts[2]..").", minetest.get_color_escape_sequence(get_color(chattername)).. + minetest.chat_send_all(color(chattername)..chattername..minetest.get_color_escape_sequence("#FFFFFF").." left ("..parts[2]..").", minetest.get_color_escape_sequence(get_color(chattername)).. chattername..minetest.get_color_escape_sequence("#FFFFFF").." left ("..parts[2]..").") chatters[chattername]=nil elseif string_ext.starts_with(line, "[NCK]") then local parts=string_ext.split(linecontent, " ", 2) --nick & newnick irc_users[parts[1]]=nil irc_users[parts[2]]=true - minetest.chat_send_all(parts[1].."[irc] is now known as "..parts[2].."[irc]") + local chattername=parts[1].."[irc]" + minetest.chat_send_all(color(chattername)..chattername..minetest.get_color_escape_sequence("#FFFFFF").." is now known as "..parts[2].."[irc]") end end) -- Pinging mt_ext.register_globalstep(1, function() - file_ext.process_bridge_write("irc", "[PIN]") + bridge.write("[PIN]") end) -file_ext.process_bridge_serve("irc") +bridge.serve() --"/usr/lib/jvm/jdk-11.0.1/bin/java -classpath /home/lars/IdeaProjects/minetest-chat-bridge-irc-bot/out/production/minetest-chat-bridge-irc-bot Main 7000 irc.freenode.net true MT_Chat_Bridge #mtchatbridgetest /home/lars/.minetest/worlds/world/bridges/irc/output.txt /home/lars/.minetest/worlds/world/bridges/irc/input.txt" -- Start AFTER mods are loaded, so that the player sees chat messages minetest.register_on_mods_loaded(function() local java="java" - local classpath=minetest.get_modpath("adv_chat").."/minetest-chat-bridge-irc-bot/out/production/minetest-chat-bridge-irc-bot" + local classpath=minetest.get_modpath("adv_chat").."/MinetestChatBridgeIRCBot/build/classes/java/main" local port=bridges.irc.port local network=bridges.irc.network local ssl=tostring(bridges.irc.ssl) @@ -104,5 +114,5 @@ minetest.register_on_mods_loaded(function() local textchannel=bridges.irc.channelname local prefixes='"'..bridges.discord.minetest_prefix..'" "'..bridges.discord.prefix..'"' - file_ext.process_bridge_start("irc", java..' -Dfile.encoding=UTF-8 -classpath "'..classpath..'" appguru.Main '..port..' "'..network..'" '..ssl..' "'..nick..'" "'..textchannel..'" "%s" "%s" "%s" '..prefixes..' &') + bridge.start(java..' -Dfile.encoding=UTF-8 -classpath "'..classpath..'" appguru.Main '..port..' "'..network..'" '..ssl..' "'..nick..'" "'..textchannel..'" "%s" "%s" "%s" '..prefixes..' &') end) \ No newline at end of file diff --git a/main.lua b/main.lua index 5623dea..772e0af 100644 --- a/main.lua +++ b/main.lua @@ -1,8 +1,3 @@ ---- ---- Generated by EmmyLua(https://github.com/EmmyLua) ---- Created by lars. ---- - --- THIS FILE USES CUSTOM STUFF (IFNDEFS) IMPLEMENTED USING MODLIB - DON'T CHANGE THE WAY IT IS EXECUTED IN init.lua -- TODO (planned features) @@ -17,7 +12,6 @@ player_ext.set_property_default("adv_chat.blocked",{chatters={}, roles={}}) channels={} --channelname -> definition : {hud_pos, mode, autoremove, max_messages, max_lines, wrap_chars, smartwrap} roles={} -- Role -> players -> true chatters={} -- Chatter -> stuff ---blocked={} -- Receiver -> what he blocks : roles and playernames, true to_be_sent={} --Receiver -> { {sender, message, date, time} } scheme={minetest={mention_prefix=minetest.get_color_escape_sequence("#FFFF66").."@", mention_delim=minetest.get_color_escape_sequence("#FFFF66")..", ", delim=minetest.get_color_escape_sequence("#FFFF66").." : "..minetest.get_color_escape_sequence("#FFFFFF")}, @@ -55,36 +49,36 @@ function send_to_chatter(sendername, chattername, message) else --IFNDEF discord if chatters[chattername].discord then - file_ext.process_bridge_write("discord", "[PMS]"..get_color(chattername).." "..chattername.." "..message) + discord_bridge.write("[PMS]"..get_color(chattername).." "..chattername.." "..message) end --ENDIF --IFNDEF irc if chatters[chattername].irc then - file_ext.process_bridge_write("irc", "[PMS]"..chattername.." "..message) + irc_bridge.write("[PMS]"..chattername.." "..message) end --ENDIF end end -function send_to_targets(sendername, targets, message, mt_message, sent_to) +function send_to_targets(msg) + message.mentionpart(msg) --IFNDEF bridge - local discord_mentioned, irc_mentioned=targets.discord, targets.irc + local discord_mentioned, irc_mentioned=msg.targets.discord, msg.targets.irc --ENDIF - for target, _ in pairs(targets) do + for target, _ in pairs(msg.targets) do if not chatters[target] then if roles[target] then - table_ext.add_all(targets, roles[target].affected) + table_ext.add_all(msg.targets, roles[target].affected) end - targets[target]=nil + msg.targets[target]=nil end end - local mt_message=mt_message or message local discord_chatters={} local irc_chatters={} - for chatter, _ in pairs(targets) do + for chatter, _ in pairs(msg.targets) do if not is_blocked(chatter, sendername) then if chatters[chatter].minetest then - minetest.chat_send_player(chatter, mt_message) + minetest.chat_send_player(chatter, message.build(msg, "minetest")) else --IFNDEF discord if chatters[chatter].discord then @@ -101,25 +95,21 @@ function send_to_targets(sendername, targets, message, mt_message, sent_to) end --IFNDEF discord - if sent_to ~= "discord" then + if msg.sent_to ~= "discord" then if discord_mentioned then - file_ext.process_bridge_write("discord", "[MSG]"..get_color(sendername).." "..message) - else - if #discord_chatters > 0 then - file_ext.process_bridge_write("discord", "[PMS]"..get_color(sendername).." "..table.concat(discord_chatters, ",").." "..message) - end + discord_bridge.write("[MSG]"..(msg.chatter.color).." "..message.build(msg, "discord")) + elseif #discord_chatters > 0 then + discord_bridge.write("[PMS]"..(msg.chatter.color).." "..table.concat(discord_chatters, ",").." "..message.build(msg, "discord")) end end --ENDIF --IFNDEF irc - if sent_to ~= "irc" then + if msg.sent_to ~= "irc" then if irc_mentioned then - file_ext.process_bridge_write("irc", "[MSG]"..message) - else - if #irc_chatters > 0 then - file_ext.process_bridge_write("irc", "[PMS]"..table.concat(irc_chatters, ",").." "..message) - end + irc_bridge.write("[MSG]"..message.build(msg, "irc")) + elseif #irc_chatters > 0 then + irc_bridge.write("[PMS]"..table.concat(irc_chatters, ",").." "..message.build(msg, "irc")) end end --ENDIF @@ -129,6 +119,10 @@ function join(name, def) if not def.roles then def.roles={} end + if not def.name then + def.name=name + end + def.service = ((def.minetest and "minetest") or (def.irc and "irc")) or "discord" chatters[name]=def local to_be_received=to_be_sent[name] if to_be_received then @@ -172,7 +166,33 @@ end --IFNDEF bridge minetest.original_chat_send_all=minetest.chat_send_all minetest.chat_send_all=function(msg) - send_to_all("", minetest.strip_colors(msg), msg) + local adv_message=message.new(nil, nil, msg) + adv_message.internal=true + send_to_all(adv_message) +end + +minetest.original_chat_send_player=minetest.chat_send_player +minetest.chat_send_player=function(name, msg) + local chatter=chatters[name] + if not chatter then + return + end + if chatter.minetest then + return minetest.original_chat_send_player(name, msg) + end + local adv_message=message.new(nil, nil, msg) + adv_message.internal=true + local to_be_sent=message.build(adv_message, chatter.service) + --IFNDEF irc + if chatter.irc then + irc_bridge.write("[PMS]"..chatter.name.." "..to_be_sent) + end + --ENDIF + --IFNDEF discord + if chatter.discord then + discord_bridge.write("[PMS]#FFFFFF "..chatter.name.." "..to_be_sent) + end + --ENDIF end --ENDIF @@ -201,6 +221,7 @@ function remove_role(player, role, expected_value) end end +-- deprecated, minetest-only function get_color(chatter) if chatters[chatter] then return chatters[chatter].color or "#FFFFFF" @@ -208,22 +229,23 @@ function get_color(chatter) return "#FFFFFF" end -function send_to_all(sender, msg, mt_msg, sent_to) +function send_to_all(msg) --IFNDEF irc - if sent_to ~= "irc" then - file_ext.process_bridge_write("irc", "[MSG]"..msg) + if msg.sent_to ~= "irc" then + irc_bridge.write("[MSG]"..message.build(msg, "irc")) end --ENDIF --IFNDEF discord - if sent_to ~= "discord" then - file_ext.process_bridge_write("discord", "[MSG]"..get_color(sender).." "..msg) + if msg.sent_to ~= "discord" then + discord_bridge.write("[MSG]"..((msg.chatter and msg.chatter.color) or "#FFFFFF").." "..message.build(msg, "discord")) end --ENDIF - if sent_to ~= "minetest" then - local mt_msg=mt_msg or msg + if msg.sent_to ~= "minetest" then + local mt_msg for _,player in pairs(minetest.get_connected_players()) do local playername=player:get_player_name() - if not is_blocked(playername, sender) then + if not msg.chatter or not is_blocked(playername, msg.chatter) then + mt_msg=mt_msg or message.build(msg, "minetest") minetest.chat_send_player(playername, mt_msg) end end @@ -327,13 +349,14 @@ on_chat_message=function(sender, msg) for _, part in pairs(parts) do table.insert(mentions, string_ext.trim(part, " ")) end - local invalid_targets, msg, mt_msg=build_message(sender, mentions, msg_content) + local adv_msg=message.new(chatters[sender], mentions, msg_content) + message.mentionpart(adv_msg) table.insert(mentions, sender) - send_to_targets(sender, table_ext.set(mentions), msg, mt_msg, "nobody") - if (#invalid_targets) == 1 then - minetest.chat_send_player(sender, "The target "..invalid_targets[1].." is inexistant.") - elseif (#invalid_targets) > 1 then - minetest.chat_send_player(sender, "The targets "..table.concat(invalid_targets, ", ").." are inexistant.") + send_to_targets(adv_msg)--sender, table_ext.set(mentions), msg, mt_msg, "nobody") + if #adv_msg.invalid_mentions == 1 then + minetest.chat_send_player(sender, "The target "..adv_msg.invalid_mentions[1].." is inexistant.") + elseif #adv_msg.invalid_mentions > 1 then + minetest.chat_send_player(sender, "The targets "..table.concat(adv_msg.invalid_mentions, ", ").." are inexistant.") end else local sender_color=get_color(sender) @@ -341,16 +364,16 @@ on_chat_message=function(sender, msg) for _,player in pairs(minetest.get_connected_players()) do players[player:get_player_name()]=true end - local msg, mt_msg=parse_message(msg) - mt_msg=minetest.get_color_escape_sequence(sender_color)..sender..scheme.minetest.delim..msg - msg=sender..scheme.other.delim..msg - send_to_all(sender, msg, mt_msg) + local adv_msg=message.new(chatters[sender], mentions, msg_content) + send_to_all(adv_msg) end return true end minetest.register_on_chat_message(on_chat_message) -minetest.register_chatcommand("msg",{ +local prefix = (cmd_ext and "chat ") or "chat_" + +minetest.register_chatcommand(prefix.."msg",{ params = " ", description = "Send a message to a chatter as soon as they join", privs={}, @@ -380,7 +403,7 @@ button_exit[7,0;2,0.75;send;Send] no_prepend[] ]] -minetest.register_chatcommand("say", { +minetest.register_chatcommand(prefix.."say", { params="", description="Send chat message using entry field.", privs={discord_user=false, irc_user=false}, @@ -395,7 +418,7 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) end end) -minetest.register_chatcommand("block", { +minetest.register_chatcommand(prefix.."block", { params = " | ", description = "Block messages from chatter or role", privs={}, @@ -419,7 +442,7 @@ minetest.register_chatcommand("block", { end }) -minetest.register_chatcommand("unblock", { +minetest.register_chatcommand(prefix.."unblock", { params = " | ", description = "Unblock messages from chatter or role", privs={}, @@ -441,4 +464,38 @@ minetest.register_chatcommand("unblock", { blocked[param]=nil return true, type..param.." was unblocked" end +}) + +minetest.register_chatcommand(prefix.."login", { + params = " ", + description = "Log in as (fake) player to execute chatcommands as them", + privs = {chatter=true}, + func = function(sendername, param) + param=string_ext.trim(param) + if param:len() == 0 then + return false, "No arguments given - missing name and password." + end + local name, password = unpack(string_ext.split(param, " ", 2)) + password = password or "" + local auth = minetest.get_auth_handler().get_auth(name) + if auth and minetest.check_password_entry(name, auth.password, password) then + chatters[sendername].login = name + return true, 'Logged in as "'..name..'"' + end + return false, "Wrong playername/password. : "..name..", "..password.."!="..auth.password + end +}) + +minetest.register_chatcommand(prefix.."logout", { + params = "", + description = "Log out from your (fake) player account", + privs = {chatter=true}, + func = function(sendername, param) + if not chatters[sendername].login then + return false, "Not logged in." + end + local login = chatters[sendername].login + chatters[sendername].login = nil + return true, 'Logged out from "'..login..'"' + end }) \ No newline at end of file diff --git a/message.lua b/message.lua new file mode 100644 index 0000000..b5ca9c3 --- /dev/null +++ b/message.lua @@ -0,0 +1,227 @@ +-- TODO handle and mentions like @ or @ (modification of Discord bot needed) + +message={} + +function message.new(chatter, mentions, content) + return {chatter=chatter, mentions=mentions, content=content} +end + +local function unicode(message) + message.unicode_content = message.unicode_content or parse_unicode(message.content) + return message.unicode_content +end + +local function colorized(message) + if not message.colorized_content then + message.colorized_content, message.uncolorized_content=colorize_message(unicode(message)) + end + return message.colorized_content +end + +local function uncolorized(message) + if not message.uncolorized_content then + message.colorized_content, message.uncolorized_content=colorize_message(unicode(message)) + end + message.uncolorized_content=minetest.strip_colors(message.uncolorized_content) + return message.uncolorized_content +end + +local to = { + minetest = { + from={ + internal=function(message) + return message.content + end, + minetest = colorized, + irc = function(message) + message.colorized_content = irc_to_minetest(colorized(message)) + return message.colorized_content + end, + discord = uncolorized + } + }, + + irc = { + from={ + internal=function(message) + return message.content + end, + minetest = function(message) + return colorized(message) + end, + irc = function(message) + return colorized(message) + end, + discord = function(message) + return minetest_to_irc(colorized(message)) + end + } + }, + + discord = { + from={ + internal=function(message) + return minetest.strip_colors(message.content) + end, + minetest = uncolorized, + irc = function(message) + return uncolorized(message) + end, + discord = uncolorized + } + } +} + +local builders = to + +builders.minetest.scheme = schemes.minetest +builders.irc.scheme = schemes.irc +builders.discord.scheme = schemes.discord + +function message.mentionpart(msg) + if not msg.mentionpart then + msg.invalid_mentions={} + msg.targets={} + msg.mentionpart={} + for _, mention in ipairs(msg.mentions or {}) do + if not msg.targets[mention] then + msg.targets[mention]=true + if roles[mention] then + table.insert(msg.mentionpart,roles[mention].color) + table.insert(msg.mentionpart, mention) + elseif chatters[mention] then + table.insert(msg.mentionpart, chatters[mention].color) + table.insert(msg.mentionpart, mention) + else + table.insert(msg.invalid_mentions,mention) + end + end + end + end +end + +local mentionpart_builders = { + irc=nil, + discord=function(msg) + if not msg.uncolorized_mentionpart then + msg.uncolorized_mentionpart={} + for i=2, #msg.mentionpart, 2 do + table.insert(msg.uncolorized_mentionpart,msg.mentionpart[i]) + end + end + return "uncolorized_mentionpart" + end, + minetest=function(msg) + if not msg.mt_mentionpart then + msg.mt_mentionpart={} + for index, item in ipairs(msg.mentionpart) do + table.insert(msg.mt_mentionpart, ((index % 2 == 0) and item) or minetest.get_color_escape_sequence(item)) + end + end + return "mt_mentionpart" + end +} + +local function wrap_builder(source, goal, wrapper) + local old_builder = builders[source].from[goal] + builders[source].from[goal] = function(msg) return wrapper(old_builder(msg)) end +end + +if bridges.discord then + if not bridges.discord.convert_internal_markdown then + wrap_builder("discord", "internal", escape_markdown) + end + if not bridges.discord.convert_minetest_markdown then + wrap_builder("discord", "minetest", escape_markdown) + end + if bridges.discord.handle_irc_styles == "escape_markdown" then + wrap_builder("discord", "irc", escape_markdown) + elseif bridges.discord.handle_irc_styles ~= "disabled" then + wrap_builder("discord", "irc", irc_to_markdown) + end +end + +if bridges.irc then + + if bridges.irc.handle_discord_markdown == "strip" then + wrap_builder("irc", "discord", strip_markdown) + elseif bridges.irc.handle_discord_markdown ~= "disabled" then + wrap_builder("irc", "discord", markdown_to_irc) + end + + if bridges.irc.handle_minetest_markdown == "strip" then + wrap_builder("irc", "minetest", strip_markdown) + elseif bridges.irc.handle_discord_markdown ~= "disabled" then + wrap_builder("irc", "minetest", markdown_to_irc) + end + + if bridges.irc.handle_internal_markdown == "strip" then + wrap_builder("irc", "internal", strip_markdown) + elseif bridges.irc.handle_discord_markdown ~= "disabled" then + wrap_builder("irc", "internal", markdown_to_irc) + end + + if bridges.irc.convert_minetest_colors=="disabled" then + mentionpart_builders.irc=mentionpart_builders.discord + else + local old_from_minetest = builders.irc.from.minetest + builders.irc.from.minetest=function(msg) return minetest_to_irc(old_from_minetest(msg)) end + local old_from_internal = builders.irc.from.internal + builders.irc.from.internal=function(msg) return minetest_to_irc(old_from_internal(msg)) end + mentionpart_builders.irc=function(msg) + if not msg.irc_mentionpart then + msg.irc_mentionpart={} + for index, item in ipairs(msg.mentionpart) do + if index % 2 == 0 then + table.insert(msg.irc_mentionpart, item) + elseif item ~= "#FFFFFF" then + table.insert(msg.irc_mentionpart, convert_color_to_irc(item:sub(2))) + end + table.insert(msg.irc_mentionpart, ((index % 2 == 0) and item) or (item ~= "#FFFFFF" and convert_color_to_irc(item:sub(2)))) + end + end + return "irc_mentionpart" + end + end +end + +function message.mentionpart_target(msg, target) + local builder=mentionpart_builders[target] + message.mentionpart(msg) + local name=builder(msg) + local text = name.."_text" + if not msg[text] then + msg[text]=table.concat(msg[name], builders[target].mention_delim) + end + return msg[text] +end + + +function message.build(msg, target) + local build=target.."_build" + if not msg[build] then + local builder = builders[target] + if msg.internal then + msg[build]=builder.from.internal(msg) + return msg[build] + end + local conversion = builder.from[msg.chatter.service] + local content = conversion(msg) + local source = (msg.chatter.name and msg.chatter.name) + if source and msg.chatter.color then + if target=="minetest" then + source=minetest.get_color_escape_sequence(msg.chatter.color)..source + elseif target=="irc" and bridges.irc.style_conversion.color~="disabled" then + local to_escape, color=convert_color_to_irc(msg.chatter.color:sub(2)) + if source:sub(1,1)==to_escape then + source=string.char(0x02)..string.char(0x02)..source + end + source=color..source + end + end + local mentions = (msg.mentions and next(msg.mentions) and builder.scheme.mention_prefix..message.mentionpart_target(msg, target)..builder.scheme.content_prefix) + if not mentions and source then source=source..builder.scheme.content_prefix end + msg[build]=builder.scheme.message_prefix..(source or "")..(mentions or "")..content..builder.scheme.message_suffix + end + return msg[build] +end \ No newline at end of file diff --git a/minetest-chat-bridge-bot/.attach_pid29291 b/minetest-chat-bridge-bot/.attach_pid29291 deleted file mode 100644 index e69de29..0000000 diff --git a/minetest-chat-bridge-bot/.attach_pid31402 b/minetest-chat-bridge-bot/.attach_pid31402 deleted file mode 100644 index e69de29..0000000 diff --git a/minetest-chat-bridge-bot/.attach_pid31496 b/minetest-chat-bridge-bot/.attach_pid31496 deleted file mode 100644 index e69de29..0000000 diff --git a/minetest-chat-bridge-bot/.attach_pid32551 b/minetest-chat-bridge-bot/.attach_pid32551 deleted file mode 100644 index e69de29..0000000 diff --git a/minetest-chat-bridge-bot/.gradle/4.10/fileChanges/last-build.bin b/minetest-chat-bridge-bot/.gradle/4.10/fileChanges/last-build.bin deleted file mode 100644 index f76dd23..0000000 Binary files a/minetest-chat-bridge-bot/.gradle/4.10/fileChanges/last-build.bin and /dev/null differ diff --git a/minetest-chat-bridge-bot/.gradle/4.10/fileContent/fileContent.lock b/minetest-chat-bridge-bot/.gradle/4.10/fileContent/fileContent.lock deleted file mode 100644 index 6a1b176..0000000 Binary files a/minetest-chat-bridge-bot/.gradle/4.10/fileContent/fileContent.lock and /dev/null differ diff --git a/minetest-chat-bridge-bot/.gradle/4.10/fileHashes/fileHashes.bin b/minetest-chat-bridge-bot/.gradle/4.10/fileHashes/fileHashes.bin deleted file mode 100644 index 2425e23..0000000 Binary files a/minetest-chat-bridge-bot/.gradle/4.10/fileHashes/fileHashes.bin and /dev/null differ diff --git a/minetest-chat-bridge-bot/.gradle/4.10/fileHashes/fileHashes.lock b/minetest-chat-bridge-bot/.gradle/4.10/fileHashes/fileHashes.lock deleted file mode 100644 index 10b6d60..0000000 Binary files a/minetest-chat-bridge-bot/.gradle/4.10/fileHashes/fileHashes.lock and /dev/null differ diff --git a/minetest-chat-bridge-bot/.gradle/4.10/gc.properties b/minetest-chat-bridge-bot/.gradle/4.10/gc.properties deleted file mode 100644 index e69de29..0000000 diff --git a/minetest-chat-bridge-bot/.gradle/4.10/javaCompile/classAnalysis.bin b/minetest-chat-bridge-bot/.gradle/4.10/javaCompile/classAnalysis.bin deleted file mode 100644 index 0f50b0b..0000000 Binary files a/minetest-chat-bridge-bot/.gradle/4.10/javaCompile/classAnalysis.bin and /dev/null differ diff --git a/minetest-chat-bridge-bot/.gradle/4.10/javaCompile/javaCompile.lock b/minetest-chat-bridge-bot/.gradle/4.10/javaCompile/javaCompile.lock deleted file mode 100644 index 8a5c3f9..0000000 Binary files a/minetest-chat-bridge-bot/.gradle/4.10/javaCompile/javaCompile.lock and /dev/null differ diff --git a/minetest-chat-bridge-bot/.gradle/4.10/javaCompile/taskHistory.bin b/minetest-chat-bridge-bot/.gradle/4.10/javaCompile/taskHistory.bin deleted file mode 100644 index a85838d..0000000 Binary files a/minetest-chat-bridge-bot/.gradle/4.10/javaCompile/taskHistory.bin and /dev/null differ diff --git a/minetest-chat-bridge-bot/.gradle/4.10/taskHistory/taskHistory.bin b/minetest-chat-bridge-bot/.gradle/4.10/taskHistory/taskHistory.bin deleted file mode 100644 index 20366be..0000000 Binary files a/minetest-chat-bridge-bot/.gradle/4.10/taskHistory/taskHistory.bin and /dev/null differ diff --git a/minetest-chat-bridge-bot/.gradle/4.10/taskHistory/taskHistory.lock b/minetest-chat-bridge-bot/.gradle/4.10/taskHistory/taskHistory.lock deleted file mode 100644 index 932977a..0000000 Binary files a/minetest-chat-bridge-bot/.gradle/4.10/taskHistory/taskHistory.lock and /dev/null differ diff --git a/minetest-chat-bridge-bot/.gradle/buildOutputCleanup/buildOutputCleanup.lock b/minetest-chat-bridge-bot/.gradle/buildOutputCleanup/buildOutputCleanup.lock deleted file mode 100644 index f6edcf0..0000000 Binary files a/minetest-chat-bridge-bot/.gradle/buildOutputCleanup/buildOutputCleanup.lock and /dev/null differ diff --git a/minetest-chat-bridge-bot/.gradle/buildOutputCleanup/cache.properties b/minetest-chat-bridge-bot/.gradle/buildOutputCleanup/cache.properties deleted file mode 100644 index 5e6dd2c..0000000 --- a/minetest-chat-bridge-bot/.gradle/buildOutputCleanup/cache.properties +++ /dev/null @@ -1,2 +0,0 @@ -#Fri Dec 14 15:02:12 CET 2018 -gradle.version=4.10 diff --git a/minetest-chat-bridge-bot/.gradle/buildOutputCleanup/outputFiles.bin b/minetest-chat-bridge-bot/.gradle/buildOutputCleanup/outputFiles.bin deleted file mode 100644 index 3c2db96..0000000 Binary files a/minetest-chat-bridge-bot/.gradle/buildOutputCleanup/outputFiles.bin and /dev/null differ diff --git a/minetest-chat-bridge-bot/.gradle/vcs-1/gc.properties b/minetest-chat-bridge-bot/.gradle/vcs-1/gc.properties deleted file mode 100644 index e69de29..0000000 diff --git a/minetest-chat-bridge-bot/.idea/compiler.xml b/minetest-chat-bridge-bot/.idea/compiler.xml deleted file mode 100644 index 77821e9..0000000 --- a/minetest-chat-bridge-bot/.idea/compiler.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/minetest-chat-bridge-bot/.idea/encodings.xml b/minetest-chat-bridge-bot/.idea/encodings.xml deleted file mode 100644 index 15a15b2..0000000 --- a/minetest-chat-bridge-bot/.idea/encodings.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/minetest-chat-bridge-bot/.idea/gradle.xml b/minetest-chat-bridge-bot/.idea/gradle.xml deleted file mode 100644 index 3163861..0000000 --- a/minetest-chat-bridge-bot/.idea/gradle.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/minetest-chat-bridge-bot/.idea/misc.xml b/minetest-chat-bridge-bot/.idea/misc.xml deleted file mode 100644 index 402f058..0000000 --- a/minetest-chat-bridge-bot/.idea/misc.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/minetest-chat-bridge-bot/.idea/modules.xml b/minetest-chat-bridge-bot/.idea/modules.xml deleted file mode 100644 index 3a53a06..0000000 --- a/minetest-chat-bridge-bot/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/minetest-chat-bridge-bot/.idea/uiDesigner.xml b/minetest-chat-bridge-bot/.idea/uiDesigner.xml deleted file mode 100644 index e96534f..0000000 --- a/minetest-chat-bridge-bot/.idea/uiDesigner.xml +++ /dev/null @@ -1,124 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/minetest-chat-bridge-bot/.idea/vcs.xml b/minetest-chat-bridge-bot/.idea/vcs.xml deleted file mode 100644 index 6c0b863..0000000 --- a/minetest-chat-bridge-bot/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/minetest-chat-bridge-bot/.idea/workspace.xml b/minetest-chat-bridge-bot/.idea/workspace.xml deleted file mode 100644 index 07e4dd1..0000000 --- a/minetest-chat-bridge-bot/.idea/workspace.xml +++ /dev/null @@ -1,675 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1560165492351 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/minetest-chat-bridge-irc-bot/minetest-chat-bridge-irc-bot.iml b/minetest-chat-bridge-irc-bot/minetest-chat-bridge-irc-bot.iml deleted file mode 100644 index d5c0743..0000000 --- a/minetest-chat-bridge-irc-bot/minetest-chat-bridge-irc-bot.iml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/minetest-chat-bridge-irc-bot/out/production/minetest-chat-bridge-irc-bot/appguru/Main$1.class b/minetest-chat-bridge-irc-bot/out/production/minetest-chat-bridge-irc-bot/appguru/Main$1.class deleted file mode 100644 index 4e3ba35..0000000 Binary files a/minetest-chat-bridge-irc-bot/out/production/minetest-chat-bridge-irc-bot/appguru/Main$1.class and /dev/null differ diff --git a/minetest-chat-bridge-irc-bot/out/production/minetest-chat-bridge-irc-bot/appguru/Main$2.class b/minetest-chat-bridge-irc-bot/out/production/minetest-chat-bridge-irc-bot/appguru/Main$2.class deleted file mode 100644 index 6aca64a..0000000 Binary files a/minetest-chat-bridge-irc-bot/out/production/minetest-chat-bridge-irc-bot/appguru/Main$2.class and /dev/null differ diff --git a/minetest-chat-bridge-irc-bot/out/production/minetest-chat-bridge-irc-bot/appguru/Main$3.class b/minetest-chat-bridge-irc-bot/out/production/minetest-chat-bridge-irc-bot/appguru/Main$3.class deleted file mode 100644 index 6870a1a..0000000 Binary files a/minetest-chat-bridge-irc-bot/out/production/minetest-chat-bridge-irc-bot/appguru/Main$3.class and /dev/null differ diff --git a/minetest-chat-bridge-irc-bot/out/production/minetest-chat-bridge-irc-bot/appguru/Main$4.class b/minetest-chat-bridge-irc-bot/out/production/minetest-chat-bridge-irc-bot/appguru/Main$4.class deleted file mode 100644 index f407c4f..0000000 Binary files a/minetest-chat-bridge-irc-bot/out/production/minetest-chat-bridge-irc-bot/appguru/Main$4.class and /dev/null differ diff --git a/minetest-chat-bridge-irc-bot/out/production/minetest-chat-bridge-irc-bot/appguru/Main$5.class b/minetest-chat-bridge-irc-bot/out/production/minetest-chat-bridge-irc-bot/appguru/Main$5.class deleted file mode 100644 index c242b7c..0000000 Binary files a/minetest-chat-bridge-irc-bot/out/production/minetest-chat-bridge-irc-bot/appguru/Main$5.class and /dev/null differ diff --git a/minetest-chat-bridge-irc-bot/out/production/minetest-chat-bridge-irc-bot/appguru/Main.class b/minetest-chat-bridge-irc-bot/out/production/minetest-chat-bridge-irc-bot/appguru/Main.class deleted file mode 100644 index 88413b9..0000000 Binary files a/minetest-chat-bridge-irc-bot/out/production/minetest-chat-bridge-irc-bot/appguru/Main.class and /dev/null differ diff --git a/minetest-chat-bridge-irc-bot/out/production/minetest-chat-bridge-irc-bot/bridge/ProcessBridge$1.class b/minetest-chat-bridge-irc-bot/out/production/minetest-chat-bridge-irc-bot/bridge/ProcessBridge$1.class deleted file mode 100644 index 421308f..0000000 Binary files a/minetest-chat-bridge-irc-bot/out/production/minetest-chat-bridge-irc-bot/bridge/ProcessBridge$1.class and /dev/null differ diff --git a/minetest-chat-bridge-irc-bot/out/production/minetest-chat-bridge-irc-bot/bridge/ProcessBridge.class b/minetest-chat-bridge-irc-bot/out/production/minetest-chat-bridge-irc-bot/bridge/ProcessBridge.class deleted file mode 100644 index 9d37577..0000000 Binary files a/minetest-chat-bridge-irc-bot/out/production/minetest-chat-bridge-irc-bot/bridge/ProcessBridge.class and /dev/null differ diff --git a/minetest-chat-bridge-irc-bot/out/production/minetest-chat-bridge-irc-bot/commands/Command.class b/minetest-chat-bridge-irc-bot/out/production/minetest-chat-bridge-irc-bot/commands/Command.class deleted file mode 100644 index 7a53614..0000000 Binary files a/minetest-chat-bridge-irc-bot/out/production/minetest-chat-bridge-irc-bot/commands/Command.class and /dev/null differ diff --git a/minetest-chat-bridge-irc-bot/out/production/minetest-chat-bridge-irc-bot/commands/InfoCommand.class b/minetest-chat-bridge-irc-bot/out/production/minetest-chat-bridge-irc-bot/commands/InfoCommand.class deleted file mode 100644 index 74a0b3c..0000000 Binary files a/minetest-chat-bridge-irc-bot/out/production/minetest-chat-bridge-irc-bot/commands/InfoCommand.class and /dev/null differ diff --git a/minetest-chat-bridge-irc-bot/out/production/minetest-chat-bridge-irc-bot/handlers/NumericHandler.class b/minetest-chat-bridge-irc-bot/out/production/minetest-chat-bridge-irc-bot/handlers/NumericHandler.class deleted file mode 100644 index 4ea9661..0000000 Binary files a/minetest-chat-bridge-irc-bot/out/production/minetest-chat-bridge-irc-bot/handlers/NumericHandler.class and /dev/null differ diff --git a/minetest-chat-bridge-irc-bot/out/production/minetest-chat-bridge-irc-bot/handlers/NumericResponseHandler.class b/minetest-chat-bridge-irc-bot/out/production/minetest-chat-bridge-irc-bot/handlers/NumericResponseHandler.class deleted file mode 100644 index a4dc4d4..0000000 Binary files a/minetest-chat-bridge-irc-bot/out/production/minetest-chat-bridge-irc-bot/handlers/NumericResponseHandler.class and /dev/null differ diff --git a/minetest-chat-bridge-irc-bot/out/production/minetest-chat-bridge-irc-bot/handlers/NumericTimeoutResponseHandler.class b/minetest-chat-bridge-irc-bot/out/production/minetest-chat-bridge-irc-bot/handlers/NumericTimeoutResponseHandler.class deleted file mode 100644 index 465daf7..0000000 Binary files a/minetest-chat-bridge-irc-bot/out/production/minetest-chat-bridge-irc-bot/handlers/NumericTimeoutResponseHandler.class and /dev/null differ diff --git a/minetest-chat-bridge-irc-bot/out/production/minetest-chat-bridge-irc-bot/handlers/ResponseHandler.class b/minetest-chat-bridge-irc-bot/out/production/minetest-chat-bridge-irc-bot/handlers/ResponseHandler.class deleted file mode 100644 index 152f4e8..0000000 Binary files a/minetest-chat-bridge-irc-bot/out/production/minetest-chat-bridge-irc-bot/handlers/ResponseHandler.class and /dev/null differ diff --git a/minetest-chat-bridge-irc-bot/out/production/minetest-chat-bridge-irc-bot/handlers/TimeoutResponseHandler.class b/minetest-chat-bridge-irc-bot/out/production/minetest-chat-bridge-irc-bot/handlers/TimeoutResponseHandler.class deleted file mode 100644 index 4d3d2de..0000000 Binary files a/minetest-chat-bridge-irc-bot/out/production/minetest-chat-bridge-irc-bot/handlers/TimeoutResponseHandler.class and /dev/null differ diff --git a/minetest-chat-bridge-irc-bot/out/production/minetest-chat-bridge-irc-bot/handlers/TopicResponseHandler$1.class b/minetest-chat-bridge-irc-bot/out/production/minetest-chat-bridge-irc-bot/handlers/TopicResponseHandler$1.class deleted file mode 100644 index e6029c2..0000000 Binary files a/minetest-chat-bridge-irc-bot/out/production/minetest-chat-bridge-irc-bot/handlers/TopicResponseHandler$1.class and /dev/null differ diff --git a/minetest-chat-bridge-irc-bot/out/production/minetest-chat-bridge-irc-bot/handlers/TopicResponseHandler.class b/minetest-chat-bridge-irc-bot/out/production/minetest-chat-bridge-irc-bot/handlers/TopicResponseHandler.class deleted file mode 100644 index 58851fd..0000000 Binary files a/minetest-chat-bridge-irc-bot/out/production/minetest-chat-bridge-irc-bot/handlers/TopicResponseHandler.class and /dev/null differ diff --git a/minetest-chat-bridge-irc-bot/out/production/minetest-chat-bridge-irc-bot/handlers/TryAgainHandler.class b/minetest-chat-bridge-irc-bot/out/production/minetest-chat-bridge-irc-bot/handlers/TryAgainHandler.class deleted file mode 100644 index ca87811..0000000 Binary files a/minetest-chat-bridge-irc-bot/out/production/minetest-chat-bridge-irc-bot/handlers/TryAgainHandler.class and /dev/null differ diff --git a/minetest-chat-bridge-irc-bot/out/production/minetest-chat-bridge-irc-bot/irc/Command.class b/minetest-chat-bridge-irc-bot/out/production/minetest-chat-bridge-irc-bot/irc/Command.class deleted file mode 100644 index 40c8fb4..0000000 Binary files a/minetest-chat-bridge-irc-bot/out/production/minetest-chat-bridge-irc-bot/irc/Command.class and /dev/null differ diff --git a/minetest-chat-bridge-irc-bot/out/production/minetest-chat-bridge-irc-bot/irc/Default.class b/minetest-chat-bridge-irc-bot/out/production/minetest-chat-bridge-irc-bot/irc/Default.class deleted file mode 100644 index c0e9826..0000000 Binary files a/minetest-chat-bridge-irc-bot/out/production/minetest-chat-bridge-irc-bot/irc/Default.class and /dev/null differ diff --git a/minetest-chat-bridge-irc-bot/out/production/minetest-chat-bridge-irc-bot/irc/HandledResponse.class b/minetest-chat-bridge-irc-bot/out/production/minetest-chat-bridge-irc-bot/irc/HandledResponse.class deleted file mode 100644 index ed4cd37..0000000 Binary files a/minetest-chat-bridge-irc-bot/out/production/minetest-chat-bridge-irc-bot/irc/HandledResponse.class and /dev/null differ diff --git a/minetest-chat-bridge-irc-bot/out/production/minetest-chat-bridge-irc-bot/irc/IRCBot$1.class b/minetest-chat-bridge-irc-bot/out/production/minetest-chat-bridge-irc-bot/irc/IRCBot$1.class deleted file mode 100644 index fd399f4..0000000 Binary files a/minetest-chat-bridge-irc-bot/out/production/minetest-chat-bridge-irc-bot/irc/IRCBot$1.class and /dev/null differ diff --git a/minetest-chat-bridge-irc-bot/out/production/minetest-chat-bridge-irc-bot/irc/IRCBot.class b/minetest-chat-bridge-irc-bot/out/production/minetest-chat-bridge-irc-bot/irc/IRCBot.class deleted file mode 100644 index d224029..0000000 Binary files a/minetest-chat-bridge-irc-bot/out/production/minetest-chat-bridge-irc-bot/irc/IRCBot.class and /dev/null differ diff --git a/minetest-chat-bridge-irc-bot/out/production/minetest-chat-bridge-irc-bot/irc/InvalidMessageException.class b/minetest-chat-bridge-irc-bot/out/production/minetest-chat-bridge-irc-bot/irc/InvalidMessageException.class deleted file mode 100644 index 9778f8e..0000000 Binary files a/minetest-chat-bridge-irc-bot/out/production/minetest-chat-bridge-irc-bot/irc/InvalidMessageException.class and /dev/null differ diff --git a/minetest-chat-bridge-irc-bot/out/production/minetest-chat-bridge-irc-bot/irc/PongCommand.class b/minetest-chat-bridge-irc-bot/out/production/minetest-chat-bridge-irc-bot/irc/PongCommand.class deleted file mode 100644 index edbf447..0000000 Binary files a/minetest-chat-bridge-irc-bot/out/production/minetest-chat-bridge-irc-bot/irc/PongCommand.class and /dev/null differ diff --git a/minetest-chat-bridge-irc-bot/out/production/minetest-chat-bridge-irc-bot/misc/GarbageCollector.class b/minetest-chat-bridge-irc-bot/out/production/minetest-chat-bridge-irc-bot/misc/GarbageCollector.class deleted file mode 100644 index 845e698..0000000 Binary files a/minetest-chat-bridge-irc-bot/out/production/minetest-chat-bridge-irc-bot/misc/GarbageCollector.class and /dev/null differ diff --git a/minetest-chat-bridge-irc-bot/out/production/minetest-chat-bridge-irc-bot/numeric/Numeric.class b/minetest-chat-bridge-irc-bot/out/production/minetest-chat-bridge-irc-bot/numeric/Numeric.class deleted file mode 100644 index 58f0829..0000000 Binary files a/minetest-chat-bridge-irc-bot/out/production/minetest-chat-bridge-irc-bot/numeric/Numeric.class and /dev/null differ diff --git a/minetest-chat-bridge-irc-bot/out/production/minetest-chat-bridge-irc-bot/numeric/NumericLookup.class b/minetest-chat-bridge-irc-bot/out/production/minetest-chat-bridge-irc-bot/numeric/NumericLookup.class deleted file mode 100644 index c75edc1..0000000 Binary files a/minetest-chat-bridge-irc-bot/out/production/minetest-chat-bridge-irc-bot/numeric/NumericLookup.class and /dev/null differ diff --git a/minetest-chat-bridge-irc-bot/res/input.txt b/minetest-chat-bridge-irc-bot/res/input.txt deleted file mode 100644 index e69de29..0000000 diff --git a/minetest-chat-bridge-irc-bot/res/output.txt b/minetest-chat-bridge-irc-bot/res/output.txt deleted file mode 100644 index 6b5d26f..0000000 --- a/minetest-chat-bridge-irc-bot/res/output.txt +++ /dev/null @@ -1,13 +0,0 @@ -[JOI]LMD #800126 #mtchatbridgetest -[CGM]LMD LMD[irc],irc oofieduudles -[JOI]LMD #800126 #mtchatbridgetest -[JOI]LMD #800126 #mtchatbridgetest -[JOI]LMD #800126 #mtchatbridgetest -[CGM]LMD ,irc -[JOI]LMD #800126 #mtchatbridgetest -[CGM]LMD LMD[irc],irc ooof -[CGM]LMD ,irc -[JOI]LMD #800126 #mtchatbridgetest -[CGM]LMD ,irc -[JOI]LMD #800126 #mtchatbridgetest -[JOI]LMD #800126 #mtchatbridgetest diff --git a/minetest-chat-bridge-irc-bot/src/bridge/ProcessBridge.java b/minetest-chat-bridge-irc-bot/src/bridge/ProcessBridge.java deleted file mode 100644 index ed95524..0000000 --- a/minetest-chat-bridge-irc-bot/src/bridge/ProcessBridge.java +++ /dev/null @@ -1,127 +0,0 @@ -package bridge; - -import appguru.Main; - -import java.io.*; -import java.util.function.Consumer; - -public class ProcessBridge { - public long last_ping; - public void ping() { - last_ping=System.currentTimeMillis(); - } - public File out_file; - public File in; - public PrintWriter out; - - public ProcessBridge(File in, File out) throws IOException { - this.out = new PrintWriter(new BufferedWriter(new FileWriter(out, true))); - this.out_file=out; - this.in=in; - } - - public void kill(String reason) { - Main.OUT.println("INFO: "+reason); - FileWriter fw = null; - try { - fw = new FileWriter(in); - fw.write(""); - fw.close(); - } catch (IOException e) { - e.printStackTrace(); - } - - try { - Main.chat_bridge.send("QUIT :" + reason); - } catch (IOException e) { - e.printStackTrace(); - } - if (!Main.chat_bridge.socket.isClosed()) { - try { - Main.chat_bridge.socket.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } - - System.exit(0); - } - - public void write(String out) { - this.out.println(out); - } - - public void serve() { - new Thread() { - public void run() { - while(true) { - try { - Thread.sleep(20); - } catch (InterruptedException e) { - e.printStackTrace(); - } - out.flush(); - out.close(); - try { - out = new PrintWriter(new BufferedWriter(new FileWriter(out_file, true))); - } catch (IOException e) { - e.printStackTrace(); - } - } - } - }.start(); - } - - public void listen(Consumer line_consumer) { - ping(); - new Thread(() -> { - while(true) - - { - try { - Thread.sleep(20); - } catch (InterruptedException e) { - e.printStackTrace(); - } - BufferedReader r = null; - try { - r = new BufferedReader(new FileReader(in)); - } catch (FileNotFoundException e) { - e.printStackTrace(); - } - String line = null; - try { - line = r.readLine(); - } catch (IOException e) { - e.printStackTrace(); - } - boolean one_line = line != null; - while (line != null) { - if (line.startsWith("[PIN]")) { // A PING YAY - ping(); - } else if (line.startsWith("[KIL]")) { - kill("Minetest server shutting down; shutting down as well."); - } else { - line_consumer.accept(line); - } - - try { - line = r.readLine(); - } catch (IOException e) { - e.printStackTrace(); - } - } - if (System.currentTimeMillis()-last_ping > Main.PING_WAIT) { - kill("No ping during the last "+(Main.PING_WAIT/1000)+"s; shutting down."); - } - if (one_line) { - try { - FileWriter fw = new FileWriter(in);fw.write("");fw.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } - } - }).start(); - } -} diff --git a/mod.conf b/mod.conf index b790625..4ae8c2c 100644 --- a/mod.conf +++ b/mod.conf @@ -1,3 +1,6 @@ name=adv_chat +title=Advanced Chat +author=Lars Mueller aka LMD or appguru(eu) description=A library for advanced chatting. -depends=modlib \ No newline at end of file +depends=modlib +optional_depends=cmdlib \ No newline at end of file diff --git a/process_bridges.lua b/process_bridges.lua new file mode 100644 index 0000000..b1a02de --- /dev/null +++ b/process_bridges.lua @@ -0,0 +1,106 @@ + + +local socket = require("socket") +function set_socket(sock) + socket=sock + build_bridge = (socket and build_socket_bridge) or build_file_bridge + set_socket=nil +end + +local os_execute = os.execute +function set_os_execute(os_exec) + os_execute = os_exec + set_os_execute = nil +end + +local ping_timeout = 5 + +function build_socket_bridge(name, logs) + local server = socket.tcp() + server:bind("*", 0) + server:settimeout(5) + local ip, port = server:getsockname() + local logs=logs or minetest.get_worldpath().."/bridges/"..name.."/logs.txt" + minetest.mkdir(minetest.get_worldpath().."/bridges/"..name) + file_ext.create_if_not_exists(logs, "") + minetest.register_on_shutdown(function() + server:close() + end) + + local self = { + info={name=name, server=server, ip=ip, port=port}, + serve=function() end + } + + function self.start(process) + os_execute(process:format("", tostring(port), logs)) + self.client = server:accept() + self.start=nil + end + + function self.write(line) + self.client:send(line.."\n") + end + + function self.listen(line_consumer) + local status, err_msg=server:listen() + if status ~= 1 then + minetest.request_shutdown("adv_chat: "..name..": socket error: "..err_msg) + end + minetest.register_globalstep(function() + local available = socket.select({self.client}, nil, 0) + if next(available) then + local line, error = self.client:receive("*l") + if not error then + if string_ext.starts_with(line, "[KIL]") then + minetest.request_shutdown("adv_chat: "..name..": process terminated: "..line:sub(6)) + else + line_consumer(line) + end + elseif error=="closed" then + minetest.request_shutdown("adv_chat: "..name..": socket closed") + end + end + end) + end + + return self +end + +function build_file_bridge(name, input, output, logs) + file_ext.process_bridge_build(name, input, output, logs) + local self = { + info={name=name, ref=file_ext.process_bridges[name]}, + serve=function() + return file_ext.process_bridge_serve(name) + end, + write=function(line) + return file_ext.process_bridge_write(name, line) + end, + listen=function(line_consumer) + function consumer(line) + if string_ext.starts_with(line, "[PIN]") then + self.last_ping = minetest.get_gametime() + elseif string_ext.starts_with(line, "[KIL]") then + minetest.request_shutdown("adv_chat: "..name..": process terminated: "..line:sub(6)) + else + return line_consumer(line) + end + end + return file_ext.process_bridge_listen(name, consumer) + end + } + self.start=function(process) + file_ext.process_bridge_start(name, process, os_execute) + self.last_ping = minetest.get_gametime() + minetest.register_globalstep(function() + if minetest.get_gametime()-self.last_ping > ping_timeout then + minetest.request_shutdown("adv_chat: "..name..": process crashed (no ping during last "..ping_timeout.."s)") + end + end) + self.start = nil + end + return self +end + +build_bridge = (socket and build_socket_bridge) or build_file_bridge \ No newline at end of file diff --git a/test.lua b/test.lua index b61d3b8..9ba88d6 100644 --- a/test.lua +++ b/test.lua @@ -1,4 +1,4 @@ --- Test for hud_channels.lua +-- Test for hud_channels.lua & text_styles.lua register_hud_channel("score", {mode="stack", hud_base_position={x=0.6, y=0.5}, autoremove_interval=4}) @@ -6,8 +6,15 @@ mt_ext.register_globalstep(1, function() if math.random() > 0.5 then local choice=math.random(1, 3) local text=({"-", "+", "~"})[choice]..tostring(math.random(1, 50)) - minetest.chat_send_all("adv_chat:hud_channels test - sending "..minetest.get_color_escape_sequence(({"#FF0000","#00FF00", "#9999FF"})[choice])..text) + --minetest.chat_send_all("adv_chat:hud_channels test - sending "..minetest.get_color_escape_sequence(({"#FF0000","#00FF00", "#9999FF"})[choice])..text) add_msg_to_hud(nil, "score", {number=(({0xFF0000, 0x00FF00, 0x9999FF})[choice]),text=text, hud_elem_type="text",scale={x=100,y=100}, alignment = {x=-1,y=0}}) end end) + +minetest.after(3, function() + minetest.chat_send_all(convert_colors("This is a \x033test. \x03This is another \x037one.")) + minetest.chat_send_all(irc_to_markdown(string.char(0x02).."bold "..string.char(0x02).." "..string.char(0x1D).."italics")) + minetest.chat_send_all(minetest.write_json(markdown_to_irc("**bold***italics*"))) + --minetest.chat_send_all(markdown_to_irc()) +end) \ No newline at end of file diff --git a/text_styles.lua b/text_styles.lua new file mode 100644 index 0000000..be4e19d --- /dev/null +++ b/text_styles.lua @@ -0,0 +1,568 @@ +-- TODO work on this +-- Support for : Colors | Styles +-- Minetest : Yes | No +-- IRC : Yes | Yes +-- Discord : No | Yes + +-- Assumptions : +-- 1. IRC users only use IRC formatting chars and don't rely on Markdown +-- 2. Minetest users only use Minetest color codes + Markdown +-- 3. Discord users only rely on Markdown +-- Note : Markdown means Discord Markdown + +-- Resulting conversions : +-- Minetest colors -> IRC colors +-- IRC colors -> Minetest colors +-- Discord styles -> IRC styles +-- IRC styles -> Discord styles + +-- Resources : +-- https://modern.ircdocs.horse/formatting.html +-- https://support.discordapp.com/hc/en-us/articles/210298617-Markdown-Text-101-Chat-Formatting-Bold-Italic-Underline- +-- https://github.com/minetest/minetest/blob/master/doc/lua_api.txt + +-- Notes: +-- While code ("`code`") to IRC is straightforward (monospace), monospace to code is undefined behavior (concat code blocks? no code blocks at all?) + +local function is_digit(char) + return char >= "0" and char <= "9" +end + +local md_escape={ + ["*"]=true, ["_"]=true, ["~"]=true, ["`"]=true, ["\\"]=true, ["|"]=true +} + +function escape_markdown(text) + local res={} + for i=1, text:len() do + local char=text:sub(i,i) + if md_escape[char] then + table.insert(res, "\\") + end + table.insert(res, char) + end + return table.concat(res) +end + +-- minetest characters: color starter +local minetest_color_starter=string.char(0x1b) + +-- irc characters +local irc_escape_code=string.char(0x02)..string.char(0x02) +local irc_disable=string.char(0x0F) +local irc_color_reverse=string.char(0x16) +local irc_color_starter=string.char(0x03) +local irc_hex_color_starter=string.char(0x04) +local irc_bold=string.char(0x02) +local irc_italics=string.char(0x1D) +local irc_underlined=string.char(0x1F) +local irc_strikethrough=string.char(0x1F) +local irc_monospace=string.char(0x11) + +-- Converts Discord-style Markdown to IRC format +local irc_style_to_md={ + [irc_bold]="**", -- Bold + [irc_italics]="*", -- Italics + [irc_underlined]="__", -- Underlined + [irc_strikethrough]="~~", -- Strikethrough +} + +local md_style_to_irc=table_ext.flip(irc_style_to_md) + +local irc_escape={ + [irc_bold]=true, [irc_italics]=true, [irc_underlined]=true, [irc_strikethrough]="~~", [irc_disable]=true, [irc_color_reverse]=true, [irc_monospace]=true +} + +local function skip_color_code(text, i) + for j=1,2 do + if is_digit(text:sub(i+1,i+1)) then + i=i+1 + end + end + if text:sub(i+1,i+1) == "," and is_digit(text:sub(i+1,i+1)) then + i=i+1 + if is_digit(text:sub(i+1,i+1)) then + i=i+1 + end + end + return i +end + +function escape_irc(text) + local res={} + local i=1 + while i <= text:len() do + local char=text:sub(i,i) + if char == irc_color_starter then + i=skip_color_code(text, i) + elseif char == irc_hex_color_starter then + i=i+6 + elseif not irc_escape[char] then + table.insert(res, char) + end + i=i+1 + end + return table.concat(res) +end + +md_style_to_irc["_"]=md_style_to_irc["*"] + +local markdown_trie = trie.new() +for tag, toggle in pairs(md_style_to_irc) do + trie.insert(markdown_trie, tag, {name=tag, reversed=tag, opening=toggle, closing=toggle, space_sensitive=true}) +end +trie.insert(markdown_trie, "||", {name="||", reversed="||", opening=irc_color_starter.."01,01", closing=irc_color_starter, escape_func=is_digit}) +trie.insert(markdown_trie, "***", {name="***", reversed="***", opening=irc_italics..irc_bold, closing=irc_italics..irc_bold, + space_sensitive=true, conversion={ + ["*"]={name="**", reversed=irc_italics.."**", opening=irc_italics..irc_bold, closing=irc_bold}, + ["**"]={name="*", reversed=irc_bold.."*", opening=irc_italics..irc_bold, closing=irc_italics} + } +}) +trie.insert(markdown_trie, "___", {reversed="___", opening=irc_underlined..irc_italics, closing=irc_underlined..irc_italics, + space_sensitive=true, conversion={ + ["_"]={name="__", reversed=irc_italics.."__", opening=irc_italics..irc_underlined, closing=irc_underlined}, + ["__"]={name="_", reversed=irc_underlined.."_", opening=irc_underlined..irc_italics, closing=irc_italics} + } +}) + +local markdown_code_tag="`" + +-- Strips Markdown for Minetest. Won't strip invalid Markdown (like 1*1=2) +function strip_markdown(markdown) + local i=1 + local res={} + local tags={} + while i <= markdown:len() do + local char = markdown:sub(i,i) + if char == markdown_code_tag then + local closing = markdown:find("[^\\]`", i+1) + if closing then + table.insert(res, markdown:sub(i+1, closing)) + i=closing+1 + goto continue + end + elseif char == "\\" and md_escape[markdown:sub(i+1,i+1)] then + table.insert(res, markdown:sub(i+1,i+1)) + i=i+1 + goto continue + elseif char == " " then + if res[#res] and res[#res].space_sensitive then + res[#res] = res[#res].reversed + table.remove(tags) + end + else + local tag, offset = trie.find_longest(markdown_trie, markdown, i) + if tag then + for index, tag_index in table_ext.rpairs(tags) do + local conversion = res[tag_index].conversion and res[tag_index].conversion[tag.name] + if res[tag_index].name == tag.name or conversion then + if tag.space_sensitive and markdown:sub(i-1,i-1) == " " then + table.insert(res, tag.reversed) + else + local index_2 = #tags + while index_2 > index do + local tag_index_2 = tags[index_2] + res[tag_index_2] = res[tag_index_2].reversed + table.remove(tags) + index_2 = index_2 - 1 + end + if conversion then + res[tag_index] = conversion + else + table.remove(tags) + res[tag_index] = "" + end + end + i=offset + goto continue + end + end + table.insert(res, tag) + table.insert(tags, #res) + i=offset + goto continue + end + end + table.insert(res, char) + ::continue:: + i=i+1 + end + for _, tag in pairs(tags) do + if res[tag].reversed then + res[tag]=res[tag].reversed + end + end + return table.concat(res) +end + +function markdown_to_irc(markdown) + local i=1 + local res={} + local tags={} + while i <= markdown:len() do + local char = markdown:sub(i,i) + if char == markdown_code_tag then + local closing = markdown:find("[^\\]`", i+1) + if closing then + table.insert(res, irc_monospace) + table.insert(res, markdown:sub(i+1, closing)) + table.insert(res, irc_monospace) + i=closing+1 + goto continue + end + elseif char == "\\" and md_escape[markdown:sub(i+1,i+1)] then + table.insert(res, markdown:sub(i+1,i+1)) + i=i+1 + goto continue + elseif char == " " then + if res[#res] and res[#res].space_sensitive then + res[#res] = res[#res].reversed + table.remove(tags) + end + else + local tag, offset = trie.find_longest(markdown_trie, markdown, i) + if tag then + for index, tag_index in table_ext.rpairs(tags) do + local conversion = res[tag_index].conversion and res[tag_index].conversion[tag.name] + if res[tag_index].name == tag.name or conversion then + if tag.space_sensitive and markdown:sub(i-1,i-1) == " " then + table.insert(res, tag.reversed) + else + local index_2 = #tags + while index_2 > index do + local tag_index_2 = tags[index_2] + res[tag_index_2] = res[tag_index_2].reversed + table.remove(tags) + index_2 = index_2 - 1 + end + if conversion then + res[tag_index] = conversion + else + table.remove(tags) + res[tag_index] = res[tag_index].opening + end + table.insert(res, tag.closing) + if tag.escape_func and tag.escape_func(markdown:sub(offset+1,offset+1)) then + table.insert(res, irc_escape_code) + end + end + i=offset + goto continue + end + end + table.insert(res, tag) + table.insert(tags, #res) + i=offset + goto continue + end + end + table.insert(res, char) + ::continue:: + i=i+1 + end + for _, tag in pairs(tags) do + if res[tag].reversed then + res[tag]=res[tag].reversed + end + end + return table.concat(res) +end + +-- Converts Markdown to IRC +function markdown_to_irc(markdown) + local i=1 + local res={} + local tags={} + while i <= markdown:len() do + local char = markdown:sub(i,i) + if char == markdown_code_tag then + local closing = markdown:find("[^\\]`", i+1) + if closing then + table.insert(res, irc_monospace) + table.insert(res, markdown:sub(i+1, closing)) + table.insert(res, irc_monospace) + i=closing+1 + goto continue + end + elseif char == "\\" and md_escape[markdown:sub(i+1,i+1)] then + table.insert(res, markdown:sub(i+1,i+1)) + i=i+1 + goto continue + elseif char == " " then + if res[#res] and res[#res].space_sensitive then + res[#res] = res[#res].reversed + table.remove(tags) + end + else + local tag, offset = trie.find_longest(markdown_trie, markdown, i) + if tag then + for index, tag_index in table_ext.rpairs(tags) do + local conversion = res[tag_index].conversion and res[tag_index].conversion[tag.name] + if res[tag_index].name == tag.name or conversion then + if tag.space_sensitive and markdown:sub(i-1,i-1) == " " then + table.insert(res, tag.reversed) + else + local index_2 = #tags + while index_2 > index do + local tag_index_2 = tags[index_2] + res[tag_index_2] = res[tag_index_2].reversed + table.remove(tags) + index_2 = index_2 - 1 + end + if conversion then + res[tag_index] = conversion + else + table.remove(tags) + res[tag_index] = res[tag_index].opening + end + table.insert(res, tag.closing) + if tag.escape_func and tag.escape_func(markdown:sub(offset+1,offset+1)) then + table.insert(res, irc_escape_code) + end + end + i=offset + goto continue + end + end + table.insert(res, tag) + table.insert(tags, #res) + i=offset + goto continue + end + end + table.insert(res, char) + ::continue:: + i=i+1 + end + for _, tag in pairs(tags) do + if res[tag].reversed then + res[tag]=res[tag].reversed + end + end + return table.concat(res) +end + +-- Converts IRC text modifiers to Discord markdown, escaping included +function irc_to_markdown(irc) + local res={} + local active={} + local i=1 + while i <= irc:len() do + local char=irc:sub(i,i) + local md=irc_style_to_md[char] + if md then + while irc:sub(i+1)==" " do + table.insert(res, " ") + i=i+1 + end + for index, open_md in table_ext.rpairs(active) do + if open_md == md then + table.remove(active, index) + local i=#res + while res[i] == " " do + i=i-1 + end + table.insert(res, i+1, md) + goto noinsert + end + end + table.insert(active, md) + table.insert(res, md) + ::noinsert:: + elseif char == irc_disable then + for _, md in table_ext.rpairs(active) do + table.insert(res, md) + end + active={} + elseif md_escape[char] then + table.insert(res, "\\") + elseif char == irc_color_starter then --color + if irc:sub(i+2, i+2) == "," then + if is_digit(irc:sub(i+1, i+1)) and irc:sub(i+1, i+1) == irc:sub(i+3, i+3) and not is_digit(irc:sub(i+4, i+4)) then + table.insert(active, "||") + table.insert(res, md) + end + elseif irc:sub(i+3, i+3) == "," then + local fg, bg = irc:sub(i+1, i+2), irc:sub(i+4, i+5) + if is_digit(irc:sub(i+1, i+1)) and is_digit(irc:sub(i+2, i+2)) and fg == bg and fg ~= "99" then + table.insert(active, "||") + table.insert(res, md) + end + else + for index, open_md in table_ext.rpairs(active) do + if open_md == "||" then + table.remove(active, index) + local j=#res + while res[j] == " " do + j=j-1 + end + table.insert(res, j+1, md) + end + end + end + i=skip_color_code(text, i) + else + table.insert(res, char) + end + i=i+1 + end + for _, thing in table_ext.rpairs(active) do + table.insert(res, thing) + end + return table.concat(res) +end + +-- Converts IRC colors to Minetest colors, background colors included +local user_defined_colors={'000000','0000FF','00FF00','FF0000', '654321','FF00FF','FFA500','FFFF00', +'90EE90','00FFFF','E0FFFF','ADD8E6','FF69B4','808080','D3D3D3'} +user_defined_colors[0]='FFFFFF' +local conversion_table={[16]='470000',[17]='472100',[18]='474700',[19]='324700',[20]='004700',[21]='00472c',[22]='004747',[23]='002747',[24]='000047',[25]='2e0047',[26]='470047',[27]='47002a',[28]='740000',[29]='743a00',[30]='747400',[31]='517400',[32]='007400',[33]='007449',[34]='007474',[35]='004074',[36]='000074',[37]='4b0074',[38]='740074',[39]='740045',[40]='b50000',[41]='b56300',[42]='b5b500',[43]='7db500',[44]='00b500',[45]='00b571',[46]='00b5b5',[47]='0063b5',[48]='0000b5',[49]='7500b5',[50]='b500b5',[51]='b5006b',[52]='ff0000',[53]='ff8c00',[54]='ffff00',[55]='b2ff00',[56]='00ff00',[57]='00ffa0',[58]='00ffff',[59]='008cff',[60]='0000ff',[61]='a500ff',[62]='ff00ff',[63]='ff0098',[64]='ff5959',[65]='ffb459',[66]='ffff71',[67]='cfff60',[68]='6fff6f',[69]='65ffc9',[70]='6dffff',[71]='59b4ff',[72]='5959ff',[73]='c459ff',[74]='ff66ff',[75]='ff59bc',[76]='ff9c9c',[77]='ffd39c',[78]='ffff9c',[79]='e2ff9c',[80]='9cff9c',[81]='9cffdb',[82]='9cffff',[83]='9cd3ff',[84]='9c9cff',[85]='dc9cff',[86]='ff9cff',[87]='ff94d3',[88]='000000',[89]='131313',[90]='282828',[91]='363636',[92]='4d4d4d',[93]='656565',[94]='818181',[95]='9f9f9f',[96]='bcbcbc',[97]='e2e2e2',[98]='ffffff'} +function hex_to_table(color) + return {tonumber(color:sub(1, 2), 16), tonumber(color:sub(3, 4), 16), tonumber(color:sub(5, 6), 16)} +end +function table_to_hex(color) + return string.format("%02X", color[1])..string.format("%02X", color[2])..string.format("%02X", color[3]) +end +table_ext.add_all(conversion_table, user_defined_colors) +local reversed = table_ext.flip(conversion_table) +for k, v in pairs(reversed) do + reversed[string.upper(k)]=v + reversed[string.lower(k)]=v +end +reversed.FFFFFF=0 +reversed.ffffff=0 + +function irc_to_minetest(irc) + local fg, background + local reversed = false + local rope={} + local i=1 + while i <= irc:len() do + local char = irc:sub(i,i) + if char == irc_color_starter then + local j=i+1 + if is_digit(irc:sub(j,j)) then + fg=irc:byte(j,j)-string.byte("0") + i=j + j=j+1 + if is_digit(irc:sub(j,j)) then + fg=fg*10+(irc:byte(j,j)-string.byte("0")) + i=j + j=j+1 + end + local bg + if irc:sub(j,j) == "," and is_digit(irc:sub(j+1,j+1)) then + local bg=irc:byte(j,j)-string.byte("0") + i=j + j=j+1 + if is_digit(irc:sub(j,j)) then + bg=bg*10+(irc:byte(j,j)-string.byte("0")) + i=j + end + end + if bg then + -- no proper implementation for background escape sequences yet - see the "Escape sequences" part of the Lua API + -- table.insert(rope, minetest.get_background_escape_sequence("#"..conversion_table[bg])) + background = bg + end + if reversed then + table.insert(rope, minetest.get_color_escape_sequence("#"..conversion_table[bg])) + else + table.insert(rope, minetest.get_color_escape_sequence("#"..conversion_table[fg])) + end + else + table.insert(rope, minetest.get_color_escape_sequence("#FFFFFF")) + end + elseif char == irc_disable then + table.insert(rope, minetest.get_color_escape_sequence("#FFFFFF")) + background = nil + elseif char == irc_color_reverse then + reversed = not reversed + if background then + table.insert(rope, minetest.get_color_escape_sequence("#"..conversion_table[background])) + -- no proper implementation for background escape sequences yet - see the "Escape sequences" part of the Lua API + -- table.insert(rope, minetest.get_background_escape_sequence("#"..conversion_table[fg])) + background, fg = fg, background + end + elseif not irc_style_to_md[char] and char ~= irc_monospace then + table.insert(rope, char) + end + i=i+1 + end + return table.concat(rope) +end + +local color_conv = bridges.irc.convert_minetest_colors + +if color_conv == "hex" then -- always use hex, no matter what + function convert_color_to_irc(color) + return nil, irc_hex_color_starter..color + end +elseif color_conv == "hex_safer" then + function convert_color_to_irc(color) + -- prefer simple colors + local rev = reversed[color] + if rev then + return ",", irc_color_starter..((rev < 10 and "0") or "")..tostring(rev) + end + return nil, irc_hex_color_starter..color + end +elseif color_conv == "disabled" then + function convert_color_to_irc(color) + return error("Color conversion to IRC is disabled. Check your config.") + end +else + local color_chooser + if color_conv == "safest" then + local closest_color_basic = closest_color_finder(table_ext.process(user_defined_colors, function(k, color) return hex_to_table(color) end)) + color_chooser = closest_color_basic + else + local closest_color_extended = closest_color_finder(table_ext.process(conversion_table, function(k, color) return hex_to_table(color) end)) + color_chooser = closest_color_extended + end + function convert_color_to_irc(color) + local rev = reversed[color] + if rev and rev <= 15 then + return function(c) return c=="," end, irc_color_starter..((rev < 10 and "0") or "")..tostring(reversed[color]) + end + local closest = reversed[table_to_hex(color_chooser(hex_to_table(color)))] + if not closest then error(table_to_hex(color_chooser(hex_to_table(color)))) end + return function(c) return c=="," end, irc_color_starter..((closest < 10 and "0") or "")..tostring(closest) + end +end + +local old_convert_color_to_irc = convert_color_to_irc +function convert_color_to_irc(color) + if color:lower() == "ffffff" then -- treat white as color reset, by default. TODO think of doing the same for close colors (can we?) + return function(c) return c >= "0" and c <= "9" end, irc_color_starter + end + return old_convert_color_to_irc(color) +end + +function minetest_to_irc(message) + local i=1 + local res={} + while i <= message:len() do + if message:sub(i,i) == minetest_color_starter and message:sub(i+1, i+4) == "(c@#" and message:sub(i+11, i+11) == ")" then + local color = message:sub(i+5, i+10) + for j=1, 6 do + local c = color:sub(j, j):lower() + if not (c >= "0" and c <= "9") and not (c >= "a" and c <= "f") then + goto continue + end + end + local needs_escape, color = convert_color_to_irc(color) + table.insert(res, color) + if needs_escape and needs_escape(message:sub(i+12, i+12)) then + table.insert(res, irc_escape_code) + end + i=i+11 + goto continue_loop + ::continue:: + end + table.insert(res, message:sub(i,i)) + ::continue_loop:: + i=i+1 + end + return table.concat(res) +end \ No newline at end of file diff --git a/trie.lua b/trie.lua new file mode 120000 index 0000000..243a921 --- /dev/null +++ b/trie.lua @@ -0,0 +1 @@ +/home/lars/.minetest/mods/cmdlib/trie.lua \ No newline at end of file diff --git a/unicode.lua b/unicode.lua index cfe949b..462d570 100644 --- a/unicode.lua +++ b/unicode.lua @@ -1,27 +1,30 @@ -unicode_patterns={"\\u","U%+"} +unicode_patterns={"\\u","U+"} function parse_utf8_codepoints(message, pattern) local last_index=0 - local begin,index=string.find(message,pattern) + local begin,index=string.find(message,pattern,0,true) local rope={} while index do local number="" - while index <= index+4 do - index=index+1 - local char=string.upper(string.sub(message, index,index)) - if char:len() == 0 or not string_ext.is_hexadecimal(string.byte(char)) then + local i=index + while i <= i+4 do + i=i+1 + local char=string.upper(string.sub(message,i,i)) + if char == "" or not ((char >= "0" and char <= "9") or (char >= "A" and char <= "F")) then break end number=number..char end number=tonumber(number, 16) - local utf_8_char=string_ext.utf8(number) - if utf_8_char then - table.insert(rope, message:sub(last_index, begin-1)) - table.insert(rope, utf_8_char) - last_index=index + if number then + local utf_8_char=string_ext.utf8(number) + if utf_8_char then + table.insert(rope, message:sub(last_index, begin-1)) + table.insert(rope, utf_8_char) + last_index=i + end end - begin,index=string.find(message,pattern, index) + begin,index=string.find(message,pattern,index,true) end table.insert(rope, message:sub(last_index)) return table.concat(rope, "")