Reworks Twitch Nodes to work with the new Triggers Workflow. (#82)

Closes #59 by reworking Twitch Nodes to use the new Trigger workflow that allows inputs that trigger through Ports with Port.UsageType.BOTH as well the functionality for Port.UsageType.VALUE_REQUEST and Port.UsageType.TRIGGER.

Co-authored-by: Eroax <eroaxebusiness@duck>
Reviewed-on: https://codeberg.org/StreamGraph/StreamGraph/pulls/82
This commit is contained in:
Eroax 2024-02-26 11:36:19 +00:00
parent c07d810bcf
commit 2d5fcd25f6
19 changed files with 404 additions and 103 deletions

View file

@ -148,6 +148,12 @@ func send_chat(msg : String, channel : String = ""):
if channel.is_empty(): if channel.is_empty():
if channels.is_empty():
push_error("NoTwitch Error: No Twitch channels have been joined.")
return
channel = channels[0] channel = channels[0]
@ -166,6 +172,6 @@ func join_chat(channel : String):
## Utility function that handles leaving the supplied [param channel]'s chat. ## Utility function that handles leaving the supplied [param channel]'s chat.
func leave_chat(channel : String): func leave_chat(channel : String):
send_chat("PART #" + channel) send_text("PART #" + channel)
channels.erase(channel) channels.erase(channel)

View file

@ -315,9 +315,12 @@ class HTTPResponse:
func request_complete(result: int, response_code: int, headers: PackedStringArray, body: PackedByteArray, http, storage): func request_complete(result: int, response_code: int, headers: PackedStringArray, body: PackedByteArray, http, storage):
var info : Dictionary var info : Dictionary
if !body.is_empty(): var str = body.get_string_from_utf8()
if !body.is_empty() and !JSON.parse_string(str) == null:
info = JSON.parse_string(body.get_string_from_utf8()) info = JSON.parse_string(body.get_string_from_utf8())
info["result"] = result info["result"] = result
info["code"] = response_code info["code"] = response_code
info["headers"] = headers info["headers"] = headers

View file

@ -22,20 +22,50 @@ func _init() -> void:
"Result" "Result"
) )
## Handles Trigger inputs, calculates.
func _receive(port : int, data : Variant) -> void:
var va = data
if !DeckType.is_valid_vector(data):
DeckHolder.logger.log_node("Vector Add: Port %d: Supplied data was not a valid Vector. Please ensure it is a Dictionary with keys 'x' and 'y'" % port)
return
var vb
if port == 0:
vb = await request_value_async(1)
else:
vb = await request_value_async(0)
var added = add_vectors(va, vb)
if added is Dictionary:
send(0, added)
func _value_request(_on_port: int) -> Variant: func _value_request(_on_port: int) -> Variant:
var va = await request_value_async(0) var va = await request_value_async(0)
var vb = await request_value_async(1) var vb = await request_value_async(1)
return add_vectors(va, vb)
func add_vectors(va, vb):
if not va or not vb: if not va or not vb:
DeckHolder.logger.log_node("Vector Add: one of the vectors is invalid.", Logger.LogType.ERROR) DeckHolder.logger.log_node("Vector Add: one of the vectors is invalid.", Logger.LogType.ERROR)
return null return null
if not (va as Dictionary).has("x") or not (va as Dictionary).has("y"): if !DeckType.is_valid_vector(va):
DeckHolder.logger.log_node("Vector Add: one of the vectors is invalid.", Logger.LogType.ERROR) DeckHolder.logger.log_node("Vector Add: one of the vectors is invalid.", Logger.LogType.ERROR)
return null return null
if not (vb as Dictionary).has("x") or not (vb as Dictionary).has("y"): if !DeckType.is_valid_vector(vb):
DeckHolder.logger.log_node("Vector Add: one of the vectors is invalid.", Logger.LogType.ERROR) DeckHolder.logger.log_node("Vector Add: one of the vectors is invalid.", Logger.LogType.ERROR)
return null return null
@ -44,3 +74,4 @@ func _value_request(_on_port: int) -> Variant:
res["y"] = va["y"] + vb["y"] res["y"] = va["y"] + vb["y"]
return res return res

View file

