package chatty;
import chatty.util.DateTime;
import chatty.util.MsgTags;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import java.util.logging.Logger;
/**
* Twitch Chat commands. All the Twitch specific commands like /mod, /timeout..
*
* @author tduva
*/
public class TwitchCommands {
private static final Logger LOGGER = Logger.getLogger(TwitchCommands.class.getName());
/**
* The delay between /mods requests. This is the delay in between each
* request, not how often it is requested for one channel (it is currently
* only requested once for each channel).
*/
private static final int REQUEST_MODS_DELAY = 30*1000;
/**
* Channels which currently wait for a /mods response that should be silent
* (no message output).
*/
private final Set<String> silentModsRequestChannel
= Collections.synchronizedSet(new HashSet<String>());
/**
* Channels for which the /mods list has already been requested.
*/
private final Set<String> modsAlreadyRequested
= Collections.synchronizedSet(new HashSet<String>());
private TwitchConnection c;
public TwitchCommands(TwitchConnection c) {
this.c = c;
}
public boolean command(String channel, String msgId, String command, String parameter) {
if (command.equals("to") || command.equals("timeout")) {
commandTimeout(channel, msgId, parameter);
}
else if (command.equals("unban")) {
commandUnban(channel, parameter);
}
else if (command.equals("untimeout")) {
commandUntimeout(channel, parameter);
}
else if (command.equals("ban")) {
commandBan(channel, msgId, parameter);
}
else if (command.equals("slow")) {
commandSlowmodeOn(channel, parameter);
}
else if (command.equals("followers")) {
followersOn(channel, parameter);
}
else if (command.equals("followersoff")) {
followersOff(channel);
}
else if (command.equals("slowoff")) {
slowmodeOff(channel);
}
else if (command.equals("subscribers")) {
subscribersOn(channel);
}
else if (command.equals("subscribersoff")) {
subscribersOff(channel);
}
else if (command.equals("emoteonly")) {
emoteonlyOn(channel);
}
else if (command.equals("emoteonlyoff")) {
emoteonlyOff(channel);
}
else if (command.equals("r9k")) {
r9kOn(channel);
}
else if (command.equals("r9koff")) {
r9kOff(channel);
}
else if (command.equals("mod")) {
commandMod(channel, parameter);
}
else if (command.equals("unmod")) {
commandUnmod(channel, parameter);
}
else if (command.equals("clear")) {
clearChannel(channel);
}
else if (command.equals("mods")) {
mods(channel);
}
else if (command.equals("fixmods")) {
modsSilent(channel);
}
else if (command.equals("host")) {
commandHostmode(channel, parameter);
}
else if (command.equals("host2")) {
commandHostmode2(Helper.toChannel(c.getUsername()), parameter);
}
else if (command.equals("unhost")) {
hostmodeOff(channel);
}
else if (command.equals("color")) {
commandColor(channel, parameter);
}
else {
return false;
}
return true;
}
private boolean onChannel(String channel, boolean message) {
return c.onChannel(channel, message);
}
private void sendMessage(String channel, String message, String echo) {
sendMessage(channel, message, echo, MsgTags.EMPTY);
}
private void sendMessage(String channel, String message, String echo, MsgTags tags) {
c.sendCommandMessage(channel, message, echo, tags);
}
private void printLine(String channel, String message) {
c.info(channel, message);
}
private void printLine(String message) {
c.info(message);
}
protected void commandTimeout(String channel, String msgId, String parameter) {
parameter = prepareAndCheckParameters(Helper.USERNAME_REGEX+"( [0-9]+( .+)?)?", parameter);
if (parameter == null) {
printLine("Usage: /to <nick> [time] [reason]");
return;
}
String[] parts = parameter.split(" ", 3);
String nick = parts[0];
long duration = 0;
String reason = null;
if (parts.length > 1) {
try {
duration = Long.parseLong(parts[1]);
} catch (NumberFormatException ex) {
// If the regex is correct, this may never happen
printLine("Usage: /to <nick> [time] [reason] (no valid time specified)");
return;
}
}
if (parts.length > 2) {
reason = parts[2];
}
timeout(channel, msgId, nick, duration, reason);
}
protected void commandSlowmodeOn(String channel, String parameter) {
if (parameter == null || parameter.isEmpty()) {
slowmodeOn(channel, 0);
}
else {
try {
int time = Integer.parseInt(parameter);
slowmodeOn(channel, time);
} catch (NumberFormatException ex) {
printLine("Usage: /slow [time] (invalid time specified)");
}
}
}
protected void commandUnban(String channel, String parameter) {
parameter = prepareAndCheckParameters(Helper.USERNAME_REGEX, parameter);
if (parameter == null) {
printLine("Usage: /unban <nick>");
return;
}
unban(channel, parameter);
}
protected void commandUntimeout(String channel, String parameter) {
parameter = prepareAndCheckParameters(Helper.USERNAME_REGEX, parameter);
if (parameter == null) {
printLine("Usage: /untimeout <nick>");
return;
}
if (onChannel(channel, true)) {
sendMessage(channel,".untimeout "+parameter, "Trying to untimeout "+parameter+"..");
}
}
protected void commandBan(String channel, String msgId, String parameter) {
parameter = prepareAndCheckParameters(Helper.USERNAME_REGEX+"( .+)?", parameter);
if (parameter == null) {
printLine("Usage: /ban <nick> [reason]");
} else {
String[] split = parameter.split(" ", 2);
if (split.length == 2) {
ban(channel, msgId, split[0], split[1]);
} else {
ban(channel, msgId, split[0], null);
}
}
}
protected void commandMod(String channel, String parameter) {
parameter = prepareAndCheckParameters(Helper.USERNAME_REGEX+"( .+)?", parameter);
if (parameter == null) {
printLine("Usage: /mod <nick>");
} else {
mod(channel, parameter);
}
}
protected void commandUnmod(String channel, String parameter) {
parameter = prepareAndCheckParameters(Helper.USERNAME_REGEX+"( .+)?", parameter);
if (parameter == null) {
printLine("Usage: /unmod <nick>");
}
else {
unmod(channel, parameter);
}
}
protected void commandColor(String channel, String parameter) {
if (parameter == null) {
printLine("Usage: /color <newcolor>");
} else {
color(channel, parameter);
}
}
protected void commandHostmode(String channel, String parameter) {
if (parameter == null) {
printLine("Usage: /host <stream>");
} else {
hostmode(channel, parameter);
}
}
protected void commandHostmode2(String channel, String parameter) {
if (parameter == null) {
printLine("Usage: /host2 <stream>");
} else {
hostmode2(channel, parameter);
}
}
public void hostmode(String channel, String target) {
if (onChannel(channel, true)) {
sendMessage(channel, ".host "+target, "Trying to host "+target+"..");
}
}
public void hostmode2(String channel, String target) {
if (c.isRegistered()) {
c.sendSpamProtectedMessage(channel, ".host "+target, false);
printLine(String.format("Trying to host %s from %s", target, channel));
} else {
printLine("Must be connected to chat to start hosting.");
}
}
public void hostmodeOff(String channel) {
if (onChannel(channel, true)) {
sendMessage(channel, ".unhost", "Trying to turn off host mode..");
}
}
public void color(String channel, String color) {
if (onChannel(channel, true)) {
sendMessage(channel, ".color "+color, "Trying to change color to "+color);
}
}
/**
* Turn on slowmode with the given amount of seconds or the default time
* (without specifying a time).
*
* @param channel The name of the channel
* @param time The time in seconds, 0 or negative numbers will make it give
* not time at all
*/
public void slowmodeOn(String channel, int time) {
if (onChannel(channel, true)) {
if (time <= 0) {
sendMessage(channel,".slow", "Trying to turn on slowmode..");
}
else {
sendMessage(channel,".slow "+time, "Trying to turn on slowmode ("+time+"s)");
}
}
}
/**
* Turns off slowmode in the given channel.
*
* @param channel The name of the channel.
*/
public void slowmodeOff(String channel) {
if (onChannel(channel, true)) {
sendMessage(channel,".slowoff", "Trying to turn off slowmode..");
}
}
public void followersOn(String channel, String time) {
if (onChannel(channel, true)) {
sendMessage(channel, ".followers "+(time != null ? time : ""), "Trying to turn on followers-only mode..");
}
}
public void followersOff(String channel) {
if (onChannel(channel, true)) {
sendMessage(channel, ".followersoff", "Trying to turn off followers-only mode..");
}
}
/**
* Turns on subscriber only mode in the given channel.
*
* @param channel The name of the channel.
*/
public void subscribersOn(String channel) {
if (onChannel(channel, true)) {
sendMessage(channel,".subscribers", "Trying to turn on subscribers mode..");
}
}
public void subscribersOff(String channel) {
if (onChannel(channel, true)) {
sendMessage(channel,".subscribersoff", "Trying to turn off subscribers mode..");
}
}
public void emoteonlyOn(String channel) {
if (onChannel(channel, true)) {
sendMessage(channel,".emoteonly", "Trying to turn on emote-only mode..");
}
}
public void emoteonlyOff(String channel) {
if (onChannel(channel, true)) {
sendMessage(channel,".emoteonlyoff", "Trying to turn off emote-only mode..");
}
}
public void r9kOn(String channel) {
if (onChannel(channel, true)) {
sendMessage(channel,".r9kbeta", "Trying to turn on r9k mode..");
}
}
public void r9kOff(String channel) {
if (onChannel(channel, true)) {
sendMessage(channel,".r9kbetaoff", "Trying to turn r9k mode off..");
}
}
public void clearChannel(String channel) {
if (onChannel(channel, true)) {
sendMessage(channel,".clear", "Trying to clear channel..");
}
}
public void ban(String channel, String msgId, String name, String reason) {
if (onChannel(channel, true)) {
MsgTags tags = createTags(msgId);
if (reason == null || reason.isEmpty()) {
sendMessage(channel,".ban "+name, "Trying to ban "+name+"..", tags);
} else {
sendMessage(channel,".ban "+name+" "+reason, "Trying to ban "+name+".. ("+reason+")", tags);
}
}
}
public void mod(String channel, String name) {
if (onChannel(channel, true)) {
sendMessage(channel,".mod "+name, "Trying to mod "+name+"..");
}
}
public void unmod(String channel, String name) {
if (onChannel(channel, true)) {
sendMessage(channel,".unmod "+name, "Trying to unmod "+name+"..");
}
}
/**
* Sends a timeout command to the server.
*
* @param channel
* @param name
* @param time
*/
public void timeout(String channel, String msgId, String name, long time, String reason) {
if (onChannel(channel, true)) {
MsgTags tags = createTags(msgId);
if (time <= 0) {
sendMessage(channel,".timeout "+name, "Trying to timeout "+name+"..", tags);
}
else {
String formatted = DateTime.duration(time*1000, 0, 2, 0);
String onlySeconds = time+"s";
String timeString = formatted.equals(onlySeconds)
? onlySeconds : onlySeconds+"/"+formatted;
if (reason == null || reason.isEmpty()) {
sendMessage(channel,".timeout "+name+" "+time,
"Trying to timeout "+name+" ("+timeString+")", tags);
} else {
sendMessage(channel,".timeout "+name+" "+time+" "+reason,
"Trying to timeout "+name+" ("+timeString+", "+reason+")", tags);
}
}
}
}
public void unban(String channel, String name) {
if (onChannel(channel, true)) {
sendMessage(channel,".unban "+name, "Trying to unban "+name+"..");
}
}
public void mods(String channel) {
if (onChannel(channel, true)) {
sendMessage(channel,".mods", "Requesting moderator list..");
}
}
public void modsSilent(String channel) {
if (onChannel(channel, true)) {
printLine(channel, "Trying to fix moderators..");
requestModsSilent(channel);
}
}
public void requestModsSilent(String channel) {
if (onChannel(channel, false)) {
silentModsRequestChannel.add(channel);
c.sendSpamProtectedMessage(channel, ".mods", false);
}
}
public boolean removeModsSilent(String channel) {
return silentModsRequestChannel.remove(channel);
}
public boolean waitingForModsSilent() {
return !silentModsRequestChannel.isEmpty();
}
/**
* Prase the list of mods as returned from the Twitch Chat. The
* comma-seperated list should start after the first colon ("The moderators
* of this room are: ..").
*
* @param text The text as received from the Twitch Chat
* @return A List of moderator names
*/
public static List<String> parseModsList(String text) {
int start = text.indexOf(":") + 1;
List<String> modsList = new ArrayList<>();
if (start > 1 && text.length() > start) {
String mods = text.substring(start);
if (!mods.trim().isEmpty()) {
String[] modsArray = mods.split(",");
for (String mod : modsArray) {
modsList.add(mod.trim());
}
}
}
return modsList;
}
/**
* Starts the timer which requests the /mods list for joined channels.
*/
public void startAutoRequestMods() {
Timer timer = new Timer(true);
timer.schedule(new TimerTask() {
@Override
public void run() {
autoRequestMods();
}
}, 1000, REQUEST_MODS_DELAY);
}
/**
* If enabled in the settings, requests /mods for one currently joined
* channel (and only one), ignoring the ones it was already requested for.
*/
private void autoRequestMods() {
if (!c.autoRequestModsEnabled()) {
return;
}
Set<String> joinedChannels = c.getJoinedChannels();
for (String channel : joinedChannels) {
if (!modsAlreadyRequested.contains(channel)) {
LOGGER.info("Auto-requesting mods for "+channel);
modsAlreadyRequested.add(channel);
requestModsSilent(channel);
return;
}
}
}
/**
* Removes one or all entries from the list of channels the /mods list was
* already requested for. This can be used on part/disconnect, since users
* are removed then.
*
* @param channel The name of the channel to remove, or null to remove all
* entries
*/
public void clearModsAlreadyRequested(String channel) {
if (channel == null) {
modsAlreadyRequested.clear();
} else {
modsAlreadyRequested.remove(channel);
}
}
private String prepareAndCheckParameters(String regex, String parameters) {
if (parameters == null) {
return null;
}
parameters = parameters.trim();
return parameters.matches(regex) ? parameters : null;
}
private MsgTags createTags(String msgId) {
if (msgId != null) {
return MsgTags.create("target-msg-id", msgId);
}
return MsgTags.EMPTY;
}
}