From: Jordan Duabe <1442368+j4ckofalltrades@users.noreply.github.com> Date: Sat, 27 Feb 2021 14:32:22 +0530 Subject: Initial Twitch Chat action implemenation --- Initial Twitch Chat action implemenation Includes server-side action for configuring connection to Twitch Chat and client-side action for sending message(s) to a channel. --- --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*.iml +.idea/ + +target/ Binary files /dev/null and b/PreBuiltPlugins/Java-Twirk-0.6.3.jar differ Binary files /dev/null and b/PreBuiltPlugins/twitch-chat-connect.jar differ Binary files /dev/null and b/PreBuiltPlugins/twitch-send-channel-msg.jar differ --- 'a/README.md' +++ b/README.md @@ -17,6 +17,25 @@ Set of trusted, pre-bundled actions and - Text Block - Media File - Media Key +- Twitch + +## Actions Help Guide + +### Twitch Chat Integration + +The first step is to acquire an OAuth token from https://twitchapps.com/tmi/, the generated OAuth token should look something like `oauth:xxxxx`. + +In the Stream-Pi Server's Plugin page enter your Twitch username, and the generated token then click on `Save Twitch chat credentials` button. You should then be able to use the pre-bundled Twitch chat actions. + +### Supported actions + +- Send channel message + +### Running locally + +Copy the `twitch-chat-connect`, `twitch-send-channel-msg`, and `Java-Twirk` jar files from the `PreBuiltPlugins` directory to your Stream-Pi server plugins' directory. + +--- ## Quick Start --- 'a/build.sh' +++ b/build.sh @@ -1,83 +1,72 @@ FOLD=../PreBuiltPlugins +pushd . || exit cd hotkeyaction && mvn clean package - mv target/hotkeyaction-1.0.0.jar $FOLD/hotkeyaction.jar cd ../mediakeyaction && mvn clean package - mv target/mediakeyaction-1.0.0.jar $FOLD/mediakeyaction.jar cd ../playaudioclipaction && mvn clean package - mv target/playaudioclipaction-1.0.0.jar $FOLD/playaudioclipaction.jar -cd ../runcommandaction && mvn clean package - +cd ../runcommandaction && mvn clean package mv target/runcommandaction-1.0.0.jar $FOLD/runcommandaction.jar cd ../textblockaction && mvn clean package - mv target/textblockaction-1.0.0.jar $FOLD/textblockaction.jar -cd ../twitteraction && mvn clean package - +cd ../twitteraction && mvn clean package mv target/twitteraction-1.0.0.jar $FOLD/twitteraction.jar cd ../websiteaction && mvn clean package - mv target/websiteaction-1.0.0.jar $FOLD/websiteaction.jar +popd || exit - -# OBS Suite - -cd ../obssuite/mother && mvn clean install package - +## OBS Suite +pushd . || exit +cd obssuite/mother && mvn clean install package mv target/obssuite_motheraction-1.0.0.jar ../$FOLD/obssuite_motheraction.jar -cd ../setcurrentprofile && mvn clean package - +cd ../setcurrentprofile && mvn clean package mv target/obssuite_setcurrentprofileaction-1.0.0.jar ../$FOLD/obssuite_setcurrentprofileaction.jar -cd ../setcurrentscene && mvn clean package - +cd ../setcurrentscene && mvn clean package mv target/obssuite_setcurrentsceneaction-1.0.0.jar ../$FOLD/obssuite_setcurrentsceneaction.jar -cd ../setcurrenttransition && mvn clean package - +cd ../setcurrenttransition && mvn clean package mv target/obssuite_setcurrenttransitionaction-1.0.0.jar ../$FOLD/obssuite_setcurrenttransitionaction.jar -cd ../setmute && mvn clean package - +cd ../setmute && mvn clean package mv target/obssuite_setmuteaction-1.0.0.jar ../$FOLD/obssuite_setmuteaction.jar -cd ../setcurrentprofile && mvn clean package - +cd ../setcurrentprofile && mvn clean package mv target/obssuite_setcurrentprofileaction-1.0.0.jar ../$FOLD/obssuite_setcurrentprofileaction.jar -cd ../setpreviewscene && mvn clean package - +cd ../setpreviewscene && mvn clean package mv target/obssuite_setpreviewsceneaction-1.0.0.jar ../$FOLD/obssuite_setpreviewsceneaction.jar cd ../setrecording && mvn clean package - mv target/obssuite_setrecordingaction-1.0.0.jar ../$FOLD/obssuite_setrecordingaction.jar -cd ../setreplaybuffer && mvn clean package - +cd ../setreplaybuffer && mvn clean package mv target/obssuite_setreplaybufferaction-1.0.0.jar ../$FOLD/obssuite_setreplaybufferaction.jar -cd ../setstreaming && mvn clean package - +cd ../setstreaming && mvn clean package mv target/obssuite_setstreamingaction-1.0.0.jar ../$FOLD/obssuite_setstreamingaction.jar -cd ../setstudiomode && mvn clean package - +cd ../setstudiomode && mvn clean package mv target/obssuite_setstudiomodeaction-1.0.0.jar ../$FOLD/obssuite_setstudiomodeaction.jar cd ../setvolume && mvn clean package - mv target/obssuite_setvolumeaction-1.0.0.jar ../$FOLD/obssuite_setvolumeaction.jar +popd || exit - - +# Twitch Chat +pushd . || exit +cd twitch/twitch-chat-connect && mvn clean package +mv target/twitch-chat-connect-1.0.0.jar ../$FOLD/twitch-chat-connect.jar + +cd ../send-channel-msg && mvn clean package +mv target/twitch-send-channel-msg-1.0.0.jar ../$FOLD/twitch-send-channel-msg.jar +popd || exit --- /dev/null +++ b/twitch/send-channel-msg/pom.xml @@ -0,0 +1,81 @@ + + + 4.0.0 + + com.stream-pi + twitch-send-channel-msg + 1.0.0 + + + 11 + 11 + 1.0.0 + 1.0.0 + 1.0.0 + 0.6.3 + + + + + jitpack.io + https://jitpack.io + + + + + + + org.apache.maven.plugins + maven-jar-plugin + 3.1.0 + + + test-jar + package + + test-jar + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + 11 + + + + + + + + com.stream-pi + util + ${streamPiUtilVersion} + + + + com.stream-pi + action-api + ${streamPiActionApiVersion} + + + + com.github.gikkman + Java-Twirk + ${javaTwirkVersion} + + + + com.stream-pi + twitch-chat-connect + ${streamPiTwitchChatConnectVersion} + + + + \ No newline at end of file --- /dev/null +++ b/twitch/send-channel-msg/src/main/java/module-info.java @@ -0,0 +1,9 @@ +module com.stream_pi.twitch.sendchannelmsgaction { + requires com.stream_pi.util; + requires com.stream_pi.action_api; + + requires com.stream_pi.twitchconnectaction; + requires Java.Twirk; + + provides com.stream_pi.action_api.normalaction.NormalAction with sendchannelmsg.SendChannelMessageAction; +} \ No newline at end of file --- /dev/null +++ b/twitch/send-channel-msg/src/main/java/sendchannelmsg/SendChannelMessageAction.java @@ -0,0 +1,117 @@ +package sendchannelmsg; + +import com.gikk.twirk.Twirk; +import com.gikk.twirk.TwirkBuilder; +import com.stream_pi.action_api.actionproperty.property.Property; +import com.stream_pi.action_api.actionproperty.property.Type; +import com.stream_pi.action_api.normalaction.NormalAction; +import com.stream_pi.util.alert.StreamPiAlert; +import com.stream_pi.util.alert.StreamPiAlertType; +import com.stream_pi.util.version.Version; +import connect.chat.TwitchChatCredentials; + +public class SendChannelMessageAction extends NormalAction +{ + + private static final String TWITCH_CHANNEL_NAME_KEY = "twitch_channel_name"; + private static final String TWITCH_CHANNEL_MSG_KEY = "twitch_channel_msg"; + + private Twirk twirk; + + public SendChannelMessageAction() + { + setName("Send Channel Message"); + setCategory("Twitch Chat"); + setVisibilityInServerSettingsPane(false); + setAuthor("j4ckofalltrades"); + setVersion(new Version(1, 0, 0)); + setHelpLink("https://github.com/stream-pi/essentialactions"); + } + + @Override + public void initProperties() throws Exception + { + Property channelName = new Property(TWITCH_CHANNEL_NAME_KEY, Type.STRING); + channelName.setDisplayName("Channel Name"); + channelName.setDefaultValueStr("channel_name"); + channelName.setCanBeBlank(false); + + Property channelMessage = new Property(TWITCH_CHANNEL_MSG_KEY, Type.STRING); + channelMessage.setDisplayName("Message"); + channelMessage.setDefaultValueStr("channel_msg"); + channelMessage.setCanBeBlank(false); + + addClientProperties(channelName, channelMessage); + } + + @Override + public void initAction() throws Exception + { + + } + + @Override + public void onActionClicked() throws Exception + { + TwitchChatCredentials.ChatCredentials credentials = TwitchChatCredentials.getCredentials(); + if (!isChatCredentialsInitialized(credentials)) + { + new StreamPiAlert( + "Twitch Chat plugin configuration uninitialized", + "Please check that the Twitch Chat plugin configuration is correct.", + StreamPiAlertType.ERROR + ).show(); + return; + } + + final String channel = getClientProperties().getSingleProperty(TWITCH_CHANNEL_NAME_KEY).getStringValue(); + final String message = getClientProperties().getSingleProperty(TWITCH_CHANNEL_MSG_KEY).getStringValue(); + + try + { + twirk = new TwirkBuilder( + getClientProperties().getSingleProperty(TWITCH_CHANNEL_NAME_KEY).getStringValue(), + credentials.getNickname(), + credentials.getOauthToken()) + .build(); + twirk.connect(); + twirk.channelMessage(message); + } catch (Exception ex) + { + new StreamPiAlert( + "Failed to send channel message", + String.format("Could not send message '%s' to '%s' channel, please try again.", + channel, message), + StreamPiAlertType.ERROR + ).show(); + } + } + + private boolean isChatCredentialsInitialized(TwitchChatCredentials.ChatCredentials credentials) + { + if (credentials == null) + { + return false; + } + + final String twitchNickname = credentials.getNickname(); + boolean isNicknameInitialized = twitchNickname != null && + !twitchNickname.isEmpty() && + !twitchNickname.equalsIgnoreCase("twitch_nickname"); + + final String twitchChatOauthToken = credentials.getOauthToken(); + boolean isTokenInitialized = twitchChatOauthToken != null && + !twitchChatOauthToken.isEmpty() && + !twitchChatOauthToken.equalsIgnoreCase("twitch_oauth_token"); + + return isNicknameInitialized && isTokenInitialized; + } + + @Override + public void onShutDown() throws Exception + { + twirk.close(); + } +} + + --- /dev/null +++ b/twitch/twitch-chat-connect/pom.xml @@ -0,0 +1,74 @@ + + + 4.0.0 + + com.stream-pi + twitch-chat-connect + 1.0.0 + + + 11 + 11 + 1.0.0 + 1.0.0 + 0.6.3 + + + + + jitpack.io + https://jitpack.io + + + + + + + org.apache.maven.plugins + maven-jar-plugin + 3.1.0 + + + test-jar + package + + test-jar + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + 11 + + + + + + + + com.github.gikkman + Java-Twirk + ${javaTwirkVersion} + + + + com.stream-pi + util + ${streamPiUtilVersion} + + + + com.stream-pi + action-api + ${streamPiActionApiVersion} + + + + \ No newline at end of file --- /dev/null +++ b/twitch/twitch-chat-connect/src/main/java/connect/TwitchChatConnectAction.java @@ -0,0 +1,96 @@ +package connect; + +import com.stream_pi.action_api.actionproperty.property.Property; +import com.stream_pi.action_api.actionproperty.property.Type; +import com.stream_pi.action_api.normalaction.NormalAction; +import com.stream_pi.util.alert.StreamPiAlert; +import com.stream_pi.util.alert.StreamPiAlertType; +import com.stream_pi.util.exception.MinorException; +import com.stream_pi.util.version.Version; +import connect.chat.TwitchChatCredentials; +import javafx.scene.control.Button; + +public class TwitchChatConnectAction extends NormalAction +{ + + private static final String TWITCH_ACCESS_TOKEN_KEY = "twitch_access_token"; + private static final String TWITCH_NICKNAME_KEY = "twitch_nickname"; + + private final Button saveCredentialsBtn; + + public TwitchChatConnectAction() + { + setName("Twitch Chat Plugin"); + setCategory("Twitch Chat"); + setVisibilityInPluginsPane(false); + setAuthor("j4ckofalltrades"); + setVersion(new Version(1, 0, 0)); + setHelpLink("https://github.com/Stream-Pi/essentialactions"); + + saveCredentialsBtn = new Button("Save Twitch chat credentials"); + setButtonBar(saveCredentialsBtn); + } + + @Override + public void initProperties() throws Exception + { + Property twitchNicknameProp = new Property(TWITCH_NICKNAME_KEY, Type.STRING); + twitchNicknameProp.setDisplayName("Twitch Username"); + twitchNicknameProp.setDefaultValueStr("twitch_nickname"); + twitchNicknameProp.setCanBeBlank(false); + + Property twitchAccessTokenProp = new Property(TWITCH_ACCESS_TOKEN_KEY, Type.STRING); + twitchAccessTokenProp.setDisplayName("Access Token"); + twitchAccessTokenProp.setDefaultValueStr("twitch_oauth_token"); + twitchAccessTokenProp.setCanBeBlank(false); + + addServerProperties(twitchNicknameProp, twitchAccessTokenProp); + } + + @Override + public void initAction() throws Exception + { + saveCredentialsBtn.setOnAction(action -> + { + try + { + persistCredentials(); + + new StreamPiAlert( + "Twitch chat credentials saved", + "Chat credentials been saved, you can now start using Twitch chat integration actions.", + StreamPiAlertType.INFORMATION) + .show(); + } catch (Exception e) + { + new StreamPiAlert( + "Failed to save chat credentials", + "An error has occurred while saving chat credentials, please try again.", + StreamPiAlertType.WARNING) + .show(); + } + }); + + persistCredentials(); + } + + private void persistCredentials() throws MinorException + { + final String token = getServerProperties().getSingleProperty(TWITCH_ACCESS_TOKEN_KEY).getStringValue(); + final String nickname = getServerProperties().getSingleProperty(TWITCH_NICKNAME_KEY).getStringValue(); + TwitchChatCredentials.setCredentials( + new TwitchChatCredentials.ChatCredentials() + .setOauthToken(token) + .setNickname(nickname)); + } + + @Override + public void onActionClicked() throws Exception + { + } + + @Override + public void onShutDown() throws Exception + { + } +} --- /dev/null +++ b/twitch/twitch-chat-connect/src/main/java/connect/chat/TwitchChatCredentials.java @@ -0,0 +1,45 @@ +package connect.chat; + +public final class TwitchChatCredentials +{ + + private static ChatCredentials credentials; + + public static ChatCredentials getCredentials() + { + return credentials; + } + + public static void setCredentials(ChatCredentials chatCredentials) + { + credentials = chatCredentials; + } + + public static class ChatCredentials + { + String nickname; + String oauthToken; + + public String getNickname() + { + return nickname; + } + + public ChatCredentials setNickname(String nickname) + { + this.nickname = nickname; + return this; + } + + public String getOauthToken() + { + return oauthToken; + } + + public ChatCredentials setOauthToken(String oauthToken) + { + this.oauthToken = oauthToken; + return this; + } + } +} --- /dev/null +++ b/twitch/twitch-chat-connect/src/main/java/module-info.java @@ -0,0 +1,13 @@ +import connect.TwitchChatConnectAction; + +module com.stream_pi.twitchconnectaction { + + requires com.stream_pi.action_api; + requires com.stream_pi.util; + + requires Java.Twirk; + + exports connect.chat; + + provides com.stream_pi.action_api.normalaction.NormalAction with TwitchChatConnectAction; +} \ No newline at end of file