diff --git a/addons/no_twitch/chat_socket.gd b/addons/no_twitch/chat_socket.gd index fc8ef21..4be540d 100644 --- a/addons/no_twitch/chat_socket.gd +++ b/addons/no_twitch/chat_socket.gd @@ -6,6 +6,7 @@ class_name Chat_Socket signal chat_received signal chat_received_raw signal chat_received_rich +signal irc_received signal chat_connected @@ -16,7 +17,7 @@ var user_regex := RegEx.new() var user_pattern := r":([\w]+)!" -func _init(owner : Twitch_Connection): +func _init(owner : No_Twitch): chat_received_rich.connect(owner.check_chat_socket.bind(true)) chat_received.connect(owner.check_chat_socket) @@ -96,7 +97,7 @@ func parse_chat_msg(msg : String, tags : Dictionary): 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) + #(__username_regex.search(split[0]).get_string(1), split[3].right(1), split[2], tags) if !tags.is_empty(): @@ -112,18 +113,14 @@ func parse_chat_msg(msg : String, tags : Dictionary): prints("Connection Established", msg) chat_connected.emit() - # Chat Joining Message - "JOIN": - - pass - - # Chat Leaving Message - "PART": - - pass + + # General IRC Messages, Notices etc. + _: + irc_received.emit(msg) + #@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. @@ -150,7 +147,7 @@ func send_chat(msg : String, channel : String = ""): if channels.is_empty(): - push_error("NoTwitch Error: No Twitch channels have been joined.") + push_error("No_Twitch Error: No Twitch channels have been joined.") return diff --git a/addons/no_twitch/demo/demo_scene.gd b/addons/no_twitch/demo/demo_scene.gd index e03a31d..855cac1 100644 --- a/addons/no_twitch/demo/demo_scene.gd +++ b/addons/no_twitch/demo/demo_scene.gd @@ -1,14 +1,15 @@ extends Control -var demo_events : Array[Twitch_Connection.EventSub_Subscription] = [ +var demo_events : Array[No_Twitch.EventSub_Subscription] = [ - Twitch_Connection.EventSub_Subscription.new("channel.update", {"broadcaster_user_id" : ""}, "", "2") + No_Twitch.EventSub_Subscription.new("channel.update", {"broadcaster_user_id" : ""}, "", "2") ] +@onready var twitch : No_Twitch = %Twitch_Connection func _ready(): - $Twitch_Connection.token_received.connect(save_token) + twitch.token_received.connect(save_token) %Get_User.pressed.connect(func(): var resp = %Twitch_Connection.request_user_info() @@ -25,13 +26,20 @@ func _ready(): %Connect_Channel_Points.pressed.connect(func(): - await %Twitch_Connection.setup_eventsub_connection() + await twitch.setup_eventsub_connection() print("Passed Await") - %Twitch_Connection.eventsub_socket.notif_received.connect(eventsub_notif_received) + twitch.eventsub_socket.notif_received.connect(eventsub_notif_received) - var resp = %Twitch_Connection.subscribe_to_channel_points(%Twitch_Connection.user_info.id) + var resp = await twitch.subscribe_to_channel_points(twitch.user_info.id) resp.response_received.connect(print_http_result) + ) + %Get_Followers.pressed.connect(func(): + + var resp = twitch.twitch_request("https://api.twitch.tv/helix/channels/followers?broadcaster_id=" + twitch.user_info.id) + await resp.response_received + print(resp.inf_data) + ) load_token() diff --git a/addons/no_twitch/demo/demo_scene.tscn b/addons/no_twitch/demo/demo_scene.tscn index c3435cd..c825285 100644 --- a/addons/no_twitch/demo/demo_scene.tscn +++ b/addons/no_twitch/demo/demo_scene.tscn @@ -91,6 +91,11 @@ unique_name_in_owner = true layout_mode = 2 text = "Connect Channel Points" +[node name="Get_Followers" type="Button" parent="VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +text = "Get Followers" + [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 index ebc9937..04585aa 100644 --- a/addons/no_twitch/demo/test_button.gd +++ b/addons/no_twitch/demo/test_button.gd @@ -1,7 +1,7 @@ extends Button -@onready var twitch_connection : Twitch_Connection = $"../../Twitch_Connection" +@onready var twitch_connection : No_Twitch = $"../../Twitch_Connection" func _pressed(): diff --git a/addons/no_twitch/eventsub_socket.gd b/addons/no_twitch/eventsub_socket.gd index b83deb8..0e2a2ff 100644 --- a/addons/no_twitch/eventsub_socket.gd +++ b/addons/no_twitch/eventsub_socket.gd @@ -5,7 +5,7 @@ const eventsub_url := "wss://eventsub.wss.twitch.tv/ws" ## Stores the "session id" for this EventSub connection. var session_id -var connection : Twitch_Connection +var connection : No_Twitch signal notif_received(data) @@ -14,7 +14,7 @@ signal welcome_received() var keepalive_timer := 0 var timeout_time : int -## Dictionary used for storing all the subscribed events in the format "subscription_type : Twitch_Connection.EventSub_Subscription" +## Dictionary used for storing all the subscribed events in the format "subscription_type : No_Twitch.EventSub_Subscription" var subscribed_events : Dictionary @@ -41,7 +41,7 @@ func poll_socket(): ## Handles setting up the connection to EventSub with an Array of the Events that should be subscribed to. -func connect_to_eventsub(events : Array[Twitch_Connection.EventSub_Subscription]): +func connect_to_eventsub(events : Array[No_Twitch.EventSub_Subscription]): connect_to_url(eventsub_url) await welcome_received @@ -55,9 +55,9 @@ func connect_to_eventsub(events : Array[Twitch_Connection.EventSub_Subscription] ## Utility function for subscribing to multiple Twitch EventSub events at once. -func subscribe_to_events(events : Array[Twitch_Connection.EventSub_Subscription]): +func subscribe_to_events(events : Array[No_Twitch.EventSub_Subscription]): - var responses : Array[Twitch_Connection.HTTPResponse] + var responses : Array[No_Twitch.HTTPResponse] for all in events: @@ -72,7 +72,7 @@ func subscribe_to_events(events : Array[Twitch_Connection.EventSub_Subscription] return responses -func new_eventsub_subscription(info, event_subscription : Twitch_Connection.EventSub_Subscription): +func new_eventsub_subscription(info, event_subscription : No_Twitch.EventSub_Subscription): if !event_subscription in subscribed_events: diff --git a/addons/no_twitch/twitch_connection.gd b/addons/no_twitch/twitch_connection.gd index edd74e6..3b3de60 100644 --- a/addons/no_twitch/twitch_connection.gd +++ b/addons/no_twitch/twitch_connection.gd @@ -1,5 +1,5 @@ extends Node -class_name Twitch_Connection +class_name No_Twitch ## Handler for a Connection to a Single Twitch Account ## @@ -113,7 +113,7 @@ func setup_eventsub_connection(events : Array[EventSub_Subscription] = [], timeo return ret -## Adds a new subscription to a given EventSub Event using a [Twitch_Connection.EventSub_Subscription] +## Adds a new subscription to a given EventSub Event using a [No_Twitch.EventSub_Subscription] ## to handle the data. func add_eventsub_subscription(sub_type : EventSub_Subscription): @@ -134,7 +134,7 @@ func subscribe_to_channel_points(user_id : String): if !eventsub_socket.socket_open: - push_error("NoTwitch Error: No EventSub Connection, please use setup_eventsub_connection first") + push_error("No_Twitch Error: No EventSub Connection, please use setup_eventsub_connection first") return @@ -181,15 +181,10 @@ func create_auth_url(port := redirect_port, id : String = client_id, redirect : return url ## Utility Function for making a generic HTTP Request to Twitch -func twitch_request(url : String, method : HTTPClient.Method = HTTPClient.METHOD_GET, body := ""): +func twitch_request(url : String, method : HTTPClient.Method = HTTPClient.METHOD_GET, body := "") -> HTTPResponse: var headers : Array = ["Authorization: Bearer " + token, "Client-Id: " + client_id] - if !body.is_empty(): - - headers.append("Content-Type: application/json") - - # Adds the Content type to the headers if we're actually sending some data along if method != HTTPClient.METHOD_GET: @@ -326,19 +321,28 @@ class HTTPResponse: info["headers"] = headers if info.has("error"): - - push_error("NoTwitch Twitch API Error: " + str(info)) + + push_error("No_Twitch Twitch API Error: " + str(info)) + emit_response(info, http, storage) return - - - info["data"] = info.get("data", [[]])[0] + + + if info.data is Array and info.data.size() == 1: + info.data = info.data[0] + # Revisit later cause Twitch weirdness + #info["data"] = info.get("data", [[]])[0] print("Response Received") + emit_response(info, http, storage) + + func emit_response(info, http, storage): + inf_data = info response_received.emit(info) - + http.queue_free() storage.erase(self) + diff --git a/classes/deck/nodes/twitch/twitch_account_info.gd b/classes/deck/nodes/twitch/twitch_account_info.gd index 8f6c711..ac58e69 100644 --- a/classes/deck/nodes/twitch/twitch_account_info.gd +++ b/classes/deck/nodes/twitch/twitch_account_info.gd @@ -13,7 +13,7 @@ func _init(): add_output_port(DeckType.Types.STRING, "Login", "", Port.UsageType.VALUE_REQUEST) add_output_port(DeckType.Types.STRING, "Display Name", "", Port.UsageType.VALUE_REQUEST) - add_output_port(DeckType.Types.STRING, "Profile Picture URL", "", Port.UsageType.VALUE_REQUEST) + add_output_port(DeckType.Types.STRING, "User ID", "", Port.UsageType.VALUE_REQUEST) add_output_port(DeckType.Types.DICTIONARY, "User Dictionary", "", Port.UsageType.VALUE_REQUEST) #{ @@ -55,7 +55,7 @@ func _value_request(from_port): 2: # Profile Picture URL - return user_dict.profile_image_url + return user_dict.id _: # Dictionary diff --git a/classes/deck/nodes/twitch/twitch_add_eventsub_subscription.gd b/classes/deck/nodes/twitch/twitch_add_eventsub_subscription.gd index 7cb4cfe..2a46656 100644 --- a/classes/deck/nodes/twitch/twitch_add_eventsub_subscription.gd +++ b/classes/deck/nodes/twitch/twitch_add_eventsub_subscription.gd @@ -12,7 +12,7 @@ enum inputs { } -var subscription_data : Twitch_Connection.EventSub_Subscription +var subscription_data : No_Twitch.EventSub_Subscription func _init(): name = "Twitch Add EventSub Subscription" @@ -68,8 +68,8 @@ func _receive(to_input_port, data: Variant) -> void: -# Creates an instance of Twitch_Connection.EventSub_Subscription to store the data with all the given inputs. - subscription_data = Twitch_Connection.EventSub_Subscription.new(event, sub_data) +# Creates an instance of No_Twitch.EventSub_Subscription to store the data with all the given inputs. + subscription_data = No_Twitch.EventSub_Subscription.new(event, sub_data) # Checks if the data has a version field, if so sets it on the EventSub_Subscription if sub_data.has("version"): @@ -81,7 +81,7 @@ func _receive(to_input_port, data: Variant) -> void: req.response_received.connect(eventsub_subscription_response) -## Handles checking the [Twitch_Connection.HTTPResponse] returned by [method Twitch_Connection.add_eventsub_subscription] to ensure that it succeeded. +## Handles checking the [No_Twitch.HTTPResponse] returned by [method No_Twitch.add_eventsub_subscription] to ensure that it succeeded. func eventsub_subscription_response(data): match data.code: diff --git a/classes/deck/nodes/twitch/twitch_chat_received.gd b/classes/deck/nodes/twitch/twitch_chat_received.gd index d831663..94ad5c8 100644 --- a/classes/deck/nodes/twitch/twitch_chat_received.gd +++ b/classes/deck/nodes/twitch/twitch_chat_received.gd @@ -14,15 +14,17 @@ func _init(): node_type = "twitch_chat_received" description = "Receives Twitch chat events from a Twitch connection." - add_output_port(DeckType.Types.STRING, "Username", "", Port.UsageType.TRIGGER) - add_output_port(DeckType.Types.STRING, "Message", "", Port.UsageType.TRIGGER) - add_output_port(DeckType.Types.STRING, "Channel", "", Port.UsageType.TRIGGER) - add_output_port(DeckType.Types.DICTIONARY, "Tags", "", Port.UsageType.TRIGGER) - add_output_port( - DeckType.Types.BOOL, - "On receive" + DeckType.Types.ANY, + "On receive", + "", + Port.UsageType.TRIGGER, ) + 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") + @@ -38,22 +40,24 @@ func _event_received(event_name : StringName, event_data : Dictionary = {}): tags = event_data var id = UUID.v4() - send(0, username, id) - send(1, message, id) - send(2, channel, id) - send(3, tags, id) - send(4, true, id) + + send(0, null, id) + send(1, username, id) + send(2, message, id) + send(3, channel, id) + send(4, tags, id) func _value_request(on_port: int) -> Variant: match on_port: - 0: - return username 1: - return message + return username 2: - return channel + return message 3: + return channel + 4: return tags - _: + 0, _: return null + diff --git a/classes/deck/nodes/twitch/twitch_get_request.gd b/classes/deck/nodes/twitch/twitch_get_request.gd new file mode 100644 index 0000000..3455694 --- /dev/null +++ b/classes/deck/nodes/twitch/twitch_get_request.gd @@ -0,0 +1,85 @@ +# (c) 2023-present Eroax +# (c) 2023-present Yagich +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +extends DeckNode + + +enum inputs { + url, + params, + trigger +} + +func _init(): + name = "Twitch Get Request" + node_type = name.to_snake_case() + description = "A simple node for doing an HTTP Request to Twitch using the Authorized Account Information" + + add_input_port(DeckType.Types.STRING, "Request URL", "field") + add_input_port(DeckType.Types.DICTIONARY, "Parameters") + add_input_port(DeckType.Types.ANY, "Trigger") + + add_output_port(DeckType.Types.DICTIONARY, "Response", "", Port.UsageType.TRIGGER) + + + +func _receive(to_input_port, data: Variant): + + if (to_input_port == 0 or to_input_port == 1) and data == null: + + return + + + var params : Dictionary + var url : String + + match to_input_port: + + inputs.url: + + url = str(data) + params = await resolve_input_port_value_async(inputs.params) + + inputs.params: + + if not data is Dictionary: + return + + params = data + url = await resolve_input_port_value_async(inputs.url) + + inputs.trigger: + + url = await resolve_input_port_value_async(inputs.url) + params = await resolve_input_port_value_async(inputs.params) + + + + if url == null or not url.begins_with("https://api.twitch.tv/helix/"): + return + + var twitch := Connections.twitch + var url_string := url + + if not params.is_empty() and not url_string.ends_with("?"): + + url_string += "?" + + + for key in params: + + var val = params[key] + + url_string += key + "=" + val + "&" + + + url_string = url_string.trim_suffix("&") + + var resp := twitch.twitch_request(url_string) + + resp.response_received.connect(func(info): + + info.erase("headers") + send(0, info) + ) + diff --git a/classes/types/deck_type.gd b/classes/types/deck_type.gd index 7c74457..9a00c88 100644 --- a/classes/types/deck_type.gd +++ b/classes/types/deck_type.gd @@ -32,6 +32,7 @@ const GODOT_TYPES_MAP := { const INVERSE_GODOT_TYPES_MAP := { TYPE_BOOL: Types.BOOL, TYPE_FLOAT: Types.NUMERIC, + TYPE_INT: Types.NUMERIC, TYPE_STRING: Types.STRING, TYPE_ARRAY: Types.ARRAY, TYPE_DICTIONARY: Types.DICTIONARY,