Initial Commit

Initial commit of Code Base.
This commit is contained in:
Mario Steele 2025-06-12 14:31:14 -05:00
parent 293b1213e1
commit c11a4ebbc2
653 changed files with 36893 additions and 1 deletions

View file

@ -0,0 +1,14 @@
extends RefCounted
class_name TwitchAnnouncementColor
static var BLUE: TwitchAnnouncementColor = TwitchAnnouncementColor.new("blue")
static var GREEN: TwitchAnnouncementColor = TwitchAnnouncementColor.new("green")
static var ORANGE: TwitchAnnouncementColor = TwitchAnnouncementColor.new("orange")
static var PURPLE: TwitchAnnouncementColor = TwitchAnnouncementColor.new("purple")
static var PRIMARY: TwitchAnnouncementColor = TwitchAnnouncementColor.new("primary")
var value;
func _init(color: String) -> void:
value = color;

View file

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

View file

@ -0,0 +1,116 @@
@icon("../assets/chat-icon.svg")
@tool
extends Twitcher
## Grants access to read and write to a chat
class_name TwitchChat
static var _log: TwitchLogger = TwitchLogger.new("TwitchChat")
static var instance: TwitchChat
@export var broadcaster_user: TwitchUser:
set(val):
broadcaster_user = val
update_configuration_warnings()
## Can be null. Then the owner of the access token will be used to send message aka the current user.
@export var sender_user: TwitchUser
@export var media_loader: TwitchMediaLoader
@export var eventsub: TwitchEventsub:
set(val):
eventsub = val
update_configuration_warnings()
@export var api: TwitchAPI:
set(val):
api = val
update_configuration_warnings()
## Should it subscribe on ready
@export var subscribe_on_ready: bool = true
## Triggered when a chat message got received
signal message_received(message: TwitchChatMessage)
func _ready() -> void:
_log.d("is ready")
if media_loader == null: media_loader = TwitchMediaLoader.instance
if api == null: api = TwitchAPI.instance
if eventsub == null: eventsub = TwitchEventsub.instance
eventsub.event.connect(_on_event_received)
if not Engine.is_editor_hint() && subscribe_on_ready:
subscribe()
func _enter_tree() -> void:
if instance == null: instance = self
func _exit_tree() -> void:
if instance == self: instance = null
## Subscribe to eventsub and preload data if not happend yet
func subscribe() -> void:
if broadcaster_user == null:
printerr("BroadcasterUser is not set. Can't subscribe to chat.")
return
if is_instance_valid(media_loader):
media_loader.preload_badges(broadcaster_user.id)
media_loader.preload_emotes(broadcaster_user.id)
for subscription: TwitchEventsubConfig in eventsub.get_subscriptions():
if subscription.type == TwitchEventsubDefinition.Type.CHANNEL_CHAT_MESSAGE and \
subscription.condition.broadcaster_user_id == broadcaster_user.id:
# it is already subscribed
return
if sender_user == null:
var current_user: TwitchGetUsers.Response = await api.get_users(null)
sender_user = current_user.data[0]
var config: TwitchEventsubConfig = TwitchEventsubConfig.new()
config.type = TwitchEventsubDefinition.Type.CHANNEL_CHAT_MESSAGE
config.condition = {
"broadcaster_user_id": broadcaster_user.id,
"user_id": sender_user.id
}
eventsub.subscribe(config)
_log.i("Listen to Chat of %s (%s)" % [broadcaster_user.display_name, broadcaster_user.id])
func _on_event_received(type: StringName, data: Dictionary) -> void:
if type != TwitchEventsubDefinition.CHANNEL_CHAT_MESSAGE.value: return
var message: TwitchChatMessage = TwitchChatMessage.from_json(data)
if message.broadcaster_user_id == broadcaster_user.id:
message_received.emit(message)
func send_message(message: String, reply_parent_message_id: String = "") -> Array[TwitchSendChatMessage.ResponseData]:
var message_body: TwitchSendChatMessage.Body = TwitchSendChatMessage.Body.new()
message_body.broadcaster_id = broadcaster_user.id
message_body.sender_id = sender_user.id
message_body.message = message
if reply_parent_message_id:
message_body.reply_parent_message_id = reply_parent_message_id
var response: TwitchSendChatMessage.Response = await api.send_chat_message(message_body)
if _log.enabled:
for message_data: TwitchSendChatMessage.ResponseData in response.data:
if not message_data.is_sent:
_log.w(message_data.drop_reason)
return response.data
func _get_configuration_warnings() -> PackedStringArray:
var result: PackedStringArray = []
if eventsub == null:
result.append("TwitchEventsub not assigned")
if api == null:
result.append("TwitchAPI not assigned")
if broadcaster_user == null:
result.append("Target broadcaster not specified")
return result

