gift/Example.gd
2023-12-02 14:34:30 +01:00

157 lines
6.5 KiB
GDScript

extends Control
# Your client id. You can share this publicly. Default is my own client_id.
# Please do not ship your project with my client_id, but feel free to test with it.
# Visit https://dev.twitch.tv/console/apps/create to create a new application.
# You can then find your client id at the bottom of the application console.
# DO NOT SHARE THE CLIENT SECRET. If you do, regenerate it.
@export var client_id : String = "9x951o0nd03na7moohwetpjjtds0or"
# The name of the channel we want to connect to.
@export var channel : String
# The username of the bot account.
@export var username : String
var id : TwitchIDConnection
var api : TwitchAPIConnection
var irc : TwitchIRCConnection
var eventsub : TwitchEventSubConnection
var cmd_handler : GIFTCommandHandler = GIFTCommandHandler.new()
var iconloader : TwitchIconDownloader
func _ready() -> void:
# We will login using the Implicit Grant Flow, which only requires a client_id.
# Alternatively, you can use the Authorization Code Grant Flow or the Client Credentials Grant Flow.
# Note that the Client Credentials Grant Flow will only return an AppAccessToken, which can not be used
# for the majority of the Twitch API or to join a chat room.
var auth : ImplicitGrantFlow = ImplicitGrantFlow.new()
# For the auth to work, we need to poll it regularly.
get_tree().process_frame.connect(auth.poll) # You can also use a timer if you don't want to poll on every frame.
# Next, we actually get our token to authenticate. We want to be able to read and write messages,
# so we request the required scopes. See https://dev.twitch.tv/docs/authentication/scopes/#twitch-access-token-scopes
var token : UserAccessToken = await(auth.login(client_id, ["chat:read", "chat:edit"]))
if (token == null):
# Authentication failed. Abort.
return
# Store the token in the ID connection, create all other connections.
id = TwitchIDConnection.new(token)
irc = TwitchIRCConnection.new(id)
api = TwitchAPIConnection.new(id)
iconloader = TwitchIconDownloader.new(api)
# For everything to work, the id connection has to be polled regularly.
get_tree().process_frame.connect(id.poll)
# Connect to the Twitch chat.
if(!await(irc.connect_to_irc(username))):
# Authentication failed. Abort.
return
# Request the capabilities. By default only twitch.tv/commands and twitch.tv/tags are used.
# Refer to https://dev.twitch.tv/docs/irc/capabilities/ for all available capapbilities.
irc.request_capabilities()
# Join the channel specified in the exported 'channel' variable.
irc.join_channel(channel)
# Add a helloworld command.
cmd_handler.add_command("helloworld", hello)
# The helloworld command can now also be executed with "hello"!
cmd_handler.add_alias("helloworld", "hello")
# Add a list command that accepts between 1 and infinite args.
cmd_handler.add_command("list", list, -1, 1)
# For the cmd handler to receive the messages, we have to forward them.
irc.chat_message.connect(cmd_handler.handle_command)
# If you also want to accept whispers, connect the signal and bind true as the last arg.
irc.whisper_message.connect(cmd_handler.handle_command.bind(true))
# For the chat example to work, we forward the messages received to the put_chat function.
irc.chat_message.connect(put_chat)
# When we press enter on the chat bar or press the send button, we want to execute the send_message
# function.
%LineEdit.text_submitted.connect(send_message.unbind(1))
%Button.pressed.connect(send_message)
# This part of the example only works if GIFT is logged in to your broadcaster account.
# If you are, you can uncomment this to also try receiving follow events.
# eventsub = TwitchEventSubConnection.new(api)
# eventsub.connect_to_eventsub()
# eventsub.event.connect(on_event)
# var user_ids : Dictionary = await(api.get_users_by_name([username]))
# print(user_ids)
# if (user_ids.has("data") && user_ids["data"].size() > 0):
# var user_id : String = user_ids["data"][0]["id"]
# eventsub.subscribe_event("channel.follow", 2, {"broadcaster_user_id": user_id, "moderator_user_id": user_id})
func hello(cmd_info : CommandInfo) -> void:
irc.chat("Hello World!")
func list(cmd_info : CommandInfo, arg_ary : PackedStringArray) -> void:
irc.chat(", ".join(arg_ary))
func on_event(type : String, data : Dictionary) -> void:
match(type):
"channel.follow":
print("%s followed your channel!" % data["user_name"])
func send_message() -> void:
irc.chat(%LineEdit.text)
%LineEdit.text = ""
func put_chat(senderdata : SenderData, msg : String):
var bottom : bool = %ChatScrollContainer.scroll_vertical == %ChatScrollContainer.get_v_scroll_bar().max_value - %ChatScrollContainer.get_v_scroll_bar().get_rect().size.y
var label : RichTextLabel = RichTextLabel.new()
var time = Time.get_time_dict_from_system()
label.fit_content = true
label.selection_enabled = true
label.push_font_size(12)
label.push_color(Color.WEB_GRAY)
label.add_text("%02d:%02d " % [time["hour"], time["minute"]])
label.pop()
label.push_font_size(14)
var badges : Array[Texture2D]
for badge in senderdata.tags["badges"].split(",", false):
label.add_image(await(iconloader.get_badge(badge, senderdata.tags["room-id"])), 0, 0, Color.WHITE, INLINE_ALIGNMENT_CENTER)
label.push_bold()
if (senderdata.tags["color"] != ""):
label.push_color(Color(senderdata.tags["color"]))
label.add_text(" %s" % senderdata.tags["display-name"])
label.push_color(Color.WHITE)
label.push_normal()
label.add_text(": ")
var locations : Array[EmoteLocation] = []
if (senderdata.tags.has("emotes")):
for emote in senderdata.tags["emotes"].split("/", false):
var data : PackedStringArray = emote.split(":")
for d in data[1].split(","):
var start_end = d.split("-")
locations.append(EmoteLocation.new(data[0], int(start_end[0]), int(start_end[1])))
locations.sort_custom(Callable(EmoteLocation, "smaller"))
if (locations.is_empty()):
label.add_text(msg)
else:
var offset = 0
for loc in locations:
label.add_text(msg.substr(offset, loc.start - offset))
label.add_image(await(iconloader.get_emote(loc.id)), 0, 0, Color.WHITE, INLINE_ALIGNMENT_CENTER)
offset = loc.end + 1
%Messages.add_child(label)
await(get_tree().process_frame)
if (bottom):
%ChatScrollContainer.scroll_vertical = %ChatScrollContainer.get_v_scroll_bar().max_value
class EmoteLocation extends RefCounted:
var id : String
var start : int
var end : int
func _init(emote_id, start_idx, end_idx):
self.id = emote_id
self.start = start_idx
self.end = end_idx
static func smaller(a : EmoteLocation, b : EmoteLocation):
return a.start < b.start