v0.5 rolling. CTCP support added to ChannelCommander.

master
Dmitry Isaenko 2019-01-29 01:28:11 +03:00
parent 6f0917f155
commit 88b119acea
8 changed files with 251 additions and 30 deletions

View File

@ -0,0 +1,25 @@
# NOTE: Filename should be servername#channel OR servernamechannel (is started not from '#'). It MUST have .csv extension and hold tab-separated-values. This example works for server='srv' and channel='#lpr'
#Join Flood Control Number of events Time Frame in seconds
JoinFloodControl 5 10
#event regexp command message_or_command message_or_command message_or_ N+1
msg ^!help(.+)? \chanmsg Ничего не знаю!
join ^Мерзавец(.+)?!.* \kickban requested
join ^Мальчиш_плохишь!.* \kick плохой!
join ^Мальчиш_плохишь!.* \ban \privmsg не возвращайся!
nick ^Мимикрирую_под.* \chanmsg перестань!
#event regexp to trigger CTCP (send to chan/privmsg) message printed if not found
msg ^cci.* \cclientinfo (^.+(\s|\t)+) нет таких:
msg ^cf.* \cfinger (^.+(\s|\t)+) не понятно кто это:
msg ^cp.* \cping (^.+(\s|\t)+) ололо
msg ^cs.* \csource (^.+(\s|\t)+) пыщ -пыщ :
msg ^ct.* \ctime (^.+(\s|\t)+) ыыы:
msg ^cui.* \cuserinfo (^.+(\s|\t)+) ыыыыы ы:
msg ^cv.* \cversion (^.+(\s|\t)+)
msg ^pci.* \pclientinfo (^.+(\s|\t)+) ололо:
msg ^pf.* \pfinger (^.+(\s|\t)+) нет таких:
msg ^pp.* \pping (^.+(\s|\t)+) нету:
msg ^ps.* \psource (^.+(\s|\t)+) хаха:
msg ^pt.* \ptime (^.+(\s|\t)+) олол:
msg ^pu.* \puserinfo (^.+(\s|\t)+) пиу-пиу:
msg ^pv.* \pversion (^.+(\s|\t)+) \chanmsg отправлено!
1 # NOTE: Filename should be servername#channel OR servernamechannel (is started not from '#'). It MUST have .csv extension and hold tab-separated-values. This example works for server='srv' and channel='#lpr'
2 #Join Flood Control Number of events Time Frame in seconds
3 JoinFloodControl 5 10
4 #event regexp command message_or_command message_or_command message_or_ N+1
5 msg ^!help(.+)? \chanmsg Ничего не знаю!
6 join ^Мерзавец(.+)?!.* \kickban requested
7 join ^Мальчиш_плохишь!.* \kick плохой!
8 join ^Мальчиш_плохишь!.* \ban \privmsg не возвращайся!
9 nick ^Мимикрирую_под.* \chanmsg перестань!
10 #event regexp to trigger CTCP (send to chan/privmsg) message printed if not found
11 msg ^cci.* \cclientinfo (^.+(\s|\t)+) нет таких:
12 msg ^cf.* \cfinger (^.+(\s|\t)+) не понятно кто это:
13 msg ^cp.* \cping (^.+(\s|\t)+) ололо
14 msg ^cs.* \csource (^.+(\s|\t)+) пыщ -пыщ :
15 msg ^ct.* \ctime (^.+(\s|\t)+) ыыы:
16 msg ^cui.* \cuserinfo (^.+(\s|\t)+) ыыыыы ы:
17 msg ^cv.* \cversion (^.+(\s|\t)+)
18 msg ^pci.* \pclientinfo (^.+(\s|\t)+) ололо:
19 msg ^pf.* \pfinger (^.+(\s|\t)+) нет таких:
20 msg ^pp.* \pping (^.+(\s|\t)+) нету:
21 msg ^ps.* \psource (^.+(\s|\t)+) хаха:
22 msg ^pt.* \ptime (^.+(\s|\t)+) олол:
23 msg ^pu.* \puserinfo (^.+(\s|\t)+) пиу-пиу:
24 msg ^pv.* \pversion (^.+(\s|\t)+) \chanmsg отправлено!

View File

@ -4,7 +4,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>loper</groupId>
<artifactId>InnaIrcBot</artifactId>
<version>0.4-SNAPSHOT</version>
<version>0.5-SNAPSHOT</version>
<packaging>jar</packaging>
<name>InnaIrcBot</name>