@ -25,6 +25,23 @@ func _init() -> void:
"Vector" "Vector"
) )
func _receive(port : int, data : Variant) -> void:
var x
var y
if port == 0:
x = data
y = await resolve_input_port_value_async(1)
else:
y = data
x = await resolve_input_port_value_async(0)
send(0, {"x" : float(x), "y" : float(y)})
func _value_request(_on_port: int) -> Dictionary: func _value_request(_on_port: int) -> Dictionary:
var x = float(await resolve_input_port_value_async(0)) var x = float(await resolve_input_port_value_async(0))

View file

@ -24,13 +24,25 @@ func _init() -> void:
) )
func _receive(port : int, data : Variant) -> void:
if !data or !DeckType.is_valid_vector(data):
DeckHolder.logger.log_node("Vector Decompose: the vector is invalid.", Logger.LogType.ERROR)
return
var id = UUID.v4()
send(0, data.x, id)
send(1, data.y, id)
func _value_request(on_port: int) -> Variant: func _value_request(on_port: int) -> Variant:
var v = await request_value_async(0) var v = await request_value_async(0)
if not v: if not v:
DeckHolder.logger.log_node("Vector Decompose: the vector is invalid.", Logger.LogType.ERROR) DeckHolder.logger.log_node("Vector Decompose: the vector is invalid.", Logger.LogType.ERROR)
return null return null
if not (v as Dictionary).has("x") or not (v as Dictionary).has("y"): if !DeckType.is_valid_vector(v):
DeckHolder.logger.log_node("Vector Decompose: the vector is invalid.", Logger.LogType.ERROR) DeckHolder.logger.log_node("Vector Decompose: the vector is invalid.", Logger.LogType.ERROR)
return null return null

View file

@ -24,20 +24,52 @@ func _init() -> void:
) )
func _receive(port : int, data : Variant) -> void:
if !DeckType.is_valid_vector(data):
DeckHolder.logger.log_node("Vector Dot: Port %d: Supplied data was not a valid Vector. Please ensure it is a Dictionary with keys 'x' and 'y'" % port)
return
var va = data
var vb
if port == 0:
vb = await resolve_input_port_value_async(1)
else:
vb = await resolve_input_port_value_async(0)
var dot = dot_vectors(va, vb)
if dot:
send(0, dot)
func _value_request(_on_port: int) -> Variant: func _value_request(_on_port: int) -> Variant:
var va = await request_value_async(0) var va = await request_value_async(0)
var vb = await request_value_async(1) var vb = await request_value_async(1)
return dot_vectors(va, vb)
func dot_vectors(va, vb):
if not va or not vb: if not va or not vb:
DeckHolder.logger.log_node("Vector Dot: one of the vectors is invalid.", Logger.LogType.ERROR) DeckHolder.logger.log_node("Vector Dot: one of the vectors is invalid.", Logger.LogType.ERROR)
return null return null
if not (va as Dictionary).has("x") or not (va as Dictionary).has("y"): if !DeckType.is_valid_vector(va):
DeckHolder.logger.log_node("Vector Dot: one of the vectors is invalid.", Logger.LogType.ERROR) DeckHolder.logger.log_node("Vector Dot: one of the vectors is invalid.", Logger.LogType.ERROR)
return null return null
if not (vb as Dictionary).has("x") or not (vb as Dictionary).has("y"): if !DeckType.is_valid_vector(vb):
DeckHolder.logger.log_node("Vector Dot: one of the vectors is invalid.", Logger.LogType.ERROR) DeckHolder.logger.log_node("Vector Dot: one of the vectors is invalid.", Logger.LogType.ERROR)
return null return null
return va.x * vb.x + va.y * vb.y return va.x * vb.x + va.y * vb.y

View file

