initial commit to allow me to stash

This commit is contained in:
rexy712
2019-03-02 12:33:31 -08:00
commit 6f078c8495
25 changed files with 2547 additions and 0 deletions

7
.gitignore vendored Normal file
View File

@@ -0,0 +1,7 @@
obj
tester
*.log
data
*.swp
*.save
testout

11
TODO Normal file
View File

@@ -0,0 +1,11 @@
file info struct as return from upload_file so as to know some metadata about the file
uploaded url
file type if able to be figured out
file size
image
dimensions
thumbnail
use libmagic to determine file types
create thumbnail images somehow

138
include/matrix.hpp Normal file
View File

@@ -0,0 +1,138 @@
#ifndef MATRIX_HPP
#define MATRIX_HPP
#include "raii/curler.hpp"
#include "raii/string.hpp"
#include "raii/rjp_string.hpp"
#include "raii/rjp_ptr.hpp"
#include "raii/filerd.hpp"
#include <vector>
namespace matrix{
struct auth_data{
raii::rjp_string bot_name;
raii::rjp_string bot_pass;
raii::rjp_string homeserver;
raii::rjp_string bot_alias;
raii::rjp_string access_token;
operator bool(void)const{
return (bot_name && bot_pass && homeserver);
}
};
struct file_info{
raii::rjp_string fileurl;
raii::string filename;
raii::string filetype;
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;
};
class bot
{
private:
struct mat_url_list{
mat_url_list(void) = default;
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;
mat_url_list& operator=(const mat_url_list&) = default;
mat_url_list& operator=(mat_url_list&&) = default;
void repopulate_accesstoken(const raii::string_base& homeserver, const raii::string_base& access_token);
void repopulate(const raii::string_base& homeserver, const raii::string_base& access_token);
void invalidate_accesstoken(void);
raii::string create_room;
raii::string file_upload;
raii::string room_list;
raii::string login;
raii::string alias_lookup;
raii::string whoami;
private:
static constexpr const char* s_proto = "https://";
};
private:
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
size_t m_sync_timeout; //max time to wait during sync in milliseconds
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;
bot& operator=(const bot&) = default;
bot& operator=(bot&&) = 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 room_alias_to_id(const raii::string_base& alias);
std::vector<raii::rjp_string> list_rooms(void);
//other networked operations
raii::string create_room(const raii::string_base& name, const raii::string_base& alias);
raii::rjp_string upload_file(const raii::string_base& filename, const raii::curl_llist& header);
image_info upload_image(const raii::string_base& filename, const raii::string_base& alias);
image_info upload_image(const raii::string_base& filename);
raii::rjp_string upload_video(const raii::string_base& filename);
raii::rjp_string send_image(const raii::string_base& room, const image_info& image);
raii::rjp_string send_video(const raii::string_base& room, const raii::string_base& file_url, const raii::string_base& filetype, const raii::string_base& filename);
raii::rjp_string send_message(const raii::string_base& room, const raii::string_base& text);
bool send_file(const raii::string_base& room, const raii::string_base& file_url);
void sync(void);
void logout(void);
protected:
raii::rjp_string _upload_file(raii::filerd& fp, const raii::curl_llist& header);
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);
raii::string _post_curl(const raii::string_base& postdata, const raii::string_base& url, const raii::curl_llist& header);
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);
raii::rjp_string _get_and_find(const raii::string_base& url, const raii::string_base& search);
raii::rjp_string _curl_reply_search(const raii::string_base& reply, const raii::string_base& search);
void _set_curl_defaults(void);
raii::string _request_access_token(const auth_data& a);
void _acquire_access_token(const auth_data& a);
};
auth_data parse_auth_data(RJP_value* root);
}
#endif

View File

@@ -0,0 +1,49 @@
#ifndef RAII_CURL_LLIST_HPP
#define RAII_CURL_LLIST_HPP
#include <curl/curl.h>
#include <utility> //forward
namespace raii{
//RAII wrapper for curl's slist (singly linked list?)
class curl_llist
{
private:
curl_slist* m_data = nullptr;
public:
curl_llist(void) = default;
template<class... Args>
curl_llist(Args&&... args){
assign(std::forward<Args>(args)...);
}
curl_llist(const curl_llist&) = delete;
curl_llist(curl_llist&& l)noexcept;
~curl_llist(void);
curl_llist& operator=(const curl_llist&) = delete;
curl_llist& operator=(curl_llist&& l)noexcept;
curl_llist& operator+=(const char* data);
operator curl_slist*(void);
operator const curl_slist*(void)const;
curl_slist* get(void);
const curl_slist* get(void)const;
void reset(curl_slist* nd = nullptr);
private:
template<class T, class... Args>
void assign(T&& t, Args&&... args){
(*this) += std::forward<T>(t);
if constexpr(sizeof...(args) > 0){
assign(std::forward<Args>(args)...);
}
}
};
}
#endif

View File

@@ -0,0 +1,41 @@
#ifndef RAII_CURL_STRING_HPP
#define RAII_CURL_STRING_HPP
#include "raii/string_base.hpp"
#include <curl/curl.h>
#include <cstddef> //size_t
namespace raii{
namespace detail{
class curl_allocator
{
public:
static void* allocate(size_t){return nullptr;}
static void* copy(const void*, size_t){return nullptr;}
static void free(void* data){
curl_free(data);
}
};
}
//curl allocated string
struct curl_string : public string_intermediary<detail::curl_allocator>
{
curl_string(const curl_string&) = default;
curl_string(curl_string&&) = default;
curl_string& operator=(const curl_string&) = default;
curl_string& operator=(curl_string&&) = default;
using string_intermediary<detail::curl_allocator>::string_intermediary;
using string_intermediary<detail::curl_allocator>::operator=;
};
}
#endif

55
include/raii/curler.hpp Normal file
View File

@@ -0,0 +1,55 @@
#ifndef RAII_CURLER_HPP
#define RAII_CURLER_HPP
#include <curl/curl.h>
#include "raii/curl_llist.hpp"
#include "raii/curl_string.hpp"
namespace raii{
//RAII wrapper for CURL* with some convenience functions added
class curler
{
private:
CURL* m_curl;
public:
curler(void);
curler(const curler& c);
curler(curler&& c)noexcept;
~curler(void);
template<class T>
curler& setopt(CURLoption option, T&& t){
curl_easy_setopt(m_curl, option, t);
return *this;
}
curler& postreq(void);
curler& getreq(void);
curler& setheader(const curl_llist& h);
curler& seturl(const char* s);
curler& seturl(const string_base& s);
curler& setuseragent(const char* s);
curler& setuseragent(const string_base& s);
curler& setuserpwd(const char* s);
curler& setuserpwd(const string_base& s);
curler& setpostdata(const char* s, curl_off_t len = -1);
curler& setpostdata(const string_base& s);
curler& forcessl(long version = CURL_SSLVERSION_DEFAULT);
void reset(void);
decltype(curl_easy_perform(m_curl)) perform(void);
curl_string encode(const char* data, int len = 0);
curl_string decode(const char* data, int* outlen = nullptr, int len = 0);
CURL* get(void);
const CURL* get(void)const;
operator CURL*(void);
operator const CURL*(void)const;
};
}
#endif

47
include/raii/filerd.hpp Normal file
View File