View file

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

View file

@ -0,0 +1,367 @@
extends RefCounted
class_name TwitchChatMessage
enum FragmentType {
text = 0,
cheermote = 1,
emote = 2,
mention = 3
}
const FRAGMENT_TYPES = ["text", "cheermote", "emote", "mention"]
enum EmoteFormat {
animated = 0,
_static = 1
}
const EMOTE_FORMATES = ["animated", "static"]
enum MessageType {
## Normal chat message
text = 0,
## The default reward where the message is highlighted
channel_points_highlighted = 1,
## Channel points were used to send this message in sub-only mode.
channel_points_sub_only = 2,
## when a new user is typing for the first time
user_intro = 3,
## When the power up message effect was used on this message
power_ups_message_effect = 4,
## When a gigantified emote was posted
power_ups_gigantified_emote = 5
}
const MESSAGE_TYPES = ["text", "channel_points_highlighted", "channel_points_sub_only", "user_intro", "power_ups_message_effect", "power_ups_gigantified_emote"]
class Message extends RefCounted:
## The chat message in plain text.
var text: String
## Ordered list of chat message fragments.
var fragments: Array[Fragment] = []
static func from_json(d: Dictionary) -> Message:
var result = Message.new()
if d.has("text") and d["text"] != null:
result.text = d["text"]
if d.has("fragments") and d["fragments"] != null:
for value in d["fragments"]:
result.fragments.append(Fragment.from_json(value))
return result
class Fragment extends RefCounted:
## The type of message fragment. See "TwitchChatMessage.FRAGMENT_TYPE_*"
var type: MessageType
## Message text in fragment.
var text: String
## Optional. Metadata pertaining to the cheermote.
var cheermote: Cheermote
## Optional. Metadata pertaining to the emote.
var emote: Emote
## Optional. Metadata pertaining to the mention.
var mention: Mention
static func from_json(d: Dictionary) -> Fragment:
var result = Fragment.new()
if d.has("type") and d["type"] != null:
result.type = FragmentType[d["type"]]
if d.has("text") and d["text"] != null:
result.text = d["text"]
if d.has("cheermote") and d["cheermote"] != null:
result.cheermote = Cheermote.from_json(d["cheermote"])
if d.has("emote") and d["emote"] != null:
result.emote = Emote.from_json(d["emote"])
if d.has("mention") and d["mention"] != null:
result.mention = Mention.from_json(d["mention"])
return result
class Mention extends RefCounted:
## The user ID of the mentioned user.
var user_id: String
## The user name of the mentioned user.
var user_name: String
## The user login of the mentioned user.
var user_login: String
static func from_json(d: Dictionary) -> Mention:
var result = Mention.new()
if d.has("user_id") and d["user_id"] != null:
result.user_id = d["user_id"]
if d.has("user_name") and d["user_name"] != null:
result.user_name = d["user_name"]
if d.has("user_login") and d["user_login"] != null:
result.user_login = d["user_login"]
return result
class Cheermote extends RefCounted:
## The name portion of the Cheermote string that you use in chat to cheer Bits. The full Cheermote string is the concatenation of {prefix} + {number of Bits}. For example, if the prefix is “Cheer” and you want to cheer 100 Bits, the full Cheermote string is Cheer100. When the Cheermote string is entered in chat, Twitch converts it to the image associated with the Bits tier that was cheered.
var prefix: String
## The amount of bits cheered.
var bits: int
## The tier level of the cheermote.
var tier: int
func get_sprite_frames(_media_loader: TwitchMediaLoader, cheermote_definition: TwitchCheermoteDefinition) -> SpriteFrames:
var cheer_results = await _media_loader.get_cheer_tier(prefix, "%s" % tier, cheermote_definition.theme, cheermote_definition.type, cheermote_definition.scale)
return cheer_results.spriteframes
static func from_json(d: Dictionary) -> Cheermote:
var result = Cheermote.new()
if d.has("prefix") and d["prefix"] != null:
result.prefix = d["prefix"]
if d.has("bits") and d["bits"] != null:
result.bits = d["bits"]
if d.has("tier") and d["tier"] != null:
result.tier = d["tier"]
return result
class Emote extends RefCounted:
## An ID that uniquely identifies this emote.
var id: String
## An ID that identifies the emote set that the emote belongs to.
var emote_set_id: String
## The ID of the broadcaster who owns the emote.
var owner_id: String
## The formats that the emote is available in. For example, if the emote is available only as a static PNG, the array contains only static. But if the emote is available as a static PNG and an animated GIF, the array contains static and animated. See: "TwitchChatMessage.EMOTE_TYPE_*"
var format: Array[EmoteFormat] = []
## Resolves the spriteframes from this emote. Check `format` for possible formats.
## Format: Defaults to animated when not available it uses static
## Scale: 1, 2, 3
func get_sprite_frames(_media_loader: TwitchMediaLoader, format: String = "", scale: int = 1, dark: bool = true) -> SpriteFrames:
var definition: TwitchEmoteDefinition = TwitchEmoteDefinition.new(id)
if dark: definition.theme_dark()
else: definition.theme_light()
match scale:
1: definition.scale_1()
2: definition.scale_2()
3: definition.scale_3()
_: definition.scale_1()
var emotes = await _media_loader.get_emotes_by_definition([definition])
return emotes[definition]
static func from_json(d: Dictionary) -> Emote:
var result = Emote.new()
if d.has("id") and d["id"] != null:
result.id = d["id"]
if d.has("emote_set_id") and d["emote_set_id"] != null:
result.emote_set_id = d["emote_set_id"]
if d.has("owner_id") and d["owner_id"] != null:
result.owner_id = d["owner_id"]
if d.has("format") and d["format"] != null:
for format in d["format"]:
if format == "static":
result.format.append(EmoteFormat._static)
elif format == "animated":
result.format.append(EmoteFormat.animated)
return result
class Badge extends RefCounted:
## An ID that identifies this set of chat badges. For example, Bits or Subscriber.
var set_id: String
## An ID that identifies this version of the badge. The ID can be any value. For example, for Bits, the ID is the Bits tier level, but for World of Warcraft, it could be Alliance or Horde.
var id: String
## Contains metadata related to the chat badges in the badges tag. Currently, this tag contains metadata only for subscriber badges, to indicate the number of months the user has been a subscriber.
var info: String
static func from_json(d: Dictionary) -> Badge:
var result = Badge.new()
if d.has("set_id") and d["set_id"] != null:
result.set_id = d["set_id"]
if d.has("id") and d["id"] != null:
result.id = d["id"]
if d.has("info") and d["info"] != null:
result.info = d["info"]
return result
class Cheer extends RefCounted:
## The amount of Bits the user cheered.
var bits: int
static func from_json(d: Dictionary) -> Cheer:
var result = Cheer.new()
if d.has("bits") and d["bits"] != null:
result.bits = d["bits"]
return result
class Reply extends RefCounted:
## An ID that uniquely identifies the parent message that this message is replying to.
var parent_message_id: String
## The message body of the parent message.
var parent_message_body: String
## User ID of the sender of the parent message.
var parent_user_id: String
## User name of the sender of the parent message.
var parent_user_name: String
## User login of the sender of the parent message.
var parent_user_login: String
## An ID that identifies the parent message of the reply thread.
var thread_message_id: String
## User ID of the sender of the threads parent message.
var thread_user_id: String
## User name of the sender of the threads parent message.
var thread_user_name: String
## User login of the sender of the threads parent message.
var thread_user_login: String
static func from_json(d: Dictionary) -> Reply:
var result = Reply.new()
if d.has("parent_message_id") and d["parent_message_id"] != null:
result.parent_message_id = d["parent_message_id"]
if d.has("parent_message_body") and d["parent_message_body"] != null:
result.parent_message_body = d["parent_message_body"]
if d.has("parent_user_id") and d["parent_user_id"] != null:
result.parent_user_id = d["parent_user_id"]
if d.has("parent_user_name") and d["parent_user_name"] != null:
result.parent_user_name = d["parent_user_name"]
if d.has("parent_user_login") and d["parent_user_login"] != null:
result.parent_user_login = d["parent_user_login"]
if d.has("thread_message_id") and d["thread_message_id"] != null:
result.thread_message_id = d["thread_message_id"]
if d.has("thread_user_id") and d["thread_user_id"] != null:
result.thread_user_id = d["thread_user_id"]
if d.has("thread_user_name") and d["thread_user_name"] != null:
result.thread_user_name = d["thread_user_name"]
if d.has("thread_user_login") and d["thread_user_login"] != null:
result.thread_user_login = d["thread_user_login"]
return result
## The broadcaster user ID.
var broadcaster_user_id: String
## The broadcaster display name.
var broadcaster_user_name: String
## The broadcaster login.
var broadcaster_user_login: String
## The user ID of the user that sent the message.
var chatter_user_id: String
## The user name of the user that sent the message.
var chatter_user_name: String
## The user login of the user that sent the message.
var chatter_user_login: String
## A UUID that identifies the message.
var message_id: String
## The structured chat message.
var message: Message
## The type of message.
var message_type: MessageType
## List of chat badges.
var badges: Array[Badge] = []
## Optional. Metadata if this message is a cheer.
var cheer: Cheer
## The color of the users name in the chat room. This is a hexadecimal RGB color code in the form, #<RGB>;. This tag may be empty if it is never set.
var color: String
## Optional. Metadata if this message is a reply.
var reply: Reply
## Optional. The ID of a channel points custom reward that was redeemed.
var channel_points_custom_reward_id: String
## Optional. The broadcaster user ID of the channel the message was sent from. Is null when the message happens in the same channel as the broadcaster. Is not null when in a shared chat session, and the action happens in the channel of a participant other than the broadcaster.
var source_broadcaster_user_id: String
## Optional. The user name of the broadcaster of the channel the message was sent from. Is null when the message happens in the same channel as the broadcaster. Is not null when in a shared chat session, and the action happens in the channel of a participant other than the broadcaster.
var source_broadcaster_user_name: String
## Optional. The login of the broadcaster of the channel the message was sent from. Is null when the message happens in the same channel as the broadcaster. Is not null when in a shared chat session, and the action happens in the channel of a participant other than the broadcaster.
var source_broadcaster_user_login: String
## Optional. The UUID that identifies the source message from the channel the message was sent from. Is null when the message happens in the same channel as the broadcaster. Is not null when in a shared chat session, and the action happens in the channel of a participant other than the broadcaster.
var source_message_id: String
## Optional. The list of chat badges for the chatter in the channel the message was sent from. Is null when the message happens in the same channel as the broadcaster. Is not null when in a shared chat session, and the action happens in the channel of a participant other than the broadcaster.
var source_badges: Array[Badge] = []
## Loads a chat message from Json decoded dictionary. TwitchService is optional in case images and badges should be load from the message.
static func from_json(d: Dictionary) -> TwitchChatMessage:
var result = TwitchChatMessage.new()
if d.has("broadcaster_user_id") and d["broadcaster_user_id"] != null:
result.broadcaster_user_id = d["broadcaster_user_id"]
if d.has("broadcaster_user_name") and d["broadcaster_user_name"] != null:
result.broadcaster_user_name = d["broadcaster_user_name"]
if d.has("broadcaster_user_login") and d["broadcaster_user_login"] != null:
result.broadcaster_user_login = d["broadcaster_user_login"]
if d.has("chatter_user_id") and d["chatter_user_id"] != null:
result.chatter_user_id = d["chatter_user_id"]
if d.has("chatter_user_name") and d["chatter_user_name"] != null:
result.chatter_user_name = d["chatter_user_name"]
if d.has("chatter_user_login") and d["chatter_user_login"] != null:
result.chatter_user_login = d["chatter_user_login"]
if d.has("message_id") and d["message_id"] != null:
result.message_id = d["message_id"]
if d.has("message") and d["message"] != null:
result.message = Message.from_json(d["message"])
if d.has("message_type") and d["message_type"] != null:
result.message_type = MessageType[d["message_type"]]
if d.has("badges") and d["badges"] != null:
for value in d["badges"]:
result.badges.append(Badge.from_json(value))
if d.has("cheer") and d["cheer"] != null:
result.cheer = Cheer.from_json(d["cheer"])
if d.has("color") and d["color"] != null:
result.color = d["color"]
if d.has("reply") and d["reply"] != null:
result.reply = Reply.from_json(d["reply"])
if d.has("channel_points_custom_reward_id") and d["channel_points_custom_reward_id"] != null:
result.channel_points_custom_reward_id = d["channel_points_custom_reward_id"]
if d.has("source_broadcaster_user_id") and d["source_broadcaster_user_id"] != null:
result.source_broadcaster_user_id = d["source_broadcaster_user_id"]
if d.has("source_broadcaster_user_name") and d["source_broadcaster_user_name"] != null:
result.source_broadcaster_user_name = d["source_broadcaster_user_name"]
if d.has("source_broadcaster_user_login") and d["source_broadcaster_user_login"] != null:
result.source_broadcaster_user_login = d["source_broadcaster_user_login"]
if d.has("source_message_id") and d["source_message_id"] != null:
result.source_message_id = d["source_message_id"]
if d.has("source_badges") and d["source_badges"] != null:
for value in d["source_badges"]:
result.source_badges.append(Badge.from_json(value))
return result
## Key: TwitchBadgeDefinition | Value: SpriteFrames
func get_badges(_media_loader: TwitchMediaLoader, scale: int = 1) -> Dictionary[TwitchBadgeDefinition, SpriteFrames]:
var definitions : Array[TwitchBadgeDefinition] = []
for badge in badges:
var badge_definition : TwitchBadgeDefinition = TwitchBadgeDefinition.new(badge.set_id, badge.id, scale, broadcaster_user_id)
definitions.append(badge_definition)
var emotes : Dictionary[TwitchBadgeDefinition, SpriteFrames] = await _media_loader.get_badges(definitions)
return emotes
## Key: TwitchBadgeDefinition | Value: SpriteFrames
func get_source_badges(_media_loader: TwitchMediaLoader, scale: int = 1) -> Dictionary[TwitchBadgeDefinition, SpriteFrames]:
var definitions : Array[TwitchBadgeDefinition] = []
for badge in source_badges:
var badge_definition : TwitchBadgeDefinition = TwitchBadgeDefinition.new(badge.set_id, badge.id, scale, broadcaster_user_id)
definitions.append(badge_definition)
var emotes : Dictionary[TwitchBadgeDefinition, SpriteFrames] = await _media_loader.get_badges(definitions)
return emotes
## Returns a the color of the user or the default when its not set never null
func get_color(default_color: String = "#AAAAAA") -> String:
return default_color if color == null || color == "" else color
## Preload all emojis in parallel to reduce loadtime
func load_emotes_from_fragment(_media_loader: TwitchMediaLoader) -> Dictionary[TwitchEmoteDefinition, SpriteFrames]:
var emotes_to_load : Array[TwitchEmoteDefinition] = []
for fragment : TwitchChatMessage.Fragment in message.fragments:
match fragment.type:
TwitchChatMessage.FragmentType.emote:
var definition : TwitchEmoteDefinition = TwitchEmoteDefinition.new(fragment.emote.id)
emotes_to_load.append(definition)
return await _media_loader.get_emotes_by_definition(emotes_to_load)

View file

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

View file

@ -0,0 +1,173 @@
@icon("res://addons/twitcher/assets/command-icon.svg")
extends Twitcher
# Untested yet
## A single command like !lurk
class_name TwitchCommand
static var ALL_COMMANDS: Array[TwitchCommand] = []
## Called when the command got received in the right format
signal command_received(from_username: String, info: TwitchCommandInfo, args: PackedStringArray)
## Called when the command got received in the wrong format
signal received_invalid_command(from_username: String, info: TwitchCommandInfo, args: PackedStringArray)
## Required permission to execute the command
enum PermissionFlag {
EVERYONE = 0,
VIP = 1,
SUB = 2,
MOD = 4,
STREAMER = 8,
MOD_STREAMER = 12, # Mods and the streamer
NON_REGULAR = 15 # Everyone but regular viewers
}
## Where the command should be accepted
enum WhereFlag {
CHAT = 1,
WHISPER = 2,
ANYWHERE = 3
}
@export var command_prefixes : Array[String] = ["!"]
## Name Command
@export var command: String
## Optional names of commands
@export var aliases: Array[String]
## Description for the user
@export_multiline var description: String
## Minimal amount of argument 0 means no argument needed
@export var args_min: int = 0
## Max amount of arguments -1 means infinite
@export var args_max: int = -1
## Wich role of user is allowed to use it
@export var permission_level: PermissionFlag = PermissionFlag.EVERYONE
## Where is it allowed to use chat or whisper or both
@export var where: WhereFlag = WhereFlag.CHAT
## All allowed users empty array means everyone
@export var allowed_users: Array[String] = []
## All chatrooms where the command listens to
@export var listen_to_chatrooms: Array[String] = []
## The eventsub to listen for chatmessages
@export var eventsub: TwitchEventsub
static func create(
eventsub: TwitchEventsub,
cmd_name: String,
callable: Callable,
min_args: int = 0,
max_args: int = 0,
permission_level: int = PermissionFlag.EVERYONE,
where: int = WhereFlag.CHAT,
allowed_users: Array[String] = [],
listen_to_chatrooms: Array[String] = []) -> TwitchCommand:
var command := TwitchCommand.new()
command.eventsub = eventsub
command.command = cmd_name
command.command_received.connect(callable)
command.args_min = min_args
command.args_max = max_args
command.permission_level = permission_level
command.where = where
command.allowed_users = allowed_users
command.listen_to_chatrooms = listen_to_chatrooms
return command
func _enter_tree() -> void:
if eventsub == null: eventsub = TwitchEventsub.instance
eventsub.event.connect(_on_event)
ALL_COMMANDS.append(self)
func _exit_tree() -> void:
eventsub.event.disconnect(_on_event)
ALL_COMMANDS.erase(self)
func _on_event(type: StringName, data: Dictionary) -> void:
if type == TwitchEventsubDefinition.CHANNEL_CHAT_MESSAGE.value:
if where & WhereFlag.CHAT != WhereFlag.CHAT: return
var message : String = data.message.text
var username : String = data.chatter_user_login
var channel_name : String = data.broadcaster_user_login
if not _should_handle(message, username, channel_name): return
var chat_message = TwitchChatMessage.from_json(data)
_handle_command(username, message, channel_name, chat_message)
if type == TwitchEventsubDefinition.USER_WHISPER_MESSAGE.value:
if where & WhereFlag.WHISPER != WhereFlag.WHISPER: return
var message : String = data.whisper.text
var from_user : String = data.from_user_login
if not _should_handle(message, from_user, from_user): return
_handle_command(from_user, message, data.to_user_login, data)
func add_alias(alias: String) -> void:
aliases.append(alias)
func _should_handle(message: String, username: String, channel_name: String) -> bool:
if not listen_to_chatrooms.is_empty() && not listen_to_chatrooms.has(channel_name): return false
if not allowed_users.is_empty() && not allowed_users.has(username): return false
if not command_prefixes.has(message.left(1)): return false
# remove the command symbol in front
message = message.right(-1)
var split : PackedStringArray = message.split(" ", true, 1)
var current_command := split[0]
if current_command != command && not aliases.has(current_command): return false
return true
func _handle_command(from_username: String, raw_message: String, to_user: String, data: Variant) -> void:
# remove the command symbol in front
raw_message = raw_message.right(-1)
var cmd_msg = raw_message.split(" ", true, 1)
var message = ""
var arg_array : PackedStringArray = []
var command = cmd_msg[0]
var info = TwitchCommandInfo.new(self, to_user, from_username, arg_array, data)
if cmd_msg.size() > 1:
message = cmd_msg[1]
arg_array.append_array(message.split(" ", false))
var to_less_arguments = arg_array.size() < args_min
var to_much_arguments = arg_array.size() > args_max
if to_much_arguments && args_max != -1 || to_less_arguments:
received_invalid_command.emit(from_username, info, arg_array)
return
var premission_required = permission_level != 0
if premission_required:
var user_perm_flags = _get_perm_flag_from_tags(data)
if user_perm_flags & permission_level == 0:
received_invalid_command.emit(from_username, info, arg_array)
return
if arg_array.size() == 0:
if args_min > 0:
received_invalid_command.emit(from_username, info, arg_array)
return
var empty_args: Array[String] = []
if args_max > 0:
command_received.emit(from_username, info, empty_args)
else:
command_received.emit(from_username, info, empty_args)
else:
command_received.emit(from_username, info, arg_array)
func _get_perm_flag_from_tags(data : Variant) -> int:
var flag: int = 0
if data is TwitchChatMessage:
var message: TwitchChatMessage = data as TwitchChatMessage
for badge in message.badges:
match badge.set_id:
"broadcaster": flag += PermissionFlag.STREAMER
"vip": flag += PermissionFlag.VIP
"moderator": flag += PermissionFlag.MOD
"subscriber": flag += PermissionFlag.SUB
return flag

View file

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

View file

@ -0,0 +1,83 @@
@icon("res://addons/twitcher/assets/command-icon.svg")
extends TwitchCommand
class_name TwitchCommandHelp
## Used to determine the Sender User if empty and to send the message back
@export var twitch_api: TwitchAPI
## Sender User that will send the answers on the command. Can be empty then the current user will be used
@export var sender_user: TwitchUser
var _current_user: TwitchUser
func _ready() -> void:
if command == "": command = "help"
command_received.connect(_on_command_receive)
if twitch_api == null: twitch_api = TwitchAPI.instance
if twitch_api == null:
push_error("Command is missing TwitchAPI to answer!")
return
var response: TwitchGetUsers.Response = await twitch_api.get_users(TwitchGetUsers.Opt.new())
_current_user = response.data[0]
if sender_user == null: sender_user = _current_user
func _on_command_receive(from_username: String, info: TwitchCommandInfo, args: PackedStringArray) -> void:
if info.original_message is TwitchChatMessage:
var help_message: String = _generate_help_message(args, false)
var chat_message: TwitchChatMessage = info.original_message as TwitchChatMessage
var message_body: TwitchSendChatMessage.Body = TwitchSendChatMessage.Body.new()
message_body.broadcaster_id = chat_message.broadcaster_user_id
message_body.sender_id = sender_user.id
message_body.message = help_message
message_body.reply_parent_message_id = chat_message.message_id
twitch_api.send_chat_message(message_body)
else:
var help_message: String = _generate_help_message(args, true)
var message: Dictionary = info.original_message
if message["to_user_id"] != _current_user.id:
push_error("Can't answer the whisper message receiver is not the user that will be used as sender!")
return
var message_body: TwitchSendWhisper.Body = TwitchSendWhisper.Body.new()
message_body.message = help_message
twitch_api.send_whisper(message_body, message["to_user_id"], message["from_user_id"])
func _generate_help_message(args: Array[String], whisper_only: bool) -> String:
var message: String = ""
var show_details: bool = not args.is_empty()
for command in TwitchCommand.ALL_COMMANDS:
if command == self: continue
var should_be_added: bool = command.where == TwitchCommand.WhereFlag.ANYWHERE \
|| command.where == TwitchCommand.WhereFlag.WHISPER && whisper_only \
|| command.where == TwitchCommand.WhereFlag.CHAT && not whisper_only
if not args.is_empty():
should_be_added = should_be_added && _is_command_in_args(command, args)
if should_be_added:
if show_details:
message += "[%s%s - %s] " % [command.command_prefixes[0], command.command, command.description]
else:
message += "%s%s, " % [command.command_prefixes[0], command.command]
if message == "":
return "No commands registered"
elif not show_details:
message = message.trim_suffix(", ")
message = "List of all Commands: %s | You can use '!help COMMAND' for details!" % message
return message
func _is_command_in_args(command: TwitchCommand, args: Array[String]) -> bool:
for arg in args:
if command.command == arg:
return true
if command.aliases.has(arg):
return true
return false

View file

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

View file

@ -0,0 +1,25 @@
extends RefCounted
## Meta information about the command sender
class_name TwitchCommandInfo
var command : TwitchCommand
var channel_name : String
var username : String
var arguments : Array[String]
## Depending on the type it's either a TwitchChatMessage or a Dictionary of the whisper message data
var original_message : Variant
func _init(
_command: TwitchCommand,
_channel_name: String,
_username: String,
_arguments: Array[String],
_original_message: Variant):
command = _command
channel_name = _channel_name
username = _username
arguments = _arguments
original_message = _original_message

View file

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