View File

@ -0,0 +1,132 @@
package InnaIrcBot.Commanders;
import InnaIrcBot.ProvidersConsumers.StreamProvider;
import java.time.LocalDateTime;
import java.util.*;
//TODO: Consider using as thread
public class CTCPHelper {
private static final CTCPHelper instance = new CTCPHelper();
public static CTCPHelper getInstance(){ return instance; }
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<>());
}
switch (ctcpType){
case "VERSION":
case "CLIENTINFO":
case "FINGER":
case "SOURCE":
case "TIME":
case "USERINFO":
waitersQueue.get(requesterServer).add(new CtcpRequest(requesterChanelOrUser, targetObject, notFoundMessage, ctcpType));
StreamProvider.writeToStream(requesterServer, "PRIVMSG "+targetObject+" :\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");
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.*$", ""))) {
StreamProvider.writeToStream(serverReplied, "PRIVMSG " + chanelOrUser + " :" + whoReplied + ": " + whatReplied);
iterator.remove();
}
}
else{
//System.out.println("Drop outdated user");
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();
}
}
}
}
}
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 getType(){ return 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 getNotFoundMessage(String userResponds){
if (this.userResponding.equals(userResponds))
if (notFoundMessage.isEmpty())
return null;
else
return notFoundMessage;
else
return null;
}
}

View File

