Changed name to client and changed test program to not include reddit

This commit is contained in:
Rexy712 2019-07-15 10:02:24 -07:00
parent 4d7a91738e
commit 74c8948139
7 changed files with 598 additions and 1223 deletions

File diff suppressed because one or more lines are too long

View File

@ -32,13 +32,13 @@
namespace matrix{
struct auth_data{
raii::rjp_string bot_name;
raii::rjp_string bot_pass;
raii::rjp_string name;
raii::rjp_string pass;
raii::rjp_string homeserver;
raii::rjp_string access_token;
operator bool(void)const{
return (bot_name && bot_pass && homeserver);
return (name && pass && homeserver);
}
};
@ -136,7 +136,7 @@ namespace matrix{
};
//main class
class bot
class client
{
private:
class mat_url_list
@ -207,18 +207,18 @@ 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 membership_info&)> m_membership_callback;
std::function<void(const client&,const msg_info&)> m_message_callback;
std::function<void(const client&, const membership_info&)> m_membership_callback;
public:
bot(const auth_data& a, const raii::string_base& useragent);
bot(const auth_data& a, raii::string&& useragent);
bot(const bot& b) = default;
bot(bot&& b) = default;
~bot(void) = default;
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;
bot& operator=(const bot&) = default;
bot& operator=(bot&&) = default;
client& operator=(const client&) = default;
client& operator=(client&&) = default;
//local getter
const raii::rjp_string& access_token(void)const;
@ -269,7 +269,6 @@ namespace matrix{
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<class Func>
void set_message_callback(Func&& f){
m_message_callback = std::forward<Func>(f);

View File

@ -1,164 +0,0 @@
/**
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/>.
*/
#include "raii/rjp_string.hpp"
#include "raii/curler.hpp"
#include "raii/string_base.hpp"
#include "raii/string.hpp"
#include "raii/curler.hpp"
namespace reddit{
struct auth_data{
raii::rjp_string bot_name;
raii::rjp_string bot_pass;
raii::rjp_string acc_name;
raii::rjp_string acc_pass;
operator bool(void)const{
return (bot_name && bot_pass && acc_name && acc_pass);
}
};
namespace time{
class period{
protected:
const char* data;
public:
constexpr period(const char* d):
data(d){}
constexpr const char* get(void)const{
return data;
}
};
extern period hour;
extern period day;
extern period week;
extern period month;
extern period year;
extern period all;
}
enum class post_type{
image, link, text, video, audio, unrecognized
};
class post
{
private:
enum post_flags{
POST_FLAGS_NONE = 0,
POST_FLAGS_CROSSPOSTED = 1
};
private:
raii::string m_post;
raii::rjp_string m_media_url;
raii::string m_hosted_video_audio;
raii::rjp_string m_author;
raii::rjp_string m_post_hint;
raii::rjp_string m_title;
raii::rjp_string m_name;
raii::rjp_string m_post_url;
post_type m_type = post_type::unrecognized;
int m_flags = POST_FLAGS_NONE;
public:
post(void) = default;
post(const raii::string_base& p);
post(raii::string_base&& p);
post(const post& p) = default;
post(post&& p) = default;
~post(void) = default;
post& operator=(const raii::string_base& p);
post& operator=(const post& p) = default;
post& operator=(post&& p) = default;
operator bool(void)const;
const raii::string& raw(void)const;
const raii::rjp_string& mediaurl(void)const;
const raii::string& hosted_video_audio(void)const;
const raii::rjp_string& posturl(void)const;
const raii::rjp_string& author(void)const;
const raii::rjp_string& post_hint(void)const;
const raii::rjp_string& title(void)const;
const raii::rjp_string& name(void)const;
bool is_crosspost(void)const;
post_type type(void)const;
private:
void _parse_post(void);
static post_type _handle_reddit_hosted_video(RJP_value* data, raii::rjp_string& media_url, raii::string& audio_url);
};
class bot
{
private:
raii::curler m_curl;
raii::string m_useragent;
raii::rjp_string m_access_token;
public:
bot(const auth_data& a, const raii::string_base& useragent);
bot(const auth_data& a, raii::string_base&& useragent);
bot(const bot& b);
bot(bot&& b);
~bot(void) = default;
bot& operator=(const bot& b);
bot& operator=(bot&& b);
const raii::rjp_string& access_token(void)const;
const raii::string& useragent(void)const;
void set_useragent(const raii::string_base&);
void set_useragent(raii::string_base&&);
void refresh_token(const auth_data& a);
post get_new_post(const raii::string_base& subreddit);
post get_new_post(const raii::string_base& subreddit, const raii::string_base& after);
post get_hot_post(const raii::string_base& subreddit);
post get_hot_post(const raii::string_base& subreddit, const raii::string_base& after);
post get_rising_post(const raii::string_base& subreddit);
post get_rising_post(const raii::string_base& subreddit, const raii::string_base& after);
post get_best_post(const raii::string_base& subreddit);
post get_best_post(const raii::string_base& subreddit, const raii::string_base& after);
post get_top_post(const raii::string_base& subreddit, time::period period = time::day);
post get_top_post(const raii::string_base& subreddit, const raii::string_base& after, time::period period = time::day);
post get_controversial_post(const raii::string_base& subreddit, time::period period = time::day);
post get_controversial_post(const raii::string_base& subreddit, const raii::string_base& after, time::period period = time::day);
protected:
static size_t _get_response_curl_callback(char* ptr, size_t size, size_t nmemb, void* userdata);
static raii::curl_llist _create_auth_header(const raii::string_base& access_token);
post _get_post(const raii::string_base& subreddit, const raii::string_base& category, const raii::string_base& extradata);
void _setup_subreddit_get_curl(const raii::curl_llist& header, const raii::string_base& url, const raii::string_base& reply);
static size_t _post_reply_curl_callback(char* ptr, size_t size, size_t nmemb, void* userdata);
static raii::string _create_request_post_data(const raii::string_base& acc_name, const raii::string_base& acc_pass);
static raii::string _create_request_userpwd(const raii::string_base& bot_name, const raii::string_base& bot_pass);
void _setup_token_request_curl(const raii::string_base& userpwd, const raii::string_base& postdata, void* result);
raii::string _request_access_token(const auth_data& a);
raii::rjp_string _acquire_access_token(const auth_data& a);
};
auth_data parse_auth_data(RJP_value* root);
}

View File

@ -26,7 +26,7 @@ CXXFLAGS:=-g -std=c++17 -Wall -pedantic -Wextra
all: CXXFLAGS+=-O0
release: CXXFLAGS+=-O2
LDFLAGS=
LDLIBS:=-lcurl -lrjp -lavformat -lavcodec -lavutil -lswresample -lswscale -lfreeimageplus
LDLIBS:=-lcurl -lrjp -lavformat -lavcodec -lavutil -lswresample -lswscale -lfreeimageplus -lpthread
STRIP:=strip
memchk:LDFLAGS+=-fsanitize=address -fno-omit-frame-pointer -fno-optimize-sibling-calls

View File

@ -1,5 +1,5 @@
/**
This file is a part of rexy's matrix bot
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
@ -60,14 +60,14 @@ namespace matrix{
details[3].value};
}
bot::bot(const auth_data& a, const raii::string_base& useragent):
client::client(const auth_data& a, const raii::string_base& useragent):
m_curl(),
m_useragent(useragent),
m_homeserver(a.homeserver)
{
_acquire_access_token(a);
}
bot::bot(const auth_data& a, raii::string&& useragent):
client::client(const auth_data& a, raii::string&& useragent):
m_curl(),
m_useragent(std::move(useragent)),
m_homeserver(a.homeserver)
@ -75,40 +75,40 @@ namespace matrix{
_acquire_access_token(a);
}
const raii::rjp_string& bot::access_token(void)const{
const raii::rjp_string& client::access_token(void)const{
return m_access_token;
}
const raii::rjp_string& bot::userid(void)const{
const raii::rjp_string& client::userid(void)const{
return m_userid;
}
const raii::string& bot::useragent(void)const{
const raii::string& client::useragent(void)const{
return m_useragent;
}
void bot::set_useragent(const raii::string_base& useragent){
void client::set_useragent(const raii::string_base& useragent){
m_useragent = useragent;
}
void bot::set_useragent(raii::string&& useragent){
void client::set_useragent(raii::string&& useragent){
m_useragent = std::move(useragent);
}
void bot::set_display_name(const raii::string_base& newname){
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 bot::set_profile_picture(const raii::string_base& media_url){
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 bot::get_display_name(void)const{
raii::rjp_string client::get_display_name(void)const{
return _get_and_find(m_urls.displayname(), "displayname"_ss);
}
raii::rjp_string bot::get_profile_picture(void)const{
raii::rjp_string client::get_profile_picture(void)const{
return _get_and_find(m_urls.profile_picture(), "avatar_url"_ss);
}
raii::rjp_string bot::room_alias_to_id(const raii::string_base& alias)const{
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<raii::rjp_string> bot::list_rooms(void)const{
std::vector<raii::rjp_string> client::list_rooms(void)const{
std::vector<raii::rjp_string> ret;
raii::string reply = _get_curl(m_urls.room_list());
if(!reply)
@ -128,7 +128,7 @@ namespace matrix{
return ret;
}
raii::string bot::create_room(const raii::string_base& name, const raii::string_base& alias)const{
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) + "\"}";
@ -137,28 +137,28 @@ namespace matrix{
return _post_curl(postdata, m_urls.create_room(), raii::curl_llist());
}
bool bot::join_room(const raii::string_base& roomid)const{
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 bot::leave_room(const raii::string_base& roomid)const{
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 bot::accept_invite(const membership_info& invite)const{
bool client::accept_invite(const membership_info& invite)const{
return join_room(invite.roomid);
}
bool bot::reject_invite(const membership_info& invite)const{
bool client::reject_invite(const membership_info& invite)const{
return leave_room(invite.roomid);
}
file_info bot::upload_file(const raii::string_base& filename)const{
file_info client::upload_file(const raii::string_base& filename)const{
return upload_file(filename, raii::static_string());
}
file_info bot::upload_file(const raii::string_base& filename, const raii::string_base& alias)const{
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 bot::upload_image(const raii::string_base& filename)const{
image_info client::upload_image(const raii::string_base& filename)const{
return upload_image(filename, raii::static_string());
}
#ifdef HAS_FREEIMAGE
@ -201,7 +201,7 @@ namespace matrix{
}
return {};
}
image_info bot::upload_image(const raii::string_base& filename, const raii::string_base& alias)const{
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);
@ -231,16 +231,16 @@ namespace matrix{
}
#else //HAS_FREEIMAGE
image_info bot::upload_image(const raii::string_base& filename, const raii::string_base& alias)const{
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 bot::upload_video(const raii::string_base& filename)const{
video_info client::upload_video(const raii::string_base& filename)const{
return upload_video(filename, raii::static_string());
}
audio_info bot::upload_audio(const raii::string_base& filename)const{
audio_info client::upload_audio(const raii::string_base& filename)const{
return upload_audio(filename, raii::static_string());
}
#ifdef HAS_FFMPEG
@ -327,7 +327,7 @@ namespace matrix{
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 bot::upload_video(const raii::string_base& filename, const raii::string_base& alias)const{
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);
@ -376,7 +376,7 @@ namespace matrix{
ret.filename = alias ? alias : filename;
return ret;
}
audio_info bot::upload_audio(const raii::string_base& filename, const raii::string_base& alias)const{
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);
@ -393,41 +393,41 @@ namespace matrix{
return ret;
}
#else //HAS_FFMPEG
video_info bot::upload_video(const raii::string_base& filename, const raii::string_base& alias)const{
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 bot::upload_audio(const raii::string_base& filename, const raii::string_base& alias)const{
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 bot::send_file(const raii::string_base& room, const file_info& file)const{
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 bot::send_image(const raii::string_base& room, const image_info& image)const{
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 bot::send_video(const raii::string_base& room, const video_info& video)const{
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 bot::send_audio(const raii::string_base& room, const audio_info& audio)const{
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 bot::send_message(const raii::string_base& room, const raii::string_base& text)const{
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 bot::send_typing(const raii::string_base& room, bool active, int timeout)const{
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 bot::redact_event(const raii::string_base& roomid, const raii::string_base& eventid, const raii::string_base& reason)const{
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()));
@ -437,15 +437,15 @@ namespace matrix{
if(!res.value) return {};
return raii::rjp_string(res.value);
}
raii::rjp_string bot::redact_event(const raii::string_base& roomid, const raii::string_base& eventid)const{
raii::rjp_string client::redact_event(const raii::string_base& roomid, const raii::string_base& eventid)const{
return redact_event(roomid, eventid, ""_ss);
}
void bot::logout(void){
void client::logout(void){
_get_curl(m_urls.logout(m_homeserver, m_access_token));
m_urls.invalidate_accesstoken();
}
raii::string bot::sync(size_t timeout){
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)
@ -474,7 +474,7 @@ namespace matrix{
Internal functions
********************************/
void bot::_handle_membership_events(RJP_value* rooms){
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);
@ -482,7 +482,7 @@ namespace matrix{
if(res.value)
_handle_other_membership(res.value);
}
void bot::_handle_other_membership(RJP_value* join){
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;
@ -508,7 +508,7 @@ namespace matrix{
}
}
}
void bot::_handle_invites(RJP_value* invites){
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;
@ -525,7 +525,7 @@ namespace matrix{
}
}
}
void bot::_handle_messages(RJP_value* messages){
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)){
@ -555,10 +555,10 @@ namespace matrix{
}
}
void bot::_send_read_receipt(const raii::string_base& roomid, const raii::string_base& eventid)const{
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 bot::_upload_file(raii::filerd& fp, const raii::curl_llist& header)const{
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);
@ -584,7 +584,7 @@ namespace matrix{
return res.value;
}
raii::rjp_string bot::_send_message(const raii::string_base& room, const raii::string_base& msg)const{
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)),
@ -592,13 +592,13 @@ namespace matrix{
"event_id"_ss);
return reply;
}
size_t bot::_post_reply_curl_callback(char* ptr, size_t size, size_t nmemb, void* userdata){
size_t client::_post_reply_curl_callback(char* ptr, size_t size, size_t nmemb, void* userdata){
raii::string* data = reinterpret_cast<raii::string*>(userdata);
(*data) += ptr;
return size*nmemb;
}
raii::string bot::_get_curl(const raii::string_base& url)const{
raii::string client::_get_curl(const raii::string_base& url)const{
raii::string reply;
m_curl.getreq();
m_curl.seturl(url);
@ -610,7 +610,7 @@ namespace matrix{
return {};
return reply;
}
raii::string bot::_post_curl(const raii::string_base& postdata, const raii::string_base& url, const raii::curl_llist& header)const{
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());
@ -637,7 +637,7 @@ namespace matrix{
src->data += to_copy;
return to_copy;
}
raii::string bot::_put_curl(const raii::string_base& putdata, const raii::string_base& url, const raii::curl_llist& header)const{
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();
@ -657,7 +657,7 @@ namespace matrix{
return {};
return reply;
}
raii::rjp_string bot::_post_and_find(const raii::string_base& data, const raii::string_base& url,
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);
@ -665,13 +665,13 @@ namespace matrix{
return {};
return _curl_reply_search(reply, target);
}
raii::rjp_string bot::_get_and_find(const raii::string_base& url, const raii::string_base& target)const{
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 bot::_curl_reply_search(const raii::string_base& reply, const raii::string_base& target)const{
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 {};
@ -680,7 +680,7 @@ namespace matrix{
return {};
return raii::rjp_string(res.value);
}
void bot::_set_curl_defaults(void)const{
void client::_set_curl_defaults(void)const{
m_curl.setopt(CURLOPT_BUFFERSIZE, 102400L);
m_curl.setopt(CURLOPT_NOPROGRESS, 1L);
m_curl.setuseragent(m_useragent);
@ -690,9 +690,9 @@ namespace matrix{
m_curl.setopt(CURLOPT_TCP_KEEPALIVE, 1L);
m_curl.setopt(CURLOPT_FAILONERROR, 1L);
}
raii::string bot::_request_access_token(const auth_data& a)const{
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.bot_name) + "\", \"password\":\"" + raii::json_escape(a.bot_pass) + "\"}");
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());
@ -708,7 +708,7 @@ namespace matrix{
return reply;
}
void bot::_get_new_access_token(const auth_data& a){
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)
@ -723,7 +723,7 @@ namespace matrix{
m_urls.repopulate_accesstoken(m_homeserver, m_access_token);
m_urls.repopulate_userid(m_homeserver, m_access_token, m_curl.encode(m_userid));
}
void bot::_acquire_access_token(const auth_data& a){
void client::_acquire_access_token(const auth_data& a){
_set_curl_defaults();
if(a.access_token){
m_access_token = a.access_token;
@ -750,28 +750,28 @@ namespace matrix{
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
bot::mat_url_list::mat_url_list(const raii::string_base& homeserver){
client::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, const raii::string_base& userid){
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 bot::mat_url_list::repopulate_accesstoken(const raii::string_base& homeserver, const raii::string_base& access_token){
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 bot::mat_url_list::repopulate_userid(const raii::string_base& homeserver, const raii::string_base& access_token, const raii::string_base& userid){
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 bot::mat_url_list::repopulate(const raii::string_base& homeserver, const raii::string_base& access_token, const raii::string_base& userid){
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 bot::mat_url_list::invalidate_accesstoken(void){
void client::mat_url_list::invalidate_accesstoken(void){
m_create_room.reset();
m_file_upload.reset();
m_room_list.reset();
@ -779,81 +779,81 @@ namespace matrix{
m_displayname.reset();
m_profile_picture.reset();
}
const raii::string& bot::mat_url_list::create_room(void)const{
const raii::string& client::mat_url_list::create_room(void)const{
return m_create_room;
}
const raii::string& bot::mat_url_list::file_upload(void)const{
const raii::string& client::mat_url_list::file_upload(void)const{
return m_file_upload;
}
const raii::string& bot::mat_url_list::room_list(void)const{
const raii::string& client::mat_url_list::room_list(void)const{
return m_room_list;
}
const raii::string& bot::mat_url_list::login(void)const{
const raii::string& client::mat_url_list::login(void)const{
return m_login;
}
const raii::string& bot::mat_url_list::alias_lookup(void)const{
const raii::string& client::mat_url_list::alias_lookup(void)const{
return m_alias_lookup;
}
const raii::string& bot::mat_url_list::whoami(void)const{
const raii::string& client::mat_url_list::whoami(void)const{
return m_whoami;
}
const raii::string& bot::mat_url_list::displayname(void)const{
const raii::string& client::mat_url_list::displayname(void)const{
return m_displayname;
}
const raii::string& bot::mat_url_list::profile_picture(void)const{
const raii::string& client::mat_url_list::profile_picture(void)const{
return m_profile_picture;
}
raii::string bot::mat_url_list::logout(const raii::string_base& homeserver, const raii::string_base& access_token)const{
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 bot::mat_url_list::join_room(const raii::string_base& homeserver, const raii::string_base& access_token, const raii::string_base& roomid)const{
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 bot::mat_url_list::leave_room(const raii::string_base& homeserver, const raii::string_base& access_token, const raii::string_base& roomid)const{
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 bot::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{
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 bot::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{
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 bot::mat_url_list::send(const raii::string_base& homeserver, const raii::string_base& access_token, const raii::string_base& roomid)const{
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 bot::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{
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 bot::mat_url_list::power_level(const raii::string_base& homeserver, const raii::string_base& access_token, const raii::string_base& roomid)const{
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 bot::mat_url_list::presence(const raii::string_base& homeserver, const raii::string_base& access_token, const raii::string_base& userid)const{
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 bot::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{
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 bot::mat_url_list::kick(const raii::string_base& homeserver, const raii::string_base& access_token, const raii::string_base& roomid)const{
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 bot::mat_url_list::ban(const raii::string_base& homeserver, const raii::string_base& access_token, const raii::string_base& roomid)const{
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 bot::mat_url_list::unban(const raii::string_base& homeserver, const raii::string_base& access_token, const raii::string_base& roomid)const{
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 bot::mat_url_list::invite(const raii::string_base& homeserver, const raii::string_base& access_token, const raii::string_base& roomid)const{
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 bot::mat_url_list::room_members(const raii::string_base& homeserver, const raii::string_base& access_token, const raii::string_base& roomid)const{
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 bot::mat_url_list::password(const raii::string_base& homeserver, const raii::string_base& access_token)const{
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 bot::mat_url_list::_initial_populate(const raii::string_base& homeserver){
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";
}

View File

@ -1,534 +0,0 @@
/**
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/>.
*/
#include "reddit.hpp"
#include "raii/rjp_string.hpp"
#include "raii/string.hpp"
#include "raii/curler.hpp"
#include "raii/rjp_ptr.hpp"
#include "raii/static_string.hpp"
#include <algorithm> //search
#include <cstring>
//no idea if this covers everything reddit might dish out at me
//there is no consistency in their content tagging. there are gifs marked as images, others as videos
//they separate audio and video streams for their hosted videos, there is no true way to tell
//what kind of content a post contains since the post_hint field might be completely nonexistent.
//it's just a game of hacking together solutions each time reddit throws me a new type of unexpected complication.
namespace reddit{
namespace time{
period hour = "hour";
period day = "day";
period week = "week";
period month = "month";
period year = "year";
period all = "all";
}
auth_data parse_auth_data(RJP_value* root){
static const char* account_names[2] = {"bot", "account"};
static const char* account_fields[2] = {"username", "password"};
auth_data ret;
RJP_search_res accounts[2];
RJP_search_res details[2];
rjp_search_members(root, 2, account_names, accounts, 0);
rjp_search_members(accounts[0].value, 2, account_fields, details, 0);
ret.bot_name = details[0].value;
ret.bot_pass = details[1].value;
rjp_search_members(accounts[1].value, 2, account_fields, details, 0);
ret.acc_name = details[0].value;
ret.acc_pass = details[1].value;
return ret;
}
static raii::rjp_string media_search(RJP_value* media){
if(!media)
return {};
RJP_search_res res = rjp_search_member(media, "reddit_video", 0);
if(!res.value)
return raii::rjp_string{};
res = rjp_search_member(res.value, "fallback_url", 0);
if(!res.value)
return raii::rjp_string{};
return raii::rjp_string(res.value);
}
static raii::rjp_string preview_search(RJP_value* root){
RJP_search_res media = rjp_search_member(root, "preview", 0);
if(!media.value)
return raii::rjp_string{};
media = rjp_search_member(media.value, "reddit_video_preview", 0);
if(!media.value)
return raii::rjp_string{};
media = rjp_search_member(media.value, "fallback_url", 0);
if(!media.value)
return raii::rjp_string{};
return raii::rjp_string(media.value);
}
static bool check_reddit_media_domain(RJP_value* root){
RJP_search_res res = rjp_search_member(root, "is_reddit_media_domain", 0);
return (res.value && rjp_value_boolean(res.value));
}
static raii::rjp_string find_video_url(RJP_value* root){
RJP_search_res media = rjp_search_member(root, "media", 0);
if(raii::rjp_string res = media_search(media.value)){
return res;
}
raii::rjp_string res = preview_search(root);
return res;
}
static bool is_gifv(const raii::string_base& str){
const char* s = str.get();
size_t len = str.length();
if(len > 5 &&
*(s+len-1) == 'v' &&
*(s+len-2) == 'f' &&
*(s+len-3) == 'i' &&
*(s+len-4) == 'g' &&
*(s+len-5) == '.')
{
return true;
}
return false;
}
static bool has_extension(const raii::string_base& str){
size_t i = 0;
for(const char* p = str.get() + str.length() - 1;*p && i < 6;--p,++i){
if(*p == '/')
return false;
else if(*p == '.')
return true;
}
return false;
}
static bool is_gfycat_link(const raii::string_base& str){
static const char gfycat[] = "gfycat.com";
return *std::search(str.get(), str.get()+str.length(), gfycat, gfycat+sizeof(gfycat)-1) != 0;
}
static bool is_imgur_link(const raii::string_base& str){
static const char imgur[] = "imgur.com";
return *std::search(str.get(), str.get()+str.length(), imgur, imgur+sizeof(imgur)-1) != 0;
}
static bool is_direct_imgur_link(const raii::string_base& str){
return is_imgur_link(str) && has_extension(str);
}
post::post(const raii::string_base& p):
m_post(p)
{
_parse_post();
}
post::post(raii::string_base&& p):
m_post(std::move(p)),
m_type(post_type::unrecognized)
{
_parse_post();
}
post& post::operator=(const raii::string_base& p){
post tmp(p);
if(!tmp)
return *this;
return (*this = std::move(tmp));
}
post::operator bool(void)const{
if(m_type == post_type::text)
return (m_post_url && m_author && m_title && m_name);
else
return (m_post_url && m_media_url && m_author && m_title && m_name);
}
const raii::string& post::raw(void)const{
return m_post;
}
const raii::rjp_string& post::mediaurl(void)const{
return m_media_url;
}
const raii::string& post::hosted_video_audio(void)const{
return m_hosted_video_audio;
}
const raii::rjp_string& post::posturl(void)const{
return m_post_url;
}
const raii::rjp_string& post::author(void)const{
return m_author;
}
const raii::rjp_string& post::post_hint(void)const{
return m_post_hint;
}
const raii::rjp_string& post::title(void)const{
return m_title;
}
const raii::rjp_string& post::name(void)const{
return m_name;
}
bool post::is_crosspost(void)const{
return (m_flags & POST_FLAGS_CROSSPOSTED);
}
post_type post::type(void)const{
return m_type;
}
void post::_parse_post(void){
raii::rjp_ptr root(rjp_parse(m_post));
if(!root)
return;
static const char* search_items[] = {"url", "author", "post_hint", "title", "id", "crosspost_parent_list"};
static constexpr size_t num_searches = sizeof(search_items)/sizeof(search_items[0]);
RJP_search_res results[num_searches];
RJP_search_res data = rjp_search_member(root.get(), "data", 0);
if(!data.value) return;
data = rjp_search_member(data.value, "children", 0);
if(!data.value) return;
data.value = rjp_get_element(data.value);
if(!data.value) return;
RJP_search_res kind = rjp_search_member(data.value, "kind", 0);
if(!kind.value) return;
data = rjp_search_member(data.value, "data", 0);
if(!data.value) return;
RJP_search_res& crosspost = results[5];
rjp_search_members(data.value, num_searches, search_items, results, 0);
//reddit will *sometimes* make the url field point to the crosspost parent's comments page.
//so we just always assume that the true link is in the crosspost parent
if(crosspost.value){
m_flags |= POST_FLAGS_CROSSPOSTED;
crosspost.value = rjp_get_element(crosspost.value);
crosspost = rjp_search_member(crosspost.value, "url", 0);
if(crosspost.value)
m_media_url = crosspost.value;
}else{
m_media_url = results[0].value;
}
m_author = results[1].value;
m_post_hint = results[2].value;
m_title = results[3].value;
m_name = raii::rjp_string(kind.value) + "_" + rjp_value_string(results[4].value);
m_post_url = "https://redd.it/" + raii::rjp_string(results[4].value);
if(m_post_hint){
//handle simple image
if(!strcmp(m_post_hint, "image")){
m_type = post_type::image;
}
//handle link
else if(!strcmp(m_post_hint, "link")){
m_type = post_type::link;
//imgur support
if(is_imgur_link(m_media_url)){
if(is_gifv(m_media_url)){ //gifv is a video
if(raii::rjp_string tmp = preview_search(data.value)){
m_media_url = std::move(tmp);
m_type = post_type::video;
}
}else{
//imgur links don't lead to the image source. adding .jpg to the link leads to the source
//except when the link is to an album or to a gifv
m_media_url += ".jpg"; //imgur is dumb
m_type = post_type::image;
}
//gfycat support
}else if(is_gfycat_link(m_media_url)){
if(raii::rjp_string tmp = find_video_url(data.value)){
m_media_url = std::move(tmp);
m_type = post_type::video;
}
}
}
//handle hosted video
else if(!strcmp(m_post_hint, "hosted:video")){
m_type = _handle_reddit_hosted_video(data.value, m_media_url, m_hosted_video_audio);
}
else if(!strcmp(m_post_hint, "rich:video")){
RJP_search_res media = rjp_search_member(data.value, "media", 0);
raii::rjp_string res = media_search(media.value);
if(res){
m_type = post_type::video;
m_media_url = std::move(res);
return;
}
res = preview_search(data.value);
if(res){
m_type = post_type::video;
m_media_url = std::move(res);
return;
}
m_type = post_type::link;
}
else{
//assume text post for other
m_type = post_type::text;
}
}else if(is_direct_imgur_link(m_media_url)){
m_type = post_type::image;
return;
}else if(check_reddit_media_domain(data.value)){
m_type = _handle_reddit_hosted_video(data.value, m_media_url, m_hosted_video_audio);
/*RJP_value* media = rjp_search_member(data.value, "media", 0).value;
if(media && (rjp_value_type(media) != json_null))
m_type = post_type::video;
else
m_type = post_type::image;
//*/
}else{
m_media_url.reset();
m_type = post_type::text;
}
}
post_type post::_handle_reddit_hosted_video(RJP_value* data, raii::rjp_string& media_url, raii::string& audio_url){
RJP_search_res media = rjp_search_member(data, "media", 0);
RJP_search_res gif = rjp_search_member(media.value, "reddit_video", 0);
//treat gif as image even though reddit thinks they're videos
if(gif.value)
gif = rjp_search_member(media.value, "is_gif", 0);
if(gif.value && rjp_value_boolean(gif.value)){
return post_type::image;
}
raii::rjp_string res = media_search(media.value);
if(!res){
res = preview_search(data);
if(!res){
return post_type::link;
}
}
media_url = std::move(res);
//reddit hosts audio and video separately. Meaning I have to find a way to manually recombine them.
//this sets up a link to the audio source of the video. the video might not actually have audio. when downloading
//from the audio link, always make sure to check for 404 errors.
static constexpr char url_base[] = "https://v.redd.it/";
static constexpr size_t url_base_len = sizeof(url_base)-1;
char* end = strstr(media_url.get()+url_base_len, "/");
if(!end)
end = media_url.get()+media_url.length();
size_t len = end - media_url.get();
audio_url = raii::string(len + 6);
memcpy(audio_url.get(), media_url.get(), len);
memcpy(audio_url.get()+len, "/audio", 6);
audio_url[len+6] = 0;
return post_type::video;
}
bot::bot(const auth_data& a, const raii::string_base& useragent):
m_curl(),
m_useragent(useragent),
m_access_token(_acquire_access_token(a)){}
bot::bot(const auth_data& a, raii::string_base&& useragent):
m_curl(),
m_useragent(std::move(useragent)),
m_access_token(_acquire_access_token(a)){}
bot::bot(const bot& b):
m_curl(b.m_curl),
m_useragent(b.m_useragent),
m_access_token(b.m_access_token){}
bot::bot(bot&& b):
m_curl(std::move(b.m_curl)),
m_useragent(std::move(b.m_useragent)),
m_access_token(std::move(b.m_access_token)){}
bot& bot::operator=(bot&& b){
m_useragent = std::move(b.m_useragent);
m_access_token = std::move(b.m_access_token);
return *this;
}
bot& bot::operator=(const bot& b){
bot tmp(b);
return *this = std::move(tmp);
}
const raii::rjp_string& bot::access_token(void)const{
return m_access_token;
}
const raii::string& bot::useragent(void)const{
return m_useragent;
}
void bot::set_useragent(const raii::string_base& s){
m_useragent = s;
}
void bot::set_useragent(raii::string_base&& s){
m_useragent = std::move(s);
}
void bot::refresh_token(const auth_data& a){
m_access_token = _acquire_access_token(a);
}
post bot::get_new_post(const raii::string_base& subreddit){
return _get_post(subreddit, "new"_ss, "limit=1"_ss);
}
post bot::get_new_post(const raii::string_base& subreddit, const raii::string_base& after){
return _get_post(subreddit, "new"_ss, raii::string("limit=1&after=" + after));
}
post bot::get_hot_post(const raii::string_base& subreddit){
return _get_post(subreddit, "hot"_ss, "limit=1"_ss);
}
post bot::get_hot_post(const raii::string_base& subreddit, const raii::string_base& after){
return _get_post(subreddit, "hot"_ss, raii::string("limit=1&after=" + after));
}
post bot::get_rising_post(const raii::string_base& subreddit){
return _get_post(subreddit, "rising"_ss, "limit=1"_ss);
}
post bot::get_rising_post(const raii::string_base& subreddit, const raii::string_base& after){
return _get_post(subreddit, "rising"_ss, raii::string("limit=1&after=" + after));
}
post bot::get_best_post(const raii::string_base& subreddit){
return _get_post(subreddit, "best"_ss, "limit=1"_ss);
}
post bot::get_best_post(const raii::string_base& subreddit, const raii::string_base& after){
return _get_post(subreddit, "best"_ss, raii::string("limit=1&after=" + after));
}
post bot::get_top_post(const raii::string_base& subreddit, time::period period){
raii::static_string pstr = period.get();
return _get_post(subreddit, "top"_ss, raii::string("limit=1&t=" + pstr));
}
post bot::get_top_post(const raii::string_base& subreddit, const raii::string_base& after, time::period period){
raii::static_string pstr = period.get();
return _get_post(subreddit, "top"_ss, raii::string("limit=1&t=" + pstr + "&after=" + after));
}
post bot::get_controversial_post(const raii::string_base& subreddit, time::period period){
raii::static_string pstr = period.get();
return _get_post(subreddit, "controversial"_ss, raii::string("limit=1&t=" + pstr));
}
post bot::get_controversial_post(const raii::string_base& subreddit, const raii::string_base& after, time::period period){
raii::static_string pstr = period.get();
return _get_post(subreddit, "controversial"_ss, raii::string("limit=1&t=" + pstr + "&after=" + after));
}
post bot::_get_post(const raii::string_base& subreddit, const raii::string_base& category, const raii::string_base& extra){
raii::string rep;
static constexpr char url_base[] = "https://oauth.reddit.com/r/";
raii::string url;
if(extra)
url = (url_base + subreddit) + "/" + category + "?" + extra;
else
url = (url_base + subreddit) + "/" + category;
raii::curl_llist header(_create_auth_header(m_access_token));
m_curl.reset();
_setup_subreddit_get_curl(header, url, rep);
m_curl.perform();
return post(rep);
}
size_t bot::_get_response_curl_callback(char* ptr, size_t size, size_t nmemb, void* userdata){
raii::rjp_string* reply = reinterpret_cast<raii::rjp_string*>(userdata);
(*reply) += ptr;
return size*nmemb;
}
raii::curl_llist bot::_create_auth_header(const raii::string_base& access_token){
return raii::curl_llist(raii::string("Authorization: bearer " + access_token));
}
void bot::_setup_subreddit_get_curl(const raii::curl_llist& header, const raii::string_base& url, const raii::string_base& reply){
m_curl.seturl(url);
m_curl.setopt(CURLOPT_BUFFERSIZE, 102400L);
m_curl.setopt(CURLOPT_NOPROGRESS, 1L);
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.setheader(header);
m_curl.setuseragent(m_useragent);
m_curl.setopt(CURLOPT_WRITEFUNCTION, _get_response_curl_callback);
m_curl.setopt(CURLOPT_WRITEDATA, &reply);
m_curl.setopt(CURLOPT_FAILONERROR, 1L);
}
size_t bot::_post_reply_curl_callback(char* ptr, size_t size, size_t nmemb, void* userdata){
raii::string* data = reinterpret_cast<raii::string*>(userdata);
(*data) += ptr;
return size*nmemb;
}
//Create reddit login data
raii::string bot::_create_request_post_data(const raii::string_base& account_name, const raii::string_base& account_pass){
return raii::string("grant_type=password&username=" + account_name + "&password=" + account_pass);
}
//Setup login data for reddit bot
raii::string bot::_create_request_userpwd(const raii::string_base& bot_name, const raii::string_base& bot_pass){
return raii::string(bot_name + ":" + bot_pass);
}
void bot::_setup_token_request_curl(const raii::string_base& userpwd, const raii::string_base& postdata, void* result){
static constexpr char reddit_token_address[] = "https://www.reddit.com/api/v1/access_token";
m_curl.setopt(CURLOPT_BUFFERSIZE, 102400L);
m_curl.seturl(reddit_token_address);
m_curl.setopt(CURLOPT_NOPROGRESS, 1L);
m_curl.setuserpwd(userpwd);
m_curl.setpostdata(postdata);
m_curl.setuseragent(m_useragent);
m_curl.setheader(raii::curl_llist());
m_curl.setopt(CURLOPT_MAXREDIRS, 50L);
m_curl.setopt(CURLOPT_FOLLOWLOCATION, 1L);
m_curl.forcessl(CURL_SSLVERSION_TLSv1_2);
m_curl.setopt(CURLOPT_CUSTOMREQUEST, "POST");
m_curl.setopt(CURLOPT_TCP_KEEPALIVE, 1L);
m_curl.setopt(CURLOPT_WRITEFUNCTION, _post_reply_curl_callback);
m_curl.setopt(CURLOPT_WRITEDATA, result);
m_curl.setopt(CURLOPT_FAILONERROR, 1L);
}
raii::string bot::_request_access_token(const auth_data& auth){
CURLcode result;
//URL encode the POST data
raii::curl_string acc_name = m_curl.encode(auth.acc_name, auth.acc_name.length());
raii::curl_string acc_pass = m_curl.encode(auth.acc_pass, auth.acc_pass.length());
//unify the post data, clean up remnants
raii::string postdata = _create_request_post_data(acc_name, acc_pass);
acc_name.reset();
acc_pass.reset();
//Unify the username/password
raii::string userpwd = _create_request_userpwd(auth.bot_name, auth.bot_pass);
//Load curl with data then run POST operation
raii::string reply;
_setup_token_request_curl(userpwd, postdata, &reply);
result = m_curl.perform();
if(result != CURLE_OK)
return {};
return reply;
}
raii::rjp_string bot::_acquire_access_token(const auth_data& a){
raii::string reply = _request_access_token(a);
if(!reply)
return raii::rjp_string{};
raii::rjp_ptr root(rjp_parse(reply));
if(!root)
return raii::rjp_string{};
RJP_search_res token = rjp_search_member(root.get(), "access_token", 0);
return raii::rjp_string{token.value};
}
}

File diff suppressed because one or more lines are too long