Add support for custom commands

This commit is contained in:
Mm2PL 2023-01-30 19:49:21 +01:00
parent 74c6948a58
commit 962d417613
No known key found for this signature in database
GPG key ID: 94AC9B80EFA15ED9
4 changed files with 132 additions and 15 deletions

View file

@ -3228,6 +3228,18 @@ QString CommandController::execCommand(const QString &textNoEmoji,
return text;
}
bool CommandController::registerPluginCommand(const QString &commandName)
{
if (this->commands_.contains(commandName))
{
return false;
}
this->commands_[commandName] = [commandName](const CommandContext &ctx) {
return getApp()->plugins->tryExecPluginCommand(commandName, ctx);
};
return true;
}
void CommandController::registerCommand(const QString &commandName,
CommandFunctionVariants commandFunction)
{

View file

@ -43,6 +43,8 @@ public:
ChannelPtr channel, const Message *message = nullptr,
std::unordered_map<QString, QString> context = {});
bool registerPluginCommand(const QString &commandName);
private:
void load(Paths &paths);

View file

@ -2,6 +2,7 @@
#include "Application.hpp"
#include "common/QLogging.hpp"
#include "controllers/commands/CommandContext.hpp"
#include "messages/MessageBuilder.hpp"
#include "providers/twitch/TwitchIrcServer.hpp"
#include "singletons/WindowManager.hpp"
@ -56,11 +57,11 @@ void PluginController::load(QFileInfo index, QDir pluginDir)
luaL_openlibs(l);
this->loadChatterinoLib(l);
luaL_dofile(l, index.absoluteFilePath().toStdString().c_str());
auto pluginName = pluginDir.dirName();
auto plugin = std::make_unique<Plugin>(pluginName, l);
this->plugins.insert({pluginName, std::move(plugin)});
luaL_dofile(l, index.absoluteFilePath().toStdString().c_str());
qCInfo(chatterinoLua) << "Loaded" << pluginName << "plugin from" << index;
}
@ -85,6 +86,49 @@ void PluginController::callEveryWithArgs(
}
}
QString PluginController::tryExecPluginCommand(const QString &commandName,
const CommandContext &ctx)
{
for (auto &[name, plugin] : this->plugins)
{
if (auto it = plugin->ownedCommands.find(commandName);
it != plugin->ownedCommands.end())
{
const auto &funcName = it->second;
auto *L = plugin->state_; // NOLINT
lua_getfield(L, LUA_REGISTRYINDEX, funcName.toStdString().c_str());
// put args on stack
lua_createtable(L, 0, 2);
auto outIdx = lua_gettop(L);
lua_createtable(L, ctx.words.count(), 0);
auto wordsIdx = lua_gettop(L);
int i = 1;
for (const auto &w : ctx.words)
{
lua_pushstring(L, w.toStdString().c_str());
lua_seti(L, wordsIdx, i);
i += 1;
}
lua_setfield(L, outIdx, "words");
lua_pushstring(L, ctx.channel->getName().toStdString().c_str());
lua_setfield(L, outIdx, "channelName");
lua_pcall(L, 1, 0, 0);
return "";
}
}
qCCritical(chatterinoLua)
<< "Something's seriously up, no plugin owns command" << commandName
<< "yet a call to execute it came in";
assert(false && "missing plugin command owner");
return "";
}
constexpr int C_FALSE = 0;
constexpr int C_TRUE = 1;
@ -94,6 +138,7 @@ int luaC2SystemMsg(lua_State *L)
{
if (lua_gettop(L) != 2)
{
qCDebug(chatterinoLua) << "system_msg: need 2 args";
luaL_error(L, "need exactly 2 arguments"); // NOLINT
lua_pushboolean(L, C_FALSE);
return 1;
@ -104,17 +149,48 @@ int luaC2SystemMsg(lua_State *L)
const auto chn = getApp()->twitch->getChannelOrEmpty(channel);
if (chn->isEmpty())
{
qCDebug(chatterinoLua) << "system_msg: no channel" << channel;
lua_pushboolean(L, C_FALSE);
return 1;
}
qCDebug(chatterinoLua) << "system_msg: OK!";
chn->addMessage(makeSystemMessage(text));
lua_pushboolean(L, C_TRUE);
return 1;
}
int luaC2RegisterCommand(lua_State *L)
{
auto *pl = getApp()->plugins->getPluginByStatePtr(L);
if (pl == nullptr)
{
luaL_error(L, "internal error: no plugin"); // NOLINT
return 0;
}
const char *name = luaL_optstring(L, 1, NULL);
if (lua_isnoneornil(L, 2))
{
// NOLINTNEXTLINE
luaL_error(L, "missing argument for register_command: function "
"\"pointer\"");
return 0;
}
auto callbackSavedName = QString("c2commandcb-%1").arg(name);
lua_setfield(L, LUA_REGISTRYINDEX, callbackSavedName.toStdString().c_str());
pl->registerCommand(name, callbackSavedName);
// delete both name and callback
lua_pop(L, 2);
return 0;
}
// NOLINTNEXTLINE
static const luaL_Reg C2LIB[] = {
{"system_msg", luaC2SystemMsg},
{"register_command", luaC2RegisterCommand},
{nullptr, nullptr},
};
}

View file

@ -1,6 +1,10 @@
#pragma once
#include "Application.hpp"
#include "common/QLogging.hpp"
#include "common/Singleton.hpp"
#include "controllers/commands/CommandContext.hpp"
#include "controllers/commands/CommandController.hpp"
#include "singletons/Paths.hpp"
#include <QDir>
@ -16,18 +20,6 @@ struct lua_State;
namespace chatterino {
//class Registration
//{
//public:
// enum Type {
// COMMAND,
// };
//
// Type type;
// QString name;
// const char *receiverFunctionName;
//};
class Plugin
{
public:
@ -38,9 +30,28 @@ public:
{
}
bool registerCommand(const QString &name, const QString &functionName)
{
if (this->ownedCommands.find(name) != this->ownedCommands.end())
{
return false;
}
auto ok = getApp()->commands->registerPluginCommand(name);
if (!ok)
{
return false;
}
this->ownedCommands.insert({name, functionName});
return true;
}
private:
lua_State *state_;
// maps command name -> function name
std::map<QString, QString> ownedCommands;
friend class PluginController;
};
@ -55,10 +66,26 @@ public:
std::function<void(const std::unique_ptr<Plugin> &pl, lua_State *L)>
argCb);
QString tryExecPluginCommand(const QString &commandName,
const CommandContext &ctx);
// NOTE: this pointer does not own the Plugin, unique_ptr still owns it
// This is required to be public because of c functions
Plugin *getPluginByStatePtr(lua_State *L)
{
for (auto &[name, plugin] : this->plugins)
{
if (plugin->state_ == L)
{
return plugin.get();
}
}
return nullptr;
}
private:
void load(QFileInfo index, QDir pluginDir);
void loadChatterinoLib(lua_State *l);
std::map<QString, std::unique_ptr<Plugin>> plugins;
};