Page MenuHomePhorge

No OneTemporary

Size
281 KB
Referenced Files
None
Subscribers
None
This file is larger than 256 KB, so syntax highlighting was skipped.
diff --git a/gtad/operation.hpp.mustache b/gtad/operation.hpp.mustache
index d89eeb7..9f36fe3 100644
--- a/gtad/operation.hpp.mustache
+++ b/gtad/operation.hpp.mustache
@@ -1,181 +1,180 @@
{{>preamble}}
#pragma once
#include "basejob.hpp"
{{#imports}}
#include {{_}}{{/imports}}
namespace Kazv {
{{#operations.operation}}
/*!{{>docCommentSummary}}{{#description}}
* {{_}}{{/description}}
*/
class {{camelCaseOperationId}}Job : public BaseJob {
public:
{{#models}}
// Inner data structures
{{#model}}
{{>docCommentShort}}
struct {{name}}{{#parents?}} :
{{#parents}}{{name}}{{>cjoin}}{{/parents}}{{/parents?}}
{
{{#vars}}
{{>docCommentShort}}
{{>maybeOmittableType}} {{nameCamelCase}};
{{/vars}}
{{#propertyMap}}
{{>docCommentShort}}
{{>maybeOmittableType}} {{nameCamelCase}};
{{/propertyMap}}
};
{{/model}}
{{/models}}
{{#responses}}
class JobResponse : public Response
{
public:
JobResponse(Response r);
bool success() const;
{{#normalResponse?}}{{#allProperties?}}
// Result properties
{{#headers}}
{{>nonInlineResponseSignature}}
{
auto it = header->find("{{baseName}}");
if (it != header->end()) {
return it->second;
} else {
return std::nullopt;
}
}
{{/headers}}
{{#inlineResponse}}
{{>docCommentShort}}
{{dataType.name}} {{paramName}}() const
{
return
{{#producesNonJson?}}
std::get<Bytes>(body)
{{/producesNonJson?}}
{{^producesNonJson?}}
std::move(jsonBody().get()).get<{{dataType.name}}>()
{{/producesNonJson?}}
;
}
{{/inlineResponse}}
{{#properties}}
{{!there's nothing in #properties if the response is inline}}
{{>nonInlineResponseSignature}};
{{/properties}}
{{/allProperties?}}{{/normalResponse?}}
};
{{/responses}}
static constexpr auto needsAuth() {
return {{^skipAuth}}true{{/skipAuth}}
{{#skipAuth}}false{{/skipAuth}};
}
// Construction/destruction
{{#allParams?}}
/*!{{>docCommentSummary}}
{{#allParams}}
* \param {{nameCamelCase}}{{#description}}
* {{_}}{{/description}}{{#_join}}
* {{/_join}}
{{/allParams}}
*/
{{/allParams?}}{{^allParams?}}
{{#summary}}
/// {{summary}}
{{/summary}}
{{/allParams?}}
explicit {{camelCaseOperationId}}Job(std::string serverUrl
{{^skipAuth}}, std::string _accessToken{{/skipAuth}}
{{#allParams?}},{{/allParams?}}
{{#allParams}}{{>joinedParamDecl}}{{/allParams}});
{{^hasBody?}}
{{/hasBody?}}
static BaseJob::Query buildQuery(
{{#queryParams}}{{>joinedParamDef}}{{/queryParams}});
static BaseJob::Body buildBody({{#allParams}}{{>joinedParamDef}}{{/allParams}});
{{#headerParams?}}
static std::map<std::string, std::string> buildHeader({{#headerParams}}{{>joinedParamDef}}{{/headerParams}});
{{/headerParams?}}
{{#producesNonJson?}}
static const immer::array<std::string> expectedContentTypes;
{{/producesNonJson?}}
{{camelCaseOperationId}}Job withData(JsonWrap j) &&;
{{camelCaseOperationId}}Job withData(JsonWrap j) const &;
};
{{#responses}}
using {{camelCaseOperationId}}Response = {{camelCaseOperationId}}Job::JobResponse;
{{/responses}}
} {{! namespace Kazv}}
namespace nlohmann
{
using namespace Kazv;
{{#models.model}}
template<>
struct adl_serializer<{{qualifiedName}}> {
-{{#in?}}
+
static void to_json(json& jo, const {{qualifiedName}} &pod)
{
if (! jo.is_object()) { jo = json::object(); }
{{#parents}}{{!assume no more one parent}}
jo = static_cast<const {{name}} &>(pod);
//nlohmann::to_json(jo, static_cast<const {{name}} &>(pod));
{{/parents}}
{{#propertyMap}}
addPropertyMapToJson(jo, pod.{{nameCamelCase}});
{{/propertyMap}}
{{#vars}}
{{#required?}}jo["{{baseName}}"s] = pod.{{nameCamelCase}};{{/required?}}
{{^required?}}addToJsonIfNeeded(jo, "{{baseName}}"s, pod.{{nameCamelCase}});{{/required?}}
{{/vars}}
}
-{{/in?}}
-{{#out?}}
+
static void from_json(const json &jo, {{qualifiedName}}& result)
{
{{#parents}}
static_cast<{{name}} &{{!of the parent!}}>(result) = jo;
//nlohmann::from_json(jo, static_cast<const {{name}} &{{!of the parent!}}>(result));
{{/parents}}
{{#vars}}
if (jo.contains("{{baseName}}"s)) {
result.{{nameCamelCase}} = jo.at("{{baseName}}"s);
}
{{/vars}}
{{#propertyMap}}
result.{{nameCamelCase}} = jo;
{{/propertyMap}}
}
-{{/out?}}
+
};
{{/models.model}}
}
namespace Kazv
{
{{/operations.operation}}
} // namespace Kazv
diff --git a/src/api/csapi/admin.hpp b/src/api/csapi/admin.hpp
index 8bc1ca9..adfb688 100644
--- a/src/api/csapi/admin.hpp
+++ b/src/api/csapi/admin.hpp
@@ -1,169 +1,203 @@
/******************************************************************************
* THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN
*/
#pragma once
#include "basejob.hpp"
namespace Kazv {
/*! \brief Gets information about a particular user.
*
* Gets information about a particular user.
*
* This API may be restricted to only be called by the user being looked
* up, or by a server admin. Server-local administrator privileges are not
* specified in this document.
*/
class GetWhoIsJob : public BaseJob {
public:
// Inner data structures
/// Gets information about a particular user.
///
/// This API may be restricted to only be called by the user being looked
/// up, or by a server admin. Server-local administrator privileges are not
/// specified in this document.
struct ConnectionInfo
{
/// Most recently seen IP address of the session.
std::optional<std::string> ip;
/// Unix timestamp that the session was last active.
std::optional<std::int_fast64_t> lastSeen;
/// User agent string last seen in the session.
std::optional<std::string> userAgent;
};
/// Gets information about a particular user.
///
/// This API may be restricted to only be called by the user being looked
/// up, or by a server admin. Server-local administrator privileges are not
/// specified in this document.
struct SessionInfo
{
/// Information particular connections in the session.
immer::array<ConnectionInfo> connections;
};
/// Gets information about a particular user.
///
/// This API may be restricted to only be called by the user being looked
/// up, or by a server admin. Server-local administrator privileges are not
/// specified in this document.
struct DeviceInfo
{
/// A user's sessions (i.e. what they did with an access token from one login).
immer::array<SessionInfo> sessions;
};
class JobResponse : public Response
{
public:
JobResponse(Response r);
bool success() const;
// Result properties
/// The Matrix user ID of the user.
std::optional<std::string> userId() const;
/// Each key is an identifier for one of the user's devices.
immer::map<std::string, DeviceInfo> devices() const;
};
static constexpr auto needsAuth() {
return true
;
}
// Construction/destruction
/*! \brief Gets information about a particular user.
*
* \param userId
* The user to look up.
*/
explicit GetWhoIsJob(std::string serverUrl
, std::string _accessToken
,
std::string userId );
static BaseJob::Query buildQuery(
);
static BaseJob::Body buildBody(std::string userId);
GetWhoIsJob withData(JsonWrap j) &&;
GetWhoIsJob withData(JsonWrap j) const &;
};
using GetWhoIsResponse = GetWhoIsJob::JobResponse;
}
namespace nlohmann
{
using namespace Kazv;
template<>
struct adl_serializer<GetWhoIsJob::ConnectionInfo> {
+ static void to_json(json& jo, const GetWhoIsJob::ConnectionInfo &pod)
+ {
+ if (! jo.is_object()) { jo = json::object(); }
+
+
+
+ addToJsonIfNeeded(jo, "ip"s, pod.ip);
+
+ addToJsonIfNeeded(jo, "last_seen"s, pod.lastSeen);
+
+ addToJsonIfNeeded(jo, "user_agent"s, pod.userAgent);
+ }
+
static void from_json(const json &jo, GetWhoIsJob::ConnectionInfo& result)
{
if (jo.contains("ip"s)) {
result.ip = jo.at("ip"s);
}
if (jo.contains("last_seen"s)) {
result.lastSeen = jo.at("last_seen"s);
}
if (jo.contains("user_agent"s)) {
result.userAgent = jo.at("user_agent"s);
}
}
+
};
template<>
struct adl_serializer<GetWhoIsJob::SessionInfo> {
+ static void to_json(json& jo, const GetWhoIsJob::SessionInfo &pod)
+ {
+ if (! jo.is_object()) { jo = json::object(); }
+
+
+
+ addToJsonIfNeeded(jo, "connections"s, pod.connections);
+ }
+
static void from_json(const json &jo, GetWhoIsJob::SessionInfo& result)
{
if (jo.contains("connections"s)) {
result.connections = jo.at("connections"s);
}
}
+
};
template<>
struct adl_serializer<GetWhoIsJob::DeviceInfo> {
+ static void to_json(json& jo, const GetWhoIsJob::DeviceInfo &pod)
+ {
+ if (! jo.is_object()) { jo = json::object(); }
+
+
+
+ addToJsonIfNeeded(jo, "sessions"s, pod.sessions);
+ }
+
static void from_json(const json &jo, GetWhoIsJob::DeviceInfo& result)
{
if (jo.contains("sessions"s)) {
result.sessions = jo.at("sessions"s);
}
}
+
};
}
namespace Kazv
{
} // namespace Kazv
diff --git a/src/api/csapi/administrative_contact.hpp b/src/api/csapi/administrative_contact.hpp
index 333a13a..80b1f4f 100644
--- a/src/api/csapi/administrative_contact.hpp
+++ b/src/api/csapi/administrative_contact.hpp
@@ -1,724 +1,759 @@
/******************************************************************************
* THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN
*/
#pragma once
#include "basejob.hpp"
#include "csapi/definitions/request_msisdn_validation.hpp"
#include "csapi/definitions/request_token_response.hpp"
#include "csapi/definitions/auth_data.hpp"
#include "csapi/definitions/request_email_validation.hpp"
namespace Kazv {
/*! \brief Gets a list of a user's third party identifiers.
*
* Gets a list of the third party identifiers that the homeserver has
* associated with the user's account.
*
* This is *not* the same as the list of third party identifiers bound to
* the user's Matrix ID in identity servers.
*
* Identifiers in this list may be used by the homeserver as, for example,
* identifiers that it will accept to reset the user's account password.
*/
class GetAccount3PIDsJob : public BaseJob {
public:
// Inner data structures
/// Gets a list of the third party identifiers that the homeserver has
/// associated with the user's account.
///
/// This is *not* the same as the list of third party identifiers bound to
/// the user's Matrix ID in identity servers.
///
/// Identifiers in this list may be used by the homeserver as, for example,
/// identifiers that it will accept to reset the user's account password.
struct ThirdPartyIdentifier
{
/// The medium of the third party identifier.
std::string medium;
/// The third party identifier address.
std::string address;
/// The timestamp, in milliseconds, when the identifier was
/// validated by the identity server.
std::int_fast64_t validatedAt;
/// The timestamp, in milliseconds, when the homeserver associated the third party identifier with the user.
std::int_fast64_t addedAt;
};
class JobResponse : public Response
{
public:
JobResponse(Response r);
bool success() const;
// Result properties
/// Gets a list of the third party identifiers that the homeserver has
/// associated with the user's account.
///
/// This is *not* the same as the list of third party identifiers bound to
/// the user's Matrix ID in identity servers.
///
/// Identifiers in this list may be used by the homeserver as, for example,
/// identifiers that it will accept to reset the user's account password.
immer::array<ThirdPartyIdentifier> threepids() const;
};
static constexpr auto needsAuth() {
return true
;
}
// Construction/destruction
/// Gets a list of a user's third party identifiers.
explicit GetAccount3PIDsJob(std::string serverUrl
, std::string _accessToken
);
static BaseJob::Query buildQuery(
);
static BaseJob::Body buildBody();
GetAccount3PIDsJob withData(JsonWrap j) &&;
GetAccount3PIDsJob withData(JsonWrap j) const &;
};
using GetAccount3PIDsResponse = GetAccount3PIDsJob::JobResponse;
}
namespace nlohmann
{
using namespace Kazv;
template<>
struct adl_serializer<GetAccount3PIDsJob::ThirdPartyIdentifier> {
+ static void to_json(json& jo, const GetAccount3PIDsJob::ThirdPartyIdentifier &pod)
+ {
+ if (! jo.is_object()) { jo = json::object(); }
+
+
+ jo["medium"s] = pod.medium;
+
+ jo["address"s] = pod.address;
+
+ jo["validated_at"s] = pod.validatedAt;
+
+ jo["added_at"s] = pod.addedAt;
+
+ }
+
static void from_json(const json &jo, GetAccount3PIDsJob::ThirdPartyIdentifier& result)
{
if (jo.contains("medium"s)) {
result.medium = jo.at("medium"s);
}
if (jo.contains("address"s)) {
result.address = jo.at("address"s);
}
if (jo.contains("validated_at"s)) {
result.validatedAt = jo.at("validated_at"s);
}
if (jo.contains("added_at"s)) {
result.addedAt = jo.at("added_at"s);
}
}
+
};
}
namespace Kazv
{
/*! \brief Adds contact information to the user's account.
*
* Adds contact information to the user's account.
*
* This endpoint is deprecated in favour of the more specific ``/3pid/add``
* and ``/3pid/bind`` endpoints.
*
* .. Note::
* Previously this endpoint supported a ``bind`` parameter. This parameter
* has been removed, making this endpoint behave as though it was ``false``.
* This results in this endpoint being an equivalent to ``/3pid/bind`` rather
* than dual-purpose.
*/
class Post3PIDsJob : public BaseJob {
public:
// Inner data structures
/// The third party credentials to associate with the account.
struct ThreePidCredentials
{
/// The client secret used in the session with the identity server.
std::string clientSecret;
/// The identity server to use.
std::string idServer;
/// An access token previously registered with the identity server. Servers
/// can treat this as optional to distinguish between r0.5-compatible clients
/// and this specification version.
std::string idAccessToken;
/// The session identifier given by the identity server.
std::string sid;
};
class JobResponse : public Response
{
public:
JobResponse(Response r);
bool success() const;
};
static constexpr auto needsAuth() {
return true
;
}
// Construction/destruction
/*! \brief Adds contact information to the user's account.
*
* \param threePidCreds
* The third party credentials to associate with the account.
*/
explicit Post3PIDsJob(std::string serverUrl
, std::string _accessToken
,
ThreePidCredentials threePidCreds );
static BaseJob::Query buildQuery(
);
static BaseJob::Body buildBody(ThreePidCredentials threePidCreds);
Post3PIDsJob withData(JsonWrap j) &&;
Post3PIDsJob withData(JsonWrap j) const &;
};
using Post3PIDsResponse = Post3PIDsJob::JobResponse;
}
namespace nlohmann
{
using namespace Kazv;
template<>
struct adl_serializer<Post3PIDsJob::ThreePidCredentials> {
+
static void to_json(json& jo, const Post3PIDsJob::ThreePidCredentials &pod)
{
if (! jo.is_object()) { jo = json::object(); }
jo["client_secret"s] = pod.clientSecret;
jo["id_server"s] = pod.idServer;
jo["id_access_token"s] = pod.idAccessToken;
jo["sid"s] = pod.sid;
}
+ static void from_json(const json &jo, Post3PIDsJob::ThreePidCredentials& result)
+ {
+
+ if (jo.contains("client_secret"s)) {
+ result.clientSecret = jo.at("client_secret"s);
+ }
+ if (jo.contains("id_server"s)) {
+ result.idServer = jo.at("id_server"s);
+ }
+ if (jo.contains("id_access_token"s)) {
+ result.idAccessToken = jo.at("id_access_token"s);
+ }
+ if (jo.contains("sid"s)) {
+ result.sid = jo.at("sid"s);
+ }
+
+ }
+
};
}
namespace Kazv
{
/*! \brief Adds contact information to the user's account.
*
* This API endpoint uses the `User-Interactive Authentication API`_.
*
* Adds contact information to the user's account. Homeservers should use 3PIDs added
* through this endpoint for password resets instead of relying on the identity server.
*
* Homeservers should prevent the caller from adding a 3PID to their account if it has
* already been added to another user's account on the homeserver.
*/
class Add3PIDJob : public BaseJob {
public:
class JobResponse : public Response
{
public:
JobResponse(Response r);
bool success() const;
};
static constexpr auto needsAuth() {
return true
;
}
// Construction/destruction
/*! \brief Adds contact information to the user's account.
*
* \param clientSecret
* The client secret used in the session with the homeserver.
*
* \param sid
* The session identifier given by the homeserver.
*
* \param auth
* Additional authentication information for the
* user-interactive authentication API.
*/
explicit Add3PIDJob(std::string serverUrl
, std::string _accessToken
,
std::string clientSecret , std::string sid , std::optional<AuthenticationData> auth = std::nullopt);
static BaseJob::Query buildQuery(
);
static BaseJob::Body buildBody(std::string clientSecret, std::string sid, std::optional<AuthenticationData> auth);
Add3PIDJob withData(JsonWrap j) &&;
Add3PIDJob withData(JsonWrap j) const &;
};
using Add3PIDResponse = Add3PIDJob::JobResponse;
}
namespace nlohmann
{
using namespace Kazv;
}
namespace Kazv
{
/*! \brief Binds a 3PID to the user's account through an Identity Service.
*
* Binds a 3PID to the user's account through the specified identity server.
*
* Homeservers should not prevent this request from succeeding if another user
* has bound the 3PID. Homeservers should simply proxy any errors received by
* the identity server to the caller.
*
* Homeservers should track successful binds so they can be unbound later.
*/
class Bind3PIDJob : public BaseJob {
public:
class JobResponse : public Response
{
public:
JobResponse(Response r);
bool success() const;
};
static constexpr auto needsAuth() {
return true
;
}
// Construction/destruction
/*! \brief Binds a 3PID to the user's account through an Identity Service.
*
* \param clientSecret
* The client secret used in the session with the identity server.
*
* \param idServer
* The identity server to use.
*
* \param idAccessToken
* An access token previously registered with the identity server.
*
* \param sid
* The session identifier given by the identity server.
*/
explicit Bind3PIDJob(std::string serverUrl
, std::string _accessToken
,
std::string clientSecret , std::string idServer , std::string idAccessToken , std::string sid );
static BaseJob::Query buildQuery(
);
static BaseJob::Body buildBody(std::string clientSecret, std::string idServer, std::string idAccessToken, std::string sid);
Bind3PIDJob withData(JsonWrap j) &&;
Bind3PIDJob withData(JsonWrap j) const &;
};
using Bind3PIDResponse = Bind3PIDJob::JobResponse;
}
namespace nlohmann
{
using namespace Kazv;
}
namespace Kazv
{
/*! \brief Deletes a third party identifier from the user's account
*
* Removes a third party identifier from the user's account. This might not
* cause an unbind of the identifier from the identity server.
*
* Unlike other endpoints, this endpoint does not take an ``id_access_token``
* parameter because the homeserver is expected to sign the request to the
* identity server instead.
*/
class Delete3pidFromAccountJob : public BaseJob {
public:
class JobResponse : public Response
{
public:
JobResponse(Response r);
bool success() const;
// Result properties
/// An indicator as to whether or not the homeserver was able to unbind
/// the 3PID from the identity server. ``success`` indicates that the
/// indentity server has unbound the identifier whereas ``no-support``
/// indicates that the identity server refuses to support the request
/// or the homeserver was not able to determine an identity server to
/// unbind from.
std::string idServerUnbindResult() const;
};
static constexpr auto needsAuth() {
return true
;
}
// Construction/destruction
/*! \brief Deletes a third party identifier from the user's account
*
* \param medium
* The medium of the third party identifier being removed.
*
* \param address
* The third party address being removed.
*
* \param idServer
* The identity server to unbind from. If not provided, the homeserver
* MUST use the ``id_server`` the identifier was added through. If the
* homeserver does not know the original ``id_server``, it MUST return
* a ``id_server_unbind_result`` of ``no-support``.
*/
explicit Delete3pidFromAccountJob(std::string serverUrl
, std::string _accessToken
,
std::string medium , std::string address , std::optional<std::string> idServer = std::nullopt);
static BaseJob::Query buildQuery(
);
static BaseJob::Body buildBody(std::string medium, std::string address, std::optional<std::string> idServer);
Delete3pidFromAccountJob withData(JsonWrap j) &&;
Delete3pidFromAccountJob withData(JsonWrap j) const &;
};
using Delete3pidFromAccountResponse = Delete3pidFromAccountJob::JobResponse;
}
namespace nlohmann
{
using namespace Kazv;
}
namespace Kazv
{
/*! \brief Removes a user's third party identifier from an identity server.
*
* Removes a user's third party identifier from the provided identity server
* without removing it from the homeserver.
*
* Unlike other endpoints, this endpoint does not take an ``id_access_token``
* parameter because the homeserver is expected to sign the request to the
* identity server instead.
*/
class Unbind3pidFromAccountJob : public BaseJob {
public:
class JobResponse : public Response
{
public:
JobResponse(Response r);
bool success() const;
// Result properties
/// An indicator as to whether or not the identity server was able to unbind
/// the 3PID. ``success`` indicates that the identity server has unbound the
/// identifier whereas ``no-support`` indicates that the identity server
/// refuses to support the request or the homeserver was not able to determine
/// an identity server to unbind from.
std::string idServerUnbindResult() const;
};
static constexpr auto needsAuth() {
return true
;
}
// Construction/destruction
/*! \brief Removes a user's third party identifier from an identity server.
*
* \param medium
* The medium of the third party identifier being removed.
*
* \param address
* The third party address being removed.
*
* \param idServer
* The identity server to unbind from. If not provided, the homeserver
* MUST use the ``id_server`` the identifier was added through. If the
* homeserver does not know the original ``id_server``, it MUST return
* a ``id_server_unbind_result`` of ``no-support``.
*/
explicit Unbind3pidFromAccountJob(std::string serverUrl
, std::string _accessToken
,
std::string medium , std::string address , std::optional<std::string> idServer = std::nullopt);
static BaseJob::Query buildQuery(
);
static BaseJob::Body buildBody(std::string medium, std::string address, std::optional<std::string> idServer);
Unbind3pidFromAccountJob withData(JsonWrap j) &&;
Unbind3pidFromAccountJob withData(JsonWrap j) const &;
};
using Unbind3pidFromAccountResponse = Unbind3pidFromAccountJob::JobResponse;
}
namespace nlohmann
{
using namespace Kazv;
}
namespace Kazv
{
/*! \brief Begins the validation process for an email address for association with the user's account.
*
* The homeserver must check that the given email address is **not**
* already associated with an account on this homeserver. This API should
* be used to request validation tokens when adding an email address to an
* account. This API's parameters and response are identical to that of
* the |/register/email/requestToken|_ endpoint. The homeserver should validate
* the email itself, either by sending a validation email itself or by using
* a service it has control over.
*/
class RequestTokenTo3PIDEmailJob : public BaseJob {
public:
class JobResponse : public Response
{
public:
JobResponse(Response r);
bool success() const;
// Result properties
/// An email was sent to the given address. Note that this may be an
/// email containing the validation token or it may be informing the
/// user of an error.
RequestTokenResponse data() const
{
return
std::move(jsonBody().get()).get<RequestTokenResponse>()
;
}
};
static constexpr auto needsAuth() {
return
false;
}
// Construction/destruction
/*! \brief Begins the validation process for an email address for association with the user's account.
*
* \param body
* The homeserver must check that the given email address is **not**
* already associated with an account on this homeserver. This API should
* be used to request validation tokens when adding an email address to an
* account. This API's parameters and response are identical to that of
* the |/register/email/requestToken|_ endpoint. The homeserver should validate
* the email itself, either by sending a validation email itself or by using
* a service it has control over.
*/
explicit RequestTokenTo3PIDEmailJob(std::string serverUrl
,
EmailValidationData body );
static BaseJob::Query buildQuery(
);
static BaseJob::Body buildBody(EmailValidationData body);
RequestTokenTo3PIDEmailJob withData(JsonWrap j) &&;
RequestTokenTo3PIDEmailJob withData(JsonWrap j) const &;
};
using RequestTokenTo3PIDEmailResponse = RequestTokenTo3PIDEmailJob::JobResponse;
}
namespace nlohmann
{
using namespace Kazv;
}
namespace Kazv
{
/*! \brief Begins the validation process for a phone number for association with the user's account.
*
* The homeserver must check that the given phone number is **not**
* already associated with an account on this homeserver. This API should
* be used to request validation tokens when adding a phone number to an
* account. This API's parameters and response are identical to that of
* the |/register/msisdn/requestToken|_ endpoint. The homeserver should validate
* the phone number itself, either by sending a validation message itself or by using
* a service it has control over.
*/
class RequestTokenTo3PIDMSISDNJob : public BaseJob {
public:
class JobResponse : public Response
{
public:
JobResponse(Response r);
bool success() const;
// Result properties
/// An SMS message was sent to the given phone number.
RequestTokenResponse data() const
{
return
std::move(jsonBody().get()).get<RequestTokenResponse>()
;
}
};
static constexpr auto needsAuth() {
return
false;
}
// Construction/destruction
/*! \brief Begins the validation process for a phone number for association with the user's account.
*
* \param body
* The homeserver must check that the given phone number is **not**
* already associated with an account on this homeserver. This API should
* be used to request validation tokens when adding a phone number to an
* account. This API's parameters and response are identical to that of
* the |/register/msisdn/requestToken|_ endpoint. The homeserver should validate
* the phone number itself, either by sending a validation message itself or by using
* a service it has control over.
*/
explicit RequestTokenTo3PIDMSISDNJob(std::string serverUrl
,
MsisdnValidationData body );
static BaseJob::Query buildQuery(
);
static BaseJob::Body buildBody(MsisdnValidationData body);
RequestTokenTo3PIDMSISDNJob withData(JsonWrap j) &&;
RequestTokenTo3PIDMSISDNJob withData(JsonWrap j) const &;
};
using RequestTokenTo3PIDMSISDNResponse = RequestTokenTo3PIDMSISDNJob::JobResponse;
}
namespace nlohmann
{
using namespace Kazv;
}
namespace Kazv
{
} // namespace Kazv
diff --git a/src/api/csapi/capabilities.hpp b/src/api/csapi/capabilities.hpp
index bdb03c4..78a5d27 100644
--- a/src/api/csapi/capabilities.hpp
+++ b/src/api/csapi/capabilities.hpp
@@ -1,150 +1,184 @@
/******************************************************************************
* THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN
*/
#pragma once
#include "basejob.hpp"
namespace Kazv {
/*! \brief Gets information about the server's capabilities.
*
* Gets information about the server's supported feature set
* and other relevant capabilities.
*/
class GetCapabilitiesJob : public BaseJob {
public:
// Inner data structures
/// Capability to indicate if the user can change their password.
struct ChangePasswordCapability
{
/// True if the user can change their password, false otherwise.
bool enabled;
};
/// The room versions the server supports.
struct RoomVersionsCapability
{
/// The default room version the server is using for new rooms.
std::string defaultVersion;
/// A detailed description of the room versions the server supports.
immer::map<std::string, std::string> available;
};
/// The custom capabilities the server supports, using the
/// Java package naming convention.
struct Capabilities
{
/// Capability to indicate if the user can change their password.
std::optional<ChangePasswordCapability> changePassword;
/// The room versions the server supports.
std::optional<RoomVersionsCapability> roomVersions;
/// The custom capabilities the server supports, using the
/// Java package naming convention.
immer::map<std::string, JsonWrap> additionalProperties;
};
class JobResponse : public Response
{
public:
JobResponse(Response r);
bool success() const;
// Result properties
/// The custom capabilities the server supports, using the
/// Java package naming convention.
Capabilities capabilities() const;
};
static constexpr auto needsAuth() {
return true
;
}
// Construction/destruction
/// Gets information about the server's capabilities.
explicit GetCapabilitiesJob(std::string serverUrl
, std::string _accessToken
);
static BaseJob::Query buildQuery(
);
static BaseJob::Body buildBody();
GetCapabilitiesJob withData(JsonWrap j) &&;
GetCapabilitiesJob withData(JsonWrap j) const &;
};
using GetCapabilitiesResponse = GetCapabilitiesJob::JobResponse;
}
namespace nlohmann
{
using namespace Kazv;
template<>
struct adl_serializer<GetCapabilitiesJob::ChangePasswordCapability> {
+ static void to_json(json& jo, const GetCapabilitiesJob::ChangePasswordCapability &pod)
+ {
+ if (! jo.is_object()) { jo = json::object(); }
+
+
+ jo["enabled"s] = pod.enabled;
+
+ }
+
static void from_json(const json &jo, GetCapabilitiesJob::ChangePasswordCapability& result)
{
if (jo.contains("enabled"s)) {
result.enabled = jo.at("enabled"s);
}
}
+
};
template<>
struct adl_serializer<GetCapabilitiesJob::RoomVersionsCapability> {
+ static void to_json(json& jo, const GetCapabilitiesJob::RoomVersionsCapability &pod)
+ {
+ if (! jo.is_object()) { jo = json::object(); }
+
+
+ jo["default"s] = pod.defaultVersion;
+
+ jo["available"s] = pod.available;
+
+ }
+
static void from_json(const json &jo, GetCapabilitiesJob::RoomVersionsCapability& result)
{
if (jo.contains("default"s)) {
result.defaultVersion = jo.at("default"s);
}
if (jo.contains("available"s)) {
result.available = jo.at("available"s);
}
}
+
};
template<>
struct adl_serializer<GetCapabilitiesJob::Capabilities> {
+ static void to_json(json& jo, const GetCapabilitiesJob::Capabilities &pod)
+ {
+ if (! jo.is_object()) { jo = json::object(); }
+
+ addPropertyMapToJson(jo, pod.additionalProperties);
+
+ addToJsonIfNeeded(jo, "m.change_password"s, pod.changePassword);
+
+ addToJsonIfNeeded(jo, "m.room_versions"s, pod.roomVersions);
+ }
+
static void from_json(const json &jo, GetCapabilitiesJob::Capabilities& result)
{
if (jo.contains("m.change_password"s)) {
result.changePassword = jo.at("m.change_password"s);
}
if (jo.contains("m.room_versions"s)) {
result.roomVersions = jo.at("m.room_versions"s);
}
result.additionalProperties = jo;
}
+
};
}
namespace Kazv
{
} // namespace Kazv
diff --git a/src/api/csapi/create_room.hpp b/src/api/csapi/create_room.hpp
index 01e4942..86afc5b 100644
--- a/src/api/csapi/create_room.hpp
+++ b/src/api/csapi/create_room.hpp
@@ -1,336 +1,371 @@
/******************************************************************************
* THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN
*/
#pragma once
#include "basejob.hpp"
namespace Kazv {
/*! \brief Create a new room
*
* Create a new room with various configuration options.
*
* The server MUST apply the normal state resolution rules when creating
* the new room, including checking power levels for each event. It MUST
* apply the events implied by the request in the following order:
*
* 1. The ``m.room.create`` event itself. Must be the first event in the
* room.
*
* 2. An ``m.room.member`` event for the creator to join the room. This is
* needed so the remaining events can be sent.
*
* 3. A default ``m.room.power_levels`` event, giving the room creator
* (and not other members) permission to send state events. Overridden
* by the ``power_level_content_override`` parameter.
*
* 4. Events set by the ``preset``. Currently these are the ``m.room.join_rules``,
* ``m.room.history_visibility``, and ``m.room.guest_access`` state events.
*
* 5. Events listed in ``initial_state``, in the order that they are
* listed.
*
* 6. Events implied by ``name`` and ``topic`` (``m.room.name`` and ``m.room.topic``
* state events).
*
* 7. Invite events implied by ``invite`` and ``invite_3pid`` (``m.room.member`` with
* ``membership: invite`` and ``m.room.third_party_invite``).
*
* The available presets do the following with respect to room state:
*
* ======================== ============== ====================== ================ =========
* Preset ``join_rules`` ``history_visibility`` ``guest_access`` Other
* ======================== ============== ====================== ================ =========
* ``private_chat`` ``invite`` ``shared`` ``can_join``
* ``trusted_private_chat`` ``invite`` ``shared`` ``can_join`` All invitees are given the same power level as the room creator.
* ``public_chat`` ``public`` ``shared`` ``forbidden``
* ======================== ============== ====================== ================ =========
*
* The server will create a ``m.room.create`` event in the room with the
* requesting user as the creator, alongside other keys provided in the
* ``creation_content``.
*/
class CreateRoomJob : public BaseJob {
public:
// Inner data structures
/// Create a new room with various configuration options.
///
/// The server MUST apply the normal state resolution rules when creating
/// the new room, including checking power levels for each event. It MUST
/// apply the events implied by the request in the following order:
///
/// 1. The ``m.room.create`` event itself. Must be the first event in the
/// room.
///
/// 2. An ``m.room.member`` event for the creator to join the room. This is
/// needed so the remaining events can be sent.
///
/// 3. A default ``m.room.power_levels`` event, giving the room creator
/// (and not other members) permission to send state events. Overridden
/// by the ``power_level_content_override`` parameter.
///
/// 4. Events set by the ``preset``. Currently these are the ``m.room.join_rules``,
/// ``m.room.history_visibility``, and ``m.room.guest_access`` state events.
///
/// 5. Events listed in ``initial_state``, in the order that they are
/// listed.
///
/// 6. Events implied by ``name`` and ``topic`` (``m.room.name`` and ``m.room.topic``
/// state events).
///
/// 7. Invite events implied by ``invite`` and ``invite_3pid`` (``m.room.member`` with
/// ``membership: invite`` and ``m.room.third_party_invite``).
///
/// The available presets do the following with respect to room state:
///
/// ======================== ============== ====================== ================ =========
/// Preset ``join_rules`` ``history_visibility`` ``guest_access`` Other
/// ======================== ============== ====================== ================ =========
/// ``private_chat`` ``invite`` ``shared`` ``can_join``
/// ``trusted_private_chat`` ``invite`` ``shared`` ``can_join`` All invitees are given the same power level as the room creator.
/// ``public_chat`` ``public`` ``shared`` ``forbidden``
/// ======================== ============== ====================== ================ =========
///
/// The server will create a ``m.room.create`` event in the room with the
/// requesting user as the creator, alongside other keys provided in the
/// ``creation_content``.
struct Invite3pid
{
/// The hostname+port of the identity server which should be used for third party identifier lookups.
std::string idServer;
/// An access token previously registered with the identity server. Servers
/// can treat this as optional to distinguish between r0.5-compatible clients
/// and this specification version.
std::string idAccessToken;
/// The kind of address being passed in the address field, for example ``email``.
std::string medium;
/// The invitee's third party identifier.
std::string address;
};
/// Create a new room with various configuration options.
///
/// The server MUST apply the normal state resolution rules when creating
/// the new room, including checking power levels for each event. It MUST
/// apply the events implied by the request in the following order:
///
/// 1. The ``m.room.create`` event itself. Must be the first event in the
/// room.
///
/// 2. An ``m.room.member`` event for the creator to join the room. This is
/// needed so the remaining events can be sent.
///
/// 3. A default ``m.room.power_levels`` event, giving the room creator
/// (and not other members) permission to send state events. Overridden
/// by the ``power_level_content_override`` parameter.
///
/// 4. Events set by the ``preset``. Currently these are the ``m.room.join_rules``,
/// ``m.room.history_visibility``, and ``m.room.guest_access`` state events.
///
/// 5. Events listed in ``initial_state``, in the order that they are
/// listed.
///
/// 6. Events implied by ``name`` and ``topic`` (``m.room.name`` and ``m.room.topic``
/// state events).
///
/// 7. Invite events implied by ``invite`` and ``invite_3pid`` (``m.room.member`` with
/// ``membership: invite`` and ``m.room.third_party_invite``).
///
/// The available presets do the following with respect to room state:
///
/// ======================== ============== ====================== ================ =========
/// Preset ``join_rules`` ``history_visibility`` ``guest_access`` Other
/// ======================== ============== ====================== ================ =========
/// ``private_chat`` ``invite`` ``shared`` ``can_join``
/// ``trusted_private_chat`` ``invite`` ``shared`` ``can_join`` All invitees are given the same power level as the room creator.
/// ``public_chat`` ``public`` ``shared`` ``forbidden``
/// ======================== ============== ====================== ================ =========
///
/// The server will create a ``m.room.create`` event in the room with the
/// requesting user as the creator, alongside other keys provided in the
/// ``creation_content``.
struct StateEvent
{
/// The type of event to send.
std::string type;
/// The state_key of the state event. Defaults to an empty string.
std::optional<std::string> stateKey;
/// The content of the event.
JsonWrap content;
};
class JobResponse : public Response
{
public:
JobResponse(Response r);
bool success() const;
// Result properties
/// The created room's ID.
std::string roomId() const;
};
static constexpr auto needsAuth() {
return true
;
}
// Construction/destruction
/*! \brief Create a new room
*
* \param visibility
* A ``public`` visibility indicates that the room will be shown
* in the published room list. A ``private`` visibility will hide
* the room from the published room list. Rooms default to
* ``private`` visibility if this key is not included. NB: This
* should not be confused with ``join_rules`` which also uses the
* word ``public``.
*
* \param roomAliasName
* The desired room alias **local part**. If this is included, a
* room alias will be created and mapped to the newly created
* room. The alias will belong on the *same* homeserver which
* created the room. For example, if this was set to "foo" and
* sent to the homeserver "example.com" the complete room alias
* would be ``#foo:example.com``.
*
* The complete room alias will become the canonical alias for
* the room.
*
* \param name
* If this is included, an ``m.room.name`` event will be sent
* into the room to indicate the name of the room. See Room
* Events for more information on ``m.room.name``.
*
* \param topic
* If this is included, an ``m.room.topic`` event will be sent
* into the room to indicate the topic for the room. See Room
* Events for more information on ``m.room.topic``.
*
* \param invite
* A list of user IDs to invite to the room. This will tell the
* server to invite everyone in the list to the newly created room.
*
* \param invite3pid
* A list of objects representing third party IDs to invite into
* the room.
*
* \param roomVersion
* The room version to set for the room. If not provided, the homeserver is
* to use its configured default. If provided, the homeserver will return a
* 400 error with the errcode ``M_UNSUPPORTED_ROOM_VERSION`` if it does not
* support the room version.
*
* \param creationContent
* Extra keys, such as ``m.federate``, to be added to the content
* of the `m.room.create`_ event. The server will clobber the following
* keys: ``creator``, ``room_version``. Future versions of the specification
* may allow the server to clobber other keys.
*
* \param initialState
* A list of state events to set in the new room. This allows
* the user to override the default state events set in the new
* room. The expected format of the state events are an object
* with type, state_key and content keys set.
*
* Takes precedence over events set by ``preset``, but gets
* overriden by ``name`` and ``topic`` keys.
*
* \param preset
* Convenience parameter for setting various default state events
* based on a preset.
*
* If unspecified, the server should use the ``visibility`` to determine
* which preset to use. A visbility of ``public`` equates to a preset of
* ``public_chat`` and ``private`` visibility equates to a preset of
* ``private_chat``.
*
* \param isDirect
* This flag makes the server set the ``is_direct`` flag on the
* ``m.room.member`` events sent to the users in ``invite`` and
* ``invite_3pid``. See `Direct Messaging`_ for more information.
*
* \param powerLevelContentOverride
* The power level content to override in the default power level
* event. This object is applied on top of the generated `m.room.power_levels`_
* event content prior to it being sent to the room. Defaults to
* overriding nothing.
*/
explicit CreateRoomJob(std::string serverUrl
, std::string _accessToken
,
std::optional<std::string> visibility = std::nullopt, std::optional<std::string> roomAliasName = std::nullopt, std::optional<std::string> name = std::nullopt, std::optional<std::string> topic = std::nullopt, immer::array<std::string> invite = {}, immer::array<Invite3pid> invite3pid = {}, std::optional<std::string> roomVersion = std::nullopt, JsonWrap creationContent = {}, immer::array<StateEvent> initialState = {}, std::optional<std::string> preset = std::nullopt, std::optional<bool> isDirect = std::nullopt, JsonWrap powerLevelContentOverride = {});
static BaseJob::Query buildQuery(
);
static BaseJob::Body buildBody(std::optional<std::string> visibility, std::optional<std::string> roomAliasName, std::optional<std::string> name, std::optional<std::string> topic, immer::array<std::string> invite, immer::array<Invite3pid> invite3pid, std::optional<std::string> roomVersion, JsonWrap creationContent, immer::array<StateEvent> initialState, std::optional<std::string> preset, std::optional<bool> isDirect, JsonWrap powerLevelContentOverride);
CreateRoomJob withData(JsonWrap j) &&;
CreateRoomJob withData(JsonWrap j) const &;
};
using CreateRoomResponse = CreateRoomJob::JobResponse;
}
namespace nlohmann
{
using namespace Kazv;
template<>
struct adl_serializer<CreateRoomJob::Invite3pid> {
+
static void to_json(json& jo, const CreateRoomJob::Invite3pid &pod)
{
if (! jo.is_object()) { jo = json::object(); }
jo["id_server"s] = pod.idServer;
jo["id_access_token"s] = pod.idAccessToken;
jo["medium"s] = pod.medium;
jo["address"s] = pod.address;
}
+ static void from_json(const json &jo, CreateRoomJob::Invite3pid& result)
+ {
+
+ if (jo.contains("id_server"s)) {
+ result.idServer = jo.at("id_server"s);
+ }
+ if (jo.contains("id_access_token"s)) {
+ result.idAccessToken = jo.at("id_access_token"s);
+ }
+ if (jo.contains("medium"s)) {
+ result.medium = jo.at("medium"s);
+ }
+ if (jo.contains("address"s)) {
+ result.address = jo.at("address"s);
+ }
+
+ }
+
};
template<>
struct adl_serializer<CreateRoomJob::StateEvent> {
+
static void to_json(json& jo, const CreateRoomJob::StateEvent &pod)
{
if (! jo.is_object()) { jo = json::object(); }
jo["type"s] = pod.type;
addToJsonIfNeeded(jo, "state_key"s, pod.stateKey);
jo["content"s] = pod.content;
}
+ static void from_json(const json &jo, CreateRoomJob::StateEvent& result)
+ {
+
+ if (jo.contains("type"s)) {
+ result.type = jo.at("type"s);
+ }
+ if (jo.contains("state_key"s)) {
+ result.stateKey = jo.at("state_key"s);
+ }
+ if (jo.contains("content"s)) {
+ result.content = jo.at("content"s);
+ }
+
+ }
+
};
}
namespace Kazv
{
} // namespace Kazv
diff --git a/src/api/csapi/keys.hpp b/src/api/csapi/keys.hpp
index 3de3a3b..05d0567 100644
--- a/src/api/csapi/keys.hpp
+++ b/src/api/csapi/keys.hpp
@@ -1,406 +1,427 @@
/******************************************************************************
* THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN
*/
#pragma once
#include "basejob.hpp"
#include "csapi/definitions/device_keys.hpp"
namespace Kazv {
/*! \brief Upload end-to-end encryption keys.
*
* Publishes end-to-end encryption keys for the device.
*/
class UploadKeysJob : public BaseJob {
public:
class JobResponse : public Response
{
public:
JobResponse(Response r);
bool success() const;
// Result properties
/// For each key algorithm, the number of unclaimed one-time keys
/// of that type currently held on the server for this device.
immer::map<std::string, int> oneTimeKeyCounts() const;
};
static constexpr auto needsAuth() {
return true
;
}
// Construction/destruction
/*! \brief Upload end-to-end encryption keys.
*
* \param deviceKeys
* Identity keys for the device. May be absent if no new
* identity keys are required.
*
* \param oneTimeKeys
* One-time public keys for "pre-key" messages. The names of
* the properties should be in the format
* ``<algorithm>:<key_id>``. The format of the key is determined
* by the `key algorithm <#key-algorithms>`_.
*
* May be absent if no new one-time keys are required.
*/
explicit UploadKeysJob(std::string serverUrl
, std::string _accessToken
,
std::optional<DeviceKeys> deviceKeys = std::nullopt, immer::map<std::string, Variant> oneTimeKeys = {});
static BaseJob::Query buildQuery(
);
static BaseJob::Body buildBody(std::optional<DeviceKeys> deviceKeys, immer::map<std::string, Variant> oneTimeKeys);
UploadKeysJob withData(JsonWrap j) &&;
UploadKeysJob withData(JsonWrap j) const &;
};
using UploadKeysResponse = UploadKeysJob::JobResponse;
}
namespace nlohmann
{
using namespace Kazv;
}
namespace Kazv
{
/*! \brief Download device identity keys.
*
* Returns the current devices and identity keys for the given users.
*/
class QueryKeysJob : public BaseJob {
public:
// Inner data structures
/// Additional data added to the device key information
/// by intermediate servers, and not covered by the
/// signatures.
struct UnsignedDeviceInfo
{
/// The display name which the user set on the device.
std::optional<std::string> deviceDisplayName;
};
/// Returns the current devices and identity keys for the given users.
struct DeviceInformation :
DeviceKeys
{
/// Additional data added to the device key information
/// by intermediate servers, and not covered by the
/// signatures.
std::optional<UnsignedDeviceInfo> unsignedData;
};
class JobResponse : public Response
{
public:
JobResponse(Response r);
bool success() const;
// Result properties
/// If any remote homeservers could not be reached, they are
/// recorded here. The names of the properties are the names of
/// the unreachable servers.
///
/// If the homeserver could be reached, but the user or device
/// was unknown, no failure is recorded. Instead, the corresponding
/// user or device is missing from the ``device_keys`` result.
immer::map<std::string, JsonWrap> failures() const;
/// Information on the queried devices. A map from user ID, to a
/// map from device ID to device information. For each device,
/// the information returned will be the same as uploaded via
/// ``/keys/upload``, with the addition of an ``unsigned``
/// property.
immer::map<std::string, immer::map<std::string, DeviceInformation>> deviceKeys() const;
};
static constexpr auto needsAuth() {
return true
;
}
// Construction/destruction
/*! \brief Download device identity keys.
*
* \param deviceKeys
* The keys to be downloaded. A map from user ID, to a list of
* device IDs, or to an empty list to indicate all devices for the
* corresponding user.
*
* \param timeout
* The time (in milliseconds) to wait when downloading keys from
* remote servers. 10 seconds is the recommended default.
*
* \param token
* If the client is fetching keys as a result of a device update received
* in a sync request, this should be the 'since' token of that sync request,
* or any later sync token. This allows the server to ensure its response
* contains the keys advertised by the notification in that sync.
*/
explicit QueryKeysJob(std::string serverUrl
, std::string _accessToken
,
immer::map<std::string, immer::array<std::string>> deviceKeys , std::optional<int> timeout = std::nullopt, std::optional<std::string> token = std::nullopt);
static BaseJob::Query buildQuery(
);
static BaseJob::Body buildBody(immer::map<std::string, immer::array<std::string>> deviceKeys, std::optional<int> timeout, std::optional<std::string> token);
QueryKeysJob withData(JsonWrap j) &&;
QueryKeysJob withData(JsonWrap j) const &;
};
using QueryKeysResponse = QueryKeysJob::JobResponse;
}
namespace nlohmann
{
using namespace Kazv;
template<>
struct adl_serializer<QueryKeysJob::UnsignedDeviceInfo> {
+ static void to_json(json& jo, const QueryKeysJob::UnsignedDeviceInfo &pod)
+ {
+ if (! jo.is_object()) { jo = json::object(); }
+
+
+
+ addToJsonIfNeeded(jo, "device_display_name"s, pod.deviceDisplayName);
+ }
+
static void from_json(const json &jo, QueryKeysJob::UnsignedDeviceInfo& result)
{
if (jo.contains("device_display_name"s)) {
result.deviceDisplayName = jo.at("device_display_name"s);
}
}
+
};
template<>
struct adl_serializer<QueryKeysJob::DeviceInformation> {
+ static void to_json(json& jo, const QueryKeysJob::DeviceInformation &pod)
+ {
+ if (! jo.is_object()) { jo = json::object(); }
+ jo = static_cast<const DeviceKeys &>(pod);
+ //nlohmann::to_json(jo, static_cast<const DeviceKeys &>(pod));
+
+
+ addToJsonIfNeeded(jo, "unsigned"s, pod.unsignedData);
+ }
+
static void from_json(const json &jo, QueryKeysJob::DeviceInformation& result)
{
static_cast<DeviceKeys &>(result) = jo;
//nlohmann::from_json(jo, static_cast<const DeviceKeys &>(result));
if (jo.contains("unsigned"s)) {
result.unsignedData = jo.at("unsigned"s);
}
}
+
};
}
namespace Kazv
{
/*! \brief Claim one-time encryption keys.
*
* Claims one-time keys for use in pre-key messages.
*/
class ClaimKeysJob : public BaseJob {
public:
class JobResponse : public Response
{
public:
JobResponse(Response r);
bool success() const;
// Result properties
/// If any remote homeservers could not be reached, they are
/// recorded here. The names of the properties are the names of
/// the unreachable servers.
///
/// If the homeserver could be reached, but the user or device
/// was unknown, no failure is recorded. Instead, the corresponding
/// user or device is missing from the ``one_time_keys`` result.
immer::map<std::string, JsonWrap> failures() const;
/// One-time keys for the queried devices. A map from user ID, to a
/// map from devices to a map from ``<algorithm>:<key_id>`` to the key object.
///
/// See the `key algorithms <#key-algorithms>`_ section for information
/// on the Key Object format.
immer::map<std::string, immer::map<std::string, Variant>> oneTimeKeys() const;
};
static constexpr auto needsAuth() {
return true
;
}
// Construction/destruction
/*! \brief Claim one-time encryption keys.
*
* \param oneTimeKeys
* The keys to be claimed. A map from user ID, to a map from
* device ID to algorithm name.
*
* \param timeout
* The time (in milliseconds) to wait when downloading keys from
* remote servers. 10 seconds is the recommended default.
*/
explicit ClaimKeysJob(std::string serverUrl
, std::string _accessToken
,
immer::map<std::string, immer::map<std::string, std::string>> oneTimeKeys , std::optional<int> timeout = std::nullopt);
static BaseJob::Query buildQuery(
);
static BaseJob::Body buildBody(immer::map<std::string, immer::map<std::string, std::string>> oneTimeKeys, std::optional<int> timeout);
ClaimKeysJob withData(JsonWrap j) &&;
ClaimKeysJob withData(JsonWrap j) const &;
};
using ClaimKeysResponse = ClaimKeysJob::JobResponse;
}
namespace nlohmann
{
using namespace Kazv;
}
namespace Kazv
{
/*! \brief Query users with recent device key updates.
*
* Gets a list of users who have updated their device identity keys since a
* previous sync token.
*
* The server should include in the results any users who:
*
* * currently share a room with the calling user (ie, both users have
* membership state ``join``); *and*
* * added new device identity keys or removed an existing device with
* identity keys, between ``from`` and ``to``.
*/
class GetKeysChangesJob : public BaseJob {
public:
class JobResponse : public Response
{
public:
JobResponse(Response r);
bool success() const;
// Result properties
/// The Matrix User IDs of all users who updated their device
/// identity keys.
immer::array<std::string> changed() const;
/// The Matrix User IDs of all users who may have left all
/// the end-to-end encrypted rooms they previously shared
/// with the user.
immer::array<std::string> left() const;
};
static constexpr auto needsAuth() {
return true
;
}
// Construction/destruction
/*! \brief Query users with recent device key updates.
*
* \param from
* The desired start point of the list. Should be the ``next_batch`` field
* from a response to an earlier call to |/sync|. Users who have not
* uploaded new device identity keys since this point, nor deleted
* existing devices with identity keys since then, will be excluded
* from the results.
*
* \param to
* The desired end point of the list. Should be the ``next_batch``
* field from a recent call to |/sync| - typically the most recent
* such call. This may be used by the server as a hint to check its
* caches are up to date.
*/
explicit GetKeysChangesJob(std::string serverUrl
, std::string _accessToken
,
std::string from , std::string to );
static BaseJob::Query buildQuery(
std::string from, std::string to);
static BaseJob::Body buildBody(std::string from, std::string to);
GetKeysChangesJob withData(JsonWrap j) &&;
GetKeysChangesJob withData(JsonWrap j) const &;
};
using GetKeysChangesResponse = GetKeysChangesJob::JobResponse;
}
namespace nlohmann
{
using namespace Kazv;
}
namespace Kazv
{
} // namespace Kazv
diff --git a/src/api/csapi/list_public_rooms.hpp b/src/api/csapi/list_public_rooms.hpp
index d44d3b3..1156277 100644
--- a/src/api/csapi/list_public_rooms.hpp
+++ b/src/api/csapi/list_public_rooms.hpp
@@ -1,373 +1,383 @@
/******************************************************************************
* THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN
*/
#pragma once
#include "basejob.hpp"
#include "csapi/definitions/public_rooms_response.hpp"
namespace Kazv {
/*! \brief Gets the visibility of a room in the directory
*
* Gets the visibility of a given room on the server's public room directory.
*/
class GetRoomVisibilityOnDirectoryJob : public BaseJob {
public:
class JobResponse : public Response
{
public:
JobResponse(Response r);
bool success() const;
// Result properties
/// The visibility of the room in the directory.
std::optional<std::string> visibility() const;
};
static constexpr auto needsAuth() {
return
false;
}
// Construction/destruction
/*! \brief Gets the visibility of a room in the directory
*
* \param roomId
* The room ID.
*/
explicit GetRoomVisibilityOnDirectoryJob(std::string serverUrl
,
std::string roomId );
static BaseJob::Query buildQuery(
);
static BaseJob::Body buildBody(std::string roomId);
GetRoomVisibilityOnDirectoryJob withData(JsonWrap j) &&;
GetRoomVisibilityOnDirectoryJob withData(JsonWrap j) const &;
};
using GetRoomVisibilityOnDirectoryResponse = GetRoomVisibilityOnDirectoryJob::JobResponse;
}
namespace nlohmann
{
using namespace Kazv;
}
namespace Kazv
{
/*! \brief Sets the visibility of a room in the room directory
*
* Sets the visibility of a given room in the server's public room
* directory.
*
* Servers may choose to implement additional access control checks
* here, for instance that room visibility can only be changed by
* the room creator or a server administrator.
*/
class SetRoomVisibilityOnDirectoryJob : public BaseJob {
public:
class JobResponse : public Response
{
public:
JobResponse(Response r);
bool success() const;
};
static constexpr auto needsAuth() {
return true
;
}
// Construction/destruction
/*! \brief Sets the visibility of a room in the room directory
*
* \param roomId
* The room ID.
*
* \param visibility
* The new visibility setting for the room.
* Defaults to 'public'.
*/
explicit SetRoomVisibilityOnDirectoryJob(std::string serverUrl
, std::string _accessToken
,
std::string roomId , std::optional<std::string> visibility = std::nullopt);
static BaseJob::Query buildQuery(
);
static BaseJob::Body buildBody(std::string roomId, std::optional<std::string> visibility);
SetRoomVisibilityOnDirectoryJob withData(JsonWrap j) &&;
SetRoomVisibilityOnDirectoryJob withData(JsonWrap j) const &;
};
using SetRoomVisibilityOnDirectoryResponse = SetRoomVisibilityOnDirectoryJob::JobResponse;
}
namespace nlohmann
{
using namespace Kazv;
}
namespace Kazv
{
/*! \brief Lists the public rooms on the server.
*
* Lists the public rooms on the server.
*
* This API returns paginated responses. The rooms are ordered by the number
* of joined members, with the largest rooms first.
*/
class GetPublicRoomsJob : public BaseJob {
public:
class JobResponse : public Response
{
public:
JobResponse(Response r);
bool success() const;
// Result properties
/// A paginated chunk of public rooms.
immer::array<PublicRoomsChunk> chunk() const;
/// A pagination token for the response. The absence of this token
/// means there are no more results to fetch and the client should
/// stop paginating.
std::optional<std::string> nextBatch() const;
/// A pagination token that allows fetching previous results. The
/// absence of this token means there are no results before this
/// batch, i.e. this is the first batch.
std::optional<std::string> prevBatch() const;
/// An estimate on the total number of public rooms, if the
/// server has an estimate.
std::optional<int> totalRoomCountEstimate() const;
};
static constexpr auto needsAuth() {
return
false;
}
// Construction/destruction
/*! \brief Lists the public rooms on the server.
*
* \param limit
* Limit the number of results returned.
*
* \param since
* A pagination token from a previous request, allowing clients to
* get the next (or previous) batch of rooms.
* The direction of pagination is specified solely by which token
* is supplied, rather than via an explicit flag.
*
* \param server
* The server to fetch the public room lists from. Defaults to the
* local server.
*/
explicit GetPublicRoomsJob(std::string serverUrl
,
std::optional<int> limit = std::nullopt, std::optional<std::string> since = std::nullopt, std::optional<std::string> server = std::nullopt);
static BaseJob::Query buildQuery(
std::optional<int> limit, std::optional<std::string> since, std::optional<std::string> server);
static BaseJob::Body buildBody(std::optional<int> limit, std::optional<std::string> since, std::optional<std::string> server);
GetPublicRoomsJob withData(JsonWrap j) &&;
GetPublicRoomsJob withData(JsonWrap j) const &;
};
using GetPublicRoomsResponse = GetPublicRoomsJob::JobResponse;
}
namespace nlohmann
{
using namespace Kazv;
}
namespace Kazv
{
/*! \brief Lists the public rooms on the server with optional filter.
*
* Lists the public rooms on the server, with optional filter.
*
* This API returns paginated responses. The rooms are ordered by the number
* of joined members, with the largest rooms first.
*/
class QueryPublicRoomsJob : public BaseJob {
public:
// Inner data structures
/// Filter to apply to the results.
struct Filter
{
/// A string to search for in the room metadata, e.g. name,
/// topic, canonical alias etc. (Optional).
std::optional<std::string> genericSearchTerm;
};
class JobResponse : public Response
{
public:
JobResponse(Response r);
bool success() const;
// Result properties
/// A paginated chunk of public rooms.
immer::array<PublicRoomsChunk> chunk() const;
/// A pagination token for the response. The absence of this token
/// means there are no more results to fetch and the client should
/// stop paginating.
std::optional<std::string> nextBatch() const;
/// A pagination token that allows fetching previous results. The
/// absence of this token means there are no results before this
/// batch, i.e. this is the first batch.
std::optional<std::string> prevBatch() const;
/// An estimate on the total number of public rooms, if the
/// server has an estimate.
std::optional<int> totalRoomCountEstimate() const;
};
static constexpr auto needsAuth() {
return true
;
}
// Construction/destruction
/*! \brief Lists the public rooms on the server with optional filter.
*
* \param server
* The server to fetch the public room lists from. Defaults to the
* local server.
*
* \param limit
* Limit the number of results returned.
*
* \param since
* A pagination token from a previous request, allowing clients
* to get the next (or previous) batch of rooms. The direction
* of pagination is specified solely by which token is supplied,
* rather than via an explicit flag.
*
* \param filter
* Filter to apply to the results.
*
* \param includeAllNetworks
* Whether or not to include all known networks/protocols from
* application services on the homeserver. Defaults to false.
*
* \param thirdPartyInstanceId
* The specific third party network/protocol to request from the
* homeserver. Can only be used if ``include_all_networks`` is false.
*/
explicit QueryPublicRoomsJob(std::string serverUrl
, std::string _accessToken
,
std::optional<std::string> server = std::nullopt, std::optional<int> limit = std::nullopt, std::optional<std::string> since = std::nullopt, std::optional<Filter> filter = std::nullopt, std::optional<bool> includeAllNetworks = std::nullopt, std::optional<std::string> thirdPartyInstanceId = std::nullopt);
static BaseJob::Query buildQuery(
std::optional<std::string> server);
static BaseJob::Body buildBody(std::optional<std::string> server, std::optional<int> limit, std::optional<std::string> since, std::optional<Filter> filter, std::optional<bool> includeAllNetworks, std::optional<std::string> thirdPartyInstanceId);
QueryPublicRoomsJob withData(JsonWrap j) &&;
QueryPublicRoomsJob withData(JsonWrap j) const &;
};
using QueryPublicRoomsResponse = QueryPublicRoomsJob::JobResponse;
}
namespace nlohmann
{
using namespace Kazv;
template<>
struct adl_serializer<QueryPublicRoomsJob::Filter> {
+
static void to_json(json& jo, const QueryPublicRoomsJob::Filter &pod)
{
if (! jo.is_object()) { jo = json::object(); }
addToJsonIfNeeded(jo, "generic_search_term"s, pod.genericSearchTerm);
}
+ static void from_json(const json &jo, QueryPublicRoomsJob::Filter& result)
+ {
+
+ if (jo.contains("generic_search_term"s)) {
+ result.genericSearchTerm = jo.at("generic_search_term"s);
+ }
+
+ }
+
};
}
namespace Kazv
{
} // namespace Kazv
diff --git a/src/api/csapi/login.hpp b/src/api/csapi/login.hpp
index 862d3a2..6c4bba2 100644
--- a/src/api/csapi/login.hpp
+++ b/src/api/csapi/login.hpp
@@ -1,229 +1,239 @@
/******************************************************************************
* THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN
*/
#pragma once
#include "basejob.hpp"
#include "csapi/definitions/wellknown/full.hpp"
#include "csapi/definitions/user_identifier.hpp"
namespace Kazv {
/*! \brief Get the supported login types to authenticate users
*
* Gets the homeserver's supported login types to authenticate users. Clients
* should pick one of these and supply it as the ``type`` when logging in.
*/
class GetLoginFlowsJob : public BaseJob {
public:
// Inner data structures
/// Gets the homeserver's supported login types to authenticate users. Clients
/// should pick one of these and supply it as the ``type`` when logging in.
struct LoginFlow
{
/// The login type. This is supplied as the ``type`` when
/// logging in.
std::optional<std::string> type;
};
class JobResponse : public Response
{
public:
JobResponse(Response r);
bool success() const;
// Result properties
/// The homeserver's supported login types
immer::array<LoginFlow> flows() const;
};
static constexpr auto needsAuth() {
return
false;
}
// Construction/destruction
/// Get the supported login types to authenticate users
explicit GetLoginFlowsJob(std::string serverUrl
);
static BaseJob::Query buildQuery(
);
static BaseJob::Body buildBody();
GetLoginFlowsJob withData(JsonWrap j) &&;
GetLoginFlowsJob withData(JsonWrap j) const &;
};
using GetLoginFlowsResponse = GetLoginFlowsJob::JobResponse;
}
namespace nlohmann
{
using namespace Kazv;
template<>
struct adl_serializer<GetLoginFlowsJob::LoginFlow> {
+ static void to_json(json& jo, const GetLoginFlowsJob::LoginFlow &pod)
+ {
+ if (! jo.is_object()) { jo = json::object(); }
+
+
+
+ addToJsonIfNeeded(jo, "type"s, pod.type);
+ }
+
static void from_json(const json &jo, GetLoginFlowsJob::LoginFlow& result)
{
if (jo.contains("type"s)) {
result.type = jo.at("type"s);
}
}
+
};
}
namespace Kazv
{
/*! \brief Authenticates the user.
*
* Authenticates the user, and issues an access token they can
* use to authorize themself in subsequent requests.
*
* If the client does not supply a ``device_id``, the server must
* auto-generate one.
*
* The returned access token must be associated with the ``device_id``
* supplied by the client or generated by the server. The server may
* invalidate any access token previously associated with that device. See
* `Relationship between access tokens and devices`_.
*/
class LoginJob : public BaseJob {
public:
class JobResponse : public Response
{
public:
JobResponse(Response r);
bool success() const;
// Result properties
/// The fully-qualified Matrix ID for the account.
std::optional<std::string> userId() const;
/// An access token for the account.
/// This access token can then be used to authorize other requests.
std::optional<std::string> accessToken() const;
/// The server_name of the homeserver on which the account has
/// been registered.
///
/// **Deprecated**. Clients should extract the server_name from
/// ``user_id`` (by splitting at the first colon) if they require
/// it. Note also that ``homeserver`` is not spelt this way.
std::optional<std::string> homeServer() const;
/// ID of the logged-in device. Will be the same as the
/// corresponding parameter in the request, if one was specified.
std::optional<std::string> deviceId() const;
/// Optional client configuration provided by the server. If present,
/// clients SHOULD use the provided object to reconfigure themselves,
/// optionally validating the URLs within. This object takes the same
/// form as the one returned from .well-known autodiscovery.
std::optional<DiscoveryInformation> wellKnown() const;
};
static constexpr auto needsAuth() {
return
false;
}
// Construction/destruction
/*! \brief Authenticates the user.
*
* \param type
* The login type being used.
*
* \param identifier
* Authenticates the user, and issues an access token they can
* use to authorize themself in subsequent requests.
*
* If the client does not supply a ``device_id``, the server must
* auto-generate one.
*
* The returned access token must be associated with the ``device_id``
* supplied by the client or generated by the server. The server may
* invalidate any access token previously associated with that device. See
* `Relationship between access tokens and devices`_.
*
* \param password
* Required when ``type`` is ``m.login.password``. The user's
* password.
*
* \param token
* Required when ``type`` is ``m.login.token``. Part of `Token-based`_ login.
*
* \param deviceId
* ID of the client device. If this does not correspond to a
* known client device, a new device will be created. The server
* will auto-generate a device_id if this is not specified.
*
* \param initialDeviceDisplayName
* A display name to assign to the newly-created device. Ignored
* if ``device_id`` corresponds to a known device.
*/
explicit LoginJob(std::string serverUrl
,
std::string type , std::optional<UserIdentifier> identifier = std::nullopt, std::optional<std::string> password = std::nullopt, std::optional<std::string> token = std::nullopt, std::optional<std::string> deviceId = std::nullopt, std::optional<std::string> initialDeviceDisplayName = std::nullopt);
static BaseJob::Query buildQuery(
);
static BaseJob::Body buildBody(std::string type, std::optional<UserIdentifier> identifier, std::optional<std::string> password, std::optional<std::string> token, std::optional<std::string> deviceId, std::optional<std::string> initialDeviceDisplayName);
LoginJob withData(JsonWrap j) &&;
LoginJob withData(JsonWrap j) const &;
};
using LoginResponse = LoginJob::JobResponse;
}
namespace nlohmann
{
using namespace Kazv;
}
namespace Kazv
{
} // namespace Kazv
diff --git a/src/api/csapi/notifications.hpp b/src/api/csapi/notifications.hpp
index 47770d0..db5b7b7 100644
--- a/src/api/csapi/notifications.hpp
+++ b/src/api/csapi/notifications.hpp
@@ -1,143 +1,163 @@
/******************************************************************************
* THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN
*/
#pragma once
#include "basejob.hpp"
namespace Kazv {
/*! \brief Gets a list of events that the user has been notified about
*
* This API is used to paginate through the list of events that the
* user has been, or would have been notified about.
*/
class GetNotificationsJob : public BaseJob {
public:
// Inner data structures
/// This API is used to paginate through the list of events that the
/// user has been, or would have been notified about.
struct Notification
{
/// The action(s) to perform when the conditions for this rule are met.
/// See `Push Rules: API`_.
immer::array<Variant> actions;
/// The Event object for the event that triggered the notification.
JsonWrap event;
/// The profile tag of the rule that matched this event.
std::optional<std::string> profileTag;
/// Indicates whether the user has sent a read receipt indicating
/// that they have read this message.
bool read;
/// The ID of the room in which the event was posted.
std::string roomId;
/// The unix timestamp at which the event notification was sent,
/// in milliseconds.
int ts;
};
class JobResponse : public Response
{
public:
JobResponse(Response r);
bool success() const;
// Result properties
/// The token to supply in the ``from`` param of the next
/// ``/notifications`` request in order to request more
/// events. If this is absent, there are no more results.
std::optional<std::string> nextToken() const;
/// The list of events that triggered notifications.
immer::array<Notification> notifications() const;
};
static constexpr auto needsAuth() {
return true
;
}
// Construction/destruction
/*! \brief Gets a list of events that the user has been notified about
*
* \param from
* Pagination token given to retrieve the next set of events.
*
* \param limit
* Limit on the number of events to return in this request.
*
* \param only
* Allows basic filtering of events returned. Supply ``highlight``
* to return only events where the notification had the highlight
* tweak set.
*/
explicit GetNotificationsJob(std::string serverUrl
, std::string _accessToken
,
std::optional<std::string> from = std::nullopt, std::optional<int> limit = std::nullopt, std::optional<std::string> only = std::nullopt);
static BaseJob::Query buildQuery(
std::optional<std::string> from, std::optional<int> limit, std::optional<std::string> only);
static BaseJob::Body buildBody(std::optional<std::string> from, std::optional<int> limit, std::optional<std::string> only);
GetNotificationsJob withData(JsonWrap j) &&;
GetNotificationsJob withData(JsonWrap j) const &;
};
using GetNotificationsResponse = GetNotificationsJob::JobResponse;
}
namespace nlohmann
{
using namespace Kazv;
template<>
struct adl_serializer<GetNotificationsJob::Notification> {
+ static void to_json(json& jo, const GetNotificationsJob::Notification &pod)
+ {
+ if (! jo.is_object()) { jo = json::object(); }
+
+
+ jo["actions"s] = pod.actions;
+
+ jo["event"s] = pod.event;
+
+
+ addToJsonIfNeeded(jo, "profile_tag"s, pod.profileTag);
+ jo["read"s] = pod.read;
+
+ jo["room_id"s] = pod.roomId;
+
+ jo["ts"s] = pod.ts;
+
+ }
+
static void from_json(const json &jo, GetNotificationsJob::Notification& result)
{
if (jo.contains("actions"s)) {
result.actions = jo.at("actions"s);
}
if (jo.contains("event"s)) {
result.event = jo.at("event"s);
}
if (jo.contains("profile_tag"s)) {
result.profileTag = jo.at("profile_tag"s);
}
if (jo.contains("read"s)) {
result.read = jo.at("read"s);
}
if (jo.contains("room_id"s)) {
result.roomId = jo.at("room_id"s);
}
if (jo.contains("ts"s)) {
result.ts = jo.at("ts"s);
}
}
+
};
}
namespace Kazv
{
} // namespace Kazv
diff --git a/src/api/csapi/old_sync.hpp b/src/api/csapi/old_sync.hpp
index 82cc599..62606a8 100644
--- a/src/api/csapi/old_sync.hpp
+++ b/src/api/csapi/old_sync.hpp
@@ -1,372 +1,408 @@
/******************************************************************************
* THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN
*/
#pragma once
#include "basejob.hpp"
#include "event.hpp"
namespace Kazv {
/*! \brief Listen on the event stream.
*
* This will listen for new events and return them to the caller. This will
* block until an event is received, or until the ``timeout`` is reached.
*
* This endpoint was deprecated in r0 of this specification. Clients
* should instead call the |/sync|_ API with a ``since`` parameter. See
* the `migration guide
* <https://matrix.org/docs/guides/client-server-migrating-from-v1.html#deprecated-endpoints>`_.
*/
class GetEventsJob : public BaseJob {
public:
class JobResponse : public Response
{
public:
JobResponse(Response r);
bool success() const;
// Result properties
/// A token which correlates to the first value in ``chunk``. This
/// is usually the same token supplied to ``from=``.
std::optional<std::string> start() const;
/// A token which correlates to the last value in ``chunk``. This
/// token should be used in the next request to ``/events``.
std::optional<std::string> end() const;
/// An array of events.
EventList chunk() const;
};
static constexpr auto needsAuth() {
return true
;
}
// Construction/destruction
/*! \brief Listen on the event stream.
*
* \param from
* The token to stream from. This token is either from a previous
* request to this API or from the initial sync API.
*
* \param timeout
* The maximum time in milliseconds to wait for an event.
*/
explicit GetEventsJob(std::string serverUrl
, std::string _accessToken
,
std::optional<std::string> from = std::nullopt, std::optional<int> timeout = std::nullopt);
static BaseJob::Query buildQuery(
std::optional<std::string> from, std::optional<int> timeout);
static BaseJob::Body buildBody(std::optional<std::string> from, std::optional<int> timeout);
GetEventsJob withData(JsonWrap j) &&;
GetEventsJob withData(JsonWrap j) const &;
};
using GetEventsResponse = GetEventsJob::JobResponse;
}
namespace nlohmann
{
using namespace Kazv;
}
namespace Kazv
{
/*! \brief Get the user's current state.
*
* This returns the full state for this user, with an optional limit on the
* number of messages per room to return.
*
* This endpoint was deprecated in r0 of this specification. Clients
* should instead call the |/sync|_ API with no ``since`` parameter. See
* the `migration guide
* <https://matrix.org/docs/guides/client-server-migrating-from-v1.html#deprecated-endpoints>`_.
*/
class InitialSyncJob : public BaseJob {
public:
// Inner data structures
/// The pagination chunk for this room.
struct PaginationChunk
{
/// A token which correlates to the first value in ``chunk``.
/// Used for pagination.
std::string start;
/// A token which correlates to the last value in ``chunk``.
/// Used for pagination.
std::string end;
/// If the user is a member of the room this will be a
/// list of the most recent messages for this room. If
/// the user has left the room this will be the
/// messages that preceeded them leaving. This array
/// will consist of at most ``limit`` elements.
EventList chunk;
};
/// This returns the full state for this user, with an optional limit on the
/// number of messages per room to return.
///
/// This endpoint was deprecated in r0 of this specification. Clients
/// should instead call the |/sync|_ API with no ``since`` parameter. See
/// the `migration guide
/// <https://matrix.org/docs/guides/client-server-migrating-from-v1.html#deprecated-endpoints>`_.
struct RoomInfo
{
/// The ID of this room.
std::string roomId;
/// The user's membership state in this room.
std::string membership;
/// The invite event if ``membership`` is ``invite``
Event invite;
/// The pagination chunk for this room.
std::optional<PaginationChunk> messages;
/// If the user is a member of the room this will be the
/// current state of the room as a list of events. If the
/// user has left the room this will be the state of the
/// room when they left it.
EventList state;
/// Whether this room is visible to the ``/publicRooms`` API
/// or not."
std::optional<std::string> visibility;
/// The private data that this user has attached to
/// this room.
EventList accountData;
};
class JobResponse : public Response
{
public:
JobResponse(Response r);
bool success() const;
// Result properties
/// A token which correlates to the last value in ``chunk``. This
/// token should be used with the ``/events`` API to listen for new
/// events.
std::string end() const;
/// A list of presence events.
EventList presence() const;
/// This returns the full state for this user, with an optional limit on the
/// number of messages per room to return.
///
/// This endpoint was deprecated in r0 of this specification. Clients
/// should instead call the |/sync|_ API with no ``since`` parameter. See
/// the `migration guide
/// <https://matrix.org/docs/guides/client-server-migrating-from-v1.html#deprecated-endpoints>`_.
immer::array<RoomInfo> rooms() const;
/// The global private data created by this user.
EventList accountData() const;
};
static constexpr auto needsAuth() {
return true
;
}
// Construction/destruction
/*! \brief Get the user's current state.
*
* \param limit
* The maximum number of messages to return for each room.
*
* \param archived
* Whether to include rooms that the user has left. If ``false`` then
* only rooms that the user has been invited to or has joined are
* included. If set to ``true`` then rooms that the user has left are
* included as well. By default this is ``false``.
*/
explicit InitialSyncJob(std::string serverUrl
, std::string _accessToken
,
std::optional<int> limit = std::nullopt, std::optional<bool> archived = std::nullopt);
static BaseJob::Query buildQuery(
std::optional<int> limit, std::optional<bool> archived);
static BaseJob::Body buildBody(std::optional<int> limit, std::optional<bool> archived);
InitialSyncJob withData(JsonWrap j) &&;
InitialSyncJob withData(JsonWrap j) const &;
};
using InitialSyncResponse = InitialSyncJob::JobResponse;
}
namespace nlohmann
{
using namespace Kazv;
template<>
struct adl_serializer<InitialSyncJob::PaginationChunk> {
+ static void to_json(json& jo, const InitialSyncJob::PaginationChunk &pod)
+ {
+ if (! jo.is_object()) { jo = json::object(); }
+
+
+ jo["start"s] = pod.start;
+
+ jo["end"s] = pod.end;
+
+ jo["chunk"s] = pod.chunk;
+
+ }
+
static void from_json(const json &jo, InitialSyncJob::PaginationChunk& result)
{
if (jo.contains("start"s)) {
result.start = jo.at("start"s);
}
if (jo.contains("end"s)) {
result.end = jo.at("end"s);
}
if (jo.contains("chunk"s)) {
result.chunk = jo.at("chunk"s);
}
}
+
};
template<>
struct adl_serializer<InitialSyncJob::RoomInfo> {
+ static void to_json(json& jo, const InitialSyncJob::RoomInfo &pod)
+ {
+ if (! jo.is_object()) { jo = json::object(); }
+
+
+ jo["room_id"s] = pod.roomId;
+
+ jo["membership"s] = pod.membership;
+
+
+ addToJsonIfNeeded(jo, "invite"s, pod.invite);
+
+ addToJsonIfNeeded(jo, "messages"s, pod.messages);
+
+ addToJsonIfNeeded(jo, "state"s, pod.state);
+
+ addToJsonIfNeeded(jo, "visibility"s, pod.visibility);
+
+ addToJsonIfNeeded(jo, "account_data"s, pod.accountData);
+ }
+
static void from_json(const json &jo, InitialSyncJob::RoomInfo& result)
{
if (jo.contains("room_id"s)) {
result.roomId = jo.at("room_id"s);
}
if (jo.contains("membership"s)) {
result.membership = jo.at("membership"s);
}
if (jo.contains("invite"s)) {
result.invite = jo.at("invite"s);
}
if (jo.contains("messages"s)) {
result.messages = jo.at("messages"s);
}
if (jo.contains("state"s)) {
result.state = jo.at("state"s);
}
if (jo.contains("visibility"s)) {
result.visibility = jo.at("visibility"s);
}
if (jo.contains("account_data"s)) {
result.accountData = jo.at("account_data"s);
}
}
+
};
}
namespace Kazv
{
/*! \brief Get a single event by event ID.
*
* Get a single event based on ``event_id``. You must have permission to
* retrieve this event e.g. by being a member in the room for this event.
*
* This endpoint was deprecated in r0 of this specification. Clients
* should instead call the |/rooms/{roomId}/event/{eventId}|_ API
* or the |/rooms/{roomId}/context/{eventId}|_ API.
*/
class GetOneEventJob : public BaseJob {
public:
class JobResponse : public Response
{
public:
JobResponse(Response r);
bool success() const;
// Result properties
/// The full event.
JsonWrap data() const
{
return
std::move(jsonBody().get()).get<JsonWrap>()
;
}
};
static constexpr auto needsAuth() {
return true
;
}
// Construction/destruction
/*! \brief Get a single event by event ID.
*
* \param eventId
* The event ID to get.
*/
explicit GetOneEventJob(std::string serverUrl
, std::string _accessToken
,
std::string eventId );
static BaseJob::Query buildQuery(
);
static BaseJob::Body buildBody(std::string eventId);
GetOneEventJob withData(JsonWrap j) &&;
GetOneEventJob withData(JsonWrap j) const &;
};
using GetOneEventResponse = GetOneEventJob::JobResponse;
}
namespace nlohmann
{
using namespace Kazv;
}
namespace Kazv
{
} // namespace Kazv
diff --git a/src/api/csapi/pusher.hpp b/src/api/csapi/pusher.hpp
index b6496db..e71cb3e 100644
--- a/src/api/csapi/pusher.hpp
+++ b/src/api/csapi/pusher.hpp
@@ -1,309 +1,358 @@
/******************************************************************************
* THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN
*/
#pragma once
#include "basejob.hpp"
namespace Kazv {
/*! \brief Gets the current pushers for the authenticated user
*
* Gets all currently active pushers for the authenticated user.
*/
class GetPushersJob : public BaseJob {
public:
// Inner data structures
/// A dictionary of information for the pusher implementation
/// itself.
struct PusherData
{
/// Required if ``kind`` is ``http``. The URL to use to send
/// notifications to.
std::optional<std::string> url;
/// The format to use when sending notifications to the Push
/// Gateway.
std::optional<std::string> format;
};
/// Gets all currently active pushers for the authenticated user.
struct Pusher
{
/// This is a unique identifier for this pusher. See ``/set`` for
/// more detail.
/// Max length, 512 bytes.
std::string pushkey;
/// The kind of pusher. ``"http"`` is a pusher that
/// sends HTTP pokes.
std::string kind;
/// This is a reverse-DNS style identifier for the application.
/// Max length, 64 chars.
std::string appId;
/// A string that will allow the user to identify what application
/// owns this pusher.
std::string appDisplayName;
/// A string that will allow the user to identify what device owns
/// this pusher.
std::string deviceDisplayName;
/// This string determines which set of device specific rules this
/// pusher executes.
std::optional<std::string> profileTag;
/// The preferred language for receiving notifications (e.g. 'en'
/// or 'en-US')
std::string lang;
/// A dictionary of information for the pusher implementation
/// itself.
PusherData data;
};
class JobResponse : public Response
{
public:
JobResponse(Response r);
bool success() const;
// Result properties
/// An array containing the current pushers for the user
immer::array<Pusher> pushers() const;
};
static constexpr auto needsAuth() {
return true
;
}
// Construction/destruction
/// Gets the current pushers for the authenticated user
explicit GetPushersJob(std::string serverUrl
, std::string _accessToken
);
static BaseJob::Query buildQuery(
);
static BaseJob::Body buildBody();
GetPushersJob withData(JsonWrap j) &&;
GetPushersJob withData(JsonWrap j) const &;
};
using GetPushersResponse = GetPushersJob::JobResponse;
}
namespace nlohmann
{
using namespace Kazv;
template<>
struct adl_serializer<GetPushersJob::PusherData> {
+ static void to_json(json& jo, const GetPushersJob::PusherData &pod)
+ {
+ if (! jo.is_object()) { jo = json::object(); }
+
+
+
+ addToJsonIfNeeded(jo, "url"s, pod.url);
+
+ addToJsonIfNeeded(jo, "format"s, pod.format);
+ }
+
static void from_json(const json &jo, GetPushersJob::PusherData& result)
{
if (jo.contains("url"s)) {
result.url = jo.at("url"s);
}
if (jo.contains("format"s)) {
result.format = jo.at("format"s);
}
}
+
};
template<>
struct adl_serializer<GetPushersJob::Pusher> {
+ static void to_json(json& jo, const GetPushersJob::Pusher &pod)
+ {
+ if (! jo.is_object()) { jo = json::object(); }
+
+
+ jo["pushkey"s] = pod.pushkey;
+
+ jo["kind"s] = pod.kind;
+
+ jo["app_id"s] = pod.appId;
+
+ jo["app_display_name"s] = pod.appDisplayName;
+
+ jo["device_display_name"s] = pod.deviceDisplayName;
+
+
+ addToJsonIfNeeded(jo, "profile_tag"s, pod.profileTag);
+ jo["lang"s] = pod.lang;
+
+ jo["data"s] = pod.data;
+
+ }
+
static void from_json(const json &jo, GetPushersJob::Pusher& result)
{
if (jo.contains("pushkey"s)) {
result.pushkey = jo.at("pushkey"s);
}
if (jo.contains("kind"s)) {
result.kind = jo.at("kind"s);
}
if (jo.contains("app_id"s)) {
result.appId = jo.at("app_id"s);
}
if (jo.contains("app_display_name"s)) {
result.appDisplayName = jo.at("app_display_name"s);
}
if (jo.contains("device_display_name"s)) {
result.deviceDisplayName = jo.at("device_display_name"s);
}
if (jo.contains("profile_tag"s)) {
result.profileTag = jo.at("profile_tag"s);
}
if (jo.contains("lang"s)) {
result.lang = jo.at("lang"s);
}
if (jo.contains("data"s)) {
result.data = jo.at("data"s);
}
}
+
};
}
namespace Kazv
{
/*! \brief Modify a pusher for this user on the homeserver.
*
* This endpoint allows the creation, modification and deletion of `pushers`_
* for this user ID. The behaviour of this endpoint varies depending on the
* values in the JSON body.
*/
class PostPusherJob : public BaseJob {
public:
// Inner data structures
/// A dictionary of information for the pusher implementation
/// itself. If ``kind`` is ``http``, this should contain ``url``
/// which is the URL to use to send notifications to.
struct PusherData
{
/// Required if ``kind`` is ``http``. The URL to use to send
/// notifications to. MUST be an HTTPS URL with a path of
/// ``/_matrix/push/v1/notify``.
std::optional<std::string> url;
/// The format to send notifications in to Push Gateways if the
/// ``kind`` is ``http``. The details about what fields the
/// homeserver should send to the push gateway are defined in the
/// `Push Gateway Specification`_. Currently the only format
/// available is 'event_id_only'.
std::optional<std::string> format;
};
class JobResponse : public Response
{
public:
JobResponse(Response r);
bool success() const;
};
static constexpr auto needsAuth() {
return true
;
}
// Construction/destruction
/*! \brief Modify a pusher for this user on the homeserver.
*
* \param pushkey
* This is a unique identifier for this pusher. The value you
* should use for this is the routing or destination address
* information for the notification, for example, the APNS token
* for APNS or the Registration ID for GCM. If your notification
* client has no such concept, use any unique identifier.
* Max length, 512 bytes.
*
* If the ``kind`` is ``"email"``, this is the email address to
* send notifications to.
*
* \param kind
* The kind of pusher to configure. ``"http"`` makes a pusher that
* sends HTTP pokes. ``"email"`` makes a pusher that emails the
* user with unread notifications. ``null`` deletes the pusher.
*
* \param appId
* This is a reverse-DNS style identifier for the application.
* It is recommended that this end with the platform, such that
* different platform versions get different app identifiers.
* Max length, 64 chars.
*
* If the ``kind`` is ``"email"``, this is ``"m.email"``.
*
* \param appDisplayName
* A string that will allow the user to identify what application
* owns this pusher.
*
* \param deviceDisplayName
* A string that will allow the user to identify what device owns
* this pusher.
*
* \param lang
* The preferred language for receiving notifications (e.g. 'en'
* or 'en-US').
*
* \param data
* A dictionary of information for the pusher implementation
* itself. If ``kind`` is ``http``, this should contain ``url``
* which is the URL to use to send notifications to.
*
* \param profileTag
* This string determines which set of device specific rules this
* pusher executes.
*
* \param append
* If true, the homeserver should add another pusher with the
* given pushkey and App ID in addition to any others with
* different user IDs. Otherwise, the homeserver must remove any
* other pushers with the same App ID and pushkey for different
* users. The default is ``false``.
*/
explicit PostPusherJob(std::string serverUrl
, std::string _accessToken
,
std::string pushkey , std::string kind , std::string appId , std::string appDisplayName , std::string deviceDisplayName , std::string lang , PusherData data , std::optional<std::string> profileTag = std::nullopt, std::optional<bool> append = std::nullopt);
static BaseJob::Query buildQuery(
);
static BaseJob::Body buildBody(std::string pushkey, std::string kind, std::string appId, std::string appDisplayName, std::string deviceDisplayName, std::string lang, PusherData data, std::optional<std::string> profileTag, std::optional<bool> append);
PostPusherJob withData(JsonWrap j) &&;
PostPusherJob withData(JsonWrap j) const &;
};
using PostPusherResponse = PostPusherJob::JobResponse;
}
namespace nlohmann
{
using namespace Kazv;
template<>
struct adl_serializer<PostPusherJob::PusherData> {
+
static void to_json(json& jo, const PostPusherJob::PusherData &pod)
{
if (! jo.is_object()) { jo = json::object(); }
addToJsonIfNeeded(jo, "url"s, pod.url);
addToJsonIfNeeded(jo, "format"s, pod.format);
}
+ static void from_json(const json &jo, PostPusherJob::PusherData& result)
+ {
+
+ if (jo.contains("url"s)) {
+ result.url = jo.at("url"s);
+ }
+ if (jo.contains("format"s)) {
+ result.format = jo.at("format"s);
+ }
+
+ }
+
};
}
namespace Kazv
{
} // namespace Kazv
diff --git a/src/api/csapi/room_initial_sync.hpp b/src/api/csapi/room_initial_sync.hpp
index acf1b65..6dff49d 100644
--- a/src/api/csapi/room_initial_sync.hpp
+++ b/src/api/csapi/room_initial_sync.hpp
@@ -1,144 +1,158 @@
/******************************************************************************
* THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN
*/
#pragma once
#include "basejob.hpp"
namespace Kazv {
/*! \brief Snapshot the current state of a room and its most recent messages.
*
* Get a copy of the current state and the most recent messages in a room.
*
* This endpoint was deprecated in r0 of this specification. There is no
* direct replacement; the relevant information is returned by the
* |/sync|_ API. See the `migration guide
* <https://matrix.org/docs/guides/client-server-migrating-from-v1.html#deprecated-endpoints>`_.
*/
class RoomInitialSyncJob : public BaseJob {
public:
// Inner data structures
/// The pagination chunk for this room.
struct PaginationChunk
{
/// A token which correlates to the first value in ``chunk``.
/// Used for pagination.
std::string start;
/// A token which correlates to the last value in ``chunk``.
/// Used for pagination.
std::string end;
/// If the user is a member of the room this will be a
/// list of the most recent messages for this room. If
/// the user has left the room this will be the
/// messages that preceeded them leaving. This array
/// will consist of at most ``limit`` elements.
EventList chunk;
};
class JobResponse : public Response
{
public:
JobResponse(Response r);
bool success() const;
// Result properties
/// The ID of this room.
std::string roomId() const;
/// The user's membership state in this room.
std::optional<std::string> membership() const;
/// The pagination chunk for this room.
std::optional<PaginationChunk> messages() const;
/// If the user is a member of the room this will be the
/// current state of the room as a list of events. If the
/// user has left the room this will be the state of the
/// room when they left it.
EventList state() const;
/// Whether this room is visible to the ``/publicRooms`` API
/// or not."
std::optional<std::string> visibility() const;
/// The private data that this user has attached to this room.
EventList accountData() const;
};
static constexpr auto needsAuth() {
return true
;
}
// Construction/destruction
/*! \brief Snapshot the current state of a room and its most recent messages.
*
* \param roomId
* The room to get the data.
*/
explicit RoomInitialSyncJob(std::string serverUrl
, std::string _accessToken
,
std::string roomId );
static BaseJob::Query buildQuery(
);
static BaseJob::Body buildBody(std::string roomId);
RoomInitialSyncJob withData(JsonWrap j) &&;
RoomInitialSyncJob withData(JsonWrap j) const &;
};
using RoomInitialSyncResponse = RoomInitialSyncJob::JobResponse;
}
namespace nlohmann
{
using namespace Kazv;
template<>
struct adl_serializer<RoomInitialSyncJob::PaginationChunk> {
+ static void to_json(json& jo, const RoomInitialSyncJob::PaginationChunk &pod)
+ {
+ if (! jo.is_object()) { jo = json::object(); }
+
+
+ jo["start"s] = pod.start;
+
+ jo["end"s] = pod.end;
+
+ jo["chunk"s] = pod.chunk;
+
+ }
+
static void from_json(const json &jo, RoomInitialSyncJob::PaginationChunk& result)
{
if (jo.contains("start"s)) {
result.start = jo.at("start"s);
}
if (jo.contains("end"s)) {
result.end = jo.at("end"s);
}
if (jo.contains("chunk"s)) {
result.chunk = jo.at("chunk"s);
}
}
+
};
}
namespace Kazv
{
} // namespace Kazv
diff --git a/src/api/csapi/rooms.hpp b/src/api/csapi/rooms.hpp
index 841de32..d50dd03 100644
--- a/src/api/csapi/rooms.hpp
+++ b/src/api/csapi/rooms.hpp
@@ -1,407 +1,419 @@
/******************************************************************************
* THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN
*/
#pragma once
#include "basejob.hpp"
#include "event.hpp"
namespace Kazv {
/*! \brief Get a single event by event ID.
*
* Get a single event based on ``roomId/eventId``. You must have permission to
* retrieve this event e.g. by being a member in the room for this event.
*/
class GetOneRoomEventJob : public BaseJob {
public:
class JobResponse : public Response
{
public:
JobResponse(Response r);
bool success() const;
// Result properties
/// The full event.
JsonWrap data() const
{
return
std::move(jsonBody().get()).get<JsonWrap>()
;
}
};
static constexpr auto needsAuth() {
return true
;
}
// Construction/destruction
/*! \brief Get a single event by event ID.
*
* \param roomId
* The ID of the room the event is in.
*
* \param eventId
* The event ID to get.
*/
explicit GetOneRoomEventJob(std::string serverUrl
, std::string _accessToken
,
std::string roomId , std::string eventId );
static BaseJob::Query buildQuery(
);
static BaseJob::Body buildBody(std::string roomId, std::string eventId);
GetOneRoomEventJob withData(JsonWrap j) &&;
GetOneRoomEventJob withData(JsonWrap j) const &;
};
using GetOneRoomEventResponse = GetOneRoomEventJob::JobResponse;
}
namespace nlohmann
{
using namespace Kazv;
}
namespace Kazv
{
/*! \brief Get the state identified by the type and key.
*
* .. For backwards compatibility with older links...
* .. _`get-matrix-client-r0-rooms-roomid-state-eventtype`:
*
* Looks up the contents of a state event in a room. If the user is
* joined to the room then the state is taken from the current
* state of the room. If the user has left the room then the state is
* taken from the state of the room when they left.
*/
class GetRoomStateWithKeyJob : public BaseJob {
public:
class JobResponse : public Response
{
public:
JobResponse(Response r);
bool success() const;
};
static constexpr auto needsAuth() {
return true
;
}
// Construction/destruction
/*! \brief Get the state identified by the type and key.
*
* \param roomId
* The room to look up the state in.
*
* \param eventType
* The type of state to look up.
*
* \param stateKey
* The key of the state to look up. Defaults to an empty string. When
* an empty string, the trailing slash on this endpoint is optional.
*/
explicit GetRoomStateWithKeyJob(std::string serverUrl
, std::string _accessToken
,
std::string roomId , std::string eventType , std::string stateKey );
static BaseJob::Query buildQuery(
);
static BaseJob::Body buildBody(std::string roomId, std::string eventType, std::string stateKey);
GetRoomStateWithKeyJob withData(JsonWrap j) &&;
GetRoomStateWithKeyJob withData(JsonWrap j) const &;
};
using GetRoomStateWithKeyResponse = GetRoomStateWithKeyJob::JobResponse;
}
namespace nlohmann
{
using namespace Kazv;
}
namespace Kazv
{
/*! \brief Get all state events in the current state of a room.
*
* Get the state events for the current state of a room.
*/
class GetRoomStateJob : public BaseJob {
public:
class JobResponse : public Response
{
public:
JobResponse(Response r);
bool success() const;
// Result properties
/// The current state of the room
EventList data() const
{
return
std::move(jsonBody().get()).get<EventList>()
;
}
};
static constexpr auto needsAuth() {
return true
;
}
// Construction/destruction
/*! \brief Get all state events in the current state of a room.
*
* \param roomId
* The room to look up the state for.
*/
explicit GetRoomStateJob(std::string serverUrl
, std::string _accessToken
,
std::string roomId );
static BaseJob::Query buildQuery(
);
static BaseJob::Body buildBody(std::string roomId);
GetRoomStateJob withData(JsonWrap j) &&;
GetRoomStateJob withData(JsonWrap j) const &;
};
using GetRoomStateResponse = GetRoomStateJob::JobResponse;
}
namespace nlohmann
{
using namespace Kazv;
}
namespace Kazv
{
/*! \brief Get the m.room.member events for the room.
*
* Get the list of members for this room.
*/
class GetMembersByRoomJob : public BaseJob {
public:
class JobResponse : public Response
{
public:
JobResponse(Response r);
bool success() const;
// Result properties
/// Get the list of members for this room.
EventList chunk() const;
};
static constexpr auto needsAuth() {
return true
;
}
// Construction/destruction
/*! \brief Get the m.room.member events for the room.
*
* \param roomId
* The room to get the member events for.
*
* \param at
* The point in time (pagination token) to return members for in the room.
* This token can be obtained from a ``prev_batch`` token returned for
* each room by the sync API. Defaults to the current state of the room,
* as determined by the server.
*
* \param membership
* The kind of membership to filter for. Defaults to no filtering if
* unspecified. When specified alongside ``not_membership``, the two
* parameters create an 'or' condition: either the membership *is*
* the same as ``membership`` **or** *is not* the same as ``not_membership``.
*
* \param notMembership
* The kind of membership to exclude from the results. Defaults to no
* filtering if unspecified.
*/
explicit GetMembersByRoomJob(std::string serverUrl
, std::string _accessToken
,
std::string roomId , std::optional<std::string> at = std::nullopt, std::optional<std::string> membership = std::nullopt, std::optional<std::string> notMembership = std::nullopt);
static BaseJob::Query buildQuery(
std::optional<std::string> at, std::optional<std::string> membership, std::optional<std::string> notMembership);
static BaseJob::Body buildBody(std::string roomId, std::optional<std::string> at, std::optional<std::string> membership, std::optional<std::string> notMembership);
GetMembersByRoomJob withData(JsonWrap j) &&;
GetMembersByRoomJob withData(JsonWrap j) const &;
};
using GetMembersByRoomResponse = GetMembersByRoomJob::JobResponse;
}
namespace nlohmann
{
using namespace Kazv;
}
namespace Kazv
{
/*! \brief Gets the list of currently joined users and their profile data.
*
* This API returns a map of MXIDs to member info objects for members of the room. The current user must be in the room for it to work, unless it is an Application Service in which case any of the AS's users must be in the room. This API is primarily for Application Services and should be faster to respond than ``/members`` as it can be implemented more efficiently on the server.
*/
class GetJoinedMembersByRoomJob : public BaseJob {
public:
// Inner data structures
/// This API returns a map of MXIDs to member info objects for members of the room. The current user must be in the room for it to work, unless it is an Application Service in which case any of the AS's users must be in the room. This API is primarily for Application Services and should be faster to respond than ``/members`` as it can be implemented more efficiently on the server.
struct RoomMember
{
/// The display name of the user this object is representing.
std::optional<std::string> displayName;
/// The mxc avatar url of the user this object is representing.
std::optional<std::string> avatarUrl;
};
class JobResponse : public Response
{
public:
JobResponse(Response r);
bool success() const;
// Result properties
/// A map from user ID to a RoomMember object.
immer::map<std::string, RoomMember> joined() const;
};
static constexpr auto needsAuth() {
return true
;
}
// Construction/destruction
/*! \brief Gets the list of currently joined users and their profile data.
*
* \param roomId
* The room to get the members of.
*/
explicit GetJoinedMembersByRoomJob(std::string serverUrl
, std::string _accessToken
,
std::string roomId );
static BaseJob::Query buildQuery(
);
static BaseJob::Body buildBody(std::string roomId);
GetJoinedMembersByRoomJob withData(JsonWrap j) &&;
GetJoinedMembersByRoomJob withData(JsonWrap j) const &;
};
using GetJoinedMembersByRoomResponse = GetJoinedMembersByRoomJob::JobResponse;
}
namespace nlohmann
{
using namespace Kazv;
template<>
struct adl_serializer<GetJoinedMembersByRoomJob::RoomMember> {
+ static void to_json(json& jo, const GetJoinedMembersByRoomJob::RoomMember &pod)
+ {
+ if (! jo.is_object()) { jo = json::object(); }
+
+
+
+ addToJsonIfNeeded(jo, "display_name"s, pod.displayName);
+
+ addToJsonIfNeeded(jo, "avatar_url"s, pod.avatarUrl);
+ }
+
static void from_json(const json &jo, GetJoinedMembersByRoomJob::RoomMember& result)
{
if (jo.contains("display_name"s)) {
result.displayName = jo.at("display_name"s);
}
if (jo.contains("avatar_url"s)) {
result.avatarUrl = jo.at("avatar_url"s);
}
}
+
};
}
namespace Kazv
{
} // namespace Kazv
diff --git a/src/api/csapi/search.hpp b/src/api/csapi/search.hpp
index 9e10feb..534cfe2 100644
--- a/src/api/csapi/search.hpp
+++ b/src/api/csapi/search.hpp
@@ -1,437 +1,599 @@
/******************************************************************************
* THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN
*/
#pragma once
#include "basejob.hpp"
#include "csapi/definitions/room_event_filter.hpp"
namespace Kazv {
/*! \brief Perform a server-side search.
*
* Performs a full text search across different categories.
*/
class SearchJob : public BaseJob {
public:
// Inner data structures
/// Configures whether any context for the events
/// returned are included in the response.
struct IncludeEventContext
{
/// How many events before the result are
/// returned. By default, this is ``5``.
std::optional<int> beforeLimit;
/// How many events after the result are
/// returned. By default, this is ``5``.
std::optional<int> afterLimit;
/// Requests that the server returns the
/// historic profile information for the users
/// that sent the events that were returned.
/// By default, this is ``false``.
std::optional<bool> includeProfile;
};
/// Configuration for group.
struct Group
{
/// Key that defines the group.
std::optional<std::string> key;
};
/// Requests that the server partitions the result set
/// based on the provided list of keys.
struct Groupings
{
/// List of groups to request.
immer::array<Group> groupBy;
};
/// Mapping of category name to search criteria.
struct RoomEventsCriteria
{
/// The string to search events for
std::string searchTerm;
/// The keys to search. Defaults to all.
immer::array<std::string> keys;
/// This takes a `filter`_.
RoomEventFilter filter;
/// The order in which to search for results.
/// By default, this is ``"rank"``.
std::optional<std::string> orderBy;
/// Configures whether any context for the events
/// returned are included in the response.
std::optional<IncludeEventContext> eventContext;
/// Requests the server return the current state for
/// each room returned.
std::optional<bool> includeState;
/// Requests that the server partitions the result set
/// based on the provided list of keys.
std::optional<Groupings> groupings;
};
/// Describes which categories to search in and their criteria.
struct Categories
{
/// Mapping of category name to search criteria.
std::optional<RoomEventsCriteria> roomEvents;
};
/// Performs a full text search across different categories.
struct UserProfile
{
/// Performs a full text search across different categories.
std::optional<std::string> displayname;
/// Performs a full text search across different categories.
std::optional<std::string> avatarUrl;
};
/// Context for result, if requested.
struct EventContext
{
/// Pagination token for the start of the chunk
std::optional<std::string> start;
/// Pagination token for the end of the chunk
std::optional<std::string> end;
/// The historic profile information of the
/// users that sent the events returned.
///
/// The ``string`` key is the user ID for which
/// the profile belongs to.
immer::map<std::string, UserProfile> profileInfo;
/// Events just before the result.
EventList eventsBefore;
/// Events just after the result.
EventList eventsAfter;
};
/// The result object.
struct Result
{
/// A number that describes how closely this result matches the search. Higher is closer.
std::optional<double> rank;
/// The event that matched.
JsonWrap result;
/// Context for result, if requested.
std::optional<EventContext> context;
};
/// The results for a particular group value.
struct GroupValue
{
/// Token that can be used to get the next batch
/// of results in the group, by passing as the
/// `next_batch` parameter to the next call. If
/// this field is absent, there are no more
/// results in this group.
std::optional<std::string> nextBatch;
/// Key that can be used to order different
/// groups.
std::optional<int> order;
/// Which results are in this group.
immer::array<std::string> results;
};
/// Mapping of category name to search criteria.
struct ResultRoomEvents
{
/// An approximate count of the total number of results found.
std::optional<int> count;
/// List of words which should be highlighted, useful for stemming which may change the query terms.
immer::array<std::string> highlights;
/// List of results in the requested order.
immer::array<Result> results;
/// The current state for every room in the results.
/// This is included if the request had the
/// ``include_state`` key set with a value of ``true``.
///
/// The ``string`` key is the room ID for which the ``State
/// Event`` array belongs to.
immer::map<std::string, EventList> state;
/// Any groups that were requested.
///
/// The outer ``string`` key is the group key requested (eg: ``room_id``
/// or ``sender``). The inner ``string`` key is the grouped value (eg:
/// a room's ID or a user's ID).
immer::map<std::string, immer::map<std::string, GroupValue>> groups;
/// Token that can be used to get the next batch of
/// results, by passing as the `next_batch` parameter to
/// the next call. If this field is absent, there are no
/// more results.
std::optional<std::string> nextBatch;
};
/// Describes which categories to search in and their criteria.
struct ResultCategories
{
/// Mapping of category name to search criteria.
std::optional<ResultRoomEvents> roomEvents;
};
class JobResponse : public Response
{
public:
JobResponse(Response r);
bool success() const;
// Result properties
/// Describes which categories to search in and their criteria.
ResultCategories searchCategories() const;
};
static constexpr auto needsAuth() {
return true
;
}
// Construction/destruction
/*! \brief Perform a server-side search.
*
* \param searchCategories
* Describes which categories to search in and their criteria.
*
* \param nextBatch
* The point to return events from. If given, this should be a
* ``next_batch`` result from a previous call to this endpoint.
*/
explicit SearchJob(std::string serverUrl
, std::string _accessToken
,
Categories searchCategories , std::optional<std::string> nextBatch = std::nullopt);
static BaseJob::Query buildQuery(
std::optional<std::string> nextBatch);
static BaseJob::Body buildBody(Categories searchCategories, std::optional<std::string> nextBatch);
SearchJob withData(JsonWrap j) &&;
SearchJob withData(JsonWrap j) const &;
};
using SearchResponse = SearchJob::JobResponse;
}
namespace nlohmann
{
using namespace Kazv;
template<>
struct adl_serializer<SearchJob::IncludeEventContext> {
+
static void to_json(json& jo, const SearchJob::IncludeEventContext &pod)
{
if (! jo.is_object()) { jo = json::object(); }
addToJsonIfNeeded(jo, "before_limit"s, pod.beforeLimit);
addToJsonIfNeeded(jo, "after_limit"s, pod.afterLimit);
addToJsonIfNeeded(jo, "include_profile"s, pod.includeProfile);
}
+ static void from_json(const json &jo, SearchJob::IncludeEventContext& result)
+ {
+
+ if (jo.contains("before_limit"s)) {
+ result.beforeLimit = jo.at("before_limit"s);
+ }
+ if (jo.contains("after_limit"s)) {
+ result.afterLimit = jo.at("after_limit"s);
+ }
+ if (jo.contains("include_profile"s)) {
+ result.includeProfile = jo.at("include_profile"s);
+ }
+
+ }
+
};
template<>
struct adl_serializer<SearchJob::Group> {
+
static void to_json(json& jo, const SearchJob::Group &pod)
{
if (! jo.is_object()) { jo = json::object(); }
addToJsonIfNeeded(jo, "key"s, pod.key);
}
+ static void from_json(const json &jo, SearchJob::Group& result)
+ {
+
+ if (jo.contains("key"s)) {
+ result.key = jo.at("key"s);
+ }
+
+ }
+
};
template<>
struct adl_serializer<SearchJob::Groupings> {
+
static void to_json(json& jo, const SearchJob::Groupings &pod)
{
if (! jo.is_object()) { jo = json::object(); }
addToJsonIfNeeded(jo, "group_by"s, pod.groupBy);
}
+ static void from_json(const json &jo, SearchJob::Groupings& result)
+ {
+
+ if (jo.contains("group_by"s)) {
+ result.groupBy = jo.at("group_by"s);
+ }
+
+ }
+
};
template<>
struct adl_serializer<SearchJob::RoomEventsCriteria> {
+
static void to_json(json& jo, const SearchJob::RoomEventsCriteria &pod)
{
if (! jo.is_object()) { jo = json::object(); }
jo["search_term"s] = pod.searchTerm;
addToJsonIfNeeded(jo, "keys"s, pod.keys);
addToJsonIfNeeded(jo, "filter"s, pod.filter);
addToJsonIfNeeded(jo, "order_by"s, pod.orderBy);
addToJsonIfNeeded(jo, "event_context"s, pod.eventContext);
addToJsonIfNeeded(jo, "include_state"s, pod.includeState);
addToJsonIfNeeded(jo, "groupings"s, pod.groupings);
}
+ static void from_json(const json &jo, SearchJob::RoomEventsCriteria& result)
+ {
+
+ if (jo.contains("search_term"s)) {
+ result.searchTerm = jo.at("search_term"s);
+ }
+ if (jo.contains("keys"s)) {
+ result.keys = jo.at("keys"s);
+ }
+ if (jo.contains("filter"s)) {
+ result.filter = jo.at("filter"s);
+ }
+ if (jo.contains("order_by"s)) {
+ result.orderBy = jo.at("order_by"s);
+ }
+ if (jo.contains("event_context"s)) {
+ result.eventContext = jo.at("event_context"s);
+ }
+ if (jo.contains("include_state"s)) {
+ result.includeState = jo.at("include_state"s);
+ }
+ if (jo.contains("groupings"s)) {
+ result.groupings = jo.at("groupings"s);
+ }
+
+ }
+
};
template<>
struct adl_serializer<SearchJob::Categories> {
+
static void to_json(json& jo, const SearchJob::Categories &pod)
{
if (! jo.is_object()) { jo = json::object(); }
addToJsonIfNeeded(jo, "room_events"s, pod.roomEvents);
}
+ static void from_json(const json &jo, SearchJob::Categories& result)
+ {
+
+ if (jo.contains("room_events"s)) {
+ result.roomEvents = jo.at("room_events"s);
+ }
+
+ }
+
};
template<>
struct adl_serializer<SearchJob::UserProfile> {
+ static void to_json(json& jo, const SearchJob::UserProfile &pod)
+ {
+ if (! jo.is_object()) { jo = json::object(); }
+
+
+
+ addToJsonIfNeeded(jo, "displayname"s, pod.displayname);
+
+ addToJsonIfNeeded(jo, "avatar_url"s, pod.avatarUrl);
+ }
+
static void from_json(const json &jo, SearchJob::UserProfile& result)
{
if (jo.contains("displayname"s)) {
result.displayname = jo.at("displayname"s);
}
if (jo.contains("avatar_url"s)) {
result.avatarUrl = jo.at("avatar_url"s);
}
}
+
};
template<>
struct adl_serializer<SearchJob::EventContext> {
+ static void to_json(json& jo, const SearchJob::EventContext &pod)
+ {
+ if (! jo.is_object()) { jo = json::object(); }
+
+
+
+ addToJsonIfNeeded(jo, "start"s, pod.start);
+
+ addToJsonIfNeeded(jo, "end"s, pod.end);
+
+ addToJsonIfNeeded(jo, "profile_info"s, pod.profileInfo);
+
+ addToJsonIfNeeded(jo, "events_before"s, pod.eventsBefore);
+
+ addToJsonIfNeeded(jo, "events_after"s, pod.eventsAfter);
+ }
+
static void from_json(const json &jo, SearchJob::EventContext& result)
{
if (jo.contains("start"s)) {
result.start = jo.at("start"s);
}
if (jo.contains("end"s)) {
result.end = jo.at("end"s);
}
if (jo.contains("profile_info"s)) {
result.profileInfo = jo.at("profile_info"s);
}
if (jo.contains("events_before"s)) {
result.eventsBefore = jo.at("events_before"s);
}
if (jo.contains("events_after"s)) {
result.eventsAfter = jo.at("events_after"s);
}
}
+
};
template<>
struct adl_serializer<SearchJob::Result> {
+ static void to_json(json& jo, const SearchJob::Result &pod)
+ {
+ if (! jo.is_object()) { jo = json::object(); }
+
+
+
+ addToJsonIfNeeded(jo, "rank"s, pod.rank);
+
+ addToJsonIfNeeded(jo, "result"s, pod.result);
+
+ addToJsonIfNeeded(jo, "context"s, pod.context);
+ }
+
static void from_json(const json &jo, SearchJob::Result& result)
{
if (jo.contains("rank"s)) {
result.rank = jo.at("rank"s);
}
if (jo.contains("result"s)) {
result.result = jo.at("result"s);
}
if (jo.contains("context"s)) {
result.context = jo.at("context"s);
}
}
+
};
template<>
struct adl_serializer<SearchJob::GroupValue> {
+ static void to_json(json& jo, const SearchJob::GroupValue &pod)
+ {
+ if (! jo.is_object()) { jo = json::object(); }
+
+
+
+ addToJsonIfNeeded(jo, "next_batch"s, pod.nextBatch);
+
+ addToJsonIfNeeded(jo, "order"s, pod.order);
+
+ addToJsonIfNeeded(jo, "results"s, pod.results);
+ }
+
static void from_json(const json &jo, SearchJob::GroupValue& result)
{
if (jo.contains("next_batch"s)) {
result.nextBatch = jo.at("next_batch"s);
}
if (jo.contains("order"s)) {
result.order = jo.at("order"s);
}
if (jo.contains("results"s)) {
result.results = jo.at("results"s);
}
}
+
};
template<>
struct adl_serializer<SearchJob::ResultRoomEvents> {
+ static void to_json(json& jo, const SearchJob::ResultRoomEvents &pod)
+ {
+ if (! jo.is_object()) { jo = json::object(); }
+
+
+
+ addToJsonIfNeeded(jo, "count"s, pod.count);
+
+ addToJsonIfNeeded(jo, "highlights"s, pod.highlights);
+
+ addToJsonIfNeeded(jo, "results"s, pod.results);
+
+ addToJsonIfNeeded(jo, "state"s, pod.state);
+
+ addToJsonIfNeeded(jo, "groups"s, pod.groups);
+
+ addToJsonIfNeeded(jo, "next_batch"s, pod.nextBatch);
+ }
+
static void from_json(const json &jo, SearchJob::ResultRoomEvents& result)
{
if (jo.contains("count"s)) {
result.count = jo.at("count"s);
}
if (jo.contains("highlights"s)) {
result.highlights = jo.at("highlights"s);
}
if (jo.contains("results"s)) {
result.results = jo.at("results"s);
}
if (jo.contains("state"s)) {
result.state = jo.at("state"s);
}
if (jo.contains("groups"s)) {
result.groups = jo.at("groups"s);
}
if (jo.contains("next_batch"s)) {
result.nextBatch = jo.at("next_batch"s);
}
}
+
};
template<>
struct adl_serializer<SearchJob::ResultCategories> {
+ static void to_json(json& jo, const SearchJob::ResultCategories &pod)
+ {
+ if (! jo.is_object()) { jo = json::object(); }
+
+
+
+ addToJsonIfNeeded(jo, "room_events"s, pod.roomEvents);
+ }
+
static void from_json(const json &jo, SearchJob::ResultCategories& result)
{
if (jo.contains("room_events"s)) {
result.roomEvents = jo.at("room_events"s);
}
}
+
};
}
namespace Kazv
{
} // namespace Kazv
diff --git a/src/api/csapi/sync.hpp b/src/api/csapi/sync.hpp
index 68a352e..b13de9e 100644
--- a/src/api/csapi/sync.hpp
+++ b/src/api/csapi/sync.hpp
@@ -1,502 +1,596 @@
/******************************************************************************
* THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN
*/
#pragma once
#include "basejob.hpp"
#include "event.hpp"
#include "csapi/definitions/event_batch.hpp"
#include "csapi/definitions/state_event_batch.hpp"
#include "csapi/definitions/timeline_batch.hpp"
namespace Kazv {
/*! \brief Synchronise the client's state and receive new messages.
*
* Synchronise the client's state with the latest state on the server.
* Clients use this API when they first log in to get an initial snapshot
* of the state on the server, and then continue to call this API to get
* incremental deltas to the state, and to receive new messages.
*
* *Note*: This endpoint supports lazy-loading. See `Filtering <#filtering>`_
* for more information. Lazy-loading members is only supported on a ``StateFilter``
* for this endpoint. When lazy-loading is enabled, servers MUST include the
* syncing user's own membership event when they join a room, or when the
* full state of rooms is requested, to aid discovering the user's avatar &
* displayname.
*
* Further, like other members, the user's own membership event is eligible
* for being considered redundant by the server. When a sync is ``limited``,
* the server MUST return membership events for events in the gap
* (between ``since`` and the start of the returned timeline), regardless
* as to whether or not they are redundant. This ensures that joins/leaves
* and profile changes which occur during the gap are not lost.
*
* Note that the default behaviour of ``state`` is to include all membership
* events, alongside other state, when lazy-loading is not enabled.
*/
class SyncJob : public BaseJob {
public:
// Inner data structures
/// Information about the room which clients may need to
/// correctly render it to users.
struct RoomSummary
{
/// The users which can be used to generate a room name
/// if the room does not have one. Required if the room's
/// ``m.room.name`` or ``m.room.canonical_alias`` state events
/// are unset or empty.
///
/// This should be the first 5 members of the room, ordered
/// by stream ordering, which are joined or invited. The
/// list must never include the client's own user ID. When
/// no joined or invited members are available, this should
/// consist of the banned and left users. More than 5 members
/// may be provided, however less than 5 should only be provided
/// when there are less than 5 members to represent.
///
/// When lazy-loading room members is enabled, the membership
/// events for the heroes MUST be included in the ``state``,
/// unless they are redundant. When the list of users changes,
/// the server notifies the client by sending a fresh list of
/// heroes. If there are no changes since the last sync, this
/// field may be omitted.
immer::array<std::string> mHeroes;
/// The number of users with ``membership`` of ``join``,
/// including the client's own user ID. If this field has
/// not changed since the last sync, it may be omitted.
/// Required otherwise.
std::optional<int> mJoinedMemberCount;
/// The number of users with ``membership`` of ``invite``.
/// If this field has not changed since the last sync, it
/// may be omitted. Required otherwise.
std::optional<int> mInvitedMemberCount;
};
/// Counts of unread notifications for this room. See the
/// `Receiving notifications section <#receiving-notifications>`_
/// for more information on how these are calculated.
struct UnreadNotificationCounts
{
/// The number of unread notifications for this room with the highlight flag set
std::optional<int> highlightCount;
/// The total number of unread notifications for this room
std::optional<int> notificationCount;
};
/// Synchronise the client's state with the latest state on the server.
/// Clients use this API when they first log in to get an initial snapshot
/// of the state on the server, and then continue to call this API to get
/// incremental deltas to the state, and to receive new messages.
///
/// *Note*: This endpoint supports lazy-loading. See `Filtering <#filtering>`_
/// for more information. Lazy-loading members is only supported on a ``StateFilter``
/// for this endpoint. When lazy-loading is enabled, servers MUST include the
/// syncing user's own membership event when they join a room, or when the
/// full state of rooms is requested, to aid discovering the user's avatar &
/// displayname.
///
/// Further, like other members, the user's own membership event is eligible
/// for being considered redundant by the server. When a sync is ``limited``,
/// the server MUST return membership events for events in the gap
/// (between ``since`` and the start of the returned timeline), regardless
/// as to whether or not they are redundant. This ensures that joins/leaves
/// and profile changes which occur during the gap are not lost.
///
/// Note that the default behaviour of ``state`` is to include all membership
/// events, alongside other state, when lazy-loading is not enabled.
struct JoinedRoom
{
/// Information about the room which clients may need to
/// correctly render it to users.
std::optional<RoomSummary> summary;
/// Updates to the state, between the time indicated by
/// the ``since`` parameter, and the start of the
/// ``timeline`` (or all state up to the start of the
/// ``timeline``, if ``since`` is not given, or
/// ``full_state`` is true).
///
/// N.B. state updates for ``m.room.member`` events will
/// be incomplete if ``lazy_load_members`` is enabled in
/// the ``/sync`` filter, and only return the member events
/// required to display the senders of the timeline events
/// in this response.
std::optional<StateEventBatch> state;
/// The timeline of messages and state changes in the
/// room.
Timeline timeline;
/// The ephemeral events in the room that aren't
/// recorded in the timeline or state of the room.
/// e.g. typing.
std::optional<EventBatch> ephemeral;
/// The private data that this user has attached to
/// this room.
std::optional<EventBatch> accountData;
/// Counts of unread notifications for this room. See the
/// `Receiving notifications section <#receiving-notifications>`_
/// for more information on how these are calculated.
std::optional<UnreadNotificationCounts> unreadNotifications;
};
/// The state of a room that the user has been invited
/// to. These state events may only have the ``sender``,
/// ``type``, ``state_key`` and ``content`` keys
/// present. These events do not replace any state that
/// the client already has for the room, for example if
/// the client has archived the room. Instead the
/// client should keep two separate copies of the
/// state: the one from the ``invite_state`` and one
/// from the archived ``state``. If the client joins
/// the room then the current state will be given as a
/// delta against the archived ``state`` not the
/// ``invite_state``.
struct InviteState
{
/// The StrippedState events that form the invite state.
EventList events;
};
/// Synchronise the client's state with the latest state on the server.
/// Clients use this API when they first log in to get an initial snapshot
/// of the state on the server, and then continue to call this API to get
/// incremental deltas to the state, and to receive new messages.
///
/// *Note*: This endpoint supports lazy-loading. See `Filtering <#filtering>`_
/// for more information. Lazy-loading members is only supported on a ``StateFilter``
/// for this endpoint. When lazy-loading is enabled, servers MUST include the
/// syncing user's own membership event when they join a room, or when the
/// full state of rooms is requested, to aid discovering the user's avatar &
/// displayname.
///
/// Further, like other members, the user's own membership event is eligible
/// for being considered redundant by the server. When a sync is ``limited``,
/// the server MUST return membership events for events in the gap
/// (between ``since`` and the start of the returned timeline), regardless
/// as to whether or not they are redundant. This ensures that joins/leaves
/// and profile changes which occur during the gap are not lost.
///
/// Note that the default behaviour of ``state`` is to include all membership
/// events, alongside other state, when lazy-loading is not enabled.
struct InvitedRoom
{
/// The state of a room that the user has been invited
/// to. These state events may only have the ``sender``,
/// ``type``, ``state_key`` and ``content`` keys
/// present. These events do not replace any state that
/// the client already has for the room, for example if
/// the client has archived the room. Instead the
/// client should keep two separate copies of the
/// state: the one from the ``invite_state`` and one
/// from the archived ``state``. If the client joins
/// the room then the current state will be given as a
/// delta against the archived ``state`` not the
/// ``invite_state``.
std::optional<InviteState> inviteState;
};
/// Synchronise the client's state with the latest state on the server.
/// Clients use this API when they first log in to get an initial snapshot
/// of the state on the server, and then continue to call this API to get
/// incremental deltas to the state, and to receive new messages.
///
/// *Note*: This endpoint supports lazy-loading. See `Filtering <#filtering>`_
/// for more information. Lazy-loading members is only supported on a ``StateFilter``
/// for this endpoint. When lazy-loading is enabled, servers MUST include the
/// syncing user's own membership event when they join a room, or when the
/// full state of rooms is requested, to aid discovering the user's avatar &
/// displayname.
///
/// Further, like other members, the user's own membership event is eligible
/// for being considered redundant by the server. When a sync is ``limited``,
/// the server MUST return membership events for events in the gap
/// (between ``since`` and the start of the returned timeline), regardless
/// as to whether or not they are redundant. This ensures that joins/leaves
/// and profile changes which occur during the gap are not lost.
///
/// Note that the default behaviour of ``state`` is to include all membership
/// events, alongside other state, when lazy-loading is not enabled.
struct LeftRoom
{
/// The state updates for the room up to the start of the timeline.
std::optional<StateEventBatch> state;
/// The timeline of messages and state changes in the
/// room up to the point when the user left.
Timeline timeline;
/// The private data that this user has attached to
/// this room.
std::optional<EventBatch> accountData;
};
/// Updates to rooms.
struct Rooms
{
/// The rooms that the user has joined, mapped as room ID to
/// room information.
immer::map<std::string, JoinedRoom> join;
/// The rooms that the user has been invited to, mapped as room ID to
/// room information.
immer::map<std::string, InvitedRoom> invite;
/// The rooms that the user has left or been banned from, mapped as room ID to
/// room information.
immer::map<std::string, LeftRoom> leave;
};
class JobResponse : public Response
{
public:
JobResponse(Response r);
bool success() const;
// Result properties
/// The batch token to supply in the ``since`` param of the next
/// ``/sync`` request.
std::string nextBatch() const;
/// Updates to rooms.
std::optional<Rooms> rooms() const;
/// The updates to the presence status of other users.
std::optional<EventBatch> presence() const;
/// The global private data created by this user.
std::optional<EventBatch> accountData() const;
/// Information on the send-to-device messages for the client
/// device, as defined in |send_to_device_sync|_.
JsonWrap toDevice() const;
/// Information on end-to-end device updates, as specified in
/// |device_lists_sync|_.
JsonWrap deviceLists() const;
/// Information on end-to-end encryption keys, as specified
/// in |device_lists_sync|_.
immer::map<std::string, int> deviceOneTimeKeysCount() const;
};
static constexpr auto needsAuth() {
return true
;
}
// Construction/destruction
/*! \brief Synchronise the client's state and receive new messages.
*
* \param filter
* The ID of a filter created using the filter API or a filter JSON
* object encoded as a string. The server will detect whether it is
* an ID or a JSON object by whether the first character is a ``"{"``
* open brace. Passing the JSON inline is best suited to one off
* requests. Creating a filter using the filter API is recommended for
* clients that reuse the same filter multiple times, for example in
* long poll requests.
*
* See `Filtering <#filtering>`_ for more information.
*
* \param since
* A point in time to continue a sync from.
*
* \param fullState
* Controls whether to include the full state for all rooms the user
* is a member of.
*
* If this is set to ``true``, then all state events will be returned,
* even if ``since`` is non-empty. The timeline will still be limited
* by the ``since`` parameter. In this case, the ``timeout`` parameter
* will be ignored and the query will return immediately, possibly with
* an empty timeline.
*
* If ``false``, and ``since`` is non-empty, only state which has
* changed since the point indicated by ``since`` will be returned.
*
* By default, this is ``false``.
*
* \param setPresence
* Controls whether the client is automatically marked as online by
* polling this API. If this parameter is omitted then the client is
* automatically marked as online when it uses this API. Otherwise if
* the parameter is set to "offline" then the client is not marked as
* being online when it uses this API. When set to "unavailable", the
* client is marked as being idle.
*
* \param timeout
* The maximum time to wait, in milliseconds, before returning this
* request. If no events (or other data) become available before this
* time elapses, the server will return a response with empty fields.
*
* By default, this is ``0``, so the server will return immediately
* even if the response is empty.
*/
explicit SyncJob(std::string serverUrl
, std::string _accessToken
,
std::optional<std::string> filter = std::nullopt, std::optional<std::string> since = std::nullopt, std::optional<bool> fullState = std::nullopt, std::optional<std::string> setPresence = std::nullopt, std::optional<int> timeout = std::nullopt);
static BaseJob::Query buildQuery(
std::optional<std::string> filter, std::optional<std::string> since, std::optional<bool> fullState, std::optional<std::string> setPresence, std::optional<int> timeout);
static BaseJob::Body buildBody(std::optional<std::string> filter, std::optional<std::string> since, std::optional<bool> fullState, std::optional<std::string> setPresence, std::optional<int> timeout);
SyncJob withData(JsonWrap j) &&;
SyncJob withData(JsonWrap j) const &;
};
using SyncResponse = SyncJob::JobResponse;
}
namespace nlohmann
{
using namespace Kazv;
template<>
struct adl_serializer<SyncJob::RoomSummary> {
+ static void to_json(json& jo, const SyncJob::RoomSummary &pod)
+ {
+ if (! jo.is_object()) { jo = json::object(); }
+
+
+
+ addToJsonIfNeeded(jo, "m.heroes"s, pod.mHeroes);
+
+ addToJsonIfNeeded(jo, "m.joined_member_count"s, pod.mJoinedMemberCount);
+
+ addToJsonIfNeeded(jo, "m.invited_member_count"s, pod.mInvitedMemberCount);
+ }
+
static void from_json(const json &jo, SyncJob::RoomSummary& result)
{
if (jo.contains("m.heroes"s)) {
result.mHeroes = jo.at("m.heroes"s);
}
if (jo.contains("m.joined_member_count"s)) {
result.mJoinedMemberCount = jo.at("m.joined_member_count"s);
}
if (jo.contains("m.invited_member_count"s)) {
result.mInvitedMemberCount = jo.at("m.invited_member_count"s);
}
}
+
};
template<>
struct adl_serializer<SyncJob::UnreadNotificationCounts> {
+ static void to_json(json& jo, const SyncJob::UnreadNotificationCounts &pod)
+ {
+ if (! jo.is_object()) { jo = json::object(); }
+
+
+
+ addToJsonIfNeeded(jo, "highlight_count"s, pod.highlightCount);
+
+ addToJsonIfNeeded(jo, "notification_count"s, pod.notificationCount);
+ }
+
static void from_json(const json &jo, SyncJob::UnreadNotificationCounts& result)
{
if (jo.contains("highlight_count"s)) {
result.highlightCount = jo.at("highlight_count"s);
}
if (jo.contains("notification_count"s)) {
result.notificationCount = jo.at("notification_count"s);
}
}
+
};
template<>
struct adl_serializer<SyncJob::JoinedRoom> {
+ static void to_json(json& jo, const SyncJob::JoinedRoom &pod)
+ {
+ if (! jo.is_object()) { jo = json::object(); }
+
+
+
+ addToJsonIfNeeded(jo, "summary"s, pod.summary);
+
+ addToJsonIfNeeded(jo, "state"s, pod.state);
+
+ addToJsonIfNeeded(jo, "timeline"s, pod.timeline);
+
+ addToJsonIfNeeded(jo, "ephemeral"s, pod.ephemeral);
+
+ addToJsonIfNeeded(jo, "account_data"s, pod.accountData);
+
+ addToJsonIfNeeded(jo, "unread_notifications"s, pod.unreadNotifications);
+ }
+
static void from_json(const json &jo, SyncJob::JoinedRoom& result)
{
if (jo.contains("summary"s)) {
result.summary = jo.at("summary"s);
}
if (jo.contains("state"s)) {
result.state = jo.at("state"s);
}
if (jo.contains("timeline"s)) {
result.timeline = jo.at("timeline"s);
}
if (jo.contains("ephemeral"s)) {
result.ephemeral = jo.at("ephemeral"s);
}
if (jo.contains("account_data"s)) {
result.accountData = jo.at("account_data"s);
}
if (jo.contains("unread_notifications"s)) {
result.unreadNotifications = jo.at("unread_notifications"s);
}
}
+
};
template<>
struct adl_serializer<SyncJob::InviteState> {
+ static void to_json(json& jo, const SyncJob::InviteState &pod)
+ {
+ if (! jo.is_object()) { jo = json::object(); }
+
+
+
+ addToJsonIfNeeded(jo, "events"s, pod.events);
+ }
+
static void from_json(const json &jo, SyncJob::InviteState& result)
{
if (jo.contains("events"s)) {
result.events = jo.at("events"s);
}
}
+
};
template<>
struct adl_serializer<SyncJob::InvitedRoom> {
+ static void to_json(json& jo, const SyncJob::InvitedRoom &pod)
+ {
+ if (! jo.is_object()) { jo = json::object(); }
+
+
+
+ addToJsonIfNeeded(jo, "invite_state"s, pod.inviteState);
+ }
+
static void from_json(const json &jo, SyncJob::InvitedRoom& result)
{
if (jo.contains("invite_state"s)) {
result.inviteState = jo.at("invite_state"s);
}
}
+
};
template<>
struct adl_serializer<SyncJob::LeftRoom> {
+ static void to_json(json& jo, const SyncJob::LeftRoom &pod)
+ {
+ if (! jo.is_object()) { jo = json::object(); }
+
+
+
+ addToJsonIfNeeded(jo, "state"s, pod.state);
+
+ addToJsonIfNeeded(jo, "timeline"s, pod.timeline);
+
+ addToJsonIfNeeded(jo, "account_data"s, pod.accountData);
+ }
+
static void from_json(const json &jo, SyncJob::LeftRoom& result)
{
if (jo.contains("state"s)) {
result.state = jo.at("state"s);
}
if (jo.contains("timeline"s)) {
result.timeline = jo.at("timeline"s);
}
if (jo.contains("account_data"s)) {
result.accountData = jo.at("account_data"s);
}
}
+
};
template<>
struct adl_serializer<SyncJob::Rooms> {
+ static void to_json(json& jo, const SyncJob::Rooms &pod)
+ {
+ if (! jo.is_object()) { jo = json::object(); }
+
+
+
+ addToJsonIfNeeded(jo, "join"s, pod.join);
+
+ addToJsonIfNeeded(jo, "invite"s, pod.invite);
+
+ addToJsonIfNeeded(jo, "leave"s, pod.leave);
+ }
+
static void from_json(const json &jo, SyncJob::Rooms& result)
{
if (jo.contains("join"s)) {
result.join = jo.at("join"s);
}
if (jo.contains("invite"s)) {
result.invite = jo.at("invite"s);
}
if (jo.contains("leave"s)) {
result.leave = jo.at("leave"s);
}
}
+
};
}
namespace Kazv
{
} // namespace Kazv
diff --git a/src/api/csapi/tags.hpp b/src/api/csapi/tags.hpp
index 2123874..d7c5787 100644
--- a/src/api/csapi/tags.hpp
+++ b/src/api/csapi/tags.hpp
@@ -1,244 +1,254 @@
/******************************************************************************
* THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN
*/
#pragma once
#include "basejob.hpp"
namespace Kazv {
/*! \brief List the tags for a room.
*
* List the tags set by a user on a room.
*/
class GetRoomTagsJob : public BaseJob {
public:
// Inner data structures
/// List the tags set by a user on a room.
struct Tag
{
/// A number in a range ``[0,1]`` describing a relative
/// position of the room under the given tag.
std::optional<float> order;
/// List the tags set by a user on a room.
JsonWrap additionalProperties;
};
class JobResponse : public Response
{
public:
JobResponse(Response r);
bool success() const;
// Result properties
/// List the tags set by a user on a room.
immer::map<std::string, Tag> tags() const;
};
static constexpr auto needsAuth() {
return true
;
}
// Construction/destruction
/*! \brief List the tags for a room.
*
* \param userId
* The id of the user to get tags for. The access token must be
* authorized to make requests for this user ID.
*
* \param roomId
* The ID of the room to get tags for.
*/
explicit GetRoomTagsJob(std::string serverUrl
, std::string _accessToken
,
std::string userId , std::string roomId );
static BaseJob::Query buildQuery(
);
static BaseJob::Body buildBody(std::string userId, std::string roomId);
GetRoomTagsJob withData(JsonWrap j) &&;
GetRoomTagsJob withData(JsonWrap j) const &;
};
using GetRoomTagsResponse = GetRoomTagsJob::JobResponse;
}
namespace nlohmann
{
using namespace Kazv;
template<>
struct adl_serializer<GetRoomTagsJob::Tag> {
+ static void to_json(json& jo, const GetRoomTagsJob::Tag &pod)
+ {
+ if (! jo.is_object()) { jo = json::object(); }
+
+ addPropertyMapToJson(jo, pod.additionalProperties);
+
+ addToJsonIfNeeded(jo, "order"s, pod.order);
+ }
+
static void from_json(const json &jo, GetRoomTagsJob::Tag& result)
{
if (jo.contains("order"s)) {
result.order = jo.at("order"s);
}
result.additionalProperties = jo;
}
+
};
}
namespace Kazv
{
/*! \brief Add a tag to a room.
*
* Add a tag to the room.
*/
class SetRoomTagJob : public BaseJob {
public:
class JobResponse : public Response
{
public:
JobResponse(Response r);
bool success() const;
};
static constexpr auto needsAuth() {
return true
;
}
// Construction/destruction
/*! \brief Add a tag to a room.
*
* \param userId
* The id of the user to add a tag for. The access token must be
* authorized to make requests for this user ID.
*
* \param roomId
* The ID of the room to add a tag to.
*
* \param tag
* The tag to add.
*
* \param order
* A number in a range ``[0,1]`` describing a relative
* position of the room under the given tag.
*
* \param additionalProperties
* Add a tag to the room.
*/
explicit SetRoomTagJob(std::string serverUrl
, std::string _accessToken
,
std::string userId , std::string roomId , std::string tag , std::optional<float> order = std::nullopt, JsonWrap additionalProperties = {});
static BaseJob::Query buildQuery(
);
static BaseJob::Body buildBody(std::string userId, std::string roomId, std::string tag, std::optional<float> order, JsonWrap additionalProperties);
SetRoomTagJob withData(JsonWrap j) &&;
SetRoomTagJob withData(JsonWrap j) const &;
};
using SetRoomTagResponse = SetRoomTagJob::JobResponse;
}
namespace nlohmann
{
using namespace Kazv;
}
namespace Kazv
{
/*! \brief Remove a tag from the room.
*
* Remove a tag from the room.
*/
class DeleteRoomTagJob : public BaseJob {
public:
class JobResponse : public Response
{
public:
JobResponse(Response r);
bool success() const;
};
static constexpr auto needsAuth() {
return true
;
}
// Construction/destruction
/*! \brief Remove a tag from the room.
*
* \param userId
* The id of the user to remove a tag for. The access token must be
* authorized to make requests for this user ID.
*
* \param roomId
* The ID of the room to remove a tag from.
*
* \param tag
* The tag to remove.
*/
explicit DeleteRoomTagJob(std::string serverUrl
, std::string _accessToken
,
std::string userId , std::string roomId , std::string tag );
static BaseJob::Query buildQuery(
);
static BaseJob::Body buildBody(std::string userId, std::string roomId, std::string tag);
DeleteRoomTagJob withData(JsonWrap j) &&;
DeleteRoomTagJob withData(JsonWrap j) const &;
};
using DeleteRoomTagResponse = DeleteRoomTagJob::JobResponse;
}
namespace nlohmann
{
using namespace Kazv;
}
namespace Kazv
{
} // namespace Kazv
diff --git a/src/api/csapi/users.hpp b/src/api/csapi/users.hpp
index 08433ab..d79aad0 100644
--- a/src/api/csapi/users.hpp
+++ b/src/api/csapi/users.hpp
@@ -1,134 +1,148 @@
/******************************************************************************
* THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN
*/
#pragma once
#include "basejob.hpp"
namespace Kazv {
/*! \brief Searches the user directory.
*
* Performs a search for users. The homeserver may
* determine which subset of users are searched, however the homeserver
* MUST at a minimum consider the users the requesting user shares a
* room with and those who reside in public rooms (known to the homeserver).
* The search MUST consider local users to the homeserver, and SHOULD
* query remote users as part of the search.
*
* The search is performed case-insensitively on user IDs and display
* names preferably using a collation determined based upon the
* ``Accept-Language`` header provided in the request, if present.
*/
class SearchUserDirectoryJob : public BaseJob {
public:
// Inner data structures
/// Performs a search for users. The homeserver may
/// determine which subset of users are searched, however the homeserver
/// MUST at a minimum consider the users the requesting user shares a
/// room with and those who reside in public rooms (known to the homeserver).
/// The search MUST consider local users to the homeserver, and SHOULD
/// query remote users as part of the search.
///
/// The search is performed case-insensitively on user IDs and display
/// names preferably using a collation determined based upon the
/// ``Accept-Language`` header provided in the request, if present.
struct User
{
/// The user's matrix user ID.
std::string userId;
/// The display name of the user, if one exists.
std::optional<std::string> displayName;
/// The avatar url, as an MXC, if one exists.
std::optional<std::string> avatarUrl;
};
class JobResponse : public Response
{
public:
JobResponse(Response r);
bool success() const;
// Result properties
/// Ordered by rank and then whether or not profile info is available.
immer::array<User> results() const;
/// Indicates if the result list has been truncated by the limit.
bool limited() const;
};
static constexpr auto needsAuth() {
return true
;
}
// Construction/destruction
/*! \brief Searches the user directory.
*
* \param searchTerm
* The term to search for
*
* \param limit
* The maximum number of results to return. Defaults to 10.
*/
explicit SearchUserDirectoryJob(std::string serverUrl
, std::string _accessToken
,
std::string searchTerm , std::optional<int> limit = std::nullopt);
static BaseJob::Query buildQuery(
);
static BaseJob::Body buildBody(std::string searchTerm, std::optional<int> limit);
SearchUserDirectoryJob withData(JsonWrap j) &&;
SearchUserDirectoryJob withData(JsonWrap j) const &;
};
using SearchUserDirectoryResponse = SearchUserDirectoryJob::JobResponse;
}
namespace nlohmann
{
using namespace Kazv;
template<>
struct adl_serializer<SearchUserDirectoryJob::User> {
+ static void to_json(json& jo, const SearchUserDirectoryJob::User &pod)
+ {
+ if (! jo.is_object()) { jo = json::object(); }
+
+
+ jo["user_id"s] = pod.userId;
+
+
+ addToJsonIfNeeded(jo, "display_name"s, pod.displayName);
+
+ addToJsonIfNeeded(jo, "avatar_url"s, pod.avatarUrl);
+ }
+
static void from_json(const json &jo, SearchUserDirectoryJob::User& result)
{
if (jo.contains("user_id"s)) {
result.userId = jo.at("user_id"s);
}
if (jo.contains("display_name"s)) {
result.displayName = jo.at("display_name"s);
}
if (jo.contains("avatar_url"s)) {
result.avatarUrl = jo.at("avatar_url"s);
}
}
+
};
}
namespace Kazv
{
} // namespace Kazv
diff --git a/src/base/kazvevents.hpp b/src/base/kazvevents.hpp
index fa54dc2..de4ccba 100644
--- a/src/base/kazvevents.hpp
+++ b/src/base/kazvevents.hpp
@@ -1,386 +1,387 @@
/*
* Copyright (C) 2020 Tusooa Zhu
*
* This file is part of libkazv.
*
* libkazv is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* libkazv is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with libkazv. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <variant>
#include "types.hpp"
#include "event.hpp"
#include "basejob.hpp"
namespace Kazv
{
struct LoginSuccessful {};
struct LoginFailed
{
std::string errorCode;
std::string error;
};
struct SyncSuccessful
{
std::string nextToken;
+ bool isInitialSync;
};
struct SyncFailed
{
};
struct PostInitialFiltersSuccessful
{
};
struct PostInitialFiltersFailed
{
std::string errorCode;
std::string error;
};
struct ReceivingPresenceEvent { Event event; };
struct ReceivingAccountDataEvent { Event event; };
struct ReceivingRoomStateEvent {
Event event;
std::string roomId;
};
struct ReceivingRoomTimelineEvent {
Event event;
std::string roomId;
};
struct ReceivingRoomAccountDataEvent {
Event event;
std::string roomId;
};
struct ReceivingToDeviceMessage
{
Event event;
};
struct RoomMembershipChanged {
RoomMembership membership;
std::string roomId;
};
struct PaginateSuccessful
{
std::string roomId;
};
struct PaginateFailed
{
std::string roomId;
};
struct CreateRoomSuccessful
{
std::string roomId;
};
struct CreateRoomFailed
{
std::string errorCode;
std::string error;
};
struct InviteUserSuccessful
{
std::string roomId;
std::string userId;
};
struct InviteUserFailed
{
std::string roomId;
std::string userId;
std::string errorCode;
std::string error;
};
struct JoinRoomSuccessful
{
std::string roomIdOrAlias;
};
struct JoinRoomFailed
{
std::string roomIdOrAlias;
std::string errorCode;
std::string error;
};
struct LeaveRoomSuccessful
{
std::string roomId;
};
struct LeaveRoomFailed
{
std::string roomId;
std::string errorCode;
std::string error;
};
struct ForgetRoomSuccessful
{
std::string roomId;
};
struct ForgetRoomFailed
{
std::string roomId;
std::string errorCode;
std::string error;
};
struct SendMessageSuccessful
{
std::string roomId;
std::string eventId;
};
struct SendMessageFailed
{
std::string roomId;
std::string errorCode;
std::string error;
};
struct SendToDeviceMessageSuccessful
{
std::string userId;
std::string deviceId;
std::string txnId;
};
struct SendToDeviceMessageFailed
{
std::string userId;
std::string deviceId;
std::string txnId;
std::string errorCode;
std::string error;
};
struct InvalidMessageFormat
{
};
struct GetRoomStatesSuccessful
{
std::string roomId;
};
struct GetRoomStatesFailed
{
std::string roomId;
std::string errorCode;
std::string error;
};
struct GetStateEventSuccessful
{
std::string roomId;
JsonWrap content;
};
struct GetStateEventFailed
{
std::string roomId;
std::string errorCode;
std::string error;
};
struct SendStateEventSuccessful
{
std::string roomId;
std::string eventId;
std::string eventType;
std::string stateKey;
};
struct SendStateEventFailed
{
std::string roomId;
std::string eventType;
std::string stateKey;
std::string errorCode;
std::string error;
};
struct SetTypingSuccessful
{
std::string roomId;
};
struct SetTypingFailed
{
std::string roomId;
std::string errorCode;
std::string error;
};
struct PostReceiptSuccessful
{
std::string roomId;
};
struct PostReceiptFailed
{
std::string roomId;
std::string errorCode;
std::string error;
};
struct SetReadMarkerSuccessful
{
std::string roomId;
};
struct SetReadMarkerFailed
{
std::string roomId;
std::string errorCode;
std::string error;
};
struct UploadContentSuccessful
{
std::string mxcUri;
std::string uploadId;
};
struct UploadContentFailed
{
std::string uploadId;
std::string errorCode;
std::string error;
};
struct DownloadContentSuccessful
{
std::string mxcUri;
immer::box<Bytes> content;
std::optional<std::string> filename;
std::optional<std::string> contentType;
};
struct DownloadContentFailed
{
std::string mxcUri;
std::string errorCode;
std::string error;
};
struct DownloadThumbnailSuccessful
{
std::string mxcUri;
immer::box<Bytes> content;
std::optional<std::string> contentType;
};
struct DownloadThumbnailFailed
{
std::string mxcUri;
std::string errorCode;
std::string error;
};
struct UploadIdentityKeysSuccessful
{
};
struct UploadIdentityKeysFailed
{
std::string errorCode;
std::string error;
};
struct UploadOneTimeKeysSuccessful
{
};
struct UploadOneTimeKeysFailed
{
std::string errorCode;
std::string error;
};
struct UnrecognizedResponse
{
Response response;
};
using KazvEvent = std::variant<
// use this for placeholder of "no events yet"
// otherwise the first LoginSuccessful event cannot be detected
std::monostate,
// matrix events
ReceivingPresenceEvent,
ReceivingAccountDataEvent,
ReceivingRoomTimelineEvent,
ReceivingRoomStateEvent,
RoomMembershipChanged,
ReceivingRoomAccountDataEvent,
ReceivingToDeviceMessage,
// auth
LoginSuccessful, LoginFailed,
// sync
SyncSuccessful, SyncFailed,
PostInitialFiltersSuccessful, PostInitialFiltersFailed,
// paginate
PaginateSuccessful, PaginateFailed,
// membership
CreateRoomSuccessful, CreateRoomFailed,
InviteUserSuccessful, InviteUserFailed,
JoinRoomSuccessful, JoinRoomFailed,
LeaveRoomSuccessful, LeaveRoomFailed,
ForgetRoomSuccessful, ForgetRoomFailed,
// send
SendMessageSuccessful, SendMessageFailed,
SendToDeviceMessageSuccessful, SendToDeviceMessageFailed,
InvalidMessageFormat,
// states
GetRoomStatesSuccessful, GetRoomStatesFailed,
GetStateEventSuccessful, GetStateEventFailed,
SendStateEventSuccessful, SendStateEventFailed,
// ephemeral
SetTypingSuccessful, SetTypingFailed,
PostReceiptSuccessful, PostReceiptFailed,
SetReadMarkerSuccessful, SetReadMarkerFailed,
// content
UploadContentSuccessful, UploadContentFailed,
DownloadContentSuccessful, DownloadContentFailed,
DownloadThumbnailSuccessful, DownloadThumbnailFailed,
// encryption
UploadIdentityKeysSuccessful, UploadIdentityKeysFailed,
UploadOneTimeKeysSuccessful, UploadOneTimeKeysFailed,
// general
UnrecognizedResponse
>;
using KazvEventList = immer::flex_vector<KazvEvent>;
}
diff --git a/src/client/CMakeLists.txt b/src/client/CMakeLists.txt
index ad52bf2..b266a8e 100644
--- a/src/client/CMakeLists.txt
+++ b/src/client/CMakeLists.txt
@@ -1,31 +1,32 @@
set(kazvclient_SRCS
sdk-model.cpp
client-model.cpp
actions/auth.cpp
actions/sync.cpp
actions/paginate.cpp
actions/membership.cpp
actions/states.cpp
actions/send.cpp
actions/ephemeral.cpp
actions/content.cpp
actions/encryption.cpp
+ device-list-tracker.cpp
room/room-model.cpp
)
add_library(kazvclient ${kazvclient_SRCS})
add_library(libkazv::kazvclient ALIAS kazvclient)
set_target_properties(kazvclient PROPERTIES VERSION ${libkazv_VERSION_STRING} SOVERSION 0)
target_link_libraries(kazvclient PUBLIC kazvbase kazvapi kazvcrypto)
target_include_directories(kazvclient PRIVATE .)
target_include_directories(kazvclient
INTERFACE
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
$<INSTALL_INTERFACE:include/kazv/client>
)
install(TARGETS kazvclient EXPORT libkazvConfig LIBRARY)
diff --git a/src/client/actions/encryption.cpp b/src/client/actions/encryption.cpp
index 25ea600..00b9c0b 100644
--- a/src/client/actions/encryption.cpp
+++ b/src/client/actions/encryption.cpp
@@ -1,326 +1,423 @@
/*
* Copyright (C) 2021 Tusooa Zhu <tusooa@vista.aero>
*
* This file is part of libkazv.
*
* libkazv is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* libkazv is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with libkazv. If not, see <https://www.gnu.org/licenses/>.
*/
#include <zug/transducer/filter.hpp>
+#include <zug/transducer/cat.hpp>
#include "encryption.hpp"
#include <debug.hpp>
#include "cursorutil.hpp"
namespace Kazv
{
using namespace CryptoConstants;
static json convertSignature(const ClientModel &m, std::string signature)
{
auto j = json::object();
j[m.userId] = json::object();
j[m.userId][ed25519 + ":" + m.deviceId] = signature;
return j;
}
ClientResult updateClient(ClientModel m, UploadIdentityKeysAction)
{
if (! m.crypto) {
kzo.client.warn() << "Client::crypto is invalid, ignoring it." << std::endl;
return { std::move(m), lager::noop };
}
auto &crypto = m.crypto.value();
auto keys =
immer::map<std::string, std::string>{}
.set(ed25519 + ":" + m.deviceId, crypto.ed25519IdentityKey())
.set(curve25519 + ":" + m.deviceId, crypto.curve25519IdentityKey());
DeviceKeys k {
m.userId,
m.deviceId,
{olmAlgo, megOlmAlgo},
keys,
{} // signatures to be added soon
};
auto j = json(k);
auto sig = crypto.sign(j);
k.signatures = convertSignature(m, sig);
auto job = m.job<UploadKeysJob>()
.make(k)
.withData(json{{"is", "identityKeys"}});
kzo.client.dbg() << "Uploading identity keys" << std::endl;
m.addJob(std::move(job));
return { std::move(m), lager::noop };
}
ClientResult updateClient(ClientModel m, GenerateAndUploadOneTimeKeysAction)
{
if (! m.crypto) {
kzo.client.warn() << "Client::crypto is invalid, ignoring it." << std::endl;
return { std::move(m), lager::noop };
}
auto &crypto = m.crypto.value();
// Keep half of max supported number of keys
int numUploadedKeys = crypto.uploadedOneTimeKeysCount(signedCurve25519);
int numKeysNeeded = crypto.maxNumberOfOneTimeKeys() / 2
- numUploadedKeys;
// Subtract the number of existing one-time keys, in case
// the previous upload was not successful.
int numKeysToGenerate = numKeysNeeded - crypto.numUnpublishedOneTimeKeys();
kzo.client.dbg() << "Generating one-time keys..." << std::endl;
kzo.client.dbg() << "Number needed: " << numKeysNeeded << std::endl;
if (numKeysNeeded <= 0) { // we have enough already
kzo.client.dbg() << "We have enough one-time keys. Ignoring this." << std::endl;
return { std::move(m), lager::noop };
}
if (numKeysToGenerate > 0) {
crypto.genOneTimeKeys(numKeysToGenerate);
}
kzo.client.dbg() << "Generating done." << std::endl;
auto keys = crypto.unpublishedOneTimeKeys();
auto cv25519Keys = keys.at(curve25519);
immer::map<std::string, Variant> oneTimeKeys;
for (auto [id, keyStr] : cv25519Keys.items()) {
json keyObject = json::object();
keyObject["key"] = keyStr;
keyObject["signatures"] = convertSignature(m, crypto.sign(keyObject));
oneTimeKeys = std::move(oneTimeKeys).set(signedCurve25519 + ":" + id, JsonWrap(keyObject));
}
auto job = m.job<UploadKeysJob>()
.make(
std::nullopt, // deviceKeys
oneTimeKeys)
.withData(json{{"is", "oneTimeKeys"}});
kzo.client.dbg() << "Uploading one time keys" << std::endl;
m.addJob(std::move(job));
return { std::move(m), lager::noop };
};
ClientResult processResponse(ClientModel m, UploadKeysResponse r)
{
if (! m.crypto) {
kzo.client.warn() << "Client::crypto is invalid, ignoring it." << std::endl;
return { std::move(m), lager::noop };
}
auto &crypto = m.crypto.value();
auto is = r.dataStr("is");
if (is == "identityKeys") {
if (! r.success()) {
kzo.client.dbg() << "Uploading identity keys failed" << std::endl;
m.addTrigger(UploadIdentityKeysFailed{r.errorCode(), r.errorMessage()});
return { std::move(m), lager::noop };
}
kzo.client.dbg() << "Uploading identity keys successful" << std::endl;
m.addTrigger(UploadIdentityKeysSuccessful{});
m.identityKeysUploaded = true;
} else {
if (! r.success()) {
kzo.client.dbg() << "Uploading one-time keys failed" << std::endl;
m.addTrigger(UploadOneTimeKeysFailed{r.errorCode(), r.errorMessage()});
return { std::move(m), lager::noop };
}
kzo.client.dbg() << "Uploading one-time keys successful" << std::endl;
m.addTrigger(UploadOneTimeKeysSuccessful{});
crypto.markOneTimeKeysAsPublished();
}
crypto.setUploadedOneTimeKeysCount(r.oneTimeKeyCounts());
return { std::move(m), lager::noop };
}
static JsonWrap cannotDecryptEvent(std::string reason)
{
return json{
{"type", "m.room.message"},
{"content", {
{"msgtype","moe.kazv.mxc.cannot.decrypt"},
{"body", "**This message cannot be decrypted due to " + reason + ".**"}}}};
}
static bool verifyEvent(ClientModel &m, Event e, const json &plainJson)
{
auto crypto = m.crypto.value();
bool valid = true;
try {
+ std::string senderCurve25519Key = e.originalJson().get()
+ .at("content").at("sender_key");
+
+ auto deviceInfoOpt = m.deviceLists.findByCurve25519Key(e.sender(), senderCurve25519Key);
+
+ if (! deviceInfoOpt) {
+ kzo.client.dbg() << "Device key " << senderCurve25519Key
+ << " unknown, thus invalid" << std::endl;
+ valid = false;
+ }
+
+ auto deviceInfo = deviceInfoOpt.value();
+
std::string algo = e.originalJson().get().at("content").at("algorithm");
if (algo == olmAlgo) {
if (! (plainJson.at("sender") == e.sender())) {
kzo.client.dbg() << "Sender does not match, thus invalid" << std::endl;
valid = false;
}
if (! (plainJson.at("recipient") == m.userId)) {
kzo.client.dbg() << "Recipient does not match, thus invalid" << std::endl;
valid = false;
}
if (! (plainJson.at("recipient_keys").at(ed25519) == crypto.ed25519IdentityKey())) {
kzo.client.dbg() << "Recipient key does not match, thus invalid" << std::endl;
valid = false;
}
- // TODO: check sender's key
+ auto thisEd25519Key = plainJson.at("keys").at(ed25519).get<std::string>();
+ if (thisEd25519Key != deviceInfo.ed25519Key) {
+ kzo.client.dbg() << "Sender ed25519 key does not match, thus invalid" << std::endl;
+ valid = false;
+ }
} else if (algo == megOlmAlgo) {
if (! (plainJson.at("room_id").get<std::string>() ==
e.originalJson().get().at("room_id").get<std::string>())) {
kzo.client.dbg() << "Room id does not match, thus invalid" << std::endl;
valid = false;
}
- // TODO: check sender key
+ if (e.originalJson().get().at("content").at("device_id").get<std::string>()
+ != deviceInfo.deviceId) {
+ kzo.client.dbg() << "Device id does not match, thus invalid" << std::endl;
+ valid = false;
+ }
+ auto actualEd25519Key = crypto.getInboundGroupSessionEd25519KeyFromEvent(e.originalJson().get());
+ if ((! actualEd25519Key)
+ || deviceInfo.ed25519Key != actualEd25519Key.value()) {
+ kzo.client.dbg() << "sender ed25519 key does not match, thus invalid" << std::endl;
+ kzo.client.dbg() << "From group session: "
+ << (actualEd25519Key ? actualEd25519Key.value() : "<none>") << std::endl;
+ kzo.client.dbg() << "From device info: " << deviceInfo.ed25519Key << std::endl;
+ valid = false;
+ }
} else {
kzo.client.dbg() << "Unknown algorithm, thus invalid" << std::endl;
valid = false;
}
} catch (const std::exception &) {
kzo.client.dbg() << "json format is not correct, thus invalid" << std::endl;
valid = false;
}
return valid;
}
static Event decryptEvent(ClientModel &m, Event e)
{
// no need for decryption
if (e.decrypted() || (! e.encrypted())) {
return e;
}
auto &crypto = m.crypto.value();
kzo.client.dbg() << "About to decrypt event: "
<< e.originalJson().get().dump() << std::endl;
auto maybePlainText = crypto.decrypt(e.originalJson().get());
if (! maybePlainText) {
kzo.client.dbg() << "Cannot decrypt: " << maybePlainText.reason() << std::endl;
return e.setDecryptedJson(cannotDecryptEvent(maybePlainText.reason()), Event::NotDecrypted);
} else {
kzo.client.dbg() << "Decrypted message: " << maybePlainText.value() << std::endl;
auto plainJson = json::parse(maybePlainText.value());
auto valid = verifyEvent(m, e, plainJson);
if (valid) {
kzo.client.dbg() << "The decrypted event is valid." << std::endl;
}
return valid
? e.setDecryptedJson(plainJson, Event::Decrypted)
: e.setDecryptedJson(cannotDecryptEvent("invalid event"), Event::NotDecrypted);
}
}
ClientModel tryDecryptEvents(ClientModel m)
{
if (! m.crypto) {
kzo.client.dbg() << "We have no encryption enabled--ignoring decryption request" << std::endl;
return m;
}
auto &crypto = m.crypto.value();
kzo.client.dbg() << "Trying to decrypt events..." << std::endl;
auto decryptFunc = [&](auto e) { return decryptEvent(m, e); };
auto takeOutRoomKeyEvents =
[&](auto e) {
if (e.type() != "m.room_key") {
// Leave it as it is
return true;
}
auto content = e.content();
std::string roomId = content.get().at("room_id");
std::string sessionId = content.get().at("session_id");
std::string sessionKey = content.get().at("session_key");
std::string senderKey = e.originalJson().get().at("content").at("sender_key");
auto k = KeyOfGroupSession{roomId, senderKey, sessionId};
- if (crypto.createInboundGroupSession(k, sessionKey, "" /* TODO */)) {
- return false;
+ std::string ed25519Key = e.decryptedJson().get().at("keys").at(ed25519);
+
+ if (crypto.createInboundGroupSession(k, sessionKey, ed25519Key)) {
+ return false; // such that this event is removed
}
return true;
};
m.toDevice = intoImmer(
EventList{},
zug::map(decryptFunc)
| zug::filter(takeOutRoomKeyEvents),
std::move(m.toDevice));
auto decryptEventInRoom =
[&](auto id, auto room) {
if (! room.encrypted) {
return;
} else {
auto messages = room.messages;
room.messages = merge(
room.messages,
intoImmer(
EventList{},
zug::filter([](auto n) {
auto e = n.second;
return e.encrypted();
})
| zug::map([=](auto n) {
auto event = n.second;
return decryptFunc(event);
}),
std::move(messages)),
keyOfTimeline);
m.roomList.rooms = std::move(m.roomList.rooms).set(id, room);
}
};
auto rooms = m.roomList.rooms;
for (auto [id, room]: rooms) {
decryptEventInRoom(id, room);
}
return m;
}
+
+ ClientResult updateClient(ClientModel m, QueryKeysAction a)
+ {
+ if (! m.crypto) {
+ kzo.client.dbg() << "We have no encryption enabled--ignoring this" << std::endl;
+ return { std::move(m), lager::noop };
+ }
+
+ immer::map<std::string, immer::array<std::string>> deviceKeys;
+ auto encryptedUsers = m.deviceLists.outdatedUsers();
+
+ if (encryptedUsers.empty()) {
+ kzo.client.dbg() << "Keys are up-to-date." << std::endl;
+ return { std::move(m), lager::noop };
+ }
+
+ kzo.client.dbg() << "We need to query keys for: " << std::endl;
+ for (auto userId: encryptedUsers) {
+ kzo.client.dbg() << userId << std::endl;
+ deviceKeys = std::move(deviceKeys).set(userId, {});
+ }
+ kzo.client.dbg() << "^" << std::endl;
+
+ auto job = m.job<QueryKeysJob>()
+ .make(std::move(deviceKeys),
+ std::nullopt, // timeout
+ a.isInitialSync ? std::nullopt : m.syncToken
+ );
+
+ m.addJob(std::move(job));
+
+ return { std::move(m), lager::noop };
+ }
+
+ ClientResult processResponse(ClientModel m, QueryKeysResponse r)
+ {
+ if (! m.crypto) {
+ kzo.client.dbg() << "We have no encryption enabled--ignoring this" << std::endl;
+ return { std::move(m), lager::noop };
+ }
+
+ if (! r.success()) {
+ kzo.client.dbg() << "query keys failed" << std::endl;
+ return { std::move(m), lager::noop };
+ }
+
+ kzo.client.dbg() << "Received a query key response" << std::endl;
+ auto &crypto = m.crypto.value();
+
+ auto usersMap = r.deviceKeys();
+
+ for (auto [userId, deviceMap] : usersMap) {
+ for (auto [deviceId, deviceInfo] : deviceMap) {
+ kzo.client.dbg() << "Key for " << userId
+ << "/" << deviceId
+ << ": " << json(deviceInfo).dump()
+ << std::endl;
+ m.deviceLists.addDevice(userId, deviceId, deviceInfo, crypto);
+ }
+ m.deviceLists.markUpToDate(userId);
+ }
+
+ return { std::move(m), lager::noop };
+ }
}
diff --git a/src/client/actions/encryption.hpp b/src/client/actions/encryption.hpp
index 770582f..4448469 100644
--- a/src/client/actions/encryption.hpp
+++ b/src/client/actions/encryption.hpp
@@ -1,33 +1,36 @@
/*
* Copyright (C) 2021 Tusooa Zhu <tusooa@vista.aero>
*
* This file is part of libkazv.
*
* libkazv is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* libkazv is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with libkazv. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include "client-model.hpp"
#include "csapi/keys.hpp"
namespace Kazv
{
ClientResult updateClient(ClientModel m, UploadIdentityKeysAction a);
ClientResult updateClient(ClientModel m, GenerateAndUploadOneTimeKeysAction a);
ClientResult processResponse(ClientModel m, UploadKeysResponse r);
ClientModel tryDecryptEvents(ClientModel m);
+
+ ClientResult updateClient(ClientModel m, QueryKeysAction a);
+ ClientResult processResponse(ClientModel m, QueryKeysResponse r);
}
diff --git a/src/client/actions/sync.cpp b/src/client/actions/sync.cpp
index 5568621..cf331f3 100644
--- a/src/client/actions/sync.cpp
+++ b/src/client/actions/sync.cpp
@@ -1,293 +1,333 @@
/*
* Copyright (C) 2020 Tusooa Zhu
*
* This file is part of libkazv.
*
* libkazv is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* libkazv is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with libkazv. If not, see <https://www.gnu.org/licenses/>.
*/
#include <lager/util.hpp>
#include <zug/transducer/map.hpp>
+#include <zug/transducer/cat.hpp>
+#include <zug/transducer/filter.hpp>
+#include <zug/sequence.hpp>
#include <jobinterface.hpp>
#include <debug.hpp>
#include "cursorutil.hpp"
#include "sync.hpp"
#include "encryption.hpp"
namespace Kazv
{
// Atomicity guaranteed: if the sync action is created
// before an action that reasonably changes Client
// (e.g. roll back to an earlier state, obtain other
// events), but executed
// after that action, the sync will still give continuous
// data about the events. (Sync will not "skip" events)
// This is because this function takes the sync token
// from the ClientModel model it is passed.
ClientResult updateClient(ClientModel m, SyncAction)
{
kzo.client.dbg() << "Start syncing with token " <<
(m.syncToken ? m.syncToken.value() : "<null>") << std::endl;
+ bool isInitialSync = ! m.syncToken;
+
std::string filter = m.syncToken ? m.incrementalSyncFilterId : m.initialSyncFilterId;
m.addJob(m.job<SyncJob>()
- .make(filter, m.syncToken));
+ .make(filter, m.syncToken)
+ .withData(json{{"is", isInitialSync ? "initial" : "incremental"}}));
return { m, lager::noop };
}
static KazvEventList loadRoomsFromSyncInPlace(ClientModel &m, SyncJob::Rooms rooms)
{
auto l = std::move(m.roomList);
auto eventsToEmit = KazvEventList{}.transient();
auto updateRoomImpl =
[&l](auto id, auto a) {
l = RoomListModel::update(
std::move(l),
UpdateRoomAction{std::move(id), std::move(a)});
};
auto updateSingleRoom =
[&, updateRoomImpl](const auto &id, const auto &room, auto membership) {
if (!l.has(id) || l[id].membership != membership) {
eventsToEmit.push_back(RoomMembershipChanged{membership, id});
}
updateRoomImpl(id, ChangeMembershipAction{membership});
auto timelineEvents =
intoImmer(
EventList{},
zug::map([=](Event e) {
return Event::fromSync(e, id);
}),
room.timeline.events);
eventsToEmit.append(
intoImmer(
KazvEventList{},
zug::map([=](Event e) -> KazvEvent {
return ReceivingRoomTimelineEvent{std::move(e), id};
}),
timelineEvents).transient());
updateRoomImpl(id, AppendTimelineAction{timelineEvents});
if (room.state) {
eventsToEmit.append(
intoImmer(
KazvEventList{},
zug::map([=](Event e) -> KazvEvent {
return ReceivingRoomStateEvent{std::move(e), id};
}),
room.state.value().events).transient());
updateRoomImpl(id, AddStateEventsAction{room.state.value().events});
// If m.room.encryption state event appears,
// configure the room to use encryption.
if (l[id].stateEvents.find(KeyOfState{"m.room.encryption", ""})) {
updateRoomImpl(id, SetRoomEncryptionAction{});
}
}
if (room.accountData) {
eventsToEmit.append(
intoImmer(
KazvEventList{},
zug::map([=](Event e) -> KazvEvent {
return ReceivingRoomAccountDataEvent{std::move(e), id};
}),
room.state.value().events).transient());
updateRoomImpl(id, AddAccountDataAction{room.accountData.value().events});
}
};
auto updateJoinedRoom =
[=](const auto &id, const auto &room) {
updateSingleRoom(id, room, RoomMembership::Join);
if (room.ephemeral) {
updateRoomImpl(id, AddEphemeralAction{room.ephemeral.value().events});
}
// TODO update other info such as
// notification and summary
};
auto updateInvitedRoom =
[=](const auto &id, const auto &room) {
updateRoomImpl(id, ChangeMembershipAction{RoomMembership::Invite});
if (room.inviteState) {
updateRoomImpl(id, ChangeInviteStateAction{room.inviteState.value().events});
}
};
auto updateLeftRoom =
[=](const auto &id, const auto &room) {
updateSingleRoom(id, room, RoomMembership::Leave);
};
for (const auto &[id, room]: rooms.join) {
updateJoinedRoom(id, room);
}
// TODO update info for invited rooms
for (const auto &[id, room]: rooms.invite) {
updateInvitedRoom(id, room);
}
for (const auto &[id, room]: rooms.leave) {
updateLeftRoom(id, room);
}
m.roomList = std::move(l);
return eventsToEmit.persistent();
}
static KazvEventList loadPresenceFromSyncInPlace(ClientModel &m, EventList presence)
{
auto eventsToEmit = intoImmer(
KazvEventList{},
zug::map([](Event e) { return ReceivingPresenceEvent{e}; }),
presence);
m.presence = merge(std::move(m.presence), presence, keyOfPresence);
return eventsToEmit;
}
static KazvEventList loadAccountDataFromSyncInPlace(ClientModel &m, EventList accountData)
{
auto eventsToEmit = intoImmer(
KazvEventList{},
zug::map([](Event e) { return ReceivingPresenceEvent{e}; }),
accountData);
m.accountData = merge(std::move(m.accountData), accountData, keyOfAccountData);
return eventsToEmit;
}
static KazvEventList loadToDeviceFromSyncInPlace(ClientModel &m, JsonWrap toDevice)
{
if (toDevice.get().contains("events")) {
auto events = toDevice.get()["events"];
auto msgs = intoImmer(
EventList{},
zug::map([](const json &j) { return Event(j); }),
events);
m.toDevice = std::move(m.toDevice) + msgs;
return intoImmer(
KazvEventList{},
zug::map([](Event e) { return ReceivingToDeviceMessage{e}; }),
msgs);
}
return {};
}
ClientResult processResponse(ClientModel m, SyncResponse r)
{
if (! r.success()) {
m.addTrigger(SyncFailed{});
m.syncing = false;
kzo.client.dbg() << "Sync failed" << std::endl;
kzo.client.dbg() << r.statusCode << std::endl;
if (isBodyJson(r.body)) {
auto j = r.jsonBody();
kzo.client.dbg() << "Json says: " << j.get().dump() << std::endl;
} else {
kzo.client.dbg() << "Response body: "
<< std::get<BaseJob::BytesBody>(r.body) << std::endl;
}
return { std::move(m), lager::noop };
}
kzo.client.dbg() << "Sync successful" << std::endl;
auto rooms = r.rooms();
auto accountData = r.accountData();
auto presence = r.presence();
// load the info that has been sync'd
m.syncToken = r.nextBatch();
if (rooms) {
m.addTriggers(loadRoomsFromSyncInPlace(m, std::move(rooms.value())));
}
if (presence) {
m.addTriggers(loadPresenceFromSyncInPlace(m, std::move(presence.value().events)));
}
if (accountData) {
m.addTriggers(loadAccountDataFromSyncInPlace(m, std::move(accountData.value().events)));
}
m.addTriggers(loadToDeviceFromSyncInPlace(m, r.toDevice()));
- // TODO: process toDevice, deviceLists, deviceOneTimeKeysCount
- auto model = tryDecryptEvents(std::move(m));
- m = std::move(model);
+ auto is = r.dataStr("is");
+ auto isInitialSync = is == "initial";
+
+ if (m.crypto) {
+ kzo.client.dbg() << "E2EE is on. Processing device lists and one-time key counts." << std::endl;
+ auto &crypto = m.crypto.value();
+ // TODO: process deviceLists,
+ if (isInitialSync) {
+ auto encryptedUsers =
+ zug::sequence(
+ zug::map([](auto n) { return n.second; })
+ | zug::filter([](auto room) { return room.encrypted; })
+ | zug::map([](auto room) { return room.joinedMemberIds(); })
+ | zug::cat,
+ // no need to use distinct here as the map will overwrite
+ m.roomList.rooms);
+
+ m.deviceLists.track(std::move(encryptedUsers));
+ } else {
+ const auto &l = r.deviceLists().get();
+ if (l.contains("changed")) {
+ const auto &changed = l.at("changed");
+ m.deviceLists.track(changed);
+ }
+
+ if (l.contains("left")) {
+ const auto &left = l.at("left");
+ m.deviceLists.untrack(left);
+ }
+ }
+
+ // deviceOneTimeKeysCount
+ crypto.setUploadedOneTimeKeysCount(r.deviceOneTimeKeysCount());
+
+ auto model = tryDecryptEvents(std::move(m));
+ m = std::move(model);
+ }
- m.addTrigger(SyncSuccessful{r.nextBatch()});
+ m.addTrigger(SyncSuccessful{r.nextBatch(), isInitialSync});
return { std::move(m), lager::noop };
}
ClientResult updateClient(ClientModel m, PostInitialFiltersAction)
{
if (m.syncing) {
return { std::move(m), lager::noop };
}
Filter initialSyncFilter;
initialSyncFilter.room.timeline.limit = 1;
initialSyncFilter.room.state.lazyLoadMembers = true;
auto firstJob = m.job<DefineFilterJob>()
.make(m.userId, initialSyncFilter)
.withData(json{{"is", "initialSyncFilter"}})
.withQueue("post-filter", CancelFutureIfFailed);
kzo.client.dbg() << "First filter: " << firstJob.requestBody() << std::endl;
m.addJob(firstJob);
Filter incrementalSyncFilter;
incrementalSyncFilter.room.timeline.limit = 20;
incrementalSyncFilter.room.state.lazyLoadMembers = true;
m.addJob(m.job<DefineFilterJob>()
.make(m.userId, incrementalSyncFilter)
.withData(json{{"is", "incrementalSyncFilter"}})
.withQueue("post-filter", CancelFutureIfFailed));
m.syncing = true;
return { std::move(m), lager::noop };
}
ClientResult processResponse(ClientModel m, DefineFilterResponse r)
{
auto is = r.dataStr("is");
if (! r.success()) {
m.syncing = false;
kzo.client.dbg() << "posting filter failed: " << r.errorCode() << r.errorMessage() << std::endl;
m.addTrigger(PostInitialFiltersFailed{r.errorCode(), r.errorMessage()});
return { std::move(m), lager::noop };
}
kzo.client.dbg() << "filter " << is << " is posted" << std::endl;
if (is == "incrementalSyncFilter") {
m.incrementalSyncFilterId = r.filterId();
m.addTrigger(PostInitialFiltersSuccessful{});
} else {
m.initialSyncFilterId = r.filterId();
}
return { std::move(m), lager::noop };
}
}
diff --git a/src/client/client-model.cpp b/src/client/client-model.cpp
index 70d6747..3c0342d 100644
--- a/src/client/client-model.cpp
+++ b/src/client/client-model.cpp
@@ -1,110 +1,111 @@
/*
* Copyright (C) 2020 Tusooa Zhu <tusooa@vista.aero>
*
* This file is part of libkazv.
*
* libkazv is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* libkazv is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with libkazv. If not, see <https://www.gnu.org/licenses/>.
*/
#include <lager/util.hpp>
#include <lager/context.hpp>
#include <functional>
#include <immer/flex_vector_transient.hpp>
#include "debug.hpp"
#include "client-model.hpp"
#include "actions/states.hpp"
#include "actions/auth.hpp"
#include "actions/membership.hpp"
#include "actions/paginate.hpp"
#include "actions/send.hpp"
#include "actions/states.hpp"
#include "actions/sync.hpp"
#include "actions/ephemeral.hpp"
#include "actions/content.hpp"
#include "actions/encryption.hpp"
namespace Kazv
{
auto ClientModel::update(ClientModel m, Action a) -> Result
{
return lager::match(std::move(a))(
[&](Error::Action a) -> Result {
m.error = Error::update(m.error, a);
return {std::move(m), lager::noop};
},
[&](RoomListAction a) -> Result {
m.roomList = RoomListModel::update(std::move(m.roomList), a);
return {std::move(m), lager::noop};
},
[&](ResubmitJobAction a) -> Result {
m.addJob(std::move(a.job));
return { std::move(m), lager::noop };
},
[&](auto a) -> decltype(updateClient(m, a)) {
return updateClient(m, a);
},
#define RESPONSE_FOR(_jobId) \
if (r.jobId() == #_jobId) { \
return processResponse(m, _jobId##Response{std::move(r)}); \
}
[&](ProcessResponseAction a) -> Result {
auto r = std::move(a.response);
// auth
RESPONSE_FOR(Login);
// paginate
RESPONSE_FOR(GetRoomEvents);
// sync
RESPONSE_FOR(Sync);
RESPONSE_FOR(DefineFilter);
// membership
RESPONSE_FOR(CreateRoom);
RESPONSE_FOR(InviteUser);
RESPONSE_FOR(JoinRoomById);
RESPONSE_FOR(JoinRoom);
RESPONSE_FOR(LeaveRoom);
RESPONSE_FOR(ForgetRoom);
// send
RESPONSE_FOR(SendMessage);
RESPONSE_FOR(SendToDevice);
// states
RESPONSE_FOR(GetRoomState);
RESPONSE_FOR(SetRoomStateWithKey);
RESPONSE_FOR(GetRoomStateWithKey);
// ephemeral
RESPONSE_FOR(SetTyping);
RESPONSE_FOR(PostReceipt);
RESPONSE_FOR(SetReadMarker);
// content
RESPONSE_FOR(UploadContent);
RESPONSE_FOR(GetContent);
RESPONSE_FOR(GetContentThumbnail);
// encryption
RESPONSE_FOR(UploadKeys);
+ RESPONSE_FOR(QueryKeys);
m.addTrigger(UnrecognizedResponse{std::move(r)});
return { std::move(m), lager::noop };
}
#undef RESPONSE_FOR
);
}
}
diff --git a/src/client/client-model.hpp b/src/client/client-model.hpp
index 20d75aa..02a53b1 100644
--- a/src/client/client-model.hpp
+++ b/src/client/client-model.hpp
@@ -1,364 +1,372 @@
/*
* Copyright (C) 2020 Tusooa Zhu
*
* This file is part of libkazv.
*
* libkazv is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* libkazv is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with libkazv. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <tuple>
#include <variant>
#include <string>
#include <optional>
#include <lager/context.hpp>
#include <boost/hana.hpp>
#ifndef NDEBUG
#include <lager/debug/cereal/struct.hpp>
#endif
#include <csapi/sync.hpp>
#include <jobinterface.hpp>
#include <eventinterface.hpp>
#include <crypto.hpp>
#include "clientfwd.hpp"
+#include "device-list-tracker.hpp"
#include "error.hpp"
#include "room/room-model.hpp"
namespace Kazv
{
inline const std::string DEFTXNID{"0"};
enum RoomVisibility
{
Private,
Public,
};
enum CreateRoomPreset
{
PrivateChat,
PublicChat,
TrustedPrivateChat,
};
enum ThumbnailResizingMethod
{
Crop,
Scale,
};
struct ClientModel
{
std::string serverUrl;
std::string userId;
std::string token;
std::string deviceId;
bool loggedIn{false};
Error error;
bool syncing{false};
std::string initialSyncFilterId;
std::string incrementalSyncFilterId;
std::optional<std::string> syncToken;
RoomListModel roomList;
immer::map<std::string /* sender */, Event> presence;
immer::map<std::string /* type */, Event> accountData;
std::string nextTxnId{DEFTXNID};
immer::flex_vector<BaseJob> nextJobs;
immer::flex_vector<KazvEvent> nextTriggers;
EventList toDevice;
std::optional<Crypto> crypto;
bool identityKeysUploaded{false};
+ DeviceListTracker deviceLists;
+
// helpers
template<class Job>
struct MakeJobT
{
template<class ...Args>
constexpr auto make(Args &&...args) const {
if constexpr (Job::needsAuth()) {
return Job(
serverUrl,
token,
std::forward<Args>(args)...);
} else {
return Job(
serverUrl,
std::forward<Args>(args)...);
}
}
std::string serverUrl;
std::string token;
};
template<class Job>
constexpr auto job() const {
return MakeJobT<Job>{serverUrl, token};
}
inline void addJob(BaseJob j) {
nextJobs = std::move(nextJobs).push_back(std::move(j));
}
inline auto popAllJobs() {
auto jobs = std::move(nextJobs);
nextJobs = DEFVAL;
return jobs;
};
inline void addTrigger(KazvEvent t) {
addTriggers({t});
}
inline void addTriggers(immer::flex_vector<KazvEvent> c) {
nextTriggers = std::move(nextTriggers) + c;
}
inline auto popAllTriggers() {
auto triggers = std::move(nextTriggers);
nextTriggers = DEFVAL;
return triggers;
}
using Action = ClientAction;
using Effect = ClientEffect;
using Result = ClientResult;
static Result update(ClientModel m, Action a);
};
// actions:
struct LoginAction {
std::string serverUrl;
std::string username;
std::string password;
std::optional<std::string> deviceName;
};
struct TokenLoginAction
{
std::string serverUrl;
std::string username;
std::string token;
std::string deviceId;
};
struct LogoutAction {};
struct SyncAction {};
struct PaginateTimelineAction
{
std::string roomId;
std::optional<int> limit;
};
struct SendMessageAction
{
std::string roomId;
Event event;
};
struct SendStateEventAction
{
std::string roomId;
Event event;
};
struct CreateRoomAction
{
using Visibility = RoomVisibility;
using Preset = CreateRoomPreset;
Visibility visibility;
std::optional<std::string> roomAliasName;
std::optional<std::string> name;
std::optional<std::string> topic;
immer::array<std::string> invite;
//immer::array<Invite3pid> invite3pid;
std::optional<std::string> roomVersion;
JsonWrap creationContent;
immer::array<Event> initialState;
std::optional<Preset> preset;
std::optional<bool> isDirect;
JsonWrap powerLevelContentOverride;
};
struct GetRoomStatesAction
{
std::string roomId;
};
struct GetStateEventAction
{
std::string roomId;
std::string type;
std::string stateKey;
};
struct InviteToRoomAction
{
std::string roomId;
std::string userId;
};
struct JoinRoomByIdAction
{
std::string roomId;
};
struct JoinRoomAction
{
std::string roomIdOrAlias;
immer::array<std::string> serverName;
};
struct LeaveRoomAction
{
std::string roomId;
};
struct ForgetRoomAction
{
std::string roomId;
};
struct SetTypingAction
{
std::string roomId;
bool typing;
std::optional<int> timeoutMs;
};
struct PostReceiptAction
{
std::string roomId;
std::string eventId;
};
struct SetReadMarkerAction
{
std::string roomId;
std::string eventId;
};
struct UploadContentAction
{
immer::box<Bytes> content;
std::optional<std::string> filename;
std::optional<std::string> contentType;
std::string uploadId; // to be used by library users
};
struct DownloadContentAction
{
std::string mxcUri;
};
struct DownloadThumbnailAction
{
std::string mxcUri;
int width;
int height;
std::optional<ThumbnailResizingMethod> method;
std::optional<bool> allowRemote;
};
struct ResubmitJobAction
{
BaseJob job;
};
struct ProcessResponseAction
{
Response response;
};
struct PostInitialFiltersAction
{
};
struct SendToDeviceMessageAction
{
Event event;
std::string userId;
std::string deviceId;
};
struct UploadIdentityKeysAction
{
};
struct GenerateAndUploadOneTimeKeysAction
{
};
+ struct QueryKeysAction
+ {
+ bool isInitialSync;
+ };
+
#ifndef NDEBUG
LAGER_CEREAL_STRUCT(LoginAction);
LAGER_CEREAL_STRUCT(TokenLoginAction);
LAGER_CEREAL_STRUCT(LogoutAction);
LAGER_CEREAL_STRUCT(SyncAction);
LAGER_CEREAL_STRUCT(PostInitialFiltersAction);
LAGER_CEREAL_STRUCT(PaginateTimelineAction);
LAGER_CEREAL_STRUCT(SendMessageAction);
LAGER_CEREAL_STRUCT(SendStateEventAction);
LAGER_CEREAL_STRUCT(SendToDeviceMessageAction);
LAGER_CEREAL_STRUCT(CreateRoomAction);
LAGER_CEREAL_STRUCT(GetRoomStatesAction);
LAGER_CEREAL_STRUCT(GetStateEventAction);
LAGER_CEREAL_STRUCT(InviteToRoomAction);
LAGER_CEREAL_STRUCT(JoinRoomByIdAction);
LAGER_CEREAL_STRUCT(JoinRoomAction);
LAGER_CEREAL_STRUCT(LeaveRoomAction);
LAGER_CEREAL_STRUCT(ForgetRoomAction);
LAGER_CEREAL_STRUCT(SetTypingAction);
LAGER_CEREAL_STRUCT(PostReceiptAction);
LAGER_CEREAL_STRUCT(ProcessResponseAction);
LAGER_CEREAL_STRUCT(SetReadMarkerAction);
LAGER_CEREAL_STRUCT(UploadContentAction);
LAGER_CEREAL_STRUCT(DownloadContentAction);
LAGER_CEREAL_STRUCT(DownloadThumbnailAction);
LAGER_CEREAL_STRUCT(ResubmitJobAction);
#endif
template<class Archive>
void serialize(Archive &ar, ClientModel &m, std::uint32_t const /*version*/)
{
ar(m.serverUrl, m.userId, m.token, m.deviceId, m.loggedIn,
m.error,
m.initialSyncFilterId,
m.incrementalSyncFilterId,
m.syncToken,
m.roomList,
m.presence,
m.accountData,
m.nextTxnId,
m.toDevice);
}
}
CEREAL_CLASS_VERSION(Kazv::ClientModel, 0);
diff --git a/src/client/clientfwd.hpp b/src/client/clientfwd.hpp
index 0d5938f..477c1ad 100644
--- a/src/client/clientfwd.hpp
+++ b/src/client/clientfwd.hpp
@@ -1,116 +1,118 @@
/*
* Copyright (C) 2020-2021 Tusooa Zhu <tusooa@vista.aero>
*
* This file is part of libkazv.
*
* libkazv is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* libkazv is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with libkazv. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <tuple>
#include <variant>
#include <lager/context.hpp>
#include "error.hpp"
#include "room/room-model.hpp"
namespace Kazv
{
class JobInterface;
class EventInterface;
struct LoginAction;
struct TokenLoginAction;
struct LogoutAction;
struct SyncAction;
struct PostInitialFiltersAction;
struct PaginateTimelineAction;
struct SendMessageAction;
struct SendStateEventAction;
struct CreateRoomAction;
struct GetRoomStatesAction;
struct GetStateEventAction;
struct InviteToRoomAction;
struct JoinRoomByIdAction;
struct EmitKazvEventsAction;
struct JoinRoomAction;
struct LeaveRoomAction;
struct ForgetRoomAction;
struct ProcessResponseAction;
struct SetTypingAction;
struct PostReceiptAction;
struct SetReadMarkerAction;
struct UploadContentAction;
struct DownloadContentAction;
struct DownloadThumbnailAction;
struct SendToDeviceMessageAction;
struct UploadIdentityKeysAction;
struct GenerateAndUploadOneTimeKeysAction;
+ struct QueryKeysAction;
struct ResubmitJobAction;
struct ClientModel;
using ClientAction = std::variant<
RoomListAction,
Error::Action,
LoginAction,
TokenLoginAction,
LogoutAction,
SyncAction,
PostInitialFiltersAction,
PaginateTimelineAction,
SendMessageAction,
SendStateEventAction,
CreateRoomAction,
GetRoomStatesAction,
GetStateEventAction,
InviteToRoomAction,
JoinRoomByIdAction,
JoinRoomAction,
LeaveRoomAction,
ForgetRoomAction,
ProcessResponseAction,
SetTypingAction,
PostReceiptAction,
SetReadMarkerAction,
UploadContentAction,
DownloadContentAction,
DownloadThumbnailAction,
SendToDeviceMessageAction,
UploadIdentityKeysAction,
GenerateAndUploadOneTimeKeysAction,
+ QueryKeysAction,
ResubmitJobAction
>;
using ClientEffect = lager::effect<ClientAction, lager::deps<>>;
using ClientResult = std::pair<ClientModel, ClientEffect>;
}
diff --git a/src/client/device-list-tracker.cpp b/src/client/device-list-tracker.cpp
new file mode 100644
index 0000000..d17e72f
--- /dev/null
+++ b/src/client/device-list-tracker.cpp
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2021 Tusooa Zhu <tusooa@vista.aero>
+ *
+ * This file is part of libkazv.
+ *
+ * libkazv is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * libkazv is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with libkazv. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "device-list-tracker.hpp"
+
+#include <algorithm>
+
+#include <zug/transducer/filter.hpp>
+
+#include <debug.hpp>
+
+namespace Kazv
+{
+ immer::flex_vector<std::string> DeviceListTracker::outdatedUsers() const
+ {
+ return intoImmer(
+ immer::flex_vector<std::string>{},
+ zug::filter([](auto n) {
+ auto [userId, outdated] = n;
+ return outdated;
+ })
+ | zug::map([](auto n) {
+ auto [userId, outdated] = n;
+ return userId;
+ }),
+ usersToTrackDeviceLists);
+ }
+
+
+ bool DeviceListTracker::addDevice(std::string userId, std::string deviceId, QueryKeysJob::DeviceInformation deviceInfo, Crypto &crypto)
+ {
+ using namespace CryptoConstants;
+ if (userId != deviceInfo.userId
+ || deviceId != deviceInfo.deviceId) {
+ return false;
+ }
+
+ // if the ed25519 key changed, reject
+ auto curEd25519Key = deviceInfo.keys[ed25519 + ":" + deviceId];
+ if (deviceLists[userId].find(deviceId)
+ && curEd25519Key != deviceLists[userId][deviceId].ed25519Key) {
+ return false;
+ }
+
+ kzo.client.dbg() << "verifying device info" << std::endl;
+
+ if (crypto.verify(deviceInfo, userId, deviceId, curEd25519Key)) {
+ kzo.client.dbg() << "passed verification" << std::endl;
+ auto info = DeviceKeyInfo{
+ deviceId,
+ deviceInfo.keys[ed25519 + ":" + deviceId],
+ deviceInfo.keys[curve25519 + ":" + deviceId],
+ deviceInfo.unsignedData ? deviceInfo.unsignedData.value().deviceDisplayName : std::nullopt
+ };
+
+ deviceLists = std::move(deviceLists)
+ .update(userId, [=](auto deviceMap) {
+ return std::move(deviceMap).set(deviceId, info);
+ });
+ return true;
+ }
+
+ kzo.client.dbg() << "did not pass verification" << std::endl;
+ return false;
+ }
+
+ void DeviceListTracker::markUpToDate(std::string userId)
+ {
+ usersToTrackDeviceLists = std::move(usersToTrackDeviceLists).set(userId, false);
+ }
+
+ std::optional<DeviceKeyInfo> DeviceListTracker::get(std::string userId, std::string deviceId) const
+ {
+ try {
+ return deviceLists.at(userId).at(deviceId);
+ } catch (const std::exception &) {
+ return std::nullopt;
+ }
+ }
+
+ std::optional<DeviceKeyInfo> DeviceListTracker::findByEd25519Key(
+ std::string userId, std::string ed25519Key) const
+ {
+ auto devices = deviceLists.at(userId);
+
+ auto it = std::find_if(devices.begin(), devices.end(),
+ [=](auto n) {
+ auto [deviceId, info] = n;
+ return info.ed25519Key == ed25519Key;
+ });
+ if (it != devices.end()) {
+ return it->second;
+ } else {
+ return std::nullopt;
+ }
+ }
+
+ std::optional<DeviceKeyInfo> DeviceListTracker::findByCurve25519Key(
+ std::string userId, std::string curve25519Key) const
+ {
+ auto devices = deviceLists.at(userId);
+
+ auto it = std::find_if(devices.begin(), devices.end(),
+ [=](auto n) {
+ auto [deviceId, info] = n;
+ return info.curve25519Key == curve25519Key;
+ });
+ if (it != devices.end()) {
+ return it->second;
+ } else {
+ return std::nullopt;
+ }
+ }
+
+}
diff --git a/src/client/device-list-tracker.hpp b/src/client/device-list-tracker.hpp
new file mode 100644
index 0000000..e69a99f
--- /dev/null
+++ b/src/client/device-list-tracker.hpp
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2021 Tusooa Zhu <tusooa@vista.aero>
+ *
+ * This file is part of libkazv.
+ *
+ * libkazv is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * libkazv is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with libkazv. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+#include <string>
+#include <immer/map.hpp>
+#include <immer/flex_vector.hpp>
+
+#include <crypto.hpp>
+#include <csapi/keys.hpp>
+
+#include "cursorutil.hpp"
+
+namespace Kazv
+{
+ struct DeviceKeyInfo
+ {
+ std::string deviceId;
+ std::string ed25519Key;
+ std::string curve25519Key;
+ std::optional<std::string> displayName;
+ };
+
+ struct DeviceListTracker
+ {
+ immer::map<std::string /* userId */, bool /* outdated */> usersToTrackDeviceLists;
+ immer::map<std::string /* userId */, immer::map<std::string /* deviceId */, DeviceKeyInfo>> deviceLists;
+
+ template<class RangeT>
+ void track(RangeT &&userIds) {
+ for (auto userId : std::forward<RangeT>(userIds)) {
+ usersToTrackDeviceLists = std::move(usersToTrackDeviceLists)
+ .set(userId, true);
+ }
+ }
+
+ template<class RangeT>
+ void untrack(RangeT &&userIds) {
+ for (auto userId : std::forward<RangeT>(userIds)) {
+ usersToTrackDeviceLists = std::move(usersToTrackDeviceLists).erase(userId);
+ }
+ }
+
+ immer::flex_vector<std::string> outdatedUsers() const;
+
+ bool addDevice(std::string userId, std::string deviceId, QueryKeysJob::DeviceInformation deviceInfo, Crypto &crypto);
+
+ void markUpToDate(std::string userId);
+
+ std::optional<DeviceKeyInfo> get(std::string userId, std::string deviceId) const;
+
+ std::optional<DeviceKeyInfo> findByEd25519Key(std::string userId, std::string ed25519Key) const;
+ std::optional<DeviceKeyInfo> findByCurve25519Key(std::string userId, std::string curve25519Key) const;
+ };
+}
diff --git a/src/client/room/room-model.cpp b/src/client/room/room-model.cpp
index afc6018..192d53d 100644
--- a/src/client/room/room-model.cpp
+++ b/src/client/room/room-model.cpp
@@ -1,98 +1,134 @@
/*
* Copyright (C) 2020 Tusooa Zhu
*
* This file is part of libkazv.
*
* libkazv is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* libkazv is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with libkazv. If not, see <https://www.gnu.org/licenses/>.
*/
#include <lager/util.hpp>
#include <zug/sequence.hpp>
#include <zug/transducer/map.hpp>
+#include <zug/transducer/filter.hpp>
#include "debug.hpp"
#include "room-model.hpp"
#include "cursorutil.hpp"
namespace Kazv
{
RoomModel RoomModel::update(RoomModel r, Action a)
{
return lager::match(std::move(a))(
[&](AddStateEventsAction a) {
r.stateEvents = merge(std::move(r.stateEvents), a.stateEvents, keyOfState);
return r;
},
[&](AppendTimelineAction a) {
auto eventIds = intoImmer(immer::flex_vector<std::string>(),
zug::map(keyOfTimeline), a.events);
r.timeline = r.timeline + eventIds;
r.messages = merge(std::move(r.messages), a.events, keyOfTimeline);
return r;
},
[&](PrependTimelineAction a) {
auto eventIds = intoImmer(immer::flex_vector<std::string>(),
zug::map(keyOfTimeline), a.events);
r.timeline = eventIds + r.timeline;
r.messages = merge(std::move(r.messages), a.events, keyOfTimeline);
r.paginateBackToken = a.paginateBackToken;
// if there are no more events we should not allow further paginating
r.canPaginateBack = a.events.size() != 0;
return r;
},
[&](AddAccountDataAction a) {
r.accountData = merge(std::move(r.accountData), a.events, keyOfAccountData);
return r;
},
[&](ChangeMembershipAction a) {
r.membership = a.membership;
return r;
},
[&](ChangeInviteStateAction a) {
r.inviteState = merge(immer::map<KeyOfState, Event>{}, a.events, keyOfState);
return r;
},
[&](AddEphemeralAction a) {
r.ephemeral = merge(std::move(r.ephemeral), a.events, keyOfEphemeral);
return r;
},
[&](SetLocalDraftAction a) {
r.localDraft = a.localDraft;
return r;
},
[&](SetRoomEncryptionAction) {
r.encrypted = true;
return r;
}
);
}
RoomListModel RoomListModel::update(RoomListModel l, Action a)
{
return lager::match(std::move(a))(
[&](UpdateRoomAction a) {
l.rooms = std::move(l.rooms)
.update(a.roomId,
[=](RoomModel oldRoom) {
oldRoom.roomId = a.roomId; // in case it is a new room
return RoomModel::update(std::move(oldRoom), a.roomAction);
});
return l;
}
);
}
+
+ immer::flex_vector<std::string> RoomModel::joinedMemberIds() const
+ {
+ using MemberNode = std::pair<std::string, Kazv::Event>;
+
+ auto memberNameTransducer =
+ zug::filter(
+ [](auto val) {
+ auto [k, v] = val;
+ auto [type, stateKey] = k;
+ return type == "m.room.member"s;
+ })
+ | zug::map(
+ [](auto val) {
+ auto [k, v] = val;
+ auto [type, stateKey] = k;
+ return MemberNode{stateKey, v};
+ })
+ | zug::filter(
+ [](auto val) {
+ auto [stateKey, ev] = val;
+ return ev.content().get()
+ .at("membership"s) == "join"s;
+ })
+ | zug::map(
+ [](auto val) {
+ auto [stateKey, ev] = val;
+ return stateKey;
+ });
+
+ return intoImmer(
+ immer::flex_vector<std::string>{},
+ memberNameTransducer,
+ stateEvents);
+ }
}
diff --git a/src/client/room/room-model.hpp b/src/client/room/room-model.hpp
index 6635c03..9c4ac7c 100644
--- a/src/client/room/room-model.hpp
+++ b/src/client/room/room-model.hpp
@@ -1,199 +1,201 @@
/*
* Copyright (C) 2020 Tusooa Zhu
*
* This file is part of libkazv.
*
* libkazv is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* libkazv is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with libkazv. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <lager/debug/cereal/struct.hpp>
#include <lager/debug/cereal/immer_flex_vector.hpp>
#include <string>
#include <variant>
#include <immer/flex_vector.hpp>
#include <immer/map.hpp>
#include <csapi/sync.hpp>
#include <event.hpp>
#include "data/cereal_map.hpp"
#include "clientutil.hpp"
namespace Kazv
{
struct AddStateEventsAction
{
immer::flex_vector<Event> stateEvents;
};
struct AppendTimelineAction
{
immer::flex_vector<Event> events;
};
struct PrependTimelineAction
{
immer::flex_vector<Event> events;
std::string paginateBackToken;
};
struct AddAccountDataAction
{
immer::flex_vector<Event> events;
};
struct ChangeMembershipAction
{
RoomMembership membership;
};
struct ChangeInviteStateAction
{
immer::flex_vector<Event> events;
};
struct AddEphemeralAction
{
EventList events;
};
struct SetLocalDraftAction
{
std::string localDraft;
};
struct SetRoomEncryptionAction
{
};
struct RoomModel
{
using Membership = RoomMembership;
std::string roomId;
immer::map<KeyOfState, Event> stateEvents;
immer::map<KeyOfState, Event> inviteState;
immer::flex_vector<std::string> timeline;
immer::map<std::string, Event> messages;
immer::map<std::string, Event> accountData;
Membership membership{};
std::string paginateBackToken;
/// whether this room has earlier events to be fetched
bool canPaginateBack{true};
immer::map<std::string, Event> ephemeral;
std::string localDraft;
bool encrypted{false};
+ immer::flex_vector<std::string> joinedMemberIds() const;
+
using Action = std::variant<
AddStateEventsAction,
AppendTimelineAction,
PrependTimelineAction,
AddAccountDataAction,
ChangeMembershipAction,
ChangeInviteStateAction,
AddEphemeralAction,
SetLocalDraftAction,
SetRoomEncryptionAction
>;
static RoomModel update(RoomModel r, Action a);
};
using RoomAction = RoomModel::Action;
inline bool operator==(RoomModel a, RoomModel b)
{
return a.roomId == b.roomId
&& a.stateEvents == b.stateEvents
&& a.inviteState == b.inviteState
&& a.timeline == b.timeline
&& a.messages == b.messages
&& a.accountData == b.accountData
&& a.membership == b.membership
&& a.paginateBackToken == b.paginateBackToken
&& a.canPaginateBack == b.canPaginateBack
&& a.ephemeral == b.ephemeral
&& a.localDraft == b.localDraft
&& a.encrypted == b.encrypted;
}
struct UpdateRoomAction
{
std::string roomId;
RoomAction roomAction;
};
struct RoomListModel
{
immer::map<std::string, RoomModel> rooms;
inline auto at(std::string id) const { return rooms.at(id); }
inline auto operator[](std::string id) const { return rooms[id]; }
inline bool has(std::string id) const { return rooms.find(id); }
using Action = std::variant<
UpdateRoomAction
>;
static RoomListModel update(RoomListModel l, Action a);
};
using RoomListAction = RoomListModel::Action;
inline bool operator==(RoomListModel a, RoomListModel b)
{
return a.rooms == b.rooms;
}
#ifndef NDEBUG
LAGER_CEREAL_STRUCT(AddStateEventsAction);
LAGER_CEREAL_STRUCT(AppendTimelineAction);
LAGER_CEREAL_STRUCT(PrependTimelineAction);
LAGER_CEREAL_STRUCT(AddAccountDataAction);
LAGER_CEREAL_STRUCT(ChangeMembershipAction);
LAGER_CEREAL_STRUCT(SetLocalDraftAction);
LAGER_CEREAL_STRUCT(ChangeInviteStateAction);
LAGER_CEREAL_STRUCT(UpdateRoomAction);
#endif
template<class Archive>
void serialize(Archive &ar, RoomModel &r, std::uint32_t const /*version*/)
{
ar(r.roomId,
r.stateEvents,
r.inviteState,
r.timeline,
r.messages,
r.accountData,
r.membership,
r.paginateBackToken,
r.canPaginateBack);
}
template<class Archive>
void serialize(Archive &ar, RoomListModel &l, std::uint32_t const /*version*/)
{
ar(l.rooms);
}
}
CEREAL_CLASS_VERSION(Kazv::RoomModel, 0);
CEREAL_CLASS_VERSION(Kazv::RoomListModel, 0);
diff --git a/src/client/room/room.hpp b/src/client/room/room.hpp
index 5b9da47..460d6dd 100644
--- a/src/client/room/room.hpp
+++ b/src/client/room/room.hpp
@@ -1,353 +1,320 @@
/*
* Copyright (C) 2020 Tusooa Zhu
*
* This file is part of libkazv.
*
* libkazv is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* libkazv is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with libkazv. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <lager/reader.hpp>
#include <lager/context.hpp>
#include <lager/with.hpp>
#include <lager/constant.hpp>
#include <lager/lenses/optional.hpp>
#include <zug/transducer/map.hpp>
#include <zug/transducer/filter.hpp>
#include <zug/sequence.hpp>
#include <immer/flex_vector_transient.hpp>
#include "debug.hpp"
#include "client-model.hpp"
#include "room-model.hpp"
#include "client/cursorutil.hpp"
namespace Kazv
{
class Room
{
public:
inline Room(lager::reader<RoomModel> room, lager::context<ClientAction> ctx)
: m_room(room)
, m_ctx(ctx) {}
/* lager::reader<MapT<KeyOfState, Event>> */
inline auto stateEvents() const {
return m_room
[&RoomModel::stateEvents];
}
/* lager::reader<std::optional<Event>> */
inline auto stateOpt(KeyOfState k) const {
return stateEvents()
[std::move(k)];
}
/* lager::reader<Event> */
inline auto state(KeyOfState k) const {
return stateOpt(k)
[lager::lenses::or_default];
}
/* lager::reader<RangeT<Event>> */
inline auto timelineEvents() const {
return m_room
.xform(zug::map([](auto r) {
auto messages = r.messages;
auto timeline = r.timeline;
return intoImmer(
immer::flex_vector<Event>{},
zug::map([=](auto eventId) {
return messages[eventId];
}),
timeline);
}));
}
/* lager::reader<std::string> */
inline auto name() const {
using namespace lager::lenses;
return stateEvents()
[KeyOfState{"m.room.name", ""}]
[or_default]
.xform(zug::map([](Event ev) {
auto content = ev.content().get();
return
content.contains("name")
? std::string(content["name"])
// TODO: use heroes to generate a name
: "<no name>";
}));
}
/* lager::reader<std::string> */
inline auto avatarMxcUri() const {
using namespace lager::lenses;
return stateEvents()
[KeyOfState{"m.room.avatar", ""}]
[or_default]
.xform(zug::map([](Event ev) {
auto content = ev.content().get();
return
content.contains("avatar")
? std::string(content["avatar"])
: "";
}));
}
/* lager::reader<RangeT<std::string>> */
inline auto members() const {
- using MemberNode = std::pair<std::string, Kazv::Event>;
- auto memberNameTransducer =
- zug::filter(
- [](auto val) {
- auto [k, v] = val;
- auto [type, stateKey] = k;
- return type == "m.room.member"s;
- })
- | zug::map(
- [](auto val) {
- auto [k, v] = val;
- auto [type, stateKey] = k;
- return MemberNode{stateKey, v};
- })
- | zug::filter(
- [](auto val) {
- auto [stateKey, ev] = val;
- return ev.content().get()
- .at("membership"s) == "join"s;
- })
- | zug::map(
- [](auto val) {
- auto [stateKey, ev] = val;
- return stateKey;
- });
-
- return m_room
- [&RoomModel::stateEvents]
- .xform(zug::map(
- [=](auto eventMap) {
- return intoImmer(
- immer::flex_vector<std::string>{},
- memberNameTransducer,
- eventMap);
- }));
-
+ return m_room.xform(zug::map([=](auto room) {
+ return room.joinedMemberIds();
+ }));
}
inline auto memberEventByCursor(lager::reader<std::string> userId) const {
return lager::with(m_room[&RoomModel::stateEvents], userId)
.xform(zug::map([](auto events, auto userId) {
auto k = KeyOfState{"m.room.member", userId};
return events[k];
}));
}
/* lager::reader<std::optional<Event>> */
inline auto memberEventFor(std::string userId) const {
return memberEventByCursor(lager::make_constant(userId));
}
lager::reader<bool> encrypted() const;
/*lager::reader<std::string>*/
KAZV_WRAP_ATTR(RoomModel, m_room, roomId);
/*lager::reader<RoomMembership>*/
KAZV_WRAP_ATTR(RoomModel, m_room, membership);
/*lager::reader<std::string>*/
KAZV_WRAP_ATTR(RoomModel, m_room, localDraft);
inline void setLocalDraft(std::string localDraft) const {
using namespace CursorOp;
m_ctx.dispatch(UpdateRoomAction{+roomId(), SetLocalDraftAction{localDraft}});
}
inline void sendMessage(Event msg) const {
using namespace CursorOp;
m_ctx.dispatch(SendMessageAction{+roomId(), msg});
}
inline void sendTextMessage(std::string text) const {
json j{
{"type", "m.room.message"},
{"content", {
{"msgtype", "m.text"},
{"body", text}
}
}
};
Event e{j};
sendMessage(e);
}
inline void refreshRoomState() const {
using namespace CursorOp;
m_ctx.dispatch(GetRoomStatesAction{+roomId()});
}
inline void getStateEvent(std::string type, std::string stateKey) const {
using namespace CursorOp;
m_ctx.dispatch(GetStateEventAction{+roomId(), type, stateKey});
}
inline void sendStateEvent(Event state) const {
using namespace CursorOp;
m_ctx.dispatch(SendStateEventAction{+roomId(), state});
}
inline void setName(std::string name) const {
json j{
{"type", "m.room.name"},
{"content", {
{"name", name}
}
}
};
Event e{j};
sendStateEvent(e);
}
// lager::reader<std::string>
inline auto topic() const {
using namespace lager::lenses;
return stateEvents()
[KeyOfState{"m.room.topic", ""}]
[or_default]
.xform(eventContent
| jsonAtOr("topic"s, ""s));
}
inline void setTopic(std::string topic) const {
json j{
{"type", "m.room.topic"},
{"content", {
{"topic", topic}
}
}
};
Event e{j};
sendStateEvent(e);
}
inline void invite(std::string userId) const {
using namespace CursorOp;
m_ctx.dispatch(InviteToRoomAction{+roomId(), userId});
}
/* lager::reader<MapT<std::string, Event>> */
inline auto ephemeralEvents() const {
return m_room
[&RoomModel::ephemeral];
}
/* lager::reader<std::optional<Event>> */
inline auto ephemeralOpt(std::string type) const {
return m_room
[&RoomModel::ephemeral]
[type];
}
/* lager::reader<Event> */
inline auto ephemeral(std::string type) const {
return m_room
[&RoomModel::ephemeral]
[type]
[lager::lenses::or_default];
}
/* lager::reader<RangeT<std::string>> */
inline auto typingUsers() const {
using namespace lager::lenses;
return ephemeral("m.typing")
.xform(eventContent
| jsonAtOr("user_ids",
immer::flex_vector<std::string>{}));
}
inline void setTyping(bool typing, std::optional<int> timeoutMs) const {
using namespace CursorOp;
m_ctx.dispatch(SetTypingAction{+roomId(), typing, timeoutMs});
}
/* lager::reader<MapT<std::string, Event>> */
inline auto accountDataEvents() const {
return m_room
[&RoomModel::accountData];
}
/* lager::reader<std::optional<Event>> */
inline auto accountDataOpt(std::string type) const {
return m_room
[&RoomModel::accountData]
[type];
}
/* lager::reader<Event> */
inline auto accountData(std::string type) const {
return m_room
[&RoomModel::accountData]
[type]
[lager::lenses::or_default];
}
/* lager::reader<std::string> */
inline auto readMarker() const {
using namespace lager::lenses;
return accountData("m.fully_read")
.xform(eventContent
| jsonAtOr("event_id", std::string{}));
}
inline void leave() const {
using namespace CursorOp;
m_ctx.dispatch(LeaveRoomAction{+roomId()});
}
inline void forget() const {
using namespace CursorOp;
m_ctx.dispatch(ForgetRoomAction{+roomId()});
}
/* lager::reader<JsonWrap> */
inline auto avatar() const {
return state(KeyOfState{"m.room.avatar", ""})
.xform(eventContent);
}
/* lager::reader<RangeT<std::string>> */
inline auto pinnedEvents() const {
return state(KeyOfState{"m.room.pinned_events", ""})
.xform(eventContent
| jsonAtOr("pinned", immer::flex_vector<std::string>{}));
}
inline void setPinnedEvents(immer::flex_vector<std::string> eventIds) const {
json j{
{"type", "m.room.pinned_events"},
{"content", {
{"pinned", eventIds}
}
}
};
Event e{j};
sendStateEvent(e);
}
private:
lager::reader<RoomModel> m_room;
lager::context<ClientAction> m_ctx;
};
}
diff --git a/src/client/sdk-model.cpp b/src/client/sdk-model.cpp
index 913bec1..f93f1b3 100644
--- a/src/client/sdk-model.cpp
+++ b/src/client/sdk-model.cpp
@@ -1,92 +1,94 @@
/*
* Copyright (C) 2020 Tusooa Zhu <tusooa@vista.aero>
*
* This file is part of libkazv.
*
* libkazv is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* libkazv is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with libkazv. If not, see <https://www.gnu.org/licenses/>.
*/
#include "sdk-model.hpp"
namespace Kazv
{
static const int syncInterval = 2000; // ms
SdkResult SdkModel::update(SdkModel s, SdkAction a)
{
return lager::match(a)(
[&](ClientAction a) -> SdkResult {
auto [newClient, clientEff] =
ClientModel::update(std::move(s.client), a);
s.client = std::move(newClient);
bool hasCrypto{s.client.crypto};
auto jobs = s.client.popAllJobs();
auto triggers = s.client.popAllTriggers();
auto eff =
[=, clientEff=std::move(clientEff)](auto &&ctx) {
clientEff(ctx);
auto &jh = getJobHandler(ctx);
auto &ee = getEventEmitter(ctx);
for (auto j : jobs) {
jh.submit(j,
[=](Response r) {
ctx.dispatch(ProcessResponseAction{r});
});
}
for (auto t : triggers) {
ee.emit(t);
// start a sync (from posting filters) immediately after login successful
// apparently, the sync is guaranteed to be processed
// after this action has been processed
if (std::holds_alternative<LoginSuccessful>(t)) {
ctx.dispatch(PostInitialFiltersAction{});
}
// start a sync `syncInterval` ms after another sync
// if we use encryption, also upload one-time keys needed
else if (std::holds_alternative<SyncSuccessful>(t)) {
+ auto syncSuccessful = std::get<SyncSuccessful>(t);
if (hasCrypto) {
ctx.dispatch(GenerateAndUploadOneTimeKeysAction{});
+ ctx.dispatch(QueryKeysAction{syncSuccessful.isInitialSync});
}
jh.setTimeout(
[=]() {
ctx.dispatch(SyncAction{});
}, syncInterval);
}
// start a sync or publish identity keys after posting initial filters
else if (std::holds_alternative<PostInitialFiltersSuccessful>(t)) {
if (hasCrypto) {
ctx.dispatch(UploadIdentityKeysAction{});
} else {
ctx.dispatch(SyncAction{});
}
}
// start sync after publishing identity keys
else if (std::holds_alternative<UploadIdentityKeysSuccessful>(t)) {
ctx.dispatch(SyncAction{});
}
}
};
return { std::move(s), eff };
}
);
}
}
diff --git a/src/crypto/crypto-p.hpp b/src/crypto/crypto-p.hpp
index 1b33c31..49c2e5d 100644
--- a/src/crypto/crypto-p.hpp
+++ b/src/crypto/crypto-p.hpp
@@ -1,66 +1,71 @@
/*
* Copyright (C) 2021 Tusooa Zhu <tusooa@vista.aero>
*
* This file is part of libkazv.
*
* libkazv is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* libkazv is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with libkazv. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <olm/olm.h>
#include <unordered_map>
#include "crypto.hpp"
#include "crypto-util.hpp"
#include "session.hpp"
#include "inbound-group-session.hpp"
namespace Kazv
{
using SessionList = std::vector<Session>;
struct CryptoPrivate
{
CryptoPrivate();
CryptoPrivate(const CryptoPrivate &that);
~CryptoPrivate();
ByteArray accountData;
OlmAccount *account;
immer::map<std::string /* algorithm */, int> uploadedOneTimeKeysCount;
int numUnpublishedKeys{0};
std::unordered_map<std::string /* theirCurve25519IdentityKey */, Session> knownSessions;
std::unordered_map<KeyOfGroupSession, InboundGroupSession> inboundGroupSessions;
+ ByteArray utilityData;
+ OlmUtility *utility;
+
+ std::size_t checkUtilError(std::size_t code) const;
+
ByteArray pickle() const;
void unpickle(ByteArray data);
ByteArray identityKeys();
std::string ed25519IdentityKey();
std::string curve25519IdentityKey();
std::size_t checkError(std::size_t code) const;
MaybeString decryptOlm(nlohmann::json content);
// Here we need the full event for eventId and originServerTs
MaybeString decryptMegOlm(nlohmann::json eventJson);
/// returns whether the session is successfully established
bool createInboundSession(std::string theirCurve25519IdentityKey,
std::string message);
};
}
diff --git a/src/crypto/crypto.cpp b/src/crypto/crypto.cpp
index d17c7a9..e523b1e 100644
--- a/src/crypto/crypto.cpp
+++ b/src/crypto/crypto.cpp
@@ -1,318 +1,374 @@
/*
* Copyright (C) 2020 Tusooa Zhu <tusooa@vista.aero>
*
* This file is part of libkazv.
*
* libkazv is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* libkazv is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with libkazv. If not, see <https://www.gnu.org/licenses/>.
*/
#include <vector>
#include <olm/olm.h>
#include <nlohmann/json.hpp>
#include <debug.hpp>
#include <event.hpp>
#include "crypto-p.hpp"
#include "session-p.hpp"
#include "crypto-util.hpp"
namespace Kazv
{
using namespace CryptoConstants;
CryptoPrivate::CryptoPrivate()
: accountData(olm_account_size(), 0)
, account(olm_account(accountData.data()))
+ , utilityData(olm_utility_size(), '\0')
+ , utility(olm_utility(utilityData.data()))
{
auto randLen = olm_create_account_random_length(account);
auto randomData = genRandom(randLen);
checkError(olm_create_account(account, randomData.data(), randLen));
}
CryptoPrivate::~CryptoPrivate()
{
olm_clear_account(account);
}
CryptoPrivate::CryptoPrivate(const CryptoPrivate &that)
: accountData(olm_account_size(), 0)
, account(olm_account(accountData.data()))
, uploadedOneTimeKeysCount(that.uploadedOneTimeKeysCount)
, numUnpublishedKeys(that.numUnpublishedKeys)
, knownSessions(that.knownSessions)
, inboundGroupSessions(that.inboundGroupSessions)
+ , utilityData(olm_utility_size(), '\0')
+ , utility(olm_utility(utilityData.data()))
{
unpickle(that.pickle());
}
ByteArray CryptoPrivate::pickle() const
{
auto key = ByteArray(3, 'x');
auto pickleData = ByteArray(olm_pickle_account_length(account), '\0');
checkError(olm_pickle_account(account, key.data(), key.size(),
pickleData.data(), pickleData.size()));
return pickleData;
}
void CryptoPrivate::unpickle(ByteArray pickleData)
{
auto key = ByteArray(3, 'x');
checkError(olm_unpickle_account(account, key.data(), key.size(),
pickleData.data(), pickleData.size()));
}
std::size_t CryptoPrivate::checkError(std::size_t code) const
{
if (code == olm_error()) {
kzo.crypto.warn() << "Olm error: " << olm_account_last_error(account) << std::endl;
}
return code;
}
+ std::size_t CryptoPrivate::checkUtilError(std::size_t code) const
+ {
+ if (code == olm_error()) {
+ kzo.crypto.warn() << "Olm utility error: " << olm_utility_last_error(utility) << std::endl;
+ }
+ return code;
+ }
+
+
MaybeString CryptoPrivate::decryptOlm(nlohmann::json content)
{
auto theirCurve25519IdentityKey = content.at("sender_key").get<std::string>();
auto ourCurve25519IdentityKey = curve25519IdentityKey();
if (! content.at("ciphertext").contains(ourCurve25519IdentityKey)) {
return NotBut("Message not intended for us");
}
auto type = content.at("ciphertext").at(ourCurve25519IdentityKey).at("type").get<int>();
auto body = content.at("ciphertext").at(ourCurve25519IdentityKey).at("body").get<std::string>();
auto hasKnownSession = knownSessions.find(theirCurve25519IdentityKey) != knownSessions.end();
if (type == 0) { // pre-key message
bool shouldCreateNewSession =
// there is no possible session
(! hasKnownSession)
// the possible session does not match this message
|| (! knownSessions.at(theirCurve25519IdentityKey).matches(body));
if (shouldCreateNewSession) {
auto created = createInboundSession(theirCurve25519IdentityKey, body);
if (! created) { // cannot create session, thus cannot decrypt
return NotBut("Cannot create session");
}
}
auto &session = knownSessions.at(theirCurve25519IdentityKey);
return session.decrypt(type, body);
} else {
if (! hasKnownSession) {
return NotBut("No available session");
}
auto &session = knownSessions.at(theirCurve25519IdentityKey);
return session.decrypt(type, body);
}
}
MaybeString CryptoPrivate::decryptMegOlm(nlohmann::json eventJson)
{
auto content = eventJson.at("content");
auto senderKey = content.at("sender_key").get<std::string>();
auto sessionId = content.at("session_id").get<std::string>();
auto roomId = eventJson.at("room_id").get<std::string>();
auto k = KeyOfGroupSession{roomId, senderKey, sessionId};
if (inboundGroupSessions.find(k) == inboundGroupSessions.end()) {
return NotBut("We do not have the keys for this");
} else {
auto msg = content.at("ciphertext").get<std::string>();
auto eventId = eventJson.at("event_id").get<std::string>();
auto originServerTs = eventJson.at("origin_server_ts").get<Timestamp>();
auto &session = inboundGroupSessions.at(k);
return session.decrypt(msg, eventId, originServerTs);
}
}
bool CryptoPrivate::createInboundSession(std::string theirCurve25519IdentityKey,
std::string message)
{
auto s = Session(InboundSessionTag{}, account,
theirCurve25519IdentityKey, message);
if (s.valid()) {
checkError(olm_remove_one_time_keys(account, s.m_d->session));
knownSessions.insert_or_assign(theirCurve25519IdentityKey, std::move(s));
return true;
}
return false;
}
Crypto::Crypto()
: m_d(new CryptoPrivate{})
{
}
Crypto::~Crypto() = default;
Crypto::Crypto(const Crypto &that)
: m_d(new CryptoPrivate(*that.m_d))
{
}
Crypto::Crypto(Crypto &&that)
: m_d(std::move(that.m_d))
{
}
Crypto &Crypto::operator=(const Crypto &that)
{
m_d.reset(new CryptoPrivate(*that.m_d));
return *this;
}
Crypto &Crypto::operator=(Crypto &&that)
{
m_d = std::move(that.m_d);
return *this;
}
ByteArray CryptoPrivate::identityKeys()
{
auto ret = ByteArray(olm_account_identity_keys_length(account), '\0');
checkError(olm_account_identity_keys(account, ret.data(), ret.size()));
return ret;
}
std::string CryptoPrivate::ed25519IdentityKey()
{
auto keys = identityKeys();
auto keyStr = std::string(keys.begin(), keys.end());
auto keyJson = nlohmann::json::parse(keyStr);
return keyJson.at(ed25519);
}
std::string CryptoPrivate::curve25519IdentityKey()
{
auto keys = identityKeys();
auto keyStr = std::string(keys.begin(), keys.end());
auto keyJson = nlohmann::json::parse(keyStr);
return keyJson.at(curve25519);
}
std::string Crypto::ed25519IdentityKey()
{
return m_d->ed25519IdentityKey();
}
std::string Crypto::curve25519IdentityKey()
{
return m_d->curve25519IdentityKey();
}
std::string Crypto::sign(nlohmann::json j)
{
j.erase("signatures");
j.erase("unsigned");
auto str = j.dump();
auto ret = ByteArray(olm_account_signature_length(m_d->account), '\0');
kzo.crypto.dbg() << "We are about to sign: " << str << std::endl;
m_d->checkError(olm_account_sign(m_d->account,
str.data(), str.size(),
ret.data(), ret.size()));
return std::string{ret.begin(), ret.end()};
}
void Crypto::setUploadedOneTimeKeysCount(immer::map<std::string /* algorithm */, int> uploadedOneTimeKeysCount)
{
m_d->uploadedOneTimeKeysCount = uploadedOneTimeKeysCount;
}
int Crypto::maxNumberOfOneTimeKeys()
{
return olm_account_max_number_of_one_time_keys(m_d->account);
}
void Crypto::genOneTimeKeys(int num)
{
auto random = genRandom(olm_account_generate_one_time_keys_random_length(m_d->account, num));
auto res = m_d->checkError(
olm_account_generate_one_time_keys(
m_d->account,
num,
random.data(), random.size()));
if (res != olm_error()) {
m_d->numUnpublishedKeys += num;
}
}
nlohmann::json Crypto::unpublishedOneTimeKeys()
{
auto keys = ByteArray(olm_account_one_time_keys_length(m_d->account), '\0');
m_d->checkError(olm_account_one_time_keys(m_d->account, keys.data(), keys.size()));
return nlohmann::json::parse(std::string(keys.begin(), keys.end()));
}
void Crypto::markOneTimeKeysAsPublished()
{
auto ret = m_d->checkError(olm_account_mark_keys_as_published(m_d->account));
if (ret != olm_error()) {
m_d->numUnpublishedKeys = 0;
}
}
int Crypto::numUnpublishedOneTimeKeys() const
{
return m_d->numUnpublishedKeys;
}
int Crypto::uploadedOneTimeKeysCount(std::string algorithm) const
{
return m_d->uploadedOneTimeKeysCount[algorithm];
}
MaybeString Crypto::decrypt(nlohmann::json eventJson)
{
auto content = eventJson.at("content");
auto algo = content.at("algorithm").get<std::string>();
if (algo == olmAlgo) {
return m_d->decryptOlm(std::move(content));
} else if (algo == megOlmAlgo) {
return m_d->decryptMegOlm(eventJson);
}
return NotBut("Algorithm " + algo + " not supported");
}
bool Crypto::createInboundGroupSession(KeyOfGroupSession k, std::string sessionKey, std::string ed25519Key)
{
auto session = InboundGroupSession(sessionKey, ed25519Key);
if (session.valid()) {
m_d->inboundGroupSessions.insert_or_assign(k, std::move(session));
return true;
}
return false;
}
+
+ bool Crypto::verify(nlohmann::json object, std::string userId, std::string deviceId, std::string ed25519Key)
+ {
+ if (! object.contains("signatures")) {
+ return false;
+ }
+ std::string signature;
+ try {
+ signature = object.at("signatures").at(userId).at(ed25519 + ":" + deviceId);
+ } catch(const std::exception &) {
+ return false;
+ }
+ object.erase("signatures");
+ object.erase("unsigned");
+
+ auto message = object.dump();
+
+ auto res = m_d->checkUtilError(
+ olm_ed25519_verify(m_d->utility,
+ ed25519Key.c_str(), ed25519Key.size(),
+ message.c_str(), message.size(),
+ signature.data(), signature.size()));
+
+ return res != olm_error();
+ }
+
+ MaybeString Crypto::getInboundGroupSessionEd25519KeyFromEvent(const nlohmann::json &eventJson) const
+ {
+ auto content = eventJson.at("content");
+
+ auto senderKey = content.at("sender_key").get<std::string>();
+ auto sessionId = content.at("session_id").get<std::string>();
+ auto roomId = eventJson.at("room_id").get<std::string>();
+
+ auto k = KeyOfGroupSession{roomId, senderKey, sessionId};
+
+ if (m_d->inboundGroupSessions.find(k) == m_d->inboundGroupSessions.end()) {
+ return NotBut("We do not have the keys for this");
+ } else {
+ auto &session = m_d->inboundGroupSessions.at(k);
+ return session.ed25519Key();
+ }
+ }
}
diff --git a/src/crypto/crypto.hpp b/src/crypto/crypto.hpp
index 768c1a3..e26490f 100644
--- a/src/crypto/crypto.hpp
+++ b/src/crypto/crypto.hpp
@@ -1,88 +1,93 @@
/*
* Copyright (C) 2020-2021 Tusooa Zhu <tusooa@vista.aero>
*
* This file is part of libkazv.
*
* libkazv is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* libkazv is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with libkazv. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <memory>
#include <nlohmann/json.hpp>
#include <immer/map.hpp>
#include <maybe.hpp>
#include "crypto-util.hpp"
namespace Kazv
{
class Session;
struct CryptoPrivate;
class Crypto
{
public:
explicit Crypto();
Crypto(const Crypto &that);
Crypto(Crypto &&that);
Crypto &operator=(const Crypto &that);
Crypto &operator=(Crypto &&that);
~Crypto();
std::string ed25519IdentityKey();
std::string curve25519IdentityKey();
std::string sign(nlohmann::json j);
void setUploadedOneTimeKeysCount(immer::map<std::string /* algorithm */, int> uploadedOneTimeKeysCount);
int uploadedOneTimeKeysCount(std::string algorithm) const;
int maxNumberOfOneTimeKeys();
void genOneTimeKeys(int num);
/**
* According to olm.h, this returns an object like
*
* {
* curve25519: {
* "AAAAAA": "wo76WcYtb0Vk/pBOdmduiGJ0wIEjW4IBMbbQn7aSnTo",
* "AAAAAB": "LRvjo46L1X2vx69sS9QNFD29HWulxrmW11Up5AfAjgU"
* }
* }
*/
nlohmann::json unpublishedOneTimeKeys();
int numUnpublishedOneTimeKeys() const;
void markOneTimeKeysAsPublished();
/// Returns decrypted message if we can decrypt it
/// otherwise returns the error
MaybeString decrypt(nlohmann::json eventJson);
bool createInboundGroupSession(KeyOfGroupSession k, std::string sessionKey, std::string ed25519Key);
+ /// Check whether the signature of userId/deviceId is valid in object
+ bool verify(nlohmann::json object, std::string userId, std::string deviceId, std::string ed25519Key);
+
+ MaybeString getInboundGroupSessionEd25519KeyFromEvent(const nlohmann::json &eventJson) const;
+
private:
friend class Session;
friend class SessionPrivate;
std::unique_ptr<CryptoPrivate> m_d;
};
}
diff --git a/src/crypto/inbound-group-session.cpp b/src/crypto/inbound-group-session.cpp
index f99ce71..c16fcdc 100644
--- a/src/crypto/inbound-group-session.cpp
+++ b/src/crypto/inbound-group-session.cpp
@@ -1,169 +1,174 @@
/*
* Copyright (C) 2021 Tusooa Zhu <tusooa@vista.aero>
*
* This file is part of libkazv.
*
* libkazv is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* libkazv is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with libkazv. If not, see <https://www.gnu.org/licenses/>.
*/
#include "inbound-group-session-p.hpp"
#include <debug.hpp>
namespace Kazv
{
std::size_t InboundGroupSessionPrivate::checkError(std::size_t code) const
{
if (code == olm_error()) {
kzo.crypto.warn() << "Olm inbound group session error: "
<< olm_inbound_group_session_last_error(session) << std::endl;
}
return code;
}
std::string InboundGroupSessionPrivate::error() const
{
return olm_inbound_group_session_last_error(session);
}
InboundGroupSessionPrivate::InboundGroupSessionPrivate()
: sessionData(olm_inbound_group_session_size(), '\0')
, session(olm_inbound_group_session(sessionData.data()))
{
}
InboundGroupSessionPrivate::InboundGroupSessionPrivate(std::string sessionKey, std::string ed25519Key)
: InboundGroupSessionPrivate()
{
this->ed25519Key = ed25519Key;
auto keyBuf = ByteArray(sessionKey.begin(), sessionKey.end());
auto res = checkError(olm_init_inbound_group_session(session, keyBuf.data(), keyBuf.size()));
if (res != olm_error()) {
valid = true;
}
}
InboundGroupSessionPrivate::InboundGroupSessionPrivate(const InboundGroupSessionPrivate &that)
: InboundGroupSessionPrivate()
{
ed25519Key = that.ed25519Key;
valid = unpickle(that.pickle());
}
ByteArray InboundGroupSessionPrivate::pickle() const
{
auto pickleData = ByteArray(olm_pickle_inbound_group_session_length(session), '\0');
auto key = ByteArray(3, 'x');
checkError(olm_pickle_inbound_group_session(session,
key.data(), key.size(),
pickleData.data(), pickleData.size()));
return pickleData;
}
bool InboundGroupSessionPrivate::unpickle(ByteArray pickleData)
{
auto key = ByteArray(3, 'x');
auto res = checkError(olm_unpickle_inbound_group_session(
session,
key.data(), key.size(),
pickleData.data(), pickleData.size()));
return res != olm_error();
}
InboundGroupSession::InboundGroupSession()
: m_d(new InboundGroupSessionPrivate)
{
}
InboundGroupSession::InboundGroupSession(std::string sessionKey, std::string ed25519Key)
: m_d(new InboundGroupSessionPrivate(std::move(sessionKey), std::move(ed25519Key)))
{
}
InboundGroupSession::~InboundGroupSession() = default;
InboundGroupSession::InboundGroupSession(const InboundGroupSession &that)
: m_d(new InboundGroupSessionPrivate(*that.m_d))
{
}
InboundGroupSession::InboundGroupSession(InboundGroupSession &&that)
: m_d(std::move(that.m_d))
{
}
InboundGroupSession &InboundGroupSession::operator=(const InboundGroupSession &that)
{
m_d.reset(new InboundGroupSessionPrivate(*that.m_d));
return *this;
}
InboundGroupSession &InboundGroupSession::operator=(InboundGroupSession &&that)
{
m_d = std::move(that.m_d);
return *this;
}
bool InboundGroupSession::valid() const
{
return m_d && m_d->valid;
}
MaybeString InboundGroupSession::decrypt(std::string message, std::string eventId, std::int_fast64_t originServerTs)
{
ByteArray msgBuffer(message.begin(), message.end());
ByteArray msgBuffer2 = msgBuffer;
auto size = m_d->checkError(olm_group_decrypt_max_plaintext_length(
m_d->session,
msgBuffer.data(), msgBuffer.size()));
if (size == olm_error()) {
return NotBut(m_d->error());
}
auto plainText = ByteArray(size, '\0');
std::uint32_t messageIndex;
auto actualSize = m_d->checkError(olm_group_decrypt(
m_d->session,
msgBuffer2.data(), msgBuffer2.size(),
plainText.data(), plainText.size(),
&messageIndex));
if (actualSize == olm_error()) {
return NotBut(m_d->error());
}
// Check for possible replay attack
auto keyForThisMsg = KeyOfDecryptedEvent{eventId, originServerTs};
if (! m_d->decryptedEvents.find(messageIndex)) {
m_d->decryptedEvents = std::move(m_d->decryptedEvents)
.set(messageIndex, keyForThisMsg);
} else { // already decrypted in the past
auto key = m_d->decryptedEvents.at(messageIndex);
if (key != keyForThisMsg) {
return NotBut("This message has been decrypted in the past, but eventId or originServerTs does not match");
}
}
return std::string(plainText.begin(), plainText.begin() + actualSize);
}
+
+ std::string InboundGroupSession::ed25519Key() const
+ {
+ return m_d->ed25519Key;
+ }
}
diff --git a/src/crypto/inbound-group-session.hpp b/src/crypto/inbound-group-session.hpp
index e01fe7a..2ce1fc9 100644
--- a/src/crypto/inbound-group-session.hpp
+++ b/src/crypto/inbound-group-session.hpp
@@ -1,51 +1,53 @@
/*
* Copyright (C) 2021 Tusooa Zhu <tusooa@vista.aero>
*
* This file is part of libkazv.
*
* libkazv is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* libkazv is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with libkazv. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <memory>
#include <maybe.hpp>
#include <event.hpp>
#include "crypto-util.hpp"
namespace Kazv
{
struct InboundGroupSessionPrivate;
class InboundGroupSession
{
public:
explicit InboundGroupSession();
explicit InboundGroupSession(std::string sessionKey, std::string ed25519Key);
InboundGroupSession(const InboundGroupSession &that);
InboundGroupSession(InboundGroupSession &&that);
InboundGroupSession &operator=(const InboundGroupSession &that);
InboundGroupSession &operator=(InboundGroupSession &&that);
~InboundGroupSession();
MaybeString decrypt(std::string message, std::string eventId, Timestamp originServerTs);
bool valid() const;
+
+ std::string ed25519Key() const;
private:
std::unique_ptr<InboundGroupSessionPrivate> m_d;
};
}

File Metadata

Mime Type
text/x-diff
Expires
Sat, Sep 19, 10:06 AM (1 d, 22 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1769255
Default Alt Text
(281 KB)

Event Timeline