Initial Commit

Initial commit of Code Base.
This commit is contained in:
Mario Steele 2025-06-12 14:31:14 -05:00
parent 293b1213e1
commit c11a4ebbc2
653 changed files with 36893 additions and 1 deletions

View file

@ -0,0 +1,55 @@
@tool
extends Control
class_name FileSelect
@export var default_path: String
@export var path: String: set = update_filepath
@export var filters: PackedStringArray: set = update_filters
@onready var line_edit: LineEdit = %LineEdit
@onready var button: Button = %Button
@onready var file_dialog: FileDialog = %FileDialog
signal file_selected(path: String)
func _ready() -> void:
var icon = EditorInterface.get_editor_theme().get_icon(&"FileBrowse", &"EditorIcons")
button.icon = icon
button.pressed.connect(_on_open_file_dialog)
file_dialog.file_selected.connect(_on_file_selected)
line_edit.text_changed.connect(_on_path_changed)
update_filepath(path)
update_filters(filters)
func update_filepath(new_path: String) -> void:
if new_path == null || new_path == "":
new_path = default_path
path = new_path
if is_inside_tree():
line_edit.text = new_path
file_dialog.current_path = new_path
func update_filters(new_filters: PackedStringArray) -> void:
filters = new_filters
if is_inside_tree():
file_dialog.filters = new_filters
func _on_open_file_dialog() -> void:
file_dialog.show()
func _on_path_changed(new_path: String) -> void:
file_dialog.current_path = new_path
path = new_path
file_selected.emit(new_path)
func _on_file_selected(new_path: String) -> void:
line_edit.text = new_path
path = new_path
file_selected.emit(new_path)

View file

@ -0,0 +1 @@
uid://cgfc1nq4f4nae

View file

@ -0,0 +1,40 @@
[gd_scene load_steps=4 format=3 uid="uid://b7smp156mdns6"]
[ext_resource type="Script" uid="uid://cgfc1nq4f4nae" path="res://addons/twitcher/editor/setup/file_select.gd" id="1_lnnjv"]
[sub_resource type="Image" id="Image_lnnjv"]
data = {
"data": PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 0, 0, 0, 0, 225, 225, 225, 127, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 0, 0, 0, 0, 224, 224, 224, 255, 225, 225, 225, 127, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 0, 0, 0, 0, 224, 224, 224, 255, 224, 224, 224, 255, 225, 225, 225, 127, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 0, 0, 0, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 225, 225, 225, 127, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 238, 225, 225, 225, 150, 225, 225, 225, 150, 224, 224, 224, 221, 224, 224, 224, 148, 224, 224, 224, 168, 224, 224, 224, 228, 224, 224, 224, 156, 224, 224, 224, 160, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 238, 230, 230, 230, 30, 255, 255, 255, 1, 255, 255, 255, 1, 0, 0, 0, 0, 255, 255, 255, 1, 255, 255, 255, 1, 0, 0, 0, 0, 255, 255, 255, 1, 255, 255, 255, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 255, 224, 224, 224, 255, 225, 225, 225, 150, 0, 0, 0, 0, 224, 224, 224, 194, 224, 224, 224, 193, 0, 0, 0, 0, 224, 224, 224, 194, 224, 224, 224, 193, 0, 0, 0, 0, 224, 224, 224, 194, 224, 224, 224, 193, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 153, 0, 0, 0, 0, 225, 225, 225, 191, 224, 224, 224, 189, 0, 0, 0, 0, 225, 225, 225, 191, 224, 224, 224, 189, 0, 0, 0, 0, 225, 225, 225, 191, 224, 224, 224, 189, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
"format": "RGBA8",
"height": 16,
"mipmaps": false,
"width": 16
}
[sub_resource type="ImageTexture" id="ImageTexture_7udr7"]
image = SubResource("Image_lnnjv")
[node name="FileSelect" type="HBoxContainer"]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
size_flags_horizontal = 3
script = ExtResource("1_lnnjv")
[node name="LineEdit" type="LineEdit" parent="."]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
tooltip_text = "Location where the OAuth Settings get saved to."
[node name="Button" type="Button" parent="."]
unique_name_in_owner = true
layout_mode = 2
icon = SubResource("ImageTexture_7udr7")
[node name="FileDialog" type="FileDialog" parent="."]
unique_name_in_owner = true
auto_translate_mode = 1
initial_position = 1

View file

@ -0,0 +1,24 @@
extends Node
@export var show_elements: Array[Control] = []
func _ready() -> void:
for element: Control in show_elements:
element.hide()
for child in get_children():
if child.has_signal(&"focus_entered"):
child.connect(&"focus_entered", _on_focus_entered)
if child.has_signal(&"focus_exited"):
child.connect(&"focus_exited", _on_focus_exited)
func _on_focus_entered() -> void:
for element: Node in show_elements:
element.show()
func _on_focus_exited() -> void:
for element: Node in show_elements:
element.hide()

View file

@ -0,0 +1 @@
uid://ddugotjvuahex

View file

