feat: add REPL for plugins (#6120)

This can be enabled by setting the `.plugins.repl.enabled` setting to true


Close Chatterino and make a backup of your settings.json before attempting any modifications.
```json5
{
  "plugins": {
    "repl": {
      "enabled": true,
    },
  },
}
```
This commit is contained in:
nerix
2025-06-28 16:04:08 +02:00
committed by GitHub
parent 9e59fb1a5f
commit b6623cff88
16 changed files with 1000 additions and 18 deletions
+2 -15
View File
@@ -31,19 +31,6 @@
namespace {
using namespace chatterino;
void logHelper(lua_State *L, Plugin *pl, QDebug stream,
const sol::variadic_args &args)
{
stream.noquote();
stream << "[" + pl->id + ":" + pl->meta.name + "]";
for (const auto &arg : args)
{
stream << lua::toString(L, arg.stack_index());
// Remove this from our stack
lua_pop(L, 1);
}
}
QDebug qdebugStreamForLogLevel(lua::api::LogLevel lvl)
{
auto base =
@@ -101,7 +88,7 @@ void c2_log(ThisPluginState L, LogLevel lvl, sol::variadic_args args)
lua::StackGuard guard(L);
{
QDebug stream = qdebugStreamForLogLevel(lvl);
logHelper(L, L.plugin(), stream, args);
L.plugin()->log(L.state(), lvl, std::move(stream), args);
}
}
@@ -261,7 +248,7 @@ void g_print(ThisPluginState L, sol::variadic_args args)
(QMessageLogger(QT_MESSAGELOG_FILE, QT_MESSAGELOG_LINE,
QT_MESSAGELOG_FUNC, chatterinoLua().categoryName())
.debug());
logHelper(L, L.plugin(), stream, args);
L.plugin()->log(L.state(), LogLevel::Info, std::move(stream), args);
}
void package_loadlib(sol::variadic_args args)
+31
View File
@@ -218,6 +218,8 @@ std::unordered_set<QString> Plugin::listRegisteredCommands()
Plugin::~Plugin()
{
this->onUnloaded();
for (auto *timer : this->activeTimeouts)
{
QObject::disconnect(timer, nullptr, nullptr, nullptr);
@@ -293,6 +295,35 @@ bool Plugin::hasHTTPPermissionFor(const QUrl &url)
});
}
void Plugin::log(lua_State *L, lua::api::LogLevel level, QDebug stream,
const sol::variadic_args &args)
{
stream.noquote();
stream << "[" + this->id + ":" + this->meta.name + "]";
QString fullMessage;
for (const auto &arg : args)
{
auto s = lua::toString(L, arg.stack_index());
stream << s;
if (!fullMessage.isEmpty())
{
fullMessage.append(' ');
}
fullMessage.append(s);
// Remove this from our stack
lua_pop(L, 1);
}
this->onLog(level, fullMessage);
}
sol::state_view Plugin::state()
{
return {this->state_};
}
bool Plugin::hasNetworkPermission() const
{
return std::ranges::any_of(this->meta.permissions, [](const auto &p) {
+13
View File
@@ -6,6 +6,7 @@
# include "controllers/plugins/LuaUtilities.hpp"
# include "controllers/plugins/PluginPermission.hpp"
# include <boost/signals2/signal.hpp>
# include <QDir>
# include <QString>
# include <QUrl>
@@ -21,6 +22,10 @@
struct lua_State;
class QTimer;
namespace chatterino::lua::api {
enum class LogLevel;
} // namespace chatterino::lua::api
namespace chatterino {
struct PluginMeta {
@@ -137,12 +142,20 @@ public:
bool hasHTTPPermissionFor(const QUrl &url);
bool hasNetworkPermission() const;
void log(lua_State *L, lua::api::LogLevel level, QDebug stream,
const sol::variadic_args &args);
sol::state_view state();
std::map<lua::api::EventType, sol::protected_function> callbacks;
// In-flight HTTP Requests
// This is a lifetime hack to ensure they get deleted with the plugin. This relies on the Plugin getting deleted on reload!
std::vector<std::shared_ptr<lua::api::HTTPRequest>> httpRequests;
boost::signals2::signal<void()> onUnloaded;
boost::signals2::signal<void(lua::api::LogLevel, const QString &)> onLog;
private:
QDir loadDirectory_;
lua_State *state_;
@@ -278,6 +278,8 @@ void PluginController::load(const QFileInfo &index, const QDir &pluginDir,
}
temp->dataDirectory().mkpath(".");
// make sure we capture log messages during load
this->onPluginLoaded(temp);
qCDebug(chatterinoLua) << "Running lua file:" << index;
int err = luaL_dofile(l, index.absoluteFilePath().toStdString().c_str());
if (err != 0)
@@ -6,6 +6,7 @@
# include "controllers/commands/CommandContext.hpp"
# include "controllers/plugins/Plugin.hpp"
# include <boost/signals2/signal.hpp>
# include <QDir>
# include <QFileInfo>
# include <QJsonArray>
@@ -63,6 +64,8 @@ public:
WebSocketPool &webSocketPool();
boost::signals2::signal<void(Plugin *)> onPluginLoaded;
private:
void loadPlugins();
void load(const QFileInfo &index, const QDir &pluginDir,
+5 -2
View File
@@ -3,9 +3,11 @@
# include "Application.hpp"
# include "common/QLogging.hpp"
# include "controllers/plugins/LuaAPI.hpp"
# include "controllers/plugins/PluginController.hpp"
# include <QObject>
# include <QStringBuilder>
# include <sol/thread.hpp>
namespace chatterino::lua {
@@ -27,9 +29,10 @@ Plugin *ThisPluginState::plugin()
void logError(Plugin *plugin, QStringView context, const QString &msg)
{
QString fullMessage = context % u" - " % msg;
qCWarning(chatterinoLua).noquote()
<< "[" + plugin->id + ":" + plugin->meta.name + "]" << context << "-"
<< msg;
<< "[" + plugin->id + ":" + plugin->meta.name + "]" << fullMessage;
plugin->onLog(api::LogLevel::Warning, fullMessage);
}
} // namespace chatterino::lua