Compare commits

..

24 commits

Author SHA1 Message Date
2e2f8b47c0 Updated SettingsPanel
Updated to use new UI controls.
2026-03-08 13:30:17 -05:00
cffe7c9ef0 Created Users Panel
This is the Tab in the main Settings dialog to show all interaction with
saved users in the database.
2026-03-08 13:29:54 -05:00
f6c2def426 Created tab for TwitchUserInfo
Displays the TwitchUserInfo to handle showing the saved user's twitch
information.
2026-03-08 13:29:21 -05:00
9328ba2356 Created UserLive Panel for Live tab
Created panel to show user live information, for when the user is live.
2026-03-08 13:28:00 -05:00
d1f7bccd15 Created UserPromo control
This control handles local settings for user, such as marking a user as
a streamer, etc etc.
2026-03-08 13:27:27 -05:00
a138ef7a0b Created SearchTwitchUser control
This control handles searching for a user on Twitch, and allows saving
them to the database.
2026-03-08 13:26:36 -05:00
6633b239cb Created UserList control
This is the control that handles showing the list of saved users, as
well as filtering through such users.
2026-03-08 13:26:01 -05:00
f0658fb87b Created UserEntry control
Created the control that will be displayed in the UserList in the
settings panel.
2026-03-08 13:25:35 -05:00
c803f31e6c Created TwitchUserInfo control
Created user control to handle showing TwitchUser information.
2026-03-08 13:24:17 -05:00
59bb3d323d Created LoadingSimple Control
Created a simple Loading control to cover other UI controls, when
something is loading.
2026-03-08 13:23:11 -05:00
c0825b4c28 Updated FloatingMenu
Removed commented Globals.disable_mouse_passthrough().
Updated Settings Window size.
2026-03-08 13:22:07 -05:00
35a70d4e79 Created new theme
Created new theme for use in UI controls.  (May be renamed to
settings_theme.tres, and used for Settings dialog only.)
2026-03-08 13:21:27 -05:00
520f699b8b Added new Images
Added image for Loading, and Twitch default User Profile pic.
2026-03-08 13:20:49 -05:00
a881c0e52b Updated Project
Removed Neon-Wave custom theme.
2026-03-08 13:20:18 -05:00
5de94e47e9 Updated Migrations
Reset all migrations, as we don't have any important data in the
database.
2026-03-08 13:20:04 -05:00
55faa9e6d3 Updated Globals
Added signals for when we are updating live streamers, and when they
have been updated.
Added setter for twitcher, adding a streamer_token_validated signal
connect to handle setting up live streamers fetch.
2026-03-08 13:19:39 -05:00
c0f0e2f513 Updated ChatManager
Updated creation of new chatter data.
Added check to see if first_seen is -1, and if so, update it.  (This is
for uesrs that are manually added)
2026-03-08 13:18:40 -05:00
92551b289b Updated AppContext
Added get_known_streamers() helper function.
2026-03-08 13:17:52 -05:00
48fe26459f Updated TwitcherExtended
Added Signals for streamer_token_validated and chatbot_token_validated.
Added bool to prevent multiple runs of Getting live streams.
Added function get_live_streamers_data()
Fixed bug in get_user_by_id() and get_user(), if user is null, don't
cache user result.
2026-03-08 13:17:36 -05:00
d6b5d2c1c2 Updated Chatter
Use Dictionary for TwitchUser, and creation of TwitchUser from
dictionary saved into the database.
Added ignore_column() for user, as it's to be used only during runtime.
2026-03-08 13:15:53 -05:00
e586141d4d Update to Twitcher
Fixed error in iter functions, where it would never return the last item
in the iteration.
2026-03-08 13:14:52 -05:00
582d50ec2d Updates to Neon Wave theme 2026-03-08 13:14:20 -05:00
c65cc3537c Updated GeneralPanel scene
Removed old neon-wave-theme
2026-03-08 13:14:06 -05:00
b36822131a Updated ChatBox
Removed debug print.
2026-03-08 13:13:36 -05:00
82 changed files with 1633 additions and 209 deletions

View file

@ -23,7 +23,6 @@ func _ready() -> void:
print("Waiting for Twitcher Ready...")
waiting_for_ready = true
await get_tree().process_frame
print("Twitcher ready!")
_orientate_chatbox()
Globals.twitcher.chat.message_received.connect(_handle_chat)

View file

@ -2,45 +2,8 @@
[ext_resource type="Script" uid="uid://cksum4dhxw4t3" path="res://UI/Controls/general_panel.gd" id="1_oafot"]
[ext_resource type="Texture2D" uid="uid://cnu6l3x820i82" path="res://UI/assets/bootstrap/eye-slash.png" id="1_rbtts"]
[ext_resource type="Texture2D" uid="uid://6wa5oaqt2vmq" path="res://UI/assets/neon-wave-theme/neon-wave-theme.png" id="2_7naex"]
[ext_resource type="Script" uid="uid://ch7qf8iy31pfy" path="res://lib/UI/line_edit_with_buttons.gd" id="2_oookw"]
[sub_resource type="StyleBoxTexture" id="StyleBoxTexture_7naex"]
content_margin_left = 10.0
content_margin_top = 8.0
content_margin_right = 181.345
texture = ExtResource("2_7naex")
texture_margin_left = 3.08429
texture_margin_top = 5.50548
texture_margin_right = 2.18471
texture_margin_bottom = 5.269
region_rect = Rect2(93, 474, 41, 33)
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_en626"]
content_margin_left = 4.0
content_margin_top = 4.0
content_margin_right = 175.345
content_margin_bottom = 4.0
bg_color = Color(0.1, 0.1, 0.1, 0.3)
border_width_bottom = 2
border_color = Color(0, 0, 0, 0.3)
corner_radius_top_left = 3
corner_radius_top_right = 3
corner_radius_bottom_right = 3
corner_radius_bottom_left = 3
corner_detail = 5
[sub_resource type="StyleBoxTexture" id="StyleBoxTexture_v8ga3"]
content_margin_left = 10.0
content_margin_top = 8.0
content_margin_right = 181.345
texture = ExtResource("2_7naex")
texture_margin_left = 3.08429
texture_margin_top = 5.50548
texture_margin_right = 2.18471
texture_margin_bottom = 5.269
region_rect = Rect2(141, 474, 41, 33)
[node name="General" type="PanelContainer" unique_id=1071328377]
anchors_preset = 15
anchor_right = 1.0
@ -163,9 +126,6 @@ text = "Password:"
[node name="ObsPass" type="LineEdit" parent="MarginContainer/VBoxContainer/GridContainer" unique_id=170190217]
unique_name_in_owner = true
layout_mode = 2
theme_override_styles/normal = SubResource("StyleBoxTexture_7naex")
theme_override_styles/read_only = SubResource("StyleBoxFlat_en626")
theme_override_styles/focus = SubResource("StyleBoxTexture_v8ga3")
secret = true
script = ExtResource("2_oookw")
right = ExtResource("1_rbtts")

View file

@ -0,0 +1,12 @@
extends PanelContainer
@onready var tui := %TwitchUserInfo
@onready var up := %UserPromo
var chatter: Chatter:
set(value):
chatter = value
if not value: return
if not tui: return
tui.populate_from_chatter(chatter)
if not up: return
up.chatter = value

View file

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

View file

@ -0,0 +1,22 @@
[gd_scene format=3 uid="uid://d3fhwrt28r08x"]
[ext_resource type="PackedScene" uid="uid://bk7elsy5s3equ" path="res://UI/Controls/twitch_user_info.tscn" id="1_bbob4"]
[ext_resource type="Script" uid="uid://bbyomfy4iqbq3" path="res://UI/Controls/internal_twitch_user_info.gd" id="1_l8fl7"]
[ext_resource type="PackedScene" uid="uid://cadil3rnqh61e" path="res://UI/Controls/user_promo.tscn" id="2_l8fl7"]
[node name="InternalTwitchUserInfo" type="PanelContainer" unique_id=1986869556]
script = ExtResource("1_l8fl7")
metadata/_tab_index = 0
[node name="HBoxContainer" type="HBoxContainer" parent="." unique_id=515716981]
layout_mode = 2
[node name="TwitchUserInfo" parent="HBoxContainer" unique_id=1944732530 instance=ExtResource("1_bbob4")]
unique_name_in_owner = true
layout_mode = 2
size_flags_vertical = 0
[node name="UserPromo" parent="HBoxContainer" unique_id=1364169576 instance=ExtResource("2_l8fl7")]
unique_name_in_owner = true
layout_mode = 2
size_flags_vertical = 1

View file

@ -0,0 +1,57 @@
extends PanelContainer
var chatter: Chatter:
set(value):
chatter = value
if not chatter:
live = null
return
live = Globals.live_streamers[chatter.twitch_id] if chatter.twitch_id in Globals.live_streamers.keys() else null
_populate_view()
var live: TwitchStream
var _ticks: int = 0
# Called when the node enters the scene tree for the first time.
func _ready() -> void:
%StreamTitle.pressed.connect(_open_stream)
%RaidCurrentUser.pressed.connect(_raid_stream)
if not chatter: return
if not live and chatter.twitch_id in Globals.live_streamers.keys():
live = Globals.live_streamers[chatter.twitch_id]
_populate_view()
func _populate_view() -> void:
if not live:
set_process(false)
return
set_process(true)
%StreamTitle.text = live.title
%StreamViewerCount.text = str(live.viewer_count)
var url := live.thumbnail_url.format({"width": 640, "height": 360})
var img = await Globals.twitcher.media.load_image(url)
%StreamThumbnail.texture = ImageTexture.create_from_image(img)
func _process(_d: float) -> void:
if not live:
set_process(false)
return
_ticks += 1
if _ticks < 30: return
_ticks = 0
var system_unix = Time.get_unix_time_from_system()
var stream_start_unix = Time.get_unix_time_from_datetime_string(live.started_at)
var elapsed = Time.get_datetime_string_from_unix_time((system_unix - stream_start_unix), true)
%StreamTime.text = elapsed.split(" ")[1]
func _open_stream() -> void:
if not live: return
OS.shell_open("https://twitch.tv/%s" % live.user_login)
func _raid_stream() -> void:
if not live: return
pass

View file

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

View file