@ -0,0 +1,101 @@
@tool
extends Node
const TwitchEditorSettings = preload("res://addons/twitcher/editor/twitch_editor_settings.gd")
const TwitchTweens = preload("res://addons/twitcher/editor/twitch_tweens.gd")
const TWITCH_SERVICE = preload("res://addons/twitcher/twitch_service.tscn")
@onready var authorization_explaination: RichTextLabel = %AuthExplain
@onready var client_id: LineEdit = %ClientId
@onready var client_secret: LineEdit = %ClientSecret
@onready var redirect_url: LineEdit = %RedirectURL
@onready var oauth_setting_file_select: FileSelect = %OauthSettingFileSelect
@onready var token_file_select: FileSelect = %TokenFileSelect
@onready var to_documentation: Button = %ToDocumentation
@onready var o_auth_save: Button = %OAuthSave
@onready var test_response: Label = %TestResponse
var has_changes: bool:
set(val):
has_changes = val
changed.emit.call_deferred()
o_auth_save.text = o_auth_save.text.trim_suffix(" (unsaved changes)")
if has_changes: o_auth_save.text += " (unsaved changes)"
signal changed
func _ready() -> void:
authorization_explaination.meta_clicked.connect(_on_link_clicked)
redirect_url.text_changed.connect(_on_text_changed)
client_id.text_changed.connect(_on_text_changed)
client_secret.text_changed.connect(_on_text_changed)
to_documentation.pressed.connect(_on_to_documentation_pressed)
oauth_setting_file_select.file_selected.connect(_on_file_changed)
token_file_select.file_selected.connect(_on_file_changed)
o_auth_save.pressed.connect(_on_save)
_load_oauth_setting()
func _load_oauth_setting() -> void:
var setting: OAuthSetting = TwitchEditorSettings.editor_oauth_setting
client_id.text = setting.client_id
client_secret.text = setting.get_client_secret()
redirect_url.text = setting.redirect_url
func _on_link_clicked(link: Variant) -> void:
OS.shell_open(link)
func _on_text_changed(val: String) -> void:
reset_response_message()
var setting: OAuthSetting = TwitchEditorSettings.editor_oauth_setting
setting.client_id = client_id.text
setting.set_client_secret(client_secret.text)
setting.redirect_url = redirect_url.text
has_changes = true
func reset_response_message() -> void:
test_response.text = ""
func _on_file_changed() -> void:
has_changes = true
func is_auth_existing() -> bool:
return is_instance_valid(TwitchEditorSettings.editor_oauth_setting)
func _on_save() -> void:
TwitchEditorSettings.save_editor_oauth_setting()
TwitchEditorSettings.save_editor_oauth_token()
var setting_path = oauth_setting_file_select.path
var setting = TwitchEditorSettings.editor_oauth_setting.duplicate(true)
setting.take_over_path(setting_path)
ResourceSaver.save(setting, setting_path)
TwitchEditorSettings.game_oauth_setting = setting
var token_path = token_file_select.path
var token = TwitchEditorSettings.editor_oauth_token.duplicate()
token.take_over_path(token_path)
ResourceSaver.save(token, token_path)
TwitchEditorSettings.game_oauth_token = token
TwitchTweens.flash(o_auth_save, Color.GREEN)
ProjectSettings.save()
has_changes = false
func _on_to_documentation_pressed() -> void:
OS.shell_open("https://dev.twitch.tv/docs/authentication/")

View file

@ -0,0 +1 @@
uid://dxql15j5ornlc

View file

