/** This file is a part of r0nk, atlas_moon, and 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/client.hpp" #include "matrix/fat_strings.hpp" #include "raii/filerd.hpp" #include "raii/static_string.hpp" #include "raii/rjp_ptr.hpp" #include "raii/util.hpp" #ifdef HAS_FREEIMAGE # include "matrix/image_thumbnail_maker.hpp" #endif #ifdef HAS_FFMPEG # define THUMBSIZE 500 # include "matrix/video_thumbnail_maker.hpp" #endif namespace matrix{ //Ctor client::client(const std::shared_ptr& ses): connection(ses){} //local getter const raii::rjp_string& client::access_token(void)const{ return m_ses->access_token; } const raii::rjp_string& client::userid(void)const{ return m_ses->userid; } const raii::string& client::useragent(void)const{ return m_ses->useragent; } //networked setter void client::set_display_name(const raii::string_base& newname){ raii::string reply = _put_curl(raii::string("{\"displayname\":\"" + newname + "\"}"), m_ses->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_ses->urls.profile_picture(), raii::curl_llist()); } //networked getter raii::rjp_string client::get_display_name(void)const{ return _get_and_find(m_ses->urls.displayname(), "displayname"_ss); } raii::rjp_string client::get_profile_picture(void)const{ return _get_and_find(m_ses->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_ses->urls.alias_lookup() + tmp), "room_id"_ss); } std::vector client::list_rooms(void)const{ std::vector ret; raii::string reply = _get_curl(m_ses->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; } //room membership 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_ses->urls.create_room(), raii::curl_llist()); } bool client::join_room(const raii::string_base& roomid)const{ return _post_curl(raii::string(), m_ses->urls.join_room(m_ses->homeserver, m_ses->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_ses->urls.leave_room(m_ses->homeserver, m_ses->access_token, m_curl.encode(roomid)), raii::curl_llist()); } bool client::accept_invite(const raii::string_base& roomid)const{ return join_room(roomid); } bool client::reject_invite(const raii::string_base& roomid)const{ return leave_room(roomid); } //other network void client::logout(void){ _get_curl(m_ses->urls.logout(m_ses->homeserver, m_ses->access_token)); } //upload media 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 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 = internal::_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_ses->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 video_info client::upload_video(const raii::string_base& filename, const raii::string_base& alias)const{ internal::initlibav(); 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; internal::_calc_video_thumbnail_dims(THUMB_SIZE, src_ctx->width, src_ctx->height, &thumbw, &thumbh); libav::packet packet = internal::_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_ses->urls.file_upload(), header, "content_uri"_ss); packet.reset(); } ret.width = src_ctx->width; ret.height = src_ctx->height; ret.mimetype = internal::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 = internal::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 //send messages 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_ses->urls.typing(m_ses->homeserver, m_ses->access_token, m_curl.encode(room), m_curl.encode(m_ses->userid)), raii::curl_llist()); else _put_curl("{\"typing\":false}"_ss, m_ses->urls.typing(m_ses->homeserver, m_ses->access_token, m_curl.encode(room), m_curl.encode(m_ses->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_ses->urls.redact(m_ses->homeserver, m_ses->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); } /******************************* Internal functions ********************************/ void client::_send_read_receipt(const raii::string_base& roomid, const raii::string_base& eventid)const{ _post_curl(""_ss, m_ses->urls.read_receipt(m_ses->homeserver, m_ses->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_ses->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_ses->urls.send(m_ses->homeserver, m_ses->access_token, m_curl.encode(room)), raii::curl_llist(), "event_id"_ss); return reply; } }