@@ -0,0 +1,47 @@
#ifndef RAII_FILERD_HPP
#define RAII_FILERD_HPP
#include <cstdio> //FILE
#include <cstddef> //size_t
#include "raii/string.hpp"
namespace raii{
//RAII wrapper for FILE*
class filerd
{
private:
FILE* m_fp = nullptr;
public:
filerd(void) = default;
filerd(const char* f, const char* mode = "r");
filerd(const filerd&) = delete;
filerd(filerd&& f);
~filerd(void);
filerd& operator=(const filerd&) = delete;
filerd& operator=(filerd&& f);
void reset(FILE* fp = nullptr);
FILE* release(void);
size_t length(void);
size_t position(void)const;
void rewind(size_t pos = 0);
operator FILE*(void);
operator const FILE*(void)const;
FILE* get(void);
const FILE* get(void)const;
operator bool(void)const;
size_t read(char* dest, size_t bytes);
raii::string read(size_t bytes);
size_t write(const char* c, size_t bytes);
size_t write(const raii::string_base&);
};
}
#endif

23
include/raii/rjp_ptr.hpp Normal file
View File

@@ -0,0 +1,23 @@
#ifndef RAII_RJP_PTR_HPP
#define RAII_RJP_PTR_HPP
#include <rjp.h>
#include <memory> //unique_ptr
namespace raii{
namespace detail{
struct rjp_tree_deleter
{
template<class T>
void operator()(T* ptr){
rjp_free_value(ptr);
}
};
}
using rjp_ptr = std::unique_ptr<RJP_value,detail::rjp_tree_deleter>;
}
#endif

View File

@@ -0,0 +1,55 @@
#ifndef RAII_RJP_STRING_HPP
#define RAII_RJP_STRING_HPP
#include <rjp.h>
#include "raii/string_base.hpp"
#include <utility> //exchange
#include <cstdlib> //memcpy
namespace raii{
namespace detail{
class rjp_allocator
{
public:
static void free(void* data){
rjp_free(data);
}
static void* allocate(size_t size){
return rjp_alloc(size);
}
static void* copy(const void* data, size_t len){
void* tmp = allocate(len);
memcpy(tmp, data, len);
return tmp;
}
};
}
//rjp allocated string
struct rjp_string : public string_intermediary<detail::rjp_allocator>
{
rjp_string(const rjp_string&) = default;
rjp_string(rjp_string&&) = default;
rjp_string& operator=(const rjp_string&) = default;
rjp_string& operator=(rjp_string&&) = default;
using string_intermediary<detail::rjp_allocator>::string_intermediary;
rjp_string(RJP_value* r):
string_intermediary<detail::rjp_allocator>(r ? std::exchange(r->string.value, nullptr) : nullptr, r ? r->string.length : 0){}
using string_intermediary<detail::rjp_allocator>::operator=;
rjp_string& operator=(RJP_value* r){
if(!r)
return *this;
reset();
m_data = std::exchange(r->string.value, nullptr);
m_length = r->string.length;
return *this;
}
};
}
#endif

View File

@@ -0,0 +1,50 @@
#ifndef RAII_STATIC_STRING_HPP
#define RAII_STATIC_STRING_HPP
#include "raii/string_base.hpp"
namespace raii{
class static_string : public string_base
{
public:
constexpr static_string(void) = default;
template<size_t N>
constexpr static_string(const char(&str)[N]):
string_base(const_cast<char*>(str), N){}
constexpr static_string(const char* str, size_t len):
string_base(const_cast<char*>(str), len){}
static_string(const char* c):
string_base(const_cast<char*>(c), strlen(c)){}
constexpr static_string(const static_string& s):
string_base(s.m_data, s.m_length){}
constexpr static_string(static_string&& s):
string_base(s.m_data, s.m_length){}
~static_string(void) = default;
static_string& operator=(const char* c){
m_data = const_cast<char*>(c);
m_length = strlen(c);
return *this;
}
static_string& operator=(const static_string& s){
m_data = s.m_data;
m_length = s.m_length;
return *this;
}
static_string& operator=(static_string&&) = delete;
private:
char* _allocate(size_t)const override final{return nullptr;}
void _free(char*)const override final{}
char* _copy(const char*,size_t)const override final{return nullptr;}
};
}
namespace{
inline raii::static_string operator"" _ss(const char* str, size_t len){
return raii::static_string(str, len);
}
}
#endif

76
include/raii/string.hpp Normal file
View File

@@ -0,0 +1,76 @@
#ifndef RAII_STRING_HPP
#define RAII_STRING_HPP
#include "raii/string_base.hpp"
#include <new> //operator new/delete
#include <cstring> //memcpy
namespace raii{
namespace detail{
class default_allocator
{
public:
static void free(void* data){
::operator delete(data);
}
static void* allocate(size_t size){
return ::operator new(size);
}
static void* copy(const void* c, size_t size){
void* tmp = allocate(size);
memcpy(tmp, c, size);
return tmp;
}
};
}
//new allocated string
struct string : public string_intermediary<detail::default_allocator>
{
string(const string&) = default;
string(string&&) = default;
string& operator=(const string&) = default;
string& operator=(string&&) = default;
using string_intermediary<detail::default_allocator>::string_intermediary;
using string_intermediary<detail::default_allocator>::operator=;
};
namespace detail{
size_t _calc_escaped_len(const char* str);
char _escape_to_letter(char escape);
size_t _sanitize_json_copy(char* dest, const char* in);
template<class Tup, size_t I = 0>
void _js_assign(char* dest, Tup&& t, size_t offset){
size_t written = _sanitize_json_copy(dest+offset, std::get<I>(t));
if constexpr(I+2 < std::tuple_size<std::remove_reference_t<Tup>>::value){
_js_assign<Tup,I+2>(dest, std::forward<Tup>(t), offset+written);
}
}
template<class T, size_t I = 0>
size_t _calc_escaped_len_all(T&& t){
size_t len = _calc_escaped_len(std::get<I>(t));
if constexpr(I+2 < std::tuple_size<std::remove_reference_t<T>>::value){
len += _calc_escaped_len_all<T,I+2>(std::forward<T>(t));
}
return len;
}
}
template<class T, typename std::enable_if<detail::is_string<T>::value && !detail::is_concrete_string<T>::value,void>::type* = nullptr>
string json_escape(T&& t){
auto tup = t.get();
size_t len = detail::_calc_escaped_len_all(tup);
char* tmp = reinterpret_cast<char*>(string::allocator_type::allocate(len+1));
detail::_js_assign(tmp, tup, 0);
tmp[len] = 0;
return string(tmp, len);
}
string json_escape(const string_base& str);
}
#endif

View File

