twitch addon

This commit is contained in:
Tabby 2025-04-21 00:17:07 +10:00
parent 2cd7af98a1
commit 07de7179c9
254 changed files with 18420 additions and 1 deletions

61
example/ChatContainer.gd Normal file
View file

@ -0,0 +1,61 @@
class_name VSTChatContainer extends VBoxContainer
var msg_node: PackedScene = preload("res://example/ChatMessage.tscn")
@onready var scroll_container = $Chat/ScrollContainer
@onready var chat_message_container = $Chat/ScrollContainer/ChatMessageContainer
func _ready():
VerySimpleTwitch.chat_message_received.connect(create_chatter_msg)
func create_chatter_msg(chatter: VSTChatter):
var msg: VSTChatMessage = msg_node.instantiate()
var badges: String = await get_badges(chatter)
chatter.message = escape_bbcode(chatter.message)
await add_emotes(chatter)
var bottom: bool = is_scroll_bottom()
msg.set_chatter_msg(badges, chatter)
chat_message_container.add_child(msg)
await get_tree().process_frame
if bottom: scroll_container.scroll_vertical = scroll_container.get_v_scroll_bar().max_value
func get_badges(chatter: VSTChatter) -> String:
var badges:= ""
for badge in chatter.tags.badges:
var result = await VerySimpleTwitch.get_badge(badge, \
chatter.tags.badges[badge], chatter.tags.user_id)
if result:
badges += "[img=center]" + result.resource_path + "[/img] "
return badges
func add_emotes(chatter: VSTChatter):
if chatter.tags.emotes.is_empty(): return
var locations: Array = []
for emote in chatter.tags.emotes:
for data in chatter.tags.emotes[emote].split(","):
var start_end = data.split("-")
locations.append(VSTEmoteLocation.new(emote, int(start_end[0]), int(start_end[1])))
locations.sort_custom(Callable(VSTEmoteLocation, "smaller"))
var offset = 0
for loc in locations:
var result = await VerySimpleTwitch.get_emote(loc.id)
var emote_string = "[img=center]" + result.resource_path +"[/img]"
chatter.message = chatter.message.substr(0, loc.start + offset) + \
emote_string + chatter.message.substr(loc.end + offset + 1)
offset += emote_string.length() + loc.start - loc.end - 1
func is_scroll_bottom() -> bool:
return scroll_container.scroll_vertical == scroll_container.get_v_scroll_bar().max_value -\
scroll_container.get_v_scroll_bar().get_rect().size.y
# Returns escaped BBCode that won't be parsed by RichTextLabel as tags.
func escape_bbcode(bbcode_text):
return bbcode_text.replace("[", "[lb]")
func clear():
for child in chat_message_container.get_children():
chat_message_container.remove_child(child)

View file

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

6
example/ChatMessage.gd Normal file
View file

@ -0,0 +1,6 @@
class_name VSTChatMessage extends HBoxContainer
func set_chatter_msg(badges: String, chatter: VSTChatter):
$RichTextLabel.text = "%02d:%02d" %[chatter.date_time_dict["hour"], chatter.date_time_dict["minute"]] + " " + badges + " [b][color="+ chatter.tags.color_hex + "]" +chatter.tags.display_name +"[/color][/b]: " + chatter.message
queue_sort()

View file

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

15
example/ChatMessage.tscn Normal file
View file

@ -0,0 +1,15 @@
[gd_scene load_steps=2 format=3 uid="uid://d3asrgnlj151a"]
[ext_resource type="Script" path="res://example/ChatMessage.gd" id="1_h3nqp"]
[node name="ChatMessage" type="HBoxContainer"]
size_flags_horizontal = 3
script = ExtResource("1_h3nqp")
[node name="RichTextLabel" type="RichTextLabel" parent="."]
layout_mode = 2
size_flags_horizontal = 3
bbcode_enabled = true
text = "Testg"
fit_content = true
scroll_active = false

159
example/Demo.tscn Normal file
View file

