import java.io.File;
import java.net.URLEncoder;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.regex.*;
import org.pircbotx.Channel;
import org.pircbotx.ShockyBot;
import org.pircbotx.hooks.events.*;
import pl.shockah.*;
import pl.shockah.shocky.Data;
import pl.shockah.shocky.Module;
import pl.shockah.shocky.Utils;
import pl.shockah.shocky.WebServer;
import pl.shockah.shocky.cmds.Command;
import pl.shockah.shocky.cmds.CommandCallback;
import pl.shockah.shocky.cmds.Parameters;
import pl.shockah.shocky.events.*;
import pl.shockah.shocky.interfaces.IRollback;
import pl.shockah.shocky.interfaces.ILinePredicate;
import pl.shockah.shocky.lines.*;
import pl.shockah.shocky.sql.*;
import pl.shockah.shocky.sql.Criterion.Operation;
public class ModuleRollback extends Module implements IRollback {
private static final Pattern durationPattern = Pattern.compile("([0-9]+)([smhd])", Pattern.CASE_INSENSITIVE);
private static final SimpleDateFormat sdf = new SimpleDateFormat();
protected Command cmd;
public static final int
TYPE_MESSAGE = 1,
TYPE_ACTION = 2,
TYPE_ENTERLEAVE = 3,
TYPE_KICK = 4,
TYPE_MODE = 5,
TYPE_MESSAGEACTION = 6,
TYPE_OTHER = 0;
private static final Criterion msgAndActCriterion = new Criterion(
new CriterionNumber("type",Operation.Equals,TYPE_MESSAGE)
+ " OR " +
new CriterionNumber("type",Operation.Equals,TYPE_ACTION));
public static void appendLines(StringBuilder sb, ArrayList<Line> lines, boolean encode) {
try {
for (int i = 0; i < lines.size(); i++) {
if (i != 0) sb.append('\n');
String line = toString(lines.get(i));
if (encode)
line = URLEncoder.encode(line,"UTF8");
sb.append(line);
}
} catch (Exception e) {e.printStackTrace();}
}
public static String toString(Line line) {
return "["+sdf.format(line.time)+"] "+(Line.getWithChannels() ? "["+line.channel+"] " : " ")+line.getMessage();
}
public String name() {return "rollback";}
public boolean isListener() {return true;}
public void onEnable(File dir) {
Data.config.setNotExists("rollback-dateformat","dd.MM.yyyy HH:mm:ss");
sdf.applyPattern(Data.config.getString("rollback-dateformat"));
sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
Command.addCommands(this, cmd = new CmdPastebin());
Command.addCommand(this, "pb", cmd);
SQL.raw("CREATE TABLE IF NOT EXISTS rollback (channel varchar(50) NOT NULL,users text,type int(1) unsigned NOT NULL,stamp bigint(20) unsigned NOT NULL,text text NOT NULL) ENGINE=InnoDB DEFAULT CHARSET=utf8;");
}
public void onDisable() {
Command.removeCommands(cmd);
}
public void onMessage(MessageEvent<ShockyBot> event) {
addRollbackLine(event.getChannel().getName(),new LineMessage(event.getChannel().getName(),event.getUser().getNick(),event.getMessage()));
}
public void onMessageOut(MessageOutEvent<ShockyBot> event) {
addRollbackLine(event.getChannel().getName(),new LineMessage(event.getChannel().getName(),event.getBot().getNick(),event.getMessage()));
}
public void onAction(ActionEvent<ShockyBot> event) {
addRollbackLine(event.getChannel().getName(),new LineAction(event.getChannel().getName(),event.getUser().getNick(),event.getMessage()));
}
public void onActionOut(ActionOutEvent<ShockyBot> event) {
addRollbackLine(event.getChannel().getName(),new LineAction(event.getChannel().getName(),event.getBot().getNick(),event.getMessage()));
}
public void onTopic(TopicEvent<ShockyBot> event) {
if (!event.isChanged()) return;
addRollbackLine(event.getChannel().getName(),new LineOther(event.getChannel().getName(),"* "+event.getUser().getNick()+" has changed the topic to: "+event.getTopic()));
}
public void onJoin(JoinEvent<ShockyBot> event) {
addRollbackLine(event.getChannel().getName(),new LineEnterLeave(event.getChannel().getName(),event.getUser().getNick(),"("+event.getUser().getHostmask()+") has joined"));
}
public void onPart(PartEvent<ShockyBot> event) {
addRollbackLine(event.getChannel().getName(),new LineEnterLeave(event.getChannel().getName(),event.getUser().getNick(),"("+event.getUser().getHostmask()+") has left"));
}
public void onQuit(QuitEvent<ShockyBot> event) {
for (Channel channel : event.getUser().getChannels()) addRollbackLine(channel.getName(),new LineEnterLeave(channel.getName(),event.getUser().getNick(),"has quit ("+event.getReason()+")"));
}
public void onKick(KickEvent<ShockyBot> event) {
addRollbackLine(event.getChannel().getName(),new LineKick(event));
}
public void onNickChange(NickChangeEvent<ShockyBot> event) {
for (Channel channel : event.getBot().getChannels(event.getUser())) addRollbackLine(channel.getName(),new LineOther(channel.getName(),"* "+event.getOldNick()+" is now known as "+event.getNewNick()));
}
public void onMode(ModeEvent<ShockyBot> event) {
addRollbackLine(event.getChannel().getName(),new LineMode(event));
}
public void onUserMode(UserModeEvent<ShockyBot> event) {
String mode = event.getMode();
if (mode.charAt(0) == ' ') mode = "+"+mode.substring(1);
for (Channel channel : event.getBot().getChannels(event.getTarget())) addRollbackLine(channel.getName(),new LineOther(channel.getName(),"* "+event.getSource().getNick()+" sets mode "+mode+" "+event.getTarget().getNick()));
}
public synchronized void addRollbackLine(String channel, Line line) {
if (channel == null || !channel.startsWith("#")) return;
channel = channel.toLowerCase();
PreparedStatement p = null;
String key = line.getClass().getName();
try {
if (SQL.statements.containsKey(key) && !SQL.statements.get(key).isClosed()) {
p = SQL.statements.get(key);
} else {
QueryInsert q = new QueryInsert(SQL.getTable("rollback"));
q.add("channel",Wildcard.blank);
q.add("stamp",Wildcard.blank);
line.fillQuery(q, true);
p = SQL.getSQLConnection().prepareStatement(q.getSQLQuery());
SQL.statements.put(key,p);
}
synchronized (p) {
p.setString(1, channel);
p.setLong(2, System.currentTimeMillis());
line.fillQuery(p, 3);
p.execute();
p.clearParameters();
}
} catch (SQLException e) {
if (p != null)
try {p.close();} catch (SQLException e1) {}
e.printStackTrace();
}
}
public ArrayList<Line> getRollbackLines(String channel, String user, String regex, String cull, boolean newest, int lines, int seconds) {
return getRollbackLines(Line.class, channel, user, regex, cull, newest, lines, seconds);
}
public Line getLine(ResultSet result) throws SQLException {
/*switch (result.getInt("type")) {
case TYPE_MESSAGE: return new LineMessage(result.getLong("stamp"),result.getString("channel"),result.getString("users"),result.getString("text"));
case TYPE_ACTION: return new LineAction(result.getLong("stamp"),result.getString("channel"),result.getString("users"),result.getString("text"));
case TYPE_ENTERLEAVE: return new LineEnterLeave(result.getLong("stamp"),result.getString("channel"),result.getString("users"),result.getString("text"));
case TYPE_KICK: return new LineKick(result.getLong("stamp"),result.getString("channel"),result.getString("users"),result.getString("text"));
case TYPE_MODE: return new LineMode(result.getLong("stamp"),result.getString("channel"),result.getString("users"),result.getString("text"));
default: return new LineOther(result.getLong("stamp"),result.getString("channel"),result.getString("text"));
}*/
switch (result.getInt("type")) {
case TYPE_MESSAGE: return new LineMessage(result);
case TYPE_ACTION: return new LineAction(result);
case TYPE_ENTERLEAVE: return new LineEnterLeave(result);
case TYPE_KICK: return new LineKick(result);
case TYPE_MODE: return new LineMode(result);
default: return new LineOther(result);
}
}
private long getOldestTime(String channel) throws SQLException {
QuerySelect q = new QuerySelect(SQL.getTable("rollback"));
if (channel != null)
q.addCriterions(new CriterionString("channel",channel.toLowerCase()));
q.setLimitCount(1);
ResultSet j = SQL.select(q);
try {
if (j == null || !j.next())
throw new SQLException("Cannot find oldest timestamp for "+channel);
return j.getLong("stamp");
} finally {
if (j != null)
j.close();
}
}
private <T extends Line> ResultSet getResults(Class<T> type, String channel, String user, String regex, String cull, boolean newest, int lines, int seconds) throws SQLException {
int intType = TYPE_OTHER;
if (type == LineMessage.class) intType = TYPE_MESSAGE;
else if (type == LineAction.class) intType = TYPE_ACTION;
else if (type == LineEnterLeave.class) intType = TYPE_ENTERLEAVE;
else if (type == LineKick.class) intType = TYPE_KICK;
else if (type == LineMode.class) intType = TYPE_MODE;
else if (type == LineWithUsers.class) intType = TYPE_MESSAGEACTION;
QuerySelect q = new QuerySelect(SQL.getTable("rollback"));
if (channel != null) q.addCriterions(new CriterionString("channel",channel.toLowerCase()));
if (user != null) q.addCriterions(new CriterionString("users",Operation.REGEXP,"(^|;)"+user.toLowerCase()+"($|;)"));
if (lines != 0)
q.setLimitCount(Math.min(lines,3000));
else
q.setLimitCount(3000);
if (seconds != 0) {
CriterionNumber criterion;
long milliseconds = (seconds*1000);
if (newest)
criterion = new CriterionNumber("stamp",Operation.GreaterOrEqual,System.currentTimeMillis()-milliseconds);
else
criterion = new CriterionNumber("stamp",Operation.LesserOrEqual,getOldestTime(channel)+milliseconds);
q.addCriterions(criterion);
}
if (regex != null && !regex.isEmpty())
q.addCriterions(new CriterionString("text",Operation.REGEXP,regex));
if (cull != null && !cull.isEmpty())
q.addCriterions(new CriterionString("text",cull,false));
if (type != Line.class) {
if (intType != TYPE_MESSAGEACTION)
q.addCriterions(new CriterionNumber("type",Operation.Equals,intType));
else
q.addCriterions(msgAndActCriterion);
}
q.addOrder("stamp",!newest);
return SQL.select(q);
}
@SuppressWarnings("unchecked")
public synchronized <T extends Line> ArrayList<T> getRollbackLines(Class<T> type, String channel, String user, String regex, String cull, boolean newest, int lines, int seconds) {
ArrayList<T> ret = new ArrayList<T>();
ResultSet result = null;
try {
result = getResults(type, channel, user, regex, cull, newest, lines, seconds);
if (result != null) {
while(result.next())
ret.add((T)getLine(result));
Collections.reverse(ret);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (result != null && !result.isClosed())
result.close();
} catch (SQLException e) {
}
}
return ret;
}
@SuppressWarnings("unchecked")
public synchronized <T extends Line> T getRollbackLine(ILinePredicate<T> predicate, Class<T> type, String channel, String user, String regex, String cull, boolean newest, int lines, int seconds) {
ResultSet result = null;
try {
result = getResults(type, channel, user, regex, cull, newest, lines, seconds);
if (result != null) {
while(result.next()) {
T line = (T) getLine(result);
if (predicate.accepts(line))
return line;
}
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
try {
if (result != null && !result.isClosed())
result.close();
} catch (SQLException e) {
}
}
return null;
}
public class CmdPastebin extends Command {
public String command() {return "pastebin";}
public String help(Parameters params) {
StringBuilder sb = new StringBuilder();
sb.append("pastebin/pb");
if (params.type == EType.Channel) {
sb.append("\npastebin [channel] [user] {lines} - uploads last lines to paste.kde.org/pastebin.com/pastebin.ca");
sb.append("\npastebin [channel] [user] -{lines} - uploads first lines to paste.kde.org/pastebin.com/pastebin.ca");
sb.append("\npastebin [channel] [user] {time}{d/h/m/s} - uploads last lines from set time to paste.kde.org/pastebin.com/pastebin.ca");
} else {
sb.append("\npastebin [channel] [user] {lines} - uploads last lines to paste.kde.org/pastebin.com/pastebin.ca");
sb.append("\npastebin [channel] [user] -{lines} - uploads first lines to paste.kde.org/pastebin.com/pastebin.ca");
sb.append("\npastebin [channel] [user] {time}{d/h/m/s} - uploads last lines from set time to paste.kde.org/pastebin.com/pastebin.ca");
}
return sb.toString();
}
public void doCommand(Parameters params, CommandCallback callback) {
String[] args = params.input.split(" ");
String pbLink = null, regex = null;
callback.type = EType.Notice;
if (args.length > 0) {
for (int i = args.length-1; i > 0; i--) {
if (args[i].equals("|")) {
regex = StringTools.implode(params.input,i+1," ");
args = StringTools.implode(args,0,i-1," ").split(" ");
break;
}
}
}
if (args.length < 1 || args.length > 3) {
callback.append(help(params));
return;
}
if (args.length >= 1) {
String aChannel = null, aUser = null, aLines;
if (args.length == 1) {
aLines = args[0];
} else if (args.length == 2) {
if (args[0].startsWith("#")) aChannel = args[0]; else aUser = args[0];
aLines = args[1];
} else {
aChannel = args[0];
aUser = args[1];
aLines = args[2];
}
if (aChannel == null && aUser == null) {
if (params.type == EType.Channel) aChannel = params.channel.getName(); else {
callback.append(help(params));
return;
}
}
if (aChannel != null) aChannel = aChannel.toLowerCase();
ArrayList<Line> list;
Matcher m = durationPattern.matcher(aLines);
if (m.find()) {
boolean additive = aLines.charAt(0) != '-';
int time = 0;
do {
int i = Integer.parseInt(m.group(1));
char c = m.group(2).charAt(0);
switch (c) {
case 's': time += i; break;
case 'm': time += i*60; break;
case 'h': time += i*3600; break;
case 'd': time += i*86400; break;
}
} while (m.find());
list = getRollbackLines(aChannel,aUser,regex,null,additive,0,time);
} else {
try {
int i = Integer.parseInt(aLines);
list = getRollbackLines(aChannel,aUser,regex,null,i >= 0,Math.abs(i),0);
} catch (NumberFormatException e) {
callback.append(help(params));
return;
}
}
if (list.isEmpty()) {
callback.append("Nothing to upload");
return;
}
pbLink = getLink(list,aUser != null && aChannel == null);
} else {
callback.append(help(params));
return;
}
if (pbLink != null) {
callback.type = EType.Channel;
callback.append(pbLink);
}
}
public String getLink(ArrayList<Line> lines, boolean withChannel) {
StringBuilder sb = new StringBuilder();
Line.setWithChannels(withChannel);
appendLines(sb,lines,!WebServer.exists());
Line.setWithChannels(false);
String link = Utils.paste(sb);
if (link != null)
return link;
return "Failed with all services";
}
}
}