mirror of
https://github.com/Chatterino/chatterino2.git
synced 2024-11-21 22:24:07 +01:00
Add support for custom commands
This commit is contained in:
parent
74c6948a58
commit
962d417613
4 changed files with 132 additions and 15 deletions
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in a new issue