Start rewriting everything

This commit is contained in:
Dmitry Isaenko 2020-10-20 03:37:20 +03:00
parent 9fcb953ba5
commit 3cf0e2fa0f
42 changed files with 1507 additions and 1377 deletions

4
Jenkinsfile vendored
View file

@ -9,12 +9,12 @@ pipeline {
stages { stages {
stage('Build') { stage('Build') {
steps { steps {
sh 'mvn -B -DskipTests clean package' sh 'mvn package'
} }
} }
stage('Test') { stage('Test') {
steps { steps {
echo 'Skip testing...' sh 'mvn test'
} }
} }
stage('Deploy') { stage('Deploy') {

View file

@ -2,12 +2,22 @@
Another one IRC bot in deep-deep beta. 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 ## Usage
` -c, --configuration <name.config> [<name1.config> ...] Read Config` ```
-c, --configuration <name.config> [<name1.config> ...] Read Config
` -g, --generate [name.config] Generate Config` -g, --generate [name.config] Generate Config
-v, --version Get application version
` -v, --version Get application version` ```
#### Configuration settings #### Configuration settings
"userNickAuthStyle": "rusnet" or "freenode" "userNickAuthStyle": "rusnet" or "freenode"
* rusnet - send '/nickserv IDENTIFY mySecretPass' * 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. * SQLite - use /yourPathSet/server.db (or /yourPathSet/yourFileName.db) sqlite file.
* MongoDB - write files to MongoDB. See ConfigurationExamples folder. * MongoDB - write files to MongoDB. See ConfigurationExamples folder.
## License ### TODO:
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:
- [ ] Documentation - [ ] Documentation
- [ ] Code refactoring - [ ] Code refactoring
- [ ] QA: good regression testing - [ ] QA: good regression testing

85
pom.xml
View file

@ -4,13 +4,14 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>loper</groupId> <groupId>loper</groupId>
<artifactId>InnaIrcBot</artifactId> <artifactId>InnaIrcBot</artifactId>
<version>0.7-SNAPSHOT</version> <version>0.8-SNAPSHOT</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<name>InnaIrcBot</name> <name>InnaIrcBot</name>
<properties> <properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties> </properties>
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>org.xerial</groupId> <groupId>org.xerial</groupId>
@ -30,31 +31,78 @@
<version>3.9.1</version> <version>3.9.1</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<dependency>
<groupId>commons-cli</groupId>
<artifactId>commons-cli</artifactId>
<version>1.4</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.5.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.5.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<version>5.5.2</version>
<scope>test</scope>
</dependency>
</dependencies> </dependencies>
<build><plugins> <build>
<plugins>
<!-- ====================================================== -->
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId> <artifactId>maven-surefire-plugin</artifactId>
<version>3.1</version> <version>2.22.2</version>
<configuration> </plugin>
<source>1.8</source> <plugin>
<target>1.8</target> <groupId>org.apache.maven.plugins</groupId>
<excludes> <artifactId>maven-failsafe-plugin</artifactId>
<exclude> <version>2.22.2</version>
**/Temporary/* </plugin>
</exclude> <!-- ====================================================== -->
</excludes> <plugin>
</configuration> <groupId>org.apache.maven.plugins</groupId>
</plugin> <artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<excludes>
<exclude>
**/Temporary/*
</exclude>
</excludes>
</configuration>
</plugin>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<version>2.4</version>
<executions>
<execution>
<id>default-jar</id>
<phase>none</phase>
</execution>
</executions>
</plugin>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId> <artifactId>maven-assembly-plugin</artifactId>
<version>2.5.3</version> <version>3.1.0</version>
<configuration> <configuration>
<archive> <archive>
<manifest> <manifest>
<mainClass> <mainClass>
InnaIrcBot.BotStart InnaIrcBot.Main
</mainClass> </mainClass>
</manifest> </manifest>
</archive> </archive>
@ -72,6 +120,7 @@
</execution> </execution>
</executions> </executions>
</plugin> </plugin>
</plugins></build> </plugins>
</build>
</project> </project>

View file

@ -1,3 +0,0 @@
Manifest-Version: 1.0
Main-Class: InnaIrcBot.BotStart

View file

@ -1,38 +1,97 @@
/**
* InnaIrcBot
* @author Dmitry Isaenko
* Russia, 2018-2019.
* */
package InnaIrcBot; package InnaIrcBot;
import InnaIrcBot.Config.StorageReader; import InnaIrcBot.config.ConfigurationFileGenerator;
import org.apache.commons.cli.*;
public class BotStart { public class BotStart {
//TODO: flood control public BotStart(String[] args){
//TODO: setDaemon(true)
//TODO: multiple connections to one server not allowed
public static void main(String[] args){ final Options cliOptions = createCliOptions();
if (args.length != 0) { CommandLineParser cliParser = new DefaultParser();
if (args.length >= 2) {
if (args[0].equals("--configuration") || args[0].equals("-c")) { try{
new Connections(args); CommandLine cli = cliParser.parse(cliOptions, args);
} else if (args[0].equals("--generate") || args[0].equals("-g")) { if (cli.hasOption('v') || cli.hasOption("version")){
StorageReader.generateDefaultConfig(args[1]); handleVersion();
} return;
} }
else if (args[0].equals("--generate") || args[0].equals("-g")){ if (cli.hasOption("c") || cli.hasOption("configuration")){
StorageReader.generateDefaultConfig(null); final String[] arguments = cli.getOptionValues("configuration");
for (String a: arguments)
ConnectionsBuilder.buildConnections(arguments);
return;
} }
else if (args[0].equals("--version") || args[0].equals("-v")) { if (cli.hasOption("g") || cli.hasOption("generate")){
System.out.println(GlobalData.getAppVersion()); final String[] arguments = cli.getOptionValues("generate");
handleGenerate(arguments);
return;
} }
handleHelp(cliOptions);
} }
else { catch (ParseException pe){
System.out.println("Usage:\n" handleHelp(cliOptions);
+" \t-c, --configuration <name.config> [<name1.config> ...]\tRead Config\n" }
+"\t-g, --generate\t[name.config]\t\t\t\tGenerate Config\n" catch (Exception e){
+"\t-v, --version\t\t\t\t\t\tGet application version"); System.out.println("Error: ");
e.printStackTrace();
} }
} }
} 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);
}
}

View file

@ -12,19 +12,12 @@ public class CTCPHelper {
private static final CTCPHelper instance = new CTCPHelper(); private static final CTCPHelper instance = new CTCPHelper();
public static CTCPHelper getInstance(){ return instance; } public static CTCPHelper getInstance(){ return instance; }
private final HashMap<String, List<CTCPRequest>> waitersQueue = new HashMap<>();
private CTCPHelper(){}
private HashMap<String, List<CtcpRequest>> waitersQueue = new HashMap<>(); void registerRequest(String server, String requesterChanelOrUser, String ctcpType, String target, String notFoundMessage){
if (!waitersQueue.containsKey(server)){ // TODO: meeeeeeeehh.. looks bad
void registerRequest(String requesterServer, String requesterChanelOrUser, String ctcpType, String targetObject, String notFoundMessage){ waitersQueue.put(server, new ArrayList<>());
/*
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<>());
} }
switch (ctcpType){ switch (ctcpType){
@ -34,107 +27,67 @@ public class CTCPHelper {
case "SOURCE": case "SOURCE":
case "TIME": case "TIME":
case "USERINFO": case "USERINFO":
waitersQueue.get(requesterServer).add(new CtcpRequest(requesterChanelOrUser, targetObject, notFoundMessage, ctcpType)); waitersQueue.get(server).add(new CTCPRequest(requesterChanelOrUser, target, notFoundMessage, ctcpType));
StreamProvider.writeToStream(requesterServer, "PRIVMSG "+targetObject+" :\u0001"+ctcpType+"\u0001"); StreamProvider.writeToStream(server, "PRIVMSG "+target+" :\u0001"+ctcpType+"\u0001");
break; break;
case "PING": // TODO case "PING": // TODO
waitersQueue.get(requesterServer).add(new CtcpRequest(requesterChanelOrUser, targetObject, notFoundMessage, ctcpType)); waitersQueue.get(server).add(new CTCPRequest(requesterChanelOrUser, target, notFoundMessage, ctcpType));
StreamProvider.writeToStream(requesterServer, "PRIVMSG "+targetObject+" :\u0001PING inna\u0001"); StreamProvider.writeToStream(server, "PRIVMSG "+target+" :\u0001PING inna\u0001");
break; break;
} }
} }
public void handleCtcpReply(String serverReplied, String whoReplied, String whatReplied){ public void handleCtcpReply(String serverReplied, String whoReplied, String whatReplied){
//System.out.println("Reply serv:|"+serverReplied+"|\nwho:|"+whoReplied+"|\nwhat:|"+whatReplied+"|");
LocalDateTime currentTime = LocalDateTime.now(); LocalDateTime currentTime = LocalDateTime.now();
if (waitersQueue.containsKey(serverReplied)){
ListIterator<CtcpRequest> iterator = waitersQueue.get(serverReplied).listIterator(); if (! waitersQueue.containsKey(serverReplied))
CtcpRequest current; return;
String chanelOrUser;
while (iterator.hasNext()){ ListIterator<CTCPRequest> iterator = waitersQueue.get(serverReplied).listIterator();
current = iterator.next();
if (current.isValid(currentTime)){ while (iterator.hasNext()){
chanelOrUser = current.getRequesterChanelOrUser(whoReplied); CTCPRequest current = iterator.next();
if ( chanelOrUser != null && current.getType().equals(whatReplied.replaceAll("\\s.*$", ""))) { if (current.isValid(currentTime)){
if (whatReplied.equals("PING inna")) String channelOrUser = current.getRequesterChanelOrUser(whoReplied);
StreamProvider.writeToStream(serverReplied, "PRIVMSG " + chanelOrUser + " :" + whoReplied + ": " + Duration.between(current.getCreationTime(), currentTime).toMillis()+"ms");
else if (channelOrUser == null || ! current.getType().equals(whatReplied.replaceAll("\\s.*$", "")))
StreamProvider.writeToStream(serverReplied, "PRIVMSG " + chanelOrUser + " :" + whoReplied + ": " + whatReplied); continue;
iterator.remove();
} if (whatReplied.equals("PING inna"))
} StreamProvider.writeToStream(serverReplied, "PRIVMSG " + channelOrUser + " :" + whoReplied + ": " +
else{ Duration.between(current.getCreationTime(), currentTime).toMillis()+"ms");
//System.out.println("Drop outdated user"); else
iterator.remove(); StreamProvider.writeToStream(serverReplied, "PRIVMSG " + channelOrUser + " :" + whoReplied + ": " + whatReplied);
}
} }
iterator.remove();
} }
} }
public void handleErrorReply(String serverReplied, String whoNotFound){ public void handleErrorReply(String serverReplied, String whoNotFound){
//System.out.println("Reply serv:|"+serverReplied+"|\nwho:|"+whoNotFound+"|\n"); //System.out.println("Reply serv:|"+serverReplied+"|\nwho:|"+whoNotFound+"|\n");
LocalDateTime currentTime = LocalDateTime.now(); LocalDateTime currentTime = LocalDateTime.now();
if (waitersQueue.containsKey(serverReplied)){ if (! waitersQueue.containsKey(serverReplied))
ListIterator<CtcpRequest> iterator = waitersQueue.get(serverReplied).listIterator(); return;
CtcpRequest current;
String chanelOrUser; ListIterator<CTCPRequest> iterator = waitersQueue.get(serverReplied).listIterator();
String notFoundMessage;
while (iterator.hasNext()){ while (iterator.hasNext()){
current = iterator.next(); CTCPRequest current = iterator.next();
if (current.isValid(currentTime)){ if (! current.isValid(currentTime))
chanelOrUser = current.getRequesterChanelOrUser(whoNotFound); iterator.remove();
if ( chanelOrUser != null) { String channelOrUser = current.getRequesterChanelOrUser(whoNotFound);
notFoundMessage = current.getNotFoundMessage(whoNotFound);
if ( notFoundMessage != null) { if (channelOrUser == null)
System.out.println(serverReplied + " PRIVMSG " + chanelOrUser + " :" + notFoundMessage + whoNotFound); continue;
StreamProvider.writeToStream(serverReplied, "PRIVMSG " + chanelOrUser + " :" + notFoundMessage + whoNotFound);
} String notFoundMessage = current.getNotFoundMessage(whoNotFound);
iterator.remove();
} if (notFoundMessage == null)
} continue;
else{
//System.out.println("Drop outdated user: 401"); System.out.println(serverReplied + " PRIVMSG " + channelOrUser + " :" + notFoundMessage + whoNotFound);
iterator.remove(); 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;
}
}

View file

@ -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;
}
}

View file

@ -8,17 +8,18 @@ import java.time.format.DateTimeFormatter;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.concurrent.BlockingQueue;
import java.util.regex.Pattern; import java.util.regex.Pattern;
//TODO: FLOOD, JOIN FLOOD //TODO: FLOOD, JOIN FLOOD
// TODO: @ configuration level: if in result we have empty string, no need to pass it to server // TODO: @ configuration level: if in result we have empty string, no need to pass it to server
public class ChanelCommander implements Runnable { public class ChanelCommander implements Runnable {
private BufferedReader reader; private final BlockingQueue<String> streamQueue;
private String server; private final String server;
private String chanel; private final String channel;
//TODO: add timers //TODO: add timers
private HashMap<String, String[]> joinMap; // Mask(Pattern) ->, Action | Where Action[0] could be: raw private final HashMap<String, String[]> joinMap; // Mask(Pattern) ->, Action | Where Action[0] could be: raw
private HashMap<String, String[]> msgMap; // Mask(Pattern) ->, Action | Where Action[0] could be: raw private final HashMap<String, String[]> msgMap; // Mask(Pattern) ->, Action | Where Action[0] could be: raw
private HashMap<String, String[]> nickMap; // Mask(Pattern) ->, Action | Where Action[0] could be: raw private final HashMap<String, String[]> nickMap; // Mask(Pattern) ->, Action | Where Action[0] could be: raw
private boolean joinFloodTrackNeed = false; private boolean joinFloodTrackNeed = false;
private JoinFloodHandler jfh; private JoinFloodHandler jfh;
@ -26,10 +27,10 @@ public class ChanelCommander implements Runnable {
private boolean joinCloneTrackNeed = false; // todo:fix private boolean joinCloneTrackNeed = false; // todo:fix
private JoinCloneHandler jch; private JoinCloneHandler jch;
public ChanelCommander(BufferedReader streamReader, String serverName, String chan, String configFilePath){ public ChanelCommander(BlockingQueue<String> stream, String serverName, String channel, String configFilePath){
this.reader = streamReader; this.streamQueue = stream;
this.server = serverName; this.server = serverName;
this.chanel = chan; this.channel = channel;
this.joinMap = new HashMap<>(); this.joinMap = new HashMap<>();
this.msgMap = new HashMap<>(); this.msgMap = new HashMap<>();
@ -39,13 +40,13 @@ public class ChanelCommander implements Runnable {
@Override @Override
public void run() { public void run() {
System.out.println("Thread for ChanelCommander started"); // TODO:REMOVE DEBUG System.out.println("["+LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"))+"] ChanelCommander thread "
String data; +server+":"+this.channel +" started");// TODO:REMOVE DEBUG
String[] dataStrings;
try { try {
while ((data = reader.readLine()) != null) { while (true) {
dataStrings = data.split(" ",3); String data = streamQueue.take();
//event initiatorArg messageArg String[] dataStrings = data.split(" ",3);
switch (dataStrings[0]) { switch (dataStrings[0]) {
case "NICK": case "NICK":
nickCame(dataStrings[2]+dataStrings[1].replaceAll("^.+?!","!")); nickCame(dataStrings[2]+dataStrings[1].replaceAll("^.+?!","!"));
@ -75,8 +76,9 @@ public class ChanelCommander implements Runnable {
break; 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 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() ? String objectToCtcp = arg1.trim().replaceAll(objectRegexp, ""); // note: trim() ?
if (!objectToCtcp.isEmpty()){ if (!objectToCtcp.isEmpty()){
if (CTCPType.startsWith("\\c")) 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 else
CTCPHelper.getInstance().registerRequest(server, simplifyNick(arg2), CTCPType.substring(2).toUpperCase(), objectToCtcp, whatToSendStringBuilder.toString()); CTCPHelper.getInstance().registerRequest(server, simplifyNick(arg2), CTCPType.substring(2).toUpperCase(), objectToCtcp, whatToSendStringBuilder.toString());
} }
@ -201,40 +203,40 @@ public class ChanelCommander implements Runnable {
executiveStr.append(" :"); executiveStr.append(" :");
} }
else { else {
executiveStr.append(chanel); executiveStr.append(channel);
executiveStr.append(" :"); executiveStr.append(" :");
executiveStr.append(simplifyNick(who)); executiveStr.append(simplifyNick(who));
executiveStr.append(": "); executiveStr.append(": ");
} }
for (int i = 0; i < messages.length; i++){ for (String message : messages) {
if ( ! messages[i].startsWith("\\")) if (!message.startsWith("\\"))
executiveStr.append(messages[i]); executiveStr.append(message);
else if (messages[i].equals("\\time")) // TODO: remove this shit else if (message.equals("\\time")) // TODO: remove this shit
executiveStr.append(LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"))); executiveStr.append(LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")));
} }
//System.out.println(executiveStr.toString()); //TODO: debug //System.out.println(executiveStr.toString()); //TODO: debug
StreamProvider.writeToStream(server, executiveStr.toString()); StreamProvider.writeToStream(server, executiveStr.toString());
} }
private void banAction(String whom){ private void banAction(String whom){
StreamProvider.writeToStream(server, "MODE "+chanel+" +b "+simplifyNick(whom)+"*!*@*"); StreamProvider.writeToStream(server, "MODE "+ channel +" +b "+simplifyNick(whom)+"*!*@*");
StreamProvider.writeToStream(server, "MODE "+chanel+" +b "+"*!*@"+whom.replaceAll("^.+@","")); StreamProvider.writeToStream(server, "MODE "+ channel +" +b "+"*!*@"+whom.replaceAll("^.+@",""));
} }
private void voiceAction(String whom){ 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){ private void kickAction(String[] messages, String whom){
StringBuilder executiveStr = new StringBuilder(); StringBuilder executiveStr = new StringBuilder();
executiveStr.append("KICK "); executiveStr.append("KICK ");
executiveStr.append(chanel); executiveStr.append(channel);
executiveStr.append(" "); executiveStr.append(" ");
executiveStr.append(simplifyNick(whom)); executiveStr.append(simplifyNick(whom));
executiveStr.append(" :"); executiveStr.append(" :");
for (int i = 0; i < messages.length; i++){ for (String message : messages) {
if ( ! messages[i].startsWith("\\")) if (!message.startsWith("\\"))
executiveStr.append(messages[i]); executiveStr.append(message);
else if (messages[i].equals("\\time")) else if (message.equals("\\time"))
executiveStr.append(LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"))); executiveStr.append(LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")));
} }
StreamProvider.writeToStream(server, executiveStr.toString()); StreamProvider.writeToStream(server, executiveStr.toString());
@ -256,12 +258,13 @@ public class ChanelCommander implements Runnable {
break; break;
case "joinfloodcontrol": case "joinfloodcontrol":
if (!directive[1].isEmpty() && !directive[2].isEmpty() && Pattern.matches("^[0-9]+?$", directive[1].trim()) && Pattern.matches("^[0-9]+?$", directive[2].trim())) { 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 events = Integer.parseInt(directive[1].trim());
int timeFrame = Integer.valueOf(directive[2].trim()); int timeFrame = Integer.parseInt(directive[2].trim());
if (events > 0 && timeFrame > 0) { if (events > 0 && timeFrame > 0) {
jfh = new JoinFloodHandler(events, timeFrame, server, chanel); jfh = new JoinFloodHandler(events, timeFrame, server, channel);
joinFloodTrackNeed = true; 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"); 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; break;
case "joinclonecontrol": case "joinclonecontrol":
if (!directive[1].isEmpty() && !directive[2].isEmpty() && Pattern.matches("^[0-9]+?$", directive[1].trim())) { 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){ 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; joinCloneTrackNeed = true;
} }
else { else {
@ -289,7 +292,9 @@ public class ChanelCommander implements Runnable {
private void readConfing(String confFilesPath){ private void readConfing(String confFilesPath){
if (!confFilesPath.endsWith(File.separator)) if (!confFilesPath.endsWith(File.separator))
confFilesPath += 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()) if (!file.exists())
return; return;
try { try {
@ -298,10 +303,9 @@ public class ChanelCommander implements Runnable {
while ((line = br.readLine()) != null) { while ((line = br.readLine()) != null) {
parse(line.split("\t")); parse(line.split("\t"));
} }
} catch (FileNotFoundException e) { }
System.out.println("Internal issue: thread ChanelCommander->readConfig() can't find file:\t\n"+e); catch (Exception e) {
} catch (IOException e){ System.out.println("Internal issue: thread ChanelCommander->readConfig():\t\n"+e.getMessage());
System.out.println("Internal issue: thread ChanelCommander->readConfig() I/O exception:\t\n"+e);
} }
} }
} }

View file

@ -22,7 +22,7 @@ public class JoinCloneHandler {
prevUserNick = ""; prevUserNick = "";
lastCame = LocalDateTime.now().minusDays(1L); lastCame = LocalDateTime.now().minusDays(1L);
} }
/*
public void track(String userNick){ public void track(String userNick){
if (userNick.matches(pattern)){ if (userNick.matches(pattern)){
if (lastCame.isAfter(LocalDateTime.now().minusSeconds(timeFrameInSeconds)) && !prevUserNick.equals(userNick)){ if (lastCame.isAfter(LocalDateTime.now().minusSeconds(timeFrameInSeconds)) && !prevUserNick.equals(userNick)){
@ -37,8 +37,41 @@ public class JoinCloneHandler {
lastCame = LocalDateTime.now(); 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){ 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){ private String getNickOnly(String fullNick){
return fullNick.replaceAll("!.*$",""); return fullNick.replaceAll("!.*$","");

View file

@ -3,58 +3,68 @@ package InnaIrcBot.Commanders;
import InnaIrcBot.ProvidersConsumers.StreamProvider; import InnaIrcBot.ProvidersConsumers.StreamProvider;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.ArrayList; import java.util.*;
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<String, LprFIFOQueue> usersOnChanel;
public JoinFloodHandler(int events, int timeFrameInSeconds, String serverName, String chanelName){ public class JoinFloodHandler {
this.eventBufferSize = events; 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<String, LinkedList<LocalDateTime>> users;
public JoinFloodHandler(int joinMaxNumber, int timeFrameInSeconds, String serverName, String channelName){
this.joinMaxNumber = joinMaxNumber;
this.timeFrameInSeconds = timeFrameInSeconds; this.timeFrameInSeconds = timeFrameInSeconds;
this.server = serverName; this.server = serverName;
this.chanel = chanelName; this.channel = channelName;
this.usersOnChanel = new HashMap<>(); this.users = new HashMap<>();
} }
public void track(String userNick){ public void track(String userNickname){
if(usersOnChanel.containsKey(userNick)){ if (isNewcomer(userNickname)) {
LocalDateTime timeOfFirstEvent = usersOnChanel.get(userNick).addLastGetFirst(); registerNewUser(userNickname);
if (timeOfFirstEvent.isAfter(LocalDateTime.now().minusSeconds(timeFrameInSeconds))) { // If first event in the queue happened after 'timeFrameSeconds ago from now' return;
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) 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<LocalDateTime> userJoinHistory = users.get(user);
LocalDateTime fistJoinTime;
userJoinHistory.addLast(LocalDateTime.now());
if (userJoinHistory.size() > joinMaxNumber){
fistJoinTime = userJoinHistory.getFirst();
userJoinHistory.removeFirst();
} }
else { else {
usersOnChanel.put(userNick, new LprFIFOQueue(eventBufferSize)); // Create buffer for tracking new user joined fistJoinTime = LocalDateTime.MIN;
usersOnChanel.get(userNick).addLastGetFirst(); // Write his/her first join time
} }
return fistJoinTime;
} }
} private void kickBanUser(String user){
StreamProvider.writeToStream(server,
class LprFIFOQueue { "PRIVMSG "+ channel +" :"+user+": join flood ("+ joinMaxNumber +" connections in "+timeFrameInSeconds+" seconds).\n"+
private int size; "MODE "+ channel +" +b "+user+"!*@*"); // TODO: consider other ban methods
private ArrayList<LocalDateTime> 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);
} }
} }

View file

@ -6,13 +6,13 @@ import InnaIrcBot.ReconnectControl;
import java.util.ArrayList; import java.util.ArrayList;
public class PrivateMsgCommander { // TODO: add black list: add users after failed login queries ; temporary ban public class PrivateMsgCommander { // TODO: add black list: add users after failed login queries ; temporary ban
private String serverName; private final String server;
private ArrayList<String> administrators; private final String password;
private String password; private final ArrayList<String> administrators;
public PrivateMsgCommander(String server, String adminPassword){ public PrivateMsgCommander(String server, String adminPassword){
this.serverName = server; this.server = server;
this.administrators = new ArrayList<>(); this.administrators = new ArrayList<>();
this.password = adminPassword.trim(); this.password = adminPassword.trim();
} }
@ -155,7 +155,7 @@ public class PrivateMsgCommander { // T
case "unvoice": case "unvoice":
if ((cmd.length == 2) && (cmd[1].split("(\\s)|(\t)+?",2).length == 2)) { if ((cmd.length == 2) && (cmd[1].split("(\\s)|(\t)+?",2).length == 2)) {
String[] voiceArgs = cmd[1].split("(\\s)|(\t)+?", 2); String[] voiceArgs = cmd[1].split("(\\s)|(\t)+?", 2);
unvoice(voiceArgs[0], voiceArgs[1]); devoice(voiceArgs[0], voiceArgs[1]);
} }
else else
tell(simplifyNick(sender), "Pattern: [-v|unvoice] <channel> <user>"); tell(simplifyNick(sender), "Pattern: [-v|unvoice] <channel> <user>");
@ -173,7 +173,7 @@ public class PrivateMsgCommander { // T
case "unhop": case "unhop":
if ((cmd.length == 2) && (cmd[1].split("(\\s)|(\t)+?",2).length == 2)) { if ((cmd.length == 2) && (cmd[1].split("(\\s)|(\t)+?",2).length == 2)) {
String[] hopArgs = cmd[1].split("(\\s)|(\t)+?", 2); String[] hopArgs = cmd[1].split("(\\s)|(\t)+?", 2);
unhop(hopArgs[0], hopArgs[1]); dehop(hopArgs[0], hopArgs[1]);
} }
else else
tell(simplifyNick(sender), "Pattern: [-h|unhop] <channel> <user>"); tell(simplifyNick(sender), "Pattern: [-h|unhop] <channel> <user>");
@ -191,7 +191,7 @@ public class PrivateMsgCommander { // T
case "unop": case "unop":
if ((cmd.length == 2) && (cmd[1].split("(\\s)|(\t)+?",2).length == 2)) { if ((cmd.length == 2) && (cmd[1].split("(\\s)|(\t)+?",2).length == 2)) {
String[] args = cmd[1].split("(\\s)|(\t)+?", 2); String[] args = cmd[1].split("(\\s)|(\t)+?", 2);
unop(args[0], args[1]); deop(args[0], args[1]);
} }
else else
tell(simplifyNick(sender), "Pattern: [-o|unoperator] <channel> <user>"); tell(simplifyNick(sender), "Pattern: [-o|unoperator] <channel> <user>");
@ -238,7 +238,7 @@ public class PrivateMsgCommander { // T
} }
else else
raw("QUIT :"+message); raw("QUIT :"+message);
ReconnectControl.update(serverName, false); ReconnectControl.update(server, false);
//ReconnectControl.update(serverName, true); //ReconnectControl.update(serverName, true);
//System.exit(0); // TODO: change to normal exit //System.exit(0); // TODO: change to normal exit
} }
@ -268,7 +268,7 @@ public class PrivateMsgCommander { // T
raw("MODE "+object+" "+mode+" "+user); raw("MODE "+object+" "+mode+" "+user);
} }
private void raw(String rawText){ private void raw(String rawText){
StreamProvider.writeToStream(serverName, rawText); StreamProvider.writeToStream(server, rawText);
} }
private void kick(String chanel, String user, String reason){ private void kick(String chanel, String user, String reason){
if (reason == null) if (reason == null)
@ -295,19 +295,19 @@ public class PrivateMsgCommander { // T
private void voice(String chanel, String user){ private void voice(String chanel, String user){
cmode(chanel, "+v", user); cmode(chanel, "+v", user);
} }
private void unvoice(String chanel, String user){ private void devoice(String chanel, String user){
cmode(chanel, "-v", user); cmode(chanel, "-v", user);
} }
private void hop(String chanel, String user){ private void hop(String chanel, String user){
cmode(chanel, "+h", user); cmode(chanel, "+h", user);
} }
private void unhop(String chanel, String user){ private void dehop(String chanel, String user){
cmode(chanel, "-h", user); cmode(chanel, "-h", user);
} }
private void op(String chanel, String user){ private void op(String chanel, String user){
cmode(chanel, "+o", user); cmode(chanel, "+o", user);
} }
private void unop(String chanel, String user){ private void deop(String chanel, String user){
cmode(chanel, "-o", user); cmode(chanel, "-o", user);
} }
private void topic(String channel, String topic){ raw("TOPIC "+channel+" :"+topic); } private void topic(String channel, String topic){ raw("TOPIC "+channel+" :"+topic); }

View file

@ -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;
}
}

View file

@ -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);
}
}
}

View file

@ -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<String, StorageFile> configs;
private ArrayList<Thread> 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<Thread> 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();
}
}
}
}

View file

@ -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());
}
}
}
}

View file

@ -1,8 +1,9 @@
package InnaIrcBot; package InnaIrcBot;
public class GlobalData { public class GlobalData {
private static final String version = "InnaIrcBot v0.7 \"Комсомолец\""; private static final String version = "InnaIrcBot v0.8 \"Коммунарка\"";
public static synchronized String getAppVersion(){ public static synchronized String getAppVersion(){
return version; return version;
} }
public static final int CHANNEL_QUEUE_CAPACITY = 500;
} }

View file

@ -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<String> channelQueue;
public IrcChannel(String channelName){
this.name = channelName;
this.channelQueue = new ArrayBlockingQueue<>(GlobalData.CHANNEL_QUEUE_CAPACITY);
}
@Override
public String toString(){
return name;
}
public BlockingQueue<String> getChannelQueue() {
return channelQueue;
}
}

View file

@ -3,48 +3,56 @@ package InnaIrcBot.LogDriver;
import java.util.HashMap; import java.util.HashMap;
public class BotDriver { public class BotDriver {
private static HashMap<String, String[][]> serverDriver = new HashMap<>(); private final static HashMap<String, String[][]> serverDriver = new HashMap<>();
private final static HashMap<String, BotSystemWorker> systemLogWorkerMap = new HashMap<>();
private static HashMap<String, BotSystemWorker> systemLogWorkerMap = new HashMap<>();
/** /**
* Define driver for desired server * Define driver for desired server
* */ * */
// TODO: add proxy worker for using with multiple drivers // TODO: add proxy worker for using with multiple drivers
public static synchronized boolean setLogDriver(String serverName, String driver, String[] driverParams, String applicationLogDir){ 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()) { if (driver == null)
String[][] drvAndParams = {
{driver},
driverParams
};
serverDriver.put(serverName, drvAndParams);
systemLogWorkerMap.put(serverName, new BotSystemWorker(serverName, applicationLogDir));
return true;
}
else
return false; 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)) { public static synchronized Worker getWorker(String server, String channel){
switch (serverDriver.get(serverName)[0][0].toLowerCase()) { if (serverDriver.containsKey(server)) {
switch (serverDriver.get(server)[0][0].toLowerCase()) {
case "files": case "files":
BotFilesWorker botFilesWorker = new BotFilesWorker(serverName, serverDriver.get(serverName)[1], chanelName); BotFilesWorker botFilesWorker = new BotFilesWorker(server, serverDriver.get(server)[1], channel);
return validateConstancy(botFilesWorker, serverName, chanelName); return validateConsistancy(botFilesWorker, server, channel);
case "sqlite": case "sqlite":
BotSQLiteWorker botSQLiteWorker = new BotSQLiteWorker(serverName, serverDriver.get(serverName)[1], chanelName); BotSQLiteWorker botSQLiteWorker = new BotSQLiteWorker(server, serverDriver.get(server)[1], channel);
return validateConstancy(botSQLiteWorker, serverName, chanelName); return validateConsistancy(botSQLiteWorker, server, channel);
case "mongodb": case "mongodb":
BotMongoWorker botMongoWorker = new BotMongoWorker(serverName, serverDriver.get(serverName)[1], chanelName); BotMongoWorker botMongoWorker = new BotMongoWorker(server, serverDriver.get(server)[1], channel);
return validateConstancy(botMongoWorker, serverName, chanelName); return validateConsistancy(botMongoWorker, server, channel);
case "zero": case "zero":
return new BotZeroWorker(); return new BotZeroWorker();
default: default:
System.out.println("BotDriver->getWorker(): Configuration issue: can't find required driver \"" System.out.println("BotDriver->getWorker(): Configuration issue: can't find required driver \""
+serverDriver.get(serverName)[0][0] +serverDriver.get(server)[0][0]
+"\".Using \"ZeroWorker\"."); +"\".Using \"ZeroWorker\".");
return new BotZeroWorker(); 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(); return new BotZeroWorker();
} }
// If channel found that it's impossible to use defined worker from user settings and asking for use ZeroWorker // 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); 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()){ if (worker.isConsistent()){
return worker; return worker;
} }
else { System.out.println("BotDriver->validateConstancy(): Unable to use "
System.out.println("BotDriver->validateConstancy(): Unable to use " +worker.getClass().getSimpleName()+" for "+server+"/"+channel
+worker.getClass().getSimpleName()+" for "+srv+"/"+chan +". Using ZeroWorker instead.");
+". Using ZeroWorker instead."); return new BotZeroWorker();
return new BotZeroWorker();
}
} }
} }

View file

@ -9,57 +9,63 @@ import java.time.format.DateTimeFormatter;
import java.util.regex.Pattern; import java.util.regex.Pattern;
public class BotFilesWorker implements Worker { public class BotFilesWorker implements Worker {
private String filePath; private final String channel;
private boolean consistent = false; private String filePath;
private DateTimeFormatter dateFormat; private final DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("HH:mm:ss");
private LocalDate dateOnFile;
private LocalDate fileWriterDay;
private FileWriter fileWriter; private FileWriter fileWriter;
private String ircServer; // hold for debug only private boolean consistent;
public BotFilesWorker(String server, String[] driverParameters, String channel){ public BotFilesWorker(String server, String[] driverParameters, String channel){
ircServer = server; this.channel = channel.replaceAll(File.separator, ",");
dateFormat = DateTimeFormatter.ofPattern("HH:mm:ss");
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 { 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){ } catch (Exception e){
System.out.println("BotFilesWorker (@"+server+")->constructor(): Failure:\n\tUnable to create directory to store DB file: \n\t" +e); System.out.println("BotFilesWorker (@"+server+")->constructor(): Failure:\n" + e.getMessage());
return; // consistent = false;
} }
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; return;
}
this.consistent = resetFileWriter(false); throw new Exception("BotFilesWorker->createServerFolder() Can't create directory: "+filePath);
} }
private boolean resetFileWriter(boolean reassign){ private void resetFileWriter() throws IOException{
try { fileWriter.close();
if (reassign) createFileWriter();
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 createFileWriter() throws IOException{
dateOnFile = LocalDate.now();
File newFile = new File(filePath+channel+"_"+ dateOnFile +".txt");
fileWriter = new FileWriter(newFile);
}
@Override @Override
public boolean isConsistent() { public boolean isConsistent() {
return consistent; return consistent;
@ -70,79 +76,73 @@ public class BotFilesWorker implements Worker {
* argument[1] should be always 'subject' * argument[1] should be always 'subject'
* */ * */
@Override @Override
public boolean logAdd(String event, String initiatorArg, String messageArg) { public boolean logAdd(String event, String initiator, String message) {
switch (event){ switch (event){
case "PRIVMSG": case "PRIVMSG":
PRIVMSG(initiatorArg, messageArg); PRIVMSG(initiator, message);
break; break;
case "JOIN": case "JOIN":
JOIN(initiatorArg, messageArg); JOIN(initiator, message);
break; break;
case "MODE": case "MODE":
MODE(initiatorArg, messageArg); MODE(initiator, message);
break; break;
case "KICK": case "KICK":
KICK(initiatorArg, messageArg); KICK(initiator, message);
break; break;
case "PART": case "PART":
PART(initiatorArg, messageArg); PART(initiator, message);
break; break;
case "QUIT": case "QUIT":
QUIT(initiatorArg, messageArg); QUIT(initiator, message);
break; break;
case "NICK": case "NICK":
NICK(initiatorArg, messageArg); NICK(initiator, message);
break; break;
case "TOPIC": case "TOPIC":
TOPIC(initiatorArg, messageArg); TOPIC(initiator, message);
break; break;
default: 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; break;
} }
return consistent; return consistent;
} }
private void prettyPrint(String string){ private void prettyPrint(String string){
//if (consistent) { // could be not-opened try {
try { if (LocalDate.now().isAfter(dateOnFile)) {
if (LocalDate.now().isAfter(fileWriterDay)) { resetFileWriter();
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();
} }
//} 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)+"] "; return "["+LocalTime.now().format(dateFormat)+"] ";
} }
private String getUserNameOnly(String userNameFull){return userNameFull.replaceAll("!.+$", "");} private String getUserNameOnly(String userNameFull){
private String getUserNameAndHost(String userNameFull){return userNameFull.replaceAll("!.+$", "")+" [!"+userNameFull.replaceAll("^.+!", "")+"] ";} return userNameFull.replaceAll("!.+$", "");
}
private String getUserNameAndHost(String userNameFull){
return userNameFull.replaceAll("!.+$", "")+" [!"+userNameFull.replaceAll("^.+!", "")+"] ";
}
private void PRIVMSG(String initiatorArg, String messageArg){ private void PRIVMSG(String initiatorArg, String messageArg){
String msg = messageArg.substring(messageArg.indexOf(":")+1); String msg = messageArg.substring(messageArg.indexOf(":")+1);
if (!Pattern.matches("^\\u0001ACTION .+\\u0001", msg)){
this.prettyPrint(genDate()+"<"+getUserNameOnly(initiatorArg)+"> "+msg+"\n"); if (Pattern.matches("^\\u0001ACTION .+\\u0001", msg)){
} prettyPrint(getCurrentTimestamp()+getUserNameOnly(initiatorArg)+msg.replaceAll("(^\\u0001ACTION)|(\\u0001$)","")+"\n");
else { return;
this.prettyPrint(genDate()+getUserNameOnly(initiatorArg)+msg.replaceAll("(^\\u0001ACTION)|(\\u0001$)","")+"\n");
} }
prettyPrint(getCurrentTimestamp()+"<"+getUserNameOnly(initiatorArg)+"> "+msg+"\n");
} }
private void JOIN(String initiatorArg, String messageArg){ 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){ private void MODE(String initiatorArg, String messageArg){
String initiatorChain; String initiatorChain;
@ -150,33 +150,38 @@ public class BotFilesWorker implements Worker {
initiatorChain = getUserNameAndHost(initiatorArg)+"set"; initiatorChain = getUserNameAndHost(initiatorArg)+"set";
else else
initiatorChain = initiatorArg+" set"; 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){ 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"); " kicked by "+getUserNameAndHost(initiatorArg)+"with reason: "+messageArg.replaceAll("^.+?:", "")+"\n");
} }
private void PART(String initiatorArg, String messageArg){ 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){ 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){ 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) { 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 @Override
public void close() { public void close() {
try { try {
if (fileWriter !=null) fileWriter.close();
fileWriter.close();
} }
catch (java.io.IOException e){ catch (NullPointerException ignore) {}
System.out.println("BotFilesWorker (@"+ircServer+")->close() failed\n\tUnable to properly close file: "+this.filePath); // Live with it. catch (IOException e){
System.out.println("BotFilesWorker->close() failed\n" +
"\tUnable to properly close file: "+this.filePath); // Live with it.
} }
this.consistent = false; this.consistent = false;
} }

View file

@ -17,73 +17,74 @@ import java.util.concurrent.TimeUnit;
* For each channel we store collection that have name of the ircServer + chanelName. * 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. * 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. public class BotMongoWorker implements Worker { //TODO consider skipping checks if server already added.
private final static Map<String, MongoClient> serversMap = Collections.synchronizedMap(new HashMap<>());
private static Map<String, MongoClient> serversMap = Collections.synchronizedMap(new HashMap<String, MongoClient>()); private final String server;
private String ircServer;
private MongoCollection<Document> collection; private MongoCollection<Document> collection;
private boolean consistent = false; private boolean consistent;
public BotMongoWorker(String ircServer, String[] driverParameters, String channel){ public BotMongoWorker(String server, String[] driverParameters, String channel){
this.ircServer = ircServer; 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 mongoUser;
String mongoPass; 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. /* // Leave this validations for better times.
CommandListener mongoCommandListener = new CommandListener() { CommandListener mongoCommandListener = new CommandListener() {
@Override @Override
public void commandStarted(CommandStartedEvent commandStartedEvent) { public void commandStarted(CommandStartedEvent commandStartedEvent) {
System.out.println("BotMongoWorker (@"+this.ircServer+"): C: commandStarted"); System.out.println("BotMongoWorker (@"+this.server+"): C: commandStarted");
} }
@Override @Override
public void commandSucceeded(CommandSucceededEvent commandSucceededEvent) { public void commandSucceeded(CommandSucceededEvent commandSucceededEvent) {
System.out.println("BotMongoWorker (@"+this.ircServer+"): C: commandSucceeded"); System.out.println("BotMongoWorker (@"+this.server+"): C: commandSucceeded");
} }
@Override @Override
public void commandFailed(CommandFailedEvent commandFailedEvent) { public void commandFailed(CommandFailedEvent commandFailedEvent) {
System.out.println("BotMongoWorker (@"+this.ircServer+"): C: commandFailed"); System.out.println("BotMongoWorker (@"+this.server+"): C: commandFailed");
//consistent = false; //consistent = false;
//close(ircServer); // ircServer recieved by constructor, not this.ircServer //close(server); // server recieved by constructor, not this.server
} }
}; };
*/ */
ServerListener mongoServerListener = new ServerListener() { ServerListener mongoServerListener = new ServerListener() {
@Override @Override
public void serverOpening(ServerOpeningEvent serverOpeningEvent) { 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 @Override
public void serverClosed(ServerClosedEvent serverClosedEvent) { 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 @Override
public void serverDescriptionChanged(ServerDescriptionChangedEvent serverDescriptionChangedEvent) { public void serverDescriptionChanged(ServerDescriptionChangedEvent serverDescriptionChangedEvent) {
if (!serverDescriptionChangedEvent.getNewDescription().isOk()) { if (!serverDescriptionChangedEvent.getNewDescription().isOk()) {
close(ircServer); // ircServer recieved by constructor, not this.ircServer close(server); // server recieved by constructor, not this.server
System.out.println("BotMongoWorker (@"+ircServer+"): ServerListener: Server description changed (exception occurs): " System.out.println("BotMongoWorker (@"+server+"): ServerListener: Server description changed (exception occurs): "
+ serverDescriptionChangedEvent.getNewDescription().getException()); + serverDescriptionChangedEvent.getNewDescription().getException());
} }
} }
@ -91,111 +92,98 @@ public class BotMongoWorker implements Worker { //TODO consi
MongoClientSettings MCS = MongoClientSettings.builder() MongoClientSettings MCS = MongoClientSettings.builder()
// .addCommandListener(mongoCommandListener) // .addCommandListener(mongoCommandListener)
.applyConnectionString(new ConnectionString("mongodb://"+mongoHostAddr)) .applyConnectionString(new ConnectionString("mongodb://"+mongoHostAddress))
.applyToClusterSettings(builder -> builder.serverSelectionTimeout(5, TimeUnit.SECONDS)) .applyToClusterSettings(builder -> builder.serverSelectionTimeout(5, TimeUnit.SECONDS))
.applyToServerSettings(builder -> builder.addServerListener(mongoServerListener)) .applyToServerSettings(builder -> builder.addServerListener(mongoServerListener))
.credential(MongoCredential.createCredential(mongoUser, mongoDBName, mongoPass.toCharArray())) .credential(MongoCredential.createCredential(mongoUser, mongoDBName, mongoPass.toCharArray()))
.build(); .build();
MongoClient mongoClient = MongoClients.create(MCS); MongoClient mongoClient = MongoClients.create(MCS);
serversMap.put(ircServer, mongoClient); serversMap.put(server, mongoClient);
} }
MongoDatabase mongoDB = serversMap.get(ircServer).getDatabase(mongoDBName); MongoDatabase mongoDB = serversMap.get(server).getDatabase(mongoDBName);
collection = mongoDB.getCollection(ircServer + channel); collection = mongoDB.getCollection(server + channel);
Document ping = new Document("ping", 1); Document ping = new Document("ping", 1);
// //
try { try {
Document answer = mongoDB.runCommand(ping); // reports to monitor thread if some fuckups happens Document answer = mongoDB.runCommand(ping); // reports to monitor thread if some fuckups happens
if (answer.get("ok") == null || (Double)answer.get("ok") != 1.0d){ if (answer.get("ok") == null || (Double)answer.get("ok") != 1.0d){
close(ircServer); close(server);
return; return;
} }
consistent = true; consistent = true;
} catch (MongoCommandException mce){ } catch (MongoCommandException mce){
System.out.println("BotMongoWorker (@"+this.ircServer+"): Command exception. Check if username/password set correctly."); System.out.println("BotMongoWorker (@"+this.server +"): Command exception. Check if username/password set correctly.");
close(ircServer); // ircServer received by constructor, not this.ircServer close(server); // server received by constructor, not this.server
} catch (MongoTimeoutException mte) { } catch (MongoTimeoutException mte) {
System.out.println("BotMongoWorker (@"+this.ircServer+"): Timeout exception."); System.out.println("BotMongoWorker (@"+this.server +"): Timeout exception.");
close(ircServer); // ircServer received by constructor, not this.ircServer close(server); // server received by constructor, not this.server
}catch (MongoException me){ }catch (MongoException me){
System.out.println("BotMongoWorker (@"+this.ircServer+"): MongoDB Exception."); System.out.println("BotMongoWorker (@"+this.server +"): MongoDB Exception.");
close(ircServer); // ircServer received by constructor, not this.ircServer close(server); // server received by constructor, not this.server
} catch (IllegalStateException ise){ } 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; consistent = false;
// no need to close() obviously // no need to close() obviously
} }
if (consistent){ if (consistent){
ThingToCloseOnDie thing = new ThingToCloseOnDie() { ThingToCloseOnDie thing = () -> {
@Override if (serversMap.containsKey(server)) {
public void die() { serversMap.get(server).close();
if (serversMap.containsKey(ircServer)) { serversMap.remove(server);
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);
}
}
} }
}; };
BotDriver.getSystemWorker(ircServer).registerInSystemWorker(thing); BotDriver.getSystemWorker(server).registerInSystemWorker(thing);
} }
} }
@Override @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()) Document document = new Document("date", getDate())
.append("event", event) .append("event", event)
.append("initiator", initiatorArg); .append("initiator", initiator);
switch (event) { switch (event) {
case "NICK":
case "JOIN":
document.append("message1", messageArg);
//preparedStatement.setString(5, null);
break;
case "PART": case "PART":
case "QUIT": case "QUIT":
case "TOPIC": case "TOPIC":
document.append("message1", messageArg.replaceAll("^.+?:", "")); document.append("message1", message.replaceAll("^.+?:", ""));
//preparedStatement.setString(5, null);
break; break;
case "MODE": case "MODE":
document.append("message1", messageArg.replaceAll("(^(.+?\\s){1})|(\\s.+$)","")); document.append("message1", message.replaceAll("(^(.+?\\s){1})|(\\s.+$)",""));
document.append("message2", messageArg.replaceAll("^(.+?\\s){2}", "")); document.append("message2", message.replaceAll("^(.+?\\s){2}", ""));
break; break;
case "KICK": case "KICK":
document.append("message1", messageArg.replaceAll("^.+?:", "")); document.append("message1", message.replaceAll("^.+?:", ""));
document.append("message2", messageArg.replaceAll("(^.+?\\s)|(\\s.+$)", "")); document.append("message2", message.replaceAll("(^.+?\\s)|(\\s.+$)", ""));
break; break;
case "PRIVMSG": case "PRIVMSG":
document.append("message1", messageArg.replaceAll("^:", "")); document.append("message1", message.replaceAll("^:", ""));
//preparedStatement.setString(5,null);
break; break;
case "NICK":
case "JOIN":
default: default:
document.append("message1", messageArg); document.append("message1", message);
//preparedStatement.setString(5,null);
break; break;
} }
try { try {
collection.insertOne(document); // TODO: call finalize? collection.insertOne(document); // TODO: call finalize?
consistent = true; // if no exceptions, then true consistent = true; // if no exceptions, then true
} catch (MongoCommandException mce){ } 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(); this.close();
} catch (MongoTimeoutException mte) { } catch (MongoTimeoutException mte) {
System.out.println("BotMongoWorker (@"+this.ircServer+")->logAdd(): Timeout exception."); System.out.println("BotMongoWorker (@"+this.server +")->logAdd(): Timeout exception.");
this.close(); this.close();
}catch (MongoException me){ }catch (MongoException me){
System.out.println("BotMongoWorker (@"+this.ircServer+")->logAdd(): MongoDB Exception."); System.out.println("BotMongoWorker (@"+this.server +")->logAdd(): MongoDB Exception.");
this.close(); this.close();
} catch (IllegalStateException ise){ } 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(); this.close();
} }
return consistent; return consistent;
@ -214,7 +202,7 @@ public class BotMongoWorker implements Worker { //TODO consi
if (serversMap.containsKey(server)) { if (serversMap.containsKey(server)) {
serversMap.get(server).close(); serversMap.get(server).close();
serversMap.remove(server); 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; consistent = false;
} }

View file

@ -155,37 +155,37 @@ public class BotSQLiteWorker implements Worker {
public boolean isConsistent() {return consistent; } public boolean isConsistent() {return consistent; }
@Override @Override
public boolean logAdd(String event, String initiatorArg, String messageArg) { public boolean logAdd(String event, String initiator, String message) {
try { try {
preparedStatement.setLong(1, getDate()); preparedStatement.setLong(1, getDate());
preparedStatement.setString(2, event); preparedStatement.setString(2, event);
preparedStatement.setString(3, initiatorArg); preparedStatement.setString(3, initiator);
switch (event) { switch (event) {
case "NICK": case "NICK":
case "JOIN": case "JOIN":
preparedStatement.setString(4, messageArg); preparedStatement.setString(4, message);
preparedStatement.setString(5, null); preparedStatement.setString(5, null);
break; break;
case "PART": case "PART":
case "QUIT": case "QUIT":
case "TOPIC": case "TOPIC":
preparedStatement.setString(4, messageArg.replaceAll("^.+?:", "")); preparedStatement.setString(4, message.replaceAll("^.+?:", ""));
preparedStatement.setString(5, null); preparedStatement.setString(5, null);
break; break;
case "MODE": case "MODE":
preparedStatement.setString(4, messageArg.replaceAll("(^(.+?\\s){1})|(\\s.+$)","")); preparedStatement.setString(4, message.replaceAll("(^(.+?\\s){1})|(\\s.+$)",""));
preparedStatement.setString(5, messageArg.replaceAll("^(.+?\\s){2}", "")); preparedStatement.setString(5, message.replaceAll("^(.+?\\s){2}", ""));
break; break;
case "KICK": case "KICK":
preparedStatement.setString(4, messageArg.replaceAll("^.+?:", "")); preparedStatement.setString(4, message.replaceAll("^.+?:", ""));
preparedStatement.setString(5, messageArg.replaceAll("(^.+?\\s)|(\\s.+$)", "")); preparedStatement.setString(5, message.replaceAll("(^.+?\\s)|(\\s.+$)", ""));
break; break;
case "PRIVMSG": case "PRIVMSG":
preparedStatement.setString(4, messageArg.replaceAll("^:", "")); preparedStatement.setString(4, message.replaceAll("^:", ""));
preparedStatement.setString(5,null); preparedStatement.setString(5,null);
break; break;
default: default:
preparedStatement.setString(4, messageArg); preparedStatement.setString(4, message);
preparedStatement.setString(5,null); preparedStatement.setString(5,null);
break; break;
} }

View file

@ -9,48 +9,43 @@ import java.time.format.DateTimeFormatter;
public class BotSystemWorker implements SystemWorker{ public class BotSystemWorker implements SystemWorker{
private FileWriter fileWriter; 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. 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; private boolean consistent = false;
public BotSystemWorker(String ircServer, String appLogDir){ public BotSystemWorker(String server, String appLogDir){
this.ircServer = ircServer; this.server = server;
this.dateFormat = DateTimeFormatter.ofPattern("HH:mm:ss");
if (appLogDir.isEmpty()) { if (appLogDir.isEmpty()) {
if (System.getProperty("os.name").toLowerCase().startsWith("win")) { appLogDir = System.getProperty("java.io.tmpdir")+File.separator+"innaircbot"+File.separator;
appLogDir = System.getProperty("user.home")+ File.separator }
+"AppData"+File.separator else if (! appLogDir.endsWith(File.separator)) {
+"Local"+File.separator appLogDir += File.separator;
+"InnaIrcBot"+File.separator;
} else {
appLogDir = "/var/log/innaircbot/";
}
} }
if (!appLogDir.endsWith(File.separator))
appLogDir = appLogDir+File.separator;
appLogDir = appLogDir+ircServer; appLogDir += server;
File logFile = new File(appLogDir); File logFile = new File(appLogDir);
try { try {
logFile.getParentFile().mkdirs(); if (! logFile.getParentFile().exists()) {
} catch (SecurityException e){ if (! logFile.getParentFile().mkdirs()){
System.out.println("BotSystemWorker (@"+ircServer+")->constructor() failed. Unable to create sub-directory(-ies) to store logs file ("+appLogDir+"):\n\t"+e); System.out.println("BotSystemWorker (@"+server+")->constructor() failed:\n" +
return; // Consistent = false "\tUnable to create sub-directory(-ies) to store log file: " + appLogDir);
} return;
if (!logFile.getParentFile().exists()) { }
System.out.println("BotSystemWorker (@"+ircServer+")->constructor() failed:\n\tUnable to create sub-directory(-ies) to store log file: " + appLogDir); }
return; fileWriter = new FileWriter(logFile, true);
}
try {
this.fileWriter = new FileWriter(logFile, true);
consistent = 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){ } 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 { try {
fileWriter.write(genDate() + event + " " + initiatorArg + " " + messageArg + "\n"); fileWriter.write(genDate() + event + " " + initiatorArg + " " + messageArg + "\n");
fileWriter.flush(); fileWriter.flush();
} catch (IOException e) { } catch (Exception e) { // ??? No ideas. Just in case. Consider removing.
System.out.println("BotSystemWorker (@" + ircServer + ")->logAdd() failed\n\tUnable to write logs of because of internal failure in LocalTime representation."); System.out.println("BotSystemWorker (@" + server + ")->logAdd() failed\n\tUnable to write logs of because of exception:\n\t" + e.getMessage());
//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);
//this.close(); //this.close();
consistent = false; consistent = false;
} }
return;
} }
else { System.out.println(genDate() + event + " " + initiatorArg + " " + messageArg + "\n");
System.out.println(genDate() + event + " " + initiatorArg + " " + messageArg + "\n");
}
} }
@Override @Override
@ -93,14 +80,11 @@ public class BotSystemWorker implements SystemWorker{
public void close() { public void close() {
if (thingToCloseOnDie != null) if (thingToCloseOnDie != null)
thingToCloseOnDie.die(); thingToCloseOnDie.die();
if (fileWriter != null) {
try { try {
fileWriter.close(); fileWriter.close();
}
catch (java.io.IOException e){
System.out.println("BotSystemWorker (@"+ircServer+")->close() failed\n\tUnable to properly close logs file."); // Live with it.
}
} }
catch (IOException | NullPointerException ignore){}
consistent = false; consistent = false;
} }
} }

View file

@ -5,7 +5,7 @@ public class BotZeroWorker implements Worker{
public boolean isConsistent() {return true;} public boolean isConsistent() {return true;}
@Override @Override
public boolean logAdd(String event, String initiatorArg, String messageArg) { return true; } public boolean logAdd(String event, String initiator, String message) { return true; }
@Override @Override
public void close() {} public void close() {}

View file

@ -6,8 +6,8 @@ public interface Worker {
boolean isConsistent(); boolean isConsistent();
boolean logAdd(String event, boolean logAdd(String event,
String initiatorArg, String initiator,
String messageArg); String message);
void close(); void close();
} }

View file

@ -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);
}
}

View file

@ -1,82 +1,86 @@
package InnaIrcBot.ProvidersConsumers; package InnaIrcBot.ProvidersConsumers;
import InnaIrcBot.Commanders.ChanelCommander; import InnaIrcBot.Commanders.ChanelCommander;
import InnaIrcBot.GlobalData;
import InnaIrcBot.IrcChannel;
import InnaIrcBot.LogDriver.BotDriver; import InnaIrcBot.LogDriver.BotDriver;
import InnaIrcBot.LogDriver.Worker; import InnaIrcBot.LogDriver.Worker;
import InnaIrcBot.config.ConfigurationManager;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.time.LocalTime; import java.time.LocalTime;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
public class ChanConsumer implements Runnable { public class ChanConsumer implements Runnable {
private BufferedReader reader; private final BlockingQueue<String> chanConsumerQueue;
private String serverName; private final String serverName;
private String channelName; private final String channelName;
private Worker writerWorker; private Worker writerWorker;
private ArrayList<String> userList; private ArrayList<String> userList;
private String nick; private String nick;
private boolean rejoin; private final boolean rejoin;
private Map<String, PrintWriter> chanList; private final Map<String, IrcChannel> channels;
private String configFilePath;
private PrintWriter chanelCommanderPipe; private Thread channelCommanderThread;
private BlockingQueue<String> queue;
private boolean endThread = false; private boolean endThread = false;
ChanConsumer(BufferedReader streamReader, String serverName, String channelName, String ownNick, String[] usersOnChan, boolean rejoinAlways, Map<String, PrintWriter> map, String configFilePath){ ChanConsumer(String serverName,
this.reader = streamReader; IrcChannel thisIrcChannel,
String ownNick,
Map<String, IrcChannel> channels) throws Exception{
this.chanConsumerQueue = thisIrcChannel.getChannelQueue();
this.serverName = serverName; this.serverName = serverName;
this.channelName = channelName; this.channelName = thisIrcChannel.toString();
this.writerWorker = BotDriver.getWorker(serverName, channelName); this.writerWorker = BotDriver.getWorker(serverName, channelName);
this.userList = new ArrayList<>(); this.userList = new ArrayList<>();
this.userList.addAll(Arrays.asList(usersOnChan));
this.nick = ownNick; this.nick = ownNick;
this.rejoin = rejoinAlways; this.rejoin = ConfigurationManager.getConfiguration(serverName).getRejoinOnKick();
this.chanList = map; this.channels = channels;
this.configFilePath = configFilePath;
// Create chanel commander thread, get pipe // 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(){ public void run(){
String data; String data;
String[] dataStrings; 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 { try {
while ((data = reader.readLine()) != null) { while (! endThread) {
data = chanConsumerQueue.take();
dataStrings = data.split(" ",3); dataStrings = data.split(" ",3);
if (!trackUsers(dataStrings[0], dataStrings[1], dataStrings[2])) if (! trackUsers(dataStrings[0], dataStrings[1], dataStrings[2]))
continue; continue;
// Send to chanel commander thread // Send to chanel commander thread
chanelCommanderPipe.println(data); queue.add(data); // TODO: Check and add consistency validation
chanelCommanderPipe.flush();
//System.out.println("|"+dataStrings[0]+"|"+dataStrings[1]+"|"+dataStrings[2]+"|");
//System.out.println("Thread: "+this.channelName +"\n\tArray:"+ userList);
if (!writerWorker.logAdd(dataStrings[0], dataStrings[1], dataStrings[2])){ // Write logs, check if LogDriver consistent. If not: 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]); 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){ channels.remove(channelName);
System.out.println("ChanConsumer (@"+serverName+"/"+channelName+")->run(): Internal issue in thread: caused I/O exception:\n\t"+e); // TODO: reconnect } catch (InterruptedException e){
System.out.println("ChanConsumer (@"+serverName+"/"+channelName+")->run(): Interrupted\n\t"+e); // TODO: reconnect?
} }
writerWorker.close(); writerWorker.close();
//Chanel commander thread's pipe should be closed //Kill sub-thread
chanelCommanderPipe.close(); channelCommanderThread.interrupt();
System.out.println("["+LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"))+"] THREAD "+serverName+":"+this.channelName +" ended"); // TODO:REMOVE DEBUG 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 case "PRIVMSG": // most common, we don't have to handle anything else
return true; return true;
case "JOIN": case "JOIN":
addUsers(simplifyNick(initiatorArg)); addUser(simplifyNick(initiatorArg));
return true; return true;
case "PART": case "PART":
delUsers(simplifyNick(initiatorArg)); // nick non-simple deleteUser(simplifyNick(initiatorArg)); // nick non-simple
return true; return true;
case "QUIT": case "QUIT":
if (userList.contains(simplifyNick(initiatorArg))) { if (userList.contains(simplifyNick(initiatorArg))) {
delUsers(simplifyNick(initiatorArg)); // nick non-simple deleteUser(simplifyNick(initiatorArg)); // nick non-simple
return true; return true;
} else }
else
return false; // user quit, but he/she is not in this channel return false; // user quit, but he/she is not in this channel
case "KICK": case "KICK":
if (rejoin && nick.equals(subjectArg.replaceAll("(^.+?\\s)|(\\s.+$)", ""))) // if it's me and I have rejoin policy 'Auto-Rejoin on 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); StreamProvider.writeToStream(serverName, "JOIN " + channelName);
delUsers(subjectArg.replaceAll("(^.+?\\s)|(\\s.+$)", "")); // nick already simplified deleteUser(subjectArg.replaceAll("(^.+?\\s)|(\\s.+$)", "")); // nick already simplified
return true; return true;
case "NICK": case "NICK":
if (userList.contains(simplifyNick(initiatorArg))) { if (userList.contains(simplifyNick(initiatorArg))) {
swapUsers(simplifyNick(initiatorArg), subjectArg); swapUsers(simplifyNick(initiatorArg), subjectArg);
return true; return true;
} else { }
else {
return false; // user changed nick, but he/she is not in this channel 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: default:
return true; return true;
} }
} }
private void addUsers(String user){ private void addUser(String user){
if (!userList.contains(user)) if (!userList.contains(user))
userList.add(user); userList.add(user);
} }
private void delUsers(String user){ private void deleteUser(String user){
if (user.equals(nick)) { if (user.equals(nick)) {
endThread = true; endThread = true;
} }
@ -129,33 +141,12 @@ public class ChanConsumer implements Runnable {
if (userNickOld.equals(nick)) if (userNickOld.equals(nick))
this.nick = userNickNew; 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 String simplifyNick(String nick){ return nick.replaceAll("!.*$",""); }
private void fixLogDriverIssues(String a, String b, String c){ private void fixLogDriverIssues(String a, String b, String c){
System.out.println("ChanConsumer (@"+serverName+"/"+channelName+")->fixLogDriverIssues(): Some issues detected. Trying to fix..."); 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 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(); this.writerWorker = BotDriver.getZeroWorker();
System.out.println("ChanConsumer (@"+serverName+"/"+channelName+")->fixLogDriverIssues(): failed to use defined LogDriver. Using ZeroWorker instead."); System.out.println("ChanConsumer (@"+serverName+"/"+channelName+")->fixLogDriverIssues(): failed to use defined LogDriver. Using ZeroWorker instead.");
} }

View file

@ -1,6 +1,7 @@
package InnaIrcBot.ProvidersConsumers; package InnaIrcBot.ProvidersConsumers;
import InnaIrcBot.Config.StorageFile; import InnaIrcBot.config.ConfigurationFile;
import InnaIrcBot.IrcChannel;
import InnaIrcBot.LogDriver.BotDriver; import InnaIrcBot.LogDriver.BotDriver;
import InnaIrcBot.ReconnectControl; import InnaIrcBot.ReconnectControl;
@ -8,131 +9,94 @@ import java.io.*;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.Socket; import java.net.Socket;
import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Collections; import java.util.*;
import java.util.HashMap; import java.util.concurrent.BlockingQueue;
import java.util.Map;
public class DataProvider implements Runnable { public class DataProvider implements Runnable {
private StorageFile configFile; private final ConfigurationFile configurationFile;
private String serverName; private final String serverName;
private String userNick; private final String nickName;
private BufferedReader rawStreamReader; private BufferedReader rawStreamReader;
private boolean ableToRun = true;
/** /**
* Initiate connection and prepare input/output streams for run() * Initiate connection and prepare input/output streams for run()
* */ * */
public DataProvider(StorageFile storageFile){ public DataProvider(ConfigurationFile configurationFile){
this.configFile = storageFile; this.configurationFile = configurationFile;
this.serverName = storageFile.getServerName(); this.serverName = configurationFile.getServerName();
this.nickName = configurationFile.getUserNick();
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 void run(){ public void run(){
if (!ableToRun || !this.initConnection(rawStreamReader) try {
|| !BotDriver.setLogDriver(serverName, configFile.getLogDriver(), configFile.getLogDriverParameters(), configFile.getApplicationLogDir())) { //Prepare logDriver for using in threads. 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(); this.close();
return; return;
} }
/* Used for sending data into consumers objects*/ /* Used for sending data into consumers objects*/
Map<String, PrintWriter> channelsMap = Collections.synchronizedMap(new HashMap<String, PrintWriter>()); Map<String, IrcChannel> ircChannels = Collections.synchronizedMap(new HashMap<>());
try {
PipedOutputStream streamOut = new PipedOutputStream(); // K-K-K-KOMBO \m/
BufferedReader streamBufferedReader = new BufferedReader( IrcChannel systemConsumerChannel = new IrcChannel("");
new InputStreamReader( BlockingQueue<String> systemConsumerQueue = systemConsumerChannel.getChannelQueue();
new PipedInputStream(streamOut), StandardCharsets.UTF_8)
);
Runnable systemConsumer = new SystemConsumer(streamBufferedReader, userNick, channelsMap, this.configFile); Thread SystemConsumerThread = new Thread(
new Thread(systemConsumer).start(); new SystemConsumer(systemConsumerQueue, nickName, ircChannels, this.configurationFile));
PrintWriter systemConsumerWriter = new PrintWriter(streamOut); 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.. ircChannels.put(systemConsumerChannel.toString(), systemConsumerChannel); // 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;
}
////////////////////////////////////// Start loop ////////////////////////////////////////////////////////////// ////////////////////////////////////// Start loop //////////////////////////////////////////////////////////////
StreamProvider.writeToStream(serverName,"NICK "+this.nickName);
StreamProvider.writeToStream(serverName,"USER "+ configurationFile.getUserIdent()+" 8 * :"+ configurationFile.getUserRealName()); // TODO: Add usermode 4 rusnet
try { try {
String rawMessage; String rawMessage;
String[] rawStrings; // prefix[0] command[1] command-parameters\r\n[2] 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. //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) { while ((rawMessage = rawStreamReader.readLine()) != null) {
//System.out.println(rawMessage); System.out.println(rawMessage);
if (rawMessage.startsWith(":")) { if (rawMessage.startsWith(":")) {
rawStrings = rawMessage rawStrings = rawMessage
.substring(1) .substring(1)
.split(" :?", 3); // Removing ':' .split(" :?", 3); // Removing ':'
String chan = rawStrings[2].replaceAll("(\\s.?$)|(\\s.+?$)", "");
chan = rawStrings[2].replaceAll("(\\s.?$)|(\\s.+?$)", "");
//System.out.println("\tChannel: "+chan+"\n\tAction: "+rawStrings[1]+"\n\tSender: "+rawStrings[0]+"\n\tMessage: "+rawStrings[2]+"\n"); //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 if (rawStrings[1].equals("QUIT") || rawStrings[1].equals("NICK")) { // replace regex
for (PrintWriter value : channelsMap.values()) { for (IrcChannel ircChannel : ircChannels.values()) {
value.println(rawStrings[1] + " " + rawStrings[0] + " " + rawStrings[2]); ircChannel.getChannelQueue().add(rawStrings[1] + " " + rawStrings[0] + " " + rawStrings[2]);
value.flush();
} }
} 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 :")) { else if (ircChannels.containsKey(chan)) {
pingSrvResponse(rawMessage); IrcChannel chnl = ircChannels.get(chan);
} else { 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); 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 System.out.println("Socket issue: I/O exception"); //Connection closed. TODO: MAYBE try reconnect
} }
finally { finally {
for (PrintWriter p :channelsMap.values()) { SystemConsumerThread.interrupt();
p.close(); close();
}
this.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 :", "")); 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(); //HANDLE ALWAYS in case thread decided to die
private void close(){
if (this.userNick == null || this.userNick.isEmpty()) { StreamProvider.delStream(serverName);
System.out.println("Configuration issue: no nickname specified."); ReconnectControl.notify(serverName);
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;
} }
} }

View file

@ -1,46 +1,39 @@
package InnaIrcBot.ProvidersConsumers; package InnaIrcBot.ProvidersConsumers;
import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.io.OutputStreamWriter; import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket; import java.net.Socket;
import java.util.HashMap; import java.util.HashMap;
import java.util.concurrent.BlockingQueue;
public class StreamProvider { public class StreamProvider {
private static HashMap<String, OutputStreamWriter> srvStreamMap = new HashMap<>(); private static final HashMap<String, OutputStreamWriter> srvStreamMap = new HashMap<>();
private static HashMap<String, PrintWriter> srvSysConsumersMap = new HashMap<>(); private static final HashMap<String, BlockingQueue<String>> srvSysConsumersMap = new HashMap<>();
public static synchronized void writeToStream(String server, String message){ public static synchronized void writeToStream(String server, String message){
try { try {
srvStreamMap.get(server).write(message+"\n"); srvStreamMap.get(server).write(message+"\n");
srvStreamMap.get(server).flush(); srvStreamMap.get(server).flush();
//System.out.println("W:"+message);
// If this application says something, then pass it into system consumer thread to handle // If this application says something, then pass it into system consumer thread to handle
if (message.startsWith("PRIVMSG ")) { if (message.startsWith("PRIVMSG ")) {
srvSysConsumersMap.get(server).println("INNA "+message); srvSysConsumersMap.get(server).add("INNA "+message);
srvSysConsumersMap.get(server).flush();
} }
} catch (java.io.IOException e){ } catch (IOException e){
System.out.println("Internal issue: StreamProvider->writeToStream() caused I/O exception:\n\t"+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){ public static synchronized void setStream(String server, Socket socket) throws IOException{
try { OutputStream outStream = socket.getOutputStream();
OutputStream outStream = socket.getOutputStream(); srvStreamMap.put(server, new OutputStreamWriter(outStream));
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 delStream(String server){ public static synchronized void delStream(String server){
srvStreamMap.remove(server); srvStreamMap.remove(server);
srvSysConsumersMap.remove(server); srvSysConsumersMap.remove(server);
} }
public static synchronized void setSysConsumer(String server, PrintWriter pw){ public static synchronized void setSysConsumer(String server, BlockingQueue<String> queue){
srvSysConsumersMap.put(server, pw); srvSysConsumersMap.put(server, queue);
} }
} }

View file

@ -2,56 +2,45 @@ package InnaIrcBot.ProvidersConsumers;
import InnaIrcBot.Commanders.CTCPHelper; import InnaIrcBot.Commanders.CTCPHelper;
import InnaIrcBot.Commanders.PrivateMsgCommander; import InnaIrcBot.Commanders.PrivateMsgCommander;
import InnaIrcBot.Config.StorageFile; import InnaIrcBot.ReconnectControl;
import InnaIrcBot.config.ConfigurationFile;
import InnaIrcBot.GlobalData; import InnaIrcBot.GlobalData;
import InnaIrcBot.IrcChannel;
import InnaIrcBot.LogDriver.BotDriver; import InnaIrcBot.LogDriver.BotDriver;
import InnaIrcBot.LogDriver.BotSystemWorker; import InnaIrcBot.LogDriver.BotSystemWorker;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.LocalTime; import java.time.LocalTime;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.regex.Pattern; import java.util.concurrent.BlockingQueue;
public class SystemConsumer implements Runnable{ public class SystemConsumer implements Runnable{
private BufferedReader reader; private final BlockingQueue<String> systemQueue;
private BotSystemWorker writerWorker; private BotSystemWorker writerWorker;
private String nick; private String nick;
private String serverName; private String serverName;
private Map<String, PrintWriter> channelsMap; private final Map<String, IrcChannel> channels;
private boolean proxyRequired; private ConfigurationFile configurationFile;
private HashMap<String, ArrayList<String>> proxyAList;
private StorageFile storageFile;
private PrivateMsgCommander commander; private PrivateMsgCommander commander;
private LocalDateTime lastCTCPReplyTime; private LocalDateTime lastCTCPReplyTime;
SystemConsumer(BufferedReader streamReader, String userNick, Map<String, PrintWriter> map, StorageFile storage) { private ArrayList<Thread> channelThreads;
this.writerWorker = BotDriver.getSystemWorker(storage.getServerName()); private int nickTail = 0;
SystemConsumer(BlockingQueue<String> systemQueue, String userNick, Map<String, IrcChannel> channels, ConfigurationFile configurationFile) {
this.systemQueue = systemQueue;
this.writerWorker = BotDriver.getSystemWorker(configurationFile.getServerName());
this.nick = userNick; this.nick = userNick;
this.serverName = storage.getServerName(); this.serverName = configurationFile.getServerName();
this.channelsMap = map; this.channels = channels;
this.reader = streamReader; this.channelThreads = new ArrayList<>();
this.configurationFile = configurationFile;
this.proxyRequired = false; this.commander = new PrivateMsgCommander(serverName, this.configurationFile.getBotAdministratorPassword());
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());
lastCTCPReplyTime = LocalDateTime.now(); lastCTCPReplyTime = LocalDateTime.now();
} }
@ -62,159 +51,115 @@ public class SystemConsumer implements Runnable{
setMainRoutine(); setMainRoutine();
for (PrintWriter p :channelsMap.values()) //TODO: check, code duplication. see Data provider constructor for (Thread channel : channelThreads) { //TODO: check, code duplication. see Data provider constructor
p.close(); channel.interrupt();
}
writerWorker.close(); writerWorker.close();
System.out.println("["+LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"))+"] THREAD "+serverName+":[system] ended"); // TODO:REMOVE DEBUG System.out.println("["+LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"))+"] THREAD "+serverName+":[system] ended"); // TODO:REMOVE DEBUG
} }
private void setMainRoutine(){ private void setMainRoutine(){
String data;
String[] dataStrings;
try { try {
while ((data = reader.readLine()) != null) { while (true) {
dataStrings = data.split(" ",3); String data = systemQueue.take();
String[] dataStrings = data.split(" ",3);
if (proxyRequired) //TODO: handle mode change
if (getProxy(dataStrings[0], dataStrings[1], dataStrings[2])) switch (dataStrings[0]){
continue; // TODO: check this. Continue is fair? case "PRIVMSG":
if (dataStrings[2].indexOf("\u0001") < dataStrings[2].lastIndexOf("\u0001")) {
if (Pattern.matches("(^[0-9]{3}$)|(^NICK$)|(^JOIN$)|(^QUIT$)|(^NOTICE$)", dataStrings[0])){ replyCTCP(simplifyNick(dataStrings[1]), dataStrings[2].substring(dataStrings[2].indexOf(":") + 1));
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]);
} }
} 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");
} }
} catch (InterruptedException ie){
private boolean getProxy(String eventNum, String sender, String message){ //TODO: if can't join: like channel with password System.out.println("Thread SystemConsumer->run() interrupted."); // TODO: reconnect OR AT LEAST DIE
if (eventNum.equals("353")) {
//writerWorker.logAdd("[proxy]", "catch: "+eventNum+" from: "+sender+" :",message);
return false; // never mind and let it flows as usual.
} }
else { catch (Exception e){
//writerWorker.logAdd("[proxy]", "catch: "+eventNum+" from: "+sender+" :",message); System.out.println("Internal issue: thread SystemConsumer->run(): "+e.getMessage()); // TODO: DO.. some thing
String chan = message.replaceAll("(\\s.?$)|(\\s.+?$)", "");
if (eventNum.equals("QUIT") || eventNum.equals("NICK")) {
for (ArrayList<String> 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;
} }
} }
private void replyCTCP(String sender, String message){ // got simplified nick 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 // TODO: Consider moving to config file. Now set to 3 sec
lastCTCPReplyTime = LocalDateTime.now(); if (lastCTCPReplyTime.isAfter(LocalDateTime.now().minusSeconds(3)))
if (message.equals("\u0001VERSION\u0001")) { return;
lastCTCPReplyTime = LocalDateTime.now();
switch (message) {
case "\u0001VERSION\u0001":
StreamProvider.writeToStream(serverName, "NOTICE " + sender + " :\u0001VERSION " + GlobalData.getAppVersion() + "\u0001"); StreamProvider.writeToStream(serverName, "NOTICE " + sender + " :\u0001VERSION " + GlobalData.getAppVersion() + "\u0001");
writerWorker.logAdd("[system]", "catch/handled CTCP VERSION from", sender); writerWorker.logAdd("[system]", "catch/handled CTCP VERSION from", sender);
//System.out.println("NOTICE "+sender+" \u0001VERSION "+ GlobalData.getAppVersion()+"\u0001"); return;
} else if (message.startsWith("\u0001PING ") && message.endsWith("\u0001")) { case "\u0001CLIENTINFO\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")) {
StreamProvider.writeToStream(serverName, "NOTICE " + sender + " :\u0001CLIENTINFO ACTION PING VERSION TIME CLIENTINFO SOURCE\u0001"); StreamProvider.writeToStream(serverName, "NOTICE " + sender + " :\u0001CLIENTINFO ACTION PING VERSION TIME CLIENTINFO SOURCE\u0001");
writerWorker.logAdd("[system]", "catch/handled CTCP CLIENTINFO from", sender); 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"); return;
} else if (message.equals("\u0001TIME\u0001")) { case "\u0001TIME\u0001":
StreamProvider.writeToStream(serverName, "NOTICE " + sender + " :\u0001TIME " + ZonedDateTime.now().format(DateTimeFormatter.RFC_1123_DATE_TIME) + "\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); 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"); return;
} else if (message.equals("\u0001SOURCE\u0001")) { case "\u0001SOURCE\u0001":
StreamProvider.writeToStream(serverName, "NOTICE " + sender + " :\u0001SOURCE https://github.com/developersu/InnaIrcBot\u0001"); StreamProvider.writeToStream(serverName, "NOTICE " + sender + " :\u0001SOURCE https://github.com/developersu/InnaIrcBot\u0001");
writerWorker.logAdd("[system]", "catch/handled CTCP TIME from", sender); 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"); return;
} else
writerWorker.logAdd("[system]", "catch unknown CTCP request \"" + message + "\" from ", sender);
} }
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 String simplifyNick(String nick){ return nick.replaceAll("!.*$",""); }
private void handleSpecial(String event, String chanel, String message){ private void handleSpecial(String event, String channelName, String message){
//System.out.println("|"+event+"|"+chanel+"|"+message+"|"); IrcChannel ircChannel = channels.get(channelName);
if (channelsMap.containsKey(chanel)){ if (ircChannel == null)
channelsMap.get(chanel).println(event+" "+nick+" "+chanel+" "+message); // WTF >< return;
channelsMap.get(chanel).flush(); String ircFormatterMessage = event+" "+nick+" "+channelName+" "+message;
//System.out.println("Formatted: |"+event+"|"+nick+"|"+chanel+" "+message+"|"); //System.out.println("Formatted: |"+event+"|"+nick+"|"+channelName+" "+message+"|");
} ircChannel.getChannelQueue().add(ircFormatterMessage);
} }
//todo: nandle nickserv messages //todo: handle nickserv messages somehow
private void handleNumeric(String eventNum, String sender, String message){ private void handleNumeric(String eventNum, String sender, String message) throws Exception{
switch (eventNum){ 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 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; break;
case "353": case "353":
writerWorker.logAdd("[system]", "catch/handled:", eventNum+" [RPL_NAMREPLY]"); writerWorker.logAdd("[system]", "catch/handled:", eventNum+" [RPL_NAMREPLY]");
String chan = message.substring(message.indexOf(" ")+3); String channelName = message.substring(nick.length()+3).replaceAll("\\s.*$", "");
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(" ");
PipedOutputStream streamOut = new PipedOutputStream(); IrcChannel ircChannel = channels.get(channelName);
try { if (ircChannel == null)
BufferedReader streamBufferedReader = new BufferedReader( return;
new InputStreamReader( ircChannel.getChannelQueue().add(eventNum+" "+sender+" "+message);
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+"|");
break; break;
case "NICK": case "NICK":
if (sender.startsWith(nick+"!")) { if (sender.startsWith(nick+"!")) {
@ -224,9 +169,19 @@ public class SystemConsumer implements Runnable{
break; break;
case "JOIN": case "JOIN":
if (sender.startsWith(nick+"!")) { if (sender.startsWith(nick+"!")) {
proxyAList.put(message, new ArrayList<>()); // Add new channel name to proxy watch-list IrcChannel newIrcChannel = new IrcChannel(message);
proxyAList.get(message).add(eventNum+" "+sender+" "+message); // Add message to array linked
this.proxyRequired = true; // Ask for proxy validators 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); writerWorker.logAdd("[system]", "joined to channel ", message);
} }
break; break;
@ -239,11 +194,79 @@ public class SystemConsumer implements Runnable{
CTCPHelper.getInstance().handleCtcpReply(serverName, simplifyNick(sender), message.replaceAll("^.+?:", "").trim()); CTCPHelper.getInstance().handleCtcpReply(serverName, simplifyNick(sender), message.replaceAll("^.+?:", "").trim());
writerWorker.logAdd("[system]", "NOTICE from "+sender+" received: ", message.replaceAll("^.+?:", "").trim()); writerWorker.logAdd("[system]", "NOTICE from "+sender+" received: ", message.replaceAll("^.+?:", "").trim());
break; 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; break;
default: default:
writerWorker.logAdd("[system]", "catch: "+eventNum+" from: "+sender+" :",message); // TODO: QUIT comes here. Do something? writerWorker.logAdd("[system]", "catch: "+eventNum+" from: "+sender+" :",message);
break; 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());
}
} }

View file

@ -3,21 +3,21 @@ package InnaIrcBot;
import java.util.HashMap; import java.util.HashMap;
public class ReconnectControl { public class ReconnectControl {
private static HashMap<String, Boolean> serversList = new HashMap<>(); private static final HashMap<String, Boolean> serversList = new HashMap<>();
public static synchronized void register(String serverName){ public static synchronized void register(String serverName){
serversList.put(serverName, true); serversList.put(serverName, true);
} }
public static synchronized void update(String serverName, boolean needReconnect){ public static synchronized void update(String serverName, boolean needReconnect){
serversList.replace(serverName, 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);
}
} }

View file

@ -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;
}
}

View file

@ -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();
}
}

View file

@ -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");
}
}

View file

@ -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<String, ConfigurationFile> 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;
}
}

View file

@ -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();
}
}
}

View file

@ -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();
}
}
}

View file

@ -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"));
}
}

View file

@ -1,10 +1,10 @@
package Temporary; package Temporary;
import InnaIrcBot.Config.StorageFile; import InnaIrcBot.config.ConfigurationFile;
public class StorageFileTest { public class StorageFileTest {
static public void main(String[] args){ static public void main(String[] args){
StorageFile config = new StorageFile( ConfigurationFile config = new ConfigurationFile(
"", "",
0, 0,
"", "",

View file

@ -1,8 +1,5 @@
package Temporary; package Temporary;
import InnaIrcBot.Config.StorageFile;
import InnaIrcBot.Config.StorageReader;
public class StorageReaderTest { public class StorageReaderTest {
public static void main(String[] args){ public static void main(String[] args){
// StorageReader.readConfig("/home/loper/bot.config"); // StorageReader.readConfig("/home/loper/bot.config");

View file

@ -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();
}
}
}
}

View file

@ -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();
}
*/
}

View file

@ -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();
}
}