Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(plugins): add basic message construction #5754

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,8 @@ set(SOURCE_FILES
controllers/plugins/api/HTTPResponse.hpp
controllers/plugins/api/IOWrapper.cpp
controllers/plugins/api/IOWrapper.hpp
controllers/plugins/api/Message.cpp
controllers/plugins/api/Message.hpp
controllers/plugins/LuaAPI.cpp
controllers/plugins/LuaAPI.hpp
controllers/plugins/LuaUtilities.cpp
Expand Down
11 changes: 5 additions & 6 deletions src/controllers/plugins/LuaUtilities.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -111,19 +111,18 @@ class StackGuard
*
* @returns Sol reference to the table
*/
template <typename T>
template <typename T, T... Additional>
requires std::is_enum_v<T>
sol::table createEnumTable(sol::state_view &lua)
{
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)
{
std::string_view name = magic_enum::enum_name<T>(v);
std::string str(name);

out.raw_set(str, v);
out.raw_set(magic_enum::enum_name<T>(v), v);
}
(out.raw_set(magic_enum::enum_name<Additional>(), Additional), ...);

return out;
}

Expand Down
20 changes: 20 additions & 0 deletions src/controllers/plugins/PluginController.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
# include "controllers/plugins/api/HTTPRequest.hpp"
# include "controllers/plugins/api/HTTPResponse.hpp"
# include "controllers/plugins/api/IOWrapper.hpp"
# include "controllers/plugins/api/Message.hpp"
# include "controllers/plugins/LuaAPI.hpp"
# include "controllers/plugins/LuaUtilities.hpp"
# include "controllers/plugins/SolTypes.hpp"
Expand Down Expand Up @@ -220,10 +221,29 @@ void PluginController::initSol(sol::state_view &lua, Plugin *plugin)
lua::api::ChannelRef::createUserType(c2);
lua::api::HTTPResponse::createUserType(c2);
lua::api::HTTPRequest::createUserType(c2);
lua::api::message::createUserType(c2);
c2["ChannelType"] = lua::createEnumTable<Channel::Type>(lua);
c2["HTTPMethod"] = lua::createEnumTable<NetworkRequestType>(lua);
c2["EventType"] = lua::createEnumTable<lua::api::EventType>(lua);
c2["LogLevel"] = lua::createEnumTable<lua::api::LogLevel>(lua);
c2["MessageFlag"] =
lua::createEnumTable<MessageFlag, MessageFlag::None>(lua);
c2["MessageElementFlag"] =
lua::createEnumTable<MessageElementFlag, //
MessageElementFlag::None, //
MessageElementFlag::TwitchEmote, //
MessageElementFlag::BttvEmote, //
MessageElementFlag::ChannelPointRewardImage, //
MessageElementFlag::FfzEmote, //
MessageElementFlag::SevenTVEmote, //
MessageElementFlag::EmoteImages, //
MessageElementFlag::EmoteText, //
MessageElementFlag::Badges, //
MessageElementFlag::EmojiAll, //
MessageElementFlag::Default //
>(lua);
c2["FontStyle"] = lua::createEnumTable<FontStyle>(lua);
c2["MessageContext"] = lua::createEnumTable<MessageContext>(lua);

sol::table io = g["io"];
io.set_function(
Expand Down
27 changes: 27 additions & 0 deletions src/controllers/plugins/api/ChannelRef.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,32 @@ void ChannelRef::add_system_message(QString text)
this->strong()->addSystemMessage(text);
}

void ChannelRef::add_message(std::shared_ptr<Message> &msg,
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(msg, ctx, overrideFlags);
}

bool ChannelRef::is_twitch_channel()
{
return this->strong()->isTwitchChannel();
Expand Down Expand Up @@ -168,6 +194,7 @@ void ChannelRef::createUserType(sol::table &c2)
"get_display_name", &ChannelRef::get_display_name,
"send_message", &ChannelRef::send_message,
"add_system_message", &ChannelRef::add_system_message,
"add_message", &ChannelRef::add_message,
"is_twitch_channel", &ChannelRef::is_twitch_channel,

// TwitchChannel
Expand Down
10 changes: 10 additions & 0 deletions src/controllers/plugins/api/ChannelRef.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,16 @@ struct ChannelRef {
*/
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);
Nerixyz marked this conversation as resolved.
Show resolved Hide resolved

/**
* Returns true for twitch channels.
* Compares the channel Type. Note that enum values aren't guaranteed, just
Expand Down
218 changes: 218 additions & 0 deletions src/controllers/plugins/api/Message.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
#include "controllers/plugins/api/Message.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;

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);
}

std::unique_ptr<TextElement> textElementFromTable(const sol::table &tbl)
{
return std::make_unique<TextElement>(
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>(
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>(
tbl.get<QString>("display_name"), tbl.get<QString>("login_name"),
tryMakeMessageColor(tbl.get<QString>("fallback_color")),
tryMakeMessageColor(tbl.get<QString>("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>(
QDateTime::fromMSecsSinceEpoch(*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)
{
QString type = 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 = QDateTime::fromMSecsSinceEpoch(*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{});
// 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 =
QDateTime::fromMSecsSinceEpoch(*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([](sol::table tbl) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: the parameter 'tbl' is copied for each invocation but only used as a const reference; consider making it a const reference [performance-unnecessary-value-param]

    c2.new_usertype<Message>("Message", sol::factories([](sol::table tbl) {
                                                                     ^

return messageFromTable(tbl);
}));
}

} // namespace chatterino::lua::api::message

#endif
13 changes: 13 additions & 0 deletions src/controllers/plugins/api/Message.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#pragma once
#ifdef CHATTERINO_HAVE_PLUGINS
# include "messages/Message.hpp"

# include <sol/forward.hpp>

namespace chatterino::lua::api::message {

void createUserType(sol::table &c2);

} // namespace chatterino::lua::api::message

#endif
23 changes: 23 additions & 0 deletions tests/snapshots/PluginMessageCtor/empty.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"input": "msg = {}",
"output": {
"badgeInfos": {
},
"badges": [
],
"channelName": "",
"count": 1,
"displayName": "",
"elements": [
],
"flags": "",
"id": "",
"localizedName": "",
"loginName": "",
"messageText": "",
"searchText": "",
"serverReceivedTime": "",
"timeoutUser": "",
"usernameColor": "#ff000000"
}
}
Loading
Loading