Add a new Channel API for experimental plugins feature (#5141)

This commit is contained in:
Mm2PL
2024-02-03 19:12:00 +01:00
committed by GitHub
parent 7fdb3841db
commit 8e9aa87a08
14 changed files with 1069 additions and 153 deletions
-91
View File
@@ -126,97 +126,6 @@ int c2_register_callback(lua_State *L)
return 0;
}
int c2_send_msg(lua_State *L)
{
QString text;
QString channel;
if (lua_gettop(L) != 2)
{
luaL_error(L, "send_msg needs exactly 2 arguments (channel and text)");
lua::push(L, false);
return 1;
}
if (!lua::pop(L, &text))
{
luaL_error(
L, "cannot get text (2nd argument of send_msg, expected a string)");
lua::push(L, false);
return 1;
}
if (!lua::pop(L, &channel))
{
luaL_error(
L,
"cannot get channel (1st argument of send_msg, expected a string)");
lua::push(L, false);
return 1;
}
const auto chn = getApp()->twitch->getChannelOrEmpty(channel);
if (chn->isEmpty())
{
auto *pl = getIApp()->getPlugins()->getPluginByStatePtr(L);
qCWarning(chatterinoLua)
<< "Plugin" << pl->id
<< "tried to send a message (using send_msg) to channel" << channel
<< "which is not known";
lua::push(L, false);
return 1;
}
QString message = text;
message = message.replace('\n', ' ');
QString outText =
getIApp()->getCommands()->execCommand(message, chn, false);
chn->sendMessage(outText);
lua::push(L, true);
return 1;
}
int c2_system_msg(lua_State *L)
{
if (lua_gettop(L) != 2)
{
luaL_error(L,
"system_msg needs exactly 2 arguments (channel and text)");
lua::push(L, false);
return 1;
}
QString channel;
QString text;
if (!lua::pop(L, &text))
{
luaL_error(
L,
"cannot get text (2nd argument of system_msg, expected a string)");
lua::push(L, false);
return 1;
}
if (!lua::pop(L, &channel))
{
luaL_error(L, "cannot get channel (1st argument of system_msg, "
"expected a string)");
lua::push(L, false);
return 1;
}
const auto chn = getApp()->twitch->getChannelOrEmpty(channel);
if (chn->isEmpty())
{
auto *pl = getIApp()->getPlugins()->getPluginByStatePtr(L);
qCWarning(chatterinoLua)
<< "Plugin" << pl->id
<< "tried to show a system message (using system_msg) in channel"
<< channel << "which is not known";
lua::push(L, false);
return 1;
}
chn->addMessage(makeSystemMessage(text));
lua::push(L, true);
return 1;
}
int c2_log(lua_State *L)
{
auto *pl = getIApp()->getPlugins()->getPluginByStatePtr(L);
+118 -21
View File
@@ -1,8 +1,12 @@
#pragma once
#ifdef CHATTERINO_HAVE_PLUGINS
# include <lua.h>
# include <QString>
# include <cassert>
# include <memory>
# include <vector>
struct lua_State;
@@ -49,6 +53,11 @@ struct CompletionList {
bool hideOthers{};
};
/**
* @includefile common/Channel.hpp
* @includefile controllers/plugins/api/ChannelRef.hpp
*/
/**
* Registers a new command called `name` which when executed will call `handler`.
*
@@ -68,27 +77,6 @@ int c2_register_command(lua_State *L);
*/
int c2_register_callback(lua_State *L);
/**
* Sends a message to `channel` with the specified text. Also executes commands.
*
* **Warning**: It is possible to trigger your own Lua command with this causing a potentially infinite loop.
*
* @lua@param channel string The name of the Twitch channel
* @lua@param text string The text to be sent
* @lua@return boolean ok
* @exposed c2.send_msg
*/
int c2_send_msg(lua_State *L);
/**
* Creates a system message (gray message) and adds it to the Twitch channel specified by `channel`.
*
* @lua@param channel string
* @lua@param text string
* @lua@return boolean ok
* @exposed c2.system_msg
*/
int c2_system_msg(lua_State *L);
/**
* Writes a message to the Chatterino log.
*
@@ -107,6 +95,115 @@ int g_print(lua_State *L);
int searcherAbsolute(lua_State *L);
int searcherRelative(lua_State *L);
// This is a fat pointer that allows us to type check values given to functions needing a userdata.
// Ensure ALL userdata given to Lua are a subclass of this! Otherwise we garbage as a pointer!
struct UserData {
enum class Type { Channel };
Type type;
bool isWeak;
};
template <UserData::Type T, typename U>
struct WeakPtrUserData : public UserData {
std::weak_ptr<U> target;
WeakPtrUserData(std::weak_ptr<U> t)
: UserData()
, target(t)
{
this->type = T;
this->isWeak = true;
}
static WeakPtrUserData<T, U> *create(lua_State *L, std::weak_ptr<U> target)
{
void *ptr = lua_newuserdata(L, sizeof(WeakPtrUserData<T, U>));
return new (ptr) WeakPtrUserData<T, U>(target);
}
static WeakPtrUserData<T, U> *from(UserData *target)
{
if (!target->isWeak)
{
return nullptr;
}
if (target->type != T)
{
return nullptr;
}
return reinterpret_cast<WeakPtrUserData<T, U> *>(target);
}
static WeakPtrUserData<T, U> *from(void *target)
{
return from(reinterpret_cast<UserData *>(target));
}
static int destroy(lua_State *L)
{
auto self = WeakPtrUserData<T, U>::from(lua_touserdata(L, -1));
// Note it is safe to only check the weakness of the pointer, as
// std::weak_ptr seems to have identical representation regardless of
// what it points to
assert(self->isWeak);
self->target.reset();
lua_pop(L, 1); // Lua deallocates the memory for full user data
return 0;
}
};
template <UserData::Type T, typename U>
struct SharedPtrUserData : public UserData {
std::shared_ptr<U> target;
SharedPtrUserData(std::shared_ptr<U> t)
: UserData()
, target(t)
{
this->type = T;
this->isWeak = false;
}
static SharedPtrUserData<T, U> *create(lua_State *L,
std::shared_ptr<U> target)
{
void *ptr = lua_newuserdata(L, sizeof(SharedPtrUserData<T, U>));
return new (ptr) SharedPtrUserData<T, U>(target);
}
static SharedPtrUserData<T, U> *from(UserData *target)
{
if (target->isWeak)
{
return nullptr;
}
if (target->type != T)
{
return nullptr;
}
return reinterpret_cast<SharedPtrUserData<T, U> *>(target);
}
static SharedPtrUserData<T, U> *from(void *target)
{
return from(reinterpret_cast<UserData *>(target));
}
static int destroy(lua_State *L)
{
auto self = SharedPtrUserData<T, U>::from(lua_touserdata(L, -1));
// Note it is safe to only check the weakness of the pointer, as
// std::shared_ptr seems to have identical representation regardless of
// what it points to
assert(!self->isWeak);
self->target.reset();
lua_pop(L, 1); // Lua deallocates the memory for full user data
return 0;
}
};
} // namespace chatterino::lua::api
#endif
+12 -1
View File
@@ -137,6 +137,17 @@ public:
/// TEMPLATES
template <typename T>
StackIdx push(lua_State *L, std::optional<T> val)
{
if (val.has_value())
{
return lua::push(L, *val);
}
lua_pushnil(L);
return lua_gettop(L);
}
template <typename T>
bool peek(lua_State *L, std::optional<T> *out, StackIdx idx = -1)
{
@@ -262,7 +273,7 @@ StackIdx push(lua_State *L, QList<T> vec)
*
* @return Stack index of newly created string.
*/
template <typename T, std::enable_if<std::is_enum_v<T>>>
template <typename T, typename std::enable_if_t<std::is_enum_v<T>, bool> = true>
StackIdx push(lua_State *L, T inp)
{
std::string_view name = magic_enum::enum_name<T>(inp);
+8 -2
View File
@@ -6,6 +6,7 @@
# include "common/QLogging.hpp"
# include "controllers/commands/CommandContext.hpp"
# include "controllers/commands/CommandController.hpp"
# include "controllers/plugins/api/ChannelRef.hpp"
# include "controllers/plugins/LuaAPI.hpp"
# include "controllers/plugins/LuaUtilities.hpp"
# include "messages/MessageBuilder.hpp"
@@ -143,10 +144,8 @@ void PluginController::openLibrariesFor(lua_State *L, const PluginMeta &meta,
// NOLINTNEXTLINE(*-avoid-c-arrays)
static const luaL_Reg c2Lib[] = {
{"system_msg", lua::api::c2_system_msg},
{"register_command", lua::api::c2_register_command},
{"register_callback", lua::api::c2_register_callback},
{"send_msg", lua::api::c2_send_msg},
{"log", lua::api::c2_log},
{nullptr, nullptr},
};
@@ -164,6 +163,13 @@ void PluginController::openLibrariesFor(lua_State *L, const PluginMeta &meta,
lua::pushEnumTable<lua::api::EventType>(L);
lua_setfield(L, c2libIdx, "EventType");
lua::pushEnumTable<lua::api::LPlatform>(L);
lua_setfield(L, c2libIdx, "Platform");
// Initialize metatables for objects
lua::api::ChannelRef::createMetatable(L);
lua_setfield(L, c2libIdx, "Channel");
lua_setfield(L, gtable, "c2");
// ban functions
+394
View File
@@ -0,0 +1,394 @@
#ifdef CHATTERINO_HAVE_PLUGINS
# include "controllers/plugins/api/ChannelRef.hpp"
# include "common/Channel.hpp"
# include "controllers/commands/CommandController.hpp"
# include "controllers/plugins/LuaAPI.hpp"
# include "controllers/plugins/LuaUtilities.hpp"
# include "messages/MessageBuilder.hpp"
# include "providers/twitch/TwitchChannel.hpp"
# include "providers/twitch/TwitchIrcServer.hpp"
# include <lauxlib.h>
# include <lua.h>
# include <cassert>
# include <memory>
# include <optional>
namespace chatterino::lua::api {
// NOLINTBEGIN(*vararg)
// NOLINTNEXTLINE(*-avoid-c-arrays)
static const luaL_Reg CHANNEL_REF_METHODS[] = {
{"is_valid", &ChannelRef::is_valid},
{"get_name", &ChannelRef::get_name},
{"get_type", &ChannelRef::get_type},
{"get_display_name", &ChannelRef::get_display_name},
{"send_message", &ChannelRef::send_message},
{"add_system_message", &ChannelRef::add_system_message},
{"is_twitch_channel", &ChannelRef::is_twitch_channel},
// Twitch
{"get_room_modes", &ChannelRef::get_room_modes},
{"get_stream_status", &ChannelRef::get_stream_status},
{"get_twitch_id", &ChannelRef::get_twitch_id},
{"is_broadcaster", &ChannelRef::is_broadcaster},
{"is_mod", &ChannelRef::is_mod},
{"is_vip", &ChannelRef::is_vip},
// misc
{"__tostring", &ChannelRef::to_string},
// static
{"by_name", &ChannelRef::get_by_name},
{"by_twitch_id", &ChannelRef::get_by_twitch_id},
{nullptr, nullptr},
};
void ChannelRef::createMetatable(lua_State *L)
{
lua::StackGuard guard(L, 1);
luaL_newmetatable(L, "c2.Channel");
lua_pushstring(L, "__index");
lua_pushvalue(L, -2); // clone metatable
lua_settable(L, -3); // metatable.__index = metatable
// Generic IWeakResource stuff
lua_pushstring(L, "__gc");
lua_pushcfunction(
L, (&WeakPtrUserData<UserData::Type::Channel, ChannelRef>::destroy));
lua_settable(L, -3); // metatable.__gc = WeakPtrUserData<...>::destroy
luaL_setfuncs(L, CHANNEL_REF_METHODS, 0);
}
ChannelPtr ChannelRef::getOrError(lua_State *L, bool expiredOk)
{
if (lua_gettop(L) < 1)
{
luaL_error(L, "Called c2.Channel method without a channel object");
return nullptr;
}
if (lua_isuserdata(L, lua_gettop(L)) == 0)
{
luaL_error(
L, "Called c2.Channel method with a non Channel 'self' argument.");
return nullptr;
}
auto *data = WeakPtrUserData<UserData::Type::Channel, Channel>::from(
lua_touserdata(L, lua_gettop(L)));
if (data == nullptr)
{
luaL_error(L,
"Called c2.Channel method with an invalid channel pointer");
return nullptr;
}
lua_pop(L, 1);
if (data->target.expired())
{
if (!expiredOk)
{
luaL_error(L,
"Usage of expired c2.Channel object. Underlying "
"resource was freed. Use Channel:is_valid() to check");
}
return nullptr;
}
return data->target.lock();
}
std::shared_ptr<TwitchChannel> ChannelRef::getTwitchOrError(lua_State *L)
{
auto ref = ChannelRef::getOrError(L);
auto ptr = dynamic_pointer_cast<TwitchChannel>(ref);
if (ptr == nullptr)
{
luaL_error(L,
"c2.Channel Twitch-only operation on non-Twitch channel.");
}
return ptr;
}
int ChannelRef::is_valid(lua_State *L)
{
ChannelPtr that = ChannelRef::getOrError(L, true);
lua::push(L, that != nullptr);
return 1;
}
int ChannelRef::get_name(lua_State *L)
{
ChannelPtr that = ChannelRef::getOrError(L);
lua::push(L, that->getName());
return 1;
}
int ChannelRef::get_type(lua_State *L)
{
ChannelPtr that = ChannelRef::getOrError(L);
lua::push(L, that->getType());
return 1;
}
int ChannelRef::get_display_name(lua_State *L)
{
ChannelPtr that = ChannelRef::getOrError(L);
lua::push(L, that->getDisplayName());
return 1;
}
int ChannelRef::send_message(lua_State *L)
{
if (lua_gettop(L) != 2 && lua_gettop(L) != 3)
{
luaL_error(L, "Channel:send_message needs 1 or 2 arguments (message "
"text and optionally execute_commands flag)");
return 0;
}
bool execcmds = false;
if (lua_gettop(L) == 3)
{
if (!lua::pop(L, &execcmds))
{
luaL_error(L, "cannot get execute_commands (2nd argument of "
"Channel:send_message)");
return 0;
}
}
QString text;
if (!lua::pop(L, &text))
{
luaL_error(L, "cannot get text (1st argument of Channel:send_message)");
return 0;
}
ChannelPtr that = ChannelRef::getOrError(L);
text = text.replace('\n', ' ');
if (execcmds)
{
text = getIApp()->getCommands()->execCommand(text, that, false);
}
that->sendMessage(text);
return 0;
}
int ChannelRef::add_system_message(lua_State *L)
{
// needs to account for the hidden self argument
if (lua_gettop(L) != 2)
{
luaL_error(
L, "Channel:add_system_message needs exactly 1 argument (message "
"text)");
return 0;
}
QString text;
if (!lua::pop(L, &text))
{
luaL_error(
L, "cannot get text (1st argument of Channel:add_system_message)");
return 0;
}
ChannelPtr that = ChannelRef::getOrError(L);
text = text.replace('\n', ' ');
that->addMessage(makeSystemMessage(text));
return 0;
}
int ChannelRef::is_twitch_channel(lua_State *L)
{
ChannelPtr that = ChannelRef::getOrError(L);
lua::push(L, that->isTwitchChannel());
return 1;
}
int ChannelRef::get_room_modes(lua_State *L)
{
auto tc = ChannelRef::getTwitchOrError(L);
const auto m = tc->accessRoomModes();
const auto modes = LuaRoomModes{
.unique_chat = m->r9k,
.subscriber_only = m->submode,
.emotes_only = m->emoteOnly,
.follower_only = (m->followerOnly == -1)
? std::nullopt
: std::optional(m->followerOnly),
.slow_mode =
(m->slowMode == 0) ? std::nullopt : std::optional(m->slowMode),
};
lua::push(L, modes);
return 1;
}
int ChannelRef::get_stream_status(lua_State *L)
{
auto tc = ChannelRef::getTwitchOrError(L);
const auto s = tc->accessStreamStatus();
const auto status = LuaStreamStatus{
.live = s->live,
.viewer_count = static_cast<int>(s->viewerCount),
.uptime = s->uptimeSeconds,
.title = s->title,
.game_name = s->game,
.game_id = s->gameId,
};
lua::push(L, status);
return 1;
}
int ChannelRef::get_twitch_id(lua_State *L)
{
auto tc = ChannelRef::getTwitchOrError(L);
lua::push(L, tc->roomId());
return 1;
}
int ChannelRef::is_broadcaster(lua_State *L)
{
auto tc = ChannelRef::getTwitchOrError(L);
lua::push(L, tc->isBroadcaster());
return 1;
}
int ChannelRef::is_mod(lua_State *L)
{
auto tc = ChannelRef::getTwitchOrError(L);
lua::push(L, tc->isMod());
return 1;
}
int ChannelRef::is_vip(lua_State *L)
{
auto tc = ChannelRef::getTwitchOrError(L);
lua::push(L, tc->isVip());
return 1;
}
int ChannelRef::get_by_name(lua_State *L)
{
if (lua_gettop(L) != 2)
{
luaL_error(L, "Channel.by_name needs exactly 2 arguments (channel "
"name and platform)");
lua_pushnil(L);
return 1;
}
LPlatform platform{};
if (!lua::pop(L, &platform))
{
luaL_error(L, "cannot get platform (2nd argument of Channel.by_name, "
"expected a string)");
lua_pushnil(L);
return 1;
}
QString name;
if (!lua::pop(L, &name))
{
luaL_error(L,
"cannot get channel name (1st argument of Channel.by_name, "
"expected a string)");
lua_pushnil(L);
return 1;
}
auto chn = getApp()->twitch->getChannelOrEmpty(name);
if (chn->isEmpty())
{
lua_pushnil(L);
return 1;
}
// pushes onto stack
WeakPtrUserData<UserData::Type::Channel, Channel>::create(
L, chn->weak_from_this());
luaL_getmetatable(L, "c2.Channel");
lua_setmetatable(L, -2);
return 1;
}
int ChannelRef::get_by_twitch_id(lua_State *L)
{
if (lua_gettop(L) != 1)
{
luaL_error(
L, "Channel.by_twitch_id needs exactly 1 arguments (channel owner "
"id)");
lua_pushnil(L);
return 1;
}
QString id;
if (!lua::pop(L, &id))
{
luaL_error(L,
"cannot get channel name (1st argument of Channel.by_name, "
"expected a string)");
lua_pushnil(L);
return 1;
}
auto chn = getApp()->twitch->getChannelOrEmptyByID(id);
if (chn->isEmpty())
{
lua_pushnil(L);
return 1;
}
// pushes onto stack
WeakPtrUserData<UserData::Type::Channel, Channel>::create(
L, chn->weak_from_this());
luaL_getmetatable(L, "c2.Channel");
lua_setmetatable(L, -2);
return 1;
}
int ChannelRef::to_string(lua_State *L)
{
ChannelPtr that = ChannelRef::getOrError(L, true);
if (that == nullptr)
{
lua_pushstring(L, "<c2.Channel expired>");
return 1;
}
QString formated = QString("<c2.Channel %1>").arg(that->getName());
lua::push(L, formated);
return 1;
}
} // namespace chatterino::lua::api
// NOLINTEND(*vararg)
//
namespace chatterino::lua {
StackIdx push(lua_State *L, const api::LuaRoomModes &modes)
{
auto out = lua::pushEmptyTable(L, 6);
# define PUSH(field) \
lua::push(L, modes.field); \
lua_setfield(L, out, #field)
PUSH(unique_chat);
PUSH(subscriber_only);
PUSH(emotes_only);
PUSH(follower_only);
PUSH(slow_mode);
# undef PUSH
return out;
}
StackIdx push(lua_State *L, const api::LuaStreamStatus &status)
{
auto out = lua::pushEmptyTable(L, 6);
# define PUSH(field) \
lua::push(L, status.field); \
lua_setfield(L, out, #field)
PUSH(live);
PUSH(viewer_count);
PUSH(uptime);
PUSH(title);
PUSH(game_name);
PUSH(game_id);
# undef PUSH
return out;
}
} // namespace chatterino::lua
#endif
+275
View File
@@ -0,0 +1,275 @@
#pragma once
#include "providers/twitch/TwitchChannel.hpp"
#include <optional>
#ifdef CHATTERINO_HAVE_PLUGINS
# include "common/Channel.hpp"
# include "controllers/plugins/LuaUtilities.hpp"
# include "controllers/plugins/PluginController.hpp"
namespace chatterino::lua::api {
// NOLINTBEGIN(readability-identifier-naming)
/**
* This enum describes a platform for the purpose of searching for a channel.
* Currently only Twitch is supported because identifying IRC channels is tricky.
* @exposeenum Platform
*/
enum class LPlatform {
Twitch,
//IRC,
};
/**
* @lua@class Channel: IWeakResource
*/
struct ChannelRef {
static void createMetatable(lua_State *L);
friend class chatterino::PluginController;
/**
* @brief Get the content of the top object on Lua stack, usually first argument to function as a ChannelPtr.
* If the object given is not a userdatum or the pointer inside that
* userdatum doesn't point to a Channel, a lua error is thrown.
*
* @param expiredOk Should an expired return nullptr instead of erroring
*/
static ChannelPtr getOrError(lua_State *L, bool expiredOk = false);
/**
* @brief Casts the result of getOrError to std::shared_ptr<TwitchChannel>
* if that fails thows a lua error.
*/
static std::shared_ptr<TwitchChannel> getTwitchOrError(lua_State *L);
public:
/**
* Returns true if the channel this object points to is valid.
* If the object expired, returns false
* If given a non-Channel object, it errors.
*
* @lua@return boolean success
* @exposed Channel:is_valid
*/
static int is_valid(lua_State *L);
/**
* Gets the channel's name. This is the lowercase login name.
*
* @lua@return string name
* @exposed Channel:get_name
*/
static int get_name(lua_State *L);
/**
* Gets the channel's type
*
* @lua@return ChannelType
* @exposed Channel:get_type
*/
static int get_type(lua_State *L);
/**
* Get the channel owner's display name. This may contain non-lowercase ascii characters.
*
* @lua@return string name
* @exposed Channel:get_display_name
*/
static int get_display_name(lua_State *L);
/**
* Sends a message to the target channel.
* Note that this does not execute client-commands.
*
* @lua@param message string
* @lua@param execute_commands boolean Should commands be run on the text?
* @exposed Channel:send_message
*/
static int send_message(lua_State *L);
/**
* Adds a system message client-side
*
* @lua@param message string
* @exposed Channel:add_system_message
*/
static int add_system_message(lua_State *L);
/**
* Returns true for twitch channels.
* Compares the channel Type. Note that enum values aren't guaranteed, just
* that they are equal to the exposed enum.
*
* @lua@return bool
* @exposed Channel:is_twitch_channel
*/
static int is_twitch_channel(lua_State *L);
/**
* Twitch Channel specific functions
*/
/**
* Returns a copy of the channel mode settings (subscriber only, r9k etc.)
*
* @lua@return RoomModes
* @exposed Channel:get_room_modes
*/
static int get_room_modes(lua_State *L);
/**
* Returns a copy of the stream status.
*
* @lua@return StreamStatus
* @exposed Channel:get_stream_status
*/
static int get_stream_status(lua_State *L);
/**
* Returns the Twitch user ID of the owner of the channel.
*
* @lua@return string
* @exposed Channel:get_twitch_id
*/
static int get_twitch_id(lua_State *L);
/**
* Returns true if the channel is a Twitch channel and the user owns it
*
* @lua@return boolean
* @exposed Channel:is_broadcaster
*/
static int is_broadcaster(lua_State *L);
/**
* Returns true if the channel is a Twitch channel and the user is a moderator in the channel
* Returns false for broadcaster.
*
* @lua@return boolean
* @exposed Channel:is_mod
*/
static int is_mod(lua_State *L);
/**
* Returns true if the channel is a Twitch channel and the user is a VIP in the channel
* Returns false for broadcaster.
*
* @lua@return boolean
* @exposed Channel:is_vip
*/
static int is_vip(lua_State *L);
/**
* Misc
*/
/**
* @lua@return string
* @exposed Channel:__tostring
*/
static int to_string(lua_State *L);
/**
* Static functions
*/
/**
* Finds a channel by name.
*
* Misc channels are marked as Twitch:
* - /whispers
* - /mentions
* - /watching
* - /live
* - /automod
*
* @lua@param name string Which channel are you looking for?
* @lua@param platform Platform Where to search for the channel?
* @lua@return Channel?
* @exposed Channel.by_name
*/
static int get_by_name(lua_State *L);
/**
* Finds a channel by the Twitch user ID of its owner.
*
* @lua@param string id ID of the owner of the channel.
* @lua@return Channel?
* @exposed Channel.by_twitch_id
*/
static int get_by_twitch_id(lua_State *L);
};
// This is a copy of the TwitchChannel::RoomModes structure, except it uses nicer optionals
/**
* @lua@class RoomModes
*/
struct LuaRoomModes {
/**
* @lua@field unique_chat boolean You might know this as r9kbeta or robot9000.
*/
bool unique_chat = false;
/**
* @lua@field subscriber_only boolean
*/
bool subscriber_only = false;
/**
* @lua@field emotes_only boolean Whether or not text is allowed in messages.
* Note that "emotes" here only means Twitch emotes, not Unicode emoji, nor 3rd party text-based emotes
*/
bool emotes_only = false;
/**
* @lua@field unique_chat number? Time in minutes you need to follow to chat or nil.
*/
std::optional<int> follower_only;
/**
* @lua@field slow_mode number? Time in seconds you need to wait before sending messages or nil.
*/
std::optional<int> slow_mode;
};
/**
* @lua@class StreamStatus
*/
struct LuaStreamStatus {
/**
* @lua@field live boolean
*/
bool live = false;
/**
* @lua@field viewer_count number
*/
int viewer_count = 0;
/**
* @lua@field uptime number Seconds since the stream started.
*/
int uptime = 0;
/**
* @lua@field title string Stream title or last stream title
*/
QString title;
/**
* @lua@field game_name string
*/
QString game_name;
/**
* @lua@field game_id string
*/
QString game_id;
};
// NOLINTEND(readability-identifier-naming)
} // namespace chatterino::lua::api
namespace chatterino::lua {
StackIdx push(lua_State *L, const api::LuaRoomModes &modes);
StackIdx push(lua_State *L, const api::LuaStreamStatus &status);
} // namespace chatterino::lua
#endif