@@ -44,4 +44,6 @@ public @interface ConfigItem
|
|||||||
boolean hidden() default false;
|
boolean hidden() default false;
|
||||||
|
|
||||||
String warning() default "";
|
String warning() default "";
|
||||||
|
|
||||||
|
boolean secret() default false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ import javax.swing.JFrame;
|
|||||||
import javax.swing.JLabel;
|
import javax.swing.JLabel;
|
||||||
import javax.swing.JOptionPane;
|
import javax.swing.JOptionPane;
|
||||||
import javax.swing.JPanel;
|
import javax.swing.JPanel;
|
||||||
|
import javax.swing.JPasswordField;
|
||||||
import javax.swing.JScrollPane;
|
import javax.swing.JScrollPane;
|
||||||
import javax.swing.JSpinner;
|
import javax.swing.JSpinner;
|
||||||
import javax.swing.JTextArea;
|
import javax.swing.JTextArea;
|
||||||
@@ -66,6 +67,7 @@ import javax.swing.border.EmptyBorder;
|
|||||||
import javax.swing.event.ChangeListener;
|
import javax.swing.event.ChangeListener;
|
||||||
import javax.swing.event.DocumentEvent;
|
import javax.swing.event.DocumentEvent;
|
||||||
import javax.swing.event.DocumentListener;
|
import javax.swing.event.DocumentListener;
|
||||||
|
import javax.swing.text.JTextComponent;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import net.runelite.client.config.ChatColorConfig;
|
import net.runelite.client.config.ChatColorConfig;
|
||||||
import net.runelite.client.config.Config;
|
import net.runelite.client.config.Config;
|
||||||
@@ -351,9 +353,20 @@ public class ConfigPanel extends PluginPanel
|
|||||||
|
|
||||||
if (cid.getType() == String.class)
|
if (cid.getType() == String.class)
|
||||||
{
|
{
|
||||||
JTextArea textField = new JTextArea();
|
JTextComponent textField;
|
||||||
textField.setLineWrap(true);
|
|
||||||
textField.setWrapStyleWord(true);
|
if (cid.getItem().secret())
|
||||||
|
{
|
||||||
|
textField = new JPasswordField();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
final JTextArea textArea = new JTextArea();
|
||||||
|
textArea.setLineWrap(true);
|
||||||
|
textArea.setWrapStyleWord(true);
|
||||||
|
textField = textArea;
|
||||||
|
}
|
||||||
|
|
||||||
textField.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
|
textField.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
|
||||||
textField.setText(configManager.getConfiguration(cd.getGroup().value(), cid.getItem().keyName()));
|
textField.setText(configManager.getConfiguration(cd.getGroup().value(), cid.getItem().keyName()));
|
||||||
|
|
||||||
@@ -548,9 +561,9 @@ public class ConfigPanel extends PluginPanel
|
|||||||
JSpinner spinner = (JSpinner) component;
|
JSpinner spinner = (JSpinner) component;
|
||||||
configManager.setConfiguration(cd.getGroup().value(), cid.getItem().keyName(), "" + spinner.getValue());
|
configManager.setConfiguration(cd.getGroup().value(), cid.getItem().keyName(), "" + spinner.getValue());
|
||||||
}
|
}
|
||||||
else if (component instanceof JTextArea)
|
else if (component instanceof JTextComponent)
|
||||||
{
|
{
|
||||||
JTextArea textField = (JTextArea) component;
|
JTextComponent textField = (JTextComponent) component;
|
||||||
configManager.setConfiguration(cd.getGroup().value(), cid.getItem().keyName(), textField.getText());
|
configManager.setConfiguration(cd.getGroup().value(), cid.getItem().keyName(), textField.getText());
|
||||||
}
|
}
|
||||||
else if (component instanceof JColorChooser)
|
else if (component instanceof JColorChooser)
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018, Adam <Adam@sigterm.info>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
* list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
package net.runelite.client.plugins.twitch;
|
||||||
|
|
||||||
|
import net.runelite.client.config.Config;
|
||||||
|
import net.runelite.client.config.ConfigGroup;
|
||||||
|
import net.runelite.client.config.ConfigItem;
|
||||||
|
|
||||||
|
@ConfigGroup("twitch")
|
||||||
|
public interface TwitchConfig extends Config
|
||||||
|
{
|
||||||
|
@ConfigItem(
|
||||||
|
keyName = "username",
|
||||||
|
name = "Username",
|
||||||
|
description = "Twitch Username",
|
||||||
|
position = 0
|
||||||
|
)
|
||||||
|
String username();
|
||||||
|
|
||||||
|
@ConfigItem(
|
||||||
|
keyName = "oauth",
|
||||||
|
name = "OAuth Token",
|
||||||
|
description = "Enter your OAuth token here. This can be found at http://www.twitchapps.com/tmi/",
|
||||||
|
secret = true,
|
||||||
|
position = 1
|
||||||
|
)
|
||||||
|
String oauthToken();
|
||||||
|
|
||||||
|
@ConfigItem(
|
||||||
|
keyName = "channel",
|
||||||
|
name = "Channel",
|
||||||
|
description = "Username of Twitch chat to join",
|
||||||
|
position = 2
|
||||||
|
)
|
||||||
|
String channel();
|
||||||
|
}
|
||||||
@@ -0,0 +1,227 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018, Adam <Adam@sigterm.info>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
* list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
package net.runelite.client.plugins.twitch;
|
||||||
|
|
||||||
|
import com.google.common.eventbus.Subscribe;
|
||||||
|
import com.google.inject.Provides;
|
||||||
|
import java.time.temporal.ChronoUnit;
|
||||||
|
import java.util.Map;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import net.runelite.api.ChatMessageType;
|
||||||
|
import net.runelite.api.Client;
|
||||||
|
import net.runelite.api.GameState;
|
||||||
|
import net.runelite.api.events.ConfigChanged;
|
||||||
|
import net.runelite.client.chat.ChatMessageBuilder;
|
||||||
|
import net.runelite.client.chat.ChatMessageManager;
|
||||||
|
import net.runelite.client.chat.ChatboxInputListener;
|
||||||
|
import net.runelite.client.chat.CommandManager;
|
||||||
|
import net.runelite.client.chat.QueuedMessage;
|
||||||
|
import net.runelite.client.config.ConfigManager;
|
||||||
|
import net.runelite.client.events.ChatboxInput;
|
||||||
|
import net.runelite.client.events.PrivateMessageInput;
|
||||||
|
import net.runelite.client.plugins.Plugin;
|
||||||
|
import net.runelite.client.plugins.PluginDescriptor;
|
||||||
|
import net.runelite.client.plugins.twitch.irc.TwitchIRCClient;
|
||||||
|
import net.runelite.client.plugins.twitch.irc.TwitchListener;
|
||||||
|
import net.runelite.client.task.Schedule;
|
||||||
|
|
||||||
|
@PluginDescriptor(
|
||||||
|
name = "Twitch",
|
||||||
|
description = "Integrates Twitch chat",
|
||||||
|
enabledByDefault = false
|
||||||
|
)
|
||||||
|
@Slf4j
|
||||||
|
public class TwitchPlugin extends Plugin implements TwitchListener, ChatboxInputListener
|
||||||
|
{
|
||||||
|
@Inject
|
||||||
|
private TwitchConfig twitchConfig;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private Client client;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private ChatMessageManager chatMessageManager;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private CommandManager commandManager;
|
||||||
|
|
||||||
|
private TwitchIRCClient twitchIRCClient;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void startUp()
|
||||||
|
{
|
||||||
|
connect();
|
||||||
|
commandManager.register(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void shutDown()
|
||||||
|
{
|
||||||
|
if (twitchIRCClient != null)
|
||||||
|
{
|
||||||
|
twitchIRCClient.close();
|
||||||
|
twitchIRCClient = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
commandManager.unregister(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
TwitchConfig provideConfig(ConfigManager configManager)
|
||||||
|
{
|
||||||
|
return configManager.getConfig(TwitchConfig.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void connect()
|
||||||
|
{
|
||||||
|
if (twitchConfig.username() != null
|
||||||
|
&& twitchConfig.oauthToken() != null
|
||||||
|
&& twitchConfig.channel() != null)
|
||||||
|
{
|
||||||
|
String channel = twitchConfig.channel();
|
||||||
|
if (!channel.startsWith("#"))
|
||||||
|
{
|
||||||
|
channel = "#" + channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
twitchIRCClient = new TwitchIRCClient(
|
||||||
|
this,
|
||||||
|
twitchConfig.username(),
|
||||||
|
twitchConfig.oauthToken(),
|
||||||
|
channel
|
||||||
|
);
|
||||||
|
twitchIRCClient.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Schedule(period = 30, unit = ChronoUnit.SECONDS, asynchronous = true)
|
||||||
|
public void checkClient()
|
||||||
|
{
|
||||||
|
if (twitchIRCClient != null)
|
||||||
|
{
|
||||||
|
if (twitchIRCClient.isConnected())
|
||||||
|
{
|
||||||
|
twitchIRCClient.pingCheck();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!twitchIRCClient.isConnected())
|
||||||
|
{
|
||||||
|
log.debug("Reconnecting...");
|
||||||
|
|
||||||
|
connect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
public void onConfigChanged(ConfigChanged configChanged)
|
||||||
|
{
|
||||||
|
if (!configChanged.getGroup().equals("twitch"))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (twitchIRCClient != null)
|
||||||
|
{
|
||||||
|
twitchIRCClient.close();
|
||||||
|
twitchIRCClient = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
connect();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addChatMessage(String sender, String message)
|
||||||
|
{
|
||||||
|
String chatMessage = new ChatMessageBuilder()
|
||||||
|
.append(message)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
chatMessageManager.queue(QueuedMessage.builder()
|
||||||
|
.type(ChatMessageType.CLANCHAT)
|
||||||
|
.sender("Twitch")
|
||||||
|
.name(sender)
|
||||||
|
.runeLiteFormattedMessage(chatMessage)
|
||||||
|
.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void privmsg(Map<String, String> tags, String message)
|
||||||
|
{
|
||||||
|
if (client.getGameState() != GameState.LOGGED_IN)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String displayName = tags.get("display-name");
|
||||||
|
addChatMessage(displayName, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void roomstate(Map<String, String> tags)
|
||||||
|
{
|
||||||
|
log.debug("Room state: {}", tags);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void usernotice(Map<String, String> tags, String message)
|
||||||
|
{
|
||||||
|
log.debug("Usernotice tags: {} message: {}", tags, message);
|
||||||
|
|
||||||
|
if (client.getGameState() != GameState.LOGGED_IN)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String sysmsg = tags.get("system-msg");
|
||||||
|
addChatMessage("[System]", sysmsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onChatboxInput(ChatboxInput chatboxInput)
|
||||||
|
{
|
||||||
|
String message = chatboxInput.getValue();
|
||||||
|
if (message.startsWith("//"))
|
||||||
|
{
|
||||||
|
message = message.substring(2);
|
||||||
|
if (message.isEmpty() || twitchIRCClient == null)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
twitchIRCClient.privmsg(message);
|
||||||
|
addChatMessage(twitchConfig.username(), message);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onPrivateMessageInput(PrivateMessageInput privateMessageInput)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018, Adam <Adam@sigterm.info>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
* list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
package net.runelite.client.plugins.twitch.irc;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
class Message
|
||||||
|
{
|
||||||
|
private final Map<String, String> tags = new HashMap<>();
|
||||||
|
private String source;
|
||||||
|
private String command;
|
||||||
|
private String[] arguments;
|
||||||
|
|
||||||
|
public static Message parse(String in)
|
||||||
|
{
|
||||||
|
Message message = new Message();
|
||||||
|
if (in.startsWith("@"))
|
||||||
|
{
|
||||||
|
String[] tags = in.substring(1)
|
||||||
|
.split(";");
|
||||||
|
for (String tag : tags)
|
||||||
|
{
|
||||||
|
int eq = tag.indexOf('=');
|
||||||
|
if (eq == -1) continue;
|
||||||
|
|
||||||
|
String key = tag.substring(0, eq);
|
||||||
|
String value = tag.substring(eq + 1)
|
||||||
|
.replace("\\:", ";")
|
||||||
|
.replace("\\s", " ")
|
||||||
|
.replace("\\\\", "\\")
|
||||||
|
.replace("\\r", "\r")
|
||||||
|
.replace("\\n", "\n");
|
||||||
|
|
||||||
|
message.tags.put(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
int sp = in.indexOf(' ');
|
||||||
|
in = in.substring(sp + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (in.startsWith(":"))
|
||||||
|
{
|
||||||
|
int sp = in.indexOf(' ');
|
||||||
|
message.source = in.substring(1, sp);
|
||||||
|
|
||||||
|
in = in.substring(sp + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
int sp = in.indexOf(' ');
|
||||||
|
if (sp == -1)
|
||||||
|
{
|
||||||
|
message.command = in;
|
||||||
|
message.arguments = new String[0];
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
message.command = in.substring(0, sp);
|
||||||
|
|
||||||
|
String args = in.substring(sp + 1);
|
||||||
|
List<String> argList = new ArrayList<>();
|
||||||
|
do
|
||||||
|
{
|
||||||
|
String arg;
|
||||||
|
if (args.startsWith(":"))
|
||||||
|
{
|
||||||
|
arg = args.substring(1);
|
||||||
|
sp = -1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sp = args.indexOf(' ');
|
||||||
|
arg = sp == -1 ? args : args.substring(0, sp);
|
||||||
|
}
|
||||||
|
args = args.substring(sp + 1);
|
||||||
|
argList.add(arg);
|
||||||
|
} while (sp != -1);
|
||||||
|
|
||||||
|
message.arguments = argList.toArray(new String[0]);
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,234 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018, Adam <Adam@sigterm.info>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
* list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
package net.runelite.client.plugins.twitch.irc;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.net.Socket;
|
||||||
|
import javax.net.SocketFactory;
|
||||||
|
import javax.net.ssl.SSLSocketFactory;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class TwitchIRCClient extends Thread implements AutoCloseable
|
||||||
|
{
|
||||||
|
private static final String HOST = "irc.chat.twitch.tv";
|
||||||
|
private static final int PORT = 6697;
|
||||||
|
private static final int READ_TIMEOUT = 60000; // ms
|
||||||
|
private static final int PING_TIMEOUT = 30000; // ms
|
||||||
|
|
||||||
|
private final TwitchListener twitchListener;
|
||||||
|
|
||||||
|
private final String username, password;
|
||||||
|
private final String channel;
|
||||||
|
|
||||||
|
private Socket socket;
|
||||||
|
private BufferedReader in;
|
||||||
|
private PrintWriter out;
|
||||||
|
private long last;
|
||||||
|
private boolean pingSent;
|
||||||
|
|
||||||
|
public TwitchIRCClient(TwitchListener twitchListener, String username, String password, String channel)
|
||||||
|
{
|
||||||
|
setName("Twitch");
|
||||||
|
this.twitchListener = twitchListener;
|
||||||
|
this.username = username;
|
||||||
|
this.password = password;
|
||||||
|
this.channel = channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
socket.close();
|
||||||
|
}
|
||||||
|
catch (IOException ex)
|
||||||
|
{
|
||||||
|
log.warn("error closing socket", ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
in = null;
|
||||||
|
out = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
SocketFactory socketFactory = SSLSocketFactory.getDefault();
|
||||||
|
socket = socketFactory.createSocket(HOST, PORT);
|
||||||
|
socket.setSoTimeout(READ_TIMEOUT);
|
||||||
|
|
||||||
|
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
|
||||||
|
out = new PrintWriter(socket.getOutputStream());
|
||||||
|
}
|
||||||
|
catch (IOException ex)
|
||||||
|
{
|
||||||
|
log.warn("unable to setup irc client", ex);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
register(username, password);
|
||||||
|
join(channel);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
String line;
|
||||||
|
|
||||||
|
while ((line = read()) != null)
|
||||||
|
{
|
||||||
|
log.debug("<- {}", line);
|
||||||
|
|
||||||
|
last = System.currentTimeMillis();
|
||||||
|
pingSent = false;
|
||||||
|
|
||||||
|
Message message = Message.parse(line);
|
||||||
|
|
||||||
|
switch (message.getCommand())
|
||||||
|
{
|
||||||
|
case "PING":
|
||||||
|
send("PONG", message.getArguments()[0]);
|
||||||
|
break;
|
||||||
|
case "PRIVMSG":
|
||||||
|
twitchListener.privmsg(message.getTags(),
|
||||||
|
message.getArguments()[1]);
|
||||||
|
break;
|
||||||
|
case "ROOMSTATE":
|
||||||
|
twitchListener.roomstate(message.getTags());
|
||||||
|
break;
|
||||||
|
case "USERNOTICE":
|
||||||
|
twitchListener.usernotice(message.getTags(),
|
||||||
|
message.getArguments().length > 0 ? message.getArguments()[0] : null);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IOException ex)
|
||||||
|
{
|
||||||
|
log.debug("error in twitch irc client", ex);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
socket.close();
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
log.warn(null, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isConnected()
|
||||||
|
{
|
||||||
|
return socket != null && socket.isConnected() && !socket.isClosed();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void pingCheck()
|
||||||
|
{
|
||||||
|
if (out == null)
|
||||||
|
{
|
||||||
|
// client is not connected yet
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!pingSent && System.currentTimeMillis() - last >= PING_TIMEOUT)
|
||||||
|
{
|
||||||
|
ping("twitch");
|
||||||
|
pingSent = true;
|
||||||
|
}
|
||||||
|
else if (pingSent)
|
||||||
|
{
|
||||||
|
log.debug("Ping timeout, disconnecting.");
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void register(String username, String oauth)
|
||||||
|
{
|
||||||
|
send("CAP", "REQ", "twitch.tv/commands twitch.tv/tags");
|
||||||
|
send("PASS", oauth);
|
||||||
|
send("NICK", username);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void join(String channel)
|
||||||
|
{
|
||||||
|
send("JOIN", channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ping(String destination)
|
||||||
|
{
|
||||||
|
send("PING", destination);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void privmsg(String message)
|
||||||
|
{
|
||||||
|
send("PRIVMSG", channel, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void send(String command, String... args)
|
||||||
|
{
|
||||||
|
StringBuilder stringBuilder = new StringBuilder();
|
||||||
|
stringBuilder.append(command);
|
||||||
|
for (int i = 0; i < args.length; ++i)
|
||||||
|
{
|
||||||
|
stringBuilder.append(' ');
|
||||||
|
if (i + 1 == args.length)
|
||||||
|
{
|
||||||
|
stringBuilder.append(':');
|
||||||
|
}
|
||||||
|
stringBuilder.append(args[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
log.debug("-> {}", stringBuilder.toString());
|
||||||
|
|
||||||
|
stringBuilder.append("\r\n");
|
||||||
|
|
||||||
|
out.write(stringBuilder.toString());
|
||||||
|
out.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String read() throws IOException
|
||||||
|
{
|
||||||
|
String line = in.readLine();
|
||||||
|
if (line == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
int len = line.length();
|
||||||
|
while (len > 0 && (line.charAt(len - 1) == '\r' || line.charAt(len - 1) == '\n'))
|
||||||
|
{
|
||||||
|
--len;
|
||||||
|
}
|
||||||
|
|
||||||
|
return line.substring(0, len);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018, Adam <Adam@sigterm.info>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
* list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
package net.runelite.client.plugins.twitch.irc;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public interface TwitchListener
|
||||||
|
{
|
||||||
|
void privmsg(Map<String, String> tags, String message);
|
||||||
|
|
||||||
|
void roomstate(Map<String, String> tags);
|
||||||
|
|
||||||
|
void usernotice(Map<String, String> tags, String message);
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018, Adam <Adam@sigterm.info>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
* list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
package net.runelite.client.plugins.twitch.irc;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class MessageTest
|
||||||
|
{
|
||||||
|
@Test
|
||||||
|
public void testParse()
|
||||||
|
{
|
||||||
|
Message message = Message.parse("@badges=subscriber/0;color=;display-name=kappa_kid_;emotes=;id=6539b42a-e945-4a83-a5b7-018149ca9fa7;mod=0;room-id=27107346;subscriber=1;tmi-sent-ts=1535926830652;turbo=0;user-id=33390095;user-type= :kappa_kid_!kappa_kid_@kappa_kid_.tmi.twitch.tv PRIVMSG #b0aty :how do u add charges to that zeah book?");
|
||||||
|
Map<String, String> messageTags = message.getTags();
|
||||||
|
assertEquals("subscriber/0", messageTags.get("badges"));
|
||||||
|
assertEquals("kappa_kid_!kappa_kid_@kappa_kid_.tmi.twitch.tv", message.getSource());
|
||||||
|
assertEquals("PRIVMSG", message.getCommand());
|
||||||
|
assertEquals("#b0aty", message.getArguments()[0]);
|
||||||
|
assertEquals("how do u add charges to that zeah book?", message.getArguments()[1]);
|
||||||
|
|
||||||
|
message = Message.parse("@badges=moderator/1,subscriber/12,bits/10000;color=#008000;display-name=Am_Sephiroth;emotes=;id=7d516b7c-de7a-4c8b-ad23-d8880b55d46b;login=am_sephiroth;mod=1;msg-id=subgift;msg-param-months=8;msg-param-recipient-display-name=IntRS;msg-param-recipient-id=189672346;msg-param-recipient-user-name=intrs;msg-param-sender-count=215;msg-param-sub-plan-name=Sick\\sNerd\\sSubscription\\s;msg-param-sub-plan=1000;room-id=49408183;subscriber=1;system-msg=Am_Sephiroth\\sgifted\\sa\\sTier\\s1\\ssub\\sto\\sIntRS!\\sThey\\shave\\sgiven\\s215\\sGift\\sSubs\\sin\\sthe\\schannel!;tmi-sent-ts=1535980032939;turbo=0;user-id=69539403;user-type=mod :tmi.twitch.tv USERNOTICE #sick_nerd");
|
||||||
|
messageTags = message.getTags();
|
||||||
|
assertEquals("Am_Sephiroth gifted a Tier 1 sub to IntRS! They have given 215 Gift Subs in the channel!", messageTags.get("system-msg"));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user