@ -25,17 +25,48 @@ func _init() -> void:
) )
func _receive(port : int, data : Variant) -> void:
var vec
var scale
if port == 0:
vec = data
scale = float(await resolve_input_port_value_async(1))
else:
scale = data
vec = await request_value_async(0)
var multiplied = multiply_by_scalar(vec, scale)
if multiplied is Dictionary:
send(0, multiplied)
func _value_request(_on_port: int) -> Variant: func _value_request(_on_port: int) -> Variant:
var v = await request_value_async(0)
if not v: var vec = await request_value_async(0)
var scale = float(await resolve_input_port_value_async(1))
return multiply_by_scalar(vec, scale)
func multiply_by_scalar(vec, scale):
if not vec:
DeckHolder.logger.log_node("Vector Mult: the vector is invalid.", Logger.LogType.ERROR) DeckHolder.logger.log_node("Vector Mult: the vector is invalid.", Logger.LogType.ERROR)
return null return null
if not (v as Dictionary).has("x") or not (v as Dictionary).has("y"): if !DeckType.is_valid_vector(vec):
DeckHolder.logger.log_node("Vector Mult: the vector is invalid.", Logger.LogType.ERROR) DeckHolder.logger.log_node("Vector Mult: the vector is invalid.", Logger.LogType.ERROR)
return null return null
var s = float(await resolve_input_port_value_async(1)) return {"x": vec.x * scale, "y": vec.y * scale}
return {"x": v.x * s, "y": v.y * s}

View file

@ -20,13 +20,29 @@ func _init() -> void:
) )
func _receive(port : int, data : Variant) -> void:
var normalized = normalize_vector(data)
if !normalized is Dictionary:
return
send(0, normalized)
func _value_request(_on_port: int) -> Variant: func _value_request(_on_port: int) -> Variant:
var v = await request_value_async(0) var v = await request_value_async(0)
return normalize_vector(v)
func normalize_vector(v):
if not v: if not v:
DeckHolder.logger.log_node("Vector Normalize: the vector is invalid.", Logger.LogType.ERROR) DeckHolder.logger.log_node("Vector Normalize: the vector is invalid.", Logger.LogType.ERROR)
return null return null
if not (v as Dictionary).has("x") or not (v as Dictionary).has("y"): if !DeckType.is_valid_vector(v):
DeckHolder.logger.log_node("Vector Normalize: the vector is invalid.", Logger.LogType.ERROR) DeckHolder.logger.log_node("Vector Normalize: the vector is invalid.", Logger.LogType.ERROR)
return null return null

View file

@ -23,19 +23,49 @@ func _init() -> void:
) )
func _receive(port : int, data : Variant) -> void:
var va = data
if !DeckType.is_valid_vector(data):
DeckHolder.logger.log_node("Vector Sub: Port %d: Supplied data was not a valid Vector. Please ensure it is a Dictionary with keys 'x' and 'y'" % port)
return
var vb
if port == 0:
vb = await request_value_async(1)
else:
vb = await request_value_async(0)
var calc = subtract_vectors(va, vb)
if calc is Dictionary:
send(0, calc)
func _value_request(_on_port: int) -> Variant: func _value_request(_on_port: int) -> Variant:
var va = await request_value_async(0) var va = await request_value_async(0)
var vb = await request_value_async(1) var vb = await request_value_async(1)
return subtract_vectors(va, vb)
func subtract_vectors(va, vb):
if not va or not vb: if not va or not vb:
DeckHolder.logger.log_node("Vector Sub: one of the vectors is invalid.", Logger.LogType.ERROR) DeckHolder.logger.log_node("Vector Sub: one of the vectors is invalid.", Logger.LogType.ERROR)
return null return null
if not (va as Dictionary).has("x") or not (va as Dictionary).has("y"): if !DeckType.is_valid_vector(va):
DeckHolder.logger.log_node("Vector Sub: one of the vectors is invalid.", Logger.LogType.ERROR) DeckHolder.logger.log_node("Vector Sub: one of the vectors is invalid.", Logger.LogType.ERROR)
return null return null
if not (vb as Dictionary).has("x") or not (vb as Dictionary).has("y"): if !DeckType.is_valid_vector(vb):
DeckHolder.logger.log_node("Vector Sub: one of the vectors is invalid.", Logger.LogType.ERROR) DeckHolder.logger.log_node("Vector Sub: one of the vectors is invalid.", Logger.LogType.ERROR)
return null return null
@ -44,3 +74,4 @@ func _value_request(_on_port: int) -> Variant:
res["y"] = va["y"] - vb["y"] res["y"] = va["y"] - vb["y"]
return res return res

