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_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

View file

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

View file

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

View file

@ -1,7 +1,7 @@
extends Button
@onready var twitch_connection : Twitch_Connection = $"../../Twitch_Connection"
@onready var twitch_connection : No_Twitch = $"../../Twitch_Connection"
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.
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:

View file

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

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, "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

View file

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

View file

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

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 := {
TYPE_BOOL: Types.BOOL,
TYPE_FLOAT: Types.NUMERIC,
TYPE_INT: Types.NUMERIC,
TYPE_STRING: Types.STRING,
TYPE_ARRAY: Types.ARRAY,
TYPE_DICTIONARY: Types.DICTIONARY,