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 {
stage('Build') {
steps {
sh 'mvn -B -DskipTests clean package'
sh 'mvn package'
}
}
stage('Test') {
steps {
echo 'Skip testing...'
sh 'mvn test'
}
}
stage('Deploy') {

View file

@ -2,12 +2,22 @@
Another one IRC bot in deep-deep beta.
## License
Source code spreads under the GNU General Public License v3 or higher. Please see LICENSE file.
####Used libraries:
* Apache commons CLI: https://commons.apache.org/proper/commons-cli/
* GSON: https://github.com/google/gson
* sqliteJDBC: https://bitbucket.org/xerial/sqlite-jdbc
* mongodb-driver-sync: https://mongodb.github.io/mongo-java-driver/3.9/
* JUnit 5: https://junit.org/junit5/
## Usage
` -c, --configuration <name.config> [<name1.config> ...] Read Config`
` -g, --generate [name.config] Generate Config`
` -v, --version Get application version`
```
-c, --configuration <name.config> [<name1.config> ...] Read Config
-g, --generate [name.config] Generate Config
-v, --version Get application version
```
#### Configuration settings
"userNickAuthStyle": "rusnet" or "freenode"
* rusnet - send '/nickserv IDENTIFY mySecretPass'
@ -18,16 +28,7 @@ Another one IRC bot in deep-deep beta.
* SQLite - use /yourPathSet/server.db (or /yourPathSet/yourFileName.db) sqlite file.
* MongoDB - write files to MongoDB. See ConfigurationExamples folder.
## License
Source code spreads under the GNU General Public License v3 or higher. Please see LICENSE file.
Used libraries:
* GSON: https://github.com/google/gson
* sqliteJDBC: https://bitbucket.org/xerial/sqlite-jdbc
* mongodb-driver-sync: https://mongodb.github.io/mongo-java-driver/3.9/
## TODO:
### TODO:
- [ ] Documentation
- [ ] Code refactoring
- [ ] QA: good regression testing

85
pom.xml
View file

@ -4,13 +4,14 @@
<modelVersion>4.0.0</modelVersion>
<groupId>loper</groupId>
<artifactId>InnaIrcBot</artifactId>
<version>0.7-SNAPSHOT</version>
<version>0.8-SNAPSHOT</version>
<packaging>jar</packaging>
<name>InnaIrcBot</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.xerial</groupId>
@ -30,31 +31,78 @@
<version>3.9.1</version>
<scope>compile</scope>
</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>
<build><plugins>
<build>
<plugins>
<!-- ====================================================== -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<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>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.2</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<version>2.22.2</version>
</plugin>
<!-- ====================================================== -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<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>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>2.5.3</version>
<version>3.1.0</version>
<configuration>
<archive>
<manifest>
<mainClass>
InnaIrcBot.BotStart
InnaIrcBot.Main
</mainClass>
</manifest>
</archive>
@ -72,6 +120,7 @@
</execution>
</executions>
</plugin>
</plugins></build>
</plugins>
</build>
</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;
import InnaIrcBot.Config.StorageReader;
import InnaIrcBot.config.ConfigurationFileGenerator;
import org.apache.commons.cli.*;
public class BotStart {
//TODO: flood control
//TODO: setDaemon(true)
//TODO: multiple connections to one server not allowed
public BotStart(String[] args){
public static void main(String[] args){
if (args.length != 0) {
if (args.length >= 2) {
if (args[0].equals("--configuration") || args[0].equals("-c")) {
new Connections(args);
} else if (args[0].equals("--generate") || args[0].equals("-g")) {
StorageReader.generateDefaultConfig(args[1]);
}
final Options cliOptions = createCliOptions();
CommandLineParser cliParser = new DefaultParser();
try{
CommandLine cli = cliParser.parse(cliOptions, args);
if (cli.hasOption('v') || cli.hasOption("version")){
handleVersion();
return;
}
else if (args[0].equals("--generate") || args[0].equals("-g")){
StorageReader.generateDefaultConfig(null);
if (cli.hasOption("c") || cli.hasOption("configuration")){
final String[] arguments = cli.getOptionValues("configuration");
for (String a: arguments)
ConnectionsBuilder.buildConnections(arguments);
return;
}
else if (args[0].equals("--version") || args[0].equals("-v")) {
System.out.println(GlobalData.getAppVersion());
if (cli.hasOption("g") || cli.hasOption("generate")){
final String[] arguments = cli.getOptionValues("generate");
handleGenerate(arguments);
return;
}
handleHelp(cliOptions);
}
else {
System.out.println("Usage:\n"
+" \t-c, --configuration <name.config> [<name1.config> ...]\tRead Config\n"
+"\t-g, --generate\t[name.config]\t\t\t\tGenerate Config\n"
+"\t-v, --version\t\t\t\t\t\tGet application version");
catch (ParseException pe){
handleHelp(cliOptions);
}
catch (Exception e){
System.out.println("Error: ");
e.printStackTrace();
}
}
}
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();
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 requesterServer, String requesterChanelOrUser, String ctcpType, String targetObject, String notFoundMessage){
/*
System.out.println("Server:|"+requesterServer+"|");
System.out.println("Chanel:|"+requesterChanelOrUser+"|");
System.out.println("Type :|"+ctcpType+"|");
System.out.println("Regexp:|"+targetObject+"|");
System.out.println("NF_mes:|"+notFoundMessage+"|\n"); // could be empty
*/
if (!waitersQueue.containsKey(requesterServer)){ // TODO: meeeeeeeehh.. looks bad
waitersQueue.put(requesterServer, new ArrayList<>());
void registerRequest(String server, String requesterChanelOrUser, String ctcpType, String target, String notFoundMessage){
if (!waitersQueue.containsKey(server)){ // TODO: meeeeeeeehh.. looks bad
waitersQueue.put(server, new ArrayList<>());
}
switch (ctcpType){
@ -34,107 +27,67 @@ public class CTCPHelper {
case "SOURCE":
case "TIME":
case "USERINFO":
waitersQueue.get(requesterServer).add(new CtcpRequest(requesterChanelOrUser, targetObject, notFoundMessage, ctcpType));
StreamProvider.writeToStream(requesterServer, "PRIVMSG "+targetObject+" :\u0001"+ctcpType+"\u0001");
waitersQueue.get(server).add(new CTCPRequest(requesterChanelOrUser, target, notFoundMessage, ctcpType));
StreamProvider.writeToStream(server, "PRIVMSG "+target+" :\u0001"+ctcpType+"\u0001");
break;
case "PING": // TODO
waitersQueue.get(requesterServer).add(new CtcpRequest(requesterChanelOrUser, targetObject, notFoundMessage, ctcpType));
StreamProvider.writeToStream(requesterServer, "PRIVMSG "+targetObject+" :\u0001PING inna\u0001");
waitersQueue.get(server).add(new CTCPRequest(requesterChanelOrUser, target, notFoundMessage, ctcpType));
StreamProvider.writeToStream(server, "PRIVMSG "+target+" :\u0001PING inna\u0001");
break;
}
}
public void handleCtcpReply(String serverReplied, String whoReplied, String whatReplied){
//System.out.println("Reply serv:|"+serverReplied+"|\nwho:|"+whoReplied+"|\nwhat:|"+whatReplied+"|");
LocalDateTime currentTime = LocalDateTime.now();
if (waitersQueue.containsKey(serverReplied)){
ListIterator<CtcpRequest> iterator = waitersQueue.get(serverReplied).listIterator();
CtcpRequest current;
String chanelOrUser;
while (iterator.hasNext()){
current = iterator.next();
if (current.isValid(currentTime)){
chanelOrUser = current.getRequesterChanelOrUser(whoReplied);
if ( chanelOrUser != null && current.getType().equals(whatReplied.replaceAll("\\s.*$", ""))) {
if (whatReplied.equals("PING inna"))
StreamProvider.writeToStream(serverReplied, "PRIVMSG " + chanelOrUser + " :" + whoReplied + ": " + Duration.between(current.getCreationTime(), currentTime).toMillis()+"ms");
else
StreamProvider.writeToStream(serverReplied, "PRIVMSG " + chanelOrUser + " :" + whoReplied + ": " + whatReplied);
iterator.remove();
}
}
else{
//System.out.println("Drop outdated user");
iterator.remove();
}
if (! waitersQueue.containsKey(serverReplied))
return;
ListIterator<CTCPRequest> iterator = waitersQueue.get(serverReplied).listIterator();
while (iterator.hasNext()){
CTCPRequest current = iterator.next();
if (current.isValid(currentTime)){
String channelOrUser = current.getRequesterChanelOrUser(whoReplied);
if (channelOrUser == null || ! current.getType().equals(whatReplied.replaceAll("\\s.*$", "")))
continue;
if (whatReplied.equals("PING inna"))
StreamProvider.writeToStream(serverReplied, "PRIVMSG " + channelOrUser + " :" + whoReplied + ": " +
Duration.between(current.getCreationTime(), currentTime).toMillis()+"ms");
else
StreamProvider.writeToStream(serverReplied, "PRIVMSG " + channelOrUser + " :" + whoReplied + ": " + whatReplied);
}
iterator.remove();
}
}
public void handleErrorReply(String serverReplied, String whoNotFound){
//System.out.println("Reply serv:|"+serverReplied+"|\nwho:|"+whoNotFound+"|\n");
LocalDateTime currentTime = LocalDateTime.now();
if (waitersQueue.containsKey(serverReplied)){
ListIterator<CtcpRequest> iterator = waitersQueue.get(serverReplied).listIterator();
CtcpRequest current;
String chanelOrUser;
String notFoundMessage;
while (iterator.hasNext()){
current = iterator.next();
if (current.isValid(currentTime)){
chanelOrUser = current.getRequesterChanelOrUser(whoNotFound);
if ( chanelOrUser != null) {
notFoundMessage = current.getNotFoundMessage(whoNotFound);
if ( notFoundMessage != null) {
System.out.println(serverReplied + " PRIVMSG " + chanelOrUser + " :" + notFoundMessage + whoNotFound);
StreamProvider.writeToStream(serverReplied, "PRIVMSG " + chanelOrUser + " :" + notFoundMessage + whoNotFound);
}
iterator.remove();
}
}
else{
//System.out.println("Drop outdated user: 401");
iterator.remove();
}
}
if (! waitersQueue.containsKey(serverReplied))
return;
ListIterator<CTCPRequest> iterator = waitersQueue.get(serverReplied).listIterator();
while (iterator.hasNext()){
CTCPRequest current = iterator.next();
if (! current.isValid(currentTime))
iterator.remove();
String channelOrUser = current.getRequesterChanelOrUser(whoNotFound);
if (channelOrUser == null)
continue;
String notFoundMessage = current.getNotFoundMessage(whoNotFound);
if (notFoundMessage == null)
continue;
System.out.println(serverReplied + " PRIVMSG " + channelOrUser + " :" + notFoundMessage + whoNotFound);
StreamProvider.writeToStream(serverReplied, "PRIVMSG " + channelOrUser + " :" + notFoundMessage + whoNotFound);
}
}
}
class CtcpRequest {
private String requesterChanelOrUser;
private String userResponding;
private LocalDateTime initiatedTime;
private String notFoundMessage;
private String CTCPtype;
CtcpRequest (String whoIsAsking, String userResponding, String notFoundMessage, String CTCPType){
this.initiatedTime = LocalDateTime.now();
this.requesterChanelOrUser = whoIsAsking;
this.userResponding = userResponding;
this.notFoundMessage = notFoundMessage;
this.CTCPtype = CTCPType;
}
String getRequesterChanelOrUser(String userResponds){ // return channel name
if (userResponding.equals(userResponds))
return requesterChanelOrUser;
else
return null;
}
boolean isValid(LocalDateTime currentTime){
return currentTime.isBefore(initiatedTime.plusSeconds(5));
}
String getType(){ return CTCPtype; }
LocalDateTime getCreationTime(){ return initiatedTime; }
String getNotFoundMessage(String userResponds){
if (this.userResponding.equals(userResponds))
if (notFoundMessage.isEmpty())
return null;
else
return notFoundMessage;
else
return null;
}
}

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

View file

@ -22,7 +22,7 @@ public class JoinCloneHandler {
prevUserNick = "";
lastCame = LocalDateTime.now().minusDays(1L);
}
/*
public void track(String userNick){
if (userNick.matches(pattern)){
if (lastCame.isAfter(LocalDateTime.now().minusSeconds(timeFrameInSeconds)) && !prevUserNick.equals(userNick)){
@ -37,8 +37,41 @@ public class JoinCloneHandler {
lastCame = LocalDateTime.now();
}
}
*/
// RUSNET
public void track(String userNick){
if (userNick.matches(pattern)){
if (lastCame.isAfter(LocalDateTime.now().minusSeconds(timeFrameInSeconds)) && !prevUserNick.equals(userNick)){
if (getNickOnly(userNick).replaceAll("[0-9].*", "").length() > 2){
StreamProvider.writeToStream(server,
"MODE "+chanel+" +b "+userNick.replaceAll("[0-9].*", "*!*@*")+"\n"+
"MODE "+chanel+" +b *!*@"+getIdentHost(userNick)+"*\n"+
"MODE "+chanel+" +b "+prevUserNick.replaceAll("[0-9].*", "*!*@*")+"\n"+
"MODE "+chanel+" +b *!*@"+getIdentHost(prevUserNick)+"*\n"+
"KICK "+chanel+" "+getNickOnly(userNick)+" :clone\n"+
"KICK "+chanel+" "+getNickOnly(prevUserNick)+" :clone"
);
}
else {
StreamProvider.writeToStream(server,
"MODE "+chanel+" +b *!*@"+getIdentHost(userNick)+"*\n"+
"MODE "+chanel+" +b *!*@"+getIdentHost(prevUserNick)+"*\n"+
"KICK "+chanel+" "+getNickOnly(userNick)+" :clone\n"+
"KICK "+chanel+" "+getNickOnly(prevUserNick)+" :clone"
);
}
}
prevUserNick = userNick;
lastCame = LocalDateTime.now();
}
}
private String getIdentHost(String fullNick){
return fullNick.replaceAll("^.*@","@");
String id = fullNick.replaceAll("^.*@","");
if (id.contains(":"))
return id.replaceAll("^([A-Fa-f0-9]{1,4}:[A-Fa-f0-9]{1,4}:)(.+)$", "$1")+"*";
else
return id;
}
private String getNickOnly(String fullNick){
return fullNick.replaceAll("!.*$","");

View file

@ -3,58 +3,68 @@ package InnaIrcBot.Commanders;
import InnaIrcBot.ProvidersConsumers.StreamProvider;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
//TODO: implement method to
public class JoinFloodHandler {
private int eventBufferSize; // How many events should happens before we start validation
private int timeFrameInSeconds; // For which period critical amount of events should happens
private String server;
private String chanel;
private HashMap<String, LprFIFOQueue> usersOnChanel;
import java.util.*;
public JoinFloodHandler(int events, int timeFrameInSeconds, String serverName, String chanelName){
this.eventBufferSize = events;
public class JoinFloodHandler {
private final int joinMaxNumber; // How many events should happens before we start validation
private final int timeFrameInSeconds; // For which period critical amount of events should happens
private final String server;
private final String channel;
protected final HashMap<String, LinkedList<LocalDateTime>> users;
public JoinFloodHandler(int joinMaxNumber, int timeFrameInSeconds, String serverName, String channelName){
this.joinMaxNumber = joinMaxNumber;
this.timeFrameInSeconds = timeFrameInSeconds;
this.server = serverName;
this.chanel = chanelName;
this.usersOnChanel = new HashMap<>();
this.channel = channelName;
this.users = new HashMap<>();
}
public void track(String userNick){
if(usersOnChanel.containsKey(userNick)){
LocalDateTime timeOfFirstEvent = usersOnChanel.get(userNick).addLastGetFirst();
if (timeOfFirstEvent.isAfter(LocalDateTime.now().minusSeconds(timeFrameInSeconds))) { // If first event in the queue happened after 'timeFrameSeconds ago from now'
StreamProvider.writeToStream(server, "PRIVMSG "+chanel+" :"+userNick+": join flood ("+eventBufferSize+" connections in "+timeFrameInSeconds+" seconds).\n"+
"MODE "+chanel+" +b "+userNick+"!*@*"); // Shut joins-liker down. By nick TODO: consider other ban methods
usersOnChanel.remove(userNick); // Delete viewing history (in case op/hop decided to unban)
}
public void track(String userNickname){
if (isNewcomer(userNickname)) {
registerNewUser(userNickname);
return;
}
if(isJoinFlooder(userNickname)){
kickBanUser(userNickname);
users.remove(userNickname);
}
}
private boolean isNewcomer(String user){
return ! users.containsKey(user);
}
private void registerNewUser(String user){
users.put(user, new LinkedList<>());
users.get(user).addFirst(LocalDateTime.now());
}
private boolean isJoinFlooder(String user){
final LocalDateTime firstJoinTime = getFirstJoinTimeAndUpdate(user);
return firstJoinTime.isAfter(LocalDateTime.now().minusSeconds(timeFrameInSeconds));
}
private LocalDateTime getFirstJoinTimeAndUpdate(String user){
LinkedList<LocalDateTime> userJoinHistory = users.get(user);
LocalDateTime fistJoinTime;
userJoinHistory.addLast(LocalDateTime.now());
if (userJoinHistory.size() > joinMaxNumber){
fistJoinTime = userJoinHistory.getFirst();
userJoinHistory.removeFirst();
}
else {
usersOnChanel.put(userNick, new LprFIFOQueue(eventBufferSize)); // Create buffer for tracking new user joined
usersOnChanel.get(userNick).addLastGetFirst(); // Write his/her first join time
fistJoinTime = LocalDateTime.MIN;
}
return fistJoinTime;
}
}
class LprFIFOQueue {
private int size;
private ArrayList<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);
private void kickBanUser(String user){
StreamProvider.writeToStream(server,
"PRIVMSG "+ channel +" :"+user+": join flood ("+ joinMaxNumber +" connections in "+timeFrameInSeconds+" seconds).\n"+
"MODE "+ channel +" +b "+user+"!*@*"); // TODO: consider other ban methods
}
}

View file

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

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;
public class GlobalData {
private static final String version = "InnaIrcBot v0.7 \"Комсомолец\"";
private static final String version = "InnaIrcBot v0.8 \"Коммунарка\"";
public static synchronized String getAppVersion(){
return version;
}
public static final int CHANNEL_QUEUE_CAPACITY = 500;
}

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

View file

@ -9,57 +9,63 @@ import java.time.format.DateTimeFormatter;
import java.util.regex.Pattern;
public class BotFilesWorker implements Worker {
private String filePath;
private boolean consistent = false;
private DateTimeFormatter dateFormat;
private LocalDate fileWriterDay;
private final String channel;
private String filePath;
private final DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("HH:mm:ss");
private LocalDate dateOnFile;
private FileWriter fileWriter;
private String ircServer; // hold for debug only
private boolean consistent;
public BotFilesWorker(String server, String[] driverParameters, String channel){
ircServer = server;
dateFormat = DateTimeFormatter.ofPattern("HH:mm:ss");
this.channel = channel.replaceAll(File.separator, ",");
channel = channel.replaceAll(File.separator, ",");
formatFilePath(server, driverParameters);
String dirLocation = driverParameters[0].trim();
if (dirLocation.endsWith(File.separator))
dirLocation = dirLocation+server;
else
dirLocation = dirLocation+File.separator+server;
this.filePath = dirLocation+File.separator+channel;
File dir = new File(dirLocation);
try {
dir.mkdirs(); // ignore result, because if it's already exists we're good. Otherwise, it will be created. Only issue that can occur is SecurityException thrown, so let's catch it.
createServerFolder();
createFileWriter();
consistent = true;
} catch (Exception e){
System.out.println("BotFilesWorker (@"+server+")->constructor(): Failure:\n\tUnable to create directory to store DB file: \n\t" +e);
return; // consistent = false;
System.out.println("BotFilesWorker (@"+server+")->constructor(): Failure:\n" + e.getMessage());
}
if (!dir.exists()) {
System.out.println("BotFilesWorker (@"+server+")->constructor() failed:\n\tUnable to create directory to store files: " + dirLocation);
}
private void formatFilePath(String server, String[] driverParameters){
String dirLocation = driverParameters[0].trim(); // TODO: MOVE trim() out of here
if (! dirLocation.endsWith(File.separator))
dirLocation += File.separator;
this.filePath = dirLocation+server+File.separator;
}
private void createServerFolder() throws Exception{
final File file = new File(filePath);
if (file.exists()){
if (file.isDirectory())
return;
else
throw new Exception("BotFilesWorker->createServerFolder() "+filePath+" is file while directory expected.");
}
if (file.mkdirs())
return;
}
this.consistent = resetFileWriter(false);
throw new Exception("BotFilesWorker->createServerFolder() Can't create directory: "+filePath);
}
private boolean resetFileWriter(boolean reassign){
try {
if (reassign)
this.fileWriter.close();
this.fileWriterDay = LocalDate.now();
this.fileWriter = new FileWriter(this.filePath+"_"+LocalDate.now().toString()+".txt", true);
return true;
}
catch (java.io.IOException e){
System.out.println("BotFilesWorker (@"+ircServer+")->resetFileWriter() failed:\n\tCan't create file to store logs: "+this.filePath);
return false;
}
private void resetFileWriter() throws IOException{
fileWriter.close();
createFileWriter();
}
private void createFileWriter() throws IOException{
dateOnFile = LocalDate.now();
File newFile = new File(filePath+channel+"_"+ dateOnFile +".txt");
fileWriter = new FileWriter(newFile);
}
@Override
public boolean isConsistent() {
return consistent;
@ -70,79 +76,73 @@ public class BotFilesWorker implements Worker {
* argument[1] should be always 'subject'
* */
@Override
public boolean logAdd(String event, String initiatorArg, String messageArg) {
public boolean logAdd(String event, String initiator, String message) {
switch (event){
case "PRIVMSG":
PRIVMSG(initiatorArg, messageArg);
PRIVMSG(initiator, message);
break;
case "JOIN":
JOIN(initiatorArg, messageArg);
JOIN(initiator, message);
break;
case "MODE":
MODE(initiatorArg, messageArg);
MODE(initiator, message);
break;
case "KICK":
KICK(initiatorArg, messageArg);
KICK(initiator, message);
break;
case "PART":
PART(initiatorArg, messageArg);
PART(initiator, message);
break;
case "QUIT":
QUIT(initiatorArg, messageArg);
QUIT(initiator, message);
break;
case "NICK":
NICK(initiatorArg, messageArg);
NICK(initiator, message);
break;
case "TOPIC":
TOPIC(initiatorArg, messageArg);
TOPIC(initiator, message);
break;
default:
this.prettyPrint("["+LocalTime.now().format(dateFormat)+"] "+event+" "+initiatorArg+" "+messageArg+"\n"); // TODO: QA @ big data
prettyPrint("["+LocalTime.now().format(dateFormat)+"] "+event+" "+initiator+" "+message+"\n"); // TODO: QA @ big data
break;
}
return consistent;
}
private void prettyPrint(String string){
//if (consistent) { // could be not-opened
try {
if (LocalDate.now().isAfter(fileWriterDay)) {
if (!resetFileWriter(true)) {
this.close(); // Error message already printed
return;
}
}
fileWriter.write(string);
fileWriter.flush();
} catch (IOException e) {
System.out.println("BotFilesWorker (@" + ircServer + ")->prettyPrint() failed\n\tUnable to write logs of " + this.filePath + " because of internal failure in LocalTime representation.");
this.close();
//consistent = false;
} catch (NullPointerException npe){
System.out.println("BotFilesWorker (@" + ircServer + ")->prettyPrint() failed\n\tUnable to write logs of " + this.filePath + " because file descriptor already closed/was not opened.");
consistent = false;
} catch (Exception unknowne){ // ??? No ideas. Just in case. Consider removing.
System.out.println("BotFilesWorker (@" + ircServer + ")->prettyPrint() failed\n\tUnable to write logs of " + this.filePath + " because of exception:\n\t"+unknowne);
this.close();
try {
if (LocalDate.now().isAfter(dateOnFile)) {
resetFileWriter();
}
//}
fileWriter.write(string);
fileWriter.flush();
} catch (Exception e){
System.out.println("BotFilesWorker->prettyPrint() failed\n" +
"\tUnable to write logs to " + this.filePath + " "+e.getMessage());
close();
consistent = false;
}
}
private String genDate(){
private String getCurrentTimestamp(){
return "["+LocalTime.now().format(dateFormat)+"] ";
}
private String getUserNameOnly(String userNameFull){return userNameFull.replaceAll("!.+$", "");}
private String getUserNameAndHost(String userNameFull){return userNameFull.replaceAll("!.+$", "")+" [!"+userNameFull.replaceAll("^.+!", "")+"] ";}
private String getUserNameOnly(String userNameFull){
return userNameFull.replaceAll("!.+$", "");
}
private String getUserNameAndHost(String userNameFull){
return userNameFull.replaceAll("!.+$", "")+" [!"+userNameFull.replaceAll("^.+!", "")+"] ";
}
private void PRIVMSG(String initiatorArg, String messageArg){
String msg = messageArg.substring(messageArg.indexOf(":")+1);
if (!Pattern.matches("^\\u0001ACTION .+\\u0001", msg)){
this.prettyPrint(genDate()+"<"+getUserNameOnly(initiatorArg)+"> "+msg+"\n");
}
else {
this.prettyPrint(genDate()+getUserNameOnly(initiatorArg)+msg.replaceAll("(^\\u0001ACTION)|(\\u0001$)","")+"\n");
if (Pattern.matches("^\\u0001ACTION .+\\u0001", msg)){
prettyPrint(getCurrentTimestamp()+getUserNameOnly(initiatorArg)+msg.replaceAll("(^\\u0001ACTION)|(\\u0001$)","")+"\n");
return;
}
prettyPrint(getCurrentTimestamp()+"<"+getUserNameOnly(initiatorArg)+"> "+msg+"\n");
}
private void JOIN(String initiatorArg, String messageArg){
this.prettyPrint(genDate()+">> "+getUserNameAndHost(initiatorArg)+"joined "+messageArg+"\n");
prettyPrint(getCurrentTimestamp()+">> "+getUserNameAndHost(initiatorArg)+"joined "+messageArg+"\n");
}
private void MODE(String initiatorArg, String messageArg){
String initiatorChain;
@ -150,33 +150,38 @@ public class BotFilesWorker implements Worker {
initiatorChain = getUserNameAndHost(initiatorArg)+"set";
else
initiatorChain = initiatorArg+" set";
this.prettyPrint(genDate()+"-!- "+initiatorChain+messageArg.substring(messageArg.indexOf(" "))+"\n");
prettyPrint(getCurrentTimestamp()+"-!- "+initiatorChain + messageArg.substring(messageArg.indexOf(" "))+"\n");
}
private void KICK(String initiatorArg, String messageArg){
this.prettyPrint(genDate()+"!<< "+messageArg.replaceAll("(^.+?\\s)|(\\s.+$)", "")+
prettyPrint(getCurrentTimestamp()+"!<< "+messageArg.replaceAll("(^.+?\\s)|(\\s.+$)", "")+
" kicked by "+getUserNameAndHost(initiatorArg)+"with reason: "+messageArg.replaceAll("^.+?:", "")+"\n");
}
private void PART(String initiatorArg, String messageArg){
this.prettyPrint(genDate()+"<< "+getUserNameAndHost(initiatorArg)+"parted: "+messageArg.replaceAll("^.+?:","")+"\n");
prettyPrint(getCurrentTimestamp()+"<< "+getUserNameAndHost(initiatorArg)+"parted: "
+ messageArg.replaceAll("^.+?:","")+"\n");
}
private void QUIT(String initiatorArg, String messageArg){
this.prettyPrint(genDate()+"<< "+getUserNameAndHost(initiatorArg)+"quit: "+messageArg.replaceAll("^.+?:","")+"\n");
prettyPrint(getCurrentTimestamp()+"<< "+getUserNameAndHost(initiatorArg)+"quit: "
+ messageArg.replaceAll("^.+?:","")+"\n");
}
private void NICK(String initiatorArg, String messageArg){
this.prettyPrint(genDate()+"-!- "+getUserNameAndHost(initiatorArg)+"changed nick to: "+messageArg+"\n");
prettyPrint(getCurrentTimestamp()+"-!- "+getUserNameAndHost(initiatorArg)+"changed nick to: "+messageArg+"\n");
}
private void TOPIC(String initiatorArg, String messageArg) {
this.prettyPrint(genDate()+"-!- "+getUserNameAndHost(initiatorArg)+"has changed topic to: "+messageArg.replaceAll("^.+?:", "")+"\n");
prettyPrint(getCurrentTimestamp()+"-!- "+getUserNameAndHost(initiatorArg)+"has changed topic to: "
+ messageArg.replaceAll("^.+?:", "")+"\n");
}
@Override
public void close() {
try {
if (fileWriter !=null)
fileWriter.close();
fileWriter.close();
}
catch (java.io.IOException e){
System.out.println("BotFilesWorker (@"+ircServer+")->close() failed\n\tUnable to properly close file: "+this.filePath); // Live with it.
catch (NullPointerException ignore) {}
catch (IOException e){
System.out.println("BotFilesWorker->close() failed\n" +
"\tUnable to properly close file: "+this.filePath); // Live with it.
}
this.consistent = false;
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,6 +1,7 @@
package InnaIrcBot.ProvidersConsumers;
import InnaIrcBot.Config.StorageFile;
import InnaIrcBot.config.ConfigurationFile;
import InnaIrcBot.IrcChannel;
import InnaIrcBot.LogDriver.BotDriver;
import InnaIrcBot.ReconnectControl;
@ -8,131 +9,94 @@ import java.io.*;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.*;
import java.util.concurrent.BlockingQueue;
public class DataProvider implements Runnable {
private StorageFile configFile;
private String serverName;
private String userNick;
private final ConfigurationFile configurationFile;
private final String serverName;
private final String nickName;
private BufferedReader rawStreamReader;
private boolean ableToRun = true;
/**
* Initiate connection and prepare input/output streams for run()
* */
public DataProvider(StorageFile storageFile){
this.configFile = storageFile;
this.serverName = storageFile.getServerName();
int port = storageFile.getServerPort();
try {
InetAddress inetAddress = InetAddress.getByName(serverName);
Socket socket = new Socket(); // TODO: set timeout?
for (int i=0; i<5; i++) {
socket.connect(new InetSocketAddress(inetAddress, port), 5000); // 10sec = 10000
if (socket.isConnected())
break;
}
if (socket.isConnected())
System.out.println("Socket connected");
else {
System.out.println("Unable to connect to remote server after 5 retry.");
this.ableToRun = false;
return;
}
if (!StreamProvider.setStream(serverName, socket)) {
this.ableToRun = false;
return;
}
InputStream inStream = socket.getInputStream();
InputStreamReader isr = new InputStreamReader(inStream, StandardCharsets.UTF_8); //TODO set charset in options;
this.rawStreamReader = new BufferedReader(isr);
} catch(UnknownHostException e){
this.ableToRun = false;
System.out.println("Internal issue: DataProvider->constructor caused unknown host exception:\n\t"+e); // caused by InetAddress
} catch (IOException e){
this.ableToRun = false;
System.out.println("Internal issue: DataProvider->constructor caused I/O exception\n\t"+e); // caused by Socket
}
}
//HANDLE ALWAYS in case thread decided to die
private void close(){
StreamProvider.delStream(serverName);
public DataProvider(ConfigurationFile configurationFile){
this.configurationFile = configurationFile;
this.serverName = configurationFile.getServerName();
this.nickName = configurationFile.getUserNick();
}
public void run(){
if (!ableToRun || !this.initConnection(rawStreamReader)
|| !BotDriver.setLogDriver(serverName, configFile.getLogDriver(), configFile.getLogDriverParameters(), configFile.getApplicationLogDir())) { //Prepare logDriver for using in threads.
try {
connect();
} catch (Exception e){
System.out.println("Internal issue: DataProvider->run() caused exception:\n\t"+e.getMessage());
e.printStackTrace();
}
ReconnectControl.register(serverName);
if (! BotDriver.setLogDriver(serverName,
configurationFile.getLogDriver(),
configurationFile.getLogDriverParameters(),
configurationFile.getApplicationLogDir())) { //Prepare logDriver for using in threads.)
this.close();
return;
}
/* Used for sending data into consumers objects*/
Map<String, PrintWriter> channelsMap = Collections.synchronizedMap(new HashMap<String, PrintWriter>());
try {
PipedOutputStream streamOut = new PipedOutputStream(); // K-K-K-KOMBO \m/
Map<String, IrcChannel> ircChannels = Collections.synchronizedMap(new HashMap<>());
BufferedReader streamBufferedReader = new BufferedReader(
new InputStreamReader(
new PipedInputStream(streamOut), StandardCharsets.UTF_8)
);
IrcChannel systemConsumerChannel = new IrcChannel("");
BlockingQueue<String> systemConsumerQueue = systemConsumerChannel.getChannelQueue();
Runnable systemConsumer = new SystemConsumer(streamBufferedReader, userNick, channelsMap, this.configFile);
new Thread(systemConsumer).start();
PrintWriter systemConsumerWriter = new PrintWriter(streamOut);
Thread SystemConsumerThread = new Thread(
new SystemConsumer(systemConsumerQueue, nickName, ircChannels, this.configurationFile));
SystemConsumerThread.start();
StreamProvider.setSysConsumer(serverName, systemConsumerWriter); // Register system consumer at StreamProvider
StreamProvider.setSysConsumer(serverName, systemConsumerQueue); // Register system consumer at StreamProvider
channelsMap.put("", systemConsumerWriter); // Not sure that PrintWriter is thread-safe..
} catch (IOException e){
System.out.println("Internal issue: DataProvider->run() I/O exception while initialized child objects.\n\t"+e); // caused by Socket
this.close();
return;
}
ircChannels.put(systemConsumerChannel.toString(), systemConsumerChannel); // Not sure that PrintWriter is thread-safe..
////////////////////////////////////// Start loop //////////////////////////////////////////////////////////////
StreamProvider.writeToStream(serverName,"NICK "+this.nickName);
StreamProvider.writeToStream(serverName,"USER "+ configurationFile.getUserIdent()+" 8 * :"+ configurationFile.getUserRealName()); // TODO: Add usermode 4 rusnet
try {
String rawMessage;
String[] rawStrings; // prefix[0] command[1] command-parameters\r\n[2]
//if there is no prefix, you should assume the message came from your client.
String chan;
// Say 'yes, we need reconnect if connection somehow died'
ReconnectControl.register(serverName);
while ((rawMessage = rawStreamReader.readLine()) != null) {
//System.out.println(rawMessage);
System.out.println(rawMessage);
if (rawMessage.startsWith(":")) {
rawStrings = rawMessage
.substring(1)
.split(" :?", 3); // Removing ':'
chan = rawStrings[2].replaceAll("(\\s.?$)|(\\s.+?$)", "");
String chan = rawStrings[2].replaceAll("(\\s.?$)|(\\s.+?$)", "");
//System.out.println("\tChannel: "+chan+"\n\tAction: "+rawStrings[1]+"\n\tSender: "+rawStrings[0]+"\n\tMessage: "+rawStrings[2]+"\n");
if (rawStrings[1].equals("QUIT") || rawStrings[1].equals("NICK")) { // replace regex
for (PrintWriter value : channelsMap.values()) {
value.println(rawStrings[1] + " " + rawStrings[0] + " " + rawStrings[2]);
value.flush();
for (IrcChannel ircChannel : ircChannels.values()) {
ircChannel.getChannelQueue().add(rawStrings[1] + " " + rawStrings[0] + " " + rawStrings[2]);
}
} else if (channelsMap.containsKey(chan)) {
channelsMap.get(chan).println(rawStrings[1] + " " + rawStrings[0] + " " + rawStrings[2]);
channelsMap.get(chan).flush();
} else {
channelsMap.get("").println(rawStrings[1] + " " + rawStrings[0] + " " + rawStrings[2]);
channelsMap.get("").flush();
}
} else if (rawMessage.startsWith("PING :")) {
pingSrvResponse(rawMessage);
} else {
else if (ircChannels.containsKey(chan)) {
IrcChannel chnl = ircChannels.get(chan);
chnl.getChannelQueue().add(rawStrings[1] + " " + rawStrings[0] + " " + rawStrings[2]);
}
else {
systemConsumerQueue.add(rawStrings[1] + " " + rawStrings[0] + " " + rawStrings[2]);
}
}
else if (rawMessage.startsWith("PING :")) {
sendPingReply(rawMessage);
}
else {
System.out.println("Not a valid response=" + rawMessage);
}
}
@ -140,92 +104,37 @@ public class DataProvider implements Runnable {
System.out.println("Socket issue: I/O exception"); //Connection closed. TODO: MAYBE try reconnect
}
finally {
for (PrintWriter p :channelsMap.values()) {
p.close();
}
this.close();
SystemConsumerThread.interrupt();
close();
}
}
private void pingSrvResponse(String rawData){
private void connect() throws Exception{
final int port = configurationFile.getServerPort();
InetAddress inetAddress = InetAddress.getByName(serverName);
Socket socket = new Socket(); // TODO: set timeout?
for (int i = 0; i < 5; i++) {
socket.connect(new InetSocketAddress(inetAddress, port), 5000); // 5sec
if (socket.isConnected())
break;
}
if (! socket.isConnected())
throw new Exception("Unable to connect server.");
StreamProvider.setStream(serverName, socket);
InputStream inStream = socket.getInputStream();
InputStreamReader isr = new InputStreamReader(inStream, StandardCharsets.UTF_8); //TODO set charset in options;
rawStreamReader = new BufferedReader(isr);
}
private void sendPingReply(String rawData){
StreamProvider.writeToStream(serverName,"PONG :" + rawData.replace("PING :", ""));
}
/**
* Initiate connection before starting main routine.
* */
private boolean initConnection(BufferedReader genericStreamReader){
int nickTail = 0; // handle appendix to nickname
this.userNick = configFile.getUserNick();
if (this.userNick == null || this.userNick.isEmpty()) {
System.out.println("Configuration issue: no nickname specified.");
return false;
}
String rawMessage;
StreamProvider.writeToStream(serverName,"NICK "+this.userNick);
StreamProvider.writeToStream(serverName,"USER "+configFile.getUserIdent()+" 8 * :"+configFile.getUserRealName()); // TODO: Add usermode 4 rusnet
try {
// 431 ERR_NONICKNAMEGIVEN how can we get this?
// 432 ERR_ERRONEUSNICKNAME covered
// 433 ERR_NICKNAMEINUSE covered
// 436 ERR_NICKCOLLISION
// 464 ERR_PASSWDMISMATCH (password for server/znc/bnc)
while ((rawMessage = genericStreamReader.readLine()) != null){
System.out.println(rawMessage);
if (rawMessage.startsWith("PING :")) {
pingSrvResponse(rawMessage);
}
if (rawMessage.contains(" 001 ")) {
StringBuilder message = new StringBuilder();
if (!configFile.getUserMode().trim().isEmpty()){
String modes = configFile.getUserMode();
modes = modes.replaceAll("[\t\\s]", "");
for(char c :modes.toCharArray()) {
message.append("MODE ");
message.append(userNick);
message.append(" +");
message.append(c);
message.append("\n");
}
}
StreamProvider.writeToStream(serverName,message.toString());
return true;
}
else if (rawMessage.contains(" 433 ")) {
if (this.userNick.substring(userNick.length()-1, userNick.length()-1).equals("|"))
this.userNick = this.userNick.substring(0,userNick.length()-1)+Integer.toString(nickTail++);
else
this.userNick = this.userNick+"|"+Integer.toString(nickTail++);
StreamProvider.writeToStream(serverName,"NICK "+this.userNick);
}
else if (rawMessage.contains(" 432 ")) {
System.out.println("Configuration issue: Nickname contains unacceptable characters (432 ERR_ERRONEUSNICKNAME).");
return false;
}
else if (rawMessage.contains(" 464 ")) {
StreamProvider.writeToStream(serverName,"PASS "+configFile.getServerPass());
}
}
if (!configFile.getUserNickPass().isEmpty() && (!configFile.getUserNickAuthStyle().isEmpty() && configFile.getUserNickAuthStyle().toLowerCase().equals("freenode")))
StreamProvider.writeToStream(serverName,"PRIVMSG NickServ :IDENTIFY "+configFile.getUserNickPass());
else if (!configFile.getUserNickPass().isEmpty() && (!configFile.getUserNickAuthStyle().isEmpty() && configFile.getUserNickAuthStyle().toLowerCase().equals("rusnet")))
StreamProvider.writeToStream(serverName,"NickServ IDENTIFY "+configFile.getUserNickPass());
else if (!configFile.getUserNickPass().isEmpty())
System.out.println("Configuration issue: Unable to determinate method of user nick identification (by password): could be \"freenode\" or \"rusnet\"\nSee \"userNickAuthStyle\".");
} catch (IOException e){
System.out.println("Internal issue: DataProvider->initConnection() caused I/O exception.");
return false;
}
return false;
//HANDLE ALWAYS in case thread decided to die
private void close(){
StreamProvider.delStream(serverName);
ReconnectControl.notify(serverName);
}
}

View file

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

View file

@ -2,56 +2,45 @@ package InnaIrcBot.ProvidersConsumers;
import InnaIrcBot.Commanders.CTCPHelper;
import InnaIrcBot.Commanders.PrivateMsgCommander;
import InnaIrcBot.Config.StorageFile;
import InnaIrcBot.ReconnectControl;
import InnaIrcBot.config.ConfigurationFile;
import InnaIrcBot.GlobalData;
import InnaIrcBot.IrcChannel;
import InnaIrcBot.LogDriver.BotDriver;
import InnaIrcBot.LogDriver.BotSystemWorker;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;
import java.util.concurrent.BlockingQueue;
public class SystemConsumer implements Runnable{
private BufferedReader reader;
private final BlockingQueue<String> systemQueue;
private BotSystemWorker writerWorker;
private String nick;
private String serverName;
private Map<String, PrintWriter> channelsMap;
private boolean proxyRequired;
private HashMap<String, ArrayList<String>> proxyAList;
private StorageFile storageFile;
private final Map<String, IrcChannel> channels;
private ConfigurationFile configurationFile;
private PrivateMsgCommander commander;
private LocalDateTime lastCTCPReplyTime;
SystemConsumer(BufferedReader streamReader, String userNick, Map<String, PrintWriter> map, StorageFile storage) {
this.writerWorker = BotDriver.getSystemWorker(storage.getServerName());
private ArrayList<Thread> channelThreads;
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.serverName = storage.getServerName();
this.channelsMap = map;
this.reader = streamReader;
this.proxyRequired = false;
this.proxyAList = new HashMap<>();
this.storageFile = storage;
this.commander = new PrivateMsgCommander(serverName, storageFile.getBotAdministratorPassword());
// Start pre-set channels
StringBuilder message = new StringBuilder();
for (String cnl : storageFile.getChannels()) { // TODO: add validation of channels.
message.append("JOIN ");
message.append(cnl);
message.append("\n");
}
StreamProvider.writeToStream(serverName,message.toString());
this.serverName = configurationFile.getServerName();
this.channels = channels;
this.channelThreads = new ArrayList<>();
this.configurationFile = configurationFile;
this.commander = new PrivateMsgCommander(serverName, this.configurationFile.getBotAdministratorPassword());
lastCTCPReplyTime = LocalDateTime.now();
}
@ -62,159 +51,115 @@ public class SystemConsumer implements Runnable{
setMainRoutine();
for (PrintWriter p :channelsMap.values()) //TODO: check, code duplication. see Data provider constructor
p.close();
for (Thread channel : channelThreads) { //TODO: check, code duplication. see Data provider constructor
channel.interrupt();
}
writerWorker.close();
System.out.println("["+LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"))+"] THREAD "+serverName+":[system] ended"); // TODO:REMOVE DEBUG
}
private void setMainRoutine(){
String data;
String[] dataStrings;
try {
while ((data = reader.readLine()) != null) {
dataStrings = data.split(" ",3);
if (proxyRequired)
if (getProxy(dataStrings[0], dataStrings[1], dataStrings[2]))
continue; // TODO: check this. Continue is fair?
if (Pattern.matches("(^[0-9]{3}$)|(^NICK$)|(^JOIN$)|(^QUIT$)|(^NOTICE$)", dataStrings[0])){
handleNumeric(dataStrings[0], dataStrings[1], dataStrings[2]);
}
else if (dataStrings[0].equals("PRIVMSG")) {
if (dataStrings[2].indexOf("\u0001") < dataStrings[2].lastIndexOf("\u0001")) {
replyCTCP(simplifyNick(dataStrings[1]), dataStrings[2].substring(dataStrings[2].indexOf(":") + 1));
//System.out.println("|"+dataStrings[1]+"|"+dataStrings[2].substring(dataStrings[2].indexOf(":") + 1)+"|");
}
else {
commander.receiver(dataStrings[1], dataStrings[2].replaceAll("^.+?:", "").trim());
writerWorker.logAdd("[system]", "PRIVMSG from "+dataStrings[1]+" received: ", dataStrings[2].replaceAll("^.+?:", "").trim());
}
}
else if (dataStrings[0].equals("INNA")) {
String[] splitter;
if (dataStrings.length > 2){ // Don't touch 'cuz it's important
splitter = dataStrings[2].split(" ", 2);
if (splitter.length == 2){
handleSpecial(dataStrings[1], splitter[0], splitter[1]);
while (true) {
String data = systemQueue.take();
String[] dataStrings = data.split(" ",3);
//TODO: handle mode change
switch (dataStrings[0]){
case "PRIVMSG":
if (dataStrings[2].indexOf("\u0001") < dataStrings[2].lastIndexOf("\u0001")) {
replyCTCP(simplifyNick(dataStrings[1]), dataStrings[2].substring(dataStrings[2].indexOf(":") + 1));
}
}
else {
commander.receiver(dataStrings[1], dataStrings[2].replaceAll("^.+?:", "").trim());
writerWorker.logAdd("[system]", "PRIVMSG from "+dataStrings[1]+" received: ",
dataStrings[2].replaceAll("^.+?:", "").trim());
}
break;
case "INNA":
String[] splitter;
if (dataStrings.length > 2){ // Don't touch 'cuz it's important
splitter = dataStrings[2].split(" ", 2);
if (splitter.length == 2){
handleSpecial(dataStrings[1], splitter[0], splitter[1]);
}
}
break;
default:
handleNumeric(dataStrings[0], dataStrings[1], dataStrings[2]);
}
else
writerWorker.logAdd(dataStrings[0], dataStrings[1], dataStrings[2]); // TODO: Track users
//System.out.println("System: "+"|"+dataStrings[0]+"|"+dataStrings[1]+"|"+dataStrings[2]+"|");
}
} catch (java.io.IOException e){
System.out.println("Internal issue: thread SystemConsumer->run() caused I/O exception."); // TODO: reconnect OR AT LEAST DIE
StreamProvider.writeToStream(serverName, "QUIT :Internal issue: thread ChanConsumer->run() caused I/O exception");
}
}
private boolean getProxy(String eventNum, String sender, String message){ //TODO: if can't join: like channel with password
if (eventNum.equals("353")) {
//writerWorker.logAdd("[proxy]", "catch: "+eventNum+" from: "+sender+" :",message);
return false; // never mind and let it flows as usual.
catch (InterruptedException ie){
System.out.println("Thread SystemConsumer->run() interrupted."); // TODO: reconnect OR AT LEAST DIE
}
else {
//writerWorker.logAdd("[proxy]", "catch: "+eventNum+" from: "+sender+" :",message);
String chan = message.replaceAll("(\\s.?$)|(\\s.+?$)", "");
if (eventNum.equals("QUIT") || eventNum.equals("NICK")) {
for (ArrayList<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;
catch (Exception e){
System.out.println("Internal issue: thread SystemConsumer->run(): "+e.getMessage()); // TODO: DO.. some thing
}
}
private void replyCTCP(String sender, String message){ // got simplified nick
if (lastCTCPReplyTime.isBefore(LocalDateTime.now().minusSeconds(3))) { // TODO: Consider moving to config file. Now set to 3 sec
lastCTCPReplyTime = LocalDateTime.now();
if (message.equals("\u0001VERSION\u0001")) {
private void replyCTCP(String sender, String message) { // got simplified nick
// TODO: Consider moving to config file. Now set to 3 sec
if (lastCTCPReplyTime.isAfter(LocalDateTime.now().minusSeconds(3)))
return;
lastCTCPReplyTime = LocalDateTime.now();
switch (message) {
case "\u0001VERSION\u0001":
StreamProvider.writeToStream(serverName, "NOTICE " + sender + " :\u0001VERSION " + GlobalData.getAppVersion() + "\u0001");
writerWorker.logAdd("[system]", "catch/handled CTCP VERSION from", sender);
//System.out.println("NOTICE "+sender+" \u0001VERSION "+ GlobalData.getAppVersion()+"\u0001");
} else if (message.startsWith("\u0001PING ") && message.endsWith("\u0001")) {
StreamProvider.writeToStream(serverName, "NOTICE " + sender + " :" + message);
writerWorker.logAdd("[system]", "catch/handled CTCP PING from", sender);
//System.out.println(":"+sender+" NOTICE "+sender.substring(0,sender.indexOf("!"))+" "+message);
} else if (message.equals("\u0001CLIENTINFO\u0001")) {
return;
case "\u0001CLIENTINFO\u0001":
StreamProvider.writeToStream(serverName, "NOTICE " + sender + " :\u0001CLIENTINFO ACTION PING VERSION TIME CLIENTINFO SOURCE\u0001");
writerWorker.logAdd("[system]", "catch/handled CTCP CLIENTINFO from", sender);
//System.out.println(":"+sender+" NOTICE "+sender.substring(0,sender.indexOf("!"))+" \u0001CLIENTINFO ACTION PING VERSION TIME CLIENTINFO\u0001");
} else if (message.equals("\u0001TIME\u0001")) {
return;
case "\u0001TIME\u0001":
StreamProvider.writeToStream(serverName, "NOTICE " + sender + " :\u0001TIME " + ZonedDateTime.now().format(DateTimeFormatter.RFC_1123_DATE_TIME) + "\u0001");
writerWorker.logAdd("[system]", "catch/handled CTCP TIME from", sender);
//System.out.println(":"+sender+" NOTICE "+sender.substring(0,sender.indexOf("!"))+" \u0001TIME "+ ZonedDateTime.now().format(DateTimeFormatter.RFC_1123_DATE_TIME)+"\u0001");
} else if (message.equals("\u0001SOURCE\u0001")) {
return;
case "\u0001SOURCE\u0001":
StreamProvider.writeToStream(serverName, "NOTICE " + sender + " :\u0001SOURCE https://github.com/developersu/InnaIrcBot\u0001");
writerWorker.logAdd("[system]", "catch/handled CTCP TIME from", sender);
//System.out.println(":"+sender+" NOTICE "+sender.substring(0,sender.indexOf("!"))+" \u0001SOURCE "+ ZonedDateTime.now().format(DateTimeFormatter.RFC_1123_DATE_TIME)+"\u0001");
} else
writerWorker.logAdd("[system]", "catch unknown CTCP request \"" + message + "\" from ", sender);
return;
}
if (message.startsWith("\u0001PING ") && message.endsWith("\u0001")) {
StreamProvider.writeToStream(serverName, "NOTICE " + sender + " :" + message);
writerWorker.logAdd("[system]", "catch/handled CTCP PING from", sender);
return;
}
writerWorker.logAdd("[system]", "catch unknown CTCP request \"" + message + "\" from ", sender);
}
private String simplifyNick(String nick){ return nick.replaceAll("!.*$",""); }
private void handleSpecial(String event, String chanel, String message){
//System.out.println("|"+event+"|"+chanel+"|"+message+"|");
if (channelsMap.containsKey(chanel)){
channelsMap.get(chanel).println(event+" "+nick+" "+chanel+" "+message); // WTF ><
channelsMap.get(chanel).flush();
//System.out.println("Formatted: |"+event+"|"+nick+"|"+chanel+" "+message+"|");
}
private void handleSpecial(String event, String channelName, String message){
IrcChannel ircChannel = channels.get(channelName);
if (ircChannel == null)
return;
String ircFormatterMessage = event+" "+nick+" "+channelName+" "+message;
//System.out.println("Formatted: |"+event+"|"+nick+"|"+channelName+" "+message+"|");
ircChannel.getChannelQueue().add(ircFormatterMessage);
}
//todo: nandle nickserv messages
private void handleNumeric(String eventNum, String sender, String message){
//todo: handle nickserv messages somehow
private void handleNumeric(String eventNum, String sender, String message) throws Exception{
switch (eventNum){
case "501": // Notify user about incorrect setup
writerWorker.logAdd("[system]", "catch/handled:", eventNum
+ " [MODE message was sent with a nickname parameter and that the a mode flag sent was not recognized.]");
break;
case "433": // TODO: try to use alternative nickname
writerWorker.logAdd("[system]", "catch/handled:", eventNum+" [nickname already in use]");
writerWorker.logAdd("[system]", "catch/handled:", eventNum
+ " [nickname already in use and will be changed]");
break;
case "353":
writerWorker.logAdd("[system]", "catch/handled:", eventNum+" [RPL_NAMREPLY]");
String chan = message.substring(message.indexOf(" ")+3);
chan = chan.substring(0, chan.indexOf(" "));
if (proxyAList.containsKey(chan)) {
String userOnChanStr = message.substring(message.indexOf(":") + 1);
userOnChanStr = userOnChanStr.replaceAll("[%@+]", "").trim();
String[] usersOnChanArr = userOnChanStr.split(" ");
String channelName = message.substring(nick.length()+3).replaceAll("\\s.*$", "");
PipedOutputStream streamOut = new PipedOutputStream();
try {
BufferedReader streamBufferedReader = new BufferedReader(
new InputStreamReader(
new PipedInputStream(streamOut), StandardCharsets.UTF_8)
);
channelsMap.put(chan, new PrintWriter(streamOut));
// % @ +
ChanConsumer consumer = new ChanConsumer(streamBufferedReader, storageFile.getServerName(), chan, nick, usersOnChanArr, storageFile.getRejoinOnKick(), channelsMap, storageFile.getChanelConfigurationsPath());
new Thread(consumer).start();
for (String msgStored : proxyAList.get(chan)) {
channelsMap.get(chan).println(msgStored);
channelsMap.get(chan).flush();
}
proxyAList.remove(chan);
if (proxyAList.isEmpty()) {
proxyRequired = false;
}
} catch (IOException e) {
System.out.println("Internal issue: SystemConsumer->handleNumeric() @ JOIN: I/O exception while initialized child objects."); // caused by Socket
return; //TODO: QA
}
}
else
System.out.println("Some internal shit happens that shouldn't happens never ever. Take your cat, call scientists and wait for singularity. Panic allowed. Log: \nEvent:|"+eventNum+"| sender:|"+sender+"| message|"+message+"|");
IrcChannel ircChannel = channels.get(channelName);
if (ircChannel == null)
return;
ircChannel.getChannelQueue().add(eventNum+" "+sender+" "+message);
break;
case "NICK":
if (sender.startsWith(nick+"!")) {
@ -224,9 +169,19 @@ public class SystemConsumer implements Runnable{
break;
case "JOIN":
if (sender.startsWith(nick+"!")) {
proxyAList.put(message, new ArrayList<>()); // Add new channel name to proxy watch-list
proxyAList.get(message).add(eventNum+" "+sender+" "+message); // Add message to array linked
this.proxyRequired = true; // Ask for proxy validators
IrcChannel newIrcChannel = new IrcChannel(message);
channels.put(message, newIrcChannel);
// % @ +
ChanConsumer consumer = new ChanConsumer(
configurationFile.getServerName(),
newIrcChannel,
nick,
channels);
Thread newIrcChannelThread = new Thread(consumer);
newIrcChannelThread.start();
channelThreads.add(newIrcChannelThread);
//proxyAList.get(message).add(eventNum+" "+sender+" "+message); // Add message to array linked
writerWorker.logAdd("[system]", "joined to channel ", message);
}
break;
@ -239,11 +194,79 @@ public class SystemConsumer implements Runnable{
CTCPHelper.getInstance().handleCtcpReply(serverName, simplifyNick(sender), message.replaceAll("^.+?:", "").trim());
writerWorker.logAdd("[system]", "NOTICE from "+sender+" received: ", message.replaceAll("^.+?:", "").trim());
break;
case "QUIT":
case "001":
sendUserModes();
sendNickPassword();
joinChannels();
break;
case "443":
String newNick = nick+"|"+nickTail++;
StreamProvider.writeToStream(serverName,"NICK "+newNick);
break;
case "464": // password for server/znc/bnc
StreamProvider.writeToStream(serverName,"PASS "+configurationFile.getServerPass());
break;
case "432":
System.out.println("Configuration issue: Nickname contains unacceptable characters (432 ERR_ERRONEUSNICKNAME).");
ReconnectControl.update(serverName, false);
break;
case "465":
ReconnectControl.update(serverName, false);
break;
case "QUIT": // TODO: Do something?
break;
default:
writerWorker.logAdd("[system]", "catch: "+eventNum+" from: "+sender+" :",message); // TODO: QUIT comes here. Do something?
writerWorker.logAdd("[system]", "catch: "+eventNum+" from: "+sender+" :",message);
break;
// 431 ERR_NONICKNAMEGIVEN how can we get this?
// 436 ERR_NICKCOLLISION
}
}
private void sendUserModes(){
String modes = configurationFile.getUserMode().replaceAll("[\t\\s]", "");
if (modes.isEmpty())
return;
StringBuilder message = new StringBuilder();
for(char mode : modes.toCharArray()) {
message.append("MODE ");
message.append(nick);
message.append(" +");
message.append(mode);
message.append("\n");
}
StreamProvider.writeToStream(serverName, message.toString());
}
private void sendNickPassword(){
if (configurationFile.getUserNickPass().isEmpty())
return;
switch (configurationFile.getUserNickAuthStyle()){
case "freenode":
StreamProvider.writeToStream(serverName,"PRIVMSG NickServ :IDENTIFY "
+ configurationFile.getUserNickPass());
break;
case "rusnet":
StreamProvider.writeToStream(serverName,"NickServ IDENTIFY "
+ configurationFile.getUserNickPass());
}
}
private void joinChannels(){
StringBuilder joinMessage = new StringBuilder();
for (String cnl : configurationFile.getChannels()) { // TODO: add validation of channels.
joinMessage.append("JOIN ");
joinMessage.append(cnl);
joinMessage.append("\n");
}
StreamProvider.writeToStream(serverName, joinMessage.toString());
}
}

View file

@ -3,21 +3,21 @@ package InnaIrcBot;
import java.util.HashMap;
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){
serversList.put(serverName, true);
}
public static synchronized void update(String serverName, boolean needReconnect){
serversList.replace(serverName, needReconnect);
}
public static synchronized boolean get(String serverName){ // could be null and it should be considered as false
if (serversList.get(serverName) == null)
return false;
else
return serversList.get(serverName);
}
public static synchronized void delete(String serverName){
serversList.remove(serverName);
}
public static synchronized void notify(String serverName) {
if (serversList.get(serverName) == null || ! serversList.get(serverName)){
serversList.remove(serverName);
return;
}
System.out.println("DEBUG: Thread "+serverName+" removed from observable list after unexpected finish.\n\t");
ConnectionsBuilder.getConnections().startNewConnection(serverName);
}
}

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;
import InnaIrcBot.Config.StorageFile;
import InnaIrcBot.config.ConfigurationFile;
public class StorageFileTest {
static public void main(String[] args){
StorageFile config = new StorageFile(
ConfigurationFile config = new ConfigurationFile(
"",
0,
"",

View file

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

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