diff options
author | Sadie Powell <sadie@witchery.services> | 2025-04-19 22:31:49 +0100 |
---|---|---|
committer | Sadie Powell <sadie@witchery.services> | 2025-04-19 22:31:49 +0100 |
commit | c8b38197670965c18df603a5a4329f6f583b77d7 (patch) | |
tree | 6522f9517cd23e93debd7524a1f989ee6575a5d6 | |
parent | e6a1982922c2b8031c14381491a435e4c87b537c (diff) |
Add the anope.account and anope.listAccounts RPC events.
-rw-r--r-- | data/modules.example.conf | 1 | ||||
-rw-r--r-- | docs/RPC/jsonrpc.js | 24 | ||||
-rw-r--r-- | docs/RPC/rpc_data.md | 125 | ||||
-rw-r--r-- | modules/rpc/rpc_data.cpp | 176 |
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) |