Initial Commit
Initial commit of Code Base.
This commit is contained in:
parent
293b1213e1
commit
c11a4ebbc2
653 changed files with 36893 additions and 1 deletions
14
addons/twitcher/chat/twitch_announcement_color.gd
Normal file
14
addons/twitcher/chat/twitch_announcement_color.gd
Normal 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;
|
||||
1
addons/twitcher/chat/twitch_announcement_color.gd.uid
Normal file
1
addons/twitcher/chat/twitch_announcement_color.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://doop8abj8sed6
|
||||
116
addons/twitcher/chat/twitch_chat.gd
Normal file
116
addons/twitcher/chat/twitch_chat.gd
Normal 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
|
||||
1
addons/twitcher/chat/twitch_chat.gd.uid
Normal file
1
addons/twitcher/chat/twitch_chat.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://dcq1bvfrqimqq
|
||||
367
addons/twitcher/chat/twitch_chat_message.gd
Normal file
367
addons/twitcher/chat/twitch_chat_message.gd
Normal 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 thread’s parent message.
|
||||
var thread_user_id: String
|
||||
## User name of the sender of the thread’s parent message.
|
||||
var thread_user_name: String
|
||||
## User login of the sender of the thread’s 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 user’s 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)
|
||||
1
addons/twitcher/chat/twitch_chat_message.gd.uid
Normal file
1
addons/twitcher/chat/twitch_chat_message.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://bxu8no18dq2e3
|
||||
173
addons/twitcher/chat/twitch_command.gd
Normal file
173
addons/twitcher/chat/twitch_command.gd
Normal 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
|
||||
1
addons/twitcher/chat/twitch_command.gd.uid
Normal file
1
addons/twitcher/chat/twitch_command.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://bmluckfvgm1c2
|
||||
83
addons/twitcher/chat/twitch_command_help.gd
Normal file
83
addons/twitcher/chat/twitch_command_help.gd
Normal 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
|
||||
|
||||
1
addons/twitcher/chat/twitch_command_help.gd.uid
Normal file
1
addons/twitcher/chat/twitch_command_help.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://ch0rxi1ogjx3q
|
||||
25
addons/twitcher/chat/twitch_command_info.gd
Normal file
25
addons/twitcher/chat/twitch_command_info.gd
Normal 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
|
||||
1
addons/twitcher/chat/twitch_command_info.gd.uid
Normal file
1
addons/twitcher/chat/twitch_command_info.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://c5k8j4ag3n0su
|
||||
Loading…
Add table
Add a link
Reference in a new issue