View file

@ -11,10 +11,10 @@ func _init():
props_to_serialize = [] props_to_serialize = []
add_output_port(DeckType.Types.STRING, "Login") add_output_port(DeckType.Types.STRING, "Login", "", Port.UsageType.VALUE_REQUEST)
add_output_port(DeckType.Types.STRING, "Display Name") add_output_port(DeckType.Types.STRING, "Display Name", "", Port.UsageType.VALUE_REQUEST)
add_output_port(DeckType.Types.STRING, "Profile Picture URL") add_output_port(DeckType.Types.STRING, "Profile Picture URL", "", Port.UsageType.VALUE_REQUEST)
add_output_port(DeckType.Types.DICTIONARY, "User Dictionary") add_output_port(DeckType.Types.DICTIONARY, "User Dictionary", "", Port.UsageType.VALUE_REQUEST)
#{ #{
#"id": "141981764", #"id": "141981764",

View file

@ -4,6 +4,14 @@
extends DeckNode extends DeckNode
enum inputs {
event_name,
sub_data,
add
}
var subscription_data : Twitch_Connection.EventSub_Subscription var subscription_data : Twitch_Connection.EventSub_Subscription
func _init(): func _init():
@ -14,38 +22,60 @@ func _init():
props_to_serialize = [] props_to_serialize = []
#Event Name #Event Name
add_input_port(DeckType.Types.STRING, "Event Name", "field") add_input_port(
DeckType.Types.STRING,
"Event Name", "field"
)
#Subscription Data #Subscription Data
add_input_port(DeckType.Types.DICTIONARY, "Subscription Data") add_input_port(
DeckType.Types.DICTIONARY,
"Subscription Data"
)
#Trigger #Trigger
add_input_port(DeckType.Types.ANY, "Add Subscription", "button") add_input_port(
DeckType.Types.ANY,
"Add Subscription", "button",
Port.UsageType.TRIGGER
)
func _receive(to_input_port, _data: Variant): func _receive(to_input_port, data: Variant) -> void:
if to_input_port != 2: var event
var sub_data
return match to_input_port:
inputs.event_name:
event = str(data)
sub_data = await resolve_input_port_value_async(1)
inputs.sub_data:
if data == null or not data is Dictionary:
DeckHolder.logger.log_node("%s: Port %d: Incorrect Subscription Data Connected, please supply a Dictionary with condition and if needed, version. Last supplied Data was: " + str(data), Logger.LogType.ERROR)
return
var input_data = await resolve_input_port_value_async(1) sub_data = data
event = await resolve_input_port_value_async(0)
inputs.add:
event = await resolve_input_port_value_async(0)
sub_data = await resolve_input_port_value_async(1)
if input_data == null or "condition" not in input_data.keys():
DeckHolder.logger.log_node(name + ": Incorrect Subscription Data Connected, please supply a Dictionary with condition and if needed, version. Last supplied Data was: " + str(input_data), Logger.LogType.ERROR)
return
var sub_type = await resolve_input_port_value_async(0)
# Creates an instance of Twitch_Connection.EventSub_Subscription to store the data with all the given inputs. # Creates an instance of Twitch_Connection.EventSub_Subscription to store the data with all the given inputs.
subscription_data = Twitch_Connection.EventSub_Subscription.new(sub_type, input_data.condition) subscription_data = Twitch_Connection.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 input_data.has("version"): if sub_data.has("version"):
subscription_data.version = input_data.version subscription_data.version = sub_data.version
# Calls the connection to add the Subscription # Calls the connection to add the Subscription

View file

@ -32,16 +32,17 @@ func _event_received(event_name : StringName, event_data : Dictionary = {}):
return return
username = event_data.username username = event_data.username
message = event_data.message message = event_data.message
channel = event_data.channel channel = event_data.channel
tags = event_data tags = event_data
send(0, username)
send(1, message) var id = UUID.v4()
send(2, channel) send(0, username, id)
send(3, tags) send(1, message, id)
send(4, true) send(2, channel, id)
send(3, tags, id)
send(4, true, id)
func _value_request(on_port: int) -> Variant: func _value_request(on_port: int) -> Variant:

