/** This file is a part of rexy's matrix client Copyright (C) 2019 rexy712 This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ #include "matrix.hpp" #include #include "raii/curl_llist.hpp" #include "raii/static_string.hpp" #include "raii/filerd.hpp" #include "fat_strings.hpp" #include "raii/util.hpp" #include "libav/frame.hpp" #include "libav/codec/context.hpp" #include "libav/fmt/context.hpp" #include "libav/packet.hpp" extern "C"{ # include //sws_scale # include //av_image_alloc } #include "common.hpp" #define THUMB_SIZE 500 namespace matrix{ auth_data parse_auth_data(RJP_value* root){ static const char* fields[] = {"username", "password", "homeserver", "access_token"}; RJP_search_res details[4]; rjp_search_members(root, 4, fields, details, 0); if(!rjp_value_string_length(details[3].value)){ return auth_data{details[0].value, details[1].value, details[2].value, raii::rjp_string{}}; } return auth_data{details[0].value, details[1].value, details[2].value, details[3].value}; } client::client(const auth_data& a, const raii::string_base& useragent): m_curl(), m_useragent(useragent), m_homeserver(a.homeserver) { _acquire_access_token(a); } client::client(const auth_data& a, raii::string&& useragent): m_curl(), m_useragent(std::move(useragent)), m_homeserver(a.homeserver) { _acquire_access_token(a); } const raii::rjp_string& client::access_token(void)const{ return m_access_token; } const raii::rjp_string& client::userid(void)const{ return m_userid; } const raii::string& client::useragent(void)const{ return m_useragent; } void client::set_useragent(const raii::string_base& useragent){ m_useragent = useragent; } void client::set_useragent(raii::string&& useragent){ m_useragent = std::move(useragent); } void client::set_display_name(const raii::string_base& newname){ raii::string reply = _put_curl(raii::string("{\"displayname\":\"" + newname + "\"}"), m_urls.displayname(), raii::curl_llist()); } void client::set_profile_picture(const raii::string_base& media_url){ raii::string reply = _put_curl(raii::string("{\"avatar_url\":\"" + media_url + "\"}"), m_urls.profile_picture(), raii::curl_llist()); } raii::rjp_string client::get_display_name(void)const{ return _get_and_find(m_urls.displayname(), "displayname"_ss); } raii::rjp_string client::get_profile_picture(void)const{ return _get_and_find(m_urls.profile_picture(), "avatar_url"_ss); } raii::rjp_string client::room_alias_to_id(const raii::string_base& alias)const{ auto tmp = m_curl.encode(alias, alias.length()); return _get_and_find(raii::string(m_urls.alias_lookup() + tmp), "room_id"_ss); } std::vector client::list_rooms(void)const{ std::vector ret; raii::string reply = _get_curl(m_urls.room_list()); if(!reply) return ret; raii::rjp_ptr root(rjp_parse(reply)); if(!root) return ret; RJP_search_res res = rjp_search_member(root.get(), "joined_rooms", 0); if(!res.value) return ret; for(RJP_value* v = rjp_get_element(res.value);v;v = rjp_next_element(v)){ ret.emplace_back(v); } return ret; } raii::string client::create_room(const raii::string_base& name, const raii::string_base& alias)const{ raii::string postdata; if(alias) postdata = "{\"name\": \"" + raii::json_escape(name) + "\",\"room_alias_name\": \"" + raii::json_escape(alias) + "\"}"; else postdata = "{\"name\": \"" + raii::json_escape(name) + "\"}"; return _post_curl(postdata, m_urls.create_room(), raii::curl_llist()); } bool client::join_room(const raii::string_base& roomid)const{ return _post_curl(raii::string(), m_urls.join_room(m_homeserver, m_access_token, m_curl.encode(roomid)), raii::curl_llist()); } bool client::leave_room(const raii::string_base& roomid)const{ return _post_curl(raii::string(), m_urls.leave_room(m_homeserver, m_access_token, m_curl.encode(roomid)), raii::curl_llist()); } bool client::accept_invite(const membership_info& invite)const{ return join_room(invite.roomid); } bool client::reject_invite(const membership_info& invite)const{ return leave_room(invite.roomid); } file_info client::upload_file(const raii::string_base& filename)const{ return upload_file(filename, raii::static_string()); } file_info client::upload_file(const raii::string_base& filename, const raii::string_base& alias)const{ raii::filerd fd(filename); if(!fd) return {}; return file_info{_upload_file(fd, raii::curl_llist{}), alias ? alias : filename, {}, fd.length()}; } image_info client::upload_image(const raii::string_base& filename)const{ return upload_image(filename, raii::static_string()); } #ifdef HAS_FREEIMAGE static std::vector _create_image_thumbnail(fipImage& image, FREE_IMAGE_FORMAT type, size_t target_size){ image.makeThumbnail(target_size); FreeImageIO fileout; std::vector buffer; fileout.write_proc = [](void* ptr, unsigned int size, unsigned int nmemb, void* fp) -> unsigned int{ std::vector& buffer = *reinterpret_cast*>(fp); buffer.insert(buffer.end(), (char*)ptr, ((char*)ptr)+size*nmemb); return size*nmemb; }; switch(type){ case FIF_JPEG: return image.saveToHandle(type, &fileout, &buffer, JPEG_QUALITYGOOD | JPEG_SUBSAMPLING_411) ? buffer : std::vector(); case FIF_PNG: return image.saveToHandle(type, &fileout, &buffer, PNG_Z_BEST_COMPRESSION) ? buffer : std::vector(); default: return image.saveToHandle(type, &fileout, &buffer) ? buffer : std::vector(); }; } static std::vector _load_image_thumbnail_info(fipImage& image, image_info& info, FREE_IMAGE_FORMAT type){ //create and upload thumbnail if(info.width > THUMB_SIZE || info.height > THUMB_SIZE){ std::vector thumb_data = _create_image_thumbnail(image, type, THUMB_SIZE); if(!thumb_data.size()){ info.thumb_width = info.width; info.thumb_height = info.height; info.thumbsize = info.filesize; }else{ info.thumb_width = image.getWidth(); info.thumb_height = image.getHeight(); info.thumbsize = thumb_data.size(); return thumb_data; } }else{ info.thumb_width = info.width; info.thumb_height = info.height; info.thumbsize = info.filesize; } return {}; } image_info client::upload_image(const raii::string_base& filename, const raii::string_base& alias)const{ image_info ret; FREE_IMAGE_FORMAT type = fipImage::identifyFIF(filename.get()); ret.mimetype = FreeImage_GetFIFMimeType(type); raii::curl_llist header(raii::string("Content-Type: " + ret.mimetype)); { raii::filerd fd(filename, "rb"); if(!fd) return ret; ret.filesize = fd.length(); ret.fileurl = _upload_file(fd, header); } fipImage image; image.load(filename.get()); if(!image.isValid()) return {}; ret.width = image.getWidth(); ret.height = image.getHeight(); ret.filename = alias ? alias : filename; std::vector thumb_data = _load_image_thumbnail_info(image, ret, type); if(thumb_data.size()) ret.thumburl = _post_and_find(raii::static_string(thumb_data.data(), thumb_data.size()), m_urls.file_upload(), header, "content_uri"_ss); return ret; } #else //HAS_FREEIMAGE image_info client::upload_image(const raii::string_base& filename, const raii::string_base& alias)const{ image_info ret = {}; ret = upload_file(filename, alias); return ret; } #endif //HAS_FREEIMAGE video_info client::upload_video(const raii::string_base& filename)const{ return upload_video(filename, raii::static_string()); } audio_info client::upload_audio(const raii::string_base& filename)const{ return upload_audio(filename, raii::static_string()); } #ifdef HAS_FFMPEG static libavcodec::context _get_libav_jpg_context(AVRational src, size_t w, size_t h){ AVCodec* jpg_codec = avcodec_find_encoder(AV_CODEC_ID_MJPEG); libavcodec::context jctx(jpg_codec); jctx->pix_fmt = AV_PIX_FMT_YUVJ420P; jctx->width = w; jctx->height = h; jctx->codec_id = AV_CODEC_ID_MJPEG; jctx->codec_type = AVMEDIA_TYPE_VIDEO; if(src.num == 0 || src.den == 0) jctx->time_base = AVRational{1, 30}; else jctx->time_base = src; jctx.open(); return jctx; } [[maybe_unused]] static void _change_color_range(SwsContext* sws_ctx){ int dummy[4]; int src_range, dest_range; int br, con, sat; sws_getColorspaceDetails(sws_ctx, (int**)&dummy, &src_range, (int**)&dummy, &dest_range, &br, &con, &sat); const int* coefs = sws_getCoefficients(SWS_CS_DEFAULT); src_range = 2; sws_setColorspaceDetails(sws_ctx, coefs, src_range, coefs, dest_range, br, con, sat); } static void _calc_video_thumbnail_dims(size_t target, size_t src_w, size_t src_h, size_t* w, size_t* h){ size_t target_width = src_w; size_t target_height = src_h; if(target_width > target_height){ if(target_width > target){ target_height = target_height * ((float)target / target_width); target_width = target; } }else{ if(target_height > target){ target_width = target_width * ((float)target / target_height); target_height = target; } } *w = target_width; *h = target_height; } static libav::packet _create_video_thumbnail(libavfmt::input_context& src, libavcodec::context& src_ctx, int index, size_t targw, size_t targh){ libavcodec::context jpg_context = _get_libav_jpg_context(src_ctx->time_base, targw, targh); libav::frame src_frame(src_ctx->width, src_ctx->height, src_ctx->pix_fmt); libav::frame jpg_frame(src_ctx->width, src_ctx->height, jpg_context->pix_fmt); libav::packet packet; SwsContext* sws_ctx = sws_getContext(src_ctx->width, src_ctx->height, src_ctx->pix_fmt, targw, targh, AV_PIX_FMT_YUV420P, SWS_BILINEAR, NULL, NULL, NULL); //_change_color_range(sws_ctx); av_image_alloc(jpg_frame->data, jpg_frame->linesize, targw, targh, jpg_context->pix_fmt, 32); jpg_frame.set_freep(); while(av_read_frame(src, packet) >= 0){ if(packet->stream_index == index){ avcodec_send_packet(src_ctx, packet); if(avcodec_receive_frame(src_ctx, src_frame) >= 0) break; } av_packet_unref(packet); } sws_scale(sws_ctx, src_frame->data, src_frame->linesize, 0, src_ctx->height, jpg_frame->data, jpg_frame->linesize); sws_freeContext(sws_ctx); if(avcodec_send_frame(jpg_context, jpg_frame) < 0){ return libav::packet(nullptr); } if(avcodec_receive_packet(jpg_context, packet) < 0){ return libav::packet(nullptr); } return packet; } static raii::string get_libav_mimetype(const libav::fmt::input_context& ctx){ const char* first = strstr(ctx->iformat->name, ","); if(first) return raii::string("video/" + raii::static_string(ctx->iformat->name, first - ctx->iformat->name)); return raii::string("video/" + raii::static_string(ctx->iformat->name)); } video_info client::upload_video(const raii::string_base& filename, const raii::string_base& alias)const{ video_info ret = {}; libav::fmt::input_context in(filename); if(!in) return {}; int stream_index = -1; for(size_t i = 0;i < in->nb_streams;++i){ if(in->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO){ stream_index = i; break; } } if(stream_index == -1) return {}; libavcodec::context src_ctx(in, stream_index); if(!src_ctx) return {}; src_ctx.open(); if(!src_ctx.is_open()) return {}; size_t thumbw, thumbh; _calc_video_thumbnail_dims(THUMB_SIZE, src_ctx->width, src_ctx->height, &thumbw, &thumbh); libav::packet packet = _create_video_thumbnail(in, src_ctx, stream_index, thumbw, thumbh); if(packet){ raii::curl_llist header("Content-Type: image/jpeg"); ret.thumb_width = thumbw; ret.thumb_height = thumbh; ret.thumbsize = packet->size; ret.thumburl = _post_and_find(raii::static_string((char*)packet->data, packet->size), m_urls.file_upload(), header, "content_uri"_ss); packet.reset(); } ret.width = src_ctx->width; ret.height = src_ctx->height; ret.mimetype = get_libav_mimetype(in); src_ctx.reset(); in.reset(); raii::curl_llist header(raii::string("Content-Type:" + ret.mimetype)); header += "Transfer-Encoding: chunked"; raii::filerd fd(filename); if(!fd) return {}; ret.filesize = fd.length(); ret.fileurl = _upload_file(fd, header); ret.filename = alias ? alias : filename; return ret; } audio_info client::upload_audio(const raii::string_base& filename, const raii::string_base& alias)const{ audio_info ret = {}; libav::fmt::input_context in(filename); ret.mimetype = get_libav_mimetype(in); in.reset(); raii::curl_llist header(raii::string("Content-Type:" + ret.mimetype)); raii::filerd fd(filename); if(!fd) return {}; ret.filesize = fd.length(); ret.fileurl = _upload_file(fd, header); ret.filename = alias ? alias : filename; return ret; } #else //HAS_FFMPEG video_info client::upload_video(const raii::string_base& filename, const raii::string_base& alias)const{ video_info ret = {}; ret = upload_file(filename, alias); return ret; } audio_info client::upload_audio(const raii::string_base& filename, const raii::string_base& alias)const{ audio_info ret = {}; ret = upload_file(filename, alias); return ret; } #endif //HAS_FFMPEG raii::rjp_string client::send_file(const raii::string_base& room, const file_info& file)const{ return _send_message(room, detail::_file_body(file)); } raii::rjp_string client::send_image(const raii::string_base& room, const image_info& image)const{ return _send_message(room, detail::_image_body(image)); } raii::rjp_string client::send_video(const raii::string_base& room, const video_info& video)const{ return _send_message(room, detail::_video_body(video)); } raii::rjp_string client::send_audio(const raii::string_base& room, const audio_info& audio)const{ return _send_message(room, detail::_audio_body(audio)); } raii::rjp_string client::send_message(const raii::string_base& room, const raii::string_base& text)const{ return _send_message(room, detail::_message_body(text)); } void client::send_typing(const raii::string_base& room, bool active, int timeout)const{ if(active) _put_curl(raii::string("{\"timeout\":" + raii::itostr(timeout) + ",\"typing\":true}"), m_urls.typing(m_homeserver, m_access_token, m_curl.encode(room), m_curl.encode(m_userid)), raii::curl_llist()); else _put_curl("{\"typing\":false}"_ss, m_urls.typing(m_homeserver, m_access_token, m_curl.encode(room), m_curl.encode(m_userid)), raii::curl_llist()); } raii::rjp_string client::redact_event(const raii::string_base& roomid, const raii::string_base& eventid, const raii::string_base& reason)const{ auto ret = _put_curl(raii::string("{\"reason\":\"" + reason + "\"}"), m_urls.redact(m_homeserver, m_access_token, m_curl.encode(roomid), m_curl.encode(eventid)), raii::curl_llist()); if(!ret) return {}; raii::rjp_ptr root(rjp_parse(ret.get())); if(!root) return {}; RJP_search_res res = rjp_search_member(root.get(), "event_id", 0); if(!res.value) return {}; return raii::rjp_string(res.value); } raii::rjp_string client::redact_event(const raii::string_base& roomid, const raii::string_base& eventid)const{ return redact_event(roomid, eventid, ""_ss); } void client::logout(void){ _get_curl(m_urls.logout(m_homeserver, m_access_token)); m_urls.invalidate_accesstoken(); } raii::string client::sync(size_t timeout){ raii::string reply = _get_curl(m_urls.sync(m_homeserver, m_access_token, m_next_batch, raii::itostr(timeout))); if(!reply) return {}; raii::rjp_ptr root(rjp_parse(reply)); if(!root) return reply; RJP_search_res res = rjp_search_member(root.get(), "next_batch", 0); if(!res.value) return reply; m_next_batch = res.value; if(m_raw_callback != nullptr){ m_raw_callback(*this, root); } res = rjp_search_member(root.get(), "rooms", 0); if(res.value){ if(m_message_callback != nullptr) _handle_messages(res.value); if(m_membership_callback != nullptr) _handle_membership_events(res.value); } return reply; } /******************************* Internal functions ********************************/ void client::_handle_membership_events(RJP_value* rooms){ RJP_search_res res = rjp_search_member(rooms, "invite", 0); if(res.value) _handle_invites(res.value); res = rjp_search_member(rooms, "join", 0); if(res.value) _handle_other_membership(res.value); } static membership_info _membership_info_from_json(RJP_value* event, RJP_value* roomid){ static constexpr const char* search_terms[] = {"event_id", "sender", "state_key", "unsigned", "type", "origin_server_ts"}; static constexpr size_t num_searches = sizeof(search_terms)/sizeof(search_terms[0]); RJP_search_res results[num_searches] = {}; rjp_search_members(event, num_searches, search_terms, results, 0); for(size_t i = 0;i < num_searches;++i){ if(!results[i].value) return {}; } RJP_search_res age = rjp_search_member(results[3].value, "age", 0); if(!age.value) return {}; raii::rjp_string room_str = rjp_member_name(roomid); raii::rjp_string sender_str = results[1].value; raii::rjp_string event_str = results[0].value; raii::rjp_string eventtype_str = results[4].value; raii::rjp_string rec_str = results[2].value; return membership_info{std::move(room_str), std::move(sender_str), std::move(event_str), std::move(eventtype_str), rjp_value_integer(results[5].value), rjp_value_integer(age.value), std::move(rec_str)}; } static msg_info _message_info_from_json(RJP_value* event, RJP_value* roomid){ static constexpr const char* searches[] = {"sender", "content", "event_id", "unsigned", "type", "origin_server_ts"}; static constexpr size_t num_searches = sizeof(searches)/sizeof(searches[0]); RJP_search_res results[num_searches] = {}; rjp_search_members(event, num_searches, searches, results, 0); for(size_t i = 0;i < num_searches;++i){ if(!results[i].value) return msg_info{}; } RJP_search_res msg = rjp_search_member(results[1].value, "msgtype", 0); if(!msg.value) return {}; RJP_search_res body = rjp_search_member(results[1].value, "body", 0); if(!body.value) return {}; RJP_search_res age = rjp_search_member(results[3].value, "age", 0); if(!age.value) return msg_info{}; raii::rjp_string room_str = rjp_member_name(roomid); raii::rjp_string sender_str = results[0].value; raii::rjp_string eventid_str = results[2].value; raii::rjp_string eventtype_str = results[3].value; raii::rjp_string msgbody_str = body.value; return msg_info{std::move(room_str), std::move(sender_str), std::move(eventid_str), std::move(eventtype_str), rjp_value_integer(results[5].value), rjp_value_integer(age.value), msg::from_str(rjp_value_string(msg.value)), std::move(msgbody_str)}; } void client::_handle_other_membership(RJP_value* join){ for(RJP_value* roomid = rjp_get_member(join);roomid;roomid = rjp_next_member(roomid)){ RJP_search_res res = rjp_search_member(roomid, "timeline", 0); if(!res.value) continue; res = rjp_search_member(res.value, "events", 0); if(!res.value) continue; for(RJP_value* event = rjp_get_element(res.value);event;event = rjp_next_element(event)){ membership_info minfo = _membership_info_from_json(event, roomid); if(!minfo.roomid) continue; if(minfo.eventtype != "m.room.member"_ss) continue; m_membership_callback(*this, minfo); } } } void client::_handle_invites(RJP_value* invites){ for(RJP_value* roomid = rjp_get_member(invites);roomid;roomid = rjp_next_member(roomid)){ RJP_search_res res = rjp_search_member(roomid, "invite_state", 0); if(!res.value) continue; res = rjp_search_member(res.value, "events", 0); if(!res.value) continue; for(RJP_value* event = rjp_get_element(res.value);event;event = rjp_next_element(event)){ membership_info minfo = _membership_info_from_json(event, roomid); if(!minfo.roomid) continue; m_membership_callback(*this, minfo); } } } void client::_handle_messages(RJP_value* messages){ RJP_search_res res = rjp_search_member(messages, "join", 0); if(!res.value) return; for(RJP_value* roomid = rjp_get_member(res.value);roomid;roomid = rjp_next_member(roomid)){ res = rjp_search_member(roomid, "timeline", 0); if(!res.value) continue; res = rjp_search_member(res.value, "events", 0); if(!res.value) continue; for(RJP_value* event = rjp_get_element(res.value);event;event = rjp_next_element(event)){ msg_info minfo = _message_info_from_json(event, roomid); m_message_callback(*this, minfo); } } } void client::_send_read_receipt(const raii::string_base& roomid, const raii::string_base& eventid)const{ _post_curl(""_ss, m_urls.read_receipt(m_homeserver, m_access_token, m_curl.encode(roomid), m_curl.encode(eventid)), raii::curl_llist()); } raii::rjp_string client::_upload_file(raii::filerd& fp, const raii::curl_llist& header)const{ raii::string fileurl; m_curl.postreq(); m_curl.setopt(CURLOPT_POSTFIELDS, NULL); m_curl.setopt(CURLOPT_READDATA, (void*)fp.get()); m_curl.setopt(CURLOPT_POSTFIELDSIZE_LARGE, (curl_off_t)fp.length()); m_curl.setopt(CURLOPT_INFILESIZE_LARGE, (curl_off_t)fp.length()); m_curl.seturl(m_urls.file_upload()); m_curl.setheader(header); m_curl.setopt(CURLOPT_WRITEFUNCTION, _post_reply_curl_callback); m_curl.setopt(CURLOPT_WRITEDATA, &fileurl); CURLcode cres = m_curl.perform(); m_curl.setopt(CURLOPT_READDATA, NULL); if(cres != CURLE_OK) return {}; if(!fileurl) return {}; raii::rjp_ptr root(rjp_parse(fileurl)); if(!root) return {}; RJP_search_res res = rjp_search_member(root.get(), "content_uri", 0); return res.value; } raii::rjp_string client::_send_message(const raii::string_base& room, const raii::string_base& msg)const{ raii::rjp_string reply = _post_and_find( msg, m_urls.send(m_homeserver, m_access_token, m_curl.encode(room)), raii::curl_llist(), "event_id"_ss); return reply; } size_t client::_post_reply_curl_callback(char* ptr, size_t size, size_t nmemb, void* userdata){ raii::string* data = reinterpret_cast(userdata); (*data) += ptr; return size*nmemb; } raii::string client::_get_curl(const raii::string_base& url)const{ raii::string reply; m_curl.getreq(); m_curl.seturl(url); m_curl.setheader(raii::curl_llist{}); m_curl.setopt(CURLOPT_WRITEFUNCTION, _post_reply_curl_callback); m_curl.setopt(CURLOPT_WRITEDATA, &reply); CURLcode res = m_curl.perform(); if(res != CURLE_OK) return {}; return reply; } raii::string client::_post_curl(const raii::string_base& postdata, const raii::string_base& url, const raii::curl_llist& header)const{ raii::string reply; m_curl.postreq(); m_curl.setopt(CURLOPT_POSTFIELDS, postdata.get()); m_curl.setopt(CURLOPT_POSTFIELDSIZE_LARGE, (curl_off_t)postdata.length()); m_curl.seturl(url); m_curl.setheader(header); m_curl.setopt(CURLOPT_WRITEFUNCTION, _post_reply_curl_callback); m_curl.setopt(CURLOPT_WRITEDATA, &reply); CURLcode res = m_curl.perform(); if(res != CURLE_OK) return {}; return reply; } struct put_data{ const char* data; size_t len; }; static size_t _put_read_curl_callback(char* buffer, size_t size, size_t nmemb, void* userdata){ put_data* src = reinterpret_cast(userdata); size_t curl_size = size*nmemb; size_t to_copy = std::min(curl_size, src->len); memcpy(buffer, src->data, to_copy); src->len -= to_copy; src->data += to_copy; return to_copy; } raii::string client::_put_curl(const raii::string_base& putdata, const raii::string_base& url, const raii::curl_llist& header)const{ raii::string reply; put_data data{putdata.get(), putdata.length()}; m_curl.putreq(); m_curl.setopt(CURLOPT_POSTFIELDS, putdata.get()); m_curl.setopt(CURLOPT_POSTFIELDSIZE_LARGE, (curl_off_t)putdata.length()); m_curl.seturl(url); m_curl.setheader(header); m_curl.setopt(CURLOPT_WRITEFUNCTION, _post_reply_curl_callback); m_curl.setopt(CURLOPT_WRITEDATA, &reply); m_curl.setopt(CURLOPT_READFUNCTION, _put_read_curl_callback); m_curl.setopt(CURLOPT_READDATA, &data); m_curl.setopt(CURLOPT_INFILESIZE, (curl_off_t)data.len); CURLcode res = m_curl.perform(); m_curl.setopt(CURLOPT_READDATA, NULL); m_curl.setopt(CURLOPT_READFUNCTION, NULL); if(res != CURLE_OK) return {}; return reply; } raii::rjp_string client::_post_and_find(const raii::string_base& data, const raii::string_base& url, const raii::curl_llist& header, const raii::string_base& target)const { raii::string reply = _post_curl(data, url, header); if(!reply) return {}; return _curl_reply_search(reply, target); } raii::rjp_string client::_get_and_find(const raii::string_base& url, const raii::string_base& target)const{ raii::string reply = _get_curl(url); if(!reply) return {}; return _curl_reply_search(reply, target); } raii::rjp_string client::_curl_reply_search(const raii::string_base& reply, const raii::string_base& target)const{ raii::rjp_ptr root(rjp_parse(reply)); if(!root) return {}; RJP_search_res res = rjp_search_member(root.get(), target.get(), 0); if(rjp_value_type(res.value) != json_string) return {}; return raii::rjp_string(res.value); } void client::_set_curl_defaults(void)const{ m_curl.setopt(CURLOPT_BUFFERSIZE, 102400L); m_curl.setopt(CURLOPT_NOPROGRESS, 1L); m_curl.setuseragent(m_useragent); m_curl.setopt(CURLOPT_MAXREDIRS, 50L); m_curl.setopt(CURLOPT_FOLLOWLOCATION, 1L); m_curl.forcessl(CURL_SSLVERSION_TLSv1_2); m_curl.setopt(CURLOPT_TCP_KEEPALIVE, 1L); m_curl.setopt(CURLOPT_FAILONERROR, 1L); } raii::string client::_request_access_token(const auth_data& a)const{ CURLcode result; raii::string postdata("{\"type\":\"m.login.password\", \"user\":\"" + raii::json_escape(a.name) + "\", \"password\":\"" + raii::json_escape(a.pass) + "\"}"); raii::string reply; m_curl.seturl(m_urls.login()); m_curl.setpostdata(postdata); m_curl.postreq(); m_curl.setopt(CURLOPT_WRITEFUNCTION, _post_reply_curl_callback); m_curl.setopt(CURLOPT_WRITEDATA, &reply); result = m_curl.perform(); if(result != CURLE_OK) return {}; return reply; } void client::_get_new_access_token(const auth_data& a){ m_urls = mat_url_list(m_homeserver); raii::string reply = _request_access_token(a); if(!reply) return; raii::rjp_ptr root(rjp_parse(reply)); if(!root) return; RJP_search_res token = rjp_search_member(root.get(), "access_token", 0); m_access_token = raii::rjp_string{token.value}; token = rjp_search_member(root.get(), "user_id", 0); m_userid = raii::rjp_string{token.value}; m_urls.repopulate_accesstoken(m_homeserver, m_access_token); m_urls.repopulate_userid(m_homeserver, m_access_token, m_curl.encode(m_userid)); } void client::_acquire_access_token(const auth_data& a){ _set_curl_defaults(); if(a.access_token){ m_access_token = a.access_token; raii::string reply; m_urls.repopulate_accesstoken(m_homeserver, m_access_token); reply = _get_curl(m_urls.whoami()); if(!reply){ DEBUG_PRINT("Given access token is invalid! Getting new token\n"); _get_new_access_token(a); return; } raii::rjp_ptr root(rjp_parse(reply)); if(!root) return; RJP_search_res id = rjp_search_member(root.get(), "user_id", 0); m_userid = raii::rjp_string(id.value); m_urls.repopulate_userid(m_homeserver, m_access_token, m_curl.encode(m_userid)); }else{ _get_new_access_token(a); } } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// client::mat_url_list::mat_url_list(const raii::string_base& homeserver){ _initial_populate(homeserver); } client::mat_url_list::mat_url_list(const raii::string_base& homeserver, const raii::string_base& access_token, const raii::string_base& userid){ repopulate(homeserver, access_token, userid); } void client::mat_url_list::repopulate_accesstoken(const raii::string_base& homeserver, const raii::string_base& access_token){ m_create_room = s_proto + homeserver + "/_matrix/client/r0/createRoom?access_token=" + access_token; m_file_upload = s_proto + homeserver + "/_matrix/media/r0/upload?access_token=" + access_token; m_room_list = s_proto + homeserver + "/_matrix/client/r0/joined_rooms?access_token=" + access_token; m_whoami = s_proto + homeserver + "/_matrix/client/r0/account/whoami?access_token=" + access_token; } void client::mat_url_list::repopulate_userid(const raii::string_base& homeserver, const raii::string_base& access_token, const raii::string_base& userid){ m_displayname = s_proto + homeserver + "/_matrix/client/r0/profile/" + userid + "/displayname?access_token=" + access_token; m_profile_picture = s_proto + homeserver + "/_matrix/client/r0/profile/" + userid + "/avatar_url?access_token=" + access_token; } void client::mat_url_list::repopulate(const raii::string_base& homeserver, const raii::string_base& access_token, const raii::string_base& userid){ _initial_populate(homeserver); repopulate_accesstoken(homeserver, access_token); repopulate_userid(homeserver, access_token, userid); } void client::mat_url_list::invalidate_accesstoken(void){ m_create_room.reset(); m_file_upload.reset(); m_room_list.reset(); m_whoami.reset(); m_displayname.reset(); m_profile_picture.reset(); } const raii::string& client::mat_url_list::create_room(void)const{ return m_create_room; } const raii::string& client::mat_url_list::file_upload(void)const{ return m_file_upload; } const raii::string& client::mat_url_list::room_list(void)const{ return m_room_list; } const raii::string& client::mat_url_list::login(void)const{ return m_login; } const raii::string& client::mat_url_list::alias_lookup(void)const{ return m_alias_lookup; } const raii::string& client::mat_url_list::whoami(void)const{ return m_whoami; } const raii::string& client::mat_url_list::displayname(void)const{ return m_displayname; } const raii::string& client::mat_url_list::profile_picture(void)const{ return m_profile_picture; } raii::string client::mat_url_list::logout(const raii::string_base& homeserver, const raii::string_base& access_token)const{ return raii::string(s_proto + homeserver + "/_matrix/client/r0/logout?access_token=" + access_token); } raii::string client::mat_url_list::join_room(const raii::string_base& homeserver, const raii::string_base& access_token, const raii::string_base& roomid)const{ return raii::string(s_proto + homeserver + "/_matrix/client/r0/rooms/" + roomid + "/join?access_token=" + access_token); } raii::string client::mat_url_list::leave_room(const raii::string_base& homeserver, const raii::string_base& access_token, const raii::string_base& roomid)const{ return raii::string(s_proto + homeserver + "/_matrix/client/r0/rooms/" + roomid + "/leave?access_token=" + access_token); } raii::string client::mat_url_list::sync(const raii::string_base& homeserver, const raii::string_base& access_token, const raii::string_base& next_batch, const raii::string_base& timeout)const{ if(!next_batch) return raii::string(s_proto + homeserver + "/_matrix/client/r0/sync?access_token=" + access_token + "&timeout=" + timeout); return raii::string(s_proto + homeserver + "/_matrix/client/r0/sync?access_token=" + access_token + "&timeout=" + timeout + "&since=" + next_batch); } raii::string client::mat_url_list::read_receipt(const raii::string_base& homeserver, const raii::string_base& access_token, const raii::string_base& roomid, const raii::string_base& eventid)const{ return raii::string(s_proto + homeserver + "/_matrix/client/r0/rooms/" + roomid + "/receipt/m.read/" + eventid + "?access_token=" + access_token); } raii::string client::mat_url_list::send(const raii::string_base& homeserver, const raii::string_base& access_token, const raii::string_base& roomid)const{ return raii::string(s_proto + homeserver + "/_matrix/client/r0/rooms/" + roomid + "/send/m.room.message?access_token=" + access_token); } raii::string client::mat_url_list::redact(const raii::string_base& homeserver, const raii::string_base& access_token, const raii::string_base& roomid, const raii::string_base& eventid)const{ return raii::string(s_proto + homeserver + "/_matrix/client/r0/rooms/" + roomid + "/redact/" + eventid + "/0?access_token=" + access_token); } raii::string client::mat_url_list::power_level(const raii::string_base& homeserver, const raii::string_base& access_token, const raii::string_base& roomid)const{ return raii::string(s_proto + homeserver + "/_matrix/client/r0/rooms/" + roomid + "/state/m.room.power_levels?access_token=" + access_token); } raii::string client::mat_url_list::presence(const raii::string_base& homeserver, const raii::string_base& access_token, const raii::string_base& userid)const{ return raii::string(s_proto + homeserver + "/_matrix/client/r0/presence/" + userid + "/status?access_token=" + access_token); } raii::string client::mat_url_list::typing(const raii::string_base& homeserver, const raii::string_base& access_token, const raii::string_base& roomid, const raii::string_base& userid)const{ return raii::string(s_proto + homeserver + "/_matrix/client/r0/rooms/" + roomid + "/typing/" + userid + "?access_token=" + access_token); } raii::string client::mat_url_list::kick(const raii::string_base& homeserver, const raii::string_base& access_token, const raii::string_base& roomid)const{ return raii::string(s_proto + homeserver + "/_matrix/client/r0/rooms/" + roomid + "/kick?access_token=" + access_token); } raii::string client::mat_url_list::ban(const raii::string_base& homeserver, const raii::string_base& access_token, const raii::string_base& roomid)const{ return raii::string(s_proto + homeserver + "/_matrix/client/r0/rooms/" + roomid + "/ban?access_token=" + access_token); } raii::string client::mat_url_list::unban(const raii::string_base& homeserver, const raii::string_base& access_token, const raii::string_base& roomid)const{ return raii::string(s_proto + homeserver + "/_matrix/client/r0/rooms/" + roomid + "/unban?access_token=" + access_token); } raii::string client::mat_url_list::invite(const raii::string_base& homeserver, const raii::string_base& access_token, const raii::string_base& roomid)const{ return raii::string(s_proto + homeserver + "/_matrix/client/r0/rooms/" + roomid + "/invite?access_token=" + access_token); } raii::string client::mat_url_list::room_members(const raii::string_base& homeserver, const raii::string_base& access_token, const raii::string_base& roomid)const{ return raii::string(s_proto + homeserver + "/_matrix/client/r0/rooms/" + roomid + "/members?access_token=" + access_token); } raii::string client::mat_url_list::password(const raii::string_base& homeserver, const raii::string_base& access_token)const{ return raii::string(s_proto + homeserver + "/_matrix/client/r0/account/password?access_token=" + access_token); } void client::mat_url_list::_initial_populate(const raii::string_base& homeserver){ m_alias_lookup = s_proto + homeserver + "/_matrix/client/r0/directory/room/"; m_login = s_proto + homeserver + "/_matrix/client/r0/login"; } }