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());
}
}