From 7816c5423090a4998cebad478c8c8f1358196f6c Mon Sep 17 00:00:00 2001 From: Rexy712 Date: Tue, 16 Jul 2019 14:14:45 -0700 Subject: [PATCH] Restructured the matrix codebase and fixed a few segfault issues in the raii strings --- include/matrix.hpp | 293 +------- include/matrix/auth.hpp | 40 + include/matrix/client.hpp | 107 +++ include/matrix/client_base.hpp | 55 ++ include/matrix/event_info.hpp | 103 +++ include/{ => matrix}/fat_strings.hpp | 0 include/matrix/image_thumbnail_maker.hpp | 73 ++ include/matrix/session.hpp | 57 ++ include/matrix/session_info.hpp | 36 + include/matrix/syncer.hpp | 78 ++ include/matrix/upload_info.hpp | 54 ++ include/matrix/url_list.hpp | 86 +++ include/matrix/video_thumbnail_maker.hpp | 117 +++ include/raii/string_base.hpp | 1 + makefile | 2 +- src/matrix.cpp | 882 ----------------------- src/matrix/client.cpp | 335 +++++++++ src/matrix/client_base.cpp | 133 ++++ src/{ => matrix}/fat_strings.cpp | 4 +- src/matrix/session.cpp | 127 ++++ src/matrix/syncer.cpp | 156 ++++ src/matrix/url_list.cpp | 134 ++++ src/raii/string_base.cpp | 47 +- src/test.cpp | 26 +- 24 files changed, 1732 insertions(+), 1214 deletions(-) create mode 100644 include/matrix/auth.hpp create mode 100644 include/matrix/client.hpp create mode 100644 include/matrix/client_base.hpp create mode 100644 include/matrix/event_info.hpp rename include/{ => matrix}/fat_strings.hpp (100%) create mode 100644 include/matrix/image_thumbnail_maker.hpp create mode 100644 include/matrix/session.hpp create mode 100644 include/matrix/session_info.hpp create mode 100644 include/matrix/syncer.hpp create mode 100644 include/matrix/upload_info.hpp create mode 100644 include/matrix/url_list.hpp create mode 100644 include/matrix/video_thumbnail_maker.hpp delete mode 100644 src/matrix.cpp create mode 100644 src/matrix/client.cpp create mode 100644 src/matrix/client_base.cpp rename src/{ => matrix}/fat_strings.cpp (98%) create mode 100644 src/matrix/session.cpp create mode 100644 src/matrix/syncer.cpp create mode 100644 src/matrix/url_list.cpp diff --git a/include/matrix.hpp b/include/matrix.hpp index 3ee036f..29896b7 100644 --- a/include/matrix.hpp +++ b/include/matrix.hpp @@ -19,298 +19,11 @@ #ifndef MATRIX_HPP #define MATRIX_HPP -#include "raii/curler.hpp" -#include "raii/string.hpp" -#include "raii/rjp_string.hpp" -#include "raii/filerd.hpp" -#include "raii/rjp_ptr.hpp" - -#include -#include +#include "matrix/client.hpp" +#include "matrix/syncer.hpp" +#include "matrix/session.hpp" #define HAS_FREEIMAGE #define HAS_FFMPEG -namespace matrix{ - struct auth_data{ - raii::rjp_string name; - raii::rjp_string pass; - raii::rjp_string homeserver; - raii::rjp_string access_token; - - operator bool(void)const{ - return (name && pass && homeserver); - } - }; - - //upload info structs - struct file_info{ - raii::rjp_string fileurl; - raii::string filename; - raii::string mimetype; - size_t filesize; - }; - struct image_info : file_info{ - size_t width; - size_t height; - - raii::rjp_string thumburl; - size_t thumb_width; - size_t thumb_height; - size_t thumbsize; - }; - struct video_info : public image_info{}; - struct audio_info : public file_info{}; - - //message handling structs - //enumerate message type but also give a string representation - class msgtype{ - private: - const char* m_str; - const int m_num; - public: - constexpr msgtype(const char* s, int n): - m_str(s), m_num(n){} - constexpr msgtype(const msgtype&) = default; - ~msgtype(void) = default; - constexpr msgtype& operator=(const msgtype&) = default; - - constexpr bool operator==(const msgtype& m){ - return m_num == m.m_num; - } - constexpr bool operator!=(const msgtype& m){ - return m_num != m.m_num; - } - constexpr const char* str(void)const{ - return m_str; - } - constexpr operator int(void)const{ - return m_num; - } - }; - //class enumeration - struct msg{ - private: - enum _types{ - _text, _audio, _video, _file, _image, _other - }; - public: - constexpr static msgtype text = msgtype("text", _text); - constexpr static msgtype audio = msgtype("audio", _audio); - constexpr static msgtype video = msgtype("video", _video); - constexpr static msgtype file = msgtype("file", _file); - constexpr static msgtype image = msgtype("image", _image); - constexpr static msgtype other = msgtype("other", _other); - - constexpr static const msgtype& from_str(const char* str){ - if(!strcmp(str, "m.text")){ - return text; - }else if(!strcmp(str, "m.audio")){ - return audio; - }else if(!strcmp(str, "m.video")){ - return video; - }else if(!strcmp(str, "m.file")){ - return file; - }else if(!strcmp(str, "m.image")){ - return image; - }else{ - return other; - } - } - }; - - struct event_info - { - const raii::rjp_string roomid; - const raii::rjp_string sender; - const raii::rjp_string eventid; - const raii::rjp_string eventtype; - const int serverts; - const int age; - }; - struct msg_info : public event_info - { - const msgtype type = msg::other; - const raii::rjp_string body; - }; - struct membership_info : public event_info - { - const raii::rjp_string recipient; - }; - - //main class - class client - { - private: - class mat_url_list - { - private: - raii::string m_create_room; - raii::string m_file_upload; - raii::string m_room_list; - raii::string m_login; - raii::string m_alias_lookup; - raii::string m_whoami; - raii::string m_displayname; - raii::string m_profile_picture; - - public: - mat_url_list(void) = default; - mat_url_list(const raii::string_base& homeserver); - mat_url_list(const raii::string_base& homeserver, const raii::string_base& access_token, const raii::string_base& userid); - mat_url_list(const mat_url_list&) = default; - mat_url_list(mat_url_list&&) = default; - mat_url_list& operator=(const mat_url_list&) = default; - mat_url_list& operator=(mat_url_list&&) = default; - - const raii::string& create_room(void)const; - const raii::string& file_upload(void)const; - const raii::string& room_list(void)const; - const raii::string& login(void)const; - const raii::string& alias_lookup(void)const; - const raii::string& whoami(void)const; - const raii::string& displayname(void)const; - const raii::string& profile_picture(void)const; - raii::string logout(const raii::string_base& homeserver, const raii::string_base& access_token)const; - raii::string join_room(const raii::string_base& homeserver, const raii::string_base& access_token, const raii::string_base& roomid)const; - raii::string leave_room(const raii::string_base& homeserver, const raii::string_base& access_token, const raii::string_base& roomid)const; - raii::string sync(const raii::string_base& homeserver, const raii::string_base& access_token, const raii::string_base& next_batch, const raii::string_base& timeout)const; - raii::string read_receipt(const raii::string_base& homeserver, const raii::string_base& access_token, const raii::string_base& roomid, const raii::string_base& eventid)const; - raii::string send(const raii::string_base& homeserver, const raii::string_base& access_token, const raii::string_base& roomid)const; - raii::string redact(const raii::string_base& homeserver, const raii::string_base& access_token, const raii::string_base& roomid, const raii::string_base& eventid)const; - raii::string power_level(const raii::string_base& homeserver, const raii::string_base& access_token, const raii::string_base& roomid)const; - raii::string presence(const raii::string_base& homeserver, const raii::string_base& access_token, const raii::string_base& userid)const; - raii::string typing(const raii::string_base& homeserver, const raii::string_base& access_token, const raii::string_base& roomid, const raii::string_base& userid)const; - raii::string kick(const raii::string_base& homeserver, const raii::string_base& access_token, const raii::string_base& roomid)const; - raii::string ban(const raii::string_base& homeserver, const raii::string_base& access_token, const raii::string_base& roomid)const; - raii::string unban(const raii::string_base& homeserver, const raii::string_base& access_token, const raii::string_base& roomid)const; - raii::string invite(const raii::string_base& homeserver, const raii::string_base& access_token, const raii::string_base& roomid)const; - raii::string room_members(const raii::string_base& homeserver, const raii::string_base& access_token, const raii::string_base& roomid)const; - raii::string password(const raii::string_base& homeserver, const raii::string_base& access_token)const; - - - void repopulate_accesstoken(const raii::string_base& homeserver, const raii::string_base& access_token); - void repopulate_userid(const raii::string_base& homeserver, const raii::string_base& access_token, const raii::string_base& userid); - void repopulate(const raii::string_base& homeserver, const raii::string_base& access_token, const raii::string_base& userid); - void invalidate_accesstoken(void); - - private: - void _initial_populate(const raii::string_base& homeserver); - static constexpr const char* s_proto = "https://"; - static constexpr const char* s_revision = "r0"; - }; - - private: - mutable raii::curler m_curl; //https access - raii::string m_useragent; //useragent to identify our application - raii::string m_homeserver; //name of our homeserver - raii::rjp_string m_access_token; //authentication - raii::rjp_string m_userid; //userid including homeserver - mat_url_list m_urls; - - raii::rjp_string m_next_batch; //string which tracks where we are in the server history - - std::function m_message_callback; - std::function m_membership_callback; - std::function m_raw_callback; - - public: - client(const auth_data& a, const raii::string_base& useragent); - client(const auth_data& a, raii::string&& useragent); - client(const client& b) = default; - client(client&& b) = default; - ~client(void) = default; - - client& operator=(const client&) = default; - client& operator=(client&&) = default; - - //local getter - const raii::rjp_string& access_token(void)const; - const raii::rjp_string& userid(void)const; - const raii::string& useragent(void)const; - - //local setter - void set_useragent(const raii::string_base&); - void set_useragent(raii::string&&); - - //networked setter - void set_display_name(const raii::string_base&); - void set_profile_picture(const raii::string_base&); - - - //networked getter - raii::rjp_string get_display_name(void)const; - raii::rjp_string get_profile_picture(void)const; - raii::rjp_string room_alias_to_id(const raii::string_base& alias)const; - std::vector list_rooms(void)const; - - //other networked operations - raii::string create_room(const raii::string_base& name, const raii::string_base& alias)const; - bool join_room(const raii::string_base& roomid)const; - bool leave_room(const raii::string_base& roomid)const; - - bool accept_invite(const membership_info& invite)const; - bool reject_invite(const membership_info& invite)const; - - //upload media - file_info upload_file(const raii::string_base& filename)const; - file_info upload_file(const raii::string_base& filename, const raii::string_base& alias)const; - image_info upload_image(const raii::string_base& filename)const; - image_info upload_image(const raii::string_base& filename, const raii::string_base& alias)const; - video_info upload_video(const raii::string_base& filename)const; - video_info upload_video(const raii::string_base& filename, const raii::string_base& alias)const; - audio_info upload_audio(const raii::string_base& filename)const; - audio_info upload_audio(const raii::string_base& filename, const raii::string_base& alias)const; - - //send messages - raii::rjp_string send_image(const raii::string_base& room, const image_info& image)const; - raii::rjp_string send_video(const raii::string_base& room, const video_info& video)const; - raii::rjp_string send_message(const raii::string_base& room, const raii::string_base& text)const; - raii::rjp_string send_audio(const raii::string_base& room, const audio_info& audio)const; - raii::rjp_string send_file(const raii::string_base& room, const file_info& file)const; - void send_typing(const raii::string_base& room, bool active, int timeout = 5000)const; - - raii::rjp_string redact_event(const raii::string_base& roomid, const raii::string_base& eventid, const raii::string_base& reason)const; - raii::rjp_string redact_event(const raii::string_base& roomid, const raii::string_base& eventid)const; - - template - void set_message_callback(Func&& f){ - m_message_callback = std::forward(f); - } - template - void set_membership_callback(Func&& f){ - m_membership_callback = std::forward(f); - } - template - void set_raw_callback(Func&& f){ - m_raw_callback = std::forward(f); - } - raii::string sync(size_t timeout); - void logout(void); - - protected: - void _handle_membership_events(RJP_value* rooms); - void _handle_other_membership(RJP_value* join); - void _handle_invites(RJP_value* invites); - void _handle_messages(RJP_value* messages); - void _send_read_receipt(const raii::string_base& roomid, const raii::string_base& eventid)const; - raii::rjp_string _upload_file(raii::filerd& fp, const raii::curl_llist& header)const; - raii::rjp_string _send_message(const raii::string_base& room, const raii::string_base& msg)const; - static size_t _post_reply_curl_callback(char* ptr, size_t size, size_t nmemb, void* userdata); - raii::string _get_curl(const raii::string_base& url)const; - raii::string _post_curl(const raii::string_base& postdata, const raii::string_base& url, const raii::curl_llist& header)const; - raii::string _put_curl(const raii::string_base& postdata, const raii::string_base& url, const raii::curl_llist& header)const; - raii::rjp_string _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::rjp_string _get_and_find(const raii::string_base& url, const raii::string_base& search)const; - raii::rjp_string _curl_reply_search(const raii::string_base& reply, const raii::string_base& search)const; - void _set_curl_defaults(void)const; - raii::string _request_access_token(const auth_data& a)const; - void _get_new_access_token(const auth_data& a); - void _acquire_access_token(const auth_data& a); - }; - - - auth_data parse_auth_data(RJP_value* root); -} - #endif diff --git a/include/matrix/auth.hpp b/include/matrix/auth.hpp new file mode 100644 index 0000000..b9d5539 --- /dev/null +++ b/include/matrix/auth.hpp @@ -0,0 +1,40 @@ +/** + This file is a part of rexy's matrix bot + 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 . +*/ + +#ifndef MATRIX_AUTH_HPP +#define MATRIX_AUTH_HPP + +#include "raii/rjp_string.hpp" + +namespace matrix{ + + struct auth_data{ + raii::string name; + raii::string pass; + raii::string homeserver; + raii::string useragent; + raii::string access_token; + + operator bool(void)const{ + return (name && pass && homeserver); + } + }; + +} + +#endif diff --git a/include/matrix/client.hpp b/include/matrix/client.hpp new file mode 100644 index 0000000..583cb4f --- /dev/null +++ b/include/matrix/client.hpp @@ -0,0 +1,107 @@ +/** + This file is a part of rexy's matrix bot + 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 . +*/ + +#ifndef MATRIX_CLIENT_HPP +#define MATRIX_CLIENT_HPP + +#include "raii/curler.hpp" +#include "raii/string.hpp" +#include "raii/rjp_string.hpp" +#include "raii/filerd.hpp" +#include "matrix/session_info.hpp" +#include "matrix/upload_info.hpp" +#include "matrix/event_info.hpp" +#include "matrix/client_base.hpp" +#include //vector +#include //shared_ptr +#include //size_t + +namespace matrix{ + class session; + //main class + class client : public internal::client_base + { + friend class ::matrix::session; + private: + const std::shared_ptr m_ses; + + public: + client(std::shared_ptr&); + client(const client& b) = default; + client(client&& b) = default; + ~client(void) = default; + + client& operator=(const client&) = default; + client& operator=(client&&) = default; + + //local getter + const raii::rjp_string& access_token(void)const; + const raii::rjp_string& userid(void)const; + const raii::string& useragent(void)const; + + //networked setter + void set_display_name(const raii::string_base&); + void set_profile_picture(const raii::string_base&); + + + //networked getter + raii::rjp_string get_display_name(void)const; + raii::rjp_string get_profile_picture(void)const; + raii::rjp_string room_alias_to_id(const raii::string_base& alias)const; + std::vector list_rooms(void)const; + + //room membership + raii::string create_room(const raii::string_base& name, const raii::string_base& alias)const; + bool join_room(const raii::string_base& roomid)const; + bool leave_room(const raii::string_base& roomid)const; + + bool accept_invite(const membership_info& invite)const; + bool reject_invite(const membership_info& invite)const; + + //other network + void logout(void); + + //upload media + file_info upload_file(const raii::string_base& filename)const; + file_info upload_file(const raii::string_base& filename, const raii::string_base& alias)const; + image_info upload_image(const raii::string_base& filename)const; + image_info upload_image(const raii::string_base& filename, const raii::string_base& alias)const; + video_info upload_video(const raii::string_base& filename)const; + video_info upload_video(const raii::string_base& filename, const raii::string_base& alias)const; + audio_info upload_audio(const raii::string_base& filename)const; + audio_info upload_audio(const raii::string_base& filename, const raii::string_base& alias)const; + + //send messages + raii::rjp_string send_image(const raii::string_base& room, const image_info& image)const; + raii::rjp_string send_video(const raii::string_base& room, const video_info& video)const; + raii::rjp_string send_message(const raii::string_base& room, const raii::string_base& text)const; + raii::rjp_string send_audio(const raii::string_base& room, const audio_info& audio)const; + raii::rjp_string send_file(const raii::string_base& room, const file_info& file)const; + void send_typing(const raii::string_base& room, bool active, int timeout = 5000)const; + + raii::rjp_string redact_event(const raii::string_base& roomid, const raii::string_base& eventid, const raii::string_base& reason)const; + raii::rjp_string redact_event(const raii::string_base& roomid, const raii::string_base& eventid)const; + + private: + void _send_read_receipt(const raii::string_base& roomid, const raii::string_base& eventid)const; + raii::rjp_string _upload_file(raii::filerd& fp, const raii::curl_llist& header)const; + raii::rjp_string _send_message(const raii::string_base& room, const raii::string_base& msg)const; + }; +} + +#endif diff --git a/include/matrix/client_base.hpp b/include/matrix/client_base.hpp new file mode 100644 index 0000000..63756d5 --- /dev/null +++ b/include/matrix/client_base.hpp @@ -0,0 +1,55 @@ +/** + This file is a part of rexy's matrix bot + 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 . +*/ + +#ifndef MATRIX_CLIENT_BASE_HPP +#define MATRIX_CLIENT_BASE_HPP + +#include "raii/curler.hpp" +#include "raii/string.hpp" +#include "raii/rjp_string.hpp" + +namespace matrix::internal{ + + class client_base + { + protected: + mutable raii::curler m_curl; + protected: + client_base(void); + client_base(const client_base&) = default; + client_base(client_base&&) = default; + ~client_base(void) = default; + client_base& operator=(const client_base&) = default; + client_base& operator=(client_base&&) = default; + + void _set_curl_useragent(const raii::string_base& useragent); + + protected: + static size_t _post_reply_curl_callback(char* ptr, size_t size, size_t nmemb, void* userdata); + raii::string _get_curl(const raii::string_base& url)const; + raii::string _post_curl(const raii::string_base& postdata, const raii::string_base& url, const raii::curl_llist& header)const; + raii::string _put_curl(const raii::string_base& postdata, const raii::string_base& url, const raii::curl_llist& header)const; + raii::rjp_string _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::rjp_string _get_and_find(const raii::string_base& url, const raii::string_base& search)const; + raii::rjp_string _curl_reply_search(const raii::string_base& reply, const raii::string_base& search)const; + void _set_curl_defaults(const raii::string_base& useragent)const; + }; + +} + +#endif diff --git a/include/matrix/event_info.hpp b/include/matrix/event_info.hpp new file mode 100644 index 0000000..4863ad7 --- /dev/null +++ b/include/matrix/event_info.hpp @@ -0,0 +1,103 @@ +/** + This file is a part of rexy's matrix bot + 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 . +*/ + +#ifndef MATRIX_EVENT_INFO_HPP +#define MATRIX_EVENT_INFO_HPP + +#include "raii/rjp_string.hpp" +#include //strcmp + +namespace matrix{ + //message handling structs + //enumerate message type but also give a string representation + class msgtype{ + private: + const char* m_str; + const int m_num; + public: + constexpr msgtype(const char* s, int n): + m_str(s), m_num(n){} + constexpr msgtype(const msgtype&) = default; + ~msgtype(void) = default; + constexpr msgtype& operator=(const msgtype&) = default; + + constexpr bool operator==(const msgtype& m){ + return m_num == m.m_num; + } + constexpr bool operator!=(const msgtype& m){ + return m_num != m.m_num; + } + constexpr const char* str(void)const{ + return m_str; + } + constexpr operator int(void)const{ + return m_num; + } + }; + //class enumeration + struct msg{ + private: + enum _types{ + _text, _audio, _video, _file, _image, _other + }; + public: + constexpr static msgtype text = msgtype("text", _text); + constexpr static msgtype audio = msgtype("audio", _audio); + constexpr static msgtype video = msgtype("video", _video); + constexpr static msgtype file = msgtype("file", _file); + constexpr static msgtype image = msgtype("image", _image); + constexpr static msgtype other = msgtype("other", _other); + + constexpr static const msgtype& from_str(const char* str){ + if(!strcmp(str, "m.text")){ + return text; + }else if(!strcmp(str, "m.audio")){ + return audio; + }else if(!strcmp(str, "m.video")){ + return video; + }else if(!strcmp(str, "m.file")){ + return file; + }else if(!strcmp(str, "m.image")){ + return image; + }else{ + return other; + } + } + }; + + struct event_info + { + const raii::rjp_string roomid; + const raii::rjp_string sender; + const raii::rjp_string eventid; + const raii::rjp_string eventtype; + const int serverts; + const int age; + }; + struct msg_info : public event_info + { + const msgtype type = msg::other; + const raii::rjp_string body; + }; + struct membership_info : public event_info + { + const raii::rjp_string recipient; + }; +} + +#endif diff --git a/include/fat_strings.hpp b/include/matrix/fat_strings.hpp similarity index 100% rename from include/fat_strings.hpp rename to include/matrix/fat_strings.hpp diff --git a/include/matrix/image_thumbnail_maker.hpp b/include/matrix/image_thumbnail_maker.hpp new file mode 100644 index 0000000..c23f46b --- /dev/null +++ b/include/matrix/image_thumbnail_maker.hpp @@ -0,0 +1,73 @@ +/** + 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 . +*/ + +#ifndef MATRIX_IMAGE_THUMBNAIL_MAKER_HPP +#define MATRIX_IMAGE_THUMBNAIL_MAKER_HPP + +#include +#include +#include //size_t +#include "matrix/upload_info.hpp" + +#define THUMBSIZE 500 + +namespace matrix::internal{ + + 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 {}; + } + +} + +#endif diff --git a/include/matrix/session.hpp b/include/matrix/session.hpp new file mode 100644 index 0000000..d25966c --- /dev/null +++ b/include/matrix/session.hpp @@ -0,0 +1,57 @@ +/** + This file is a part of rexy's matrix bot + 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 . +*/ + +#ifndef MATRIX_SESSION_HPP +#define MATRIX_SESSION_HPP + +#include "matrix/client.hpp" +#include "matrix/syncer.hpp" +#include "matrix/session_info.hpp" +#include "matrix/auth.hpp" +#include "raii/string.hpp" +#include //shared_ptr +#include //pair + +namespace matrix{ + class session + { + private: + std::shared_ptr m_ses; + client m_client; + syncer m_sync; + public: + session(const auth_data&); + //local setter + void set_useragent(const raii::string_base&); + void set_useragent(raii::string&&); + + client& get_client(void); + const client& get_client(void)const; + syncer& get_syncer(void); + const syncer& get_syncer(void)const; + + void invalidate(void); + private: + void _populate_session_info(const auth_data& a); + raii::rjp_string _get_userid(void); + raii::string _request_access_token(const raii::string_base& name, const raii::string_base& pass, const raii::string_base& loginurl)const; + std::pair _get_new_access_token(const raii::string_base& name, const raii::string_base& pass, const raii::string_base& loginurl); + }; +} + +#endif diff --git a/include/matrix/session_info.hpp b/include/matrix/session_info.hpp new file mode 100644 index 0000000..51837f5 --- /dev/null +++ b/include/matrix/session_info.hpp @@ -0,0 +1,36 @@ +/** + This file is a part of rexy's matrix bot + 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 . +*/ + +#ifndef MATRIX_SESSION_INFO_HPP +#define MATRIX_SESSION_INFO_HPP + +#include "raii/string.hpp" +#include "raii/rjp_string.hpp" +#include "matrix/url_list.hpp" + +namespace matrix::internal{ + struct session_info{ + raii::string useragent; //useragent to identify our application + raii::string homeserver; //name of our homeserver + raii::rjp_string access_token; //authentication + raii::rjp_string userid; //userid including homeserver + mat_url_list urls; + }; +} + +#endif diff --git a/include/matrix/syncer.hpp b/include/matrix/syncer.hpp new file mode 100644 index 0000000..0e0659b --- /dev/null +++ b/include/matrix/syncer.hpp @@ -0,0 +1,78 @@ +/** + This file is a part of rexy's matrix bot + 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 . +*/ + +#ifndef MATRIX_SYNCER_HPP +#define MATRIX_SYNCER_HPP + +#include "raii/curler.hpp" +#include "raii/rjp_ptr.hpp" +#include "raii/rjp_string.hpp" +#include "raii/string.hpp" +#include "matrix/event_info.hpp" +#include "matrix/session_info.hpp" +#include "matrix/client_base.hpp" +#include //size_t +#include //forward +#include //function +#include //shared_ptr + +namespace matrix{ + class session; + class syncer : public internal::client_base + { + friend class ::matrix::session; + private: + const std::shared_ptr m_ses; + + std::function m_message_callback; + std::function m_membership_callback; + std::function m_raw_callback; + raii::rjp_string m_next_batch; //string which tracks where we are in the server history + + public: + syncer(std::shared_ptr&); + syncer(const syncer& b) = default; + syncer(syncer&& b) = default; + ~syncer(void) = default; + + syncer& operator=(const syncer&) = default; + syncer& operator=(syncer&&) = default; + + template + void set_message_callback(Func&& f){ + m_message_callback = std::forward(f); + } + template + void set_membership_callback(Func&& f){ + m_membership_callback = std::forward(f); + } + template + void set_raw_callback(Func&& f){ + m_raw_callback = std::forward(f); + } + raii::string sync(size_t timeout); + + private: + void _handle_membership_events(RJP_value* rooms); + void _handle_other_membership(RJP_value* join); + void _handle_invites(RJP_value* invites); + void _handle_messages(RJP_value* messages); + }; +} + +#endif diff --git a/include/matrix/upload_info.hpp b/include/matrix/upload_info.hpp new file mode 100644 index 0000000..063df59 --- /dev/null +++ b/include/matrix/upload_info.hpp @@ -0,0 +1,54 @@ +/** + This file is a part of rexy's matrix bot + 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 . +*/ + +#ifndef MATRIX_UPLOAD_INFO_HPP +#define MATRIX_UPLOAD_INFO_HPP + +#include "raii/rjp_string.hpp" +#include "raii/string.hpp" +#include //size_t + +namespace matrix{ + + struct file_info{ + raii::rjp_string fileurl; + raii::string filename; + raii::string mimetype; + size_t filesize; + }; + struct image_info : public file_info{ + using file_info::operator=; + size_t width; + size_t height; + + raii::rjp_string thumburl; + size_t thumb_width; + size_t thumb_height; + size_t thumbsize; + }; + struct video_info : public image_info{ + using image_info::operator=; + }; + struct audio_info : public file_info{ + using file_info::operator=; + + }; + +} + +#endif diff --git a/include/matrix/url_list.hpp b/include/matrix/url_list.hpp new file mode 100644 index 0000000..003054e --- /dev/null +++ b/include/matrix/url_list.hpp @@ -0,0 +1,86 @@ +/** + This file is a part of rexy's matrix bot + 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 . +*/ + +#ifndef MATRIX_URL_LIST_HPP +#define MATRIX_URL_LIST_HPP + +#include "raii/string.hpp" + +namespace matrix{ + class mat_url_list + { + private: + raii::string m_create_room; + raii::string m_file_upload; + raii::string m_room_list; + raii::string m_alias_lookup; + raii::string m_whoami; + raii::string m_displayname; + raii::string m_profile_picture; + + public: + mat_url_list(void) = default; + mat_url_list(const raii::string_base& homeserver); + mat_url_list(const raii::string_base& homeserver, const raii::string_base& access_token, const raii::string_base& userid); + mat_url_list(const mat_url_list&) = default; + mat_url_list(mat_url_list&&) = default; + mat_url_list& operator=(const mat_url_list&) = default; + mat_url_list& operator=(mat_url_list&&) = default; + + static raii::string stat_whoami(const raii::string_base& homeserver, const raii::string_base& access_token); + static raii::string login(const raii::string_base& homeserver); + + const raii::string& create_room(void)const; + const raii::string& file_upload(void)const; + const raii::string& room_list(void)const; + const raii::string& alias_lookup(void)const; + const raii::string& whoami(void)const; + const raii::string& displayname(void)const; + const raii::string& profile_picture(void)const; + raii::string logout(const raii::string_base& homeserver, const raii::string_base& access_token)const; + raii::string join_room(const raii::string_base& homeserver, const raii::string_base& access_token, const raii::string_base& roomid)const; + raii::string leave_room(const raii::string_base& homeserver, const raii::string_base& access_token, const raii::string_base& roomid)const; + raii::string sync(const raii::string_base& homeserver, const raii::string_base& access_token, const raii::string_base& next_batch, const raii::string_base& timeout)const; + raii::string read_receipt(const raii::string_base& homeserver, const raii::string_base& access_token, const raii::string_base& roomid, const raii::string_base& eventid)const; + raii::string send(const raii::string_base& homeserver, const raii::string_base& access_token, const raii::string_base& roomid)const; + raii::string redact(const raii::string_base& homeserver, const raii::string_base& access_token, const raii::string_base& roomid, const raii::string_base& eventid)const; + raii::string power_level(const raii::string_base& homeserver, const raii::string_base& access_token, const raii::string_base& roomid)const; + raii::string presence(const raii::string_base& homeserver, const raii::string_base& access_token, const raii::string_base& userid)const; + raii::string typing(const raii::string_base& homeserver, const raii::string_base& access_token, const raii::string_base& roomid, const raii::string_base& userid)const; + raii::string kick(const raii::string_base& homeserver, const raii::string_base& access_token, const raii::string_base& roomid)const; + raii::string ban(const raii::string_base& homeserver, const raii::string_base& access_token, const raii::string_base& roomid)const; + raii::string unban(const raii::string_base& homeserver, const raii::string_base& access_token, const raii::string_base& roomid)const; + raii::string invite(const raii::string_base& homeserver, const raii::string_base& access_token, const raii::string_base& roomid)const; + raii::string room_members(const raii::string_base& homeserver, const raii::string_base& access_token, const raii::string_base& roomid)const; + raii::string password(const raii::string_base& homeserver, const raii::string_base& access_token)const; + + + void repopulate_accesstoken(const raii::string_base& homeserver, const raii::string_base& access_token); + void repopulate_userid(const raii::string_base& homeserver, const raii::string_base& access_token, const raii::string_base& userid); + void repopulate(const raii::string_base& homeserver, const raii::string_base& access_token, const raii::string_base& userid); + void invalidate_accesstoken(void); + + private: + void _initial_populate(const raii::string_base& homeserver); + static constexpr const char* s_proto = "https://"; + static constexpr const char* s_revision = "r0"; + }; + +} + +#endif diff --git a/include/matrix/video_thumbnail_maker.hpp b/include/matrix/video_thumbnail_maker.hpp new file mode 100644 index 0000000..f8bc366 --- /dev/null +++ b/include/matrix/video_thumbnail_maker.hpp @@ -0,0 +1,117 @@ +/** + 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 "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 //size_t + +namespace matrix::internal{ + 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)); + } +} + +#endif diff --git a/include/raii/string_base.hpp b/include/raii/string_base.hpp index 521279f..0c81f3d 100644 --- a/include/raii/string_base.hpp +++ b/include/raii/string_base.hpp @@ -117,6 +117,7 @@ namespace raii{ const char& operator[](size_t i)const; protected: + string_base& _copy_string(const char* s, size_t len); template static void _assign(char* dest, Tup&& t, size_t offset){ memcpy(dest+offset, std::get(t), std::get(t)); diff --git a/makefile b/makefile index 7b6aa23..65b182d 100644 --- a/makefile +++ b/makefile @@ -14,7 +14,7 @@ #Copyright 2018-2019 rexy712 -SOURCE_DIRS:=src src/raii +SOURCE_DIRS:=src src/raii src/matrix OBJDIR:=obj DEPDIR:=$(OBJDIR)/dep INCLUDE_DIRS:=include diff --git a/src/matrix.cpp b/src/matrix.cpp deleted file mode 100644 index 8ce4ef3..0000000 --- a/src/matrix.cpp +++ /dev/null @@ -1,882 +0,0 @@ -/** - 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"; - } - -} - diff --git a/src/matrix/client.cpp b/src/matrix/client.cpp new file mode 100644 index 0000000..d28be78 --- /dev/null +++ b/src/matrix/client.cpp @@ -0,0 +1,335 @@ +/** + 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/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(std::shared_ptr& ses): + client_base(), + m_ses(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 membership_info& invite)const{ + return join_room(invite.roomid); + } + bool client::reject_invite(const membership_info& invite)const{ + return leave_room(invite.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{ + 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; + } +} diff --git a/src/matrix/client_base.cpp b/src/matrix/client_base.cpp new file mode 100644 index 0000000..abaf6c9 --- /dev/null +++ b/src/matrix/client_base.cpp @@ -0,0 +1,133 @@ +/** + This file is a part of rexy's matrix bot + 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_base.hpp" +#include "raii/rjp_ptr.hpp" +#include "raii/static_string.hpp" +#include //size_t +#include //min, max + +namespace matrix::internal{ + + client_base::client_base(void): + m_curl() + { + _set_curl_defaults(""_ss); + } + void client_base::_set_curl_useragent(const raii::string_base& useragent){ + m_curl.setuseragent(useragent); + } + size_t client_base::_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_base::_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_base::_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_base::_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_base::_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_base::_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_base::_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_base::_set_curl_defaults(const raii::string_base& useragent)const{ + m_curl.setopt(CURLOPT_BUFFERSIZE, 102400L); + m_curl.setopt(CURLOPT_NOPROGRESS, 1L); + m_curl.setuseragent(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); + } + +} diff --git a/src/fat_strings.cpp b/src/matrix/fat_strings.cpp similarity index 98% rename from src/fat_strings.cpp rename to src/matrix/fat_strings.cpp index 4fcacb7..4a6990f 100644 --- a/src/fat_strings.cpp +++ b/src/matrix/fat_strings.cpp @@ -16,8 +16,8 @@ along with this program. If not, see . */ -#include "fat_strings.hpp" -#include "matrix.hpp" +#include "matrix/fat_strings.hpp" +#include "matrix/upload_info.hpp" #include "raii/static_string.hpp" #include "raii/util.hpp" diff --git a/src/matrix/session.cpp b/src/matrix/session.cpp new file mode 100644 index 0000000..ba5c657 --- /dev/null +++ b/src/matrix/session.cpp @@ -0,0 +1,127 @@ +/** + 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/session.hpp" +#include "raii/rjp_ptr.hpp" +#include "raii/curler.hpp" +#include "raii/util.hpp" + +namespace matrix{ + + session::session(const auth_data& auth): + m_ses(new internal::session_info), + m_client(m_ses), + m_sync(m_ses) + { + _populate_session_info(auth); + m_client._set_curl_useragent(m_ses->useragent); + m_sync._set_curl_useragent(m_ses->useragent); + } + + void session::set_useragent(const raii::string_base& agent){ + m_ses->useragent = agent; + } + void session::set_useragent(raii::string&& agent){ + m_ses->useragent = std::move(agent); + } + + client& session::get_client(void){ + return m_client; + } + const client& session::get_client(void)const{ + return m_client; + } + syncer& session::get_syncer(void){ + return m_sync; + } + const syncer& session::get_syncer(void)const{ + return m_sync; + } + + void session::invalidate(void){ + m_ses->useragent.reset(); + m_ses->homeserver.reset(); + m_ses->access_token.reset(); + m_ses->userid.reset(); + m_ses->urls.invalidate_accesstoken(); + } + + void session::_populate_session_info(const auth_data& a){ + m_ses->useragent = a.useragent; + m_ses->homeserver = a.homeserver; + m_ses->access_token = a.access_token; + auto reply = m_client._get_curl(mat_url_list::stat_whoami(m_ses->homeserver, m_ses->access_token)); + if(!reply){ + auto [token, id] = _get_new_access_token(a.name, a.pass, mat_url_list::login(m_ses->homeserver)); + if(token && id){ + m_ses->access_token = std::move(token); + m_ses->userid = std::move(id); + m_ses->urls.repopulate(m_ses->homeserver, m_ses->access_token, m_ses->userid); + }else{ + invalidate(); + } + }else{ + m_ses->urls.repopulate_accesstoken(m_ses->homeserver, m_ses->access_token); + m_ses->userid = _get_userid(); + m_ses->urls.repopulate_userid(m_ses->homeserver, m_ses->access_token, m_ses->userid); + } + } + raii::string session::_request_access_token(const raii::string_base& name, const raii::string_base& pass, const raii::string_base& loginurl)const{ + CURLcode result; + raii::string postdata("{\"type\":\"m.login.password\", \"user\":\"" + raii::json_escape(name) + "\", \"password\":\"" + raii::json_escape(pass) + "\"}"); + raii::string reply; + raii::curler& cur = m_client.m_curl; + + cur.seturl(loginurl); + cur.setpostdata(postdata); + cur.postreq(); + cur.setopt(CURLOPT_WRITEFUNCTION, client::_post_reply_curl_callback); + cur.setopt(CURLOPT_WRITEDATA, &reply); + + result = cur.perform(); + + if(result != CURLE_OK) + return {}; + + return reply; + } + raii::rjp_string session::_get_userid(void){ + auto reply = m_client._get_curl(mat_url_list::stat_whoami(m_ses->homeserver, m_ses->access_token)); + if(!reply) + return {}; + raii::rjp_ptr root(rjp_parse(reply)); + if(!root) + return {}; + RJP_search_res id = rjp_search_member(root.get(), "user_id", 0); + return raii::rjp_string{id.value}; + } + std::pair session::_get_new_access_token(const raii::string_base& name, const raii::string_base& pass, const raii::string_base& loginurl){ + raii::string reply = _request_access_token(name, pass, loginurl); + if(!reply) + return {}; + raii::rjp_ptr root(rjp_parse(reply)); + if(!root) + return {}; + std::pair retval; + RJP_search_res token = rjp_search_member(root.get(), "access_token", 0); + retval.first = raii::rjp_string{token.value}; + token = rjp_search_member(root.get(), "user_id", 0); + retval.second = raii::rjp_string{token.value}; + return retval; + } +} diff --git a/src/matrix/syncer.cpp b/src/matrix/syncer.cpp new file mode 100644 index 0000000..91bbd5e --- /dev/null +++ b/src/matrix/syncer.cpp @@ -0,0 +1,156 @@ +/** + 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/syncer.hpp" +#include "raii/static_string.hpp" +#include "raii/rjp_ptr.hpp" +#include "raii/util.hpp" + +namespace matrix{ + + syncer::syncer(std::shared_ptr& ses): + client_base(), + m_ses(ses){} + + raii::string syncer::sync(size_t timeout){ + raii::string reply = _get_curl(m_ses->urls.sync(m_ses->homeserver, m_ses->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(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; + } + + + 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 syncer::_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); + } + void syncer::_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(minfo); + } + } + } + void syncer::_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(minfo); + } + } + } + void syncer::_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(minfo); + } + } + } +} diff --git a/src/matrix/url_list.cpp b/src/matrix/url_list.cpp new file mode 100644 index 0000000..7b45d19 --- /dev/null +++ b/src/matrix/url_list.cpp @@ -0,0 +1,134 @@ +/** + 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/url_list.hpp" +#include "raii/string.hpp" + +namespace matrix{ + + mat_url_list::mat_url_list(const raii::string_base& homeserver){ + _initial_populate(homeserver); + } + 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 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 = stat_whoami(homeserver, access_token); + } + void 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 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 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& mat_url_list::create_room(void)const{ + return m_create_room; + } + const raii::string& mat_url_list::file_upload(void)const{ + return m_file_upload; + } + const raii::string& mat_url_list::room_list(void)const{ + return m_room_list; + } + raii::string mat_url_list::login(const raii::string_base& homeserver){ + return raii::string(s_proto + homeserver + "/_matrix/client/r0/login"); + } + raii::string mat_url_list::stat_whoami(const raii::string_base& homeserver, const raii::string_base& access_token){ + return raii::string(s_proto + homeserver + "/_matrix/client/r0/account/whoami?access_token=" + access_token); + } + const raii::string& mat_url_list::alias_lookup(void)const{ + return m_alias_lookup; + } + const raii::string& mat_url_list::whoami(void)const{ + return m_whoami; + } + const raii::string& mat_url_list::displayname(void)const{ + return m_displayname; + } + const raii::string& mat_url_list::profile_picture(void)const{ + return m_profile_picture; + } + raii::string 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 mat_url_list::_initial_populate(const raii::string_base& homeserver){ + m_alias_lookup = s_proto + homeserver + "/_matrix/client/r0/directory/room/"; + } + +} diff --git a/src/raii/string_base.cpp b/src/raii/string_base.cpp index 90e4483..153fc5c 100644 --- a/src/raii/string_base.cpp +++ b/src/raii/string_base.cpp @@ -28,37 +28,15 @@ namespace raii{ m_length(len), m_data(nullptr){} string_base& string_base::operator=(const char* c){ - size_t len = strlen(c); - if(len <= m_length){ - strcpy(m_data, c); - }else{ - _free(m_data); - m_data = _copy(c, len+1); - if(!m_data){ - m_length = 0; - return *this; - } - } - m_length = len; - return *this; + return _copy_string(c, strlen(c)); } string_base& string_base::operator=(const string_base& s){ - if(s.m_length <= m_length){ - strcpy(m_data, s.m_data); - }else{ - _free(m_data); - m_data = _copy(s.m_data, s.m_length+1); - if(!m_data){ - m_length = 0; - return *this; - } - } - m_length = s.m_length; - return *this; + return _copy_string(s.m_data, s.m_length); } void string_base::reset(char* val){ _free(m_data); m_data = val; + m_length = val ? strlen(val) : 0; } size_t string_base::length(void)const{ @@ -88,5 +66,24 @@ namespace raii{ const char& string_base::operator[](size_t i)const{ return m_data[i]; } + string_base& string_base::_copy_string(const char* s, size_t len){ + if(!len){ + _free(m_data); + m_length = 0; + return *this; + } + if(len <= m_length){ + strcpy(m_data, s); + }else{ + _free(m_data); + m_data = _copy(s, len+1); + if(!m_data){ + m_length = 0; + return *this; + } + } + m_length = len; + return *this; + } } diff --git a/src/test.cpp b/src/test.cpp index 77e1bed..a835dca 100644 --- a/src/test.cpp +++ b/src/test.cpp @@ -26,23 +26,21 @@ #include #include -void sync_fn(matrix::client client, std::atomic_bool& should_quit){ - auto sync_reply = client.sync(0); - client.set_message_callback([&](const matrix::client& client, const matrix::msg_info& msg)->void - { - if(msg.body == "!exit"_ss){ +void sync_fn(matrix::syncer& syn, std::atomic_bool& should_quit){ + auto sync_reply = syn.sync(0); + + syn.set_message_callback([&](const matrix::msg_info& msg)->void{ + printf("%s\n", msg.body.get()); + if(!strcmp(msg.body.get(), "!exit")){ should_quit = true; - client.send_message(msg.roomid, "Shutting down..."_ss); - }else if(msg.body == "!info"_ss){ - client.send_message(msg.roomid, "This is an example of a client which responds to commands!"_ss); } }); while(!should_quit){ - sync_reply = client.sync(30000); + sync_reply = syn.sync(30000); } } -void keyboard_fn(matrix::client client, std::atomic_bool& should_quit){ +void keyboard_fn(matrix::client& client, std::atomic_bool& should_quit){ char buffer[2048]; while(!should_quit){ fgets(buffer, 2048, stdin); @@ -59,13 +57,13 @@ int main(){ const char* password = "password"; const char* useragent = "rexy712s test bot"; const char* homeserver = "matrix.org"; - matrix::auth_data auth{username, password, homeserver}; + matrix::auth_data auth{username, password, homeserver, useragent}; - matrix::client matclient(auth, useragent); + matrix::session ses(auth); std::atomic_bool should_quit = false; - std::thread sync_thread(sync_fn, matclient, std::ref(should_quit)); - std::thread key_thread(keyboard_fn, std::move(matclient), std::ref(should_quit)); + std::thread sync_thread(sync_fn, std::ref(ses.get_syncer()), std::ref(should_quit)); + std::thread key_thread(keyboard_fn, std::ref(ses.get_client()), std::ref(should_quit)); //one of these threads will always hang until another input is recieved sync_thread.join();