Allow attaching tags with extra info to stats rows.

Stats responses are incredibly non-standard and no clients render
them correctly. This makes using /STATS a massive pain in the ass
for users.

However, now we have message tags we have a way to fix this. We can
send a <trailing> with the message details and for clients (probably
bots) that need to parse the response we can include the specific
details in message tags enabled by the new inspircd.org/stats-tags
capability.

The average user will get this:

    :<server> 210 <nick> <stats-char> :<stats-message>

If however they enable the newcapability they will get this instead:

    @inspircd.org/stats-foo=bar;inspircd.org/stats-baz=bax :<server> 210 <nick> <stats-char> :<stats-message>
This commit is contained in:
Sadie Powell 2022-11-30 04:08:30 +00:00
parent bfd4c5d44e
commit 5dd3629f12
2 changed files with 67 additions and 7 deletions

View File

@ -57,6 +57,24 @@ public:
: Numeric(num)
{
}
/** Attaches a stats tag to the response.
* @param stats The stats request that is being responded to.
* @param name The name of the stats tag. Will be prefixed with `inspircd.org/stats-`.
* @param value The value of the stats tag. Will be escaped before attaching.
*/
inline Row& AddTag(Stats::Context& stats, const std::string& name, const std::string& value);
/** Attaches multiple tags to the response.
* @param stats The stats request that is being responded to.
* @param tags An list of tags to attach. See AddTag for how this will be processed.
*/
inline Row& AddTags(Stats::Context& stats, std::initializer_list<std::pair<std::string, std::string>>&& tags)
{
for (const auto& [name, value] : tags)
AddTag(stats, name, value);
return *this;
}
};
class Stats::Context final
@ -65,6 +83,9 @@ class Stats::Context final
*/
User* const source;
/** The provider for inspircd.org/stats-* tags. */
ClientProtocol::MessageTagProvider& tagprov;
/** List of reply rows
*/
std::vector<Row> rows;
@ -78,12 +99,16 @@ public:
* @param src Source user of the STATS request, can be a local or remote user
* @param sym Symbol (letter) indicating the type of the request
*/
Context(User* src, char sym)
Context(ClientProtocol::MessageTagProvider& prov, User* src, char sym)
: source(src)
, tagprov(prov)
, symbol(sym)
{
}
/** Retrieves the provider of inspircd.org/stats-* tags. */
auto& GetTagProvider() const { return tagprov; }
/** Get the source user of the STATS request
* @return Source user of the STATS request
*/
@ -102,25 +127,35 @@ public:
/** Add a row to the reply list
* @param row Reply to add
*/
void AddRow(const Row& row) { rows.push_back(row); }
Row& AddRow(const Row& row)
{
rows.push_back(row);
return rows.back();
}
template <typename... Param>
void AddRow(unsigned int numeric, Param&&... p)
Row& AddRow(unsigned int numeric, Param&&... p)
{
Row n(numeric);
n.push(std::forward<Param>(p)...);
AddRow(n);
return AddRow(n);
}
/** Adds a row to the stats response using a generic numeric.
* @param p One or more fields to add to the response.
*/
template <typename... Param>
void AddGenericRow(Param&&... p)
Row& AddGenericRow(Param&&... p)
{
Row n(RPL_STATS);
n.push(GetSymbol());
n.push(std::forward<Param>(p)...);
AddRow(n);
return AddRow(n);
}
};
inline Stats::Row& Stats::Row::AddTag(Stats::Context& stats, const std::string& name, const std::string& value)
{
Numeric::Numeric::AddTag("inspircd.org/stats-" + name, &stats.GetTagProvider(), ClientProtocol::Message::EscapeTag(value));
return *this;
}

View File

@ -28,6 +28,7 @@
#include "inspircd.h"
#include "xline.h"
#include "modules/cap.h"
#include "modules/stats.h"
#ifdef _WIN32
@ -36,10 +37,33 @@
# include <sys/resource.h>
#endif
class StatsTagsProvider
: public ClientProtocol::MessageTagProvider
{
private:
Cap::Capability statscap;
public:
StatsTagsProvider(Module* mod)
: ClientProtocol::MessageTagProvider(mod)
, statscap(mod, "inspircd.org/stats-tags")
{
}
bool ShouldSendTag(LocalUser* user, const ClientProtocol::MessageTagData& tagdata) override
{
return statscap.IsEnabled(user);
}
};
class CommandStats final
: public Command
{
private:
Events::ModuleEventProvider statsevprov;
StatsTagsProvider statstags;
void DoStats(Stats::Context& stats);
public:
@ -49,6 +73,7 @@ public:
CommandStats(Module* Creator)
: Command(Creator, "STATS", 1, 2)
, statsevprov(Creator, "event/stats")
, statstags(Creator)
{
syntax = { "<symbol> [<servername>]" };
}
@ -333,7 +358,7 @@ CmdResult CommandStats::Handle(User* user, const Params& parameters)
return CmdResult::SUCCESS;
}
Stats::Context stats(user, parameters[0][0]);
Stats::Context stats(statstags, user, parameters[0][0]);
DoStats(stats);
for (const auto& row : stats.GetRows())