summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSadie Powell <sadie@witchery.services>2025-04-19 22:31:49 +0100
committerSadie Powell <sadie@witchery.services>2025-04-19 22:31:49 +0100
commitc8b38197670965c18df603a5a4329f6f583b77d7 (patch)
tree6522f9517cd23e93debd7524a1f989ee6575a5d6
parente6a1982922c2b8031c14381491a435e4c87b537c (diff)
Add the anope.account and anope.listAccounts RPC events.
-rw-r--r--data/modules.example.conf1
-rw-r--r--docs/RPC/jsonrpc.js24
-rw-r--r--docs/RPC/rpc_data.md125
-rw-r--r--modules/rpc/rpc_data.cpp176
4 files changed, 324 insertions, 2 deletions
diff --git a/data/modules.example.conf b/data/modules.example.conf
index 58c167c86..a017e54fe 100644
--- a/data/modules.example.conf
+++ b/data/modules.example.conf
@@ -843,6 +843,7 @@ module
*
* Adds support for the following RPC methods:
*
+ * anope.listAccounts anope.account
* anope.listChannels anope.channel
* anope.listOpers anope.oper
* anope.listServers anope.server
diff --git a/docs/RPC/jsonrpc.js b/docs/RPC/jsonrpc.js
index 357cbc844..8f15a4ee8 100644
--- a/docs/RPC/jsonrpc.js
+++ b/docs/RPC/jsonrpc.js
@@ -45,6 +45,30 @@ class AnopeRPC {
}
/**
+ * Retrieves a list of accounts.
+ *
+ * Requires the rpc_data module to be loaded.
+ *
+ * @param {string} The level of detail to request.
+ * @returns {array} An array of account names.
+ */
+ listAccounts(detail = "name") {
+ return this.run("anope.listAccounts", detail);
+ }
+
+ /**
+ * Retrieves information about the specified account.
+ *
+ * Requires the rpc_data module to be loaded.
+ *
+ * @param {string} name The name of the account.
+ * @returns {object} An object containing information about the account.
+ */
+ account(name) {
+ return this.run("anope.account", name);
+ }
+
+ /**
* Retrieves a list of channels.
*
* Requires the rpc_data module to be loaded.
diff --git a/docs/RPC/rpc_data.md b/docs/RPC/rpc_data.md
index 7bb8d18c2..03bc9fa0b 100644
--- a/docs/RPC/rpc_data.md
+++ b/docs/RPC/rpc_data.md
@@ -1,5 +1,130 @@
# Anope `rpc_data` RPC interface
+## `anope.listAccounts`
+
+Lists all accounts that exist on the network.
+
+### Parameters
+
+Index | Description
+----- | -----------
+0 | If specified then the level of detail to retrieve. Can be set to "full" to retrieve all information or "name" to just retrieve the account names. Defaults to "name".
+
+### Errors
+
+Code | Description
+------ | -----------
+-32099 | The specified detail level does not exist.
+
+### Result
+
+If the detail level is not specified or is "name" then an array of account names.
+
+If the detail level is "full" then a mapping of account names to information about the account. See `anope.account` for more information on the data structure.
+
+#### Example
+
+```json
+["account1", "account2", "account3"]
+```
+
+## `anope.account`
+
+Retrieves information about the specified account.
+
+### Parameters
+
+Index | Description
+----- | -----------
+0 | A nickname belonging to the account.
+
+### Errors
+
+Code | Description
+------ | -----------
+-32099 | The specified account does not exist.
+
+### Result
+
+Returns a map containing information about the account.
+
+Key | Type | Description
+--- | ---- | -----------
+display | string | The display nickname of the account.
+email | string or null | The email address associated with the account or null if one has not been specified.
+extensions | map | A key-value map of the extensions set on this account by modules.
+language | string or null | The language associated with the account or null if the account uses the default language.
+lastmail | int | The time at which an email was last sent for this account.
+nicks | map | Information about nicknames that belong to the account keyed by the nickname.
+nicks.\*.extensions | map | A key-value map of the extensions set on this nickname by modules.
+nicks.\*.lastseen | int | The time at which this nickname was last used.
+nicks.\*.registered | int | The time at which this nickname was registered.
+nicks.\*.vhost | map or null | The vhost associated with the account or null if the user has no vhost.
+nicks.\*.vhost.created | int | The time at which the vhost was created.
+nicks.\*.vhost.creator | string | The nickname of the creator of the vhost.
+nicks.\*.vhost.host | string | The host segment of the vhost.
+nicks.\*.vhost.ident | string or null | The ident segment of the vhost or null if there is not one.
+nicks.\*.vhost.mask | string | The user@host or host mask of the vhost.
+opertype | map or null | The oper type associated with the account or null if they are not a services operator.
+opertype.commands | array[string] | The commands that the services operator type can use.
+opertype.name | string | The name of the services operator type.
+opertype.privileges | array[string] | The privileges that the services operator type has.
+registered | int | The time at which the account was registered.
+uniqueid | uint | The unique immutable identifier of the account.
+users | array[string] | The IRC users who are currently logged in to this account.
+
+#### Example
+
+```json
+{
+ "display": "foo",
+ "email": "example@example.com",
+ "extensions": {
+ "AUTOOP": true,
+ "HIDE_EMAIL": true,
+ "HIDE_MASK": true,
+ "MEMO_RECEIVE": true,
+ "MEMO_SIGNON": true,
+ "NS_PRIVATE": true,
+ "PROTECT": true,
+ "PROTECT_AFTER": 69
+ },
+ "language": null,
+ "lastmail": 1745071858,
+ "nicks": {
+ "foo": {
+ "extensions": {
+ "NS_NO_EXPIRE": true
+ },
+ "lastseen": 1745074153,
+ "registered": 1745071857,
+ "vhost": null
+ },
+ "bar": {
+ "extensions": {},
+ "lastseen": 1745072602,
+ "registered": 1745071857,
+ "vhost": {
+ "created": 1745072653,
+ "creator": "foo",
+ "host": "bar.baz",
+ "ident": "foo",
+ "mask": "foo@bar.baz"
+ }
+ }
+ },
+ "opertype": {
+ "commands": ["hostserv/*", "operserv/session"],
+ "name": "Helper",
+ "privileges": ["chanserv/no-register-limit"]
+ },
+ "registered": 1745071857,
+ "uniqueid": 11085415958920757000,
+ "users": [
+ "foo"
+ ]
+}
+```
## `anope.listChannels`
Lists all channels that exist on the network.
diff --git a/modules/rpc/rpc_data.cpp b/modules/rpc/rpc_data.cpp
index 5f7a14e63..2edb4ea11 100644
--- a/modules/rpc/rpc_data.cpp
+++ b/modules/rpc/rpc_data.cpp
@@ -11,13 +11,180 @@
enum
{
- // Used by anope.channel, anope.oper, anope.server, and anope.user
+ // Used by anope.{account,channel,oper,server,user}.
ERR_NO_SUCH_TARGET = RPC::ERR_CUSTOM_START,
- // Used by anope.listChannels, anope.listOpers, anope.listServers, and anope.listUsers
+ // Used by anope.list{Accounts,Channels,Opers,Servers,Users}.
ERR_NO_SUCH_DETAIL = RPC::ERR_CUSTOM_START,
};
+class SaveData final
+ : public Serialize::Data
+{
+public:
+ Anope::map<std::stringstream> data;
+
+ std::iostream &operator[](const Anope::string &key) override
+ {
+ return data[key];
+ }
+
+ static void Serialize(const Extensible *e, const Serializable *s, RPC::Map &map)
+ {
+ SaveData data;
+ Extensible::ExtensibleSerialize(e, s, data);
+ for (const auto &[k, v] : data.data)
+ {
+ auto vs = v.str();
+ switch (data.GetType(k))
+ {
+ case Serialize::DataType::BOOL:
+ map.Reply(k, Anope::Convert<bool>(vs, false));
+ break;
+ case Serialize::DataType::FLOAT:
+ map.Reply(k, Anope::Convert<double>(vs, 0.0));
+ break;
+ case Serialize::DataType::INT:
+ map.Reply(k, Anope::Convert<int64_t>(vs, 0));
+ break;
+ case Serialize::DataType::TEXT:
+ {
+ if (vs.empty())
+ map.Reply(k, nullptr);
+ else
+ map.Reply(k, vs);
+ break;
+ }
+ case Serialize::DataType::UINT:
+ map.Reply(k, Anope::Convert<uint64_t>(vs, 0));
+ break;
+ }
+ }
+ }
+};
+
+class AnopeListAccountsRPCEvent final
+ : public RPC::Event
+{
+public:
+ AnopeListAccountsRPCEvent(Module *o)
+ : RPC::Event(o, "anope.listAccounts")
+ {
+ }
+
+ static void GetInfo(NickCore *nc, RPC::Map &root)
+ {
+ root
+ .Reply("display", nc->display)
+ .Reply("lastmail", nc->lastmail)
+ .Reply("registered", nc->time_registered)
+ .Reply("uniqueid", nc->GetId());
+
+ if (nc->email.empty())
+ root.Reply("email", nullptr);
+ else
+ root.Reply("email", nc->email);
+
+ SaveData::Serialize(nc, nc, root.ReplyMap("extensions"));
+
+ if (nc->language.empty())
+ root.Reply("language", nullptr);
+ else
+ root.Reply("language", nc->language);
+
+ auto &nicks = root.ReplyMap("nicks");
+ for (const auto *na : *nc->aliases)
+ {
+ auto &nick = nicks.ReplyMap(na->nick);
+ nick.Reply("lastseen", na->last_seen)
+ .Reply("registered", nc->time_registered);
+
+ SaveData::Serialize(na, na, nick.ReplyMap("extensions"));
+ if (na->HasVHost())
+ {
+ auto &vhost = nick.ReplyMap("vhost");
+ vhost
+ .Reply("created", na->GetVHostCreated())
+ .Reply("creator", na->GetVHostCreator())
+ .Reply("host", na->GetVHostHost())
+ .Reply("mask", na->GetVHostMask());
+
+ if (na->GetVHostIdent().empty())
+ vhost.Reply("ident", nullptr);
+ else
+ vhost.Reply("ident", na->GetVHostIdent());
+ }
+ else
+ nick.Reply("vhost", nullptr);
+ }
+
+ if (nc->o)
+ {
+ auto &opertype = root.ReplyMap("opertype");
+ opertype.Reply("name", nc->o->ot->GetName());
+
+ auto &commands = opertype.ReplyArray("commands");
+ for (const auto &command : nc->o->ot->GetCommands())
+ commands.Reply(command);
+
+ auto &privileges = opertype.ReplyArray("privileges");
+ for (const auto &privilege : nc->o->ot->GetPrivs())
+ privileges.Reply(privilege);
+ }
+ else
+ root.Reply("opertype", nullptr);
+
+ auto &users = root.ReplyArray("users");
+ for (const auto *u : nc->users)
+ users.Reply(u->nick);
+ }
+
+ bool Run(RPC::ServiceInterface *iface, HTTPClient *client, RPC::Request &request) override
+ {
+ const auto detail = request.data.empty() ? "name" : request.data[0];
+ if (detail.equals_ci("name"))
+ {
+ auto &root = request.Root<RPC::Array>();
+ for (auto &[_, nc] : *NickCoreList)
+ root.Reply(nc->display);
+ }
+ else if (detail.equals_ci("full"))
+ {
+ auto &root = request.Root<RPC::Map>();
+ for (auto &[_, nc] : *NickCoreList)
+ GetInfo(nc, root.ReplyMap(nc->display));
+ }
+ else
+ {
+ request.Error(ERR_NO_SUCH_DETAIL, "No such detail level");
+ }
+ return true;
+ }
+};
+
+class AnopeAccountRPCEvent final
+ : public RPC::Event
+{
+public:
+ AnopeAccountRPCEvent(Module *o)
+ : RPC::Event(o, "anope.account", 1)
+ {
+ }
+
+ bool Run(RPC::ServiceInterface *iface, HTTPClient *client, RPC::Request &request) override
+ {
+ auto *na = NickAlias::Find(request.data[0]);
+ if (!na)
+ {
+ request.Error(ERR_NO_SUCH_TARGET, "No such account");
+ return true;
+ }
+
+ AnopeListAccountsRPCEvent::GetInfo(na->nc, request.Root());
+ return true;
+ }
+};
+
class AnopeListChannelsRPCEvent final
: public RPC::Event
{
@@ -435,6 +602,9 @@ class ModuleRPCData final
: public Module
{
private:
+ AnopeListAccountsRPCEvent anopelistaccountsrpcevent;
+ AnopeAccountRPCEvent anopeaccountrpcevent;
+
AnopeListChannelsRPCEvent anopelistchannelsrpcevent;
AnopeChannelRPCEvent anopechannelrpcevent;
@@ -450,6 +620,8 @@ private:
public:
ModuleRPCData(const Anope::string &modname, const Anope::string &creator)
: Module(modname, creator, EXTRA | VENDOR)
+ , anopelistaccountsrpcevent(this)
+ , anopeaccountrpcevent(this)
, anopelistchannelsrpcevent(this)
, anopechannelrpcevent(this)
, anopelistopersrpcevent(this)