v0.1
This commit is contained in:
commit
b72c61a019
29 changed files with 2154 additions and 0 deletions
6
.idea/vcs.xml
Normal file
6
.idea/vcs.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
16
InnaIrcBot.iml
Normal file
16
InnaIrcBot.iml
Normal file
|
@ -0,0 +1,16 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/test" isTestSource="true" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/.idea" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="gson-2.8.5" level="project" />
|
||||
<orderEntry type="library" name="sqlite-jdbc-3.23.1" level="project" />
|
||||
</component>
|
||||
</module>
|
40
src/InnaIrcBot/BotStart.java
Normal file
40
src/InnaIrcBot/BotStart.java
Normal file
|
@ -0,0 +1,40 @@
|
|||
/**
|
||||
* InnaIrcBot
|
||||
* @author Dmitry Isaenko
|
||||
* @version 0.1 "Батлкрузер"
|
||||
* Russia, 2018.
|
||||
* */
|
||||
package InnaIrcBot;
|
||||
|
||||
import InnaIrcBot.Config.StorageFile;
|
||||
import InnaIrcBot.Config.StorageReader;
|
||||
import InnaIrcBot.ProvidersConsumers.DataProvider;
|
||||
|
||||
public class BotStart {
|
||||
//TODO: Steam link, flood control, kikbana
|
||||
//TODO: setDaemon(true)
|
||||
//TODO: multiple connections to one server not allowed
|
||||
|
||||
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]);
|
||||
} else if (args[0].equals("--version") || args[0].equals("-v")) {
|
||||
System.out.println(GlobalData.getAppVersion());
|
||||
}
|
||||
}
|
||||
else if (args[0].equals("--generate") || args[0].equals("-g")){
|
||||
StorageReader.generateDefaultConfig(null);
|
||||
}
|
||||
}
|
||||
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\t\t\t\tGenerate Config\n"
|
||||
+"\t-v, --version\t\t\t\t\t\t\t\t\t\t\tGet application version");
|
||||
}
|
||||
}
|
||||
}
|
235
src/InnaIrcBot/Commanders/ChanelCommander.java
Normal file
235
src/InnaIrcBot/Commanders/ChanelCommander.java
Normal file
|
@ -0,0 +1,235 @@
|
|||
package InnaIrcBot.Commanders;
|
||||
|
||||
import InnaIrcBot.ProvidersConsumers.StreamProvider;
|
||||
|
||||
import java.io.*;
|
||||
import java.time.LocalTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
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;
|
||||
//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 boolean joinFloodTrackNeed = false;
|
||||
private JoinFloodHandler jfh;
|
||||
|
||||
public ChanelCommander(BufferedReader streamReader, String serverName, String chan, String configFilePath){
|
||||
this.reader = streamReader;
|
||||
this.server = serverName;
|
||||
this.chanel = chan;
|
||||
|
||||
this.joinMap = new HashMap<>();
|
||||
this.msgMap = new HashMap<>();
|
||||
this.nickMap = new HashMap<>();
|
||||
readConfing(configFilePath);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
System.out.println("Thread for ChanelCommander started"); // TODO:REMOVE DEBUG
|
||||
String data;
|
||||
String[] dataStrings;
|
||||
try {
|
||||
while ((data = reader.readLine()) != null) {
|
||||
dataStrings = data.split(" ",3);
|
||||
//event initiatorArg messageArg
|
||||
switch (dataStrings[0]) {
|
||||
case "NICK":
|
||||
nickCame(dataStrings[2]+dataStrings[1].replaceAll("^.+?!","!"));
|
||||
break; // todo: need to track join flood
|
||||
case "JOIN":
|
||||
if (joinFloodTrackNeed)
|
||||
jfh.track(simplifyNick(dataStrings[1]));
|
||||
joinCame(dataStrings[1]);
|
||||
break;
|
||||
case "PRIVMSG":
|
||||
privmsgCame(dataStrings[1], dataStrings[2]);
|
||||
break;
|
||||
/*
|
||||
case "PART": // todo: need to track join flood? Fuck that. Track using JOIN
|
||||
break;
|
||||
case "QUIT": // todo: need this?
|
||||
break;
|
||||
case "TOPIC": // todo: need this?
|
||||
break;
|
||||
case "MODE": // todo: need this?
|
||||
break;
|
||||
case "KICK": // todo: need this?
|
||||
break; */
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (java.io.IOException e){
|
||||
System.out.println("Internal issue: thread ChanelCommander->run() caused I/O exception:\n\t"+e); // TODO: reconnect
|
||||
}
|
||||
System.out.println("Thread for ChanelCommander ended"); // TODO:REMOVE DEBUG
|
||||
}
|
||||
|
||||
// Do we need old nick?
|
||||
private void nickCame(String newNick){
|
||||
came(nickMap, newNick, newNick);
|
||||
}
|
||||
private void joinCame(String who){
|
||||
came(joinMap, who, who);
|
||||
}
|
||||
private void privmsgCame(String who, String what){
|
||||
came(msgMap, what, who);
|
||||
}
|
||||
|
||||
private void came(HashMap<String, String[]> map, String arg1, String arg2){
|
||||
for (String pattern : map.keySet())
|
||||
if (Pattern.matches(pattern, arg1)){ // NOTE: validation based on new nick //TODO: parse here
|
||||
String[] cmdOrMsg = map.get(pattern);
|
||||
for (int i = 0; i<cmdOrMsg.length;) {
|
||||
//switch (map.get(pattern)[0]){
|
||||
ArrayList<String> whatToSend;
|
||||
switch (cmdOrMsg[i]) {
|
||||
case "\\chanmsg":
|
||||
whatToSend = new ArrayList<>();
|
||||
for (i++; (i < cmdOrMsg.length) && !(cmdOrMsg[i].startsWith("\\")); i++)
|
||||
whatToSend.add(cmdOrMsg[i]);
|
||||
msgAction(whatToSend.toArray(new String[0]), arg2, false);
|
||||
break;
|
||||
case "\\privmsg":
|
||||
whatToSend = new ArrayList<>();
|
||||
for (i++; (i < cmdOrMsg.length) && !(cmdOrMsg[i].startsWith("\\")); i++)
|
||||
whatToSend.add(cmdOrMsg[i]);
|
||||
msgAction(whatToSend.toArray(new String[0]), arg2, true);
|
||||
break;
|
||||
case "\\ban":
|
||||
banAction(arg2);
|
||||
i++;
|
||||
break;
|
||||
case "\\kick":
|
||||
whatToSend = new ArrayList<>();
|
||||
for (i++; (i < cmdOrMsg.length) && !(cmdOrMsg[i].startsWith("\\")); i++)
|
||||
whatToSend.add(cmdOrMsg[i]);
|
||||
kickAction(whatToSend.toArray(new String[0]), arg2);
|
||||
break;
|
||||
case "\\kickban":
|
||||
whatToSend = new ArrayList<>();
|
||||
for (i++; (i < cmdOrMsg.length) && !(cmdOrMsg[i].startsWith("\\")); i++)
|
||||
whatToSend.add(cmdOrMsg[i]);
|
||||
banAction(arg2);
|
||||
kickAction(whatToSend.toArray(new String[0]), arg2);
|
||||
break;
|
||||
case "\\raw":
|
||||
StringBuilder whatToSendRaw = new StringBuilder();
|
||||
for (i++; (i < cmdOrMsg.length) && !(cmdOrMsg[i].startsWith("\\")); i++)
|
||||
whatToSendRaw.append(cmdOrMsg[i]);
|
||||
StreamProvider.writeToStream(server, whatToSendRaw.toString()); //TODO
|
||||
break; //todo: add script
|
||||
default:
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
///////// /////////
|
||||
private void msgAction(String[] messages, String who, boolean sendToPrivate){
|
||||
StringBuilder executiveStr = new StringBuilder();
|
||||
executiveStr.append("PRIVMSG ");
|
||||
if(sendToPrivate) {
|
||||
executiveStr.append(simplifyNick(who));
|
||||
executiveStr.append(" :");
|
||||
}
|
||||
else {
|
||||
executiveStr.append(chanel);
|
||||
executiveStr.append(" :");
|
||||
executiveStr.append(simplifyNick(who));
|
||||
executiveStr.append(": ");
|
||||
}
|
||||
|
||||
for (int i = 1; i<messages.length; i++){
|
||||
if ( ! messages[i].startsWith("\\"))
|
||||
executiveStr.append(messages[i]);
|
||||
else if (messages[i].equals("\\time"))
|
||||
executiveStr.append(LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")));
|
||||
}
|
||||
//System.out.println(executiveStr); //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("^.+@",""));
|
||||
}
|
||||
private void kickAction(String[] messages, String whom){
|
||||
StringBuilder executiveStr = new StringBuilder();
|
||||
executiveStr.append("KICK ");
|
||||
executiveStr.append(chanel);
|
||||
executiveStr.append(" ");
|
||||
executiveStr.append(simplifyNick(whom));
|
||||
executiveStr.append(" :");
|
||||
|
||||
for (int i = 1; i<messages.length; i++){
|
||||
if ( ! messages[i].startsWith("\\"))
|
||||
executiveStr.append(messages[i]);
|
||||
else if (messages[i].equals("\\time"))
|
||||
executiveStr.append(LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")));
|
||||
}
|
||||
StreamProvider.writeToStream(server, executiveStr.toString());
|
||||
|
||||
}
|
||||
// TSV
|
||||
private void parse(String[] directive){
|
||||
if (directive.length >= 3 && directive[0] != null && !directive[0].startsWith("#") && directive[1] != null && directive[2] != null){
|
||||
//System.out.println(Arrays.toString(directive)); // TODO:debug
|
||||
switch (directive[0].toLowerCase()){
|
||||
case "join":
|
||||
joinMap.put(directive[1], Arrays.copyOfRange(directive, 2, directive.length));
|
||||
break;
|
||||
case "msg":
|
||||
msgMap.put(directive[1], Arrays.copyOfRange(directive, 2, directive.length));
|
||||
break;
|
||||
case "nick":
|
||||
nickMap.put(directive[1], Arrays.copyOfRange(directive, 2, directive.length));
|
||||
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());
|
||||
if (events > 0 && timeFrame > 0) {
|
||||
jfh = new JoinFloodHandler(events, timeFrame, server, chanel);
|
||||
joinFloodTrackNeed = true;
|
||||
} else {
|
||||
System.out.println("Internal issue: thread ChanelCommander->parse(): 'Number of events' and/or 'Time Frame in seconds' should be greater then 0");
|
||||
}
|
||||
}
|
||||
else
|
||||
System.out.println("Internal issue: thread ChanelCommander->parse(): 'Number of events' and/or 'Time Frame in seconds' should be numbers greater then 0");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
private String simplifyNick(String nick){ return nick.replaceAll("!.*$",""); }
|
||||
|
||||
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
|
||||
if (!file.exists())
|
||||
return;
|
||||
try {
|
||||
BufferedReader br = new BufferedReader(new FileReader(file));
|
||||
String line;
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
60
src/InnaIrcBot/Commanders/JoinFloodHandler.java
Normal file
60
src/InnaIrcBot/Commanders/JoinFloodHandler.java
Normal file
|
@ -0,0 +1,60 @@
|
|||
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;
|
||||
|
||||
public JoinFloodHandler(int events, int timeFrameInSeconds, String serverName, String chanelName){
|
||||
this.eventBufferSize = events;
|
||||
this.timeFrameInSeconds = timeFrameInSeconds;
|
||||
this.server = serverName;
|
||||
this.chanel = chanelName;
|
||||
this.usersOnChanel = 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)
|
||||
}
|
||||
}
|
||||
else {
|
||||
usersOnChanel.put(userNick, new LprFIFOQueue(eventBufferSize)); // Create buffer for tracking new user joined
|
||||
usersOnChanel.get(userNick).addLastGetFirst(); // Write his/her first join time
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
316
src/InnaIrcBot/Commanders/PrivateMsgCommander.java
Normal file
316
src/InnaIrcBot/Commanders/PrivateMsgCommander.java
Normal file
|
@ -0,0 +1,316 @@
|
|||
package InnaIrcBot.Commanders;
|
||||
|
||||
import InnaIrcBot.GlobalData;
|
||||
import InnaIrcBot.ProvidersConsumers.StreamProvider;
|
||||
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 PrivateMsgCommander(String server, String adminPassword){
|
||||
this.serverName = server;
|
||||
this.administrators = new ArrayList<>();
|
||||
this.password = adminPassword.trim();
|
||||
}
|
||||
|
||||
public void receiver(String sender, String message){
|
||||
if (!password.isEmpty()) {
|
||||
if (administrators.contains(sender) && !message.isEmpty()) {
|
||||
String[] cmd = message.split("(\\s)|(\t)+?", 2);
|
||||
cmd[0] = cmd[0].toLowerCase();
|
||||
|
||||
switch (cmd[0]){
|
||||
case "tell":
|
||||
if ((cmd.length == 2) && (cmd[1].split("(\\s)|(\t)+?",2).length == 2)) {
|
||||
String[] tellArgs = cmd[1].split("(\\s)|(\t)+?", 2);
|
||||
tell(tellArgs[0], tellArgs[1]);
|
||||
}
|
||||
else
|
||||
tell(simplifyNick(sender), "Pattern: tell <nick> <message>");
|
||||
break;
|
||||
case "join":
|
||||
if (cmd.length == 2)
|
||||
join(cmd[1]);
|
||||
else
|
||||
tell(simplifyNick(sender), "Pattern: join <channel>");
|
||||
break;
|
||||
case "quit":
|
||||
if (cmd.length == 2)
|
||||
quit(cmd[1]);
|
||||
else
|
||||
quit("");
|
||||
break;
|
||||
case "nick":
|
||||
if (cmd.length == 2)
|
||||
nick(cmd[1]);
|
||||
else
|
||||
tell(simplifyNick(sender), "Pattern: nick <new_Nick>");
|
||||
break;
|
||||
case "part": //TODO: update
|
||||
if (cmd.length == 2)
|
||||
part(cmd[1]);
|
||||
else
|
||||
tell(simplifyNick(sender), "Pattern: part <channel> [reason]");
|
||||
break;
|
||||
case "ctcp":
|
||||
if ((cmd.length == 2) && (cmd[1].split("(\\s)|(\t)+?",2).length == 2)) {
|
||||
String[] ctcpArgs = cmd[1].split("(\\s)|(\t)+?", 2);
|
||||
ctcp(ctcpArgs[0], ctcpArgs[1]);
|
||||
}
|
||||
else
|
||||
tell(simplifyNick(sender), "Pattern: ctcp <object> <CTCP-command>");
|
||||
break;
|
||||
case "notice":
|
||||
if ((cmd.length == 2) && (cmd[1].split("(\\s)|(\t)+?",2).length == 2)) {
|
||||
String[] noticeArgs = cmd[1].split("(\\s)|(\t)+?", 2);
|
||||
notice(noticeArgs[0], noticeArgs[1]);
|
||||
}
|
||||
else
|
||||
tell(simplifyNick(sender), "Pattern: notice <nick> <message>");
|
||||
break;
|
||||
case "umode":
|
||||
if ((cmd.length == 2) && (cmd[1].split("(\\s)|(\t)+?",2).length == 2)) {
|
||||
String[] modeArgs = cmd[1].split("(\\s)|(\t)+?", 2);
|
||||
umode(modeArgs[0], modeArgs[1]);
|
||||
}
|
||||
else
|
||||
tell(simplifyNick(sender), "Pattern: umode <object> <[+|-]mode_single_char>");
|
||||
break;
|
||||
case "raw":
|
||||
if (cmd.length == 2)
|
||||
raw(cmd[1]);
|
||||
else
|
||||
tell(simplifyNick(sender), "Pattern: raw <any_text_to_server>");
|
||||
break;
|
||||
case "cmode":
|
||||
if ((cmd.length >= 2) && (cmd[1].split("(\\s)|(\t)+?",3).length >= 2)) {
|
||||
String[] args = cmd[1].split("(\\s)|(\t)+?", 3);
|
||||
if (args.length == 2)
|
||||
cmode(args[0], args[1], null);
|
||||
else if(args.length == 3)
|
||||
cmode(args[0], args[1], args[2]);
|
||||
}
|
||||
else
|
||||
tell(simplifyNick(sender), "Pattern: cmode <channel> [<mode>|<mode> <pattern_user>]");
|
||||
|
||||
break;
|
||||
case "k":
|
||||
case "kick":
|
||||
if ((cmd.length >= 2) && (cmd[1].split("(\\s)|(\t)+?",3).length >= 2)) {
|
||||
String[] args = cmd[1].split("(\\s)|(\t)+?", 3);
|
||||
if (args.length == 2)
|
||||
kick(args[0], args[1], null);
|
||||
else if(args.length == 3)
|
||||
kick(args[0], args[1], args[2]);
|
||||
}
|
||||
else
|
||||
tell(simplifyNick(sender), "Pattern: [k|kick] <channel> <user> [reason]");
|
||||
break;
|
||||
case "b":
|
||||
case "ban":
|
||||
if ((cmd.length == 2) && (cmd[1].split("(\\s)|(\t)+?",2).length == 2)) {
|
||||
String[] banArgs = cmd[1].split("(\\s)|(\t)+?", 2);
|
||||
ban(banArgs[0], banArgs[1]);
|
||||
}
|
||||
else
|
||||
tell(simplifyNick(sender), "Pattern: [b|ban] <channel> <user>");
|
||||
break;
|
||||
case "-b":
|
||||
case "unban":
|
||||
if ((cmd.length == 2) && (cmd[1].split("(\\s)|(\t)+?",2).length == 2)) {
|
||||
String[] banArgs = cmd[1].split("(\\s)|(\t)+?", 2);
|
||||
unban(banArgs[0], banArgs[1]);
|
||||
}
|
||||
else
|
||||
tell(simplifyNick(sender), "Pattern: [-b|unban] <channel> <user>");
|
||||
break;
|
||||
case "kb":
|
||||
case "kickban":
|
||||
if ((cmd.length >= 2) && (cmd[1].split("(\\s)|(\t)+?",3).length >= 2)) {
|
||||
String[] args = cmd[1].split("(\\s)|(\t)+?", 3);
|
||||
if (args.length == 2)
|
||||
kickban(args[0], args[1], null);
|
||||
else if(args.length == 3)
|
||||
kickban(args[0], args[1], args[2]);
|
||||
}
|
||||
else
|
||||
tell(simplifyNick(sender), "Pattern: [kb|kickban] <channel> <user>");
|
||||
break;
|
||||
case "v":
|
||||
case "voice":
|
||||
if ((cmd.length == 2) && (cmd[1].split("(\\s)|(\t)+?",2).length == 2)) {
|
||||
String[] voiceArgs = cmd[1].split("(\\s)|(\t)+?", 2);
|
||||
voice(voiceArgs[0], voiceArgs[1]);
|
||||
}
|
||||
else
|
||||
tell(simplifyNick(sender), "Pattern: [v|voice] <channel> <user>");
|
||||
break;
|
||||
case "-v":
|
||||
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]);
|
||||
}
|
||||
else
|
||||
tell(simplifyNick(sender), "Pattern: [-v|unvoice] <channel> <user>");
|
||||
break;
|
||||
case "h":
|
||||
case "hop":
|
||||
if ((cmd.length == 2) && (cmd[1].split("(\\s)|(\t)+?",2).length == 2)) {
|
||||
String[] hopArgs = cmd[1].split("(\\s)|(\t)+?", 2);
|
||||
hop(hopArgs[0], hopArgs[1]);
|
||||
}
|
||||
else
|
||||
tell(simplifyNick(sender), "Pattern: [h|hop] <channel> <user>");
|
||||
break;
|
||||
case "-h":
|
||||
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]);
|
||||
}
|
||||
else
|
||||
tell(simplifyNick(sender), "Pattern: [-h|unhop] <channel> <user>");
|
||||
break;
|
||||
case "o":
|
||||
case "op":
|
||||
if ((cmd.length == 2) && (cmd[1].split("(\\s)|(\t)+?",2).length == 2)) {
|
||||
String[] operatorArgs = cmd[1].split("(\\s)|(\t)+?", 2);
|
||||
op(operatorArgs[0], operatorArgs[1]);
|
||||
}
|
||||
else
|
||||
tell(simplifyNick(sender), "Pattern: [o|operator] <channel> <user>");
|
||||
break;
|
||||
case "-o":
|
||||
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]);
|
||||
}
|
||||
else
|
||||
tell(simplifyNick(sender), "Pattern: [-o|unoperator] <channel> <user>");
|
||||
break;
|
||||
case "topic":
|
||||
if ((cmd.length == 2) && (cmd[1].split("(\\s)|(\t)+?",2).length == 2)) {
|
||||
String[] args = cmd[1].split("(\\s)|(\t)+?", 2);
|
||||
topic(args[0], args[1]);
|
||||
}
|
||||
else
|
||||
tell(simplifyNick(sender), "Pattern: topic <channel> <topic>");
|
||||
break;
|
||||
case "invite":
|
||||
if ((cmd.length == 2) && (cmd[1].split("(\\s)|(\t)+?",2).length == 2)) {
|
||||
String[] args = cmd[1].split("(\\s)|(\t)+?", 2);
|
||||
invite(args[0], args[1]);
|
||||
}
|
||||
else
|
||||
tell(simplifyNick(sender), "Pattern: invite <user> <channel>");
|
||||
break;
|
||||
default:
|
||||
tell(simplifyNick(sender), "Unknown command. Could be: join, part, quit, tell, nick, ctcp, notice, umode, cmode, raw, kick[k], ban[b], unban[-b], kickban[kb], voice[v], unvoice[-v], hop[h], unhop[-h], op[o], unop[-o], topic, invite and (login)");
|
||||
} // TODO: chanel limits set/remove
|
||||
} else {
|
||||
if (!message.isEmpty() && message.startsWith("login ")) {
|
||||
login(sender, message.replaceAll("^([\t\\s]+)?login([\t\\s]+)|([\t\\s]+$)", ""));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void join(String channel){
|
||||
raw("JOIN "+channel);
|
||||
}
|
||||
private void part(String channel){
|
||||
raw("PART "+channel);
|
||||
}
|
||||
private void quit(String message){
|
||||
if (message.isEmpty()){
|
||||
raw("QUIT :"+ GlobalData.getAppVersion());
|
||||
}
|
||||
else
|
||||
raw("QUIT :"+message);
|
||||
ReconnectControl.update(serverName, false);
|
||||
//ReconnectControl.update(serverName, true);
|
||||
//System.exit(0); // TODO: change to normal exit
|
||||
}
|
||||
private void tell(String channelUser, String message){
|
||||
raw("PRIVMSG "+channelUser+" :"+message);
|
||||
}
|
||||
private void nick(String newNick){
|
||||
raw("NICK "+newNick);
|
||||
}
|
||||
private void ctcp(String object, String command){
|
||||
raw("PRIVMSG "+object+" :\u0001"+command.toUpperCase()+"\u0001");
|
||||
}
|
||||
private void notice(String channelUser, String message){
|
||||
raw("NOTICE "+channelUser+" :"+message);
|
||||
}
|
||||
private void umode(String object, String mode){
|
||||
raw("MODE "+object+" "+mode);
|
||||
}
|
||||
private void cmode(String object, String mode, String user){
|
||||
if (user == null)
|
||||
raw("MODE "+object+" "+mode);
|
||||
else
|
||||
raw("MODE "+object+" "+mode+" "+user);
|
||||
}
|
||||
private void raw(String rawText){
|
||||
StreamProvider.writeToStream(serverName, rawText);
|
||||
}
|
||||
private void kick(String chanel, String user, String reason){
|
||||
if (reason == null)
|
||||
raw("KICK "+chanel+" "+user+" :requested");
|
||||
else
|
||||
raw("KICK "+chanel+" "+user+" :"+reason);
|
||||
}
|
||||
private void ban(String chanel, String user){
|
||||
cmode(chanel, "+b", simplifyNick(user)+"*!*@*"); // TODO: work on patter.n
|
||||
if (user.contains("@")){
|
||||
cmode(chanel, "+b", "*!*@"+user.replaceAll("^.+@",""));
|
||||
}
|
||||
}
|
||||
private void unban(String chanel, String user){
|
||||
cmode(chanel, "-b", simplifyNick(user)+"*!*@*");
|
||||
if (user.contains("@")){
|
||||
cmode(chanel, "-b", "*!*@"+user.replaceAll("^.+@",""));
|
||||
}
|
||||
}
|
||||
private void kickban(String chanel, String user, String reason){
|
||||
cmode(chanel, "+b", simplifyNick(user)+"*!*@*");
|
||||
kick(chanel, user, reason);
|
||||
}
|
||||
private void voice(String chanel, String user){
|
||||
cmode(chanel, "+v", user);
|
||||
}
|
||||
private void unvoice(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){
|
||||
cmode(chanel, "-h", user);
|
||||
}
|
||||
private void op(String chanel, String user){
|
||||
cmode(chanel, "+o", user);
|
||||
}
|
||||
private void unop(String chanel, String user){
|
||||
cmode(chanel, "-o", user);
|
||||
}
|
||||
private void topic(String channel, String topic){ raw("TOPIC "+channel+" :"+topic); }
|
||||
private void invite(String user, String chanel){
|
||||
raw("INVITE "+user+" "+chanel);
|
||||
}
|
||||
|
||||
private void login(String candidate, String key){
|
||||
if (key.equals(password)) {
|
||||
administrators.add(candidate);
|
||||
tell(simplifyNick(candidate), "Granted.");
|
||||
}
|
||||
}
|
||||
private String simplifyNick(String nick){ return nick.replaceAll("!.*$",""); }
|
||||
}
|
63
src/InnaIrcBot/Config/StorageFile.java
Normal file
63
src/InnaIrcBot/Config/StorageFile.java
Normal file
|
@ -0,0 +1,63 @@
|
|||
package InnaIrcBot.Config;
|
||||
|
||||
public class StorageFile {
|
||||
private final String serverName;
|
||||
private final int serverPort;
|
||||
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;
|
||||
|
||||
public String getServerName() { return serverName; }
|
||||
public int getServerPort() { return serverPort; }
|
||||
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 StorageFile(String serverName,
|
||||
int serverPort,
|
||||
String[] channels,
|
||||
String userNick,
|
||||
String userIdent,
|
||||
String userRealName,
|
||||
String userNickPass,
|
||||
String userNickAuthStyle,
|
||||
String userMode,
|
||||
boolean rejoinOnKick,
|
||||
String logDriver,
|
||||
String[] logDriverParameters,
|
||||
String botAdministratorPassword,
|
||||
String chanelConfigurationsPath){
|
||||
this.serverName = serverName;
|
||||
this.serverPort = serverPort;
|
||||
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;
|
||||
}
|
||||
}
|
95
src/InnaIrcBot/Config/StorageReader.java
Normal file
95
src/InnaIrcBot/Config/StorageReader.java
Normal file
|
@ -0,0 +1,95 @@
|
|||
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")
|
||||
);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
82
src/InnaIrcBot/Connections.java
Normal file
82
src/InnaIrcBot/Connections.java
Normal file
|
@ -0,0 +1,82 @@
|
|||
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.isEmpty()");
|
||||
break;
|
||||
}
|
||||
else {
|
||||
it = connectionsList.iterator();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
8
src/InnaIrcBot/GlobalData.java
Normal file
8
src/InnaIrcBot/GlobalData.java
Normal file
|
@ -0,0 +1,8 @@
|
|||
package InnaIrcBot;
|
||||
|
||||
public class GlobalData {
|
||||
public static final String version = "InnaIrcBot v0.1 \"Батлкрузер\"";
|
||||
public static synchronized String getAppVersion(){
|
||||
return version;
|
||||
}
|
||||
}
|
38
src/InnaIrcBot/LogDriver/BotDriver.java
Normal file
38
src/InnaIrcBot/LogDriver/BotDriver.java
Normal file
|
@ -0,0 +1,38 @@
|
|||
package InnaIrcBot.LogDriver;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
public class BotDriver {
|
||||
private static HashMap<String, String[][]> serverDriver = new HashMap<>();
|
||||
/**
|
||||
* Define driver for desired server
|
||||
* */ // TODO: add proxy worker for using with multiple drivers
|
||||
public static synchronized boolean setFileDriver(String serverName, String driver, String[] driverParams){
|
||||
if (!driver.isEmpty() && driverParams != null && driverParams.length > 0 && driverParams[0] != null && !driverParams[0].isEmpty()) {
|
||||
String[][] drvAndParams = {
|
||||
{driver},
|
||||
driverParams
|
||||
};
|
||||
serverDriver.put(serverName, drvAndParams);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
public static synchronized Worker getWorker(String serverName, String chanelName){
|
||||
if (serverDriver.containsKey(serverName)) {
|
||||
switch (serverDriver.get(serverName)[0][0]) {
|
||||
case "Files":
|
||||
return new BotFilesWorker(serverName, serverDriver.get(serverName)[1], chanelName);
|
||||
case "SQLite":
|
||||
return new BotSQLiteWorker(serverName, serverDriver.get(serverName)[1], chanelName);
|
||||
case "Zero":
|
||||
return new BotZeroWorker();
|
||||
default:
|
||||
System.out.println("Configuration issue: BotDriver->getWorker() can't find required driver \""+serverDriver.get(serverName)[0][0]+"\".Using \"ZeroWorker\".");
|
||||
return new BotZeroWorker();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
163
src/InnaIrcBot/LogDriver/BotFilesWorker.java
Normal file
163
src/InnaIrcBot/LogDriver/BotFilesWorker.java
Normal file
|
@ -0,0 +1,163 @@
|
|||
package InnaIrcBot.LogDriver;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalTime;
|
||||
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 FileWriter fileWriter;
|
||||
|
||||
public BotFilesWorker(String server, String[] driverParameters, String channel){
|
||||
if (System.getProperty("os.name").startsWith("Windows")){
|
||||
channel = channel.replaceAll("\",",",");
|
||||
}
|
||||
else {
|
||||
channel = channel.replaceAll("/",",");
|
||||
}
|
||||
|
||||
driverParameters[0] = driverParameters[0].trim(); //Consider parameters[0] as dirLocation
|
||||
String dirLocation;
|
||||
if (driverParameters[0].endsWith(File.separator))
|
||||
dirLocation = driverParameters[0]+server;
|
||||
else
|
||||
dirLocation = driverParameters[0]+File.separator+server;
|
||||
File dir = new File(dirLocation);
|
||||
dir.mkdirs();
|
||||
if (!dir.exists()) {
|
||||
System.out.println("Unable to create directory to store files: " + dirLocation); //TODO: notify requester
|
||||
this.consistent = false;
|
||||
}
|
||||
this.filePath = dirLocation+File.separator+channel;
|
||||
|
||||
dateFormat = DateTimeFormatter.ofPattern("HH:mm:ss");
|
||||
if (resetFileWriter(false))
|
||||
this.consistent = true;
|
||||
}
|
||||
|
||||
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("Internal issue: BotFilesWorker->constructor() can't create file to store logs: "+this.filePath);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public boolean isConsistent() {
|
||||
return consistent;
|
||||
}
|
||||
|
||||
/**
|
||||
* argument[0] should be always 'from whom'
|
||||
* argument[1] should be always 'subject'
|
||||
* */
|
||||
@Override
|
||||
public void logAdd(String event, String initiatorArg, String messageArg) {
|
||||
switch (event){
|
||||
case "PRIVMSG":
|
||||
PRIVMSG(initiatorArg, messageArg);
|
||||
break;
|
||||
case "JOIN":
|
||||
JOIN(initiatorArg, messageArg);
|
||||
break;
|
||||
case "MODE":
|
||||
MODE(initiatorArg, messageArg);
|
||||
break;
|
||||
case "KICK":
|
||||
KICK(initiatorArg, messageArg);
|
||||
break;
|
||||
case "PART":
|
||||
PART(initiatorArg, messageArg);
|
||||
break;
|
||||
case "QUIT":
|
||||
QUIT(initiatorArg, messageArg);
|
||||
break;
|
||||
case "NICK":
|
||||
NICK(initiatorArg, messageArg);
|
||||
break;
|
||||
case "TOPIC":
|
||||
TOPIC(initiatorArg, messageArg);
|
||||
break;
|
||||
default:
|
||||
this.prettyPrint("["+LocalTime.now().format(dateFormat)+"] "+event+" "+initiatorArg+" "+messageArg+"\n"); // TODO: QA @ big data
|
||||
break;
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void close() {
|
||||
try {
|
||||
fileWriter.close();
|
||||
}
|
||||
catch (java.io.IOException e){
|
||||
System.out.println("Internal issue: BotFilesWorker->close() failed\n\tUnable to properly close file: "+this.filePath); // Live with it.
|
||||
}
|
||||
|
||||
}
|
||||
private void prettyPrint(String string){
|
||||
if (LocalDate.now().isAfter(fileWriterDay))
|
||||
resetFileWriter(true);
|
||||
try {
|
||||
fileWriter.write(string);
|
||||
fileWriter.flush();
|
||||
} catch (IOException e) {
|
||||
System.out.println("Internal issue: BotFilesWorker->prettyPrint() failed\n\tUnable to write logs of "+this.filePath+" because of internal failure in LocalTime representation.");
|
||||
consistent = false;
|
||||
}
|
||||
}
|
||||
private String genDate(){
|
||||
return "["+LocalTime.now().format(dateFormat)+"] ";
|
||||
}
|
||||
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");
|
||||
}
|
||||
}
|
||||
private void JOIN(String initiatorArg, String messageArg){
|
||||
this.prettyPrint(genDate()+">> "+getUserNameAndHost(initiatorArg)+"joined "+messageArg+"\n");
|
||||
}
|
||||
private void MODE(String initiatorArg, String messageArg){
|
||||
String initiatorChain;
|
||||
if (initiatorArg.contains("!"))
|
||||
initiatorChain = getUserNameAndHost(initiatorArg)+"set";
|
||||
else
|
||||
initiatorChain = initiatorArg+" set";
|
||||
this.prettyPrint(genDate()+"-!- "+initiatorChain+messageArg.substring(messageArg.indexOf(" "))+"\n");
|
||||
}
|
||||
private void KICK(String initiatorArg, String messageArg){
|
||||
this.prettyPrint(genDate()+"!<< "+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");
|
||||
}
|
||||
private void QUIT(String initiatorArg, String messageArg){
|
||||
this.prettyPrint(genDate()+"<< "+getUserNameAndHost(initiatorArg)+" quit: "+messageArg.replaceAll("^.+?:","")+"\n");
|
||||
}
|
||||
private void NICK(String initiatorArg, String messageArg){
|
||||
this.prettyPrint(genDate()+"-!- "+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");
|
||||
}
|
||||
}
|
201
src/InnaIrcBot/LogDriver/BotSQLiteWorker.java
Normal file
201
src/InnaIrcBot/LogDriver/BotSQLiteWorker.java
Normal file
|
@ -0,0 +1,201 @@
|
|||
package InnaIrcBot.LogDriver;
|
||||
|
||||
import org.sqlite.SQLiteConfig;
|
||||
import org.sqlite.SQLiteOpenMode;
|
||||
|
||||
import java.io.File;
|
||||
import java.sql.*;
|
||||
|
||||
public class BotSQLiteWorker implements Worker {
|
||||
|
||||
private Connection connection;
|
||||
private boolean consistent = false;
|
||||
private PreparedStatement preparedStatement;
|
||||
/**
|
||||
* Don't even think of changing this balalaika.
|
||||
* */
|
||||
public BotSQLiteWorker(String server, String[] driverParameters, String channel){ // TODO: threads on SQLite level // remember: One file one DB
|
||||
driverParameters[0] = driverParameters[0].trim();
|
||||
File dir = new File(driverParameters[0]);
|
||||
dir.mkdirs();
|
||||
if (!dir.exists()) {
|
||||
System.out.println("Unable to create directory to store DB file: " + driverParameters[0]); //TODO: notify requester
|
||||
this.consistent = false;
|
||||
}
|
||||
String connectionURL;
|
||||
if (driverParameters[0].endsWith(File.separator))
|
||||
connectionURL = "jdbc:sqlite:"+driverParameters[0]+server+".db";
|
||||
else
|
||||
connectionURL = "jdbc:sqlite:"+driverParameters[0]+File.separator+server+".db";
|
||||
|
||||
String safeChanName = channel.trim().replaceAll("\"","\\\""); // TODO: use trim in every driver/worker?
|
||||
try {
|
||||
SQLiteConfig sqlConfig = new SQLiteConfig();
|
||||
sqlConfig.setOpenMode(SQLiteOpenMode.NOMUTEX); //SQLITE_OPEN_NOMUTEX : multithreaded mode
|
||||
|
||||
this.connection = DriverManager.getConnection(connectionURL, sqlConfig.toProperties());
|
||||
if (connection != null){
|
||||
// Create table if not created
|
||||
Statement statement = connection.createStatement();
|
||||
String query = "CREATE TABLE IF NOT EXISTS \""+safeChanName+"\" ("
|
||||
+ " id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,"
|
||||
+ " unixtime INTEGER,"
|
||||
+ " event TEXT,"
|
||||
+ " subject TEXT,"
|
||||
+ " message TEXT,"
|
||||
+ " object TEXT"
|
||||
+");";
|
||||
statement.executeUpdate(query);
|
||||
|
||||
// Check table representation
|
||||
ResultSet rs = statement.executeQuery("PRAGMA table_info(\""+safeChanName+"\");"); // executeQuery never null
|
||||
boolean[] schemaResultCheck = {false, false, false, false, false, false};
|
||||
|
||||
while (rs.next()) {
|
||||
switch (rs.getInt("cid")) {
|
||||
case 0:
|
||||
if (rs.getString("name").equals("id")
|
||||
&& rs.getString("type").equals("INTEGER")
|
||||
&& (rs.getInt("notnull") == 1)
|
||||
&& (rs.getString("dflt_value") == null)
|
||||
&& (rs.getInt("pk") == 1))
|
||||
schemaResultCheck[0] = true;
|
||||
//System.out.println("Got 0");
|
||||
break;
|
||||
case 1:
|
||||
if (rs.getString("name").equals("unixtime")
|
||||
&& rs.getString("type").equals("INTEGER")
|
||||
&& (rs.getInt("notnull") == 0)
|
||||
&& (rs.getString("dflt_value") == null)
|
||||
&& (rs.getInt("pk") == 0))
|
||||
schemaResultCheck[1] = true;
|
||||
//System.out.println("Got 1");
|
||||
break;
|
||||
case 2:
|
||||
if (rs.getString("name").equals("event")
|
||||
&& rs.getString("type").equals("TEXT")
|
||||
&& (rs.getInt("notnull") == 0)
|
||||
&& (rs.getString("dflt_value") == null)
|
||||
&& (rs.getInt("pk") == 0))
|
||||
schemaResultCheck[2] = true;
|
||||
//System.out.println("Got 2");
|
||||
break;
|
||||
case 3:
|
||||
if (rs.getString("name").equals("subject")
|
||||
&& rs.getString("type").equals("TEXT")
|
||||
&& (rs.getInt("notnull") == 0)
|
||||
&& (rs.getString("dflt_value") == null)
|
||||
&& (rs.getInt("pk") == 0))
|
||||
schemaResultCheck[3] = true;
|
||||
//System.out.println("Got 3");
|
||||
break;
|
||||
case 4:
|
||||
if (rs.getString("name").equals("message")
|
||||
&& rs.getString("type").equals("TEXT")
|
||||
&& (rs.getInt("notnull") == 0)
|
||||
&& (rs.getString("dflt_value") == null)
|
||||
&& (rs.getInt("pk") == 0))
|
||||
schemaResultCheck[4] = true;
|
||||
//System.out.println("Got 4");
|
||||
break;
|
||||
case 5:
|
||||
if (rs.getString("name").equals("object")
|
||||
&& rs.getString("type").equals("TEXT")
|
||||
&& (rs.getInt("notnull") == 0)
|
||||
&& (rs.getString("dflt_value") == null)
|
||||
&& (rs.getInt("pk") == 0))
|
||||
schemaResultCheck[5] = true;
|
||||
//System.out.println("Got 5");
|
||||
break;
|
||||
default:
|
||||
for (int i = 0; i <= 5; i++) {
|
||||
schemaResultCheck[i] = false; // If more then 5 elements, ruin results
|
||||
}
|
||||
}
|
||||
}
|
||||
// Validating result: it table in DB have expected schema. If not, removing and recreating table.
|
||||
for (boolean element: schemaResultCheck) {
|
||||
if (!element) {
|
||||
System.out.println("BotSQLiteWorker: Found already existing table for channel with incoorect syntax: removing table and re-creating.");
|
||||
statement.executeUpdate("DROP TABLE \"" + safeChanName + "\";");
|
||||
statement.executeUpdate(query);
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.consistent = true;
|
||||
|
||||
this.preparedStatement = connection.prepareStatement(
|
||||
"INSERT INTO \""+safeChanName
|
||||
+"\" (unixtime, event, subject, message, object) "
|
||||
+"VALUES (?, ?, ?, ?, ?);");
|
||||
}
|
||||
else {
|
||||
this.consistent = false;
|
||||
}
|
||||
}
|
||||
catch (SQLException e){
|
||||
System.out.println("Internal issue: BotSQLiteWorker->constructor() failed\n\t"+e);
|
||||
this.consistent = false;
|
||||
}
|
||||
|
||||
}
|
||||
private long getDate(){
|
||||
return System.currentTimeMillis() / 1000L; // UNIX time
|
||||
}
|
||||
@Override
|
||||
public boolean isConsistent() {return consistent; }
|
||||
|
||||
@Override
|
||||
public void logAdd(String event, String initiatorArg, String messageArg) {
|
||||
try {
|
||||
preparedStatement.setLong(1, getDate());
|
||||
preparedStatement.setString(2, event);
|
||||
preparedStatement.setString(3, initiatorArg);
|
||||
switch (event) {
|
||||
case "NICK":
|
||||
case "JOIN":
|
||||
preparedStatement.setString(4, messageArg);
|
||||
preparedStatement.setString(5, null);
|
||||
break;
|
||||
case "PART":
|
||||
case "QUIT":
|
||||
case "TOPIC":
|
||||
preparedStatement.setString(4, messageArg.replaceAll("^.+?:", ""));
|
||||
preparedStatement.setString(5, null);
|
||||
break;
|
||||
case "MODE":
|
||||
preparedStatement.setString(4, messageArg.replaceAll("(^(.+?\\s){1})|(\\s.+$)",""));
|
||||
preparedStatement.setString(5, messageArg.replaceAll("^(.+?\\s){2}", ""));
|
||||
break;
|
||||
case "KICK":
|
||||
preparedStatement.setString(4,messageArg.replaceAll("^.+?:", ""));
|
||||
preparedStatement.setString(5,messageArg.replaceAll("(^.+?\\s)|(\\s.+$)", ""));
|
||||
break;
|
||||
case "PRIVMSG":
|
||||
preparedStatement.setString(4,messageArg.replaceAll("^:", ""));
|
||||
preparedStatement.setString(5,null);
|
||||
break;
|
||||
default:
|
||||
preparedStatement.setString(4,messageArg);
|
||||
preparedStatement.setString(5,null);
|
||||
break;
|
||||
}
|
||||
preparedStatement.executeUpdate();
|
||||
}
|
||||
catch (SQLException e){
|
||||
System.out.println("Internal issue: BotSQLiteWorker->logAdd() failed\n\t"+e);
|
||||
this.consistent = false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
try {
|
||||
//System.out.println("SQLite drier closed");
|
||||
this.connection.close();
|
||||
}
|
||||
catch (SQLException e){
|
||||
System.out.println("Internal issue: BotSQLiteWorker->close() failed\n\t" + e);
|
||||
}
|
||||
}
|
||||
}
|
12
src/InnaIrcBot/LogDriver/BotZeroWorker.java
Normal file
12
src/InnaIrcBot/LogDriver/BotZeroWorker.java
Normal file
|
@ -0,0 +1,12 @@
|
|||
package InnaIrcBot.LogDriver;
|
||||
|
||||
public class BotZeroWorker implements Worker{
|
||||
@Override
|
||||
public boolean isConsistent() {return true;}
|
||||
|
||||
@Override
|
||||
public void logAdd(String event, String initiatorArg, String messageArg) {}
|
||||
|
||||
@Override
|
||||
public void close() {}
|
||||
}
|
13
src/InnaIrcBot/LogDriver/Worker.java
Normal file
13
src/InnaIrcBot/LogDriver/Worker.java
Normal file
|
@ -0,0 +1,13 @@
|
|||
package InnaIrcBot.LogDriver;
|
||||
|
||||
public interface Worker {
|
||||
boolean consistent = false;
|
||||
|
||||
boolean isConsistent();
|
||||
|
||||
void logAdd(String event,
|
||||
String initiatorArg,
|
||||
String messageArg);
|
||||
|
||||
void close();
|
||||
}
|
150
src/InnaIrcBot/ProvidersConsumers/ChanConsumer.java
Normal file
150
src/InnaIrcBot/ProvidersConsumers/ChanConsumer.java
Normal file
|
@ -0,0 +1,150 @@
|
|||
package InnaIrcBot.ProvidersConsumers;
|
||||
|
||||
import InnaIrcBot.Commanders.ChanelCommander;
|
||||
import InnaIrcBot.LogDriver.BotDriver;
|
||||
import InnaIrcBot.LogDriver.Worker;
|
||||
|
||||
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;
|
||||
|
||||
|
||||
public class ChanConsumer implements Runnable {
|
||||
private BufferedReader reader;
|
||||
private String serverName;
|
||||
private String channelName;
|
||||
private Worker writerWorker;
|
||||
private ArrayList<String> userList;
|
||||
private String nick;
|
||||
private boolean rejoin;
|
||||
private Map<String, PrintWriter> chanList;
|
||||
private String configFilePath;
|
||||
|
||||
private PrintWriter chanelCommanderPipe;
|
||||
|
||||
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;
|
||||
this.serverName = serverName;
|
||||
this.channelName = channelName;
|
||||
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;
|
||||
// Create chanel commander thread, get pipe
|
||||
this.chanelCommanderPipe = getChanelCommander();
|
||||
}
|
||||
|
||||
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
|
||||
try {
|
||||
while ((data = reader.readLine()) != null) {
|
||||
dataStrings = data.split(" ",3);
|
||||
|
||||
if (!trackUsers(dataStrings[0], dataStrings[1], dataStrings[2]))
|
||||
continue;
|
||||
|
||||
writerWorker.logAdd(dataStrings[0], dataStrings[1], dataStrings[2]);
|
||||
// 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);
|
||||
|
||||
if (endThread) {
|
||||
reader.close();
|
||||
chanList.get(channelName).close();
|
||||
chanList.remove(channelName);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (java.io.IOException e){
|
||||
System.out.println("Internal issue: thread ChanConsumer->run() caused I/O exception:\n\t"+e); // TODO: reconnect
|
||||
}
|
||||
writerWorker.close();
|
||||
//Chanel commander thread's pipe should be closed
|
||||
chanelCommanderPipe.close();
|
||||
System.out.println("["+LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"))+"] THREAD "+serverName+":"+this.channelName +" ended"); // TODO:REMOVE DEBUG
|
||||
}
|
||||
|
||||
private boolean trackUsers(String event, String initiatorArg, String subjectArg){
|
||||
switch (event) {
|
||||
case "PRIVMSG": // most common, we don't have to handle anything else
|
||||
return true;
|
||||
case "JOIN":
|
||||
addUsers(initiatorArg);
|
||||
return true;
|
||||
case "PART":
|
||||
delUsers(initiatorArg);
|
||||
return true;
|
||||
case "QUIT": // TODO fix: use regex
|
||||
if (userList.contains(initiatorArg.replaceAll("!.+$", ""))) {
|
||||
delUsers(initiatorArg);
|
||||
return true;
|
||||
} else
|
||||
return false; // user quit, but he/she is not in this channel
|
||||
case "KICK":
|
||||
if (rejoin && nick.equals(subjectArg.substring(subjectArg.indexOf(" ") + 1, subjectArg.indexOf(" :"))))
|
||||
StreamProvider.writeToStream(serverName, "JOIN " + channelName);
|
||||
delUsers(subjectArg.substring(subjectArg.indexOf(" ") + 1, subjectArg.indexOf(" :")));
|
||||
return true;
|
||||
case "NICK":
|
||||
if (userList.contains(initiatorArg.replaceAll("!.+$", ""))) {
|
||||
swapUsers(initiatorArg, subjectArg);
|
||||
return true;
|
||||
} else {
|
||||
return false; // user changed nick, but he/she is not in this channel
|
||||
}
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private void addUsers(String user){
|
||||
if (!userList.contains(user.replaceAll("!.+$", "")))
|
||||
userList.add(user.replaceAll("!.+$", ""));
|
||||
}
|
||||
private void delUsers(String user){
|
||||
if (user.replaceAll("!.+$", "").equals(nick)) {
|
||||
endThread = true;
|
||||
}
|
||||
userList.remove(user.replaceAll("!.+$", ""));
|
||||
}
|
||||
private void swapUsers(String userNickOld, String userNickNew){
|
||||
userList.remove(userNickOld.replaceAll("!.+$", ""));
|
||||
userList.add(userNickNew);
|
||||
if (userNickOld.replaceAll("!.+$", "").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("Internal issue: ChanConsumer->getChanelCommander() I/O exception while initialized child objects."); // caused by Socket
|
||||
endThread = true;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
233
src/InnaIrcBot/ProvidersConsumers/DataProvider.java
Normal file
233
src/InnaIrcBot/ProvidersConsumers/DataProvider.java
Normal file
|
@ -0,0 +1,233 @@
|
|||
package InnaIrcBot.ProvidersConsumers;
|
||||
|
||||
import InnaIrcBot.Config.StorageFile;
|
||||
import InnaIrcBot.LogDriver.BotDriver;
|
||||
import InnaIrcBot.ReconnectControl;
|
||||
|
||||
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;
|
||||
|
||||
|
||||
public class DataProvider implements Runnable {
|
||||
private StorageFile configFile;
|
||||
private String serverName;
|
||||
private String userNick;
|
||||
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 void run(){
|
||||
if (!ableToRun) {
|
||||
this.close();
|
||||
return;
|
||||
}
|
||||
|
||||
if(!this.initConnection(rawStreamReader)) {
|
||||
this.close();
|
||||
return;
|
||||
}
|
||||
|
||||
//Prepare logDriver for using in threads.
|
||||
if(!BotDriver.setFileDriver(serverName, configFile.getLogDriver(), configFile.getLogDriverParameters())) {
|
||||
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/
|
||||
|
||||
BufferedReader streamBufferedReader = new BufferedReader(
|
||||
new InputStreamReader(
|
||||
new PipedInputStream(streamOut), StandardCharsets.UTF_8)
|
||||
);
|
||||
|
||||
Runnable consumer = new SystemConsumer(streamBufferedReader, userNick, channelsMap, this.configFile);
|
||||
new Thread(consumer).start();
|
||||
channelsMap.put("", new PrintWriter(streamOut)); // Not sure that PrintWriter is thread-safe..
|
||||
} catch (IOException e){
|
||||
System.out.println("Internal issue: DataProvider->run() I/O exception while initialized child objects.\n\t"+e); // caused by Socket
|
||||
this.close();
|
||||
return;
|
||||
}
|
||||
////////////////////////////////////// Start loop //////////////////////////////////////////////////////////////
|
||||
|
||||
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);
|
||||
if (rawMessage.startsWith(":")) {
|
||||
rawStrings = rawMessage
|
||||
.substring(1)
|
||||
.split(" :?", 3); // Removing ':'
|
||||
|
||||
|
||||
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();
|
||||
}
|
||||
} 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 {
|
||||
System.out.println("Not a valid response=" + rawMessage);
|
||||
}
|
||||
}
|
||||
} catch (IOException e){
|
||||
System.out.println("Socket issue: I/O exception"); //Connection closed. TODO: MAYBE try reconnect
|
||||
}
|
||||
finally {
|
||||
for (PrintWriter p :channelsMap.values()) {
|
||||
p.close();
|
||||
}
|
||||
this.close();
|
||||
}
|
||||
}
|
||||
|
||||
private void pingSrvResponse(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
|
||||
|
||||
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\".");
|
||||
|
||||
try {
|
||||
// 431 ERR_NONICKNAMEGIVEN how can we get this?
|
||||
// 432 ERR_ERRONEUSNICKNAME covered
|
||||
// 433 ERR_NICKNAMEINUSE covered
|
||||
// 436 ERR_NICKCOLLISION
|
||||
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;
|
||||
}
|
||||
}
|
||||
} catch (IOException e){
|
||||
System.out.println("Internal issue: DataProvider->initConnection() caused I/O exception.");
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
34
src/InnaIrcBot/ProvidersConsumers/StreamProvider.java
Normal file
34
src/InnaIrcBot/ProvidersConsumers/StreamProvider.java
Normal file
|
@ -0,0 +1,34 @@
|
|||
package InnaIrcBot.ProvidersConsumers;
|
||||
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.net.Socket;
|
||||
import java.util.HashMap;
|
||||
|
||||
public class StreamProvider {
|
||||
|
||||
private static HashMap<String, OutputStreamWriter> srvStreamMap = new HashMap<>();
|
||||
//private static OutputStreamWriter streamWriter;
|
||||
|
||||
public static synchronized void writeToStream(String server, String message){
|
||||
try {
|
||||
srvStreamMap.get(server).write(message+"\n");
|
||||
srvStreamMap.get(server).flush();
|
||||
} catch (java.io.IOException e){
|
||||
System.out.println("Internal issue: StreamProvider->writeToStream() caused I/O exception.");
|
||||
}
|
||||
}
|
||||
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.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public static synchronized void delStream(String server){
|
||||
srvStreamMap.remove(server);
|
||||
}
|
||||
}
|
206
src/InnaIrcBot/ProvidersConsumers/SystemConsumer.java
Normal file
206
src/InnaIrcBot/ProvidersConsumers/SystemConsumer.java
Normal file
|
@ -0,0 +1,206 @@
|
|||
package InnaIrcBot.ProvidersConsumers;
|
||||
|
||||
import InnaIrcBot.Commanders.PrivateMsgCommander;
|
||||
import InnaIrcBot.Config.StorageFile;
|
||||
import InnaIrcBot.GlobalData;
|
||||
import InnaIrcBot.LogDriver.BotDriver;
|
||||
import InnaIrcBot.LogDriver.Worker;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
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.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class SystemConsumer implements Runnable{
|
||||
private BufferedReader reader;
|
||||
private Worker 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 PrivateMsgCommander commander;
|
||||
|
||||
SystemConsumer(BufferedReader streamReader, String userNick, Map<String, PrintWriter> map, StorageFile storage) {
|
||||
this.writerWorker = BotDriver.getWorker(storage.getServerName(), "system");
|
||||
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());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
System.out.println("["+LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"))+"] THREAD "+serverName+":[system] started"); // TODO:REMOVE DEBUG
|
||||
|
||||
setMainRoutine();
|
||||
|
||||
for (PrintWriter p :channelsMap.values()) //TODO: check, code duplication. see Data provider constructor
|
||||
p.close();
|
||||
|
||||
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;
|
||||
|
||||
if (dataStrings[0].equals("PRIVMSG") && dataStrings[2].indexOf("\u0001") < dataStrings[2].lastIndexOf("\u0001"))
|
||||
replyCTCP(dataStrings[1], dataStrings[2].substring(dataStrings[2].indexOf(":")+1));
|
||||
else if (Pattern.matches("(^[0-9]{3}$)|(^NICK$)|(^JOIN$)", dataStrings[0])){
|
||||
handleNumeric(dataStrings[0], dataStrings[1], dataStrings[2]);
|
||||
}
|
||||
else if (dataStrings[0].equals("PRIVMSG")) {
|
||||
commander.receiver(dataStrings[1], dataStrings[2].replaceAll("^.+?:", "").trim());
|
||||
writerWorker.logAdd("[system]", "PRIVMSG sent to", "commander");
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
private boolean getProxy(String eventNum, String sender, String message){ //TODO: if can't join: like channel with password
|
||||
if (eventNum.equals("353"))
|
||||
return false; // never mind and let it flows as usual.
|
||||
else {
|
||||
String chan = message.replaceAll("(\\s.?$)|(\\s.+?$)", "");
|
||||
|
||||
if (eventNum.equals("QUIT") || eventNum.equals("NICK")) {
|
||||
for (ArrayList<String> key : proxyAList.values())
|
||||
key.add(eventNum + " " + sender + " " + message);
|
||||
return false;
|
||||
} else if (chan.equals(nick))
|
||||
return false;
|
||||
else if (proxyAList.keySet().contains(chan)) {
|
||||
proxyAList.get(chan).add(eventNum + " " + sender + " " + message);
|
||||
return true;
|
||||
} else
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void replyCTCP(String sender, String message){
|
||||
if (message.equals("\u0001VERSION\u0001")){
|
||||
StreamProvider.writeToStream(serverName,"NOTICE "+simplifyNick(sender)+" :\u0001VERSION "+ GlobalData.getAppVersion()+"\u0001");
|
||||
writerWorker.logAdd("[system]", "catch/handled CTCP VERSION from", simplifyNick(sender));
|
||||
System.out.println(sender+" "+message);
|
||||
System.out.println("NOTICE "+simplifyNick(sender)+" \u0001VERSION "+ GlobalData.getAppVersion()+"\u0001");
|
||||
}
|
||||
else if (message.startsWith("\u0001PING ") && message.endsWith("\u0001")){
|
||||
StreamProvider.writeToStream(serverName,"NOTICE "+simplifyNick(sender)+" :"+message);
|
||||
writerWorker.logAdd("[system]", "catch/handled CTCP PING from", simplifyNick(sender));
|
||||
//System.out.println(":"+simplifyNick(sender)+" NOTICE "+sender.substring(0,sender.indexOf("!"))+" "+message);
|
||||
}
|
||||
else if (message.equals("\u0001CLIENTINFO\u0001")){
|
||||
StreamProvider.writeToStream(serverName,"NOTICE "+simplifyNick(sender)+" :\u0001CLIENTINFO ACTION PING VERSION TIME CLIENTINFO\u0001");
|
||||
writerWorker.logAdd("[system]", "catch/handled CTCP CLIENTINFO from", simplifyNick(sender));
|
||||
//System.out.println(":"+simplifyNick(sender)+" NOTICE "+sender.substring(0,sender.indexOf("!"))+" \u0001CLIENTINFO ACTION PING VERSION TIME CLIENTINFO\u0001");
|
||||
}
|
||||
else if (message.equals("\u0001TIME\u0001")){
|
||||
StreamProvider.writeToStream(serverName,"NOTICE "+simplifyNick(sender)+" :\u0001TIME "+ ZonedDateTime.now().format(DateTimeFormatter.RFC_1123_DATE_TIME)+"\u0001");
|
||||
writerWorker.logAdd("[system]", "catch/handled CTCP TIME from", simplifyNick(sender));
|
||||
//System.out.println(":"+simplifyNick(sender)+" NOTICE "+sender.substring(0,sender.indexOf("!"))+" \u0001TIME "+ ZonedDateTime.now().format(DateTimeFormatter.RFC_1123_DATE_TIME)+"\u0001");
|
||||
} else
|
||||
writerWorker.logAdd("[system]", "catch CTCP request \""+message+"\" from ", simplifyNick(sender));
|
||||
}
|
||||
private String simplifyNick(String nick){ return nick.replaceAll("!.*$",""); }
|
||||
|
||||
//todo: nandle nickserv messages
|
||||
|
||||
private void handleNumeric(String eventNum, String sender, String message){
|
||||
switch (eventNum){
|
||||
case "433": // TODO: try to use alternative nickname
|
||||
writerWorker.logAdd("[system]", "catch/handled:", eventNum+" [nickname already in use]");
|
||||
break;
|
||||
case "353":
|
||||
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(" ");
|
||||
|
||||
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.");
|
||||
break;
|
||||
case "NICK":
|
||||
if (sender.startsWith(nick+"!")) {
|
||||
String oldNick = nick;
|
||||
nick = message.trim();
|
||||
|
||||
writerWorker.logAdd("[system]", "catch/handled own NICK change from:", sender+" to: "+message);
|
||||
}
|
||||
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
|
||||
}
|
||||
break;
|
||||
default:
|
||||
writerWorker.logAdd("[system]", "catch: "+eventNum+" from: "+sender+" :",message);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
43
src/InnaIrcBot/README.md
Normal file
43
src/InnaIrcBot/README.md
Normal file
|
@ -0,0 +1,43 @@
|
|||
# InnaIrcBot
|
||||
|
||||
Another one IRC bot in deep beta.
|
||||
|
||||
##Usage
|
||||
` -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'
|
||||
* freenode - send '/msg nickserv IDENTIFY mySecretPass'
|
||||
|
||||
"logDriver" could be "Files", "SQLite" or "Zero"
|
||||
* Files - log everything to files using /yourPathSet/serverName/#chanelName_YYYY-MM-DD.txt format.
|
||||
* SQLite - use /yourPathSet/server.db (or /yourPathSet/yourFileName.db) sqlite file.
|
||||
|
||||
##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
|
||||
|
||||
|
||||
## TODO:
|
||||
- [ ] Documentation
|
||||
- [ ] Code refactoring
|
||||
- [ ] QA: good regression testing
|
||||
- [ ] CI/CD
|
||||
- [ ] Suppress messages from server or handle them separately from selected worker
|
||||
- [ ] Logs backend workers as threads (SQLite and co. are too slow)
|
||||
- [ ] Logs backend worker for mongodb
|
||||
- [ ] Logs backend worker for redis/redis node
|
||||
- [ ] Re-implement connection routine
|
||||
- [ ] Availability to run scripts @ 'ChanelCommander'
|
||||
- [ ] Docker(+compose) package
|
||||
- [ ] Flood tracker
|
||||
- [ ] Deep configuration files validation
|
||||
- [ ] Maven or Gradle build
|
||||
- [ ] ncurses-like or/and GUI configuration files (server/chanel setting) editor
|
23
src/InnaIrcBot/ReconnectControl.java
Normal file
23
src/InnaIrcBot/ReconnectControl.java
Normal file
|
@ -0,0 +1,23 @@
|
|||
package InnaIrcBot;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
public class ReconnectControl {
|
||||
private static 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);
|
||||
}
|
||||
|
||||
}
|
BIN
src/InnaIrcBot/lib/gson-2.8.5.jar
Normal file
BIN
src/InnaIrcBot/lib/gson-2.8.5.jar
Normal file
Binary file not shown.
BIN
src/InnaIrcBot/lib/sqlite-jdbc-3.23.1.jar
Normal file
BIN
src/InnaIrcBot/lib/sqlite-jdbc-3.23.1.jar
Normal file
Binary file not shown.
3
src/META-INF/MANIFEST.MF
Normal file
3
src/META-INF/MANIFEST.MF
Normal file
|
@ -0,0 +1,3 @@
|
|||
Manifest-Version: 1.0
|
||||
Main-Class: InnaIrcBot.BotStart
|
||||
|
37
src/test/DriverTest.java
Normal file
37
src/test/DriverTest.java
Normal file
|
@ -0,0 +1,37 @@
|
|||
import InnaIrcBot.LogDriver.BotDriver;
|
||||
import InnaIrcBot.LogDriver.Worker;
|
||||
|
||||
public class DriverTest {
|
||||
|
||||
public static void main(String[] args){
|
||||
if (BotDriver.setFileDriver("irc.tomsk.net", "SQLiteDriver", new String[]{"/tmp/"}))
|
||||
System.out.println("Successful driver initiation");
|
||||
else {
|
||||
System.out.println("Failed driver initiation");
|
||||
return;
|
||||
}
|
||||
|
||||
Worker fw1 = BotDriver.getWorker("irc.tomsk.net","#lpr");
|
||||
Worker fw2 = BotDriver.getWorker("irc.tomsk.net","#main");
|
||||
Worker fw3 = BotDriver.getWorker("irc.tomsk.net","##loper");
|
||||
|
||||
if ((fw1 !=null) && (fw2 !=null) && (fw3 !=null)){
|
||||
System.out.println("LogFile1: "+fw1.isConsistent());
|
||||
System.out.println("LogFile2: "+fw2.isConsistent());
|
||||
System.out.println("LogFile3: "+fw3.isConsistent());
|
||||
|
||||
fw1.logAdd("JOIN", "de_su!loper@desktop.lan", "message1");
|
||||
fw1.logAdd("PART", "de_su!loper@desktop.lan", "#chan1");
|
||||
|
||||
fw2.logAdd("JOIN", "de_su!loper@desktop.lan", "message2");
|
||||
fw2.logAdd("PART", "de_su!loper@desktop.lan", "#chan2");
|
||||
|
||||
fw3.logAdd("JOIN", "de_su!loper@desktop.lan", "message3");
|
||||
fw3.logAdd("PART", "de_su!loper@desktop.lan", "#chan3");
|
||||
|
||||
fw1.close();
|
||||
fw2.close();
|
||||
fw3.close();
|
||||
}
|
||||
}
|
||||
}
|
14
src/test/ReconnectControlTest.java
Normal file
14
src/test/ReconnectControlTest.java
Normal file
|
@ -0,0 +1,14 @@
|
|||
import InnaIrcBot.ReconnectControl;
|
||||
import com.sun.org.apache.regexp.internal.RE;
|
||||
|
||||
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"));
|
||||
}
|
||||
}
|
27
src/test/StorageFileTest.java
Normal file
27
src/test/StorageFileTest.java
Normal file
|
@ -0,0 +1,27 @@
|
|||
import InnaIrcBot.Config.StorageFile;
|
||||
|
||||
public class StorageFileTest {
|
||||
static public void main(String[] args){
|
||||
StorageFile config = new StorageFile(
|
||||
"",
|
||||
0,
|
||||
null,
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
true,
|
||||
"",
|
||||
new String[]{null},
|
||||
"",
|
||||
""
|
||||
);
|
||||
|
||||
System.out.println(config.getLogDriver().isEmpty());
|
||||
System.out.println(config.getLogDriverParameters().length);
|
||||
|
||||
|
||||
}
|
||||
}
|
10
src/test/StorageReaderTest.java
Normal file
10
src/test/StorageReaderTest.java
Normal file
|
@ -0,0 +1,10 @@
|
|||
import InnaIrcBot.Config.StorageFile;
|
||||
import InnaIrcBot.Config.StorageReader;
|
||||
|
||||
public class StorageReaderTest {
|
||||
public static void main(String[] args){
|
||||
// StorageReader.readConfig("/home/loper/bot.config");
|
||||
// StorageFile storageFile = StorageReader.getConfig();
|
||||
// System.out.println(storageFile.getLogDriver() == null);
|
||||
}
|
||||
}
|
26
src/test/brokenJoinFloodHandlerTest.java
Normal file
26
src/test/brokenJoinFloodHandlerTest.java
Normal file
|
@ -0,0 +1,26 @@
|
|||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue