feat(plugins): add basic message construction (#5754)
This commit is contained in:
@@ -10,6 +10,7 @@
|
|||||||
- Minor: Added WebSocket API for plugins. (#6076, #6186, #6314, #6315)
|
- Minor: Added WebSocket API for plugins. (#6076, #6186, #6314, #6315)
|
||||||
- Minor: Allow for themes to set transparent values for window background on Linux. (#6137)
|
- Minor: Allow for themes to set transparent values for window background on Linux. (#6137)
|
||||||
- Minor: Popup overlay now only draws an outline when being interacted with. (#6140)
|
- Minor: Popup overlay now only draws an outline when being interacted with. (#6140)
|
||||||
|
- Minor: Added basic message API to plugins. (#5754)
|
||||||
- Minor: Made filters searchable in the Settings dialog search bar. (#5890)
|
- Minor: Made filters searchable in the Settings dialog search bar. (#5890)
|
||||||
- Minor: Updated emojis to Unicode 16.0. (#6155)
|
- Minor: Updated emojis to Unicode 16.0. (#6155)
|
||||||
- Minor: Allow disabling of double-click tab renaming through setting. (#6163, #6184)
|
- Minor: Allow disabling of double-click tab renaming through setting. (#6163, #6184)
|
||||||
|
|||||||
Vendored
+208
@@ -57,6 +57,12 @@ declare namespace c2 {
|
|||||||
|
|
||||||
add_system_message(message: string): void;
|
add_system_message(message: string): void;
|
||||||
|
|
||||||
|
add_message(
|
||||||
|
message: Message,
|
||||||
|
context?: MessageContext,
|
||||||
|
override_flags?: MessageFlag | null
|
||||||
|
): void;
|
||||||
|
|
||||||
is_twitch_channel(): boolean;
|
is_twitch_channel(): boolean;
|
||||||
|
|
||||||
get_room_modes(): RoomModes;
|
get_room_modes(): RoomModes;
|
||||||
@@ -152,4 +158,206 @@ declare namespace c2 {
|
|||||||
) => WebSocket;
|
) => WebSocket;
|
||||||
}
|
}
|
||||||
var WebSocket: WebSocketConstructor;
|
var WebSocket: WebSocketConstructor;
|
||||||
|
|
||||||
|
interface Message {
|
||||||
|
__dummy: void; // avoid being an empty interface
|
||||||
|
}
|
||||||
|
interface MessageConstructor {
|
||||||
|
new: (this: void, init: MessageInit) => Message;
|
||||||
|
}
|
||||||
|
var Message: MessageConstructor;
|
||||||
|
|
||||||
|
interface MessageInit {
|
||||||
|
flags?: MessageFlag;
|
||||||
|
id?: string;
|
||||||
|
parse_time?: number;
|
||||||
|
search_text?: string;
|
||||||
|
message_text?: string;
|
||||||
|
login_name?: string;
|
||||||
|
display_name?: string;
|
||||||
|
localized_name?: string;
|
||||||
|
user_id?: string;
|
||||||
|
channel_name?: string;
|
||||||
|
username_color?: string;
|
||||||
|
server_received_time?: number;
|
||||||
|
highlight_color?: string | null;
|
||||||
|
elements?: MessageElementInit[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MessageElementInitBase {
|
||||||
|
tooltip?: string;
|
||||||
|
trailing_space?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
type MessageColor = "text" | "link" | "system" | string;
|
||||||
|
|
||||||
|
type MessageElementInit =
|
||||||
|
| TextElementInit
|
||||||
|
| SingleLineTextElementInit
|
||||||
|
| MentionElementInit
|
||||||
|
| TimestampElementInit
|
||||||
|
| TwitchModerationElementInit
|
||||||
|
| LinebreakElementInit
|
||||||
|
| ReplyCurveElementInit;
|
||||||
|
|
||||||
|
interface TextElementInit extends MessageElementInitBase {
|
||||||
|
type: "text";
|
||||||
|
text: string;
|
||||||
|
flags?: MessageElementFlag;
|
||||||
|
color?: MessageColor;
|
||||||
|
style?: FontStyle;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SingleLineTextElementInit extends MessageElementInitBase {
|
||||||
|
type: "single-line-text";
|
||||||
|
text: string;
|
||||||
|
flags?: MessageElementFlag;
|
||||||
|
color?: MessageColor;
|
||||||
|
style?: FontStyle;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MentionElementInit extends MessageElementInitBase {
|
||||||
|
type: "mention";
|
||||||
|
display_name: string;
|
||||||
|
login_name: string;
|
||||||
|
fallback_color: MessageColor;
|
||||||
|
user_color: MessageColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TimestampElementInit extends MessageElementInitBase {
|
||||||
|
type: "timestamp";
|
||||||
|
time?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TwitchModerationElementInit extends MessageElementInitBase {
|
||||||
|
type: "twitch-moderation";
|
||||||
|
}
|
||||||
|
|
||||||
|
interface LinebreakElementInit extends MessageElementInitBase {
|
||||||
|
type: "linebreak";
|
||||||
|
flags?: MessageElementFlag;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ReplyCurveElementInit extends MessageElementInitBase {
|
||||||
|
type: "reply-curve";
|
||||||
|
}
|
||||||
|
|
||||||
|
enum MessageFlag {
|
||||||
|
None = 0,
|
||||||
|
System = 0,
|
||||||
|
Timeout = 0,
|
||||||
|
Highlighted = 0,
|
||||||
|
DoNotTriggerNotification = 0,
|
||||||
|
Centered = 0,
|
||||||
|
Disabled = 0,
|
||||||
|
DisableCompactEmotes = 0,
|
||||||
|
Collapsed = 0,
|
||||||
|
ConnectedMessage = 0,
|
||||||
|
DisconnectedMessage = 0,
|
||||||
|
Untimeout = 0,
|
||||||
|
PubSub = 0,
|
||||||
|
Subscription = 0,
|
||||||
|
DoNotLog = 0,
|
||||||
|
AutoMod = 0,
|
||||||
|
RecentMessage = 0,
|
||||||
|
Whisper = 0,
|
||||||
|
HighlightedWhisper = 0,
|
||||||
|
Debug = 0,
|
||||||
|
Similar = 0,
|
||||||
|
RedeemedHighlight = 0,
|
||||||
|
RedeemedChannelPointReward = 0,
|
||||||
|
ShowInMentions = 0,
|
||||||
|
FirstMessage = 0,
|
||||||
|
ReplyMessage = 0,
|
||||||
|
ElevatedMessage = 0,
|
||||||
|
SubscribedThread = 0,
|
||||||
|
CheerMessage = 0,
|
||||||
|
LiveUpdatesAdd = 0,
|
||||||
|
LiveUpdatesRemove = 0,
|
||||||
|
LiveUpdatesUpdate = 0,
|
||||||
|
AutoModOffendingMessageHeader = 0,
|
||||||
|
AutoModOffendingMessage = 0,
|
||||||
|
LowTrustUsers = 0,
|
||||||
|
RestrictedMessage = 0,
|
||||||
|
MonitoredMessage = 0,
|
||||||
|
Action = 0,
|
||||||
|
SharedMessage = 0,
|
||||||
|
AutoModBlockedTerm = 0,
|
||||||
|
ClearChat = 0,
|
||||||
|
EventSub = 0,
|
||||||
|
ModerationAction = 0,
|
||||||
|
InvalidReplyTarget = 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum MessageElementFlag {
|
||||||
|
None = 0,
|
||||||
|
Misc = 0,
|
||||||
|
Text = 0,
|
||||||
|
Username = 0,
|
||||||
|
Timestamp = 0,
|
||||||
|
TwitchEmoteImage = 0,
|
||||||
|
TwitchEmoteText = 0,
|
||||||
|
TwitchEmote = 0,
|
||||||
|
BttvEmoteImage = 0,
|
||||||
|
BttvEmoteText = 0,
|
||||||
|
BttvEmote = 0,
|
||||||
|
ChannelPointReward = 0,
|
||||||
|
ChannelPointRewardImage = 0,
|
||||||
|
FfzEmoteImage = 0,
|
||||||
|
FfzEmoteText = 0,
|
||||||
|
FfzEmote = 0,
|
||||||
|
SevenTVEmoteImage = 0,
|
||||||
|
SevenTVEmoteText = 0,
|
||||||
|
SevenTVEmote = 0,
|
||||||
|
EmoteImages = 0,
|
||||||
|
EmoteText = 0,
|
||||||
|
BitsStatic = 0,
|
||||||
|
BitsAnimated = 0,
|
||||||
|
BadgeSharedChannel = 0,
|
||||||
|
BadgeGlobalAuthority = 0,
|
||||||
|
BadgePredictions = 0,
|
||||||
|
BadgeChannelAuthority = 0,
|
||||||
|
BadgeSubscription = 0,
|
||||||
|
BadgeVanity = 0,
|
||||||
|
BadgeChatterino = 0,
|
||||||
|
BadgeSevenTV = 0,
|
||||||
|
BadgeFfz = 0,
|
||||||
|
Badges = 0,
|
||||||
|
ChannelName = 0,
|
||||||
|
BitsAmount = 0,
|
||||||
|
ModeratorTools = 0,
|
||||||
|
EmojiImage = 0,
|
||||||
|
EmojiText = 0,
|
||||||
|
EmojiAll = 0,
|
||||||
|
AlwaysShow = 0,
|
||||||
|
Collapsed = 0,
|
||||||
|
Mention = 0,
|
||||||
|
LowercaseLinks = 0,
|
||||||
|
RepliedMessage = 0,
|
||||||
|
ReplyButton = 0,
|
||||||
|
Default = 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum FontStyle {
|
||||||
|
Tiny,
|
||||||
|
ChatSmall,
|
||||||
|
ChatMediumSmall,
|
||||||
|
ChatMedium,
|
||||||
|
ChatMediumBold,
|
||||||
|
ChatMediumItalic,
|
||||||
|
ChatLarge,
|
||||||
|
ChatVeryLarge,
|
||||||
|
TimestampMedium,
|
||||||
|
UiMedium,
|
||||||
|
UiMediumBold,
|
||||||
|
UiTabs,
|
||||||
|
EndType,
|
||||||
|
ChatStart,
|
||||||
|
ChatEnd,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum MessageContext {
|
||||||
|
Original,
|
||||||
|
Repost,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -112,6 +112,13 @@ function c2.Channel:send_message(message, execute_commands) end
|
|||||||
---@param message string
|
---@param message string
|
||||||
function c2.Channel:add_system_message(message) end
|
function c2.Channel:add_system_message(message) end
|
||||||
|
|
||||||
|
--- Adds a message client-side
|
||||||
|
---
|
||||||
|
---@param message c2.Message
|
||||||
|
---@param context? c2.MessageContext The context of the message being added
|
||||||
|
---@param override_flags? c2.MessageFlag|nil Flags to override the message's flags (some splits might filter for this)
|
||||||
|
function c2.Channel:add_message(message, context, override_flags) end
|
||||||
|
|
||||||
--- Returns true for twitch channels.
|
--- Returns true for twitch channels.
|
||||||
--- Compares the channel Type. Note that enum values aren't guaranteed, just
|
--- Compares the channel Type. Note that enum values aren't guaranteed, just
|
||||||
--- that they are equal to the exposed enum.
|
--- that they are equal to the exposed enum.
|
||||||
@@ -257,6 +264,227 @@ function c2.HTTPRequest.create(method, url) end
|
|||||||
|
|
||||||
-- End src/controllers/plugins/api/HTTPRequest.hpp
|
-- End src/controllers/plugins/api/HTTPRequest.hpp
|
||||||
|
|
||||||
|
-- Begin src/controllers/plugins/api/Message.hpp
|
||||||
|
|
||||||
|
|
||||||
|
---A chat message
|
||||||
|
---@class c2.Message
|
||||||
|
c2.Message = {}
|
||||||
|
|
||||||
|
---A table to initialize a new message
|
||||||
|
---@class MessageInit
|
||||||
|
---@field flags? c2.MessageFlag Message flags (see `c2.MessageFlags`)
|
||||||
|
---@field id? string The (ideally unique) message ID
|
||||||
|
---@field parse_time? number Time the message was parsed (in milliseconds since epoch)
|
||||||
|
---@field search_text? string Text to that is compared when searching for messages
|
||||||
|
---@field message_text? string The message text (used for filters for example)
|
||||||
|
---@field login_name? string The login name of the sender
|
||||||
|
---@field display_name? string The display name of the sender
|
||||||
|
---@field localized_name? string The localized name of the sender (this is used for CJK names, otherwise it's empty)
|
||||||
|
---@field user_id? string The ID of the user who sent the message
|
||||||
|
---@field channel_name? string The name of the channel this message appeared in
|
||||||
|
---@field username_color? string The color of the username
|
||||||
|
---@field server_received_time? number The time the server received the message (in milliseconds since epoch)
|
||||||
|
---@field highlight_color? string|nil The color of the highlight (if any)
|
||||||
|
---@field elements? MessageElementInit[] The elements of the message
|
||||||
|
|
||||||
|
---A base table to initialize a new message element
|
||||||
|
---@class MessageElementInitBase
|
||||||
|
---@field tooltip? string Tooltip text
|
||||||
|
---@field trailing_space? boolean Whether to add a trailing space after the element (default: true)
|
||||||
|
|
||||||
|
---@alias MessageColor "text"|"link"|"system"|string A color for a text element - "text", "link", and "system" are special values that take the current theme into account
|
||||||
|
|
||||||
|
---A table to initialize a new message text element
|
||||||
|
---@class TextElementInit : MessageElementInitBase
|
||||||
|
---@field type "text" The type of the element
|
||||||
|
---@field text string The text of this element
|
||||||
|
---@field flags? c2.MessageElementFlag Message element flags (see `c2.MessageElementFlags`)
|
||||||
|
---@field color? MessageColor The color of the text
|
||||||
|
---@field style? c2.FontStyle The font style of the text
|
||||||
|
|
||||||
|
---A table to initialize a new message single-line text element
|
||||||
|
---@class SingleLineTextElementInit : MessageElementInitBase
|
||||||
|
---@field type "single-line-text" The type of the element
|
||||||
|
---@field text string The text of this element
|
||||||
|
---@field flags? c2.MessageElementFlag Message element flags (see `c2.MessageElementFlags`)
|
||||||
|
---@field color? MessageColor The color of the text
|
||||||
|
---@field style? c2.FontStyle The font style of the text
|
||||||
|
|
||||||
|
---A table to initialize a new mention element
|
||||||
|
---@class MentionElementInit : MessageElementInitBase
|
||||||
|
---@field type "mention" The type of the element
|
||||||
|
---@field display_name string The display name of the mentioned user
|
||||||
|
---@field login_name string The login name of the mentioned user
|
||||||
|
---@field fallback_color MessageColor The color of the element in case the "Colorize @usernames" is disabled
|
||||||
|
---@field user_color MessageColor The color of the element in case the "Colorize @usernames" is enabled
|
||||||
|
|
||||||
|
---A table to initialize a new timestamp element
|
||||||
|
---@class TimestampElementInit : MessageElementInitBase
|
||||||
|
---@field type "timestamp" The type of the element
|
||||||
|
---@field time number? The time of the timestamp (in milliseconds since epoch). If not provided, the current time is used.
|
||||||
|
|
||||||
|
---A table to initialize a new Twitch moderation element (all the custom moderation buttons)
|
||||||
|
---@class TwitchModerationElementInit : MessageElementInitBase
|
||||||
|
---@field type "twitch-moderation" The type of the element
|
||||||
|
|
||||||
|
---A table to initialize a new linebreak element
|
||||||
|
---@class LinebreakElementInit : MessageElementInitBase
|
||||||
|
---@field type "linebreak" The type of the element
|
||||||
|
---@field flags? c2.MessageElementFlag Message element flags (see `c2.MessageElementFlags`)
|
||||||
|
|
||||||
|
---A table to initialize a new reply curve element
|
||||||
|
---@class ReplyCurveElementInit : MessageElementInitBase
|
||||||
|
---@field type "reply-curve" The type of the element
|
||||||
|
|
||||||
|
---@alias MessageElementInit TextElementInit|SingleLineTextElementInit|MentionElementInit|TimestampElementInit|TwitchModerationElementInit|LinebreakElementInit|ReplyCurveElementInit
|
||||||
|
|
||||||
|
--- Creates a new message
|
||||||
|
---
|
||||||
|
---@param init MessageInit The message initialization table
|
||||||
|
---@return c2.Message msg The new message
|
||||||
|
function c2.Message.new(init) end
|
||||||
|
-- Begin src/singletons/Fonts.hpp
|
||||||
|
|
||||||
|
---@enum c2.FontStyle
|
||||||
|
c2.FontStyle = {
|
||||||
|
Tiny = {}, ---@type c2.FontStyle.Tiny
|
||||||
|
ChatSmall = {}, ---@type c2.FontStyle.ChatSmall
|
||||||
|
ChatMediumSmall = {}, ---@type c2.FontStyle.ChatMediumSmall
|
||||||
|
ChatMedium = {}, ---@type c2.FontStyle.ChatMedium
|
||||||
|
ChatMediumBold = {}, ---@type c2.FontStyle.ChatMediumBold
|
||||||
|
ChatMediumItalic = {}, ---@type c2.FontStyle.ChatMediumItalic
|
||||||
|
ChatLarge = {}, ---@type c2.FontStyle.ChatLarge
|
||||||
|
ChatVeryLarge = {}, ---@type c2.FontStyle.ChatVeryLarge
|
||||||
|
TimestampMedium = {}, ---@type c2.FontStyle.TimestampMedium
|
||||||
|
UiMedium = {}, ---@type c2.FontStyle.UiMedium
|
||||||
|
UiMediumBold = {}, ---@type c2.FontStyle.UiMediumBold
|
||||||
|
UiTabs = {}, ---@type c2.FontStyle.UiTabs
|
||||||
|
EndType = {}, ---@type c2.FontStyle.EndType
|
||||||
|
ChatStart = {}, ---@type c2.FontStyle.ChatStart
|
||||||
|
ChatEnd = {}, ---@type c2.FontStyle.ChatEnd
|
||||||
|
}
|
||||||
|
|
||||||
|
-- End src/singletons/Fonts.hpp
|
||||||
|
|
||||||
|
-- Begin src/messages/MessageElement.hpp
|
||||||
|
|
||||||
|
---@enum c2.MessageElementFlag
|
||||||
|
c2.MessageElementFlag = {
|
||||||
|
None = 0,
|
||||||
|
Misc = 0,
|
||||||
|
Text = 0,
|
||||||
|
Username = 0,
|
||||||
|
Timestamp = 0,
|
||||||
|
TwitchEmoteImage = 0,
|
||||||
|
TwitchEmoteText = 0,
|
||||||
|
TwitchEmote = 0,
|
||||||
|
BttvEmoteImage = 0,
|
||||||
|
BttvEmoteText = 0,
|
||||||
|
BttvEmote = 0,
|
||||||
|
ChannelPointReward = 0,
|
||||||
|
ChannelPointRewardImage = 0,
|
||||||
|
FfzEmoteImage = 0,
|
||||||
|
FfzEmoteText = 0,
|
||||||
|
FfzEmote = 0,
|
||||||
|
SevenTVEmoteImage = 0,
|
||||||
|
SevenTVEmoteText = 0,
|
||||||
|
SevenTVEmote = 0,
|
||||||
|
EmoteImages = 0,
|
||||||
|
EmoteText = 0,
|
||||||
|
BitsStatic = 0,
|
||||||
|
BitsAnimated = 0,
|
||||||
|
BadgeSharedChannel = 0,
|
||||||
|
BadgeGlobalAuthority = 0,
|
||||||
|
BadgePredictions = 0,
|
||||||
|
BadgeChannelAuthority = 0,
|
||||||
|
BadgeSubscription = 0,
|
||||||
|
BadgeVanity = 0,
|
||||||
|
BadgeChatterino = 0,
|
||||||
|
BadgeSevenTV = 0,
|
||||||
|
BadgeFfz = 0,
|
||||||
|
Badges = 0,
|
||||||
|
ChannelName = 0,
|
||||||
|
BitsAmount = 0,
|
||||||
|
ModeratorTools = 0,
|
||||||
|
EmojiImage = 0,
|
||||||
|
EmojiText = 0,
|
||||||
|
EmojiAll = 0,
|
||||||
|
AlwaysShow = 0,
|
||||||
|
Collapsed = 0,
|
||||||
|
Mention = 0,
|
||||||
|
LowercaseLinks = 0,
|
||||||
|
RepliedMessage = 0,
|
||||||
|
ReplyButton = 0,
|
||||||
|
Default = 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
-- End src/messages/MessageElement.hpp
|
||||||
|
|
||||||
|
-- Begin src/messages/MessageFlag.hpp
|
||||||
|
|
||||||
|
---@enum c2.MessageFlag
|
||||||
|
c2.MessageFlag = {
|
||||||
|
None = 0,
|
||||||
|
System = 0,
|
||||||
|
Timeout = 0,
|
||||||
|
Highlighted = 0,
|
||||||
|
DoNotTriggerNotification = 0,
|
||||||
|
Centered = 0,
|
||||||
|
Disabled = 0,
|
||||||
|
DisableCompactEmotes = 0,
|
||||||
|
Collapsed = 0,
|
||||||
|
ConnectedMessage = 0,
|
||||||
|
DisconnectedMessage = 0,
|
||||||
|
Untimeout = 0,
|
||||||
|
PubSub = 0,
|
||||||
|
Subscription = 0,
|
||||||
|
DoNotLog = 0,
|
||||||
|
AutoMod = 0,
|
||||||
|
RecentMessage = 0,
|
||||||
|
Whisper = 0,
|
||||||
|
HighlightedWhisper = 0,
|
||||||
|
Debug = 0,
|
||||||
|
Similar = 0,
|
||||||
|
RedeemedHighlight = 0,
|
||||||
|
RedeemedChannelPointReward = 0,
|
||||||
|
ShowInMentions = 0,
|
||||||
|
FirstMessage = 0,
|
||||||
|
ReplyMessage = 0,
|
||||||
|
ElevatedMessage = 0,
|
||||||
|
SubscribedThread = 0,
|
||||||
|
CheerMessage = 0,
|
||||||
|
LiveUpdatesAdd = 0,
|
||||||
|
LiveUpdatesRemove = 0,
|
||||||
|
LiveUpdatesUpdate = 0,
|
||||||
|
AutoModOffendingMessageHeader = 0,
|
||||||
|
AutoModOffendingMessage = 0,
|
||||||
|
LowTrustUsers = 0,
|
||||||
|
RestrictedMessage = 0,
|
||||||
|
MonitoredMessage = 0,
|
||||||
|
Action = 0,
|
||||||
|
SharedMessage = 0,
|
||||||
|
AutoModBlockedTerm = 0,
|
||||||
|
ClearChat = 0,
|
||||||
|
EventSub = 0,
|
||||||
|
ModerationAction = 0,
|
||||||
|
InvalidReplyTarget = 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
-- End src/messages/MessageFlag.hpp
|
||||||
|
|
||||||
|
-- Begin src/common/enums/MessageContext.hpp
|
||||||
|
|
||||||
|
---@enum c2.MessageContext
|
||||||
|
c2.MessageContext = {
|
||||||
|
Original = {}, ---@type c2.MessageContext.Original
|
||||||
|
Repost = {}, ---@type c2.MessageContext.Repost
|
||||||
|
}
|
||||||
|
|
||||||
|
-- End src/common/enums/MessageContext.hpp
|
||||||
|
|
||||||
|
-- End src/controllers/plugins/api/Message.hpp
|
||||||
|
|
||||||
-- Begin src/controllers/plugins/api/WebSocket.hpp
|
-- Begin src/controllers/plugins/api/WebSocket.hpp
|
||||||
|
|
||||||
---@class c2.WebSocket
|
---@class c2.WebSocket
|
||||||
|
|||||||
@@ -350,6 +350,24 @@ Example:
|
|||||||
pajladas:add_system_message("Hello, world!")
|
pajladas:add_system_message("Hello, world!")
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### `Channel:add_message(message[, context[, override_flags]])`
|
||||||
|
|
||||||
|
Add a rich message to a channel. The message can be created with [`Message.new`](#messagenewdata).
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```lua
|
||||||
|
channel:add_message(c2.Message.new({
|
||||||
|
id = "myplugin-1234",
|
||||||
|
elements = {
|
||||||
|
{
|
||||||
|
type = "text",
|
||||||
|
text = "Hello, World!",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
```
|
||||||
|
|
||||||
##### `Channel:is_twitch_channel()`
|
##### `Channel:is_twitch_channel()`
|
||||||
|
|
||||||
Returns `true` if the channel is a Twitch channel, that is its type name has
|
Returns `true` if the channel is a Twitch channel, that is its type name has
|
||||||
@@ -503,6 +521,42 @@ request:execute()
|
|||||||
-- ConnectionRefusedError
|
-- ConnectionRefusedError
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### `Message`
|
||||||
|
|
||||||
|
Allows creation of rich chat messages. This is currently limited but is expected to be expanded soon.
|
||||||
|
|
||||||
|
##### `Message.new(data)`
|
||||||
|
|
||||||
|
Creates a new message from a table. The message can be added to a channel using
|
||||||
|
[`Channel:add_message`](#channeladd_messagemessage-context-override_flags):
|
||||||
|
|
||||||
|
```lua
|
||||||
|
c2.register_command("/testing", function(ctx)
|
||||||
|
ctx.channel:add_message(c2.Message.new({
|
||||||
|
id = "myplugin-1234",
|
||||||
|
highlight_color = "#80ff0000",
|
||||||
|
flags = c2.MessageFlag.Highlighted,
|
||||||
|
elements = {
|
||||||
|
{
|
||||||
|
type = "text",
|
||||||
|
color = "link",
|
||||||
|
text = "Hover me!",
|
||||||
|
tooltip = "<h1>This is text from my plugin</h1>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type = "mention",
|
||||||
|
display_name = "@User",
|
||||||
|
login_name = "twitchdev",
|
||||||
|
fallback_color = "text",
|
||||||
|
user_color = "blue",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
end)
|
||||||
|
```
|
||||||
|
|
||||||
|
The full range of options can be found in the typing files ([LuaLS](./plugin-meta.lua), [TypeScript](./chatterino.d.ts)).
|
||||||
|
|
||||||
### Input/Output API
|
### Input/Output API
|
||||||
|
|
||||||
These functions are wrappers for Lua's I/O library. Functions on file pointer
|
These functions are wrappers for Lua's I/O library. Functions on file pointer
|
||||||
|
|||||||
+95
-25
@@ -27,6 +27,14 @@ Valid commands are:
|
|||||||
@lua@class and @lua@field have special treatment when it comes to generation of spacing new lines
|
@lua@class and @lua@field have special treatment when it comes to generation of spacing new lines
|
||||||
|
|
||||||
Non-command lines of comments are written with a space after '---'
|
Non-command lines of comments are written with a space after '---'
|
||||||
|
|
||||||
|
To insert larger fragments of documentation, comments like
|
||||||
|
/* @lua-fragment
|
||||||
|
---@class foo
|
||||||
|
...
|
||||||
|
*/
|
||||||
|
will be inserted as-is. These only work in the top-level scope. They don't work
|
||||||
|
for class bodies.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from io import TextIOWrapper
|
from io import TextIOWrapper
|
||||||
@@ -55,10 +63,18 @@ def strip_line(line: str):
|
|||||||
return re.sub(r"^/\*\*|^\*|\*/$", "", line).strip()
|
return re.sub(r"^/\*\*|^\*|\*/$", "", line).strip()
|
||||||
|
|
||||||
|
|
||||||
|
def strip_comments(line: str):
|
||||||
|
return re.sub(r"//.*|/\*.*?\*/", "", line).strip()
|
||||||
|
|
||||||
|
|
||||||
def is_comment_start(line: str):
|
def is_comment_start(line: str):
|
||||||
return line.startswith("/**")
|
return line.startswith("/**")
|
||||||
|
|
||||||
|
|
||||||
|
def is_fragment_start(line: str):
|
||||||
|
return line.startswith("/* @lua-fragment")
|
||||||
|
|
||||||
|
|
||||||
def is_enum_class(line: str):
|
def is_enum_class(line: str):
|
||||||
return line.startswith("enum class")
|
return line.startswith("enum class")
|
||||||
|
|
||||||
@@ -96,20 +112,26 @@ class Reader:
|
|||||||
return self.lines[self.line_idx - 1].strip()
|
return self.lines[self.line_idx - 1].strip()
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def next_doc_comment(self) -> Optional[list[str]]:
|
def next_item(self) -> list[str] | str | None:
|
||||||
|
"""Finds the next documentation comment or fragment and advances the cursor"""
|
||||||
|
# find the start
|
||||||
|
while (line := self.next_line()) is not None:
|
||||||
|
if is_comment_start(line):
|
||||||
|
return self._read_doc_comment(line)
|
||||||
|
elif is_fragment_start(line):
|
||||||
|
return self._read_fragment()
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _read_doc_comment(self, starting_line: str) -> Optional[list[str]]:
|
||||||
"""Reads a documentation comment (/** ... */) and advances the cursor"""
|
"""Reads a documentation comment (/** ... */) and advances the cursor"""
|
||||||
lines = []
|
lines = []
|
||||||
# find the start
|
line = starting_line
|
||||||
while (line := self.next_line()) is not None and not is_comment_start(line):
|
|
||||||
pass
|
|
||||||
if line is None:
|
|
||||||
return None
|
|
||||||
|
|
||||||
stripped = strip_line(line)
|
stripped = strip_line(line)
|
||||||
if stripped:
|
if stripped:
|
||||||
lines.append(stripped)
|
lines.append(stripped)
|
||||||
|
|
||||||
if stripped.endswith("*/"):
|
if line.endswith("*/"):
|
||||||
return lines if lines else None
|
return lines if lines else None
|
||||||
|
|
||||||
while (line := self.next_line()) is not None:
|
while (line := self.next_line()) is not None:
|
||||||
@@ -131,6 +153,17 @@ class Reader:
|
|||||||
|
|
||||||
return lines if lines else None
|
return lines if lines else None
|
||||||
|
|
||||||
|
def _read_fragment(self) -> Optional[str]:
|
||||||
|
"""Reads an inline fragment comment (/* @lua-fragment ... */) and advances the cursor"""
|
||||||
|
|
||||||
|
body = ""
|
||||||
|
while (line := self.next_line()) is not None:
|
||||||
|
if line.endswith("*/"):
|
||||||
|
break
|
||||||
|
body += "\n" + line
|
||||||
|
|
||||||
|
return body
|
||||||
|
|
||||||
def read_class_body(self) -> list[list[str]]:
|
def read_class_body(self) -> list[list[str]]:
|
||||||
"""The reader must be at the first line of the class/struct body. All comments inside the class are returned."""
|
"""The reader must be at the first line of the class/struct body. All comments inside the class are returned."""
|
||||||
items = []
|
items = []
|
||||||
@@ -143,7 +176,8 @@ class Reader:
|
|||||||
nesting += line.count("{") - line.count("}")
|
nesting += line.count("{") - line.count("}")
|
||||||
self.next_line()
|
self.next_line()
|
||||||
continue
|
continue
|
||||||
doc = self.next_doc_comment()
|
doc = self.next_item()
|
||||||
|
assert not isinstance(doc, str), "Fragment inside class body found"
|
||||||
if not doc:
|
if not doc:
|
||||||
break
|
break
|
||||||
items.append(doc)
|
items.append(doc)
|
||||||
@@ -153,6 +187,7 @@ class Reader:
|
|||||||
"""The reader must be before an enum class definition (possibly with some comments before). It returns all variants."""
|
"""The reader must be before an enum class definition (possibly with some comments before). It returns all variants."""
|
||||||
items = []
|
items = []
|
||||||
is_comment = False
|
is_comment = False
|
||||||
|
waiting_for_end = False
|
||||||
while (line := self.peek_line()) is not None and not line.startswith("};"):
|
while (line := self.peek_line()) is not None and not line.startswith("};"):
|
||||||
self.next_line()
|
self.next_line()
|
||||||
if is_comment:
|
if is_comment:
|
||||||
@@ -175,7 +210,16 @@ class Reader:
|
|||||||
if line.startswith("enum class"):
|
if line.startswith("enum class"):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
items.append(line.rstrip(","))
|
if waiting_for_end:
|
||||||
|
if line.endswith(","):
|
||||||
|
waiting_for_end = False
|
||||||
|
continue
|
||||||
|
m = re.match(r"^([\w_]+)", line)
|
||||||
|
if not m:
|
||||||
|
continue
|
||||||
|
|
||||||
|
items.append(m.group(1))
|
||||||
|
waiting_for_end = not strip_comments(line).endswith(",")
|
||||||
|
|
||||||
return items
|
return items
|
||||||
|
|
||||||
@@ -214,6 +258,25 @@ def write_func(path: Path, line: int, comments: list[str], out: TextIOWrapper):
|
|||||||
out.write(f"function {name}({lua_params}) end\n\n")
|
out.write(f"function {name}({lua_params}) end\n\n")
|
||||||
|
|
||||||
|
|
||||||
|
def write_enum(
|
||||||
|
name: str,
|
||||||
|
description: str | None,
|
||||||
|
variants: list[str],
|
||||||
|
is_flags: bool,
|
||||||
|
out: TextIOWrapper,
|
||||||
|
):
|
||||||
|
if description:
|
||||||
|
out.write(f"--- {description}\n")
|
||||||
|
out.write(f"---@enum {name}\n")
|
||||||
|
out.write(f"{name} = {{\n")
|
||||||
|
|
||||||
|
def value(variant):
|
||||||
|
return "0," if is_flags else f"{{}}, ---@type {name}.{variant}"
|
||||||
|
|
||||||
|
out.write("\n".join([f" {variant} = {value(variant)}" for variant in variants]))
|
||||||
|
out.write("\n}\n\n")
|
||||||
|
|
||||||
|
|
||||||
def read_file(path: Path, out: TextIOWrapper):
|
def read_file(path: Path, out: TextIOWrapper):
|
||||||
print("Reading", path.relative_to(repo_root))
|
print("Reading", path.relative_to(repo_root))
|
||||||
with path.open("r") as f:
|
with path.open("r") as f:
|
||||||
@@ -221,9 +284,14 @@ def read_file(path: Path, out: TextIOWrapper):
|
|||||||
|
|
||||||
reader = Reader(lines)
|
reader = Reader(lines)
|
||||||
while reader.has_next():
|
while reader.has_next():
|
||||||
doc_comment = reader.next_doc_comment()
|
doc_comment = reader.next_item()
|
||||||
if not doc_comment:
|
if not doc_comment:
|
||||||
break
|
break
|
||||||
|
elif isinstance(doc_comment, str):
|
||||||
|
out.write(doc_comment)
|
||||||
|
out.write("\n")
|
||||||
|
continue
|
||||||
|
|
||||||
header_comment = None
|
header_comment = None
|
||||||
if not doc_comment[0].startswith("@"):
|
if not doc_comment[0].startswith("@"):
|
||||||
if len(doc_comment) == 1:
|
if len(doc_comment) == 1:
|
||||||
@@ -241,21 +309,23 @@ def read_file(path: Path, out: TextIOWrapper):
|
|||||||
reader.line_no(),
|
reader.line_no(),
|
||||||
f"Invalid enum exposure - one command expected, got {len(header)}",
|
f"Invalid enum exposure - one command expected, got {len(header)}",
|
||||||
)
|
)
|
||||||
name = header[0].split(" ", 1)[1]
|
args = header[0].split(" ")[1:]
|
||||||
printmsg(path, reader.line_no(), f"enum {name}")
|
if len(args) < 1 or len(args) > 2:
|
||||||
if header_comment:
|
panic(
|
||||||
out.write(f"--- {header_comment}\n")
|
path,
|
||||||
out.write(f"---@enum {name}\n")
|
reader.line_no(),
|
||||||
out.write(f"{name} = {{\n")
|
f"Invalid @exposeenum - expected 2 arguments, got {len(args)}",
|
||||||
out.write(
|
|
||||||
"\n".join(
|
|
||||||
[
|
|
||||||
f" {variant} = {{}}, ---@type {name}.{variant}"
|
|
||||||
for variant in reader.read_enum_variants()
|
|
||||||
]
|
|
||||||
)
|
)
|
||||||
|
name = args[0]
|
||||||
|
is_flags = len(args) >= 2 and args[1] == "[flags]"
|
||||||
|
printmsg(path, reader.line_no(), f"enum {name}")
|
||||||
|
write_enum(
|
||||||
|
name,
|
||||||
|
header_comment,
|
||||||
|
reader.read_enum_variants(),
|
||||||
|
is_flags,
|
||||||
|
out,
|
||||||
)
|
)
|
||||||
out.write("\n}\n\n")
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# class
|
# class
|
||||||
@@ -322,7 +392,7 @@ def inline_command(path: Path, line: int, comment: str, out: TextIOWrapper):
|
|||||||
"Unexpected @lua@class command. @lua@class must be placed at the start of the comment block!",
|
"Unexpected @lua@class command. @lua@class must be placed at the start of the comment block!",
|
||||||
)
|
)
|
||||||
elif comment.startswith("@lua@"):
|
elif comment.startswith("@lua@"):
|
||||||
out.write(f'---{comment.replace("@lua", "", 1)}\n')
|
out.write(f"---{comment.replace('@lua', '', 1)}\n")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@@ -250,6 +250,8 @@ set(SOURCE_FILES
|
|||||||
controllers/plugins/api/HTTPResponse.hpp
|
controllers/plugins/api/HTTPResponse.hpp
|
||||||
controllers/plugins/api/IOWrapper.cpp
|
controllers/plugins/api/IOWrapper.cpp
|
||||||
controllers/plugins/api/IOWrapper.hpp
|
controllers/plugins/api/IOWrapper.hpp
|
||||||
|
controllers/plugins/api/Message.cpp
|
||||||
|
controllers/plugins/api/Message.hpp
|
||||||
controllers/plugins/api/WebSocket.cpp
|
controllers/plugins/api/WebSocket.cpp
|
||||||
controllers/plugins/api/WebSocket.hpp
|
controllers/plugins/api/WebSocket.hpp
|
||||||
controllers/plugins/LuaAPI.cpp
|
controllers/plugins/LuaAPI.cpp
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
|
/** @exposeenum c2.MessageContext */
|
||||||
|
|
||||||
/// Context of the message being added to a channel
|
/// Context of the message being added to a channel
|
||||||
enum class MessageContext {
|
enum class MessageContext {
|
||||||
/// This message is the original
|
/// This message is the original
|
||||||
|
|||||||
@@ -85,6 +85,7 @@ sol::table toTable(lua_State *L, const CompletionEvent &ev);
|
|||||||
* @includefile controllers/plugins/api/ChannelRef.hpp
|
* @includefile controllers/plugins/api/ChannelRef.hpp
|
||||||
* @includefile controllers/plugins/api/HTTPResponse.hpp
|
* @includefile controllers/plugins/api/HTTPResponse.hpp
|
||||||
* @includefile controllers/plugins/api/HTTPRequest.hpp
|
* @includefile controllers/plugins/api/HTTPRequest.hpp
|
||||||
|
* @includefile controllers/plugins/api/Message.hpp
|
||||||
* @includefile controllers/plugins/api/WebSocket.hpp
|
* @includefile controllers/plugins/api/WebSocket.hpp
|
||||||
* @includefile common/network/NetworkCommon.hpp
|
* @includefile common/network/NetworkCommon.hpp
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -111,19 +111,18 @@ public:
|
|||||||
*
|
*
|
||||||
* @returns Sol reference to the table
|
* @returns Sol reference to the table
|
||||||
*/
|
*/
|
||||||
template <typename T>
|
template <typename T, T... Additional>
|
||||||
requires std::is_enum_v<T>
|
requires std::is_enum_v<T>
|
||||||
sol::table createEnumTable(sol::state_view &lua)
|
sol::table createEnumTable(sol::state_view &lua)
|
||||||
{
|
{
|
||||||
constexpr auto values = magic_enum::enum_values<T>();
|
constexpr auto values = magic_enum::enum_values<T>();
|
||||||
auto out = lua.create_table(0, values.size());
|
auto out = lua.create_table(0, values.size() + sizeof...(Additional));
|
||||||
for (const T v : values)
|
for (const T v : values)
|
||||||
{
|
{
|
||||||
std::string_view name = magic_enum::enum_name<T>(v);
|
out.raw_set(magic_enum::enum_name<T>(v), v);
|
||||||
std::string str(name);
|
|
||||||
|
|
||||||
out.raw_set(str, v);
|
|
||||||
}
|
}
|
||||||
|
(out.raw_set(magic_enum::enum_name<Additional>(), Additional), ...);
|
||||||
|
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,11 +11,13 @@
|
|||||||
# include "controllers/plugins/api/HTTPRequest.hpp"
|
# include "controllers/plugins/api/HTTPRequest.hpp"
|
||||||
# include "controllers/plugins/api/HTTPResponse.hpp"
|
# include "controllers/plugins/api/HTTPResponse.hpp"
|
||||||
# include "controllers/plugins/api/IOWrapper.hpp"
|
# include "controllers/plugins/api/IOWrapper.hpp"
|
||||||
|
# include "controllers/plugins/api/Message.hpp"
|
||||||
# include "controllers/plugins/api/WebSocket.hpp"
|
# include "controllers/plugins/api/WebSocket.hpp"
|
||||||
# include "controllers/plugins/LuaAPI.hpp"
|
# include "controllers/plugins/LuaAPI.hpp"
|
||||||
# include "controllers/plugins/LuaUtilities.hpp"
|
# include "controllers/plugins/LuaUtilities.hpp"
|
||||||
# include "controllers/plugins/SolTypes.hpp"
|
# include "controllers/plugins/SolTypes.hpp"
|
||||||
# include "messages/MessageBuilder.hpp"
|
# include "messages/MessageBuilder.hpp"
|
||||||
|
# include "messages/MessageElement.hpp"
|
||||||
# include "singletons/Paths.hpp"
|
# include "singletons/Paths.hpp"
|
||||||
# include "singletons/Settings.hpp"
|
# include "singletons/Settings.hpp"
|
||||||
|
|
||||||
@@ -222,10 +224,16 @@ void PluginController::initSol(sol::state_view &lua, Plugin *plugin)
|
|||||||
lua::api::HTTPResponse::createUserType(c2);
|
lua::api::HTTPResponse::createUserType(c2);
|
||||||
lua::api::HTTPRequest::createUserType(c2);
|
lua::api::HTTPRequest::createUserType(c2);
|
||||||
lua::api::WebSocket::createUserType(c2, plugin);
|
lua::api::WebSocket::createUserType(c2, plugin);
|
||||||
|
lua::api::message::createUserType(c2);
|
||||||
c2["ChannelType"] = lua::createEnumTable<Channel::Type>(lua);
|
c2["ChannelType"] = lua::createEnumTable<Channel::Type>(lua);
|
||||||
c2["HTTPMethod"] = lua::createEnumTable<NetworkRequestType>(lua);
|
c2["HTTPMethod"] = lua::createEnumTable<NetworkRequestType>(lua);
|
||||||
c2["EventType"] = lua::createEnumTable<lua::api::EventType>(lua);
|
c2["EventType"] = lua::createEnumTable<lua::api::EventType>(lua);
|
||||||
c2["LogLevel"] = lua::createEnumTable<lua::api::LogLevel>(lua);
|
c2["LogLevel"] = lua::createEnumTable<lua::api::LogLevel>(lua);
|
||||||
|
c2["MessageFlag"] =
|
||||||
|
lua::createEnumTable<MessageFlag, MessageFlag::None>(lua);
|
||||||
|
c2["MessageElementFlag"] = lua::createEnumTable<MessageElementFlag>(lua);
|
||||||
|
c2["FontStyle"] = lua::createEnumTable<FontStyle>(lua);
|
||||||
|
c2["MessageContext"] = lua::createEnumTable<MessageContext>(lua);
|
||||||
|
|
||||||
sol::table io = g["io"];
|
sol::table io = g["io"];
|
||||||
io.set_function(
|
io.set_function(
|
||||||
|
|||||||
@@ -88,6 +88,32 @@ void ChannelRef::add_system_message(QString text)
|
|||||||
this->strong()->addSystemMessage(text);
|
this->strong()->addSystemMessage(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ChannelRef::add_message(std::shared_ptr<Message> &message,
|
||||||
|
sol::variadic_args va)
|
||||||
|
{
|
||||||
|
MessageContext ctx = [&] {
|
||||||
|
if (va.size() >= 1)
|
||||||
|
{
|
||||||
|
return va.get<MessageContext>();
|
||||||
|
}
|
||||||
|
return MessageContext::Original;
|
||||||
|
}();
|
||||||
|
auto overrideFlags = [&]() -> std::optional<MessageFlags> {
|
||||||
|
if (va.size() >= 2)
|
||||||
|
{
|
||||||
|
auto flags = va.get<std::optional<MessageFlag>>(1);
|
||||||
|
if (flags)
|
||||||
|
{
|
||||||
|
return MessageFlags{*flags};
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}();
|
||||||
|
|
||||||
|
this->strong()->addMessage(message, ctx, overrideFlags);
|
||||||
|
}
|
||||||
|
|
||||||
bool ChannelRef::is_twitch_channel()
|
bool ChannelRef::is_twitch_channel()
|
||||||
{
|
{
|
||||||
return this->strong()->isTwitchChannel();
|
return this->strong()->isTwitchChannel();
|
||||||
@@ -168,6 +194,7 @@ void ChannelRef::createUserType(sol::table &c2)
|
|||||||
"get_display_name", &ChannelRef::get_display_name,
|
"get_display_name", &ChannelRef::get_display_name,
|
||||||
"send_message", &ChannelRef::send_message,
|
"send_message", &ChannelRef::send_message,
|
||||||
"add_system_message", &ChannelRef::add_system_message,
|
"add_system_message", &ChannelRef::add_system_message,
|
||||||
|
"add_message", &ChannelRef::add_message,
|
||||||
"is_twitch_channel", &ChannelRef::is_twitch_channel,
|
"is_twitch_channel", &ChannelRef::is_twitch_channel,
|
||||||
|
|
||||||
// TwitchChannel
|
// TwitchChannel
|
||||||
|
|||||||
@@ -71,6 +71,16 @@ public:
|
|||||||
*/
|
*/
|
||||||
void add_system_message(QString text);
|
void add_system_message(QString text);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a message client-side
|
||||||
|
*
|
||||||
|
* @lua@param message c2.Message
|
||||||
|
* @lua@param context? c2.MessageContext The context of the message being added
|
||||||
|
* @lua@param override_flags? c2.MessageFlag|nil Flags to override the message's flags (some splits might filter for this)
|
||||||
|
* @exposed c2.Channel:add_message
|
||||||
|
*/
|
||||||
|
void add_message(std::shared_ptr<Message> &message, sol::variadic_args va);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true for twitch channels.
|
* Returns true for twitch channels.
|
||||||
* Compares the channel Type. Note that enum values aren't guaranteed, just
|
* Compares the channel Type. Note that enum values aren't guaranteed, just
|
||||||
|
|||||||
@@ -0,0 +1,249 @@
|
|||||||
|
#include "controllers/plugins/api/Message.hpp"
|
||||||
|
|
||||||
|
#include "Application.hpp"
|
||||||
|
#include "messages/MessageElement.hpp"
|
||||||
|
|
||||||
|
#ifdef CHATTERINO_HAVE_PLUGINS
|
||||||
|
|
||||||
|
# include "controllers/plugins/SolTypes.hpp"
|
||||||
|
# include "messages/Message.hpp"
|
||||||
|
|
||||||
|
# include <sol/sol.hpp>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
using namespace chatterino;
|
||||||
|
|
||||||
|
QDateTime datetimeFromOffset(qint64 offset)
|
||||||
|
{
|
||||||
|
auto dt = QDateTime::fromMSecsSinceEpoch(offset);
|
||||||
|
|
||||||
|
# ifdef CHATTERINO_WITH_TESTS
|
||||||
|
if (getApp()->isTest())
|
||||||
|
{
|
||||||
|
return dt.toUTC();
|
||||||
|
}
|
||||||
|
# endif
|
||||||
|
|
||||||
|
return dt;
|
||||||
|
}
|
||||||
|
|
||||||
|
MessageColor tryMakeMessageColor(const QString &name,
|
||||||
|
MessageColor fallback = MessageColor::Text)
|
||||||
|
{
|
||||||
|
if (name.isEmpty())
|
||||||
|
{
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
if (name == u"text")
|
||||||
|
{
|
||||||
|
return MessageColor::Text;
|
||||||
|
}
|
||||||
|
if (name == u"link")
|
||||||
|
{
|
||||||
|
return MessageColor::Link;
|
||||||
|
}
|
||||||
|
if (name == u"system")
|
||||||
|
{
|
||||||
|
return MessageColor::System;
|
||||||
|
}
|
||||||
|
// custom
|
||||||
|
return QColor(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
T requiredGet(const sol::table &tbl, auto &&key)
|
||||||
|
{
|
||||||
|
auto v = tbl.get<sol::optional<T>>(std::forward<decltype(key)>(key));
|
||||||
|
if (!v)
|
||||||
|
{
|
||||||
|
throw std::runtime_error(std::string{"Missing required property: "} +
|
||||||
|
key);
|
||||||
|
}
|
||||||
|
return *std::move(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<TextElement> textElementFromTable(const sol::table &tbl)
|
||||||
|
{
|
||||||
|
return std::make_unique<TextElement>(
|
||||||
|
requiredGet<QString>(tbl, "text"),
|
||||||
|
tbl.get_or("flags", MessageElementFlag::Text),
|
||||||
|
tryMakeMessageColor(tbl.get_or("color", QString{})),
|
||||||
|
tbl.get_or("style", FontStyle::ChatMedium));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<SingleLineTextElement> singleLineTextElementFromTable(
|
||||||
|
const sol::table &tbl)
|
||||||
|
{
|
||||||
|
return std::make_unique<SingleLineTextElement>(
|
||||||
|
requiredGet<QString>(tbl, "text"),
|
||||||
|
tbl.get_or("flags", MessageElementFlag::Text),
|
||||||
|
tryMakeMessageColor(tbl.get_or("color", QString{})),
|
||||||
|
tbl.get_or("style", FontStyle::ChatMedium));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<MentionElement> mentionElementFromTable(const sol::table &tbl)
|
||||||
|
{
|
||||||
|
// no flags!
|
||||||
|
return std::make_unique<MentionElement>(
|
||||||
|
requiredGet<QString>(tbl, "display_name"),
|
||||||
|
requiredGet<QString>(tbl, "login_name"),
|
||||||
|
tryMakeMessageColor(requiredGet<QString>(tbl, "fallback_color")),
|
||||||
|
tryMakeMessageColor(requiredGet<QString>(tbl, "user_color")));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<TimestampElement> timestampElementFromTable(
|
||||||
|
const sol::table &tbl)
|
||||||
|
{
|
||||||
|
// no flags!
|
||||||
|
auto time = tbl.get<std::optional<qint64>>("time");
|
||||||
|
if (time)
|
||||||
|
{
|
||||||
|
return std::make_unique<TimestampElement>(
|
||||||
|
datetimeFromOffset(*time).time());
|
||||||
|
}
|
||||||
|
return std::make_unique<TimestampElement>();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<TwitchModerationElement> twitchModerationElementFromTable()
|
||||||
|
{
|
||||||
|
// no flags!
|
||||||
|
return std::make_unique<TwitchModerationElement>();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<LinebreakElement> linebreakElementFromTable(
|
||||||
|
const sol::table &tbl)
|
||||||
|
{
|
||||||
|
return std::make_unique<LinebreakElement>(
|
||||||
|
tbl.get_or("flags", MessageElementFlag::None));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<ReplyCurveElement> replyCurveElementFromTable()
|
||||||
|
{
|
||||||
|
// no flags!
|
||||||
|
return std::make_unique<ReplyCurveElement>();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<MessageElement> elementFromTable(const sol::table &tbl)
|
||||||
|
{
|
||||||
|
auto type = requiredGet<QString>(tbl, "type");
|
||||||
|
std::unique_ptr<MessageElement> el;
|
||||||
|
if (type == u"text")
|
||||||
|
{
|
||||||
|
el = textElementFromTable(tbl);
|
||||||
|
}
|
||||||
|
else if (type == u"single-line-text")
|
||||||
|
{
|
||||||
|
el = singleLineTextElementFromTable(tbl);
|
||||||
|
}
|
||||||
|
else if (type == u"mention")
|
||||||
|
{
|
||||||
|
el = mentionElementFromTable(tbl);
|
||||||
|
}
|
||||||
|
else if (type == u"timestamp")
|
||||||
|
{
|
||||||
|
el = timestampElementFromTable(tbl);
|
||||||
|
}
|
||||||
|
else if (type == u"twitch-moderation")
|
||||||
|
{
|
||||||
|
el = twitchModerationElementFromTable();
|
||||||
|
}
|
||||||
|
else if (type == u"linebreak")
|
||||||
|
{
|
||||||
|
el = linebreakElementFromTable(tbl);
|
||||||
|
}
|
||||||
|
else if (type == u"reply-curve")
|
||||||
|
{
|
||||||
|
el = replyCurveElementFromTable();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Invalid message type");
|
||||||
|
}
|
||||||
|
assert(el);
|
||||||
|
|
||||||
|
el->setTrailingSpace(tbl.get_or("trailing_space", true));
|
||||||
|
el->setTooltip(tbl.get_or("tooltip", QString{}));
|
||||||
|
|
||||||
|
return el;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<Message> messageFromTable(const sol::table &tbl)
|
||||||
|
{
|
||||||
|
auto msg = std::make_shared<Message>();
|
||||||
|
msg->flags = tbl.get_or("flags", MessageFlag::None);
|
||||||
|
|
||||||
|
// This takes a UTC offset (not the milliseconds since the start of the day)
|
||||||
|
auto parseTime = tbl.get<std::optional<qint64>>("parse_time");
|
||||||
|
if (parseTime)
|
||||||
|
{
|
||||||
|
msg->parseTime = datetimeFromOffset(*parseTime).time();
|
||||||
|
}
|
||||||
|
|
||||||
|
msg->id = tbl.get_or("id", QString{});
|
||||||
|
msg->searchText = tbl.get_or("search_text", QString{});
|
||||||
|
msg->messageText = tbl.get_or("message_text", QString{});
|
||||||
|
msg->loginName = tbl.get_or("login_name", QString{});
|
||||||
|
msg->displayName = tbl.get_or("display_name", QString{});
|
||||||
|
msg->localizedName = tbl.get_or("localized_name", QString{});
|
||||||
|
msg->userID = tbl.get_or("user_id", QString{});
|
||||||
|
// missing: timeoutUser
|
||||||
|
msg->channelName = tbl.get_or("channel_name", QString{});
|
||||||
|
|
||||||
|
auto usernameColor = tbl.get_or("username_color", QString{});
|
||||||
|
if (!usernameColor.isEmpty())
|
||||||
|
{
|
||||||
|
msg->usernameColor = QColor(usernameColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto serverReceivedTime =
|
||||||
|
tbl.get<std::optional<qint64>>("server_received_time");
|
||||||
|
if (serverReceivedTime)
|
||||||
|
{
|
||||||
|
msg->serverReceivedTime = datetimeFromOffset(*serverReceivedTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
// missing: badges
|
||||||
|
// missing: badgeInfos
|
||||||
|
|
||||||
|
// we construct a color on the fly here
|
||||||
|
auto highlightColor = tbl.get_or("highlight_color", QString{});
|
||||||
|
if (!highlightColor.isEmpty())
|
||||||
|
{
|
||||||
|
msg->highlightColor = std::make_shared<QColor>(highlightColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
// missing: replyThread
|
||||||
|
// missing: replyParent
|
||||||
|
// missing: count
|
||||||
|
|
||||||
|
auto elements = tbl.get<std::optional<sol::table>>("elements");
|
||||||
|
if (elements)
|
||||||
|
{
|
||||||
|
auto size = elements->size();
|
||||||
|
for (size_t i = 1; i <= size; i++)
|
||||||
|
{
|
||||||
|
msg->elements.emplace_back(
|
||||||
|
elementFromTable(elements->get<sol::table>(i)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// missing: reward
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
namespace chatterino::lua::api::message {
|
||||||
|
|
||||||
|
void createUserType(sol::table &c2)
|
||||||
|
{
|
||||||
|
c2.new_usertype<Message>("Message",
|
||||||
|
sol::factories([](const sol::table &tbl) {
|
||||||
|
return messageFromTable(tbl);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace chatterino::lua::api::message
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
#pragma once
|
||||||
|
#ifdef CHATTERINO_HAVE_PLUGINS
|
||||||
|
# include "messages/Message.hpp"
|
||||||
|
|
||||||
|
# include <sol/forward.hpp>
|
||||||
|
|
||||||
|
namespace chatterino::lua::api::message {
|
||||||
|
|
||||||
|
/* @lua-fragment
|
||||||
|
---A chat message
|
||||||
|
---@class c2.Message
|
||||||
|
c2.Message = {}
|
||||||
|
|
||||||
|
---A table to initialize a new message
|
||||||
|
---@class MessageInit
|
||||||
|
---@field flags? c2.MessageFlag Message flags (see `c2.MessageFlags`)
|
||||||
|
---@field id? string The (ideally unique) message ID
|
||||||
|
---@field parse_time? number Time the message was parsed (in milliseconds since epoch)
|
||||||
|
---@field search_text? string Text to that is compared when searching for messages
|
||||||
|
---@field message_text? string The message text (used for filters for example)
|
||||||
|
---@field login_name? string The login name of the sender
|
||||||
|
---@field display_name? string The display name of the sender
|
||||||
|
---@field localized_name? string The localized name of the sender (this is used for CJK names, otherwise it's empty)
|
||||||
|
---@field user_id? string The ID of the user who sent the message
|
||||||
|
---@field channel_name? string The name of the channel this message appeared in
|
||||||
|
---@field username_color? string The color of the username
|
||||||
|
---@field server_received_time? number The time the server received the message (in milliseconds since epoch)
|
||||||
|
---@field highlight_color? string|nil The color of the highlight (if any)
|
||||||
|
---@field elements? MessageElementInit[] The elements of the message
|
||||||
|
|
||||||
|
---A base table to initialize a new message element
|
||||||
|
---@class MessageElementInitBase
|
||||||
|
---@field tooltip? string Tooltip text
|
||||||
|
---@field trailing_space? boolean Whether to add a trailing space after the element (default: true)
|
||||||
|
|
||||||
|
---@alias MessageColor "text"|"link"|"system"|string A color for a text element - "text", "link", and "system" are special values that take the current theme into account
|
||||||
|
|
||||||
|
---A table to initialize a new message text element
|
||||||
|
---@class TextElementInit : MessageElementInitBase
|
||||||
|
---@field type "text" The type of the element
|
||||||
|
---@field text string The text of this element
|
||||||
|
---@field flags? c2.MessageElementFlag Message element flags (see `c2.MessageElementFlags`)
|
||||||
|
---@field color? MessageColor The color of the text
|
||||||
|
---@field style? c2.FontStyle The font style of the text
|
||||||
|
|
||||||
|
---A table to initialize a new message single-line text element
|
||||||
|
---@class SingleLineTextElementInit : MessageElementInitBase
|
||||||
|
---@field type "single-line-text" The type of the element
|
||||||
|
---@field text string The text of this element
|
||||||
|
---@field flags? c2.MessageElementFlag Message element flags (see `c2.MessageElementFlags`)
|
||||||
|
---@field color? MessageColor The color of the text
|
||||||
|
---@field style? c2.FontStyle The font style of the text
|
||||||
|
|
||||||
|
---A table to initialize a new mention element
|
||||||
|
---@class MentionElementInit : MessageElementInitBase
|
||||||
|
---@field type "mention" The type of the element
|
||||||
|
---@field display_name string The display name of the mentioned user
|
||||||
|
---@field login_name string The login name of the mentioned user
|
||||||
|
---@field fallback_color MessageColor The color of the element in case the "Colorize @usernames" is disabled
|
||||||
|
---@field user_color MessageColor The color of the element in case the "Colorize @usernames" is enabled
|
||||||
|
|
||||||
|
---A table to initialize a new timestamp element
|
||||||
|
---@class TimestampElementInit : MessageElementInitBase
|
||||||
|
---@field type "timestamp" The type of the element
|
||||||
|
---@field time number? The time of the timestamp (in milliseconds since epoch). If not provided, the current time is used.
|
||||||
|
|
||||||
|
---A table to initialize a new Twitch moderation element (all the custom moderation buttons)
|
||||||
|
---@class TwitchModerationElementInit : MessageElementInitBase
|
||||||
|
---@field type "twitch-moderation" The type of the element
|
||||||
|
|
||||||
|
---A table to initialize a new linebreak element
|
||||||
|
---@class LinebreakElementInit : MessageElementInitBase
|
||||||
|
---@field type "linebreak" The type of the element
|
||||||
|
---@field flags? c2.MessageElementFlag Message element flags (see `c2.MessageElementFlags`)
|
||||||
|
|
||||||
|
---A table to initialize a new reply curve element
|
||||||
|
---@class ReplyCurveElementInit : MessageElementInitBase
|
||||||
|
---@field type "reply-curve" The type of the element
|
||||||
|
|
||||||
|
---@alias MessageElementInit TextElementInit|SingleLineTextElementInit|MentionElementInit|TimestampElementInit|TwitchModerationElementInit|LinebreakElementInit|ReplyCurveElementInit
|
||||||
|
|
||||||
|
--- Creates a new message
|
||||||
|
---
|
||||||
|
---@param init MessageInit The message initialization table
|
||||||
|
---@return c2.Message msg The new message
|
||||||
|
function c2.Message.new(init) end
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @includefile singletons/Fonts.hpp
|
||||||
|
* @includefile messages/MessageElement.hpp
|
||||||
|
* @includefile messages/MessageFlag.hpp
|
||||||
|
* @includefile common/enums/MessageContext.hpp
|
||||||
|
*/
|
||||||
|
|
||||||
|
/// Creates the c2.Message user type
|
||||||
|
void createUserType(sol::table &c2);
|
||||||
|
|
||||||
|
} // namespace chatterino::lua::api::message
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -31,6 +31,7 @@ using ImagePtr = std::shared_ptr<Image>;
|
|||||||
struct Emote;
|
struct Emote;
|
||||||
using EmotePtr = std::shared_ptr<const Emote>;
|
using EmotePtr = std::shared_ptr<const Emote>;
|
||||||
|
|
||||||
|
/** @exposeenum c2.MessageElementFlag [flags] */
|
||||||
enum class MessageElementFlag : int64_t {
|
enum class MessageElementFlag : int64_t {
|
||||||
None = 0LL,
|
None = 0LL,
|
||||||
Misc = (1LL << 0),
|
Misc = (1LL << 0),
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
|
/** @exposeenum c2.MessageFlag [flags] */
|
||||||
enum class MessageFlag : std::int64_t {
|
enum class MessageFlag : std::int64_t {
|
||||||
None = 0LL,
|
None = 0LL,
|
||||||
System = (1LL << 0),
|
System = (1LL << 0),
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ namespace chatterino {
|
|||||||
class Settings;
|
class Settings;
|
||||||
class Paths;
|
class Paths;
|
||||||
|
|
||||||
|
/** @exposeenum c2.FontStyle */
|
||||||
enum class FontStyle : uint8_t {
|
enum class FontStyle : uint8_t {
|
||||||
Tiny,
|
Tiny,
|
||||||
ChatSmall,
|
ChatSmall,
|
||||||
|
|||||||
@@ -2131,10 +2131,9 @@ void ChannelView::mouseMoveEvent(QMouseEvent *event)
|
|||||||
element->getTooltip(), getTooltipScale(scale)));
|
element->getTooltip(), getTooltipScale(scale)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else if (auto *linkElement = dynamic_cast<LinkElement *>(element))
|
||||||
{
|
{
|
||||||
auto thumbnailSize = getSettings()->thumbnailSize;
|
auto thumbnailSize = getSettings()->thumbnailSize;
|
||||||
auto *linkElement = dynamic_cast<LinkElement *>(element);
|
|
||||||
if (linkElement)
|
if (linkElement)
|
||||||
{
|
{
|
||||||
if (linkElement->linkInfo()->isPending())
|
if (linkElement->linkInfo()->isPending())
|
||||||
@@ -2145,6 +2144,13 @@ void ChannelView::mouseMoveEvent(QMouseEvent *event)
|
|||||||
this->setLinkInfoTooltip(linkElement->linkInfo());
|
this->setLinkInfoTooltip(linkElement->linkInfo());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this->tooltipWidget_->setOne(TooltipEntry{
|
||||||
|
.image = nullptr,
|
||||||
|
.text = element->getTooltip(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
this->tooltipWidget_->moveTo(
|
this->tooltipWidget_->moveTo(
|
||||||
event->globalPosition().toPoint() + QPoint(16, 16),
|
event->globalPosition().toPoint() + QPoint(16, 16),
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"input": "msg = {}",
|
||||||
|
"output": {
|
||||||
|
"badgeInfos": {
|
||||||
|
},
|
||||||
|
"badges": [
|
||||||
|
],
|
||||||
|
"channelName": "",
|
||||||
|
"count": 1,
|
||||||
|
"displayName": "",
|
||||||
|
"elements": [
|
||||||
|
],
|
||||||
|
"flags": "",
|
||||||
|
"id": "",
|
||||||
|
"localizedName": "",
|
||||||
|
"loginName": "",
|
||||||
|
"messageText": "",
|
||||||
|
"searchText": "",
|
||||||
|
"serverReceivedTime": "",
|
||||||
|
"timeoutUser": "",
|
||||||
|
"userID": "",
|
||||||
|
"usernameColor": "#ff000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
{
|
||||||
|
"input": [
|
||||||
|
"msg = {elements={",
|
||||||
|
" { type = 'linebreak' },",
|
||||||
|
" { type = 'linebreak', flags = c2.MessageElementFlag.BttvEmoteImage },",
|
||||||
|
" { type = 'linebreak', tooltip = 't' },",
|
||||||
|
" { type = 'linebreak', trailing_space = false },",
|
||||||
|
"}}"
|
||||||
|
],
|
||||||
|
"output": {
|
||||||
|
"badgeInfos": {
|
||||||
|
},
|
||||||
|
"badges": [
|
||||||
|
],
|
||||||
|
"channelName": "",
|
||||||
|
"count": 1,
|
||||||
|
"displayName": "",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"flags": "",
|
||||||
|
"link": {
|
||||||
|
"type": "None",
|
||||||
|
"value": ""
|
||||||
|
},
|
||||||
|
"tooltip": "",
|
||||||
|
"trailingSpace": true,
|
||||||
|
"type": "LinebreakElement"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"flags": "BttvEmoteImage",
|
||||||
|
"link": {
|
||||||
|
"type": "None",
|
||||||
|
"value": ""
|
||||||
|
},
|
||||||
|
"tooltip": "",
|
||||||
|
"trailingSpace": true,
|
||||||
|
"type": "LinebreakElement"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"flags": "",
|
||||||
|
"link": {
|
||||||
|
"type": "None",
|
||||||
|
"value": ""
|
||||||
|
},
|
||||||
|
"tooltip": "t",
|
||||||
|
"trailingSpace": true,
|
||||||
|
"type": "LinebreakElement"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"flags": "",
|
||||||
|
"link": {
|
||||||
|
"type": "None",
|
||||||
|
"value": ""
|
||||||
|
},
|
||||||
|
"tooltip": "",
|
||||||
|
"trailingSpace": false,
|
||||||
|
"type": "LinebreakElement"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"flags": "",
|
||||||
|
"id": "",
|
||||||
|
"localizedName": "",
|
||||||
|
"loginName": "",
|
||||||
|
"messageText": "",
|
||||||
|
"searchText": "",
|
||||||
|
"serverReceivedTime": "",
|
||||||
|
"timeoutUser": "",
|
||||||
|
"userID": "",
|
||||||
|
"usernameColor": "#ff000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
{
|
||||||
|
"input": [
|
||||||
|
"msg = {elements={",
|
||||||
|
" { type = 'mention', display_name = 'd', login_name = 'l', fallback_color = 'red', user_color = 'green' },",
|
||||||
|
" { type = 'mention', display_name = 'd', login_name = 'l', fallback_color = 'red', user_color = 'green', flags = c2.MessageElementFlag.BttvEmote },",
|
||||||
|
" { type = 'mention', display_name = 'd', login_name = 'l', fallback_color = 'red', user_color = 'green', tooltip = 't' },",
|
||||||
|
" { type = 'mention', display_name = 'd', login_name = 'l', fallback_color = 'red', user_color = 'green', trailing_space = false },",
|
||||||
|
"}}"
|
||||||
|
],
|
||||||
|
"output": {
|
||||||
|
"badgeInfos": {
|
||||||
|
},
|
||||||
|
"badges": [
|
||||||
|
],
|
||||||
|
"channelName": "",
|
||||||
|
"count": 1,
|
||||||
|
"displayName": "",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"color": "Text",
|
||||||
|
"fallbackColor": "#ffff0000",
|
||||||
|
"flags": "Text|Mention",
|
||||||
|
"link": {
|
||||||
|
"type": "None",
|
||||||
|
"value": ""
|
||||||
|
},
|
||||||
|
"style": "ChatMedium",
|
||||||
|
"tooltip": "",
|
||||||
|
"trailingSpace": true,
|
||||||
|
"type": "MentionElement",
|
||||||
|
"userColor": "#ff008000",
|
||||||
|
"userLoginName": "l",
|
||||||
|
"words": [
|
||||||
|
"d"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "Text",
|
||||||
|
"fallbackColor": "#ffff0000",
|
||||||
|
"flags": "Text|Mention",
|
||||||
|
"link": {
|
||||||
|
"type": "None",
|
||||||
|
"value": ""
|
||||||
|
},
|
||||||
|
"style": "ChatMedium",
|
||||||
|
"tooltip": "",
|
||||||
|
"trailingSpace": true,
|
||||||
|
"type": "MentionElement",
|
||||||
|
"userColor": "#ff008000",
|
||||||
|
"userLoginName": "l",
|
||||||
|
"words": [
|
||||||
|
"d"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "Text",
|
||||||
|
"fallbackColor": "#ffff0000",
|
||||||
|
"flags": "Text|Mention",
|
||||||
|
"link": {
|
||||||
|
"type": "None",
|
||||||
|
"value": ""
|
||||||
|
},
|
||||||
|
"style": "ChatMedium",
|
||||||
|
"tooltip": "t",
|
||||||
|
"trailingSpace": true,
|
||||||
|
"type": "MentionElement",
|
||||||
|
"userColor": "#ff008000",
|
||||||
|
"userLoginName": "l",
|
||||||
|
"words": [
|
||||||
|
"d"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "Text",
|
||||||
|
"fallbackColor": "#ffff0000",
|
||||||
|
"flags": "Text|Mention",
|
||||||
|
"link": {
|
||||||
|
"type": "None",
|
||||||
|
"value": ""
|
||||||
|
},
|
||||||
|
"style": "ChatMedium",
|
||||||
|
"tooltip": "",
|
||||||
|
"trailingSpace": false,
|
||||||
|
"type": "MentionElement",
|
||||||
|
"userColor": "#ff008000",
|
||||||
|
"userLoginName": "l",
|
||||||
|
"words": [
|
||||||
|
"d"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"flags": "",
|
||||||
|
"id": "",
|
||||||
|
"localizedName": "",
|
||||||
|
"loginName": "",
|
||||||
|
"messageText": "",
|
||||||
|
"searchText": "",
|
||||||
|
"serverReceivedTime": "",
|
||||||
|
"timeoutUser": "",
|
||||||
|
"userID": "",
|
||||||
|
"usernameColor": "#ff000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
{
|
||||||
|
"input": [
|
||||||
|
"msg = {",
|
||||||
|
" flags = c2.MessageFlag.System | c2.MessageFlag.Disabled,",
|
||||||
|
" id = 'foo-bar',",
|
||||||
|
" parse_time = 420000,",
|
||||||
|
" search_text = 'search',",
|
||||||
|
" message_text = 'message',",
|
||||||
|
" login_name = 'login',",
|
||||||
|
" display_name = 'display',",
|
||||||
|
" localized_name = 'local',",
|
||||||
|
" user_id = 'user-id',",
|
||||||
|
" username_color = 'blue',",
|
||||||
|
" server_received_time = 1230000,",
|
||||||
|
" highlight_color = '#12345678',",
|
||||||
|
" channel_name = 'channel',",
|
||||||
|
" elements = {",
|
||||||
|
" { type = 'text', text = 'aliens walking' }",
|
||||||
|
" }",
|
||||||
|
"}"
|
||||||
|
],
|
||||||
|
"output": {
|
||||||
|
"badgeInfos": {
|
||||||
|
},
|
||||||
|
"badges": [
|
||||||
|
],
|
||||||
|
"channelName": "channel",
|
||||||
|
"count": 1,
|
||||||
|
"displayName": "display",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"color": "Text",
|
||||||
|
"flags": "Text",
|
||||||
|
"link": {
|
||||||
|
"type": "None",
|
||||||
|
"value": ""
|
||||||
|
},
|
||||||
|
"style": "ChatMedium",
|
||||||
|
"tooltip": "",
|
||||||
|
"trailingSpace": true,
|
||||||
|
"type": "TextElement",
|
||||||
|
"words": [
|
||||||
|
"aliens",
|
||||||
|
"walking"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"flags": "System|Disabled",
|
||||||
|
"highlightColor": "#12345678",
|
||||||
|
"id": "foo-bar",
|
||||||
|
"localizedName": "local",
|
||||||
|
"loginName": "login",
|
||||||
|
"messageText": "message",
|
||||||
|
"searchText": "search",
|
||||||
|
"serverReceivedTime": "1970-01-01T00:20:30Z",
|
||||||
|
"timeoutUser": "",
|
||||||
|
"userID": "user-id",
|
||||||
|
"usernameColor": "#ff0000ff"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
{
|
||||||
|
"input": [
|
||||||
|
"msg = {elements={",
|
||||||
|
" { type = 'reply-curve' },",
|
||||||
|
" { type = 'reply-curve', flags = c2.MessageElementFlag.BttvEmote },",
|
||||||
|
" { type = 'reply-curve', tooltip = 't' },",
|
||||||
|
" { type = 'reply-curve', trailing_space = false },",
|
||||||
|
"}}"
|
||||||
|
],
|
||||||
|
"output": {
|
||||||
|
"badgeInfos": {
|
||||||
|
},
|
||||||
|
"badges": [
|
||||||
|
],
|
||||||
|
"channelName": "",
|
||||||
|
"count": 1,
|
||||||
|
"displayName": "",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"flags": "RepliedMessage",
|
||||||
|
"link": {
|
||||||
|
"type": "None",
|
||||||
|
"value": ""
|
||||||
|
},
|
||||||
|
"tooltip": "",
|
||||||
|
"trailingSpace": true,
|
||||||
|
"type": "ReplyCurveElement"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"flags": "RepliedMessage",
|
||||||
|
"link": {
|
||||||
|
"type": "None",
|
||||||
|
"value": ""
|
||||||
|
},
|
||||||
|
"tooltip": "",
|
||||||
|
"trailingSpace": true,
|
||||||
|
"type": "ReplyCurveElement"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"flags": "RepliedMessage",
|
||||||
|
"link": {
|
||||||
|
"type": "None",
|
||||||
|
"value": ""
|
||||||
|
},
|
||||||
|
"tooltip": "t",
|
||||||
|
"trailingSpace": true,
|
||||||
|
"type": "ReplyCurveElement"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"flags": "RepliedMessage",
|
||||||
|
"link": {
|
||||||
|
"type": "None",
|
||||||
|
"value": ""
|
||||||
|
},
|
||||||
|
"tooltip": "",
|
||||||
|
"trailingSpace": false,
|
||||||
|
"type": "ReplyCurveElement"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"flags": "",
|
||||||
|
"id": "",
|
||||||
|
"localizedName": "",
|
||||||
|
"loginName": "",
|
||||||
|
"messageText": "",
|
||||||
|
"searchText": "",
|
||||||
|
"serverReceivedTime": "",
|
||||||
|
"timeoutUser": "",
|
||||||
|
"userID": "",
|
||||||
|
"usernameColor": "#ff000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,188 @@
|
|||||||
|
{
|
||||||
|
"input": [
|
||||||
|
"msg = {elements={",
|
||||||
|
" { type = 'single-line-text', text = '' },",
|
||||||
|
" { type = 'single-line-text', text = 'foo' },",
|
||||||
|
" { type = 'single-line-text', text = 'foo bar' },",
|
||||||
|
" { type = 'single-line-text', text = 'foo\\nbar' },",
|
||||||
|
" { type = 'single-line-text', text = 'foo', flags = c2.MessageElementFlag.Text | c2.MessageElementFlag.Timestamp },",
|
||||||
|
" { type = 'single-line-text', text = 'foo', color = 'text' },",
|
||||||
|
" { type = 'single-line-text', text = 'foo', color = 'system' },",
|
||||||
|
" { type = 'single-line-text', text = 'foo', color = 'link' },",
|
||||||
|
" { type = 'single-line-text', text = 'foo', color = 'green' },",
|
||||||
|
" { type = 'single-line-text', text = 'foo', style = c2.FontStyle.ChatSmall },",
|
||||||
|
"}}"
|
||||||
|
],
|
||||||
|
"output": {
|
||||||
|
"badgeInfos": {
|
||||||
|
},
|
||||||
|
"badges": [
|
||||||
|
],
|
||||||
|
"channelName": "",
|
||||||
|
"count": 1,
|
||||||
|
"displayName": "",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"color": "Text",
|
||||||
|
"flags": "Text",
|
||||||
|
"link": {
|
||||||
|
"type": "None",
|
||||||
|
"value": ""
|
||||||
|
},
|
||||||
|
"style": "ChatMedium",
|
||||||
|
"tooltip": "",
|
||||||
|
"trailingSpace": true,
|
||||||
|
"type": "SingleLineTextElement",
|
||||||
|
"words": [
|
||||||
|
""
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "Text",
|
||||||
|
"flags": "Text",
|
||||||
|
"link": {
|
||||||
|
"type": "None",
|
||||||
|
"value": ""
|
||||||
|
},
|
||||||
|
"style": "ChatMedium",
|
||||||
|
"tooltip": "",
|
||||||
|
"trailingSpace": true,
|
||||||
|
"type": "SingleLineTextElement",
|
||||||
|
"words": [
|
||||||
|
"foo"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "Text",
|
||||||
|
"flags": "Text",
|
||||||
|
"link": {
|
||||||
|
"type": "None",
|
||||||
|
"value": ""
|
||||||
|
},
|
||||||
|
"style": "ChatMedium",
|
||||||
|
"tooltip": "",
|
||||||
|
"trailingSpace": true,
|
||||||
|
"type": "SingleLineTextElement",
|
||||||
|
"words": [
|
||||||
|
"foo",
|
||||||
|
"bar"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "Text",
|
||||||
|
"flags": "Text",
|
||||||
|
"link": {
|
||||||
|
"type": "None",
|
||||||
|
"value": ""
|
||||||
|
},
|
||||||
|
"style": "ChatMedium",
|
||||||
|
"tooltip": "",
|
||||||
|
"trailingSpace": true,
|
||||||
|
"type": "SingleLineTextElement",
|
||||||
|
"words": [
|
||||||
|
"foo\nbar"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "Text",
|
||||||
|
"flags": "Text|Timestamp",
|
||||||
|
"link": {
|
||||||
|
"type": "None",
|
||||||
|
"value": ""
|
||||||
|
},
|
||||||
|
"style": "ChatMedium",
|
||||||
|
"tooltip": "",
|
||||||
|
"trailingSpace": true,
|
||||||
|
"type": "SingleLineTextElement",
|
||||||
|
"words": [
|
||||||
|
"foo"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "Text",
|
||||||
|
"flags": "Text",
|
||||||
|
"link": {
|
||||||
|
"type": "None",
|
||||||
|
"value": ""
|
||||||
|
},
|
||||||
|
"style": "ChatMedium",
|
||||||
|
"tooltip": "",
|
||||||
|
"trailingSpace": true,
|
||||||
|
"type": "SingleLineTextElement",
|
||||||
|
"words": [
|
||||||
|
"foo"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "System",
|
||||||
|
"flags": "Text",
|
||||||
|
"link": {
|
||||||
|
"type": "None",
|
||||||
|
"value": ""
|
||||||
|
},
|
||||||
|
"style": "ChatMedium",
|
||||||
|
"tooltip": "",
|
||||||
|
"trailingSpace": true,
|
||||||
|
"type": "SingleLineTextElement",
|
||||||
|
"words": [
|
||||||
|
"foo"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "Link",
|
||||||
|
"flags": "Text",
|
||||||
|
"link": {
|
||||||
|
"type": "None",
|
||||||
|
"value": ""
|
||||||
|
},
|
||||||
|
"style": "ChatMedium",
|
||||||
|
"tooltip": "",
|
||||||
|
"trailingSpace": true,
|
||||||
|
"type": "SingleLineTextElement",
|
||||||
|
"words": [
|
||||||
|
"foo"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "#ff008000",
|
||||||
|
"flags": "Text",
|
||||||
|
"link": {
|
||||||
|
"type": "None",
|
||||||
|
"value": ""
|
||||||
|
},
|
||||||
|
"style": "ChatMedium",
|
||||||
|
"tooltip": "",
|
||||||
|
"trailingSpace": true,
|
||||||
|
"type": "SingleLineTextElement",
|
||||||
|
"words": [
|
||||||
|
"foo"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "Text",
|
||||||
|
"flags": "Text",
|
||||||
|
"link": {
|
||||||
|
"type": "None",
|
||||||
|
"value": ""
|
||||||
|
},
|
||||||
|
"style": "ChatSmall",
|
||||||
|
"tooltip": "",
|
||||||
|
"trailingSpace": true,
|
||||||
|
"type": "SingleLineTextElement",
|
||||||
|
"words": [
|
||||||
|
"foo"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"flags": "",
|
||||||
|
"id": "",
|
||||||
|
"localizedName": "",
|
||||||
|
"loginName": "",
|
||||||
|
"messageText": "",
|
||||||
|
"searchText": "",
|
||||||
|
"serverReceivedTime": "",
|
||||||
|
"timeoutUser": "",
|
||||||
|
"userID": "",
|
||||||
|
"usernameColor": "#ff000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,188 @@
|
|||||||
|
{
|
||||||
|
"input": [
|
||||||
|
"msg = {elements={",
|
||||||
|
" { type = 'text', text = '' },",
|
||||||
|
" { type = 'text', text = 'foo' },",
|
||||||
|
" { type = 'text', text = 'foo bar' },",
|
||||||
|
" { type = 'text', text = 'foo\\nbar' },",
|
||||||
|
" { type = 'text', text = 'foo', flags = c2.MessageElementFlag.Text | c2.MessageElementFlag.Timestamp },",
|
||||||
|
" { type = 'text', text = 'foo', color = 'text' },",
|
||||||
|
" { type = 'text', text = 'foo', color = 'system' },",
|
||||||
|
" { type = 'text', text = 'foo', color = 'link' },",
|
||||||
|
" { type = 'text', text = 'foo', color = 'green' },",
|
||||||
|
" { type = 'text', text = 'foo', style = c2.FontStyle.ChatSmall },",
|
||||||
|
"}}"
|
||||||
|
],
|
||||||
|
"output": {
|
||||||
|
"badgeInfos": {
|
||||||
|
},
|
||||||
|
"badges": [
|
||||||
|
],
|
||||||
|
"channelName": "",
|
||||||
|
"count": 1,
|
||||||
|
"displayName": "",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"color": "Text",
|
||||||
|
"flags": "Text",
|
||||||
|
"link": {
|
||||||
|
"type": "None",
|
||||||
|
"value": ""
|
||||||
|
},
|
||||||
|
"style": "ChatMedium",
|
||||||
|
"tooltip": "",
|
||||||
|
"trailingSpace": true,
|
||||||
|
"type": "TextElement",
|
||||||
|
"words": [
|
||||||
|
""
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "Text",
|
||||||
|
"flags": "Text",
|
||||||
|
"link": {
|
||||||
|
"type": "None",
|
||||||
|
"value": ""
|
||||||
|
},
|
||||||
|
"style": "ChatMedium",
|
||||||
|
"tooltip": "",
|
||||||
|
"trailingSpace": true,
|
||||||
|
"type": "TextElement",
|
||||||
|
"words": [
|
||||||
|
"foo"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "Text",
|
||||||
|
"flags": "Text",
|
||||||
|
"link": {
|
||||||
|
"type": "None",
|
||||||
|
"value": ""
|
||||||
|
},
|
||||||
|
"style": "ChatMedium",
|
||||||
|
"tooltip": "",
|
||||||
|
"trailingSpace": true,
|
||||||
|
"type": "TextElement",
|
||||||
|
"words": [
|
||||||
|
"foo",
|
||||||
|
"bar"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "Text",
|
||||||
|
"flags": "Text",
|
||||||
|
"link": {
|
||||||
|
"type": "None",
|
||||||
|
"value": ""
|
||||||
|
},
|
||||||
|
"style": "ChatMedium",
|
||||||
|
"tooltip": "",
|
||||||
|
"trailingSpace": true,
|
||||||
|
"type": "TextElement",
|
||||||
|
"words": [
|
||||||
|
"foo\nbar"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "Text",
|
||||||
|
"flags": "Text|Timestamp",
|
||||||
|
"link": {
|
||||||
|
"type": "None",
|
||||||
|
"value": ""
|
||||||
|
},
|
||||||
|
"style": "ChatMedium",
|
||||||
|
"tooltip": "",
|
||||||
|
"trailingSpace": true,
|
||||||
|
"type": "TextElement",
|
||||||
|
"words": [
|
||||||
|
"foo"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "Text",
|
||||||
|
"flags": "Text",
|
||||||
|
"link": {
|
||||||
|
"type": "None",
|
||||||
|
"value": ""
|
||||||
|
},
|
||||||
|
"style": "ChatMedium",
|
||||||
|
"tooltip": "",
|
||||||
|
"trailingSpace": true,
|
||||||
|
"type": "TextElement",
|
||||||
|
"words": [
|
||||||
|
"foo"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "System",
|
||||||
|
"flags": "Text",
|
||||||
|
"link": {
|
||||||
|
"type": "None",
|
||||||
|
"value": ""
|
||||||
|
},
|
||||||
|
"style": "ChatMedium",
|
||||||
|
"tooltip": "",
|
||||||
|
"trailingSpace": true,
|
||||||
|
"type": "TextElement",
|
||||||
|
"words": [
|
||||||
|
"foo"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "Link",
|
||||||
|
"flags": "Text",
|
||||||
|
"link": {
|
||||||
|
"type": "None",
|
||||||
|
"value": ""
|
||||||
|
},
|
||||||
|
"style": "ChatMedium",
|
||||||
|
"tooltip": "",
|
||||||
|
"trailingSpace": true,
|
||||||
|
"type": "TextElement",
|
||||||
|
"words": [
|
||||||
|
"foo"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "#ff008000",
|
||||||
|
"flags": "Text",
|
||||||
|
"link": {
|
||||||
|
"type": "None",
|
||||||
|
"value": ""
|
||||||
|
},
|
||||||
|
"style": "ChatMedium",
|
||||||
|
"tooltip": "",
|
||||||
|
"trailingSpace": true,
|
||||||
|
"type": "TextElement",
|
||||||
|
"words": [
|
||||||
|
"foo"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "Text",
|
||||||
|
"flags": "Text",
|
||||||
|
"link": {
|
||||||
|
"type": "None",
|
||||||
|
"value": ""
|
||||||
|
},
|
||||||
|
"style": "ChatSmall",
|
||||||
|
"tooltip": "",
|
||||||
|
"trailingSpace": true,
|
||||||
|
"type": "TextElement",
|
||||||
|
"words": [
|
||||||
|
"foo"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"flags": "",
|
||||||
|
"id": "",
|
||||||
|
"localizedName": "",
|
||||||
|
"loginName": "",
|
||||||
|
"messageText": "",
|
||||||
|
"searchText": "",
|
||||||
|
"serverReceivedTime": "",
|
||||||
|
"timeoutUser": "",
|
||||||
|
"userID": "",
|
||||||
|
"usernameColor": "#ff000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,139 @@
|
|||||||
|
{
|
||||||
|
"input": [
|
||||||
|
"msg = {elements={",
|
||||||
|
" { type = 'timestamp' },",
|
||||||
|
" { type = 'timestamp', time = 1230000, flags = c2.MessageElementFlag.BttvEmote },",
|
||||||
|
" { type = 'timestamp', tooltip = 't' },",
|
||||||
|
" { type = 'timestamp', trailing_space = false },",
|
||||||
|
"}}"
|
||||||
|
],
|
||||||
|
"output": {
|
||||||
|
"badgeInfos": {
|
||||||
|
},
|
||||||
|
"badges": [
|
||||||
|
],
|
||||||
|
"channelName": "",
|
||||||
|
"count": 1,
|
||||||
|
"displayName": "",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"element": {
|
||||||
|
"color": "System",
|
||||||
|
"flags": "Timestamp",
|
||||||
|
"link": {
|
||||||
|
"type": "None",
|
||||||
|
"value": ""
|
||||||
|
},
|
||||||
|
"style": "TimestampMedium",
|
||||||
|
"tooltip": "",
|
||||||
|
"trailingSpace": true,
|
||||||
|
"type": "TextElement",
|
||||||
|
"words": [
|
||||||
|
"0:00"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"flags": "Timestamp",
|
||||||
|
"format": "",
|
||||||
|
"link": {
|
||||||
|
"type": "None",
|
||||||
|
"value": ""
|
||||||
|
},
|
||||||
|
"time": "00:00:00",
|
||||||
|
"tooltip": "",
|
||||||
|
"trailingSpace": true,
|
||||||
|
"type": "TimestampElement"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"element": {
|
||||||
|
"color": "System",
|
||||||
|
"flags": "Timestamp",
|
||||||
|
"link": {
|
||||||
|
"type": "None",
|
||||||
|
"value": ""
|
||||||
|
},
|
||||||
|
"style": "TimestampMedium",
|
||||||
|
"tooltip": "",
|
||||||
|
"trailingSpace": true,
|
||||||
|
"type": "TextElement",
|
||||||
|
"words": [
|
||||||
|
"0:20"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"flags": "Timestamp",
|
||||||
|
"format": "",
|
||||||
|
"link": {
|
||||||
|
"type": "None",
|
||||||
|
"value": ""
|
||||||
|
},
|
||||||
|
"time": "00:20:30",
|
||||||
|
"tooltip": "",
|
||||||
|
"trailingSpace": true,
|
||||||
|
"type": "TimestampElement"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"element": {
|
||||||
|
"color": "System",
|
||||||
|
"flags": "Timestamp",
|
||||||
|
"link": {
|
||||||
|
"type": "None",
|
||||||
|
"value": ""
|
||||||
|
},
|
||||||
|
"style": "TimestampMedium",
|
||||||
|
"tooltip": "",
|
||||||
|
"trailingSpace": true,
|
||||||
|
"type": "TextElement",
|
||||||
|
"words": [
|
||||||
|
"0:00"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"flags": "Timestamp",
|
||||||
|
"format": "",
|
||||||
|
"link": {
|
||||||
|
"type": "None",
|
||||||
|
"value": ""
|
||||||
|
},
|
||||||
|
"time": "00:00:00",
|
||||||
|
"tooltip": "t",
|
||||||
|
"trailingSpace": true,
|
||||||
|
"type": "TimestampElement"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"element": {
|
||||||
|
"color": "System",
|
||||||
|
"flags": "Timestamp",
|
||||||
|
"link": {
|
||||||
|
"type": "None",
|
||||||
|
"value": ""
|
||||||
|
},
|
||||||
|
"style": "TimestampMedium",
|
||||||
|
"tooltip": "",
|
||||||
|
"trailingSpace": true,
|
||||||
|
"type": "TextElement",
|
||||||
|
"words": [
|
||||||
|
"0:00"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"flags": "Timestamp",
|
||||||
|
"format": "",
|
||||||
|
"link": {
|
||||||
|
"type": "None",
|
||||||
|
"value": ""
|
||||||
|
},
|
||||||
|
"time": "00:00:00",
|
||||||
|
"tooltip": "",
|
||||||
|
"trailingSpace": false,
|
||||||
|
"type": "TimestampElement"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"flags": "",
|
||||||
|
"id": "",
|
||||||
|
"localizedName": "",
|
||||||
|
"loginName": "",
|
||||||
|
"messageText": "",
|
||||||
|
"searchText": "",
|
||||||
|
"serverReceivedTime": "",
|
||||||
|
"timeoutUser": "",
|
||||||
|
"userID": "",
|
||||||
|
"usernameColor": "#ff000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
{
|
||||||
|
"input": [
|
||||||
|
"msg = {elements={",
|
||||||
|
" { type = 'twitch-moderation' },",
|
||||||
|
" { type = 'twitch-moderation', flags = c2.MessageElementFlag.BttvEmote },",
|
||||||
|
" { type = 'twitch-moderation', tooltip = 't' },",
|
||||||
|
" { type = 'twitch-moderation', trailing_space = false },",
|
||||||
|
"}}"
|
||||||
|
],
|
||||||
|
"output": {
|
||||||
|
"badgeInfos": {
|
||||||
|
},
|
||||||
|
"badges": [
|
||||||
|
],
|
||||||
|
"channelName": "",
|
||||||
|
"count": 1,
|
||||||
|
"displayName": "",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"flags": "ModeratorTools",
|
||||||
|
"link": {
|
||||||
|
"type": "None",
|
||||||
|
"value": ""
|
||||||
|
},
|
||||||
|
"tooltip": "",
|
||||||
|
"trailingSpace": true,
|
||||||
|
"type": "TwitchModerationElement"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"flags": "ModeratorTools",
|
||||||
|
"link": {
|
||||||
|
"type": "None",
|
||||||
|
"value": ""
|
||||||
|
},
|
||||||
|
"tooltip": "",
|
||||||
|
"trailingSpace": true,
|
||||||
|
"type": "TwitchModerationElement"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"flags": "ModeratorTools",
|
||||||
|
"link": {
|
||||||
|
"type": "None",
|
||||||
|
"value": ""
|
||||||
|
},
|
||||||
|
"tooltip": "t",
|
||||||
|
"trailingSpace": true,
|
||||||
|
"type": "TwitchModerationElement"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"flags": "ModeratorTools",
|
||||||
|
"link": {
|
||||||
|
"type": "None",
|
||||||
|
"value": ""
|
||||||
|
},
|
||||||
|
"tooltip": "",
|
||||||
|
"trailingSpace": false,
|
||||||
|
"type": "TwitchModerationElement"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"flags": "",
|
||||||
|
"id": "",
|
||||||
|
"localizedName": "",
|
||||||
|
"loginName": "",
|
||||||
|
"messageText": "",
|
||||||
|
"searchText": "",
|
||||||
|
"serverReceivedTime": "",
|
||||||
|
"timeoutUser": "",
|
||||||
|
"userID": "",
|
||||||
|
"usernameColor": "#ff000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
+151
-1
@@ -10,6 +10,8 @@
|
|||||||
# include "controllers/plugins/PluginController.hpp"
|
# include "controllers/plugins/PluginController.hpp"
|
||||||
# include "controllers/plugins/PluginPermission.hpp"
|
# include "controllers/plugins/PluginPermission.hpp"
|
||||||
# include "controllers/plugins/SolTypes.hpp" // IWYU pragma: keep
|
# include "controllers/plugins/SolTypes.hpp" // IWYU pragma: keep
|
||||||
|
# include "lib/Snapshot.hpp"
|
||||||
|
# include "messages/Message.hpp"
|
||||||
# include "mocks/BaseApplication.hpp"
|
# include "mocks/BaseApplication.hpp"
|
||||||
# include "mocks/Channel.hpp"
|
# include "mocks/Channel.hpp"
|
||||||
# include "mocks/Emotes.hpp"
|
# include "mocks/Emotes.hpp"
|
||||||
@@ -32,6 +34,8 @@ using chatterino::mock::MockChannel;
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
constexpr bool UPDATE_SNAPSHOTS = false;
|
||||||
|
|
||||||
const QString TEST_SETTINGS = R"(
|
const QString TEST_SETTINGS = R"(
|
||||||
{
|
{
|
||||||
"plugins": {
|
"plugins": {
|
||||||
@@ -104,7 +108,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
PluginController plugins;
|
PluginController plugins;
|
||||||
mock::EmptyLogging logging;
|
mock::Logging logging;
|
||||||
CommandController commands;
|
CommandController commands;
|
||||||
mock::Emotes emotes;
|
mock::Emotes emotes;
|
||||||
MockTwitch twitch;
|
MockTwitch twitch;
|
||||||
@@ -861,4 +865,150 @@ TEST_F(PluginTest, testWebSocketUnsetFns)
|
|||||||
waiter.waitForRequest();
|
waiter.waitForRequest();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(PluginTest, MessageElementFlag)
|
||||||
|
{
|
||||||
|
configure();
|
||||||
|
lua->script(R"lua(
|
||||||
|
values = {}
|
||||||
|
for k, v in pairs(c2.MessageElementFlag) do
|
||||||
|
table.insert(values, ("%s=0x%x"):format(k, v))
|
||||||
|
end
|
||||||
|
table.sort(values, function(a, b) return a:lower() < b:lower() end)
|
||||||
|
out = table.concat(values, ",")
|
||||||
|
)lua");
|
||||||
|
|
||||||
|
const char *VALUES = "AlwaysShow=0x2000000,"
|
||||||
|
"BadgeChannelAuthority=0x8000,"
|
||||||
|
"BadgeChatterino=0x40000,"
|
||||||
|
"BadgeFfz=0x80000,"
|
||||||
|
"BadgeGlobalAuthority=0x2000,"
|
||||||
|
"BadgePredictions=0x4000,"
|
||||||
|
"BadgeSevenTV=0x1000000000,"
|
||||||
|
"BadgeSharedChannel=0x2000000000,"
|
||||||
|
"BadgeSubscription=0x10000,"
|
||||||
|
"BadgeVanity=0x20000,"
|
||||||
|
"BitsAmount=0x200000,"
|
||||||
|
"BitsAnimated=0x1000,"
|
||||||
|
"BitsStatic=0x800,"
|
||||||
|
"BttvEmoteImage=0x40,"
|
||||||
|
"BttvEmoteText=0x80,"
|
||||||
|
"ChannelName=0x100000,"
|
||||||
|
"ChannelPointReward=0x100,"
|
||||||
|
"Collapsed=0x4000000,"
|
||||||
|
"EmojiImage=0x800000,"
|
||||||
|
"EmojiText=0x1000000,"
|
||||||
|
"FfzEmoteImage=0x200,"
|
||||||
|
"FfzEmoteText=0x400,"
|
||||||
|
"LowercaseLinks=0x20000000,"
|
||||||
|
"Mention=0x8000000,"
|
||||||
|
"Misc=0x1,"
|
||||||
|
"ModeratorTools=0x400000,"
|
||||||
|
"RepliedMessage=0x100000000,"
|
||||||
|
"ReplyButton=0x200000000,"
|
||||||
|
"SevenTVEmoteImage=0x400000000,"
|
||||||
|
"SevenTVEmoteText=0x800000000,"
|
||||||
|
"Text=0x2,"
|
||||||
|
"Timestamp=0x8,"
|
||||||
|
"TwitchEmoteImage=0x10,"
|
||||||
|
"TwitchEmoteText=0x20,"
|
||||||
|
"Username=0x4";
|
||||||
|
|
||||||
|
std::string got = (*lua)["out"];
|
||||||
|
ASSERT_EQ(got, VALUES);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(PluginTest, ChannelAddMessage)
|
||||||
|
{
|
||||||
|
configure();
|
||||||
|
lua->script(R"lua(
|
||||||
|
function do_it(chan)
|
||||||
|
local Repost = c2.MessageContext.Repost
|
||||||
|
local Original = c2.MessageContext.Original
|
||||||
|
chan:add_message(c2.Message.new({ id = "1" }))
|
||||||
|
chan:add_message(c2.Message.new({ id = "2" }), Repost)
|
||||||
|
chan:add_message(c2.Message.new({ id = "3" }), Original, nil)
|
||||||
|
chan:add_message(c2.Message.new({ id = "4" }), Repost, c2.MessageFlag.DoNotLog)
|
||||||
|
chan:add_message(c2.Message.new({ id = "5" }), Original, c2.MessageFlag.DoNotLog)
|
||||||
|
chan:add_message(c2.Message.new({ id = "6" }), Original, c2.MessageFlag.System)
|
||||||
|
end
|
||||||
|
)lua");
|
||||||
|
|
||||||
|
auto chan = std::make_shared<MockChannel>("mock");
|
||||||
|
|
||||||
|
std::vector<MessagePtr> logged;
|
||||||
|
EXPECT_CALL(this->app->logging, addMessage)
|
||||||
|
.Times(3)
|
||||||
|
.WillRepeatedly(
|
||||||
|
[&](const auto &, const auto &msg, const auto &, const auto &) {
|
||||||
|
logged.emplace_back(msg);
|
||||||
|
});
|
||||||
|
|
||||||
|
std::vector<std::pair<MessagePtr, std::optional<MessageFlags>>> added;
|
||||||
|
std::ignore = chan->messageAppended.connect([&](auto &&...args) {
|
||||||
|
added.emplace_back(std::forward<decltype(args)>(args)...);
|
||||||
|
});
|
||||||
|
|
||||||
|
(*lua)["do_it"](lua::api::ChannelRef(chan));
|
||||||
|
|
||||||
|
ASSERT_EQ(added.size(), 6);
|
||||||
|
ASSERT_EQ(added[0].first->id, "1");
|
||||||
|
ASSERT_FALSE(added[0].second.has_value());
|
||||||
|
ASSERT_EQ(added[1].first->id, "2");
|
||||||
|
ASSERT_FALSE(added[1].second.has_value());
|
||||||
|
ASSERT_EQ(added[2].first->id, "3");
|
||||||
|
ASSERT_FALSE(added[2].second.has_value());
|
||||||
|
ASSERT_EQ(added[3].first->id, "4");
|
||||||
|
ASSERT_EQ(added[3].second, MessageFlags{MessageFlag::DoNotLog});
|
||||||
|
ASSERT_EQ(added[4].first->id, "5");
|
||||||
|
ASSERT_EQ(added[4].second, MessageFlags{MessageFlag::DoNotLog});
|
||||||
|
ASSERT_EQ(added[5].first->id, "6");
|
||||||
|
ASSERT_EQ(added[5].second, MessageFlags{MessageFlag::System});
|
||||||
|
|
||||||
|
ASSERT_EQ(logged.size(), 3);
|
||||||
|
ASSERT_EQ(added[0].first, logged[0]);
|
||||||
|
ASSERT_EQ(added[2].first, logged[1]);
|
||||||
|
ASSERT_EQ(added[5].first, logged[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
class PluginMessageConstructionTest
|
||||||
|
: public PluginTest,
|
||||||
|
public ::testing::WithParamInterface<QString>
|
||||||
|
{
|
||||||
|
};
|
||||||
|
TEST_P(PluginMessageConstructionTest, Run)
|
||||||
|
{
|
||||||
|
auto fixture = testlib::Snapshot::read("PluginMessageCtor", GetParam());
|
||||||
|
|
||||||
|
configure();
|
||||||
|
std::string script;
|
||||||
|
if (fixture->input().isArray())
|
||||||
|
{
|
||||||
|
for (auto line : fixture->input().toArray())
|
||||||
|
{
|
||||||
|
script += line.toString().toStdString() + '\n';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
script = fixture->inputString().toStdString() + '\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
script += "out = c2.Message.new(msg)";
|
||||||
|
lua->script(script);
|
||||||
|
|
||||||
|
Message *got = (*lua)["out"];
|
||||||
|
|
||||||
|
ASSERT_TRUE(fixture->run(got->toJson(), UPDATE_SNAPSHOTS));
|
||||||
|
}
|
||||||
|
|
||||||
|
INSTANTIATE_TEST_SUITE_P(
|
||||||
|
PluginMessageConstruction, PluginMessageConstructionTest,
|
||||||
|
testing::ValuesIn(testlib::Snapshot::discover("PluginMessageCtor")));
|
||||||
|
|
||||||
|
// verify that all snapshots are included
|
||||||
|
TEST(PluginMessageConstructionTest, Integrity)
|
||||||
|
{
|
||||||
|
ASSERT_FALSE(UPDATE_SNAPSHOTS); // make sure fixtures are actually tested
|
||||||
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -41,9 +41,9 @@ namespace chatterino::testlib {
|
|||||||
///
|
///
|
||||||
/// TEST_P(ExampleTest, Run) {
|
/// TEST_P(ExampleTest, Run) {
|
||||||
/// auto fixture = testlib::Snapshot::read("category", GetParam());
|
/// auto fixture = testlib::Snapshot::read("category", GetParam());
|
||||||
/// auto output = functionToTest(fixture.input()); // or input{String,Utf8}
|
/// auto output = functionToTest(fixture->input()); // or input{String,Utf8}
|
||||||
/// // if snapshots are supposed to be updated, this will write the output
|
/// // if snapshots are supposed to be updated, this will write the output
|
||||||
/// ASSERT_TRUE(fixture.run(output, UPDATE_SNAPSHOTS));
|
/// ASSERT_TRUE(fixture->run(output, UPDATE_SNAPSHOTS));
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// INSTANTIATE_TEST_SUITE_P(ExampleInstance, ExampleTest,
|
/// INSTANTIATE_TEST_SUITE_P(ExampleInstance, ExampleTest,
|
||||||
|
|||||||
Reference in New Issue
Block a user