Add files

This commit is contained in:
Ategon 2024-09-28 18:34:09 -04:00
parent a6a83f5a0b
commit 91cb09a1e7
43 changed files with 1208 additions and 0 deletions

22
TwitchGod.tscn Normal file
View file

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

19
auth.gd Normal file
View file

@ -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 = [
""
]

6
http/HttpClient.tscn Normal file
View file

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

6
http/HttpServer.tscn Normal file
View file

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

6
http/TwitchSetting.tscn Normal file
View file

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

View file

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

118
http/http_client.gd Normal file
View file

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

78
http/http_server.gd Normal file
View file

@ -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", "<html><head><title>Login</title><script>window.close()</script></head><body>Success!</body></html>".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();

24
http/twitch_setting.gd Normal file
View file

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

71
http/websocket_client.gd Normal file
View file

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

1
icons/bell.svg Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-bell"><path d="M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9"/><path d="M10.3 21a1.94 1.94 0 0 0 3.4 0"/></svg>

After

Width:  |  Height:  |  Size: 302 B

37
icons/bell.svg.import Normal file
View file

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

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-bot-message-square"><path d="M12 6V2H8"/><path d="m8 18-4 4V8a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2Z"/><path d="M2 12h2"/><path d="M9 11v2"/><path d="M15 11v2"/><path d="M20 12h2"/></svg>

After

Width:  |  Height:  |  Size: 391 B

View file

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

1
icons/cog.svg Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-cog"><path d="M12 20a8 8 0 1 0 0-16 8 8 0 0 0 0 16Z"/><path d="M12 14a2 2 0 1 0 0-4 2 2 0 0 0 0 4Z"/><path d="M12 2v2"/><path d="M12 22v-2"/><path d="m17 20.66-1-1.73"/><path d="M11 10.27 7 3.34"/><path d="m20.66 17-1.73-1"/><path d="m3.34 7 1.73 1"/><path d="M14 12h8"/><path d="M2 12h2"/><path d="m20.66 7-1.73 1"/><path d="m3.34 17 1.73-1"/><path d="m17 3.34-1 1.73"/><path d="m11 13.73-4 6.93"/></svg>

After

Width:  |  Height:  |  Size: 607 B

37
icons/cog.svg.import Normal file
View file

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

1
icons/key-round.svg Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-key-round"><path d="M2 18v3c0 .6.4 1 1 1h4v-3h3v-3h2l1.4-1.4a6.5 6.5 0 1 0-4-4Z"/><circle cx="16.5" cy="7.5" r=".5" fill="currentColor"/></svg>

After

Width:  |  Height:  |  Size: 345 B

View file

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

1
icons/message-square.svg Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-message-square"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>

After

Width:  |  Height:  |  Size: 290 B

View file

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

1
icons/pyramid.svg Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-pyramid"><path d="M2.5 16.88a1 1 0 0 1-.32-1.43l9-13.02a1 1 0 0 1 1.64 0l9 13.01a1 1 0 0 1-.32 1.44l-8.51 4.86a2 2 0 0 1-1.98 0Z"/><path d="M12 2v20"/></svg>

After

Width:  |  Height:  |  Size: 359 B

37
icons/pyramid.svg.import Normal file
View file

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

1
icons/regex.svg Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-regex"><path d="M17 3v10"/><path d="m12.67 5.5 8.66 5"/><path d="m12.67 10.5 8.66-5"/><path d="M9 17a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v2a2 2 0 0 0 2 2h2a2 2 0 0 0 2-2v-2z"/></svg>

After

Width:  |  Height:  |  Size: 370 B

37
icons/regex.svg.import Normal file
View file

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

1
icons/server.svg Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-server"><rect width="20" height="8" x="2" y="2" rx="2" ry="2"/><rect width="20" height="8" x="2" y="14" rx="2" ry="2"/><line x1="6" x2="6.01" y1="6" y2="6"/><line x1="6" x2="6.01" y1="18" y2="18"/></svg>

After

Width:  |  Height:  |  Size: 405 B

37
icons/server.svg.import Normal file
View file

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

1
icons/twitch.svg Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-twitch"><path d="M21 2H3v16h5v4l4-4h5l4-4V2zm-10 9V7m5 4V7"/></svg>

After

Width:  |  Height:  |  Size: 269 B

37
icons/twitch.svg.import Normal file
View file

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

1
icons/unplug.svg Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-unplug"><path d="m19 5 3-3"/><path d="m2 22 3-3"/><path d="M6.3 20.3a2.4 2.4 0 0 0 3.4 0L12 18l-6-6-2.3 2.3a2.4 2.4 0 0 0 0 3.4Z"/><path d="M7.5 13.5 10 11"/><path d="M10.5 16.5 13 14"/><path d="m12 6 6 6 2.3-2.3a2.4 2.4 0 0 0 0-3.4l-2.6-2.6a2.4 2.4 0 0 0-3.4 0Z"/></svg>

After

Width:  |  Height:  |  Size: 473 B

37
icons/unplug.svg.import Normal file
View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

215
twitch_events.gd Normal file
View file

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

82
twitch_god.gd Normal file
View file

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

12
twitch_scopes.gd Normal file
View file

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