From 45cb5aa590423e3c83de8dd94d5d5fa83464bf29 Mon Sep 17 00:00:00 2001 From: Ategon Date: Mon, 21 Oct 2024 22:30:26 -0400 Subject: [PATCH] Initial commit --- .gitattributes | 2 + .gitignore | 3 + Main.tscn | 9 + addons/TwitchGod/TwitchGod.tscn | 22 ++ addons/TwitchGod/http/HttpClient.tscn | 6 + addons/TwitchGod/http/HttpServer.tscn | 6 + addons/TwitchGod/http/TwitchSetting.tscn | 6 + addons/TwitchGod/http/WebsocketClient.tscn | 6 + addons/TwitchGod/http/http_client.gd | 118 ++++++++++ addons/TwitchGod/http/http_server.gd | 78 +++++++ addons/TwitchGod/http/twitch_setting.gd | 24 ++ addons/TwitchGod/http/websocket_client.gd | 71 ++++++ addons/TwitchGod/icons/bell.svg | 1 + addons/TwitchGod/icons/bell.svg.import | 37 +++ addons/TwitchGod/icons/bot-message-square.svg | 1 + .../icons/bot-message-square.svg.import | 37 +++ addons/TwitchGod/icons/cog.svg | 1 + addons/TwitchGod/icons/cog.svg.import | 37 +++ addons/TwitchGod/icons/key-round.svg | 1 + addons/TwitchGod/icons/key-round.svg.import | 37 +++ addons/TwitchGod/icons/message-square.svg | 1 + .../TwitchGod/icons/message-square.svg.import | 37 +++ addons/TwitchGod/icons/pyramid.svg | 1 + addons/TwitchGod/icons/pyramid.svg.import | 37 +++ addons/TwitchGod/icons/regex.svg | 1 + addons/TwitchGod/icons/regex.svg.import | 37 +++ addons/TwitchGod/icons/server.svg | 1 + addons/TwitchGod/icons/server.svg.import | 37 +++ addons/TwitchGod/icons/twitch.svg | 1 + addons/TwitchGod/icons/twitch.svg.import | 37 +++ addons/TwitchGod/icons/unplug.svg | 1 + addons/TwitchGod/icons/unplug.svg.import | 37 +++ .../nodes/TwitchChatCommandListener.tscn | 12 + .../nodes/TwitchChatMessageListener.tscn | 12 + .../nodes/TwitchChatRegexListener.tscn | 12 + .../TwitchGod/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 ++ .../TwitchGod/nodes/twitch_event_listener.gd | 24 ++ .../nodes/twitch_point_redemption_listener.gd | 18 ++ addons/TwitchGod/twitch_events.gd | 215 ++++++++++++++++++ addons/TwitchGod/twitch_god.gd | 82 +++++++ addons/TwitchGod/twitch_scopes.gd | 12 + icon.svg | 1 + icon.svg.import | 37 +++ main.gd | 5 + project.godot | 43 ++++ src/AddedChannel.tscn | 18 ++ src/Emote.tscn | 9 + src/Emotes.tscn | 122 ++++++++++ src/add_to_whitelist.gd | 83 +++++++ src/added_channel.gd | 9 + src/emote.gd | 16 ++ src/emotes.gd | 103 +++++++++ 56 files changed, 1649 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 Main.tscn create mode 100644 addons/TwitchGod/TwitchGod.tscn create mode 100644 addons/TwitchGod/http/HttpClient.tscn create mode 100644 addons/TwitchGod/http/HttpServer.tscn create mode 100644 addons/TwitchGod/http/TwitchSetting.tscn create mode 100644 addons/TwitchGod/http/WebsocketClient.tscn create mode 100644 addons/TwitchGod/http/http_client.gd create mode 100644 addons/TwitchGod/http/http_server.gd create mode 100644 addons/TwitchGod/http/twitch_setting.gd create mode 100644 addons/TwitchGod/http/websocket_client.gd create mode 100644 addons/TwitchGod/icons/bell.svg create mode 100644 addons/TwitchGod/icons/bell.svg.import create mode 100644 addons/TwitchGod/icons/bot-message-square.svg create mode 100644 addons/TwitchGod/icons/bot-message-square.svg.import create mode 100644 addons/TwitchGod/icons/cog.svg create mode 100644 addons/TwitchGod/icons/cog.svg.import create mode 100644 addons/TwitchGod/icons/key-round.svg create mode 100644 addons/TwitchGod/icons/key-round.svg.import create mode 100644 addons/TwitchGod/icons/message-square.svg create mode 100644 addons/TwitchGod/icons/message-square.svg.import create mode 100644 addons/TwitchGod/icons/pyramid.svg create mode 100644 addons/TwitchGod/icons/pyramid.svg.import create mode 100644 addons/TwitchGod/icons/regex.svg create mode 100644 addons/TwitchGod/icons/regex.svg.import create mode 100644 addons/TwitchGod/icons/server.svg create mode 100644 addons/TwitchGod/icons/server.svg.import create mode 100644 addons/TwitchGod/icons/twitch.svg create mode 100644 addons/TwitchGod/icons/twitch.svg.import create mode 100644 addons/TwitchGod/icons/unplug.svg create mode 100644 addons/TwitchGod/icons/unplug.svg.import create mode 100644 addons/TwitchGod/nodes/TwitchChatCommandListener.tscn create mode 100644 addons/TwitchGod/nodes/TwitchChatMessageListener.tscn create mode 100644 addons/TwitchGod/nodes/TwitchChatRegexListener.tscn create mode 100644 addons/TwitchGod/nodes/TwitchEventListener.tscn create mode 100644 addons/TwitchGod/nodes/TwitchPointRedemptionListener.tscn create mode 100644 addons/TwitchGod/nodes/twitch_chat_command_listener.gd create mode 100644 addons/TwitchGod/nodes/twitch_chat_message_listener.gd create mode 100644 addons/TwitchGod/nodes/twitch_chat_regex_listener.gd create mode 100644 addons/TwitchGod/nodes/twitch_event_listener.gd create mode 100644 addons/TwitchGod/nodes/twitch_point_redemption_listener.gd create mode 100644 addons/TwitchGod/twitch_events.gd create mode 100644 addons/TwitchGod/twitch_god.gd create mode 100644 addons/TwitchGod/twitch_scopes.gd create mode 100644 icon.svg create mode 100644 icon.svg.import create mode 100644 main.gd create mode 100644 project.godot create mode 100644 src/AddedChannel.tscn create mode 100644 src/Emote.tscn create mode 100644 src/Emotes.tscn create mode 100644 src/add_to_whitelist.gd create mode 100644 src/added_channel.gd create mode 100644 src/emote.gd create mode 100644 src/emotes.gd diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..8ad74f7 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Normalize EOL for all files that Git considers text files. +* text=auto eol=lf diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c417f6c --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +# Godot 4+ specific ignores +.godot/ +/**/auth.gd \ No newline at end of file diff --git a/Main.tscn b/Main.tscn new file mode 100644 index 0000000..1b5630c --- /dev/null +++ b/Main.tscn @@ -0,0 +1,9 @@ +[gd_scene load_steps=3 format=3 uid="uid://fh070t2wv8xi"] + +[ext_resource type="Script" path="res://main.gd" id="1_2diot"] +[ext_resource type="PackedScene" uid="uid://dsp5r4i4h08v2" path="res://src/Emotes.tscn" id="2_s32fb"] + +[node name="Main" type="Node2D"] +script = ExtResource("1_2diot") + +[node name="Emotes" parent="." instance=ExtResource("2_s32fb")] diff --git a/addons/TwitchGod/TwitchGod.tscn b/addons/TwitchGod/TwitchGod.tscn new file mode 100644 index 0000000..11c63b4 --- /dev/null +++ b/addons/TwitchGod/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/addons/TwitchGod/http/HttpClient.tscn b/addons/TwitchGod/http/HttpClient.tscn new file mode 100644 index 0000000..f2971d3 --- /dev/null +++ b/addons/TwitchGod/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/addons/TwitchGod/http/HttpServer.tscn b/addons/TwitchGod/http/HttpServer.tscn new file mode 100644 index 0000000..d8886c0 --- /dev/null +++ b/addons/TwitchGod/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/addons/TwitchGod/http/TwitchSetting.tscn b/addons/TwitchGod/http/TwitchSetting.tscn new file mode 100644 index 0000000..df6a55c --- /dev/null +++ b/addons/TwitchGod/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/addons/TwitchGod/http/WebsocketClient.tscn b/addons/TwitchGod/http/WebsocketClient.tscn new file mode 100644 index 0000000..4a5a244 --- /dev/null +++ b/addons/TwitchGod/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/addons/TwitchGod/http/http_client.gd b/addons/TwitchGod/http/http_client.gd new file mode 100644 index 0000000..91210e0 --- /dev/null +++ b/addons/TwitchGod/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/addons/TwitchGod/http/http_server.gd b/addons/TwitchGod/http/http_server.gd new file mode 100644 index 0000000..b593549 --- /dev/null +++ b/addons/TwitchGod/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/addons/TwitchGod/http/twitch_setting.gd b/addons/TwitchGod/http/twitch_setting.gd new file mode 100644 index 0000000..b8ea33c --- /dev/null +++ b/addons/TwitchGod/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/addons/TwitchGod/http/websocket_client.gd b/addons/TwitchGod/http/websocket_client.gd new file mode 100644 index 0000000..48a7306 --- /dev/null +++ b/addons/TwitchGod/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/addons/TwitchGod/icons/bell.svg b/addons/TwitchGod/icons/bell.svg new file mode 100644 index 0000000..95eb12f --- /dev/null +++ b/addons/TwitchGod/icons/bell.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/addons/TwitchGod/icons/bell.svg.import b/addons/TwitchGod/icons/bell.svg.import new file mode 100644 index 0000000..6682d08 --- /dev/null +++ b/addons/TwitchGod/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/addons/TwitchGod/icons/bot-message-square.svg b/addons/TwitchGod/icons/bot-message-square.svg new file mode 100644 index 0000000..67fe514 --- /dev/null +++ b/addons/TwitchGod/icons/bot-message-square.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/addons/TwitchGod/icons/bot-message-square.svg.import b/addons/TwitchGod/icons/bot-message-square.svg.import new file mode 100644 index 0000000..2a5900d --- /dev/null +++ b/addons/TwitchGod/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/addons/TwitchGod/icons/cog.svg b/addons/TwitchGod/icons/cog.svg new file mode 100644 index 0000000..7457690 --- /dev/null +++ b/addons/TwitchGod/icons/cog.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/addons/TwitchGod/icons/cog.svg.import b/addons/TwitchGod/icons/cog.svg.import new file mode 100644 index 0000000..1c1e0d2 --- /dev/null +++ b/addons/TwitchGod/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/addons/TwitchGod/icons/key-round.svg b/addons/TwitchGod/icons/key-round.svg new file mode 100644 index 0000000..f37bb5d --- /dev/null +++ b/addons/TwitchGod/icons/key-round.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/addons/TwitchGod/icons/key-round.svg.import b/addons/TwitchGod/icons/key-round.svg.import new file mode 100644 index 0000000..1963f8c --- /dev/null +++ b/addons/TwitchGod/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/addons/TwitchGod/icons/message-square.svg b/addons/TwitchGod/icons/message-square.svg new file mode 100644 index 0000000..d4535a8 --- /dev/null +++ b/addons/TwitchGod/icons/message-square.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/addons/TwitchGod/icons/message-square.svg.import b/addons/TwitchGod/icons/message-square.svg.import new file mode 100644 index 0000000..f60f2a7 --- /dev/null +++ b/addons/TwitchGod/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/addons/TwitchGod/icons/pyramid.svg b/addons/TwitchGod/icons/pyramid.svg new file mode 100644 index 0000000..c18a94d --- /dev/null +++ b/addons/TwitchGod/icons/pyramid.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/addons/TwitchGod/icons/pyramid.svg.import b/addons/TwitchGod/icons/pyramid.svg.import new file mode 100644 index 0000000..93471c0 --- /dev/null +++ b/addons/TwitchGod/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/addons/TwitchGod/icons/regex.svg b/addons/TwitchGod/icons/regex.svg new file mode 100644 index 0000000..cdf2a29 --- /dev/null +++ b/addons/TwitchGod/icons/regex.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/addons/TwitchGod/icons/regex.svg.import b/addons/TwitchGod/icons/regex.svg.import new file mode 100644 index 0000000..3f77c79 --- /dev/null +++ b/addons/TwitchGod/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/addons/TwitchGod/icons/server.svg b/addons/TwitchGod/icons/server.svg new file mode 100644 index 0000000..223bda7 --- /dev/null +++ b/addons/TwitchGod/icons/server.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/addons/TwitchGod/icons/server.svg.import b/addons/TwitchGod/icons/server.svg.import new file mode 100644 index 0000000..b46bd3f --- /dev/null +++ b/addons/TwitchGod/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/addons/TwitchGod/icons/twitch.svg b/addons/TwitchGod/icons/twitch.svg new file mode 100644 index 0000000..bc5fed3 --- /dev/null +++ b/addons/TwitchGod/icons/twitch.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/addons/TwitchGod/icons/twitch.svg.import b/addons/TwitchGod/icons/twitch.svg.import new file mode 100644 index 0000000..09d613f --- /dev/null +++ b/addons/TwitchGod/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/addons/TwitchGod/icons/unplug.svg b/addons/TwitchGod/icons/unplug.svg new file mode 100644 index 0000000..cf3e661 --- /dev/null +++ b/addons/TwitchGod/icons/unplug.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/addons/TwitchGod/icons/unplug.svg.import b/addons/TwitchGod/icons/unplug.svg.import new file mode 100644 index 0000000..6e7ba51 --- /dev/null +++ b/addons/TwitchGod/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/addons/TwitchGod/nodes/TwitchChatCommandListener.tscn b/addons/TwitchGod/nodes/TwitchChatCommandListener.tscn new file mode 100644 index 0000000..ddd76bb --- /dev/null +++ b/addons/TwitchGod/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/addons/TwitchGod/nodes/TwitchChatMessageListener.tscn b/addons/TwitchGod/nodes/TwitchChatMessageListener.tscn new file mode 100644 index 0000000..cc35751 --- /dev/null +++ b/addons/TwitchGod/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/addons/TwitchGod/nodes/TwitchChatRegexListener.tscn b/addons/TwitchGod/nodes/TwitchChatRegexListener.tscn new file mode 100644 index 0000000..d9f98c8 --- /dev/null +++ b/addons/TwitchGod/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/addons/TwitchGod/nodes/TwitchEventListener.tscn b/addons/TwitchGod/nodes/TwitchEventListener.tscn new file mode 100644 index 0000000..8fd1442 --- /dev/null +++ b/addons/TwitchGod/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/addons/TwitchGod/nodes/TwitchPointRedemptionListener.tscn b/addons/TwitchGod/nodes/TwitchPointRedemptionListener.tscn new file mode 100644 index 0000000..72f6d3f --- /dev/null +++ b/addons/TwitchGod/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/addons/TwitchGod/nodes/twitch_chat_command_listener.gd b/addons/TwitchGod/nodes/twitch_chat_command_listener.gd new file mode 100644 index 0000000..c918541 --- /dev/null +++ b/addons/TwitchGod/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/addons/TwitchGod/nodes/twitch_chat_message_listener.gd b/addons/TwitchGod/nodes/twitch_chat_message_listener.gd new file mode 100644 index 0000000..8c2107d --- /dev/null +++ b/addons/TwitchGod/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/addons/TwitchGod/nodes/twitch_chat_regex_listener.gd b/addons/TwitchGod/nodes/twitch_chat_regex_listener.gd new file mode 100644 index 0000000..8617529 --- /dev/null +++ b/addons/TwitchGod/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/addons/TwitchGod/nodes/twitch_event_listener.gd b/addons/TwitchGod/nodes/twitch_event_listener.gd new file mode 100644 index 0000000..ee313f3 --- /dev/null +++ b/addons/TwitchGod/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/addons/TwitchGod/nodes/twitch_point_redemption_listener.gd b/addons/TwitchGod/nodes/twitch_point_redemption_listener.gd new file mode 100644 index 0000000..0fdddf4 --- /dev/null +++ b/addons/TwitchGod/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/addons/TwitchGod/twitch_events.gd b/addons/TwitchGod/twitch_events.gd new file mode 100644 index 0000000..8c11296 --- /dev/null +++ b/addons/TwitchGod/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/addons/TwitchGod/twitch_god.gd b/addons/TwitchGod/twitch_god.gd new file mode 100644 index 0000000..3f8e3bd --- /dev/null +++ b/addons/TwitchGod/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/addons/TwitchGod/twitch_scopes.gd b/addons/TwitchGod/twitch_scopes.gd new file mode 100644 index 0000000..557be4d --- /dev/null +++ b/addons/TwitchGod/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 +} diff --git a/icon.svg b/icon.svg new file mode 100644 index 0000000..1a10d58 --- /dev/null +++ b/icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icon.svg.import b/icon.svg.import new file mode 100644 index 0000000..5e1f86b --- /dev/null +++ b/icon.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dyu3h6ohevyaf" +path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://icon.svg" +dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.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/main.gd b/main.gd new file mode 100644 index 0000000..99cab3b --- /dev/null +++ b/main.gd @@ -0,0 +1,5 @@ +extends Node2D + + +func _ready() -> void: + get_viewport().transparent_bg = true diff --git a/project.godot b/project.godot new file mode 100644 index 0000000..ff13688 --- /dev/null +++ b/project.godot @@ -0,0 +1,43 @@ +; Engine configuration file. +; It's best edited using the editor UI and not directly, +; since the parameters that go here are not all obvious. +; +; Format: +; [section] ; section goes between [] +; param=value ; assign values to parameters + +config_version=5 + +[application] + +config/name="EmoteWall" +run/main_scene="res://Main.tscn" +config/features=PackedStringArray("4.3") +config/icon="res://icon.svg" + +[autoload] + +TwitchGod="*res://addons/TwitchGod/TwitchGod.tscn" + +[display] + +window/size/transparent=true +window/per_pixel_transparency/allowed=true + +[input] + +show_whitelist_popup={ +"deadzone": 0.5, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":87,"key_label":0,"unicode":119,"location":0,"echo":false,"script":null) +] +} +hide_whitelist_popup={ +"deadzone": 0.5, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":69,"key_label":0,"unicode":101,"location":0,"echo":false,"script":null) +] +} +enter_text={ +"deadzone": 0.5, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194309,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) +] +} diff --git a/src/AddedChannel.tscn b/src/AddedChannel.tscn new file mode 100644 index 0000000..72c4d88 --- /dev/null +++ b/src/AddedChannel.tscn @@ -0,0 +1,18 @@ +[gd_scene load_steps=2 format=3 uid="uid://dwsnd5vbnrup"] + +[ext_resource type="Script" path="res://src/added_channel.gd" id="1_gtue1"] + +[node name="AddedChannel" type="HBoxContainer"] +script = ExtResource("1_gtue1") + +[node name="Label" type="Label" parent="."] +custom_minimum_size = Vector2(220, 0) +layout_mode = 2 +text = "Test" +horizontal_alignment = 1 + +[node name="Button" type="Button" parent="."] +layout_mode = 2 +text = "X" + +[connection signal="pressed" from="Button" to="." method="_on_button_pressed"] diff --git a/src/Emote.tscn b/src/Emote.tscn new file mode 100644 index 0000000..69419c5 --- /dev/null +++ b/src/Emote.tscn @@ -0,0 +1,9 @@ +[gd_scene load_steps=2 format=3 uid="uid://b40miugn8pf1c"] + +[ext_resource type="Script" path="res://src/emote.gd" id="1_6boyd"] + +[node name="Emote" type="TextureRect"] +offset_right = 40.0 +offset_bottom = 40.0 +pivot_offset = Vector2(20, 20) +script = ExtResource("1_6boyd") diff --git a/src/Emotes.tscn b/src/Emotes.tscn new file mode 100644 index 0000000..e498379 --- /dev/null +++ b/src/Emotes.tscn @@ -0,0 +1,122 @@ +[gd_scene load_steps=5 format=3 uid="uid://dsp5r4i4h08v2"] + +[ext_resource type="Script" path="res://src/emotes.gd" id="1_fhqqu"] +[ext_resource type="PackedScene" uid="uid://b2585bqiywbwv" path="res://addons/TwitchGod/nodes/TwitchEventListener.tscn" id="2_iwst4"] +[ext_resource type="PackedScene" uid="uid://b40miugn8pf1c" path="res://src/Emote.tscn" id="3_52qj6"] +[ext_resource type="Script" path="res://src/add_to_whitelist.gd" id="4_quosf"] + +[node name="Emotes" type="Control"] +layout_mode = 3 +anchors_preset = 0 +offset_right = 1152.0 +offset_bottom = 648.0 +script = ExtResource("1_fhqqu") + +[node name="TwitchEventListener" parent="." instance=ExtResource("2_iwst4")] +event = 9 + +[node name="Emote" parent="." instance=ExtResource("3_52qj6")] +layout_mode = 0 + +[node name="AddToWhitelist" type="Control" parent="."] +layout_mode = 1 +anchors_preset = 8 +anchor_left = 0.5 +anchor_top = 0.5 +anchor_right = 0.5 +anchor_bottom = 0.5 +offset_left = -264.0 +offset_top = -119.0 +offset_right = 264.0 +offset_bottom = 119.0 +grow_horizontal = 2 +grow_vertical = 2 +pivot_offset = Vector2(268, 120) +script = ExtResource("4_quosf") + +[node name="InsertIdLineEdit" type="LineEdit" parent="AddToWhitelist"] +layout_mode = 1 +anchors_preset = 8 +anchor_left = 0.5 +anchor_top = 0.5 +anchor_right = 0.5 +anchor_bottom = 0.5 +offset_left = -158.0 +offset_top = -35.0 +offset_right = 158.0 +offset_bottom = -4.0 +grow_horizontal = 2 +grow_vertical = 2 +placeholder_text = "Insert Channel Name" + +[node name="SubmitButton" type="Button" parent="AddToWhitelist"] +layout_mode = 1 +anchors_preset = 8 +anchor_left = 0.5 +anchor_top = 0.5 +anchor_right = 0.5 +anchor_bottom = 0.5 +offset_left = -140.0 +offset_top = 9.0 +offset_right = -58.0 +offset_bottom = 26.0 +grow_horizontal = 2 +grow_vertical = 2 +text = "Submit" + +[node name="CloseButton" type="Button" parent="AddToWhitelist"] +layout_mode = 1 +anchors_preset = 1 +anchor_left = 1.0 +anchor_right = 1.0 +offset_left = -26.0 +offset_bottom = 31.0 +grow_horizontal = 0 +text = "X" + +[node name="AddLabel" type="Label" parent="AddToWhitelist"] +layout_mode = 1 +anchors_preset = 8 +anchor_left = 0.5 +anchor_top = 0.5 +anchor_right = 0.5 +anchor_bottom = 0.5 +offset_left = -134.0 +offset_top = -79.0 +offset_right = 134.0 +offset_bottom = -36.0 +grow_horizontal = 2 +grow_vertical = 2 +theme_override_font_sizes/font_size = 24 +text = "Add to Whitelist" +horizontal_alignment = 1 + +[node name="AddedChannels" type="HBoxContainer" parent="AddToWhitelist"] +layout_mode = 1 +anchors_preset = 8 +anchor_left = 0.5 +anchor_top = 0.5 +anchor_right = 0.5 +anchor_bottom = 0.5 +offset_left = -361.0 +offset_top = 71.0 +offset_right = 307.0 +offset_bottom = 291.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="AddedChannels1" type="VBoxContainer" parent="AddToWhitelist/AddedChannels"] +layout_mode = 2 + +[node name="AddedChannels2" type="VBoxContainer" parent="AddToWhitelist/AddedChannels"] +layout_mode = 2 + +[node name="AddedChannels3" type="VBoxContainer" parent="AddToWhitelist/AddedChannels"] +layout_mode = 2 + +[connection signal="received" from="TwitchEventListener" to="." method="_on_twitch_event_listener_received"] +[connection signal="new_whitelist_id" from="AddToWhitelist" to="." method="_on_add_to_whitelist_new_whitelist_id"] +[connection signal="focus_entered" from="AddToWhitelist/InsertIdLineEdit" to="AddToWhitelist" method="_on_insert_id_line_edit_focus_entered"] +[connection signal="focus_exited" from="AddToWhitelist/InsertIdLineEdit" to="AddToWhitelist" method="_on_insert_id_line_edit_focus_exited"] +[connection signal="pressed" from="AddToWhitelist/SubmitButton" to="AddToWhitelist" method="_on_submit_button_pressed"] +[connection signal="pressed" from="AddToWhitelist/CloseButton" to="AddToWhitelist" method="_on_close_button_pressed"] diff --git a/src/add_to_whitelist.gd b/src/add_to_whitelist.gd new file mode 100644 index 0000000..955b6c3 --- /dev/null +++ b/src/add_to_whitelist.gd @@ -0,0 +1,83 @@ +extends Control + +signal new_whitelist_id(id: String, name: String) + +@onready var insert_id_line_edit: LineEdit = $InsertIdLineEdit + +var size_tween +var line_focused = false + + +func _ready() -> void: + scale = Vector2.ZERO + + +func _process(delta: float) -> void: + if Input.is_action_just_pressed("show_whitelist_popup"): + show_popup() + elif Input.is_action_just_pressed("hide_whitelist_popup") and not line_focused: + hide_popup() + elif Input.is_action_just_pressed("enter_text") and line_focused: + _on_submit_button_pressed() + + +func show_popup(): + if size_tween: + size_tween.kill() + + visible = true + size_tween = create_tween() + size_tween.set_ease(Tween.EASE_OUT) + size_tween.set_trans(Tween.TRANS_BACK) + size_tween.tween_property(self, "scale", Vector2.ONE, 0.5) + + +func hide_popup(): + if size_tween: + size_tween.kill() + + size_tween = create_tween() + size_tween.set_ease(Tween.EASE_OUT) + size_tween.set_trans(Tween.TRANS_QUAD) + size_tween.tween_property(self, "scale", Vector2.ZERO, 0.25) + size_tween.tween_callback(func(): + visible = false + insert_id_line_edit.clear() + ) + + +func _on_close_button_pressed() -> void: + hide_popup() + + +func _on_lookup_button_pressed() -> void: + OS.shell_open("https://www.streamweasels.com/tools/convert-twitch-username-%20to-user-id/") + + +func _on_submit_button_pressed() -> void: + var value = insert_id_line_edit.text + insert_id_line_edit.clear() + + if value.is_valid_int(): + var data = await TwitchGod.http_client.request(TwitchGod.http_client.API_TYPE.USERS, { + "id": value + }) + + if not data.data.size() == 0: + new_whitelist_id.emit(data.data[0].id, data.data[0].display_name) + else: + var data = await TwitchGod.http_client.request(TwitchGod.http_client.API_TYPE.USERS, { + "login": value + }) + + if not data.data.size() == 0: + new_whitelist_id.emit(data.data[0].id, data.data[0].display_name) + + + +func _on_insert_id_line_edit_focus_entered() -> void: + line_focused = true + + +func _on_insert_id_line_edit_focus_exited() -> void: + line_focused = false diff --git a/src/added_channel.gd b/src/added_channel.gd new file mode 100644 index 0000000..9b73375 --- /dev/null +++ b/src/added_channel.gd @@ -0,0 +1,9 @@ +extends HBoxContainer + +signal remove_channel + +@onready var label: Label = $Label + + +func _on_button_pressed() -> void: + remove_channel.emit() diff --git a/src/emote.gd b/src/emote.gd new file mode 100644 index 0000000..ff44b48 --- /dev/null +++ b/src/emote.gd @@ -0,0 +1,16 @@ +extends TextureRect + +func _ready(): + position.y += 100 + + var tween = create_tween() + tween.set_ease(Tween.EASE_OUT) + tween.set_trans(Tween.TRANS_BACK) + tween.tween_property(self, "position:y", -100, 1.5).as_relative().from_current() + + var tween2 = create_tween() + tween.set_ease(Tween.EASE_IN) + tween2.set_trans(Tween.TRANS_BACK) + tween2.set_parallel() + tween2.tween_property(self, "modulate", Color.TRANSPARENT, 0.5).set_delay(1.25) + tween2.tween_property(self, "scale", Vector2(0, 0), 0.5).set_delay(1.25) diff --git a/src/emotes.gd b/src/emotes.gd new file mode 100644 index 0000000..78b0142 --- /dev/null +++ b/src/emotes.gd @@ -0,0 +1,103 @@ +extends Control + +@onready var texture_rect = $Emote +@onready var added_channels_1: VBoxContainer = $AddToWhitelist/AddedChannels/AddedChannels1 +@onready var added_channels_2: VBoxContainer = $AddToWhitelist/AddedChannels/AddedChannels2 +@onready var added_channels_3: VBoxContainer = $AddToWhitelist/AddedChannels/AddedChannels3 + +const ADDED_CHANNEL = preload("res://src/AddedChannel.tscn") +const EMOTE = preload("res://src/Emote.tscn") +var whitelist = [] +var emote_cache = {} + + +func _ready() -> void: + _load_from_whitelist() + + +func _save_to_whitelist(): + var file = FileAccess.open("user://whitelist.txt", FileAccess.WRITE) + + for channel in whitelist: + file.store_line("%s#%s" % [channel.id, channel.name]) + + +func _update_labels(): + for child in added_channels_1.get_children(): + child.queue_free() + for child in added_channels_2.get_children(): + child.queue_free() + for child in added_channels_3.get_children(): + child.queue_free() + + for i in range(0, whitelist.size()): + if i < 21: + var new_label = ADDED_CHANNEL.instantiate() + new_label.remove_channel.connect(_remove_from_whitelist.bind(whitelist[i].id)) + if i < 7: + added_channels_1.add_child(new_label) + elif i < 14: + added_channels_2.add_child(new_label) + elif i < 21: + added_channels_3.add_child(new_label) + new_label.label.text = whitelist[i].name + + +func _load_from_whitelist(): + if FileAccess.file_exists("user://whitelist.txt"): + var file = FileAccess.open("user://whitelist.txt", FileAccess.READ) + var content = file.get_as_text() + var filecontent = content.split("\n") + for i in range(0, filecontent.size()): + var idsplit = filecontent[i].split("#") + if idsplit.size() > 1: + whitelist.push_back({ + "id": idsplit[0], + "name": idsplit[1] + }) + else: + whitelist = [] + + _update_labels() + + +func _on_twitch_event_listener_received(data): + for fragment in data.message.fragments: + if fragment.type != "emote": + continue + + if not whitelist.filter(func (x): return x.id == fragment.emote.owner_id).size(): + continue + + if not emote_cache.has(fragment.emote.id): + var image_buffer = await TwitchGod.http_client.request(HttpClient.API_TYPE.EMOTE, { "id": fragment.emote.id }) + var image = Image.new() + image.load_png_from_buffer(image_buffer) + var texture = ImageTexture.create_from_image(image) + emote_cache[fragment.emote.id] = texture + + var new_emote = EMOTE.instantiate() + new_emote.position.x = randi_range(50, 900) + new_emote.position.y = 580 + new_emote.texture = emote_cache[fragment.emote.id] + add_child(new_emote) + + +func _remove_from_whitelist(id: String): + whitelist = whitelist.filter(func (x): return x.id != id) + + _update_labels() + _save_to_whitelist() + + +func _on_add_to_whitelist_new_whitelist_id(id: String, name: String) -> void: + if whitelist.filter(func (x): return x.id == id).size(): + return + + whitelist.push_back({ + "id": id, + "name": name + }) + + _update_labels() + _save_to_whitelist()