feat(plugins): minimal account API (#6554)

Co-authored-by: Mm2PL <mm2pl+gh@kotmisia.pl>
Reviewed-by: Mm2PL <mm2pl+gh@kotmisia.pl>
This commit is contained in:
nerix
2025-12-05 15:56:51 +01:00
committed by GitHub
parent c4ed716c33
commit c67953d14f
10 changed files with 266 additions and 0 deletions
+1
View File
@@ -12,6 +12,7 @@
- Minor: Consolidate font picking into one dialog. (#6531)
- Minor: Added a menu action to sort tabs alphabetically. (#6551)
- Minor: Fixed "edit hotkey" dialog opening like a normal window. (#6540)
- Minor: Added an API to get the current Twitch account in plugins. (#6554)
- Minor: Added a setting to show the stream title in live messages. (#6572)
- Minor: Added broadcaster-only `/poll`, `/cancelpoll`, and `/endpoll` commands. (#6583, #6605)
- Minor: Added broadcaster-only `/prediction` command to start a prediction. (#6583)
+10
View File
@@ -376,6 +376,16 @@ declare namespace c2 {
Original,
Repost,
}
class TwitchAccount implements IWeakResource {
is_valid(): boolean;
user_login(): string;
user_id(): string;
color(): string;
is_anon(): boolean;
}
function current_account(): TwitchAccount;
}
declare module "chatterino.json" {
+32
View File
@@ -54,6 +54,38 @@ c2.ChannelType = {
-- End src/common/Channel.hpp
-- Begin src/controllers/plugins/api/Accounts.hpp
---@class c2.TwitchAccount
c2.TwitchAccount = {}
--- Returns true if the account this object points to is valid.
--- If the object expired, returns false
---
---@return boolean success
function c2.TwitchAccount:is_valid() end
---@return string user_login The (login) name of the account
function c2.TwitchAccount:login() end
---@return string user_id The Twitch user ID of the account
function c2.TwitchAccount:id() end
---@return string? color Color in chat of this account. `nil` if not yet known
function c2.TwitchAccount:color() end
---@return boolean is_anon `true` if this account is an anonymous account (no associated Twitch user)
function c2.TwitchAccount:is_anon() end
---@return string str
function c2.TwitchAccount:__tostring() end
---Gets the currently logged in Twitch account. This account might be an anonymous one (see `is_anon`).
---@return c2.TwitchAccount account
function c2.current_account() end
-- End src/controllers/plugins/api/Accounts.hpp
-- Begin src/controllers/plugins/api/ChannelRef.hpp
-- Begin src/providers/twitch/TwitchChannel.hpp
+45
View File
@@ -219,6 +219,51 @@ c2.register_callback(
)
```
#### `current_account()`
Returns a `TwitchAccount` representing the current account.
#### `TwitchAccount`
Represents a Twitch account. There can only be one selected at a time but
unselected accounts stay valid. An account can become invalid if removed by the
user in the settings. Using an invalid account produces an error.
```lua
local acc = c2.current_account()
print(acc:is_valid()) -- true unless user removed account
```
##### `TwitchAccount:login()`
Returns the login name of the account. This string may only contain lowercase ASCII.
```lua
print(acc:login()) -- "mm2pl"
```
##### `TwitchAccount:id()`
Returns the Twitch user ID of the account. This uniquely and persistently identifies the account.
```lua
print(acc:id()) -- "117691339"
```
##### `TwitchAccount:color()`
Returns the color in chat of this account. If the user has not sent any
messages this will be `nil`.
```lua
print(acc:color()) -- "#ffdaa520"
```
##### `TwitchAccount:is_anon()`
Returns `true` if this account is an anonymous account (no associated Twitch user).
#### `ChannelType` enum
This table describes channel types Chatterino supports. The values behind the
+2
View File
@@ -250,6 +250,8 @@ set(SOURCE_FILES
controllers/pings/MutedChannelModel.hpp
controllers/plugins/api/Accounts.cpp
controllers/plugins/api/Accounts.hpp
controllers/plugins/api/ChannelRef.cpp
controllers/plugins/api/ChannelRef.hpp
controllers/plugins/api/EventType.hpp
+1
View File
@@ -82,6 +82,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/HTTPResponse.hpp
* @includefile controllers/plugins/api/HTTPRequest.hpp
@@ -7,6 +7,7 @@
# include "common/QLogging.hpp"
# include "controllers/commands/CommandContext.hpp"
# include "controllers/commands/CommandController.hpp"
# include "controllers/plugins/api/Accounts.hpp"
# include "controllers/plugins/api/ChannelRef.hpp"
# include "controllers/plugins/api/HTTPRequest.hpp"
# include "controllers/plugins/api/HTTPResponse.hpp"
@@ -232,6 +233,7 @@ void PluginController::initSol(sol::state_view &lua, Plugin *plugin)
lua::api::HTTPRequest::createUserType(c2);
lua::api::WebSocket::createUserType(c2, plugin);
lua::api::message::createUserType(c2);
lua::api::createAccounts(c2);
c2["ChannelType"] = lua::createEnumTable<Channel::Type>(lua);
c2["HTTPMethod"] = lua::createEnumTable<NetworkRequestType>(lua);
c2["EventType"] = lua::createEnumTable<lua::api::EventType>(lua);
+97
View File
@@ -0,0 +1,97 @@
#include "controllers/plugins/api/Accounts.hpp"
#include "Application.hpp"
#include "controllers/accounts/AccountController.hpp"
#include "controllers/plugins/SolTypes.hpp" // IWYU pragma: keep
#include "providers/twitch/TwitchAccount.hpp"
#include "util/WeakPtrHelpers.hpp"
#include <sol/sol.hpp>
#include <optional>
namespace {
using namespace chatterino;
class WeakTwitchAccount
{
public:
WeakTwitchAccount(std::weak_ptr<TwitchAccount> account)
: weak(std::move(account))
{
}
QString login() const
{
return this->shared()->getUserName();
}
QString id() const
{
return this->shared()->getUserId();
}
std::optional<QString> color() const
{
auto c = this->shared()->color();
if (c.isValid())
{
return c.name(QColor::HexArgb);
}
return std::nullopt;
}
bool isAnon() const
{
return this->shared()->isAnon();
}
bool isValid() const
{
return !this->weak.expired();
}
bool operator==(const WeakTwitchAccount &other) const
{
return weakOwnerEquals(this->weak, other.weak);
}
private:
std::shared_ptr<TwitchAccount> shared() const
{
auto shared = this->weak.lock();
if (!shared)
{
throw std::runtime_error("Expired account");
}
return shared;
}
std::weak_ptr<TwitchAccount> weak;
};
} // namespace
namespace chatterino::lua::api {
void createAccounts(sol::table &c2)
{
c2.new_usertype<WeakTwitchAccount>(
// clang-format off
"TwitchAccount", sol::no_constructor,
sol::meta_function::to_string, &WeakTwitchAccount::login,
"is_valid", &WeakTwitchAccount::isValid,
"login", &WeakTwitchAccount::login,
"id", &WeakTwitchAccount::id,
"color", &WeakTwitchAccount::color,
"is_anon", &WeakTwitchAccount::isAnon
// clang-format on
);
c2.set_function("current_account", [] {
return WeakTwitchAccount(getApp()->getAccounts()->twitch.getCurrent());
});
}
} // namespace chatterino::lua::api
+42
View File
@@ -0,0 +1,42 @@
#pragma once
#ifdef CHATTERINO_HAVE_PLUGINS
# include <sol/forward.hpp>
namespace chatterino::lua::api {
/* @lua-fragment
---@class c2.TwitchAccount
c2.TwitchAccount = {}
--- Returns true if the account this object points to is valid.
--- If the object expired, returns false
---
---@return boolean success
function c2.TwitchAccount:is_valid() end
---@return string user_login The (login) name of the account
function c2.TwitchAccount:login() end
---@return string user_id The Twitch user ID of the account
function c2.TwitchAccount:id() end
---@return string? color Color in chat of this account. `nil` if not yet known
function c2.TwitchAccount:color() end
---@return boolean is_anon `true` if this account is an anonymous account (no associated Twitch user)
function c2.TwitchAccount:is_anon() end
---@return string str
function c2.TwitchAccount:__tostring() end
---Gets the currently logged in Twitch account. This account might be an anonymous one (see `is_anon`).
---@return c2.TwitchAccount account
function c2.current_account() end
*/
void createAccounts(sol::table &c2);
} // namespace chatterino::lua::api
#endif
+34
View File
@@ -1,7 +1,9 @@
#include "mocks/Helix.hpp"
#ifdef CHATTERINO_HAVE_PLUGINS
# include "Application.hpp"
# include "common/Channel.hpp"
# include "common/network/NetworkCommon.hpp"
# include "controllers/accounts/AccountController.hpp"
# include "controllers/commands/Command.hpp" // IWYU pragma: keep
# include "controllers/commands/CommandController.hpp"
# include "controllers/plugins/api/ChannelRef.hpp"
@@ -43,6 +45,15 @@ const QString TEST_SETTINGS = R"(
"enabledPlugins": [
"test"
]
},
"accounts": {
"uid117166826": {
"username": "testaccount_420",
"userID": "117166826",
"clientID": "abc",
"oauthToken": "def"
},
"current": "testaccount_420"
}
}
)";
@@ -107,11 +118,18 @@ public:
return &this->logging;
}
AccountController *getAccounts() override
{
return &this->accounts;
}
PluginController plugins;
mock::Logging logging;
CommandController commands;
mock::EmoteController emotes;
MockTwitch twitch;
AccountController accounts;
mock::Helix helix;
};
QDir luaTestBaseDir(const QString &category)
@@ -200,6 +218,8 @@ protected:
this->channel = app->twitch.mm2pl;
this->rawpl->dataDirectory().mkpath(".");
initializeHelix(&this->app->helix);
this->app->accounts.load();
}
void TearDown() override
@@ -1124,4 +1144,18 @@ TEST(PluginMessageConstructionTest, Integrity)
ASSERT_FALSE(UPDATE_SNAPSHOTS); // make sure fixtures are actually tested
}
TEST_F(PluginTest, testAccounts)
{
configure();
auto res = lua->script(R"lua(
local current = c2.current_account()
assert(current:login() == "testaccount_420")
assert(current:id() == "117166826")
assert(current:color() == nil) -- unset
assert(not current:is_anon())
)lua");
ASSERT_TRUE(res.valid()) << res.get<sol::error>().what();
}
#endif