@@ -0,0 +1,303 @@
#ifndef RAII_STRING_BASE_HPP
#define RAII_STRING_BASE_HPP
#include <cstddef> //size_t
#include <cstring> //strlen, strcpy
#include <cstdlib> //memcpy
#include <type_traits>
#include <utility>
#include <tuple>
namespace raii{
class string_expr{};
class string_base;
namespace detail{
std::true_type is_string_helper(string_expr);
std::false_type is_string_helper(...);
template<class T>
struct is_string{
static constexpr bool value = std::is_same<std::true_type,decltype(is_string_helper(std::declval<T>()))>::value;
};
std::true_type is_string_base(string_base*);
std::false_type is_string_base(...);
template<class T>
struct is_concrete_string{
static constexpr bool value = std::is_same<std::true_type,decltype(is_string_base(std::declval<typename std::decay<T>::type*>()))>::value;
};
template<class... Args>
std::true_type is_tuple_helper(std::tuple<Args...>);
std::false_type is_tuple_helper(...);
template<class T>
struct is_tuple{
static constexpr bool value = std::is_same<std::true_type,decltype(is_tuple_helper(std::declval<T>()))>::value;
};
}
//Base of all RAII strings. Its use is allowing passing of raii strings to functions without knowing the exact type
class string_base : public string_expr
{
protected:
size_t m_length = 0;
char* m_data = nullptr;
protected:
constexpr string_base(void) = default;
//Initialize without copying
constexpr string_base(char* data, size_t len):
m_length(len), m_data(data){}
//Allocate without assigning
string_base(size_t len);
//Copy ctor (do nothing)
string_base(const string_base&){}
public:
virtual ~string_base(void) = default;
public:
//Copy from c string
string_base& operator=(const char* c);
//Copy from other string_base
string_base& operator=(const string_base& s);
//Move from other string base
template<class T, typename std::enable_if<detail::is_string<T>::value && !detail::is_concrete_string<T>::value,void>::type* = nullptr>
string_base& operator=(T&& t){
size_t len = t.length();
char* tmp;
if(len > m_length){
tmp = _allocate(len+1);
_assign(tmp, t.get(), 0);
_free(m_data);
}else{
tmp = m_data;
_assign(tmp, t.get(), 0);
}
m_data = tmp;
m_data[len] = 0;
m_length = len;
return *this;
}
//Replace managed pointer. Frees existing value
void reset(char* val = nullptr);
//Stop managing stored pointer. Does not free.
char* release(void);
//Length of string not including null terminator
size_t length(void)const;
//direct access to managed pointer
char* get(void);
const char* get(void)const;
operator char*(void);
operator const char*(void)const;
//true if m_data is not null
operator bool(void)const;
char& operator[](size_t i);
const char& operator[](size_t i)const;
protected:
template<class Tup, size_t I = 0>
static void _assign(char* dest, Tup&& t, size_t offset){
memcpy(dest+offset, std::get<I>(t), std::get<I+1>(t));
if constexpr(I+2 < std::tuple_size<Tup>::value){
_assign<Tup,I+2>(dest, std::forward<Tup>(t), offset+std::get<I+1>(t));
}
}
private:
virtual char* _allocate(size_t)const = 0;
virtual void _free(char*)const = 0;
virtual char* _copy(const char*, size_t)const = 0;
};
//Supplies all functions that string_base can't implement
template<class Allocator>
class string_intermediary : public string_base
{
public:
using allocator_type = Allocator;
public:
string_intermediary(void) = default;
string_intermediary(char* data, size_t len):
string_base(data, len){}
string_intermediary(const char* data):
string_base(strlen(data))
{
m_data = reinterpret_cast<char*>(Allocator::copy(data, m_length+1));
}
string_intermediary(size_t len):
string_base(reinterpret_cast<char*>(Allocator::allocate(len+1)), len){}
//normal copy and move ctors
string_intermediary(const string_intermediary& b):
string_base(reinterpret_cast<char*>(Allocator::copy(b.m_data, b.m_length+1)), b.m_length){}
string_intermediary(string_intermediary&& s):
string_base(std::exchange(s.m_data, nullptr), s.m_length){}
string_intermediary(const string_base& b):
string_base(reinterpret_cast<char*>(Allocator::copy(b.get(), b.length()+1)), b.length()){}
//copy from string expression
template<class T, typename std::enable_if<detail::is_string<T>::value && !detail::is_concrete_string<T>::value,void>::type* = nullptr>
string_intermediary(T&& t){
size_t len = t.length();
char* tmp = reinterpret_cast<char*>(Allocator::allocate(len+1));
_assign(tmp, t.get(), 0);
m_data = tmp;
m_data[len] = 0;
m_length = len;
}
//dtor
~string_intermediary(void){
Allocator::free(m_data);
}
string_intermediary& operator=(const string_intermediary&) = default;
string_intermediary& operator=(string_intermediary&& s){
std::swap(m_data, s.m_data);
m_length = s.m_length;
return *this;
}
using string_base::operator=;
string_intermediary operator+(const string_base& s)const{
string_intermediary tmp(reinterpret_cast<char*>(Allocator::allocate(m_length + s.length() + 1)), m_length+s.length());
memcpy(tmp.get(), m_data, m_length);
strcpy(tmp.get()+m_length, s.get());
return tmp;
}
string_intermediary operator+(const char* c)const{
size_t len = strlen(c);
string_intermediary tmp(reinterpret_cast<char*>(Allocator::allocate(m_length + len + 1)), m_length+len);
memcpy(tmp.get(), m_data, m_length);
strcpy(tmp.get()+m_length, c);
return tmp;
}
private:
char* _allocate(size_t len)const override final{
return reinterpret_cast<char*>(Allocator::allocate(len));
}
void _free(char* ptr)const override final{
Allocator::free(ptr);
}
char* _copy(const char* ptr, size_t len)const override final{
return reinterpret_cast<char*>(Allocator::copy(ptr, len));
}
};
//check for member function 'length'
namespace detail{
template<class T>
struct has_len{
template<class U, class V>
struct check;
template<class U>
static std::true_type test(check<U,decltype(&U::length)>*);
template<class U>
static std::false_type test(...);
static constexpr bool value = std::is_same<std::true_type,decltype(test<T>(0))>::value;
};
}
//Like an expression template but not really
template<class Left, class Right>
class string_cat_expr : public string_expr
{
private:
Left m_l;
Right m_r;
public:
template<class T, class U>
string_cat_expr(T&& l, U&& r):
m_l(std::forward<Left>(l)),
m_r(std::forward<Right>(r)){}
string_cat_expr(const string_cat_expr& s):
m_l(s.m_l),
m_r(s.m_r){}
string_cat_expr(string_cat_expr&& s):
m_l(s.m_l),
m_r(s.m_r){}
size_t length(void)const{
return _llen() + _rlen();
}
auto get(void){
return std::tuple_cat(_lget(), _rget());
}
private:
auto _lget(void){
if constexpr(detail::is_string<Left>::value){
if constexpr(detail::is_tuple<decltype(m_l.get())>::value){
//string_cat_expr
return m_l.get();
}else{
//string_base
return std::make_tuple(m_l.get(), m_l.length());
}
}else{
//c string
return std::make_tuple(m_l, strlen(m_l));
}
}
auto _rget(void){
if constexpr(detail::is_string<Right>::value){
if constexpr(detail::is_tuple<decltype(m_r.get())>::value){
return m_r.get();
}else{
return std::make_tuple(m_r.get(), m_r.length());
}
}else{
return std::make_tuple(m_r, strlen(m_r));
}
}
size_t _llen(void)const{
if constexpr(detail::has_len<typename std::remove_reference<Left>::type>::value){
return m_l.length();
}else{
return strlen(m_l);
}
}
size_t _rlen(void)const{
if constexpr(detail::has_len<typename std::remove_reference<Right>::type>::value){
return m_r.length();
}else{
return strlen(m_r);
}
}
};
}
template<class Right, typename std::enable_if<raii::detail::is_string<Right>::value,void>::type* = nullptr>
auto operator+(const char* left, Right&& right){
return raii::string_cat_expr<const char*,decltype(std::forward<Right>(right))>(left, std::forward<Right>(right));
}
template<class Left, typename std::enable_if<raii::detail::is_string<Left>::value,void>::type* = nullptr>
auto operator+(Left&& left, const char* right){
return raii::string_cat_expr<decltype(std::forward<Left>(left)),const char*>(std::forward<Left>(left), right);
}
template<class Left, class Right, typename std::enable_if<raii::detail::is_string<Left>::value&&raii::detail::is_string<Right>::value,void>::type* = nullptr>
auto operator+(Left&& l, Right&& r){
return raii::string_cat_expr<decltype(std::forward<Left>(l)),decltype(std::forward<Right>(r))>(std::forward<Left>(l), std::forward<Right>(r));
}
template<class Left, class Right, typename std::enable_if<raii::detail::is_string<Left>::value&&raii::detail::is_string<Right>::value,void>::type* = nullptr>
decltype(auto) operator+=(Left& l, Right&& r){
return l = (l + std::forward<Right>(r));
}
template<class Left, typename std::enable_if<raii::detail::is_string<Left>::value,void>::type* = nullptr>
decltype(auto) operator+=(Left& l, const char* r){
return l = (l + r);
}
#endif

