From 18450d661d68aa3d91f4f9377fcf907bec052828 Mon Sep 17 00:00:00 2001 From: issork Date: Fri, 3 Mar 2023 16:35:57 +0100 Subject: [PATCH] fixed #20 --- Gift.gd | 13 ++-- Node.tscn | 1 + README.md | 17 +++--- addons/gift/gift_node.gd | 124 ++++++++++----------------------------- addons/gift/plugin.cfg | 2 +- 5 files changed, 53 insertions(+), 104 deletions(-) diff --git a/Gift.gd b/Gift.gd index 17aac0a..add7f17 100644 --- a/Gift.gd +++ b/Gift.gd @@ -3,7 +3,7 @@ extends Gift func _ready() -> void: cmd_no_permission.connect(no_permission) chat_message.connect(on_chat) - channel_follow.connect(on_follow) + event.connect(on_event) # I use a file in the working directory to store auth data # so that I don't accidentally push it to the repository. @@ -24,8 +24,11 @@ func _ready() -> void: if (success): request_caps() join_channel(initial_channel) - events.append("channel.follow") await(connect_to_eventsub()) + # Refer to https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types/ for details on + # what events exist, which API versions are available and which conditions are required. + # Make sure your token has all required scopes for the event. + subscribe_event("channel.follow", 2, {"broadcaster_user_id": user_id, "moderator_user_id": user_id}) # Adds a command with a specified permission flag. # All implementations must take at least one arg for the command info. @@ -75,8 +78,10 @@ func _ready() -> void: # Send a whisper to target user # whisper("TEST", initial_channel) -func on_follow(data : Dictionary) -> void: - print("%s followed your channel!" % data["user_name"]) +func on_event(type : String, data : Dictionary) -> void: + match(type): + "channel.follow": + print("%s followed your channel!" % data["user_name"]) func on_chat(data : SenderData, msg : String) -> void: %ChatContainer.put_chat(data, msg) diff --git a/Node.tscn b/Node.tscn index e9abc8c..7078594 100644 --- a/Node.tscn +++ b/Node.tscn @@ -15,6 +15,7 @@ grow_vertical = 2 [node name="Gift" type="Node" parent="."] script = ExtResource("1") +scopes = Array[String](["chat:edit", "chat:read", "moderator:read:followers"]) [node name="ChatContainer" type="VBoxContainer" parent="."] unique_name_in_owner = true diff --git a/README.md b/README.md index 9b79898..a544857 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ extends Gift func _ready() -> void: cmd_no_permission.connect(no_permission) chat_message.connect(on_chat) - channel_follow.connect(on_follow) + event.connect(on_event) # I use a file in the working directory to store auth data # so that I don't accidentally push it to the repository. @@ -48,8 +48,11 @@ func _ready() -> void: if (success): request_caps() join_channel(initial_channel) - events.append("channel.follow") await(connect_to_eventsub()) + # Refer to https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types/ for details on + # what events exist, which API versions are available and which conditions are required. + # Make sure your token has all required scopes for the event. + subscribe_event("channel.follow", 2, {"broadcaster_user_id": user_id, "moderator_user_id": user_id}) # Adds a command with a specified permission flag. # All implementations must take at least one arg for the command info. @@ -99,8 +102,10 @@ func _ready() -> void: # Send a whisper to target user # whisper("TEST", initial_channel) -func on_follow(data : Dictionary) -> void: - print("%s followed your channel!" % data["user_name"]) +func on_event(type : String, data : Dictionary) -> void: + match(type): + "channel.follow": + print("%s followed your channel!" % data["user_name"]) func on_chat(data : SenderData, msg : String) -> void: %ChatContainer.put_chat(data, msg) @@ -174,9 +179,7 @@ func list(cmd_info : CommandInfo, arg_ary : PackedStringArray) -> void: |events_id(id)|id(String))|The id has been received from the welcome message.| |events_reconnect|-|Twitch directed the bot to reconnect to a different URL.| |events_revoked|event(String), reason(String)|Twitch revoked a event subscription| - -Events from EventSub are named just like their subscription name, with all '.' replaced by '_'. -Example: channel.follow emits the signal channel_follow(data(Dictionary)) +|event|type(String), data(Dictionary)|A subscribed eventsub event omitted data.| *** ### Functions: diff --git a/addons/gift/gift_node.gd b/addons/gift/gift_node.gd index 428729d..86e5b41 100644 --- a/addons/gift/gift_node.gd +++ b/addons/gift/gift_node.gd @@ -44,52 +44,8 @@ signal events_reconnect # Twitch revoked a event subscription signal events_revoked(event, reason) -# Currently supported Twitch events. Refer to https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types/ for details. -#signal channel_update -signal channel_follow(event_dict) # Beta, Twitch might change the API. -signal channel_subscribe(event_dict) -#signal channel_subscription_end -signal channel_subscription_gift(event_dict) -signal channel_subscription_message(event_dict) -signal channel_cheer(event_dict) -#signal channel_raid -#signal channel_ban -#signal channel_unban -#signal channel_moderator_add -#signal channel_moderator_remove -#signal channel_points_custom_reward_add -#signal channel_points_custom_reward_update -#signal channel_points_custom_reward_remove -signal channel_points_custom_reward_redemption_add(event_dict) -signal channel_points_custom_reward_redemption_update(event_dict) -#signal channel_poll_begin -#signal channel_poll_progress -#signal channel_poll_end -#signal channel_prediction_begin -#signal channel_prediction_progress -#signal channel_prediction_lock -#signal channel_prediction_end -signal channel_charity_campaign_donate(event_dict) -#signal channel_charity_campaign_start -#signal channel_charity_campaign_progress -#signal channel_charity_campaign_stop -#signal drop_entitlement_grant -signal extension_bits_transaction_create(event_dict) -#signal channel_goal_begin -#signal channel_goal_progress -#signal channel_goal_end -#signal channel_hype_train_begin -#signal channel_hype_train_progress -#signal channel_hype_train_end -#signal channel_shield_mode_begin -#signal channel_shield_mode_end -#signal channel_shoutout_create -#signal channel_shoutout_receive -#signal stream_online -#signal stream_offline -#signal user_authorization_grant -#signal user_authorization_revoke -#signal user_update +# Refer to https://dev.twitch.tv/docs/eventsub/eventsub-reference/ data contained in the data dictionary. +signal event(type, data) @export_category("IRC") @@ -102,10 +58,6 @@ signal extension_bits_transaction_create(event_dict) ## Scopes to request for the token. Look at https://dev.twitch.tv/docs/authentication/scopes/ for a list of all available scopes. @export var scopes : Array[String] = ["chat:edit", "chat:read"] -@export_category("EventSub") -## Events to subscribe to. Make sure you have requested the required scope. Full list available at https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types/ -@export var events : Array[String] = [] - @export_category("Emotes/Badges") ## If true, caches emotes/badges to disk, so that they don't have to be redownloaded on every restart. @@ -148,6 +100,8 @@ var connected : bool = false var user_regex : RegEx = RegEx.new() var twitch_restarting : bool = false +const USER_AGENT = "User-Agent: GIFT/4.0.0 (Godot Engine)" + enum RequestType { EMOTE, BADGE, @@ -243,6 +197,9 @@ func get_token() -> void: peer.poll() if (peer.get_available_bytes() > 0): var response = peer.get_utf8_string(peer.get_available_bytes()) + if (response == ""): + print("Empty response. Check if your redirect URL is set to http://localhost:18297.") + return var start : int = response.find("?") response = response.substr(start + 1, response.find(" ", start) - start) var data : Dictionary = {} @@ -269,7 +226,7 @@ func get_token() -> void: peer.disconnect_from_host() var request : HTTPRequest = HTTPRequest.new() add_child(request) - request.request("https://id.twitch.tv/oauth2/token", ["User-Agent: GIFT/3.0.0 (Godot Engine)", "Content-Type: application/x-www-form-urlencoded"], HTTPClient.METHOD_POST, "client_id=" + client_id + "&client_secret=" + client_secret + "&code=" + data["code"] + "&grant_type=authorization_code&redirect_uri=http://localhost:18297") + request.request("https://id.twitch.tv/oauth2/token", [USER_AGENT, "Content-Type: application/x-www-form-urlencoded"], HTTPClient.METHOD_POST, "client_id=" + client_id + "&client_secret=" + client_secret + "&code=" + data["code"] + "&grant_type=authorization_code&redirect_uri=http://localhost:18297") var answer = await(request.request_completed) if (!DirAccess.dir_exists_absolute("user://gift/auth")): DirAccess.make_dir_recursive_absolute("user://gift/auth") @@ -285,7 +242,7 @@ func get_token() -> void: func is_token_valid(token : String) -> String: var request : HTTPRequest = HTTPRequest.new() add_child(request) - request.request("https://id.twitch.tv/oauth2/validate", ["User-Agent: GIFT/3.0.0 (Godot Engine)", "Authorization: OAuth " + token]) + request.request("https://id.twitch.tv/oauth2/validate", [USER_AGENT, "Authorization: OAuth " + token]) var data = await(request.request_completed) if (data[1] == 200): var payload : Dictionary = JSON.parse_string(data[3].get_string_from_utf8()) @@ -392,26 +349,8 @@ func process_event(data : PackedByteArray) -> void: "revocation": events_revoked.emit(payload["subscription"]["type"], payload["subscription"]["status"]) "notification": - var event : Dictionary = payload["event"] - match payload["subscription"]["type"]: - "channel.follow": - channel_follow.emit(event) - "channel.subscribe": - channel_subscribe.emit(event) - "channel.subscription.gift": - channel_subscription_gift.emit(event) - "channel.subscription.message": - channel_subscription_message.emit(event) - "channel.cheer": - channel_cheer.emit(event) - "channel.charity_campaign.donate": - channel_charity_campaign_donate.emit(event) - "channel.channel_points_custom_reward_redemption.add": - channel_points_custom_reward_redemption_add.emit(event) - "channel.channel_points_custom_reward_redemption.update": - channel_points_custom_reward_redemption_update.emit(event) - "extensions.bits.transaction.create": - extension_bits_transaction_create.emit(event) + var event_data : Dictionary = payload["event"] + event.emit(payload["subscription"]["type"], event_data) # Connect to Twitch IRC. Make sure to authenticate first. func connect_to_irc() -> bool: @@ -432,28 +371,29 @@ func connect_to_eventsub(url : String = "wss://eventsub-beta.wss.twitch.tv/ws") eventsub.connect_to_url(url) print("Connecting to Twitch EventSub.") await(events_id) - for event in events: - var request : HTTPRequest = HTTPRequest.new() - var version - if (event == "channel_follow"): - version = "beta" - else: - version = "1" - var data : Dictionary = {} - data["type"] = event - data["version"] = version - data["condition"] = {"broadcaster_user_id":user_id} - data["transport"] = { - "method":"websocket", - "session_id":session_id - } - add_child(request) - request.request("https://api.twitch.tv/helix/eventsub/subscriptions", ["User-Agent: GIFT/3.0.0 (Godot Engine)", "Authorization: Bearer " + token["access_token"], "Client-Id:" + client_id, "Content-Type: application/json"], HTTPClient.METHOD_POST, JSON.stringify(data)) - var reply : Array = await(request.request_completed) - var response : Dictionary = JSON.parse_string(reply[3].get_string_from_utf8()) - print("Now listening to %s events for broadcaster_id %s." % [response["data"][0]["type"], response["data"][0]["condition"]["broadcaster_user_id"]]) events_connected.emit() +# Refer to https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types/ for details on +# which API versions are available and which conditions are required. +func subscribe_event(event_name : String, version : int, conditions : Dictionary) -> void: + var data : Dictionary = {} + data["type"] = event_name + data["version"] = str(version) + data["condition"] = conditions + data["transport"] = { + "method":"websocket", + "session_id":session_id + } + var request : HTTPRequest = HTTPRequest.new() + add_child(request) + request.request("https://api.twitch.tv/helix/eventsub/subscriptions", [USER_AGENT, "Authorization: Bearer " + token["access_token"], "Client-Id:" + client_id, "Content-Type: application/json"], HTTPClient.METHOD_POST, JSON.stringify(data)) + var reply : Array = await(request.request_completed) + var response : Dictionary = JSON.parse_string(reply[3].get_string_from_utf8()) + if (response.has("error")): + print("Subscription failed for event '%s'. Error %s (%s): %s" % [event_name, response["status"], response["error"], response["message"]]) + return + print("Now listening to '%s' events." % event_name) + # Request capabilities from twitch. func request_caps(caps : String = "twitch.tv/commands twitch.tv/tags twitch.tv/membership") -> void: send("CAP REQ :" + caps) diff --git a/addons/gift/plugin.cfg b/addons/gift/plugin.cfg index bacb37f..35aced6 100755 --- a/addons/gift/plugin.cfg +++ b/addons/gift/plugin.cfg @@ -3,5 +3,5 @@ name="Godot IRC For Twitch" description="Godot websocket implementation for Twitch IRC." author="issork" -version="3.0.1" +version="4.0.0" script="gift.gd"