1#include "mtxclient/http/client.hpp"
2#include "mtx/log.hpp"
3#include "mtxclient/http/client_impl.hpp"
4
5#include <mutex>
6#include <thread>
7
8#include <nlohmann/json.hpp>
9
10#include <coeurl/client.hpp>
11#include <coeurl/request.hpp>
12#include <utility>
13
14#include "mtxclient/utils.hpp"
15
16#include "mtx/log.hpp"
17#include "mtx/requests.hpp"
18#include "mtx/responses.hpp"
19
20using namespace mtx::http;
21
22namespace mtx::http {
23struct ClientPrivate
24{
25 coeurl::Client client;
26};
27
28void
29UIAHandler::next(const user_interactive::Auth &auth) const
30{
31 next_(*this, auth);
32}
33}
34
35Client::Client(const std::string &server, uint16_t port)
36 : p{new ClientPrivate}
37{
38 set_server(server);
39 set_port(port);
40
41 p->client.set_verify_peer(true);
42 p->client.connection_timeout(t: 60);
43}
44
45// call destuctor of work queue and ios first!
46Client::~Client() { p.reset(); }
47
48void
49Client::shutdown()
50{
51 p->client.shutdown();
52}
53
54void
55Client::alt_svc_cache_path(const std::string &path)
56{
57 p->client.alt_svc_cache_path(path);
58}
59
60coeurl::Headers
61mtx::http::Client::prepare_headers(bool requires_auth)
62{
63 coeurl::Headers headers;
64 headers["User-Agent"] = "mtxclient v0.9.2";
65
66 if (requires_auth && !access_token_.empty())
67 headers["Authorization"] = "Bearer " + access_token();
68
69 return headers;
70}
71
72std::string
73mtx::http::Client::endpoint_to_url(const std::string &endpoint, const char *endpoint_namespace)
74{
75 return protocol_ + "://" + server_ + ":" + std::to_string(val: port_) + endpoint_namespace +
76 endpoint;
77}
78
79void
80mtx::http::Client::post(const std::string &endpoint,
81 const std::string &req,
82 mtx::http::TypeErasedCallback cb,
83 bool requires_auth,
84 const std::string &content_type)
85{
86 p->client.post(
87 url: endpoint_to_url(endpoint),
88 request_body: req,
89 mimetype: content_type,
90 callback: [cb = std::move(cb)](const coeurl::Request &r) {
91 cb(r.response_headers(), r.response(), r.error_code(), r.response_code());
92 },
93 headers: prepare_headers(requires_auth));
94}
95
96void
97mtx::http::Client::delete_(const std::string &endpoint, ErrCallback cb, bool requires_auth)
98{
99 p->client.delete_(
100 url: endpoint_to_url(endpoint),
101 callback: [cb = std::move(cb)](const coeurl::Request &r) {
102 mtx::http::ClientError client_error;
103 if (r.error_code()) {
104 client_error.error_code = r.error_code();
105 return cb(client_error);
106 }
107
108 client_error.status_code = r.response_code();
109
110 // We only count 2xx status codes as success.
111 if (client_error.status_code < 200 || client_error.status_code >= 300) {
112 // The homeserver should return an error struct.
113 try {
114 nlohmann::json json_error = nlohmann::json::parse(i: r.response());
115 client_error.matrix_error = json_error.get<mtx::errors::Error>();
116 } catch (const nlohmann::json::exception &e) {
117 client_error.parse_error =
118 std::string(e.what()) + ": " + std::string(r.response());
119 }
120 return cb(client_error);
121 }
122 return cb({});
123 },
124 headers: prepare_headers(requires_auth));
125}
126
127void
128mtx::http::Client::put(const std::string &endpoint,
129 const std::string &req,
130 mtx::http::TypeErasedCallback cb,
131 bool requires_auth)
132{
133 p->client.put(
134 url: endpoint_to_url(endpoint),
135 request_body: req,
136 mimetype: "application/json",
137 callback: [cb = std::move(cb)](const coeurl::Request &r) {
138 cb(r.response_headers(), r.response(), r.error_code(), r.response_code());
139 },
140 headers: prepare_headers(requires_auth));
141}
142
143void
144mtx::http::Client::get(const std::string &endpoint,
145 mtx::http::TypeErasedCallback cb,
146 bool requires_auth,
147 const std::string &endpoint_namespace,
148 int num_redirects)
149{
150 p->client.get(
151 url: endpoint_to_url(endpoint, endpoint_namespace: endpoint_namespace.c_str()),
152 callback: [cb = std::move(cb)](const coeurl::Request &r) {
153 cb(r.response_headers(), r.response(), r.error_code(), r.response_code());
154 },
155 headers: prepare_headers(requires_auth),
156 max_redirects: num_redirects);
157}
158
159void
160Client::verify_certificates(bool enabled)
161{
162 p->client.set_verify_peer(enabled);
163}
164
165void
166Client::set_server(const std::string &server)
167{
168 std::string_view server_name = server;
169 std::uint16_t port = 443;
170 this->protocol_ = "https";
171 // Remove https prefix, if it exists
172 if (server_name.substr(pos: 0, n: 8) == "https://") {
173 server_name.remove_prefix(n: 8);
174 port = 443;
175 }
176 if (server_name.substr(pos: 0, n: 7) == "http://") {
177 server_name.remove_prefix(n: 7);
178 port = 80;
179 this->protocol_ = "http";
180 }
181 if (server_name.size() > 0 && server_name.back() == '/')
182 server_name.remove_suffix(n: 1);
183
184 if (std::count(first: server_name.begin(), last: server_name.end(), value: ':') == 1) {
185 auto colon_offset = server_name.find(c: ':');
186 server_ = std::string(server_name.substr(pos: 0, n: colon_offset));
187
188 auto tmp = std::string(server_name.substr(pos: colon_offset + 1));
189 if (mtx::client::utils::is_number(s: tmp)) {
190 port_ = static_cast<std::uint16_t>(std::stoul(str: tmp));
191 return;
192 }
193 }
194
195 server_ = std::string(server_name);
196 port_ = port;
197}
198
199void
200Client::close(bool force)
201{
202 p->client.close(force);
203}
204
205//
206// Client API endpoints
207//
208
209void
210Client::login(const std::string &user,
211 const std::string &password,
212 const std::string &device_name,
213 Callback<mtx::responses::Login> callback)
214{
215 mtx::requests::Login req;
216 req.identifier = mtx::requests::login_identifier::User{.user: user};
217 req.password = password;
218 req.initial_device_display_name = device_name;
219
220 login(req, cb: std::move(callback));
221}
222
223void
224Client::login(const std::string &user,
225 const std::string &password,
226 Callback<mtx::responses::Login> callback)
227{
228 mtx::requests::Login req;
229 req.identifier = mtx::requests::login_identifier::User{.user: user};
230 req.password = password;
231
232 login(req, cb: std::move(callback));
233}
234
235void
236Client::login(const mtx::requests::Login &req, Callback<mtx::responses::Login> callback)
237{
238 post<mtx::requests::Login, mtx::responses::Login>(
239 endpoint: "/client/v3/login",
240 req,
241 callback: [_this = shared_from_this(),
242 callback = std::move(callback)](const mtx::responses::Login &resp, RequestErr err) {
243 if (!err && resp.access_token.size()) {
244 _this->user_id_ = resp.user_id;
245 _this->device_id_ = resp.device_id;
246 _this->access_token_ = resp.access_token;
247 }
248 callback(resp, err);
249 },
250 requires_auth: false);
251}
252
253void
254Client::get_login(Callback<mtx::responses::LoginFlows> cb)
255{
256 get<mtx::responses::LoginFlows>(
257 endpoint: "/client/v3/login",
258 callback: [cb = std::move(cb)](const mtx::responses::LoginFlows &res, HeaderFields, RequestErr err) {
259 cb(res, err);
260 },
261 requires_auth: false);
262}
263
264std::string
265Client::login_sso_redirect(std::string redirectUrl, const std::string &idp)
266{
267 const std::string idp_suffix = idp.empty() ? idp : ("/" + mtx::client::utils::url_encode(s: idp));
268 return protocol_ + "://" + server() + ":" + std::to_string(val: port()) +
269 "/_matrix/client/v3/login/sso/redirect" + idp_suffix + "?" +
270 mtx::client::utils::query_params(params: {{"redirectUrl", redirectUrl}});
271}
272
273void
274Client::well_known(Callback<mtx::responses::WellKnown> callback)
275{
276 get<mtx::responses::WellKnown>(
277 endpoint: "/matrix/client",
278 callback: [cb = std::move(callback)](
279 const mtx::responses::WellKnown &res, HeaderFields, RequestErr err) { cb(res, err); },
280 requires_auth: false,
281 endpoint_namespace: "/.well-known",
282 num_redirects: 30);
283}
284
285void
286Client::logout(Callback<mtx::responses::Logout> callback)
287{
288 mtx::requests::Logout req;
289
290 post<mtx::requests::Logout, mtx::responses::Logout>(
291 endpoint: "/client/v3/logout",
292 req,
293 callback: [_this = shared_from_this(),
294 callback = std::move(callback)](const mtx::responses::Logout &res, RequestErr err) {
295 if (!err) {
296 // Clear the now invalid access token when logout is successful
297 _this->clear();
298 }
299 // Pass up response and error to supplied callback
300 callback(res, err);
301 });
302}
303
304void
305Client::notifications(uint64_t limit,
306 const std::string &from,
307 const std::string &only,
308 Callback<mtx::responses::Notifications> cb)
309{
310 std::map<std::string, std::string> params;
311 params.emplace(args: "limit", args: std::to_string(val: limit));
312
313 if (!from.empty()) {
314 params.emplace(args: "from", args: from);
315 }
316
317 if (!only.empty()) {
318 params.emplace(args: "only", args: only);
319 }
320
321 get<mtx::responses::Notifications>(
322 endpoint: "/client/v3/notifications?" + mtx::client::utils::query_params(params),
323 callback: [cb = std::move(cb)](const mtx::responses::Notifications &res, HeaderFields, RequestErr err) {
324 cb(res, err);
325 });
326}
327
328void
329Client::get_pushrules(Callback<mtx::pushrules::GlobalRuleset> cb)
330{
331 get<mtx::pushrules::GlobalRuleset>(
332 endpoint: "/client/v3/pushrules/",
333 callback: [cb = std::move(cb)](const mtx::pushrules::GlobalRuleset &res, HeaderFields, RequestErr err) {
334 cb(res, err);
335 });
336}
337
338void
339Client::get_pushrules(const std::string &scope,
340 const std::string &kind,
341 const std::string &ruleId,
342 Callback<mtx::pushrules::PushRule> cb)
343{
344 get<mtx::pushrules::PushRule>(
345 endpoint: "/client/v3/pushrules/" + mtx::client::utils::url_encode(s: scope) + "/" +
346 mtx::client::utils::url_encode(s: kind) + "/" + mtx::client::utils::url_encode(s: ruleId),
347 callback: [cb = std::move(cb)](const mtx::pushrules::PushRule &res, HeaderFields, RequestErr err) {
348 cb(res, err);
349 });
350}
351
352void
353Client::delete_pushrules(const std::string &scope,
354 const std::string &kind,
355 const std::string &ruleId,
356 ErrCallback cb)
357{
358 delete_(endpoint: "/client/v3/pushrules/" + mtx::client::utils::url_encode(s: scope) + "/" +
359 mtx::client::utils::url_encode(s: kind) + "/" + mtx::client::utils::url_encode(s: ruleId),
360 cb: std::move(cb));
361}
362
363void
364Client::put_pushrules(const std::string &scope,
365 const std::string &kind,
366 const std::string &ruleId,
367 const mtx::pushrules::PushRule &rule,
368 ErrCallback cb,
369 const std::string &before,
370 const std::string &after)
371{
372 std::map<std::string, std::string> params;
373
374 if (!before.empty())
375 params.emplace(args: "before", args: before);
376
377 if (!after.empty())
378 params.emplace(args: "after", args: after);
379
380 std::string path = "/client/v3/pushrules/" + mtx::client::utils::url_encode(s: scope) + "/" +
381 mtx::client::utils::url_encode(s: kind) + "/" +
382 mtx::client::utils::url_encode(s: ruleId);
383 if (!params.empty())
384 path += "?" + mtx::client::utils::query_params(params);
385 put<mtx::pushrules::PushRule>(endpoint: path, req: rule, callback: std::move(cb));
386}
387
388void
389Client::get_pushrules_enabled(const std::string &scope,
390 const std::string &kind,
391 const std::string &ruleId,
392 Callback<mtx::pushrules::Enabled> cb)
393{
394 get<mtx::pushrules::Enabled>(endpoint: "/client/v3/pushrules/" + mtx::client::utils::url_encode(s: scope) +
395 "/" + mtx::client::utils::url_encode(s: kind) + "/" +
396 mtx::client::utils::url_encode(s: ruleId) + "/enabled",
397 callback: [cb = std::move(cb)](const mtx::pushrules::Enabled &res,
398 HeaderFields,
399 RequestErr err) { cb(res, err); });
400}
401
402void
403Client::put_pushrules_enabled(const std::string &scope,
404 const std::string &kind,
405 const std::string &ruleId,
406 bool enabled,
407 ErrCallback cb)
408{
409 put<mtx::pushrules::Enabled>(endpoint: "/client/v3/pushrules/" + mtx::client::utils::url_encode(s: scope) +
410 "/" + mtx::client::utils::url_encode(s: kind) + "/" +
411 mtx::client::utils::url_encode(s: ruleId) + "/enabled",
412 req: {.enabled: enabled},
413 callback: std::move(cb));
414}
415
416void
417Client::get_pushrules_actions(const std::string &scope,
418 const std::string &kind,
419 const std::string &ruleId,
420 Callback<mtx::pushrules::actions::Actions> cb)
421{
422 get<mtx::pushrules::actions::Actions>(
423 endpoint: "/client/v3/pushrules/" + mtx::client::utils::url_encode(s: scope) + "/" +
424 mtx::client::utils::url_encode(s: kind) + "/" + mtx::client::utils::url_encode(s: ruleId) +
425 "/actions",
426 callback: [cb = std::move(cb)](const mtx::pushrules::actions::Actions &res,
427 HeaderFields,
428 RequestErr err) { cb(res, err); });
429}
430
431void
432Client::put_pushrules_actions(const std::string &scope,
433 const std::string &kind,
434 const std::string &ruleId,
435 const mtx::pushrules::actions::Actions &actions,
436 ErrCallback cb)
437{
438 put<mtx::pushrules::actions::Actions>(endpoint: "/client/v3/pushrules/" +
439 mtx::client::utils::url_encode(s: scope) + "/" +
440 mtx::client::utils::url_encode(s: kind) + "/" +
441 mtx::client::utils::url_encode(s: ruleId) + "/actions",
442 req: actions,
443 callback: std::move(cb));
444}
445
446void
447Client::set_avatar_url(const std::string &avatar_url, ErrCallback callback)
448{
449 mtx::requests::AvatarUrl req;
450 req.avatar_url = avatar_url;
451
452 put<mtx::requests::AvatarUrl>(
453 endpoint: "/client/v3/profile/" + mtx::client::utils::url_encode(s: user_id_.to_string()) + "/avatar_url",
454 req,
455 callback: std::move(callback));
456}
457
458void
459Client::set_displayname(const std::string &displayname, ErrCallback callback)
460{
461 mtx::requests::DisplayName req;
462 req.displayname = displayname;
463
464 put<mtx::requests::DisplayName>(
465 endpoint: "/client/v3/profile/" + mtx::client::utils::url_encode(s: user_id_.to_string()) + "/displayname",
466 req,
467 callback: std::move(callback));
468}
469
470void
471Client::get_profile(const std::string &user_id, Callback<mtx::responses::Profile> callback)
472{
473 get<mtx::responses::Profile>(
474 endpoint: "/client/v3/profile/" + mtx::client::utils::url_encode(s: user_id),
475 callback: [callback = std::move(callback)](
476 const mtx::responses::Profile &res, HeaderFields, RequestErr err) { callback(res, err); });
477}
478
479void
480Client::get_avatar_url(const std::string &user_id, Callback<mtx::responses::AvatarUrl> callback)
481{
482 get<mtx::responses::AvatarUrl>(
483 endpoint: "/client/v3/profile/" + mtx::client::utils::url_encode(s: user_id) + "/avatar_url",
484 callback: [callback = std::move(callback)](const mtx::responses::AvatarUrl &res,
485 HeaderFields,
486 RequestErr err) { callback(res, err); });
487}
488
489void
490Client::get_tags(const std::string &room_id, Callback<mtx::events::account_data::Tags> cb)
491{
492 get<mtx::events::account_data::Tags>(
493 endpoint: "/client/v3/user/" + mtx::client::utils::url_encode(s: user_id_.to_string()) + "/rooms/" +
494 mtx::client::utils::url_encode(s: room_id) + "/tags",
495 callback: [cb = std::move(cb)](const mtx::events::account_data::Tags &res,
496 HeaderFields,
497 RequestErr err) { cb(res, err); });
498}
499void
500Client::put_tag(const std::string &room_id,
501 const std::string &tag_name,
502 const mtx::events::account_data::Tag &tag,
503 ErrCallback cb)
504{
505 put<mtx::events::account_data::Tag>(endpoint: "/client/v3/user/" +
506 mtx::client::utils::url_encode(s: user_id_.to_string()) +
507 "/rooms/" + mtx::client::utils::url_encode(s: room_id) +
508 "/tags/" + mtx::client::utils::url_encode(s: tag_name),
509 req: tag,
510 callback: std::move(cb));
511}
512void
513Client::delete_tag(const std::string &room_id, const std::string &tag_name, ErrCallback cb)
514{
515 delete_(endpoint: "/client/v3/user/" + mtx::client::utils::url_encode(s: user_id_.to_string()) + "/rooms/" +
516 mtx::client::utils::url_encode(s: room_id) + "/tags/" +
517 mtx::client::utils::url_encode(s: tag_name),
518 cb: std::move(cb));
519}
520
521void
522Client::create_room(const mtx::requests::CreateRoom &room_options,
523 Callback<mtx::responses::CreateRoom> callback)
524{
525 post<mtx::requests::CreateRoom, mtx::responses::CreateRoom>(
526 endpoint: "/client/v3/createRoom", req: room_options, callback: std::move(callback));
527}
528
529void
530Client::join_room(const std::string &room, Callback<mtx::responses::RoomId> callback)
531{
532 join_room(room, via: {}, cb: std::move(callback));
533}
534
535void
536Client::join_room(const std::string &room,
537 const std::vector<std::string> &via,
538 Callback<mtx::responses::RoomId> callback,
539 const std::string &reason)
540{
541 using mtx::client::utils::url_encode;
542 std::string query;
543 if (!via.empty()) {
544 query = "?server_name=" + url_encode(s: via[0]);
545 for (size_t i = 1; i < via.size(); i++) {
546 query += "&server_name=" + url_encode(s: via[i]);
547 }
548 }
549 auto api_path = "/client/v3/join/" + url_encode(s: room) + query;
550
551 auto body = nlohmann::json::object();
552 if (!reason.empty())
553 body["reason"] = reason;
554
555 post<std::string, mtx::responses::RoomId>(endpoint: api_path, req: body.dump(), callback: std::move(callback));
556}
557
558void
559Client::knock_room(const std::string &room,
560 const std::vector<std::string> &via,
561 Callback<mtx::responses::RoomId> cb,
562 const std::string &reason)
563{
564 using mtx::client::utils::url_encode;
565 std::string query;
566 if (!via.empty()) {
567 query = "?server_name=" + url_encode(s: via[0]);
568 for (size_t i = 1; i < via.size(); i++) {
569 query += "&server_name=" + url_encode(s: via[i]);
570 }
571 }
572 auto api_path = "/client/v3/knock/" + url_encode(s: room) + query;
573
574 auto body = nlohmann::json::object();
575 if (!reason.empty())
576 body["reason"] = reason;
577
578 post<std::string, mtx::responses::RoomId>(endpoint: api_path, req: body.dump(), callback: std::move(cb));
579}
580
581void
582Client::leave_room(const std::string &room_id,
583 Callback<mtx::responses::Empty> callback,
584 const std::string &reason)
585{
586 auto api_path = "/client/v3/rooms/" + mtx::client::utils::url_encode(s: room_id) + "/leave";
587
588 auto body = nlohmann::json::object();
589 if (!reason.empty())
590 body["reason"] = reason;
591
592 post<std::string, mtx::responses::Empty>(endpoint: api_path, req: body.dump(), callback: std::move(callback));
593}
594
595void
596Client::invite_user(const std::string &room_id,
597 const std::string &user_id,
598 Callback<mtx::responses::RoomInvite> callback,
599 const std::string &reason)
600{
601 mtx::requests::RoomMembershipChange req;
602 req.user_id = user_id;
603 req.reason = reason;
604
605 auto api_path = "/client/v3/rooms/" + mtx::client::utils::url_encode(s: room_id) + "/invite";
606
607 post<mtx::requests::RoomMembershipChange, mtx::responses::RoomInvite>(
608 endpoint: api_path, req, callback: std::move(callback));
609}
610
611void
612Client::kick_user(const std::string &room_id,
613 const std::string &user_id,
614 Callback<mtx::responses::Empty> callback,
615 const std::string &reason)
616{
617 mtx::requests::RoomMembershipChange req;
618 req.user_id = user_id;
619 req.reason = reason;
620
621 auto api_path = "/client/v3/rooms/" + mtx::client::utils::url_encode(s: room_id) + "/kick";
622
623 post<mtx::requests::RoomMembershipChange, mtx::responses::Empty>(
624 endpoint: api_path, req, callback: std::move(callback));
625}
626
627void
628Client::ban_user(const std::string &room_id,
629 const std::string &user_id,
630 Callback<mtx::responses::Empty> callback,
631 const std::string &reason)
632{
633 mtx::requests::RoomMembershipChange req;
634 req.user_id = user_id;
635 req.reason = reason;
636
637 auto api_path = "/client/v3/rooms/" + mtx::client::utils::url_encode(s: room_id) + "/ban";
638
639 post<mtx::requests::RoomMembershipChange, mtx::responses::Empty>(
640 endpoint: api_path, req, callback: std::move(callback));
641}
642
643void
644Client::unban_user(const std::string &room_id,
645 const std::string &user_id,
646 Callback<mtx::responses::Empty> callback,
647 const std::string &reason)
648{
649 mtx::requests::RoomMembershipChange req;
650 req.user_id = user_id;
651 req.reason = reason;
652
653 auto api_path = "/client/v3/rooms/" + mtx::client::utils::url_encode(s: room_id) + "/unban";
654
655 post<mtx::requests::RoomMembershipChange, mtx::responses::Empty>(
656 endpoint: api_path, req, callback: std::move(callback));
657}
658
659void
660Client::sync(const SyncOpts &opts, Callback<mtx::responses::Sync> callback)
661{
662 std::map<std::string, std::string> params;
663
664 if (!opts.filter.empty())
665 params.emplace(args: "filter", args: opts.filter);
666
667 if (!opts.since.empty())
668 params.emplace(args: "since", args: opts.since);
669
670 if (opts.full_state)
671 params.emplace(args: "full_state", args: "true");
672
673 if (opts.set_presence)
674 params.emplace(args: "set_presence", args: mtx::presence::to_string(state: opts.set_presence.value()));
675
676 params.emplace(args: "timeout", args: std::to_string(val: opts.timeout));
677
678 get<mtx::responses::Sync>(
679 endpoint: "/client/v3/sync?" + mtx::client::utils::query_params(params),
680 callback: [callback = std::move(callback)](
681 const mtx::responses::Sync &res, HeaderFields, RequestErr err) { callback(res, err); });
682}
683
684void
685Client::versions(Callback<mtx::responses::Versions> callback)
686{
687 get<mtx::responses::Versions>(
688 endpoint: "/client/versions",
689 callback: [callback = std::move(callback)](
690 const mtx::responses::Versions &res, HeaderFields, RequestErr err) { callback(res, err); });
691}
692
693void
694Client::capabilities(Callback<mtx::responses::capabilities::Capabilities> callback)
695{
696 get<mtx::responses::capabilities::Capabilities>(
697 endpoint: "/client/v3/capabilities",
698 callback: [callback = std::move(callback)](const mtx::responses::capabilities::Capabilities &res,
699 HeaderFields,
700 RequestErr err) { callback(res, err); });
701}
702
703void
704Client::upload(const std::string &data,
705 const std::string &content_type,
706 const std::string &filename,
707 Callback<mtx::responses::ContentURI> cb)
708{
709 std::map<std::string, std::string> params = {{"filename", filename}};
710
711 const auto api_path = "/media/v3/upload?" + client::utils::query_params(params);
712 post<std::string, mtx::responses::ContentURI>(
713 endpoint: api_path, req: data, callback: std::move(cb), requires_auth: true, content_type);
714}
715
716std::string
717mtx::http::Client::mxc_to_download_url(const std::string &mxc_url)
718{
719 auto url = mtx::client::utils::parse_mxc_url(url: mxc_url);
720 return endpoint_to_url(endpoint: "/media/v3/download/" + url.server + "/" + url.media_id);
721}
722
723void
724Client::download(const std::string &mxc_url,
725 std::function<void(const std::string &res,
726 const std::string &content_type,
727 const std::string &original_filename,
728 RequestErr err)> callback)
729{
730 auto url = mtx::client::utils::parse_mxc_url(url: mxc_url);
731 download(server: url.server, media_id: url.media_id, cb: std::move(callback));
732}
733
734void
735Client::get_thumbnail(const ThumbOpts &opts, Callback<std::string> callback, bool try_download)
736{
737 std::map<std::string, std::string> params;
738 params.emplace(args: "width", args: std::to_string(val: opts.width));
739 params.emplace(args: "height", args: std::to_string(val: opts.height));
740 params.emplace(args: "method", args: opts.method);
741
742 auto mxc = mtx::client::utils::parse_mxc_url(url: opts.mxc_url);
743 const auto api_path = "/media/v3/thumbnail/" + mxc.server + "/" + mxc.media_id + "?" +
744 client::utils::query_params(params);
745 get<std::string>(
746 endpoint: api_path,
747 callback: [callback = std::move(callback),
748 try_download,
749 mxc = std::move(mxc),
750 _this = shared_from_this()](const std::string &res, HeaderFields, RequestErr err) {
751 if (err && try_download) {
752 const int status_code = static_cast<int>(err->status_code);
753
754 if (status_code == 404) {
755 _this->download(server: mxc.server,
756 media_id: mxc.media_id,
757 cb: [callback](const std::string &res,
758 const std::string &, // content_type
759 const std::string &, // original_filename
760 RequestErr err) { callback(res, err); });
761 return;
762 }
763 }
764
765 callback(res, err);
766 });
767}
768
769void
770Client::download(const std::string &server,
771 const std::string &media_id,
772 std::function<void(const std::string &res,
773 const std::string &content_type,
774 const std::string &original_filename,
775 RequestErr err)> callback)
776{
777 const auto api_path = "/media/v3/download/" + server + "/" + media_id;
778 get<std::string>(
779 endpoint: api_path,
780 callback: [callback =
781 std::move(callback)](const std::string &res, HeaderFields fields, RequestErr err) {
782 std::string content_type, original_filename;
783
784 if (fields) {
785 if (fields->find(x: "Content-Type") != fields->end())
786 content_type = fields->at(k: "Content-Type");
787 if (fields->find(x: "Content-Disposition") != fields->end()) {
788 auto value = fields->at(k: "Content-Disposition");
789
790 if (auto pos = value.find(s: "filename"); pos != std::string::npos) {
791 if (auto start = value.find(c: '"', pos: pos); start != std::string::npos) {
792 auto end = value.find(c: '"', pos: start + 1);
793 original_filename = value.substr(pos: start + 1, n: end - start - 2);
794 } else if (start = value.find(c: '='); start != std::string::npos) {
795 original_filename = value.substr(pos: start + 1);
796 }
797 }
798 }
799 }
800
801 callback(res, content_type, original_filename, err);
802 });
803}
804
805void
806Client::start_typing(const std::string &room_id, uint64_t timeout, ErrCallback callback)
807{
808 using mtx::client::utils::url_encode;
809 const auto api_path =
810 "/client/v3/rooms/" + url_encode(s: room_id) + "/typing/" + url_encode(s: user_id_.to_string());
811
812 mtx::requests::TypingNotification req;
813 req.typing = true;
814 req.timeout = timeout;
815
816 put<mtx::requests::TypingNotification>(endpoint: api_path, req, callback: std::move(callback));
817}
818
819void
820Client::stop_typing(const std::string &room_id, ErrCallback callback)
821{
822 using mtx::client::utils::url_encode;
823 const auto api_path =
824 "/client/v3/rooms/" + url_encode(s: room_id) + "/typing/" + url_encode(s: user_id_.to_string());
825
826 mtx::requests::TypingNotification req;
827 req.typing = false;
828
829 put<mtx::requests::TypingNotification>(endpoint: api_path, req, callback: std::move(callback));
830}
831
832void
833Client::presence_status(const std::string &user_id,
834 Callback<mtx::events::presence::Presence> callback)
835{
836 using mtx::client::utils::url_encode;
837 const auto api_path = "/client/v3/presence/" + url_encode(s: user_id) + "/status";
838 get<mtx::events::presence::Presence>(
839 endpoint: api_path,
840 callback: [callback = std::move(callback)](const mtx::events::presence::Presence &res,
841 HeaderFields,
842 RequestErr err) { callback(res, err); });
843}
844void
845Client::put_presence_status(mtx::presence::PresenceState state,
846 std::optional<std::string> status_msg,
847 ErrCallback cb)
848{
849 using mtx::client::utils::url_encode;
850 const auto api_path = "/client/v3/presence/" + url_encode(s: user_id_.to_string()) + "/status";
851
852 nlohmann::json body;
853 body["presence"] = mtx::presence::to_string(state);
854 if (status_msg)
855 body["status_msg"] = *status_msg;
856
857 put<nlohmann::json>(endpoint: api_path, req: body, callback: std::move(cb));
858}
859
860void
861Client::get_event(const std::string &room_id,
862 const std::string &event_id,
863 Callback<mtx::events::collections::TimelineEvents> callback)
864{
865 using namespace mtx::client::utils;
866 const auto api_path =
867 "/client/v3/rooms/" + url_encode(s: room_id) + "/event/" + url_encode(s: event_id);
868
869 get<mtx::events::collections::TimelineEvent>(
870 endpoint: api_path,
871 callback: [callback = std::move(callback)](const mtx::events::collections::TimelineEvent &res,
872 HeaderFields,
873 RequestErr err) { callback(res.data, err); });
874}
875
876void
877Client::members(const std::string &room_id,
878 Callback<mtx::responses::Members> cb,
879 const std::string &at,
880 std::optional<mtx::events::state::Membership> membership,
881 std::optional<mtx::events::state::Membership> not_membership)
882{
883 std::map<std::string, std::string> params;
884
885 std::string query;
886
887 if (!at.empty())
888 params.emplace(args: "at", args: at);
889 if (membership)
890 params.emplace(args: "membership", args: events::state::membershipToString(membership: *membership));
891 if (not_membership)
892 params.emplace(args: "not_membership", args: events::state::membershipToString(membership: *not_membership));
893
894 const auto api_path = "/client/v3/rooms/" + mtx::client::utils::url_encode(s: room_id) +
895 "/members?" + client::utils::query_params(params);
896
897 get<mtx::responses::Members>(endpoint: api_path,
898 callback: [cb = std::move(cb)](const mtx::responses::Members &res,
899 HeaderFields,
900 RequestErr err) { cb(res, err); });
901}
902
903void
904Client::messages(const MessagesOpts &opts, Callback<mtx::responses::Messages> callback)
905{
906 std::map<std::string, std::string> params;
907
908 params.emplace(args: "dir", args: to_string(dir: opts.dir));
909
910 if (!opts.from.empty())
911 params.emplace(args: "from", args: opts.from);
912 if (!opts.to.empty())
913 params.emplace(args: "to", args: opts.to);
914 if (opts.limit > 0)
915 params.emplace(args: "limit", args: std::to_string(val: opts.limit));
916 if (!opts.filter.empty())
917 params.emplace(args: "filter", args: opts.filter);
918
919 const auto api_path = "/client/v3/rooms/" + mtx::client::utils::url_encode(s: opts.room_id) +
920 "/messages?" + client::utils::query_params(params);
921
922 get<mtx::responses::Messages>(
923 endpoint: api_path,
924 callback: [callback = std::move(callback)](
925 const mtx::responses::Messages &res, HeaderFields, RequestErr err) { callback(res, err); });
926}
927
928void
929Client::upload_filter(const nlohmann::json &j, Callback<mtx::responses::FilterId> callback)
930{
931 const auto api_path =
932 "/client/v3/user/" + mtx::client::utils::url_encode(s: user_id_.to_string()) + "/filter";
933
934 post<nlohmann::json, mtx::responses::FilterId>(endpoint: api_path, req: j, callback: std::move(callback));
935}
936
937void
938Client::read_event(const std::string &room_id,
939 const std::string &event_id,
940 ErrCallback callback,
941 bool hidden)
942{
943 const auto api_path =
944 "/client/v3/rooms/" + mtx::client::utils::url_encode(s: room_id) + "/read_markers";
945
946 nlohmann::json body = {
947 {"m.fully_read", event_id},
948 {"org.matrix.msc2285.read.private", event_id},
949 {"m.read.private", event_id},
950 };
951
952 if (!hidden)
953 body["m.read"] = event_id;
954
955 post<nlohmann::json, mtx::responses::Empty>(
956 endpoint: api_path,
957 req: body,
958 callback: [callback = std::move(callback)](const mtx::responses::Empty, RequestErr err) {
959 callback(err);
960 });
961}
962
963void
964Client::redact_event(const std::string &room_id,
965 const std::string &event_id,
966 Callback<mtx::responses::EventId> callback,
967 const std::string &reason)
968{
969 const auto api_path = "/client/v3/rooms/" + mtx::client::utils::url_encode(s: room_id) +
970 "/redact/" + mtx::client::utils::url_encode(s: event_id) + "/" +
971 mtx::client::utils::url_encode(s: mtx::client::utils::random_token());
972
973 nlohmann::json body = nlohmann::json::object();
974 if (!reason.empty()) {
975 body["reason"] = reason;
976 }
977
978 put<nlohmann::json, mtx::responses::EventId>(endpoint: api_path, req: body, callback: std::move(callback));
979}
980
981void
982Client::registration(const std::string &user,
983 const std::string &pass,
984 UIAHandler uia_handler,
985 Callback<mtx::responses::Register> cb,
986 const std::string &initial_device_display_name)
987{
988 nlohmann::json req = {{"username", user}, {"password", pass}};
989
990 if (!initial_device_display_name.empty())
991 req["initial_device_display_name"] = initial_device_display_name;
992
993 uia_handler.next_ = [this, req, cb = std::move(cb)](const UIAHandler &h,
994 const nlohmann::json &auth) {
995 auto request = req;
996 if (!auth.empty())
997 request["auth"] = auth;
998
999 post<nlohmann::json, mtx::responses::Register>(
1000 endpoint: "/client/v3/register",
1001 req: request,
1002 callback: [this, cb, h](auto &r, RequestErr e) {
1003 if (e && e->status_code == 401) {
1004 mtx::utils::log::log()->debug(fmt: "{}", args: e);
1005 h.prompt(h, e->matrix_error.unauthorized);
1006 } else {
1007 if (!e && !r.access_token.empty()) {
1008 this->user_id_ = r.user_id;
1009 this->device_id_ = r.device_id;
1010 this->access_token_ = r.access_token;
1011 }
1012 cb(r, e);
1013 }
1014 },
1015 requires_auth: false);
1016 };
1017
1018 uia_handler.next_(uia_handler, {});
1019}
1020
1021void
1022Client::registration(Callback<mtx::responses::Register> cb)
1023{
1024 post<nlohmann::json, mtx::responses::Register>(
1025 endpoint: "/client/v3/register", req: nlohmann::json::object(), callback: std::move(cb), requires_auth: false);
1026}
1027
1028void
1029Client::registration_token_validity(const std::string token,
1030 Callback<mtx::responses::RegistrationTokenValidity> cb)
1031{
1032 const auto api_path = "/client/v1/register/m.login.registration_token/validity?" +
1033 mtx::client::utils::query_params(params: {{"token", token}});
1034
1035 get<mtx::responses::RegistrationTokenValidity>(
1036 endpoint: api_path,
1037 callback: [cb = std::move(cb)](const mtx::responses::RegistrationTokenValidity &res,
1038 HeaderFields,
1039 RequestErr err) { cb(res, err); });
1040}
1041
1042void
1043Client::register_email_request_token(const requests::RequestEmailToken &r,
1044 Callback<mtx::responses::RequestToken> cb)
1045{
1046 post(endpoint: "/client/v3/register/email/requestToken", req: r, callback: std::move(cb));
1047}
1048
1049void
1050Client::register_username_available(const std::string &username,
1051 Callback<mtx::responses::Available> cb)
1052{
1053 get<mtx::responses::Available>(
1054 endpoint: "/client/v3/register/available?username=" + mtx::client::utils::url_encode(s: username),
1055 callback: [cb = std::move(cb)](const mtx::responses::Available &res, HeaderFields, RequestErr err) {
1056 cb(res, err);
1057 });
1058}
1059
1060void
1061Client::verify_email_request_token(const requests::RequestEmailToken &r,
1062 Callback<mtx::responses::RequestToken> cb)
1063{
1064 post(endpoint: "/client/v3/account/password/email/requestToken", req: r, callback: std::move(cb));
1065}
1066
1067void
1068Client::register_phone_request_token(const requests::RequestMSISDNToken &r,
1069 Callback<mtx::responses::RequestToken> cb)
1070{
1071 post(endpoint: "/client/v3/register/msisdn/requestToken", req: r, callback: std::move(cb));
1072}
1073void
1074Client::verify_phone_request_token(const requests::RequestMSISDNToken &r,
1075 Callback<mtx::responses::RequestToken> cb)
1076{
1077 post(endpoint: "/client/v3/account/password/msisdn/requestToken", req: r, callback: std::move(cb));
1078}
1079
1080void
1081Client::validate_submit_token(const std::string &url,
1082 const requests::IdentitySubmitToken &r,
1083 Callback<mtx::responses::Success> cb)
1084{
1085 // some dancing to send to an arbitrary, server provided url
1086 auto callback = prepare_callback<mtx::responses::Success>(
1087 callback: [cb = std::move(cb)](const mtx::responses::Success &res, HeaderFields, RequestErr err) {
1088 cb(res, err);
1089 });
1090 p->client.post(
1091 url,
1092 request_body: nlohmann::json(r).dump(),
1093 mimetype: "application/json",
1094 callback: [callback = std::move(callback)](const coeurl::Request &r) {
1095 callback(r.response_headers(), r.response(), r.error_code(), r.response_code());
1096 },
1097 headers: prepare_headers(requires_auth: false));
1098}
1099
1100void
1101Client::send_state_event(const std::string &room_id,
1102 const std::string &event_type,
1103 const std::string &state_key,
1104 const nlohmann::json &payload,
1105 Callback<mtx::responses::EventId> callback)
1106{
1107 const auto api_path = "/client/v3/rooms/" + mtx::client::utils::url_encode(s: room_id) +
1108 "/state/" + mtx::client::utils::url_encode(s: event_type) + "/" +
1109 mtx::client::utils::url_encode(s: state_key);
1110
1111 put<nlohmann::json, mtx::responses::EventId>(endpoint: api_path, req: payload, callback: std::move(callback));
1112}
1113
1114void
1115Client::send_to_device(const std::string &event_type,
1116 const std::string &txn_id,
1117 const nlohmann::json &body,
1118 ErrCallback callback)
1119{
1120 const auto api_path = "/client/v3/sendToDevice/" + mtx::client::utils::url_encode(s: event_type) +
1121 "/" + mtx::client::utils::url_encode(s: txn_id);
1122
1123 put<nlohmann::json>(endpoint: api_path, req: body, callback: std::move(callback));
1124}
1125
1126void
1127Client::resolve_room_alias(const std::string &alias, Callback<mtx::responses::RoomId> cb)
1128{
1129 const auto api_path = "/client/v3/directory/room/" + mtx::client::utils::url_encode(s: alias);
1130
1131 get<mtx::responses::RoomId>(endpoint: api_path,
1132 callback: [cb = std::move(cb)](const mtx::responses::RoomId &res,
1133 HeaderFields,
1134 RequestErr err) { cb(res, err); });
1135}
1136void
1137Client::add_room_alias(const std::string &alias, const std::string &roomid, ErrCallback cb)
1138{
1139 const auto api_path = "/client/v3/directory/room/" + mtx::client::utils::url_encode(s: alias);
1140 auto body = nlohmann::json::object();
1141 body["room_id"] = roomid;
1142 put<nlohmann::json>(endpoint: api_path, req: body, callback: std::move(cb));
1143}
1144
1145void
1146Client::delete_room_alias(const std::string &alias, ErrCallback cb)
1147{
1148 delete_(endpoint: "/client/v3/directory/room/" + mtx::client::utils::url_encode(s: alias), cb: std::move(cb));
1149}
1150
1151void
1152Client::list_room_aliases(const std::string &room_id, Callback<mtx::responses::Aliases> cb)
1153{
1154 const auto api_path =
1155 "/client/v3/rooms/" + mtx::client::utils::url_encode(s: room_id) + "/aliases";
1156
1157 get<mtx::responses::Aliases>(endpoint: api_path,
1158 callback: [cb = std::move(cb)](const mtx::responses::Aliases &res,
1159 HeaderFields,
1160 RequestErr err) { cb(res, err); });
1161}
1162
1163void
1164Client::get_room_visibility(const std::string &room_id,
1165 Callback<mtx::responses::PublicRoomVisibility> cb)
1166{
1167 const auto api_path =
1168 "/client/v3/directory/list/room/" + mtx::client::utils::url_encode(s: room_id);
1169
1170 get<mtx::responses::PublicRoomVisibility>(
1171 endpoint: api_path,
1172 callback: [cb = std::move(cb)](const mtx::responses::PublicRoomVisibility &res,
1173 HeaderFields,
1174 RequestErr err) { cb(res, err); });
1175}
1176
1177void
1178Client::put_room_visibility(const std::string &room_id,
1179 const mtx::requests::PublicRoomVisibility &req,
1180 ErrCallback cb)
1181{
1182 const auto api_path =
1183 "/client/v3/directory/list/room/" + mtx::client::utils::url_encode(s: room_id);
1184 put<mtx::requests::PublicRoomVisibility>(endpoint: api_path, req, callback: std::move(cb));
1185}
1186
1187void
1188Client::post_public_rooms(const mtx::requests::PublicRooms &req,
1189 Callback<mtx::responses::PublicRooms> cb,
1190 const std::string &server)
1191{
1192 std::string api_path = "/client/v3/publicRooms";
1193
1194 if (!server.empty())
1195 api_path += "?" + mtx::client::utils::query_params(params: {{"server", server}});
1196 post<mtx::requests::PublicRooms, mtx::responses::PublicRooms>(endpoint: api_path, req, callback: std::move(cb));
1197}
1198
1199void
1200Client::get_public_rooms(Callback<mtx::responses::PublicRooms> cb,
1201 const std::string &server,
1202 size_t limit,
1203 const std::string &since)
1204{
1205 std::string api_path = "/client/v3/publicRooms";
1206
1207 std::map<std::string, std::string> params;
1208 if (!server.empty())
1209 params["server"] = server;
1210 if (limit > 0)
1211 params["limit"] = std::to_string(val: limit);
1212 if (!since.empty())
1213 params["since"] = since;
1214
1215 if (!params.empty())
1216 api_path += "?" + mtx::client::utils::query_params(params);
1217
1218 get<mtx::responses::PublicRooms>(endpoint: api_path,
1219 callback: [cb = std::move(cb)](const mtx::responses::PublicRooms &res,
1220 HeaderFields,
1221 RequestErr err) { cb(res, err); });
1222}
1223
1224void
1225Client::get_hierarchy(const std::string &room_id,
1226 Callback<mtx::responses::HierarchyRooms> cb,
1227 const std::string &from,
1228 size_t limit,
1229 size_t max_depth,
1230 bool suggested_only)
1231{
1232 std::string api_path =
1233 "/client/v1/rooms/" + mtx::client::utils::url_encode(s: room_id) + "/hierarchy";
1234
1235 std::map<std::string, std::string> params;
1236 if (limit > 0)
1237 params["limit"] = std::to_string(val: limit);
1238 if (max_depth > 0)
1239 params["max_depth"] = std::to_string(val: max_depth);
1240 if (suggested_only)
1241 params["suggested_only"] = "true";
1242 if (!from.empty())
1243 params["from"] = from;
1244
1245 if (!params.empty())
1246 api_path += "?" + mtx::client::utils::query_params(params);
1247
1248 get<mtx::responses::HierarchyRooms>(
1249 endpoint: api_path,
1250 callback: [cb = std::move(cb)](
1251 const mtx::responses::HierarchyRooms &res, HeaderFields, RequestErr err) { cb(res, err); });
1252}
1253
1254void
1255Client::get_summary(const std::string &room_id,
1256 Callback<mtx::responses::PublicRoomsChunk> cb,
1257 std::vector<std::string> via)
1258{
1259 std::string query;
1260 if (!via.empty()) {
1261 query = "?via=" + mtx::client::utils::url_encode(s: via[0]);
1262 for (size_t i = 1; i < via.size(); i++) {
1263 query += "&via=" + mtx::client::utils::url_encode(s: via[i]);
1264 }
1265 }
1266 std::string api_path = "/client/unstable/im.nheko.summary/rooms/" +
1267 mtx::client::utils::url_encode(s: room_id) + "/summary" + query;
1268
1269 get<mtx::responses::PublicRoomsChunk>(
1270 endpoint: api_path,
1271 callback: [this, room_id, cb = std::move(cb)](
1272 const mtx::responses::PublicRoomsChunk &res, HeaderFields, RequestErr err) {
1273 if (!err || !(err->status_code == 404 || err->status_code == 400))
1274 cb(res, err);
1275 else if (!room_id.empty() && room_id[0] == '#')
1276 resolve_room_alias(
1277 alias: room_id, cb: [this, cb](const mtx::responses::RoomId &room, RequestErr err) {
1278 if (room.room_id.empty())
1279 cb({}, err);
1280 else
1281 get_hierarchy(
1282 room_id: room.room_id,
1283 cb: [cb](const mtx::responses::HierarchyRooms &res, RequestErr err) {
1284 if (res.rooms.empty())
1285 cb({}, err);
1286 else
1287 cb(res.rooms.front(), err);
1288 },
1289 from: "",
1290 limit: 1);
1291 });
1292 else
1293 get_hierarchy(
1294 room_id,
1295 cb: [cb](const mtx::responses::HierarchyRooms &res, RequestErr err) {
1296 if (res.rooms.empty())
1297 cb({}, err);
1298 else
1299 cb(res.rooms.front(), err);
1300 },
1301 from: "",
1302 limit: 1);
1303 });
1304}
1305
1306//
1307// Device related endpoints
1308//
1309
1310void
1311Client::query_devices(Callback<mtx::responses::QueryDevices> cb)
1312{
1313 get<mtx::responses::QueryDevices>(endpoint: "/client/v3/devices",
1314 callback: [cb = std::move(cb)](const mtx::responses::QueryDevices &res,
1315 HeaderFields,
1316 RequestErr err) { cb(res, err); });
1317}
1318
1319void
1320Client::get_device(const std::string &device_id, Callback<mtx::responses::Device> cb)
1321{
1322 get<mtx::responses::Device>(endpoint: "/client/v3/devices/" + mtx::client::utils::url_encode(s: device_id),
1323 callback: [cb = std::move(cb)](const mtx::responses::Device &res,
1324 HeaderFields,
1325 RequestErr err) { cb(res, err); });
1326}
1327
1328void
1329Client::set_device_name(const std::string &device_id,
1330 const std::string &display_name,
1331 ErrCallback callback)
1332{
1333 mtx::requests::DeviceUpdate req;
1334 req.display_name = display_name;
1335
1336 put<mtx::requests::DeviceUpdate>(
1337 endpoint: "/client/v3/devices/" + mtx::client::utils::url_encode(s: device_id), req, callback: std::move(callback));
1338}
1339
1340void
1341Client::delete_device(const std::string &device_id, UIAHandler uia_handler, ErrCallback cb)
1342{
1343 nlohmann::json req;
1344 req["devices"] = {device_id};
1345
1346 uia_handler.next_ = [this, req, cb = std::move(cb)](const UIAHandler &h,
1347 const nlohmann::json &auth) {
1348 auto request = req;
1349 if (!auth.empty())
1350 request["auth"] = auth;
1351
1352 post<nlohmann::json, mtx::responses::Empty>(
1353 endpoint: "/client/v3/delete_devices", req: request, callback: [cb, h](auto &, RequestErr e) {
1354 if (e && e->status_code == 401 && !e->matrix_error.unauthorized.flows.empty())
1355 h.prompt(h, e->matrix_error.unauthorized);
1356 else
1357 cb(e);
1358 });
1359 };
1360
1361 uia_handler.next_(uia_handler, {});
1362}
1363
1364void
1365Client::delete_devices(const std::vector<std::string> &device_ids,
1366 UIAHandler uia_handler,
1367 ErrCallback cb)
1368{
1369 nlohmann::json req;
1370 req["devices"] = device_ids;
1371
1372 uia_handler.next_ = [this, req = std::move(req), cb = std::move(cb)](
1373 const UIAHandler &h, const nlohmann::json &auth) {
1374 auto request = req;
1375 if (!auth.empty())
1376 request["auth"] = auth;
1377
1378 post<nlohmann::json, mtx::responses::Empty>(
1379 endpoint: "/client/v3/delete_devices", req: request, callback: [cb, h](auto &, RequestErr e) {
1380 if (e && e->status_code == 401 && !e->matrix_error.unauthorized.flows.empty())
1381 h.prompt(h, e->matrix_error.unauthorized);
1382 else
1383 cb(e);
1384 });
1385 };
1386
1387 uia_handler.next_(uia_handler, {});
1388}
1389
1390//
1391// Encryption related endpoints
1392//
1393
1394void
1395Client::upload_keys(const mtx::requests::UploadKeys &req,
1396 Callback<mtx::responses::UploadKeys> callback)
1397{
1398 post<mtx::requests::UploadKeys, mtx::responses::UploadKeys>(
1399 endpoint: "/client/v3/keys/upload", req, callback: std::move(callback));
1400}
1401
1402void
1403Client::keys_signatures_upload(const mtx::requests::KeySignaturesUpload &req,
1404 Callback<mtx::responses::KeySignaturesUpload> cb)
1405{
1406 post<mtx::requests::KeySignaturesUpload, mtx::responses::KeySignaturesUpload>(
1407 endpoint: "/client/v3/keys/signatures/upload", req, callback: std::move(cb));
1408}
1409
1410void
1411Client::device_signing_upload(const mtx::requests::DeviceSigningUpload &deviceKeys,
1412 UIAHandler uia_handler,
1413 ErrCallback cb)
1414{
1415 nlohmann::json req = deviceKeys;
1416
1417 uia_handler.next_ = [this, req = std::move(req), cb = std::move(cb)](
1418 const UIAHandler &h, const nlohmann::json &auth) {
1419 auto request = req;
1420 if (!auth.empty())
1421 request["auth"] = auth;
1422
1423 post<nlohmann::json, mtx::responses::Empty>(
1424 endpoint: "/client/v3/keys/device_signing/upload", req: request, callback: [cb, h](auto &, RequestErr e) {
1425 if (e && e->status_code == 401 && !e->matrix_error.unauthorized.flows.empty())
1426 h.prompt(h, e->matrix_error.unauthorized);
1427 else
1428 cb(e);
1429 });
1430 };
1431
1432 uia_handler.next_(uia_handler, {});
1433}
1434
1435void
1436Client::query_keys(const mtx::requests::QueryKeys &req,
1437 Callback<mtx::responses::QueryKeys> callback)
1438{
1439 post<mtx::requests::QueryKeys, mtx::responses::QueryKeys>(
1440 endpoint: "/client/v3/keys/query", req, callback: std::move(callback));
1441}
1442
1443//! Claims one-time keys for use in pre-key messages.
1444void
1445Client::claim_keys(const mtx::requests::ClaimKeys &req, Callback<mtx::responses::ClaimKeys> cb)
1446{
1447 post<mtx::requests::ClaimKeys, mtx::responses::ClaimKeys>(
1448 endpoint: "/client/v3/keys/claim", req, callback: std::move(cb));
1449}
1450
1451void
1452Client::key_changes(const std::string &from,
1453 const std::string &to,
1454 Callback<mtx::responses::KeyChanges> callback)
1455{
1456 std::map<std::string, std::string> params;
1457
1458 if (!from.empty())
1459 params.emplace(args: "from", args: from);
1460
1461 if (!to.empty())
1462 params.emplace(args: "to", args: to);
1463
1464 get<mtx::responses::KeyChanges>(
1465 endpoint: "/client/v3/keys/changes?" + mtx::client::utils::query_params(params),
1466 callback: [callback = std::move(callback)](const mtx::responses::KeyChanges &res,
1467 HeaderFields,
1468 RequestErr err) { callback(res, err); });
1469}
1470
1471//
1472// Key backup endpoints
1473//
1474void
1475Client::backup_version(Callback<mtx::responses::backup::BackupVersion> cb)
1476{
1477 get<mtx::responses::backup::BackupVersion>(
1478 endpoint: "/client/v3/room_keys/version",
1479 callback: [cb = std::move(cb)](const mtx::responses::backup::BackupVersion &res,
1480 HeaderFields,
1481 RequestErr err) { cb(res, err); });
1482}
1483void
1484Client::backup_version(const std::string &version,
1485 Callback<mtx::responses::backup::BackupVersion> cb)
1486{
1487 get<mtx::responses::backup::BackupVersion>(
1488 endpoint: "/client/v3/room_keys/version/" + mtx::client::utils::url_encode(s: version),
1489 callback: [cb = std::move(cb)](const mtx::responses::backup::BackupVersion &res,
1490 HeaderFields,
1491 RequestErr err) { cb(res, err); });
1492}
1493
1494void
1495Client::update_backup_version(const std::string &version,
1496 const mtx::responses::backup::BackupVersion &data,
1497 ErrCallback cb)
1498{
1499 put<mtx::responses::backup::BackupVersion>(endpoint: "/client/v3/room_keys/version/" +
1500 mtx::client::utils::url_encode(s: version),
1501 req: data,
1502 callback: std::move(cb));
1503}
1504
1505void
1506Client::post_backup_version(const std::string &algorithm,
1507 const std::string &auth_data,
1508 Callback<mtx::responses::Version> cb)
1509{
1510 nlohmann::json req = {{"algorithm", algorithm},
1511 {"auth_data", nlohmann::json::parse(i: auth_data)}};
1512 post<nlohmann::json, mtx::responses::Version>(
1513 endpoint: "/client/v3/room_keys/version", req, callback: std::move(cb));
1514}
1515void
1516Client::room_keys(const std::string &version, Callback<mtx::responses::backup::KeysBackup> cb)
1517{
1518 get<mtx::responses::backup::KeysBackup>(
1519 endpoint: "/client/v3/room_keys/keys?" + mtx::client::utils::query_params(params: {{"version", version}}),
1520 callback: [cb = std::move(cb)](const mtx::responses::backup::KeysBackup &res,
1521 HeaderFields,
1522 RequestErr err) { cb(res, err); });
1523}
1524void
1525Client::room_keys(const std::string &version,
1526 const std::string &room_id,
1527 Callback<mtx::responses::backup::RoomKeysBackup> cb)
1528{
1529 get<mtx::responses::backup::RoomKeysBackup>(
1530 endpoint: "/client/v3/room_keys/keys/" + mtx::client::utils::url_encode(s: room_id) + "?" +
1531 mtx::client::utils::query_params(params: {{"version", version}}),
1532 callback: [cb = std::move(cb)](const mtx::responses::backup::RoomKeysBackup &res,
1533 HeaderFields,
1534 RequestErr err) { cb(res, err); });
1535}
1536void
1537Client::room_keys(const std::string &version,
1538 const std::string &room_id,
1539 const std::string &session_id,
1540 Callback<mtx::responses::backup::SessionBackup> cb)
1541{
1542 get<mtx::responses::backup::SessionBackup>(
1543 endpoint: "/client/v3/room_keys/keys/" + mtx::client::utils::url_encode(s: room_id) + "/" +
1544 mtx::client::utils::url_encode(s: session_id) + "?" +
1545 mtx::client::utils::query_params(params: {{"version", version}}),
1546 callback: [cb = std::move(cb)](const mtx::responses::backup::SessionBackup &res,
1547 HeaderFields,
1548 RequestErr err) { cb(res, err); });
1549}
1550
1551void
1552Client::put_room_keys(const std::string &version,
1553 const mtx::responses::backup::KeysBackup &keys,
1554 ErrCallback cb)
1555{
1556 put(endpoint: "/client/v3/room_keys/keys?" + mtx::client::utils::query_params(params: {{"version", version}}),
1557 req: keys,
1558 callback: std::move(cb));
1559}
1560void
1561Client::put_room_keys(const std::string &version,
1562 const std::string &room_id,
1563 const mtx::responses::backup::RoomKeysBackup &keys,
1564 ErrCallback cb)
1565{
1566 put(endpoint: "/client/v3/room_keys/keys/" + mtx::client::utils::url_encode(s: room_id) + "?" +
1567 mtx::client::utils::query_params(params: {{"version", version}}),
1568 req: keys,
1569 callback: std::move(cb));
1570}
1571void
1572Client::put_room_keys(const std::string &version,
1573 const std::string &room_id,
1574 const std::string &session_id,
1575 const mtx::responses::backup::SessionBackup &keys,
1576 ErrCallback cb)
1577{
1578 put(endpoint: "/client/v3/room_keys/keys/" + mtx::client::utils::url_encode(s: room_id) + "/" +
1579 mtx::client::utils::url_encode(s: session_id) + "?" +
1580 mtx::client::utils::query_params(params: {{"version", version}}),
1581 req: keys,
1582 callback: std::move(cb));
1583}
1584
1585//! Retrieve a specific secret
1586void
1587Client::secret_storage_secret(const std::string &secret_id,
1588 Callback<mtx::secret_storage::Secret> cb)
1589{
1590 get<mtx::secret_storage::Secret>(
1591 endpoint: "/client/v3/user/" + mtx::client::utils::url_encode(s: user_id_.to_string()) + "/account_data/" +
1592 mtx::client::utils::url_encode(s: secret_id),
1593 callback: [cb = std::move(cb)](const mtx::secret_storage::Secret &res, HeaderFields, RequestErr err) {
1594 cb(res, err);
1595 });
1596}
1597//! Retrieve information about a key
1598void
1599Client::secret_storage_key(const std::string &key_id,
1600 Callback<mtx::secret_storage::AesHmacSha2KeyDescription> cb)
1601{
1602 get<mtx::secret_storage::AesHmacSha2KeyDescription>(
1603 endpoint: "/client/v3/user/" + mtx::client::utils::url_encode(s: user_id_.to_string()) +
1604 "/account_data/m.secret_storage.key." + mtx::client::utils::url_encode(s: key_id),
1605 callback: [cb = std::move(cb)](const mtx::secret_storage::AesHmacSha2KeyDescription &res,
1606 HeaderFields,
1607 RequestErr err) { cb(res, err); });
1608}
1609
1610//! Upload a specific secret
1611void
1612Client::upload_secret_storage_secret(const std::string &secret_id,
1613 const mtx::secret_storage::Secret &secret,
1614 ErrCallback cb)
1615{
1616 put(endpoint: "/client/v3/user/" + mtx::client::utils::url_encode(s: user_id_.to_string()) +
1617 "/account_data/" + mtx::client::utils::url_encode(s: secret_id),
1618 req: secret,
1619 callback: std::move(cb));
1620}
1621
1622//! Upload information about a key
1623void
1624Client::upload_secret_storage_key(const std::string &key_id,
1625 const mtx::secret_storage::AesHmacSha2KeyDescription &desc,
1626 ErrCallback cb)
1627{
1628 put(endpoint: "/client/v3/user/" + mtx::client::utils::url_encode(s: user_id_.to_string()) +
1629 "/account_data/m.secret_storage.key." + mtx::client::utils::url_encode(s: key_id),
1630 req: desc,
1631 callback: std::move(cb));
1632}
1633
1634void
1635Client::set_secret_storage_default_key(const std::string &key_id, ErrCallback cb)
1636{
1637 nlohmann::json key = {{"key", key_id}};
1638 put(endpoint: "/client/v3/user/" + mtx::client::utils::url_encode(s: user_id_.to_string()) +
1639 "/account_data/m.secret_storage.default_key",
1640 req: key,
1641 callback: std::move(cb));
1642}
1643
1644void
1645Client::enable_encryption(const std::string &room, Callback<mtx::responses::EventId> callback)
1646{
1647 using namespace mtx::events;
1648 state::Encryption event;
1649
1650 send_state_event<state::Encryption>(room_id: room, state_key: "", payload: event, callback: std::move(callback));
1651}
1652
1653void
1654Client::get_turn_server(Callback<mtx::responses::TurnServer> cb)
1655{
1656 get<mtx::responses::TurnServer>(endpoint: "/client/v3/voip/turnServer",
1657 callback: [cb = std::move(cb)](const mtx::responses::TurnServer &res,
1658 HeaderFields,
1659 RequestErr err) { cb(res, err); });
1660}
1661
1662void
1663Client::set_pusher(const mtx::requests::SetPusher &req, Callback<mtx::responses::Empty> cb)
1664{
1665 post<mtx::requests::SetPusher, mtx::responses::Empty>(
1666 endpoint: "/client/v3/pushers/set", req, callback: std::move(cb));
1667}
1668
1669void
1670Client::search_user_directory(const std::string &search_term,
1671 Callback<mtx::responses::Users> callback,
1672 int limit)
1673{
1674 nlohmann::json req = {{"search_term", search_term}};
1675 if (limit >= 0)
1676 req["limit"] = limit;
1677 post<nlohmann::json, mtx::responses::Users>(
1678 endpoint: "/client/v3/user_directory/search", req, callback: std::move(callback));
1679}
1680
1681// Template instantiations for the various send functions
1682
1683#define MTXCLIENT_SEND_STATE_EVENT(Content) \
1684 template void mtx::http::Client::send_state_event<mtx::events::Content>( \
1685 const std::string &, \
1686 const std::string &state_key, \
1687 const mtx::events::Content &, \
1688 Callback<mtx::responses::EventId> cb); \
1689 template void mtx::http::Client::send_state_event<mtx::events::Content>( \
1690 const std::string &, const mtx::events::Content &, Callback<mtx::responses::EventId> cb); \
1691 template void mtx::http::Client::get_state_event<mtx::events::Content>( \
1692 const std::string &room_id, \
1693 const std::string &type, \
1694 const std::string &state_key, \
1695 Callback<mtx::events::Content> cb); \
1696 template void mtx::http::Client::get_state_event<mtx::events::Content>( \
1697 const std::string &room_id, \
1698 const std::string &state_key, \
1699 Callback<mtx::events::Content> cb);
1700
1701MTXCLIENT_SEND_STATE_EVENT(state::Aliases)
1702MTXCLIENT_SEND_STATE_EVENT(state::Avatar)
1703MTXCLIENT_SEND_STATE_EVENT(state::CanonicalAlias)
1704MTXCLIENT_SEND_STATE_EVENT(state::Create)
1705MTXCLIENT_SEND_STATE_EVENT(state::Encryption)
1706MTXCLIENT_SEND_STATE_EVENT(state::GuestAccess)
1707MTXCLIENT_SEND_STATE_EVENT(state::HistoryVisibility)
1708MTXCLIENT_SEND_STATE_EVENT(state::JoinRules)
1709MTXCLIENT_SEND_STATE_EVENT(state::Member)
1710MTXCLIENT_SEND_STATE_EVENT(state::Name)
1711MTXCLIENT_SEND_STATE_EVENT(state::PinnedEvents)
1712MTXCLIENT_SEND_STATE_EVENT(state::PowerLevels)
1713MTXCLIENT_SEND_STATE_EVENT(state::Tombstone)
1714MTXCLIENT_SEND_STATE_EVENT(state::Topic)
1715MTXCLIENT_SEND_STATE_EVENT(state::Widget)
1716MTXCLIENT_SEND_STATE_EVENT(state::policy_rule::UserRule)
1717MTXCLIENT_SEND_STATE_EVENT(state::policy_rule::RoomRule)
1718MTXCLIENT_SEND_STATE_EVENT(state::policy_rule::ServerRule)
1719MTXCLIENT_SEND_STATE_EVENT(state::space::Child)
1720MTXCLIENT_SEND_STATE_EVENT(state::space::Parent)
1721MTXCLIENT_SEND_STATE_EVENT(msc2545::ImagePack)
1722
1723#define MTXCLIENT_SEND_ROOM_MESSAGE(Content) \
1724 template void mtx::http::Client::send_room_message<Content>( \
1725 const std::string &, \
1726 const std::string &, \
1727 const Content &, \
1728 Callback<mtx::responses::EventId> cb); \
1729 template void mtx::http::Client::send_room_message<Content>( \
1730 const std::string &, const Content &, Callback<mtx::responses::EventId> cb);
1731
1732MTXCLIENT_SEND_ROOM_MESSAGE(mtx::events::msg::Encrypted)
1733MTXCLIENT_SEND_ROOM_MESSAGE(mtx::events::msg::StickerImage)
1734MTXCLIENT_SEND_ROOM_MESSAGE(mtx::events::msg::Reaction)
1735MTXCLIENT_SEND_ROOM_MESSAGE(mtx::events::msg::Audio)
1736MTXCLIENT_SEND_ROOM_MESSAGE(mtx::events::msg::Emote)
1737MTXCLIENT_SEND_ROOM_MESSAGE(mtx::events::msg::File)
1738MTXCLIENT_SEND_ROOM_MESSAGE(mtx::events::msg::Image)
1739MTXCLIENT_SEND_ROOM_MESSAGE(mtx::events::msg::Notice)
1740MTXCLIENT_SEND_ROOM_MESSAGE(mtx::events::msg::Text)
1741MTXCLIENT_SEND_ROOM_MESSAGE(mtx::events::msg::Video)
1742MTXCLIENT_SEND_ROOM_MESSAGE(mtx::events::msg::Confetti)
1743// MTXCLIENT_SEND_ROOM_MESSAGE(mtx::events::msg::KeyVerificationRequest)
1744// MTXCLIENT_SEND_ROOM_MESSAGE(mtx::events::msg::KeyVerificationStart)
1745// MTXCLIENT_SEND_ROOM_MESSAGE(mtx::events::msg::KeyVerificationReady)
1746// MTXCLIENT_SEND_ROOM_MESSAGE(mtx::events::msg::KeyVerificationDone)
1747// MTXCLIENT_SEND_ROOM_MESSAGE(mtx::events::msg::KeyVerificationAccept)
1748// MTXCLIENT_SEND_ROOM_MESSAGE(mtx::events::msg::KeyVerificationCancel)
1749// MTXCLIENT_SEND_ROOM_MESSAGE(mtx::events::msg::KeyVerificationKey)
1750// MTXCLIENT_SEND_ROOM_MESSAGE(mtx::events::msg::KeyVerificationMac)
1751MTXCLIENT_SEND_ROOM_MESSAGE(mtx::events::voip::CallInvite)
1752MTXCLIENT_SEND_ROOM_MESSAGE(mtx::events::voip::CallCandidates)
1753MTXCLIENT_SEND_ROOM_MESSAGE(mtx::events::voip::CallAnswer)
1754MTXCLIENT_SEND_ROOM_MESSAGE(mtx::events::voip::CallHangUp)
1755MTXCLIENT_SEND_ROOM_MESSAGE(mtx::events::voip::CallSelectAnswer)
1756MTXCLIENT_SEND_ROOM_MESSAGE(mtx::events::voip::CallReject)
1757MTXCLIENT_SEND_ROOM_MESSAGE(mtx::events::voip::CallNegotiate)
1758
1759#define MTXCLIENT_SEND_TO_DEVICE(Content) \
1760 template void mtx::http::Client::send_to_device<Content>( \
1761 const std::string &txid, \
1762 const std::map<mtx::identifiers::User, std::map<std::string, Content>> &messages, \
1763 ErrCallback callback);
1764
1765MTXCLIENT_SEND_TO_DEVICE(mtx::events::msg::RoomKey)
1766MTXCLIENT_SEND_TO_DEVICE(mtx::events::msg::ForwardedRoomKey)
1767MTXCLIENT_SEND_TO_DEVICE(mtx::events::msg::KeyRequest)
1768MTXCLIENT_SEND_TO_DEVICE(mtx::events::msg::OlmEncrypted)
1769MTXCLIENT_SEND_TO_DEVICE(mtx::events::msg::Encrypted)
1770MTXCLIENT_SEND_TO_DEVICE(mtx::events::msg::Dummy)
1771MTXCLIENT_SEND_TO_DEVICE(mtx::events::msg::KeyVerificationRequest)
1772MTXCLIENT_SEND_TO_DEVICE(mtx::events::msg::KeyVerificationStart)
1773MTXCLIENT_SEND_TO_DEVICE(mtx::events::msg::KeyVerificationReady)
1774MTXCLIENT_SEND_TO_DEVICE(mtx::events::msg::KeyVerificationDone)
1775MTXCLIENT_SEND_TO_DEVICE(mtx::events::msg::KeyVerificationAccept)
1776MTXCLIENT_SEND_TO_DEVICE(mtx::events::msg::KeyVerificationCancel)
1777MTXCLIENT_SEND_TO_DEVICE(mtx::events::msg::KeyVerificationKey)
1778MTXCLIENT_SEND_TO_DEVICE(mtx::events::msg::KeyVerificationMac)
1779MTXCLIENT_SEND_TO_DEVICE(mtx::events::msg::SecretSend)
1780MTXCLIENT_SEND_TO_DEVICE(mtx::events::msg::SecretRequest)
1781
1782#define MTXCLIENT_ACCOUNT_DATA(Payload) \
1783 template void mtx::http::Client::put_room_account_data<Payload>(const std::string &room_id, \
1784 const std::string &type, \
1785 const Payload &payload, \
1786 ErrCallback cb); \
1787 template void mtx::http::Client::put_room_account_data<Payload>( \
1788 const std::string &room_id, const Payload &payload, ErrCallback cb); \
1789 template void mtx::http::Client::put_account_data<Payload>( \
1790 const std::string &type, const Payload &payload, ErrCallback cb); \
1791 template void mtx::http::Client::put_account_data<Payload>(const Payload &payload, \
1792 ErrCallback cb); \
1793 template void mtx::http::Client::get_room_account_data<Payload>( \
1794 const std::string &room_id, const std::string &type, Callback<Payload> payload); \
1795 template void mtx::http::Client::get_room_account_data<Payload>(const std::string &room_id, \
1796 Callback<Payload> cb); \
1797 template void mtx::http::Client::get_account_data<Payload>(const std::string &type, \
1798 Callback<Payload> payload); \
1799 template void mtx::http::Client::get_account_data<Payload>(Callback<Payload> cb);
1800
1801MTXCLIENT_ACCOUNT_DATA(mtx::events::msc2545::ImagePack)
1802MTXCLIENT_ACCOUNT_DATA(mtx::events::msc2545::ImagePackRooms)
1803MTXCLIENT_ACCOUNT_DATA(mtx::events::account_data::nheko_extensions::HiddenEvents)
1804MTXCLIENT_ACCOUNT_DATA(mtx::events::account_data::Tags)
1805MTXCLIENT_ACCOUNT_DATA(mtx::events::account_data::Direct)
1806