initial commit to allow me to stash
This commit is contained in:
7
.gitignore
vendored
Normal file
7
.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
obj
|
||||
tester
|
||||
*.log
|
||||
data
|
||||
*.swp
|
||||
*.save
|
||||
testout
|
||||
11
TODO
Normal file
11
TODO
Normal 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
138
include/matrix.hpp
Normal 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
|
||||
49
include/raii/curl_llist.hpp
Normal file
49
include/raii/curl_llist.hpp
Normal 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
|
||||
41
include/raii/curl_string.hpp
Normal file
41
include/raii/curl_string.hpp
Normal 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
55
include/raii/curler.hpp
Normal 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
47
include/raii/filerd.hpp
Normal 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
23
include/raii/rjp_ptr.hpp
Normal 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
|
||||
55
include/raii/rjp_string.hpp
Normal file
55
include/raii/rjp_string.hpp
Normal 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
|
||||
50
include/raii/static_string.hpp
Normal file
50
include/raii/static_string.hpp
Normal 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
76
include/raii/string.hpp
Normal 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
|
||||
303
include/raii/string_base.hpp
Normal file
303
include/raii/string_base.hpp
Normal 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
137
include/reddit.hpp
Normal 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
86
makefile
Normal 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
39
matrix_curls.txt
Normal 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
2
reddit_oauth_curls.txt
Normal 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
434
src/matrix.cpp
Normal 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
41
src/raii/curl_llist.cpp
Normal 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
89
src/raii/curler.cpp
Normal 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
78
src/raii/filerd.cpp
Normal 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
140
src/raii/string_base.cpp
Normal 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
438
src/reddit.cpp
Normal 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
169
src/test.cpp
Normal 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
37
upload_bot_image.sh
Executable 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
2
youtube_curl.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
curl -XGET http://www.youtube.com/get_video_info?video_id=<>
|
||||
|
||||
Reference in New Issue
Block a user