From a8e342202d7f429446a7193fc86ccbc0d0438820 Mon Sep 17 00:00:00 2001 From: tastytea Date: Tue, 14 Jan 2020 20:44:08 +0100 Subject: [PATCH 1/8] Move types into types.hpp and add answer_type::next() and prev(). --- include/connection.hpp | 2 +- include/curl_wrapper.hpp | 40 +-------------- include/instance.hpp | 2 +- include/mastodonpp.hpp | 2 +- include/{answer.hpp => types.hpp} | 85 +++++++++++++++++++++++++++++-- src/instance.cpp | 1 - src/{answer.cpp => types.cpp} | 39 +++++++++++++- 7 files changed, 124 insertions(+), 47 deletions(-) rename include/{answer.hpp => types.hpp} (58%) rename src/{answer.cpp => types.cpp} (61%) diff --git a/include/connection.hpp b/include/connection.hpp index 27119e4..bf37219 100644 --- a/include/connection.hpp +++ b/include/connection.hpp @@ -17,10 +17,10 @@ #ifndef MASTODONPP_CONNECTION_HPP #define MASTODONPP_CONNECTION_HPP -#include "answer.hpp" #include "api.hpp" #include "curl_wrapper.hpp" #include "instance.hpp" +#include "types.hpp" #include #include diff --git a/include/curl_wrapper.hpp b/include/curl_wrapper.hpp index 30fa1ce..890b36e 100644 --- a/include/curl_wrapper.hpp +++ b/include/curl_wrapper.hpp @@ -17,28 +17,20 @@ #ifndef MASTODONPP_CURL_WRAPPER_HPP #define MASTODONPP_CURL_WRAPPER_HPP -#include "answer.hpp" +#include "types.hpp" #include "curl/curl.h" -#include #include #include #include -#include -#include -#include namespace mastodonpp { -using std::map; using std::mutex; using std::string; using std::string_view; -using std::pair; -using std::variant; -using std::vector; /*! * @brief The HTTP method. @@ -54,36 +46,6 @@ enum class http_method DELETE }; -/*! - * @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 - * { - * {"poll[expires_in]", "86400"}, - * {"poll[options]", vector{"Yes", "No", "Maybe"}}, - * {"status", "How is the weather?"} - * }; - * @endcode - * - * @since 0.1.0 - */ -using parametermap = - map>>; - -/*! - * @brief A single parameter of a parametermap. - * - * @since 0.1.0 - */ -using parameterpair = - pair>>; - /*! * @brief Handles the details of network connections. * diff --git a/include/instance.hpp b/include/instance.hpp index df62646..a7feb78 100644 --- a/include/instance.hpp +++ b/include/instance.hpp @@ -18,7 +18,7 @@ #define MASTODONPP_INSTANCE_HPP #include "curl_wrapper.hpp" -#include "answer.hpp" +#include "types.hpp" #include #include diff --git a/include/mastodonpp.hpp b/include/mastodonpp.hpp index ca396d7..65c1af7 100644 --- a/include/mastodonpp.hpp +++ b/include/mastodonpp.hpp @@ -17,11 +17,11 @@ #ifndef MASTODONPP_HPP #define MASTODONPP_HPP -#include "answer.hpp" #include "api.hpp" #include "connection.hpp" #include "exceptions.hpp" #include "instance.hpp" +#include "types.hpp" /*! * @headerfile mastodonpp.hpp mastodonpp/mastodonpp.hpp diff --git a/include/answer.hpp b/include/types.hpp similarity index 58% rename from include/answer.hpp rename to include/types.hpp index b820a70..bfd5e42 100644 --- a/include/answer.hpp +++ b/include/types.hpp @@ -14,22 +14,62 @@ * along with this program. If not, see . */ -#ifndef MASTODONPP_ANSWER_HPP -#define MASTODONPP_ANSWER_HPP +// Types that are used in more than one file. + +#ifndef MASTODONPP_TYPES_HPP +#define MASTODONPP_TYPES_HPP #include +#include #include #include #include +#include +#include +#include namespace mastodonpp { using std::uint8_t; using std::uint16_t; +using std::map; using std::ostream; using std::string; using std::string_view; +using std::pair; +using std::variant; +using std::vector; + +/*! + * @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 + * { + * {"poll[expires_in]", "86400"}, + * {"poll[options]", vector{"Yes", "No", "Maybe"}}, + * {"status", "How is the weather?"} + * }; + * @endcode + * + * @since 0.1.0 + */ +using parametermap = + map>>; + +/*! + * @brief A single parameter of a parametermap. + * + * @since 0.1.0 + */ +using parameterpair = + pair>>; /*! * @brief Return type for Request%s. @@ -110,9 +150,48 @@ struct answer_type * * @since 0.1.0 */ + [[nodiscard]] string_view get_header(string_view field) const; + + /*! + * @brief Returns the parameters needed for the next entries. + * + * Parses the `Link` header. + * + * @since 0.3.0 + */ + [[nodiscard]] + inline parametermap next() const + { + return parse_pagination(true); + } + + /*! + * @brief Returns the parameters needed for the previous entries. + * + * + * Parses the `Link` header. + * + * @since 0.3.0 + */ + [[nodiscard]] + inline parametermap prev() const + { + return parse_pagination(false); + } + +private: + /*! + * @brief Returns the parameters needed for the next or previous entries. + * + * + * Parses the `Link` header. + * + * @since 0.3.0 + */ + parametermap parse_pagination(bool next) const; }; } // namespace mastodonpp -#endif // MASTODONPP_ANSWER_HPP +#endif // MASTODONPP_TYPES_HPP diff --git a/src/instance.cpp b/src/instance.cpp index cd99645..5e9126d 100644 --- a/src/instance.cpp +++ b/src/instance.cpp @@ -14,7 +14,6 @@ * along with this program. If not, see . */ -#include "answer.hpp" #include "instance.hpp" #include "log.hpp" diff --git a/src/answer.cpp b/src/types.cpp similarity index 61% rename from src/answer.cpp rename to src/types.cpp index 0db9176..8c4575c 100644 --- a/src/answer.cpp +++ b/src/types.cpp @@ -14,7 +14,8 @@ * along with this program. If not, see . */ -#include "answer.hpp" +#include "log.hpp" +#include "types.hpp" #include #include @@ -60,4 +61,40 @@ string_view answer_type::get_header(const string_view field) const return {}; } +parametermap answer_type::parse_pagination(const bool next) const +{ + const string_view link{get_header("Link")}; + if (link.empty()) + { + return {}; + } + + const auto direction{next ? R"(rel="next")" : R"(rel="prev")"}; + auto endpos{link.find(direction)}; + endpos = link.rfind('>', endpos); + auto startpos{link.rfind('?', endpos) + 1}; + const string_view paramstr{link.substr(startpos, endpos - startpos)}; + debuglog << "Found parameters in Link header: " << paramstr << '\n'; + + startpos = 0; + parametermap parameters; + while ((endpos = paramstr.find('=', startpos)) != string_view::npos) + { + parameterpair param; + param.first = paramstr.substr(startpos, endpos - startpos); + startpos = endpos + 1; + endpos = paramstr.find('&', startpos); + param.second = paramstr.substr(startpos, endpos - startpos); + parameters.insert(param); + + if (endpos == string_view::npos) + { + break; + } + startpos = endpos + 1; + } + + return parameters; +} + } // namespace mastodonpp From aafb55e7eb9061fa393a029f82d5322e961d0ce4 Mon Sep 17 00:00:00 2001 From: tastytea Date: Tue, 14 Jan 2020 20:44:58 +0100 Subject: [PATCH 2/8] Tick boxes in readme. --- README.adoc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.adoc b/README.adoc index 645fe31..e0d94c3 100644 --- a/README.adoc +++ b/README.adoc @@ -31,9 +31,9 @@ This is still a work in progress; here is a rough overview of the features: * [x] `GET`, Streaming `GET`, `POST`, `PATCH`, `PUT` and `DELETE` requests. * [x] Report maximum allowed character per post. -* [ ] Comfortable access to pagination headers. -* [ ] Comfortable function to register a new “app” (get an access token). -* [ ] Report which mime types are allowed for posting statuses. +* [x] Comfortable access to pagination headers. +* [x] Comfortable function to register a new “app” (get an access token). +* [x] Report which mime types are allowed for posting statuses. == Usage From 223db7b2555958e37ab149303e05e5d54edd81bc Mon Sep 17 00:00:00 2001 From: tastytea Date: Tue, 14 Jan 2020 21:03:04 +0100 Subject: [PATCH 3/8] Use regex to extract max_toot_chars. --- src/instance.cpp | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/instance.cpp b/src/instance.cpp index 5e9126d..7b1d5c1 100644 --- a/src/instance.cpp +++ b/src/instance.cpp @@ -53,19 +53,16 @@ uint64_t Instance::get_max_chars() noexcept _max_chars = [&answer] { - auto &body{answer.body}; - size_t pos_start{body.find("max_toot_chars")}; - if (pos_start == string::npos) - { - debuglog << "max_toot_chars not found.\n"; - return default_max_chars; - } - pos_start = body.find(':', pos_start) + 1; - const size_t pos_end{body.find(',', pos_start)}; + const regex re_chars{R"("max_toot_chars"\s*:\s*([^"]+))"}; + smatch match; - const auto max_toot_chars{body.substr(pos_start, - pos_end - pos_start)}; - return static_cast(stoull(max_toot_chars)); + if (regex_search(answer.body, match, re_chars)) + { + return static_cast(stoull(match[1].str())); + } + + debuglog << "max_toot_chars not found.\n"; + return default_max_chars; }(); debuglog << "Set _max_chars to: " << _max_chars << '\n'; } From 0d8b2e849077be8a67c22e95ff90f13a56bfd3e7 Mon Sep 17 00:00:00 2001 From: tastytea Date: Tue, 14 Jan 2020 21:53:42 +0100 Subject: [PATCH 4/8] Use regex to extract NodeInfo addresses. --- src/instance.cpp | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/instance.cpp b/src/instance.cpp index 7b1d5c1..ac2f308 100644 --- a/src/instance.cpp +++ b/src/instance.cpp @@ -77,7 +77,6 @@ uint64_t Instance::get_max_chars() noexcept answer_type Instance::get_nodeinfo() { - debuglog << "Finding location of NodeInfo on " << _hostname << "…\n"; auto answer{make_request(http_method::GET, _baseuri + "/.well-known/nodeinfo", {})}; if (!answer) @@ -86,15 +85,15 @@ answer_type Instance::get_nodeinfo() return answer; } - size_t pos{0}; vector hrefs; - constexpr string_view searchstring{R"("href":")"}; - while ((pos = answer.body.find(searchstring, pos)) != string::npos) + const regex re_href{R"("href"\s*:\s*"([^"]+)\")"}; + smatch match; + string body = answer.body; + while (regex_search(body, match, re_href)) { - pos += searchstring.size(); - auto endpos{answer.body.find('"', pos)}; - hrefs.push_back(answer.body.substr(pos, endpos - pos)); + hrefs.push_back(match[1].str()); debuglog << "Found href: " << hrefs.back() << '\n'; + body = match.suffix(); } sort(hrefs.begin(), hrefs.end()); // We assume they are sortable strings. debuglog << "Selecting href: " << hrefs.back() << '\n'; From bd7952e901ab103051faa945f5aa49e9bb3b026d Mon Sep 17 00:00:00 2001 From: tastytea Date: Tue, 14 Jan 2020 22:21:09 +0100 Subject: [PATCH 5/8] Fixed the minimum libcurl version for rpms. --- cmake/packages.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/packages.cmake b/cmake/packages.cmake index f14d273..c0929e7 100644 --- a/cmake/packages.cmake +++ b/cmake/packages.cmake @@ -38,7 +38,7 @@ if(WITH_RPM) set(CPACK_GENERATOR "RPM") set(CPACK_RPM_PACKAGE_LICENSE "AGPL-3") set(CPACK_RPM_PACKAGE_URL "https://schlomp.space/tastytea/${PROJECT_NAME}") - set(CPACK_RPM_PACKAGE_REQUIRES "libcurl >= 7.32") + set(CPACK_RPM_PACKAGE_REQUIRES "libcurl >= 7.56") list(APPEND CPACK_RPM_EXCLUDE_FROM_AUTO_FILELIST_ADDITION "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}/cmake" "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}/pkgconfig") From c192f352f004c3a24a8012c8fdc9ce83d7fbd15a Mon Sep 17 00:00:00 2001 From: tastytea Date: Tue, 14 Jan 2020 22:28:14 +0100 Subject: [PATCH 6/8] Extract post formats via regex. --- src/instance.cpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/instance.cpp b/src/instance.cpp index ac2f308..fb8db81 100644 --- a/src/instance.cpp +++ b/src/instance.cpp @@ -121,23 +121,23 @@ vector Instance::get_post_formats() noexcept return _post_formats; } - constexpr string_view searchstring{R"("postFormats":[)"}; - auto pos{answer.body.find(searchstring)}; - if (pos == string::npos) + const regex re_allformats(R"("postFormats"\s*:\s*\[([^\]]+)\])"); + smatch match; + if (!regex_search(answer.body, match, re_allformats)) { debuglog << "Couldn't find metadata.postFormats.\n"; _post_formats = {default_value}; return _post_formats; } - pos += searchstring.size(); - auto endpos{answer.body.find("],", pos)}; - string formats{answer.body.substr(pos, endpos - pos)}; - debuglog << "Extracted postFormats: " << formats << '\n'; + string allformats{match[1].str()}; + debuglog << "Found postFormats: " << allformats << '\n'; - while ((pos = formats.find('"', 1)) != string::npos) + const regex re_format(R"(\s*"([^"]+)\"\s*,?)"); + + while (regex_search(allformats, match, re_format)) { - _post_formats.push_back(formats.substr(1, pos - 1)); - formats.erase(0, pos + 2); // 2 is the length of: ", + _post_formats.push_back(match[1].str()); + allformats = match.suffix(); debuglog << "Found postFormat: " << _post_formats.back() << '\n'; } } From 0d8caf93e229bdd45d57aedfeaf4ed6c901f31d7 Mon Sep 17 00:00:00 2001 From: tastytea Date: Tue, 14 Jan 2020 22:47:23 +0100 Subject: [PATCH 7/8] Fixed endpoint translations. --- src/api.cpp | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/api.cpp b/src/api.cpp index 9a0dae6..66e963b 100644 --- a/src/api.cpp +++ b/src/api.cpp @@ -38,7 +38,7 @@ const map API::_endpoint_map {v1::accounts_id_followers, "/api/v1/accounts//followers"}, {v1::accounts_id_following, "/api/v1/accounts//following"}, {v1::accounts_id_lists, "/api/v1/accounts//lists"}, - {v1::accounts_id_identity_proofs, "/api/v1/accounts//identity/proofs"}, + {v1::accounts_id_identity_proofs, "/api/v1/accounts//identity_proofs"}, {v1::accounts_id_follow, "/api/v1/accounts//follow"}, {v1::accounts_id_unfollow, "/api/v1/accounts//unfollow"}, {v1::accounts_id_block, "/api/v1/accounts//block"}, @@ -58,23 +58,23 @@ const map API::_endpoint_map {v1::blocks, "/api/v1/blocks"}, - {v1::domain_blocks, "/api/v1/domain/blocks"}, + {v1::domain_blocks, "/api/v1/domain_blocks"}, {v1::filters, "/api/v1/filters"}, {v1::filters_id, "/api/v1/filters/"}, {v1::reports, "/api/v1/reports"}, - {v1::follow_requests, "/api/v1/follow/requests"}, + {v1::follow_requests, "/api/v1/follow_requests"}, {v1::follow_requests_id_authorize, - "/api/v1/follow/requests//authorize"}, - {v1::follow_requests_id_reject, "/api/v1/follow/requests//reject"}, + "/api/v1/follow_requests//authorize"}, + {v1::follow_requests_id_reject, "/api/v1/follow_requests//reject"}, {v1::endorsements, "/api/v1/endorsements"}, {v1::featured_tags, "/api/v1/featured/tags"}, - {v1::featured_tags_id, "/api/v1/featured/tags/"}, - {v1::featured_tags_suggestions, "/api/v1/featured/tags/suggestions"}, + {v1::featured_tags_id, "/api/v1/featured_tags/"}, + {v1::featured_tags_suggestions, "/api/v1/featured_tags/suggestions"}, {v1::preferences, "/api/v1/preferences"}, @@ -84,8 +84,8 @@ const map API::_endpoint_map {v1::statuses, "/api/v1/statuses"}, {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"}, + {v1::statuses_id_reblogged_by, "/api/v1/statuses//reblogged_by"}, + {v1::statuses_id_favourited_by, "/api/v1/statuses//favourited_by"}, {v1::statuses_id_favourite, "/api/v1/statuses//favourite"}, {v1::statuses_id_unfavourite, "/api/v1/statuses//unfavourite"}, {v1::statuses_id_reblog, "/api/v1/statuses//reblog"}, @@ -103,13 +103,13 @@ const map API::_endpoint_map {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/"}, + {v1::scheduled_statuses, "/api/v1/scheduled_statuses"}, + {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/"}, + {v1::timelines_list_list_id, "/api/v1/timelines/list/"}, {v1::conversations, "/api/v1/conversations"}, {v1::conversations_id, "/api/v1/conversations/"}, @@ -150,7 +150,7 @@ const map API::_endpoint_map {v1::admin_accounts, "/api/v1/admin/accounts"}, {v1::admin_accounts_id, "/api/v1/admin/accounts/"}, {v1::admin_accounts_account_id_action, - "/api/v1/admin/accounts/account//action"}, + "/api/v1/admin/accounts//action"}, {v1::admin_accounts_id_approve, "/api/v1/admin/accounts//approve"}, {v1::admin_accounts_id_reject, "/api/v1/admin/accounts//reject"}, {v1::admin_accounts_id_enable, "/api/v1/admin/accounts//enable"}, @@ -159,7 +159,7 @@ const map API::_endpoint_map {v1::admin_reports, "/api/v1/admin/reports"}, {v1::admin_reports_id, "/api/v1/admin/reports/"}, {v1::admin_reports_id_assign_to_self, - "/api/v1/admin/reports//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_reopen, "/api/v1/admin/reports//reopen"}, From 59a9747c06ef72d362bf51888b84c6116952e64d Mon Sep 17 00:00:00 2001 From: tastytea Date: Tue, 14 Jan 2020 22:50:14 +0100 Subject: [PATCH 8/8] Add account_id and list_id to list of parameters to replace in URIs. --- src/curl_wrapper.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/curl_wrapper.cpp b/src/curl_wrapper.cpp index 0fb0540..b034bf9 100644 --- a/src/curl_wrapper.cpp +++ b/src/curl_wrapper.cpp @@ -353,8 +353,8 @@ bool CURLWrapper::replace_parameter_in_uri(string &uri, { static constexpr array replace { - "id", "nickname", "nickname_or_id", - "hashtag", "permission_group" + "id", "nickname", "nickname_or_id", "account_id", + "list_id", "hashtag", "permission_group" }; if (any_of(replace.begin(), replace.end(), [¶meter](const auto &s) { return s == parameter.first; }))