feat(plugins): add display name change event to Channel (#6594)
Reviewed-by: Mm2PL <mm2pl+gh@kotmisia.pl>
This commit is contained in:
@@ -27,6 +27,7 @@
|
||||
- Minor: Added Markdown support to user notes. (#6490)
|
||||
- Minor: Moderators and VIPs in shared chats now show their channel badges. (#6653)
|
||||
- Minor: Added message read/update methods to the `Channel` plugin API. (#6650)
|
||||
- Minor: Added a display name change event to `Channel` for plugins. (#6594)
|
||||
- Minor: Added action to reset `/watching`. (#6759)
|
||||
- Minor: Removed messaging about running flatpak. This is already apparent in the newer Flatpak runtimes. (#6768)
|
||||
- Minor: Add `/(un)monitor` and `/(un)restrict` commands for moderators. (#6750)
|
||||
|
||||
Vendored
+10
@@ -46,12 +46,22 @@ declare namespace c2 {
|
||||
game_id: string;
|
||||
}
|
||||
|
||||
class ConnectionHandle {
|
||||
disconnect(): void;
|
||||
block(): void;
|
||||
unblock(): void;
|
||||
is_blocked(): boolean;
|
||||
is_connected(): boolean;
|
||||
}
|
||||
|
||||
class Channel implements IWeakResource {
|
||||
is_valid(): boolean;
|
||||
get_name(): string;
|
||||
get_type(): ChannelType;
|
||||
get_display_name(): string;
|
||||
|
||||
on_display_name_changed(cb: () => void): ConnectionHandle;
|
||||
|
||||
send_message(message: string, execute_commands: boolean): void;
|
||||
send_message(message: string): void;
|
||||
|
||||
|
||||
@@ -244,6 +244,12 @@ function c2.Channel:is_vip() end
|
||||
---@return string
|
||||
function c2.Channel:__tostring() end
|
||||
|
||||
--- Callback when the channel display name changes.
|
||||
---
|
||||
---@param cb fun()
|
||||
---@return c2.ConnectionHandle hdl
|
||||
function c2.Channel:on_display_name_changed(cb) end
|
||||
|
||||
--- Finds a channel by name.
|
||||
--- Misc channels are marked as Twitch:
|
||||
--- - /whispers
|
||||
@@ -264,6 +270,37 @@ function c2.Channel.by_twitch_id(id) end
|
||||
|
||||
-- End src/controllers/plugins/api/ChannelRef.hpp
|
||||
|
||||
-- Begin src/controllers/plugins/api/ConnectionHandle.hpp
|
||||
|
||||
|
||||
|
||||
---@class c2.ConnectionHandle
|
||||
---This type represents a handle to a registration of a callback for an event handler.
|
||||
---Conceptually, the event has a _connection_ to the callback/handler.
|
||||
---This handle can be used to modify that connection.
|
||||
---It does not automatically disconnect the connection when it's destroyed (in `__gc`) -
|
||||
---`disconnect()` has to be called manually.
|
||||
c2.ConnectionHandle = {}
|
||||
|
||||
---Disconnect the signal
|
||||
function c2.ConnectionHandle:disconnect() end
|
||||
|
||||
---Block events on this connection
|
||||
function c2.ConnectionHandle:block() end
|
||||
|
||||
---Unblock events on this connection
|
||||
function c2.ConnectionHandle:unblock() end
|
||||
|
||||
---Is this connection currently blocked?
|
||||
---@return boolean is_blocked
|
||||
function c2.ConnectionHandle:is_blocked() end
|
||||
|
||||
---Is this connection still connected?
|
||||
---@return boolean is_connected
|
||||
function c2.ConnectionHandle:is_connected() end
|
||||
|
||||
-- End src/controllers/plugins/api/ConnectionHandle.hpp
|
||||
|
||||
-- Begin src/controllers/plugins/api/HTTPResponse.hpp
|
||||
|
||||
---@class c2.HTTPResponse
|
||||
|
||||
@@ -352,6 +352,15 @@ saddummys:get_display_name() -- "서새봄냥"
|
||||
|
||||
<!-- F Korean Twitch, apparently you were not profitable enough -->
|
||||
|
||||
##### `Channel:on_display_name_changed(cb)`
|
||||
|
||||
Callback when the channel display name changes. The callback doesn't get any
|
||||
arguments, use [`Channel:get_display_name`](#channelget_display_name) to get the
|
||||
updated name.
|
||||
|
||||
This returns a [`ConnectionHandle`](#connectionhandle) which can be used to
|
||||
disconnect the handler.
|
||||
|
||||
##### `Channel:send_message(message[, execute_commands])`
|
||||
|
||||
Sends a message to the channel with the given text. If `execute_commands` is
|
||||
@@ -775,6 +784,34 @@ This table describes links available to plugins.
|
||||
| `JumpToMessage` | ID of the message | Highlight the message with given ID in current split, do nothing if it was not found | n/a |
|
||||
| `InsertText` | Any text, command or emote | Insert text into split input | n/a |
|
||||
|
||||
#### `ConnectionHandle`
|
||||
|
||||
This type represents a handle to a registration of a callback for an event handler.
|
||||
Conceptually, the event has a _connection_ to the callback/handler.
|
||||
This handle can be used to modify that connection.
|
||||
It does not automatically disconnect the connection when it's destroyed (in `__gc`) -
|
||||
[`disconnect()`](#connectionhandledisconnect) has to be called manually.
|
||||
|
||||
##### `ConnectionHandle:disconnect()`
|
||||
|
||||
Disconnect the signal.
|
||||
|
||||
##### `ConnectionHandle:block()`
|
||||
|
||||
Block events on this connection.
|
||||
|
||||
##### `ConnectionHandle:unblock()`
|
||||
|
||||
Unblock events on this connection.
|
||||
|
||||
##### `ConnectionHandle:is_blocked()`
|
||||
|
||||
Is this connection currently blocked?
|
||||
|
||||
##### `ConnectionHandle:is_connected()`
|
||||
|
||||
Is this connection still connected?
|
||||
|
||||
### Input/Output API
|
||||
|
||||
These functions are wrappers for Lua's I/O library. Functions on file pointer
|
||||
|
||||
@@ -256,6 +256,8 @@ set(SOURCE_FILES
|
||||
controllers/plugins/api/Accounts.hpp
|
||||
controllers/plugins/api/ChannelRef.cpp
|
||||
controllers/plugins/api/ChannelRef.hpp
|
||||
controllers/plugins/api/ConnectionHandle.cpp
|
||||
controllers/plugins/api/ConnectionHandle.hpp
|
||||
controllers/plugins/api/DebugLibrary.cpp
|
||||
controllers/plugins/api/DebugLibrary.hpp
|
||||
controllers/plugins/api/EventType.hpp
|
||||
@@ -275,6 +277,8 @@ set(SOURCE_FILES
|
||||
controllers/plugins/api/Message.hpp
|
||||
controllers/plugins/api/WebSocket.cpp
|
||||
controllers/plugins/api/WebSocket.hpp
|
||||
controllers/plugins/ConnectionManager.cpp
|
||||
controllers/plugins/ConnectionManager.hpp
|
||||
controllers/plugins/LuaAPI.cpp
|
||||
controllers/plugins/LuaAPI.hpp
|
||||
controllers/plugins/LuaUtilities.cpp
|
||||
@@ -287,6 +291,8 @@ set(SOURCE_FILES
|
||||
controllers/plugins/PluginMeta.hpp
|
||||
controllers/plugins/PluginPermission.cpp
|
||||
controllers/plugins/PluginPermission.hpp
|
||||
controllers/plugins/PluginRef.cpp
|
||||
controllers/plugins/PluginRef.hpp
|
||||
controllers/plugins/SolTypes.cpp
|
||||
controllers/plugins/SolTypes.hpp
|
||||
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
#include "controllers/plugins/ConnectionManager.hpp"
|
||||
|
||||
#ifdef CHATTERINO_HAVE_PLUGINS
|
||||
|
||||
# include <algorithm>
|
||||
|
||||
namespace chatterino::lua {
|
||||
|
||||
api::ConnectionHandle ConnectionManager::add(
|
||||
pajlada::Signals::ScopedConnection &&conn)
|
||||
{
|
||||
this->removeDisconnected();
|
||||
return {
|
||||
.connection = this->connections.emplace_back(
|
||||
std::make_shared<pajlada::Signals::ScopedConnection>(
|
||||
std::move(conn))),
|
||||
};
|
||||
}
|
||||
|
||||
void ConnectionManager::clear()
|
||||
{
|
||||
this->connections.clear();
|
||||
}
|
||||
|
||||
void ConnectionManager::removeDisconnected()
|
||||
{
|
||||
auto [first, last] =
|
||||
std::ranges::remove_if(this->connections, [](const auto &conn) {
|
||||
return !conn || !conn->isConnected();
|
||||
});
|
||||
this->connections.erase(first, last);
|
||||
}
|
||||
|
||||
} // namespace chatterino::lua
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,46 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef CHATTERINO_HAVE_PLUGINS
|
||||
|
||||
# include "controllers/plugins/api/ConnectionHandle.hpp"
|
||||
|
||||
# include <pajlada/signals/scoped-connection.hpp>
|
||||
|
||||
# include <vector>
|
||||
|
||||
namespace chatterino::lua {
|
||||
|
||||
class PluginWeakRef;
|
||||
|
||||
/// A manager for pajlada::Signals connections.
|
||||
///
|
||||
/// In contrast to the existing connection managers, such as a regular
|
||||
/// std::vector, this tries to avoid storing expired connections, because there
|
||||
/// is only one per plugin. We also store a `std::shared_ptr<ScopedConnection>`
|
||||
/// to be able to give the plugin a `std::weak_ptr<ScopedConnection>` which it
|
||||
/// can use to disconnect.
|
||||
class ConnectionManager
|
||||
{
|
||||
public:
|
||||
ConnectionManager() = default;
|
||||
|
||||
api::ConnectionHandle add(pajlada::Signals::ScopedConnection &&conn);
|
||||
|
||||
template <typename Signal, typename Callback>
|
||||
api::ConnectionHandle managedConnect(Signal &signal, Callback cb)
|
||||
{
|
||||
return this->add(signal.connect(std::forward<Callback>(cb)));
|
||||
}
|
||||
|
||||
void clear();
|
||||
|
||||
private:
|
||||
void removeDisconnected();
|
||||
|
||||
std::vector<std::shared_ptr<pajlada::Signals::ScopedConnection>>
|
||||
connections;
|
||||
};
|
||||
|
||||
} // namespace chatterino::lua
|
||||
|
||||
#endif
|
||||
@@ -88,6 +88,7 @@ sol::table toTable(lua_State *L, const CompletionEvent &ev);
|
||||
* @includefile common/Channel.hpp
|
||||
* @includefile controllers/plugins/api/Accounts.hpp
|
||||
* @includefile controllers/plugins/api/ChannelRef.hpp
|
||||
* @includefile controllers/plugins/api/ConnectionHandle.hpp
|
||||
* @includefile controllers/plugins/api/HTTPResponse.hpp
|
||||
* @includefile controllers/plugins/api/HTTPRequest.hpp
|
||||
* @includefile controllers/plugins/api/Message.hpp
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
# include "common/QLogging.hpp"
|
||||
# include "controllers/commands/CommandController.hpp"
|
||||
# include "controllers/plugins/PluginPermission.hpp"
|
||||
# include "controllers/plugins/SignalCallback.hpp"
|
||||
|
||||
# include <lua.h>
|
||||
# include <QLoggingCategory>
|
||||
@@ -48,6 +49,11 @@ std::unordered_set<QString> Plugin::listRegisteredCommands()
|
||||
return out;
|
||||
}
|
||||
|
||||
lua::SignalCallback Plugin::createCallback(sol::main_protected_function pfn)
|
||||
{
|
||||
return {this->selfRef_.weak(), std::move(pfn)};
|
||||
}
|
||||
|
||||
Plugin::~Plugin()
|
||||
{
|
||||
this->onUnloaded();
|
||||
@@ -57,6 +63,7 @@ Plugin::~Plugin()
|
||||
QObject::disconnect(timer, nullptr, nullptr, nullptr);
|
||||
timer->deleteLater();
|
||||
}
|
||||
this->selfRef_.destroy();
|
||||
this->httpRequests.clear();
|
||||
qCDebug(chatterinoLua) << "Destroyed" << this->activeTimeouts.size()
|
||||
<< "timers for plugin" << this->id
|
||||
|
||||
@@ -7,7 +7,9 @@
|
||||
#ifdef CHATTERINO_HAVE_PLUGINS
|
||||
# include "controllers/plugins/api/EventType.hpp"
|
||||
# include "controllers/plugins/api/HTTPRequest.hpp"
|
||||
# include "controllers/plugins/ConnectionManager.hpp"
|
||||
# include "controllers/plugins/PluginMeta.hpp"
|
||||
# include "controllers/plugins/PluginRef.hpp"
|
||||
|
||||
# include <boost/signals2/signal.hpp>
|
||||
# include <QDir>
|
||||
@@ -29,6 +31,10 @@ namespace chatterino::lua::api {
|
||||
enum class LogLevel;
|
||||
} // namespace chatterino::lua::api
|
||||
|
||||
namespace chatterino::lua {
|
||||
struct SignalCallback;
|
||||
} // namespace chatterino::lua
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class Plugin
|
||||
@@ -43,6 +49,7 @@ public:
|
||||
, meta(std::move(meta))
|
||||
, loadDirectory_(loadDirectory)
|
||||
, state_(state)
|
||||
, selfRef_(state ? this : nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -91,6 +98,8 @@ public:
|
||||
return it->second;
|
||||
}
|
||||
|
||||
lua::SignalCallback createCallback(sol::main_protected_function pfn);
|
||||
|
||||
/**
|
||||
* If the plugin crashes while evaluating the main file, this function will return the error
|
||||
*/
|
||||
@@ -99,6 +108,8 @@ public:
|
||||
return this->error_;
|
||||
}
|
||||
|
||||
lua::PluginWeakRef weakRef() const;
|
||||
|
||||
int addTimeout(QTimer *timer);
|
||||
void removeTimeout(QTimer *timer);
|
||||
|
||||
@@ -119,11 +130,14 @@ public:
|
||||
|
||||
boost::signals2::signal<void()> onUnloaded;
|
||||
boost::signals2::signal<void(lua::api::LogLevel, const QString &)> onLog;
|
||||
lua::ConnectionManager connections;
|
||||
|
||||
private:
|
||||
QDir loadDirectory_;
|
||||
lua_State *state_;
|
||||
|
||||
lua::PluginRef selfRef_;
|
||||
|
||||
QString error_;
|
||||
|
||||
// maps command name -> function
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
# include "controllers/commands/CommandController.hpp"
|
||||
# include "controllers/plugins/api/Accounts.hpp"
|
||||
# include "controllers/plugins/api/ChannelRef.hpp"
|
||||
# include "controllers/plugins/api/ConnectionHandle.hpp"
|
||||
# include "controllers/plugins/api/DebugLibrary.hpp"
|
||||
# include "controllers/plugins/api/HTTPRequest.hpp"
|
||||
# include "controllers/plugins/api/HTTPResponse.hpp"
|
||||
@@ -244,6 +245,7 @@ void PluginController::initSol(sol::state_view &lua, Plugin *plugin)
|
||||
lua::api::HTTPResponse::createUserType(c2);
|
||||
lua::api::HTTPRequest::createUserType(c2);
|
||||
lua::api::WebSocket::createUserType(c2, plugin);
|
||||
lua::api::ConnectionHandle::createUserType(c2);
|
||||
lua::api::message::createUserType(c2);
|
||||
lua::api::createAccounts(c2);
|
||||
c2["ChannelType"] = lua::createEnumTable<Channel::Type>(lua);
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
#include "controllers/plugins/PluginRef.hpp"
|
||||
|
||||
#ifdef CHATTERINO_HAVE_PLUGINS
|
||||
|
||||
# include "Application.hpp"
|
||||
# include "common/QLogging.hpp"
|
||||
# include "controllers/plugins/Plugin.hpp"
|
||||
|
||||
# include <QMessageBox>
|
||||
# include <QStringBuilder>
|
||||
|
||||
namespace chatterino::lua {
|
||||
|
||||
PluginRef::PluginRef(Plugin *plugin)
|
||||
: shared(plugin, /*deleter=*/[](void *) {})
|
||||
{
|
||||
assert(plugin != nullptr);
|
||||
}
|
||||
|
||||
PluginRef::PluginRef(std::shared_ptr<Plugin> plugin)
|
||||
: shared(std::move(plugin))
|
||||
{
|
||||
}
|
||||
|
||||
PluginWeakRef PluginRef::weak() const noexcept
|
||||
{
|
||||
return {this->shared};
|
||||
}
|
||||
|
||||
Plugin *PluginRef::plugin() const noexcept
|
||||
{
|
||||
return this->shared.get();
|
||||
}
|
||||
|
||||
void PluginRef::destroy()
|
||||
{
|
||||
auto useCount = this->shared.use_count();
|
||||
if (useCount > 1)
|
||||
{
|
||||
qCWarning(chatterinoLua)
|
||||
<< "Destroying PluginRef with" << useCount
|
||||
<< "strong references. Expected one reference (the plugin itself).";
|
||||
assert(false && "Too many strong references.");
|
||||
}
|
||||
|
||||
this->shared.reset();
|
||||
}
|
||||
|
||||
PluginWeakRef::PluginWeakRef(std::weak_ptr<Plugin> weak)
|
||||
: weak(std::move(weak))
|
||||
{
|
||||
}
|
||||
|
||||
PluginRef PluginWeakRef::strong() const noexcept
|
||||
{
|
||||
return {this->weak.lock()};
|
||||
}
|
||||
|
||||
bool PluginWeakRef::isAlive() const noexcept
|
||||
{
|
||||
return !this->weak.expired();
|
||||
}
|
||||
|
||||
} // namespace chatterino::lua
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,89 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef CHATTERINO_HAVE_PLUGINS
|
||||
|
||||
# include <memory>
|
||||
|
||||
namespace chatterino {
|
||||
class Plugin;
|
||||
} // namespace chatterino
|
||||
|
||||
namespace chatterino::lua {
|
||||
|
||||
class PluginWeakRef;
|
||||
|
||||
/// A strong reference to a plugin.
|
||||
///
|
||||
/// This reference is similar to a shared pointer with the big difference that
|
||||
/// it doesn't own the plugin. Destroying all `PluginRef`s will _not_ destroy
|
||||
/// the plugin (-> the destructor of the shared_ptr does nothing).
|
||||
///
|
||||
/// The plugin itself stores one strong reference. This reference is destroyed
|
||||
/// with destroy() right before the Lua state is closed. At that point, the
|
||||
/// reference count must be exactly 1 - meaning only the plugin itself has the
|
||||
/// strong reference.
|
||||
///
|
||||
/// Any object that keeps a reference to the plugin or to Lua values should keep
|
||||
/// a `PluginWeakRef`. For example, callbacks for signals keep this weak
|
||||
/// reference and avoid interacting with Lua references if the plugin is dead.
|
||||
/// While accessing a plugin, objects should upgrade their weak reference to a
|
||||
/// strong one. This way destroy() can detect potentially dangling references.
|
||||
class PluginRef
|
||||
{
|
||||
public:
|
||||
PluginRef() = default;
|
||||
PluginRef(PluginRef &&) = default;
|
||||
PluginRef &operator=(const PluginRef &) = default;
|
||||
PluginRef &operator=(PluginRef &&) = default;
|
||||
PluginRef(const PluginRef &) = default;
|
||||
~PluginRef() = default;
|
||||
|
||||
PluginWeakRef weak() const noexcept;
|
||||
Plugin *plugin() const noexcept;
|
||||
|
||||
operator bool() const noexcept
|
||||
{
|
||||
return this->shared != nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
PluginRef(Plugin *plugin);
|
||||
PluginRef(std::shared_ptr<Plugin> plugin);
|
||||
|
||||
/// To be called by the plugin before the Lua state is closed.
|
||||
///
|
||||
/// Resets the reference if there are no other shared references.
|
||||
/// Otherwise, aborts execution.
|
||||
void destroy();
|
||||
|
||||
std::shared_ptr<Plugin> shared;
|
||||
|
||||
friend Plugin;
|
||||
friend PluginWeakRef;
|
||||
};
|
||||
|
||||
class PluginWeakRef
|
||||
{
|
||||
public:
|
||||
PluginWeakRef() = default;
|
||||
PluginWeakRef(const PluginWeakRef &) = default;
|
||||
PluginWeakRef(PluginWeakRef &&) = default;
|
||||
PluginWeakRef &operator=(const PluginWeakRef &) = default;
|
||||
PluginWeakRef &operator=(PluginWeakRef &&) = default;
|
||||
~PluginWeakRef() = default;
|
||||
|
||||
PluginRef strong() const noexcept;
|
||||
|
||||
bool isAlive() const noexcept;
|
||||
|
||||
private:
|
||||
PluginWeakRef(std::weak_ptr<Plugin> weak);
|
||||
|
||||
std::weak_ptr<Plugin> weak;
|
||||
|
||||
friend PluginRef;
|
||||
};
|
||||
|
||||
} // namespace chatterino::lua
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,76 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef CHATTERINO_HAVE_PLUGINS
|
||||
|
||||
# include "controllers/plugins/PluginRef.hpp"
|
||||
# include "controllers/plugins/SolTypes.hpp"
|
||||
# include "debug/AssertInGuiThread.hpp"
|
||||
|
||||
namespace chatterino::lua {
|
||||
|
||||
struct SignalCallback {
|
||||
SignalCallback(PluginWeakRef pluginRef, sol::main_protected_function pfn)
|
||||
: pluginRef(std::move(pluginRef))
|
||||
, pfn(std::move(pfn))
|
||||
{
|
||||
assert(this->pfn.valid());
|
||||
}
|
||||
|
||||
SignalCallback(const SignalCallback &other) = default;
|
||||
SignalCallback(SignalCallback &&) = default;
|
||||
|
||||
SignalCallback &operator=(const SignalCallback &other)
|
||||
{
|
||||
if (this == &other)
|
||||
{
|
||||
return *this;
|
||||
}
|
||||
|
||||
if (!this->pluginRef.isAlive())
|
||||
{
|
||||
// Reset the function reference before we assign a new function to
|
||||
// it to avoid destroying it.
|
||||
this->pfn.abandon();
|
||||
}
|
||||
this->pluginRef = other.pluginRef;
|
||||
this->pfn = other.pfn;
|
||||
return *this;
|
||||
}
|
||||
|
||||
SignalCallback &operator=(SignalCallback &&other) noexcept
|
||||
{
|
||||
std::swap(this->pfn, other.pfn);
|
||||
std::swap(this->pluginRef, other.pluginRef);
|
||||
return *this;
|
||||
}
|
||||
|
||||
~SignalCallback()
|
||||
{
|
||||
assertInGuiThread();
|
||||
if (!this->pluginRef.isAlive())
|
||||
{
|
||||
this->pfn.abandon(); // don't destruct the function in this case
|
||||
}
|
||||
}
|
||||
|
||||
void operator()(auto &&...args) const
|
||||
{
|
||||
assertInGuiThread();
|
||||
auto strong = this->pluginRef.strong();
|
||||
if (!strong)
|
||||
{
|
||||
assert(false && "faulty signal handling");
|
||||
return;
|
||||
}
|
||||
loggedVoidCall(this->pfn, u"SignalCallback::operator()",
|
||||
strong.plugin(), std::forward<decltype(args)>(args)...);
|
||||
}
|
||||
|
||||
private:
|
||||
PluginWeakRef pluginRef;
|
||||
sol::main_protected_function pfn;
|
||||
};
|
||||
|
||||
} // namespace chatterino::lua
|
||||
|
||||
#endif
|
||||
@@ -8,6 +8,8 @@
|
||||
# include "Application.hpp"
|
||||
# include "common/Channel.hpp"
|
||||
# include "controllers/commands/CommandController.hpp"
|
||||
# include "controllers/plugins/Plugin.hpp"
|
||||
# include "controllers/plugins/SignalCallback.hpp"
|
||||
# include "controllers/plugins/SolTypes.hpp"
|
||||
# include "messages/Message.hpp"
|
||||
# include "providers/twitch/TwitchChannel.hpp"
|
||||
@@ -243,6 +245,15 @@ bool ChannelRef::operator==(const ChannelRef &other) const noexcept
|
||||
return weakOwnerEquals(this->weak, other.weak);
|
||||
}
|
||||
|
||||
api::ConnectionHandle ChannelRef::on_display_name_changed(
|
||||
ThisPluginState state, sol::main_protected_function pfn)
|
||||
{
|
||||
auto *plugin = state.plugin();
|
||||
return plugin->connections.managedConnect(
|
||||
this->strong()->displayNameChanged,
|
||||
plugin->createCallback(std::move(pfn)));
|
||||
}
|
||||
|
||||
std::optional<ChannelRef> ChannelRef::get_by_name(const QString &name)
|
||||
{
|
||||
auto chan = getApp()->getTwitch()->getChannelOrEmpty(name);
|
||||
@@ -292,6 +303,8 @@ void ChannelRef::createUserType(sol::table &c2)
|
||||
"has_messages", &ChannelRef::has_messages,
|
||||
"count_messages", &ChannelRef::count_messages,
|
||||
|
||||
"on_display_name_changed", &ChannelRef::on_display_name_changed,
|
||||
|
||||
// TwitchChannel
|
||||
"get_room_modes", &ChannelRef::get_room_modes,
|
||||
"get_stream_status", &ChannelRef::get_stream_status,
|
||||
|
||||
@@ -9,9 +9,15 @@
|
||||
|
||||
# include <sol/forward.hpp>
|
||||
|
||||
namespace chatterino::lua {
|
||||
class ThisPluginState;
|
||||
} // namespace chatterino::lua
|
||||
|
||||
namespace chatterino::lua::api {
|
||||
// NOLINTBEGIN(readability-identifier-naming)
|
||||
|
||||
struct ConnectionHandle;
|
||||
|
||||
/**
|
||||
* @includefile providers/twitch/TwitchChannel.hpp
|
||||
*/
|
||||
@@ -241,6 +247,16 @@ public:
|
||||
|
||||
bool operator==(const ChannelRef &other) const noexcept;
|
||||
|
||||
/**
|
||||
* Callback when the channel display name changes.
|
||||
*
|
||||
* @lua@param cb fun()
|
||||
* @lua@return c2.ConnectionHandle hdl
|
||||
* @exposed c2.Channel:on_display_name_changed
|
||||
*/
|
||||
ConnectionHandle on_display_name_changed(ThisPluginState state,
|
||||
sol::main_protected_function pfn);
|
||||
|
||||
/**
|
||||
* Static functions
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
#include "controllers/plugins/api/ConnectionHandle.hpp"
|
||||
|
||||
#ifdef CHATTERINO_HAVE_PLUGINS
|
||||
|
||||
# include <sol/sol.hpp>
|
||||
|
||||
namespace chatterino::lua::api {
|
||||
|
||||
void ConnectionHandle::createUserType(sol::table &c2)
|
||||
{
|
||||
c2.new_usertype<ConnectionHandle>(
|
||||
"ConnectionHandle", sol::no_constructor, //
|
||||
"disconnect",
|
||||
[](ConnectionHandle &hdl) {
|
||||
auto locked = hdl.connection.lock();
|
||||
if (locked)
|
||||
{
|
||||
*locked = pajlada::Signals::ScopedConnection{};
|
||||
}
|
||||
},
|
||||
"block",
|
||||
[](ConnectionHandle &hdl) {
|
||||
if (auto locked = hdl.connection.lock())
|
||||
{
|
||||
locked->block();
|
||||
}
|
||||
},
|
||||
"unblock",
|
||||
[](ConnectionHandle &hdl) {
|
||||
if (auto locked = hdl.connection.lock())
|
||||
{
|
||||
locked->unblock();
|
||||
}
|
||||
},
|
||||
"is_blocked",
|
||||
[](ConnectionHandle &hdl) {
|
||||
if (auto locked = hdl.connection.lock())
|
||||
{
|
||||
return locked->isBlocked();
|
||||
}
|
||||
return false;
|
||||
},
|
||||
"is_connected",
|
||||
[](ConnectionHandle &hdl) {
|
||||
if (auto locked = hdl.connection.lock())
|
||||
{
|
||||
return locked->isConnected();
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace chatterino::lua::api
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,48 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef CHATTERINO_HAVE_PLUGINS
|
||||
|
||||
# include <pajlada/signals/scoped-connection.hpp>
|
||||
# include <sol/forward.hpp>
|
||||
|
||||
# include <memory>
|
||||
|
||||
namespace chatterino::lua::api {
|
||||
|
||||
/* @lua-fragment
|
||||
|
||||
---@class c2.ConnectionHandle
|
||||
---This type represents a handle to a registration of a callback for an event handler.
|
||||
---Conceptually, the event has a _connection_ to the callback/handler.
|
||||
---This handle can be used to modify that connection.
|
||||
---It does not automatically disconnect the connection when it's destroyed (in `__gc`) -
|
||||
---`disconnect()` has to be called manually.
|
||||
c2.ConnectionHandle = {}
|
||||
|
||||
---Disconnect the signal
|
||||
function c2.ConnectionHandle:disconnect() end
|
||||
|
||||
---Block events on this connection
|
||||
function c2.ConnectionHandle:block() end
|
||||
|
||||
---Unblock events on this connection
|
||||
function c2.ConnectionHandle:unblock() end
|
||||
|
||||
---Is this connection currently blocked?
|
||||
---@return boolean is_blocked
|
||||
function c2.ConnectionHandle:is_blocked() end
|
||||
|
||||
---Is this connection still connected?
|
||||
---@return boolean is_connected
|
||||
function c2.ConnectionHandle:is_connected() end
|
||||
|
||||
*/
|
||||
struct ConnectionHandle {
|
||||
std::weak_ptr<pajlada::Signals::ScopedConnection> connection;
|
||||
|
||||
static void createUserType(sol::table &c2);
|
||||
};
|
||||
|
||||
} // namespace chatterino::lua::api
|
||||
|
||||
#endif
|
||||
@@ -1506,6 +1506,56 @@ TEST_F(PluginTest, LuaVersion)
|
||||
static_assert(LUA_VERSION_NUM >= 504);
|
||||
}
|
||||
|
||||
TEST_F(PluginTest, ChannelOnDisplayNameChanged)
|
||||
{
|
||||
this->configure();
|
||||
|
||||
bool gotEvent = false;
|
||||
this->lua->set_function("on_test_event", [&] {
|
||||
gotEvent = true;
|
||||
});
|
||||
|
||||
auto chan = std::make_shared<MockChannel>("mock");
|
||||
this->lua->set("chan", lua::api::ChannelRef(chan));
|
||||
sol::protected_function init = this->lua->script(R"lua(
|
||||
hdl = nil
|
||||
return function(chan)
|
||||
hdl = chan:on_display_name_changed(on_test_event)
|
||||
end
|
||||
)lua");
|
||||
|
||||
ASSERT_TRUE(init(lua::api::ChannelRef(chan)).valid());
|
||||
|
||||
ASSERT_TRUE(this->lua->script("assert(hdl ~= nil)").valid());
|
||||
|
||||
// regular delivery
|
||||
chan->displayNameChanged.invoke();
|
||||
ASSERT_TRUE(gotEvent);
|
||||
|
||||
// blocked connection
|
||||
ASSERT_TRUE(this->lua->script("hdl:block()").valid());
|
||||
ASSERT_TRUE(this->lua->script("assert(hdl:is_blocked())").valid());
|
||||
|
||||
gotEvent = false;
|
||||
chan->displayNameChanged.invoke();
|
||||
ASSERT_FALSE(gotEvent);
|
||||
|
||||
// unblocked connection
|
||||
ASSERT_TRUE(this->lua->script("hdl:unblock()").valid());
|
||||
ASSERT_TRUE(this->lua->script("assert(not hdl:is_blocked())").valid());
|
||||
|
||||
gotEvent = false;
|
||||
chan->displayNameChanged.invoke();
|
||||
ASSERT_TRUE(gotEvent);
|
||||
|
||||
// disconnect
|
||||
ASSERT_TRUE(this->lua->script("hdl:disconnect()").valid());
|
||||
ASSERT_TRUE(this->lua->script("assert(not hdl:is_connected())").valid());
|
||||
|
||||
gotEvent = false;
|
||||
ASSERT_FALSE(gotEvent);
|
||||
}
|
||||
|
||||
class PluginMessageConstructionTest
|
||||
: public PluginTest,
|
||||
public ::testing::WithParamInterface<QString>
|
||||
|
||||
Reference in New Issue
Block a user