@ -0,0 +1,159 @@
[gd_scene load_steps=4 format=3 uid="uid://rr154c1ykwoh"]
[ext_resource type="PackedScene" uid="uid://yhuh0huitds8" path="res://example/TwitchChat.tscn" id="1_o6alc"]
[ext_resource type="Script" path="res://example/demo.gd" id="1_uj756"]
[ext_resource type="Texture2D" uid="uid://yarcej88qjri" path="res://example/arrow_down.svg" id="2_nmg8t"]
[node name="Demo" type="Control"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1_uj756")
[node name="VBoxContainer" type="VBoxContainer" parent="."]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="TabContainer" type="TabContainer" parent="VBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
current_tab = 0
[node name="Oauth Connection" type="PanelContainer" parent="VBoxContainer/TabContainer"]
layout_mode = 2
metadata/_tab_index = 0
[node name="MarginContainer" type="MarginContainer" parent="VBoxContainer/TabContainer/Oauth Connection"]
layout_mode = 2
theme_override_constants/margin_left = 20
theme_override_constants/margin_top = 20
theme_override_constants/margin_right = 20
theme_override_constants/margin_bottom = 20
[node name="OauthConnectionLayout" type="VBoxContainer" parent="VBoxContainer/TabContainer/Oauth Connection/MarginContainer"]
layout_mode = 2
[node name="OauthTitleToggle" type="Button" parent="VBoxContainer/TabContainer/Oauth Connection/MarginContainer/OauthConnectionLayout"]
unique_name_in_owner = true
layout_mode = 2
toggle_mode = true
button_pressed = true
text = "Get Token and login to channel"
icon = ExtResource("2_nmg8t")
alignment = 0
[node name="Description" type="RichTextLabel" parent="VBoxContainer/TabContainer/Oauth Connection/MarginContainer/OauthConnectionLayout"]
layout_mode = 2
bbcode_enabled = true
text = "You can use [color=#d2a8ff] VerySimpleTwitch.get_token_and_login_chat()[/color] to retrieve the token and automatically login to the twitch chat.
| [color=gray]Note: You will need to set up the CLIENT_ID in the Settings tab and configure the Twitch app accordingly."
fit_content = true
[node name="HSeparator" type="HSeparator" parent="VBoxContainer/TabContainer/Oauth Connection/MarginContainer/OauthConnectionLayout"]
layout_mode = 2
theme_override_constants/separation = 20
[node name="LoginToken" type="Button" parent="VBoxContainer/TabContainer/Oauth Connection/MarginContainer/OauthConnectionLayout"]
unique_name_in_owner = true
layout_mode = 2
text = "Get Token and Login Chat"
[node name="Anonymous Connection" type="PanelContainer" parent="VBoxContainer/TabContainer"]
visible = false
layout_mode = 2
metadata/_tab_index = 1
[node name="MarginContainer" type="MarginContainer" parent="VBoxContainer/TabContainer/Anonymous Connection"]
layout_mode = 2
theme_override_constants/margin_left = 20
theme_override_constants/margin_top = 20
theme_override_constants/margin_right = 20
theme_override_constants/margin_bottom = 20
[node name="AnonymousConnectionLayout" type="VBoxContainer" parent="VBoxContainer/TabContainer/Anonymous Connection/MarginContainer"]
layout_mode = 2
[node name="AnonymousTitleToggle" type="Button" parent="VBoxContainer/TabContainer/Anonymous Connection/MarginContainer/AnonymousConnectionLayout"]
unique_name_in_owner = true
layout_mode = 2
toggle_mode = true
button_pressed = true
text = "Simple anonymous connection"
icon = ExtResource("2_nmg8t")
alignment = 0
[node name="Description" type="RichTextLabel" parent="VBoxContainer/TabContainer/Anonymous Connection/MarginContainer/AnonymousConnectionLayout"]
layout_mode = 2
bbcode_enabled = true
text = "This is the easiest way to use the plugin. You can use [color=#d2a8ff]VerySimpleTwitch.login_chat_anon(\"channel_name\")[/color] to connect to the channel without needing a token or any settings customization."
fit_content = true
[node name="HSeparator" type="HSeparator" parent="VBoxContainer/TabContainer/Anonymous Connection/MarginContainer/AnonymousConnectionLayout"]
layout_mode = 2
theme_override_constants/separation = 20
[node name="LoginChat" type="VBoxContainer" parent="VBoxContainer/TabContainer/Anonymous Connection/MarginContainer/AnonymousConnectionLayout"]
layout_mode = 2
[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/TabContainer/Anonymous Connection/MarginContainer/AnonymousConnectionLayout/LoginChat"]
layout_mode = 2
[node name="Label" type="Label" parent="VBoxContainer/TabContainer/Anonymous Connection/MarginContainer/AnonymousConnectionLayout/LoginChat/HBoxContainer"]
layout_mode = 2
text = "Channel Name: "
[node name="ChannelName" type="LineEdit" parent="VBoxContainer/TabContainer/Anonymous Connection/MarginContainer/AnonymousConnectionLayout/LoginChat/HBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 4
placeholder_text = "MyChannelName"
[node name="LoginAnon" type="Button" parent="VBoxContainer/TabContainer/Anonymous Connection/MarginContainer/AnonymousConnectionLayout/LoginChat"]
unique_name_in_owner = true
layout_mode = 2
text = "Login Anon"
[node name="LoggedLayout" type="HBoxContainer" parent="VBoxContainer"]
unique_name_in_owner = true
visible = false
layout_mode = 2
alignment = 1
[node name="LogoutButton" type="Button" parent="VBoxContainer/LoggedLayout"]
layout_mode = 2
size_flags_horizontal = 3
text = "Logout"
[node name="ClearChar" type="Button" parent="VBoxContainer/LoggedLayout"]
layout_mode = 2
size_flags_horizontal = 3
text = "Clear chat"
[node name="ConnectedLabel" type="Label" parent="VBoxContainer"]
unique_name_in_owner = true
visible = false
layout_mode = 2
text = "Connected to channel"
[node name="TwitchChat" parent="VBoxContainer" instance=ExtResource("1_o6alc")]
unique_name_in_owner = true
layout_mode = 2
size_flags_vertical = 3
[connection signal="toggled" from="VBoxContainer/TabContainer/Oauth Connection/MarginContainer/OauthConnectionLayout/OauthTitleToggle" to="." method="_on_oauth_title_toggle_toggled"]
[connection signal="toggled" from="VBoxContainer/TabContainer/Oauth Connection/MarginContainer/OauthConnectionLayout/OauthTitleToggle" to="VBoxContainer/TabContainer/Oauth Connection/MarginContainer/OauthConnectionLayout/Description" method="set_visible"]
[connection signal="pressed" from="VBoxContainer/TabContainer/Oauth Connection/MarginContainer/OauthConnectionLayout/LoginToken" to="." method="login_token"]
[connection signal="toggled" from="VBoxContainer/TabContainer/Anonymous Connection/MarginContainer/AnonymousConnectionLayout/AnonymousTitleToggle" to="." method="_on_anonymous_title_toggle_toggled"]
[connection signal="toggled" from="VBoxContainer/TabContainer/Anonymous Connection/MarginContainer/AnonymousConnectionLayout/AnonymousTitleToggle" to="VBoxContainer/TabContainer/Anonymous Connection/MarginContainer/AnonymousConnectionLayout/Description" method="set_visible"]
[connection signal="pressed" from="VBoxContainer/TabContainer/Anonymous Connection/MarginContainer/AnonymousConnectionLayout/LoginChat/LoginAnon" to="." method="login_anon"]
[connection signal="pressed" from="VBoxContainer/LoggedLayout/LogoutButton" to="." method="logout"]
[connection signal="pressed" from="VBoxContainer/LoggedLayout/ClearChar" to="." method="clear_chat"]

28
example/TwitchChat.tscn Normal file
View file

@ -0,0 +1,28 @@
[gd_scene load_steps=2 format=3 uid="uid://yhuh0huitds8"]
[ext_resource type="Script" path="res://example/ChatContainer.gd" id="2_f56i7"]
[node name="TwitchChat" type="VBoxContainer"]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("2_f56i7")
[node name="Chat" type="Panel" parent="."]
layout_mode = 2
size_flags_vertical = 3
[node name="ScrollContainer" type="ScrollContainer" parent="Chat"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="ChatMessageContainer" type="VBoxContainer" parent="Chat/ScrollContainer"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3

1
example/arrow_down.svg Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e8eaed"><path d="M480-344 240-584l56-56 184 184 184-184 56 56-240 240Z"/></svg>

After

Width:  |  Height:  |  Size: 178 B

View file

@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://yarcej88qjri"
path="res://.godot/imported/arrow_down.svg-87daaaad8be1a207d564c640aec7c7c9.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://example/arrow_down.svg"
dest_files=["res://.godot/imported/arrow_down.svg-87daaaad8be1a207d564c640aec7c7c9.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false

1
example/arrow_right.svg Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e8eaed"><path d="M504-480 320-664l56-56 240 240-240 240-56-56 184-184Z"/></svg>

After

Width:  |  Height:  |  Size: 178 B

View file

@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://c3rgh75lgvj1a"
path="res://.godot/imported/arrow_right.svg-5de23ade3dd4ccfc5abe74276afcee24.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://example/arrow_right.svg"
dest_files=["res://.godot/imported/arrow_right.svg-5de23ade3dd4ccfc5abe74276afcee24.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false

54
example/demo.gd Normal file
View file

@ -0,0 +1,54 @@
extends Node
func login_anon():
await VerySimpleTwitch.login_chat_anon(%ChannelName.text)
_show_logout_layout()
func login_token():
await VerySimpleTwitch.get_token_and_login_chat()
_show_logout_layout()
func logout():
VerySimpleTwitch.end_chat_client()
%TwitchChat.clear()
_show_login_layout()
#region Local methods to simplify demo
func _show_login_layout():
%TabContainer.set_tab_disabled(0, false)
%TabContainer.set_tab_disabled(1, false)
%LoggedLayout.hide()
%LoginToken.show()
%LoginAnon.show()
%ChannelName.editable = true
%ChannelName.selecting_enabled = true
func _show_logout_layout():
%TabContainer.set_tab_disabled(0, true)
%TabContainer.set_tab_disabled(1, true)
%LoginAnon.hide()
%LoggedLayout.show()
%LoginToken.hide()
%ChannelName.editable = false
%ChannelName.selecting_enabled = false
func clear_chat() -> void:
%TwitchChat.clear()
func _on_oauth_title_toggle_toggled(toggled_on: bool) -> void:
_update_toggle_icon(%OauthTitleToggle, toggled_on)
func _on_anonymous_title_toggle_toggled(toggled_on: bool) -> void:
_update_toggle_icon(%AnonymousTitleToggle, toggled_on)
func _update_toggle_icon(toggle_button: Button, toggled_on: bool) -> void:
var icon_path = "res://example/arrow_down.svg" if toggled_on else "res://example/arrow_right.svg"
var icon = load(icon_path)
toggle_button.icon = icon
#endregion

1
example/demo.gd.uid Normal file
View file

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