Updates NoTwitch + Implements Twitch Get Request (#124)

Patch the Array/Dictionary/Array problem with NoTwitch
Plus tweaks the existing Connected Account Info + Chat Received Nodes.
Reviewed-on: https://codeberg.org/StreamGraph/StreamGraph/pulls/124
Co-authored-by: Eroax <eroaxebusiness@duck.com>
Co-committed-by: Eroax <eroaxebusiness@duck.com>
This commit is contained in:
Eroax 2024-03-17 10:28:54 +00:00 committed by Eroax
parent edeb8e22dc
commit b94c3702cc
11 changed files with 167 additions and 63 deletions

View file

@ -6,6 +6,7 @@ class_name Chat_Socket
signal chat_received signal chat_received
signal chat_received_raw signal chat_received_raw
signal chat_received_rich signal chat_received_rich
signal irc_received
signal chat_connected signal chat_connected
@ -16,7 +17,7 @@ var user_regex := RegEx.new()
var user_pattern := r":([\w]+)!" 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_rich.connect(owner.check_chat_socket.bind(true))
chat_received.connect(owner.check_chat_socket) 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)) msg_dict.merge(parse_tags(tags))
prints(msg_dict.username, msg_dict.message, msg_dict.channel) 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(): if !tags.is_empty():
@ -112,15 +113,11 @@ func parse_chat_msg(msg : String, tags : Dictionary):
prints("Connection Established", msg) prints("Connection Established", msg)
chat_connected.emit() chat_connected.emit()
# Chat Joining Message
"JOIN":
pass # General IRC Messages, Notices etc.
_:
# Chat Leaving Message irc_received.emit(msg)
"PART":
pass
@ -150,7 +147,7 @@ func send_chat(msg : String, channel : String = ""):
if channels.is_empty(): 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 return

View file

