diff --git a/CMakeLists.txt b/CMakeLists.txt index d82878e..8bdb8bf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,9 +1,9 @@ cmake_minimum_required (VERSION 3.7) project (mastodon-cpp - VERSION 0.0.0 + VERSION 0.0.1 LANGUAGES CXX ) -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -rdynamic") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14") set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Wall") # Write SHA1 hash and date of current git commit to a header file. Also version. @@ -38,7 +38,7 @@ set_target_properties(mastodon-cpp PROPERTIES SOVERSION ${mastodon-cpp_VERSION_MAJOR} ) install(TARGETS mastodon-cpp DESTINATION lib) -#target_link_libraries(mastodon-cpp pthread boost_system boost_filesystem dl) +target_link_libraries(mastodon-cpp pthread boost_system ssl crypto) install(FILES ${PROJECT_SOURCE_DIR}/src/lib/mastodon-cpp.hpp DESTINATION include) # Example client @@ -59,19 +59,3 @@ if(NOT NOTESTS) add_test(${bin} ${bin}) endforeach() endif() - - -# Binary and source packages -# Some variables are not respected, it seems -# include(InstallRequiredSystemLibraries) -# set(CPACK_GENERATOR "TGZ") -# set(CPACK_RESOURCE_FILE_LICENSE "${PROJECT_SOURCE_DIR}/LICENSE") -# set(CPACK_PACKAGE_VERSION_MAJOR "${cdcl_VERSION_MAJOR}") -# set(CPACK_PACKAGE_VERSION_MINOR "${cdcl_VERSION_MINOR}") -# set(CPACK_PACKAGE_VERSION_PATCH "${cdcl_VERSION_PATCH}") -# set(CPACK_PACKAGE_DESCRIPTION_SUMMARY, "Edit checklists on CalDAV servers.") -# set(CPACK_PACKAGE_DESCRIPTION_FILE "${PROJECT_SOURCE_DIR}/README.md") -# set(CPACK_PACKAGE_CHECKSUM, "SHA256") -# set(CPACK_SOURCE_PACKAGE_FILE_NAME, "${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}") -# set(CPACK_SOURCE_IGNORE_FILES, "/\\\\.git/;/build/;.\\\\.sublime-") -# include (CPack) diff --git a/README.md b/README.md index 7b09c2b..ea522e9 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,6 @@ -**mastodon-cpp** is an interface for the Mastodon API. +**mastodon-cpp** is a CPP wrapper for the Mastodon API. The aim is to be as simple as possible. +The library takes care of the network stuff. You submit a query and get the raw JSON. +All versions below 1.0.0 (SOVERSION 0) are considered insecure, unstable and can change drastically any time. # Install ## Dependencies @@ -18,24 +20,39 @@ make # Usage -… + Mastodon::API masto("social.example.com", "access token"); + std::cout << masto.get(Mastodon::API::v1::accounts_id, "12345") << '\n'; -# Status of implementation +There is an example in `src/example`. - * [ ] GET /api/v1/accounts/:id - * [ ] GET /api/v1/accounts/verify_credentials +# TODO + * Version 0.1.0 + * [ ] Implement all GET methods + * [ ] Proper error handling + * [x] Network stuff + * Version 0.2.0 + * [ ] Implement all PATCH methods + * [ ] Implement all POST methods + * [ ] Implement all DELETE methods + * Later + [ ] Escape user input + [ ] Asynchronous I/O + +## Status of implementation + * [x] GET /api/v1/accounts/:id + * [x] GET /api/v1/accounts/verify_credentials * [ ] PATCH /api/v1/accounts/update_credentials - * [ ] GET /api/v1/accounts/:id/followers - * [ ] GET /api/v1/accounts/:id/following - * [ ] GET /api/v1/accounts/:id/statuses + * [x] GET /api/v1/accounts/:id/followers + * [x] GET /api/v1/accounts/:id/following + * [x] GET /api/v1/accounts/:id/statuses * [ ] POST /api/v1/accounts/:id/follow * [ ] POST /api/v1/accounts/:id/unfollow - * [ ] GET /api/v1/accounts/:id/block - * [ ] GET /api/v1/accounts/:id/unblock - * [ ] GET /api/v1/accounts/:id/mute - * [ ] GET /api/v1/accounts/:id/unmute - * [ ] GET /api/v1/accounts/relationships - * [ ] GET /api/v1/accounts/search + * [ ] POST /api/v1/accounts/:id/block + * [ ] POST /api/v1/accounts/:id/unblock + * [ ] POST /api/v1/accounts/:id/mute + * [ ] POST /api/v1/accounts/:id/unmute + * [x] GET /api/v1/accounts/relationships + * [x] GET /api/v1/accounts/search * [ ] POST /api/v1/apps * [ ] GET /api/v1/blocks * [ ] GET /api/v1/favourites @@ -69,7 +86,7 @@ * [ ] GET /api/v1/timelines/list/:list_id # Copyright - Copyright © 2017 tastytea . + Copyright © 2018 tastytea . License GPLv3: GNU GPL version 3 . This program comes with ABSOLUTELY NO WARRANTY. This is free software, and you are welcome to redistribute it under certain conditions. diff --git a/src/api_get.cpp b/src/api_get.cpp new file mode 100644 index 0000000..189443e --- /dev/null +++ b/src/api_get.cpp @@ -0,0 +1,95 @@ +/* This file is part of mastodon-cpp. + * Copyright © 2018 tastytea + * + * 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, version 3. + * + * 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 . + */ + +#include +#include +#include +#include "mastodon-cpp.hpp" + +using namespace Mastodon; +using std::string; +using std::cerr; +const string API::get(const Mastodon::API::v1 &method) +{ + const std::vector v{}; + return get(method, v); +} + +const string API::get(const Mastodon::API::v1 &method, + const std::vector ¶meters) +{ + string strmethod = ""; + switch (method) + { + case v1::accounts_verify_credentials: + strmethod = "/api/v1/accounts/verify_credentials"; + break; + default: + cerr << "ERROR: Invalid method.\n"; + break; + } + + string answer; + _http.request_sync(http::method::GET, strmethod, answer); + return answer; +} + +const string API::get(const Mastodon::API::v1 &method, + const string &argument) +{ + const std::vector v; + return get(method, argument, v); +} +const string API::get(const Mastodon::API::v1 &method, + const string &argument, + const std::vector ¶meters) +{ + string strmethod = ""; + switch (method) + { + case v1::accounts_id: + strmethod = "/api/v1/accounts/" + argument; + break; + case v1::accounts_id_followers: + strmethod = "/api/v1/accounts/" + argument + "/followers"; + break; + case v1::accounts_id_following: + strmethod = "/api/v1/accounts/" + argument + "/following"; + break; + case v1::accounts_id_statuses: + strmethod = "/api/v1/accounts/" + argument + "/statuses"; + break; + case v1::accounts_relationships: + strmethod = "/api/v1/accounts/relationships?id=" + argument; + break; + case v1::accounts_search: + strmethod = "/api/v1/accounts/search?q=" + argument; + break; + default: + cerr << "ERROR: Invalid method.\n"; + return ""; + break; + } + + string answer; + _http.request_sync(http::method::GET, strmethod, answer); + return answer; +} + +const string API::get(const std::string &method) +{ + return method; +} diff --git a/src/example/example.cpp b/src/example/example.cpp index cdaa9f3..67ae8c9 100644 --- a/src/example/example.cpp +++ b/src/example/example.cpp @@ -1,22 +1,19 @@ /* This file is part of mastodon-cpp. - * Copyright © 2018 tastytea - * - * 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, version 3. - * - * 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 . */ +#include #include "../mastodon-cpp.hpp" int main(int argc, char *argv[]) { - // + if (argc < 3) + { + std::cerr << "usage: " << argv[0] << " \n"; + return 1; + } + + Mastodon::API masto(argv[1], argv[2]); + + std::cout << masto.get(Mastodon::API::v1::accounts_id, + "44897") << '\n'; } diff --git a/src/http.cpp b/src/http.cpp new file mode 100644 index 0000000..6acd0f5 --- /dev/null +++ b/src/http.cpp @@ -0,0 +1,157 @@ +/* This file is part of mastodon-cpp. + * Copyright © 2018 tastytea + * + * 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, version 3. + * + * 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 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include "mastodon-cpp.hpp" + +using namespace Mastodon; +using std::string; +using std::cerr; + +using boost::asio::ip::tcp; +namespace ssl = boost::asio::ssl; +typedef ssl::stream ssl_socket; + +API::http::http(const string &instance, const string &access_token, + const string &useragent) +: _instance(instance) +, _access_token(access_token) +, _useragent(useragent) +, _ctx(ssl::context::tlsv12) +, _resolver(_io_service) +, _socket(_io_service, _ctx) +{ + _ctx.set_options(ssl::context::tlsv12 | ssl::context::tlsv11 | + ssl::context::no_sslv3 | ssl::context::no_sslv2 | + ssl::context::no_tlsv1); + _ctx.set_default_verify_paths(); +} + +const std::uint16_t API::http::request_sync(const method &meth, + const string &path, + string &answer) +{ + return request_sync(meth, path, "", answer); +} + +const std::uint16_t API::http::request_sync(const method &meth, + const string &path, + const string &data, + string &answer) +{ + try + { + tcp::resolver::query query(_instance, "https"); + tcp::resolver::iterator endpoint_iterator = _resolver.resolve(query); + boost::asio::connect(_socket.lowest_layer(), endpoint_iterator); + _socket.lowest_layer().set_option(tcp::no_delay(true)); + // Server Name Indication (SNI) + SSL_set_tlsext_host_name(_socket.native_handle(), _instance.c_str()); + + _socket.set_verify_mode(ssl::verify_peer); + _socket.set_verify_callback(ssl::rfc2818_verification(_instance)); + + _socket.handshake(ssl_socket::client); + + boost::asio::streambuf request; + std::ostream request_stream(&request); + switch (meth) + { + case http::method::GET: + request_stream << "GET"; + request_stream << " " << path; + break; + // case http::method::PATCH: + // request_stream << "PATCH"; + // break; + // case http::method::POST: + // request_stream << "POST"; + // break; + // case http::method::DELETE: + // request_stream << "DELETE"; + // break; + default: + cerr << "NOT IMPLEMENTED\n"; + return 0xffff; + } + request_stream << " HTTP/1.0\r\n"; + request_stream << "Host: " << _instance << "\r\n"; + request_stream << "Accept: */*\r\n"; + request_stream << "Connection: close\r\n"; + request_stream << "User-Agent: " << _useragent << "\r\n"; + request_stream << "Authorization: Bearer " + << _access_token << "\r\n\r\n"; + boost::asio::write(_socket, request); + + boost::asio::streambuf response; + boost::asio::read_until(_socket, response, "\r\n"); + + // Check that response is OK. + std::istream response_stream(&response); + std::string http_version; + std::uint16_t status_code; + std::string status_message; + response_stream >> http_version; + response_stream >> status_code; + std::getline(response_stream, status_message); + if (!response_stream || http_version.substr(0, 5) != "HTTP/") + { + cerr << "Invalid response\n"; + return 0xffff; + } + if (status_code != 200) + { + cerr << "Response returned with status code " << status_code + << ": " << status_message << "\n"; + return 0xffff; + } + + // Read headers + boost::asio::read_until(_socket, response, "\r\n\r\n"); + std::string header; + while (std::getline(response_stream, header) && header != "\r") + {} + //response.consume(response.size()); + + // Read body + boost::system::error_code error; + answer = ""; + std::ostringstream ss; + while (boost::asio::read(_socket, response, + boost::asio::transfer_at_least(1), error)) + { + ss << &response; + } + if (error != boost::asio::error::eof) + { + throw boost::system::system_error(error); + } + answer = ss.str(); + } + catch (const std::exception &e) + { + cerr << "Exception: " << e.what() << "\n"; + return 0xffff; + } + + return 0; +} diff --git a/src/mastodon-cpp.cpp b/src/mastodon-cpp.cpp index 44e4c38..3e348fc 100644 --- a/src/mastodon-cpp.cpp +++ b/src/mastodon-cpp.cpp @@ -14,12 +14,23 @@ * along with this program. If not, see . */ -#include +#include +#include "version.hpp" #include "mastodon-cpp.hpp" -using std::cout; +using namespace Mastodon; +using std::string; -Mastodon::Mastodon() +API::API(const string &instance, const string &access_token) +: _instance(instance) +, _access_token(access_token) +, _useragent(string("mastodon-cpp/") + global::version) +, _http(instance, access_token, _useragent) { - cout << "Test.\n"; + // +} + +const void API::set_useragent(const std::string &useragent) +{ + _useragent = useragent; } diff --git a/src/mastodon-cpp.hpp b/src/mastodon-cpp.hpp index 268ba0a..96f447b 100644 --- a/src/mastodon-cpp.hpp +++ b/src/mastodon-cpp.hpp @@ -17,10 +17,81 @@ #ifndef MASTODON_CPP_HPP #define MASTODON_CPP_HPP -class Mastodon +#include +#include +#include +#include +#include + +namespace Mastodon { -public: - Mastodon(); -}; + class API + { + public: + enum class v1 + { + accounts_id, + accounts_verify_credentials, + accounts_id_followers, + accounts_id_following, + accounts_id_statuses, + accounts_relationships, + accounts_search + }; + + explicit API(const std::string &instance, + const std::string &access_token); + // Select one of the predefined methods. + const std::string get(const Mastodon::API::v1 &method); + const std::string get(const Mastodon::API::v1 &method, + const std::vector ¶meters); + const std::string get(const Mastodon::API::v1 &method, + const std::string &argument, + const std::vector ¶meters); + const std::string get(const Mastodon::API::v1 &method, + const std::string &argument); + // Supply a custom method as string. + const std::string get(const std::string &method); + + const void set_useragent(const std::string &useragent); + + private: + const std::string _instance; + const std::string _access_token; + std::string _useragent; + + class http + { + public: + enum class method + { + GET, + PATCH, + POST, + DELETE + }; + + explicit http(const std::string &instance, + const std::string &access_token, + const std::string &useragent); + const std::uint16_t request_sync(const method &meth, + const std::string &path, + std::string &answer); + const std::uint16_t request_sync(const method &meth, + const std::string &path, + const std::string &data, + std::string &answer); + + private: + const std::string _instance; + const std::string _access_token; + const std::string _useragent; + boost::asio::ssl::context _ctx; + boost::asio::io_service _io_service; + boost::asio::ip::tcp::resolver _resolver; + boost::asio::ssl::stream _socket; + } _http; + }; +} #endif diff --git a/src/tests/test_00_library_is_loadable.cpp b/src/tests/test_00_library_is_loadable.cpp index 6cd566c..8565086 100644 --- a/src/tests/test_00_library_is_loadable.cpp +++ b/src/tests/test_00_library_is_loadable.cpp @@ -1,22 +1,9 @@ /* This file is part of mastodon-cpp. - * Copyright © 2018 tastytea - * - * 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, version 3. - * - * 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 . */ #include "../mastodon-cpp.hpp" int main(int argc, char *argv[]) { - Mastodon test; + Mastodon::API test("", ""); } diff --git a/src/version.hpp.in b/src/version.hpp.in index eef0aee..b5e35eb 100644 --- a/src/version.hpp.in +++ b/src/version.hpp.in @@ -1,10 +1,14 @@ #ifndef VERSION_HPP #define VERSION_HPP -namespace global { - static constexpr char version[] = "@PROJECT_VERSION@"; - static constexpr char git_sha1[] ="@GIT_SHA1@"; - static constexpr char git_date[] ="@GIT_DATE@"; +namespace Mastodon +{ + namespace global + { + static constexpr char version[] = "@PROJECT_VERSION@"; + static constexpr char git_sha1[] ="@GIT_SHA1@"; + static constexpr char git_date[] ="@GIT_DATE@"; + } } #endif // VERSION_HPP