diff --git a/Jenkinsfile b/Jenkinsfile index 31df65f..5625357 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -9,12 +9,12 @@ pipeline { stages { stage('Build') { steps { - sh 'mvn -B -DskipTests clean package' + sh 'mvn package' } } stage('Test') { steps { - echo 'Skip testing...' + sh 'mvn test' } } stage('Deploy') { diff --git a/README.md b/README.md index 3015c0d..f09b5a6 100644 --- a/README.md +++ b/README.md @@ -2,12 +2,22 @@ Another one IRC bot in deep-deep beta. +## License +Source code spreads under the GNU General Public License v3 or higher. Please see LICENSE file. + +####Used libraries: +* Apache commons CLI: https://commons.apache.org/proper/commons-cli/ +* GSON: https://github.com/google/gson +* sqliteJDBC: https://bitbucket.org/xerial/sqlite-jdbc +* mongodb-driver-sync: https://mongodb.github.io/mongo-java-driver/3.9/ +* JUnit 5: https://junit.org/junit5/ + ## Usage -` -c, --configuration [ ...] Read Config` - -` -g, --generate [name.config] Generate Config` - -` -v, --version Get application version` +``` + -c, --configuration [ ...] Read Config + -g, --generate [name.config] Generate Config + -v, --version Get application version +``` #### Configuration settings "userNickAuthStyle": "rusnet" or "freenode" * rusnet - send '/nickserv IDENTIFY mySecretPass' @@ -18,16 +28,7 @@ Another one IRC bot in deep-deep beta. * SQLite - use /yourPathSet/server.db (or /yourPathSet/yourFileName.db) sqlite file. * MongoDB - write files to MongoDB. See ConfigurationExamples folder. -## License -Source code spreads under the GNU General Public License v3 or higher. Please see LICENSE file. - -Used libraries: -* GSON: https://github.com/google/gson -* sqliteJDBC: https://bitbucket.org/xerial/sqlite-jdbc -* mongodb-driver-sync: https://mongodb.github.io/mongo-java-driver/3.9/ - - -## TODO: +### TODO: - [ ] Documentation - [ ] Code refactoring - [ ] QA: good regression testing diff --git a/pom.xml b/pom.xml index 1730bea..9ebae2c 100644 --- a/pom.xml +++ b/pom.xml @@ -4,13 +4,14 @@ 4.0.0 loper InnaIrcBot - 0.7-SNAPSHOT + 0.8-SNAPSHOT jar InnaIrcBot UTF-8 + org.xerial @@ -30,31 +31,78 @@ 3.9.1 compile + + commons-cli + commons-cli + 1.4 + compile + + + org.junit.jupiter + junit-jupiter-engine + 5.5.2 + test + + + org.junit.jupiter + junit-jupiter-api + 5.5.2 + test + + + org.junit.jupiter + junit-jupiter-params + 5.5.2 + test + - + + + - org.apache.maven.plugins - maven-compiler-plugin - 3.1 - - 1.8 - 1.8 - - - **/Temporary/* - - - - + org.apache.maven.plugins + maven-surefire-plugin + 2.22.2 + + + org.apache.maven.plugins + maven-failsafe-plugin + 2.22.2 + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + 1.8 + 1.8 + + + **/Temporary/* + + + + + + maven-jar-plugin + 2.4 + + + default-jar + none + + + org.apache.maven.plugins maven-assembly-plugin - 2.5.3 + 3.1.0 - InnaIrcBot.BotStart + InnaIrcBot.Main @@ -72,6 +120,7 @@ - + + \ No newline at end of file diff --git a/src/META-INF/MANIFEST.MF b/src/META-INF/MANIFEST.MF deleted file mode 100644 index fc38ee6..0000000 --- a/src/META-INF/MANIFEST.MF +++ /dev/null @@ -1,3 +0,0 @@ -Manifest-Version: 1.0 -Main-Class: InnaIrcBot.BotStart - diff --git a/src/main/java/InnaIrcBot/BotStart.java b/src/main/java/InnaIrcBot/BotStart.java index 26ebf64..5fc4565 100644 --- a/src/main/java/InnaIrcBot/BotStart.java +++ b/src/main/java/InnaIrcBot/BotStart.java @@ -1,38 +1,97 @@ -/** - * InnaIrcBot - * @author Dmitry Isaenko - * Russia, 2018-2019. - * */ package InnaIrcBot; -import InnaIrcBot.Config.StorageReader; +import InnaIrcBot.config.ConfigurationFileGenerator; +import org.apache.commons.cli.*; public class BotStart { -//TODO: flood control -//TODO: setDaemon(true) -//TODO: multiple connections to one server not allowed + public BotStart(String[] args){ - public static void main(String[] args){ - if (args.length != 0) { - if (args.length >= 2) { - if (args[0].equals("--configuration") || args[0].equals("-c")) { - new Connections(args); - } else if (args[0].equals("--generate") || args[0].equals("-g")) { - StorageReader.generateDefaultConfig(args[1]); - } + final Options cliOptions = createCliOptions(); + CommandLineParser cliParser = new DefaultParser(); + + try{ + CommandLine cli = cliParser.parse(cliOptions, args); + if (cli.hasOption('v') || cli.hasOption("version")){ + handleVersion(); + return; } - else if (args[0].equals("--generate") || args[0].equals("-g")){ - StorageReader.generateDefaultConfig(null); + if (cli.hasOption("c") || cli.hasOption("configuration")){ + final String[] arguments = cli.getOptionValues("configuration"); + for (String a: arguments) + ConnectionsBuilder.buildConnections(arguments); + return; } - else if (args[0].equals("--version") || args[0].equals("-v")) { - System.out.println(GlobalData.getAppVersion()); + if (cli.hasOption("g") || cli.hasOption("generate")){ + final String[] arguments = cli.getOptionValues("generate"); + handleGenerate(arguments); + return; } + + handleHelp(cliOptions); } - else { - System.out.println("Usage:\n" - +" \t-c, --configuration [ ...]\tRead Config\n" - +"\t-g, --generate\t[name.config]\t\t\t\tGenerate Config\n" - +"\t-v, --version\t\t\t\t\t\tGet application version"); + catch (ParseException pe){ + handleHelp(cliOptions); + } + catch (Exception e){ + System.out.println("Error: "); + e.printStackTrace(); } } -} \ No newline at end of file + private Options createCliOptions(){ + final Options options = new Options(); + + final Option helpOption = Option.builder("h") + .longOpt("help") + .desc("Show this help") + .hasArg(false) + .build(); + + final Option versionOption = Option.builder("v") + .longOpt("version") + .desc("Show application version") + .hasArg(false) + .build(); + + final Option configurationOption = Option.builder("c") + .longOpt("configuration") + .desc("Start with configuration") + .hasArg(true) + .build(); + + final Option generateOption = Option.builder("g") + .longOpt("generate") + .desc("Create configuration template") + .hasArg(true) + .numberOfArgs(1) + .build(); + + final OptionGroup group = new OptionGroup(); + group.addOption(helpOption); + group.addOption(versionOption); + group.addOption(configurationOption); + group.addOption(generateOption); + + options.addOptionGroup(group); + + return options; + } + + private void handleVersion(){ + System.out.println(GlobalData.getAppVersion()); + } + + private void handleHelp(Options cliOptions){ + new HelpFormatter().printHelp( + 120, + "InnaIrcBot.jar [OPTION]... [FILE]...", + "options:", + cliOptions, + "\n"); + } + private void handleGenerate(String[] arguments){ + if (arguments.length > 0) + ConfigurationFileGenerator.generate(arguments[0]); + else + ConfigurationFileGenerator.generate(null); + } +} diff --git a/src/main/java/InnaIrcBot/Commanders/CTCPHelper.java b/src/main/java/InnaIrcBot/Commanders/CTCPHelper.java index f678338..af5a1a6 100644 --- a/src/main/java/InnaIrcBot/Commanders/CTCPHelper.java +++ b/src/main/java/InnaIrcBot/Commanders/CTCPHelper.java @@ -12,19 +12,12 @@ public class CTCPHelper { private static final CTCPHelper instance = new CTCPHelper(); public static CTCPHelper getInstance(){ return instance; } + private final HashMap> waitersQueue = new HashMap<>(); + private CTCPHelper(){} - private HashMap> waitersQueue = new HashMap<>(); - - void registerRequest(String requesterServer, String requesterChanelOrUser, String ctcpType, String targetObject, String notFoundMessage){ - /* - System.out.println("Server:|"+requesterServer+"|"); - System.out.println("Chanel:|"+requesterChanelOrUser+"|"); - System.out.println("Type :|"+ctcpType+"|"); - System.out.println("Regexp:|"+targetObject+"|"); - System.out.println("NF_mes:|"+notFoundMessage+"|\n"); // could be empty - */ - if (!waitersQueue.containsKey(requesterServer)){ // TODO: meeeeeeeehh.. looks bad - waitersQueue.put(requesterServer, new ArrayList<>()); + void registerRequest(String server, String requesterChanelOrUser, String ctcpType, String target, String notFoundMessage){ + if (!waitersQueue.containsKey(server)){ // TODO: meeeeeeeehh.. looks bad + waitersQueue.put(server, new ArrayList<>()); } switch (ctcpType){ @@ -34,107 +27,67 @@ public class CTCPHelper { case "SOURCE": case "TIME": case "USERINFO": - waitersQueue.get(requesterServer).add(new CtcpRequest(requesterChanelOrUser, targetObject, notFoundMessage, ctcpType)); - StreamProvider.writeToStream(requesterServer, "PRIVMSG "+targetObject+" :\u0001"+ctcpType+"\u0001"); + waitersQueue.get(server).add(new CTCPRequest(requesterChanelOrUser, target, notFoundMessage, ctcpType)); + StreamProvider.writeToStream(server, "PRIVMSG "+target+" :\u0001"+ctcpType+"\u0001"); break; case "PING": // TODO - waitersQueue.get(requesterServer).add(new CtcpRequest(requesterChanelOrUser, targetObject, notFoundMessage, ctcpType)); - StreamProvider.writeToStream(requesterServer, "PRIVMSG "+targetObject+" :\u0001PING inna\u0001"); + waitersQueue.get(server).add(new CTCPRequest(requesterChanelOrUser, target, notFoundMessage, ctcpType)); + StreamProvider.writeToStream(server, "PRIVMSG "+target+" :\u0001PING inna\u0001"); break; } } public void handleCtcpReply(String serverReplied, String whoReplied, String whatReplied){ - //System.out.println("Reply serv:|"+serverReplied+"|\nwho:|"+whoReplied+"|\nwhat:|"+whatReplied+"|"); LocalDateTime currentTime = LocalDateTime.now(); - if (waitersQueue.containsKey(serverReplied)){ - ListIterator iterator = waitersQueue.get(serverReplied).listIterator(); - CtcpRequest current; - String chanelOrUser; - while (iterator.hasNext()){ - current = iterator.next(); - if (current.isValid(currentTime)){ - chanelOrUser = current.getRequesterChanelOrUser(whoReplied); - if ( chanelOrUser != null && current.getType().equals(whatReplied.replaceAll("\\s.*$", ""))) { - if (whatReplied.equals("PING inna")) - StreamProvider.writeToStream(serverReplied, "PRIVMSG " + chanelOrUser + " :" + whoReplied + ": " + Duration.between(current.getCreationTime(), currentTime).toMillis()+"ms"); - else - StreamProvider.writeToStream(serverReplied, "PRIVMSG " + chanelOrUser + " :" + whoReplied + ": " + whatReplied); - iterator.remove(); - } - } - else{ - //System.out.println("Drop outdated user"); - iterator.remove(); - } + + if (! waitersQueue.containsKey(serverReplied)) + return; + + ListIterator iterator = waitersQueue.get(serverReplied).listIterator(); + + while (iterator.hasNext()){ + CTCPRequest current = iterator.next(); + if (current.isValid(currentTime)){ + String channelOrUser = current.getRequesterChanelOrUser(whoReplied); + + if (channelOrUser == null || ! current.getType().equals(whatReplied.replaceAll("\\s.*$", ""))) + continue; + + if (whatReplied.equals("PING inna")) + StreamProvider.writeToStream(serverReplied, "PRIVMSG " + channelOrUser + " :" + whoReplied + ": " + + Duration.between(current.getCreationTime(), currentTime).toMillis()+"ms"); + else + StreamProvider.writeToStream(serverReplied, "PRIVMSG " + channelOrUser + " :" + whoReplied + ": " + whatReplied); } + iterator.remove(); + } } public void handleErrorReply(String serverReplied, String whoNotFound){ //System.out.println("Reply serv:|"+serverReplied+"|\nwho:|"+whoNotFound+"|\n"); LocalDateTime currentTime = LocalDateTime.now(); - if (waitersQueue.containsKey(serverReplied)){ - ListIterator iterator = waitersQueue.get(serverReplied).listIterator(); - CtcpRequest current; - String chanelOrUser; - String notFoundMessage; - while (iterator.hasNext()){ - current = iterator.next(); - if (current.isValid(currentTime)){ - chanelOrUser = current.getRequesterChanelOrUser(whoNotFound); - if ( chanelOrUser != null) { - notFoundMessage = current.getNotFoundMessage(whoNotFound); - if ( notFoundMessage != null) { - System.out.println(serverReplied + " PRIVMSG " + chanelOrUser + " :" + notFoundMessage + whoNotFound); - StreamProvider.writeToStream(serverReplied, "PRIVMSG " + chanelOrUser + " :" + notFoundMessage + whoNotFound); - } - iterator.remove(); - } - } - else{ - //System.out.println("Drop outdated user: 401"); - iterator.remove(); - } - } + if (! waitersQueue.containsKey(serverReplied)) + return; + + ListIterator iterator = waitersQueue.get(serverReplied).listIterator(); + + while (iterator.hasNext()){ + CTCPRequest current = iterator.next(); + if (! current.isValid(currentTime)) + iterator.remove(); + String channelOrUser = current.getRequesterChanelOrUser(whoNotFound); + + if (channelOrUser == null) + continue; + + String notFoundMessage = current.getNotFoundMessage(whoNotFound); + + if (notFoundMessage == null) + continue; + + System.out.println(serverReplied + " PRIVMSG " + channelOrUser + " :" + notFoundMessage + whoNotFound); + StreamProvider.writeToStream(serverReplied, "PRIVMSG " + channelOrUser + " :" + notFoundMessage + whoNotFound); } } } - -class CtcpRequest { - private String requesterChanelOrUser; - private String userResponding; - private LocalDateTime initiatedTime; - private String notFoundMessage; - private String CTCPtype; - CtcpRequest (String whoIsAsking, String userResponding, String notFoundMessage, String CTCPType){ - this.initiatedTime = LocalDateTime.now(); - this.requesterChanelOrUser = whoIsAsking; - this.userResponding = userResponding; - this.notFoundMessage = notFoundMessage; - this.CTCPtype = CTCPType; - } - String getRequesterChanelOrUser(String userResponds){ // return channel name - if (userResponding.equals(userResponds)) - return requesterChanelOrUser; - else - return null; - } - boolean isValid(LocalDateTime currentTime){ - return currentTime.isBefore(initiatedTime.plusSeconds(5)); - } - - String getType(){ return CTCPtype; } - - LocalDateTime getCreationTime(){ return initiatedTime; } - - String getNotFoundMessage(String userResponds){ - if (this.userResponding.equals(userResponds)) - if (notFoundMessage.isEmpty()) - return null; - else - return notFoundMessage; - else - return null; - } -} diff --git a/src/main/java/InnaIrcBot/Commanders/CTCPRequest.java b/src/main/java/InnaIrcBot/Commanders/CTCPRequest.java new file mode 100644 index 0000000..7cbc6e0 --- /dev/null +++ b/src/main/java/InnaIrcBot/Commanders/CTCPRequest.java @@ -0,0 +1,39 @@ +package InnaIrcBot.Commanders; + +import java.time.LocalDateTime; + +class CTCPRequest { + private final String requesterChanelOrUser; + private final String userResponding; + private final LocalDateTime initiatedTime; + private final String notFoundMessage; + private final String CTCPtype; + + CTCPRequest(String whoIsAsking, String userResponding, String notFoundMessage, String CTCPType){ + this.initiatedTime = LocalDateTime.now(); + this.requesterChanelOrUser = whoIsAsking; + this.userResponding = userResponding; + this.notFoundMessage = notFoundMessage; + this.CTCPtype = CTCPType; + } + + String getRequesterChanelOrUser(String userResponds){ // return channel name + if (userResponding.equals(userResponds)) + return requesterChanelOrUser; + return null; + } + + boolean isValid(LocalDateTime currentTime){ + return currentTime.isBefore(initiatedTime.plusSeconds(5)); + } + + String getType(){ return CTCPtype; } + + LocalDateTime getCreationTime(){ return initiatedTime; } + + String getNotFoundMessage(String userResponds){ + if (userResponding.equals(userResponds) && ! notFoundMessage.isEmpty()) + return notFoundMessage; + return null; + } +} \ No newline at end of file diff --git a/src/main/java/InnaIrcBot/Commanders/ChanelCommander.java b/src/main/java/InnaIrcBot/Commanders/ChanelCommander.java index 6091811..525bd4b 100644 --- a/src/main/java/InnaIrcBot/Commanders/ChanelCommander.java +++ b/src/main/java/InnaIrcBot/Commanders/ChanelCommander.java @@ -8,17 +8,18 @@ import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; +import java.util.concurrent.BlockingQueue; import java.util.regex.Pattern; //TODO: FLOOD, JOIN FLOOD // TODO: @ configuration level: if in result we have empty string, no need to pass it to server public class ChanelCommander implements Runnable { - private BufferedReader reader; - private String server; - private String chanel; + private final BlockingQueue streamQueue; + private final String server; + private final String channel; //TODO: add timers - private HashMap joinMap; // Mask(Pattern) ->, Action | Where Action[0] could be: raw - private HashMap msgMap; // Mask(Pattern) ->, Action | Where Action[0] could be: raw - private HashMap nickMap; // Mask(Pattern) ->, Action | Where Action[0] could be: raw + private final HashMap joinMap; // Mask(Pattern) ->, Action | Where Action[0] could be: raw + private final HashMap msgMap; // Mask(Pattern) ->, Action | Where Action[0] could be: raw + private final HashMap nickMap; // Mask(Pattern) ->, Action | Where Action[0] could be: raw private boolean joinFloodTrackNeed = false; private JoinFloodHandler jfh; @@ -26,10 +27,10 @@ public class ChanelCommander implements Runnable { private boolean joinCloneTrackNeed = false; // todo:fix private JoinCloneHandler jch; - public ChanelCommander(BufferedReader streamReader, String serverName, String chan, String configFilePath){ - this.reader = streamReader; + public ChanelCommander(BlockingQueue stream, String serverName, String channel, String configFilePath){ + this.streamQueue = stream; this.server = serverName; - this.chanel = chan; + this.channel = channel; this.joinMap = new HashMap<>(); this.msgMap = new HashMap<>(); @@ -39,13 +40,13 @@ public class ChanelCommander implements Runnable { @Override public void run() { - System.out.println("Thread for ChanelCommander started"); // TODO:REMOVE DEBUG - String data; - String[] dataStrings; + System.out.println("["+LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"))+"] ChanelCommander thread " + +server+":"+this.channel +" started");// TODO:REMOVE DEBUG try { - while ((data = reader.readLine()) != null) { - dataStrings = data.split(" ",3); - //event initiatorArg messageArg + while (true) { + String data = streamQueue.take(); + String[] dataStrings = data.split(" ",3); + switch (dataStrings[0]) { case "NICK": nickCame(dataStrings[2]+dataStrings[1].replaceAll("^.+?!","!")); @@ -75,8 +76,9 @@ public class ChanelCommander implements Runnable { break; } } - } catch (java.io.IOException e){ - System.out.println("Internal issue: thread ChanelCommander->run() caused I/O exception:\n\t"+e); // TODO: reconnect + } + catch (InterruptedException ie){ + System.out.println("Internal issue: thread ChanelCommander->run() interrupted.\n\t"); // TODO: reconnect } System.out.println("Thread for ChanelCommander ended"); // TODO:REMOVE DEBUG } @@ -175,7 +177,7 @@ public class ChanelCommander implements Runnable { String objectToCtcp = arg1.trim().replaceAll(objectRegexp, ""); // note: trim() ? if (!objectToCtcp.isEmpty()){ if (CTCPType.startsWith("\\c")) - CTCPHelper.getInstance().registerRequest(server, chanel, CTCPType.substring(2).toUpperCase(), objectToCtcp, whatToSendStringBuilder.toString()); + CTCPHelper.getInstance().registerRequest(server, channel, CTCPType.substring(2).toUpperCase(), objectToCtcp, whatToSendStringBuilder.toString()); else CTCPHelper.getInstance().registerRequest(server, simplifyNick(arg2), CTCPType.substring(2).toUpperCase(), objectToCtcp, whatToSendStringBuilder.toString()); } @@ -201,40 +203,40 @@ public class ChanelCommander implements Runnable { executiveStr.append(" :"); } else { - executiveStr.append(chanel); + executiveStr.append(channel); executiveStr.append(" :"); executiveStr.append(simplifyNick(who)); executiveStr.append(": "); } - for (int i = 0; i < messages.length; i++){ - if ( ! messages[i].startsWith("\\")) - executiveStr.append(messages[i]); - else if (messages[i].equals("\\time")) // TODO: remove this shit + for (String message : messages) { + if (!message.startsWith("\\")) + executiveStr.append(message); + else if (message.equals("\\time")) // TODO: remove this shit executiveStr.append(LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"))); } //System.out.println(executiveStr.toString()); //TODO: debug StreamProvider.writeToStream(server, executiveStr.toString()); } private void banAction(String whom){ - StreamProvider.writeToStream(server, "MODE "+chanel+" +b "+simplifyNick(whom)+"*!*@*"); - StreamProvider.writeToStream(server, "MODE "+chanel+" +b "+"*!*@"+whom.replaceAll("^.+@","")); + StreamProvider.writeToStream(server, "MODE "+ channel +" +b "+simplifyNick(whom)+"*!*@*"); + StreamProvider.writeToStream(server, "MODE "+ channel +" +b "+"*!*@"+whom.replaceAll("^.+@","")); } private void voiceAction(String whom){ - StreamProvider.writeToStream(server, "MODE "+chanel+" +v "+simplifyNick(whom)); + StreamProvider.writeToStream(server, "MODE "+ channel +" +v "+simplifyNick(whom)); } private void kickAction(String[] messages, String whom){ StringBuilder executiveStr = new StringBuilder(); executiveStr.append("KICK "); - executiveStr.append(chanel); + executiveStr.append(channel); executiveStr.append(" "); executiveStr.append(simplifyNick(whom)); executiveStr.append(" :"); - for (int i = 0; i < messages.length; i++){ - if ( ! messages[i].startsWith("\\")) - executiveStr.append(messages[i]); - else if (messages[i].equals("\\time")) + for (String message : messages) { + if (!message.startsWith("\\")) + executiveStr.append(message); + else if (message.equals("\\time")) executiveStr.append(LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"))); } StreamProvider.writeToStream(server, executiveStr.toString()); @@ -256,12 +258,13 @@ public class ChanelCommander implements Runnable { break; case "joinfloodcontrol": if (!directive[1].isEmpty() && !directive[2].isEmpty() && Pattern.matches("^[0-9]+?$", directive[1].trim()) && Pattern.matches("^[0-9]+?$", directive[2].trim())) { - int events = Integer.valueOf(directive[1].trim()); - int timeFrame = Integer.valueOf(directive[2].trim()); + int events = Integer.parseInt(directive[1].trim()); + int timeFrame = Integer.parseInt(directive[2].trim()); if (events > 0 && timeFrame > 0) { - jfh = new JoinFloodHandler(events, timeFrame, server, chanel); + jfh = new JoinFloodHandler(events, timeFrame, server, channel); joinFloodTrackNeed = true; - } else { + } + else { System.out.println("Internal issue: thread ChanelCommander->parse(): 'Number of events' and/or 'Time Frame in seconds' should be greater than 0"); } } @@ -270,9 +273,9 @@ public class ChanelCommander implements Runnable { break; case "joinclonecontrol": if (!directive[1].isEmpty() && !directive[2].isEmpty() && Pattern.matches("^[0-9]+?$", directive[1].trim())) { - int events = Integer.valueOf(directive[1].trim()); + int events = Integer.parseInt(directive[1].trim()); if (events > 0){ - jch = new JoinCloneHandler(directive[2], events, server, chanel); // TODO: REMOVE + jch = new JoinCloneHandler(directive[2], events, server, channel); // TODO: REMOVE joinCloneTrackNeed = true; } else { @@ -289,7 +292,9 @@ public class ChanelCommander implements Runnable { private void readConfing(String confFilesPath){ if (!confFilesPath.endsWith(File.separator)) confFilesPath += File.separator; - File file = new File(confFilesPath+server+chanel+".csv"); // TODO: add/search for filename + + File file = new File(confFilesPath+server+ channel +".csv"); // TODO: add/search for filename + if (!file.exists()) return; try { @@ -298,10 +303,9 @@ public class ChanelCommander implements Runnable { while ((line = br.readLine()) != null) { parse(line.split("\t")); } - } catch (FileNotFoundException e) { - System.out.println("Internal issue: thread ChanelCommander->readConfig() can't find file:\t\n"+e); - } catch (IOException e){ - System.out.println("Internal issue: thread ChanelCommander->readConfig() I/O exception:\t\n"+e); + } + catch (Exception e) { + System.out.println("Internal issue: thread ChanelCommander->readConfig():\t\n"+e.getMessage()); } } } \ No newline at end of file diff --git a/src/main/java/InnaIrcBot/Commanders/JoinCloneHandler.java b/src/main/java/InnaIrcBot/Commanders/JoinCloneHandler.java index 9162baa..31b49df 100644 --- a/src/main/java/InnaIrcBot/Commanders/JoinCloneHandler.java +++ b/src/main/java/InnaIrcBot/Commanders/JoinCloneHandler.java @@ -22,7 +22,7 @@ public class JoinCloneHandler { prevUserNick = ""; lastCame = LocalDateTime.now().minusDays(1L); } - + /* public void track(String userNick){ if (userNick.matches(pattern)){ if (lastCame.isAfter(LocalDateTime.now().minusSeconds(timeFrameInSeconds)) && !prevUserNick.equals(userNick)){ @@ -37,8 +37,41 @@ public class JoinCloneHandler { lastCame = LocalDateTime.now(); } } + */ + // RUSNET + public void track(String userNick){ + if (userNick.matches(pattern)){ + if (lastCame.isAfter(LocalDateTime.now().minusSeconds(timeFrameInSeconds)) && !prevUserNick.equals(userNick)){ + if (getNickOnly(userNick).replaceAll("[0-9].*", "").length() > 2){ + StreamProvider.writeToStream(server, + "MODE "+chanel+" +b "+userNick.replaceAll("[0-9].*", "*!*@*")+"\n"+ + "MODE "+chanel+" +b *!*@"+getIdentHost(userNick)+"*\n"+ + "MODE "+chanel+" +b "+prevUserNick.replaceAll("[0-9].*", "*!*@*")+"\n"+ + "MODE "+chanel+" +b *!*@"+getIdentHost(prevUserNick)+"*\n"+ + "KICK "+chanel+" "+getNickOnly(userNick)+" :clone\n"+ + "KICK "+chanel+" "+getNickOnly(prevUserNick)+" :clone" + ); + } + else { + StreamProvider.writeToStream(server, + "MODE "+chanel+" +b *!*@"+getIdentHost(userNick)+"*\n"+ + "MODE "+chanel+" +b *!*@"+getIdentHost(prevUserNick)+"*\n"+ + "KICK "+chanel+" "+getNickOnly(userNick)+" :clone\n"+ + "KICK "+chanel+" "+getNickOnly(prevUserNick)+" :clone" + ); + } + + } + prevUserNick = userNick; + lastCame = LocalDateTime.now(); + } + } private String getIdentHost(String fullNick){ - return fullNick.replaceAll("^.*@","@"); + String id = fullNick.replaceAll("^.*@",""); + if (id.contains(":")) + return id.replaceAll("^([A-Fa-f0-9]{1,4}:[A-Fa-f0-9]{1,4}:)(.+)$", "$1")+"*"; + else + return id; } private String getNickOnly(String fullNick){ return fullNick.replaceAll("!.*$",""); diff --git a/src/main/java/InnaIrcBot/Commanders/JoinFloodHandler.java b/src/main/java/InnaIrcBot/Commanders/JoinFloodHandler.java index a22ffd6..befb9eb 100644 --- a/src/main/java/InnaIrcBot/Commanders/JoinFloodHandler.java +++ b/src/main/java/InnaIrcBot/Commanders/JoinFloodHandler.java @@ -3,58 +3,68 @@ package InnaIrcBot.Commanders; import InnaIrcBot.ProvidersConsumers.StreamProvider; import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.HashMap; -//TODO: implement method to -public class JoinFloodHandler { - private int eventBufferSize; // How many events should happens before we start validation - private int timeFrameInSeconds; // For which period critical amount of events should happens - private String server; - private String chanel; - private HashMap usersOnChanel; +import java.util.*; - public JoinFloodHandler(int events, int timeFrameInSeconds, String serverName, String chanelName){ - this.eventBufferSize = events; +public class JoinFloodHandler { + private final int joinMaxNumber; // How many events should happens before we start validation + private final int timeFrameInSeconds; // For which period critical amount of events should happens + private final String server; + private final String channel; + protected final HashMap> users; + + public JoinFloodHandler(int joinMaxNumber, int timeFrameInSeconds, String serverName, String channelName){ + this.joinMaxNumber = joinMaxNumber; this.timeFrameInSeconds = timeFrameInSeconds; this.server = serverName; - this.chanel = chanelName; - this.usersOnChanel = new HashMap<>(); + this.channel = channelName; + this.users = new HashMap<>(); } - public void track(String userNick){ - if(usersOnChanel.containsKey(userNick)){ - LocalDateTime timeOfFirstEvent = usersOnChanel.get(userNick).addLastGetFirst(); - if (timeOfFirstEvent.isAfter(LocalDateTime.now().minusSeconds(timeFrameInSeconds))) { // If first event in the queue happened after 'timeFrameSeconds ago from now' - StreamProvider.writeToStream(server, "PRIVMSG "+chanel+" :"+userNick+": join flood ("+eventBufferSize+" connections in "+timeFrameInSeconds+" seconds).\n"+ - "MODE "+chanel+" +b "+userNick+"!*@*"); // Shut joins-liker down. By nick TODO: consider other ban methods - usersOnChanel.remove(userNick); // Delete viewing history (in case op/hop decided to unban) - } + public void track(String userNickname){ + if (isNewcomer(userNickname)) { + registerNewUser(userNickname); + return; + } + + if(isJoinFlooder(userNickname)){ + kickBanUser(userNickname); + users.remove(userNickname); + } + } + + private boolean isNewcomer(String user){ + return ! users.containsKey(user); + } + + private void registerNewUser(String user){ + users.put(user, new LinkedList<>()); + users.get(user).addFirst(LocalDateTime.now()); + } + + private boolean isJoinFlooder(String user){ + final LocalDateTime firstJoinTime = getFirstJoinTimeAndUpdate(user); + return firstJoinTime.isAfter(LocalDateTime.now().minusSeconds(timeFrameInSeconds)); + } + + private LocalDateTime getFirstJoinTimeAndUpdate(String user){ + LinkedList userJoinHistory = users.get(user); + LocalDateTime fistJoinTime; + + userJoinHistory.addLast(LocalDateTime.now()); + + if (userJoinHistory.size() > joinMaxNumber){ + fistJoinTime = userJoinHistory.getFirst(); + userJoinHistory.removeFirst(); } else { - usersOnChanel.put(userNick, new LprFIFOQueue(eventBufferSize)); // Create buffer for tracking new user joined - usersOnChanel.get(userNick).addLastGetFirst(); // Write his/her first join time + fistJoinTime = LocalDateTime.MIN; } + + return fistJoinTime; } -} - -class LprFIFOQueue { - private int size; - private ArrayList dateTimes;// todo stack or deque - - LprFIFOQueue(int size){ - this.size = size; - this.dateTimes = new ArrayList<>(); - } - - LocalDateTime addLastGetFirst(){ // FIFO-like - // set - if (dateTimes.size() >= size) - dateTimes.remove(0); - dateTimes.add(LocalDateTime.now()); // add current time - // get - if (dateTimes.size() < size) - return LocalDateTime.MIN; // todo: check if null is better - else - return dateTimes.get(0); + private void kickBanUser(String user){ + StreamProvider.writeToStream(server, + "PRIVMSG "+ channel +" :"+user+": join flood ("+ joinMaxNumber +" connections in "+timeFrameInSeconds+" seconds).\n"+ + "MODE "+ channel +" +b "+user+"!*@*"); // TODO: consider other ban methods } } \ No newline at end of file diff --git a/src/main/java/InnaIrcBot/Commanders/PrivateMsgCommander.java b/src/main/java/InnaIrcBot/Commanders/PrivateMsgCommander.java index 02d7641..7ee629c 100644 --- a/src/main/java/InnaIrcBot/Commanders/PrivateMsgCommander.java +++ b/src/main/java/InnaIrcBot/Commanders/PrivateMsgCommander.java @@ -6,13 +6,13 @@ import InnaIrcBot.ReconnectControl; import java.util.ArrayList; -public class PrivateMsgCommander { // TODO: add black list: add users after failed login queries ; temporary ban - private String serverName; - private ArrayList administrators; - private String password; +public class PrivateMsgCommander { // TODO: add black list: add users after failed login queries ; temporary ban + private final String server; + private final String password; + private final ArrayList administrators; public PrivateMsgCommander(String server, String adminPassword){ - this.serverName = server; + this.server = server; this.administrators = new ArrayList<>(); this.password = adminPassword.trim(); } @@ -155,7 +155,7 @@ public class PrivateMsgCommander { // T case "unvoice": if ((cmd.length == 2) && (cmd[1].split("(\\s)|(\t)+?",2).length == 2)) { String[] voiceArgs = cmd[1].split("(\\s)|(\t)+?", 2); - unvoice(voiceArgs[0], voiceArgs[1]); + devoice(voiceArgs[0], voiceArgs[1]); } else tell(simplifyNick(sender), "Pattern: [-v|unvoice] "); @@ -173,7 +173,7 @@ public class PrivateMsgCommander { // T case "unhop": if ((cmd.length == 2) && (cmd[1].split("(\\s)|(\t)+?",2).length == 2)) { String[] hopArgs = cmd[1].split("(\\s)|(\t)+?", 2); - unhop(hopArgs[0], hopArgs[1]); + dehop(hopArgs[0], hopArgs[1]); } else tell(simplifyNick(sender), "Pattern: [-h|unhop] "); @@ -191,7 +191,7 @@ public class PrivateMsgCommander { // T case "unop": if ((cmd.length == 2) && (cmd[1].split("(\\s)|(\t)+?",2).length == 2)) { String[] args = cmd[1].split("(\\s)|(\t)+?", 2); - unop(args[0], args[1]); + deop(args[0], args[1]); } else tell(simplifyNick(sender), "Pattern: [-o|unoperator] "); @@ -238,7 +238,7 @@ public class PrivateMsgCommander { // T } else raw("QUIT :"+message); - ReconnectControl.update(serverName, false); + ReconnectControl.update(server, false); //ReconnectControl.update(serverName, true); //System.exit(0); // TODO: change to normal exit } @@ -268,7 +268,7 @@ public class PrivateMsgCommander { // T raw("MODE "+object+" "+mode+" "+user); } private void raw(String rawText){ - StreamProvider.writeToStream(serverName, rawText); + StreamProvider.writeToStream(server, rawText); } private void kick(String chanel, String user, String reason){ if (reason == null) @@ -295,19 +295,19 @@ public class PrivateMsgCommander { // T private void voice(String chanel, String user){ cmode(chanel, "+v", user); } - private void unvoice(String chanel, String user){ + private void devoice(String chanel, String user){ cmode(chanel, "-v", user); } private void hop(String chanel, String user){ cmode(chanel, "+h", user); } - private void unhop(String chanel, String user){ + private void dehop(String chanel, String user){ cmode(chanel, "-h", user); } private void op(String chanel, String user){ cmode(chanel, "+o", user); } - private void unop(String chanel, String user){ + private void deop(String chanel, String user){ cmode(chanel, "-o", user); } private void topic(String channel, String topic){ raw("TOPIC "+channel+" :"+topic); } diff --git a/src/main/java/InnaIrcBot/Config/StorageFile.java b/src/main/java/InnaIrcBot/Config/StorageFile.java deleted file mode 100644 index 9cc5634..0000000 --- a/src/main/java/InnaIrcBot/Config/StorageFile.java +++ /dev/null @@ -1,71 +0,0 @@ -package InnaIrcBot.Config; - -public class StorageFile { - private final String serverName; - private final int serverPort; - private final String serverPass; - private final String[] channels; - private final String userNick; - private final String userIdent; - private final String userRealName; - private final String userNickPass; - private final String userNickAuthStyle; - private final String userMode; - private final boolean rejoinOnKick; - private final String logDriver; - private final String[] logDriverParameters; - private final String botAdministratorPassword; - private final String chanelConfigurationsPath; - private final String applicationLogDir; - - public String getServerName() { return serverName; } - public int getServerPort() { return serverPort; } - public String getServerPass() { return serverPass; } - public String[] getChannels() { return channels; } - public String getUserNick() { return userNick; } - public String getUserIdent() { return userIdent; } - public String getUserRealName() { return userRealName; } - public String getUserNickPass() { return userNickPass; } - public String getUserNickAuthStyle() { return userNickAuthStyle; } - public String getUserMode() { return userMode; } - public boolean getRejoinOnKick() { return rejoinOnKick; } - public String getLogDriver() { return logDriver; } - public String[] getLogDriverParameters() { return logDriverParameters; } - public String getBotAdministratorPassword() { return botAdministratorPassword; } - public String getChanelConfigurationsPath() { return chanelConfigurationsPath; } - public String getApplicationLogDir() { return applicationLogDir; } - - public StorageFile(String serverName, - int serverPort, - String serverPass, - String[] channels, - String userNick, - String userIdent, - String userRealName, - String userNickPass, - String userNickAuthStyle, - String userMode, - boolean rejoinOnKick, - String logDriver, - String[] logDriverParameters, - String botAdministratorPassword, - String chanelConfigurationsPath, - String applicationLogDir){ - this.serverName = serverName; - this.serverPort = serverPort; - this.serverPass = serverPass; - this.channels = channels; - this.userIdent = userIdent; - this.userNick = userNick; - this.userRealName = userRealName; - this.userNickPass = userNickPass; - this.userNickAuthStyle = userNickAuthStyle; - this.userMode = userMode; - this.rejoinOnKick = rejoinOnKick; - this.logDriver = logDriver; - this.logDriverParameters = logDriverParameters; - this.botAdministratorPassword = botAdministratorPassword; - this.chanelConfigurationsPath = chanelConfigurationsPath; - this.applicationLogDir = applicationLogDir; - } -} diff --git a/src/main/java/InnaIrcBot/Config/StorageReader.java b/src/main/java/InnaIrcBot/Config/StorageReader.java deleted file mode 100644 index a3ce54f..0000000 --- a/src/main/java/InnaIrcBot/Config/StorageReader.java +++ /dev/null @@ -1,97 +0,0 @@ -package InnaIrcBot.Config; - -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; - -import java.io.*; -import java.nio.charset.StandardCharsets; - -public class StorageReader { - public static StorageFile readConfig(String pathToFile){ // TODO: NULL or object - StorageFile storageObject = null; - - File configFile = new File(pathToFile); - - try (Reader fileReader = new InputStreamReader(new FileInputStream(configFile))) { - storageObject = new Gson().fromJson(fileReader, StorageFile.class); - return validateConfig(storageObject); - } catch (java.io.FileNotFoundException e){ - System.out.println("Configuration file not found."); - return null; - } catch (java.io.IOException e){ - System.out.println("Configuration file is empty or incorrect."); - return null; - } - } - private static StorageFile validateConfig(StorageFile sf){ //TODO: more validation - - if(sf.getServerName().isEmpty()){ - System.out.println("Server not defined in configuration file."); - return null; - } - else if(sf.getServerPort() <= 0){ - System.out.println("Server port set incorrectly in configuration file."); - return null; - } - else - return sf; - } - public static void generateDefaultConfig(String pathToFile){ - File savingFile; - if (pathToFile == null) { // no pathToFile? create new in homeDir - pathToFile = System.getProperty("user.dir") - +File.separator - +"myBotConfig.conf"; - savingFile = new File(pathToFile); - } - else if(pathToFile.endsWith(File.separator)) { // ends with .../ then create in dir - pathToFile = pathToFile + "myBotConfig.conf"; - savingFile = new File(pathToFile); - if (!savingFile.getParentFile().exists()) - savingFile.getParentFile().mkdirs(); - } - else { // check if it's dir, if yes, then create inside - savingFile = new File(pathToFile); - if (savingFile.exists() && savingFile.isDirectory()) { - pathToFile = pathToFile + File.separator + "myBotConfig.conf"; - savingFile = new File(pathToFile); - } - else if (!savingFile.getParentFile().exists()) - savingFile.getParentFile().mkdirs(); - } - try { - savingFile.createNewFile(); - Writer writerFile = new OutputStreamWriter(new FileOutputStream(savingFile.getAbsolutePath()), StandardCharsets.UTF_8); - - StorageFile storageFileObject = new StorageFile("srv", - 6667, - "", - new String[] {"#lpr", - "#main"}, - "user_nick", - "ident", - "bot", - "", - "freenode", - "ix", - true, - "Files", - new String[] {System.getProperty("user.home")}, - "pswd", - System.getProperty("user.home"), - "/var/logs/" - ); - - Gson writingStorageObject = new GsonBuilder().setPrettyPrinting().create(); - writingStorageObject.toJson(storageFileObject, writerFile); - writerFile.close(); - System.out.println("Configuration file created: " + pathToFile); - } catch (java.io.FileNotFoundException e){ - System.out.println("Configuration file not found or can't create:\n\t"+e); - } catch (java.io.UnsupportedEncodingException e){ - System.out.println("Unsupported encoding of the configuration file:\n\t"+e); - } catch (java.io.IOException e){ - System.out.println("Unable to write configuration file: I/O exception:\n\t"+e); - } - } -} diff --git a/src/main/java/InnaIrcBot/Connections.java b/src/main/java/InnaIrcBot/Connections.java deleted file mode 100644 index 7e256a4..0000000 --- a/src/main/java/InnaIrcBot/Connections.java +++ /dev/null @@ -1,82 +0,0 @@ -package InnaIrcBot; - -import InnaIrcBot.Config.StorageFile; -import InnaIrcBot.Config.StorageReader; -import InnaIrcBot.ProvidersConsumers.DataProvider; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; - -public class Connections { -//todo PING Server if not available - private HashMap configs; - private ArrayList connectionsList; - - Connections(String[] args){ - this.configs = new HashMap<>(); - this.connectionsList = new ArrayList<>(); - StorageFile tempConf = null; - - for (int i = 1; i < args.length; i++){ - if ((tempConf = StorageReader.readConfig(args[i])) != null) { - configs.put(tempConf.getServerName(), tempConf); - } - else - System.out.println("Connections->constructor: configuration argument dropped: "+args[i]); - } - if (!configs.isEmpty()){ - handleThreads(); - } - else { - System.out.println("Connections->constructor: Nothing to execute."); - } - } - private void createAndStartThread(String serverName){ - //if connectionsList already contains record with name, it should be removed from there first - // if there are few configs with same server name then.. fuckup - - Runnable runnableConnection = new DataProvider(configs.get(serverName)); - Thread threadConnection = new Thread(runnableConnection, serverName); - threadConnection.start(); - connectionsList.add(threadConnection); - } - private void handleThreads() { - // Crate array of threads - for (String serverName : configs.keySet()) - createAndStartThread(serverName); - // Watch threads - Iterator it = connectionsList.iterator(); - - while (it.hasNext()) { - System.out.println("\n" + it.next().getName() + "\n"); - } - - while (!connectionsList.isEmpty()) { // While we have something in connectionList - while (it.hasNext()) { // Proceed for every thread in the list - Thread curThread = it.next(); - if (!curThread.isAlive()) // If thread is dead - if (ReconnectControl.get(curThread.getName())) { // And ReconnectControl says that this thread shouldn't be dead - ReconnectControl.delete(curThread.getName()); // [Try to] remove rule-record from ReconnectControl structure - connectionsList.remove(curThread); - createAndStartThread(curThread.getName()); - System.out.println("DEBUG: Thread "+curThread.getName()+" going to restart after unexpected finish.\n\t"+connectionsList.toString()); - break; - } else { // And ReconnectControl says that this thread death expected - ReconnectControl.delete(curThread.getName()); // [Try to] remove rule-record from ReconnectControl structure - connectionsList.remove(curThread); - System.out.println("DEBUG: Thread "+curThread.getName()+" removed from observable list after expected finish.\n\t"+connectionsList.toString()); - break; - } - } - if (connectionsList.isEmpty()) { - System.out.println("connectionsList is empty. Exit."); - break; - } - else { - it = connectionsList.iterator(); - } - - } - } -} diff --git a/src/main/java/InnaIrcBot/ConnectionsBuilder.java b/src/main/java/InnaIrcBot/ConnectionsBuilder.java new file mode 100644 index 0000000..6747f7e --- /dev/null +++ b/src/main/java/InnaIrcBot/ConnectionsBuilder.java @@ -0,0 +1,57 @@ +package InnaIrcBot; + +import InnaIrcBot.ProvidersConsumers.DataProvider; +import InnaIrcBot.config.ConfigurationFile; +import InnaIrcBot.config.ConfigurationManager; + +public class ConnectionsBuilder { + private static Connections connections; + + public static Connections getConnections(){ + return connections; + } + + static void buildConnections(String[] pathsToConfigurationFiles){ + connections = new Connections(pathsToConfigurationFiles); + } + + static class Connections { + private Connections(String[] pathsToConfigurationFiles){ + for (String configuration: pathsToConfigurationFiles) { + ConfigurationFile configurationFile = registerConfiguration(configuration); + if (configuration != null) { + startNewConnection(configurationFile); + } + } + } + + private ConfigurationFile registerConfiguration(String configuration){ + try { + return ConfigurationManager.readAndSetConfiguration(configuration); + } + catch (Exception e){ + System.out.println("Connections->constructor: configuration argument dropped because of "+e.getMessage()); + } + return null; + } + + private void startNewConnection(ConfigurationFile configurationFile){ + // if connectionsList already contains record with name, it should be removed from there first + // if there are few configs with same server name then.. fuckup + DataProvider runnableConnection = new DataProvider(configurationFile); + Thread threadConnection = new Thread(runnableConnection); + threadConnection.start(); + } + + public void startNewConnection(String serverName){ + try { + ConfigurationFile configurationFile = ConfigurationManager.getConfiguration(serverName); + startNewConnection(configurationFile); + } + catch (Exception e){ + System.out.println("Unable to start new connectionL "+e.getMessage()); + } + } + } + +} diff --git a/src/main/java/InnaIrcBot/GlobalData.java b/src/main/java/InnaIrcBot/GlobalData.java index d787d53..7bfe309 100644 --- a/src/main/java/InnaIrcBot/GlobalData.java +++ b/src/main/java/InnaIrcBot/GlobalData.java @@ -1,8 +1,9 @@ package InnaIrcBot; public class GlobalData { - private static final String version = "InnaIrcBot v0.7 \"Комсомолец\""; + private static final String version = "InnaIrcBot v0.8 \"Коммунарка\""; public static synchronized String getAppVersion(){ return version; } + public static final int CHANNEL_QUEUE_CAPACITY = 500; } diff --git a/src/main/java/InnaIrcBot/IrcChannel.java b/src/main/java/InnaIrcBot/IrcChannel.java new file mode 100644 index 0000000..c199ade --- /dev/null +++ b/src/main/java/InnaIrcBot/IrcChannel.java @@ -0,0 +1,23 @@ +package InnaIrcBot; + +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; + +public class IrcChannel { + private final String name; + private final BlockingQueue channelQueue; + + public IrcChannel(String channelName){ + this.name = channelName; + this.channelQueue = new ArrayBlockingQueue<>(GlobalData.CHANNEL_QUEUE_CAPACITY); + } + + @Override + public String toString(){ + return name; + } + + public BlockingQueue getChannelQueue() { + return channelQueue; + } +} diff --git a/src/main/java/InnaIrcBot/LogDriver/BotDriver.java b/src/main/java/InnaIrcBot/LogDriver/BotDriver.java index daf2a0b..184858b 100644 --- a/src/main/java/InnaIrcBot/LogDriver/BotDriver.java +++ b/src/main/java/InnaIrcBot/LogDriver/BotDriver.java @@ -3,48 +3,56 @@ package InnaIrcBot.LogDriver; import java.util.HashMap; public class BotDriver { - private static HashMap serverDriver = new HashMap<>(); - - private static HashMap systemLogWorkerMap = new HashMap<>(); + private final static HashMap serverDriver = new HashMap<>(); + private final static HashMap systemLogWorkerMap = new HashMap<>(); /** * Define driver for desired server * */ // TODO: add proxy worker for using with multiple drivers public static synchronized boolean setLogDriver(String serverName, String driver, String[] driverParams, String applicationLogDir){ - if (!driver.isEmpty() && driverParams != null && driverParams.length > 0 && driverParams[0] != null && !driverParams[0].isEmpty()) { - String[][] drvAndParams = { - {driver}, - driverParams - }; - serverDriver.put(serverName, drvAndParams); - systemLogWorkerMap.put(serverName, new BotSystemWorker(serverName, applicationLogDir)); - return true; - } - else + if (driver == null) return false; + if (driver.isEmpty()) + return false; + if (driverParams.length == 0) + return false; + if (driverParams[0] == null) + return false; + if (driverParams[0].isEmpty()) + return false; + + String[][] drvAndParams = { + {driver}, + driverParams + }; + serverDriver.put(serverName, drvAndParams); + systemLogWorkerMap.put(serverName, new BotSystemWorker(serverName, applicationLogDir)); + return true; } - public static synchronized Worker getWorker(String serverName, String chanelName){ - if (serverDriver.containsKey(serverName)) { - switch (serverDriver.get(serverName)[0][0].toLowerCase()) { + + public static synchronized Worker getWorker(String server, String channel){ + if (serverDriver.containsKey(server)) { + switch (serverDriver.get(server)[0][0].toLowerCase()) { case "files": - BotFilesWorker botFilesWorker = new BotFilesWorker(serverName, serverDriver.get(serverName)[1], chanelName); - return validateConstancy(botFilesWorker, serverName, chanelName); + BotFilesWorker botFilesWorker = new BotFilesWorker(server, serverDriver.get(server)[1], channel); + return validateConsistancy(botFilesWorker, server, channel); case "sqlite": - BotSQLiteWorker botSQLiteWorker = new BotSQLiteWorker(serverName, serverDriver.get(serverName)[1], chanelName); - return validateConstancy(botSQLiteWorker, serverName, chanelName); + BotSQLiteWorker botSQLiteWorker = new BotSQLiteWorker(server, serverDriver.get(server)[1], channel); + return validateConsistancy(botSQLiteWorker, server, channel); case "mongodb": - BotMongoWorker botMongoWorker = new BotMongoWorker(serverName, serverDriver.get(serverName)[1], chanelName); - return validateConstancy(botMongoWorker, serverName, chanelName); + BotMongoWorker botMongoWorker = new BotMongoWorker(server, serverDriver.get(server)[1], channel); + return validateConsistancy(botMongoWorker, server, channel); case "zero": return new BotZeroWorker(); default: System.out.println("BotDriver->getWorker(): Configuration issue: can't find required driver \"" - +serverDriver.get(serverName)[0][0] + +serverDriver.get(server)[0][0] +"\".Using \"ZeroWorker\"."); return new BotZeroWorker(); } } - System.out.println("BotDriver->getWorker(): Unknown issue: Channel exists for non-existing server.\n\tUsing ZeroWorker."); + System.out.println("Issue on BotDriver->getWorker(): Channel requested for non-existing server?\n" + + "\tUsing ZeroWorker."); return new BotZeroWorker(); } // If channel found that it's impossible to use defined worker from user settings and asking for use ZeroWorker @@ -54,15 +62,13 @@ public class BotDriver { return systemLogWorkerMap.get(serverName); } - private static Worker validateConstancy(Worker worker, String srv, String chan){ // synchronized? + private static Worker validateConsistancy(Worker worker, String server, String channel){ // synchronized? if (worker.isConsistent()){ return worker; } - else { - System.out.println("BotDriver->validateConstancy(): Unable to use " - +worker.getClass().getSimpleName()+" for "+srv+"/"+chan - +". Using ZeroWorker instead."); - return new BotZeroWorker(); - } + System.out.println("BotDriver->validateConstancy(): Unable to use " + +worker.getClass().getSimpleName()+" for "+server+"/"+channel + +". Using ZeroWorker instead."); + return new BotZeroWorker(); } } diff --git a/src/main/java/InnaIrcBot/LogDriver/BotFilesWorker.java b/src/main/java/InnaIrcBot/LogDriver/BotFilesWorker.java index f8aae71..7e0882a 100644 --- a/src/main/java/InnaIrcBot/LogDriver/BotFilesWorker.java +++ b/src/main/java/InnaIrcBot/LogDriver/BotFilesWorker.java @@ -9,57 +9,63 @@ import java.time.format.DateTimeFormatter; import java.util.regex.Pattern; public class BotFilesWorker implements Worker { - private String filePath; - private boolean consistent = false; - private DateTimeFormatter dateFormat; - - private LocalDate fileWriterDay; + private final String channel; + private String filePath; + private final DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("HH:mm:ss"); + private LocalDate dateOnFile; private FileWriter fileWriter; - private String ircServer; // hold for debug only + private boolean consistent; public BotFilesWorker(String server, String[] driverParameters, String channel){ - ircServer = server; - dateFormat = DateTimeFormatter.ofPattern("HH:mm:ss"); + this.channel = channel.replaceAll(File.separator, ","); - channel = channel.replaceAll(File.separator, ","); + formatFilePath(server, driverParameters); - String dirLocation = driverParameters[0].trim(); - if (dirLocation.endsWith(File.separator)) - dirLocation = dirLocation+server; - else - dirLocation = dirLocation+File.separator+server; - - this.filePath = dirLocation+File.separator+channel; - - File dir = new File(dirLocation); try { - dir.mkdirs(); // ignore result, because if it's already exists we're good. Otherwise, it will be created. Only issue that can occur is SecurityException thrown, so let's catch it. + createServerFolder(); + createFileWriter(); + consistent = true; } catch (Exception e){ - System.out.println("BotFilesWorker (@"+server+")->constructor(): Failure:\n\tUnable to create directory to store DB file: \n\t" +e); - return; // consistent = false; + System.out.println("BotFilesWorker (@"+server+")->constructor(): Failure:\n" + e.getMessage()); } - if (!dir.exists()) { - System.out.println("BotFilesWorker (@"+server+")->constructor() failed:\n\tUnable to create directory to store files: " + dirLocation); + } + + private void formatFilePath(String server, String[] driverParameters){ + String dirLocation = driverParameters[0].trim(); // TODO: MOVE trim() out of here + + if (! dirLocation.endsWith(File.separator)) + dirLocation += File.separator; + + this.filePath = dirLocation+server+File.separator; + } + + private void createServerFolder() throws Exception{ + final File file = new File(filePath); + + if (file.exists()){ + if (file.isDirectory()) + return; + else + throw new Exception("BotFilesWorker->createServerFolder() "+filePath+" is file while directory expected."); + } + + if (file.mkdirs()) return; - } - this.consistent = resetFileWriter(false); + throw new Exception("BotFilesWorker->createServerFolder() Can't create directory: "+filePath); } - private boolean resetFileWriter(boolean reassign){ - try { - if (reassign) - this.fileWriter.close(); - this.fileWriterDay = LocalDate.now(); - this.fileWriter = new FileWriter(this.filePath+"_"+LocalDate.now().toString()+".txt", true); - return true; - } - catch (java.io.IOException e){ - System.out.println("BotFilesWorker (@"+ircServer+")->resetFileWriter() failed:\n\tCan't create file to store logs: "+this.filePath); - return false; - } + private void resetFileWriter() throws IOException{ + fileWriter.close(); + createFileWriter(); } + private void createFileWriter() throws IOException{ + dateOnFile = LocalDate.now(); + File newFile = new File(filePath+channel+"_"+ dateOnFile +".txt"); + fileWriter = new FileWriter(newFile); + } + @Override public boolean isConsistent() { return consistent; @@ -70,79 +76,73 @@ public class BotFilesWorker implements Worker { * argument[1] should be always 'subject' * */ @Override - public boolean logAdd(String event, String initiatorArg, String messageArg) { + public boolean logAdd(String event, String initiator, String message) { switch (event){ case "PRIVMSG": - PRIVMSG(initiatorArg, messageArg); + PRIVMSG(initiator, message); break; case "JOIN": - JOIN(initiatorArg, messageArg); + JOIN(initiator, message); break; case "MODE": - MODE(initiatorArg, messageArg); + MODE(initiator, message); break; case "KICK": - KICK(initiatorArg, messageArg); + KICK(initiator, message); break; case "PART": - PART(initiatorArg, messageArg); + PART(initiator, message); break; case "QUIT": - QUIT(initiatorArg, messageArg); + QUIT(initiator, message); break; case "NICK": - NICK(initiatorArg, messageArg); + NICK(initiator, message); break; case "TOPIC": - TOPIC(initiatorArg, messageArg); + TOPIC(initiator, message); break; default: - this.prettyPrint("["+LocalTime.now().format(dateFormat)+"] "+event+" "+initiatorArg+" "+messageArg+"\n"); // TODO: QA @ big data + prettyPrint("["+LocalTime.now().format(dateFormat)+"] "+event+" "+initiator+" "+message+"\n"); // TODO: QA @ big data break; } return consistent; } private void prettyPrint(String string){ - //if (consistent) { // could be not-opened - try { - if (LocalDate.now().isAfter(fileWriterDay)) { - if (!resetFileWriter(true)) { - this.close(); // Error message already printed - return; - } - } - fileWriter.write(string); - fileWriter.flush(); - } catch (IOException e) { - System.out.println("BotFilesWorker (@" + ircServer + ")->prettyPrint() failed\n\tUnable to write logs of " + this.filePath + " because of internal failure in LocalTime representation."); - this.close(); - //consistent = false; - } catch (NullPointerException npe){ - System.out.println("BotFilesWorker (@" + ircServer + ")->prettyPrint() failed\n\tUnable to write logs of " + this.filePath + " because file descriptor already closed/was not opened."); - consistent = false; - } catch (Exception unknowne){ // ??? No ideas. Just in case. Consider removing. - System.out.println("BotFilesWorker (@" + ircServer + ")->prettyPrint() failed\n\tUnable to write logs of " + this.filePath + " because of exception:\n\t"+unknowne); - this.close(); + try { + if (LocalDate.now().isAfter(dateOnFile)) { + resetFileWriter(); } - //} + fileWriter.write(string); + fileWriter.flush(); + } catch (Exception e){ + System.out.println("BotFilesWorker->prettyPrint() failed\n" + + "\tUnable to write logs to " + this.filePath + " "+e.getMessage()); + close(); + consistent = false; + } } - private String genDate(){ + private String getCurrentTimestamp(){ return "["+LocalTime.now().format(dateFormat)+"] "; } - private String getUserNameOnly(String userNameFull){return userNameFull.replaceAll("!.+$", "");} - private String getUserNameAndHost(String userNameFull){return userNameFull.replaceAll("!.+$", "")+" [!"+userNameFull.replaceAll("^.+!", "")+"] ";} + private String getUserNameOnly(String userNameFull){ + return userNameFull.replaceAll("!.+$", ""); + } + private String getUserNameAndHost(String userNameFull){ + return userNameFull.replaceAll("!.+$", "")+" [!"+userNameFull.replaceAll("^.+!", "")+"] "; + } private void PRIVMSG(String initiatorArg, String messageArg){ String msg = messageArg.substring(messageArg.indexOf(":")+1); - if (!Pattern.matches("^\\u0001ACTION .+\\u0001", msg)){ - this.prettyPrint(genDate()+"<"+getUserNameOnly(initiatorArg)+"> "+msg+"\n"); - } - else { - this.prettyPrint(genDate()+getUserNameOnly(initiatorArg)+msg.replaceAll("(^\\u0001ACTION)|(\\u0001$)","")+"\n"); + + if (Pattern.matches("^\\u0001ACTION .+\\u0001", msg)){ + prettyPrint(getCurrentTimestamp()+getUserNameOnly(initiatorArg)+msg.replaceAll("(^\\u0001ACTION)|(\\u0001$)","")+"\n"); + return; } + prettyPrint(getCurrentTimestamp()+"<"+getUserNameOnly(initiatorArg)+"> "+msg+"\n"); } private void JOIN(String initiatorArg, String messageArg){ - this.prettyPrint(genDate()+">> "+getUserNameAndHost(initiatorArg)+"joined "+messageArg+"\n"); + prettyPrint(getCurrentTimestamp()+">> "+getUserNameAndHost(initiatorArg)+"joined "+messageArg+"\n"); } private void MODE(String initiatorArg, String messageArg){ String initiatorChain; @@ -150,33 +150,38 @@ public class BotFilesWorker implements Worker { initiatorChain = getUserNameAndHost(initiatorArg)+"set"; else initiatorChain = initiatorArg+" set"; - this.prettyPrint(genDate()+"-!- "+initiatorChain+messageArg.substring(messageArg.indexOf(" "))+"\n"); + + prettyPrint(getCurrentTimestamp()+"-!- "+initiatorChain + messageArg.substring(messageArg.indexOf(" "))+"\n"); } private void KICK(String initiatorArg, String messageArg){ - this.prettyPrint(genDate()+"!<< "+messageArg.replaceAll("(^.+?\\s)|(\\s.+$)", "")+ + prettyPrint(getCurrentTimestamp()+"!<< "+messageArg.replaceAll("(^.+?\\s)|(\\s.+$)", "")+ " kicked by "+getUserNameAndHost(initiatorArg)+"with reason: "+messageArg.replaceAll("^.+?:", "")+"\n"); } private void PART(String initiatorArg, String messageArg){ - this.prettyPrint(genDate()+"<< "+getUserNameAndHost(initiatorArg)+"parted: "+messageArg.replaceAll("^.+?:","")+"\n"); + prettyPrint(getCurrentTimestamp()+"<< "+getUserNameAndHost(initiatorArg)+"parted: " + + messageArg.replaceAll("^.+?:","")+"\n"); } private void QUIT(String initiatorArg, String messageArg){ - this.prettyPrint(genDate()+"<< "+getUserNameAndHost(initiatorArg)+"quit: "+messageArg.replaceAll("^.+?:","")+"\n"); + prettyPrint(getCurrentTimestamp()+"<< "+getUserNameAndHost(initiatorArg)+"quit: " + + messageArg.replaceAll("^.+?:","")+"\n"); } private void NICK(String initiatorArg, String messageArg){ - this.prettyPrint(genDate()+"-!- "+getUserNameAndHost(initiatorArg)+"changed nick to: "+messageArg+"\n"); + prettyPrint(getCurrentTimestamp()+"-!- "+getUserNameAndHost(initiatorArg)+"changed nick to: "+messageArg+"\n"); } private void TOPIC(String initiatorArg, String messageArg) { - this.prettyPrint(genDate()+"-!- "+getUserNameAndHost(initiatorArg)+"has changed topic to: "+messageArg.replaceAll("^.+?:", "")+"\n"); + prettyPrint(getCurrentTimestamp()+"-!- "+getUserNameAndHost(initiatorArg)+"has changed topic to: " + + messageArg.replaceAll("^.+?:", "")+"\n"); } @Override public void close() { try { - if (fileWriter !=null) - fileWriter.close(); + fileWriter.close(); } - catch (java.io.IOException e){ - System.out.println("BotFilesWorker (@"+ircServer+")->close() failed\n\tUnable to properly close file: "+this.filePath); // Live with it. + catch (NullPointerException ignore) {} + catch (IOException e){ + System.out.println("BotFilesWorker->close() failed\n" + + "\tUnable to properly close file: "+this.filePath); // Live with it. } this.consistent = false; } diff --git a/src/main/java/InnaIrcBot/LogDriver/BotMongoWorker.java b/src/main/java/InnaIrcBot/LogDriver/BotMongoWorker.java index 220c8ae..c0c2ee3 100644 --- a/src/main/java/InnaIrcBot/LogDriver/BotMongoWorker.java +++ b/src/main/java/InnaIrcBot/LogDriver/BotMongoWorker.java @@ -17,73 +17,74 @@ import java.util.concurrent.TimeUnit; * For each channel we store collection that have name of the ircServer + chanelName. * If user decides to use one MongoDB for various servers, he may use even one DB (declared in configuration file) and store collections in there. * **/ -public class BotMongoWorker implements Worker { //TODO consider skipping checks if server already added. - - private static Map serversMap = Collections.synchronizedMap(new HashMap()); - - private String ircServer; +public class BotMongoWorker implements Worker { //TODO consider skipping checks if server already added. + private final static Map serversMap = Collections.synchronizedMap(new HashMap<>()); + private final String server; private MongoCollection collection; - private boolean consistent = false; + private boolean consistent; - public BotMongoWorker(String ircServer, String[] driverParameters, String channel){ - this.ircServer = ircServer; + public BotMongoWorker(String server, String[] driverParameters, String channel){ + this.server = server; + + if (driverParameters.length < 2) + return; + if (driverParameters[0].isEmpty()) + return; + if (driverParameters[1].isEmpty()) + return; + + String mongoHostAddress = driverParameters[0]; + String mongoDBName = driverParameters[1]; - String mongoHostAddr; - String mongoDBName; String mongoUser; String mongoPass; - if (driverParameters.length >= 2 && !driverParameters[0].isEmpty() && !driverParameters[1].isEmpty()) { - mongoHostAddr = driverParameters[0]; - mongoDBName = driverParameters[1]; - if (driverParameters.length == 4 && !driverParameters[2].isEmpty() && !driverParameters[3].isEmpty()) { - mongoUser = driverParameters[2]; - mongoPass = driverParameters[3]; - } - else { // Consider that DB does not require auth, therefore any credentials are fine, since not null ;) - mongoUser = "anon"; - mongoPass = "anon"; - } - } - else - return; // consistent = false - if (!serversMap.containsKey(ircServer)){ + if (driverParameters.length == 4 && !driverParameters[2].isEmpty() && !driverParameters[3].isEmpty()) { + mongoUser = driverParameters[2]; + mongoPass = driverParameters[3]; + } + else { // Consider that DB does not require auth, therefore any credentials are fine, since not null ;) + mongoUser = "anon"; + mongoPass = "anon"; + } + + if (! serversMap.containsKey(server)){ /* // Leave this validations for better times. CommandListener mongoCommandListener = new CommandListener() { @Override public void commandStarted(CommandStartedEvent commandStartedEvent) { - System.out.println("BotMongoWorker (@"+this.ircServer+"): C: commandStarted"); + System.out.println("BotMongoWorker (@"+this.server+"): C: commandStarted"); } @Override public void commandSucceeded(CommandSucceededEvent commandSucceededEvent) { - System.out.println("BotMongoWorker (@"+this.ircServer+"): C: commandSucceeded"); + System.out.println("BotMongoWorker (@"+this.server+"): C: commandSucceeded"); } @Override public void commandFailed(CommandFailedEvent commandFailedEvent) { - System.out.println("BotMongoWorker (@"+this.ircServer+"): C: commandFailed"); + System.out.println("BotMongoWorker (@"+this.server+"): C: commandFailed"); //consistent = false; - //close(ircServer); // ircServer recieved by constructor, not this.ircServer + //close(server); // server recieved by constructor, not this.server } }; */ ServerListener mongoServerListener = new ServerListener() { @Override public void serverOpening(ServerOpeningEvent serverOpeningEvent) { - System.out.println("BotMongoWorker (@"+ircServer+"): ServerListener: Server opened successfully: "+serverOpeningEvent.getServerId()); + System.out.println("BotMongoWorker (@"+server+"): ServerListener: Server opened successfully: "+serverOpeningEvent.getServerId()); } @Override public void serverClosed(ServerClosedEvent serverClosedEvent) { - System.out.println("BotMongoWorker (@"+ircServer+"): ServerListener: Server has been closed"); + System.out.println("BotMongoWorker (@"+server+"): ServerListener: Server has been closed"); } @Override public void serverDescriptionChanged(ServerDescriptionChangedEvent serverDescriptionChangedEvent) { if (!serverDescriptionChangedEvent.getNewDescription().isOk()) { - close(ircServer); // ircServer recieved by constructor, not this.ircServer - System.out.println("BotMongoWorker (@"+ircServer+"): ServerListener: Server description changed (exception occurs): " + close(server); // server recieved by constructor, not this.server + System.out.println("BotMongoWorker (@"+server+"): ServerListener: Server description changed (exception occurs): " + serverDescriptionChangedEvent.getNewDescription().getException()); } } @@ -91,111 +92,98 @@ public class BotMongoWorker implements Worker { //TODO consi MongoClientSettings MCS = MongoClientSettings.builder() // .addCommandListener(mongoCommandListener) - .applyConnectionString(new ConnectionString("mongodb://"+mongoHostAddr)) + .applyConnectionString(new ConnectionString("mongodb://"+mongoHostAddress)) .applyToClusterSettings(builder -> builder.serverSelectionTimeout(5, TimeUnit.SECONDS)) .applyToServerSettings(builder -> builder.addServerListener(mongoServerListener)) .credential(MongoCredential.createCredential(mongoUser, mongoDBName, mongoPass.toCharArray())) .build(); MongoClient mongoClient = MongoClients.create(MCS); - serversMap.put(ircServer, mongoClient); + serversMap.put(server, mongoClient); } - MongoDatabase mongoDB = serversMap.get(ircServer).getDatabase(mongoDBName); - collection = mongoDB.getCollection(ircServer + channel); + MongoDatabase mongoDB = serversMap.get(server).getDatabase(mongoDBName); + collection = mongoDB.getCollection(server + channel); Document ping = new Document("ping", 1); // try { Document answer = mongoDB.runCommand(ping); // reports to monitor thread if some fuckups happens if (answer.get("ok") == null || (Double)answer.get("ok") != 1.0d){ - close(ircServer); + close(server); return; } consistent = true; } catch (MongoCommandException mce){ - System.out.println("BotMongoWorker (@"+this.ircServer+"): Command exception. Check if username/password set correctly."); - close(ircServer); // ircServer received by constructor, not this.ircServer + System.out.println("BotMongoWorker (@"+this.server +"): Command exception. Check if username/password set correctly."); + close(server); // server received by constructor, not this.server } catch (MongoTimeoutException mte) { - System.out.println("BotMongoWorker (@"+this.ircServer+"): Timeout exception."); - close(ircServer); // ircServer received by constructor, not this.ircServer + System.out.println("BotMongoWorker (@"+this.server +"): Timeout exception."); + close(server); // server received by constructor, not this.server }catch (MongoException me){ - System.out.println("BotMongoWorker (@"+this.ircServer+"): MongoDB Exception."); - close(ircServer); // ircServer received by constructor, not this.ircServer + System.out.println("BotMongoWorker (@"+this.server +"): MongoDB Exception."); + close(server); // server received by constructor, not this.server } catch (IllegalStateException ise){ - System.out.println("BotMongoWorker (@"+this.ircServer+"): Illegal state exception: MongoDB server already closed (not an issue)."); + System.out.println("BotMongoWorker (@"+this.server +"): Illegal state exception: MongoDB server already closed (not an issue)."); consistent = false; // no need to close() obviously } if (consistent){ - ThingToCloseOnDie thing = new ThingToCloseOnDie() { - @Override - public void die() { - if (serversMap.containsKey(ircServer)) { - try { - serversMap.get(ircServer).close(); - serversMap.remove(ircServer); - }catch (Exception e){ - System.out.println("ThingToCloseOnDie: something went wrong when tried to close MongoDB connection\t\n"+e); - } - } + ThingToCloseOnDie thing = () -> { + if (serversMap.containsKey(server)) { + serversMap.get(server).close(); + serversMap.remove(server); } }; - BotDriver.getSystemWorker(ircServer).registerInSystemWorker(thing); + BotDriver.getSystemWorker(server).registerInSystemWorker(thing); } } @Override - public boolean logAdd(String event, String initiatorArg, String messageArg) { + public boolean logAdd(String event, String initiator, String message) { Document document = new Document("date", getDate()) .append("event", event) - .append("initiator", initiatorArg); + .append("initiator", initiator); switch (event) { - case "NICK": - case "JOIN": - document.append("message1", messageArg); - //preparedStatement.setString(5, null); - break; case "PART": case "QUIT": case "TOPIC": - document.append("message1", messageArg.replaceAll("^.+?:", "")); - //preparedStatement.setString(5, null); + document.append("message1", message.replaceAll("^.+?:", "")); break; case "MODE": - document.append("message1", messageArg.replaceAll("(^(.+?\\s){1})|(\\s.+$)","")); - document.append("message2", messageArg.replaceAll("^(.+?\\s){2}", "")); + document.append("message1", message.replaceAll("(^(.+?\\s){1})|(\\s.+$)","")); + document.append("message2", message.replaceAll("^(.+?\\s){2}", "")); break; case "KICK": - document.append("message1", messageArg.replaceAll("^.+?:", "")); - document.append("message2", messageArg.replaceAll("(^.+?\\s)|(\\s.+$)", "")); + document.append("message1", message.replaceAll("^.+?:", "")); + document.append("message2", message.replaceAll("(^.+?\\s)|(\\s.+$)", "")); break; case "PRIVMSG": - document.append("message1", messageArg.replaceAll("^:", "")); - //preparedStatement.setString(5,null); + document.append("message1", message.replaceAll("^:", "")); break; + case "NICK": + case "JOIN": default: - document.append("message1", messageArg); - //preparedStatement.setString(5,null); + document.append("message1", message); break; } try { - collection.insertOne(document); // TODO: call finalize? + collection.insertOne(document); // TODO: call finalize? consistent = true; // if no exceptions, then true } catch (MongoCommandException mce){ - System.out.println("BotMongoWorker (@"+this.ircServer+")->logAdd(): Command exception. Check if username/password set correctly."); + System.out.println("BotMongoWorker (@"+this.server +")->logAdd(): Command exception. Check if username/password set correctly."); this.close(); } catch (MongoTimeoutException mte) { - System.out.println("BotMongoWorker (@"+this.ircServer+")->logAdd(): Timeout exception."); + System.out.println("BotMongoWorker (@"+this.server +")->logAdd(): Timeout exception."); this.close(); }catch (MongoException me){ - System.out.println("BotMongoWorker (@"+this.ircServer+")->logAdd(): MongoDB Exception."); + System.out.println("BotMongoWorker (@"+this.server +")->logAdd(): MongoDB Exception."); this.close(); } catch (IllegalStateException ise){ - System.out.println("BotMongoWorker (@"+this.ircServer+")->logAdd(): Illegal state exception: MongoDB server already closed (not an issue)."); + System.out.println("BotMongoWorker (@"+this.server +")->logAdd(): Illegal state exception: MongoDB server already closed (not an issue)."); this.close(); } return consistent; @@ -214,7 +202,7 @@ public class BotMongoWorker implements Worker { //TODO consi if (serversMap.containsKey(server)) { serversMap.get(server).close(); serversMap.remove(server); - System.out.println("BotMongoWorker (@"+this.ircServer+")->close() [forced by listeners]"); + System.out.println("BotMongoWorker (@"+this.server +")->close() [forced by listeners]"); } consistent = false; } diff --git a/src/main/java/InnaIrcBot/LogDriver/BotSQLiteWorker.java b/src/main/java/InnaIrcBot/LogDriver/BotSQLiteWorker.java index f862468..10f6e20 100644 --- a/src/main/java/InnaIrcBot/LogDriver/BotSQLiteWorker.java +++ b/src/main/java/InnaIrcBot/LogDriver/BotSQLiteWorker.java @@ -155,37 +155,37 @@ public class BotSQLiteWorker implements Worker { public boolean isConsistent() {return consistent; } @Override - public boolean logAdd(String event, String initiatorArg, String messageArg) { + public boolean logAdd(String event, String initiator, String message) { try { preparedStatement.setLong(1, getDate()); preparedStatement.setString(2, event); - preparedStatement.setString(3, initiatorArg); + preparedStatement.setString(3, initiator); switch (event) { case "NICK": case "JOIN": - preparedStatement.setString(4, messageArg); + preparedStatement.setString(4, message); preparedStatement.setString(5, null); break; case "PART": case "QUIT": case "TOPIC": - preparedStatement.setString(4, messageArg.replaceAll("^.+?:", "")); + preparedStatement.setString(4, message.replaceAll("^.+?:", "")); preparedStatement.setString(5, null); break; case "MODE": - preparedStatement.setString(4, messageArg.replaceAll("(^(.+?\\s){1})|(\\s.+$)","")); - preparedStatement.setString(5, messageArg.replaceAll("^(.+?\\s){2}", "")); + preparedStatement.setString(4, message.replaceAll("(^(.+?\\s){1})|(\\s.+$)","")); + preparedStatement.setString(5, message.replaceAll("^(.+?\\s){2}", "")); break; case "KICK": - preparedStatement.setString(4, messageArg.replaceAll("^.+?:", "")); - preparedStatement.setString(5, messageArg.replaceAll("(^.+?\\s)|(\\s.+$)", "")); + preparedStatement.setString(4, message.replaceAll("^.+?:", "")); + preparedStatement.setString(5, message.replaceAll("(^.+?\\s)|(\\s.+$)", "")); break; case "PRIVMSG": - preparedStatement.setString(4, messageArg.replaceAll("^:", "")); + preparedStatement.setString(4, message.replaceAll("^:", "")); preparedStatement.setString(5,null); break; default: - preparedStatement.setString(4, messageArg); + preparedStatement.setString(4, message); preparedStatement.setString(5,null); break; } diff --git a/src/main/java/InnaIrcBot/LogDriver/BotSystemWorker.java b/src/main/java/InnaIrcBot/LogDriver/BotSystemWorker.java index 390a3ea..6440e23 100644 --- a/src/main/java/InnaIrcBot/LogDriver/BotSystemWorker.java +++ b/src/main/java/InnaIrcBot/LogDriver/BotSystemWorker.java @@ -9,48 +9,43 @@ import java.time.format.DateTimeFormatter; public class BotSystemWorker implements SystemWorker{ private FileWriter fileWriter; - private DateTimeFormatter dateFormat; + private final DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("HH:mm:ss"); private ThingToCloseOnDie thingToCloseOnDie; // call .die() method of this classes when this (system log class) dies. - String ircServer; + private final String server; private boolean consistent = false; - public BotSystemWorker(String ircServer, String appLogDir){ - this.ircServer = ircServer; - this.dateFormat = DateTimeFormatter.ofPattern("HH:mm:ss"); - + public BotSystemWorker(String server, String appLogDir){ + this.server = server; if (appLogDir.isEmpty()) { - if (System.getProperty("os.name").toLowerCase().startsWith("win")) { - appLogDir = System.getProperty("user.home")+ File.separator - +"AppData"+File.separator - +"Local"+File.separator - +"InnaIrcBot"+File.separator; - } else { - appLogDir = "/var/log/innaircbot/"; - } + appLogDir = System.getProperty("java.io.tmpdir")+File.separator+"innaircbot"+File.separator; + } + else if (! appLogDir.endsWith(File.separator)) { + appLogDir += File.separator; } - if (!appLogDir.endsWith(File.separator)) - appLogDir = appLogDir+File.separator; - appLogDir = appLogDir+ircServer; + appLogDir += server; + File logFile = new File(appLogDir); + try { - logFile.getParentFile().mkdirs(); - } catch (SecurityException e){ - System.out.println("BotSystemWorker (@"+ircServer+")->constructor() failed. Unable to create sub-directory(-ies) to store logs file ("+appLogDir+"):\n\t"+e); - return; // Consistent = false - } - if (!logFile.getParentFile().exists()) { - System.out.println("BotSystemWorker (@"+ircServer+")->constructor() failed:\n\tUnable to create sub-directory(-ies) to store log file: " + appLogDir); - return; - } - try { - this.fileWriter = new FileWriter(logFile, true); + if (! logFile.getParentFile().exists()) { + if (! logFile.getParentFile().mkdirs()){ + System.out.println("BotSystemWorker (@"+server+")->constructor() failed:\n" + + "\tUnable to create sub-directory(-ies) to store log file: " + appLogDir); + return; + } + } + fileWriter = new FileWriter(logFile, true); consistent = true; + } catch (SecurityException e){ + System.out.println("BotSystemWorker (@"+server+")->constructor() failed.\n" + + "\tUnable to create sub-directory(-ies) to store logs file ("+appLogDir+"):\n\t"+e.getMessage()); } catch (IOException oie){ - System.out.println("BotSystemWorker (@"+ircServer+")->constructor() failed:\n\tUnable to open file to store logs: " + appLogDir); + System.out.println("BotSystemWorker (@"+server+")->constructor() failed:\n" + + "\tUnable to open file to store logs: " + appLogDir + " "+ oie.getMessage()); } } @@ -64,22 +59,14 @@ public class BotSystemWorker implements SystemWorker{ try { fileWriter.write(genDate() + event + " " + initiatorArg + " " + messageArg + "\n"); fileWriter.flush(); - } catch (IOException e) { - System.out.println("BotSystemWorker (@" + ircServer + ")->logAdd() failed\n\tUnable to write logs of because of internal failure in LocalTime representation."); - //this.close(); - consistent = false; - } catch (NullPointerException npe) { - System.out.println("BotSystemWorker (@" + ircServer + ")->logAdd() failed\n\tUnable to write logs of because file descriptor already closed/was not opened."); - consistent = false; - } catch (Exception unknowne) { // ??? No ideas. Just in case. Consider removing. - System.out.println("BotSystemWorker (@" + ircServer + ")->logAdd() failed\n\tUnable to write logs of because of exception:\n\t" + unknowne); + } catch (Exception e) { // ??? No ideas. Just in case. Consider removing. + System.out.println("BotSystemWorker (@" + server + ")->logAdd() failed\n\tUnable to write logs of because of exception:\n\t" + e.getMessage()); //this.close(); consistent = false; } + return; } - else { - System.out.println(genDate() + event + " " + initiatorArg + " " + messageArg + "\n"); - } + System.out.println(genDate() + event + " " + initiatorArg + " " + messageArg + "\n"); } @Override @@ -93,14 +80,11 @@ public class BotSystemWorker implements SystemWorker{ public void close() { if (thingToCloseOnDie != null) thingToCloseOnDie.die(); - if (fileWriter != null) { - try { - fileWriter.close(); - } - catch (java.io.IOException e){ - System.out.println("BotSystemWorker (@"+ircServer+")->close() failed\n\tUnable to properly close logs file."); // Live with it. - } + + try { + fileWriter.close(); } + catch (IOException | NullPointerException ignore){} consistent = false; } } diff --git a/src/main/java/InnaIrcBot/LogDriver/BotZeroWorker.java b/src/main/java/InnaIrcBot/LogDriver/BotZeroWorker.java index e3aead6..e3b90d0 100644 --- a/src/main/java/InnaIrcBot/LogDriver/BotZeroWorker.java +++ b/src/main/java/InnaIrcBot/LogDriver/BotZeroWorker.java @@ -5,7 +5,7 @@ public class BotZeroWorker implements Worker{ public boolean isConsistent() {return true;} @Override - public boolean logAdd(String event, String initiatorArg, String messageArg) { return true; } + public boolean logAdd(String event, String initiator, String message) { return true; } @Override public void close() {} diff --git a/src/main/java/InnaIrcBot/LogDriver/Worker.java b/src/main/java/InnaIrcBot/LogDriver/Worker.java index 8449d77..40c2119 100644 --- a/src/main/java/InnaIrcBot/LogDriver/Worker.java +++ b/src/main/java/InnaIrcBot/LogDriver/Worker.java @@ -6,8 +6,8 @@ public interface Worker { boolean isConsistent(); boolean logAdd(String event, - String initiatorArg, - String messageArg); + String initiator, + String message); void close(); } diff --git a/src/main/java/InnaIrcBot/Main.java b/src/main/java/InnaIrcBot/Main.java new file mode 100644 index 0000000..faea2c5 --- /dev/null +++ b/src/main/java/InnaIrcBot/Main.java @@ -0,0 +1,16 @@ +/** + * InnaIrcBot + * @author Dmitry Isaenko + * Russia, 2018-2020. + * */ +package InnaIrcBot; + +public class Main { +//TODO: flood control +//TODO: setDaemon(true) +//TODO: multiple connections to one server not allowed + + public static void main(String[] args){ + new BotStart(args); + } +} \ No newline at end of file diff --git a/src/main/java/InnaIrcBot/ProvidersConsumers/ChanConsumer.java b/src/main/java/InnaIrcBot/ProvidersConsumers/ChanConsumer.java index 70e4079..2ea1498 100644 --- a/src/main/java/InnaIrcBot/ProvidersConsumers/ChanConsumer.java +++ b/src/main/java/InnaIrcBot/ProvidersConsumers/ChanConsumer.java @@ -1,82 +1,86 @@ package InnaIrcBot.ProvidersConsumers; import InnaIrcBot.Commanders.ChanelCommander; +import InnaIrcBot.GlobalData; +import InnaIrcBot.IrcChannel; import InnaIrcBot.LogDriver.BotDriver; import InnaIrcBot.LogDriver.Worker; +import InnaIrcBot.config.ConfigurationManager; -import java.io.*; -import java.nio.charset.StandardCharsets; import java.time.LocalTime; import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.Arrays; import java.util.Map; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; public class ChanConsumer implements Runnable { - private BufferedReader reader; - private String serverName; - private String channelName; + private final BlockingQueue chanConsumerQueue; + private final String serverName; + private final String channelName; private Worker writerWorker; private ArrayList userList; private String nick; - private boolean rejoin; - private Map chanList; - private String configFilePath; + private final boolean rejoin; + private final Map channels; - private PrintWriter chanelCommanderPipe; + private Thread channelCommanderThread; + private BlockingQueue queue; private boolean endThread = false; - ChanConsumer(BufferedReader streamReader, String serverName, String channelName, String ownNick, String[] usersOnChan, boolean rejoinAlways, Map map, String configFilePath){ - this.reader = streamReader; + ChanConsumer(String serverName, + IrcChannel thisIrcChannel, + String ownNick, + Map channels) throws Exception{ + this.chanConsumerQueue = thisIrcChannel.getChannelQueue(); this.serverName = serverName; - this.channelName = channelName; + this.channelName = thisIrcChannel.toString(); this.writerWorker = BotDriver.getWorker(serverName, channelName); this.userList = new ArrayList<>(); - this.userList.addAll(Arrays.asList(usersOnChan)); this.nick = ownNick; - this.rejoin = rejoinAlways; - this.chanList = map; - this.configFilePath = configFilePath; + this.rejoin = ConfigurationManager.getConfiguration(serverName).getRejoinOnKick(); + this.channels = channels; // Create chanel commander thread, get pipe - this.chanelCommanderPipe = getChanelCommander(); + getChanelCommander( + ConfigurationManager.getConfiguration(serverName).getChanelConfigurationsPath() + ); + } + // Create ChanelCommander + private void getChanelCommander(String chanelConfigurationsPath){ + this.queue = new ArrayBlockingQueue<>(GlobalData.CHANNEL_QUEUE_CAPACITY); + ChanelCommander commander = new ChanelCommander(queue, serverName, channelName, chanelConfigurationsPath); + this.channelCommanderThread = new Thread(commander); + this.channelCommanderThread.start(); } public void run(){ String data; String[] dataStrings; - System.out.println("["+LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"))+"] THREAD "+serverName+":"+this.channelName +" started"); // TODO:REMOVE DEBUG + System.out.println("["+LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"))+"] ChanConsumer thread "+serverName+":"+this.channelName +" started"); // TODO:REMOVE DEBUG try { - while ((data = reader.readLine()) != null) { + while (! endThread) { + data = chanConsumerQueue.take(); dataStrings = data.split(" ",3); - if (!trackUsers(dataStrings[0], dataStrings[1], dataStrings[2])) + if (! trackUsers(dataStrings[0], dataStrings[1], dataStrings[2])) continue; - // Send to chanel commander thread - chanelCommanderPipe.println(data); - chanelCommanderPipe.flush(); - //System.out.println("|"+dataStrings[0]+"|"+dataStrings[1]+"|"+dataStrings[2]+"|"); - //System.out.println("Thread: "+this.channelName +"\n\tArray:"+ userList); + queue.add(data); // TODO: Check and add consistency validation if (!writerWorker.logAdd(dataStrings[0], dataStrings[1], dataStrings[2])){ // Write logs, check if LogDriver consistent. If not: this.fixLogDriverIssues(dataStrings[0], dataStrings[1], dataStrings[2]); } - - if (endThread) { - reader.close(); - chanList.get(channelName).close(); - chanList.remove(channelName); - break; - } } - } catch (java.io.IOException e){ - System.out.println("ChanConsumer (@"+serverName+"/"+channelName+")->run(): Internal issue in thread: caused I/O exception:\n\t"+e); // TODO: reconnect + channels.remove(channelName); + } catch (InterruptedException e){ + System.out.println("ChanConsumer (@"+serverName+"/"+channelName+")->run(): Interrupted\n\t"+e); // TODO: reconnect? } writerWorker.close(); - //Chanel commander thread's pipe should be closed - chanelCommanderPipe.close(); + //Kill sub-thread + channelCommanderThread.interrupt(); System.out.println("["+LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"))+"] THREAD "+serverName+":"+this.channelName +" ended"); // TODO:REMOVE DEBUG } @@ -85,39 +89,47 @@ public class ChanConsumer implements Runnable { case "PRIVMSG": // most common, we don't have to handle anything else return true; case "JOIN": - addUsers(simplifyNick(initiatorArg)); + addUser(simplifyNick(initiatorArg)); return true; case "PART": - delUsers(simplifyNick(initiatorArg)); // nick non-simple + deleteUser(simplifyNick(initiatorArg)); // nick non-simple return true; case "QUIT": if (userList.contains(simplifyNick(initiatorArg))) { - delUsers(simplifyNick(initiatorArg)); // nick non-simple + deleteUser(simplifyNick(initiatorArg)); // nick non-simple return true; - } else + } + else return false; // user quit, but he/she is not in this channel case "KICK": if (rejoin && nick.equals(subjectArg.replaceAll("(^.+?\\s)|(\\s.+$)", ""))) // if it's me and I have rejoin policy 'Auto-Rejoin on kick'. StreamProvider.writeToStream(serverName, "JOIN " + channelName); - delUsers(subjectArg.replaceAll("(^.+?\\s)|(\\s.+$)", "")); // nick already simplified + deleteUser(subjectArg.replaceAll("(^.+?\\s)|(\\s.+$)", "")); // nick already simplified return true; case "NICK": if (userList.contains(simplifyNick(initiatorArg))) { swapUsers(simplifyNick(initiatorArg), subjectArg); return true; - } else { + } + else { return false; // user changed nick, but he/she is not in this channel } + case "353": + String userOnChanStr = subjectArg.substring(subjectArg.indexOf(":") + 1); + userOnChanStr = userOnChanStr.replaceAll("[%@+]", "").trim(); + String[] usersOnChanArr = userOnChanStr.split(" "); + userList.addAll(Arrays.asList(usersOnChanArr)); + return true; default: return true; } } - private void addUsers(String user){ + private void addUser(String user){ if (!userList.contains(user)) userList.add(user); } - private void delUsers(String user){ + private void deleteUser(String user){ if (user.equals(nick)) { endThread = true; } @@ -129,33 +141,12 @@ public class ChanConsumer implements Runnable { if (userNickOld.equals(nick)) this.nick = userNickNew; } - // Create ChanelCommander - private PrintWriter getChanelCommander(){ - PipedOutputStream streamOut = new PipedOutputStream(); - try { - BufferedReader streamBufferedReader = new BufferedReader( - new InputStreamReader( - new PipedInputStream(streamOut), StandardCharsets.UTF_8) - ); - - ChanelCommander commander = new ChanelCommander(streamBufferedReader, serverName, channelName, configFilePath); - - new Thread(commander).start(); - - return new PrintWriter(streamOut); - - } catch (IOException e) { - System.out.println("ChanConsumer (@"+serverName+"/"+channelName+")->getChanelCommander(): Internal issue: I/O exception while initialized child objects:\n\t"+e); // caused by Socket - endThread = true; - return null; - } - } private String simplifyNick(String nick){ return nick.replaceAll("!.*$",""); } private void fixLogDriverIssues(String a, String b, String c){ System.out.println("ChanConsumer (@"+serverName+"/"+channelName+")->fixLogDriverIssues(): Some issues detected. Trying to fix..."); this.writerWorker = BotDriver.getWorker(serverName, channelName); // Reset logDriver and try using the same one - if (!writerWorker.logAdd(a,b,c)){ // Write to it what was not written (most likely) and if it's still not consistent: + if (! writerWorker.logAdd(a, b, c)){ // Write to it what was not written (most likely) and if it's still not consistent: this.writerWorker = BotDriver.getZeroWorker(); System.out.println("ChanConsumer (@"+serverName+"/"+channelName+")->fixLogDriverIssues(): failed to use defined LogDriver. Using ZeroWorker instead."); } diff --git a/src/main/java/InnaIrcBot/ProvidersConsumers/DataProvider.java b/src/main/java/InnaIrcBot/ProvidersConsumers/DataProvider.java index d3575d8..6b0bf58 100644 --- a/src/main/java/InnaIrcBot/ProvidersConsumers/DataProvider.java +++ b/src/main/java/InnaIrcBot/ProvidersConsumers/DataProvider.java @@ -1,6 +1,7 @@ package InnaIrcBot.ProvidersConsumers; -import InnaIrcBot.Config.StorageFile; +import InnaIrcBot.config.ConfigurationFile; +import InnaIrcBot.IrcChannel; import InnaIrcBot.LogDriver.BotDriver; import InnaIrcBot.ReconnectControl; @@ -8,131 +9,94 @@ import java.io.*; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.Socket; -import java.net.UnknownHostException; import java.nio.charset.StandardCharsets; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; +import java.util.*; +import java.util.concurrent.BlockingQueue; public class DataProvider implements Runnable { - private StorageFile configFile; - private String serverName; - private String userNick; + private final ConfigurationFile configurationFile; + private final String serverName; + private final String nickName; private BufferedReader rawStreamReader; - private boolean ableToRun = true; /** * Initiate connection and prepare input/output streams for run() * */ - public DataProvider(StorageFile storageFile){ - this.configFile = storageFile; - this.serverName = storageFile.getServerName(); - - int port = storageFile.getServerPort(); - - try { - InetAddress inetAddress = InetAddress.getByName(serverName); - Socket socket = new Socket(); // TODO: set timeout? - for (int i=0; i<5; i++) { - socket.connect(new InetSocketAddress(inetAddress, port), 5000); // 10sec = 10000 - if (socket.isConnected()) - break; - } - if (socket.isConnected()) - System.out.println("Socket connected"); - else { - System.out.println("Unable to connect to remote server after 5 retry."); - this.ableToRun = false; - return; - } - - if (!StreamProvider.setStream(serverName, socket)) { - this.ableToRun = false; - return; - } - InputStream inStream = socket.getInputStream(); - InputStreamReader isr = new InputStreamReader(inStream, StandardCharsets.UTF_8); //TODO set charset in options; - this.rawStreamReader = new BufferedReader(isr); - } catch(UnknownHostException e){ - this.ableToRun = false; - System.out.println("Internal issue: DataProvider->constructor caused unknown host exception:\n\t"+e); // caused by InetAddress - } catch (IOException e){ - this.ableToRun = false; - System.out.println("Internal issue: DataProvider->constructor caused I/O exception\n\t"+e); // caused by Socket - } - } - //HANDLE ALWAYS in case thread decided to die - private void close(){ - StreamProvider.delStream(serverName); + public DataProvider(ConfigurationFile configurationFile){ + this.configurationFile = configurationFile; + this.serverName = configurationFile.getServerName(); + this.nickName = configurationFile.getUserNick(); } public void run(){ - if (!ableToRun || !this.initConnection(rawStreamReader) - || !BotDriver.setLogDriver(serverName, configFile.getLogDriver(), configFile.getLogDriverParameters(), configFile.getApplicationLogDir())) { //Prepare logDriver for using in threads. + try { + connect(); + } catch (Exception e){ + System.out.println("Internal issue: DataProvider->run() caused exception:\n\t"+e.getMessage()); + e.printStackTrace(); + } + + ReconnectControl.register(serverName); + + if (! BotDriver.setLogDriver(serverName, + configurationFile.getLogDriver(), + configurationFile.getLogDriverParameters(), + configurationFile.getApplicationLogDir())) { //Prepare logDriver for using in threads.) this.close(); return; } /* Used for sending data into consumers objects*/ - Map channelsMap = Collections.synchronizedMap(new HashMap()); - try { - PipedOutputStream streamOut = new PipedOutputStream(); // K-K-K-KOMBO \m/ + Map ircChannels = Collections.synchronizedMap(new HashMap<>()); - BufferedReader streamBufferedReader = new BufferedReader( - new InputStreamReader( - new PipedInputStream(streamOut), StandardCharsets.UTF_8) - ); + IrcChannel systemConsumerChannel = new IrcChannel(""); + BlockingQueue systemConsumerQueue = systemConsumerChannel.getChannelQueue(); - Runnable systemConsumer = new SystemConsumer(streamBufferedReader, userNick, channelsMap, this.configFile); - new Thread(systemConsumer).start(); - PrintWriter systemConsumerWriter = new PrintWriter(streamOut); + Thread SystemConsumerThread = new Thread( + new SystemConsumer(systemConsumerQueue, nickName, ircChannels, this.configurationFile)); + SystemConsumerThread.start(); - StreamProvider.setSysConsumer(serverName, systemConsumerWriter); // Register system consumer at StreamProvider + StreamProvider.setSysConsumer(serverName, systemConsumerQueue); // Register system consumer at StreamProvider - channelsMap.put("", systemConsumerWriter); // Not sure that PrintWriter is thread-safe.. - } catch (IOException e){ - System.out.println("Internal issue: DataProvider->run() I/O exception while initialized child objects.\n\t"+e); // caused by Socket - this.close(); - return; - } + ircChannels.put(systemConsumerChannel.toString(), systemConsumerChannel); // Not sure that PrintWriter is thread-safe.. ////////////////////////////////////// Start loop ////////////////////////////////////////////////////////////// + StreamProvider.writeToStream(serverName,"NICK "+this.nickName); + StreamProvider.writeToStream(serverName,"USER "+ configurationFile.getUserIdent()+" 8 * :"+ configurationFile.getUserRealName()); // TODO: Add usermode 4 rusnet try { String rawMessage; String[] rawStrings; // prefix[0] command[1] command-parameters\r\n[2] //if there is no prefix, you should assume the message came from your client. - String chan; - // Say 'yes, we need reconnect if connection somehow died' - ReconnectControl.register(serverName); while ((rawMessage = rawStreamReader.readLine()) != null) { - //System.out.println(rawMessage); + System.out.println(rawMessage); if (rawMessage.startsWith(":")) { rawStrings = rawMessage .substring(1) .split(" :?", 3); // Removing ':' - - chan = rawStrings[2].replaceAll("(\\s.?$)|(\\s.+?$)", ""); + String chan = rawStrings[2].replaceAll("(\\s.?$)|(\\s.+?$)", ""); //System.out.println("\tChannel: "+chan+"\n\tAction: "+rawStrings[1]+"\n\tSender: "+rawStrings[0]+"\n\tMessage: "+rawStrings[2]+"\n"); if (rawStrings[1].equals("QUIT") || rawStrings[1].equals("NICK")) { // replace regex - for (PrintWriter value : channelsMap.values()) { - value.println(rawStrings[1] + " " + rawStrings[0] + " " + rawStrings[2]); - value.flush(); + for (IrcChannel ircChannel : ircChannels.values()) { + ircChannel.getChannelQueue().add(rawStrings[1] + " " + rawStrings[0] + " " + rawStrings[2]); } - } else if (channelsMap.containsKey(chan)) { - channelsMap.get(chan).println(rawStrings[1] + " " + rawStrings[0] + " " + rawStrings[2]); - channelsMap.get(chan).flush(); - } else { - channelsMap.get("").println(rawStrings[1] + " " + rawStrings[0] + " " + rawStrings[2]); - channelsMap.get("").flush(); } - } else if (rawMessage.startsWith("PING :")) { - pingSrvResponse(rawMessage); - } else { + else if (ircChannels.containsKey(chan)) { + IrcChannel chnl = ircChannels.get(chan); + chnl.getChannelQueue().add(rawStrings[1] + " " + rawStrings[0] + " " + rawStrings[2]); + } + else { + systemConsumerQueue.add(rawStrings[1] + " " + rawStrings[0] + " " + rawStrings[2]); + } + } + else if (rawMessage.startsWith("PING :")) { + sendPingReply(rawMessage); + } + else { System.out.println("Not a valid response=" + rawMessage); } } @@ -140,92 +104,37 @@ public class DataProvider implements Runnable { System.out.println("Socket issue: I/O exception"); //Connection closed. TODO: MAYBE try reconnect } finally { - for (PrintWriter p :channelsMap.values()) { - p.close(); - } - this.close(); + SystemConsumerThread.interrupt(); + close(); } } - private void pingSrvResponse(String rawData){ + private void connect() throws Exception{ + final int port = configurationFile.getServerPort(); + InetAddress inetAddress = InetAddress.getByName(serverName); + Socket socket = new Socket(); // TODO: set timeout? + for (int i = 0; i < 5; i++) { + socket.connect(new InetSocketAddress(inetAddress, port), 5000); // 5sec + if (socket.isConnected()) + break; + } + if (! socket.isConnected()) + throw new Exception("Unable to connect server."); + + StreamProvider.setStream(serverName, socket); + + InputStream inStream = socket.getInputStream(); + InputStreamReader isr = new InputStreamReader(inStream, StandardCharsets.UTF_8); //TODO set charset in options; + rawStreamReader = new BufferedReader(isr); + } + + private void sendPingReply(String rawData){ StreamProvider.writeToStream(serverName,"PONG :" + rawData.replace("PING :", "")); } - /** - * Initiate connection before starting main routine. - * */ - private boolean initConnection(BufferedReader genericStreamReader){ - int nickTail = 0; // handle appendix to nickname - this.userNick = configFile.getUserNick(); - - if (this.userNick == null || this.userNick.isEmpty()) { - System.out.println("Configuration issue: no nickname specified."); - return false; - } - String rawMessage; - - StreamProvider.writeToStream(serverName,"NICK "+this.userNick); - StreamProvider.writeToStream(serverName,"USER "+configFile.getUserIdent()+" 8 * :"+configFile.getUserRealName()); // TODO: Add usermode 4 rusnet - - try { - // 431 ERR_NONICKNAMEGIVEN how can we get this? - // 432 ERR_ERRONEUSNICKNAME covered - // 433 ERR_NICKNAMEINUSE covered - // 436 ERR_NICKCOLLISION - // 464 ERR_PASSWDMISMATCH (password for server/znc/bnc) - while ((rawMessage = genericStreamReader.readLine()) != null){ - System.out.println(rawMessage); - if (rawMessage.startsWith("PING :")) { - pingSrvResponse(rawMessage); - } - if (rawMessage.contains(" 001 ")) { - - StringBuilder message = new StringBuilder(); - - if (!configFile.getUserMode().trim().isEmpty()){ - String modes = configFile.getUserMode(); - modes = modes.replaceAll("[\t\\s]", ""); - - for(char c :modes.toCharArray()) { - message.append("MODE "); - message.append(userNick); - message.append(" +"); - message.append(c); - message.append("\n"); - } - } - StreamProvider.writeToStream(serverName,message.toString()); - - return true; - } - else if (rawMessage.contains(" 433 ")) { - if (this.userNick.substring(userNick.length()-1, userNick.length()-1).equals("|")) - this.userNick = this.userNick.substring(0,userNick.length()-1)+Integer.toString(nickTail++); - else - this.userNick = this.userNick+"|"+Integer.toString(nickTail++); - - StreamProvider.writeToStream(serverName,"NICK "+this.userNick); - } - else if (rawMessage.contains(" 432 ")) { - System.out.println("Configuration issue: Nickname contains unacceptable characters (432 ERR_ERRONEUSNICKNAME)."); - return false; - } - else if (rawMessage.contains(" 464 ")) { - StreamProvider.writeToStream(serverName,"PASS "+configFile.getServerPass()); - } - } - - if (!configFile.getUserNickPass().isEmpty() && (!configFile.getUserNickAuthStyle().isEmpty() && configFile.getUserNickAuthStyle().toLowerCase().equals("freenode"))) - StreamProvider.writeToStream(serverName,"PRIVMSG NickServ :IDENTIFY "+configFile.getUserNickPass()); - else if (!configFile.getUserNickPass().isEmpty() && (!configFile.getUserNickAuthStyle().isEmpty() && configFile.getUserNickAuthStyle().toLowerCase().equals("rusnet"))) - StreamProvider.writeToStream(serverName,"NickServ IDENTIFY "+configFile.getUserNickPass()); - else if (!configFile.getUserNickPass().isEmpty()) - System.out.println("Configuration issue: Unable to determinate method of user nick identification (by password): could be \"freenode\" or \"rusnet\"\nSee \"userNickAuthStyle\"."); - - } catch (IOException e){ - System.out.println("Internal issue: DataProvider->initConnection() caused I/O exception."); - return false; - } - return false; + //HANDLE ALWAYS in case thread decided to die + private void close(){ + StreamProvider.delStream(serverName); + ReconnectControl.notify(serverName); } } diff --git a/src/main/java/InnaIrcBot/ProvidersConsumers/StreamProvider.java b/src/main/java/InnaIrcBot/ProvidersConsumers/StreamProvider.java index b9945df..aa22911 100644 --- a/src/main/java/InnaIrcBot/ProvidersConsumers/StreamProvider.java +++ b/src/main/java/InnaIrcBot/ProvidersConsumers/StreamProvider.java @@ -1,46 +1,39 @@ package InnaIrcBot.ProvidersConsumers; +import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; -import java.io.PrintWriter; import java.net.Socket; import java.util.HashMap; +import java.util.concurrent.BlockingQueue; public class StreamProvider { - private static HashMap srvStreamMap = new HashMap<>(); - private static HashMap srvSysConsumersMap = new HashMap<>(); + private static final HashMap srvStreamMap = new HashMap<>(); + private static final HashMap> srvSysConsumersMap = new HashMap<>(); public static synchronized void writeToStream(String server, String message){ try { srvStreamMap.get(server).write(message+"\n"); srvStreamMap.get(server).flush(); - //System.out.println("W:"+message); // If this application says something, then pass it into system consumer thread to handle if (message.startsWith("PRIVMSG ")) { - srvSysConsumersMap.get(server).println("INNA "+message); - srvSysConsumersMap.get(server).flush(); + srvSysConsumersMap.get(server).add("INNA "+message); } - } catch (java.io.IOException e){ - System.out.println("Internal issue: StreamProvider->writeToStream() caused I/O exception:\n\t"+e); + } catch (IOException e){ + System.out.println("Internal issue: StreamProvider->writeToStream() caused I/O exception:\n\t"+e.getMessage()); } } - public static synchronized boolean setStream(String server, Socket socket){ - try { - OutputStream outStream = socket.getOutputStream(); - srvStreamMap.put(server, new OutputStreamWriter(outStream)); - return true; - } catch (java.io.IOException e){ - System.out.println("Internal issue: StreamProvider->setStream() caused I/O exception:\n\t"+e); - return false; - } + public static synchronized void setStream(String server, Socket socket) throws IOException{ + OutputStream outStream = socket.getOutputStream(); + srvStreamMap.put(server, new OutputStreamWriter(outStream)); } public static synchronized void delStream(String server){ srvStreamMap.remove(server); srvSysConsumersMap.remove(server); } - public static synchronized void setSysConsumer(String server, PrintWriter pw){ - srvSysConsumersMap.put(server, pw); + public static synchronized void setSysConsumer(String server, BlockingQueue queue){ + srvSysConsumersMap.put(server, queue); } } diff --git a/src/main/java/InnaIrcBot/ProvidersConsumers/SystemConsumer.java b/src/main/java/InnaIrcBot/ProvidersConsumers/SystemConsumer.java index 6ae7bb6..c0b7dd7 100644 --- a/src/main/java/InnaIrcBot/ProvidersConsumers/SystemConsumer.java +++ b/src/main/java/InnaIrcBot/ProvidersConsumers/SystemConsumer.java @@ -2,56 +2,45 @@ package InnaIrcBot.ProvidersConsumers; import InnaIrcBot.Commanders.CTCPHelper; import InnaIrcBot.Commanders.PrivateMsgCommander; -import InnaIrcBot.Config.StorageFile; +import InnaIrcBot.ReconnectControl; +import InnaIrcBot.config.ConfigurationFile; import InnaIrcBot.GlobalData; +import InnaIrcBot.IrcChannel; import InnaIrcBot.LogDriver.BotDriver; import InnaIrcBot.LogDriver.BotSystemWorker; -import java.io.*; -import java.nio.charset.StandardCharsets; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.util.ArrayList; -import java.util.HashMap; import java.util.Map; -import java.util.regex.Pattern; +import java.util.concurrent.BlockingQueue; public class SystemConsumer implements Runnable{ - private BufferedReader reader; + private final BlockingQueue systemQueue; private BotSystemWorker writerWorker; private String nick; private String serverName; - private Map channelsMap; - private boolean proxyRequired; - private HashMap> proxyAList; - private StorageFile storageFile; + private final Map channels; + private ConfigurationFile configurationFile; private PrivateMsgCommander commander; private LocalDateTime lastCTCPReplyTime; - SystemConsumer(BufferedReader streamReader, String userNick, Map map, StorageFile storage) { - this.writerWorker = BotDriver.getSystemWorker(storage.getServerName()); + private ArrayList channelThreads; + private int nickTail = 0; + + SystemConsumer(BlockingQueue systemQueue, String userNick, Map channels, ConfigurationFile configurationFile) { + this.systemQueue = systemQueue; + this.writerWorker = BotDriver.getSystemWorker(configurationFile.getServerName()); this.nick = userNick; - this.serverName = storage.getServerName(); - this.channelsMap = map; - this.reader = streamReader; - - this.proxyRequired = false; - this.proxyAList = new HashMap<>(); - this.storageFile = storage; - - this.commander = new PrivateMsgCommander(serverName, storageFile.getBotAdministratorPassword()); - // Start pre-set channels - StringBuilder message = new StringBuilder(); - for (String cnl : storageFile.getChannels()) { // TODO: add validation of channels. - message.append("JOIN "); - message.append(cnl); - message.append("\n"); - } - StreamProvider.writeToStream(serverName,message.toString()); + this.serverName = configurationFile.getServerName(); + this.channels = channels; + this.channelThreads = new ArrayList<>(); + this.configurationFile = configurationFile; + this.commander = new PrivateMsgCommander(serverName, this.configurationFile.getBotAdministratorPassword()); lastCTCPReplyTime = LocalDateTime.now(); } @@ -62,159 +51,115 @@ public class SystemConsumer implements Runnable{ setMainRoutine(); - for (PrintWriter p :channelsMap.values()) //TODO: check, code duplication. see Data provider constructor - p.close(); + for (Thread channel : channelThreads) { //TODO: check, code duplication. see Data provider constructor + channel.interrupt(); + } writerWorker.close(); System.out.println("["+LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"))+"] THREAD "+serverName+":[system] ended"); // TODO:REMOVE DEBUG } private void setMainRoutine(){ - String data; - String[] dataStrings; try { - while ((data = reader.readLine()) != null) { - dataStrings = data.split(" ",3); - - if (proxyRequired) - if (getProxy(dataStrings[0], dataStrings[1], dataStrings[2])) - continue; // TODO: check this. Continue is fair? - - if (Pattern.matches("(^[0-9]{3}$)|(^NICK$)|(^JOIN$)|(^QUIT$)|(^NOTICE$)", dataStrings[0])){ - handleNumeric(dataStrings[0], dataStrings[1], dataStrings[2]); - } - else if (dataStrings[0].equals("PRIVMSG")) { - if (dataStrings[2].indexOf("\u0001") < dataStrings[2].lastIndexOf("\u0001")) { - replyCTCP(simplifyNick(dataStrings[1]), dataStrings[2].substring(dataStrings[2].indexOf(":") + 1)); - //System.out.println("|"+dataStrings[1]+"|"+dataStrings[2].substring(dataStrings[2].indexOf(":") + 1)+"|"); - } - else { - commander.receiver(dataStrings[1], dataStrings[2].replaceAll("^.+?:", "").trim()); - writerWorker.logAdd("[system]", "PRIVMSG from "+dataStrings[1]+" received: ", dataStrings[2].replaceAll("^.+?:", "").trim()); - } - } - else if (dataStrings[0].equals("INNA")) { - String[] splitter; - if (dataStrings.length > 2){ // Don't touch 'cuz it's important - splitter = dataStrings[2].split(" ", 2); - if (splitter.length == 2){ - handleSpecial(dataStrings[1], splitter[0], splitter[1]); + while (true) { + String data = systemQueue.take(); + String[] dataStrings = data.split(" ",3); + //TODO: handle mode change + switch (dataStrings[0]){ + case "PRIVMSG": + if (dataStrings[2].indexOf("\u0001") < dataStrings[2].lastIndexOf("\u0001")) { + replyCTCP(simplifyNick(dataStrings[1]), dataStrings[2].substring(dataStrings[2].indexOf(":") + 1)); } - } + else { + commander.receiver(dataStrings[1], dataStrings[2].replaceAll("^.+?:", "").trim()); + writerWorker.logAdd("[system]", "PRIVMSG from "+dataStrings[1]+" received: ", + dataStrings[2].replaceAll("^.+?:", "").trim()); + } + break; + case "INNA": + String[] splitter; + if (dataStrings.length > 2){ // Don't touch 'cuz it's important + splitter = dataStrings[2].split(" ", 2); + if (splitter.length == 2){ + handleSpecial(dataStrings[1], splitter[0], splitter[1]); + } + } + break; + default: + handleNumeric(dataStrings[0], dataStrings[1], dataStrings[2]); } - else - writerWorker.logAdd(dataStrings[0], dataStrings[1], dataStrings[2]); // TODO: Track users - //System.out.println("System: "+"|"+dataStrings[0]+"|"+dataStrings[1]+"|"+dataStrings[2]+"|"); } - } catch (java.io.IOException e){ - System.out.println("Internal issue: thread SystemConsumer->run() caused I/O exception."); // TODO: reconnect OR AT LEAST DIE - StreamProvider.writeToStream(serverName, "QUIT :Internal issue: thread ChanConsumer->run() caused I/O exception"); } - } - private boolean getProxy(String eventNum, String sender, String message){ //TODO: if can't join: like channel with password - if (eventNum.equals("353")) { - //writerWorker.logAdd("[proxy]", "catch: "+eventNum+" from: "+sender+" :",message); - return false; // never mind and let it flows as usual. + catch (InterruptedException ie){ + System.out.println("Thread SystemConsumer->run() interrupted."); // TODO: reconnect OR AT LEAST DIE } - else { - //writerWorker.logAdd("[proxy]", "catch: "+eventNum+" from: "+sender+" :",message); - String chan = message.replaceAll("(\\s.?$)|(\\s.+?$)", ""); - - if (eventNum.equals("QUIT") || eventNum.equals("NICK")) { - for (ArrayList key : proxyAList.values()) - key.add(eventNum + " " + sender + " " + message); - return false; - } else if (chan.equals(nick)) - return false; - else if (proxyAList.keySet().contains(chan)) { - proxyAList.get(chan).add(eventNum + " " + sender + " " + message); - return true; - } else - return false; + catch (Exception e){ + System.out.println("Internal issue: thread SystemConsumer->run(): "+e.getMessage()); // TODO: DO.. some thing } } - private void replyCTCP(String sender, String message){ // got simplified nick - if (lastCTCPReplyTime.isBefore(LocalDateTime.now().minusSeconds(3))) { // TODO: Consider moving to config file. Now set to 3 sec - lastCTCPReplyTime = LocalDateTime.now(); - if (message.equals("\u0001VERSION\u0001")) { + private void replyCTCP(String sender, String message) { // got simplified nick + // TODO: Consider moving to config file. Now set to 3 sec + if (lastCTCPReplyTime.isAfter(LocalDateTime.now().minusSeconds(3))) + return; + + lastCTCPReplyTime = LocalDateTime.now(); + + switch (message) { + case "\u0001VERSION\u0001": StreamProvider.writeToStream(serverName, "NOTICE " + sender + " :\u0001VERSION " + GlobalData.getAppVersion() + "\u0001"); writerWorker.logAdd("[system]", "catch/handled CTCP VERSION from", sender); - //System.out.println("NOTICE "+sender+" \u0001VERSION "+ GlobalData.getAppVersion()+"\u0001"); - } else if (message.startsWith("\u0001PING ") && message.endsWith("\u0001")) { - StreamProvider.writeToStream(serverName, "NOTICE " + sender + " :" + message); - writerWorker.logAdd("[system]", "catch/handled CTCP PING from", sender); - //System.out.println(":"+sender+" NOTICE "+sender.substring(0,sender.indexOf("!"))+" "+message); - } else if (message.equals("\u0001CLIENTINFO\u0001")) { + return; + case "\u0001CLIENTINFO\u0001": StreamProvider.writeToStream(serverName, "NOTICE " + sender + " :\u0001CLIENTINFO ACTION PING VERSION TIME CLIENTINFO SOURCE\u0001"); writerWorker.logAdd("[system]", "catch/handled CTCP CLIENTINFO from", sender); - //System.out.println(":"+sender+" NOTICE "+sender.substring(0,sender.indexOf("!"))+" \u0001CLIENTINFO ACTION PING VERSION TIME CLIENTINFO\u0001"); - } else if (message.equals("\u0001TIME\u0001")) { + return; + case "\u0001TIME\u0001": StreamProvider.writeToStream(serverName, "NOTICE " + sender + " :\u0001TIME " + ZonedDateTime.now().format(DateTimeFormatter.RFC_1123_DATE_TIME) + "\u0001"); writerWorker.logAdd("[system]", "catch/handled CTCP TIME from", sender); - //System.out.println(":"+sender+" NOTICE "+sender.substring(0,sender.indexOf("!"))+" \u0001TIME "+ ZonedDateTime.now().format(DateTimeFormatter.RFC_1123_DATE_TIME)+"\u0001"); - } else if (message.equals("\u0001SOURCE\u0001")) { + return; + case "\u0001SOURCE\u0001": StreamProvider.writeToStream(serverName, "NOTICE " + sender + " :\u0001SOURCE https://github.com/developersu/InnaIrcBot\u0001"); writerWorker.logAdd("[system]", "catch/handled CTCP TIME from", sender); - //System.out.println(":"+sender+" NOTICE "+sender.substring(0,sender.indexOf("!"))+" \u0001SOURCE "+ ZonedDateTime.now().format(DateTimeFormatter.RFC_1123_DATE_TIME)+"\u0001"); - } else - writerWorker.logAdd("[system]", "catch unknown CTCP request \"" + message + "\" from ", sender); + return; } + if (message.startsWith("\u0001PING ") && message.endsWith("\u0001")) { + StreamProvider.writeToStream(serverName, "NOTICE " + sender + " :" + message); + writerWorker.logAdd("[system]", "catch/handled CTCP PING from", sender); + return; + } + writerWorker.logAdd("[system]", "catch unknown CTCP request \"" + message + "\" from ", sender); } private String simplifyNick(String nick){ return nick.replaceAll("!.*$",""); } - private void handleSpecial(String event, String chanel, String message){ - //System.out.println("|"+event+"|"+chanel+"|"+message+"|"); - if (channelsMap.containsKey(chanel)){ - channelsMap.get(chanel).println(event+" "+nick+" "+chanel+" "+message); // WTF >< - channelsMap.get(chanel).flush(); - //System.out.println("Formatted: |"+event+"|"+nick+"|"+chanel+" "+message+"|"); - } + private void handleSpecial(String event, String channelName, String message){ + IrcChannel ircChannel = channels.get(channelName); + if (ircChannel == null) + return; + String ircFormatterMessage = event+" "+nick+" "+channelName+" "+message; + //System.out.println("Formatted: |"+event+"|"+nick+"|"+channelName+" "+message+"|"); + ircChannel.getChannelQueue().add(ircFormatterMessage); } - //todo: nandle nickserv messages - private void handleNumeric(String eventNum, String sender, String message){ + //todo: handle nickserv messages somehow + private void handleNumeric(String eventNum, String sender, String message) throws Exception{ switch (eventNum){ + case "501": // Notify user about incorrect setup + writerWorker.logAdd("[system]", "catch/handled:", eventNum + + " [MODE message was sent with a nickname parameter and that the a mode flag sent was not recognized.]"); + break; case "433": // TODO: try to use alternative nickname - writerWorker.logAdd("[system]", "catch/handled:", eventNum+" [nickname already in use]"); + writerWorker.logAdd("[system]", "catch/handled:", eventNum + + " [nickname already in use and will be changed]"); break; case "353": writerWorker.logAdd("[system]", "catch/handled:", eventNum+" [RPL_NAMREPLY]"); - String chan = message.substring(message.indexOf(" ")+3); - chan = chan.substring(0, chan.indexOf(" ")); - if (proxyAList.containsKey(chan)) { - String userOnChanStr = message.substring(message.indexOf(":") + 1); - userOnChanStr = userOnChanStr.replaceAll("[%@+]", "").trim(); - String[] usersOnChanArr = userOnChanStr.split(" "); + String channelName = message.substring(nick.length()+3).replaceAll("\\s.*$", ""); - PipedOutputStream streamOut = new PipedOutputStream(); - try { - BufferedReader streamBufferedReader = new BufferedReader( - new InputStreamReader( - new PipedInputStream(streamOut), StandardCharsets.UTF_8) - ); - - channelsMap.put(chan, new PrintWriter(streamOut)); - // % @ + - ChanConsumer consumer = new ChanConsumer(streamBufferedReader, storageFile.getServerName(), chan, nick, usersOnChanArr, storageFile.getRejoinOnKick(), channelsMap, storageFile.getChanelConfigurationsPath()); - new Thread(consumer).start(); - - for (String msgStored : proxyAList.get(chan)) { - channelsMap.get(chan).println(msgStored); - channelsMap.get(chan).flush(); - } - - proxyAList.remove(chan); - if (proxyAList.isEmpty()) { - proxyRequired = false; - } - } catch (IOException e) { - System.out.println("Internal issue: SystemConsumer->handleNumeric() @ JOIN: I/O exception while initialized child objects."); // caused by Socket - return; //TODO: QA - } - } - else - System.out.println("Some internal shit happens that shouldn't happens never ever. Take your cat, call scientists and wait for singularity. Panic allowed. Log: \nEvent:|"+eventNum+"| sender:|"+sender+"| message|"+message+"|"); + IrcChannel ircChannel = channels.get(channelName); + if (ircChannel == null) + return; + ircChannel.getChannelQueue().add(eventNum+" "+sender+" "+message); break; case "NICK": if (sender.startsWith(nick+"!")) { @@ -224,9 +169,19 @@ public class SystemConsumer implements Runnable{ break; case "JOIN": if (sender.startsWith(nick+"!")) { - proxyAList.put(message, new ArrayList<>()); // Add new channel name to proxy watch-list - proxyAList.get(message).add(eventNum+" "+sender+" "+message); // Add message to array linked - this.proxyRequired = true; // Ask for proxy validators + IrcChannel newIrcChannel = new IrcChannel(message); + + channels.put(message, newIrcChannel); + // % @ + + ChanConsumer consumer = new ChanConsumer( + configurationFile.getServerName(), + newIrcChannel, + nick, + channels); + Thread newIrcChannelThread = new Thread(consumer); + newIrcChannelThread.start(); + channelThreads.add(newIrcChannelThread); + //proxyAList.get(message).add(eventNum+" "+sender+" "+message); // Add message to array linked writerWorker.logAdd("[system]", "joined to channel ", message); } break; @@ -239,11 +194,79 @@ public class SystemConsumer implements Runnable{ CTCPHelper.getInstance().handleCtcpReply(serverName, simplifyNick(sender), message.replaceAll("^.+?:", "").trim()); writerWorker.logAdd("[system]", "NOTICE from "+sender+" received: ", message.replaceAll("^.+?:", "").trim()); break; - case "QUIT": + case "001": + sendUserModes(); + sendNickPassword(); + joinChannels(); + break; + case "443": + String newNick = nick+"|"+nickTail++; + StreamProvider.writeToStream(serverName,"NICK "+newNick); + break; + case "464": // password for server/znc/bnc + StreamProvider.writeToStream(serverName,"PASS "+configurationFile.getServerPass()); + break; + case "432": + System.out.println("Configuration issue: Nickname contains unacceptable characters (432 ERR_ERRONEUSNICKNAME)."); + ReconnectControl.update(serverName, false); + + break; + case "465": + ReconnectControl.update(serverName, false); + + break; + case "QUIT": // TODO: Do something? break; default: - writerWorker.logAdd("[system]", "catch: "+eventNum+" from: "+sender+" :",message); // TODO: QUIT comes here. Do something? + writerWorker.logAdd("[system]", "catch: "+eventNum+" from: "+sender+" :",message); break; + // 431 ERR_NONICKNAMEGIVEN how can we get this? + // 436 ERR_NICKCOLLISION } } + + private void sendUserModes(){ + String modes = configurationFile.getUserMode().replaceAll("[\t\\s]", ""); + + if (modes.isEmpty()) + return; + + StringBuilder message = new StringBuilder(); + for(char mode : modes.toCharArray()) { + message.append("MODE "); + message.append(nick); + message.append(" +"); + message.append(mode); + message.append("\n"); + } + + StreamProvider.writeToStream(serverName, message.toString()); + } + + private void sendNickPassword(){ + if (configurationFile.getUserNickPass().isEmpty()) + return; + + switch (configurationFile.getUserNickAuthStyle()){ + case "freenode": + StreamProvider.writeToStream(serverName,"PRIVMSG NickServ :IDENTIFY " + + configurationFile.getUserNickPass()); + break; + case "rusnet": + StreamProvider.writeToStream(serverName,"NickServ IDENTIFY " + + configurationFile.getUserNickPass()); + } + } + + private void joinChannels(){ + StringBuilder joinMessage = new StringBuilder(); + + for (String cnl : configurationFile.getChannels()) { // TODO: add validation of channels. + joinMessage.append("JOIN "); + joinMessage.append(cnl); + joinMessage.append("\n"); + } + + StreamProvider.writeToStream(serverName, joinMessage.toString()); + } } \ No newline at end of file diff --git a/src/main/java/InnaIrcBot/ReconnectControl.java b/src/main/java/InnaIrcBot/ReconnectControl.java index b4ea5ce..0f87b81 100644 --- a/src/main/java/InnaIrcBot/ReconnectControl.java +++ b/src/main/java/InnaIrcBot/ReconnectControl.java @@ -3,21 +3,21 @@ package InnaIrcBot; import java.util.HashMap; public class ReconnectControl { - private static HashMap serversList = new HashMap<>(); + private static final HashMap serversList = new HashMap<>(); public static synchronized void register(String serverName){ serversList.put(serverName, true); } public static synchronized void update(String serverName, boolean needReconnect){ serversList.replace(serverName, needReconnect); } - public static synchronized boolean get(String serverName){ // could be null and it should be considered as false - if (serversList.get(serverName) == null) - return false; - else - return serversList.get(serverName); - } - public static synchronized void delete(String serverName){ - serversList.remove(serverName); - } + public static synchronized void notify(String serverName) { + if (serversList.get(serverName) == null || ! serversList.get(serverName)){ + serversList.remove(serverName); + return; + } + + System.out.println("DEBUG: Thread "+serverName+" removed from observable list after unexpected finish.\n\t"); + ConnectionsBuilder.getConnections().startNewConnection(serverName); + } } diff --git a/src/main/java/InnaIrcBot/config/ConfigurationFile.java b/src/main/java/InnaIrcBot/config/ConfigurationFile.java new file mode 100644 index 0000000..9dd774a --- /dev/null +++ b/src/main/java/InnaIrcBot/config/ConfigurationFile.java @@ -0,0 +1,79 @@ +package InnaIrcBot.config; + +public class ConfigurationFile { + private String serverName; + private int serverPort; + private String serverPass; + private String[] channels; + private String userNick; + private String userIdent; + private String userRealName; + private String userNickPass; + private String userNickAuthStyle; + private String userMode; + private boolean rejoinOnKick; + private String logDriver; + private String[] logDriverParameters; + private String botAdministratorPassword; + private String chanelConfigurationsPath; + private String applicationLogDir; + + public ConfigurationFile(String serverName, + int serverPort, + String serverPass, + String[] channels, + String userNick, + String userIdent, + String userRealName, + String userNickPass, + String userNickAuthStyle, + String userMode, + boolean rejoinOnKick, + String logDriver, + String[] logDriverParameters, + String botAdministratorPassword, + String chanelConfigurationsPath, + String applicationLogDir){ + this.serverName = serverName; + this.serverPort = serverPort; + this.serverPass = serverPass; + this.channels = channels; + this.userIdent = userIdent; + this.userNick = userNick; + this.userRealName = userRealName; + this.userNickPass = userNickPass; + this.userNickAuthStyle = userNickAuthStyle; + this.userMode = userMode; + this.rejoinOnKick = rejoinOnKick; + this.logDriver = logDriver; + this.logDriverParameters = logDriverParameters; + this.botAdministratorPassword = botAdministratorPassword; + this.chanelConfigurationsPath = chanelConfigurationsPath; + this.applicationLogDir = applicationLogDir; + } + + public String getServerName() { return nonNullString(serverName); } + public int getServerPort() { return serverPort; } + public String getServerPass() { return nonNullString(serverPass); } + public String[] getChannels() { return channels; } + public String getUserNick() { return nonNullString(userNick); } + public String getUserIdent() { return nonNullString(userIdent); } + public String getUserRealName() { return nonNullString(userRealName); } + public String getUserNickPass() { return nonNullString(userNickPass); } + public String getUserNickAuthStyle() { return nonNullString(userNickAuthStyle); } + public String getUserMode() { return nonNullString(userMode); } + public boolean getRejoinOnKick() { return rejoinOnKick; } + public String getLogDriver() { return nonNullString(logDriver); } + public String[] getLogDriverParameters() { return logDriverParameters; } + public String getBotAdministratorPassword() { return nonNullString(botAdministratorPassword); } + public String getChanelConfigurationsPath() { return nonNullString(chanelConfigurationsPath); } + public String getApplicationLogDir() { return nonNullString(applicationLogDir); } + + public void setUserNickAuthStyle(String userNickAuthStyle) { + this.userNickAuthStyle = userNickAuthStyle; + } + + private String nonNullString(String value){ + return value == null ? "" : value; + } +} diff --git a/src/main/java/InnaIrcBot/config/ConfigurationFileGenerator.java b/src/main/java/InnaIrcBot/config/ConfigurationFileGenerator.java new file mode 100644 index 0000000..c5d3f03 --- /dev/null +++ b/src/main/java/InnaIrcBot/config/ConfigurationFileGenerator.java @@ -0,0 +1,92 @@ +package InnaIrcBot.config; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +public class ConfigurationFileGenerator { + private String fileLocation; + + public static void generate(String fileLocation){ + new ConfigurationFileGenerator(fileLocation); + } + + private ConfigurationFileGenerator(String fileLocation){ + this.fileLocation = fileLocation; + + try { + if (locationNotDefined()) { // create new in homeDir + setLocationDefault(); + } + else if(locationIsFolder()) { // ends with .../ then create in dir + setLocationInsideFolder(); + } + + createConfigurationFile(); + + System.out.println("Configuration file created: " + this.fileLocation); // TODO: Move to l4j + } catch (IOException e){ + System.out.println("Unable to write configuration file: \n\t"+e.getMessage()); + } + } + + private void setLocationDefault(){ + fileLocation = System.getProperty("user.dir") + + File.separator + + "myBotConfig.conf"; + } + + private boolean locationNotDefined(){ + return fileLocation == null; + } + + private boolean locationIsFolder(){ + return fileLocation.endsWith(File.separator) || Files.isDirectory(Paths.get(fileLocation)); + } + + private void setLocationInsideFolder() throws IOException{ + createFoldersIfNeeded(); + if (fileLocation.endsWith(File.separator)) + fileLocation = fileLocation + "myBotConfig.conf"; + else + fileLocation = fileLocation + File.separator + "myBotConfig.conf"; + } + private void createFoldersIfNeeded() throws IOException{ + Path folderPath = Paths.get(fileLocation); + if (! Files.exists(folderPath)) + Files.createDirectories(folderPath); + } + private void createConfigurationFile() throws IOException{ + File configurationFile = new File(this.fileLocation); + + Writer writerFile = new OutputStreamWriter(new FileOutputStream(configurationFile.getAbsolutePath()), StandardCharsets.UTF_8); + + ConfigurationFile configurationFileObject = new ConfigurationFile("srv", + 6667, + "", + new String[] {"#lpr", + "#main"}, + "user_nick", + "ident", + "bot", + "", + "freenode", + "ix", + true, + "Files", + new String[] {System.getProperty("user.home")}, + "pswd", + System.getProperty("user.home"), + "/var/logs/" + ); + + Gson writingStorageObject = new GsonBuilder().setPrettyPrinting().create(); + writingStorageObject.toJson(configurationFileObject, writerFile); + writerFile.close(); + } +} diff --git a/src/main/java/InnaIrcBot/config/ConfigurationFileReader.java b/src/main/java/InnaIrcBot/config/ConfigurationFileReader.java new file mode 100644 index 0000000..ed9b59b --- /dev/null +++ b/src/main/java/InnaIrcBot/config/ConfigurationFileReader.java @@ -0,0 +1,50 @@ +package InnaIrcBot.config; + +import com.google.gson.Gson; + +import java.io.*; + +public class ConfigurationFileReader { + private ConfigurationFileReader(){} + + static ConfigurationFile read(String pathToFile) throws Exception{ // TODO: NULL or object + ConfigurationFile storageObject; + + File configFile = new File(pathToFile); + + try (Reader fileReader = new InputStreamReader(new FileInputStream(configFile))) { + storageObject = new Gson().fromJson(fileReader, ConfigurationFile.class); + } + + validate(storageObject); + + return storageObject; + } + + private static void validate(ConfigurationFile configurationFile) throws Exception{ //TODO: more validation + if (configurationFile.getServerName().isEmpty()) + throw new Exception("Server not defined in configuration file."); + + if (configurationFile.getServerPort() <= 0) + throw new Exception("Server port set incorrectly in configuration file."); + + String nick = configurationFile.getUserNick(); + if (nick.isEmpty()) + throw new Exception("Configuration issue: no nickname specified. "); + + if (! configurationFile.getUserNickPass().isEmpty()) { + if (configurationFile.getUserNickAuthStyle().isEmpty()) + throw new Exception("Configuration issue: password specified while auth method is not."); + + configurationFile.setUserNickAuthStyle( configurationFile.getUserNickAuthStyle().toLowerCase() ); + + if ( ! configurationFile.getUserNickAuthStyle().equals("rusnet") && ! configurationFile.getUserNickAuthStyle().equals("freenode")) + throw new Exception("Configuration issue: userNickAuthStyle could be freenode or rusnet."); + } + + if (configurationFile.getServerPort() <= 0 || configurationFile.getServerPort() > 65535) + throw new Exception("Server port number cannot be less/equal zero or greater then 65535"); + } + + +} diff --git a/src/main/java/InnaIrcBot/config/ConfigurationManager.java b/src/main/java/InnaIrcBot/config/ConfigurationManager.java new file mode 100644 index 0000000..c5ec99e --- /dev/null +++ b/src/main/java/InnaIrcBot/config/ConfigurationManager.java @@ -0,0 +1,22 @@ +package InnaIrcBot.config; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +public class ConfigurationManager { + private final static Map configurations = Collections.synchronizedMap(new HashMap<>()); + + public static ConfigurationFile readAndSetConfiguration(String pathToConfigurationFile) throws Exception{ + ConfigurationFile configurationFile = ConfigurationFileReader.read(pathToConfigurationFile); + configurations.put(configurationFile.getServerName(), configurationFile); + return configurationFile; + } + + public static ConfigurationFile getConfiguration(String serverName) throws Exception{ + ConfigurationFile configurationFile = configurations.get(serverName); + if (configurationFile == null) + throw new Exception("No configuration found for server "+serverName); + return configurationFile; + } +} diff --git a/src/main/java/Temporary/DriverTestFiles.java b/src/main/java/Temporary/DriverTestFiles.java deleted file mode 100644 index 550551b..0000000 --- a/src/main/java/Temporary/DriverTestFiles.java +++ /dev/null @@ -1,71 +0,0 @@ -package Temporary; - -import InnaIrcBot.LogDriver.BotDriver; -import InnaIrcBot.LogDriver.Worker; - -public class DriverTestFiles { - - public static void main(String[] args){ - if (BotDriver.setLogDriver("irc.tomsk.net", "files", new String[]{"/tmp/logs/"}, "/tmp/appLogs/")) - System.out.println("DRVT_Files: Successful driver initiation"); - else { - System.out.println("DRVT_Files: Failed driver initiation"); - return; - } - - Worker fw1 = BotDriver.getWorker("irc.tomsk.net","system"); - Worker fw2 = BotDriver.getWorker("irc.tomsk.net","#main"); - Worker fw3 = BotDriver.getWorker("irc.tomsk.net","#lpr"); - - if ((fw1 !=null) && (fw2 !=null) && (fw3 !=null)){ - System.out.println("DRVT_Files:LogFile1: "+fw1.isConsistent()); - System.out.println("DRVT_Files:LogFile2: "+fw2.isConsistent()); - System.out.println("DRVT_Files:LogFile3: "+fw3.isConsistent()); - boolean res; - - res = fw1.logAdd("JOIN", "de_su!loper@desktop.lan", "message1"); - System.out.println("DRVT_Files:fw1 exec result: "+res); - res = fw1.logAdd("PRIVMSG", "de_su!loper@desktop.lan", ": some text here"); - System.out.println("DRVT_Files:fw1 exec result: "+res); - res = fw1.logAdd("PRIVMSG", "de_su!loper@desktop.lan", ": more random tests"); - System.out.println("DRVT_Files:fw1 exec result: "+res); - res = fw1.logAdd("NICK", "de_su!loper@desktop.lan", "developer_su"); - System.out.println("DRVT_Files:fw1 exec result: "+res); - res = fw1.logAdd("MODE", "de_su!loper@desktop.lan", "+b username"); - System.out.println("DRVT_Files:fw1 exec result: "+res); - res = fw1.logAdd("PART", "de_su!loper@desktop.lan", "#chan1"); - System.out.println("DRVT_Files:fw1 exec result: "+res); - - res = fw2.logAdd("JOIN", "de_su!loper@desktop.lan", "message2"); - System.out.println("DRVT_Files:fw2 exec result: "+res); - res = fw2.logAdd("PRIVMSG", "de_su!loper@desktop.lan", ": some text here"); - System.out.println("DRVT_Files:fw2 exec result: "+res); - res = fw2.logAdd("PRIVMSG", "de_su!loper@desktop.lan", ": more random tests"); - System.out.println("DRVT_Files:fw2 exec result: "+res); - - res = fw2.logAdd("NICK", "de_su!loper@desktop.lan", "developer_su"); - System.out.println("DRVT_Files:fw2 exec result: "+res); - res = fw2.logAdd("MODE", "de_su!loper@desktop.lan", "+b username"); - System.out.println("DRVT_Files:fw2 exec result: "+res); - res = fw2.logAdd("PART", "de_su!loper@desktop.lan", "#chan2"); - System.out.println("DRVT_Files:fw2 exec result: "+res); - - res = fw3.logAdd("JOIN", "de_su!loper@desktop.lan", "message3"); - System.out.println("DRVT_Files:fw3 exec result: "+res); - res = fw3.logAdd("PRIVMSG", "de_su!loper@desktop.lan", ": some text here"); - System.out.println("DRVT_Files:fw3 exec result: "+res); - res = fw3.logAdd("PRIVMSG", "de_su!loper@desktop.lan", ": more random tests"); - System.out.println("DRVT_Files:fw3 exec result: "+res); - res = fw3.logAdd("NICK", "de_su!loper@desktop.lan", "developer_su"); - System.out.println("DRVT_Files:fw3 exec result: "+res); - res = fw3.logAdd("MODE", "de_su!loper@desktop.lan", "+b username"); - System.out.println("DRVT_Files:fw3 exec result: "+res); - res = fw3.logAdd("PART", "de_su!loper@desktop.lan", "#chan3"); - System.out.println("DRVT_Files:fw3 exec result: "+res); - - fw1.close(); - fw2.close(); - fw3.close(); - } - } -} diff --git a/src/main/java/Temporary/DriverTestSQLite.java b/src/main/java/Temporary/DriverTestSQLite.java deleted file mode 100644 index 91cf562..0000000 --- a/src/main/java/Temporary/DriverTestSQLite.java +++ /dev/null @@ -1,70 +0,0 @@ -package Temporary; - -import InnaIrcBot.LogDriver.BotDriver; -import InnaIrcBot.LogDriver.Worker; - -public class DriverTestSQLite { - - public static void main(String[] args){ - if (BotDriver.setLogDriver("irc.tomsk.net", "SQLite", new String[]{"/tmp/logs/mylogs"}, "/tmp/appLogs/")) - System.out.println("DRVT_SQLite:Successful driver initiation"); - else { - System.out.println("DRVT_SQLite:Failed driver initiation"); - return; - } - - Worker fw1 = BotDriver.getWorker("irc.tomsk.net","system"); - Worker fw2 = BotDriver.getWorker("irc.tomsk.net","#main"); - Worker fw3 = BotDriver.getWorker("irc.tomsk.net","#lpr"); - - if ((fw1 !=null) && (fw2 !=null) && (fw3 !=null)){ - System.out.println("DRVT_SQLite:LogFile1: "+fw1.isConsistent()); - System.out.println("DRVT_SQLite:LogFile2: "+fw2.isConsistent()); - System.out.println("DRVT_SQLite:LogFile3: "+fw3.isConsistent()); - boolean res; - - res = fw1.logAdd("JOIN", "de_su!loper@desktop.lan", "message1"); - System.out.println("DRVT_SQLite:fw1 exec result: "+res); - res = fw1.logAdd("PRIVMSG", "de_su!loper@desktop.lan", ": some text here"); - System.out.println("DRVT_SQLite:fw1 exec result: "+res); - res = fw1.logAdd("PRIVMSG", "de_su!loper@desktop.lan", ": more random tests"); - System.out.println("DRVT_SQLite:fw1 exec result: "+res); - res = fw1.logAdd("NICK", "de_su!loper@desktop.lan", "developer_su"); - System.out.println("DRVT_SQLite:fw1 exec result: "+res); - res = fw1.logAdd("MODE", "de_su!loper@desktop.lan", "+b username"); - System.out.println("DRVT_SQLite:fw1 exec result: "+res); - res = fw1.logAdd("PART", "de_su!loper@desktop.lan", "#chan1"); - System.out.println("DRVT_SQLite:fw1 exec result: "+res); - - res = fw2.logAdd("JOIN", "de_su!loper@desktop.lan", "message2"); - System.out.println("DRVT_SQLite:fw2 exec result: "+res); - res = fw2.logAdd("PRIVMSG", "de_su!loper@desktop.lan", ": some text here"); - System.out.println("DRVT_SQLite:fw2 exec result: "+res); - res = fw2.logAdd("PRIVMSG", "de_su!loper@desktop.lan", ": more random tests"); - System.out.println("DRVT_SQLite:fw2 exec result: "+res); - res = fw2.logAdd("NICK", "de_su!loper@desktop.lan", "developer_su"); - System.out.println("DRVT_SQLite:fw2 exec result: "+res); - res = fw2.logAdd("MODE", "de_su!loper@desktop.lan", "+b username"); - System.out.println("DRVT_SQLite:fw2 exec result: "+res); - res = fw2.logAdd("PART", "de_su!loper@desktop.lan", "#chan2"); - System.out.println("DRVT_SQLite:fw2 exec result: "+res); - - res = fw3.logAdd("JOIN", "de_su!loper@desktop.lan", "message3"); - System.out.println("DRVT_SQLite:fw3 exec result: "+res); - res = fw3.logAdd("PRIVMSG", "de_su!loper@desktop.lan", ": some text here"); - System.out.println("DRVT_SQLite:fw3 exec result: "+res); - res = fw3.logAdd("PRIVMSG", "de_su!loper@desktop.lan", ": more random tests"); - System.out.println("DRVT_SQLite:fw3 exec result: "+res); - res = fw3.logAdd("NICK", "de_su!loper@desktop.lan", "developer_su"); - System.out.println("DRVT_SQLite:fw3 exec result: "+res); - res = fw3.logAdd("MODE", "de_su!loper@desktop.lan", "+b username"); - System.out.println("DRVT_SQLite:fw3 exec result: "+res); - res = fw3.logAdd("PART", "de_su!loper@desktop.lan", "#chan3"); - System.out.println("DRVT_SQLite:fw3 exec result: "+res); - - fw1.close(); - fw2.close(); - fw3.close(); - } - } -} diff --git a/src/main/java/Temporary/ReconnectControlTest.java b/src/main/java/Temporary/ReconnectControlTest.java deleted file mode 100644 index ab2ff90..0000000 --- a/src/main/java/Temporary/ReconnectControlTest.java +++ /dev/null @@ -1,15 +0,0 @@ -package Temporary; - -import InnaIrcBot.ReconnectControl; - -public class ReconnectControlTest { - public static void main(String[] args){ - ReconnectControl.register("testing"); - ReconnectControl.register("testing1"); - ReconnectControl.update("testing1", false); - - System.out.println(ReconnectControl.get("testing")); - System.out.println(ReconnectControl.get("testing1")); - System.out.println(ReconnectControl.get("wrong")); - } -} diff --git a/src/main/java/Temporary/StorageFileTest.java b/src/main/java/Temporary/StorageFileTest.java index 25ad336..e431a11 100644 --- a/src/main/java/Temporary/StorageFileTest.java +++ b/src/main/java/Temporary/StorageFileTest.java @@ -1,10 +1,10 @@ package Temporary; -import InnaIrcBot.Config.StorageFile; +import InnaIrcBot.config.ConfigurationFile; public class StorageFileTest { static public void main(String[] args){ - StorageFile config = new StorageFile( + ConfigurationFile config = new ConfigurationFile( "", 0, "", diff --git a/src/main/java/Temporary/StorageReaderTest.java b/src/main/java/Temporary/StorageReaderTest.java index 8e579e8..ddfca14 100644 --- a/src/main/java/Temporary/StorageReaderTest.java +++ b/src/main/java/Temporary/StorageReaderTest.java @@ -1,8 +1,5 @@ package Temporary; -import InnaIrcBot.Config.StorageFile; -import InnaIrcBot.Config.StorageReader; - public class StorageReaderTest { public static void main(String[] args){ // StorageReader.readConfig("/home/loper/bot.config"); diff --git a/src/main/java/Temporary/brokenJoinFloodHandlerTest.java b/src/main/java/Temporary/brokenJoinFloodHandlerTest.java deleted file mode 100644 index 8bb8457..0000000 --- a/src/main/java/Temporary/brokenJoinFloodHandlerTest.java +++ /dev/null @@ -1,28 +0,0 @@ -package Temporary; - -import InnaIrcBot.Commanders.JoinFloodHandler; - -import java.time.LocalDateTime; -import java.util.concurrent.TimeUnit; - -public class brokenJoinFloodHandlerTest { - - public static void main(String[] args){ - new brokenJoinFloodHandlerTest(); - JoinFloodHandler jdh = new JoinFloodHandler(5, 5, "SRV_NAME", "#CHAN_NAME"); - System.out.println("Envents:\t5\n" - +"Time Frame:\t5 sec\n" - +"ServerName:\tSRV_NAME\n" - +"Chanel:\t#CHAN_NAME\n"); - for (int i=0; i<40; i++) { - System.out.println("Join for two users happened @"+ LocalDateTime.now()); - jdh.track("eblan"); - jdh.track("eban'ko"); - try { - TimeUnit.SECONDS.sleep(1); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - } -} diff --git a/src/test/java/InnaIrcBot/Commanders/JoinFloodHandlerTest.java b/src/test/java/InnaIrcBot/Commanders/JoinFloodHandlerTest.java new file mode 100644 index 0000000..bd9a1f8 --- /dev/null +++ b/src/test/java/InnaIrcBot/Commanders/JoinFloodHandlerTest.java @@ -0,0 +1,72 @@ +package InnaIrcBot.Commanders; + +import InnaIrcBot.ProvidersConsumers.StreamProvider; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.net.*; +import java.util.concurrent.ArrayBlockingQueue; + +import static org.junit.jupiter.api.Assertions.*; + +class JoinFloodHandlerTest { + + private static final String serverName = "testServer"; + private static final String channelName = "testChannel"; + + private final JoinFloodHandler joinFloodHandler = new JoinFloodHandler(3, 5, serverName, channelName); + private static final String userNickName = "John"; + private Thread socketTestThread; + + JoinFloodHandlerTest(){ + try{ + socketTestThread = new Thread(() -> { + try{ + ServerSocket serverSocket = new ServerSocket(60000); + serverSocket.accept(); + } + catch (Exception e){ + e.printStackTrace(); + } + }); + socketTestThread.start(); + + Socket testSocket = new Socket(); + testSocket.connect(new InetSocketAddress(60000)); + + StreamProvider.setStream(serverName, testSocket); + StreamProvider.setSysConsumer(serverName, new ArrayBlockingQueue<>(100)); + } + catch (IOException e){ + e.printStackTrace(); + } + } + @DisplayName("JoinFloodHandler: timeout 5s & limit 3 attempts") + @Test + void track() throws Exception{ + assertNull(joinFloodHandler.users.get(userNickName)); + + joinFloodHandler.track(userNickName); + Thread.sleep(1000); + joinFloodHandler.track(userNickName); + Thread.sleep(2000); + joinFloodHandler.track(userNickName); + Thread.sleep(1990); + joinFloodHandler.track(userNickName); + + assertNull(joinFloodHandler.users.get(userNickName)); + + Thread.sleep(900); + joinFloodHandler.track(userNickName); + + assertTrue(joinFloodHandler.users.containsKey(userNickName)); + } +/* + @AfterAll + static void cleanup(){ + socketTestThread.interrupt(); + } + */ +} \ No newline at end of file diff --git a/src/test/java/InnaIrcBot/LogDriver/BotDriverTest.java b/src/test/java/InnaIrcBot/LogDriver/BotDriverTest.java new file mode 100644 index 0000000..98bb31d --- /dev/null +++ b/src/test/java/InnaIrcBot/LogDriver/BotDriverTest.java @@ -0,0 +1,111 @@ +package InnaIrcBot.LogDriver; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.io.TempDir; + +import java.nio.file.Path; + +class BotDriverTest { + @TempDir + Path mainLogsDir, + mainSQLiteLogsDir; + + private static final String serverNameFiles = "irc.example.com"; + private static final String serverNameSQLite = "irc2.example.com"; + private Worker fw1; + private Worker fw2; + private Worker fw3; + + @DisplayName("BotDriver: test files driver") + @Test + void driverFilesTest() { + assertTrue(this::initializeFilesLogDriver); + createWorkersForFiles(); + checkConsistency(); + checkFilesWorkers(); + validateDriver(); + checkFilesWorkers(); + close(); + } + private void createWorkersForFiles(){ + fw1 = BotDriver.getWorker(serverNameFiles,"system"); + fw2 = BotDriver.getWorker(serverNameFiles,"#main"); + fw3 = BotDriver.getWorker(serverNameFiles,"#lpr"); + } + + @DisplayName("BotDriver: test SQLite driver") + @Test + void driverSQLiteTest() { + assertTrue(this::initializeSQLiteLogDriver); + createWorkersForSQLite(); + checkConsistency(); + checkSQLiteWorkers(); + validateDriver(); + checkSQLiteWorkers(); + close(); + } + private void createWorkersForSQLite(){ + fw1 = BotDriver.getWorker(serverNameSQLite,"system"); + fw2 = BotDriver.getWorker(serverNameSQLite,"#main"); + fw3 = BotDriver.getWorker(serverNameSQLite,"#lpr"); + } + + void checkConsistency(){ + assertTrue(fw1.isConsistent()); + assertTrue(fw2.isConsistent()); + assertTrue(fw3.isConsistent()); + } + + void checkFilesWorkers(){ + assertTrue(fw1 instanceof BotFilesWorker); + assertTrue(fw2 instanceof BotFilesWorker); + assertTrue(fw3 instanceof BotFilesWorker); + } + + void checkSQLiteWorkers(){ + assertTrue(fw1 instanceof BotSQLiteWorker); + assertTrue(fw2 instanceof BotSQLiteWorker); + assertTrue(fw3 instanceof BotSQLiteWorker); + } + + void validateDriver(){ + assertTrue(fw1.logAdd("JOIN", "de_su!loper@desktop.lan", "message1")); + assertTrue(fw1.logAdd("PRIVMSG", "de_su!loper@desktop.lan", ": some text here")); + assertTrue(fw1.logAdd("PRIVMSG", "de_su!loper@desktop.lan", ": more random tests")); + assertTrue(fw1.logAdd("NICK", "de_su!loper@desktop.lan", "developer_su")); + assertTrue(fw1.logAdd("MODE", "de_su!loper@desktop.lan", "+b username")); + assertTrue(fw1.logAdd("PART", "de_su!loper@desktop.lan", "#chan1")); + + assertTrue(fw2.logAdd("JOIN", "de_su!loper@desktop.lan", "message2")); + assertTrue(fw2.logAdd("PRIVMSG", "de_su!loper@desktop.lan", ": some text here")); + assertTrue(fw2.logAdd("PRIVMSG", "de_su!loper@desktop.lan", ": more random tests")); + assertTrue(fw2.logAdd("NICK", "de_su!loper@desktop.lan", "developer_su")); + assertTrue(fw2.logAdd("MODE", "de_su!loper@desktop.lan", "+b username")); + assertTrue(fw2.logAdd("PART", "de_su!loper@desktop.lan", "#chan2")); + + assertTrue(fw3.logAdd("JOIN", "de_su!loper@desktop.lan", "message3")); + assertTrue(fw3.logAdd("PRIVMSG", "de_su!loper@desktop.lan", ": some text here")); + assertTrue(fw3.logAdd("PRIVMSG", "de_su!loper@desktop.lan", ": more random tests")); + assertTrue(fw3.logAdd("NICK", "de_su!loper@desktop.lan", "developer_su")); + assertTrue(fw3.logAdd("MODE", "de_su!loper@desktop.lan", "+b username")); + assertTrue(fw3.logAdd("PART", "de_su!loper@desktop.lan", "#chan3")); + } + + private boolean initializeFilesLogDriver(){ + return BotDriver.setLogDriver(serverNameFiles, "files", new String[]{mainLogsDir.toString()}, ""); + } + + private boolean initializeSQLiteLogDriver(){ + return BotDriver.setLogDriver(serverNameSQLite, "sqlite", new String[]{mainSQLiteLogsDir.toString()}, ""); + } + + private void close(){ + fw1.close(); + fw2.close(); + fw3.close(); + } +} \ No newline at end of file