@ -1,14 +1,15 @@
extends Control 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(): func _ready():
$Twitch_Connection.token_received.connect(save_token) twitch.token_received.connect(save_token)
%Get_User.pressed.connect(func(): %Get_User.pressed.connect(func():
var resp = %Twitch_Connection.request_user_info() var resp = %Twitch_Connection.request_user_info()
@ -25,13 +26,20 @@ func _ready():
%Connect_Channel_Points.pressed.connect(func(): %Connect_Channel_Points.pressed.connect(func():
await %Twitch_Connection.setup_eventsub_connection() await twitch.setup_eventsub_connection()
print("Passed Await") 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) 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() load_token()

View file

@ -91,6 +91,11 @@ unique_name_in_owner = true
layout_mode = 2 layout_mode = 2
text = "Connect Channel Points" 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="."] [node name="Twitch_Connection" type="Node" parent="."]
unique_name_in_owner = true unique_name_in_owner = true
script = ExtResource("1_13a4v") script = ExtResource("1_13a4v")

View file

@ -1,7 +1,7 @@
extends Button extends Button
@onready var twitch_connection : Twitch_Connection = $"../../Twitch_Connection" @onready var twitch_connection : No_Twitch = $"../../Twitch_Connection"
func _pressed(): func _pressed():

View file

@ -5,7 +5,7 @@ const eventsub_url := "wss://eventsub.wss.twitch.tv/ws"
## Stores the "session id" for this EventSub connection. ## Stores the "session id" for this EventSub connection.
var session_id var session_id
var connection : Twitch_Connection var connection : No_Twitch
signal notif_received(data) signal notif_received(data)
@ -14,7 +14,7 @@ signal welcome_received()
var keepalive_timer := 0 var keepalive_timer := 0
var timeout_time : int 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 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. ## 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) connect_to_url(eventsub_url)
await welcome_received 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. ## 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: for all in events:
@ -72,7 +72,7 @@ func subscribe_to_events(events : Array[Twitch_Connection.EventSub_Subscription]
return responses 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: if !event_subscription in subscribed_events:

View file

@ -1,5 +1,5 @@
extends Node extends Node
class_name Twitch_Connection class_name No_Twitch
## Handler for a Connection to a Single Twitch Account ## Handler for a Connection to a Single Twitch Account
## ##
@ -113,7 +113,7 @@ func setup_eventsub_connection(events : Array[EventSub_Subscription] = [], timeo
return ret 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. ## to handle the data.
func add_eventsub_subscription(sub_type : EventSub_Subscription): 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: 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 return
@ -181,15 +181,10 @@ func create_auth_url(port := redirect_port, id : String = client_id, redirect :
return url return url
## Utility Function for making a generic HTTP Request to Twitch ## 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] 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 # Adds the Content type to the headers if we're actually sending some data along
if method != HTTPClient.METHOD_GET: if method != HTTPClient.METHOD_GET:
@ -327,13 +322,21 @@ class HTTPResponse:
if info.has("error"): 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 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") print("Response Received")
emit_response(info, http, storage)
func emit_response(info, http, storage):
inf_data = info inf_data = info
response_received.emit(info) response_received.emit(info)
@ -342,6 +345,7 @@ class HTTPResponse:
## Data handler class for making it easier to send the data used for EventSub Subscriptions over ## Data handler class for making it easier to send the data used for EventSub Subscriptions over
## [member eventsub_socket] ## [member eventsub_socket]
class EventSub_Subscription: class EventSub_Subscription:

View file

@ -13,7 +13,7 @@ func _init():
add_output_port(DeckType.Types.STRING, "Login", "", Port.UsageType.VALUE_REQUEST) 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, "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) 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 2: # Profile Picture URL
return user_dict.profile_image_url return user_dict.id
_: # Dictionary _: # Dictionary

View file

@ -12,7 +12,7 @@ enum inputs {
} }
var subscription_data : Twitch_Connection.EventSub_Subscription var subscription_data : No_Twitch.EventSub_Subscription
func _init(): func _init():
name = "Twitch Add EventSub Subscription" 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. # Creates an instance of No_Twitch.EventSub_Subscription to store the data with all the given inputs.
subscription_data = Twitch_Connection.EventSub_Subscription.new(event, sub_data) 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 # Checks if the data has a version field, if so sets it on the EventSub_Subscription
if sub_data.has("version"): if sub_data.has("version"):
@ -81,7 +81,7 @@ func _receive(to_input_port, data: Variant) -> void:
req.response_received.connect(eventsub_subscription_response) 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): func eventsub_subscription_response(data):
match data.code: match data.code:

View file

@ -14,15 +14,17 @@ func _init():
node_type = "twitch_chat_received" node_type = "twitch_chat_received"
description = "Receives Twitch chat events from a Twitch connection." 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( add_output_port(
DeckType.Types.BOOL, DeckType.Types.ANY,
"On receive" "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 tags = event_data
var id = UUID.v4() var id = UUID.v4()
send(0, username, id)
send(1, message, id) send(0, null, id)
send(2, channel, id) send(1, username, id)
send(3, tags, id) send(2, message, id)
send(4, true, id) send(3, channel, id)
send(4, tags, id)
func _value_request(on_port: int) -> Variant: func _value_request(on_port: int) -> Variant:
match on_port: match on_port:
0:
return username
1: 1:
return message return username
2: 2:
return channel return message
3: 3:
return channel
4:
return tags return tags
_: 0, _:
return null return null

View file

@ -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)
)

View file

@ -32,6 +32,7 @@ const GODOT_TYPES_MAP := {
const INVERSE_GODOT_TYPES_MAP := { const INVERSE_GODOT_TYPES_MAP := {
TYPE_BOOL: Types.BOOL, TYPE_BOOL: Types.BOOL,
TYPE_FLOAT: Types.NUMERIC, TYPE_FLOAT: Types.NUMERIC,
TYPE_INT: Types.NUMERIC,
TYPE_STRING: Types.STRING, TYPE_STRING: Types.STRING,
TYPE_ARRAY: Types.ARRAY, TYPE_ARRAY: Types.ARRAY,
TYPE_DICTIONARY: Types.DICTIONARY, TYPE_DICTIONARY: Types.DICTIONARY,