2019-10-16 18:39:57 +02:00
|
|
|
#include <3ds.h>
|
2023-12-10 20:24:28 +01:00
|
|
|
#include <curl/curl.h>
|
2019-10-16 18:39:57 +02:00
|
|
|
#include <jansson.h>
|
2019-10-16 21:26:06 +02:00
|
|
|
#include <malloc.h>
|
2023-12-10 20:24:28 +01:00
|
|
|
#include <matrixclient.h>
|
|
|
|
#include <olm/olm.h>
|
2023-12-11 21:17:47 +01:00
|
|
|
#include <cstdio>
|
2023-12-10 20:24:28 +01:00
|
|
|
#include <cstring>
|
|
|
|
#include <filesystem>
|
|
|
|
#include <fstream>
|
|
|
|
#include <iostream>
|
|
|
|
#include <map>
|
|
|
|
#include <olm/account.hh>
|
2019-10-18 13:10:54 +02:00
|
|
|
#include <string>
|
2023-12-11 21:17:47 +01:00
|
|
|
#include <utility>
|
2019-10-18 13:10:54 +02:00
|
|
|
#include <vector>
|
2023-12-10 20:24:28 +01:00
|
|
|
#include "memorystore.h"
|
|
|
|
#include "olm/base64.hh"
|
|
|
|
#include "util.h"
|
2019-10-16 22:57:55 +02:00
|
|
|
|
2019-10-16 21:26:06 +02:00
|
|
|
#include <sys/socket.h>
|
|
|
|
|
|
|
|
#define SOC_ALIGN 0x1000
|
|
|
|
#define SOC_BUFFERSIZE 0x100000
|
2019-10-17 10:45:55 +02:00
|
|
|
#define SYNC_TIMEOUT 10000
|
|
|
|
|
2019-10-17 20:31:00 +02:00
|
|
|
#if DEBUG
|
|
|
|
#define D
|
|
|
|
#else
|
|
|
|
#define D for(;0;)
|
|
|
|
#endif
|
|
|
|
|
2019-10-20 23:49:08 +02:00
|
|
|
#if DEBUG
|
2023-12-11 21:17:47 +01:00
|
|
|
PrintConsole* topScreenDebugConsole = nullptr;
|
2019-10-20 23:49:08 +02:00
|
|
|
#endif
|
2019-10-18 12:40:19 +02:00
|
|
|
|
2019-10-20 23:49:08 +02:00
|
|
|
#if DEBUG
|
2019-10-21 17:00:10 +02:00
|
|
|
#define printf_top(f_, ...) do {consoleSelect(topScreenDebugConsole);printf((f_), ##__VA_ARGS__);} while(0)
|
2019-10-20 23:49:08 +02:00
|
|
|
#else
|
|
|
|
#define printf_top(f_, ...) do {} while(0)
|
|
|
|
#endif
|
2019-10-18 12:40:19 +02:00
|
|
|
|
2019-10-17 10:45:55 +02:00
|
|
|
namespace Matrix {
|
2019-10-16 21:26:06 +02:00
|
|
|
|
2019-10-18 12:40:19 +02:00
|
|
|
#define POST_BUFFERSIZE 0x100000
|
|
|
|
|
2023-12-11 21:17:47 +01:00
|
|
|
static u32 *SOC_buffer = nullptr;
|
2019-10-18 12:40:19 +02:00
|
|
|
bool HTTPC_inited = false;
|
2019-10-27 19:55:05 +01:00
|
|
|
bool haveHttpcSupport = false;
|
2019-10-16 18:39:57 +02:00
|
|
|
|
2019-10-17 10:45:55 +02:00
|
|
|
Client::Client(std::string homeserverUrl, std::string matrixToken, Store* clientStore) {
|
2023-12-11 21:17:47 +01:00
|
|
|
hsUrl = std::move(homeserverUrl);
|
|
|
|
token = std::move(matrixToken);
|
2019-10-17 10:45:55 +02:00
|
|
|
if (!clientStore) {
|
2019-10-19 18:12:10 +02:00
|
|
|
clientStore = new MemoryStore;
|
2019-10-17 10:45:55 +02:00
|
|
|
}
|
|
|
|
store = clientStore;
|
2019-10-20 23:49:08 +02:00
|
|
|
#if DEBUG
|
2019-10-21 17:00:10 +02:00
|
|
|
if (!topScreenDebugConsole) {
|
|
|
|
topScreenDebugConsole = new PrintConsole;
|
|
|
|
consoleInit(GFX_TOP, topScreenDebugConsole);
|
2019-10-18 12:40:19 +02:00
|
|
|
}
|
2019-10-20 23:49:08 +02:00
|
|
|
#endif
|
2019-10-16 22:57:55 +02:00
|
|
|
}
|
|
|
|
|
2023-12-09 14:39:57 +01:00
|
|
|
std::string Client::getToken() const {
|
2019-10-17 14:00:02 +02:00
|
|
|
return token;
|
|
|
|
}
|
|
|
|
|
2023-12-11 21:17:47 +01:00
|
|
|
bool Client::login(const std::string& username, const std::string& password, const std::string& device_id) {
|
2019-10-17 14:00:02 +02:00
|
|
|
json_t* request = json_object();
|
|
|
|
json_object_set_new(request, "type", json_string("m.login.password"));
|
|
|
|
json_t* identifier = json_object();
|
|
|
|
json_object_set_new(identifier, "type", json_string("m.id.user"));
|
|
|
|
json_object_set_new(identifier, "user", json_string(username.c_str()));
|
|
|
|
json_object_set_new(request, "identifier", identifier);
|
|
|
|
json_object_set_new(request, "password", json_string(password.c_str()));
|
|
|
|
json_object_set_new(request, "initial_device_display_name", json_string("Nintendo 3DS"));
|
2023-12-09 14:39:57 +01:00
|
|
|
if(!device_id.empty()) {
|
|
|
|
json_object_set_new(request, "device_id", json_string(device_id.c_str()));
|
|
|
|
}
|
2023-12-10 17:59:13 +01:00
|
|
|
json_t* ret = doRequest("POST", "/_matrix/client/v3/login", request);
|
2019-10-17 14:00:02 +02:00
|
|
|
json_decref(request);
|
2023-12-09 14:39:57 +01:00
|
|
|
printf_top("Result : %s\n" ,json_dumps(ret, JSON_ENSURE_ASCII | JSON_ESCAPE_SLASH));
|
2019-10-21 10:15:09 +02:00
|
|
|
const char* tokenCStr = json_object_get_string_value(ret, "access_token");
|
2019-10-21 00:22:38 +02:00
|
|
|
if (!tokenCStr) {
|
2019-10-21 10:15:09 +02:00
|
|
|
if (ret) json_decref(ret);
|
2019-10-21 00:22:38 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
token = tokenCStr;
|
2019-10-17 14:00:02 +02:00
|
|
|
json_decref(ret);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2019-10-19 18:12:10 +02:00
|
|
|
void Client::logout() {
|
2023-12-10 17:59:13 +01:00
|
|
|
json_t* ret = doRequest("POST", "/_matrix/client/v3/logout");
|
2019-10-19 18:12:10 +02:00
|
|
|
if (ret) {
|
|
|
|
json_decref(ret);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-10-28 14:37:31 +01:00
|
|
|
std::string Client::getUserId() {
|
2023-12-09 14:39:57 +01:00
|
|
|
if (!userIdCache.empty()) {
|
2019-10-28 14:37:31 +01:00
|
|
|
return userIdCache;
|
2019-10-17 12:54:49 +02:00
|
|
|
}
|
2023-12-10 17:59:13 +01:00
|
|
|
json_t* ret = doRequest("GET", "/_matrix/client/v3/account/whoami");
|
2019-10-21 10:15:09 +02:00
|
|
|
const char* userIdCStr = json_object_get_string_value(ret, "user_id");
|
2019-10-21 00:22:38 +02:00
|
|
|
if (!userIdCStr) {
|
2019-10-21 10:15:09 +02:00
|
|
|
if (ret) json_decref(ret);
|
2019-10-21 00:22:38 +02:00
|
|
|
return "";
|
|
|
|
}
|
2023-12-09 14:39:57 +01:00
|
|
|
const char* deviceIdCStr = json_object_get_string_value(ret, "device_id");
|
|
|
|
std::string deviceIdStr = deviceIdCStr;
|
2019-10-21 00:22:38 +02:00
|
|
|
std::string userIdStr = userIdCStr;
|
2019-10-17 10:45:55 +02:00
|
|
|
json_decref(ret);
|
2019-10-17 12:54:49 +02:00
|
|
|
userIdCache = std::string(userIdStr);
|
2023-12-09 14:39:57 +01:00
|
|
|
deviceIdCache = std::string(deviceIdStr);
|
2019-10-17 12:54:49 +02:00
|
|
|
return userIdCache;
|
2019-10-17 10:45:55 +02:00
|
|
|
}
|
2023-12-09 14:39:57 +01:00
|
|
|
std::string Client::getDeviceId() {
|
|
|
|
if(!deviceIdCache.empty()){
|
|
|
|
return deviceIdCache;
|
|
|
|
}
|
2023-12-10 17:59:13 +01:00
|
|
|
json_t* ret = doRequest("GET", "/_matrix/client/v3/account/whoami");
|
2023-12-09 14:39:57 +01:00
|
|
|
const char* userIdCStr = json_object_get_string_value(ret, "user_id");
|
|
|
|
if (!userIdCStr) {
|
|
|
|
if (ret) json_decref(ret);
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
const char* deviceIdCStr = json_object_get_string_value(ret, "device_id");
|
|
|
|
std::string deviceIdStr = deviceIdCStr;
|
|
|
|
std::string userIdStr = userIdCStr;
|
|
|
|
json_decref(ret);
|
|
|
|
userIdCache = std::string(userIdStr);
|
|
|
|
deviceIdCache = std::string(deviceIdStr);
|
|
|
|
return deviceIdCache;
|
|
|
|
}
|
2019-10-17 10:45:55 +02:00
|
|
|
|
2019-10-17 13:22:39 +02:00
|
|
|
std::string Client::resolveRoom(std::string alias) {
|
|
|
|
if (alias[0] == '!') {
|
|
|
|
return alias; // this is already a room ID, nothing to do
|
|
|
|
}
|
2023-12-10 17:59:13 +01:00
|
|
|
json_t* ret = doRequest("GET", "/_matrix/client/v3/directory/room/" + urlencode(alias));
|
2019-10-21 10:15:09 +02:00
|
|
|
const char* roomIdCStr = json_object_get_string_value(ret, "room_id");
|
2019-10-21 00:22:38 +02:00
|
|
|
if (!roomIdCStr) {
|
2019-10-21 10:15:09 +02:00
|
|
|
if (ret) json_decref(ret);
|
2019-10-21 00:22:38 +02:00
|
|
|
return "";
|
|
|
|
}
|
|
|
|
std::string roomIdStr = roomIdCStr;
|
2019-10-21 12:38:04 +02:00
|
|
|
printf_top("Room ID: %s\n", roomIdStr.c_str());
|
2019-10-17 13:22:39 +02:00
|
|
|
json_decref(ret);
|
|
|
|
return roomIdStr;
|
|
|
|
}
|
|
|
|
|
2019-10-18 13:10:54 +02:00
|
|
|
std::vector<std::string> Client::getJoinedRooms() {
|
|
|
|
std::vector<std::string> rooms;
|
2023-12-10 17:59:13 +01:00
|
|
|
json_t* ret = doRequest("GET", "/_matrix/client/v3/joined_rooms");
|
2019-10-18 13:10:54 +02:00
|
|
|
json_t* roomsArr = json_object_get(ret, "joined_rooms");
|
|
|
|
if (!roomsArr) {
|
|
|
|
json_decref(ret);
|
|
|
|
return rooms;
|
|
|
|
}
|
|
|
|
size_t index;
|
|
|
|
json_t* value;
|
|
|
|
json_array_foreach(roomsArr, index, value) {
|
2019-10-21 00:22:38 +02:00
|
|
|
const char* val = json_string_value(value);
|
|
|
|
if (val) {
|
2023-12-11 21:17:47 +01:00
|
|
|
rooms.emplace_back(val);
|
2019-10-21 00:22:38 +02:00
|
|
|
}
|
2019-10-18 13:10:54 +02:00
|
|
|
}
|
|
|
|
json_decref(ret);
|
|
|
|
return rooms;
|
|
|
|
}
|
|
|
|
|
|
|
|
RoomInfo Client::getRoomInfo(std::string roomId) {
|
|
|
|
// if we resolve the roomId here it only resolves once
|
|
|
|
roomId = resolveRoom(roomId);
|
|
|
|
RoomInfo info = {
|
2023-12-09 23:32:43 +01:00
|
|
|
getRoomName(roomId),
|
|
|
|
getRoomTopic(roomId),
|
|
|
|
getRoomAvatar(roomId),
|
2019-10-18 13:26:52 +02:00
|
|
|
};
|
|
|
|
return info;
|
|
|
|
}
|
|
|
|
|
2019-10-24 14:24:02 +02:00
|
|
|
ExtraRoomInfo Client::getExtraRoomInfo(std::string roomId) {
|
|
|
|
roomId = resolveRoom(roomId);
|
|
|
|
ExtraRoomInfo info;
|
|
|
|
info.canonicalAlias = getCanonicalAlias(roomId);
|
|
|
|
|
|
|
|
// next fetch the members
|
2023-12-10 17:59:13 +01:00
|
|
|
std::string path = "/_matrix/client/v3/rooms/" + urlencode(roomId) + "/joined_members";
|
2019-10-24 14:24:02 +02:00
|
|
|
json_t* ret = doRequest("GET", path);
|
|
|
|
if (!ret) {
|
|
|
|
return info;
|
|
|
|
}
|
|
|
|
json_t* joined = json_object_get(ret, "joined");
|
|
|
|
if (!joined || json_typeof(joined) != JSON_OBJECT) {
|
|
|
|
json_decref(ret);
|
|
|
|
return info;
|
|
|
|
}
|
|
|
|
|
|
|
|
const char* mxid;
|
|
|
|
json_t* member;
|
|
|
|
json_object_foreach(joined, mxid, member) {
|
|
|
|
const char* displayname = json_object_get_string_value(member, "display_name");
|
|
|
|
const char* avatarUrl = json_object_get_string_value(member, "avatar_url");
|
|
|
|
MemberInfo memInfo;
|
|
|
|
if (displayname) {
|
|
|
|
memInfo.displayname = displayname;
|
|
|
|
}
|
|
|
|
if (avatarUrl) {
|
|
|
|
memInfo.avatarUrl = avatarUrl;
|
|
|
|
}
|
|
|
|
info.members[mxid] = memInfo;
|
|
|
|
}
|
|
|
|
json_decref(ret);
|
|
|
|
return info;
|
|
|
|
}
|
|
|
|
|
2023-12-11 21:17:47 +01:00
|
|
|
MemberInfo Client::getMemberInfo(const std::string& userId, const std::string& roomId) {
|
|
|
|
std::string displayname;
|
|
|
|
std::string avatarUrl;
|
|
|
|
if (!roomId.empty()) {
|
|
|
|
// first try fetching from the room
|
2019-10-18 13:26:52 +02:00
|
|
|
json_t* ret = getStateEvent(roomId, "m.room.member", userId);
|
|
|
|
if (ret) {
|
2019-10-21 10:15:09 +02:00
|
|
|
char* valCStr;
|
|
|
|
valCStr = json_object_get_string_value(ret, "displayname");
|
|
|
|
if (valCStr) {
|
|
|
|
displayname = valCStr;
|
2019-10-18 13:26:52 +02:00
|
|
|
}
|
2019-10-21 10:15:09 +02:00
|
|
|
valCStr = json_object_get_string_value(ret, "avatar_url");
|
|
|
|
if (valCStr) {
|
|
|
|
avatarUrl = valCStr;
|
2019-10-18 13:26:52 +02:00
|
|
|
}
|
|
|
|
json_decref(ret);
|
|
|
|
}
|
|
|
|
}
|
2023-12-11 21:17:47 +01:00
|
|
|
if (displayname.empty()) {
|
2019-10-18 13:26:52 +02:00
|
|
|
// then attempt the account
|
2023-12-10 17:59:13 +01:00
|
|
|
std::string path = "/_matrix/client/v3/profile/" + urlencode(userId);
|
2019-10-18 13:26:52 +02:00
|
|
|
json_t* ret = doRequest("GET", path);
|
|
|
|
if (ret) {
|
2019-10-21 10:15:09 +02:00
|
|
|
char* valCStr;
|
|
|
|
valCStr = json_object_get_string_value(ret, "displayname");
|
|
|
|
if (valCStr) {
|
|
|
|
displayname = valCStr;
|
2019-10-18 13:26:52 +02:00
|
|
|
}
|
2019-10-21 10:15:09 +02:00
|
|
|
valCStr = json_object_get_string_value(ret, "avatar_url");
|
|
|
|
if (valCStr) {
|
|
|
|
avatarUrl = valCStr;
|
2019-10-18 13:26:52 +02:00
|
|
|
}
|
|
|
|
json_decref(ret);
|
|
|
|
}
|
|
|
|
}
|
2019-10-24 12:48:48 +02:00
|
|
|
MemberInfo info = {
|
2023-12-09 23:32:43 +01:00
|
|
|
displayname,
|
|
|
|
avatarUrl,
|
2019-10-18 13:10:54 +02:00
|
|
|
};
|
|
|
|
return info;
|
|
|
|
}
|
|
|
|
|
2023-12-11 21:17:47 +01:00
|
|
|
std::string Client::getRoomName(const std::string& roomId) {
|
2019-10-18 13:10:54 +02:00
|
|
|
json_t* ret = getStateEvent(roomId, "m.room.name", "");
|
2019-10-21 10:15:09 +02:00
|
|
|
const char* nameCStr = json_object_get_string_value(ret, "name");
|
2019-10-21 00:22:38 +02:00
|
|
|
if (!nameCStr) {
|
2019-10-21 10:15:09 +02:00
|
|
|
if (ret) json_decref(ret);
|
2019-10-21 00:22:38 +02:00
|
|
|
return "";
|
|
|
|
}
|
|
|
|
std::string nameStr = nameCStr;
|
2019-10-18 13:10:54 +02:00
|
|
|
json_decref(ret);
|
|
|
|
return nameStr;
|
|
|
|
}
|
|
|
|
|
2023-12-11 21:17:47 +01:00
|
|
|
std::string Client::getRoomTopic(const std::string& roomId) {
|
2019-10-18 13:10:54 +02:00
|
|
|
json_t* ret = getStateEvent(roomId, "m.room.topic", "");
|
2019-10-21 10:15:09 +02:00
|
|
|
const char* topicCStr = json_object_get_string_value(ret, "topic");
|
2019-10-21 00:22:38 +02:00
|
|
|
if (!topicCStr) {
|
2019-10-21 10:15:09 +02:00
|
|
|
if (ret) json_decref(ret);
|
2019-10-21 00:22:38 +02:00
|
|
|
return "";
|
|
|
|
}
|
|
|
|
std::string topicStr = topicCStr;
|
2019-10-18 13:10:54 +02:00
|
|
|
json_decref(ret);
|
|
|
|
return topicStr;
|
|
|
|
}
|
|
|
|
|
2023-12-11 21:17:47 +01:00
|
|
|
std::string Client::getRoomAvatar(const std::string& roomId) {
|
2019-10-18 13:10:54 +02:00
|
|
|
json_t* ret = getStateEvent(roomId, "m.room.avatar", "");
|
2019-10-21 10:15:09 +02:00
|
|
|
const char* urlCStr = json_object_get_string_value(ret, "url");
|
2019-10-21 00:22:38 +02:00
|
|
|
if (!urlCStr) {
|
2019-10-21 10:15:09 +02:00
|
|
|
if (ret) json_decref(ret);
|
2019-10-21 00:22:38 +02:00
|
|
|
return "";
|
|
|
|
}
|
|
|
|
std::string urlStr = urlCStr;
|
2019-10-18 13:10:54 +02:00
|
|
|
json_decref(ret);
|
|
|
|
return urlStr;
|
|
|
|
}
|
|
|
|
|
2023-12-11 21:17:47 +01:00
|
|
|
std::string Client::getCanonicalAlias(const std::string& roomId) {
|
2019-10-24 14:24:02 +02:00
|
|
|
json_t* ret = getStateEvent(roomId, "m.room.canonical_alias", "");
|
|
|
|
const char* aliasCStr = json_object_get_string_value(ret, "alias");
|
|
|
|
if (!aliasCStr) {
|
|
|
|
if (ret) json_decref(ret);
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
std::string aliasStr = aliasCStr;
|
|
|
|
json_decref(ret);
|
|
|
|
return aliasStr;
|
|
|
|
}
|
|
|
|
|
2023-12-11 21:17:47 +01:00
|
|
|
void Client::sendReadReceipt(const std::string& roomId, const std::string& eventId) {
|
|
|
|
std::string roomDecoded = resolveRoom(roomId);
|
|
|
|
std::string path = "/_matrix/client/v3/rooms/" + urlencode(roomDecoded) + "/receipt/m.read/" + urlencode(eventId);
|
2019-10-26 14:24:09 +02:00
|
|
|
json_t* ret = doRequest("POST", path);
|
|
|
|
if (ret) {
|
|
|
|
json_decref(ret);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-12-09 15:36:46 +01:00
|
|
|
void Client::uploadKeys(json_t* body) {
|
2023-12-10 17:59:13 +01:00
|
|
|
std::string path = "/_matrix/client/v3/keys/upload";
|
2023-12-09 15:36:46 +01:00
|
|
|
json_t* ret = doRequest("POST", path, body);
|
|
|
|
if (ret) {
|
|
|
|
json_decref(ret);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-10-26 18:25:02 +02:00
|
|
|
void Client::setTyping(std::string roomId, bool typing, u32 timeout) {
|
|
|
|
roomId = resolveRoom(roomId);
|
|
|
|
std::string userId = getUserId();
|
2023-12-10 17:59:13 +01:00
|
|
|
std::string path = "/_matrix/client/v3/rooms/" + urlencode(roomId) + "/typing/" + urlencode(userId);
|
2019-10-26 18:25:02 +02:00
|
|
|
json_t* request = json_object();
|
|
|
|
json_object_set_new(request, "typing", json_boolean(typing));
|
|
|
|
json_object_set_new(request, "timeout", json_integer(timeout));
|
|
|
|
json_t* ret = doRequest("PUT", path, request);
|
|
|
|
json_decref(request);
|
|
|
|
json_decref(ret);
|
|
|
|
}
|
|
|
|
|
2023-12-11 21:17:47 +01:00
|
|
|
std::string Client::sendEmote(const std::string& roomId, const std::string& text) {
|
2019-10-17 13:22:39 +02:00
|
|
|
json_t* request = json_object();
|
|
|
|
json_object_set_new(request, "msgtype", json_string("m.emote"));
|
|
|
|
json_object_set_new(request, "body", json_string(text.c_str()));
|
|
|
|
std::string eventId = sendMessage(roomId, request);
|
|
|
|
json_decref(request);
|
|
|
|
return eventId;
|
|
|
|
}
|
|
|
|
|
2023-12-11 21:17:47 +01:00
|
|
|
std::string Client::sendNotice(const std::string& roomId, const std::string& text) {
|
2019-10-17 13:22:39 +02:00
|
|
|
json_t* request = json_object();
|
|
|
|
json_object_set_new(request, "msgtype", json_string("m.notice"));
|
|
|
|
json_object_set_new(request, "body", json_string(text.c_str()));
|
|
|
|
std::string eventId = sendMessage(roomId, request);
|
|
|
|
json_decref(request);
|
|
|
|
return eventId;
|
|
|
|
}
|
|
|
|
|
2023-12-11 21:17:47 +01:00
|
|
|
std::string Client::sendText(const std::string& roomId, const std::string& text) {
|
2019-10-16 22:57:55 +02:00
|
|
|
json_t* request = json_object();
|
|
|
|
json_object_set_new(request, "msgtype", json_string("m.text"));
|
|
|
|
json_object_set_new(request, "body", json_string(text.c_str()));
|
|
|
|
std::string eventId = sendMessage(roomId, request);
|
|
|
|
json_decref(request);
|
|
|
|
return eventId;
|
|
|
|
}
|
|
|
|
|
2023-12-11 21:17:47 +01:00
|
|
|
std::string Client::sendMessage(const std::string& roomId, json_t* content) {
|
2019-10-16 22:57:55 +02:00
|
|
|
return sendEvent(roomId, "m.room.message", content);
|
|
|
|
}
|
|
|
|
|
2023-12-11 16:06:09 +01:00
|
|
|
|
|
|
|
void Client::sendEventToDevice(const std::string& eventType, json_t* content){
|
|
|
|
std::string txid = std::to_string(time(nullptr)) + "_REQ_" + std::to_string(requestId++);
|
2023-12-10 19:52:00 +01:00
|
|
|
std::string path = "/_matrix/client/v3/sendToDevice/" + urlencode(eventType) + "/" + urlencode(txid);
|
2023-12-11 16:06:09 +01:00
|
|
|
json_t* messages = json_object();
|
|
|
|
json_object_set(messages, "messages", content);
|
|
|
|
json_t* ret = doRequest("PUT", path, messages, 5, nullptr, false);
|
|
|
|
json_decref(messages);
|
2023-12-10 19:52:00 +01:00
|
|
|
if (ret) json_decref(ret);
|
|
|
|
}
|
|
|
|
|
2023-12-11 21:17:47 +01:00
|
|
|
std::string Client::sendEvent(const std::string& roomId, const std::string& eventType, json_t* content) {
|
|
|
|
std::string resolvedId = resolveRoom(roomId);
|
|
|
|
std::string txid = std::to_string(time(nullptr)) + "_REQ_" + std::to_string(requestId++);
|
|
|
|
std::string path = "/_matrix/client/v3/rooms/" + urlencode(resolvedId) + "/send/" + urlencode(eventType) + "/" + urlencode(txid);
|
2023-12-11 16:06:09 +01:00
|
|
|
json_t* ret = doRequest("PUT", path, content, 5, nullptr, false);
|
2019-10-21 10:15:09 +02:00
|
|
|
const char* eventIdCStr = json_object_get_string_value(ret, "event_id");
|
2019-10-21 00:22:38 +02:00
|
|
|
if (!eventIdCStr) {
|
2019-10-21 10:15:09 +02:00
|
|
|
if (ret) json_decref(ret);
|
2019-10-21 00:22:38 +02:00
|
|
|
return "";
|
|
|
|
}
|
|
|
|
std::string eventIdStr = eventIdCStr;
|
2019-10-16 22:57:55 +02:00
|
|
|
json_decref(ret);
|
|
|
|
return eventIdStr;
|
2019-10-16 18:39:57 +02:00
|
|
|
}
|
|
|
|
|
2023-12-11 21:17:47 +01:00
|
|
|
json_t* Client::getStateEvent(const std::string& roomId, const std::string& type, const std::string& stateKey) {
|
|
|
|
std::string resolvedId = resolveRoom(roomId);
|
|
|
|
std::string path = "/_matrix/client/v3/rooms/" + urlencode(resolvedId) + "/state/" + urlencode(type) + "/" + urlencode(stateKey);
|
2019-10-18 13:10:54 +02:00
|
|
|
return doRequest("GET", path);
|
|
|
|
}
|
|
|
|
|
2023-12-11 21:17:47 +01:00
|
|
|
std::string Client::sendStateEvent(const std::string& roomId, const std::string& type, const std::string& stateKey, json_t* content) {
|
|
|
|
std::string resolvedId = resolveRoom(roomId);
|
|
|
|
std::string path = "/_matrix/client/v3/rooms/" + urlencode(resolvedId) + "/state/" + urlencode(type) + "/" + urlencode(stateKey);
|
2019-10-17 13:22:39 +02:00
|
|
|
json_t* ret = doRequest("PUT", path, content);
|
2019-10-21 10:15:09 +02:00
|
|
|
const char* eventIdCStr = json_object_get_string_value(ret, "event_id");
|
2019-10-21 00:22:38 +02:00
|
|
|
if (!eventIdCStr) {
|
2019-10-21 10:15:09 +02:00
|
|
|
if (ret) json_decref(ret);
|
2019-10-21 00:22:38 +02:00
|
|
|
return "";
|
|
|
|
}
|
|
|
|
std::string eventIdStr = eventIdCStr;
|
2019-10-17 13:22:39 +02:00
|
|
|
json_decref(ret);
|
|
|
|
return eventIdStr;
|
|
|
|
}
|
|
|
|
|
2023-12-11 21:17:47 +01:00
|
|
|
std::string Client::redactEvent(const std::string& roomId, const std::string& eventId, const std::string& reason) {
|
|
|
|
std::string resolvedId = resolveRoom(roomId);
|
|
|
|
std::string txid = std::to_string(time(nullptr)) + "_REQ_" + std::to_string(requestId++);
|
2019-10-17 13:22:39 +02:00
|
|
|
json_t* content = json_object();
|
2023-12-11 16:06:09 +01:00
|
|
|
if (!reason.empty()) {
|
2019-10-17 13:22:39 +02:00
|
|
|
json_object_set_new(content, "reason", json_string(reason.c_str()));
|
|
|
|
}
|
2023-12-11 21:17:47 +01:00
|
|
|
std::string path = "/_matrix/client/v3/rooms/" + urlencode(resolvedId) + "/redact/" + urlencode(eventId) + "/" + txid;
|
2023-12-11 16:06:09 +01:00
|
|
|
json_t* ret = doRequest("PUT", path, content, 5, nullptr, false);
|
2019-10-17 13:22:39 +02:00
|
|
|
json_decref(content);
|
2019-10-21 10:15:09 +02:00
|
|
|
const char* eventIdCStr = json_object_get_string_value(ret, "event_id");
|
2019-10-21 00:22:38 +02:00
|
|
|
if (!eventIdCStr) {
|
2019-10-21 10:15:09 +02:00
|
|
|
if (ret) json_decref(ret);
|
2019-10-21 00:22:38 +02:00
|
|
|
return "";
|
|
|
|
}
|
|
|
|
std::string eventIdStr = eventIdCStr;
|
2019-10-17 13:22:39 +02:00
|
|
|
json_decref(ret);
|
|
|
|
return eventIdStr;
|
|
|
|
}
|
|
|
|
|
2019-10-17 12:54:49 +02:00
|
|
|
void startSyncLoopWithoutClass(void* arg) {
|
2019-10-19 18:12:10 +02:00
|
|
|
((Client*)arg)->syncLoop();
|
2019-10-17 12:54:49 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void Client::startSyncLoop() {
|
|
|
|
stopSyncLoop(); // first we stop an already running sync loop
|
2019-10-17 14:00:02 +02:00
|
|
|
isSyncing = true;
|
2019-10-17 12:54:49 +02:00
|
|
|
stopSyncing = false;
|
|
|
|
s32 prio = 0;
|
|
|
|
svcGetThreadPriority(&prio, CUR_THREAD_HANDLE);
|
|
|
|
syncThread = threadCreate(startSyncLoopWithoutClass, this, 8*1024, prio-1, -2, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Client::stopSyncLoop() {
|
|
|
|
stopSyncing = true;
|
2019-10-17 14:00:02 +02:00
|
|
|
if (isSyncing) {
|
|
|
|
threadJoin(syncThread, U64_MAX);
|
|
|
|
threadFree(syncThread);
|
|
|
|
}
|
|
|
|
isSyncing = false;
|
2019-10-17 12:54:49 +02:00
|
|
|
}
|
|
|
|
|
2019-10-20 20:39:52 +02:00
|
|
|
void Client::setEventCallback(eventCallback cb) {
|
|
|
|
callbacks.event = cb;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Client::setLeaveRoomCallback(eventCallback cb) {
|
|
|
|
callbacks.leaveRoom = cb;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Client::setInviteRoomCallback(eventCallback cb) {
|
|
|
|
callbacks.inviteRoom = cb;
|
2019-10-17 21:52:34 +02:00
|
|
|
}
|
|
|
|
|
2019-10-20 23:49:08 +02:00
|
|
|
void Client::setRoomInfoCallback(roomInfoCallback cb) {
|
|
|
|
callbacks.roomInfo = cb;
|
|
|
|
}
|
|
|
|
|
2019-10-26 12:54:09 +02:00
|
|
|
void Client::setRoomLimitedCallback(roomLimitedCallback cb) {
|
|
|
|
callbacks.roomLimited = cb;
|
|
|
|
}
|
|
|
|
|
2019-10-17 10:45:55 +02:00
|
|
|
void Client::processSync(json_t* sync) {
|
2023-12-11 20:58:32 +01:00
|
|
|
json_t* to_device = json_object_get(sync, "to_device");
|
|
|
|
if(to_device){
|
|
|
|
processToDevice(to_device);
|
|
|
|
}
|
2019-10-17 10:45:55 +02:00
|
|
|
json_t* rooms = json_object_get(sync, "rooms");
|
|
|
|
if (!rooms) {
|
|
|
|
return; // nothing to do
|
|
|
|
}
|
|
|
|
json_t* leftRooms = json_object_get(rooms, "leave");
|
|
|
|
json_t* invitedRooms = json_object_get(rooms, "invite");
|
|
|
|
json_t* joinedRooms = json_object_get(rooms, "join");
|
2023-12-11 20:58:32 +01:00
|
|
|
|
2019-10-17 10:45:55 +02:00
|
|
|
const char* roomId;
|
|
|
|
json_t* room;
|
2019-10-17 21:52:34 +02:00
|
|
|
size_t index;
|
|
|
|
json_t* event;
|
2019-10-17 10:45:55 +02:00
|
|
|
|
2019-10-20 20:39:52 +02:00
|
|
|
if (leftRooms && callbacks.leaveRoom) {
|
2019-10-17 10:45:55 +02:00
|
|
|
json_object_foreach(leftRooms, roomId, room) {
|
|
|
|
// rooms that we left
|
2019-10-20 20:39:52 +02:00
|
|
|
json_t* timeline = json_object_get(room, "timeline");
|
|
|
|
if (!timeline) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
json_t* events = json_object_get(timeline, "events");
|
|
|
|
if (!events) {
|
|
|
|
continue;
|
|
|
|
}
|
2023-12-11 21:17:47 +01:00
|
|
|
json_t* leaveEvent = nullptr;
|
2019-10-20 20:39:52 +02:00
|
|
|
json_array_foreach(events, index, event) {
|
|
|
|
// check if the event type is m.room.member
|
2019-10-21 10:15:09 +02:00
|
|
|
char* val;
|
|
|
|
val = json_object_get_string_value(event, "type");
|
2019-10-20 20:39:52 +02:00
|
|
|
if (!val) {
|
|
|
|
continue;
|
|
|
|
}
|
2019-10-21 10:15:09 +02:00
|
|
|
if (strcmp(val, "m.room.member") != 0) {
|
2019-10-20 20:39:52 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
// check if it is actually us
|
2019-10-21 10:15:09 +02:00
|
|
|
val = json_object_get_string_value(event, "state_key");
|
2019-10-20 20:39:52 +02:00
|
|
|
if (!val) {
|
|
|
|
continue;
|
|
|
|
}
|
2019-10-21 10:15:09 +02:00
|
|
|
if (strcmp(val, getUserId().c_str()) != 0) {
|
2019-10-20 20:39:52 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
// we do *not* check for event age as we don't have unsigned stuffs in our timeline due to our filter
|
|
|
|
// so we just assume that the events arrive in the correct order (probably true)
|
|
|
|
leaveEvent = event;
|
|
|
|
}
|
|
|
|
if (!leaveEvent) {
|
2019-10-21 12:38:04 +02:00
|
|
|
printf_top("Left room %s without an event\n", roomId);
|
2019-10-20 20:39:52 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
callbacks.leaveRoom(roomId, leaveEvent);
|
2019-10-17 10:45:55 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-10-20 20:39:52 +02:00
|
|
|
if (invitedRooms && callbacks.inviteRoom) {
|
2019-10-17 10:45:55 +02:00
|
|
|
json_object_foreach(invitedRooms, roomId, room) {
|
|
|
|
// rooms that we were invited to
|
2019-10-20 20:39:52 +02:00
|
|
|
json_t* invite_state = json_object_get(room, "invite_state");
|
|
|
|
if (!invite_state) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
json_t* events = json_object_get(invite_state, "events");
|
|
|
|
if (!events) {
|
|
|
|
continue;
|
|
|
|
}
|
2023-12-11 21:17:47 +01:00
|
|
|
json_t* inviteEvent = nullptr;
|
2019-10-20 20:39:52 +02:00
|
|
|
json_array_foreach(events, index, event) {
|
|
|
|
// check if the event type is m.room.member
|
2019-10-21 10:15:09 +02:00
|
|
|
char* val;
|
|
|
|
val = json_object_get_string_value(event, "type");
|
2019-10-20 20:39:52 +02:00
|
|
|
if (!val) {
|
|
|
|
continue;
|
|
|
|
}
|
2019-10-21 10:15:09 +02:00
|
|
|
if (strcmp(val, "m.room.member") != 0) {
|
2019-10-20 20:39:52 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
// check if it is actually us
|
2019-10-21 10:15:09 +02:00
|
|
|
val = json_object_get_string_value(event, "state_key");
|
2019-10-20 20:39:52 +02:00
|
|
|
if (!val) {
|
|
|
|
continue;
|
|
|
|
}
|
2019-10-21 10:15:09 +02:00
|
|
|
if (strcmp(val, getUserId().c_str()) != 0) {
|
2019-10-20 20:39:52 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
// check for if it was an invite event
|
|
|
|
json_t* content = json_object_get(event, "content");
|
|
|
|
if (!content) {
|
|
|
|
continue;
|
|
|
|
}
|
2019-10-21 10:15:09 +02:00
|
|
|
val = json_object_get_string_value(content, "membership");
|
2019-10-20 20:39:52 +02:00
|
|
|
if (!val) {
|
|
|
|
continue;
|
|
|
|
}
|
2019-10-21 10:15:09 +02:00
|
|
|
if (strcmp(val, "invite") != 0) {
|
2019-10-20 20:39:52 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
// we do *not* check for event age as we don't have unsigned stuffs in our timeline due to our filter
|
|
|
|
// so we just assume that the events arrive in the correct order (probably true)
|
|
|
|
inviteEvent = event;
|
|
|
|
}
|
|
|
|
if (!inviteEvent) {
|
2019-10-21 12:38:04 +02:00
|
|
|
printf_top("Invite to room %s without an event\n", roomId);
|
2019-10-20 20:39:52 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
callbacks.inviteRoom(roomId, inviteEvent);
|
2019-10-17 10:45:55 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-10-20 23:49:08 +02:00
|
|
|
if (joinedRooms) {
|
2019-10-17 10:45:55 +02:00
|
|
|
json_object_foreach(joinedRooms, roomId, room) {
|
|
|
|
// rooms that we are joined
|
2019-10-20 23:49:08 +02:00
|
|
|
json_t* state = json_object_get(room, "state");
|
|
|
|
if (callbacks.roomInfo && state) {
|
|
|
|
json_t* events = json_object_get(state, "events");
|
|
|
|
if (events) {
|
|
|
|
RoomInfo info;
|
|
|
|
bool addedInfo = false;
|
|
|
|
json_array_foreach(events, index, event) {
|
2019-10-21 10:15:09 +02:00
|
|
|
const char* typeCStr = json_object_get_string_value(event, "type");
|
|
|
|
if (!typeCStr) {
|
2019-10-20 23:49:08 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
json_t* content = json_object_get(event, "content");
|
|
|
|
if (!content) {
|
|
|
|
continue;
|
|
|
|
}
|
2019-10-21 10:15:09 +02:00
|
|
|
if (strcmp(typeCStr, "m.room.name") == 0) {
|
|
|
|
const char* nameCStr = json_object_get_string_value(content, "name");
|
|
|
|
if (nameCStr) {
|
|
|
|
info.name = nameCStr;
|
|
|
|
addedInfo = true;
|
2019-10-20 23:49:08 +02:00
|
|
|
}
|
2019-10-21 10:15:09 +02:00
|
|
|
} else if (strcmp(typeCStr, "m.room.topic") == 0) {
|
2019-10-21 17:00:10 +02:00
|
|
|
const char* topicCStr = json_object_get_string_value(content, "topic");
|
2019-10-21 10:15:09 +02:00
|
|
|
if (topicCStr) {
|
|
|
|
info.topic = topicCStr;
|
|
|
|
addedInfo = true;
|
2019-10-20 23:49:08 +02:00
|
|
|
}
|
2019-10-21 10:15:09 +02:00
|
|
|
} else if (strcmp(typeCStr, "m.room.avatar") == 0) {
|
2019-10-21 17:00:10 +02:00
|
|
|
const char* urlCStr = json_object_get_string_value(content, "url");
|
2019-10-21 10:15:09 +02:00
|
|
|
if (urlCStr) {
|
|
|
|
info.avatarUrl = urlCStr;
|
|
|
|
addedInfo = true;
|
2019-10-20 23:49:08 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (addedInfo) {
|
|
|
|
callbacks.roomInfo(roomId, info);
|
|
|
|
}
|
|
|
|
}
|
2019-10-17 10:45:55 +02:00
|
|
|
}
|
2019-10-20 23:49:08 +02:00
|
|
|
json_t* timeline = json_object_get(room, "timeline");
|
2019-10-26 12:54:09 +02:00
|
|
|
if (callbacks.roomLimited && timeline) {
|
|
|
|
json_t* limited = json_object_get(timeline, "limited");
|
|
|
|
const char* prevBatch = json_object_get_string_value(timeline, "prev_batch");
|
|
|
|
if (limited && prevBatch && json_typeof(limited) == JSON_TRUE) {
|
|
|
|
callbacks.roomLimited(roomId, prevBatch);
|
|
|
|
}
|
|
|
|
}
|
2019-10-20 23:49:08 +02:00
|
|
|
if (callbacks.event && timeline) {
|
|
|
|
json_t* events = json_object_get(timeline, "events");
|
|
|
|
if (events) {
|
|
|
|
json_array_foreach(events, index, event) {
|
|
|
|
callbacks.event(roomId, event);
|
|
|
|
}
|
|
|
|
}
|
2019-10-17 10:45:55 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-10-19 18:12:10 +02:00
|
|
|
void Client::registerFilter() {
|
|
|
|
static const char *json =
|
|
|
|
"{"
|
|
|
|
" \"account_data\": {"
|
|
|
|
" \"types\": ["
|
|
|
|
" \"m.direct\""
|
|
|
|
" ]"
|
|
|
|
" },"
|
|
|
|
" \"presence\": {"
|
|
|
|
" \"limit\": 0,"
|
|
|
|
" \"types\": [\"none\"]"
|
|
|
|
" },"
|
|
|
|
" \"room\": {"
|
|
|
|
" \"account_data\": {"
|
|
|
|
" \"limit\": 0,"
|
|
|
|
" \"types\": [\"none\"]"
|
|
|
|
" },"
|
|
|
|
" \"ephemeral\": {"
|
|
|
|
" \"limit\": 0,"
|
|
|
|
" \"types\": []"
|
|
|
|
" },"
|
|
|
|
" \"state\": {"
|
|
|
|
" \"limit\": 3,"
|
|
|
|
" \"types\": ["
|
|
|
|
" \"m.room.name\","
|
|
|
|
" \"m.room.topic\","
|
|
|
|
" \"m.room.avatar\""
|
|
|
|
" ]"
|
|
|
|
" },"
|
|
|
|
" \"timeline\": {"
|
|
|
|
" \"limit\": 10,"
|
|
|
|
" \"lazy_load_members\": true"
|
|
|
|
" }"
|
|
|
|
" },"
|
|
|
|
" \"event_format\": \"client\","
|
|
|
|
" \"event_fields\": ["
|
|
|
|
" \"type\","
|
|
|
|
" \"content\","
|
|
|
|
" \"sender\","
|
|
|
|
" \"state_key\","
|
|
|
|
" \"event_id\","
|
2019-10-24 21:32:28 +02:00
|
|
|
" \"origin_server_ts\","
|
|
|
|
" \"redacts\""
|
2019-10-19 18:12:10 +02:00
|
|
|
" ]"
|
|
|
|
"}";
|
|
|
|
|
|
|
|
json_error_t error;
|
|
|
|
json_t* filter = json_loads(json, 0, &error);
|
|
|
|
if (!filter) {
|
2019-10-21 12:38:04 +02:00
|
|
|
printf_top("PANIC!!!!! INVALID FILTER JSON!!!!\n");
|
|
|
|
printf_top("%s\n", error.text);
|
|
|
|
printf_top("At %d:%d (%d)\n", error.line, error.column, error.position);
|
2019-10-19 18:12:10 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
std::string userId = getUserId();
|
2023-12-10 17:59:13 +01:00
|
|
|
json_t* ret = doRequest("POST", "/_matrix/client/v3/user/" + urlencode(userId) + "/filter", filter);
|
2019-10-19 18:12:10 +02:00
|
|
|
json_decref(filter);
|
2019-10-21 10:15:09 +02:00
|
|
|
const char* filterIdCStr = json_object_get_string_value(ret, "filter_id");
|
2019-10-21 00:22:38 +02:00
|
|
|
if (!filterIdCStr) {
|
2019-10-21 10:15:09 +02:00
|
|
|
if (ret) json_decref(ret);
|
2019-10-21 00:22:38 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
std::string filterIdStr = filterIdCStr;
|
2019-10-19 18:12:10 +02:00
|
|
|
json_decref(ret);
|
|
|
|
store->setFilterId(filterIdStr);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Client::syncLoop() {
|
2019-10-18 21:22:10 +02:00
|
|
|
u32 timeout = 60;
|
2019-10-17 20:31:00 +02:00
|
|
|
while (true) {
|
|
|
|
if (stopSyncing) {
|
|
|
|
return;
|
2019-10-17 10:45:55 +02:00
|
|
|
}
|
2023-12-11 21:17:47 +01:00
|
|
|
std::string syncToken = store->getSyncToken();
|
2019-10-19 18:12:10 +02:00
|
|
|
std::string filterId = store->getFilterId();
|
2023-12-11 21:17:47 +01:00
|
|
|
if (filterId.empty()) {
|
2019-10-19 18:12:10 +02:00
|
|
|
registerFilter();
|
|
|
|
filterId = store->getFilterId();
|
|
|
|
}
|
2019-10-28 19:47:52 +01:00
|
|
|
CURLcode res;
|
2023-12-11 21:17:47 +01:00
|
|
|
json_t* ret = doSync(syncToken, filterId, timeout, &res);
|
2019-10-17 20:31:00 +02:00
|
|
|
if (ret) {
|
2019-10-18 19:46:39 +02:00
|
|
|
timeout = 60;
|
2019-10-17 20:31:00 +02:00
|
|
|
// set the token for the next batch
|
2019-10-21 10:15:09 +02:00
|
|
|
const char* tokenCStr = json_object_get_string_value(ret, "next_batch");
|
|
|
|
if (tokenCStr) {
|
|
|
|
store->setSyncToken(tokenCStr);
|
2019-10-17 20:31:00 +02:00
|
|
|
} else {
|
|
|
|
store->setSyncToken("");
|
|
|
|
}
|
|
|
|
processSync(ret);
|
|
|
|
json_decref(ret);
|
2019-10-18 19:46:39 +02:00
|
|
|
} else {
|
2019-10-28 19:47:52 +01:00
|
|
|
if (res == CURLE_OPERATION_TIMEDOUT) {
|
2019-10-18 19:46:39 +02:00
|
|
|
timeout += 10*60;
|
2019-10-21 12:38:04 +02:00
|
|
|
printf_top("Timeout reached, increasing it to %lu\n", timeout);
|
2019-10-18 19:46:39 +02:00
|
|
|
}
|
2019-10-17 20:31:00 +02:00
|
|
|
}
|
|
|
|
svcSleepThread((u64)1000000ULL * (u64)200);
|
2019-10-17 10:45:55 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-12-11 21:17:47 +01:00
|
|
|
json_t* Client::doSync(const std::string& syncToken, const std::string& filter, u32 timeout, CURLcode* res) {
|
2019-10-21 12:38:04 +02:00
|
|
|
// printf_top("Doing sync with token %s\n", token.c_str());
|
2019-10-17 10:45:55 +02:00
|
|
|
|
2019-10-19 18:12:10 +02:00
|
|
|
std::string query = "?full_state=false&timeout=" + std::to_string(SYNC_TIMEOUT) + "&filter=" + urlencode(filter);
|
2023-12-11 21:17:47 +01:00
|
|
|
if (!syncToken.empty()) {
|
|
|
|
query += "&since=" + syncToken;
|
2019-10-17 10:45:55 +02:00
|
|
|
}
|
2023-12-11 21:17:47 +01:00
|
|
|
return doRequest("GET", "/_matrix/client/v3/sync" + query, nullptr, timeout, res);
|
2019-10-17 10:45:55 +02:00
|
|
|
}
|
|
|
|
|
2019-10-16 21:26:06 +02:00
|
|
|
size_t DoRequestWriteCallback(char *contents, size_t size, size_t nmemb, void *userp) {
|
2019-10-28 14:37:31 +01:00
|
|
|
// printf_top("----\n%s\n", ((std::string*)userp)->c_str());
|
2019-10-16 21:26:06 +02:00
|
|
|
((std::string*)userp)->append((char*)contents, size * nmemb);
|
|
|
|
return size * nmemb;
|
|
|
|
}
|
2019-10-16 18:39:57 +02:00
|
|
|
|
2019-10-18 12:40:19 +02:00
|
|
|
bool doingCurlRequest = false;
|
2019-10-18 19:46:39 +02:00
|
|
|
bool doingHttpcRequest = false;
|
2019-10-18 12:40:19 +02:00
|
|
|
|
2023-12-11 21:17:47 +01:00
|
|
|
json_t* Client::doRequest(const char* method, const std::string& path, json_t* body, u32 timeout, CURLcode* retRes, bool needsRequest) {
|
2019-10-16 21:26:06 +02:00
|
|
|
std::string url = hsUrl + path;
|
2023-12-11 16:06:09 +01:00
|
|
|
if(needsRequest) {
|
|
|
|
requestId++;
|
|
|
|
}
|
2019-10-28 19:47:52 +01:00
|
|
|
return doRequestCurl(method, url, body, timeout, retRes);
|
2019-10-28 14:37:31 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
CURLM* curl_multi_handle;
|
|
|
|
std::map<CURLM*, CURLcode> curl_handles_done;
|
|
|
|
Thread curl_multi_loop_thread;
|
|
|
|
|
2023-12-11 21:17:47 +01:00
|
|
|
[[noreturn]] void curl_multi_loop(void* p) {
|
2019-10-28 14:37:31 +01:00
|
|
|
int openHandles = 0;
|
|
|
|
while(true) {
|
|
|
|
CURLMcode mc = curl_multi_perform(curl_multi_handle, &openHandles);
|
|
|
|
if (mc != CURLM_OK) {
|
|
|
|
printf_top("curl multi fail: %u\n", mc);
|
2019-10-27 19:55:05 +01:00
|
|
|
}
|
2019-10-28 14:37:31 +01:00
|
|
|
// curl_multi_wait(curl_multi_handle, NULL, 0, 1000, &openHandles);
|
|
|
|
CURLMsg* msg;
|
|
|
|
int msgsLeft;
|
|
|
|
while ((msg = curl_multi_info_read(curl_multi_handle, &msgsLeft))) {
|
|
|
|
if (msg->msg == CURLMSG_DONE) {
|
|
|
|
curl_handles_done[msg->easy_handle] = msg->data.result;
|
|
|
|
}
|
2019-10-27 19:55:05 +01:00
|
|
|
}
|
2019-10-28 19:47:52 +01:00
|
|
|
if (!openHandles) {
|
|
|
|
svcSleepThread((u64)1000000ULL * 100);
|
|
|
|
}
|
2019-10-18 12:40:19 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-12-11 21:17:47 +01:00
|
|
|
json_t* Client::doRequestCurl(const char* method, const std::string& url, json_t* body, u32 timeout, CURLcode* retRes) {
|
2023-12-10 17:59:13 +01:00
|
|
|
// printf_top("Opening Request %d with CURL\n%s\n", requestId, url.c_str());
|
2019-10-16 21:26:06 +02:00
|
|
|
|
|
|
|
if (!SOC_buffer) {
|
2019-10-18 12:40:19 +02:00
|
|
|
SOC_buffer = (u32*)memalign(0x1000, POST_BUFFERSIZE);
|
2019-10-16 21:26:06 +02:00
|
|
|
if (!SOC_buffer) {
|
2023-12-11 21:17:47 +01:00
|
|
|
return nullptr;
|
2019-10-16 18:39:57 +02:00
|
|
|
}
|
2019-10-18 12:40:19 +02:00
|
|
|
if (socInit(SOC_buffer, POST_BUFFERSIZE) != 0) {
|
2023-12-11 21:17:47 +01:00
|
|
|
return nullptr;
|
2019-10-16 18:39:57 +02:00
|
|
|
}
|
2019-10-28 14:37:31 +01:00
|
|
|
curl_multi_handle = curl_multi_init();
|
|
|
|
s32 prio = 0;
|
|
|
|
svcGetThreadPriority(&prio, CUR_THREAD_HANDLE);
|
2023-12-11 21:17:47 +01:00
|
|
|
curl_multi_loop_thread = threadCreate(curl_multi_loop, nullptr, 8*1024, prio-1, -2, true);
|
2019-10-16 18:39:57 +02:00
|
|
|
}
|
|
|
|
|
2019-10-16 21:26:06 +02:00
|
|
|
CURL* curl = curl_easy_init();
|
|
|
|
if (!curl) {
|
2019-10-21 12:38:04 +02:00
|
|
|
printf_top("curl init failed\n");
|
2023-12-11 21:17:47 +01:00
|
|
|
return nullptr;
|
2019-10-16 18:39:57 +02:00
|
|
|
}
|
2019-10-16 21:26:06 +02:00
|
|
|
std::string readBuffer;
|
2023-12-11 21:17:47 +01:00
|
|
|
struct curl_slist* headers = nullptr;
|
|
|
|
if (!token.empty()) {
|
2019-10-17 14:00:02 +02:00
|
|
|
headers = curl_slist_append(headers, ("Authorization: Bearer " + token).c_str());
|
|
|
|
}
|
2023-12-11 21:17:47 +01:00
|
|
|
char* bodyStr = nullptr;
|
2019-10-16 22:57:55 +02:00
|
|
|
if (body) {
|
|
|
|
headers = curl_slist_append(headers, "Content-Type: application/json");
|
2019-10-18 21:22:10 +02:00
|
|
|
bodyStr = json_dumps(body, JSON_ENSURE_ASCII | JSON_ESCAPE_SLASH);
|
2019-10-16 22:57:55 +02:00
|
|
|
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, bodyStr);
|
|
|
|
}
|
2019-10-16 21:26:06 +02:00
|
|
|
curl_easy_setopt(curl, CURLOPT_BUFFERSIZE, 102400L);
|
|
|
|
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
|
|
|
|
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1L);
|
|
|
|
curl_easy_setopt(curl, CURLOPT_USERAGENT, "3ds");
|
|
|
|
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
|
|
|
|
curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 50L);
|
|
|
|
curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, (long)CURL_HTTP_VERSION_2TLS);
|
2019-10-16 22:57:55 +02:00
|
|
|
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
|
2019-10-16 21:26:06 +02:00
|
|
|
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
|
|
|
|
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, method);
|
|
|
|
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, DoRequestWriteCallback);
|
|
|
|
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer);
|
2019-10-18 19:46:39 +02:00
|
|
|
curl_easy_setopt(curl, CURLOPT_TIMEOUT, timeout);
|
2019-10-16 21:26:06 +02:00
|
|
|
|
2019-10-28 14:37:31 +01:00
|
|
|
curl_multi_add_handle(curl_multi_handle, curl);
|
|
|
|
|
|
|
|
while (curl_handles_done.count(curl) == 0) {
|
|
|
|
svcSleepThread((u64)1000000ULL * 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
CURLcode res = curl_handles_done[curl];
|
|
|
|
curl_handles_done.erase(curl);
|
|
|
|
curl_multi_remove_handle(curl_multi_handle, curl);
|
|
|
|
|
2019-10-16 21:26:06 +02:00
|
|
|
// curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
|
|
|
|
// curl_easy_setopt(curl, CURLOPT_STDERR, stdout);
|
2019-10-17 13:22:39 +02:00
|
|
|
curl_easy_cleanup(curl);
|
2019-10-18 21:22:10 +02:00
|
|
|
if (bodyStr) free(bodyStr);
|
2019-10-28 19:47:52 +01:00
|
|
|
if (retRes) *retRes = res;
|
2019-10-16 21:26:06 +02:00
|
|
|
if (res != CURLE_OK) {
|
2019-10-21 12:38:04 +02:00
|
|
|
printf_top("curl res not ok %d\n", res);
|
2023-12-11 21:17:47 +01:00
|
|
|
return nullptr;
|
2019-10-16 18:39:57 +02:00
|
|
|
}
|
|
|
|
|
2019-10-28 14:37:31 +01:00
|
|
|
// printf_top("++++\n%s\n", readBuffer.c_str());
|
2023-12-10 17:59:13 +01:00
|
|
|
// printf_top("Body size: %d\n", readBuffer.length());
|
2019-10-16 18:39:57 +02:00
|
|
|
json_error_t error;
|
2019-10-16 22:57:55 +02:00
|
|
|
json_t* content = json_loads(readBuffer.c_str(), 0, &error);
|
2019-10-16 18:39:57 +02:00
|
|
|
if (!content) {
|
2019-10-21 12:38:04 +02:00
|
|
|
printf_top("Failed to parse json\n");
|
2023-12-11 21:17:47 +01:00
|
|
|
return nullptr;
|
2019-10-18 12:40:19 +02:00
|
|
|
}
|
|
|
|
return content;
|
|
|
|
}
|
|
|
|
|
2023-12-10 20:24:28 +01:00
|
|
|
void Client::print_json(json_t *json){
|
|
|
|
char* data = json_dumps(json, 0);
|
|
|
|
puts(data);
|
|
|
|
free(data);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Client::generate_otk(size_t otkcount) {
|
|
|
|
size_t len = acc.generate_one_time_keys_random_length(otkcount);
|
|
|
|
std::unique_ptr<uint8_t[]> otkrandom = std::make_unique<uint8_t[]>(len);
|
|
|
|
PS_GenerateRandomBytes(otkrandom.get(), len);
|
|
|
|
acc.generate_one_time_keys(otkcount, otkrandom.get(), len);
|
|
|
|
}
|
|
|
|
void Client::generate_otk() {
|
|
|
|
size_t otkcount = acc.max_number_of_one_time_keys() / 2;
|
|
|
|
generate_otk(otkcount);
|
|
|
|
}
|
|
|
|
void Client::generate_device_key() {
|
|
|
|
size_t len = acc.new_account_random_length();
|
|
|
|
std::unique_ptr<uint8_t[]> random = std::make_unique<uint8_t[]>(len);
|
|
|
|
PS_GenerateRandomBytes(random.get(), len);
|
|
|
|
acc.new_account(random.get(), len);
|
|
|
|
}
|
|
|
|
void Client::generate_fallback_key() {
|
|
|
|
size_t len = acc.generate_fallback_key_random_length();
|
|
|
|
std::unique_ptr<uint8_t[]> random = std::make_unique<uint8_t[]>(len);
|
|
|
|
PS_GenerateRandomBytes(random.get(), len);
|
|
|
|
acc.generate_fallback_key(random.get(), len);
|
|
|
|
}
|
|
|
|
// Make sure to remove any unsigned and signatures block before doing this !
|
|
|
|
void Client::sign_json(json_t* json) {
|
|
|
|
puts("Signing json");
|
|
|
|
char* jsonStr = json_dumps(json, JSON_COMPACT | JSON_SORT_KEYS);
|
|
|
|
size_t signlen = acc.signature_length();
|
|
|
|
size_t ptrlen = olm::encode_base64_length(signlen);
|
|
|
|
std::unique_ptr<uint8_t[]> signature = std::make_unique<uint8_t[]>(ptrlen);
|
|
|
|
acc.sign(reinterpret_cast<const uint8_t*>(jsonStr), strlen(jsonStr), signature.get() + ptrlen - signlen, signlen);
|
|
|
|
free(jsonStr);
|
|
|
|
|
|
|
|
olm::encode_base64(signature.get() + ptrlen - signlen, signlen, signature.get());
|
|
|
|
|
|
|
|
json_t* signitem = json_object();
|
|
|
|
std::cout << std::string(reinterpret_cast<const char*>(signature.get()), ptrlen) << std::endl;
|
|
|
|
json_object_set_new(signitem, ("ed25519:" + getDeviceId()).c_str(), json_stringn(reinterpret_cast<const char*>(signature.get()), ptrlen));
|
|
|
|
json_t* signobj = json_object();
|
|
|
|
json_object_set_new(signobj, getUserId().c_str(), signitem);
|
|
|
|
json_object_set_new(json, "signatures", signobj);
|
|
|
|
}
|
|
|
|
json_t* Client::get_device_keys() {
|
|
|
|
json_t* device_keys = json_object();
|
|
|
|
json_t* algorithms = json_array();
|
|
|
|
json_array_append_new(algorithms, json_string("m.olm.v1.curve25519-aes-sha2"));
|
|
|
|
json_array_append_new(algorithms, json_string("m.megolm.v1.aes-sha2"));
|
|
|
|
json_object_set_new(device_keys, "algorithms", algorithms);
|
|
|
|
json_object_set_new(device_keys, "device_id", json_string(getDeviceId().c_str()));
|
|
|
|
json_object_set_new(device_keys, "user_id", json_string(getUserId().c_str()));
|
|
|
|
|
|
|
|
// Extract keys
|
|
|
|
size_t acclen = acc.get_identity_json_length();
|
|
|
|
std::unique_ptr<uint8_t[]> acckeys = std::make_unique<uint8_t[]>(acclen);
|
|
|
|
size_t bytes = acc.get_identity_json(acckeys.get(), acclen);
|
|
|
|
std::cout << "Parsing bytes" << std::endl;
|
|
|
|
json_error_t error;
|
|
|
|
sleep(5);
|
|
|
|
std::cout << "Heading into it..." << std::endl;
|
|
|
|
json_t* keys = json_loadb(reinterpret_cast<const char*>(acckeys.get()), bytes, 0, &error);
|
|
|
|
if (keys == nullptr) {
|
|
|
|
printf("error: on line %d at char %d: %s\n", error.line, error.column, error.text);
|
|
|
|
sleep(10);
|
|
|
|
}
|
|
|
|
std::cout << "Reloaded data" << std::endl;
|
|
|
|
std::cout << "Adding bits" << std::endl;
|
|
|
|
|
|
|
|
// Merge keys
|
|
|
|
json_object_set(device_keys, ("ed25519:" + getDeviceId()).c_str(), json_object_get(keys, "ed25519"));
|
|
|
|
json_object_set(device_keys, ("curve25519:" + getDeviceId()).c_str(), json_object_get(keys, "curve25519"));
|
|
|
|
json_decref(keys);
|
|
|
|
std::cout << "Finalizing" << std::endl;
|
|
|
|
return device_keys;
|
|
|
|
}
|
|
|
|
json_t* Client::get_fallback_keys() {
|
|
|
|
json_t* fallback_keys = json_object();
|
|
|
|
// Extract keys
|
|
|
|
size_t acclen = acc.get_unpublished_fallback_key_json_length();
|
|
|
|
auto acckeys = std::make_unique<uint8_t[]>(acclen);
|
|
|
|
size_t bytes = acc.get_unpublished_fallback_key_json(acckeys.get(), acclen);
|
|
|
|
json_t* keys = json_loadb(reinterpret_cast<const char*>(acckeys.get()), bytes, 0, nullptr);
|
|
|
|
json_t* keyobj = json_object_get(keys, "curve25519");
|
|
|
|
void* iter = json_object_iter(keyobj);
|
|
|
|
|
|
|
|
while (iter != nullptr) {
|
|
|
|
const char* keyid = json_object_iter_key(iter);
|
|
|
|
json_t* keyval = json_object_iter_value(iter);
|
|
|
|
json_t* tosign = json_object();
|
|
|
|
json_object_set(tosign, "key", keyval);
|
|
|
|
// object is fallback key
|
|
|
|
json_object_set_new(tosign, "fallback", json_true());
|
|
|
|
|
|
|
|
sign_json(tosign);
|
|
|
|
json_object_set(fallback_keys, (std::string("signed_curve25519:") + keyid).c_str(), tosign);
|
|
|
|
iter = json_object_iter_next(keyobj, iter);
|
|
|
|
}
|
|
|
|
json_decref(keys);
|
|
|
|
return fallback_keys;
|
|
|
|
}
|
|
|
|
json_t* Client::get_unpublished_otk() {
|
|
|
|
json_t* otk_keys = json_object();
|
|
|
|
// Extract keys
|
|
|
|
size_t acclen = acc.get_one_time_keys_json_length();
|
|
|
|
auto acckeys = std::make_unique<uint8_t[]>(acclen);
|
|
|
|
size_t bytes = acc.get_one_time_keys_json(acckeys.get(), acclen);
|
|
|
|
json_t* keys = json_loadb(reinterpret_cast<const char*>(acckeys.get()), bytes, 0, nullptr);
|
|
|
|
|
|
|
|
json_t* keyobj = json_object_get(keys, "curve25519");
|
|
|
|
void* iter = json_object_iter(keyobj);
|
|
|
|
|
|
|
|
while (iter != nullptr) {
|
|
|
|
const char* keyid = json_object_iter_key(iter);
|
|
|
|
json_t* keyval = json_object_iter_value(iter);
|
|
|
|
json_t* tosign = json_object();
|
|
|
|
json_object_set(tosign, "key", keyval);
|
|
|
|
|
|
|
|
sign_json(tosign);
|
|
|
|
json_object_set(otk_keys, (std::string("signed_curve25519:") + keyid).c_str(), tosign);
|
|
|
|
iter = json_object_iter_next(keyobj, iter);
|
|
|
|
}
|
|
|
|
json_decref(keys);
|
|
|
|
return otk_keys;
|
|
|
|
}
|
|
|
|
void Client::save_keys() {
|
|
|
|
size_t pacclen = olm::pickle_length(acc);
|
|
|
|
auto m = std::make_unique<uint8_t[]>(pacclen);
|
|
|
|
olm::pickle(m.get(), acc);
|
|
|
|
FILE* file = fopen("secret-keys", "w");
|
|
|
|
fwrite(m.get(), pacclen, 1, file);
|
|
|
|
fclose(file);
|
|
|
|
}
|
|
|
|
void Client::upload_keys() { // Upload the keys ---
|
|
|
|
// Build device key json
|
|
|
|
json_t* upload_keys = json_object();
|
|
|
|
|
|
|
|
json_t* device_keys = get_device_keys();
|
|
|
|
std::cout << "Gathered device keys" << std::endl;
|
|
|
|
// Sign keys block
|
|
|
|
sign_json(device_keys);
|
|
|
|
std::cout << "Signed device keys" << std::endl;
|
|
|
|
json_object_set_new(upload_keys, "device_keys", device_keys);
|
|
|
|
|
|
|
|
json_t* fallback_keys = get_fallback_keys();
|
|
|
|
std::cout << "Signed fallback keys" << std::endl;
|
|
|
|
// fallback keys are already signed
|
|
|
|
json_object_set_new(upload_keys, "fallback_keys", fallback_keys);
|
|
|
|
|
|
|
|
// OTK keys
|
|
|
|
json_t* otk = get_unpublished_otk();
|
|
|
|
std::cout << "Signed OTK keys" << std::endl;
|
|
|
|
// OTK keys are already signed
|
|
|
|
json_object_set_new(upload_keys, "one_time_keys", otk);
|
|
|
|
|
|
|
|
uploadKeys(upload_keys);
|
|
|
|
std::cout << "Keys uploaded !" << std::endl;
|
|
|
|
print_json(upload_keys);
|
|
|
|
json_decref(upload_keys);
|
|
|
|
acc.mark_keys_as_published();
|
|
|
|
// Whether a key or not is published is saved, let's save again
|
|
|
|
save_keys();
|
|
|
|
}
|
|
|
|
void Client::start_encryption() {
|
|
|
|
// std::FILE* file = std::fopen("secret-keys", "r");
|
|
|
|
std::ifstream ifs("secret-keys", std::ios::binary | std::ios::ate);
|
|
|
|
if (!ifs.is_open()) {
|
|
|
|
std::cout << "No secret keys found, generating " << std::endl;
|
|
|
|
// Generate and save to file
|
|
|
|
// Device keys
|
|
|
|
generate_device_key();
|
|
|
|
std::cout << "Generated device key" << std::endl;
|
|
|
|
// One time keys
|
|
|
|
generate_otk();
|
|
|
|
std::cout << "Generated OTK key" << std::endl;
|
|
|
|
// Fallback keys
|
|
|
|
generate_fallback_key();
|
|
|
|
std::cout << "Generated fallback key" << std::endl;
|
|
|
|
upload_keys();
|
|
|
|
|
|
|
|
} else {
|
|
|
|
// Load from file
|
|
|
|
std::ifstream::pos_type fsize = ifs.tellg();
|
|
|
|
if (fsize == -1) {
|
|
|
|
std::cout << "What." << std::endl;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
auto data = std::make_unique<uint8_t[]>(fsize);
|
|
|
|
ifs.seekg(0, std::ios::beg);
|
|
|
|
ifs.read(reinterpret_cast<char*>(data.get()), static_cast<std::streamsize>(fsize));
|
|
|
|
olm::unpickle(data.get(), data.get() + fsize, acc);
|
|
|
|
}
|
2023-12-11 20:58:32 +01:00
|
|
|
}
|
|
|
|
void Client::getDevices(){
|
|
|
|
// To get the devices, we need to do a /keys/query req
|
|
|
|
// We then need to keep up to date using /sync to_device (see processToDevice)
|
|
|
|
// This will need to all be cached
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
void Client::processToDevice(json_t* data) {
|
|
|
|
|
2023-12-10 20:24:28 +01:00
|
|
|
}
|
2019-10-17 10:45:55 +02:00
|
|
|
}; // namespace Matrix
|