diff --git a/TODO b/TODO index c6b6e0e..c745dc3 100644 --- a/TODO +++ b/TODO @@ -8,10 +8,11 @@ general/other: matrix: thread pool for workers. rehurn std::promise? handle user admin levels - ignore own messages + ignore own messages/invites handle events other than user messages organize matrix urls. Prevent reallocation of reusable urls. generate nonreusable urls for matrix in an organized manner. + ignore old messages based on value of 'age' parameter? reddit: handle imgur albums diff --git a/examples/commands.cpp b/examples/commands.cpp new file mode 100644 index 0000000..c150239 --- /dev/null +++ b/examples/commands.cpp @@ -0,0 +1,49 @@ +/** + 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 . +*/ + +//example of a bot which responds to commands + +#include "matrix.hpp" +#include "raii/static_string.hpp" + +int main(){ + const char* username = "username"; + const char* password = "password"; + const char* useragent = "rexy712s info bot"; + const char* homeserver = "matrix.org"; + matrix::auth_data auth{username, password, homeserver}; + + matrix::bot matbot(auth, useragent); + auto sync_reply = matbot.sync(0); //initial sync + + bool should_quit = false; + matbot.set_message_callback([&](const matrix::bot& bot, const matrix::msg_info& msg)->void + { + if(msg.body == "!exit"_ss){ + should_quit = true; + bot.send_message(msg.roomid, "[INFO] Shutting down..."_ss); + }else if(msg.body == "!info"_ss){ + bot.send_message(msg.roomid, "This is an example of a bot which responds to commands!"_ss); + } + }); + + while(!should_quit){ + sync_reply = matbot.sync(30000); + } + +} diff --git a/examples/join.cpp b/examples/join.cpp new file mode 100644 index 0000000..e6109b3 --- /dev/null +++ b/examples/join.cpp @@ -0,0 +1,41 @@ +/** + 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 . +*/ + +//Example of a bot which accepts any invite that it is given + +#include "matrix.hpp" + +int main(){ + const char* username = "username"; + const char* password = "password"; + const char* useragent = "rexy712s invite accepter"; + const char* homeserver = "matrix.org"; + matrix::auth_data auth{username, password, homeserver}; + + + matrix::bot matbot(auth, useragent); + auto sync_reply = matbot.sync(0); //initial sync + + matbot.set_invite_callback([&](const matrix::bot& bot, const matrix::invite_info& invite)->void{ + bot.join_room(invite.roomid); + }); + + while(1){ + sync_reply = matbot.sync(30000); + } +} diff --git a/include/matrix.hpp b/include/matrix.hpp index 3b01349..740ebae 100644 --- a/include/matrix.hpp +++ b/include/matrix.hpp @@ -118,11 +118,17 @@ namespace matrix{ struct msg_info { - const raii::rjp_string roomid; - const raii::rjp_string sender; + const raii::rjp_string& roomid; + const raii::rjp_string& sender; const msgtype type; - const raii::rjp_string body; - const raii::rjp_string eventid; + const raii::rjp_string& body; + const raii::rjp_string& eventid; + }; + struct invite_info + { + const raii::rjp_string& roomid; + const raii::rjp_string& sender; + const raii::rjp_string& eventid; }; class bot @@ -130,6 +136,7 @@ namespace matrix{ private: struct mat_url_list{ 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); mat_url_list(const mat_url_list&) = default; mat_url_list(mat_url_list&&) = default; @@ -148,6 +155,7 @@ namespace matrix{ raii::string whoami; private: + void _initial_populate(const raii::string_base& homeserver); static constexpr const char* s_proto = "https://"; }; private: @@ -161,6 +169,7 @@ namespace matrix{ raii::rjp_string m_next_batch; //string which tracks where we are in the server history std::function m_message_callback; + std::function m_invite_callback; public: bot(const auth_data& a, const raii::string_base& useragent); @@ -192,6 +201,7 @@ namespace matrix{ //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; //upload media file_info upload_file(const raii::string_base& filename)const; @@ -211,13 +221,19 @@ namespace matrix{ raii::rjp_string send_file(const raii::string_base& room, const file_info& file)const; template - void set_sync_callback(Func&& f){ + void set_message_callback(Func&& f){ m_message_callback = std::forward(f); } + template + void set_invite_callback(Func&& f){ + m_invite_callback = std::forward(f); + } raii::string sync(size_t timeout); void logout(void); protected: + 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; diff --git a/src/matrix.cpp b/src/matrix.cpp index 5e18d65..9f68ebc 100644 --- a/src/matrix.cpp +++ b/src/matrix.cpp @@ -52,6 +52,9 @@ namespace matrix{ details[3].value}; } + bot::mat_url_list::mat_url_list(const raii::string_base& homeserver){ + _initial_populate(homeserver); + } bot::mat_url_list::mat_url_list(const raii::string_base& homeserver, const raii::string_base& access_token){ repopulate(homeserver, access_token); } @@ -63,8 +66,7 @@ namespace matrix{ } void bot::mat_url_list::repopulate(const raii::string_base& homeserver, const raii::string_base& access_token){ repopulate_accesstoken(homeserver, access_token); - alias_lookup = s_proto + homeserver + "/_matrix/client/r0/directory/room/"; - login = s_proto + homeserver + "/_matrix/client/r0/login"; + _initial_populate(homeserver); } void bot::mat_url_list::invalidate_accesstoken(void){ create_room.reset(); @@ -72,6 +74,10 @@ namespace matrix{ room_list.reset(); whoami.reset(); } + void bot::mat_url_list::_initial_populate(const raii::string_base& homeserver){ + alias_lookup = s_proto + homeserver + "/_matrix/client/r0/directory/room/"; + login = s_proto + homeserver + "/_matrix/client/r0/login"; + } @@ -146,6 +152,10 @@ namespace matrix{ return _post_curl(postdata, m_urls.create_room, raii::curl_llist()); } + bool bot::join_room(const raii::string_base& roomid)const{ + raii::string url = "https://" + m_homeserver + "/_matrix/client/r0/rooms/" + m_curl.encode(roomid) + "/join?access_token=" + m_access_token; + return _post_curl(raii::string(), url, raii::curl_llist()); + } file_info bot::upload_file(const raii::string_base& filename)const{ return upload_file(filename, raii::static_string()); } @@ -436,14 +446,45 @@ namespace matrix{ if(!res.value) return reply; m_next_batch = res.value; - if(m_message_callback == nullptr){ - return reply; - } res = rjp_search_member(root.get(), "rooms", 0); - if(!res.value) return reply; - res = rjp_search_member(res.value, "join", 0); - if(!res.value) return reply; + if(res.value){ + if(m_message_callback != nullptr) + _handle_messages(res.value); + if(m_invite_callback != nullptr) + _handle_invites(res.value); + } + return reply; + } + + +/******************************* + Internal functions +********************************/ + + void bot::_handle_invites(RJP_value* invites){ + RJP_search_res res = rjp_search_member(invites, "invite", 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, "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)){ + static const char* search_terms[] = {"event_id", "sender", "state_key"}; + 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); + if(!results[0].value || !results[1].value || !results[2].value) continue; + if(strcmp(rjp_value_string(results[2].value), m_userid)) continue; + raii::rjp_string room_str = rjp_member_name(roomid); + m_invite_callback(*this, invite_info{room_str, raii::rjp_string(results[1].value), raii::rjp_string(results[0].value)}); + } + } + } + void bot::_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; @@ -468,16 +509,8 @@ namespace matrix{ _send_read_receipt(room_str, eventid_str); } } - - return reply; } - - -/******************************* - Internal functions -********************************/ - void bot::_send_read_receipt(const raii::string_base& roomid, const raii::string_base& eventid)const{ raii::string url = "https://" + m_homeserver + "/_matrix/client/r0/rooms/" + m_curl.encode(roomid) + "/receipt/m.read/" + m_curl.encode(eventid) + "?access_token=" + m_access_token; _post_curl(""_ss, url, raii::curl_llist()); @@ -646,6 +679,7 @@ namespace matrix{ RJP_search_res id = rjp_search_member(root.get(), "user_id", 0); m_userid = raii::rjp_string(id.value); }else{ + m_urls = mat_url_list(m_homeserver); raii::string reply = _request_access_token(a); if(!reply) return; diff --git a/src/test.cpp b/src/test.cpp index 278c7d5..35e325e 100644 --- a/src/test.cpp +++ b/src/test.cpp @@ -427,11 +427,16 @@ int main(){ bot.send_image(msg.roomid, {"mxc://matrix.org/SYkDDTUwcfscliYTuIfYFIrx"_ss, "nipple.jpg"_ss, "image/jpeg"_ss, 40202, 512, 402, {}, 512, 402, 40202}); } }; - matbot.set_sync_callback(sync_callback); + auto invite_callback = [&](const matrix::bot& bot, const matrix::invite_info& invite)->bool{ + bot.join_room(invite.roomid); + return true; + }; + matbot.set_message_callback(sync_callback); + matbot.set_invite_callback(invite_callback); while(!should_quit){ sync_reply = matbot.sync(30000); - //DEBUG_PRINT("%s\n", sync_reply.get()); + DEBUG_PRINT("%s\n", sync_reply.get()); } }