diff --git a/UI/Controls/chat_box.gd b/UI/Controls/chat_box.gd index 2e5403ce..898e61a3 100644 --- a/UI/Controls/chat_box.gd +++ b/UI/Controls/chat_box.gd @@ -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) diff --git a/UI/Controls/general_panel.tscn b/UI/Controls/general_panel.tscn index 35e1cf0d..b0e8d6cb 100644 --- a/UI/Controls/general_panel.tscn +++ b/UI/Controls/general_panel.tscn @@ -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") diff --git a/UI/Controls/internal_twitch_user_info.gd b/UI/Controls/internal_twitch_user_info.gd new file mode 100644 index 00000000..990e0124 --- /dev/null +++ b/UI/Controls/internal_twitch_user_info.gd @@ -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 diff --git a/UI/Controls/internal_twitch_user_info.gd.uid b/UI/Controls/internal_twitch_user_info.gd.uid new file mode 100644 index 00000000..6d2521d7 --- /dev/null +++ b/UI/Controls/internal_twitch_user_info.gd.uid @@ -0,0 +1 @@ +uid://bbyomfy4iqbq3 diff --git a/UI/Controls/internal_twitch_user_info.tscn b/UI/Controls/internal_twitch_user_info.tscn new file mode 100644 index 00000000..6a02b2a2 --- /dev/null +++ b/UI/Controls/internal_twitch_user_info.tscn @@ -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 diff --git a/UI/Controls/internal_user_live.gd b/UI/Controls/internal_user_live.gd new file mode 100644 index 00000000..6fb19b05 --- /dev/null +++ b/UI/Controls/internal_user_live.gd @@ -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 diff --git a/UI/Controls/internal_user_live.gd.uid b/UI/Controls/internal_user_live.gd.uid new file mode 100644 index 00000000..de49b705 --- /dev/null +++ b/UI/Controls/internal_user_live.gd.uid @@ -0,0 +1 @@ +uid://cdtfvedghei5f diff --git a/UI/Controls/internal_user_live.tscn b/UI/Controls/internal_user_live.tscn new file mode 100644 index 00000000..2d170619 --- /dev/null +++ b/UI/Controls/internal_user_live.tscn @@ -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" diff --git a/UI/Controls/loading_simple.gd b/UI/Controls/loading_simple.gd new file mode 100644 index 00000000..515b5d0d --- /dev/null +++ b/UI/Controls/loading_simple.gd @@ -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) diff --git a/UI/Controls/loading_simple.gd.uid b/UI/Controls/loading_simple.gd.uid new file mode 100644 index 00000000..8b14aa42 --- /dev/null +++ b/UI/Controls/loading_simple.gd.uid @@ -0,0 +1 @@ +uid://bkhjets1gnsk2 diff --git a/UI/Controls/loading_simple.tscn b/UI/Controls/loading_simple.tscn new file mode 100644 index 00000000..7a5c3af6 --- /dev/null +++ b/UI/Controls/loading_simple.tscn @@ -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") diff --git a/UI/Controls/search_twitch_user.gd b/UI/Controls/search_twitch_user.gd new file mode 100644 index 00000000..36639ac9 --- /dev/null +++ b/UI/Controls/search_twitch_user.gd @@ -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 diff --git a/UI/Controls/search_twitch_user.gd.uid b/UI/Controls/search_twitch_user.gd.uid new file mode 100644 index 00000000..8b70926c --- /dev/null +++ b/UI/Controls/search_twitch_user.gd.uid @@ -0,0 +1 @@ +uid://dgd1pmou5c35b diff --git a/UI/Controls/search_twitch_user.tscn b/UI/Controls/search_twitch_user.tscn new file mode 100644 index 00000000..6516bed3 --- /dev/null +++ b/UI/Controls/search_twitch_user.tscn @@ -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 diff --git a/UI/Controls/twitch_user_info.gd b/UI/Controls/twitch_user_info.gd new file mode 100644 index 00000000..f8540b58 --- /dev/null +++ b/UI/Controls/twitch_user_info.gd @@ -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)) diff --git a/UI/Controls/twitch_user_info.gd.uid b/UI/Controls/twitch_user_info.gd.uid new file mode 100644 index 00000000..efbb0286 --- /dev/null +++ b/UI/Controls/twitch_user_info.gd.uid @@ -0,0 +1 @@ +uid://bj86xfgm00ay2 diff --git a/UI/Controls/twitch_user_info.tscn b/UI/Controls/twitch_user_info.tscn new file mode 100644 index 00000000..9cfe01ff --- /dev/null +++ b/UI/Controls/twitch_user_info.tscn @@ -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 diff --git a/UI/Controls/user_entry.gd b/UI/Controls/user_entry.gd new file mode 100644 index 00000000..9619de88 --- /dev/null +++ b/UI/Controls/user_entry.gd @@ -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) diff --git a/UI/Controls/user_entry.gd.uid b/UI/Controls/user_entry.gd.uid new file mode 100644 index 00000000..a46cfbf3 --- /dev/null +++ b/UI/Controls/user_entry.gd.uid @@ -0,0 +1 @@ +uid://ldua0xcjjws4 diff --git a/UI/Controls/user_entry.tscn b/UI/Controls/user_entry.tscn new file mode 100644 index 00000000..7805b1f1 --- /dev/null +++ b/UI/Controls/user_entry.tscn @@ -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 diff --git a/UI/Controls/user_list.gd b/UI/Controls/user_list.gd new file mode 100644 index 00000000..88b4dc7d --- /dev/null +++ b/UI/Controls/user_list.gd @@ -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() diff --git a/UI/Controls/user_list.gd.uid b/UI/Controls/user_list.gd.uid new file mode 100644 index 00000000..72ad9fd4 --- /dev/null +++ b/UI/Controls/user_list.gd.uid @@ -0,0 +1 @@ +uid://bgur6pwnuh27h diff --git a/UI/Controls/user_list.tscn b/UI/Controls/user_list.tscn new file mode 100644 index 00000000..5b0e6ec4 --- /dev/null +++ b/UI/Controls/user_list.tscn @@ -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 diff --git a/UI/Controls/user_promo.gd b/UI/Controls/user_promo.gd new file mode 100644 index 00000000..b2299d97 --- /dev/null +++ b/UI/Controls/user_promo.gd @@ -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() diff --git a/UI/Controls/user_promo.gd.uid b/UI/Controls/user_promo.gd.uid new file mode 100644 index 00000000..94406cd7 --- /dev/null +++ b/UI/Controls/user_promo.gd.uid @@ -0,0 +1 @@ +uid://8frnm7cda0ht diff --git a/UI/Controls/user_promo.tscn b/UI/Controls/user_promo.tscn new file mode 100644 index 00000000..dc11c139 --- /dev/null +++ b/UI/Controls/user_promo.tscn @@ -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" diff --git a/UI/Controls/users.gd b/UI/Controls/users.gd new file mode 100644 index 00000000..e1482d71 --- /dev/null +++ b/UI/Controls/users.gd @@ -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 diff --git a/UI/Controls/users.gd.uid b/UI/Controls/users.gd.uid new file mode 100644 index 00000000..996c641c --- /dev/null +++ b/UI/Controls/users.gd.uid @@ -0,0 +1 @@ +uid://ykgacitkm8qw diff --git a/UI/Controls/users.tscn b/UI/Controls/users.tscn new file mode 100644 index 00000000..b4a59e06 --- /dev/null +++ b/UI/Controls/users.tscn @@ -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 diff --git a/UI/assets/loading.png b/UI/assets/loading.png new file mode 100644 index 00000000..5624f271 Binary files /dev/null and b/UI/assets/loading.png differ diff --git a/UI/assets/loading.png.import b/UI/assets/loading.png.import new file mode 100644 index 00000000..0240a0fc --- /dev/null +++ b/UI/assets/loading.png.import @@ -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 diff --git a/UI/assets/main_theme.tres b/UI/assets/main_theme.tres new file mode 100644 index 00000000..c157f77f --- /dev/null +++ b/UI/assets/main_theme.tres @@ -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") diff --git a/UI/assets/neon-wave-theme/neon-wave.tres b/UI/assets/neon-wave-theme/neon-wave.tres index d370a5bd..b9dcea57 100644 --- a/UI/assets/neon-wave-theme/neon-wave.tres +++ b/UI/assets/neon-wave-theme/neon-wave.tres @@ -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) diff --git a/UI/assets/twitch_user_profile_pic.png b/UI/assets/twitch_user_profile_pic.png new file mode 100644 index 00000000..da80b70b Binary files /dev/null and b/UI/assets/twitch_user_profile_pic.png differ diff --git a/UI/assets/twitch_user_profile_pic.png.import b/UI/assets/twitch_user_profile_pic.png.import new file mode 100644 index 00000000..a26a7e26 --- /dev/null +++ b/UI/assets/twitch_user_profile_pic.png.import @@ -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 diff --git a/UI/floating_menu.gd b/UI/floating_menu.gd index 5580b64c..f74f48d2 100644 --- a/UI/floating_menu.gd +++ b/UI/floating_menu.gd @@ -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" diff --git a/UI/settings_panel.tscn b/UI/settings_panel.tscn index 976e0784..322c83c7 100644 --- a/UI/settings_panel.tscn +++ b/UI/settings_panel.tscn @@ -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 diff --git a/addons/twitcher/editor/api_generator/twitch_api_generator.gd b/addons/twitcher/editor/api_generator/twitch_api_generator.gd index 4fa4dbed..524e2d22 100644 --- a/addons/twitcher/editor/api_generator/twitch_api_generator.gd +++ b/addons/twitcher/editor/api_generator/twitch_api_generator.gd @@ -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 diff --git a/addons/twitcher/generated/twitch_get_all_stream_tags.gd b/addons/twitcher/generated/twitch_get_all_stream_tags.gd index 63e57ef3..a0dd9959 100644 --- a/addons/twitcher/generated/twitch_get_all_stream_tags.gd +++ b/addons/twitcher/generated/twitch_get_all_stream_tags.gd @@ -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 diff --git a/addons/twitcher/generated/twitch_get_banned_users.gd b/addons/twitcher/generated/twitch_get_banned_users.gd index 28a239b6..17af403d 100644 --- a/addons/twitcher/generated/twitch_get_banned_users.gd +++ b/addons/twitcher/generated/twitch_get_banned_users.gd @@ -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 diff --git a/addons/twitcher/generated/twitch_get_blocked_terms.gd b/addons/twitcher/generated/twitch_get_blocked_terms.gd index 7c949dd6..d11a4f79 100644 --- a/addons/twitcher/generated/twitch_get_blocked_terms.gd +++ b/addons/twitcher/generated/twitch_get_blocked_terms.gd @@ -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 diff --git a/addons/twitcher/generated/twitch_get_broadcaster_subscriptions.gd b/addons/twitcher/generated/twitch_get_broadcaster_subscriptions.gd index b1cf953c..9ce53f5e 100644 --- a/addons/twitcher/generated/twitch_get_broadcaster_subscriptions.gd +++ b/addons/twitcher/generated/twitch_get_broadcaster_subscriptions.gd @@ -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 diff --git a/addons/twitcher/generated/twitch_get_channel_followers.gd b/addons/twitcher/generated/twitch_get_channel_followers.gd index 6f06e2ef..63b37d33 100644 --- a/addons/twitcher/generated/twitch_get_channel_followers.gd +++ b/addons/twitcher/generated/twitch_get_channel_followers.gd @@ -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 diff --git a/addons/twitcher/generated/twitch_get_channel_stream_schedule.gd b/addons/twitcher/generated/twitch_get_channel_stream_schedule.gd index 44d4a0a0..f2fbac69 100644 --- a/addons/twitcher/generated/twitch_get_channel_stream_schedule.gd +++ b/addons/twitcher/generated/twitch_get_channel_stream_schedule.gd @@ -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 diff --git a/addons/twitcher/generated/twitch_get_charity_campaign_donations.gd b/addons/twitcher/generated/twitch_get_charity_campaign_donations.gd index eb374a1c..5a21a5b0 100644 --- a/addons/twitcher/generated/twitch_get_charity_campaign_donations.gd +++ b/addons/twitcher/generated/twitch_get_charity_campaign_donations.gd @@ -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 diff --git a/addons/twitcher/generated/twitch_get_chatters.gd b/addons/twitcher/generated/twitch_get_chatters.gd index c17b94a3..4785666d 100644 --- a/addons/twitcher/generated/twitch_get_chatters.gd +++ b/addons/twitcher/generated/twitch_get_chatters.gd @@ -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 diff --git a/addons/twitcher/generated/twitch_get_clips.gd b/addons/twitcher/generated/twitch_get_clips.gd index 71cf1397..322c1d7a 100644 --- a/addons/twitcher/generated/twitch_get_clips.gd +++ b/addons/twitcher/generated/twitch_get_clips.gd @@ -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 diff --git a/addons/twitcher/generated/twitch_get_conduit_shards.gd b/addons/twitcher/generated/twitch_get_conduit_shards.gd index 8f381359..3e7f17e5 100644 --- a/addons/twitcher/generated/twitch_get_conduit_shards.gd +++ b/addons/twitcher/generated/twitch_get_conduit_shards.gd @@ -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 diff --git a/addons/twitcher/generated/twitch_get_custom_reward_redemption.gd b/addons/twitcher/generated/twitch_get_custom_reward_redemption.gd index 1ea99e37..7a2233a6 100644 --- a/addons/twitcher/generated/twitch_get_custom_reward_redemption.gd +++ b/addons/twitcher/generated/twitch_get_custom_reward_redemption.gd @@ -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 diff --git a/addons/twitcher/generated/twitch_get_drops_entitlements.gd b/addons/twitcher/generated/twitch_get_drops_entitlements.gd index bf57c894..3483e064 100644 --- a/addons/twitcher/generated/twitch_get_drops_entitlements.gd +++ b/addons/twitcher/generated/twitch_get_drops_entitlements.gd @@ -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 diff --git a/addons/twitcher/generated/twitch_get_event_sub_subscriptions.gd b/addons/twitcher/generated/twitch_get_event_sub_subscriptions.gd index 5a6846ea..7ca657ce 100644 --- a/addons/twitcher/generated/twitch_get_event_sub_subscriptions.gd +++ b/addons/twitcher/generated/twitch_get_event_sub_subscriptions.gd @@ -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 diff --git a/addons/twitcher/generated/twitch_get_extension_analytics.gd b/addons/twitcher/generated/twitch_get_extension_analytics.gd index 9bbe40c2..831ced82 100644 --- a/addons/twitcher/generated/twitch_get_extension_analytics.gd +++ b/addons/twitcher/generated/twitch_get_extension_analytics.gd @@ -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 diff --git a/addons/twitcher/generated/twitch_get_extension_live_channels.gd b/addons/twitcher/generated/twitch_get_extension_live_channels.gd index f9c157c3..ddb46753 100644 --- a/addons/twitcher/generated/twitch_get_extension_live_channels.gd +++ b/addons/twitcher/generated/twitch_get_extension_live_channels.gd @@ -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 diff --git a/addons/twitcher/generated/twitch_get_extension_transactions.gd b/addons/twitcher/generated/twitch_get_extension_transactions.gd index fa779031..dd6cca19 100644 --- a/addons/twitcher/generated/twitch_get_extension_transactions.gd +++ b/addons/twitcher/generated/twitch_get_extension_transactions.gd @@ -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 diff --git a/addons/twitcher/generated/twitch_get_followed_channels.gd b/addons/twitcher/generated/twitch_get_followed_channels.gd index 80454bff..9cf56644 100644 --- a/addons/twitcher/generated/twitch_get_followed_channels.gd +++ b/addons/twitcher/generated/twitch_get_followed_channels.gd @@ -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 - \ No newline at end of file + diff --git a/addons/twitcher/generated/twitch_get_followed_streams.gd b/addons/twitcher/generated/twitch_get_followed_streams.gd index 4fd53f26..a9bd657c 100644 --- a/addons/twitcher/generated/twitch_get_followed_streams.gd +++ b/addons/twitcher/generated/twitch_get_followed_streams.gd @@ -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 - \ No newline at end of file + diff --git a/addons/twitcher/generated/twitch_get_game_analytics.gd b/addons/twitcher/generated/twitch_get_game_analytics.gd index 9f6e5f88..ed7d2eea 100644 --- a/addons/twitcher/generated/twitch_get_game_analytics.gd +++ b/addons/twitcher/generated/twitch_get_game_analytics.gd @@ -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 - \ No newline at end of file + diff --git a/addons/twitcher/generated/twitch_get_hype_train_events.gd b/addons/twitcher/generated/twitch_get_hype_train_events.gd index 78aa217f..1d50d806 100644 --- a/addons/twitcher/generated/twitch_get_hype_train_events.gd +++ b/addons/twitcher/generated/twitch_get_hype_train_events.gd @@ -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 diff --git a/addons/twitcher/generated/twitch_get_moderated_channels.gd b/addons/twitcher/generated/twitch_get_moderated_channels.gd index 2df0517f..c677d22c 100644 --- a/addons/twitcher/generated/twitch_get_moderated_channels.gd +++ b/addons/twitcher/generated/twitch_get_moderated_channels.gd @@ -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 - \ No newline at end of file + diff --git a/addons/twitcher/generated/twitch_get_moderators.gd b/addons/twitcher/generated/twitch_get_moderators.gd index be05dc0f..5e2923e4 100644 --- a/addons/twitcher/generated/twitch_get_moderators.gd +++ b/addons/twitcher/generated/twitch_get_moderators.gd @@ -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 - \ No newline at end of file + diff --git a/addons/twitcher/generated/twitch_get_polls.gd b/addons/twitcher/generated/twitch_get_polls.gd index 73b7a4ba..ebe1d7d2 100644 --- a/addons/twitcher/generated/twitch_get_polls.gd +++ b/addons/twitcher/generated/twitch_get_polls.gd @@ -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 - \ No newline at end of file + diff --git a/addons/twitcher/generated/twitch_get_predictions.gd b/addons/twitcher/generated/twitch_get_predictions.gd index 60db8c03..c963a1a9 100644 --- a/addons/twitcher/generated/twitch_get_predictions.gd +++ b/addons/twitcher/generated/twitch_get_predictions.gd @@ -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 - \ No newline at end of file + diff --git a/addons/twitcher/generated/twitch_get_stream_markers.gd b/addons/twitcher/generated/twitch_get_stream_markers.gd index 409de04f..a2dc3a56 100644 --- a/addons/twitcher/generated/twitch_get_stream_markers.gd +++ b/addons/twitcher/generated/twitch_get_stream_markers.gd @@ -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 - \ No newline at end of file + diff --git a/addons/twitcher/generated/twitch_get_streams.gd b/addons/twitcher/generated/twitch_get_streams.gd index 1a26d3cd..b13e6359 100644 --- a/addons/twitcher/generated/twitch_get_streams.gd +++ b/addons/twitcher/generated/twitch_get_streams.gd @@ -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 - \ No newline at end of file + diff --git a/addons/twitcher/generated/twitch_get_top_games.gd b/addons/twitcher/generated/twitch_get_top_games.gd index 8f5dbcf4..dedd15af 100644 --- a/addons/twitcher/generated/twitch_get_top_games.gd +++ b/addons/twitcher/generated/twitch_get_top_games.gd @@ -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 - \ No newline at end of file + diff --git a/addons/twitcher/generated/twitch_get_unban_requests.gd b/addons/twitcher/generated/twitch_get_unban_requests.gd index 3d81eb68..26807d76 100644 --- a/addons/twitcher/generated/twitch_get_unban_requests.gd +++ b/addons/twitcher/generated/twitch_get_unban_requests.gd @@ -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 - \ No newline at end of file + diff --git a/addons/twitcher/generated/twitch_get_user_block_list.gd b/addons/twitcher/generated/twitch_get_user_block_list.gd index a639251c..ef0ccae3 100644 --- a/addons/twitcher/generated/twitch_get_user_block_list.gd +++ b/addons/twitcher/generated/twitch_get_user_block_list.gd @@ -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 - \ No newline at end of file + diff --git a/addons/twitcher/generated/twitch_get_user_emotes.gd b/addons/twitcher/generated/twitch_get_user_emotes.gd index 615ba01c..fda1a162 100644 --- a/addons/twitcher/generated/twitch_get_user_emotes.gd +++ b/addons/twitcher/generated/twitch_get_user_emotes.gd @@ -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 - \ No newline at end of file + diff --git a/addons/twitcher/generated/twitch_get_vi_ps.gd b/addons/twitcher/generated/twitch_get_vi_ps.gd index ad2aa22b..58cc49a5 100644 --- a/addons/twitcher/generated/twitch_get_vi_ps.gd +++ b/addons/twitcher/generated/twitch_get_vi_ps.gd @@ -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 - \ No newline at end of file + diff --git a/addons/twitcher/generated/twitch_get_videos.gd b/addons/twitcher/generated/twitch_get_videos.gd index 38521050..027612ed 100644 --- a/addons/twitcher/generated/twitch_get_videos.gd +++ b/addons/twitcher/generated/twitch_get_videos.gd @@ -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 - \ No newline at end of file + diff --git a/addons/twitcher/generated/twitch_search_categories.gd b/addons/twitcher/generated/twitch_search_categories.gd index f9635442..5a2bc2bc 100644 --- a/addons/twitcher/generated/twitch_search_categories.gd +++ b/addons/twitcher/generated/twitch_search_categories.gd @@ -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 - \ No newline at end of file + diff --git a/addons/twitcher/generated/twitch_search_channels.gd b/addons/twitcher/generated/twitch_search_channels.gd index 3e6e55b3..9b3c0491 100644 --- a/addons/twitcher/generated/twitch_search_channels.gd +++ b/addons/twitcher/generated/twitch_search_channels.gd @@ -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 - \ No newline at end of file + diff --git a/lib/app_context.gd b/lib/app_context.gd index 1f622660..b642fc88 100644 --- a/lib/app_context.gd +++ b/lib/app_context.gd @@ -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 diff --git a/lib/chat_manager.gd b/lib/chat_manager.gd index 2dc679bb..6c6d1274 100644 --- a/lib/chat_manager.gd +++ b/lib/chat_manager.gd @@ -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 diff --git a/lib/globals.gd b/lib/globals.gd index 171896ed..e316c0c0 100644 --- a/lib/globals.gd +++ b/lib/globals.gd @@ -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") diff --git a/lib/models/chatter.gd b/lib/models/chatter.gd index ac518f37..3a128128 100644 --- a/lib/models/chatter.gd +++ b/lib/models/chatter.gd @@ -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") diff --git a/lib/twitcher_extended/twitcher_extended.gd b/lib/twitcher_extended/twitcher_extended.gd index 7921d427..30d20c45 100644 --- a/lib/twitcher_extended/twitcher_extended.gd +++ b/lib/twitcher_extended/twitcher_extended.gd @@ -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 diff --git a/migrations/001_initial.gd b/migrations/001_initial.gd index d778a2c6..e1bd7575 100644 --- a/migrations/001_initial.gd +++ b/migrations/001_initial.gd @@ -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") diff --git a/migrations/001_initial.gd.uid b/migrations/001_initial.gd.uid index 2ee930bb..30615956 100644 --- a/migrations/001_initial.gd.uid +++ b/migrations/001_initial.gd.uid @@ -1 +1 @@ -uid://byvn338f077hm +uid://biw7xekbciqwi diff --git a/migrations/002_add_steam_and_itch_apps.gd b/migrations/002_add_steam_and_itch_apps.gd deleted file mode 100644 index bc6504f2..00000000 --- a/migrations/002_add_steam_and_itch_apps.gd +++ /dev/null @@ -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") diff --git a/migrations/002_add_steam_and_itch_apps.gd.uid b/migrations/002_add_steam_and_itch_apps.gd.uid deleted file mode 100644 index 478dfa99..00000000 --- a/migrations/002_add_steam_and_itch_apps.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://dkoc1y0bjblfw diff --git a/project.godot b/project.godot index f93d0b00..7f2f0d55 100644 --- a/project.godot +++ b/project.godot @@ -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={