View file

@ -13,12 +13,11 @@ func _init():
props_to_serialize = [] props_to_serialize = []
# Adds a port that allows specifying what type of event to listen for. # Adds a port that allows specifying what type of event to listen for.
add_input_port(DeckType.Types.STRING, "Event Name", "field") add_input_port(DeckType.Types.STRING, "Event Name", "field", Port.UsageType.VALUE_REQUEST)
# Adds a port that outputs when the Event has been received # Adds a port that outputs when the Event has been received
add_output_port(DeckType.Types.ANY, "Event Received") add_output_port(DeckType.Types.ANY, "Event Received", "", Port.UsageType.TRIGGER)
# Adds a port that outputs the data received when the Event has been received. # Adds a port that outputs the data received when the Event has been received.
add_output_port(DeckType.Types.DICTIONARY, "Event Data") add_output_port(DeckType.Types.DICTIONARY, "Event Data", "", Port.UsageType.BOTH)
@ -39,10 +38,11 @@ func _event_received(event_name: StringName, event_data: Dictionary = {}):
cached_event_data = event_data.payload cached_event_data = event_data.payload
# Sends to indicate that the specified event has happened. # Sends to indicate that the specified event has happened.
send(0, null) send(0, event_data)
send(1, event_data) send(1, event_data)
func _value_request(port): func _value_request(port):
if port != 1: if port != 1:

View file

@ -15,16 +15,21 @@ func _init():
add_input_port(DeckType.Types.STRING, "Channel", "field") add_input_port(DeckType.Types.STRING, "Channel", "field")
# Adds Trigger for leaving the specified channel # Adds Trigger for leaving the specified channel
add_input_port(DeckType.Types.ANY, "Join Channel", "button") add_input_port(DeckType.Types.ANY, "Join Channel", "button", Port.UsageType.TRIGGER)
func _receive(to_input_port, _data: Variant): func _receive(to_input_port, data: Variant):
if to_input_port != 1: var channel
return if to_input_port == 0:
channel = data
Connections.twitch.join_channel(await resolve_input_port_value_async(0)) if to_input_port == 1 or not channel is String:
channel = await resolve_input_port_value_async(0)
Connections.twitch.join_channel(channel)

View file

@ -15,16 +15,21 @@ func _init():
add_input_port(DeckType.Types.STRING, "Channel", "field") add_input_port(DeckType.Types.STRING, "Channel", "field")
# Adds Trigger for leaving the specified channel # Adds Trigger for leaving the specified channel
add_input_port(DeckType.Types.ANY, "Leave Channel", "button") add_input_port(DeckType.Types.ANY, "Leave Channel", "button", Port.UsageType.TRIGGER)
func _receive(to_input_port, _data: Variant): func _receive(to_input_port, data: Variant):
if to_input_port != 1: var channel
return if to_input_port == 0:
channel = data
Connections.twitch.leave_channel(await resolve_input_port_value_async(0)) if to_input_port == 1 or not channel is String:
channel = await resolve_input_port_value_async(0)
Connections.twitch.leave_channel(channel)

View file

@ -8,23 +8,37 @@ func _init():
name = "Twitch Remove EventSub Subscription" name = "Twitch Remove EventSub Subscription"
node_type = name.to_snake_case() node_type = name.to_snake_case()
description = "Removes an EventSub Subscription by it's type." description = "Removes an EventSub Subscription by it's type."
appears_in_search = false
props_to_serialize = [] props_to_serialize = []
add_input_port(DeckType.Types.STRING, "Subscription Type", "field") add_input_port(
add_input_port(DeckType.Types.ANY, "Remove Subscription", "button") DeckType.Types.STRING,
"Subscription Type",
"field"
)
add_input_port(
DeckType.Types.ANY,
"Remove Subscription",
"button"
)
func _receive(to_input_port, _data: Variant):
if to_input_port != 1: func _receive(to_input_port, data: Variant):
return var sub_type
if to_input_port == 0:
sub_type = str(data)
else:
sub_type = await resolve_input_port_value_async(0)
var sub_type = await resolve_input_port_value_async(0)
Connections.twitch.remove_eventsub_subscription_type(sub_type).response_completed.connect(eventsub_response_received) Connections.twitch.remove_eventsub_subscription_type(sub_type).response_completed.connect(eventsub_response_received)

View file

@ -12,15 +12,27 @@ func _init():
props_to_serialize = [] props_to_serialize = []
# Adds the User input. # Adds the User input.
add_input_port(DeckType.Types.STRING, "User Login", "field") add_input_port(
DeckType.Types.STRING,
"User ID",
"field"
)
# Adds the Checkbox for using IDs vs Logins and sets it to true. # Adds the Checkbox for using IDs vs Logins and sets it to true.
add_input_port(DeckType.Types.BOOL, "User Input as ID", "checkbox") add_input_port(
DeckType.Types.BOOL,
"User Input as ID",
"checkbox",
Port.UsageType.VALUE_REQUEST
)
get_input_ports()[1].set_value(true) get_input_ports()[1].set_value(true)
get_input_ports()[1].value_updated.connect(switch_user_input_type) get_input_ports()[1].value_updated.connect(switch_user_input_type)
add_output_port(DeckType.Types.ANY, "User Info") add_output_port(
DeckType.Types.ANY,
"User Info"
)
func switch_user_input_type(value): func switch_user_input_type(value):
@ -40,19 +52,37 @@ func switch_user_input_type(value):
func _receive(port, data) -> void:
if not port == 0:
return
data = str(data)
var req_resp = await request_user_info(data, await resolve_input_port_value_async(1))
send(0, req_resp)
func _value_request(_from_port): func _value_request(_from_port):
var user_input = await resolve_input_port_value_async(0) var user_input = await resolve_input_port_value_async(0)
var as_id = await resolve_input_port_value_async(1) var as_id = await resolve_input_port_value_async(1)
return await request_user_info(user_input, as_id)
func request_user_info(user, as_id):
var req_url := "https://api.twitch.tv/helix/users?" var req_url := "https://api.twitch.tv/helix/users?"
if as_id: if as_id:
req_url += "id=" + user_input req_url += "id=" + user
else: else:
req_url += "login=" + user_input req_url += "login=" + user
var resp = Connections.twitch.twitch_request(req_url) var resp = Connections.twitch.twitch_request(req_url)
@ -62,4 +92,3 @@ func _value_request(_from_port):
return data return data

View file

@ -3,6 +3,13 @@
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
extends DeckNode extends DeckNode
enum inputs {
message,
channel,
send
}
func _init(): func _init():
name = "Twitch Send Chat" name = "Twitch Send Chat"
@ -19,7 +26,6 @@ func _init():
"Channel", "Channel",
"field" "field"
) )
add_input_port( add_input_port(
DeckType.Types.BOOL, DeckType.Types.BOOL,
"Send", "Send",
@ -28,24 +34,46 @@ func _init():
func _receive(to_input_port, _data: Variant): func _receive(to_input_port, data: Variant):
if to_input_port != 2: data = str(data)
return var message
var channel
match to_input_port:
inputs.message:
message = data
channel = await resolve_input_port_value_async(inputs.channel)
inputs.channel:
channel = data
message = await resolve_input_port_value_async(inputs.message)
inputs.send:
channel = await resolve_input_port_value_async(inputs.channel)
message = await resolve_input_port_value_async(inputs.message)
var msg = await resolve_input_port_value_async(0)
var channel = await resolve_input_port_value_async(1)
if channel == null: if channel == null:
channel = "" channel = ""
if msg == null: if message == null:
return return
if message.is_empty():
DeckHolder.logger.log_node("Twitch Send Chat: Port %d: Supplied message was empty." % [to_input_port])
Connections.twitch.send_chat(message, channel)
Connections.twitch.send_chat(msg, channel)

View file

@ -54,3 +54,13 @@ static func convert_value(value: Variant, to: Types) -> Variant:
static func type_str(type: Types) -> String: static func type_str(type: Types) -> String:
return str(Types.keys()[type]) return str(Types.keys()[type])
## Validates whether the given Dictionary is a "Vector", AKA that it has an X and a Y key.
static func is_valid_vector(dict : Variant):
if !dict is Dictionary:
return false
return dict.has("x") and dict.has("y")