From c071e4d64485e2799466b5dcb32fd0fb288d6fa5 Mon Sep 17 00:00:00 2001 From: Eroax Date: Fri, 8 Dec 2023 09:53:06 +0000 Subject: [PATCH] TWITCH INTEGRATION (#13) Implemented a basic Twitch Connection, + Twitch Receive Chat and Twitch Send Chat Co-authored-by: Eroax Reviewed-on: https://codeberg.org/Eroax/StreamGraph/pulls/13 --- addons/no_twitch/chat_socket.gd | 166 ++++++++++++++++++ addons/no_twitch/demo/Chat_Join.gd | 33 ++++ addons/no_twitch/demo/demo_scene.gd | 27 +++ addons/no_twitch/demo/demo_scene.tscn | 81 +++++++++ addons/no_twitch/demo/test_button.gd | 9 + addons/no_twitch/demo/token_saver.gd | 4 + addons/no_twitch/plugin.cfg | 7 + addons/no_twitch/plugin.gd | 12 ++ addons/no_twitch/twitch_connection.gd | 131 ++++++++++++++ addons/no_twitch/websocket_client.gd | 58 ++++++ classes/connections/connections.gd | 6 + classes/deck/nodes/twitch_chat_received.gd | 28 +++ classes/deck/nodes/twitch_send_chat.gd | 57 ++++++ graph_node_renderer/deck_holder_renderer.gd | 9 + graph_node_renderer/deck_holder_renderer.tscn | 16 +- graph_node_renderer/twitch_setup_dialog.gd | 18 ++ graph_node_renderer/twitch_setup_dialog.tscn | 48 +++++ project.godot | 2 +- 18 files changed, 709 insertions(+), 3 deletions(-) create mode 100644 addons/no_twitch/chat_socket.gd create mode 100644 addons/no_twitch/demo/Chat_Join.gd create mode 100644 addons/no_twitch/demo/demo_scene.gd create mode 100644 addons/no_twitch/demo/demo_scene.tscn create mode 100644 addons/no_twitch/demo/test_button.gd create mode 100644 addons/no_twitch/demo/token_saver.gd create mode 100644 addons/no_twitch/plugin.cfg create mode 100644 addons/no_twitch/plugin.gd create mode 100644 addons/no_twitch/twitch_connection.gd create mode 100644 addons/no_twitch/websocket_client.gd create mode 100644 classes/deck/nodes/twitch_chat_received.gd create mode 100644 classes/deck/nodes/twitch_send_chat.gd create mode 100644 graph_node_renderer/twitch_setup_dialog.gd create mode 100644 graph_node_renderer/twitch_setup_dialog.tscn diff --git a/addons/no_twitch/chat_socket.gd b/addons/no_twitch/chat_socket.gd new file mode 100644 index 0000000..eb7f1dc --- /dev/null +++ b/addons/no_twitch/chat_socket.gd @@ -0,0 +1,166 @@ +extends "res://addons/no_twitch/websocket_client.gd" + +## Wrapper class around [Websocket_Client] that handles Twitch Chat + +signal chat_received +signal chat_received_raw +signal chat_received_rich + +signal chat_connected + +var channels : Array[String] +var extra_info : bool + +var user_regex := RegEx.new() +var user_pattern := r":([\w]+)!" + + +func _init(): + + packet_received.connect(data_received) + + user_regex.compile(user_pattern) + + +## Connects to the Twitch IRC server. +func connect_to_chat(token, extra = false, nick = "terribletwitch"): + + extra_info = extra + + connect_to_url("wss://irc-ws.chat.twitch.tv:443") + await socket_open + send_text("PASS oauth:" + token) + send_text("NICK " + nick) + + if extra: + + send_text("CAP REQ :twitch.tv/commands twitch.tv/tags") + + + +## Handles checking the received packet from [signal Websocket_Client.packet_received] +func data_received(packet : PackedByteArray): + +# Gets the text from the packet, strips the end and splits the different lines + var messages = packet.get_string_from_utf8().strip_edges(false).split("\r\n") + + for msg in messages: + # Checks if this is a message that has tags enabled and if so, parses out the tags. + var tags : Dictionary + if msg.begins_with("@"): + + # Grabs the actual end of the string with the message in it. + var real_msg = msg.split(" ", false, 1) + msg = real_msg[1] + + # Loops through all the tags splitting them up by ; and then by = to get the keys and values. + for tag in real_msg[0].split(";"): + + var key_value = tag.split("=") + tags[key_value[0]] = key_value[1] + + + + parse_chat_msg(msg, tags) + + +#@badge-info=subscriber/34;badges=broadcaster/1,subscriber/6,game-developer/1;client-nonce=b5009ae3ee034a7706d86fe221882925;color=#2E8B57;display-name=EroAxee;emotes=;first-msg=0;flags=;id=be05dae8-4067-4edf-83f2-e6be02974904;mod=0;returning-chatter=0;room-id=160349129;subscriber=1;tmi-sent-ts=1702009303249;turbo=0;user-id=160349129;user-type= :eroaxee!eroaxee@eroaxee.tmi.twitch.tv PRIVMSG #eroaxee :More + +## Parses the given [param msg] [String] +func parse_chat_msg(msg : String, tags : Dictionary): + + var msg_dict : Dictionary + + if msg == "PING :tmi.twitch.tv": + + send_text(msg.replace("PING", "PONG")) + + return + + + var msg_notice = msg.split(" ")[1] + match msg_notice: + + "PRIVMSG": + + var space_split = msg.split(" ", true, 3) + + msg_dict["username"] = user_regex.search(msg).get_string(1) + msg_dict["message"] = space_split[3].trim_prefix(":") + msg_dict["channel"] = space_split[2].trim_prefix("#") + msg_dict.merge(parse_tags(tags)) + prints(msg_dict.username, msg_dict.message, msg_dict.channel) + +#(__username_regex.search(split[0]).get_string(1), split[3].right(1), split[2], tags) + + if !tags.is_empty(): + + chat_received_rich.emit(msg_dict) + return + + chat_received.emit(msg_dict) + + + # Connection Message + "001": + + prints("Connection Established", msg) + chat_connected.emit() + + # Chat Joining Message + "JOIN": + + pass + + # Chat Leaving Message + "PART": + + pass + + + +#@badge-info=subscriber/34;badges=broadcaster/1,subscriber/6,game-developer/1;client-nonce=02d73777ab1fab1aee33ada1830d52b5;color=#2E8B57;display-name=EroAxee;emotes=;first-msg=0;flags=;id=4ff91a8c-b965-43f8-85a1-ddd541a2b438;mod=0;returning-chatter=0;room-id=160349129;subscriber=1;tmi-sent-ts=1701850826667;turbo=0;user-id=160349129;user-type= :eroaxee!eroaxee@eroaxee.tmi.twitch.tv PRIVMSG #eroaxee :Stuff + +## Utility function that takes a Dictionary of tags from a Twitch message and parses them to be slightly more usable. +func parse_tags(tags : Dictionary): + + var new_tags : Dictionary + for all in tags.keys(): + + if all == "badges": + + tags[all] = tags[all].split(",") + + + new_tags[all.replace("-", "_")] = tags[all] + + + return new_tags +#{ "@badge-info": "subscriber/34", "badges": "broadcaster/1,subscriber/6,game-developer/1", "client-nonce": "b2e3524806f51c94cadd61d338bc14ed", "color": "#2E8B57", "display-name": "EroAxee", "emotes": "", "first-msg": "0", "flags": "", "id": "494dc47e-0d9c-4407-83ec-309764e1adf3", "mod": "0", "returning-chatter": "0", "room-id": "160349129", "subscriber": "1", "tmi-sent-ts": "1701853794297", "turbo": "0", "user-id": "160349129", "user-type": "" } + +## Wrapper function around [method WebSocketPeer.send_text] +func send_chat(msg : String, channel : String = ""): + + if channel.is_empty(): + + channel = channels[0] + + + channel = channel.strip_edges() + + send_text("PRIVMSG #" + channel + " :" + msg + "\r\n") + + +## Utility function that handles joining the supplied [param channel]'s chat. +func join_chat(channel : String): + + send_text("JOIN #" + channel + "\r\n") + channels.append(channel) + + +## Utility function that handles leaving the supplied [param channel]'s chat. +func leave_chat(channel : String): + + send_chat("PART #" + channel) + channels.erase(channel) + diff --git a/addons/no_twitch/demo/Chat_Join.gd b/addons/no_twitch/demo/Chat_Join.gd new file mode 100644 index 0000000..6bc2608 --- /dev/null +++ b/addons/no_twitch/demo/Chat_Join.gd @@ -0,0 +1,33 @@ +extends HBoxContainer + +var channel : String + +func _ready(): + + %Channel_Input.text_changed.connect(update_channel) + %Join_Chat.pressed.connect(join_chat) + %Start_Chat_Connection.pressed.connect(start_chat_connection) + + %Chat_Msg.text_submitted.connect(send_chat) + %Send_Button.pressed.connect(func(): send_chat(%Chat_Msg.text)) + + +func update_channel(new_channel): + + channel = new_channel + + +func start_chat_connection(): + + %Twitch_Connection.setup_chat_connection(channel) + + +func join_chat(): + + %Twitch_Connection.join_channel(channel) + + +func send_chat(chat : String): + + %Twitch_Connection.send_chat_to_channel(chat, %Channel.text) + diff --git a/addons/no_twitch/demo/demo_scene.gd b/addons/no_twitch/demo/demo_scene.gd new file mode 100644 index 0000000..1af75dd --- /dev/null +++ b/addons/no_twitch/demo/demo_scene.gd @@ -0,0 +1,27 @@ +extends Control + +func _ready(): + + $Twitch_Connection.token_received.connect(save_token) + + load_token() + + +func save_token(token): + + var res = TokenSaver.new() + res.token = token + ResourceSaver.save(res, "user://token.tres") + + +func load_token(): + + if !FileAccess.file_exists("user://token.tres"): + + return + + + var res = ResourceLoader.load("user://token.tres") + $Twitch_Connection.token = res.token + + diff --git a/addons/no_twitch/demo/demo_scene.tscn b/addons/no_twitch/demo/demo_scene.tscn new file mode 100644 index 0000000..a636685 --- /dev/null +++ b/addons/no_twitch/demo/demo_scene.tscn @@ -0,0 +1,81 @@ +[gd_scene load_steps=5 format=3 uid="uid://dhss3lpo1mhke"] + +[ext_resource type="Script" path="res://addons/no_twitch/twitch_connection.gd" id="1_13a4v"] +[ext_resource type="Script" path="res://addons/no_twitch/demo/demo_scene.gd" id="1_ebv0f"] +[ext_resource type="Script" path="res://addons/no_twitch/demo/test_button.gd" id="1_hhhwv"] +[ext_resource type="Script" path="res://addons/no_twitch/demo/Chat_Join.gd" id="2_b8f4l"] + +[node name="Control" type="Control"] +layout_mode = 3 +anchors_preset = 8 +anchor_left = 0.5 +anchor_top = 0.5 +anchor_right = 0.5 +anchor_bottom = 0.5 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("1_ebv0f") + +[node name="VBoxContainer" type="VBoxContainer" parent="."] +layout_mode = 1 +anchors_preset = 8 +anchor_left = 0.5 +anchor_top = 0.5 +anchor_right = 0.5 +anchor_bottom = 0.5 +offset_left = -206.0 +offset_top = -68.0 +offset_right = 211.0 +offset_bottom = 68.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="Authenticate" type="Button" parent="VBoxContainer"] +layout_mode = 2 +text = "Authenticate" +script = ExtResource("1_hhhwv") + +[node name="Start_Chat_Connection" type="Button" parent="VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +text = "Start Connection" + +[node name="Chat_Join" type="HBoxContainer" parent="VBoxContainer"] +layout_mode = 2 +script = ExtResource("2_b8f4l") + +[node name="Channel_Input" type="LineEdit" parent="VBoxContainer/Chat_Join"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +placeholder_text = "Channel" + +[node name="Join_Chat" type="Button" parent="VBoxContainer/Chat_Join"] +unique_name_in_owner = true +layout_mode = 2 +text = "Join" + +[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer"] +layout_mode = 2 + +[node name="Channel" type="LineEdit" parent="VBoxContainer/HBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 0.45 +placeholder_text = "Channel" + +[node name="Chat_Msg" type="LineEdit" parent="VBoxContainer/HBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +placeholder_text = "Chat Message" + +[node name="Send_Button" type="Button" parent="VBoxContainer/HBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +text = "Send" + +[node name="Twitch_Connection" type="Node" parent="."] +unique_name_in_owner = true +script = ExtResource("1_13a4v") diff --git a/addons/no_twitch/demo/test_button.gd b/addons/no_twitch/demo/test_button.gd new file mode 100644 index 0000000..7592510 --- /dev/null +++ b/addons/no_twitch/demo/test_button.gd @@ -0,0 +1,9 @@ +extends Button + + +@onready var twitch_connection : Twitch_Connection = $"../../Twitch_Connection" + +func _pressed(): + + twitch_connection.authenticate_with_twitch() + diff --git a/addons/no_twitch/demo/token_saver.gd b/addons/no_twitch/demo/token_saver.gd new file mode 100644 index 0000000..b7e1d43 --- /dev/null +++ b/addons/no_twitch/demo/token_saver.gd @@ -0,0 +1,4 @@ +extends Resource +class_name TokenSaver + +@export var token : String diff --git a/addons/no_twitch/plugin.cfg b/addons/no_twitch/plugin.cfg new file mode 100644 index 0000000..ad3e4e2 --- /dev/null +++ b/addons/no_twitch/plugin.cfg @@ -0,0 +1,7 @@ +[plugin] + +name="No Twitch" +description="" +author="Eroax" +version="0.0.1" +script="plugin.gd" diff --git a/addons/no_twitch/plugin.gd b/addons/no_twitch/plugin.gd new file mode 100644 index 0000000..45a417e --- /dev/null +++ b/addons/no_twitch/plugin.gd @@ -0,0 +1,12 @@ +@tool +extends EditorPlugin + + +func _enter_tree(): + # Initialization of the plugin goes here. + pass + + +func _exit_tree(): + # Clean-up of the plugin goes here. + pass diff --git a/addons/no_twitch/twitch_connection.gd b/addons/no_twitch/twitch_connection.gd new file mode 100644 index 0000000..c152372 --- /dev/null +++ b/addons/no_twitch/twitch_connection.gd @@ -0,0 +1,131 @@ +extends Node +class_name Twitch_Connection + +## Handler for a Connection to a Single Twitch Account +## +## Used for getting authentication and handling websocket connections. + +signal token_received(token : String) + +@export var client_id := "qyjg1mtby1ycs5scm1pvctos7yvyc1" +@export var redirect_uri := "http://localhost" +## Port that the redirect_uri will head to on your local system. Defaults to 80 for most cases. Linux tends to prefer 8000 or possibly 1338 +@export var redirect_port := "80" + +var twitch_url := "https://id.twitch.tv/oauth2/authorize?response_type=token&" + +var auth_server : TCPServer +## Websocket used for handling the chat connection. +var chat_socket = preload("res://addons/no_twitch/chat_socket.gd").new() + +var state := make_state() +var token : String + +func _ready(): + + chat_socket.chat_received.connect(check_chat_socket) + + +## Handles the basic Twitch Authentication process to request and then later receive a Token (using [method check_auth_peer]) +func authenticate_with_twitch(client_id = client_id): + auth_server = TCPServer.new() + + OS.shell_open(create_auth_url()) + auth_server.listen(int(redirect_port)) + + +## Sets up a single chat connection. Joining 1 room with [param token] as it's "PASS" specifying what account it's on. And the optional [param default_chat] specifying the default room to join. While [param nick] specifies the "nickname" (Not the username on Twitch) +func setup_chat_connection(default_chat : String = "", token : String = token, request_twitch_info = true, nick = "terribletwitch"): + + # Temporary, closes the socket to allow connecting multiple times without needing to reset. + chat_socket.close() + # Connects to the Twitch IRC server. + chat_socket.connect_to_chat(token, request_twitch_info) + await chat_socket.socket_open + + if !default_chat.is_empty(): + + chat_socket.join_chat(default_chat) + + + +## Joins the given [param channel] over IRC. Essentially just sending JOIN #[param channel] +func join_channel(channel : String): + + chat_socket.join_chat(channel) + + +func send_chat(msg : String, channel : String = ""): + + chat_socket.send_chat(msg, channel) + + +func _process(delta): + + if auth_server and auth_server.is_listening() and auth_server.is_connection_available(): + + check_auth_peer(auth_server.take_connection()) + + + if chat_socket: + + chat_socket.poll_socket() + + + +## Utility function for creating a Twitch Authentication URL with the given +## [param scopes], Twitch Client ID ([param id]) and [param redirect_uri]. +## [param id] defaults to [member client_id] and both [param redirect] and +## [param redirect_port] +func create_auth_url(scopes : Array[String] = ["chat:read", "chat:edit"], port := redirect_port, id : String = client_id, redirect : String = redirect_uri): + + var str_scopes : String + + for all in scopes: + + str_scopes += " " + all + str_scopes = str_scopes.strip_edges() + + var url = twitch_url + "client_id=" + id + "&redirect_uri=" + redirect + "&scope=" + str_scopes + "&state=" + str(state) + + return url + +## Utility function for creating a "state" used for different requests for some extra security as a semi password. +func make_state(len : int = 16) -> String: + + var crypto = Crypto.new() + var state = crypto.generate_random_bytes(len).hex_encode() + + return state + + +func check_auth_peer(peer : StreamPeerTCP): + + var info = peer.get_utf8_string(peer.get_available_bytes()) + printraw(info) + + var script = "" + + peer.put_data(str("HTTP/1.1 200\n\n" + script).to_utf8_buffer()) + + + var resp_state = info.split("&state=") + +# Ensures that the received state is correct. + if !resp_state.size() > 1 or resp_state[0] == state: + + return + + + var token = info.split("access_token=")[1].split("&scope=")[0].strip_edges() + printraw("Token: ", token, "\n") + self.token = token + token_received.emit(token) + + +func check_chat_socket(dict): + + prints(dict.user, dict.message) + + + diff --git a/addons/no_twitch/websocket_client.gd b/addons/no_twitch/websocket_client.gd new file mode 100644 index 0000000..cfe4752 --- /dev/null +++ b/addons/no_twitch/websocket_client.gd @@ -0,0 +1,58 @@ +extends WebSocketPeer +class_name Websocket_Client + +## Helper Class for handling the freaking polling of a WebSocketPeer + +## Emitted when [method WebSocketPeer.get_ready_state()] returns +## [member WebSocketPeer.STATE_OPEN] +signal socket_open +## Emitted when [method WebSocketPeer.get_ready_state()] returns +## [member WebSocketPeer.STATE_CLOSED] +signal socket_closed(close_code, close_reason) +## Emitted when [method WebSocketPeer.get_ready_state()] returns +## [member WebSocketPeer.STATE_CONNECTING] +signal socket_connecting +## Emitted when [method WebSocketPeer.get_ready_state()] returns +## [member WebSocketPeer.STATE_CLOSING] +signal socket_closing +## Emitted when [method WebSocketPeer.get_available_packets()] returns greater +## than 0. Or, when a packet has been received. +signal packet_received(packet_data) + +## Works as a wrapper around [method WebSocketPeer.poll] to handle the logic of +## checking get_ready_state() more simply. +func poll_socket(): + + poll() + + var state = get_ready_state() + + match state: + + STATE_OPEN: + + socket_open.emit(get_connected_host(), get_connected_port()) + + if get_available_packet_count() > 0: + + packet_received.emit(get_packet()) + + + + STATE_CONNECTING: + + socket_connecting.emit() + + + STATE_CLOSING: + + socket_closing.emit() + + + STATE_CLOSED: + + socket_closed.emit(get_close_code(), get_close_reason()) + + + + diff --git a/classes/connections/connections.gd b/classes/connections/connections.gd index 4acff4d..3d9dc36 100644 --- a/classes/connections/connections.gd +++ b/classes/connections/connections.gd @@ -1,3 +1,9 @@ class_name Connections static var obs_websocket +static var twitch + +static func _twitch_chat_received(msg_dict : Dictionary): + + DeckHolder.send_event(&"twitch_chat", msg_dict) + diff --git a/classes/deck/nodes/twitch_chat_received.gd b/classes/deck/nodes/twitch_chat_received.gd new file mode 100644 index 0000000..54dbb7a --- /dev/null +++ b/classes/deck/nodes/twitch_chat_received.gd @@ -0,0 +1,28 @@ +extends DeckNode + + +func _init(): + name = "Twitch Chat Received" + node_type = "twitch_chat_received" + description = "Receives Twitch Chat Events from a Twitch Connection" + category = "twitch" + + add_output_port(DeckType.Types.STRING, "Username") + add_output_port(DeckType.Types.STRING, "Message") + add_output_port(DeckType.Types.STRING, "Channel") + add_output_port(DeckType.Types.DICTIONARY, "Tags") + + + +func _event_received(event_name : StringName, event_data : Dictionary = {}): + + if event_name != &"twitch_chat": + + return + + + send(0, event_data.username) + send(1, event_data.message) + send(2, event_data.channel) + send(3, event_data) + diff --git a/classes/deck/nodes/twitch_send_chat.gd b/classes/deck/nodes/twitch_send_chat.gd new file mode 100644 index 0000000..75cf5f0 --- /dev/null +++ b/classes/deck/nodes/twitch_send_chat.gd @@ -0,0 +1,57 @@ +extends DeckNode + + +func _init(): + name = "Twitch Send Chat" + node_type = "twitch_send_chat" + description = "Sends Twitch chat Messages" + category = "twitch" + + add_input_port( + DeckType.Types.STRING, + "Message", + "field" + ) + add_input_port( + DeckType.Types.STRING, + "Channel", + "field" + ) + + add_input_port( + DeckType.Types.BOOL, + "Send", + "button" + ) + + + +func _receive(to_input_port, data: Variant, extra_data: Array = []): + + if to_input_port != 2: + + return + + + var msg = get_value_for_input_port(0) + var channel = get_value_for_input_port(1) + + if channel == null: + + channel = "" + + if msg == null: + + return + + + Connections.twitch.send_chat(msg, channel) + + +func get_value_for_input_port(port: int) -> Variant: + if request_value(port) != null: + return request_value(port) + elif get_input_ports()[port].value_callback.get_object() && get_input_ports()[port].value_callback.call() != null: + return get_input_ports()[port].value_callback.call() + else: + return null diff --git a/graph_node_renderer/deck_holder_renderer.gd b/graph_node_renderer/deck_holder_renderer.gd index 687dff2..670f108 100644 --- a/graph_node_renderer/deck_holder_renderer.gd +++ b/graph_node_renderer/deck_holder_renderer.gd @@ -32,6 +32,7 @@ enum FileMenuId { enum ConnectionsMenuId { OBS, + TWITCH, } ## Weak Reference to the Deck that is currently going to be saved. @@ -40,9 +41,11 @@ var _deck_to_save: WeakRef @onready var no_obsws: NoOBSWS = %NoOBSWS as NoOBSWS @onready var obs_setup_dialog: OBSWebsocketSetupDialog = $OBSWebsocketSetupDialog as OBSWebsocketSetupDialog +@onready var twitch_setup_dialog : TwitchSetupDialog = $Twitch_Setup_Dialog as TwitchSetupDialog func _ready() -> void: + tab_container.add_button_pressed.connect(add_empty_deck) tab_container.tab_close_requested.connect( @@ -53,6 +56,10 @@ func _ready() -> void: file_dialog.canceled.connect(disconnect_file_dialog_signals) Connections.obs_websocket = no_obsws + Connections.twitch = %Twitch_Connection + + Connections.twitch.chat_socket.chat_received_rich.connect(Connections._twitch_chat_received) + file_popup_menu.set_item_shortcut(FileMenuId.NEW, new_deck_shortcut) file_popup_menu.set_item_shortcut(FileMenuId.OPEN, open_deck_shortcut) @@ -172,6 +179,8 @@ func _on_connections_id_pressed(id: int) -> void: match id: ConnectionsMenuId.OBS: obs_setup_dialog.popup_centered() + ConnectionsMenuId.TWITCH: + twitch_setup_dialog.popup_centered() func _on_obs_websocket_setup_dialog_connect_button_pressed(state: OBSWebsocketSetupDialog.ConnectionState) -> void: diff --git a/graph_node_renderer/deck_holder_renderer.tscn b/graph_node_renderer/deck_holder_renderer.tscn index 492ddaa..ab3e0e4 100644 --- a/graph_node_renderer/deck_holder_renderer.tscn +++ b/graph_node_renderer/deck_holder_renderer.tscn @@ -1,10 +1,12 @@ -[gd_scene load_steps=16 format=3 uid="uid://duaah5x0jhkn6"] +[gd_scene load_steps=18 format=3 uid="uid://duaah5x0jhkn6"] [ext_resource type="Script" path="res://graph_node_renderer/deck_holder_renderer.gd" id="1_67g2g"] [ext_resource type="PackedScene" uid="uid://b84f2ngtcm5b8" path="res://graph_node_renderer/tab_container_custom.tscn" id="1_s3ug2"] [ext_resource type="Theme" uid="uid://dqqdqscid2iem" path="res://graph_node_renderer/default_theme.tres" id="1_tgul2"] [ext_resource type="Script" path="res://addons/no-obs-ws/NoOBSWS.gd" id="4_nu72u"] +[ext_resource type="Script" path="res://addons/no_twitch/twitch_connection.gd" id="5_3n36q"] [ext_resource type="PackedScene" uid="uid://eioso6jb42jy" path="res://graph_node_renderer/obs_websocket_setup_dialog.tscn" id="5_uo2gj"] +[ext_resource type="PackedScene" uid="uid://bq2lxmbnic4lc" path="res://graph_node_renderer/twitch_setup_dialog.tscn" id="7_7rhap"] [sub_resource type="InputEventKey" id="InputEventKey_giamc"] device = -1 @@ -114,9 +116,11 @@ unique_name_in_owner = true [node name="Connections" type="PopupMenu" parent="MarginContainer/VSplitContainer/VBoxContainer/MenuBar"] unique_name_in_owner = true -item_count = 1 +item_count = 2 item_0/text = "OBS..." item_0/id = 0 +item_1/text = "Twitch.." +item_1/id = 1 [node name="TabContainerCustom" parent="MarginContainer/VSplitContainer/VBoxContainer" instance=ExtResource("1_s3ug2")] unique_name_in_owner = true @@ -137,7 +141,15 @@ use_native_dialog = true unique_name_in_owner = true script = ExtResource("4_nu72u") +[node name="Twitch_Connection" type="Node" parent="Connections"] +unique_name_in_owner = true +script = ExtResource("5_3n36q") + [node name="OBSWebsocketSetupDialog" parent="." instance=ExtResource("5_uo2gj")] +visible = false + +[node name="Twitch_Setup_Dialog" parent="." instance=ExtResource("7_7rhap")] +visible = false [connection signal="id_pressed" from="MarginContainer/VSplitContainer/VBoxContainer/MenuBar/File" to="." method="_on_file_id_pressed"] [connection signal="id_pressed" from="MarginContainer/VSplitContainer/VBoxContainer/MenuBar/Connections" to="." method="_on_connections_id_pressed"] diff --git a/graph_node_renderer/twitch_setup_dialog.gd b/graph_node_renderer/twitch_setup_dialog.gd new file mode 100644 index 0000000..bd31886 --- /dev/null +++ b/graph_node_renderer/twitch_setup_dialog.gd @@ -0,0 +1,18 @@ +extends ConfirmationDialog +class_name TwitchSetupDialog + +func _ready(): + + %Authenticate.pressed.connect(authenticate_twitch) + confirmed.connect(connect_to_chat) + + +func authenticate_twitch(): + + Connections.twitch.authenticate_with_twitch(%Client_ID.text) + + +func connect_to_chat(): + + Connections.twitch.setup_chat_connection(%Default_Chat.text) + diff --git a/graph_node_renderer/twitch_setup_dialog.tscn b/graph_node_renderer/twitch_setup_dialog.tscn new file mode 100644 index 0000000..60a57b7 --- /dev/null +++ b/graph_node_renderer/twitch_setup_dialog.tscn @@ -0,0 +1,48 @@ +[gd_scene load_steps=2 format=3 uid="uid://bq2lxmbnic4lc"] + +[ext_resource type="Script" path="res://graph_node_renderer/twitch_setup_dialog.gd" id="1_xx7my"] + +[node name="twitch_setup_dialog" type="ConfirmationDialog"] +title = "Twitch Connection Setup" +position = Vector2i(0, 36) +size = Vector2i(375, 158) +visible = true +min_size = Vector2i(375, 150) +script = ExtResource("1_xx7my") + +[node name="VBoxContainer" type="VBoxContainer" parent="."] +anchors_preset = 5 +anchor_left = 0.5 +anchor_right = 0.5 +offset_left = -179.5 +offset_top = 8.0 +offset_right = 179.5 +offset_bottom = 109.0 +grow_horizontal = 2 +size_flags_horizontal = 4 + +[node name="Authentication" type="HBoxContainer" parent="VBoxContainer"] +layout_mode = 2 + +[node name="Client_ID" type="LineEdit" parent="VBoxContainer/Authentication"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +tooltip_text = "The Twitch Client ID used by this Connection for it's Authentication. Defaults to the built in Client ID that the program ships with." +placeholder_text = "Custom Client ID (Optional)" + +[node name="Authenticate" type="Button" parent="VBoxContainer/Authentication"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 0.25 +text = "Authenticate" + +[node name="CheckBox" type="CheckBox" parent="VBoxContainer"] +layout_mode = 2 +text = "Extra Chat Info" + +[node name="Default_Chat" type="LineEdit" parent="VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +placeholder_text = "Default Chat Channel" diff --git a/project.godot b/project.godot index 01d38d7..f8f7f75 100644 --- a/project.godot +++ b/project.godot @@ -25,7 +25,7 @@ NodeDB="*res://classes/deck/node_db.gd" [editor_plugins] -enabled=PackedStringArray("res://addons/no-obs-ws/plugin.cfg") +enabled=PackedStringArray("res://addons/no-obs-ws/plugin.cfg", "res://addons/no_twitch/plugin.cfg") [input]