package chatty;
import chatty.gui.components.admin.StatusHistory;
import chatty.util.commands.CustomCommands;
import chatty.util.api.usericons.Usericon;
import chatty.util.api.usericons.UsericonManager;
import chatty.ChannelStateManager.ChannelStateListener;
import chatty.util.api.TwitchApiResultListener;
import chatty.util.api.Emoticon;
import chatty.util.api.StreamInfoListener;
import chatty.util.api.TokenInfo;
import chatty.util.api.StreamInfo;
import chatty.util.api.ChannelInfo;
import chatty.util.api.TwitchApi;
import chatty.Version.VersionListener;
import chatty.WhisperManager.WhisperListener;
import chatty.gui.GuiUtil;
import chatty.gui.MainGui;
import chatty.util.BTTVEmotes;
import chatty.util.BotNameManager;
import chatty.util.DateTime;
import chatty.util.EmoticonListener;
import chatty.util.ffz.FrankerFaceZ;
import chatty.util.ffz.FrankerFaceZListener;
import chatty.util.ImageCache;
import chatty.util.LogUtil;
import chatty.util.MiscUtil;
import chatty.util.ProcessManager;
import chatty.util.RawMessageTest;
import chatty.util.Speedruncom;
import chatty.util.StreamHighlightHelper;
import chatty.util.StreamStatusWriter;
import chatty.util.StringUtil;
import chatty.util.TwitchEmotes;
import chatty.util.TwitchEmotes.TwitchEmotesListener;
import chatty.util.Webserver;
import chatty.util.api.ChatInfo;
import chatty.util.api.CheerEmoticon;
import chatty.util.api.EmoticonSizeCache;
import chatty.util.api.EmoticonUpdate;
import chatty.util.api.Emoticons;
import chatty.util.api.Follower;
import chatty.util.api.FollowerInfo;
import chatty.util.api.StreamInfo.ViewerStats;
import chatty.util.api.TwitchApi.RequestResultCode;
import chatty.util.api.pubsub.Message;
import chatty.util.api.pubsub.ModeratorActionData;
import chatty.util.api.pubsub.PubSubListener;
import chatty.util.chatlog.ChatLog;
import chatty.util.commands.CustomCommand;
import chatty.util.commands.Parameters;
import chatty.util.settings.Settings;
import chatty.util.settings.SettingsListener;
import chatty.util.srl.SpeedrunsLive;
import java.io.File;
import java.nio.file.Paths;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import javax.swing.SwingUtilities;
/**
* The main client class, responsible for managing most parts of the program.
*
* @author tduva
*/
public class TwitchClient {
private static final Logger LOGGER = Logger.getLogger(TwitchClient.class.getName());
private volatile boolean shuttingDown = false;
private volatile boolean settingsAlreadySavedOnExit = false;
/**
* The URL to get a token. Needs to end with the scopes so other ones can be
* added.
*/
public static final String REQUEST_TOKEN_URL = ""
+ "https://api.twitch.tv/kraken/oauth2/authorize"
+ "?response_type=token"
+ "&client_id="+Chatty.CLIENT_ID
+ "&redirect_uri="+Chatty.REDIRECT_URI
+ "&force_verify=true"
+ "&scope=chat_login";
/**
* The interval to check version in (seconds)
*/
private static final int CHECK_VERSION_INTERVAL = 60*60*24*2;
/**
* Holds the Settings object, which is used to store and retrieve renametings
*/
public final Settings settings;
public final ChatLog chatLog;
private final TwitchConnection c;
/**
* Holds the TwitchApi object, which is used to make API requests
*/
public final TwitchApi api;
public final chatty.util.api.pubsub.Manager pubsub;
public final TwitchEmotes twitchemotes;
public final BTTVEmotes bttvEmotes;
public final FrankerFaceZ frankerFaceZ;
public final ChannelFavorites channelFavorites;
public final UsercolorManager usercolorManager;
public final UsericonManager usericonManager;
public final Addressbook addressbook;
public final SpeedrunsLive speedrunsLive;
public final Speedruncom speedruncom;
public final StatusHistory statusHistory;
public final StreamStatusWriter streamStatusWriter;
protected final BotNameManager botNameManager;
protected final CustomNames customNames;
/**
* Holds the UserManager instance, which manages all the user objects.
*/
//protected UserManager users = new UserManager();
/**
* A reference to the Main Gui.
*/
protected MainGui g;
private final List<String> cachedDebugMessages = new ArrayList<>();
private final List<String> cachedWarningMessages = new ArrayList<>();
/**
* User used for testing without connecting.
*/
private User testUser;
private final StreamInfo testStreamInfo = new StreamInfo("testStreamInfo", null);
private Webserver webserver;
private final SettingsManager settingsManager;
private final SpamProtection spamProtection;
public final CustomCommands customCommands;
private final StreamHighlightHelper streamHighlights;
private final Set<String> refreshRequests = Collections.synchronizedSet(new HashSet<String>());
private final WhisperManager w;
private final IrcLogger ircLogger;
private boolean fixServer = false;
public TwitchClient(Map<String, String> args) {
// Logging
new Logging(this);
Thread.setDefaultUncaughtExceptionHandler(new ErrorHandler());
LOGGER.info("### Log start ("+DateTime.fullDateTime()+")");
LOGGER.info(Chatty.chattyVersion());
LOGGER.info(Helper.systemInfo());
LOGGER.info("[Working Directory] "+System.getProperty("user.dir")
+" [Settings Directory] "+Chatty.getUserDataDirectory()
+" [Classpath] "+System.getProperty("java.class.path")
+" [Library Path] "+System.getProperty("java.library.path"));
LOGGER.info("Retina Display: "+GuiUtil.hasRetinaDisplay());
// Create after Logging is created, since that resets some stuff
ircLogger = new IrcLogger();
createTestUser("tduva", "#m_tt");
settings = new Settings(Chatty.getUserDataDirectory()+"settings");
api = new TwitchApi(new TwitchApiResults(), new MyStreamInfoListener());
twitchemotes = new TwitchEmotes(new TwitchemotesListener());
bttvEmotes = new BTTVEmotes(new EmoteListener());
// Settings
settingsManager = new SettingsManager(settings);
settingsManager.defineSettings();
settingsManager.loadSettingsFromFile();
settingsManager.backupFiles();
settingsManager.loadCommandLineSettings(args);
settingsManager.overrideSettings();
settingsManager.debugSettings();
pubsub = new chatty.util.api.pubsub.Manager(
settings.getString("pubsub"), new PubSubResults(), api);
frankerFaceZ = new FrankerFaceZ(new EmoticonsListener(), settings);
ImageCache.setDefaultPath(Paths.get(Chatty.getCacheDirectory()+"img"));
ImageCache.setCachingEnabled(settings.getBoolean("imageCache"));
ImageCache.clearOldFiles();
EmoticonSizeCache.loadFromFile();
channelFavorites = new ChannelFavorites(settings);
usercolorManager = new UsercolorManager(settings);
usericonManager = new UsericonManager(settings);
customCommands = new CustomCommands(settings, api);
customCommands.loadFromSettings();
botNameManager = new BotNameManager(settings);
settings.addSettingsListener(new SettingSaveListener());
streamHighlights = new StreamHighlightHelper(settings, api);
customNames = new CustomNames(settings);
chatLog = new ChatLog(settings);
chatLog.start();
testUser.setUsericonManager(usericonManager);
testUser.setUsercolorManager(usercolorManager);
addressbook = new Addressbook(Chatty.getUserDataDirectory()+"addressbook",
Chatty.getUserDataDirectory()+"addressbookImport.txt", settings);
addressbook.loadFromFile();
addressbook.setSomewhatUniqueCategories(settings.getString("abUniqueCats"));
if (settings.getBoolean("abAutoImport")) {
addressbook.enableAutoImport();
}
testUser.setAddressbook(addressbook);
speedrunsLive = new SpeedrunsLive();
speedruncom = new Speedruncom(api);
statusHistory = new StatusHistory(settings);
settings.addSettingsListener(statusHistory);
spamProtection = new SpamProtection();
spamProtection.setLinesPerSeconds(settings.getString("spamProtection"));
c = new TwitchConnection(new Messages(), settings, "main");
c.setAddressbook(addressbook);
c.setCustomNamesManager(customNames);
c.setUsercolorManager(usercolorManager);
c.setUsericonManager(usericonManager);
c.setBotNameManager(botNameManager);
c.addChannelStateListener(new ChannelStateUpdater());
c.setMaxReconnectionAttempts(settings.getLong("maxReconnectionAttempts"));
w = new WhisperManager(new MyWhisperListener(), settings, c);
streamStatusWriter = new StreamStatusWriter(Chatty.getUserDataDirectory(), api);
streamStatusWriter.setSetting(settings.getString("statusWriter"));
streamStatusWriter.setEnabled(settings.getBoolean("enableStatusWriter"));
settings.addSettingChangeListener(streamStatusWriter);
initDxSettings();
GuiUtil.setLookAndFeel(settings.getString("laf"));
// Create GUI
LOGGER.info("Create GUI..");
g = new MainGui(this);
g.loadSettings();
g.showGui();
if (Chatty.DEBUG) {
getSpecialUser().setEmoteSets("130,4280,33,42,19194");
g.addUser("", new User("josh", ""));
g.addUser("", new User("joshua", ""));
User j = new User("joshimuz", "Joshimuz", "");
j.addMessage("abc", false, null);
j.setDisplayNick("Joshimoose");
j.setTurbo(true);
g.addUser("", j);
g.addUser("", new User("jolzi", ""));
g.addUser("", new User("john", ""));
g.addUser("", new User("tduva", ""));
User kb = new User("kabukibot", "Kabukibot", "");
kb.setBot(true);
g.addUser("", kb);
g.addUser("", new User("lotsofs", "LotsOfS", ""));
g.addUser("", new User("anders", ""));
g.addUser("", new User("apex1", ""));
User af = new User("applefan", "");
Map<String, String> badges = new LinkedHashMap<>();
badges.put("bits", "100");
af.setTwitchBadges(badges);
g.addUser("", af);
g.addUser("", new User("austrian_", ""));
g.addUser("", new User("adam_ak", ""));
g.addUser("", new User("astroman", ""));
g.addUser("", new User("xxxandre369xxx", ""));
g.addUser("", new User("all_that_stuff_", ""));
g.addUser("", new User("adam_ak_stole_my_bike", ""));
g.addUser("", new User("bikelover", ""));
g.addUser("", new User("bicyclefan", ""));
g.addUser("", new User("botnak", "Botnak", ""));
g.addUser("", new User("brett", ""));
g.addUser("", new User("bll", ""));
g.addUser("", new User("bzp______________", ""));
g.addUser("", new User("7_dm", ""));
String[] chans = new String[]{"europeanspeedsterassembly","esamarathon2","heinki","joshimuz","lotsofs","test","a","b","c"};
for (String chan : chans) {
//g.printLine(chan, "test");
}
}
}
public void init() {
// Output any cached warning messages
warning(null);
// Before checkNewVersion(), so "updateAvailable" is already updated
checkForVersionChange();
// Check version, if enabled in this build
if (Chatty.VERSION_CHECK_ENABLED) {
checkNewVersion();
}
// Connect or open connect dialog
if (settings.getBoolean("connectOnStartup")) {
prepareConnection();
} else {
switch ((int)settings.getLong("onStart")) {
case 1:
g.openConnectDialog(null);
break;
case 2:
prepareConnectionWithChannel(settings.getString("autojoinChannel"));
break;
case 3:
prepareConnectionWithChannel(settings.getString("previousChannel"));
break;
case 4:
prepareConnectionWithChannel(Helper.buildStreamsString(channelFavorites.getFavorites()));
break;
}
}
new UpdateTimer(g);
// Shutdown hook
Runtime.getRuntime().addShutdownHook(new Thread(new Shutdown(this)));
}
/**
* Based on the current renametings, rename the system properties to disable
* Direct3D and/or DirectDraw.
*/
private void initDxSettings() {
try {
Boolean d3d = !settings.getBoolean("nod3d");
System.setProperty("sun.java2d.d3d", d3d.toString());
Boolean ddraw = settings.getBoolean("noddraw");
System.setProperty("sun.java2d.noddraw", ddraw.toString());
//System.out.println(System.getProperty("sun.java2d.opengl"));
LOGGER.info("Drawing settings: d3d: "+d3d+" / noddraw: "+ddraw);
} catch (SecurityException ex) {
LOGGER.warning("Error setting drawing settings: "+ex.getLocalizedMessage());
}
}
/**
* Check if the current version (Chatty.VERSION) is different from the
* "currentVersion" in the settings, which means a different version is
* being run compared to the last time.
*
* If a new version is detected, updates the "currentVersion" setting,
* clears the "updateAvailable" setting and opens the release info.
*/
private void checkForVersionChange() {
String currentVersion = settings.getString("currentVersion");
if (!currentVersion.equals(Chatty.VERSION)) {
settings.setString("currentVersion", Chatty.VERSION);
// Changed version, so should check for update properly again
settings.setString("updateAvailable", "");
g.openReleaseInfo();
}
}
/**
* Checks for a new version if the last check was long enough ago.
*/
private void checkNewVersion() {
if (!settings.getBoolean("checkNewVersion")) {
return;
}
/**
* Check if enough time has passed since the last check.
*/
long ago = System.currentTimeMillis() - settings.getLong("versionLastChecked");
if (ago/1000 < CHECK_VERSION_INTERVAL) {
/**
* If not checking, check if update was detected last time.
*/
String updateAvailable = settings.getString("updateAvailable");
if (!updateAvailable.isEmpty()) {
g.setUpdateAvailable(updateAvailable);
}
return;
}
settings.setLong("versionLastChecked", System.currentTimeMillis());
g.printSystem("Checking for new version..");
new Version(new VersionListener() {
@Override
public void versionChecked(String version, String info, boolean isNewVersion) {
if (isNewVersion) {
String infoText = "";
if (!info.isEmpty()) {
infoText = "[" + info + "] ";
}
g.printSystem("New version available: "+version+" "+infoText
+"(Go to <Help-Website> to download)");
g.setUpdateAvailable(version);
settings.setString("updateAvailable", version);
} else {
g.printSystem("You already have the newest version.");
}
}
});
}
/**
* Creates the test user, also allowing it to be recreated with another name
* or channel for testing while the programm is running.
*
* @param name
* @param channel
*/
private void createTestUser(String name, String channel) {
testUser = new User(name, "abc" ,channel);
testUser.setColor("blue");
testUser.setGlobalMod(true);
testUser.setBot(true);
//testUser.setTurbo(true);
//testUser.setModerator(true);
//testUser.setSubscriber(true);
testUser.setEmoteSets("4280");
//testUser.setAdmin(true);
//testUser.setStaff(true);
//testUser.setBroadcaster(true);
LinkedHashMap<String, String> badgesTest = new LinkedHashMap<>();
badgesTest.put("moderator", "1");
badgesTest.put("premium", "1");
badgesTest.put("bits", "1000000");
testUser.setTwitchBadges(badgesTest);
}
/**
* Close all channels except the ones in the given Array.
*
* @param except
*/
private void closeAllChannelsExcept(String[] except) {
Set<String> copy = c.getOpenChannels();
for (String channel : copy) {
if (!Arrays.asList(except).contains(channel)) {
closeChannel(channel);
}
}
}
/**
* Close a channel by either parting it if it is currently joined or
* just closing the tab.
*
* @param channel
*/
public void closeChannel(String channel) {
if (c.onChannel(channel)) {
c.partChannel(channel);
}
else { // Always remove channel (or try to), so it can be closed even if it bugged out
logViewerstats(channel);
c.closeChannel(channel);
frankerFaceZ.left(channel);
g.removeChannel(channel);
chatLog.closeChannel(channel);
}
}
private void addressbookCommands(String channel, User user, String text) {
if (settings.getString("abCommandsChannel").equals(channel)
&& user.isModerator()) {
text = text.trim();
if (text.length() < 2) {
return;
}
String command = text.split(" ")[0].substring(1);
List<String> activatedCommands =
Arrays.asList(settings.getString("abCommands").split(","));
if (activatedCommands.contains(command)) {
String commandText = text.substring(1);
g.printSystem("[Ab/Mod] "+addressbook.command(commandText));
}
}
}
public String getUsername() {
return c.getUsername();
}
public User getUser(String channel, String name) {
return c.getUser(channel, name);
}
public void clearUserList() {
c.setAllOffline();
g.clearUsers(null);
}
private String getServer() {
String serverDefault = settings.getString("serverDefault");
String serverTemp = settings.getString("server");
return serverTemp.length() > 0 ? serverTemp : serverDefault;
}
private String getPorts() {
String portDefault = settings.getString("portDefault");
String portTemp = settings.getString("port");
return portTemp.length() > 0 ? portTemp : portDefault;
}
/**
* Prepare connection using renametings and default server.
*
* @return
*/
public final boolean prepareConnection() {
return prepareConnection(getServer(), getPorts());
}
public boolean prepareConnection(boolean rejoinOpenChannels) {
if (rejoinOpenChannels) {
return prepareConnection(getServer(), getPorts(), null);
} else {
return prepareConnection();
}
}
public final boolean prepareConnectionWithChannel(String channel) {
return prepareConnection(getServer(), getPorts(), channel);
}
public boolean prepareConnection(String server, String ports) {
return prepareConnection(server, ports, settings.getString("channel"));
}
public final boolean prepareConnectionAnyChannel(String server, String ports) {
String channel = null;
if (c.getOpenChannels().isEmpty()) {
channel = settings.getString("channel");
}
return prepareConnection(server, ports, channel);
}
/**
* Prepares the connection while getting everything from the renametings,
* except the server/port.
*
* @param server
* @param ports
* @return
*/
public boolean prepareConnection(String server, String ports, String channel) {
String username = settings.getString("username");
String password = settings.getString("password");
boolean usePassword = settings.getBoolean("usePassword");
String token = settings.getString("token");
String login = "oauth:"+token;
if (token.isEmpty()) {
login = "";
}
if (usePassword) {
login = password;
LOGGER.info("Using password instead of token.");
}
return prepareConnection(username,login,channel,server, ports);
}
/**
* Prepare connection using given credentials and channel, but use default
* server.
*
* @param name
* @param password
* @param channel
* @return
*/
// public boolean prepareConnection(String name, String password, String channel) {
// return prepareConnection(name, password, channel, getServer(), getPorts());
// }
/**
* Prepares the connection to the given channel with the given credentials.
*
* This does stuff that should only be done once, unless the given parameters
* change. So this shouldn't be repeated for just reconnecting.
*
* @param name The username to use for connecting.
* @param password The password to connect with.
* @param channel The channel(s) to join after connecting, if this is null
* then it rejoins the currently open channels (if any)
* @param server The server to connect to.
* @param ports The port to connect to.
* @return true if no formal error occured, false otherwise
*/
public boolean prepareConnection(String name, String password,
String channel, String server, String ports) {
fixServer = false;
if (c.getState() > Irc.STATE_OFFLINE) {
g.showMessage("Cannot connect: Already connected.");
return false;
}
if (name == null || name.isEmpty() || password == null || password.isEmpty()) {
g.showMessage("Cannot connect: Incomplete login data.");
return false;
}
String[] autojoin;
Set<String> openChannels = c.getOpenChannels();
if (channel == null) {
autojoin = new String[openChannels.size()];
openChannels.toArray(autojoin);
} else {
autojoin = Helper.parseChannels(channel);
}
if (autojoin.length == 0) {
g.showMessage("A channel to join has to be specified.");
return false;
}
if (server == null || server.isEmpty()) {
g.showMessage("Invalid server specified.");
return false;
}
closeAllChannelsExcept(autojoin);
settings.setString("username", name);
if (channel != null) {
settings.setString("channel", channel);
}
api.requestUserId(Helper.toStream(autojoin));
c.connect(server, ports, name, password, autojoin);
return true;
}
public boolean disconnect() {
return c.disconnect();
}
public void joinChannels(Set<String> channels) {
c.joinChannels(channels);
}
public void joinChannel(String channels) {
c.joinChannel(channels);
}
public int getState() {
return c.getState();
}
/**
* Anything entered in a channel input box is reacted to here.
*
* This must be safe input (i.e. input directly by the local user) because
* this can execute all kind of commands.
*
* @param channel
* @param text
*/
public void textInput(String channel, String text) {
if (text.isEmpty()) {
return;
}
if (text.startsWith("/")) {
commandInput(channel, text);
}
else {
if (c.onChannel(channel)) {
sendMessage(channel, text, true);
}
else if (channel.startsWith("$")) {
w.whisperChannel(channel, text);
}
else if (channel.startsWith("*")) {
c.sendCommandMessage(channel, text, "> "+text);
}
else {
g.printLine("Not in a channel");
// For testing:
// (Also creates a channel with an empty string)
if (Chatty.DEBUG) {
g.printMessage(channel,testUser,text,false,null,1);
}
}
}
}
private void sendMessage(String channel, String text) {
sendMessage(channel, text, false);
}
/**
*
* @param channel
* @param text
* @param allowCommandMessageLocally Commands like !highlight, which
* normally only working for received messages, will be triggered when
* sending a message as well
*/
private void sendMessage(String channel, String text, boolean allowCommandMessageLocally) {
if (c.sendSpamProtectedMessage(channel, text, false)) {
User user = c.localUserJoined(channel);
g.printMessage(channel, user, text, false, null, 0);
if (allowCommandMessageLocally) {
modCommandAddStreamHighlight(user, text);
}
} else {
g.printLine("# Message not sent to prevent ban: " + text);
}
}
/**
* Checks if the given channel should be open.
*
* @param channel The channel name
* @return
*/
public boolean isChannelOpen(String channel) {
return c.isChannelOpen(channel);
}
public boolean isUserlistLoaded(String channel) {
return c.isUserlistLoaded(channel);
}
public String getHostedChannel(String channel) {
return c.getChannelState(channel).getHosting();
}
/**
* Text has to start with a /.
*
* This must be safe input (i.e. input directly by the local user) because
* this can execute all kind of commands.
*
* @param channel
* @param text
* @return
*/
public boolean commandInput(String channel, String text) {
String[] split = text.split(" ", 2);
String command = split[0].substring(1);
String parameter = null;
if (split.length == 2) {
parameter = split[1];
}
return command(channel, command, parameter);
}
/**
* Reacts on all of the commands entered in the channel input box.
*
* This must be safe input (i.e. input directly by the local user) because
* this can execute all kind of commands.
*
* @param channel
* @param command
* @param parameter
* @return
*/
public boolean command(String channel, String command, String parameter) {
return command(channel, command, parameter, null);
}
public boolean command(String channel, String command, String parameter,
String msgId) {
//System.out.println(channel+" "+command+" "+parameter);
command = StringUtil.toLowerCase(command);
//---------------
// Connection/IRC
//---------------
if (command.equals("quit")) {
c.quit();
}
else if (command.equals("server")) {
commandServer(parameter);
}
else if (command.equals("reconnect")) {
commandReconnect();
}
else if (command.equals("connection")) {
g.printLine(c.getConnectionInfo());
}
else if (command.equals("join")) {
commandJoinChannel(parameter);
}
else if (command.equals("part") || command.equals("close")) {
commandPartChannel(channel);
}
else if (command.equals("raw")) {
if (parameter != null) {
c.sendRaw(parameter);
}
}
else if (command.equals("me")) {
commandActionMessage(channel,parameter);
}
else if (command.equals("msg")) {
commandCustomMessage(parameter);
}
else if (command.equals("w")) {
w.whisperCommand(parameter, false);
}
else if (command.equals("changetoken")) {
g.changeToken(parameter);
}
//------------
// System/Util
//------------
else if (command.equals("dir")) {
g.printSystem("Settings directory: '"+Chatty.getUserDataDirectory()+"'");
}
else if (command.equals("wdir")) {
g.printSystem("Working directory: '"+Chatty.getWorkingDirectory()+"'");
}
else if (command.equals("opendir")) {
MiscUtil.openFolder(new File(Chatty.getUserDataDirectory()), g);
}
else if (command.equals("openwdir")) {
MiscUtil.openFolder(new File(Chatty.getWorkingDirectory()), g);
}
else if (command.equals("openbackupdir")) {
MiscUtil.openFolder(new File(Chatty.getBackupDirectory()), g);
}
else if (command.equals("copy")) {
MiscUtil.copyToClipboard(parameter);
}
else if (command.equals("releaseinfo")) {
g.openReleaseInfo();
}
else if (command.equals("echo")) {
if (parameter != null) {
g.printLine(parameter);
} else {
g.printLine("Invalid parameters: /echo <message>");
}
}
else if (command.equals("uptime")) {
g.printSystem("Chatty has been running for "+Chatty.uptime());
}
else if (command.equals("appinfo")) {
g.printSystem(LogUtil.getMemoryUsage());
}
//-----------------------
// Settings/Customization
//-----------------------
else if (command.equals("set")) {
g.printSystem(settings.setTextual(parameter));
}
else if (command.equals("get")) {
g.printSystem(settings.getTextual(parameter));
}
else if (command.equals("clearsetting")) {
g.printSystem(settings.clearTextual(parameter));
}
else if (command.equals("reset")) {
g.printSystem(settings.resetTextual(parameter));
}
else if (command.equals("add")) {
g.printSystem(settings.addTextual(parameter));
}
else if (command.equals("remove")) {
g.printSystem(settings.removeTextual(parameter));
}
else if (command.equals("setcolor")) {
if (parameter != null) {
g.setColor(parameter);
}
}
else if (command.equals("setname")) {
g.printLine(customNames.commandSetCustomName(parameter));
}
else if (command.equals("resetname")) {
g.printLine(customNames.commandResetCustomname(parameter));
}
else if (command.equals("customcompletion")) {
commandCustomCompletion(parameter);
}
else if (command.equals("users") || command.equals("ab")) {
g.printSystem("[Addressbook] "
+addressbook.command(parameter != null ? parameter : ""));
}
else if (command.equals("abimport")) {
g.printSystem("[Addressbook] Importing from file..");
addressbook.importFromFile();
}
//-------
// Ignore
//-------
else if (command.equals("ignore")) {
commandSetIgnored(parameter, null, true);
}
else if (command.equals("unignore")) {
commandSetIgnored(parameter, null, false);
}
else if (command.equals("ignorechat")) {
commandSetIgnored(parameter, "chat", true);
}
else if (command.equals("unignorechat")) {
commandSetIgnored(parameter, "chat", false);
}
else if (command.equals("ignorewhisper")) {
commandSetIgnored(parameter, "whisper", true);
}
else if (command.equals("unignorewhisper")) {
commandSetIgnored(parameter, "whisper", false);
}
//--------------
// Emotes/Images
//--------------
else if (command.equals("myemotes")) {
commandMyEmotes();
}
else if (command.equals("emoteinfo")) {
g.printSystem(g.emoticons.getEmoteInfo(parameter));
}
else if (command.equals("ffz")) {
if (parameter != null && parameter.startsWith("following")) {
commandFFZFollowing(channel, parameter);
} else {
commandFFZ(channel);
}
}
else if (command.equals("ffzglobal")) {
commandFFZ(null);
}
else if (command.equals("ffzws")) {
g.printSystem("[FFZ-WS] Status: "+frankerFaceZ.getWsStatus());
}
else if (command.equals("pubsubstatus")) {
g.printSystem("[PubSub] Status: "+pubsub.getStatus());
}
else if (command.equals("refresh")) {
commandRefresh(channel, parameter);
}
else if (command.equals("clearimagecache")) {
g.printLine("Clearing image cache (this can take a few seconds)");
ImageCache.clearCache(null);
g.printLine("Image cache cleared.");
}
else if (command.equals("clearemotecache")) {
ImageCache.clearCache("emote_"+parameter);
g.printLine("Emoticon image cache for type "+parameter+" cleared.");
}
//------
// Other
//------
else if (command.equals("follow")) {
commandFollow(channel, parameter);
}
else if (command.equals("unfollow")) {
commandUnfollow(channel, parameter);
}
else if (command.equals("addstreamhighlight")) {
commandAddStreamHighlight(channel, parameter);
}
else if (command.equals("openstreamhighlights")) {
commandOpenStreamHighlights(channel);
}
else if (command.equals("testnotification")) {
g.showTestNotification(parameter);
}
else if (command.equals("clearchat")) {
g.clearChat();
}
else if (command.equals("resortuserlist")) {
g.resortUsers(channel);
}
else if (command.equals("proc")) {
g.printSystem("[Proc] "+ProcessManager.command(parameter));
}
else if (c.command(channel, command, parameter, msgId)) {
// Already done if true
}
else if (g.commandGui(command, parameter)) {
// Already done if true :P
}
// Has to be tested last, so regular commands with the same name take
// precedence
else if (customCommands.containsCommand(command, channel)) {
customCommand(channel, command, parameter);
}
else if (command.equals("debug")) {
String[] split = parameter.split(" ", 2);
String actualCommand = split[0];
String actualParamter = split[1];
testCommands(channel, actualCommand, actualParamter);
}
//--------------------
// Only for testing
else if (Chatty.DEBUG || settings.getBoolean("debugCommands")) {
testCommands(channel, command, parameter);
}
//----------------------
else {
g.printLine("Unknown command: "+command+" (Remember you can also "
+ "enter Twitch Chat Commands with a point in front: \".mods\")");
return false;
}
return true;
}
private void testCommands(String channel, String command, String parameter) {
if (command.equals("addchans")) {
String[] splitSpace = parameter.split(" ");
String[] split2 = splitSpace[0].split(",");
for (String chan : split2) {
g.printLine(chan, "test");
}
} else if (command.equals("settestuser")) {
String[] split = parameter.split(" ");
createTestUser(split[0], split[1]);
} else if (command.equals("setemoteset")) {
testUser.setEmoteSets(parameter);
} else if (command.equals("setemoteset2")) {
getSpecialUser().setEmoteSets(parameter);
} else if (command.equals("getemoteset")) {
g.printLine(g.emoticons.getEmoticons(Integer.parseInt(parameter)).toString());
} else if (command.equals("testcolor")) {
testUser.setColor(parameter);
} else if (command.equals("testupdatenotification")) {
g.setUpdateAvailable("[test]");
} else if (command.equals("testannouncement")) {
g.setAnnouncementAvailable(Boolean.parseBoolean(parameter));
} else if (command.equals("removechan")) {
g.removeChannel(parameter);
} else if (command.equals("testtimer")) {
new Thread(new TestTimer(this, new Integer(parameter))).start();
} // else if (command.equals("usertest")) {
// System.out.println(users.getChannelsAndUsersByUserName(parameter));
// }
// else if (command.equals("insert2")) {
// g.printLine("\u0E07");
// }
else if (command.equals("bantest")) {
int duration = -1;
String reason = "";
if (parameter != null) {
String[] split = parameter.split(" ", 2);
duration = Integer.parseInt(split[0]);
if (split.length > 1) {
reason = split[1];
}
}
g.userBanned(testUser, duration, reason, null);
} else if (command.equals("bantest2")) {
String[] split = parameter.split(" ", 3);
int duration = -1;
if (split.length > 1) {
duration = Integer.parseInt(split[1]);
}
String reason = "";
if (split.length > 2) {
reason = split[2];
}
g.userBanned(c.getUser(channel, split[0]), duration, reason, null);
} else if (command.equals("userjoined")) {
c.userJoined("#test", parameter);
} else if (command.equals("echomessage")) {
String[] parts = parameter.split(" ");
g.printMessage(parts[0], testUser, parts[1], false, null, 0);
} else if (command.equals("loadffz")) {
frankerFaceZ.requestEmotes(parameter, true);
} else if (command.equals("testtw")) {
g.showTokenWarning();
} else if (command.equals("tsonline")) {
testStreamInfo.set(parameter, "Game", 123, -1);
g.addStreamInfo(testStreamInfo);
} else if (command.equals("tsoffline")) {
testStreamInfo.setOffline();
g.addStreamInfo(testStreamInfo);
} else if (command.equals("testspam")) {
g.printLine("test" + spamProtection.getAllowance() + spamProtection.tryMessage());
} else if (command.equals("tsv")) {
testStreamInfo.set("Title", "Game", Integer.parseInt(parameter), -1);
} else if (command.equals("tsvs")) {
System.out.println(testStreamInfo.getViewerStats(true));
} else if (command.equals("tsaoff")) {
StreamInfo info = api.getStreamInfo(g.getActiveStream(), null);
info.setOffline();
} else if (command.equals("tsaon")) {
StreamInfo info = api.getStreamInfo(g.getActiveStream(), null);
info.set("Test", "Game", 12, System.currentTimeMillis() - 1000);
} else if (command.equals("usericonsinfo")) {
usericonManager.debug();
} else if (command.equals("userlisttest")) {
g.printMessage("test1", testUser, "short message", false, null, 0);
g.printMessage("test2", testUser, "short message2", false, null, 0);
g.printCompact("test3", "MOD", testUser);
g.printCompact("test3", "MOD", testUser);
g.printCompact("test3", "MOD", testUser);
g.printCompact("test3", "MOD", testUser);
g.printCompact("test3", "MOD", testUser);
g.printCompact("test3", "MOD", testUser);
g.printCompact("test3", "MOD", testUser);
g.printMessage("test3", testUser, "longer message abc hmm fwef wef wef wefwe fwe ewfwe fwef wwefwef"
+ "fjwfjfwjefjwefjwef wfejfkwlefjwoefjwf wfjwoeifjwefiowejfef wefjoiwefj", false, null, 0);
g.printMessage("test3", testUser, "longer message abc hmm fwef wef wef wefwe fwe ewfwe fwef wwefwef"
+ "fjwfjfwjefjwefjwoeifjwefiowejfef wefjoiwefj", false, null, 0);
g.printMessage("test3", testUser, "longer wef wef wefwe fwe ewfwe fwef wwefwef"
+ "fjwfjfwjefjwefjwef wfejfkwlefjwoefjwf wfjwoeifjwefiowejfef wefjoiwefj", false, null, 0);
g.printCompact("test4", "MOD", testUser);
g.printCompact("test5", "MOD", testUser);
g.printCompact("test6", "MOD", testUser);
g.printCompact("test7", "MOD", testUser);
g.printCompact("test8", "MOD", testUser);
g.printCompact("test9", "MOD", testUser);
g.printMessage("test10", testUser, "longer message abc hmm fwef wef wef wefwe fwe ewfwe fwef wwefwef"
+ "fjwfjfwjefjwefjwef wfejfkwlefjwoefjwf wfjwoeifjwefiowejfef wefjoiwefj", false, null, 0);
} else if (command.equals("requestfollowers")) {
api.getFollowers(parameter);
} else if (command.equals("simulate2")) {
c.simulate(parameter);
} else if (command.equals("simulate")) {
if (parameter.equals("bits")) {
parameter = "bits "+g.emoticons.getCheerEmotesString(null);
} else if (parameter.equals("bitslocal")) {
parameter = "bits "+g.emoticons.getCheerEmotesString(Helper.toStream(channel));
} else if (parameter.startsWith("bits ")) {
parameter = "bits "+parameter.substring("bits ".length());
}
String raw = RawMessageTest.simulateIRC(channel, parameter, c.getUsername());
if (raw != null) {
c.simulate(raw);
}
} else if (command.equals("lb")) {
String[] split = parameter.split("&");
String message = "";
for (int i=0;i<split.length;i++) {
if (!message.isEmpty()) {
message += "\r";
}
message += split[i];
}
sendMessage(channel, message);
} else if (command.equals("c1")) {
sendMessage(channel, (char)1+parameter);
} else if (command.equals("gc")) {
Runtime.getRuntime().gc();
LogUtil.logMemoryUsage();
} else if (command.equals("wsconnect")) {
frankerFaceZ.connectWs();
} else if (command.equals("wsdisconnect")) {
frankerFaceZ.disconnectWs();
} else if (command.equals("psconnect")) {
pubsub.connect();
} else if (command.equals("psdisconnect")) {
pubsub.disconnect();
} else if (command.equals("modactiontest")) {
List<String> args = new ArrayList<>();
args.add("tirean");
args.add("300");
args.add("still not using LiveSplit Autosplitter D:");
g.printModerationAction(new ModeratorActionData("", "", "tduvatest", "timeout", args, "tduva", ""), false);
} else if (command.equals("modactiontest2")) {
List<String> args = new ArrayList<>();
args.add("tduva");
args.add("fuck and stuff like that, rather long message and whatnot Kappa b "+new Random().nextInt(100));
g.printModerationAction(new ModeratorActionData("", "", parameter == null ? "tduvatest" : parameter, "twitchbot_rejected", args, "twitchbot", "TEST"+Math.random()), false);
} else if (command.equals("repeat")) {
String[] split = parameter.split(" ", 2);
int count = Integer.parseInt(split[0]);
for (int i=0;i<count;i++) {
commandInput(channel, "/"+split[1]);
}
} else if (command.equals("modactiontest3")) {
List<String> args = new ArrayList<>();
args.add("tduva");
g.printModerationAction(new ModeratorActionData("", "", "tduvatest", "approved_twitchbot_message", args, "tduvatest", "TEST"+Math.random()), false);
} else if (command.equals("loadsoferrors")) {
for (int i=0;i<10000;i++) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
Helper.unhandledException();
}
});
}
} else if (command.equals("getuserid")) {
if (parameter == null) {
g.printSystem("Parameter required.");
} else {
api.getUserIdAsap(r -> {
String result = r.getData().toString();
if (r.hasError()) {
result += " Error: "+r.getError();
}
g.printSystem(result);
}, parameter.split("[ ,]"));
}
} else if (command.equals("getuserids2")) {
api.getUserIDsTest2(parameter);
} else if (command.equals("getuserids3")) {
api.getUserIDsTest3(parameter);
}
}
public void anonCustomCommand(String channel, CustomCommand command, Parameters parameters) {
if (command.getError() != null) {
g.printLine("Custom command invalid: "+command.getError());
return;
}
if (channel == null) {
g.printLine("Custom command: Not on a channel");
return;
}
String result = customCommands.command(command, parameters, channel);
if (result == null) {
g.printLine("Custom command: Insufficient parameters/data");
} else if (result.isEmpty()) {
g.printLine("Custom command: No action specified");
} else {
textInput(channel, result);
}
}
public void customCommand(String channel, String command, String parameter) {
if (channel == null) {
g.printLine("Custom command: Not on a channel");
return;
}
if (!customCommands.containsCommand(command, channel)) {
g.printLine("Custom command not found: "+command);
return;
}
String result = customCommands.command(command, Parameters.create(parameter), channel);
if (result == null) {
g.printLine("Custom command '"+command+"': Insufficient parameters/data");
} else if (result.isEmpty()) {
// This shouldn't actually happen if edited through the settings,
// which should trim() out whitespace, so that the command won't
// have a result if it's empty and thus won't be added as a command.
// Although it can also happen if the command just contains a \
// (which is interpreted as an escape character).
g.printLine("Custom command '"+command+"': No action specified");
} else {
// Check what command is called in the result of this command
String[] resultSplit = result.split(" ", 2);
String resultCommand = resultSplit[0];
if (resultCommand.startsWith("/")
&& customCommands.containsCommand(resultCommand.substring(1), channel)) {
g.printLine("Custom command '"+command+"': Calling another custom "
+ "command ('"+resultCommand.substring(1)+"') is not allowed");
} else {
textInput(channel, result);
}
}
}
/**
* Adds or removes the name given in the parameter on the ignore list. This
* is done for either regular ignores (chat) and/or whisper ignores
* depending on the given type.
*
* Outputs a message with the new state depending on whether any change (at
* least one list was changed) occured or not.
*
* @param parameter The first word is used as name to ignore/unignore
* @param type Can be "chat", "whisper" or null to affect both lists
* @param ignore true to ignore, false to unignore
*/
public void commandSetIgnored(String parameter, String type, boolean ignore) {
if (parameter != null && !parameter.isEmpty()) {
String[] split = parameter.split(" ");
String name = split[0].toLowerCase();
String message = "";
List<String> setting = new ArrayList<>();
if (type == null || type.equals("chat")) {
message = "in chat";
setting.add("ignoredUsers");
}
if (type == null || type.equals("whisper")) {
message = StringUtil.append(message, "/", "from whispering you");
setting.add("ignoredUsersWhisper");
}
boolean changed = false;
for (String s : setting) {
if (ignore) {
if (settings.setAdd(s, name)) {
changed = true;
}
} else {
if (settings.listRemove(s, name)) {
changed = true;
}
}
}
if (changed) {
if (ignore) {
g.printSystem(String.format("Ignore: '%s' now ignored %s",
name, message));
} else {
g.printSystem(String.format("Ignore: '%s' no longer ignored %s",
name, message));
}
} else {
if (ignore) {
g.printSystem(String.format("Ignore: '%s' already ignored %s",
name, message));
} else {
g.printSystem(String.format("Ignore: '%s' not ignored %s",
name, message));
}
}
} else {
g.printSystem("Ignore: Invalid name");
}
}
private void commandServer(String parameter) {
if (parameter == null) {
g.printLine("Usage: /server <address>[:port]");
return;
}
String[] split = parameter.split(":");
if (split.length == 1) {
prepareConnectionAnyChannel(split[0], getPorts());
} else if (split.length == 2) {
prepareConnectionAnyChannel(split[0], split[1]);
} else {
g.printLine("Invalid format. Usage: /server <address>[:port]");
}
}
/**
* Command to join channel entered.
*
* @param channel
*/
public void commandJoinChannel(String channel) {
if (channel == null) {
g.printLine("A channel to join needs to be specified.");
} else {
channel = StringUtil.toLowerCase(channel.trim());
c.joinChannel(channel);
}
}
/**
* Command to part channel entered.
*
* @param channel
*/
private void commandPartChannel(String channel) {
if (channel == null || channel.isEmpty()) {
g.printLine("No channel to leave.");
} else {
closeChannel(channel);
}
}
/**
* React to action message (/me) command and send message if on channel.
*
* @param channel The channel to send the message to
* @param message The message to send
*/
private void commandActionMessage(String channel, String message) {
if (message != null) {
sendActionMessage(channel, message);
} else {
g.printLine("Usage: /me <message>");
}
}
public void sendActionMessage(String channel, String message) {
if (c.onChannel(channel, true)) {
if (c.sendSpamProtectedMessage(channel, message, true)) {
g.printMessage(channel, c.localUserJoined(channel), message, true, null, 0);
} else {
g.printLine("# Action Message not sent to prevent ban: " + message);
}
}
}
private void commandCustomMessage(String parameter) {
if (parameter != null && !parameter.isEmpty()) {
String[] split = parameter.split(" ", 2);
if (split.length == 2) {
String to = split[0];
String message = split[1];
c.sendSpamProtectedMessage(to, message, false);
g.printLine(String.format("-> %s: %s", to, message));
return;
}
}
g.printSystem("Invalid parameters.");
}
public void commandReconnect() {
if (c.disconnect()) {
c.reconnect();
} else {
g.printLine("Could not reconnect.");
}
}
private void commandCustomCompletion(String parameter) {
String usage = "Usage: /customCompletion <add/set/remove> <item> <value>";
if (parameter == null) {
g.printLine(usage);
return;
}
String[] split = parameter.split(" ", 3);
if (split.length < 2) {
g.printLine(usage);
} else {
String type = split[0];
String key = split[1];
if (type.equals("add") || type.equals("set")) {
if (split.length < 3) {
g.printLine("Invalid number of parameters for adding completion item.");
} else {
String value = split[2];
if (!type.equals("set") && settings.mapGet("customCompletion", key) != null) {
g.printLine("Completion item '"+key+"' already exists, use '/customCompletion set <key> <value>' to overwrite");
} else {
settings.mapPut("customCompletion", key, value);
g.printLine("Set custom completion '"+key+"' to '"+value+"'");
}
}
} else if (type.equals("remove")) {
settings.mapRemove("customCompletion", key);
g.printLine("Removed '"+key+"' from custom completion");
} else {
g.printLine(usage);
}
}
}
/**
* Follows the stream given in the parameter, or the channel if no parameter
* is given.
*
* @param channel
* @param parameter
*/
public void commandFollow(String channel, String parameter) {
String user = settings.getString("username");
String target = Helper.toStream(channel);
if (parameter != null && !parameter.isEmpty()) {
target = Helper.toStream(parameter.trim());
}
if (!Helper.validateStream(target)) {
g.printSystem("No valid channel to follow.");
return;
}
if (!Helper.validateStream(user)) {
g.printSystem("No valid username.");
return;
}
api.followChannel(user, target);
}
public void commandUnfollow(String channel, String parameter) {
String user = settings.getString("username");
String target = Helper.toStream(channel);
if (parameter != null && !parameter.isEmpty()) {
target = Helper.toStream(parameter.trim());
}
if (!Helper.validateStream(target)) {
g.printSystem("No valid channel to unfollow.");
return;
}
if (!Helper.validateStream(user)) {
g.printSystem("No valid username.");
return;
}
api.unfollowChannel(user, target);
}
public void commandAddStreamHighlight(String channel, String parameter) {
g.printLine(channel, streamHighlights.addHighlight(channel, parameter));
}
public void commandOpenStreamHighlights(String channel) {
g.printLine(channel, streamHighlights.openFile());
}
public void modCommandAddStreamHighlight(User user, String message) {
// Stream Highlights
String result = streamHighlights.modCommand(user, message);
if (result != null) {
result = user.getDisplayNick() + ": " + result;
if (settings.getBoolean("streamHighlightChannelRespond")) {
sendMessage(user.getChannel(), result);
} else {
g.printLine(user.getChannel(), result);
}
}
}
private void commandRefresh(String channel, String parameter) {
if (!Helper.validateChannel(channel)) {
channel = null;
}
if (parameter == null) {
g.printLine("Usage: /refresh <type> (see help)");
} else if (parameter.equals("emoticons")) {
g.printLine("Refreshing emoticons.. (this can take a few seconds)");
refreshRequests.add("emoticons");
//Emoticons.clearCache(Emoticon.Type.TWITCH);
api.requestEmoticons(true);
} else if (parameter.equals("bits")) {
g.printLine("Refreshing bits..");
refreshRequests.add("bits");
api.getCheers(channel, true);
} else if (parameter.equals("badges")) {
if (!Helper.validateChannel(channel)) {
g.printLine("Must be on a channel to use this.");
} else {
g.printLine("Refreshing badges for " + channel + "..");
refreshRequests.add("badges");
api.getGlobalBadges(true);
api.getRoomBadges(Helper.toStream(channel), true);
}
} else if (parameter.equals("ffz")) {
if (channel == null || channel.isEmpty()) {
g.printLine("Must be on a channel to use this.");
} else {
g.printLine("Refreshing FFZ emotes for "+channel+"..");
refreshRequests.add("ffz");
frankerFaceZ.requestEmotes(channel, true);
}
} else if (parameter.equals("ffzglobal")) {
g.printLine("Refreshing global FFZ emotes..");
refreshRequests.add("ffzglobal");
frankerFaceZ.requestGlobalEmotes(true);
} else if (parameter.equals("bttvemotes")) {
g.printLine("Refreshing BTTV emotes..");
refreshRequests.add("bttvemotes");
bttvEmotes.requestEmotes("$global$", true);
bttvEmotes.requestEmotes(channel, true);
} else if (parameter.equals("emotesets")) {
g.printLine("Refreshing emoteset information..");
refreshRequests.add("emotesets");
twitchemotes.requestEmotesets(true);
} else {
g.printLine("Usage: /refresh <type> (invalid type, see help)");
}
}
public User getSpecialUser() {
return c.getSpecialUser();
}
/**
* Outputs the emotesets for the local user. This might not work correctly
* if the user is changed or the emotesets change during the session.
*/
private void commandMyEmotes() {
Set<Integer> emotesets = getSpecialUser().getEmoteSet();
if (emotesets.isEmpty()) {
g.printLine("No subscriber emotes found. (Only works if you joined"
+ " any channel before.)");
} else {
StringBuilder b = new StringBuilder("Your subemotes: ");
String sep = "";
for (Integer emoteset : emotesets) {
b.append(sep);
if (Emoticons.isTurboEmoteset(emoteset)) {
b.append("Turbo/Prime emotes");
} else {
String sep2 = "";
for (Emoticon emote : g.emoticons.getEmoticons(emoteset)) {
b.append(sep2);
b.append(emote.code);
sep2 = ", ";
}
}
sep = " / ";
}
g.printLine(b.toString());
}
}
private void commandFFZ(String channel) {
Set<Emoticon> output;
StringBuilder b = new StringBuilder();
if (channel == null) {
b.append("Global FFZ emotes: ");
output = Emoticons.filterByType(g.emoticons.getGlobalTwitchEmotes(), Emoticon.Type.FFZ);
} else {
b.append("This channel's FFZ emotes: ");
Set<Emoticon> emotes = g.emoticons.getEmoticons(Helper.toStream(channel));
output = Emoticons.filterByType(emotes, Emoticon.Type.FFZ);
}
if (output.isEmpty()) {
b.append("None found.");
}
String sep = "";
for (Emoticon emote : output) {
b.append(sep);
b.append(emote.code);
sep = ", ";
}
g.printLine(channel, b.toString());
}
private void commandFFZFollowing(String channel, String parameter) {
String stream = Helper.toStream(channel);
if (stream == null) {
g.printSystem("FFZ: No valid channel.");
} else if (!c.isRegistered()) {
g.printSystem("FFZ: You have to be connected to use this command.");
} else {
if (!stream.equals(c.getUsername())) {
g.printSystem("FFZ: You may only be able to run this command on your own channel.");
}
parameter = parameter.substring("following".length()).trim();
frankerFaceZ.setFollowing(c.getUsername(), stream, parameter);
}
}
/**
* Add a debugmessage to the GUI. If the GUI wasn't created yet, add it
* to a cache that is send to the GUI once it is created. This is done
* automatically when a debugmessage is added after the GUI was created.
*
* @param line
*/
public void debug(String line) {
if (shuttingDown) {
return;
}
synchronized(cachedDebugMessages) {
if (g == null) {
cachedDebugMessages.add(line);
} else {
if (!cachedDebugMessages.isEmpty()) {
g.printDebug("[Start of cached messages]");
for (String cachedLine : cachedDebugMessages) {
g.printDebug(cachedLine);
}
g.printDebug("[End of cached messages]");
// No longer used
cachedDebugMessages.clear();
}
g.printDebug(line);
}
}
}
public void debugFFZ(String line) {
if (shuttingDown || g == null) {
return;
}
g.printDebugFFZ(line);
}
public void debugPubSub(String line) {
if (shuttingDown || g == null) {
return;
}
g.printDebugPubSub(line);
}
/**
* Output a warning.
*
* @param line
*/
public final void warning(String line) {
if (shuttingDown) {
return;
}
synchronized(cachedWarningMessages) {
if (g == null) {
cachedWarningMessages.add(line);
} else {
if (!cachedWarningMessages.isEmpty()) {
for (String cachedLine : cachedWarningMessages) {
g.printLine(cachedLine);
}
cachedWarningMessages.clear();
}
if (line != null) {
g.printLine(line);
}
}
}
}
private class PubSubResults implements PubSubListener {
@Override
public void messageReceived(Message message) {
if (message.data != null && message.data instanceof ModeratorActionData) {
ModeratorActionData data = (ModeratorActionData)message.data;
if (data.stream != null) {
g.printModerationAction(data, data.created_by.equals(c.getUsername()));
chatLog.modAction(data);
User user = c.getUser(Helper.toChannel(data.stream), data.created_by);
user.addModAction(data.getCommandAndParameters());
g.updateUserinfo(user);
}
}
}
@Override
public void info(String info) {
g.printDebugPubSub(info);
}
}
/**
* Redirects request results from the API.
*/
private class TwitchApiResults implements TwitchApiResultListener {
@Override
public void receivedEmoticons(Set<Emoticon> emoticons) {
g.addEmoticons(emoticons);
if (refreshRequests.contains("emoticons")) {
g.printLine("Emoticons list updated.");
refreshRequests.remove("emoticons");
}
}
@Override
public void tokenVerified(String token, TokenInfo tokenInfo) {
g.tokenVerified(token, tokenInfo);
}
@Override
public void runCommercialResult(String stream, String text, RequestResultCode result) {
commercialResult(stream, text, result);
}
@Override
public void receivedChannelInfo(String channel, ChannelInfo info, RequestResultCode result) {
g.setChannelInfo(channel, info, result);
}
@Override
public void putChannelInfoResult(RequestResultCode result) {
g.putChannelInfoResult(result);
}
@Override
public void accessDenied() {
checkToken();
}
@Override
public void receivedUsericons(List<Usericon> icons) {
usericonManager.addDefaultIcons(icons);
if (refreshRequests.contains("badges2")) {
g.printLine("Badges2 updated.");
refreshRequests.remove("badges2");
}
if (refreshRequests.contains("badges")) {
g.printLine("Badges updated.");
refreshRequests.remove("badges");
}
}
@Override
public void receivedFollowers(FollowerInfo followerInfo) {
g.setFollowerInfo(followerInfo);
followerInfoNames(followerInfo);
receivedFollowerOrSubscriberCount(followerInfo);
}
/**
* Set follower/subscriber count in StreamInfo and send to Stream Status
* Writer.
*
* @param followerInfo
*/
private void receivedFollowerOrSubscriberCount(FollowerInfo followerInfo) {
if (followerInfo.requestError) {
return;
}
StreamInfo streamInfo = api.getStreamInfo(followerInfo.stream, null);
boolean changed = false;
if (followerInfo.type == Follower.Type.SUBSCRIBER) {
changed = streamInfo.setSubscriberCount(followerInfo.total);
} else if (followerInfo.type == Follower.Type.FOLLOWER) {
changed = streamInfo.setFollowerCount(followerInfo.total);
}
if (changed && streamInfo.isValid()) {
streamStatusWriter.streamStatus(streamInfo);
}
}
@Override
public void newFollowers(FollowerInfo followerInfo) {
g.followerSound(Helper.toValidChannel(followerInfo.stream));
}
@Override
public void receivedSubscribers(FollowerInfo info) {
g.setSubscriberInfo(info);
followerInfoNames(info);
receivedFollowerOrSubscriberCount(info);
}
private void followerInfoNames(FollowerInfo info) {
}
@Override
public void receivedDisplayName(String name, String displayName) {
}
@Override
public void receivedServer(String channel, String server) {
LOGGER.info("Received server info: "+channel+"/"+server);
if (fixServer && server != null) {
String s = Helper.getServer(server);
int p = Helper.getPort(server);
c.disconnect();
prepareConnectionAnyChannel(s, String.valueOf(p));
}
else {
g.printLine(channel, "An error occured requesting server info.");
}
}
@Override
public void followResult(String message) {
g.printSystem(message);
}
@Override
public void receivedChatInfo(ChatInfo chatInfo) {
g.setChatInfo(chatInfo);
}
@Override
public void autoModResult(String result, String msgId) {
g.autoModRequestResult(result, msgId);
}
@Override
public void receivedCheerEmoticons(Set<CheerEmoticon> emoticons) {
if (refreshRequests.contains("bits")) {
g.printLine("Bits received.");
refreshRequests.remove("bits");
}
g.setCheerEmotes(emoticons);
}
}
private void checkToken() {
api.checkToken(settings.getString("token"));
}
// Webserver
public void startWebserver() {
if (webserver == null) {
webserver = new Webserver(new WebserverListener());
new Thread(webserver).start();
}
else {
LOGGER.warning("Webserver already running");
// When webserver is already running, it should be started
g.webserverStarted();
}
}
public void stopWebserver() {
if (webserver != null) {
webserver.stop();
}
else {
LOGGER.info("No webserver running, can't stop it");
}
}
private class WebserverListener implements Webserver.WebserverListener {
@Override
public void webserverStarted() {
g.webserverStarted();
}
@Override
public void webserverStopped() {
webserver = null;
}
@Override
public void webserverError(String error) {
g.webserverError(error);
webserver = null;
}
@Override
public void webserverTokenReceived(String token) {
g.webserverTokenReceived(token);
}
};
private class MyStreamInfoListener implements StreamInfoListener {
private final ConcurrentMap<StreamInfo, Object> notFoundInfoDone
= new ConcurrentHashMap<>();
/**
* The StreamInfo has been updated with new data from the API.
*
* This may still hold a lock from the StreamInfoManager.
*
* @param info
*/
@Override
public void streamInfoUpdated(StreamInfo info) {
g.updateState(true);
g.updateChannelInfo();
g.addStreamInfo(info);
String channel = "#"+info.getStream();
if (isChannelOpen(channel)) {
// Log viewerstats if channel is still open and thus a log
// is being written
chatLog.viewerstats(channel, info.getViewerStats(false));
if (info.getOnline() && info.isValid()) {
chatLog.viewercount(channel, info.getViewers());
}
}
streamStatusWriter.streamStatus(info);
if (info.isNotFound() && notFoundInfoDone.putIfAbsent(info, info) == null) {
g.printLine("** This channel doesn't seem to exist on Twitch. "
+ "You may not be able to join this channel, but trying"
+ " anyways. **");
}
}
/**
* Displays the new stream status. Prints to the channel of the stream,
* which should usually be open because only current channels get stream
* data requested, but check if its still open (request response is
* delayed and it could have been closed in the meantime).
*
* This may still hold a lock from the StreamInfoManager.
*
* @param info
* @param newStatus
*/
@Override
public void streamInfoStatusChanged(StreamInfo info, String newStatus) {
String channel = "#" + info.getStream();
if (isChannelOpen(channel)) {
if (settings.getBoolean("printStreamStatus")) {
g.printLine(channel, "~" + newStatus + "~");
}
g.setChannelNewStatus(channel, newStatus);
/**
* Only do warning/unhost stuff if stream is only at most 15
* minutes old. This prevents unhosting at the end of the stream
* when the status may change from online -> offline -> online
* due to cached data from the Twitch API and unhost when the
* streamer already hosted someone else intentionally.
*/
if (info.getOnline()
&& info.getTimeStartedWithPicnicAgo() < 15*60*1000
&& getHostedChannel(channel) != null) {
if (settings.getBoolean("autoUnhost")
&& c.onChannel(channel)
&& (
info.stream.equals(c.getUsername())
|| settings.listContains("autoUnhostStreams", info.stream)
)) {
c.sendCommandMessage(channel, ".unhost", "Trying to turn off host mode.. (Auto-Unhost)");
} else {
g.printLine(channel, "** Still hosting another channel while streaming. **");
}
}
}
if (info.getOnline() || !settings.getBoolean("ignoreOfflineNotifications")) {
g.statusNotification(channel, newStatus);
}
}
}
/**
* Log viewerstats for any open channels, which can be used to log any
* remaining data on all channels when the program is closed.
*/
private void logAllViewerstats() {
for (String channel : c.getOpenChannels()) {
logViewerstats(channel);
}
}
/**
* Gets the viewerstats for the given channel and logs them. This can be
* used to log any remaining data when a channel is closed or the program is
* exited.
*
* @param channel
*/
private void logViewerstats(String channel) {
if (Helper.isRegularChannel(channel)) {
ViewerStats stats = api.getStreamInfo(Helper.toStream(channel), null).getViewerStats(true);
chatLog.viewerstats(channel, stats);
}
}
/**
* For testing. This requires Chatty.DEBUG and Chatty.HOTKEY to be enabled.
*
* If enabled, AltGr+T can be used to trigger this method.
*/
public void testHotkey() {
// g.printMessage("", testUser, "abc 1", false);
//Helper.unhandledException();
//g.showTokenWarning();
//g.showTestNotification();
//logViewerstats("test");
g.showTokenWarning();
//g.testHotkey();
}
/**
* Tries to run a commercial on the given stream with the given length.
*
* Outputs a message about it in the appropriate channel.
*
* @param stream The stream to run the commercial in
* @param length The length of the commercial in seconds
*/
public void runCommercial(String stream, int length) {
if (stream == null || stream.isEmpty()) {
commercialResult(stream, "Can't run commercial, not on a channel.", TwitchApi.RequestResultCode.FAILED);
}
else {
String channel = "#"+stream;
if (isChannelOpen(channel)) {
g.printLine(channel, "Trying to run "+length+"s commercial..");
} else {
g.printLine("Trying to run "+length+"s commercial.. ("+stream+")");
}
api.runCommercial(stream, length);
}
}
/**
* Work with the result on trying to run a commercial, which mostly is
* returned by the Twitch API, but may also be immediately called if
* something is formally wrong (like no or empty stream name specified).
*
* Outputs an info text about the result to the appropriate channel and
* tells the GUI so a message can be displayed in the admin dialog.
*
* @param stream
* @param text
* @param result
*/
private void commercialResult(String stream, String text, RequestResultCode result) {
String channel = "#"+stream;
if (isChannelOpen(channel)) {
g.printLine(channel, text);
} else {
g.printLine(text+" ("+stream+")");
}
g.commercialResult(stream, text, result);
}
/**
* Receive FrankerFaceZ emoticons and icons.
*/
private class EmoticonsListener implements FrankerFaceZListener {
@Override
public void channelEmoticonsReceived(EmoticonUpdate emotes) {
g.updateEmoticons(emotes);
if (refreshRequests.contains("ffz")) {
g.printLine("FFZ emotes updated.");
refreshRequests.remove("ffz");
}
if (refreshRequests.contains("ffzglobal")) {
g.printLine("Global FFZ emotes updated.");
refreshRequests.remove("ffzglobal");
}
}
@Override
public void usericonsReceived(List<Usericon> icons) {
usericonManager.addDefaultIcons(icons);
}
@Override
public void botNamesReceived(Set<String> botNames) {
if (settings.getBoolean("botNamesFFZ")) {
botNameManager.addBotNames(null, botNames);
}
}
@Override
public void wsInfo(String info) {
g.printDebugFFZ(info);
}
@Override
public void authorizeUser(String code) {
c.sendSpamProtectedMessage("#frankerfacezauthorizer", "AUTH "+code, false);
}
@Override
public void wsUserInfo(String info) {
g.printSystem("FFZ: "+info);
}
}
/**
* Requests the third-party emotes for the channel, if enabled.
*
* @param channel The name of the channel (can be stream or channel)
*/
public void requestChannelEmotes(String channel) {
if (settings.getBoolean("ffz")) {
frankerFaceZ.requestEmotes(channel, false);
frankerFaceZ.autoUpdateFeatureFridayEmotes();
}
if (settings.getBoolean("bttvEmotes")) {
bttvEmotes.requestEmotes(channel, false);
}
}
private class EmoteListener implements EmoticonListener {
@Override
public void receivedEmoticons(Set<Emoticon> emoticons) {
g.addEmoticons(emoticons);
if (refreshRequests.contains("bttvemotes")) {
g.printLine("BTTV emotes updated.");
refreshRequests.remove("bttvemotes");
}
}
@Override
public void receivedBotNames(String stream, Set<String> names) {
if (settings.getBoolean("botNamesBTTV")) {
String channel = Helper.toValidChannel(stream);
botNameManager.addBotNames(channel, names);
}
}
}
private class TwitchemotesListener implements TwitchEmotesListener {
@Override
public void emotesetsReceived(Map<Integer, String> emotesetStreams) {
if (refreshRequests.contains("emotesets")) {
g.printLine("Emoteset information updated.");
refreshRequests.remove("emotesets");
}
g.setEmotesets(emotesetStreams);
c.setEmotesets(emotesetStreams);
}
}
/**
* Only used for testing. You have to restart Chatty for the spam protection
* in the connectin to change.
*
* @param value
*/
public void setLinesPerSeconds(String value) {
spamProtection.setLinesPerSeconds(value);
}
/**
* Exit the program. Do some cleanup first and save stuff to file (settings,
* addressbook, chatlogs).
*
* Should run in EDT.
*/
public void exit() {
shuttingDown = true;
saveSettings(true);
logAllViewerstats();
c.disconnect();
frankerFaceZ.disconnectWs();
pubsub.disconnect();
g.cleanUp();
chatLog.close();
System.exit(0);
}
/**
* Save all settings to file.
*
* @param onExit If true, this will save the settings only if they haven't
* already been saved with this being true before
*/
public void saveSettings(boolean onExit) {
if (onExit) {
if (settingsAlreadySavedOnExit) {
return;
}
settingsAlreadySavedOnExit = true;
}
LOGGER.info("Saving settings..");
System.out.println("Saving settings..");
// Prepare saving settings
if (g != null && g.guiCreated) {
g.saveWindowStates();
}
// Actually write settings to file
if (!onExit || !settings.getBoolean("dontSaveSettings")) {
addressbook.saveToFile();
settings.saveSettingsToJson();
}
}
private class SettingSaveListener implements SettingsListener {
@Override
public void aboutToSaveSettings(Settings settings) {
Collection<String> openChans;
if (SwingUtilities.isEventDispatchThread()) {
openChans = g.getOpenChannels();
} else {
openChans = c.getOpenChannels();
}
settings.setString("previousChannel", Helper.buildStreamsString(openChans));
EmoticonSizeCache.saveToFile();
}
}
private class Messages implements TwitchConnection.ConnectionListener {
private void checkModLogListen(User user) {
if (user.hasChannelModeratorRights() && user.getName().equals(c.getUsername())) {
pubsub.setLocalUsername(c.getUsername());
pubsub.listenModLog(Helper.toStream(user.getChannel()), settings.getString("token"));
}
}
@Override
public void onChannelJoined(User user) {
String channel = user.getChannel();
channelFavorites.addChannelToHistory(channel);
g.printLine(channel,"You have joined " + channel);
// Icons and FFZ/BTTV Emotes
//api.requestChatIcons(Helper.toStream(channel), false);
api.getGlobalBadges(false);
if (Helper.validateStreamStrict(user.getStream())) {
api.getRoomBadges(channel, false);
api.getCheers(channel, false);
requestChannelEmotes(channel);
frankerFaceZ.joined(channel);
checkModLogListen(user);
}
}
@Override
public void onChannelLeft(String channel) {
chatLog.info(channel, "You have left "+channel);
closeChannel(channel);
frankerFaceZ.left(channel);
pubsub.unlistenModLog(Helper.toStream(channel));
}
@Override
public void onJoin(User user) {
String channel = user.getChannel();
if (settings.getBoolean("showJoinsParts") && showUserInGui(user)) {
g.printCompact(channel,"JOIN", user);
}
g.playSound("joinPart", channel);
chatLog.compact(channel, "JOIN", user.getRegularDisplayNick());
}
@Override
public void onPart(User user) {
if (settings.getBoolean("showJoinsParts") && showUserInGui(user)) {
g.printCompact(user.getChannel(), "PART", user);
}
chatLog.compact(user.getChannel(), "PART", user.getRegularDisplayNick());
g.playSound("joinPart", user.getChannel());
}
@Override
public void onUserUpdated(User user) {
if (showUserInGui(user)) {
g.updateUser(user);
}
g.updateUserinfo(user);
checkModLogListen(user);
}
@Override
public void onChannelMessage(User user, String message, boolean action,
String emotes, String id, int bits) {
g.printMessage(user.getChannel(), user, message, action, emotes, bits, id);
if (!action) {
addressbookCommands(user.getChannel(), user, message);
modCommandAddStreamHighlight(user, message);
}
}
@Override
public void onNotice(String message) {
g.printLine("[Notice] "+message);
}
@Override
public void onInfo(String channel, String infoMessage) {
g.printLine(channel, infoMessage);
}
@Override
public void onInfo(String message) {
g.printLine(message);
}
@Override
public void onJoinAttempt(String channel) {
/**
* This should be the event where the channel is first opened, and
* the stream info should be output then. If the stream info is
* already valid, then it is output now, otherwise it is requested
* by this and output once it is received. Doing this later, like
* onJoin, won't work because opening the channel will always
* request stream info, so it might be output twice (once onJoin, a
* second time because it is new).
*/
if (!isChannelOpen(channel)) {
g.printStreamInfo(channel);
}
g.printLine(channel, "Joining "+channel+"..");
}
@Override
public void onUserAdded(User user) {
if (showUserInGui(user)) {
g.addUser(user.getChannel(), user);
}
}
private boolean showUserInGui(User user) {
if (!settings.getBoolean("ignoredUsersHideInGUI")) {
return true;
}
return !settings.listContains("ignoredUsers", user.getName());
}
@Override
public void onUserRemoved(User user) {
g.removeUser(user.getChannel(), user);
}
private final Pattern findId = Pattern.compile(
"([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})",
Pattern.CASE_INSENSITIVE);
@Override
public void onBan(User user, long duration, String reason, String targetMsgId) {
User localUser = c.getLocalUser(user.getChannel());
// Matcher m = findId.matcher(reason);
// String id = null;
// if (m.find()) {
// id = m.group();
// reason = reason.replace(id, "").trim();
// }
if (localUser != user && !localUser.hasModeratorRights()) {
// Remove reason if not the affected user and not a mod, to be
// consistent with other applications
reason = "";
}
g.userBanned(user, duration, reason, targetMsgId);
ChannelInfo channelInfo = api.getOnlyCachedChannelInfo(user.getName());
chatLog.userBanned(user.getChannel(), user.getRegularDisplayNick(),
duration, reason, channelInfo);
}
@Override
public void onRegistered() {
g.updateHighlightSetUsername(c.getUsername());
//pubsub.listenModLog(c.getUsername(), settings.getString("token"));
}
@Override
public void onMod(User user) {
boolean modMessagesEnabled = settings.getBoolean("showModMessages");
String channel = user.getChannel();
if (modMessagesEnabled && showUserInGui(user)) {
g.printCompact(channel, "MOD", user);
}
chatLog.compact(channel, "MOD", user.getRegularDisplayNick());
}
@Override
public void onUnmod(User user) {
boolean modMessagesEnabled = settings.getBoolean("showModMessages");
String channel = user.getChannel();
if (modMessagesEnabled && showUserInGui(user)) {
g.printCompact(channel, "UNMOD", user);
}
chatLog.compact(channel, "UNMOD", user.getRegularDisplayNick());
}
@Override
public void onDisconnect(int reason, String reasonMessage) {
//g.clearUsers();
if (reason == Irc.ERROR_REGISTRATION_FAILED) {
checkToken();
}
if (reason == Irc.ERROR_CONNECTION_CLOSED) {
pubsub.checkConnection();
}
}
@Override
public void onConnectionStateChanged(int state) {
g.updateState(true);
}
@Override
public void onSpecialUserUpdated() {
g.updateEmotesDialog();
g.updateEmoteNames();
}
@Override
public void onConnectError(String message) {
g.printLine(message);
}
@Override
public void onJoinError(Set<String> toJoin, String errorChannel, TwitchConnection.JoinError error) {
if (error == TwitchConnection.JoinError.NOT_REGISTERED) {
String validChannels = Helper.buildStreamsString(toJoin);
if (c.isOffline()) {
g.openConnectDialog(validChannels);
}
g.printLine("Can't join '" + validChannels + "' (not connected)");
} else if (error == TwitchConnection.JoinError.ALREADY_JOINED) {
if (toJoin.size() == 1) {
g.switchToChannel(errorChannel);
} else {
g.printLine("Can't join '" + errorChannel + "' (already joined)");
}
} else if (error == TwitchConnection.JoinError.INVALID_NAME) {
g.printLine("Can't join '"+errorChannel+"' (invalid channelname)");
}
}
@Override
public void onRawReceived(String text) {
ircLogger.onRawReceived(text);
}
@Override
public void onRawSent(String text) {
ircLogger.onRawSent(text);
}
@Override
public void onGlobalInfo(String message) {
g.printLineAll(message);
}
@Override
public void onUserlistCleared(String channel) {
g.clearUsers(channel);
}
@Override
public void onHost(String channel, String target) {
}
@Override
public void onChannelCleared(String channel) {
if (channel != null) {
if (settings.getBoolean("clearChatOnChannelCleared")) {
g.clearChat(channel);
}
g.printLine(channel, "Channel was cleared by a moderator.");
} else {
g.printLine("One of the channels you joined was cleared by a moderator.");
}
}
@Override
public void onWhisper(User user, String message, String emotes) {
w.whisperReceived(user, message, emotes);
}
@Override
public void onSubscriberNotification(String channel, User user, String text, String message, int months, String emotes) {
//System.out.println(channel+" "+user+" "+months);
g.printSubscriberMessage(channel, user, text, message, months, emotes);
// May be using dummy User if from twitchnotify that doesn't contain a propery name tag
if (user.getName().isEmpty()) {
return;
}
String name = user.getName();
if (!settings.getString("abSubMonthsChan").equalsIgnoreCase(channel)) {
return;
}
List<Long> monthsDef = new ArrayList<>();
settings.getList("abSubMonths", monthsDef);
long max = 0;
for (long entry : monthsDef) {
if (months >= entry && entry > max) {
max = entry;
}
}
if (name != null && max > 0) {
String cat = max+"months";
addressbook.add(name, cat);
LOGGER.info(String.format("[Subscriber] Added '%s' with category '%s'",
name, cat));
}
}
@Override
public void onSpecialMessage(String name, String message) {
g.printLine(name, message);
}
@Override
public void onRoomId(String channel, String id) {
if (Helper.isRegularChannel(channel)) {
api.setUserId(Helper.toStream(channel), id);
}
}
}
private class IrcLogger {
private final Logger IRC_LOGGER = Logger.getLogger(TwitchClient.IrcLogger.class.getName());
IrcLogger() {
IRC_LOGGER.setUseParentHandlers(false);
IRC_LOGGER.addHandler(Logging.getIrcFileHandler());
}
public void onRawReceived(String text) {
if (settings.getBoolean("debugLogIrc")) {
g.printDebugIrc(">> " + text);
}
if (settings.getBoolean("debugLogIrcFile")) {
IRC_LOGGER.info(">> " + text);
}
}
public void onRawSent(String text) {
if (settings.getBoolean("debugLogIrc")) {
g.printDebugIrc("<<< " + text);
}
if (settings.getBoolean("debugLogIrcFile")) {
IRC_LOGGER.info("SENT: " + text);
}
}
}
public ChannelState getChannelState(String channel) {
return c.getChannelState(channel);
}
private class ChannelStateUpdater implements ChannelStateListener {
@Override
public void channelStateUpdated(ChannelState state) {
g.updateState(true);
}
}
public boolean isWhisperAvailable() {
return w.isAvailable();
}
private class MyWhisperListener implements WhisperListener {
@Override
public void whisperReceived(User user, String message, String emotes) {
g.printMessage(WhisperManager.WHISPER_CHANNEL, user, message, false, emotes, 0);
g.updateUser(user);
}
@Override
public void info(String message) {
g.printLine(message);
}
@Override
public void whisperSent(User to, String message) {
g.printMessage(WhisperManager.WHISPER_CHANNEL, to, message, true, null, 0);
}
}
}