From 91cb09a1e7b54af6e004671b9f48b3384222407b Mon Sep 17 00:00:00 2001 From: Ategon Date: Sat, 28 Sep 2024 18:34:09 -0400 Subject: [PATCH] Add files --- TwitchGod.tscn | 22 +++ auth.gd | 19 ++ http/HttpClient.tscn | 6 + http/HttpServer.tscn | 6 + http/TwitchSetting.tscn | 6 + http/WebsocketClient.tscn | 6 + http/http_client.gd | 118 ++++++++++++ http/http_server.gd | 78 ++++++++ http/twitch_setting.gd | 24 +++ http/websocket_client.gd | 71 +++++++ icons/bell.svg | 1 + icons/bell.svg.import | 37 ++++ icons/bot-message-square.svg | 1 + icons/bot-message-square.svg.import | 37 ++++ icons/cog.svg | 1 + icons/cog.svg.import | 37 ++++ icons/key-round.svg | 1 + icons/key-round.svg.import | 37 ++++ icons/message-square.svg | 1 + icons/message-square.svg.import | 37 ++++ icons/pyramid.svg | 1 + icons/pyramid.svg.import | 37 ++++ icons/regex.svg | 1 + icons/regex.svg.import | 37 ++++ icons/server.svg | 1 + icons/server.svg.import | 37 ++++ icons/twitch.svg | 1 + icons/twitch.svg.import | 37 ++++ icons/unplug.svg | 1 + icons/unplug.svg.import | 37 ++++ nodes/TwitchChatCommandListener.tscn | 12 ++ nodes/TwitchChatMessageListener.tscn | 12 ++ nodes/TwitchChatRegexListener.tscn | 12 ++ nodes/TwitchEventListener.tscn | 6 + nodes/TwitchPointRedemptionListener.tscn | 12 ++ nodes/twitch_chat_command_listener.gd | 26 +++ nodes/twitch_chat_message_listener.gd | 17 ++ nodes/twitch_chat_regex_listener.gd | 24 +++ nodes/twitch_event_listener.gd | 24 +++ nodes/twitch_point_redemption_listener.gd | 18 ++ twitch_events.gd | 215 ++++++++++++++++++++++ twitch_god.gd | 82 +++++++++ twitch_scopes.gd | 12 ++ 43 files changed, 1208 insertions(+) create mode 100644 TwitchGod.tscn create mode 100644 auth.gd create mode 100644 http/HttpClient.tscn create mode 100644 http/HttpServer.tscn create mode 100644 http/TwitchSetting.tscn create mode 100644 http/WebsocketClient.tscn create mode 100644 http/http_client.gd create mode 100644 http/http_server.gd create mode 100644 http/twitch_setting.gd create mode 100644 http/websocket_client.gd create mode 100644 icons/bell.svg create mode 100644 icons/bell.svg.import create mode 100644 icons/bot-message-square.svg create mode 100644 icons/bot-message-square.svg.import create mode 100644 icons/cog.svg create mode 100644 icons/cog.svg.import create mode 100644 icons/key-round.svg create mode 100644 icons/key-round.svg.import create mode 100644 icons/message-square.svg create mode 100644 icons/message-square.svg.import create mode 100644 icons/pyramid.svg create mode 100644 icons/pyramid.svg.import create mode 100644 icons/regex.svg create mode 100644 icons/regex.svg.import create mode 100644 icons/server.svg create mode 100644 icons/server.svg.import create mode 100644 icons/twitch.svg create mode 100644 icons/twitch.svg.import create mode 100644 icons/unplug.svg create mode 100644 icons/unplug.svg.import create mode 100644 nodes/TwitchChatCommandListener.tscn create mode 100644 nodes/TwitchChatMessageListener.tscn create mode 100644 nodes/TwitchChatRegexListener.tscn create mode 100644 nodes/TwitchEventListener.tscn create mode 100644 nodes/TwitchPointRedemptionListener.tscn create mode 100644 nodes/twitch_chat_command_listener.gd create mode 100644 nodes/twitch_chat_message_listener.gd create mode 100644 nodes/twitch_chat_regex_listener.gd create mode 100644 nodes/twitch_event_listener.gd create mode 100644 nodes/twitch_point_redemption_listener.gd create mode 100644 twitch_events.gd create mode 100644 twitch_god.gd create mode 100644 twitch_scopes.gd diff --git a/TwitchGod.tscn b/TwitchGod.tscn new file mode 100644 index 0000000..11c63b4 --- /dev/null +++ b/TwitchGod.tscn @@ -0,0 +1,22 @@ +[gd_scene load_steps=7 format=3 uid="uid://bsved37ft3klq"] + +[ext_resource type="Script" path="res://addons/TwitchGod/twitch_god.gd" id="1_41vp4"] +[ext_resource type="PackedScene" uid="uid://dsev0vyt8vkuf" path="res://addons/TwitchGod/http/HttpClient.tscn" id="2_04lmt"] +[ext_resource type="PackedScene" uid="uid://dxaclxfi6m2gk" path="res://addons/TwitchGod/http/WebsocketClient.tscn" id="2_tgrgj"] +[ext_resource type="PackedScene" uid="uid://bn2omqaosdoqu" path="res://addons/TwitchGod/http/TwitchSetting.tscn" id="2_v0pcb"] +[ext_resource type="PackedScene" uid="uid://d7mhkh8sua4x" path="res://addons/TwitchGod/http/HttpServer.tscn" id="4_fhjvp"] +[ext_resource type="Script" path="res://addons/TwitchGod/auth.gd" id="6_dmql6"] + +[node name="TwitchGod" type="Node"] +script = ExtResource("1_41vp4") + +[node name="TwitchSetting" parent="." instance=ExtResource("2_v0pcb")] + +[node name="HttpServer" parent="." instance=ExtResource("4_fhjvp")] + +[node name="WebsocketClient" parent="." instance=ExtResource("2_tgrgj")] + +[node name="HttpClient" parent="." instance=ExtResource("2_04lmt")] + +[node name="Auth" type="Node" parent="."] +script = ExtResource("6_dmql6") diff --git a/auth.gd b/auth.gd new file mode 100644 index 0000000..dd12266 --- /dev/null +++ b/auth.gd @@ -0,0 +1,19 @@ +extends Node + +const BROADCASTER_ID = "" +const USER_ID = "" +const CLIENT_SECRET = "" +const CLIENT_ID = "" +const PORT = 7171 +const SCOPE = "moderator%3Amanage%3Ashoutouts+channel%3Aread%3Apolls+channel%3Amanage%3Apolls+user%3Aread%3Achat+user%3Awrite%3Achat+channel%3Aread%3Aredemptions+channel%3Amanage%3Aredemptions+channel%3Aread%3Apolls" +const SUBS = [ + TwitchEvents.Event.CHANNEL_CHAT_MESSAGE, + TwitchEvents.Event.CHANNEL_POLL_BEGIN, + TwitchEvents.Event.CHANNEL_CHANNEL_POINTS_CUSTOM_REWARD_REDEMPTION_ADD, + TwitchEvents.Event.CHANNEL_POLL_BEGIN, + TwitchEvents.Event.CHANNEL_POLL_PROGRESS, + TwitchEvents.Event.CHANNEL_POLL_END +] +const SCOPES = [ + "" +] diff --git a/http/HttpClient.tscn b/http/HttpClient.tscn new file mode 100644 index 0000000..f2971d3 --- /dev/null +++ b/http/HttpClient.tscn @@ -0,0 +1,6 @@ +[gd_scene load_steps=2 format=3 uid="uid://dsev0vyt8vkuf"] + +[ext_resource type="Script" path="res://addons/TwitchGod/http/http_client.gd" id="1_prokr"] + +[node name="HttpClient" type="Node"] +script = ExtResource("1_prokr") diff --git a/http/HttpServer.tscn b/http/HttpServer.tscn new file mode 100644 index 0000000..d8886c0 --- /dev/null +++ b/http/HttpServer.tscn @@ -0,0 +1,6 @@ +[gd_scene load_steps=2 format=3 uid="uid://d7mhkh8sua4x"] + +[ext_resource type="Script" path="res://addons/TwitchGod/http/http_server.gd" id="1_im1bn"] + +[node name="HttpServer" type="Node"] +script = ExtResource("1_im1bn") diff --git a/http/TwitchSetting.tscn b/http/TwitchSetting.tscn new file mode 100644 index 0000000..df6a55c --- /dev/null +++ b/http/TwitchSetting.tscn @@ -0,0 +1,6 @@ +[gd_scene load_steps=2 format=3 uid="uid://bn2omqaosdoqu"] + +[ext_resource type="Script" path="res://addons/TwitchGod/http/twitch_setting.gd" id="1_qpw3i"] + +[node name="TwitchSetting" type="Node"] +script = ExtResource("1_qpw3i") diff --git a/http/WebsocketClient.tscn b/http/WebsocketClient.tscn new file mode 100644 index 0000000..4a5a244 --- /dev/null +++ b/http/WebsocketClient.tscn @@ -0,0 +1,6 @@ +[gd_scene load_steps=2 format=3 uid="uid://dxaclxfi6m2gk"] + +[ext_resource type="Script" path="res://addons/TwitchGod/http/websocket_client.gd" id="1_2ut2u"] + +[node name="WebsocketClient" type="Node"] +script = ExtResource("1_2ut2u") diff --git a/http/http_client.gd b/http/http_client.gd new file mode 100644 index 0000000..91210e0 --- /dev/null +++ b/http/http_client.gd @@ -0,0 +1,118 @@ +extends Node +class_name HttpClient + +signal request_completed(type: API_TYPE, data: Dictionary) + +const API_URL = "https://api.twitch.tv/helix" + +enum API_TYPE { + AUTH, + CREATE_EVENTSUB, + VERIFY, + SEND_CHAT_MESSAGE, + SHOUTOUT, + USERS, + EMOTE +} + +var auth + +func send_message(message: String): + var parent = get_parent() + + request(API_TYPE.SEND_CHAT_MESSAGE, {}, { + "broadcaster_id": parent.auth.BROADCASTER_ID, + "sender_id": parent.auth.USER_ID, + "message": message + }) + +func request(type: API_TYPE, arguments: Dictionary = {}, data: Dictionary = {}): + var path = _get_path_from_type(type, arguments) + var headers = _get_headers_from_type(type) + var method = HTTPClient.METHOD_POST + + if type == API_TYPE.VERIFY: + headers.push_back("Authorization: OAuth %s" % arguments.token) + auth = arguments.token + method = HTTPClient.METHOD_GET + if type == API_TYPE.USERS: + method = HTTPClient.METHOD_GET + if type == API_TYPE.EMOTE: + method = HTTPClient.METHOD_GET + + var request = HTTPRequest.new() + add_child(request) + #prints(path, headers, method, JSON.stringify(data)) + if method == HTTPClient.METHOD_GET: + request.request(path, headers, method) + else: + request.request(path, headers, method, JSON.stringify(data)) + request.request_completed.connect(_on_request_completed.bind(type)) + + var response = await request.request_completed + + if response[2].has("Content-Type: image/png"): + return response[3] + + var string = "" + for character in response[3]: + string += char(character) + + var json = JSON.parse_string(string) + + return json + + +func _on_request_completed(result: int, response_code: int, headers: PackedStringArray, body: PackedByteArray, type: API_TYPE): + var string = "" + for character in body: + string += char(character) + + if headers.has("Content-Type: application/json"): + var json = JSON.parse_string(string) + + request_completed.emit(type, json) + +func _get_path_from_type(type: API_TYPE, arguments: Dictionary): + var comp_args = "" + + for argument in arguments: + if comp_args == "": + comp_args += "?%s=%s" % [argument, arguments[argument]] + else: + comp_args += "&%s=%s" % [argument, arguments[argument]] + + match type: + API_TYPE.AUTH: + return "https://id.twitch.tv/oauth2/token" + comp_args + API_TYPE.CREATE_EVENTSUB: + return "%s/eventsub/subscriptions" % [API_URL] + API_TYPE.VERIFY: + return "https://id.twitch.tv/oauth2/validate" + API_TYPE.SEND_CHAT_MESSAGE: + return "%s/chat/messages" % [API_URL] + comp_args + API_TYPE.USERS: + return "%s/users" % [API_URL] + comp_args + API_TYPE.SHOUTOUT: + return "%s/chat/shoutouts" % [API_URL] + comp_args + API_TYPE.EMOTE: + return "https://static-cdn.jtvnw.net/emoticons/v2/%s/static/dark/1.0" % [arguments.id] + + +func _get_headers_from_type(type: API_TYPE): + match type: + API_TYPE.AUTH: + return [] + API_TYPE.VERIFY: + return [] + _: + return [_get_bearer(), _get_client_id(), "Content-Type: application/json"] + + +func _get_bearer(): + return "Authorization: Bearer %s" % [auth] + + +func _get_client_id(): + var parent = get_parent() + return "Client-Id: %s" % [parent.auth.CLIENT_ID] diff --git a/http/http_server.gd b/http/http_server.gd new file mode 100644 index 0000000..b593549 --- /dev/null +++ b/http/http_server.gd @@ -0,0 +1,78 @@ +extends Node + +var server: TCPServer + +signal started +signal received(auth) + +var clients: Array[StreamPeerTCP] = [] + +var listening = false + +func start(): + var port = get_parent().auth.PORT + print(port) + server = TCPServer.new() + server.listen(port) + + + +func _process(delta): + if !server: return + if(!server.is_listening()): return; + + if not listening: + print("SERVER STARTED") + started.emit() + listening = true + + if(server.is_connection_available()): + _handle_connect(); + + for client in clients: + _process_request(client); + + +func _handle_connect(): + var client := server.take_connection() + clients.push_back(client) + print("CONNECT") + + +func _process_request(client: StreamPeerTCP): + match client.get_status(): + StreamPeerTCP.STATUS_CONNECTED: + client.poll() + if client.get_available_bytes() > 0: + var string = client.get_utf8_string(client.get_available_bytes()) + var split = string.split("\n") + var first = split[0] + var regex = RegEx.new() + regex.compile("code=(.*)&") + var result = regex.search(first) + if result: + var auth = result.get_string(1) + _send_response(client, "200 OK", "LoginSuccess!".to_utf8_buffer()); + received.emit(auth) + +func auth(arguments: Dictionary): + var comp_args = "" + + for argument in arguments: + if comp_args == "": + comp_args += "?%s=%s" % [argument, arguments[argument]] + else: + comp_args += "&%s=%s" % [argument, arguments[argument]] + + OS.shell_open("https://id.twitch.tv/oauth2/authorize%s" % [comp_args]) + + +func _send_response(client, response_code : String, body : PackedByteArray) -> void: + client.put_data(("HTTP/1.1 %s\r\n" % response_code).to_utf8_buffer()) + client.put_data("Server: Godot Engine\r\n".to_utf8_buffer()) + client.put_data(("Content-Length: %d\r\n"% body.size()).to_utf8_buffer()) + client.put_data("Connection: close\r\n".to_utf8_buffer()) + client.put_data("Content-Type: text/html; charset=UTF-8\r\n".to_utf8_buffer()) + client.put_data("\r\n".to_utf8_buffer()) + client.put_data(body) + client.disconnect_from_host(); diff --git a/http/twitch_setting.gd b/http/twitch_setting.gd new file mode 100644 index 0000000..b8ea33c --- /dev/null +++ b/http/twitch_setting.gd @@ -0,0 +1,24 @@ +@tool +extends Node + + + + + +func _init(): + add_custom_project_setting("twitch_god/client_id", "Test", TYPE_STRING) + print("Test") + +func add_custom_project_setting(name: String, default_value, type: int, hint: int = PROPERTY_HINT_NONE, hint_string: String = ""): + #if ProjectSettings.has_setting(name): return + + var setting_info: Dictionary = { + "name": name, + "type": type, + "hint": hint, + "hint_string": hint_string + } + + ProjectSettings.set_setting(name, default_value) + ProjectSettings.add_property_info(setting_info) + ProjectSettings.set_initial_value(name, default_value) diff --git a/http/websocket_client.gd b/http/websocket_client.gd new file mode 100644 index 0000000..48a7306 --- /dev/null +++ b/http/websocket_client.gd @@ -0,0 +1,71 @@ +extends Node +class_name WebsocketClient + +signal opened +signal received(type: TwitchEvents.Event, data: Dictionary) + +const URL = "wss://eventsub.wss.twitch.tv/ws" + +var socket := WebSocketPeer.new() +var connection_state := WebSocketPeer.STATE_CLOSED +var id := "" + + +# -- Built in Methods + + +func _process(delta: float) -> void: + socket.poll() + + var new_connection_state := socket.get_ready_state() + if new_connection_state != connection_state: _change_state(new_connection_state) + + match connection_state: + WebSocketPeer.STATE_OPEN: + _read_data() + + +# -- Public Methods + + +## Open up the websocket to start listening +func open(): + socket.connect_to_url(URL) + + +# -- Private Methods + + +func _change_state(new_state: WebSocketPeer.State): + match new_state: + WebSocketPeer.STATE_OPEN: + opened.emit() + + connection_state = new_state + + +func _read_data(): + while (socket.get_available_packet_count()): + var packet := socket.get_packet() + var data = _read_packet(packet) + var payload = data.payload + + match data.metadata.message_type: + "session_welcome": + id = payload.session.id + "session_keepalive": + pass + "session_reconnect": + pass + _: + var event_type = TwitchEvents.get_event_type_from_name(payload.subscription.type) + received.emit(event_type, payload.event) + + +func _read_packet(packet: PackedByteArray): + var string = "" + for chunk in packet: + string += char(chunk) + var data = JSON.parse_string(string) + + return data diff --git a/icons/bell.svg b/icons/bell.svg new file mode 100644 index 0000000..95eb12f --- /dev/null +++ b/icons/bell.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icons/bell.svg.import b/icons/bell.svg.import new file mode 100644 index 0000000..6682d08 --- /dev/null +++ b/icons/bell.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dbbslha8yjaga" +path="res://.godot/imported/bell.svg-b8e0c96cd4e3b89ad8e1d4d55e418910.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/TwitchGod/icons/bell.svg" +dest_files=["res://.godot/imported/bell.svg-b8e0c96cd4e3b89ad8e1d4d55e418910.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/icons/bot-message-square.svg b/icons/bot-message-square.svg new file mode 100644 index 0000000..67fe514 --- /dev/null +++ b/icons/bot-message-square.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icons/bot-message-square.svg.import b/icons/bot-message-square.svg.import new file mode 100644 index 0000000..2a5900d --- /dev/null +++ b/icons/bot-message-square.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dgxn1mb2i8ttj" +path="res://.godot/imported/bot-message-square.svg-786cdae9f8058aaa187e28cad3dbee61.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/TwitchGod/icons/bot-message-square.svg" +dest_files=["res://.godot/imported/bot-message-square.svg-786cdae9f8058aaa187e28cad3dbee61.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/icons/cog.svg b/icons/cog.svg new file mode 100644 index 0000000..7457690 --- /dev/null +++ b/icons/cog.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icons/cog.svg.import b/icons/cog.svg.import new file mode 100644 index 0000000..1c1e0d2 --- /dev/null +++ b/icons/cog.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dsr1447417xrq" +path="res://.godot/imported/cog.svg-6bc9ddbf8b718bdb9823647c50ba7c4c.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/TwitchGod/icons/cog.svg" +dest_files=["res://.godot/imported/cog.svg-6bc9ddbf8b718bdb9823647c50ba7c4c.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/icons/key-round.svg b/icons/key-round.svg new file mode 100644 index 0000000..f37bb5d --- /dev/null +++ b/icons/key-round.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icons/key-round.svg.import b/icons/key-round.svg.import new file mode 100644 index 0000000..1963f8c --- /dev/null +++ b/icons/key-round.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c1dp2foe4qm3v" +path="res://.godot/imported/key-round.svg-09fd537499c75dbded8a700396a22502.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/TwitchGod/icons/key-round.svg" +dest_files=["res://.godot/imported/key-round.svg-09fd537499c75dbded8a700396a22502.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/icons/message-square.svg b/icons/message-square.svg new file mode 100644 index 0000000..d4535a8 --- /dev/null +++ b/icons/message-square.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icons/message-square.svg.import b/icons/message-square.svg.import new file mode 100644 index 0000000..f60f2a7 --- /dev/null +++ b/icons/message-square.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dyg54d6jepjgh" +path="res://.godot/imported/message-square.svg-2af3a5bb9409f01fb3cbfc5b7a3dcbd7.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/TwitchGod/icons/message-square.svg" +dest_files=["res://.godot/imported/message-square.svg-2af3a5bb9409f01fb3cbfc5b7a3dcbd7.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/icons/pyramid.svg b/icons/pyramid.svg new file mode 100644 index 0000000..c18a94d --- /dev/null +++ b/icons/pyramid.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icons/pyramid.svg.import b/icons/pyramid.svg.import new file mode 100644 index 0000000..93471c0 --- /dev/null +++ b/icons/pyramid.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://d1738kasi15th" +path="res://.godot/imported/pyramid.svg-b581da31e5fb1851b7edda64e9bc57e8.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/TwitchGod/icons/pyramid.svg" +dest_files=["res://.godot/imported/pyramid.svg-b581da31e5fb1851b7edda64e9bc57e8.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/icons/regex.svg b/icons/regex.svg new file mode 100644 index 0000000..cdf2a29 --- /dev/null +++ b/icons/regex.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icons/regex.svg.import b/icons/regex.svg.import new file mode 100644 index 0000000..3f77c79 --- /dev/null +++ b/icons/regex.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dinqgmvqeen8t" +path="res://.godot/imported/regex.svg-35497d236d1bbc6045dd332d794afc45.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/TwitchGod/icons/regex.svg" +dest_files=["res://.godot/imported/regex.svg-35497d236d1bbc6045dd332d794afc45.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/icons/server.svg b/icons/server.svg new file mode 100644 index 0000000..223bda7 --- /dev/null +++ b/icons/server.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icons/server.svg.import b/icons/server.svg.import new file mode 100644 index 0000000..b46bd3f --- /dev/null +++ b/icons/server.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cix7tp6kf6k44" +path="res://.godot/imported/server.svg-b484a4103196fbad974efece4ea210c6.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/TwitchGod/icons/server.svg" +dest_files=["res://.godot/imported/server.svg-b484a4103196fbad974efece4ea210c6.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/icons/twitch.svg b/icons/twitch.svg new file mode 100644 index 0000000..bc5fed3 --- /dev/null +++ b/icons/twitch.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icons/twitch.svg.import b/icons/twitch.svg.import new file mode 100644 index 0000000..09d613f --- /dev/null +++ b/icons/twitch.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cupvtgpmau5tl" +path="res://.godot/imported/twitch.svg-caa6e01720fb38b32b0fe8194c462f21.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/TwitchGod/icons/twitch.svg" +dest_files=["res://.godot/imported/twitch.svg-caa6e01720fb38b32b0fe8194c462f21.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/icons/unplug.svg b/icons/unplug.svg new file mode 100644 index 0000000..cf3e661 --- /dev/null +++ b/icons/unplug.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icons/unplug.svg.import b/icons/unplug.svg.import new file mode 100644 index 0000000..6e7ba51 --- /dev/null +++ b/icons/unplug.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://donvetfqnw6o4" +path="res://.godot/imported/unplug.svg-36958912a145d5a5ae67d5324e3af911.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/TwitchGod/icons/unplug.svg" +dest_files=["res://.godot/imported/unplug.svg-36958912a145d5a5ae67d5324e3af911.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/nodes/TwitchChatCommandListener.tscn b/nodes/TwitchChatCommandListener.tscn new file mode 100644 index 0000000..ddd76bb --- /dev/null +++ b/nodes/TwitchChatCommandListener.tscn @@ -0,0 +1,12 @@ +[gd_scene load_steps=3 format=3 uid="uid://cunhrde7dsed6"] + +[ext_resource type="Script" path="res://addons/TwitchGod/nodes/twitch_chat_command_listener.gd" id="1_y088d"] +[ext_resource type="PackedScene" uid="uid://b2585bqiywbwv" path="res://addons/TwitchGod/nodes/TwitchEventListener.tscn" id="2_p4sf8"] + +[node name="TwitchChatCommandListener" type="Node"] +script = ExtResource("1_y088d") + +[node name="TwitchEventListener" parent="." instance=ExtResource("2_p4sf8")] +event = 9 + +[connection signal="received" from="TwitchEventListener" to="." method="_on_twitch_event_listener_received"] diff --git a/nodes/TwitchChatMessageListener.tscn b/nodes/TwitchChatMessageListener.tscn new file mode 100644 index 0000000..cc35751 --- /dev/null +++ b/nodes/TwitchChatMessageListener.tscn @@ -0,0 +1,12 @@ +[gd_scene load_steps=3 format=3 uid="uid://ceqp0eqllp4ai"] + +[ext_resource type="Script" path="res://addons/TwitchGod/nodes/twitch_chat_message_listener.gd" id="1_8blbc"] +[ext_resource type="PackedScene" uid="uid://b2585bqiywbwv" path="res://addons/TwitchGod/nodes/TwitchEventListener.tscn" id="2_23mvm"] + +[node name="TwitchChatMessageListener" type="Node"] +script = ExtResource("1_8blbc") + +[node name="TwitchEventListener" parent="." instance=ExtResource("2_23mvm")] +event = 9 + +[connection signal="received" from="TwitchEventListener" to="." method="_on_twitch_event_listener_received"] diff --git a/nodes/TwitchChatRegexListener.tscn b/nodes/TwitchChatRegexListener.tscn new file mode 100644 index 0000000..d9f98c8 --- /dev/null +++ b/nodes/TwitchChatRegexListener.tscn @@ -0,0 +1,12 @@ +[gd_scene load_steps=3 format=3 uid="uid://bln2snjbqkg42"] + +[ext_resource type="Script" path="res://addons/TwitchGod/nodes/twitch_chat_regex_listener.gd" id="1_snfls"] +[ext_resource type="PackedScene" uid="uid://b2585bqiywbwv" path="res://addons/TwitchGod/nodes/TwitchEventListener.tscn" id="2_lqtl8"] + +[node name="TwitchChatRegexListener" type="Node"] +script = ExtResource("1_snfls") + +[node name="TwitchEventListener" parent="." instance=ExtResource("2_lqtl8")] +event = 9 + +[connection signal="received" from="TwitchEventListener" to="." method="_on_twitch_event_listener_received"] diff --git a/nodes/TwitchEventListener.tscn b/nodes/TwitchEventListener.tscn new file mode 100644 index 0000000..8fd1442 --- /dev/null +++ b/nodes/TwitchEventListener.tscn @@ -0,0 +1,6 @@ +[gd_scene load_steps=2 format=3 uid="uid://b2585bqiywbwv"] + +[ext_resource type="Script" path="res://addons/TwitchGod/nodes/twitch_event_listener.gd" id="1_siryd"] + +[node name="TwitchEventListener" type="Node"] +script = ExtResource("1_siryd") diff --git a/nodes/TwitchPointRedemptionListener.tscn b/nodes/TwitchPointRedemptionListener.tscn new file mode 100644 index 0000000..72f6d3f --- /dev/null +++ b/nodes/TwitchPointRedemptionListener.tscn @@ -0,0 +1,12 @@ +[gd_scene load_steps=3 format=3 uid="uid://vghgdn7lns0x"] + +[ext_resource type="Script" path="res://addons/TwitchGod/nodes/twitch_point_redemption_listener.gd" id="1_l7obv"] +[ext_resource type="PackedScene" uid="uid://b2585bqiywbwv" path="res://addons/TwitchGod/nodes/TwitchEventListener.tscn" id="2_86a3u"] + +[node name="TwitchPointRedemptionListener" type="Node"] +script = ExtResource("1_l7obv") + +[node name="TwitchEventListener" parent="." instance=ExtResource("2_86a3u")] +event = 36 + +[connection signal="received" from="TwitchEventListener" to="." method="_on_twitch_event_listener_received"] diff --git a/nodes/twitch_chat_command_listener.gd b/nodes/twitch_chat_command_listener.gd new file mode 100644 index 0000000..c918541 --- /dev/null +++ b/nodes/twitch_chat_command_listener.gd @@ -0,0 +1,26 @@ +@icon("../icons/bot-message-square.svg") +class_name TwitchChatCommandListener +extends Node + +signal received(data: Dictionary) + +@export var names: Array[String] = [] + + +# -- Private Methods + + +func _on_twitch_event_listener_received(data): + var message = data.message.text + + for name in names: + if message.begins_with("!%s " % [name]) or message == ("!%s" % [name]): + var args = data.message.text.split(" ") + + args.remove_at(0) + + data.args = args + data.argcount = args.size() + + received.emit(data) + return diff --git a/nodes/twitch_chat_message_listener.gd b/nodes/twitch_chat_message_listener.gd new file mode 100644 index 0000000..8c2107d --- /dev/null +++ b/nodes/twitch_chat_message_listener.gd @@ -0,0 +1,17 @@ +@icon("../icons/message-square.svg") +class_name TwitchChatMessageListener +extends Node + +signal received(data: Dictionary) + +@export var messages: Array[String] = [] + + +# -- Private Methods + + +func _on_twitch_event_listener_received(data): + var message = data.message.text + + if messages.has(message): + received.emit(data) diff --git a/nodes/twitch_chat_regex_listener.gd b/nodes/twitch_chat_regex_listener.gd new file mode 100644 index 0000000..8617529 --- /dev/null +++ b/nodes/twitch_chat_regex_listener.gd @@ -0,0 +1,24 @@ +@icon("../icons/regex.svg") +class_name TwitchChatRegexListener +extends Node + +signal received(data: Dictionary) + +@export var regexes: Array[String] = [] + + +# -- Private Methods + + +func _on_twitch_event_listener_received(data): + var message = data.message.text + + for regex in regexes: + var pattern = RegEx.new() + pattern.compile(regex) + + var result = pattern.search(message) + + if result: + received.emit(data) + return diff --git a/nodes/twitch_event_listener.gd b/nodes/twitch_event_listener.gd new file mode 100644 index 0000000..ee313f3 --- /dev/null +++ b/nodes/twitch_event_listener.gd @@ -0,0 +1,24 @@ +@icon("../icons/bell.svg") +class_name TwitchEventListener +extends Node + +signal received(data: Dictionary) + +@export var event: TwitchEvents.Event + + +# -- Built in Methods + + +func _ready(): + assert(event != null) + + TwitchGod.websocket_client.received.connect(_on_received) + + +# -- Private Methods + + +func _on_received(type: TwitchEvents.Event, data: Dictionary): + if type == event: + received.emit(data); diff --git a/nodes/twitch_point_redemption_listener.gd b/nodes/twitch_point_redemption_listener.gd new file mode 100644 index 0000000..0fdddf4 --- /dev/null +++ b/nodes/twitch_point_redemption_listener.gd @@ -0,0 +1,18 @@ +class_name TwitchPointRedemptionListener +extends Node + +signal received(data: Dictionary) + +@export var titles: Array[String] = [] + + +# -- Private Methods + + +func _on_twitch_event_listener_received(data): + var reward_title = data.reward.title + + print(reward_title) + + if titles.has(reward_title): + received.emit(data) diff --git a/twitch_events.gd b/twitch_events.gd new file mode 100644 index 0000000..8c11296 --- /dev/null +++ b/twitch_events.gd @@ -0,0 +1,215 @@ +@tool +class_name TwitchEvents +extends Object + +enum Event { + AUTOMOD_MESSAGE_HOLD, + AUTOMOD_MESSAGE_UPDATE, + AUTOMOD_SETTINGS_UPDATE, + AUTOMOD_TERMS_UPDATE, + CHANNEL_UPDATE, + CHANNEL_FOLLOW, + CHANNEL_AD_BREAK_BEGIN, + CHANNEL_CHAT_CLEAR, + CHANNEL_CHAT_CLEAR_USER_MESSAGES, + CHANNEL_CHAT_MESSAGE, + CHANNEL_CHAT_MESSAGE_DELETE, + CHANNEL_CHAT_NOTIFICATION, + CHANNEL_CHAT_SETTINGS_UPDATE, + CHANNEL_CHAT_USER_MESSAGE_HOLD, + CHANNEL_CHAT_USER_MESSAGE_UPDATE, + CHANNEL_SUBSCRIBE, + CHANNEL_SUBSCRIPTION_END, + CHANNEL_SUBSCRIPTION_GIFT, + CHANNEL_SUBSCRIPTION_MESSAGE, + CHANNEL_CHEER, + CHANNEL_RAID, + CHANNEL_BAN, + CHANNEL_UNBAN, + CHANNEL_UNBAN_REQUEST_CREATE, + CHANNEL_UNBAN_REQUEST_RESOLVE, + CHANNEL_MODERATE, + CHANNEL_MODERATOR_ADD, + CHANNEL_MODERATOR_REMOVE, + CHANNEL_GUEST_STAR_SESSION_BEGIN, + CHANNEL_GUEST_STAR_SESSION_END, + CHANNEL_GUEST_STAR_GUEST_UPDATE, + CHANNEL_GUEST_STAR_SETTINGS_UPDATE, + CHANNEL_CHANNEL_POINTS_AUTOMATIC_REWARD_REDEMPTION_ADD, + CHANNEL_CHANNEL_POINTS_CUSTOM_REWARD_ADD, + CHANNEL_CHANNEL_POINTS_CUSTOM_REWARD_UPDATE, + CHANNEL_CHANNEL_POINTS_CUSTOM_REWARD_REMOVE, + CHANNEL_CHANNEL_POINTS_CUSTOM_REWARD_REDEMPTION_ADD, + CHANNEL_CHANNEL_POINTS_CUSTOM_REWARD_REDEMPTION_UPDATE, + CHANNEL_POLL_BEGIN, + CHANNEL_POLL_PROGRESS, + CHANNEL_POLL_END, + CHANNEL_PREDICTION_BEGIN, + CHANNEL_PREDICTION_PROGRESS, + CHANNEL_PREDICTION_LOCK, + CHANNEL_PREDICTION_END, + CHANNEL_SUSPICIOUS_USER_MESSAGE, + CHANNEL_SUSPICIOUS_USER_UPDATE, + CHANNEL_VIP_ADD, + CHANNEL_VIP_REMOVE, + CHANNEL_CHARITY_CAMPAIGN_DONATE, + CHANNEL_CHARITY_CAMPAIGN_START, + CHANNEL_CHARITY_CAMPAIGN_PROGRESS, + CHANNEL_CHARITY_CAMPAIGN_STOP, + CONDUIT_SHARD_DISABLED, + DROP_ENTITLEMENT_GRANT, + EXTENSION_BITS_TRANSACTION_CREATE, + CHANNEL_GOAL_BEGIN, + CHANNEL_GOAL_PROGRESS, + CHANNEL_GOAL_END, + CHANNEL_HYPE_TRAIN_BEGIN, + CHANNEL_HYPE_TRAIN_PROGRESS, + CHANNEL_HYPE_TRAIN_END, + CHANNEL_SHIELD_MODE_BEGIN, + CHANNEL_SHIELD_MODE_END, + CHANNEL_SHOUTOUT_CREATE, + CHANNEL_SHOUTOUT_RECEIVE, + STREAM_ONLINE, + STREAM_OFFLINE, + USER_AUTHORIZATION_GRANT, + USER_AUTHORIZATION_REVOKE, + USER_UPDATE, + USER_WHISPER_RECEIVED +} + +static var _events = [ + EventInfo.new(Event.AUTOMOD_MESSAGE_HOLD, "automod.message.hold", "1"), + EventInfo.new(Event.AUTOMOD_MESSAGE_UPDATE, "automod.message.update", "1"), + EventInfo.new(Event.AUTOMOD_SETTINGS_UPDATE, "automod.settings.update", "1"), + EventInfo.new(Event.AUTOMOD_TERMS_UPDATE, "automod.terms.update", "1"), + EventInfo.new(Event.CHANNEL_UPDATE, "channel.update", "2"), + EventInfo.new(Event.CHANNEL_FOLLOW, "channel.follow", "2"), + EventInfo.new(Event.CHANNEL_AD_BREAK_BEGIN, "channel.ad_break.begin", "1"), + EventInfo.new(Event.CHANNEL_CHAT_CLEAR, "channel.chat.clear", "1"), + EventInfo.new(Event.CHANNEL_CHAT_CLEAR_USER_MESSAGES, "channel.chat.clear_user_messages", "1"), + EventInfo.new(Event.CHANNEL_CHAT_MESSAGE, "channel.chat.message", "1"), + EventInfo.new(Event.CHANNEL_CHAT_MESSAGE_DELETE, "channel.chat.message_delete", "1"), + EventInfo.new(Event.CHANNEL_CHAT_NOTIFICATION, "channel.chat.notification", "1"), + EventInfo.new(Event.CHANNEL_CHAT_SETTINGS_UPDATE, "channel.chat_settings.update", "1"), + EventInfo.new(Event.CHANNEL_CHAT_USER_MESSAGE_HOLD, "channel.chat.user_message_hold", "1"), + EventInfo.new(Event.CHANNEL_CHAT_USER_MESSAGE_UPDATE, "channel.chat.user_message_update", "1"), + EventInfo.new(Event.CHANNEL_SUBSCRIBE, "channel.subscribe", "1"), + EventInfo.new(Event.CHANNEL_SUBSCRIPTION_END, "channel.subscription.end", "1"), + EventInfo.new(Event.CHANNEL_SUBSCRIPTION_GIFT, "channel.subscription.gift", "1"), + EventInfo.new(Event.CHANNEL_SUBSCRIPTION_MESSAGE, "channel.subscription.message", "1"), + EventInfo.new(Event.CHANNEL_CHEER, "channel.cheer", "1"), + EventInfo.new(Event.CHANNEL_RAID, "channel.raid", "1"), + EventInfo.new(Event.CHANNEL_BAN, "channel.ban", "1"), + EventInfo.new(Event.CHANNEL_UNBAN, "channel.unban", "1"), + EventInfo.new(Event.CHANNEL_UNBAN_REQUEST_CREATE, "channel.unban_request.create", "1"), + EventInfo.new(Event.CHANNEL_UNBAN_REQUEST_RESOLVE, "channel.unban_request.resolve", "1"), + EventInfo.new(Event.CHANNEL_MODERATE, "channel.moderate", "1"), + EventInfo.new(Event.CHANNEL_MODERATOR_ADD, "channel.moderator.add", "1"), + EventInfo.new(Event.CHANNEL_MODERATOR_REMOVE, "channel.moderator.remove", "1"), + EventInfo.new(Event.CHANNEL_GUEST_STAR_SESSION_BEGIN, "channel.guest_star_session.begin", "beta"), + EventInfo.new(Event.CHANNEL_GUEST_STAR_SESSION_END, "channel.guest_star_session.end", "beta"), + EventInfo.new(Event.CHANNEL_GUEST_STAR_GUEST_UPDATE, "channel.guest_star_guest.update", "beta"), + EventInfo.new(Event.CHANNEL_GUEST_STAR_SETTINGS_UPDATE, "channel.guest_star_settings.update", "beta"), + EventInfo.new(Event.CHANNEL_CHANNEL_POINTS_AUTOMATIC_REWARD_REDEMPTION_ADD, "channel.channel_points_automatic_reward_redemption.add", "1"), + EventInfo.new(Event.CHANNEL_CHANNEL_POINTS_CUSTOM_REWARD_ADD, "channel.channel_points_custom_reward.add", "1"), + EventInfo.new(Event.CHANNEL_CHANNEL_POINTS_CUSTOM_REWARD_UPDATE, "channel.channel_points_custom_reward.update", "1"), + EventInfo.new(Event.CHANNEL_CHANNEL_POINTS_CUSTOM_REWARD_REMOVE, "channel.channel_points_custom_reward.remove", "1"), + EventInfo.new(Event.CHANNEL_CHANNEL_POINTS_CUSTOM_REWARD_REDEMPTION_ADD, "channel.channel_points_custom_reward_redemption.add", "1"), + EventInfo.new(Event.CHANNEL_CHANNEL_POINTS_CUSTOM_REWARD_REDEMPTION_UPDATE, "channel.channel_points_custom_reward_redemption.update", "1"), + EventInfo.new(Event.CHANNEL_POLL_BEGIN, "channel.poll.begin", "1"), + EventInfo.new(Event.CHANNEL_POLL_PROGRESS, "channel.poll.progress", "1"), + EventInfo.new(Event.CHANNEL_POLL_END, "channel.poll.end", "1"), + EventInfo.new(Event.CHANNEL_PREDICTION_BEGIN, "channel.prediction.begin", "1"), + EventInfo.new(Event.CHANNEL_PREDICTION_PROGRESS, "channel.prediction.progress", "1"), + EventInfo.new(Event.CHANNEL_PREDICTION_LOCK, "channel.prediction.lock", "1"), + EventInfo.new(Event.CHANNEL_PREDICTION_END, "channel.prediction.end", "1"), + EventInfo.new(Event.CHANNEL_SUSPICIOUS_USER_MESSAGE, "channel.suspicious_user.message", "1"), + EventInfo.new(Event.CHANNEL_SUSPICIOUS_USER_UPDATE, "channel.suspicious_user.update", "1"), + EventInfo.new(Event.CHANNEL_VIP_ADD, "channel.vip.add", "1"), + EventInfo.new(Event.CHANNEL_VIP_REMOVE, "channel.vip.remove", "1"), + EventInfo.new(Event.CHANNEL_CHARITY_CAMPAIGN_DONATE, "channel.charity_campaign.donate", "1"), + EventInfo.new(Event.CHANNEL_CHARITY_CAMPAIGN_START, "channel.charity_campaign.start", "1"), + EventInfo.new(Event.CHANNEL_CHARITY_CAMPAIGN_PROGRESS, "channel.charity_campaign.progress", "1"), + EventInfo.new(Event.CHANNEL_CHARITY_CAMPAIGN_STOP, "channel.charity_campaign.stop", "1"), + EventInfo.new(Event.CONDUIT_SHARD_DISABLED, "conduit.shard.disabled", "1"), + EventInfo.new(Event.DROP_ENTITLEMENT_GRANT, "drop.entitlement.grant", "1"), + EventInfo.new(Event.EXTENSION_BITS_TRANSACTION_CREATE, "extension.bits_transaction.create", "1"), + EventInfo.new(Event.CHANNEL_GOAL_BEGIN, "channel.goal.begin", "1"), + EventInfo.new(Event.CHANNEL_GOAL_PROGRESS, "channel.goal.progress", "1"), + EventInfo.new(Event.CHANNEL_GOAL_END, "channel.goal.end", "1"), + EventInfo.new(Event.CHANNEL_HYPE_TRAIN_BEGIN, "channel.hype_train.begin", "1"), + EventInfo.new(Event.CHANNEL_HYPE_TRAIN_PROGRESS, "channel.hype_train.progress", "1"), + EventInfo.new(Event.CHANNEL_HYPE_TRAIN_END, "channel.hype_train.end", "1"), + EventInfo.new(Event.CHANNEL_SHIELD_MODE_BEGIN, "channel.shield_mode.begin", "1"), + EventInfo.new(Event.CHANNEL_SHIELD_MODE_END, "channel.shield_mode.end", "1"), + EventInfo.new(Event.CHANNEL_SHOUTOUT_CREATE, "channel.shoutout.create", "1"), + EventInfo.new(Event.CHANNEL_SHOUTOUT_RECEIVE, "channel.shoutout.receive", "1"), + EventInfo.new(Event.STREAM_ONLINE, "stream.online", "1"), + EventInfo.new(Event.STREAM_OFFLINE, "stream.offline", "1"), + EventInfo.new(Event.USER_AUTHORIZATION_GRANT, "user.authorization.grant", "1"), + EventInfo.new(Event.USER_AUTHORIZATION_REVOKE, "user.authorization.revoke", "1"), + EventInfo.new(Event.USER_UPDATE, "user.update", "1"), + EventInfo.new(Event.USER_WHISPER_RECEIVED, "user.whisper.message", "1") +] + + +# -- Public Methods + + +static func get_event_from_type(type: Event): + var result = _events.filter(func(event): return event.type == type) + + if result: + return result[0] + else: + return null + + +static func get_event_from_name(name: String): + var result = _events.filter(func(event): return event.name == name) + + if result: + return result[0] + else: + return null + + +static func get_event_type_from_name(name: String): + var result = _events.filter(func(event): return event.name == name) + + if result: + return result[0].type + else: + return null + + +# -- Classes + + +class EventInfo: + var type: Event + var name: String + var version: String + var conditions: Array[String] + + + func _init(type: Event, name: String, version: String, conditions: Array[String] = []): + self.type = type + self.name = name + self.version = version + self.conditions = conditions + + + func generate_sub(arguments: Dictionary): + return { + "type": self.name, + "version": self.version, + "condition": { # Change to generalize for all types + "broadcaster_user_id": arguments.broadcaster, + "user_id": arguments.user + }, + "transport": { + "method": "websocket", + "session_id": arguments.websocket + } + } diff --git a/twitch_god.gd b/twitch_god.gd new file mode 100644 index 0000000..3f8e3bd --- /dev/null +++ b/twitch_god.gd @@ -0,0 +1,82 @@ +extends Node + +@onready var http_client = $"HttpClient" +@onready var websocket_client = $"WebsocketClient" +@onready var http_server = $"HttpServer" +@onready var auth = $"Auth" + +const API_URL = "https://api.twitch.tv/helix" +var access_token = null + + +# -- Built-in Methods + + +func _ready(): + print("READY") + http_server.started.connect(_on_server_started) + http_server.received.connect(_on_server_received) + websocket_client.opened.connect(_on_websocket_opened) + http_client.request_completed.connect(_on_request_completed) + + http_server.start() + + +# -- Private Methods + + +func _on_server_started(): + websocket_client.open() + + +func _on_websocket_opened(): + http_server.auth( + { + "response_type": "code", + "client_id": auth.CLIENT_ID, + "redirect_uri": "http://localhost:%d" % [auth.PORT], + "scope": auth.SCOPE + } + ) + + +func _on_server_received(auth2): + http_client.request( + HttpClient.API_TYPE.AUTH, + { + "grant_type": "authorization_code", + "client_id": auth.CLIENT_ID, + "redirect_uri": "http://localhost:%d" % [auth.PORT], + "code": auth2, + "client_secret": auth.CLIENT_SECRET + } + ) + + +func _on_request_completed(type: HttpClient.API_TYPE, data: Dictionary): + match type: + HttpClient.API_TYPE.AUTH: + http_client.request( + HttpClient.API_TYPE.VERIFY, + { + "token": data.access_token + } + ) + HttpClient.API_TYPE.VERIFY: + if not websocket_client.id: + push_error("Tried to use nonexistent websocket id. Please report to Ategon.") + return + + for sub in auth.SUBS: + var event = TwitchEvents.get_event_from_type(sub) + var sub_data = event.generate_sub({ + "broadcaster": auth.BROADCASTER_ID, + "user": auth.USER_ID, + "websocket": websocket_client.id + }) + + http_client.request( + HttpClient.API_TYPE.CREATE_EVENTSUB, + {}, + sub_data + ) diff --git a/twitch_scopes.gd b/twitch_scopes.gd new file mode 100644 index 0000000..557be4d --- /dev/null +++ b/twitch_scopes.gd @@ -0,0 +1,12 @@ +@tool +class_name TwitchScopes +extends Object + +enum Scope { + ANALYTICS_READ_EXTENSIONS, + ANALYTICS_READ_GAMES, + BITS_READ, + CHANNEL_MANAGE_ADS, + CHANNEL_READ_ADS, + CHANNEL_MANAGE_BROADCAST +}