-
Notifications
You must be signed in to change notification settings - Fork 23
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
36 changed files
with
1,078 additions
and
1,179 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,156 @@ | ||
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.