added invite handling and a couple example programs

This commit is contained in:
rexy712
2019-03-26 13:39:35 -07:00
parent 4b8b7e3918
commit 8d24247e42
6 changed files with 170 additions and 24 deletions

3
TODO
View File

@@ -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

49
examples/commands.cpp Normal file
View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
//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);
}
}

41
examples/join.cpp Normal file
View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
//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);
}
}

View File

@@ -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<void(const bot&,const msg_info&)> m_message_callback;
std::function<void(const bot&, const invite_info&)> 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<class Func>
void set_sync_callback(Func&& f){
void set_message_callback(Func&& f){
m_message_callback = std::forward<Func>(f);
}
template<class Func>
void set_invite_callback(Func&& f){
m_invite_callback = std::forward<Func>(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;

View File

@@ -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;

View File

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