@ -0,0 +1,96 @@
[gd_scene format=3 uid="uid://bipoye4ww4ua6"]
[ext_resource type="Script" uid="uid://cdtfvedghei5f" path="res://UI/Controls/internal_user_live.gd" id="1_rl2q8"]
[ext_resource type="Texture2D" uid="uid://b3372gsnwqsyn" path="res://UI/assets/bootstrap/twitch.svg" id="1_sw5jj"]
[ext_resource type="Texture2D" uid="uid://bu2juj2beyws7" path="res://UI/assets/twitch_user_profile_pic.png" id="2_rl2q8"]
[node name="InternalUserLive" type="PanelContainer" unique_id=17956466]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1_rl2q8")
metadata/_tab_index = 1
[node name="VBoxContainer" type="VBoxContainer" parent="." unique_id=1170164100]
layout_mode = 2
[node name="StreamTitle" type="Button" parent="VBoxContainer" unique_id=1072998029]
unique_name_in_owner = true
layout_mode = 2
[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer" unique_id=12650367]
layout_mode = 2
size_flags_vertical = 3
[node name="StreamThumbnail" type="TextureRect" parent="VBoxContainer/HBoxContainer" unique_id=641456832]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
expand_mode = 1
stretch_mode = 5
[node name="UserChat" type="PanelContainer" parent="VBoxContainer/HBoxContainer" unique_id=1609959534]
unique_name_in_owner = true
visible = false
layout_mode = 2
[node name="VBoxContainer" type="VBoxContainer" parent="VBoxContainer/HBoxContainer/UserChat" unique_id=471477121]
custom_minimum_size = Vector2(300, 0)
layout_mode = 2
[node name="Chat" type="PanelContainer" parent="VBoxContainer/HBoxContainer/UserChat/VBoxContainer" unique_id=2018394431]
layout_mode = 2
size_flags_vertical = 3
[node name="ScrollContainer" type="ScrollContainer" parent="VBoxContainer/HBoxContainer/UserChat/VBoxContainer/Chat" unique_id=1349693918]
layout_mode = 2
[node name="ChatHistory" type="VBoxContainer" parent="VBoxContainer/HBoxContainer/UserChat/VBoxContainer/Chat/ScrollContainer" unique_id=1839863005]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="Message" type="LineEdit" parent="VBoxContainer/HBoxContainer/UserChat/VBoxContainer" unique_id=1978478528]
unique_name_in_owner = true
layout_mode = 2
placeholder_text = "chat"
[node name="PanelContainer" type="PanelContainer" parent="VBoxContainer" unique_id=1134033714]
layout_mode = 2
[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/PanelContainer" unique_id=411231899]
layout_mode = 2
[node name="RaidCurrentUser" type="Button" parent="VBoxContainer/PanelContainer/HBoxContainer" unique_id=288716491]
unique_name_in_owner = true
custom_minimum_size = Vector2(250, 0)
layout_mode = 2
text = "Raid!"
icon = ExtResource("1_sw5jj")
expand_icon = true
[node name="ChatLiveSteramer" type="LineEdit" parent="VBoxContainer/PanelContainer/HBoxContainer" unique_id=789542500]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
[node name="StreamTime" type="Label" parent="VBoxContainer/PanelContainer/HBoxContainer" unique_id=969178063]
unique_name_in_owner = true
layout_mode = 2
text = "00:15:42"
[node name="Icon" type="TextureRect" parent="VBoxContainer/PanelContainer/HBoxContainer" unique_id=521936560]
unique_name_in_owner = true
custom_minimum_size = Vector2(24, 24)
layout_mode = 2
texture = ExtResource("2_rl2q8")
expand_mode = 1
stretch_mode = 5
[node name="StreamViewerCount" type="Label" parent="VBoxContainer/PanelContainer/HBoxContainer" unique_id=20195332]
unique_name_in_owner = true
layout_mode = 2
text = "15"

View file

@ -0,0 +1,16 @@
@tool
extends PanelContainer
@export var rotation_speed: float = 1.0
# Called when the node enters the scene tree for the first time.
func _ready() -> void:
visibility_changed.connect(_on_visibility_changed)
func _on_visibility_changed() -> void:
set_process(is_visible_in_tree())
func _process(d: float) -> void:
%Spinner.rotation += d * TAU * rotation_speed
%Spinner.rotation = wrapf(%Spinner.rotation, 0, TAU)

View file

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

View file

@ -0,0 +1,17 @@
[gd_scene format=3 uid="uid://cbr5aed24dvty"]
[ext_resource type="Script" uid="uid://bkhjets1gnsk2" path="res://UI/Controls/loading_simple.gd" id="1_86jie"]
[ext_resource type="Texture2D" uid="uid://btyra86eut5se" path="res://UI/assets/loading.png" id="1_iuxiu"]
[node name="LoadingSimple" type="PanelContainer" unique_id=814067408]
script = ExtResource("1_86jie")
[node name="CenterContainer" type="CenterContainer" parent="." unique_id=897030486]
layout_mode = 2
[node name="Pin" type="Control" parent="CenterContainer" unique_id=1268794672]
layout_mode = 2
[node name="Spinner" type="Sprite2D" parent="CenterContainer/Pin" unique_id=903875911]
unique_name_in_owner = true
texture = ExtResource("1_iuxiu")

View file

@ -0,0 +1,55 @@
extends PanelContainer
signal dlg_closed()
signal users_updated()
# Called when the node enters the scene tree for the first time.
func _ready() -> void:
%SearchUser.text_submitted.connect(_handle_search_user)
%SearchId.text_submitted.connect(_handle_search_id)
%SaveUser.pressed.connect(_handle_save_user)
%UpdateUser.pressed.connect(_handle_update_user)
%ClearUser.pressed.connect(%TwitchUserInfo.clear)
func _handle_search_user(user: String) -> void:
%TwitchUserInfo.show_busy()
var t_user := await Globals.twitcher.get_user(user)
if t_user == null:
%TwitchUserInfo.show_normal()
DisplayServer.dialog_show("Failed to find Twitch User", "Failed to find the Twitch user going by %s" % user, ["Ok"], dlg_closed.emit)
await dlg_closed
return
%TwitchUserInfo.populate_from_twitch_user(t_user)
func _handle_search_id(id: String) -> void:
%TwitchUserInfo.show_busy()
var t_user := await Globals.twitcher.get_user_by_id(id)
if t_user == null:
%TwitchUserInfo.show_normal()
DisplayServer.dialog_show("Failed to find Twitch User", "Unable to find a Twitch user with the associated id %s" % id, ["Ok"], dlg_closed.emit)
await dlg_closed
return
%TwitchUserInfo.populate_from_twitch_user(t_user)
func _handle_save_user() -> void:
var user: TwitchUser = %TwitchUserInfo.t_user
var chatter: Chatter = Globals.context.chatters.find_one(Condition.new().equal("twitch_id", user.id))
if chatter:
DisplayServer.dialog_show("Add Twitch User", "This user already exists in the database.", ["Ok"], dlg_closed.emit)
await dlg_closed
return
chatter = Chatter.new()
chatter.twitch_id = user.id
chatter.user = user
chatter.first_added = Time.get_unix_time_from_system()
chatter.first_seen = -1
chatter.last_seen = -1
Globals.context.chatters.append(chatter)
%TwitchUserInfo.clear()
users_updated.emit()
func _handle_update_user() -> void:
pass

View file

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

View file

@ -0,0 +1,68 @@
[gd_scene format=3 uid="uid://2ifvkmqd0q3i"]
[ext_resource type="Texture2D" uid="uid://bexdhdpprh6aa" path="res://UI/assets/font_awesome/magnifying-glass-location.svg" id="1_c6q78"]
[ext_resource type="Script" uid="uid://dgd1pmou5c35b" path="res://UI/Controls/search_twitch_user.gd" id="1_k513e"]
[ext_resource type="PackedScene" uid="uid://bk7elsy5s3equ" path="res://UI/Controls/twitch_user_info.tscn" id="2_k513e"]
[ext_resource type="Texture2D" uid="uid://bqgx2li6ejy4q" path="res://UI/assets/bootstrap/floppy-fill.png" id="3_hc0ai"]
[ext_resource type="Texture2D" uid="uid://ch7pe2qtxu5vt" path="res://UI/assets/font_awesome/user-pen.svg" id="4_21lmp"]
[ext_resource type="Texture2D" uid="uid://di8hel7cykf1y" path="res://UI/assets/font_awesome/eraser.svg" id="5_c6q78"]
[node name="SearchTwitchUser" type="PanelContainer" unique_id=83637811]
script = ExtResource("1_k513e")
[node name="VBoxContainer" type="VBoxContainer" parent="." unique_id=574238165]
layout_mode = 2
[node name="Search" type="HBoxContainer" parent="VBoxContainer" unique_id=335042598]
layout_mode = 2
[node name="ico" type="TextureRect" parent="VBoxContainer/Search" unique_id=2076335426]
custom_minimum_size = Vector2(32, 32)
layout_mode = 2
texture = ExtResource("1_c6q78")
expand_mode = 1
stretch_mode = 5
[node name="SearchUser" type="LineEdit" parent="VBoxContainer/Search" unique_id=1739256223]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
placeholder_text = "search by username"
[node name="SearchId" type="LineEdit" parent="VBoxContainer/Search" unique_id=778191154]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
placeholder_text = "search by user ID"
[node name="TwitchUserInfo" parent="VBoxContainer" unique_id=1944732530 instance=ExtResource("2_k513e")]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 0
[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer" unique_id=663178970]
layout_mode = 2
[node name="SaveUser" type="Button" parent="VBoxContainer/HBoxContainer" unique_id=1903961333]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
text = "Save new user"
icon = ExtResource("3_hc0ai")
expand_icon = true
[node name="UpdateUser" type="Button" parent="VBoxContainer/HBoxContainer" unique_id=1835546110]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
text = "Update User"
icon = ExtResource("4_21lmp")
expand_icon = true
[node name="ClearUser" type="Button" parent="VBoxContainer/HBoxContainer" unique_id=2034415437]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
text = "Clear"
icon = ExtResource("5_c6q78")
expand_icon = true

View file

@ -0,0 +1,98 @@
extends PanelContainer
const CHEVRONS = [
preload("res://UI/assets/font_awesome/chevron-left.svg"),
preload("res://UI/assets/font_awesome/chevron-right.svg")
]
@export var expanded: bool = false
signal extra_expanded(is_expanded: bool)
var is_extra_panel_expanded: bool
var tw_expand: Tween
var t_user: TwitchUser
var chatter: Chatter
func _ready() -> void:
toggle_extra_panel(expanded)
%ExpandExtraInfo.pressed.connect(func(): toggle_extra_panel(!is_extra_panel_expanded))
%LoadingSimple.hide()
func show_busy() -> void:
%LoadingSimple.show()
func show_normal() -> void:
%LoadingSimple.hide()
func populate_from_twitch_user(_t_user: TwitchUser) -> void:
%LoadingSimple.show()
clear()
t_user = _t_user
if not t_user: return
%Username.text = t_user.login
%DisplayName.text = t_user.display_name
%UserId.text = t_user.id
%ProfilePictureURL.text = t_user.profile_image_url
if t_user.profile_image_url:
%AvatarImg.texture = await Globals.twitcher.media.load_profile_image(t_user)
else:
%AvatarImg.texture = preload("res://UI/assets/twitch_user_profile_pic.png")
%Type.text = t_user.type
%ChannelDescription.text = t_user.description
%BroadcasterType.text = t_user.broadcaster_type
%OfflineImageURL.text = t_user.offline_image_url
%ViewCount.text = str(t_user.view_count)
%LoadingSimple.hide()
func populate_from_chatter(_chatter: Chatter) -> void:
%LoadingSimple.show()
clear()
chatter = _chatter
if not chatter: return
t_user = await Globals.twitcher.get_user_by_id(chatter.twitch_id)
%Username.text = t_user.login
%DisplayName.text = t_user.display_name
%UserId.text = t_user.id
%ProfilePictureURL.text = t_user.profile_image_url
if t_user.profile_image_url:
%AvatarImg.texture = await Globals.twitcher.media.load_profile_image(t_user)
else:
%AvatarImg.texture = preload("res://UI/assets/twitch_user_profile_pic.png")
%Type.text = t_user.type
%ChannelDescription.text = t_user.description
%BroadcasterType.text = t_user.broadcaster_type
%OfflineImageURL.text = t_user.offline_image_url
%ViewCount.text = str(t_user.view_count)
%LoadingSimple.hide()
func clear() -> void:
%AvatarImg.texture = preload("res://UI/assets/twitch_user_profile_pic.png")
%Username.text = ""
%DisplayName.text = ""
%UserId.text = ""
%ProfilePictureURL.text = ""
%ChatColor.color = Color.TRANSPARENT
%Type.text = ""
%ChannelDescription.text = ""
%BroadcasterType.text = ""
%OfflineImageURL.text = ""
%ViewCount.text = ""
func toggle_extra_panel(val: bool) -> void:
is_extra_panel_expanded = val
%ExpandExtraInfo.icon = CHEVRONS[0] if is_extra_panel_expanded else CHEVRONS[1]
var min_size_x: float = 400 if is_extra_panel_expanded else 0
if tw_expand:
tw_expand.kill()
%ExtraInfo.show()
tw_expand = create_tween()
tw_expand.set_ease(Tween.EASE_OUT)
tw_expand.set_trans(Tween.TRANS_CUBIC)
tw_expand.tween_property(%ExtraInfo, ^"custom_minimum_size:x", min_size_x, 0.3)
if !is_extra_panel_expanded:
tw_expand.tween_property(%ExtraInfo, ^"visible", false, 0.0)
tw_expand.tween_callback(extra_expanded.emit.bind(is_extra_panel_expanded))

View file

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

View file

@ -0,0 +1,159 @@
[gd_scene format=3 uid="uid://bk7elsy5s3equ"]
[ext_resource type="Script" uid="uid://bj86xfgm00ay2" path="res://UI/Controls/twitch_user_info.gd" id="1_2rtl8"]
[ext_resource type="Texture2D" uid="uid://bu2juj2beyws7" path="res://UI/assets/twitch_user_profile_pic.png" id="1_wdavc"]
[ext_resource type="Texture2D" uid="uid://cjiu8qsg8kcvk" path="res://UI/assets/font_awesome/chevron-right.svg" id="2_8x1yt"]
[ext_resource type="Theme" uid="uid://dh11pgqmtpeig" path="res://UI/assets/main_theme.tres" id="2_grpru"]
[ext_resource type="PackedScene" uid="uid://cbr5aed24dvty" path="res://UI/Controls/loading_simple.tscn" id="5_ssps2"]
[node name="TwitchUserInfo" type="PanelContainer" unique_id=1944732530]
offset_right = 294.0
offset_bottom = 128.0
script = ExtResource("1_2rtl8")
[node name="HBoxContainer" type="HBoxContainer" parent="." unique_id=835428733]
layout_mode = 2
[node name="AvatarImg" type="TextureRect" parent="HBoxContainer" unique_id=847416416]
unique_name_in_owner = true
custom_minimum_size = Vector2(0, 128)
layout_mode = 2
texture = ExtResource("1_wdavc")
expand_mode = 2
stretch_mode = 4
[node name="RefreshAvatar" type="Button" parent="HBoxContainer/AvatarImg" unique_id=470568045]
unique_name_in_owner = true
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme = ExtResource("2_grpru")
theme_type_variation = &"BlankButton"
[node name="VBoxContainer" type="VBoxContainer" parent="HBoxContainer" unique_id=2091869482]
layout_mode = 2
size_flags_horizontal = 3
[node name="HBoxContainer" type="HBoxContainer" parent="HBoxContainer/VBoxContainer" unique_id=1790412527]
custom_minimum_size = Vector2(420, 0)
layout_mode = 2
[node name="DisplayName" type="LineEdit" parent="HBoxContainer/VBoxContainer/HBoxContainer" unique_id=112898446]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
placeholder_text = "display name"
[node name="UserId" type="LineEdit" parent="HBoxContainer/VBoxContainer/HBoxContainer" unique_id=301550074]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
placeholder_text = "user id"
[node name="ChannelDescription" type="TextEdit" parent="HBoxContainer/VBoxContainer" unique_id=1913020364]
unique_name_in_owner = true
custom_minimum_size = Vector2(420, 0)
layout_mode = 2
size_flags_vertical = 3
placeholder_text = "Channel description"
wrap_mode = 1
indent_wrapped_lines = true
[node name="ExpandExtraInfo" type="Button" parent="HBoxContainer" unique_id=943960057]
unique_name_in_owner = true
custom_minimum_size = Vector2(18, 0)
layout_mode = 2
theme_type_variation = &"BlankButton"
icon = ExtResource("2_8x1yt")
icon_alignment = 1
expand_icon = true
[node name="ExtraInfo" type="PanelContainer" parent="HBoxContainer" unique_id=1175412769]
unique_name_in_owner = true
layout_mode = 2
[node name="ScrollContainer" type="ScrollContainer" parent="HBoxContainer/ExtraInfo" unique_id=1105674920]
layout_mode = 2
[node name="VBoxContainer" type="VBoxContainer" parent="HBoxContainer/ExtraInfo/ScrollContainer" unique_id=1573319906]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="Username" type="LineEdit" parent="HBoxContainer/ExtraInfo/ScrollContainer/VBoxContainer" unique_id=1399710760]
unique_name_in_owner = true
layout_mode = 2
placeholder_text = "username"
[node name="ProfilePictureURL" type="LineEdit" parent="HBoxContainer/ExtraInfo/ScrollContainer/VBoxContainer" unique_id=1845036114]
unique_name_in_owner = true
layout_mode = 2
placeholder_text = "profile picture url"
[node name="HBoxContainer" type="HBoxContainer" parent="HBoxContainer/ExtraInfo/ScrollContainer/VBoxContainer" unique_id=655171068]
layout_mode = 2
[node name="Label" type="Label" parent="HBoxContainer/ExtraInfo/ScrollContainer/VBoxContainer/HBoxContainer" unique_id=1450064024]
layout_mode = 2
text = "Twitch chat color"
[node name="ChatColor" type="ColorRect" parent="HBoxContainer/ExtraInfo/ScrollContainer/VBoxContainer/HBoxContainer" unique_id=2056189419]
unique_name_in_owner = true
custom_minimum_size = Vector2(64, 16)
layout_mode = 2
[node name="HBoxContainer2" type="HBoxContainer" parent="HBoxContainer/ExtraInfo/ScrollContainer/VBoxContainer" unique_id=55699403]
layout_mode = 2
[node name="Label" type="Label" parent="HBoxContainer/ExtraInfo/ScrollContainer/VBoxContainer/HBoxContainer2" unique_id=1163546465]
layout_mode = 2
text = "type"
[node name="Type" type="LineEdit" parent="HBoxContainer/ExtraInfo/ScrollContainer/VBoxContainer/HBoxContainer2" unique_id=403251125]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
[node name="HBoxContainer3" type="HBoxContainer" parent="HBoxContainer/ExtraInfo/ScrollContainer/VBoxContainer" unique_id=1302353290]
layout_mode = 2
[node name="Label" type="Label" parent="HBoxContainer/ExtraInfo/ScrollContainer/VBoxContainer/HBoxContainer3" unique_id=2125425960]
layout_mode = 2
text = "broadcaster type"
[node name="BroadcasterType" type="LineEdit" parent="HBoxContainer/ExtraInfo/ScrollContainer/VBoxContainer/HBoxContainer3" unique_id=136580961]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
[node name="HBoxContainer4" type="HBoxContainer" parent="HBoxContainer/ExtraInfo/ScrollContainer/VBoxContainer" unique_id=315878161]
layout_mode = 2
[node name="Label" type="Label" parent="HBoxContainer/ExtraInfo/ScrollContainer/VBoxContainer/HBoxContainer4" unique_id=1792499029]
layout_mode = 2
text = "offline image url"
[node name="OfflineImageURL" type="LineEdit" parent="HBoxContainer/ExtraInfo/ScrollContainer/VBoxContainer/HBoxContainer4" unique_id=1036495270]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
[node name="HBoxContainer5" type="HBoxContainer" parent="HBoxContainer/ExtraInfo/ScrollContainer/VBoxContainer" unique_id=376702304]
layout_mode = 2
[node name="Label" type="Label" parent="HBoxContainer/ExtraInfo/ScrollContainer/VBoxContainer/HBoxContainer5" unique_id=1139447144]
layout_mode = 2
text = "view count"
[node name="ViewCount" type="LineEdit" parent="HBoxContainer/ExtraInfo/ScrollContainer/VBoxContainer/HBoxContainer5" unique_id=1793906256]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
[node name="LoadingSimple" parent="." unique_id=814067408 instance=ExtResource("5_ssps2")]
unique_name_in_owner = true
visible = false
layout_mode = 2

112
UI/Controls/user_entry.gd Normal file
View file

@ -0,0 +1,112 @@
extends PanelContainer
class_name UserEntry
var chatter: Chatter
var tw_hidden: Tween
var is_expanded: bool = false
var scroll: ScrollContainer
var is_visible_in_scroll: bool:
get:
if not scroll: return false
return scroll.get_global_rect().intersects(get_global_rect())
var is_profile_picture_loaded: bool = false
signal user_selected(chatter: Chatter)
# Called when the node enters the scene tree for the first time.
func _ready() -> void:
scroll = get_parent().get_parent()
scroll.get_v_scroll_bar().scrolling.connect(check_update_profile_picture)
visibility_changed.connect(check_update_profile_picture)
update()
toggle_buttons(false)
Globals.live_streamers_updated.connect(func(): %LiveStatus.visible = chatter.twitch_id in Globals.live_streamers.keys())
await get_tree().process_frame
check_update_profile_picture()
%User.pressed.connect(user_selected.emit.bind(chatter))
%Shoutout.pressed.connect(func(): Globals.twitcher.shoutout(chatter.user))
%Promote.pressed.connect(func(): Globals.twitcher.send_message(chatter.promo_msg))
func update() -> void:
if not chatter:
push_error("No user for button!")
return
if not chatter.user:
chatter.user = await Globals.twitcher.get_user_by_id(chatter.twitch_id)
%User.text = chatter.user.display_name
%Shoutout.disabled = !chatter.is_streamer
%Promote.disabled = chatter.promo_msg == ""
%LiveStatus.visible = chatter.twitch_id in Globals.live_streamers.keys()
%Raid.disabled = not chatter.twitch_id in Globals.live_streamers.keys()
func update_profile_picture() -> void:
if not chatter.user.profile_image_url: return
%LoadingSimple.show()
%AvatarImg.texture = await Globals.twitcher.media.load_profile_image(chatter.user)
%LoadingSimple.hide()
func reload_twitch_user() -> void:
var t_user: TwitchUser = await Globals.twitcher.get_user_by_id(chatter.twitch_id)
if not t_user:
return
chatter.user = t_user
chatter.save()
update()
update_profile_picture()
func check_update_profile_picture() -> void:
if is_profile_picture_loaded: return
if not chatter: return
await get_tree().process_frame
if not is_visible_in_scroll: return
is_profile_picture_loaded = true
await update_profile_picture()
if not %AvatarImg.texture: is_profile_picture_loaded = false
func toggle_buttons(vis: bool) -> void:
const ANIM_SPEED = 0.2
const MIN_SIZE = Vector2(32,32)
if tw_hidden:
tw_hidden.kill()
tw_hidden = create_tween()
tw_hidden.set_ease(Tween.EASE_OUT).set_trans(Tween.TRANS_CUBIC)
var min_size: Vector2 = MIN_SIZE if vis else Vector2.ZERO
var cover_btn_alpha: float = float(!vis)
var btns_alpha: float = float(vis)
for btn: Button in %Menu.get_children():
tw_hidden.parallel().tween_property(
btn,
^"custom_minimum_size",
min_size,
ANIM_SPEED
)
tw_hidden.parallel().tween_property(
btn,
^"modulate:a",
btns_alpha,
ANIM_SPEED
)
%ButtonMenu.show()
tw_hidden.tween_property(%ButtonMenu, ^"modulate:a", cover_btn_alpha, ANIM_SPEED)
if vis:
tw_hidden.tween_property(%ButtonMenu, ^"visible", false, ANIM_SPEED)
is_expanded = true
set_process(true)
func _process(_delta: float) -> void:
if !is_expanded:
set_process(false)
return
var m_pos: Vector2 = get_global_mouse_position()
var is_inside: bool = %Toggle.get_global_rect().has_point(m_pos)
if is_inside:
return
toggle_buttons(false)
is_expanded = false
set_process(false)

View file

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

136
UI/Controls/user_entry.tscn Normal file
View file

@ -0,0 +1,136 @@
[gd_scene format=3 uid="uid://c3hb5od24tfr3"]
[ext_resource type="Texture2D" uid="uid://bu2juj2beyws7" path="res://UI/assets/twitch_user_profile_pic.png" id="1_8xi1h"]
[ext_resource type="Theme" uid="uid://dh11pgqmtpeig" path="res://UI/assets/main_theme.tres" id="1_r4556"]
[ext_resource type="Script" uid="uid://ldua0xcjjws4" path="res://UI/Controls/user_entry.gd" id="1_xsfty"]
[ext_resource type="PackedScene" uid="uid://cbr5aed24dvty" path="res://UI/Controls/loading_simple.tscn" id="3_mi6bx"]
[ext_resource type="Texture2D" uid="uid://ed2yvxs0b8sv" path="res://UI/assets/bootstrap/megaphone-fill.png" id="3_sck47"]
[ext_resource type="Texture2D" uid="uid://eybcntflwjpx" path="res://UI/assets/bootstrap/megaphone.svg" id="4_0jtnn"]
[ext_resource type="Texture2D" uid="uid://bb2asei1pibev" path="res://UI/assets/bootstrap/arrow-repeat.png" id="5_xsfty"]
[ext_resource type="Texture2D" uid="uid://b3372gsnwqsyn" path="res://UI/assets/bootstrap/twitch.svg" id="6_mi6bx"]
[ext_resource type="Texture2D" uid="uid://d1qh7e8vpfm1p" path="res://UI/assets/font_awesome/ellipsis.svg" id="7_k1oax"]
[ext_resource type="Texture2D" uid="uid://echc7jyluqi0" path="res://UI/assets/bootstrap/trash.png" id="9_k1oax"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ddi2y"]
content_margin_left = 4.0
content_margin_top = 4.0
content_margin_right = 4.0
content_margin_bottom = 4.0
bg_color = Color(0.101960786, 0.101960786, 0.101960786, 1)
corner_radius_top_left = 3
corner_radius_top_right = 3
corner_radius_bottom_right = 3
corner_radius_bottom_left = 3
corner_detail = 5
[node name="UserEntry" type="PanelContainer" unique_id=1215925059]
offset_right = 40.0
offset_bottom = 40.0
script = ExtResource("1_xsfty")
[node name="HBoxContainer" type="HBoxContainer" parent="." unique_id=582266053]
layout_mode = 2
[node name="AvatarImg" type="TextureRect" parent="HBoxContainer" unique_id=1364124968]
unique_name_in_owner = true
custom_minimum_size = Vector2(48, 48)
layout_mode = 2
texture = ExtResource("1_8xi1h")
expand_mode = 1
stretch_mode = 5
[node name="LoadingSimple" parent="HBoxContainer/AvatarImg" unique_id=814067408 instance=ExtResource("3_mi6bx")]
unique_name_in_owner = true
visible = false
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="OpenSteram" type="Button" parent="HBoxContainer/AvatarImg" unique_id=118186600]
unique_name_in_owner = true
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme = ExtResource("1_r4556")
theme_type_variation = &"BlankButton"
[node name="User" type="Button" parent="HBoxContainer" unique_id=565524419]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
text = "Falinere"
[node name="LiveStatus" type="ColorRect" parent="HBoxContainer/User" unique_id=1459190091]
unique_name_in_owner = true
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
mouse_filter = 2
color = Color(0.3647059, 1, 0.44313726, 0.3764706)
[node name="Shoutout" type="Button" parent="HBoxContainer" unique_id=85236405]
unique_name_in_owner = true
custom_minimum_size = Vector2(32, 32)
layout_mode = 2
icon = ExtResource("3_sck47")
icon_alignment = 1
expand_icon = true
[node name="Toggle" type="PanelContainer" parent="HBoxContainer" unique_id=174111778]
unique_name_in_owner = true
layout_mode = 2
[node name="Menu" type="HBoxContainer" parent="HBoxContainer/Toggle" unique_id=1481656507]
unique_name_in_owner = true
layout_mode = 2
[node name="Promote" type="Button" parent="HBoxContainer/Toggle/Menu" unique_id=2137206096]
unique_name_in_owner = true
custom_minimum_size = Vector2(32, 32)
layout_mode = 2
icon = ExtResource("4_0jtnn")
icon_alignment = 1
expand_icon = true
[node name="Refresh" type="Button" parent="HBoxContainer/Toggle/Menu" unique_id=1938170984]
unique_name_in_owner = true
custom_minimum_size = Vector2(32, 32)
layout_mode = 2
icon = ExtResource("5_xsfty")
icon_alignment = 1
expand_icon = true
[node name="Raid" type="Button" parent="HBoxContainer/Toggle/Menu" unique_id=527951722]
unique_name_in_owner = true
modulate = Color(0.6392157, 0.38431373, 0.84313726, 1)
custom_minimum_size = Vector2(32, 32)
layout_mode = 2
icon = ExtResource("6_mi6bx")
icon_alignment = 1
expand_icon = true
[node name="Delete" type="Button" parent="HBoxContainer/Toggle/Menu" unique_id=596657869]
unique_name_in_owner = true
modulate = Color(1, 0, 0, 1)
custom_minimum_size = Vector2(32, 32)
layout_mode = 2
icon = ExtResource("9_k1oax")
icon_alignment = 1
expand_icon = true
[node name="ButtonMenu" type="Button" parent="HBoxContainer/Toggle" unique_id=887606573]
unique_name_in_owner = true
layout_mode = 2
theme_override_styles/normal = SubResource("StyleBoxFlat_ddi2y")
icon = ExtResource("7_k1oax")
icon_alignment = 1
expand_icon = true

57
UI/Controls/user_list.gd Normal file
View file

@ -0,0 +1,57 @@
extends PanelContainer
signal user_selected(chatter: Chatter)
const USER_ENTRY = preload("res://UI/Controls/user_entry.tscn")
var entries_list: Array[UserEntry] = []
var filtering_live: bool = false:
set(value):
filtering_live = value
if value:
filter_live_users()
else:
reset_filter()
# Called when the node enters the scene tree for the first time.
func _ready() -> void:
%FilterLive.pressed.connect(func(): filtering_live = !filtering_live)
func clear_list() -> void:
for node in %UserList.get_children():
node.queue_free()
func update_list() -> void:
for chatter: Chatter in Globals.context.chatters.all():
if entries_list.any(func(x: UserEntry): return x.chatter.id == chatter.id):
continue
var inst := USER_ENTRY.instantiate()
inst.chatter = chatter
entries_list.append(inst)
inst.tree_exiting.connect(entries_list.erase.bind(inst))
inst.user_selected.connect(user_selected.emit)
%UserList.add_child(inst)
entries_list.sort_custom(func(x: UserEntry, y: UserEntry): return x.chatter.id < y.chatter.id)
var i = 0
for entry: UserEntry in entries_list:
%UserList.move_child(entry,i)
i += 1
func populate_list() -> void:
for chatter: Chatter in Globals.context.chatters.all():
var inst := USER_ENTRY.instantiate()
inst.chatter = chatter
entries_list.append(inst)
inst.tree_exiting.connect(entries_list.erase.bind(inst))
inst.user_selected.connect(user_selected.emit)
%UserList.add_child(inst)
func reset_filter() -> void:
for entry: UserEntry in %UserList.get_children():
entry.visible = true
func filter_live_users() -> void:
reset_filter()
for entry: UserEntry in %UserList.get_children():
entry.visible = entry.chatter.twitch_id in Globals.live_streamers.keys()

View file

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

View file

@ -0,0 +1,56 @@
[gd_scene format=3 uid="uid://cdm7rbq5547xp"]
[ext_resource type="Script" uid="uid://bgur6pwnuh27h" path="res://UI/Controls/user_list.gd" id="1_cj73x"]
[ext_resource type="Texture2D" uid="uid://bb2asei1pibev" path="res://UI/assets/bootstrap/arrow-repeat.png" id="1_o0xn2"]
[ext_resource type="Texture2D" uid="uid://c4le71w5j6nq5" path="res://UI/assets/simple/twitch.svg" id="2_cj73x"]
[ext_resource type="Texture2D" uid="uid://cnofay0htsof1" path="res://UI/assets/bootstrap/sort-up.svg" id="3_7xwno"]
[node name="UserList" type="PanelContainer" unique_id=1448466108]
script = ExtResource("1_cj73x")
[node name="VBoxContainer" type="VBoxContainer" parent="." unique_id=128633507]
layout_mode = 2
[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer" unique_id=1963562084]
layout_mode = 2
[node name="Filter" type="LineEdit" parent="VBoxContainer/HBoxContainer" unique_id=557386234]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
placeholder_text = "Filter"
[node name="Refresh" type="Button" parent="VBoxContainer/HBoxContainer" unique_id=1543716134]
unique_name_in_owner = true
custom_minimum_size = Vector2(32, 32)
layout_mode = 2
icon = ExtResource("1_o0xn2")
icon_alignment = 1
expand_icon = true
[node name="FilterLive" type="Button" parent="VBoxContainer/HBoxContainer" unique_id=736301841]
unique_name_in_owner = true
custom_minimum_size = Vector2(32, 32)
layout_mode = 2
icon = ExtResource("2_cj73x")
icon_alignment = 1
expand_icon = true
[node name="Sort" type="Button" parent="VBoxContainer/HBoxContainer" unique_id=429146136]
unique_name_in_owner = true
custom_minimum_size = Vector2(32, 32)
layout_mode = 2
icon = ExtResource("3_7xwno")
icon_alignment = 1
expand_icon = true
[node name="ScrollContainer" type="ScrollContainer" parent="VBoxContainer" unique_id=2048134332]
layout_mode = 2
size_flags_vertical = 3
horizontal_scroll_mode = 3
[node name="UserList" type="VBoxContainer" parent="VBoxContainer/ScrollContainer" unique_id=1488715496]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3

65
UI/Controls/user_promo.gd Normal file
View file

@ -0,0 +1,65 @@
extends PanelContainer
var chatter: Chatter:
set(value):
chatter = value
if not chatter:
_clear_promos()
return
_populate_promos()
func _ready() -> void:
for btn in [%Streamer, %Shoutout, %Promote]:
btn.pressed.connect(_update_promos)
for le in [%YouTubeHandle, %BlueSkyHandle, %WebSite, %ShoutoutMessage, %PromotionMessage]:
le.text_changed.connect(_update_promos)
if not chatter: return
_populate_promos()
func _clear_promos() -> void:
%Streamer.button_pressed = false
%Shoutout.button_pressed = false
%Promote.button_pressed = false
%YouTubeHandle.text = ""
%BlueSkyHandle.text = ""
%WebSite.text = ""
%ShoutoutMessage.text = ""
%PromotionMessage.text = ""
func _populate_promos() -> void:
%Streamer.button_pressed = chatter.is_streamer
%Shoutout.button_pressed = chatter.auto_shoutout
%Promote.button_pressed = chatter.extra_data.has("auto-promo")
%YouTubeHandle.text = chatter.urls.youtube if chatter.urls.has("youtube") else ""
%BlueSkyHandle.text = chatter.urls.bluesky if chatter.urls.has("bluesky") else ""
%WebSite.text = chatter.urls.website if chatter.urls.has("website") else ""
%ShoutoutMessage.text = chatter.extra_data.shoutout_message if chatter.extra_data.has("shoutout_message") else ""
%PromotionMessage.text = chatter.extra_data.promotion_message if chatter.extra_data.has("promotion_message") else ""
func _update_promos() -> void:
if not chatter: return
chatter.is_streamer = %Streamer.button_pressed
chatter.auto_shoutout = %Shoutout.button_pressed
chatter.extra_data["auto_promo"] = %Promote.button_pressed
if %YouTubeHandle.text != "":
chatter.urls["youtube"] = %YouTubeHandle.text
else:
chatter.urls.erase("youtube")
if %BlueSkyHandle.text != "":
chatter.urls["bluesky"] = %BlueSkyHandle.text
else:
chatter.urls.erase("bluesky")
if %WebSite.text != "":
chatter.urls["website"] = %WebSite.text
else:
chatter.urls.erase("website")
if %ShoutoutMessage.text != "":
chatter.extra_data.shoutout_message = %ShoutoutMessage.text
else:
chatter.extra_data.erase("shoutout_message")
if %PromotionMessage.text != "":
chatter.extra_data.promotion_message = %PromotionMessage.text
else:
chatter.extra_data.erase("promotion_message")
chatter.save()

View file

@ -0,0 +1 @@
uid://8frnm7cda0ht

155
UI/Controls/user_promo.tscn Normal file
View file

@ -0,0 +1,155 @@
[gd_scene format=3 uid="uid://cadil3rnqh61e"]
[ext_resource type="Script" uid="uid://8frnm7cda0ht" path="res://UI/Controls/user_promo.gd" id="1_jcejw"]
[ext_resource type="Texture2D" uid="uid://cup1m40podywb" path="res://UI/assets/bootstrap/megaphone-fill.svg" id="1_nwev1"]
[ext_resource type="Texture2D" uid="uid://dp06rjedx170o" path="res://UI/assets/bootstrap/globe2.svg" id="2_jcejw"]
[node name="UserPromo" type="PanelContainer" unique_id=1364169576]
offset_right = 406.0
offset_bottom = 136.0
size_flags_horizontal = 3
size_flags_vertical = 3
script = ExtResource("1_jcejw")
[node name="ScrollContainer" type="ScrollContainer" parent="." unique_id=1229873159]
layout_mode = 2
horizontal_scroll_mode = 0
[node name="VBoxContainer" type="VBoxContainer" parent="ScrollContainer" unique_id=120163548]
layout_mode = 2
[node name="HBoxContainer" type="HBoxContainer" parent="ScrollContainer/VBoxContainer" unique_id=98113976]
layout_mode = 2
[node name="Streamer" type="CheckButton" parent="ScrollContainer/VBoxContainer/HBoxContainer" unique_id=737955603]
unique_name_in_owner = true
layout_mode = 2
text = "Streamer"
[node name="Shoutout" type="CheckButton" parent="ScrollContainer/VBoxContainer/HBoxContainer" unique_id=378103028]
unique_name_in_owner = true
layout_mode = 2
text = "Shoutout"
[node name="Promote" type="CheckButton" parent="ScrollContainer/VBoxContainer/HBoxContainer" unique_id=929607483]
unique_name_in_owner = true
layout_mode = 2
text = "Auto-Promote"
[node name="WorksWith" type="HBoxContainer" parent="ScrollContainer/VBoxContainer" unique_id=1239050314]
layout_mode = 2
size_flags_horizontal = 3
[node name="Label" type="Label" parent="ScrollContainer/VBoxContainer/WorksWith" unique_id=400379538]
custom_minimum_size = Vector2(100, 0)
layout_mode = 2
text = "Works with"
[node name="OptionButton" type="OptionButton" parent="ScrollContainer/VBoxContainer/WorksWith" unique_id=1872170308]
layout_mode = 2
size_flags_horizontal = 3
[node name="YouTube" type="HBoxContainer" parent="ScrollContainer/VBoxContainer" unique_id=1207903264]
layout_mode = 2
size_flags_horizontal = 3
[node name="Label" type="Label" parent="ScrollContainer/VBoxContainer/YouTube" unique_id=1593070954]
custom_minimum_size = Vector2(100, 0)
layout_mode = 2
text = "YouTube"
[node name="YtPromo" type="Button" parent="ScrollContainer/VBoxContainer/YouTube" unique_id=1096467567]
unique_name_in_owner = true
custom_minimum_size = Vector2(24, 24)
layout_mode = 2
icon = ExtResource("1_nwev1")
icon_alignment = 1
expand_icon = true
[node name="YtLink" type="Button" parent="ScrollContainer/VBoxContainer/YouTube" unique_id=50317293]
unique_name_in_owner = true
custom_minimum_size = Vector2(24, 24)
layout_mode = 2
icon = ExtResource("2_jcejw")
icon_alignment = 1
expand_icon = true
[node name="YouTubeHandle" type="LineEdit" parent="ScrollContainer/VBoxContainer/YouTube" unique_id=603065520]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
placeholder_text = "YouTube @handle"
[node name="BlueSky" type="HBoxContainer" parent="ScrollContainer/VBoxContainer" unique_id=1489223257]
layout_mode = 2
size_flags_horizontal = 3
[node name="Label" type="Label" parent="ScrollContainer/VBoxContainer/BlueSky" unique_id=381069814]
custom_minimum_size = Vector2(100, 0)
layout_mode = 2
text = "Bluesky"
[node name="BsPromo" type="Button" parent="ScrollContainer/VBoxContainer/BlueSky" unique_id=658543783]
unique_name_in_owner = true
custom_minimum_size = Vector2(24, 24)
layout_mode = 2
icon = ExtResource("1_nwev1")
icon_alignment = 1
expand_icon = true
[node name="BsLink" type="Button" parent="ScrollContainer/VBoxContainer/BlueSky" unique_id=1961953027]
unique_name_in_owner = true
custom_minimum_size = Vector2(24, 24)
layout_mode = 2
icon = ExtResource("2_jcejw")
icon_alignment = 1
expand_icon = true
[node name="BlueSkyHandle" type="LineEdit" parent="ScrollContainer/VBoxContainer/BlueSky" unique_id=360439078]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
placeholder_text = "b.sky @handle"
[node name="WebSite" type="HBoxContainer" parent="ScrollContainer/VBoxContainer" unique_id=573488452]
layout_mode = 2
size_flags_horizontal = 3
[node name="Label" type="Label" parent="ScrollContainer/VBoxContainer/WebSite" unique_id=1373223946]
custom_minimum_size = Vector2(100, 0)
layout_mode = 2
text = "Website"
[node name="WsPromo" type="Button" parent="ScrollContainer/VBoxContainer/WebSite" unique_id=973904899]
unique_name_in_owner = true
custom_minimum_size = Vector2(24, 24)
layout_mode = 2
icon = ExtResource("1_nwev1")
icon_alignment = 1
expand_icon = true
[node name="WsLink" type="Button" parent="ScrollContainer/VBoxContainer/WebSite" unique_id=666822841]
unique_name_in_owner = true
custom_minimum_size = Vector2(24, 24)
layout_mode = 2
icon = ExtResource("2_jcejw")
icon_alignment = 1
expand_icon = true
[node name="WebSite" type="LineEdit" parent="ScrollContainer/VBoxContainer/WebSite" unique_id=1937578185]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
placeholder_text = "https://"
[node name="ShoutoutMessage" type="LineEdit" parent="ScrollContainer/VBoxContainer" unique_id=1766315801]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
placeholder_text = "shoutout message"
[node name="PromotionMessage" type="LineEdit" parent="ScrollContainer/VBoxContainer" unique_id=1002295180]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
placeholder_text = "promotion message"

22
UI/Controls/users.gd Normal file
View file

@ -0,0 +1,22 @@
extends PanelContainer
@onready var itu := %InternalTwitchUserInfo
@onready var iul := %InternalUserLive
# Called when the node enters the scene tree for the first time.
func _ready() -> void:
%SearchTwitchUser.users_updated.connect(%UserList.update_list)
%UserList.populate_list()
%UserList.user_selected.connect(_handle_user_selected)
set_tab_names()
func set_tab_names() -> void:
for pnl in %Tabs.get_children():
var title := pnl.name.trim_prefix("Internal").capitalize()
%Tabs.set_tab_title(pnl.get_index(), title)
func _handle_user_selected(user: Chatter) -> void:
itu.chatter = user
iul.chatter = user
%Tabs.current_tab = 0

1
UI/Controls/users.gd.uid Normal file
View file

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

53
UI/Controls/users.tscn Normal file
View file

@ -0,0 +1,53 @@
[gd_scene format=3 uid="uid://b7ubexvohw507"]
[ext_resource type="Script" uid="uid://ykgacitkm8qw" path="res://UI/Controls/users.gd" id="1_8sme5"]
[ext_resource type="PackedScene" uid="uid://cdm7rbq5547xp" path="res://UI/Controls/user_list.tscn" id="1_wmtpy"]
[ext_resource type="PackedScene" uid="uid://2ifvkmqd0q3i" path="res://UI/Controls/search_twitch_user.tscn" id="2_hl8i0"]
[ext_resource type="PackedScene" uid="uid://d3fhwrt28r08x" path="res://UI/Controls/internal_twitch_user_info.tscn" id="4_jqw2j"]
[ext_resource type="PackedScene" uid="uid://bipoye4ww4ua6" path="res://UI/Controls/internal_user_live.tscn" id="5_8sme5"]
[node name="Users" type="PanelContainer" unique_id=823449222]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1_8sme5")
metadata/_tab_index = 1
[node name="HSplitContainer" type="HSplitContainer" parent="." unique_id=1436900852]
layout_mode = 2
split_offsets = PackedInt32Array(400)
split_offset = 400
[node name="UserList" parent="HSplitContainer" unique_id=1448466108 instance=ExtResource("1_wmtpy")]
unique_name_in_owner = true
layout_mode = 2
[node name="UserManager" type="PanelContainer" parent="HSplitContainer" unique_id=1405139410]
layout_mode = 2
[node name="VBoxContainer" type="VBoxContainer" parent="HSplitContainer/UserManager" unique_id=558998019]
layout_mode = 2
[node name="SearchTwitchUser" parent="HSplitContainer/UserManager/VBoxContainer" unique_id=83637811 instance=ExtResource("2_hl8i0")]
unique_name_in_owner = true
layout_mode = 2
[node name="HSeparator" type="HSeparator" parent="HSplitContainer/UserManager/VBoxContainer" unique_id=195418375]
layout_mode = 2
[node name="Tabs" type="TabContainer" parent="HSplitContainer/UserManager/VBoxContainer" unique_id=140135258]
unique_name_in_owner = true
custom_minimum_size = Vector2(0, 300)
layout_mode = 2
current_tab = 0
[node name="InternalTwitchUserInfo" parent="HSplitContainer/UserManager/VBoxContainer/Tabs" unique_id=1986869556 instance=ExtResource("4_jqw2j")]
unique_name_in_owner = true
layout_mode = 2
[node name="InternalUserLive" parent="HSplitContainer/UserManager/VBoxContainer/Tabs" unique_id=17956466 instance=ExtResource("5_8sme5")]
unique_name_in_owner = true
visible = false
layout_mode = 2

BIN
UI/assets/loading.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

View file

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://btyra86eut5se"
path="res://.godot/imported/loading.png-4fc8cc04835bfef30cc2af9f2713a390.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://UI/assets/loading.png"
dest_files=["res://.godot/imported/loading.png-4fc8cc04835bfef30cc2af9f2713a390.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
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/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
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

24
UI/assets/main_theme.tres Normal file
View file

@ -0,0 +1,24 @@
[gd_resource type="Theme" format=3 uid="uid://dh11pgqmtpeig"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_dfugh"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_hlhir"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_dfugh"]
bg_color = Color(1, 1, 1, 0.40392157)
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_hlhir"]
bg_color = Color(1, 1, 1, 0.57254905)
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_ay4fc"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_u7u7u"]
[resource]
BlankButton/base_type = &"Button"
BlankButton/styles/disabled = SubResource("StyleBoxEmpty_dfugh")
BlankButton/styles/focus = SubResource("StyleBoxEmpty_hlhir")
BlankButton/styles/hover = SubResource("StyleBoxFlat_dfugh")
BlankButton/styles/hover_pressed = SubResource("StyleBoxFlat_hlhir")
BlankButton/styles/normal = SubResource("StyleBoxEmpty_ay4fc")
BlankButton/styles/pressed = SubResource("StyleBoxEmpty_u7u7u")

View file

@ -6,6 +6,18 @@
[ext_resource type="FontFile" uid="uid://b6u8lwedqawa7" path="res://UI/assets/neon-wave-theme/polentical_neon/Polentical Neon Bold italic.ttf" id="4_0diuh"]
[ext_resource type="FontFile" uid="uid://cx1a4aqqxhsrn" path="res://UI/assets/neon-wave-theme/polentical_neon/Polentical Neon Italic.ttf" id="5_xqdwr"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_xf6ah"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_0diuh"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_xqdwr"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_j1ev3"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_7gyuu"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_3cibo"]
[sub_resource type="StyleBoxTexture" id="8"]
content_margin_top = 7.0
content_margin_bottom = 7.0
@ -664,6 +676,13 @@ cache/0/36/0/scale = 1.0
[resource]
default_font = SubResource("9")
default_font_size = 16
BlankButton/base_type = &"Button"
BlankButton/styles/disabled = SubResource("StyleBoxEmpty_xf6ah")
BlankButton/styles/focus = SubResource("StyleBoxEmpty_0diuh")
BlankButton/styles/hover = SubResource("StyleBoxEmpty_xqdwr")
BlankButton/styles/hover_pressed = SubResource("StyleBoxEmpty_j1ev3")
BlankButton/styles/normal = SubResource("StyleBoxEmpty_7gyuu")
BlankButton/styles/pressed = SubResource("StyleBoxEmpty_3cibo")
Button/colors/font_color = Color(0.752941, 0.992157, 0.988235, 1)
Button/colors/font_color_hover = Color(0.443137, 1, 0.941176, 1)
Button/colors/font_color_pressed = Color(0.180392, 0.0666667, 0.572549, 1)

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

View file

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://bu2juj2beyws7"
path="res://.godot/imported/twitch_user_profile_pic.png-84b4929ded0b3c7ca1c948ecfe563915.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://UI/assets/twitch_user_profile_pic.png"
dest_files=["res://.godot/imported/twitch_user_profile_pic.png-84b4929ded0b3c7ca1c948ecfe563915.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
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/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
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

View file

@ -172,9 +172,8 @@ func _handle_settings() -> void:
win.always_on_top = false
win.mode = Window.MODE_WINDOWED
var pnl := SETTINGS_PANEL.instantiate()
#Globals.disable_mouse_passthrough()
win.add_child(pnl)
win.size = Vector2i(1000,800)
win.size = Vector2i(1530,800)
win.close_requested.connect(func(): win.queue_free(); _set_win = null)
win.name = "SettingsWindow"
win.title = "Settings"

View file

@ -1,32 +1,15 @@
[gd_scene format=3 uid="uid://cgr17lw8rfgkx"]
[ext_resource type="Script" uid="uid://dotqpotld2j34" path="res://UI/settings_panel.gd" id="1_3whce"]
[ext_resource type="PackedScene" uid="uid://b7ubexvohw507" path="res://UI/Controls/users.tscn" id="3_3whce"]
[ext_resource type="PackedScene" uid="uid://diloovqtdspia" path="res://UI/Controls/general_panel.tscn" id="4_tlshb"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_o0fp7"]
content_margin_left = 0.0
content_margin_top = 0.0
content_margin_right = 0.0
content_margin_bottom = 0.0
bg_color = Color(0.1, 0.1, 0.1, 0.6)
border_width_left = 4
border_width_top = 4
border_width_right = 4
border_width_bottom = 4
border_color = Color(0.18497416, 0.5543967, 0.8036863, 1)
corner_radius_top_left = 10
corner_radius_top_right = 10
corner_radius_bottom_right = 10
corner_radius_bottom_left = 10
corner_detail = 5
[node name="SettingsPanel" type="PanelContainer" unique_id=570683183]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme_override_styles/panel = SubResource("StyleBoxFlat_o0fp7")
script = ExtResource("1_3whce")
[node name="MarginContainer" type="MarginContainer" parent="." unique_id=1895715421]
@ -47,10 +30,9 @@ current_tab = 0
[node name="General" parent="MarginContainer/VBoxContainer/TabContainer" unique_id=1071328377 instance=ExtResource("4_tlshb")]
layout_mode = 2
[node name="Users" type="PanelContainer" parent="MarginContainer/VBoxContainer/TabContainer" unique_id=244819974]
[node name="Users" parent="MarginContainer/VBoxContainer/TabContainer" unique_id=823449222 instance=ExtResource("3_3whce")]
visible = false
layout_mode = 2
metadata/_tab_index = 1
[node name="Music" type="PanelContainer" parent="MarginContainer/VBoxContainer/TabContainer" unique_id=22912491]
visible = false

View file

@ -552,7 +552,7 @@ func _iter_next(iter: Array) -> bool:
func _iter_get(iter: Variant) -> Variant:
if {data_variable_name}.size() - 1 == _cur_iter && _has_pagination():
if {data_variable_name}.size() == _cur_iter && _has_pagination():
await next_page()
return iter"""
var copy_code: String

View file

@ -79,7 +79,7 @@ class Response extends TwitchData:
func _iter_get(iter: Variant) -> Variant:
if data.size() - 1 == _cur_iter && _has_pagination():
if data.size() == _cur_iter && _has_pagination():
await next_page()
return iter

View file

@ -79,7 +79,7 @@ class Response extends TwitchData:
func _iter_get(iter: Variant) -> Variant:
if data.size() - 1 == _cur_iter && _has_pagination():
if data.size() == _cur_iter && _has_pagination():
await next_page()
return iter

View file

@ -79,7 +79,7 @@ class Response extends TwitchData:
func _iter_get(iter: Variant) -> Variant:
if data.size() - 1 == _cur_iter && _has_pagination():
if data.size() == _cur_iter && _has_pagination():
await next_page()
return iter

View file

@ -99,7 +99,7 @@ class Response extends TwitchData:
func _iter_get(iter: Variant) -> Variant:
if data.size() - 1 == _cur_iter && _has_pagination():
if data.size() == _cur_iter && _has_pagination():
await next_page()
return iter

View file

@ -89,7 +89,7 @@ class Response extends TwitchData:
func _iter_get(iter: Variant) -> Variant:
if data.size() - 1 == _cur_iter && _has_pagination():
if data.size() == _cur_iter && _has_pagination():
await next_page()
return iter

View file

@ -146,7 +146,7 @@ class ResponseData extends TwitchData:
func _iter_get(iter: Variant) -> Variant:
if segments.size() - 1 == _cur_iter && _has_pagination():
if segments.size() == _cur_iter && _has_pagination():
await next_page()
return iter

View file

@ -79,7 +79,7 @@ class Response extends TwitchData:
func _iter_get(iter: Variant) -> Variant:
if data.size() - 1 == _cur_iter && _has_pagination():
if data.size() == _cur_iter && _has_pagination():
await next_page()
return iter

View file

@ -89,7 +89,7 @@ class Response extends TwitchData:
func _iter_get(iter: Variant) -> Variant:
if data.size() - 1 == _cur_iter && _has_pagination():
if data.size() == _cur_iter && _has_pagination():
await next_page()
return iter

View file

@ -79,7 +79,7 @@ class Response extends TwitchData:
func _iter_get(iter: Variant) -> Variant:
if data.size() - 1 == _cur_iter && _has_pagination():
if data.size() == _cur_iter && _has_pagination():
await next_page()
return iter

View file

@ -79,7 +79,7 @@ class Response extends TwitchData:
func _iter_get(iter: Variant) -> Variant:
if data.size() - 1 == _cur_iter && _has_pagination():
if data.size() == _cur_iter && _has_pagination():
await next_page()
return iter

View file

@ -79,7 +79,7 @@ class Response extends TwitchData:
func _iter_get(iter: Variant) -> Variant:
if data.size() - 1 == _cur_iter && _has_pagination():
if data.size() == _cur_iter && _has_pagination():
await next_page()
return iter

View file

@ -79,7 +79,7 @@ class Response extends TwitchData:
func _iter_get(iter: Variant) -> Variant:
if data.size() - 1 == _cur_iter && _has_pagination():
if data.size() == _cur_iter && _has_pagination():
await next_page()
return iter

View file

@ -109,7 +109,7 @@ class Response extends TwitchData:
func _iter_get(iter: Variant) -> Variant:
if data.size() - 1 == _cur_iter && _has_pagination():
if data.size() == _cur_iter && _has_pagination():
await next_page()
return iter

View file

@ -79,7 +79,7 @@ class Response extends TwitchData:
func _iter_get(iter: Variant) -> Variant:
if data.size() - 1 == _cur_iter && _has_pagination():
if data.size() == _cur_iter && _has_pagination():
await next_page()
return iter

View file

@ -78,7 +78,7 @@ class Response extends TwitchData:
func _iter_get(iter: Variant) -> Variant:
if data.size() - 1 == _cur_iter && _has_pagination():
if data.size() == _cur_iter && _has_pagination():
await next_page()
return iter

View file

@ -79,7 +79,7 @@ class Response extends TwitchData:
func _iter_get(iter: Variant) -> Variant:
if data.size() - 1 == _cur_iter && _has_pagination():
if data.size() == _cur_iter && _has_pagination():
await next_page()
return iter

View file

@ -89,7 +89,7 @@ class Response extends TwitchData:
func _iter_get(iter: Variant) -> Variant:
if data.size() - 1 == _cur_iter && _has_pagination():
if data.size() == _cur_iter && _has_pagination():
await next_page()
return iter
@ -213,4 +213,4 @@ class Opt extends TwitchData:
if d.get("after", null) != null:
result.after = d["after"]
return result

View file

@ -79,7 +79,7 @@ class Response extends TwitchData:
func _iter_get(iter: Variant) -> Variant:
if data.size() - 1 == _cur_iter && _has_pagination():
if data.size() == _cur_iter && _has_pagination():
await next_page()
return iter
@ -141,4 +141,4 @@ class Opt extends TwitchData:
if d.get("after", null) != null:
result.after = d["after"]
return result

View file

@ -79,7 +79,7 @@ class Response extends TwitchData:
func _iter_get(iter: Variant) -> Variant:
if data.size() - 1 == _cur_iter && _has_pagination():
if data.size() == _cur_iter && _has_pagination():
await next_page()
return iter
@ -185,4 +185,4 @@ class Opt extends TwitchData:
if d.get("after", null) != null:
result.after = d["after"]
return result

View file

@ -79,7 +79,7 @@ class Response extends TwitchData:
func _iter_get(iter: Variant) -> Variant:
if data.size() - 1 == _cur_iter && _has_pagination():
if data.size() == _cur_iter && _has_pagination():
await next_page()
return iter

View file

@ -79,7 +79,7 @@ class Response extends TwitchData:
func _iter_get(iter: Variant) -> Variant:
if data.size() - 1 == _cur_iter && _has_pagination():
if data.size() == _cur_iter && _has_pagination():
await next_page()
return iter
@ -188,4 +188,4 @@ class Opt extends TwitchData:
if d.get("first", null) != null:
result.first = d["first"]
return result

View file

@ -79,7 +79,7 @@ class Response extends TwitchData:
func _iter_get(iter: Variant) -> Variant:
if data.size() - 1 == _cur_iter && _has_pagination():
if data.size() == _cur_iter && _has_pagination():
await next_page()
return iter
@ -152,4 +152,4 @@ class Opt extends TwitchData:
if d.get("after", null) != null:
result.after = d["after"]
return result

View file

@ -79,7 +79,7 @@ class Response extends TwitchData:
func _iter_get(iter: Variant) -> Variant:
if data.size() - 1 == _cur_iter && _has_pagination():
if data.size() == _cur_iter && _has_pagination():
await next_page()
return iter
@ -152,4 +152,4 @@ class Opt extends TwitchData:
if d.get("after", null) != null:
result.after = d["after"]
return result

View file

@ -79,7 +79,7 @@ class Response extends TwitchData:
func _iter_get(iter: Variant) -> Variant:
if data.size() - 1 == _cur_iter && _has_pagination():
if data.size() == _cur_iter && _has_pagination():
await next_page()
return iter
@ -150,4 +150,4 @@ class Opt extends TwitchData:
if d.get("after", null) != null:
result.after = d["after"]
return result

View file

@ -79,7 +79,7 @@ class Response extends TwitchData:
func _iter_get(iter: Variant) -> Variant:
if data.size() - 1 == _cur_iter && _has_pagination():
if data.size() == _cur_iter && _has_pagination():
await next_page()
return iter
@ -169,4 +169,4 @@ class Opt extends TwitchData:
if d.get("after", null) != null:
result.after = d["after"]
return result

View file

@ -79,7 +79,7 @@ class Response extends TwitchData:
func _iter_get(iter: Variant) -> Variant:
if data.size() - 1 == _cur_iter && _has_pagination():
if data.size() == _cur_iter && _has_pagination():
await next_page()
return iter
@ -200,4 +200,4 @@ class Opt extends TwitchData:
if d.get("after", null) != null:
result.after = d["after"]
return result

View file

@ -79,7 +79,7 @@ class Response extends TwitchData:
func _iter_get(iter: Variant) -> Variant:
if data.size() - 1 == _cur_iter && _has_pagination():
if data.size() == _cur_iter && _has_pagination():
await next_page()
return iter
@ -149,4 +149,4 @@ class Opt extends TwitchData:
if d.get("before", null) != null:
result.before = d["before"]
return result

View file

@ -79,7 +79,7 @@ class Response extends TwitchData:
func _iter_get(iter: Variant) -> Variant:
if data.size() - 1 == _cur_iter && _has_pagination():
if data.size() == _cur_iter && _has_pagination():
await next_page()
return iter
@ -308,4 +308,4 @@ class Opt extends TwitchData:
if d.get("first", null) != null:
result.first = d["first"]
return result

View file

@ -79,7 +79,7 @@ class Response extends TwitchData:
func _iter_get(iter: Variant) -> Variant:
if data.size() - 1 == _cur_iter && _has_pagination():
if data.size() == _cur_iter && _has_pagination():
await next_page()
return iter
@ -141,4 +141,4 @@ class Opt extends TwitchData:
if d.get("after", null) != null:
result.after = d["after"]
return result

View file

@ -93,7 +93,7 @@ class Response extends TwitchData:
func _iter_get(iter: Variant) -> Variant:
if data.size() - 1 == _cur_iter && _has_pagination():
if data.size() == _cur_iter && _has_pagination():
await next_page()
return iter
@ -277,4 +277,4 @@ class Opt extends TwitchData:
if d.get("broadcaster_id", null) != null:
result.broadcaster_id = d["broadcaster_id"]
return result

View file

@ -79,7 +79,7 @@ class Response extends TwitchData:
func _iter_get(iter: Variant) -> Variant:
if data.size() - 1 == _cur_iter && _has_pagination():
if data.size() == _cur_iter && _has_pagination():
await next_page()
return iter
@ -107,4 +107,4 @@ class ResponsePagination extends TwitchData:
if d.get("cursor", null) != null:
result.cursor = d["cursor"]
return result

View file

@ -79,7 +79,7 @@ class Response extends TwitchData:
func _iter_get(iter: Variant) -> Variant:
if data.size() - 1 == _cur_iter && _has_pagination():
if data.size() == _cur_iter && _has_pagination():
await next_page()
return iter
@ -246,4 +246,4 @@ class Opt extends TwitchData:
if d.get("before", null) != null:
result.before = d["before"]
return result

View file

@ -79,7 +79,7 @@ class Response extends TwitchData:
func _iter_get(iter: Variant) -> Variant:
if data.size() - 1 == _cur_iter && _has_pagination():
if data.size() == _cur_iter && _has_pagination():
await next_page()
return iter
@ -141,4 +141,4 @@ class Opt extends TwitchData:
if d.get("after", null) != null:
result.after = d["after"]
return result

View file

@ -79,7 +79,7 @@ class Response extends TwitchData:
func _iter_get(iter: Variant) -> Variant:
if data.size() - 1 == _cur_iter && _has_pagination():
if data.size() == _cur_iter && _has_pagination():
await next_page()
return iter
@ -149,4 +149,4 @@ class Opt extends TwitchData:
if d.get("after", null) != null:
result.after = d["after"]
return result

View file

@ -9,3 +9,9 @@ func _init() -> void:
chatters = DbSet.new(Chatter)
itch_apps = DbSet.new(ItchIOAppData)
steam_apps = DbSet.new(SteamAppData)
func get_known_streamers() -> Array[Chatter]:
var known: Array[Chatter] = []
for chatter: Chatter in chatters.find_many(Condition.new().equal("is_streamer", true)):
known.append(chatter)
return known

View file

@ -37,12 +37,16 @@ func _handle_message(message: TwitchChatMessage) -> void:
chatter = Globals.context.chatters.find_one(Condition.new().equal("twitch_id", id))
if not chatter:
chatter = Chatter.new()
chatter.twitch_id = id
chatter.first_seen = Time.get_unix_time_from_system()
chatter.last_seen = chatter.first_seen
chatter.twitch_id = user.id
chatter.user = user
chatter.first_added = Time.get_unix_time_from_system()
chatter.first_seen = chatter.first_added
chatter.last_seen = chatter.first_added
Globals.context.chatters.append(chatter)
first_seen = true
else:
if chatter.first_seen == -1.0:
chatter.first_seen = Time.get_unix_time_from_system()
chatter.last_seen = Time.get_unix_time_from_system()
chatter.save()
first_msg = true

View file

@ -1,14 +1,30 @@
extends Node
var twitcher: TwitcherExtended
#region Signals
signal live_streamers_updating
signal live_streamers_updated
#endregion
#region Public Variables
var twitcher: TwitcherExtended:
set(val):
twitcher = val
twitcher.streamer_token_validated.connect(_setup_live_stream_timer)
var context: OverlayContext
var settings: OverlaySettings
var main_win: OverlayWindow
var live_streamers: Dictionary[String, TwitchStream] = {}
#endregion
#region Private Variables
var _pt_except: Array[Control] = []
var _pt_mouse: bool = false
var _current_pt_mask: PackedVector2Array = []
var _hull_points: PackedVector2Array = []
var _debug_draw: DebugDraw
var _tmr_live_stream: Timer
#endregion
# Called when the node enters the scene tree for the first time.
func _ready() -> void:
@ -38,6 +54,25 @@ func _input(_event: InputEvent) -> void:
_debug_draw = DebugDraw.new()
add_child(_debug_draw)
func _setup_live_stream_timer() -> void:
if _tmr_live_stream:
return
_tmr_live_stream = Timer.new()
_tmr_live_stream.name = "TimerLiveStreamCheck"
_tmr_live_stream.wait_time = 240
_tmr_live_stream.one_shot = false
_tmr_live_stream.timeout.connect(_run_live_streamer_update)
add_child(_tmr_live_stream)
_run_live_streamer_update()
_tmr_live_stream.start()
func _run_live_streamer_update() -> void:
live_streamers_updating.emit()
live_streamers = await twitcher.get_live_streamers_data()
live_streamers_updated.emit()
func save_settings() -> void:
ResourceSaver.save(settings, "user://settings.tres")

View file

@ -14,23 +14,40 @@ enum ChatterLevel
@export var id: int
@export var twitch_id: String
@export var nickname: String = ""
@export var user_data: Dictionary:
set(value):
user_data = value
if user == null:
user = TwitchUser.from_json(user_data)
@export var first_added: float = 0.0
@export var first_seen: float = 0.0
@export var last_seen: float = 0.0
@export var known_engine: String = ""
@export var steam_games: Array[int] = []
@export var itch_games: Dictionary[String, String] = {}
@export var urls: Dictionary[String, String] = {}
@export var is_indie_game_dev: bool = false
@export var is_on_team: bool = false
@export var level: ChatterLevel = ChatterLevel.NEW
@export var is_streamer: bool = false
@export var auto_shoutout: bool = false
@export var shoutout_as_devteam: bool = false
@export var promo_msg: String = ""
@export var level: ChatterLevel = ChatterLevel.NEW
@export var notes: String = ""
@export var scores: Dictionary[String, int] = {}
@export var extra_data: Dictionary[String, Variant] = {}
@export var first_seen: float = 0.0
@export var last_seen: float = 0.0
@export var user: TwitchUser:
get():
if not user and user_data:
user = TwitchUser.from_json(user_data)
return user
set(value):
user = value
if user:
user_data = user.to_dict()
static func _setup() -> void:
set_table_name(Chatter, "chatters")
set_column_flags(Chatter, "id", Types.Flags.PRIMARY_KEY|Types.Flags.AUTO_INCREMENT|Types.Flags.NOT_NULL)
set_column_flags(Chatter, "twitch_id", Types.Flags.NOT_NULL)
ignore_column(Chatter, "user")

View file

@ -4,6 +4,8 @@ class_name TwitcherExtended
#region Signals
signal _waiting_for_authentication
signal streamer_token_validated()
signal chatbot_token_validated()
#endregion
#region Constants
@ -65,6 +67,7 @@ var chatbot_token_loaded: bool = false
var _cache_users: Dictionary[String, TwitchUser] = {}
var _log: TwitchLogger = TwitchLogger.new("TwitcherExtended")
var _commands: Dictionary[String, TwitchCommand] = {}
var _is_processing_streams: bool = false
#endregion
enum AuthStatus {
@ -195,6 +198,7 @@ func load_streamer_token() -> AuthStatus:
_log.d("Token authorized")
streamer_user = await service.get_current_user()
streamer_token_loaded = true
streamer_token_validated.emit()
return AuthStatus.AUTHORIZED
if streamer_token.has_refresh_token():
_log.d("Token needs refreshed")
@ -259,6 +263,7 @@ func load_chatbot_token() -> AuthStatus:
_log.d("Token authroized")
bot_user = await chatbot_auth.get_user()
chatbot_token_loaded = true
chatbot_token_validated.emit()
return AuthStatus.AUTHORIZED
if chatbot_token.has_refresh_token():
_log.d("Token needs refreshed")
@ -287,7 +292,7 @@ func setup_chatbot() -> bool:
return res
#endregion
#region Public API shared between both Streamer and Chatbot as needed.
#region Public API shared between both Streamer and Chatbot as needed. (Twitcher standard methods)
func send_message(message: String, as_streamer: bool = false) -> void:
if as_streamer:
await chat.send_message(message)
@ -351,16 +356,18 @@ func get_users(...usernames: Array) -> Array[TwitchUser]:
func get_user_by_id(user_id: String) -> TwitchUser:
if _cache_users.has(user_id): return _cache_users[user_id]
var user: TwitchUser = await service.get_user_by_id(user_id)
_cache_users[user_id] = user
_cache_users[user.login] = user
if user:
_cache_users[user_id] = user
_cache_users[user.login] = user
return user
func get_user(username: String) -> TwitchUser:
username = username.trim_prefix("@")
if _cache_users.has(username): return _cache_users[username]
var user: TwitchUser = await service.get_user(username)
_cache_users[user.id] = user
_cache_users[username] = user
if user:
_cache_users[user.id] = user
_cache_users[username] = user
return user
func subscribe_event(definition: TwitchEventsubDefinition, conditions: Dictionary) -> TwitchEventsubConfig:
@ -487,3 +494,42 @@ func get_cheermote_data() -> Array[TwitchCheermote]:
func get_cheermote(definition: TwitchCheermoteDefinition) -> Dictionary:
return await media.get_cheermotes(definition)
#endregion
#region Extended Methods
func get_live_streamers_data(user_ids: Array = []) -> Dictionary[String, TwitchStream]:
if _is_processing_streams:
return {}
_is_processing_streams = true
if user_ids.is_empty():
var known := Globals.context.get_known_streamers()
user_ids = known.map(func(x: Chatter): return x.twitch_id)
var streams_data: Dictionary[String, TwitchStream] = {}
var opt := TwitchGetStreams.Opt.new()
opt.type = "live"
var iter: int = 0
const MAX_ITER = 100
while not user_ids.is_empty():
iter += 1
if iter > MAX_ITER:
_log.e("Reached max iterations while getting live stream data.")
break
var new_batch: Array[String] = []
new_batch.assign(user_ids.slice(0,99))
user_ids = user_ids.slice(99)
opt.user_id = new_batch
var streams_iterator := await api.get_streams(opt)
print("Fetching live...")
for stream_promise in streams_iterator:
var stream_data: TwitchStream = await stream_promise
if stream_data:
print("%s(%s) is live" % [stream_data.user_name, stream_data.user_id])
streams_data[stream_data.user_id] = stream_data
else:
print("WTF!!!! Iter is null?")
print("Fetching is done.")
_is_processing_streams = false
return streams_data
#endregion

View file

@ -5,30 +5,91 @@ func _up() -> void:
# Create Table chatters
table = create_table("chatters")
table.add_column("id", Types.DataType.INT, Types.Flags.PRIMARY_KEY | Types.Flags.NOT_NULL, {})
table.add_column("id", Types.DataType.INT, Types.Flags.PRIMARY_KEY | Types.Flags.AUTO_INCREMENT | Types.Flags.NOT_NULL, {})
table.add_column("twitch_id", Types.DataType.STRING, Types.Flags.NOT_NULL, {})
table.add_column("nickname", Types.DataType.STRING, Types.Flags.NONE, {})
table.add_column("user_data", Types.DataType.DICTIONARY, Types.Flags.NONE, {})
table.add_column("first_added", Types.DataType.REAL, Types.Flags.NONE, {})
table.add_column("first_seen", Types.DataType.REAL, Types.Flags.NONE, {})
table.add_column("last_seen", Types.DataType.REAL, Types.Flags.NONE, {})
table.add_column("known_engine", Types.DataType.STRING, Types.Flags.NONE, {})
table.add_column("steam_games", Types.DataType.ARRAY, Types.Flags.NONE, {})
table.add_column("itch_games", Types.DataType.DICTIONARY, Types.Flags.NONE, {})
table.add_column("urls", Types.DataType.DICTIONARY, Types.Flags.NONE, {})
table.add_column("is_indie_game_dev", Types.DataType.BOOL, Types.Flags.NONE, {})
table.add_column("is_on_team", Types.DataType.BOOL, Types.Flags.NONE, {})
table.add_column("level", Types.DataType.INT, Types.Flags.NONE, {})
table.add_column("is_streamer", Types.DataType.BOOL, Types.Flags.NONE, {})
table.add_column("auto_shoutout", Types.DataType.BOOL, Types.Flags.NONE, {})
table.add_column("shoutout_as_devteam", Types.DataType.BOOL, Types.Flags.NONE, {})
table.add_column("promo_msg", Types.DataType.STRING, Types.Flags.NONE, {})
table.add_column("level", Types.DataType.INT, Types.Flags.NONE, {})
table.add_column("notes", Types.DataType.STRING, Types.Flags.NONE, {})
table.add_column("scores", Types.DataType.DICTIONARY, Types.Flags.NONE, {})
table.add_column("extra_data", Types.DataType.DICTIONARY, Types.Flags.NONE, {})
table.add_column("first_seen", Types.DataType.REAL, Types.Flags.NONE, {})
table.add_column("last_seen", Types.DataType.REAL, Types.Flags.NONE, {})
# Create Table itch_apps
table = create_table("itch_apps")
table.add_column("id", Types.DataType.INT, Types.Flags.PRIMARY_KEY | Types.Flags.NOT_NULL, {})
table.add_column("title", Types.DataType.STRING, Types.Flags.NONE, {})
table.add_column("authors", Types.DataType.ARRAY, Types.Flags.NONE, {})
table.add_column("tags", Types.DataType.ARRAY, Types.Flags.NONE, {})
table.add_column("links", Types.DataType.DICTIONARY, Types.Flags.NONE, {})
table.add_column("cover_image", Types.DataType.STRING, Types.Flags.NONE, {})
table.add_column("price", Types.DataType.STRING, Types.Flags.NONE, {})
table.add_column("description", Types.DataType.STRING, Types.Flags.NONE, {})
table.add_column("screenshots_thumbnails", Types.DataType.ARRAY, Types.Flags.NONE, {})
table.add_column("screenshots_full", Types.DataType.ARRAY, Types.Flags.NONE, {})
table.add_column("added", Types.DataType.REAL, Types.Flags.NONE, {})
# Create Table steam_apps
table = create_table("steam_apps")
table.add_column("steam_app_id", Types.DataType.INT, Types.Flags.PRIMARY_KEY | Types.Flags.NOT_NULL, {})
table.add_column("type", Types.DataType.STRING, Types.Flags.NONE, {})
table.add_column("name", Types.DataType.STRING, Types.Flags.NONE, {})
table.add_column("required_age", Types.DataType.INT, Types.Flags.NONE, {})
table.add_column("is_free", Types.DataType.BOOL, Types.Flags.NONE, {})
table.add_column("short_description", Types.DataType.STRING, Types.Flags.NONE, {})
table.add_column("detailed_description", Types.DataType.STRING, Types.Flags.NONE, {})
table.add_column("header_image", Types.DataType.STRING, Types.Flags.NONE, {})
table.add_column("capsule_image", Types.DataType.STRING, Types.Flags.NONE, {})
table.add_column("capsule_imagev5", Types.DataType.STRING, Types.Flags.NONE, {})
table.add_column("website", Types.DataType.STRING, Types.Flags.NONE, {})
table.add_column("developers", Types.DataType.ARRAY, Types.Flags.NONE, {})
table.add_column("publishers", Types.DataType.ARRAY, Types.Flags.NONE, {})
table.add_column("platforms", Types.DataType.DICTIONARY, Types.Flags.NONE, {})
table.add_column("genres", Types.DataType.ARRAY, Types.Flags.NONE, {})
table.add_column("release_date", Types.DataType.DICTIONARY, Types.Flags.NONE, {})
table.add_column("screenshots_full", Types.DataType.ARRAY, Types.Flags.NONE, {})
table.add_column("screenshots_thumbs", Types.DataType.ARRAY, Types.Flags.NONE, {})
table.add_column("trailer_mp4", Types.DataType.DICTIONARY, Types.Flags.NONE, {})
table.add_column("trailer_webm", Types.DataType.DICTIONARY, Types.Flags.NONE, {})
table.add_column("trailer_thumbnail", Types.DataType.STRING, Types.Flags.NONE, {})
table.add_column("price_currency", Types.DataType.STRING, Types.Flags.NONE, {})
table.add_column("price_initial", Types.DataType.INT, Types.Flags.NONE, {})
table.add_column("price_final", Types.DataType.INT, Types.Flags.NONE, {})
table.add_column("price_discount_percent", Types.DataType.INT, Types.Flags.NONE, {})
table.add_column("price_initial_formatted", Types.DataType.STRING, Types.Flags.NONE, {})
table.add_column("price_final_formatted", Types.DataType.STRING, Types.Flags.NONE, {})
table.add_column("categories", Types.DataType.ARRAY, Types.Flags.NONE, {})
table.add_column("dlc", Types.DataType.ARRAY, Types.Flags.NONE, {})
table.add_column("packages", Types.DataType.ARRAY, Types.Flags.NONE, {})
table.add_column("total_achievements", Types.DataType.INT, Types.Flags.NONE, {})
table.add_column("highlighted_achievements", Types.DataType.ARRAY, Types.Flags.NONE, {})
table.add_column("pc_requirements", Types.DataType.STRING, Types.Flags.NONE, {})
table.add_column("mac_requirements", Types.DataType.STRING, Types.Flags.NONE, {})
table.add_column("linux_requirements", Types.DataType.STRING, Types.Flags.NONE, {})
table.add_column("support_email", Types.DataType.STRING, Types.Flags.NONE, {})
table.add_column("support_url", Types.DataType.STRING, Types.Flags.NONE, {})
table.add_column("background_image", Types.DataType.STRING, Types.Flags.NONE, {})
table.add_column("background_image_raw", Types.DataType.STRING, Types.Flags.NONE, {})
func _down() -> void:
# Drop Table chatters
drop_table("chatters")
# Drop Table itch_apps
drop_table("itch_apps")
# Drop Table steam_apps
drop_table("steam_apps")

View file

@ -1 +1 @@
uid://byvn338f077hm
uid://biw7xekbciqwi

View file

@ -1,68 +0,0 @@
extends Migration
func _up() -> void:
# Insert upgrade instructions to modify database structure below
# Create Table itch_apps
var table := create_table("itch_apps")
table.add_column("id", Types.DataType.INT, Types.Flags.PRIMARY_KEY | Types.Flags.NOT_NULL, {})
table.add_column("title", Types.DataType.STRING, Types.Flags.NONE, {})
table.add_column("authors", Types.DataType.ARRAY, Types.Flags.NONE, {})
table.add_column("tags", Types.DataType.ARRAY, Types.Flags.NONE, {})
table.add_column("links", Types.DataType.DICTIONARY, Types.Flags.NONE, {})
table.add_column("cover_image", Types.DataType.STRING, Types.Flags.NONE, {})
table.add_column("price", Types.DataType.STRING, Types.Flags.NONE, {})
table.add_column("description", Types.DataType.STRING, Types.Flags.NONE, {})
table.add_column("screenshots_thumbnails", Types.DataType.ARRAY, Types.Flags.NONE, {})
table.add_column("screenshots_full", Types.DataType.ARRAY, Types.Flags.NONE, {})
table.add_column("added", Types.DataType.REAL, Types.Flags.NONE, {})
# Create Table steam_apps
table = create_table("steam_apps")
table.add_column("steam_app_id", Types.DataType.INT, Types.Flags.PRIMARY_KEY | Types.Flags.NOT_NULL, {})
table.add_column("type", Types.DataType.STRING, Types.Flags.NONE, {})
table.add_column("name", Types.DataType.STRING, Types.Flags.NONE, {})
table.add_column("required_age", Types.DataType.INT, Types.Flags.NONE, {})
table.add_column("is_free", Types.DataType.BOOL, Types.Flags.NONE, {})
table.add_column("short_description", Types.DataType.STRING, Types.Flags.NONE, {})
table.add_column("detailed_description", Types.DataType.STRING, Types.Flags.NONE, {})
table.add_column("header_image", Types.DataType.STRING, Types.Flags.NONE, {})
table.add_column("capsule_image", Types.DataType.STRING, Types.Flags.NONE, {})
table.add_column("capsule_imagev5", Types.DataType.STRING, Types.Flags.NONE, {})
table.add_column("website", Types.DataType.STRING, Types.Flags.NONE, {})
table.add_column("developers", Types.DataType.ARRAY, Types.Flags.NONE, {})
table.add_column("publishers", Types.DataType.ARRAY, Types.Flags.NONE, {})
table.add_column("platforms", Types.DataType.DICTIONARY, Types.Flags.NONE, {})
table.add_column("genres", Types.DataType.ARRAY, Types.Flags.NONE, {})
table.add_column("release_date", Types.DataType.DICTIONARY, Types.Flags.NONE, {})
table.add_column("screenshots_full", Types.DataType.ARRAY, Types.Flags.NONE, {})
table.add_column("screenshots_thumbs", Types.DataType.ARRAY, Types.Flags.NONE, {})
table.add_column("trailer_mp4", Types.DataType.DICTIONARY, Types.Flags.NONE, {})
table.add_column("trailer_webm", Types.DataType.DICTIONARY, Types.Flags.NONE, {})
table.add_column("trailer_thumbnail", Types.DataType.STRING, Types.Flags.NONE, {})
table.add_column("price_currency", Types.DataType.STRING, Types.Flags.NONE, {})
table.add_column("price_initial", Types.DataType.INT, Types.Flags.NONE, {})
table.add_column("price_final", Types.DataType.INT, Types.Flags.NONE, {})
table.add_column("price_discount_percent", Types.DataType.INT, Types.Flags.NONE, {})
table.add_column("price_initial_formatted", Types.DataType.STRING, Types.Flags.NONE, {})
table.add_column("price_final_formatted", Types.DataType.STRING, Types.Flags.NONE, {})
table.add_column("categories", Types.DataType.ARRAY, Types.Flags.NONE, {})
table.add_column("dlc", Types.DataType.ARRAY, Types.Flags.NONE, {})
table.add_column("packages", Types.DataType.ARRAY, Types.Flags.NONE, {})
table.add_column("total_achievements", Types.DataType.INT, Types.Flags.NONE, {})
table.add_column("highlighted_achievements", Types.DataType.ARRAY, Types.Flags.NONE, {})
table.add_column("pc_requirements", Types.DataType.STRING, Types.Flags.NONE, {})
table.add_column("mac_requirements", Types.DataType.STRING, Types.Flags.NONE, {})
table.add_column("linux_requirements", Types.DataType.STRING, Types.Flags.NONE, {})
table.add_column("support_email", Types.DataType.STRING, Types.Flags.NONE, {})
table.add_column("support_url", Types.DataType.STRING, Types.Flags.NONE, {})
table.add_column("background_image", Types.DataType.STRING, Types.Flags.NONE, {})
table.add_column("background_image_raw", Types.DataType.STRING, Types.Flags.NONE, {})
pass
func _down() -> void:
# Insert downgrade instructions to modify database structure below.
# Drop Table itch_apps
drop_table("itch_apps")
# Drop Table steam_apps
drop_table("steam_apps")

View file

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

View file

@ -43,10 +43,6 @@ window/per_pixel_transparency/allowed=true
enabled=PackedStringArray("res://addons/gdata_orm/plugin.cfg", "res://addons/gde_gozen/plugin.cfg", "res://addons/kenny_spritesheet_importer/plugin.cfg", "res://addons/no-obs-ws/plugin.cfg", "res://addons/sc_editor/plugin.cfg", "res://addons/twitcher/plugin.cfg")
[gui]
theme/custom="uid://u5qh1r677ec2"
[input]
spawn_avatar={