@ -0,0 +1,182 @@
[gd_scene load_steps=6 format=3 uid="uid://dm6jvnuikxtei"]
[ext_resource type="Script" uid="uid://dxql15j5ornlc" path="res://addons/twitcher/editor/setup/page_authorization.gd" id="1_78hk7"]
[ext_resource type="LabelSettings" uid="uid://bnsxy6gcm8q11" path="res://addons/twitcher/assets/title_label_settings.tres" id="2_owlil"]
[ext_resource type="PackedScene" uid="uid://b7smp156mdns6" path="res://addons/twitcher/editor/setup/file_select.tscn" id="3_dbhpx"]
[ext_resource type="Script" uid="uid://ddugotjvuahex" path="res://addons/twitcher/editor/setup/focus_child_show.gd" id="3_o4tdm"]
[ext_resource type="PackedScene" uid="uid://bfksyo3klyvdn" path="res://addons/twitcher/editor/setup/test_credentials.tscn" id="5_dbhpx"]
[node name="Authorization" type="MarginContainer"]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme_override_constants/margin_left = 10
theme_override_constants/margin_top = 10
theme_override_constants/margin_right = 10
theme_override_constants/margin_bottom = 10
script = ExtResource("1_78hk7")
metadata/_tab_index = 1
[node name="Layout" type="VBoxContainer" parent="."]
layout_mode = 2
size_flags_vertical = 3
[node name="Title" type="Label" parent="Layout"]
layout_mode = 2
text = "Step 2: Authorization"
label_settings = ExtResource("2_owlil")
horizontal_alignment = 1
[node name="ToDocumentation" type="Button" parent="Layout/Title"]
unique_name_in_owner = true
layout_mode = 1
anchors_preset = 6
anchor_left = 1.0
anchor_top = 0.5
anchor_right = 1.0
anchor_bottom = 0.5
offset_left = -52.0
offset_top = -15.5
offset_bottom = 15.5
grow_horizontal = 0
grow_vertical = 2
text = "DOCS"
metadata/_edit_use_anchors_ = true
[node name="PanelContainer" type="PanelContainer" parent="Layout"]
layout_mode = 2
[node name="MarginContainer" type="MarginContainer" parent="Layout/PanelContainer"]
layout_mode = 2
theme_override_constants/margin_left = 10
theme_override_constants/margin_top = 10
theme_override_constants/margin_right = 10
theme_override_constants/margin_bottom = 10
[node name="VBoxContainer" type="VBoxContainer" parent="Layout/PanelContainer/MarginContainer"]
layout_mode = 2
[node name="AuthorizationTitle" type="Label" parent="Layout/PanelContainer/MarginContainer/VBoxContainer"]
layout_mode = 2
text = "General"
label_settings = ExtResource("2_owlil")
horizontal_alignment = 1
[node name="AuthExplain" type="RichTextLabel" parent="Layout/PanelContainer/MarginContainer/VBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
theme_override_font_sizes/normal_font_size = 12
bbcode_enabled = true
text = "The credentials that are used by the editor and game to connect to Twitch. You can request your credentials [url=https://dev.twitch.tv/console/apps/create]Twitch Dev Console[/url] for more informations see [url=https://twitcher.kani.dev/#authorization]Documentation[/url]"
fit_content = true
vertical_alignment = 1
[node name="AuthorizationOptions" type="GridContainer" parent="Layout/PanelContainer/MarginContainer/VBoxContainer" node_paths=PackedStringArray("show_elements")]
layout_mode = 2
size_flags_vertical = 3
size_flags_stretch_ratio = 3.0
columns = 2
script = ExtResource("3_o4tdm")
show_elements = [NodePath("../AuthExplain")]
[node name="ClientIdLabel" type="Label" parent="Layout/PanelContainer/MarginContainer/VBoxContainer/AuthorizationOptions"]
layout_mode = 2
text = "Client ID:"
[node name="ClientId" type="LineEdit" parent="Layout/PanelContainer/MarginContainer/VBoxContainer/AuthorizationOptions"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
text = "1ae0idgxbvn6vi97ls7d89cyd919oq"
[node name="ClientSecretLabel" type="Label" parent="Layout/PanelContainer/MarginContainer/VBoxContainer/AuthorizationOptions"]
layout_mode = 2
text = "Client Secret:"
[node name="ClientSecret" type="LineEdit" parent="Layout/PanelContainer/MarginContainer/VBoxContainer/AuthorizationOptions"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
text = "k22x037lmmrmkmwvy79xr19qfy993g"
secret = true
[node name="RedirectURLLabel" type="Label" parent="Layout/PanelContainer/MarginContainer/VBoxContainer/AuthorizationOptions"]
layout_mode = 2
text = "Redirect URL:"
[node name="RedirectURL" type="LineEdit" parent="Layout/PanelContainer/MarginContainer/VBoxContainer/AuthorizationOptions"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
tooltip_text = "Location that Twitch is calling after the login process. Take care that this one is the same that you used during creation of the application within the twitch dev console."
text = "http://localhost:7170"
[node name="HSeparator2" type="HSeparator" parent="Layout/PanelContainer/MarginContainer/VBoxContainer"]
layout_mode = 2
theme_override_constants/separation = 10
[node name="GameSettingTitle" type="Label" parent="Layout/PanelContainer/MarginContainer/VBoxContainer"]
layout_mode = 2
text = "Game"
label_settings = ExtResource("2_owlil")
horizontal_alignment = 1
[node name="GameExplain" type="RichTextLabel" parent="Layout/PanelContainer/MarginContainer/VBoxContainer"]
layout_mode = 2
theme_override_font_sizes/normal_font_size = 12
bbcode_enabled = true
text = "These settings are needed for the game to connect with Twitch."
fit_content = true
vertical_alignment = 1
[node name="GameSetting" type="GridContainer" parent="Layout/PanelContainer/MarginContainer/VBoxContainer" node_paths=PackedStringArray("show_elements")]
layout_mode = 2
columns = 2
script = ExtResource("3_o4tdm")
show_elements = [NodePath("../GameExplain")]
[node name="OauthSettingLabel" type="Label" parent="Layout/PanelContainer/MarginContainer/VBoxContainer/GameSetting"]
layout_mode = 2
text = "Auth File Path:"
[node name="OauthSettingFileSelect" parent="Layout/PanelContainer/MarginContainer/VBoxContainer/GameSetting" instance=ExtResource("3_dbhpx")]
unique_name_in_owner = true
layout_mode = 2
default_path = "res://addons/twitcher/twitch_oauth_setting.tres"
path = "res://addons/twitcher/twitch_oauth_setting.tres"
filters = PackedStringArray("*.tres", "*.res")
[node name="TokenLabel" type="Label" parent="Layout/PanelContainer/MarginContainer/VBoxContainer/GameSetting"]
layout_mode = 2
text = "Token File Path:"
[node name="TokenFileSelect" parent="Layout/PanelContainer/MarginContainer/VBoxContainer/GameSetting" instance=ExtResource("3_dbhpx")]
unique_name_in_owner = true
layout_mode = 2
default_path = "res://addons/twitcher/default_oauth_token.tres"
path = "res://addons/twitcher/default_oauth_token.tres"
filters = PackedStringArray("*.tres", "*.res")
[node name="HSeparator" type="HSeparator" parent="Layout/PanelContainer/MarginContainer/VBoxContainer"]
layout_mode = 2
theme_override_constants/separation = 10
[node name="HBoxContainer" type="HBoxContainer" parent="Layout"]
layout_mode = 2
[node name="TestCredentials" parent="Layout/HBoxContainer" node_paths=PackedStringArray("test_response") instance=ExtResource("5_dbhpx")]
layout_mode = 2
size_flags_horizontal = 3
test_response = NodePath("../../TestResponse")
[node name="OAuthSave" type="Button" parent="Layout/HBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
text = "Save"
[node name="TestResponse" type="Label" parent="Layout"]
unique_name_in_owner = true
layout_mode = 2

View file

@ -0,0 +1,139 @@
@tool
extends MarginContainer
enum UseCase {
Overlay, Game, Other
}
const PRESET_GAME_SCOPES = preload("res://addons/twitcher/auth/preset_game_scopes.tres")
const PRESET_OVERLAY_SCOPES = preload("res://addons/twitcher/auth/preset_overlay_scopes.tres")
const TwitchScopeProperty = preload("res://addons/twitcher/editor/inspector/twitch_scope_property.gd")
const TwitchEditorSettings = preload("res://addons/twitcher/editor/twitch_editor_settings.gd")
const TwitchTweens = preload("res://addons/twitcher/editor/twitch_tweens.gd")
@export var choose_button_group: ButtonGroup
@onready var overlay: CheckBox = %Overlay
@onready var game: CheckBox = %Game
@onready var something_else: CheckBox = %SomethingElse
@onready var scope_list: RichTextLabel = %ScopeList
@onready var to_documentation: Button = %ToDocumentation
@onready var scope_file_select: FileSelect = %ScopeFileSelect
@onready var scopes_container: HBoxContainer = %Scopes
@onready var advanced_edit: CheckButton = %AdvancedEdit
@onready var other_scope_options: PanelContainer = %OtherScopeOptions
@onready var extended_scope_info: PanelContainer = %ExtendedScopeInfo
@onready var save: Button = %Save
var other_scope_property: TwitchScopeProperty = TwitchScopeProperty.new()
var scopes: TwitchOAuthScopes: set = update_scopes
var has_changes: bool:
set(val):
has_changes = val
changed.emit()
save.text = save.text.trim_suffix(" (unsaved changes)")
if has_changes: save.text += " (unsaved changes)"
signal changed
signal use_case_changed(use_case: UseCase)
func _ready() -> void:
to_documentation.pressed.connect(_on_to_documentation_pressed)
choose_button_group.pressed.connect(_on_choose)
scope_file_select.file_selected.connect(_on_scope_file_selected)
save.pressed.connect(_on_save_pressed)
other_scope_property.scope_selected.connect(_on_scope_info)
advanced_edit.toggled.connect(_on_toggle_advanced_edit)
scopes_container.hide()
extended_scope_info.hide()
extended_scope_info.add_child(other_scope_property)
# Reset radio buttons cause it's a tool script and the radio button stay and won't throw another signal otherwise
game.set_pressed_no_signal(false)
overlay.set_pressed_no_signal(false)
something_else.set_pressed_no_signal(false)
scope_file_select.path = TwitchEditorSettings.get_scope_path()
match TwitchEditorSettings.project_preset:
TwitchEditorSettings.PRESET_GAME:
game.button_pressed = true
TwitchEditorSettings.PRESET_OVERLAY:
overlay.button_pressed = true
TwitchEditorSettings.PRESET_OTHER:
something_else.button_pressed = true
# Needs to be resetted cause the radio reset will change the has_changges to true
has_changes = false
func _on_scope_file_selected(path: String) -> void:
has_changes = true
if FileAccess.file_exists(path):
var resource = load(path)
if resource is OAuthScopes: scopes = resource
else: OS.alert("The selected scope is not a scope file, it will be overwritten!")
func _on_toggle_advanced_edit(toggled_on: bool) -> void:
extended_scope_info.visible = toggled_on
func _on_choose(button: BaseButton) -> void:
match button:
overlay:
use_case_changed.emit(UseCase.Overlay)
scopes = PRESET_OVERLAY_SCOPES.duplicate(true)
advanced_edit.button_pressed = false
TwitchEditorSettings.project_preset = TwitchEditorSettings.PRESET_OVERLAY
game:
use_case_changed.emit(UseCase.Game)
scopes = PRESET_GAME_SCOPES.duplicate(true)
advanced_edit.button_pressed = false
TwitchEditorSettings.project_preset = TwitchEditorSettings.PRESET_GAME
something_else:
use_case_changed.emit(UseCase.Other)
scopes = TwitchOAuthScopes.new()
advanced_edit.button_pressed = true
TwitchEditorSettings.project_preset = TwitchEditorSettings.PRESET_OTHER
_show_selected_scopes()
has_changes = true
func _on_scope_info(scope: TwitchScope.Definition) -> void:
_show_selected_scopes()
func _on_save_pressed() -> void:
var s_path = scope_file_select.path
scopes.take_over_path(s_path)
ResourceSaver.save(scopes, s_path)
TwitchEditorSettings.set_scope_path(s_path)
TwitchTweens.flash(save, Color.GREEN)
has_changes = false
func _show_selected_scopes() -> void:
scopes_container.show()
if scopes.used_scopes.is_empty():
scope_list.text = "[i]No scopes selected yet[/i]"
return
var scope_description: String = ""
for scope_name: StringName in scopes.used_scopes:
var scope: TwitchScope.Definition = TwitchScope.SCOPE_MAP[scope_name]
scope_description += "[b]%s[/b] - %s\n\n" % [scope.value, scope.description]
scope_list.text = scope_description
func _on_to_documentation_pressed() -> void:
OS.shell_open("https://dev.twitch.tv/docs/authentication/scopes/")
func update_scopes(val: TwitchOAuthScopes) -> void:
scopes = val
other_scope_property.set_object_and_property(scopes, "")
other_scope_property.update_property()
_show_selected_scopes()

View file

@ -0,0 +1 @@
uid://cjni881olloyf

View file

@ -0,0 +1,162 @@
[gd_scene load_steps=5 format=3 uid="uid://c7pja1druikbn"]
[ext_resource type="Script" uid="uid://cjni881olloyf" path="res://addons/twitcher/editor/setup/page_use_case.gd" id="1_2qemh"]
[ext_resource type="LabelSettings" uid="uid://bnsxy6gcm8q11" path="res://addons/twitcher/assets/title_label_settings.tres" id="1_r6qea"]
[ext_resource type="ButtonGroup" uid="uid://bkocyfdqvh4t" path="res://addons/twitcher/editor/setup/use_case_button_group.tres" id="1_vqr26"]
[ext_resource type="PackedScene" uid="uid://b7smp156mdns6" path="res://addons/twitcher/editor/setup/file_select.tscn" id="4_c6y6e"]
[node name="UseCase" type="MarginContainer"]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme_override_constants/margin_left = 10
theme_override_constants/margin_top = 10
theme_override_constants/margin_right = 10
theme_override_constants/margin_bottom = 10
script = ExtResource("1_2qemh")
choose_button_group = ExtResource("1_vqr26")
metadata/_tab_index = 0
[node name="ScrollContainer" type="ScrollContainer" parent="."]
layout_mode = 2
[node name="SelectionContainer" type="VBoxContainer" parent="ScrollContainer"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="Title" type="Label" parent="ScrollContainer/SelectionContainer"]
layout_mode = 2
text = "Step 1: Use Case"
label_settings = ExtResource("1_r6qea")
horizontal_alignment = 1
[node name="ToDocumentation" type="Button" parent="ScrollContainer/SelectionContainer/Title"]
unique_name_in_owner = true
layout_mode = 1
anchors_preset = 6
anchor_left = 1.0
anchor_top = 0.5
anchor_right = 1.0
anchor_bottom = 0.5
offset_left = -52.0
offset_top = -15.5
offset_bottom = 15.5
grow_horizontal = 0
grow_vertical = 2
text = "DOCS"
metadata/_edit_use_anchors_ = true
[node name="Explaination" type="RichTextLabel" parent="ScrollContainer/SelectionContainer"]
layout_mode = 2
theme_override_font_sizes/normal_font_size = 12
bbcode_enabled = true
text = "To help you with scopes and authentication please select your use case."
fit_content = true
vertical_alignment = 1
[node name="ChooseLabel" type="Label" parent="ScrollContainer/SelectionContainer"]
layout_mode = 2
text = "What do you want to make:"
[node name="Overlay" type="CheckBox" parent="ScrollContainer/SelectionContainer"]
unique_name_in_owner = true
layout_mode = 2
button_pressed = true
button_group = ExtResource("1_vqr26")
text = "Overlay"
[node name="Game" type="CheckBox" parent="ScrollContainer/SelectionContainer"]
unique_name_in_owner = true
layout_mode = 2
button_group = ExtResource("1_vqr26")
text = "Game"
[node name="SomethingElse" type="CheckBox" parent="ScrollContainer/SelectionContainer"]
unique_name_in_owner = true
layout_mode = 2
button_group = ExtResource("1_vqr26")
text = "I know what I do / Something else"
[node name="HSeparator" type="HSeparator" parent="ScrollContainer/SelectionContainer"]
layout_mode = 2
theme_override_constants/separation = 20
[node name="Scopes" type="HBoxContainer" parent="ScrollContainer/SelectionContainer"]
unique_name_in_owner = true
layout_mode = 2
size_flags_vertical = 3
[node name="ExtendedScopeInfo" type="PanelContainer" parent="ScrollContainer/SelectionContainer/Scopes"]
unique_name_in_owner = true
visible = false
layout_mode = 2
[node name="OtherScopeOptions" type="PanelContainer" parent="ScrollContainer/SelectionContainer/Scopes"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
[node name="VBoxContainer" type="VBoxContainer" parent="ScrollContainer/SelectionContainer/Scopes/OtherScopeOptions"]
layout_mode = 2
[node name="ScopeListLabel" type="Label" parent="ScrollContainer/SelectionContainer/Scopes/OtherScopeOptions/VBoxContainer"]
layout_mode = 2
text = "Scopes:"
label_settings = ExtResource("1_r6qea")
[node name="AdvancedEdit" type="CheckButton" parent="ScrollContainer/SelectionContainer/Scopes/OtherScopeOptions/VBoxContainer/ScopeListLabel"]
unique_name_in_owner = true
layout_mode = 1
anchors_preset = 6
anchor_left = 1.0
anchor_top = 0.5
anchor_right = 1.0
anchor_bottom = 0.5
offset_left = -168.0
offset_top = -15.5
offset_bottom = 15.5
grow_horizontal = 0
grow_vertical = 2
text = "Edit (Advanced)"
[node name="ScopeList" type="RichTextLabel" parent="ScrollContainer/SelectionContainer/Scopes/OtherScopeOptions/VBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
bbcode_enabled = true
text = "[b]user:read:chat[/b] - Receive chatroom messages and informational notifications relating to a channels chatroom.
[b]user:write:chat[/b] - Send chat messages to a chatroom.
[b]moderator:read:followers[/b] - Read the followers of a broadcaster.
[b]bits:read[/b] - View Bits information for a channel.
[b]channel:read:redemptions[/b] - View Channel Points custom rewards and their redemptions on a channel.
[b]channel:manage:redemptions[/b] - Manage Channel Points custom rewards and their redemptions on a channel.
"
fit_content = true
[node name="HSeparator" type="HSeparator" parent="ScrollContainer/SelectionContainer/Scopes/OtherScopeOptions/VBoxContainer"]
layout_mode = 2
theme_override_constants/separation = 10
[node name="ScopeFileLabel" type="Label" parent="ScrollContainer/SelectionContainer/Scopes/OtherScopeOptions/VBoxContainer"]
layout_mode = 2
text = "Save selected scopes:"
[node name="ScopeFileSelect" parent="ScrollContainer/SelectionContainer/Scopes/OtherScopeOptions/VBoxContainer" instance=ExtResource("4_c6y6e")]
unique_name_in_owner = true
layout_mode = 2
default_path = "res://twitch_scopes.tres"
path = "res://twitch_scopes.tres"
filters = PackedStringArray("*.tres", "*.res")
[node name="Save" type="Button" parent="ScrollContainer/SelectionContainer/Scopes/OtherScopeOptions/VBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
text = "Save Scopes"

View file

@ -0,0 +1,74 @@
@tool
extends MarginContainer
const script_path: String = "res://addons/twitcher/twitch_service.tscn"
const autoload_name: String = "Twitch"
const setting_key: String = "autoload/%s" % autoload_name
const setting_value: String = "*" + script_path
@onready var autoload_install: Button = %AutoloadInstall
@onready var autoload_info: Label = %AutoloadInfo
@onready var autoload_description: RichTextLabel = %AutoloadDescription
func _ready() -> void:
autoload_description.text = autoload_description.text.format({
"autoload_name": autoload_name
})
autoload_install.pressed.connect(_on_install_autoload_pressed)
_update_install_autoload()
func _update_install_autoload() -> void:
if ProjectSettings.has_setting(setting_key):
autoload_install.text = "Uninstall Autoload"
else:
autoload_install.text = "Install Autoload"
func _on_install_autoload_pressed() -> void:
if ProjectSettings.has_setting(setting_key):
_uninstall_autoload()
else:
_install_autload()
_update_install_autoload()
func _uninstall_autoload() -> void:
ProjectSettings.clear(setting_key)
var err = ProjectSettings.save()
if err == OK:
autoload_info.text = "Autoload '%s' uninstalled successfully!\nYou might need to reload the current project for changes to fully apply everywhere in the editor immediately." % autoload_name
print("Successfully removed autoload: %s" % autoload_name)
else:
autoload_info.text = "Failed to save project settings.\nError code: %s" % error_string(err)
printerr("Failed to save project settings! Error: ", error_string(err))
func _install_autload() -> void:
if not FileAccess.file_exists(script_path):
OS.alert("The TwitchService file does not exist at:\n" + script_path, "Error")
return
var setting_key: String = "autoload/%s" % autoload_name
var setting_value: String = "*" + script_path
if ProjectSettings.has_setting(setting_key):
var existing_value = ProjectSettings.get_setting(setting_key)
if existing_value == setting_value:
autoload_info.text = "Autoload '%s' with the same path is already installed." % autoload_name
return
else:
autoload_info.text = "Autoload '%s' already exists but points to a different path (%s)." % [autoload_name, existing_value]
return
ProjectSettings.set_setting(setting_key, setting_value)
var err = ProjectSettings.save()
if err == OK:
autoload_info.text = "Autoload '%s' installed successfully!\nYou might need to reload the current project for changes to fully apply everywhere in the editor immediately." % autoload_name
print("Successfully added autoload: %s -> %s" % [autoload_name, script_path])
else:
autoload_info.text = "Failed to save project settings.\nError code: %s" % error_string(err)
printerr("Failed to save project settings! Error: ", error_string(err))

View file

@ -0,0 +1 @@
uid://dw37fk6mah3jk

View file

@ -0,0 +1,96 @@
[gd_scene load_steps=4 format=3 uid="uid://d4l63q706mkhw"]
[ext_resource type="Script" uid="uid://dw37fk6mah3jk" path="res://addons/twitcher/editor/setup/page_utilities.gd" id="1_sexj5"]
[ext_resource type="LabelSettings" uid="uid://bnsxy6gcm8q11" path="res://addons/twitcher/assets/title_label_settings.tres" id="1_yi5sa"]
[ext_resource type="LabelSettings" uid="uid://cng881nsuud80" path="res://addons/twitcher/assets/warning_label_settings.tres" id="3_fj7h2"]
[node name="Utilities" type="MarginContainer"]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme_override_constants/margin_left = 10
theme_override_constants/margin_top = 10
theme_override_constants/margin_right = 10
theme_override_constants/margin_bottom = 10
script = ExtResource("1_sexj5")
metadata/_tab_index = 2
[node name="ScrollContainer" type="ScrollContainer" parent="."]
layout_mode = 2
[node name="Container" type="VBoxContainer" parent="ScrollContainer"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="Header" type="Label" parent="ScrollContainer/Container"]
layout_mode = 2
text = "Utilities"
label_settings = ExtResource("1_yi5sa")
horizontal_alignment = 1
[node name="HSeparator" type="HSeparator" parent="ScrollContainer/Container"]
layout_mode = 2
theme_override_constants/separation = 20
[node name="AutoloadDescription" type="RichTextLabel" parent="ScrollContainer/Container"]
unique_name_in_owner = true
layout_mode = 2
bbcode_enabled = true
text = "For basic use cases an autoload is the easiest way to setup Twitcher. Adds an autoload named [b]{autoload_name}[/b]
[i]Advantage:[/i]
+ easy access everywhere
[i]Disadvantage:[/i]
- It will be always initialized, even when you want to test a small scene standalone Tiwtcher will do authorization, subscribing to eventsub etc.
[b]Alternative:[/b]
The first nodes in the scene tree of every major nodes like TwitchAPI, TwitchEventsub, TwitchChat, TwitchMediaLoader and TwitchService are available as Singleton via their instance variable.
Example
[code]TwitchAPI.instance.get_users(...)[/code]"
fit_content = true
[node name="AutoloadInstall" type="Button" parent="ScrollContainer/Container"]
unique_name_in_owner = true
layout_mode = 2
text = "Uninstall Autoload"
[node name="AutoloadInfo" type="Label" parent="ScrollContainer/Container"]
unique_name_in_owner = true
custom_minimum_size = Vector2(0, 100)
layout_mode = 2
label_settings = ExtResource("3_fj7h2")
autowrap_mode = 3
[node name="HSeparator2" type="HSeparator" parent="ScrollContainer/Container"]
layout_mode = 2
theme_override_constants/separation = 20
[node name="AutoloadDescription2" type="RichTextLabel" parent="ScrollContainer/Container"]
visible = false
layout_mode = 2
bbcode_enabled = true
text = "For a game with Twitch integration you probably want to read the chat from the streamer to act on the commands. For that the streamer need to authorize your application. For the authorization flow use the [url=https://dev.twitch.tv/docs/authentication/getting-tokens-oauth/#device-code-grant-flow]Device Code Flow[/url] so that you don't share the client secret in the code.
[b]Hints:[/b]
- Take care that you take the least amount of scopes for the game otherwise you could scare the streamer away.
[i]Advantage:[/i]
+ easy access everywhere
[i]Disadvantage:[/i]
- It will be always initialized, even when you want to test a small scene standalone Tiwtcher will do authorization, subscribing to eventsub etc.
[b]Alternative:[/b]
The first nodes in the scene tree of every major nodes like TwitchAPI, TwitchEventsub, TwitchChat, TwitchMediaLoader and TwitchService are available as Singleton via their instance variable.
Example
[code]TwitchAPI.instance.get_users(...)[/code]"
fit_content = true
[node name="Button" type="Button" parent="ScrollContainer/Container"]
visible = false
layout_mode = 2
text = "Add Game Example"

View file

@ -0,0 +1,58 @@
@tool
extends Window
const PageUseCase = preload("res://addons/twitcher/editor/setup/page_use_case.gd")
const TwitchEditorSettings = preload("res://addons/twitcher/editor/twitch_editor_settings.gd")
const PageAuthorization = preload("res://addons/twitcher/editor/setup/page_authorization.gd")
#Setup
#- Check for Authorization Stuff
#-- Client Credentials
#-- Editor Token
#-- Scopes
#- Auth Button
#- Create Base Node Structure
@onready var authorization: PageAuthorization = %Authorization
@onready var use_case: PageUseCase = %UseCase as PageUseCase
@onready var close: Button = %Close
@onready var startup_check: CheckButton = %StartupCheck
func _ready():
close_requested.connect(_on_close)
close.pressed.connect(_on_close)
startup_check.toggled.connect(_on_toggle_startup_check)
startup_check.button_pressed = TwitchEditorSettings.show_setup_on_startup
use_case.changed.connect(_on_changed)
authorization.changed.connect(_on_changed)
pass
func _on_changed() -> void:
close.text = close.text.trim_suffix(" (unsaved changes)")
if use_case.has_changes || authorization.has_changes:
close.text = close.text + " (unsaved changes)"
func _on_toggle_startup_check(toggle_on: bool) -> void:
TwitchEditorSettings.show_setup_on_startup = toggle_on
ProjectSettings.save()
func _input(event: InputEvent) -> void:
if event is InputEventKey:
var key_event: InputEventKey = event as InputEventKey
if key_event.keycode == KEY_ESCAPE:
_on_close()
func _on_close() -> void:
if use_case.has_changes || authorization.has_changes:
var popup = ConfirmationDialog.new()
popup.dialog_text = "You have unsaved changes! Are you sure to close the setup?"
popup.confirmed.connect(queue_free)
add_child(popup)
popup.popup_centered()
else:
queue_free()

View file

@ -0,0 +1 @@
uid://bbguje3a0cl8t

View file

@ -0,0 +1,51 @@
[gd_scene load_steps=6 format=3 uid="uid://wu1fprbhr62"]
[ext_resource type="Script" uid="uid://bbguje3a0cl8t" path="res://addons/twitcher/editor/setup/setup.gd" id="1_o5snq"]
[ext_resource type="PackedScene" uid="uid://c7pja1druikbn" path="res://addons/twitcher/editor/setup/page_use_case.tscn" id="2_6678v"]
[ext_resource type="PackedScene" uid="uid://dm6jvnuikxtei" path="res://addons/twitcher/editor/setup/page_authorization.tscn" id="3_qcivh"]
[ext_resource type="PackedScene" uid="uid://d4l63q706mkhw" path="res://addons/twitcher/editor/setup/page_utilities.tscn" id="4_qcivh"]
[sub_resource type="ButtonGroup" id="ButtonGroup_6678v"]
[node name="SetupWindow" type="Window"]
title = "Setup Twitcher"
initial_position = 2
size = Vector2i(800, 800)
script = ExtResource("1_o5snq")
[node name="VBoxContainer" type="VBoxContainer" parent="."]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="Setup" type="TabContainer" parent="VBoxContainer"]
layout_mode = 2
size_flags_vertical = 3
current_tab = 0
[node name="UseCase" parent="VBoxContainer/Setup" instance=ExtResource("2_6678v")]
unique_name_in_owner = true
layout_mode = 2
choose_button_group = SubResource("ButtonGroup_6678v")
[node name="Authorization" parent="VBoxContainer/Setup" instance=ExtResource("3_qcivh")]
unique_name_in_owner = true
visible = false
layout_mode = 2
[node name="Utilities" parent="VBoxContainer/Setup" instance=ExtResource("4_qcivh")]
visible = false
layout_mode = 2
[node name="StartupCheck" type="CheckButton" parent="VBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
button_pressed = true
text = "Show on startup (you can open it via 'Project/Tools/Twitcher Setup')"
[node name="Close" type="Button" parent="VBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
text = "Close"

View file

@ -0,0 +1,58 @@
@tool
extends Button
const TwitchTweens = preload("res://addons/twitcher/editor/twitch_tweens.gd")
const TwitchEditorSettings = preload("res://addons/twitcher/editor/twitch_editor_settings.gd")
@export var oauth_setting: OAuthSetting: set = update_oauth_setting
@export var oauth_token: OAuthToken: set = update_oauth_token
@export var test_response: Label
@onready var twitch_auth: TwitchAuth = %TwitchAuth
signal authorized
func _ready() -> void:
pressed.connect(_pressed)
oauth_setting = TwitchEditorSettings.editor_oauth_setting
oauth_token = TwitchEditorSettings.editor_oauth_token
func _notification(what: int) -> void:
if what == NOTIFICATION_PREDELETE:
oauth_token.authorized.disconnect(_on_authorized)
func _pressed() -> void:
TwitchTweens.loading(self)
await twitch_auth.authorize()
if twitch_auth.token.is_token_valid():
if test_response:
test_response.text = "Credentials are valid!"
test_response.add_theme_color_override(&"font_color", Color.GREEN)
TwitchTweens.flash(self, Color.GREEN)
authorized.emit()
else:
if test_response:
test_response.text = "Credentials are invalid!"
test_response.add_theme_color_override(&"font_color", Color.RED)
TwitchTweens.flash(self, Color.RED)
func update_oauth_token(new_oauth_token: OAuthToken) -> void:
oauth_token = new_oauth_token
oauth_token.authorized.connect(_on_authorized)
if is_inside_tree():
twitch_auth.token = new_oauth_token
func update_oauth_setting(new_oauth_setting: OAuthSetting) -> void:
oauth_setting = new_oauth_setting
disabled = not oauth_setting.is_valid()
if is_inside_tree():
twitch_auth.oauth_setting = oauth_setting
func _on_authorized() -> void:
if is_inside_tree(): authorized.emit()

View file

@ -0,0 +1 @@
uid://13afcys4swos

View file

@ -0,0 +1,37 @@
[gd_scene load_steps=9 format=3 uid="uid://bfksyo3klyvdn"]
[ext_resource type="Script" uid="uid://13afcys4swos" path="res://addons/twitcher/editor/setup/test_credentials.gd" id="1_j2v3o"]
[ext_resource type="Script" uid="uid://iv0mgv0lu8b0" path="res://addons/twitcher/auth/twitch_auth.gd" id="1_kojf4"]
[ext_resource type="Script" uid="uid://b3n3et8mebjcc" path="res://addons/twitcher/auth/twitch_oauth_scopes.gd" id="3_2rqpn"]
[ext_resource type="Resource" path="user://editor_oauth_setting.tres" id="3_kojf4"]
[ext_resource type="Resource" path="user://editor_oauth_token.tres" id="4_j2v3o"]
[ext_resource type="Script" uid="uid://bf0wi70haua35" path="res://addons/twitcher/lib/oOuch/oauth.gd" id="6_hkawa"]
[ext_resource type="Script" uid="uid://blnbogtrshw4r" path="res://addons/twitcher/auth/twitch_token_handler.gd" id="7_v5ghs"]
[sub_resource type="Resource" id="Resource_1cpcx"]
script = ExtResource("3_2rqpn")
used_scopes = Array[StringName]([])
metadata/_custom_type_script = "uid://b3n3et8mebjcc"
[node name="TestCredentials" type="Button"]
text = "Test Credentials"
script = ExtResource("1_j2v3o")
[node name="TwitchAuth" type="Node" parent="."]
unique_name_in_owner = true
script = ExtResource("1_kojf4")
oauth_setting = ExtResource("3_kojf4")
token = ExtResource("4_j2v3o")
scopes = SubResource("Resource_1cpcx")
metadata/_custom_type_script = "uid://iv0mgv0lu8b0"
[node name="OAuth" type="Node" parent="TwitchAuth" node_paths=PackedStringArray("token_handler")]
script = ExtResource("6_hkawa")
oauth_setting = ExtResource("3_kojf4")
scopes = SubResource("Resource_1cpcx")
token_handler = NodePath("../TokenHandler")
[node name="TokenHandler" type="Node" parent="TwitchAuth"]
script = ExtResource("7_v5ghs")
oauth_setting = ExtResource("3_kojf4")
token = ExtResource("4_j2v3o")

View file

@ -0,0 +1,3 @@
[gd_resource type="ButtonGroup" format=3 uid="uid://bkocyfdqvh4t"]
[resource]