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;
|
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,
|
void CommandController::registerCommand(const QString &commandName,
|
||||||
CommandFunctionVariants commandFunction)
|
CommandFunctionVariants commandFunction)
|
||||||
{
|
{
|
||||||
|
|
|
@ -43,6 +43,8 @@ public:
|
||||||
ChannelPtr channel, const Message *message = nullptr,
|
ChannelPtr channel, const Message *message = nullptr,
|
||||||
std::unordered_map<QString, QString> context = {});
|
std::unordered_map<QString, QString> context = {});
|
||||||
|
|
||||||
|
bool registerPluginCommand(const QString &commandName);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void load(Paths &paths);
|
void load(Paths &paths);
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
#include "Application.hpp"
|
#include "Application.hpp"
|
||||||
#include "common/QLogging.hpp"
|
#include "common/QLogging.hpp"
|
||||||
|
#include "controllers/commands/CommandContext.hpp"
|
||||||
#include "messages/MessageBuilder.hpp"
|
#include "messages/MessageBuilder.hpp"
|
||||||
#include "providers/twitch/TwitchIrcServer.hpp"
|
#include "providers/twitch/TwitchIrcServer.hpp"
|
||||||
#include "singletons/WindowManager.hpp"
|
#include "singletons/WindowManager.hpp"
|
||||||
|
@ -56,11 +57,11 @@ void PluginController::load(QFileInfo index, QDir pluginDir)
|
||||||
luaL_openlibs(l);
|
luaL_openlibs(l);
|
||||||
this->loadChatterinoLib(l);
|
this->loadChatterinoLib(l);
|
||||||
|
|
||||||
luaL_dofile(l, index.absoluteFilePath().toStdString().c_str());
|
|
||||||
|
|
||||||
auto pluginName = pluginDir.dirName();
|
auto pluginName = pluginDir.dirName();
|
||||||
auto plugin = std::make_unique<Plugin>(pluginName, l);
|
auto plugin = std::make_unique<Plugin>(pluginName, l);
|
||||||
this->plugins.insert({pluginName, std::move(plugin)});
|
this->plugins.insert({pluginName, std::move(plugin)});
|
||||||
|
|
||||||
|
luaL_dofile(l, index.absoluteFilePath().toStdString().c_str());
|
||||||
qCInfo(chatterinoLua) << "Loaded" << pluginName << "plugin from" << index;
|
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_FALSE = 0;
|
||||||
constexpr int C_TRUE = 1;
|
constexpr int C_TRUE = 1;
|
||||||
|
|
||||||
|
@ -94,6 +138,7 @@ int luaC2SystemMsg(lua_State *L)
|
||||||
{
|
{
|
||||||
if (lua_gettop(L) != 2)
|
if (lua_gettop(L) != 2)
|
||||||
{
|
{
|
||||||
|
qCDebug(chatterinoLua) << "system_msg: need 2 args";
|
||||||
luaL_error(L, "need exactly 2 arguments"); // NOLINT
|
luaL_error(L, "need exactly 2 arguments"); // NOLINT
|
||||||
lua_pushboolean(L, C_FALSE);
|
lua_pushboolean(L, C_FALSE);
|
||||||
return 1;
|
return 1;
|
||||||
|
@ -104,17 +149,48 @@ int luaC2SystemMsg(lua_State *L)
|
||||||
const auto chn = getApp()->twitch->getChannelOrEmpty(channel);
|
const auto chn = getApp()->twitch->getChannelOrEmpty(channel);
|
||||||
if (chn->isEmpty())
|
if (chn->isEmpty())
|
||||||
{
|
{
|
||||||
|
qCDebug(chatterinoLua) << "system_msg: no channel" << channel;
|
||||||
lua_pushboolean(L, C_FALSE);
|
lua_pushboolean(L, C_FALSE);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
qCDebug(chatterinoLua) << "system_msg: OK!";
|
||||||
chn->addMessage(makeSystemMessage(text));
|
chn->addMessage(makeSystemMessage(text));
|
||||||
lua_pushboolean(L, C_TRUE);
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOLINTNEXTLINE
|
// NOLINTNEXTLINE
|
||||||
static const luaL_Reg C2LIB[] = {
|
static const luaL_Reg C2LIB[] = {
|
||||||
{"system_msg", luaC2SystemMsg},
|
{"system_msg", luaC2SystemMsg},
|
||||||
|
{"register_command", luaC2RegisterCommand},
|
||||||
{nullptr, nullptr},
|
{nullptr, nullptr},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "Application.hpp"
|
||||||
|
#include "common/QLogging.hpp"
|
||||||
#include "common/Singleton.hpp"
|
#include "common/Singleton.hpp"
|
||||||
|
#include "controllers/commands/CommandContext.hpp"
|
||||||
|
#include "controllers/commands/CommandController.hpp"
|
||||||
#include "singletons/Paths.hpp"
|
#include "singletons/Paths.hpp"
|
||||||
|
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
|
@ -16,18 +20,6 @@ struct lua_State;
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
//class Registration
|
|
||||||
//{
|
|
||||||
//public:
|
|
||||||
// enum Type {
|
|
||||||
// COMMAND,
|
|
||||||
// };
|
|
||||||
//
|
|
||||||
// Type type;
|
|
||||||
// QString name;
|
|
||||||
// const char *receiverFunctionName;
|
|
||||||
//};
|
|
||||||
|
|
||||||
class Plugin
|
class Plugin
|
||||||
{
|
{
|
||||||
public:
|
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:
|
private:
|
||||||
lua_State *state_;
|
lua_State *state_;
|
||||||
|
|
||||||
|
// maps command name -> function name
|
||||||
|
std::map<QString, QString> ownedCommands;
|
||||||
|
|
||||||
friend class PluginController;
|
friend class PluginController;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -55,10 +66,26 @@ public:
|
||||||
std::function<void(const std::unique_ptr<Plugin> &pl, lua_State *L)>
|
std::function<void(const std::unique_ptr<Plugin> &pl, lua_State *L)>
|
||||||
argCb);
|
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:
|
private:
|
||||||
void load(QFileInfo index, QDir pluginDir);
|
void load(QFileInfo index, QDir pluginDir);
|
||||||
void loadChatterinoLib(lua_State *l);
|
void loadChatterinoLib(lua_State *l);
|
||||||
|
|
||||||
std::map<QString, std::unique_ptr<Plugin>> plugins;
|
std::map<QString, std::unique_ptr<Plugin>> plugins;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue