From 62e4620e4d5c06afd9b1c2af2c69578ac54d5865 Mon Sep 17 00:00:00 2001 From: tastytea Date: Fri, 10 Jan 2020 21:11:03 +0100 Subject: [PATCH 01/20] Add access token to readme example. --- README.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.adoc b/README.adoc index 3660d32..604da37 100644 --- a/README.adoc +++ b/README.adoc @@ -53,7 +53,7 @@ Have a look at the link:{uri-reference}[reference]. int main() { - mastodonpp::Instance instance{"example.com", {}}; + mastodonpp::Instance instance{"example.com", "123AccessToken123"}; mastodonpp::Connection connection{instance}; const mastodonpp::parametermap parameters From 5b246a8cad47398dc92d480545dde1fa1bb51460 Mon Sep 17 00:00:00 2001 From: tastytea Date: Fri, 10 Jan 2020 21:14:18 +0100 Subject: [PATCH 02/20] Typo. --- README.adoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.adoc b/README.adoc index 604da37..ee9288b 100644 --- a/README.adoc +++ b/README.adoc @@ -88,7 +88,7 @@ emerge -a dev-cpp/mastodonpp === Debian and Ubuntu We automatically generate packages for Debian buster (10) and Ubuntu bionic -(18.04), but only for x86_64 (amd64). Download the them at +(18.04), but only for x86_64 (amd64). Download them at link:{uri-base}/releases[schlomp.space]. [source,shell] @@ -99,7 +99,7 @@ apt install ./libmastodonpp*.deb === CentOS We automatically generate packages for CentOS 8, but only for x86_64 -(amd64). Download the them at link:{uri-base}/releases[schlomp.space]. +(amd64). Download them at link:{uri-base}/releases[schlomp.space]. [source,shell] -------------------------------------------------------------------------------- From f0a35bfd5b5d92fdf2f5c9b2a50498c4b8018e99 Mon Sep 17 00:00:00 2001 From: tastytea Date: Sat, 11 Jan 2020 13:17:35 +0100 Subject: [PATCH 03/20] Add add_mime_part(). Makes code more readable and decreases duplication. --- include/curl_wrapper.hpp | 12 +++++++ src/curl_wrapper.cpp | 67 +++++++++++++++++----------------------- 2 files changed, 40 insertions(+), 39 deletions(-) diff --git a/include/curl_wrapper.hpp b/include/curl_wrapper.hpp index 97df5b7..8523057 100644 --- a/include/curl_wrapper.hpp +++ b/include/curl_wrapper.hpp @@ -294,6 +294,18 @@ private: */ void add_parameters_to_uri(string &uri, const parametermap ¶meters); + /*! + * @brief Add `*curl_mimepart` to `*curl_mime`. + * + * @param mime Initialized `*curl_mime`. + * @param name Name of the field. + * @param data Data of the field, or \@filename. + * + * @since 0.1.1 + */ + void add_mime_part(curl_mime *mime, + string_view name, string_view data) const; + /*! * @brief Convert parametermap to `*curl_mime`. * diff --git a/src/curl_wrapper.cpp b/src/curl_wrapper.cpp index 91fb39e..4853628 100644 --- a/src/curl_wrapper.cpp +++ b/src/curl_wrapper.cpp @@ -339,12 +339,37 @@ void CURLWrapper::add_parameters_to_uri(string &uri, } } +void CURLWrapper::add_mime_part(curl_mime *mime, + string_view name, string_view data) const +{ + curl_mimepart *part{curl_mime_addpart(mime)}; + if (part == nullptr) + { + throw CURLException{"Could not build HTTP form."}; + } + + CURLcode code{curl_mime_name(part, name.data())}; + if (code != CURLE_OK) + { + throw CURLException{code, "Could not build HTTP form."}; + } + + code = curl_mime_data(part, data.data(), CURL_ZERO_TERMINATED); + if (code != CURLE_OK) + { + throw CURLException{code, "Could not build HTTP form."}; + } + + debuglog << "Set form part: " << name << " = " << data << '\n'; +} + curl_mime *CURLWrapper::parameters_to_curl_mime(string &uri, const parametermap ¶meters) { debuglog << "Building HTTP form.\n"; curl_mime *mime{curl_mime_init(_connection)}; + for (const auto ¶m : parameters) { if (replace_parameter_in_uri(uri, param)) @@ -352,52 +377,16 @@ curl_mime *CURLWrapper::parameters_to_curl_mime(string &uri, continue; } - CURLcode code; if (holds_alternative(param.second)) { - curl_mimepart *part{curl_mime_addpart(mime)}; - if (part == nullptr) - { - throw CURLException{"Could not build HTTP form."}; - } - - code = curl_mime_name(part, param.first.data()); - if (code != CURLE_OK) - { - throw CURLException{code, "Could not build HTTP form."}; - } - - code = curl_mime_data(part, get(param.second).data(), - CURL_ZERO_TERMINATED); - if (code != CURLE_OK) - { - throw CURLException{code, "Could not build HTTP form."}; - } - debuglog << "Set form part: " << param.first << " = " - << get(param.second) << '\n'; + add_mime_part(mime, param.first, get(param.second)); } else { for (const auto &arg : get>(param.second)) { - curl_mimepart *part{curl_mime_addpart(mime)}; - if (part == nullptr) - { - throw CURLException{"Could not build HTTP form."}; - } - - const string name{string(param.first) += "[]"}; - code = curl_mime_name(part, name.c_str()); - if (code != CURLE_OK) - { - throw CURLException{code, "Could not build HTTP form."}; - } - code = curl_mime_data(part, arg.data(), CURL_ZERO_TERMINATED); - if (code != CURLE_OK) - { - throw CURLException{code, "Could not build HTTP form."}; - } - debuglog << "Set form part: " << name << " = " << arg << '\n'; + const string_view name{string(param.first) += "[]"}; + add_mime_part(mime, name, arg); } } } From 18de12c762df2ed5a183bb0119182eb6ec0cc8b1 Mon Sep 17 00:00:00 2001 From: tastytea Date: Sat, 11 Jan 2020 14:13:55 +0100 Subject: [PATCH 04/20] Typo. --- README.adoc | 2 +- examples/example03_post_status.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.adoc b/README.adoc index ee9288b..186f9c0 100644 --- a/README.adoc +++ b/README.adoc @@ -58,7 +58,7 @@ int main() const mastodonpp::parametermap parameters { - {"status", "How is the wheather?"}, + {"status", "How is the weather?"}, {"poll[options]", vector{"Nice", "not nice"}}, {"poll[expires_in]", "86400"} }; diff --git a/examples/example03_post_status.cpp b/examples/example03_post_status.cpp index db5545b..f2e473a 100644 --- a/examples/example03_post_status.cpp +++ b/examples/example03_post_status.cpp @@ -49,7 +49,7 @@ int main(int argc, char *argv[]) constexpr auto poll_seconds{60 * 60 * 24 * 2}; // 2 days. const masto::parametermap parameters { - {"status", "How is the wheather?"}, + {"status", "How is the weather?"}, {"poll[options]", vector{"Nice", "not nice"}}, {"poll[expires_in]", to_string(poll_seconds)} }; From 8d04f7e9ea8896f0e2e5b85cc8b86ef15a4d74aa Mon Sep 17 00:00:00 2001 From: tastytea Date: Sat, 11 Jan 2020 14:27:10 +0100 Subject: [PATCH 05/20] Add support for sending files in HTTP forms. --- include/curl_wrapper.hpp | 6 +++--- include/mastodonpp.hpp | 5 ++++- src/curl_wrapper.cpp | 10 +++++++++- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/include/curl_wrapper.hpp b/include/curl_wrapper.hpp index 8523057..44f3b0d 100644 --- a/include/curl_wrapper.hpp +++ b/include/curl_wrapper.hpp @@ -297,9 +297,9 @@ private: /*! * @brief Add `*curl_mimepart` to `*curl_mime`. * - * @param mime Initialized `*curl_mime`. - * @param name Name of the field. - * @param data Data of the field, or \@filename. + * @param mime Initialized `*curl_mime`. @param name Name of the field. + * @param data Data of the field. If it begins with `\@file:, the + * rest of the ergument is treated as a filename. * * @since 0.1.1 */ diff --git a/include/mastodonpp.hpp b/include/mastodonpp.hpp index 9d00369..a696eac 100644 --- a/include/mastodonpp.hpp +++ b/include/mastodonpp.hpp @@ -69,7 +69,10 @@ * * @subsection input Input * - * All text input is expected to be UTF-8. + * * All text input is expected to be UTF-8. + * + * * To send a file, use “\@file:” followed by the file name as value + * in the @link mastodonpp::parametermap parametermap@endlink. * * @section exceptions Exceptions * diff --git a/src/curl_wrapper.cpp b/src/curl_wrapper.cpp index 4853628..047b799 100644 --- a/src/curl_wrapper.cpp +++ b/src/curl_wrapper.cpp @@ -354,7 +354,15 @@ void CURLWrapper::add_mime_part(curl_mime *mime, throw CURLException{code, "Could not build HTTP form."}; } - code = curl_mime_data(part, data.data(), CURL_ZERO_TERMINATED); + if (data.substr(0, 6) == "@file:") + { + const string_view filename{data.substr(6)}; + code = curl_mime_filedata(part, filename.data()); + } + else + { + code = curl_mime_data(part, data.data(), CURL_ZERO_TERMINATED); + } if (code != CURLE_OK) { throw CURLException{code, "Could not build HTTP form."}; From 45e22b5d1ea1c40f44ed89da8387853c2615beef Mon Sep 17 00:00:00 2001 From: tastytea Date: Sat, 11 Jan 2020 14:38:03 +0100 Subject: [PATCH 06/20] Improve parametermap example. --- include/curl_wrapper.hpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/include/curl_wrapper.hpp b/include/curl_wrapper.hpp index 44f3b0d..de160d2 100644 --- a/include/curl_wrapper.hpp +++ b/include/curl_wrapper.hpp @@ -61,8 +61,9 @@ enum class http_method * @code * parametermap parameters * { - * {"id", "12"}, - * {"poll[options]", vector{"Yes", "No", "Maybe"}} + * {"poll[expires_in]", "86400"}, + * {"poll[options]", vector{"Yes", "No", "Maybe"}}, + * {"status", "How is the weather?"} * }; * @endcode * From 7eda3ae9e1709e84064a499f0d5d0fed67be6215 Mon Sep 17 00:00:00 2001 From: tastytea Date: Sat, 11 Jan 2020 14:44:14 +0100 Subject: [PATCH 07/20] Fix argument check in example03. --- examples/example03_post_status.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/example03_post_status.cpp b/examples/example03_post_status.cpp index f2e473a..62fc919 100644 --- a/examples/example03_post_status.cpp +++ b/examples/example03_post_status.cpp @@ -33,7 +33,7 @@ using std::vector; int main(int argc, char *argv[]) { const vector args(argv, argv + argc); - if (args.size() <= 1) + if (args.size() <= 2) { cerr << "Usage: " << args[0] << " \n"; return 1; From d14f750c393a5b618dc2d4e6f98f81244ba3c4d4 Mon Sep 17 00:00:00 2001 From: tastytea Date: Sat, 11 Jan 2020 15:25:20 +0100 Subject: [PATCH 08/20] Add example for posting an attachment. --- examples/example04_post_with_attachment.cpp | 99 +++++++++++++++++++++ include/mastodonpp.hpp | 2 +- 2 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 examples/example04_post_with_attachment.cpp diff --git a/examples/example04_post_with_attachment.cpp b/examples/example04_post_with_attachment.cpp new file mode 100644 index 0000000..46c493c --- /dev/null +++ b/examples/example04_post_with_attachment.cpp @@ -0,0 +1,99 @@ +/* This file is part of mastodonpp. + * Copyright © 2020 tastytea + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +// Post a status (/api/v1/status) with an attachment (/api/v1/media). + +#include "mastodonpp.hpp" + +#include +#include +#include +#include + +namespace masto = mastodonpp; +using std::cout; +using std::cerr; +using std::endl; +using std::string; +using std::to_string; +using std::string_view; +using std::vector; + +int main(int argc, char *argv[]) +{ + const vector args(argv, argv + argc); + if (args.size() <= 3) + { + cerr << "Usage: " << args[0] << " \n"; + return 1; + } + + try + { + // Initialize an Instance and a Connection. + masto::Instance instance{args[1], args[2]}; + masto::Connection connection{instance}; + const string_view filename{args[3]}; + + // Create attachment. + auto answer{connection.post(masto::API::v1::media, + { + {"file", string("@file:") += filename}, + {"description", "Test."} + })}; + + // Get the ID of the attachment. + // You normally would use a JSON parser, of course. I don't use one + // because I don't want to add a dependency just for an example. + const auto pos{answer.body.find(R"("id":")") + 6}; + const auto endpos{answer.body.find(R"(",)", pos)}; + const auto media_id{answer.body.substr(pos, endpos - pos)}; + cout << "Attachment has ID: " << media_id << endl; + + // Post the status. Note that “media_ids” always has to be a vector. + answer = connection.post(masto::API::v1::statuses, + { + {"status", "Attachment test."}, + {"media_ids", + vector{media_id}} + }); + if (answer) + { + cout << "Successfully posted " << filename << ".\n"; + } + else + { + if (answer.curl_error_code == 0) + { + // If it is no libcurl error, it must be an HTTP error. + cerr << "HTTP status: " << answer.http_status << endl; + } + else + { + // Network errors like “Couldn't resolve host.”. + cerr << "libcurl error " << to_string(answer.curl_error_code) + << ": " << answer.error_message << endl; + } + } + } + catch (const masto::CURLException &e) + { + // Only libcurl errors that are not network errors will be thrown. + // There went probably something wrong with the initialization. + cerr << e.what() << endl; + } + + return 0; +} diff --git a/include/mastodonpp.hpp b/include/mastodonpp.hpp index a696eac..a92cff7 100644 --- a/include/mastodonpp.hpp +++ b/include/mastodonpp.hpp @@ -70,7 +70,6 @@ * @subsection input Input * * * All text input is expected to be UTF-8. - * * * To send a file, use “\@file:” followed by the file name as value * in the @link mastodonpp::parametermap parametermap@endlink. * @@ -102,6 +101,7 @@ * @example example01_instance_info.cpp * @example example02_streaming.cpp * @example example03_post_status.cpp + * @example example04_post_with_attachment.cpp */ /*! From fc210142f5fa8959bb992c175119054080efef37 Mon Sep 17 00:00:00 2001 From: tastytea Date: Sat, 11 Jan 2020 15:25:34 +0100 Subject: [PATCH 09/20] Expand parametermap documentation. --- include/curl_wrapper.hpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/include/curl_wrapper.hpp b/include/curl_wrapper.hpp index de160d2..9e7ee48 100644 --- a/include/curl_wrapper.hpp +++ b/include/curl_wrapper.hpp @@ -57,6 +57,10 @@ enum class http_method /*! * @brief `std::map` of parameters for %API calls. * + * Note that arrays always have to be specified as vectors, even if they have + * only 1 element. To send a file, use “\@file:” followed by the file + * name as value. + * * Example: * @code * parametermap parameters From 5051664136357bb38dc235084e82c4d741f25c3f Mon Sep 17 00:00:00 2001 From: tastytea Date: Sat, 11 Jan 2020 15:52:29 +0100 Subject: [PATCH 10/20] Fixed some pleroma endpoints. --- include/api.hpp | 6 +++--- src/api.cpp | 7 +++---- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/include/api.hpp b/include/api.hpp index 7c89af4..aab5f41 100644 --- a/include/api.hpp +++ b/include/api.hpp @@ -281,9 +281,9 @@ public: disable_account, account_register, - pleroma_notification_settings, - pleroma_healthcheck, - pleroma_change_email + notification_settings, + healthcheck, + change_email }; /*! diff --git a/src/api.cpp b/src/api.cpp index cb60a8b..78ffdcc 100644 --- a/src/api.cpp +++ b/src/api.cpp @@ -239,10 +239,9 @@ const map API::_endpoint_map {pleroma::disable_account, "/api/pleroma/disable_account"}, {pleroma::account_register, "/api/pleroma/account/register"}, - {pleroma::pleroma_notification_settings, - "/api/pleroma/pleroma/notification_settings"}, - {pleroma::pleroma_healthcheck, "/api/pleroma/pleroma/healthcheck"}, - {pleroma::pleroma_change_email, "/api/pleroma/pleroma/change_email"}, + {pleroma::notification_settings, "/api/pleroma/notification_settings"}, + {pleroma::healthcheck, "/api/pleroma/healthcheck"}, + {pleroma::change_email, "/api/pleroma/change_email"}, }; } // namespace mastodonpp From f556df296f7395a3662e1c371d9b942f404105cd Mon Sep 17 00:00:00 2001 From: tastytea Date: Sat, 11 Jan 2020 16:07:40 +0100 Subject: [PATCH 11/20] Add HTTP methods PATCH and PUT. --- include/connection.hpp | 52 ++++++++++++++++++++++++++++++++++++++++++ src/connection.cpp | 14 ++++++++++++ src/curl_wrapper.cpp | 18 ++++++++++++++- 3 files changed, 83 insertions(+), 1 deletion(-) diff --git a/include/connection.hpp b/include/connection.hpp index 0ed09eb..398b8d5 100644 --- a/include/connection.hpp +++ b/include/connection.hpp @@ -161,6 +161,58 @@ public: return post(endpoint, {}); } + /*! + * @brief Make a HTTP PATCH call with parameters. + * + * @param endpoint Endpoint as API::endpoint_type or `std::string_view`. + * @param parameters A map of parameters. + * + * + * @since 0.1.0 + */ + [[nodiscard]] + answer_type patch(const endpoint_variant &endpoint, + const parametermap ¶meters); + + /*! + * @brief Make a HTTP PATCH call. + * + * @param endpoint Endpoint as API::endpoint_type or `std::string_view`. + * + * @since 0.1.0 + */ + [[nodiscard]] + inline answer_type patch(const endpoint_variant &endpoint) + { + return post(endpoint, {}); + } + + /*! + * @brief Make a HTTP PUT call with parameters. + * + * @param endpoint Endpoint as API::endpoint_type or `std::string_view`. + * @param parameters A map of parameters. + * + * + * @since 0.1.0 + */ + [[nodiscard]] + answer_type put(const endpoint_variant &endpoint, + const parametermap ¶meters); + + /*! + * @brief Make a HTTP PUT call. + * + * @param endpoint Endpoint as API::endpoint_type or `std::string_view`. + * + * @since 0.1.0 + */ + [[nodiscard]] + inline answer_type put(const endpoint_variant &endpoint) + { + return post(endpoint, {}); + } + /*! * @brief Copy new stream contents and delete the “original”. * diff --git a/src/connection.cpp b/src/connection.cpp index f74a816..9cde7d3 100644 --- a/src/connection.cpp +++ b/src/connection.cpp @@ -61,6 +61,20 @@ answer_type Connection::post(const endpoint_variant &endpoint, endpoint_to_uri(endpoint), parameters); } +answer_type Connection::patch(const endpoint_variant &endpoint, + const parametermap ¶meters) +{ + return make_request(http_method::PATCH, + endpoint_to_uri(endpoint), parameters); +} + +answer_type Connection::put(const endpoint_variant &endpoint, + const parametermap ¶meters) +{ + return make_request(http_method::PUT, + endpoint_to_uri(endpoint), parameters); +} + string Connection::get_new_stream_contents() { buffer_mutex.lock(); diff --git a/src/curl_wrapper.cpp b/src/curl_wrapper.cpp index 047b799..7b2b352 100644 --- a/src/curl_wrapper.cpp +++ b/src/curl_wrapper.cpp @@ -111,14 +111,30 @@ answer_type CURLWrapper::make_request(const http_method &method, string uri, } case http_method::PATCH: { + if (!parameters.empty()) + { + curl_mime *mime{parameters_to_curl_mime(uri, parameters)}; + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) + code = curl_easy_setopt(_connection, CURLOPT_MIMEPOST, mime); + } + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) code = curl_easy_setopt(_connection, CURLOPT_CUSTOMREQUEST, "PATCH"); + break; } case http_method::PUT: { + if (!parameters.empty()) + { + curl_mime *mime{parameters_to_curl_mime(uri, parameters)}; + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) + code = curl_easy_setopt(_connection, CURLOPT_MIMEPOST, mime); + } + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) - code = curl_easy_setopt(_connection, CURLOPT_UPLOAD, 1L); + code = curl_easy_setopt(_connection, CURLOPT_CUSTOMREQUEST, "PUT"); + break; } case http_method::DELETE: From 5aa5bf4c9c9c01851acc5f96afc55e6c4c595121 Mon Sep 17 00:00:00 2001 From: tastytea Date: Sat, 11 Jan 2020 16:10:15 +0100 Subject: [PATCH 12/20] 80 char rule. --- examples/example04_post_with_attachment.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/example04_post_with_attachment.cpp b/examples/example04_post_with_attachment.cpp index 46c493c..f40170f 100644 --- a/examples/example04_post_with_attachment.cpp +++ b/examples/example04_post_with_attachment.cpp @@ -36,7 +36,8 @@ int main(int argc, char *argv[]) const vector args(argv, argv + argc); if (args.size() <= 3) { - cerr << "Usage: " << args[0] << " \n"; + cerr << "Usage: " << args[0] + << " \n"; return 1; } From c67c9db40cccc635f8b2261abb685e64d6e740b8 Mon Sep 17 00:00:00 2001 From: tastytea Date: Sat, 11 Jan 2020 16:13:14 +0100 Subject: [PATCH 13/20] Fix wrong endpoint translations. --- src/api.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/api.cpp b/src/api.cpp index 78ffdcc..e7645d1 100644 --- a/src/api.cpp +++ b/src/api.cpp @@ -31,8 +31,8 @@ const map API::_endpoint_map {v1::apps_verify_credentials, "/api/v1/apps/verify/credentials"}, {v1::accounts, "/api/v1/accounts"}, - {v1::accounts_verify_credentials, "/api/v1/accounts/verify/credentials"}, - {v1::accounts_update_credentials, "/api/v1/accounts/update/credentials"}, + {v1::accounts_verify_credentials, "/api/v1/accounts/verify_credentials"}, + {v1::accounts_update_credentials, "/api/v1/accounts/update_credentials"}, {v1::accounts_id, "/api/v1/accounts/"}, {v1::accounts_id_statuses, "/api/v1/accounts//statuses"}, {v1::accounts_id_followers, "/api/v1/accounts//followers"}, From ed43f74c01f7a91d40fd5e4ef2396cd0fe9c7ff7 Mon Sep 17 00:00:00 2001 From: tastytea Date: Sat, 11 Jan 2020 16:14:52 +0100 Subject: [PATCH 14/20] Add examples 05 and 06. --- ...example05_update_notification_settings.cpp | 84 +++++++++++++++++++ examples/example06_update_name.cpp | 83 ++++++++++++++++++ include/mastodonpp.hpp | 2 + 3 files changed, 169 insertions(+) create mode 100644 examples/example05_update_notification_settings.cpp create mode 100644 examples/example06_update_name.cpp diff --git a/examples/example05_update_notification_settings.cpp b/examples/example05_update_notification_settings.cpp new file mode 100644 index 0000000..5690265 --- /dev/null +++ b/examples/example05_update_notification_settings.cpp @@ -0,0 +1,84 @@ +/* This file is part of mastodonpp. + * Copyright © 2020 tastytea + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +// Update notification settings (/api/pleroma/notification_settings). + +#include "mastodonpp.hpp" + +#include +#include +#include +#include + +namespace masto = mastodonpp; +using std::cout; +using std::cerr; +using std::endl; +using std::to_string; +using std::string_view; +using std::vector; + +int main(int argc, char *argv[]) +{ + const vector args(argv, argv + argc); + if (args.size() <= 2) + { + cerr << "Usage: " << args[0] << " \n"; + return 1; + } + + try + { + // Initialize an Instance and a Connection. + masto::Instance instance{args[1], args[2]}; + masto::Connection connection{instance}; + + // Update the settings. + const auto answer{connection.put( + masto::API::pleroma::notification_settings, + { + {"followers", "true"}, + {"follows", "true"}, + {"remote", "true"}, + {"local", "true"}, + })}; + if (answer) + { + cout << answer << endl; + } + else + { + if (answer.curl_error_code == 0) + { + // If it is no libcurl error, it must be an HTTP error. + cerr << "HTTP status: " << answer.http_status << endl; + } + else + { + // Network errors like “Couldn't resolve host.”. + cerr << "libcurl error " << to_string(answer.curl_error_code) + << ": " << answer.error_message << endl; + } + } + } + catch (const masto::CURLException &e) + { + // Only libcurl errors that are not network errors will be thrown. + // There went probably something wrong with the initialization. + cerr << e.what() << endl; + } + + return 0; +} diff --git a/examples/example06_update_name.cpp b/examples/example06_update_name.cpp new file mode 100644 index 0000000..699fc32 --- /dev/null +++ b/examples/example06_update_name.cpp @@ -0,0 +1,83 @@ +/* This file is part of mastodonpp. + * Copyright © 2020 tastytea + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +// Update account display name settings (/api/v1/accounts/update_credentials). + +#include "mastodonpp.hpp" + +#include +#include +#include +#include + +namespace masto = mastodonpp; +using std::cout; +using std::cerr; +using std::endl; +using std::to_string; +using std::string_view; +using std::vector; + +int main(int argc, char *argv[]) +{ + const vector args(argv, argv + argc); + if (args.size() <= 3) + { + cerr << "Usage: " << args[0] + << " \n"; + return 1; + } + const auto name{args[3]}; + + try + { + // Initialize an Instance and a Connection. + masto::Instance instance{args[1], args[2]}; + masto::Connection connection{instance}; + + // Update the settings. + const auto answer{connection.patch( + masto::API::v1::accounts_update_credentials, + { + {"display_name", name}, + })}; + if (answer) + { + cout << "Successfully changed display name.\n"; + } + else + { + if (answer.curl_error_code == 0) + { + // If it is no libcurl error, it must be an HTTP error. + cerr << "HTTP status: " << answer.http_status << endl; + } + else + { + // Network errors like “Couldn't resolve host.”. + cerr << "libcurl error " << to_string(answer.curl_error_code) + << ": " << answer.error_message << endl; + } + } + } + catch (const masto::CURLException &e) + { + // Only libcurl errors that are not network errors will be thrown. + // There went probably something wrong with the initialization. + cerr << e.what() << endl; + } + + return 0; +} diff --git a/include/mastodonpp.hpp b/include/mastodonpp.hpp index a92cff7..05202af 100644 --- a/include/mastodonpp.hpp +++ b/include/mastodonpp.hpp @@ -102,6 +102,8 @@ * @example example02_streaming.cpp * @example example03_post_status.cpp * @example example04_post_with_attachment.cpp + * @example example05_update_notification_settings.cpp + * @example example06_update_name.cpp */ /*! From 905a5aae0e4a1eb7bfedab5ba6b7c6a22763237b Mon Sep 17 00:00:00 2001 From: tastytea Date: Sat, 11 Jan 2020 16:16:55 +0100 Subject: [PATCH 15/20] Fix version information for Connection::patch() and put(). --- include/connection.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/include/connection.hpp b/include/connection.hpp index 398b8d5..d746a5c 100644 --- a/include/connection.hpp +++ b/include/connection.hpp @@ -168,7 +168,7 @@ public: * @param parameters A map of parameters. * * - * @since 0.1.0 + * @since 0.2.0 */ [[nodiscard]] answer_type patch(const endpoint_variant &endpoint, @@ -179,7 +179,7 @@ public: * * @param endpoint Endpoint as API::endpoint_type or `std::string_view`. * - * @since 0.1.0 + * @since 0.2.0 */ [[nodiscard]] inline answer_type patch(const endpoint_variant &endpoint) @@ -194,7 +194,7 @@ public: * @param parameters A map of parameters. * * - * @since 0.1.0 + * @since 0.2.0 */ [[nodiscard]] answer_type put(const endpoint_variant &endpoint, @@ -205,7 +205,7 @@ public: * * @param endpoint Endpoint as API::endpoint_type or `std::string_view`. * - * @since 0.1.0 + * @since 0.2.0 */ [[nodiscard]] inline answer_type put(const endpoint_variant &endpoint) From 03b097c6a6f47a875f74f84f4a114e2820d07039 Mon Sep 17 00:00:00 2001 From: tastytea Date: Sat, 11 Jan 2020 16:20:33 +0100 Subject: [PATCH 16/20] Fix patch() and put() requests without parameters. Connection::patch() and Connection::put() without parameters were making a POST request. --- include/connection.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/connection.hpp b/include/connection.hpp index d746a5c..536123e 100644 --- a/include/connection.hpp +++ b/include/connection.hpp @@ -184,7 +184,7 @@ public: [[nodiscard]] inline answer_type patch(const endpoint_variant &endpoint) { - return post(endpoint, {}); + return patch(endpoint, {}); } /*! @@ -210,7 +210,7 @@ public: [[nodiscard]] inline answer_type put(const endpoint_variant &endpoint) { - return post(endpoint, {}); + return put(endpoint, {}); } /*! From 5c61d6fd27fa6a9cf766f603cbc12e04acc7c887 Mon Sep 17 00:00:00 2001 From: tastytea Date: Sat, 11 Jan 2020 16:23:28 +0100 Subject: [PATCH 17/20] Add support for DELETE requests. --- include/connection.hpp | 26 ++++++++++++++++++++++++++ src/connection.cpp | 7 +++++++ src/curl_wrapper.cpp | 8 ++++++++ 3 files changed, 41 insertions(+) diff --git a/include/connection.hpp b/include/connection.hpp index 536123e..761f777 100644 --- a/include/connection.hpp +++ b/include/connection.hpp @@ -213,6 +213,32 @@ public: return put(endpoint, {}); } + /*! + * @brief Make a HTTP DELETE call with parameters. + * + * @param endpoint Endpoint as API::endpoint_type or `std::string_view`. + * @param parameters A map of parameters. + * + * + * @since 0.2.0 + */ + [[nodiscard]] + answer_type del(const endpoint_variant &endpoint, + const parametermap ¶meters); + + /*! + * @brief Make a HTTP DELETE call. + * + * @param endpoint Endpoint as API::endpoint_type or `std::string_view`. + * + * @since 0.2.0 + */ + [[nodiscard]] + inline answer_type del(const endpoint_variant &endpoint) + { + return del(endpoint, {}); + } + /*! * @brief Copy new stream contents and delete the “original”. * diff --git a/src/connection.cpp b/src/connection.cpp index 9cde7d3..3a10d14 100644 --- a/src/connection.cpp +++ b/src/connection.cpp @@ -75,6 +75,13 @@ answer_type Connection::put(const endpoint_variant &endpoint, endpoint_to_uri(endpoint), parameters); } +answer_type Connection::del(const endpoint_variant &endpoint, + const parametermap ¶meters) +{ + return make_request(http_method::DELETE, + endpoint_to_uri(endpoint), parameters); +} + string Connection::get_new_stream_contents() { buffer_mutex.lock(); diff --git a/src/curl_wrapper.cpp b/src/curl_wrapper.cpp index 7b2b352..6b62389 100644 --- a/src/curl_wrapper.cpp +++ b/src/curl_wrapper.cpp @@ -139,8 +139,16 @@ answer_type CURLWrapper::make_request(const http_method &method, string uri, } case http_method::DELETE: { + if (!parameters.empty()) + { + curl_mime *mime{parameters_to_curl_mime(uri, parameters)}; + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) + code = curl_easy_setopt(_connection, CURLOPT_MIMEPOST, mime); + } + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) code = curl_easy_setopt(_connection, CURLOPT_CUSTOMREQUEST, "DELETE"); + break; } } From 4a940062d850b193948596db6136b024c0c3cafb Mon Sep 17 00:00:00 2001 From: tastytea Date: Sat, 11 Jan 2020 16:40:13 +0100 Subject: [PATCH 18/20] Fix wrong endpoint translations. I forgot a lot of s. --- src/api.cpp | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/api.cpp b/src/api.cpp index e7645d1..d56298b 100644 --- a/src/api.cpp +++ b/src/api.cpp @@ -61,7 +61,7 @@ const map API::_endpoint_map {v1::domain_blocks, "/api/v1/domain/blocks"}, {v1::filters, "/api/v1/filters"}, - {v1::filters_id, "/api/v1/filters/id"}, + {v1::filters_id, "/api/v1/filters/"}, {v1::reports, "/api/v1/reports"}, @@ -73,16 +73,16 @@ const map API::_endpoint_map {v1::endorsements, "/api/v1/endorsements"}, {v1::featured_tags, "/api/v1/featured/tags"}, - {v1::featured_tags_id, "/api/v1/featured/tags/id"}, + {v1::featured_tags_id, "/api/v1/featured/tags/"}, {v1::featured_tags_suggestions, "/api/v1/featured/tags/suggestions"}, {v1::preferences, "/api/v1/preferences"}, {v1::suggestions, "/api/v1/suggestions"}, - {v1::suggestions_account_id, "/api/v1/suggestions/account/id"}, + {v1::suggestions_account_id, "/api/v1/suggestions/account/"}, {v1::statuses, "/api/v1/statuses"}, - {v1::statuses_id, "/api/v1/statuses/id"}, + {v1::statuses_id, "/api/v1/statuses/"}, {v1::statuses_id_context, "/api/v1/statuses//context"}, {v1::statuses_id_reblogged_by, "/api/v1/statuses//reblogged/by"}, {v1::statuses_id_favourited_by, "/api/v1/statuses//favourited/by"}, @@ -98,21 +98,21 @@ const map API::_endpoint_map {v1::statuses_id_unpin, "/api/v1/statuses//unpin"}, {v1::media, "/api/v1/media"}, - {v1::media_id, "/api/v1/media/id"}, + {v1::media_id, "/api/v1/media/"}, - {v1::polls_id, "/api/v1/polls/id"}, + {v1::polls_id, "/api/v1/polls/"}, {v1::polls_id_votes, "/api/v1/polls//votes"}, {v1::scheduled_statuses, "/api/v1/scheduled/statuses"}, - {v1::scheduled_statuses_id, "/api/v1/scheduled/statuses/id"}, + {v1::scheduled_statuses_id, "/api/v1/scheduled/statuses/"}, {v1::timelines_public, "/api/v1/timelines/public"}, {v1::timelines_tag_hashtag, "/api/v1/timelines/tag/"}, {v1::timelines_home, "/api/v1/timelines/home"}, - {v1::timelines_list_list_id, "/api/v1/timelines/list/list/id"}, + {v1::timelines_list_list_id, "/api/v1/timelines/list/list/"}, {v1::conversations, "/api/v1/conversations"}, - {v1::conversations_id, "/api/v1/conversations/id"}, + {v1::conversations_id, "/api/v1/conversations/"}, {v1::conversations_id_read, "/api/v1/conversations//read"}, {v1::lists, "/api/v1/lists"}, @@ -131,7 +131,7 @@ const map API::_endpoint_map {v1::streaming_direct, "/api/v1/streaming/direct"}, {v1::notifications, "/api/v1/notifications"}, - {v1::notifications_id, "/api/v1/notifications/id"}, + {v1::notifications_id, "/api/v1/notifications/"}, {v1::notifications_clear, "/api/v1/notifications/clear"}, {v1::notifications_id_dismiss, "/api/v1/notifications//dismiss"}, @@ -148,7 +148,7 @@ const map API::_endpoint_map {v1::custom_emojis, "/api/v1/custom/emojis"}, {v1::admin_accounts, "/api/v1/admin/accounts"}, - {v1::admin_accounts_id, "/api/v1/admin/accounts/id"}, + {v1::admin_accounts_id, "/api/v1/admin/accounts/"}, {v1::admin_accounts_account_id_action, "/api/v1/admin/accounts/account//action"}, {v1::admin_accounts_id_approve, "/api/v1/admin/accounts//approve"}, @@ -157,11 +157,11 @@ const map API::_endpoint_map {v1::admin_accounts_id_unsilence, "/api/v1/admin/accounts//unsilence"}, {v1::admin_accounts_id_unsuspend, "/api/v1/admin/accounts//unsuspend"}, {v1::admin_reports, "/api/v1/admin/reports"}, - {v1::admin_reports_id, "/api/v1/admin/reports/id"}, + {v1::admin_reports_id, "/api/v1/admin/reports/"}, {v1::admin_reports_id_assign_to_self, "/api/v1/admin/reports//assign/to/self"}, {v1::admin_reports_id_unassign, "/api/v1/admin/reports//unassign"}, - {v1::admin_reports_id_resolve, "/api/v1/admin/reports/resolve"}, + {v1::admin_reports_id_resolve, "/api/v1/admin/reports//resolve"}, {v1::admin_reports_id_reopen, "/api/v1/admin/reports//reopen"}, {v1::pleroma_notifications_read, " /api/v1/pleroma/notifications/read"}, @@ -171,7 +171,7 @@ const map API::_endpoint_map {v1::pleroma_accounts_id_unsubscribe, "/api/v1/pleroma/accounts//unsubscribe"}, {v1::pleroma_accounts_id_favourites, - "/api/v1/pleroma/accounts/:id/favourites"}, + "/api/v1/pleroma/accounts//favourites"}, {v1::pleroma_accounts_update_avatar, "/api/v1/pleroma/accounts/update_avatar"}, {v1::pleroma_accounts_update_banner, From 078f6d0c730dba9013e456f7ce40ee667bbe53cf Mon Sep 17 00:00:00 2001 From: tastytea Date: Sat, 11 Jan 2020 16:42:01 +0100 Subject: [PATCH 19/20] Log URI replacements. --- src/curl_wrapper.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/curl_wrapper.cpp b/src/curl_wrapper.cpp index 6b62389..f613766 100644 --- a/src/curl_wrapper.cpp +++ b/src/curl_wrapper.cpp @@ -317,6 +317,8 @@ bool CURLWrapper::replace_parameter_in_uri(string &uri, { uri.replace(pos, parameter.first.size() + 2, get(parameter.second)); + debuglog << "Replaced :" << parameter.first << " in URI with " + << get(parameter.second) << '\n'; return true; } } From f3f8efac2543e6712f0206091d00a8be56090353 Mon Sep 17 00:00:00 2001 From: tastytea Date: Sat, 11 Jan 2020 16:45:31 +0100 Subject: [PATCH 20/20] Add DELETE example. --- examples/example07_delete_status.cpp | 98 ++++++++++++++++++++++++++++ include/mastodonpp.hpp | 1 + 2 files changed, 99 insertions(+) create mode 100644 examples/example07_delete_status.cpp diff --git a/examples/example07_delete_status.cpp b/examples/example07_delete_status.cpp new file mode 100644 index 0000000..fc333b5 --- /dev/null +++ b/examples/example07_delete_status.cpp @@ -0,0 +1,98 @@ +/* This file is part of mastodonpp. + * Copyright © 2020 tastytea + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +// Post a status (/api/v1/status), then delete it (/api/v1/statuses/:id). + +#include "mastodonpp.hpp" + +#include +#include +#include +#include +#include +#include + +namespace masto = mastodonpp; +using namespace std::chrono_literals; +using std::cout; +using std::cerr; +using std::endl; +using std::to_string; +using std::string_view; +using std::this_thread::sleep_for; +using std::vector; + +int main(int argc, char *argv[]) +{ + const vector args(argv, argv + argc); + if (args.size() <= 2) + { + cerr << "Usage: " << args[0] << " \n"; + return 1; + } + + try + { + // Initialize an Instance and a Connection. + masto::Instance instance{args[1], args[2]}; + masto::Connection connection{instance}; + + // Post a status. + auto answer{connection.post(masto::API::v1::statuses, + {{"status", "Delete me."}})}; + if (answer) + { + cout << "Successfully posted a status.\n"; + + // Get the ID of the post. + // You normally would use a JSON parser, of course. I don't use one + // because I don't want to add a dependency just for an example. + const auto pos{answer.body.rfind(R"("id":")") + 6}; + const auto endpos{answer.body.find(R"(",)", pos)}; + const auto id{answer.body.substr(pos, endpos - pos)}; + cout << "Post has ID: " << id << endl; + cout << "Waiting 10 seconds…\n"; + sleep_for(10s); + + answer = connection.del(masto::API::v1::statuses_id, {{"id", id}}); + if (answer) + { + cout << "Successfully deleted the status.\n"; + } + } + else + { + if (answer.curl_error_code == 0) + { + // If it is no libcurl error, it must be an HTTP error. + cerr << "HTTP status: " << answer.http_status << endl; + } + else + { + // Network errors like “Couldn't resolve host.”. + cerr << "libcurl error " << to_string(answer.curl_error_code) + << ": " << answer.error_message << endl; + } + } + } + catch (const masto::CURLException &e) + { + // Only libcurl errors that are not network errors will be thrown. + // There went probably something wrong with the initialization. + cerr << e.what() << endl; + } + + return 0; +} diff --git a/include/mastodonpp.hpp b/include/mastodonpp.hpp index 05202af..d6f8564 100644 --- a/include/mastodonpp.hpp +++ b/include/mastodonpp.hpp @@ -104,6 +104,7 @@ * @example example04_post_with_attachment.cpp * @example example05_update_notification_settings.cpp * @example example06_update_name.cpp + * @example example07_delete_status.cpp */ /*!