137
include/reddit.hpp Normal file
View File

@@ -0,0 +1,137 @@
#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{
namespace detail{
class time_period{
protected:
const char* data;
public:
constexpr time_period(const char* d):
data(d){}
constexpr const char* get(void)const{
return data;
}
};
}
extern detail::time_period hour;
extern detail::time_period day;
extern detail::time_period week;
extern detail::time_period month;
extern detail::time_period year;
extern detail::time_period all;
}
enum class post_type{
image, link, text, video, unrecognized
};
class post
{
private:
raii::string m_post;
raii::rjp_string m_media_url;
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;
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::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;
post_type type(void)const;
private:
void _parse_post(void);
};
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&&);
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::detail::time_period period = time::day);
post get_top_post(const raii::string_base& subreddit, const raii::string_base& after, time::detail::time_period period = time::day);
post get_controversial_post(const raii::string_base& subreddit, time::detail::time_period period = time::day);
post get_controversial_post(const raii::string_base& subreddit, const raii::string_base& after, time::detail::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);
}

86
makefile Normal file
View File

@@ -0,0 +1,86 @@
#This program is free software: you can redistribute it and/or modify
#it under the terms of the GNU 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 General Public License for more details.
#You should have received a copy of the GNU General Public License
#along with this program. If not, see <http://www.gnu.org/licenses/>.
#Copyright 2018-2019 rexy712
SOURCE_DIRS:=src src/raii
OBJDIR:=obj
DEPDIR:=$(OBJDIR)/dep
INCLUDE_DIRS:=include
EXT:=cpp
MAIN_EXECUTABLE:=tester
CXX:=g++
CXXFLAGS:=-g -std=c++17 -Wall -pedantic -Wextra
all: CXXFLAGS+=-O0
release: CXXFLAGS+=-O2
LDFLAGS=
LDLIBS:=-lcurl -lrjp -lavformat -lavcodec -lavutil -lswresample -lswscale -lfreeimageplus
STRIP:=strip
memchk:LDFLAGS+=-fsanitize=address -fno-omit-frame-pointer -fno-optimize-sibling-calls
memchk:CXXFLAGS+=-O0 -fsanitize=address -fno-omit-frame-pointer -fno-optimize-sibling-calls
ifeq ($(OS),Windows_NT)
mkdir=mkdir $(subst /,\,$(1)) > NUL 2>&1
rm=del /F $(1) > NUL 2>&1
rmdir=rd /s /q $(1) > NUL 2>&1
move=move /y $(subst /,\,$(1)) $(subst /,\,$(2)) > NUL 2>&1
MAIN_EXECUTABLE:=$(MAIN_EXECUTABLE).exe
LDLIBS:=-lglfw3 -lSOIL -lgl3w -lm -lopengl32 -lglu32 -lgdi32 -lkernel32
else
mkdir=mkdir -p $(1)
rm=rm -f $(1)
rmdir=rm -rf $(1)
move=mv $(1) $(2)
endif
INTERNAL_CXXFLAGS=-c $(foreach dir,$(INCLUDE_DIRS),-I"$(dir)") -MMD -MP -MF"$(DEPDIR)/$(notdir $(patsubst %.o,%.d,$@))"
SOURCES:=$(foreach source,$(SOURCE_DIRS),$(foreach ext,$(EXT),$(wildcard $(source)/*.$(ext))))
OBJECTS:=$(addprefix $(OBJDIR)/,$(subst \,.,$(subst /,.,$(addsuffix .o,$(SOURCES)))))
all: $(MAIN_EXECUTABLE)
memchk: $(MAIN_EXECUTABLE)
$(MAIN_EXECUTABLE): $(OBJECTS)
$(CXX) $(LDFLAGS) $^ -o "$(basename $@)" $(LDLIBS)
.PHONY: release
release: $(OBJECTS)
$(CXX) $(LDFLAGS) $^ -o "$(basename $(MAIN_EXECUTABLE))" $(LDLIBS)
$(STRIP) --strip-all "$(MAIN_EXECUTABLE)"
define GENERATE_OBJECTS
$$(OBJDIR)/$(subst \,.,$(subst /,.,$(1))).%.o: $(1)/%
$$(CXX) $$(CXXFLAGS) $$(INTERNAL_CXXFLAGS) "$$<" -o "$$@"
endef
$(foreach dir,$(SOURCE_DIRS),$(eval $(call GENERATE_OBJECTS,$(dir))))
$(OBJECTS): | $(OBJDIR) $(DEPDIR)
$(OBJDIR):
$(call mkdir,"$@")
$(DEPDIR):
$(call mkdir,"$@")
.PHONY: clean
clean:
$(call rmdir,"$(DEPDIR)")
$(call rmdir,"$(OBJDIR)")
$(call rm,"$(MAIN_EXECUTABLE)")
-include $(wildcard $(DEPDIR)/*.d)

39
matrix_curls.txt Normal file
View File

@@ -0,0 +1,39 @@
#register user
curl -X POST -d '{"username":"<name>", "password":"<password>", "auth": {"type":"m.login.dummy"}}' "https://<homeserver>/_matrix/client/r0/register"
#set display name
curl -X PUT -d '{"displayname": "<name>"}' "https://<homeserver>/_matrix/client/r0/profile/@<userid>:<homeserver>/displayname?access_token=<>"
#set profile picture
curl -X PUT -d '{"avatar_url": "mxc://<homeserver>/<media_id>"}' "https://<homeserver>/_matrix/client/unstable/profile/@<user_id>:<homeserver>/avatar_url?access_token=<>"
#get server login methods
curl -X GET "https://<homeserver>/_matrix/client/r0/login"
#get access token
curl -X POST -d '{"type":"m.login.password", "user":"<username>", "password":"<password>"}' "https://<homeserver>/_matrix/client/r0/login"
#list joined rooms
curl -X GET "https://<homeserver>/_matrix/client/r0/joined_rooms?access_token=<>"
#create room
curl -X POST -d '{"name": "<room name>", "room_alias_name":"<unique_alias>"}' "https://<homeserver>/_matrix/client/r0/createRoom?access_token=<>"
#get room events
curl -X GET "https://<homeserver>/_matrix/client/r0/rooms/"'!'"<room_id>:<homeserver>/state?access_token=<>"
#upload file (image)
curl -X POST -H "Content-Type: image/<image type (png/jpeg)>" --data-binary @<filename> "https://<homeserver>/_matrix/media/r0/upload?access_token=<>&filename=<filename>"
#send image to room
#there is no way to caption an image at the moment, so you have to either send a separate message
#or set the filename as the caption
curl -X POST -d '{"body": "<text content/filename>","info": {"mimetype": "image/<image_type (png/jpeg)>"},"msgtype": "m.image","url": "mxc://<homeserver>/<media_id>"}' "https://<homeserver>/_matrix/client/r0/rooms/"'!'"<room_id>:<homeserver>/send/m.room.message?access_token=<>"
#send text to room
curl -X POST -d '{"body": "text", "msgtype": "m.text"}' "https://<homeserver>/_matrix/client/r0/rooms"'!'"<room_id>:<homeserver>/send/m.room.message?access_token=<>"
#use the sync api to get message updates
#get room name if it exists
curl -XGET 'https://<homeserver>/_matrix/client/r0/rooms/<roomid>/state/m.room.name?access_token=<>'

2
reddit_oauth_curls.txt Normal file
View File

@@ -0,0 +1,2 @@
curl -X POST -A "UserAgent: puller_bot" -d 'grant_type=password&username=rexy712&password=Mynameisrexy712' --user '1Bs38m2_jL9bow:dCQLZVFypHQcqqxBPAAe_kw609A' https://www.reddit.com/api/v1/access_token
curl -H "Authorization: bearer 81077812340-8u0foxY-a6mQ9B_W5wrhA1ZDZRQ" -A "UserAgent: puller_bot" "https://oauth.reddit.com/r/ProgrammerHumor/new?limit=1" | python -mjson.tool

434
src/matrix.cpp Normal file
View File

@@ -0,0 +1,434 @@
#include "matrix.hpp"
#include "raii/curl_llist.hpp"
#include "raii/static_string.hpp"
#include "raii/rjp_ptr.hpp"
#include "raii/filerd.hpp"
#include <FreeImagePlus.h>
extern "C"{
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
}
namespace matrix{
auth_data parse_auth_data(RJP_value* root){
static const char* fields[] = {"username", "password", "homeserver", "alias", "access_token"};
RJP_search_res details[5];
rjp_search_members(root, 5, fields, details, 0);
return auth_data{details[0].value,
details[1].value,
details[2].value,
details[3].value,
details[4].value};
}
//shamelessly stolen from stackoverflow (of all the things to need to steal)
constexpr static size_t intlen(int i){
if(i >= 100000) {
if(i >= 10000000) {
if(i >= 1000000000) return 10;
if(i >= 100000000) return 9;
return 8;
}
if(i >= 1000000) return 7;
return 6;
} else {
if(i >= 1000) {
if(i >= 10000) return 5;
return 4;
} else {
if(i >= 100) return 3;
if(i >= 10) return 2;
return 1;
}
}
}
static raii::string itostr(int i){
if(i == 0)
return raii::string("0");
int place = intlen(i);
raii::string ret(place);
char* buf = ret.get();
buf[place] = 0;
while(i != 0){
int rem = i % 10;
buf[--place] = rem + '0';
i /= 10;
}
return ret;
}
bot::mat_url_list::mat_url_list(const raii::string_base& homeserver, const raii::string_base& access_token){
repopulate(homeserver, access_token);
}
void bot::mat_url_list::repopulate_accesstoken(const raii::string_base& homeserver, const raii::string_base& access_token){
create_room = s_proto + homeserver + "/_matrix/client/r0/createRoom?access_token=" + access_token;
file_upload = s_proto + homeserver + "/_matrix/media/r0/upload?access_token=" + access_token;
room_list = s_proto + homeserver + "/_matrix/client/r0/joined_rooms?access_token=" + access_token;
whoami = s_proto + homeserver + "/_matrix/client/r0/account/whoami?access_token=" + access_token;
}
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";
}
void bot::mat_url_list::invalidate_accesstoken(void){
create_room.reset();
file_upload.reset();
room_list.reset();
whoami.reset();
}
bot::bot(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):
m_curl(),
m_useragent(std::move(useragent)),
m_homeserver(a.homeserver)
{
_acquire_access_token(a);
}
const raii::rjp_string& bot::access_token(void)const{
return m_access_token;
}
const raii::rjp_string& bot::userid(void)const{
return m_userid;
}
const raii::string& bot::useragent(void)const{
return m_useragent;
}
void bot::set_useragent(const raii::string_base& useragent){
m_useragent = useragent;
}
void bot::set_useragent(raii::string&& useragent){
m_useragent = std::move(useragent);
}
raii::rjp_string bot::room_alias_to_id(const raii::string_base& alias){
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){
std::vector<raii::rjp_string> 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 bot::create_room(const raii::string_base& name, const raii::string_base& alias){
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());
}
raii::rjp_string bot::upload_file(const raii::string_base& filename, const raii::curl_llist& header){
raii::filerd fd(filename);
if(!fd) return {};
return _upload_file(fd, header);
}
image_info bot::upload_image(const raii::string_base& filename){
return upload_image(filename, raii::static_string());
}
image_info bot::upload_image(const raii::string_base& filename, const raii::string_base& alias){
image_info ret;
fipImage image;
auto formattotype = [](auto type) -> const char*{
switch(type){
case FIF_JPEG: return "jpeg";
case FIF_PNG: return "png";
case FIF_GIF: return "gif";
default:
;
};
return nullptr;
};
auto type = fipImage::identifyFIF(filename.get());
image.load(filename.get());
raii::filerd fd(filename, "rb");
if(!fd) return {};
ret.width = image.getWidth();
ret.height = image.getHeight();
ret.filetype = formattotype(type);
ret.filename = alias ? alias : filename;
ret.filesize = fd.length();
raii::curl_llist header(raii::string("Content-Type: image/" + ret.filetype));
ret.fileurl = _upload_file(fd, header);
fd.reset(fopen("anewNewimage", "w+"));
//TODO remove temporary file
image.makeThumbnail(500);
FreeImageIO fileout;
fileout.read_proc = [](void* ptr, unsigned int size, unsigned int nmemb, void* fp) -> unsigned int{
return fread(ptr, size, nmemb, (FILE*)fp);
};
fileout.write_proc = [](void* ptr, unsigned int size, unsigned int nmemb, void* fp) -> unsigned int{
return fwrite(ptr, size, nmemb, (FILE*)fp);
};
fileout.seek_proc = [](void* fp, long int off, int whence) -> int{
return fseek((FILE*)fp, off, whence);
};
fileout.tell_proc = [](void* fp) -> long int{
return ftell((FILE*)fp);
};
bool b;
switch(type){
case FIF_JPEG:
b = image.saveToHandle(type, &fileout, (fi_handle)(fd.get()), JPEG_QUALITYGOOD | JPEG_SUBSAMPLING_411);
break;
case FIF_PNG:
b = image.saveToHandle(type, &fileout, (fi_handle)(fd.get()), PNG_Z_BEST_COMPRESSION);
break;
case FIF_GIF:
b = image.saveToHandle(type, &fileout, (fi_handle)(fd.get()));
break;
default:
;
};
ret.thumb_width = image.getWidth();
ret.thumb_height = image.getHeight();
fd.rewind();
ret.thumburl = _upload_file(fd, header);
ret.thumbsize = fd.length();
return ret;
}
raii::rjp_string bot::upload_video(const raii::string_base& filename){
image_info ret;
av_register_all();
AVFormatContext* context = NULL;
avformat_open_input(&context, filename.get(), NULL, NULL);
avformat_find_stream_info(context, NULL);
av_dump_format(context, 0, filename.get(), false);
avformat_close_input(&context);
raii::filerd fd(filename);
if(!fd) return {};
raii::curl_llist header(raii::string("Content-Type: video/mp4"));
return _upload_file(fd, header);
}
raii::rjp_string bot::send_image(const raii::string_base& room, const image_info& image){
raii::string mimetype = "\"mimetype\":\"image/" + raii::json_escape(image.filetype) + "\"";
raii::string url = raii::json_escape(image.fileurl);
const raii::string_base* thumburl;
if(image.thumburl)
thumburl = &image.thumburl;
else
thumburl = &image.fileurl;
raii::string body =
"{"
"\"body\":\"" + raii::json_escape(image.filename) + "\","
"\"info\":{"
"\"h\":" + itostr(image.height) + "," +
mimetype + ","
"\"size\":" + itostr(image.filesize) + ","
"\"thumnail_info\":{"
"\"h\":" + itostr(image.thumb_height) + "," +
mimetype + ","
"\"size\":" + itostr(image.thumbsize) + ","
"\"w\":" + itostr(image.thumb_width) +
"},"
"\"thumbnail_url\":\"" + (*thumburl) + "\","
"\"w\":" + itostr(image.width) +
"},"
"\"msgtype\":\"m.image\","
"\"url\":\"" + url + "\""
"}";
raii::rjp_string reply = _post_and_find(
body,
raii::string("https://" + m_homeserver + "/_matrix/client/r0/rooms/" + m_curl.encode(room) + "/send/m.room.message?access_token=" + m_access_token),
raii::curl_llist(),
"event_id"_ss);
return reply;
}
raii::rjp_string bot::send_video(const raii::string_base& room, const raii::string_base& file_url, const raii::string_base& filetype, const raii::string_base& filename){
raii::rjp_string reply = _post_and_find(raii::string("{\"body\":\"" + raii::json_escape(filename) + "\",\"info\":{\"w\":854,\"mimetype\":\"video/mp4\"},\"msgtype\":\"m.video\",\"url\":\"" + raii::json_escape(file_url) + "\"}"),
raii::string("https://" + m_homeserver + "/_matrix/client/r0/rooms/" + m_curl.encode(room) + "/send/m.room.message?access_token=" + m_access_token),
raii::curl_llist(),
"event_id"_ss);
return reply;
}
raii::rjp_string bot::send_message(const raii::string_base& room, const raii::string_base& text){
return _post_and_find(raii::string("{\"body\":\""_ss + raii::json_escape(text) + "\",\"msgtype\":\"m.text\"}"_ss),
raii::string("https://" + m_homeserver + "/_matrix/client/r0/rooms/" + m_curl.encode(room) + "/send/m.room.message?access_token=" + m_access_token),
raii::curl_llist(),
"event_id"_ss);
}
void bot::logout(void){
_get_curl(raii::string("https://" + m_homeserver + "/_matrix/client/r0/logout?access_token=" + m_access_token));
m_urls.invalidate_accesstoken();
}
raii::rjp_string bot::_upload_file(raii::filerd& fp, const raii::curl_llist& header){
raii::string fileurl;
m_curl.postreq();
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;
}
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;
}
raii::string bot::_get_curl(const raii::string_base& url){
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 bot::_post_curl(const raii::string_base& postdata, const raii::string_base& url, const raii::curl_llist& header){
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;
}
raii::rjp_string bot::_post_and_find(const raii::string_base& data, const raii::string_base& url,
const raii::curl_llist& header, const raii::string_base& target)
{
raii::string reply = _post_curl(data, url, header);
if(!reply)
return {};
return _curl_reply_search(reply, target);
}
raii::rjp_string bot::_get_and_find(const raii::string_base& url, const raii::string_base& target){
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){
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 bot::_set_curl_defaults(void){
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);
}
raii::string bot::_request_access_token(const auth_data& a){
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 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 bot::_acquire_access_token(const auth_data& a){
_set_curl_defaults();
if(a.access_token){
m_access_token = a.access_token;
m_urls = mat_url_list(m_homeserver, m_access_token);
raii::string reply = _get_curl(m_urls.whoami);
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);
m_userid = raii::rjp_string(id.value);
}else{
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};
m_urls = mat_url_list(m_homeserver, m_access_token);
token = rjp_search_member(root.get(), "user_id", 0);
m_userid = raii::rjp_string{token.value};
}
}
}

41
src/raii/curl_llist.cpp Normal file
View File

@@ -0,0 +1,41 @@
#include "raii/curl_llist.hpp"
#include <utility> //exchange
namespace raii{
curl_llist::curl_llist(curl_llist&& l)noexcept:
m_data(std::exchange(l.m_data, nullptr)){}
curl_llist::~curl_llist(void){
curl_slist_free_all(m_data);
}
curl_llist& curl_llist::operator=(curl_llist&& l)noexcept{
std::swap(m_data, l.m_data);
return *this;
}
curl_llist& curl_llist::operator+=(const char* data){
m_data = curl_slist_append(m_data, data);
return *this;
}
curl_llist::operator curl_slist*(void){
return m_data;
}
curl_llist::operator const curl_slist*(void)const{
return m_data;
}
curl_slist* curl_llist::get(void){
return m_data;
}
const curl_slist* curl_llist::get(void)const{
return m_data;
}
void curl_llist::reset(curl_slist* nd){
curl_slist_free_all(m_data);
m_data = nd;
}
}

89
src/raii/curler.cpp Normal file
View File

@@ -0,0 +1,89 @@
#include "raii/curler.hpp"
#include <utility> //exchange
namespace raii{
//RAII wrapper for CURL* with some convenience functions added
curler::curler(void):
m_curl(curl_easy_init()){}
curler::curler(const curler& c):
m_curl(curl_easy_duphandle(c.m_curl)){}
curler::curler(curler&& c)noexcept:
m_curl(std::exchange(c.m_curl, nullptr)){}
curler::~curler(void){
curl_easy_cleanup(m_curl);
}
curler& curler::postreq(void){
return setopt(CURLOPT_POST, 1L);
}
curler& curler::getreq(void){
return setopt(CURLOPT_HTTPGET, 1L);
}
curler& curler::setheader(const curl_llist& h){
return setopt(CURLOPT_HTTPHEADER, h.get());
}
curler& curler::seturl(const char* s){
return setopt(CURLOPT_URL, s);
}
curler& curler::seturl(const string_base& s){
return seturl(s.get());
}
curler& curler::setuseragent(const char* s){
return setopt(CURLOPT_USERAGENT, s);
}
curler& curler::setuseragent(const string_base& s){
return setuseragent(s.get());
}
curler& curler::setuserpwd(const char* s){
return setopt(CURLOPT_USERPWD, s);
}
curler& curler::setuserpwd(const string_base& s){
return setuserpwd(s.get());
}
curler& curler::setpostdata(const char* s, curl_off_t len){
setopt(CURLOPT_POSTFIELDS, s);
setopt(CURLOPT_POSTFIELDSIZE_LARGE, len);
return *this;
}
curler& curler::setpostdata(const string_base& s){
setpostdata(s.get(), s.length());
return *this;
}
curler& curler::forcessl(long version){
setopt(CURLOPT_SSL_VERIFYPEER, 1);
setopt(CURLOPT_SSL_VERIFYHOST, 1);
setopt(CURLOPT_SSLVERSION, version);
return *this;
}
void curler::reset(void){
curl_easy_reset(m_curl);
}
auto curler::perform(void) -> decltype(curl_easy_perform(m_curl)){
return curl_easy_perform(m_curl);
}
curl_string curler::encode(const char* data, int len){
char* tmp = curl_easy_escape(m_curl, data, len);
return curl_string(tmp, strlen(tmp));
}
curl_string curler::decode(const char* data, int* outlen, int len){
return curl_string(curl_easy_unescape(m_curl, data, len, outlen), *outlen);
}
CURL* curler::get(void){
return m_curl;
}
const CURL* curler::get(void)const{
return m_curl;
}
curler::operator CURL*(void){
return m_curl;
}
curler::operator const CURL*(void)const{
return m_curl;
}
}

78
src/raii/filerd.cpp Normal file
View File

@@ -0,0 +1,78 @@
#include "raii/filerd.hpp"
#include <cstdio> //fopen, fclose
#include <utility> //exchange, swap
namespace raii{
filerd::filerd(const char* f, const char* mode):
m_fp(fopen(f, mode)){}
filerd::filerd(filerd&& f):
m_fp(std::exchange(f.m_fp, nullptr)){}
filerd::~filerd(void){
if(m_fp)
fclose(m_fp);
}
filerd& filerd::operator=(filerd&& f){
std::swap(m_fp, f.m_fp);
return *this;
}
void filerd::reset(FILE* fp){
if(m_fp)
fclose(m_fp);
m_fp = fp;
}
FILE* filerd::release(void){
return std::exchange(m_fp, nullptr);
}
size_t filerd::length(void){
if(!m_fp)
return 0;
size_t tmp, ret;
tmp = ftell(m_fp);
fseek(m_fp, 0, SEEK_END);
ret = ftell(m_fp);
fseek(m_fp, tmp, SEEK_SET);
return ret;
}
size_t filerd::position(void)const{
return ftell(m_fp);
}
void filerd::rewind(size_t pos){
fseek(m_fp, pos, SEEK_SET);
}
filerd::operator FILE*(void){
return m_fp;
}
filerd::operator const FILE*(void)const{
return m_fp;
}
FILE* filerd::get(void){
return m_fp;
}
const FILE* filerd::get(void)const{
return m_fp;
}
filerd::operator bool(void)const{
return m_fp;
}
size_t filerd::read(char* dest, size_t bytes){
return fread(dest, 1, bytes, m_fp);
}
raii::string filerd::read(size_t bytes){
char* tmp = reinterpret_cast<char*>(raii::string::allocator_type::allocate(bytes));
size_t written = read(tmp, bytes);
return raii::string(tmp, written);
}
size_t filerd::write(const char* c, size_t bytes){
return fwrite(c, 1, bytes, m_fp);
}
size_t filerd::write(const raii::string_base& c){
return write(c.get(), c.length());
}
}

140
src/raii/string_base.cpp Normal file
View File

@@ -0,0 +1,140 @@
#include "raii/string_base.hpp"
#include "raii/string.hpp"
#include <utility> //exchange, swap
#include <cstdlib> //memcpy
#include <cstring> //strcpy, strlen
#include <new> //bad_alloc
namespace raii{
string_base::string_base(size_t len):
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)
throw std::bad_alloc{};
}
m_length = len;
return *this;
}
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)
throw std::bad_alloc{};
}
m_length = s.m_length;
return *this;
}
void string_base::reset(char* val){
_free(m_data);
m_data = val;
}
size_t string_base::length(void)const{
return m_length;
}
char* string_base::get(void){
return m_data;
}
const char* string_base::get(void)const{
return m_data;
}
string_base::operator bool(void)const{
return m_data;
}
string_base::operator char*(void){
return m_data;
}
string_base::operator const char*(void)const{
return m_data;
}
char* string_base::release(void){
return std::exchange(m_data, nullptr);
}
char& string_base::operator[](size_t i){
return m_data[i];
}
const char& string_base::operator[](size_t i)const{
return m_data[i];
}
namespace detail{
size_t _calc_escaped_len(const char* str){
size_t ret = 0;
for(const char* c = str;*c;++c){
switch(*c){
case '\\':
case '"':
case '/':
case '\n':
case '\r':
case '\b':
case '\f':
case '\t':
++ret;
break;
};
++ret;
}
return ret;
}
char _escape_to_letter(char escape){
switch(escape){
case '\n':
return 'n';
case '\r':
return 'r';
case '\b':
return 'b';
case '\f':
return 'f';
case '\t':
return 't';
};
return '\\';
}
size_t _sanitize_json_copy(char* dest, const char* in){
size_t pos = 0;
for(const char* c = in;*c;++c){
switch(*c){
case '"':
case '\\':
case '/':
dest[pos++] = '\\';
dest[pos++] = *c;
break;
case '\n':
case '\r':
case '\b':
case '\f':
case '\t':
dest[pos++] = '\\';
dest[pos++] = _escape_to_letter(*c);
break;
default:
dest[pos++] = *c;
};
}
dest[pos] = 0;
return pos;
}
}
string json_escape(const string_base& str){
size_t len = detail::_calc_escaped_len(str.get());
char* tmp = reinterpret_cast<char*>(string::allocator_type::allocate(len+1));
detail::_sanitize_json_copy(tmp, str);
tmp[len] = 0;
return string(tmp, len);
}
}

438
src/reddit.cpp Normal file
View File

@@ -0,0 +1,438 @@
#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
namespace reddit{
namespace time{
detail::time_period hour = "hour";
detail::time_period day = "day";
detail::time_period week = "week";
detail::time_period month = "month";
detail::time_period year = "year";
detail::time_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* root){
RJP_search_res media = rjp_search_member(root, "media", 0);
if(!media.value)
return raii::rjp_string{};
media = rjp_search_member(media.value, "reddit_video", 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 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){
if(raii::rjp_string res = media_search(root))
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[] = "i.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();
}
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"};
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_members(data.value, num_searches, search_items, results, 0);
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)){
if(raii::rjp_string tmp = preview_search(data.value)){
m_media_url = std::move(tmp);
m_type = post_type::video;
}
}else{
m_media_url += ".jpg"; //imgur is dumb
m_type = post_type::image;
}
}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") || !strcmp(m_post_hint, "rich:video")){
raii::rjp_string res = media_search(data.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;
}
//assume text post for other
else{
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)){
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& 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::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;
}
post_type post::type(void)const{
return m_type;
}
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);
}
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::detail::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::detail::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::detail::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::detail::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);
}
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.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);
}
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};
}
}

169
src/test.cpp Normal file
View File

@@ -0,0 +1,169 @@
#include <cstdio>
#include <rjp.h>
#include <string.h>
#include <utility>
#include <tuple>
#include <memory>
#include <algorithm>
#include <functional>
#include <curl/curl.h>
#define DEBUG
#ifdef DEBUG
# define DEBUG_PRINT(...) do{printf(__VA_ARGS__);}while(0)
#else
# define DEBUG_PRINT(...) do{}while(0)
#endif
#include "raii/curler.hpp"
#include "raii/filerd.hpp"
#include "raii/rjp_string.hpp"
#include "raii/string.hpp"
#include "raii/rjp_ptr.hpp"
#include "raii/static_string.hpp"
#include "reddit.hpp"
#include "matrix.hpp"
//Get username/password for reddit account and bot. Plus a useragent string
std::tuple<reddit::auth_data,matrix::auth_data,raii::rjp_string> parse_data_file(const raii::rjp_ptr& root){
RJP_search_res res = rjp_search_member(root.get(), "reddit", 0);
reddit::auth_data red_ret = reddit::parse_auth_data(res.value);
res = rjp_search_member(root.get(), "matrix", 0);
matrix::auth_data mat_ret = matrix::parse_auth_data(res.value);
res = rjp_search_member(root.get(), "useragent", 0);
return std::tuple<reddit::auth_data,matrix::auth_data,raii::rjp_string>(std::move(red_ret), std::move(mat_ret), raii::rjp_string(res.value));
}
//Read in file containing username/password details
raii::rjp_ptr read_data_file(const char* file){
raii::filerd fp(file);
size_t blen;
if(!fp){
return nullptr;
}
blen = fp.length();
raii::string buff(blen);
fread(buff, blen, 1, fp);
buff[blen] = 0;
return raii::rjp_ptr(rjp_parse(buff));
}
size_t filewrite_response(char* ptr, size_t size, size_t nmemb, void* userdata){
fwrite(ptr, size, nmemb, reinterpret_cast<FILE*>(userdata));
return nmemb*size;
}
void file_output_curl(raii::curler& curl, const raii::string_base& url){
//Download the post's image
raii::filerd fp("testout", "w");
if(!fp)
fprintf(stderr, "unable to open file for writing\n");
curl.seturl(url);
curl.setopt(CURLOPT_BUFFERSIZE, 102400L);
curl.setopt(CURLOPT_NOPROGRESS, 1L);
curl.setopt(CURLOPT_FOLLOWLOCATION, 1L);
curl.setopt(CURLOPT_MAXREDIRS, 50L);
curl.forcessl(CURL_SSLVERSION_TLSv1_2);
curl.setopt(CURLOPT_WRITEFUNCTION, filewrite_response);
curl.setopt(CURLOPT_WRITEDATA, fp.get());
curl.perform();
}
void write_to_file(const char* file, const raii::string_base& data){
raii::filerd out(file, "a");
out.write(data);
out.write("\n"_ss);
}
int main(){
//Read data file
DEBUG_PRINT("reading data file \"data\"\n");
raii::rjp_ptr root = read_data_file("data");
if(!root){
fprintf(stderr, "Could not open data file\n");
return 1;
}
//Parse data file
auto [reddit_auth,matrix_auth,useragent] = parse_data_file(root);
if(!(reddit_auth && matrix_auth && useragent)){
fprintf(stderr, "Missing data field\n");
return 2;
}
//Get reddit post
reddit::bot mybot(reddit_auth, useragent);
DEBUG_PRINT("reddit bot initialized\n");
matrix::bot matbot(matrix_auth, useragent);
DEBUG_PRINT("matrix bot initialized\n");
reddit::post reply;
{
int retries = 5;
do{
reply = mybot.get_new_post("ProgrammerHumor"_ss, reply.name());
if(reply.type() != reddit::post_type::text)
break;
--retries;
DEBUG_PRINT("Not an image.\nTODO, search for another\n");
if(reply.post_hint())
DEBUG_PRINT("post_hint: %s\n", reply.post_hint().get());
}while(retries);
}
if(!reply){
fprintf(stderr, "Did not recieve a reply!\n");
return 3;
}
write_to_file("post.log", reply.raw());
DEBUG_PRINT("name: %s\n", reply.name().get());
DEBUG_PRINT("title: %s\n", reply.title().get());
DEBUG_PRINT("author: %s\n", reply.author().get());
DEBUG_PRINT("mediaurl: %s\n", reply.mediaurl().get());
DEBUG_PRINT("posturl: %s\n", reply.posturl().get());
raii::curler curl;
if(reply.type() == reddit::post_type::image)
{
DEBUG_PRINT("Got an image\n");
file_output_curl(curl, reply.mediaurl());
auto img_data = matbot.upload_image("testout"_ss, reply.name());
auto val = matbot.send_image("!QeYfNDCRodtNohhnaI:matrix.org"_ss, img_data);
DEBUG_PRINT("image event: %s\n", val.get());
val = matbot.send_message("!QeYfNDCRodtNohhnaI:matrix.org"_ss, raii::string(reply.title() + "\n" + reply.posturl()));
DEBUG_PRINT("text event: %s\n", val.get());
}
else if(reply.type() == reddit::post_type::video)
{
DEBUG_PRINT("Got a video\n");
file_output_curl(curl, reply.mediaurl());
auto vid_url = matbot.upload_video("testout"_ss);
//rudimentary video sending
auto val = matbot.send_video("!QeYfNDCRodtNohhnaI:matrix.org"_ss, vid_url, "mp4"_ss, raii::static_string("testout"));
val = matbot.send_message("!QeYfNDCRodtNohhnaI:matrix.org"_ss, raii::string(reply.title() + "\n" + reply.posturl()));
}
else if(reply.type() == reddit::post_type::link)
{
DEBUG_PRINT("Got a link\n");
return 0;
}
else
{
DEBUG_PRINT("post_hint: %s\n", reply.post_hint().get());
DEBUG_PRINT("TODO HANDLE THIS TYPE OF POST\n");
return 0;
}
}

37
upload_bot_image.sh Executable file
View File

@@ -0,0 +1,37 @@
#!/bin/bash
#A script to easily upload an image to a matrix homeserver for use as a profile picture of the bot.
#Will print out the URI of the uploaded image if it succeeds. If it fails, I have no guess as to what might happen tbh
image_file="$1"
homeserver="$2"
username="$3"
password="$4"
if [ -z "$image_file" ];then
echo "need image file to upload"
exit 1
fi
if [ -z "$homeserver" ];then
echo "need homeserver to upload to"
exit 2
fi
if [ -z "$username" ];then
echo "need username"
exit 3
fi
if [ -z "$password" ];then
stty -echo
printf "Password: "
read password
stty echo
echo
fi
image_type="$(file "$image_file" | grep -o "[^ \t]* image data" | tr '[:upper:]' '[:lower:]' | cut -d' ' -f1)"
access_token="$(curl -X POST -d '{"type":"m.login.password", "user":"'"$username"'", "password":"'"${password}"'"}' "https://${homeserver}/_matrix/client/r0/login" | grep "access_token" | sed -e 's/^[ \t]*//' -e 's/[\"\,]//g' | cut -d' ' -f2)"
media_id="$(curl -X POST -H "Content-Type: image/${image_type}" --data-binary @"$image_file" "https://${homeserver}/_matrix/media/r0/upload?access_token=${access_token}&filename=$(basename $image_file)" | cut -d':' -f2- | sed -e 's/[\"\}]//g')"
echo "$media_id"

2
youtube_curl.txt Normal file
View File

@@ -0,0 +1,2 @@
curl -XGET http://www.youtube.com/get_video_info?video_id=<>