@ -94,48 +94,84 @@ public class ChanelCommander implements Runnable {
for (String pattern : map.keySet())
if (Pattern.matches(pattern, arg1)){ // NOTE: validation based on new nick //TODO: parse here
String[] cmdOrMsg = map.get(pattern);
StringBuilder whatToSendStringBuilder;
ArrayList<String> whatToSendList;
for (int i = 0; i<cmdOrMsg.length;) {
//switch (map.get(pattern)[0]){
ArrayList<String> whatToSend;
switch (cmdOrMsg[i]) {
case "\\chanmsg":
whatToSend = new ArrayList<>();
whatToSendList = new ArrayList<>();
for (i++; (i < cmdOrMsg.length) && !(cmdOrMsg[i].startsWith("\\")); i++)
whatToSend.add(cmdOrMsg[i]);
msgAction(whatToSend.toArray(new String[0]), arg2, false);
whatToSendList.add(cmdOrMsg[i]);
msgAction(whatToSendList.toArray(new String[0]), arg2, false);
break;
case "\\privmsg":
whatToSend = new ArrayList<>();
whatToSendList = new ArrayList<>();
for (i++; (i < cmdOrMsg.length) && !(cmdOrMsg[i].startsWith("\\")); i++)
whatToSend.add(cmdOrMsg[i]);
msgAction(whatToSend.toArray(new String[0]), arg2, true);
whatToSendList.add(cmdOrMsg[i]);
msgAction(whatToSendList.toArray(new String[0]), arg2, true);
break;
case "\\ban":
banAction(arg2);
i++;
break;
case "\\kick":
whatToSend = new ArrayList<>();
whatToSendList = new ArrayList<>();
for (i++; (i < cmdOrMsg.length) && !(cmdOrMsg[i].startsWith("\\")); i++)
whatToSend.add(cmdOrMsg[i]);
kickAction(whatToSend.toArray(new String[0]), arg2);
whatToSendList.add(cmdOrMsg[i]);
kickAction(whatToSendList.toArray(new String[0]), arg2);
break;
case "\\kickban":
whatToSend = new ArrayList<>();
whatToSendList = new ArrayList<>();
for (i++; (i < cmdOrMsg.length) && !(cmdOrMsg[i].startsWith("\\")); i++)
whatToSend.add(cmdOrMsg[i]);
whatToSendList.add(cmdOrMsg[i]);
banAction(arg2);
kickAction(whatToSend.toArray(new String[0]), arg2);
kickAction(whatToSendList.toArray(new String[0]), arg2);
break;
case "\\raw":
StringBuilder whatToSendRaw = new StringBuilder();
whatToSendStringBuilder = new StringBuilder();
for (i++; (i < cmdOrMsg.length) && !(cmdOrMsg[i].startsWith("\\")); i++)
whatToSendRaw.append(cmdOrMsg[i]);
StreamProvider.writeToStream(server, whatToSendRaw.toString()); //TODO
whatToSendStringBuilder.append(cmdOrMsg[i]);
StreamProvider.writeToStream(server, whatToSendStringBuilder.toString()); //TODO
break; //todo: add script
case "\\whois": // result will be noted in 'system' log
whoisAction(arg2);
i++;
break;
case "\\cclientinfo": // NOTE: All this handled by CTCPHelper instance
case "\\cfinger": // C - publish request result to chan
case "\\cping":
case "\\csource":
case "\\ctime":
case "\\cuserinfo":
case "\\cversion":
case "\\pclientinfo": // P - reply to privmsg
case "\\pfinger":
case "\\pping":
case "\\psource":
case "\\ptime":
case "\\puserinfo":
case "\\pversion":
String CTCPType = cmdOrMsg[i];
String objectRegexp = null;
whatToSendStringBuilder = new StringBuilder();
for (i++; (i < cmdOrMsg.length) && !(cmdOrMsg[i].startsWith("\\")); i++){
if (objectRegexp == null && !cmdOrMsg[i].trim().isEmpty())
objectRegexp = cmdOrMsg[i].trim();
else
whatToSendStringBuilder.append(cmdOrMsg[i]);
}
if (objectRegexp != null) {
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());
else
CTCPHelper.getInstance().registerRequest(server, simplifyNick(arg2), CTCPType.substring(2).toUpperCase(), objectToCtcp, whatToSendStringBuilder.toString());
}
}
break;
default:
i++;
@ -147,6 +183,7 @@ public class ChanelCommander implements Runnable {
private void whoisAction(String who){ // TODO: maybe we have to extend functionality to reuse received information.
StreamProvider.writeToStream(server, "WHOIS "+simplifyNick(who));
}
private void msgAction(String[] messages, String who, boolean sendToPrivate){
StringBuilder executiveStr = new StringBuilder();
executiveStr.append("PRIVMSG ");
@ -164,7 +201,7 @@ public class ChanelCommander implements Runnable {
for (int i = 0; i < messages.length; i++){
if ( ! messages[i].startsWith("\\"))
executiveStr.append(messages[i]);
else if (messages[i].equals("\\time"))
else if (messages[i].equals("\\time")) // TODO: remove this shit
executiveStr.append(LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")));
}
//System.out.println(executiveStr.toString()); //TODO: debug

View File

@ -25,7 +25,7 @@ public class JoinFloodHandler {
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"+
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)
}

View File

@ -1,7 +1,7 @@
package InnaIrcBot;
public class GlobalData {
private static final String version = "InnaIrcBot v0.4 \"Карские Ворота\"";
private static final String version = "InnaIrcBot v0.5 \"Шикотан\"";
public static synchronized String getAppVersion(){
return version;
}

View File

@ -17,12 +17,12 @@ public class StreamProvider {
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")) {
if (message.startsWith("PRIVMSG ")) {
srvSysConsumersMap.get(server).println("INNA "+message);
srvSysConsumersMap.get(server).flush();
}
} catch (java.io.IOException e){
System.out.println("Internal issue: StreamProvider->writeToStream() caused I/O exception.");
System.out.println("Internal issue: StreamProvider->writeToStream() caused I/O exception:\n\t"+e);
}
}
public static synchronized boolean setStream(String server, Socket socket){
@ -31,7 +31,7 @@ public class StreamProvider {
srvStreamMap.put(server, new OutputStreamWriter(outStream));
return true;
} catch (java.io.IOException e){
System.out.println("Internal issue: StreamProvider->setStream() caused I/O exception.");
System.out.println("Internal issue: StreamProvider->setStream() caused I/O exception:\n\t"+e);
return false;
}
}

View File

@ -1,5 +1,6 @@
package InnaIrcBot.ProvidersConsumers;
import InnaIrcBot.Commanders.CTCPHelper;
import InnaIrcBot.Commanders.PrivateMsgCommander;
import InnaIrcBot.Config.StorageFile;
import InnaIrcBot.GlobalData;
@ -29,7 +30,6 @@ public class SystemConsumer implements Runnable{
private PrivateMsgCommander commander;
SystemConsumer(BufferedReader streamReader, String userNick, Map<String, PrintWriter> map, StorageFile storage) {
//this.writerWorker = BotDriver.getWorker(storage.getServerName(), "system");
this.writerWorker = BotDriver.getSystemWorker(storage.getServerName());
this.nick = userNick;
this.serverName = storage.getServerName();
@ -77,6 +77,7 @@ public class SystemConsumer implements Runnable{
if (dataStrings[0].equals("PRIVMSG") && 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 if (Pattern.matches("(^[0-9]{3}$)|(^NICK$)|(^JOIN$)|(^QUIT$)", dataStrings[0])){
handleNumeric(dataStrings[0], dataStrings[1], dataStrings[2]);
@ -85,6 +86,10 @@ public class SystemConsumer implements Runnable{
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("NOTICE")) {
handleNotice(dataStrings[1], dataStrings[2].replaceAll("^.+?:", "").trim());
}
else if (dataStrings[0].equals("INNA")) {
String[] splitter;
if (dataStrings.length > 2){ // Don't touch 'cuz it's important
@ -104,9 +109,12 @@ public class SystemConsumer implements Runnable{
}
}
private boolean getProxy(String eventNum, String sender, String message){ //TODO: if can't join: like channel with password
if (eventNum.equals("353"))
if (eventNum.equals("353")) {
//writerWorker.logAdd("[proxy]", "catch: "+eventNum+" from: "+sender+" :",message);
return false; // never mind and let it flows as usual.
}
else {
//writerWorker.logAdd("[proxy]", "catch: "+eventNum+" from: "+sender+" :",message);
String chan = message.replaceAll("(\\s.?$)|(\\s.+?$)", "");
if (eventNum.equals("QUIT") || eventNum.equals("NICK")) {
@ -127,7 +135,6 @@ public class SystemConsumer implements Runnable{
if (message.equals("\u0001VERSION\u0001")){
StreamProvider.writeToStream(serverName,"NOTICE "+sender+" :\u0001VERSION "+ GlobalData.getAppVersion()+"\u0001");
writerWorker.logAdd("[system]", "catch/handled CTCP VERSION from", sender);
//System.out.println(sender+" "+message);
//System.out.println("NOTICE "+sender+" \u0001VERSION "+ GlobalData.getAppVersion()+"\u0001");
}
else if (message.startsWith("\u0001PING ") && message.endsWith("\u0001")){
@ -136,7 +143,7 @@ public class SystemConsumer implements Runnable{
//System.out.println(":"+sender+" NOTICE "+sender.substring(0,sender.indexOf("!"))+" "+message);
}
else if (message.equals("\u0001CLIENTINFO\u0001")){
StreamProvider.writeToStream(serverName,"NOTICE "+sender+" :\u0001CLIENTINFO ACTION PING VERSION TIME CLIENTINFO\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");
}
@ -145,12 +152,21 @@ public class SystemConsumer implements Runnable{
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")){
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);
}
private String simplifyNick(String nick){ return nick.replaceAll("!.*$",""); }
private void handleNotice(String sender, String message){
writerWorker.logAdd("[system]", "NOTICE from "+sender+" received: ", message);
CTCPHelper.getInstance().handleCtcpReply(serverName, simplifyNick(sender), message);
}
private void handleSpecial(String event, String chanel, String message){
//System.out.println("|"+event+"|"+chanel+"|"+message+"|");
@ -167,6 +183,7 @@ public class SystemConsumer implements Runnable{
writerWorker.logAdd("[system]", "catch/handled:", eventNum+" [nickname already in use]");
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)) {
@ -206,7 +223,7 @@ public class SystemConsumer implements Runnable{
case "NICK":
if (sender.startsWith(nick+"!")) {
nick = message.trim();
writerWorker.logAdd("[system]", "catch own NICK change from:", sender+" to: "+message);
writerWorker.logAdd("[system]", "catch own NICK change:", sender+" to: "+message);
}
break;
case "JOIN":
@ -217,6 +234,16 @@ public class SystemConsumer implements Runnable{
writerWorker.logAdd("[system]", "joined to channel ", message);
}
break;
case "401": // No such nick/channel
//System.out.println("|"+message.replaceAll("^(\\s)?.+?(\\s)|((\\s)?:No such nick/channel)","")+"|");
CTCPHelper.getInstance().handleErrorReply(serverName, message.replaceAll("^(\\s)?.+?(\\s)|((\\s)?:No such nick/channel)",""));
writerWorker.logAdd("[system]", "catch: "+eventNum+" from: "+sender+" :",message+" [ok]");
break;
/*
case "NOTICE":
writerWorker.logAdd("[system]", eventNum+" from: "+sender+" received: ",message);
break;
*/
default:
writerWorker.logAdd("[system]", "catch: "+eventNum+" from: "+sender+" :",message); // TODO: QUIT comes here. Do something.
break;