Merge branch 'develop' into main
This commit is contained in:
commit
f2594d2c46
|
@ -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
|
||||
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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 <string>
|
||||
#include <string_view>
|
||||
|
|
|
@ -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 <map>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
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 “<tt>\@file:</tt>” followed by the file
|
||||
* name as value.
|
||||
*
|
||||
* Example:
|
||||
* @code
|
||||
* parametermap parameters
|
||||
* {
|
||||
* {"poll[expires_in]", "86400"},
|
||||
* {"poll[options]", vector<string_view>{"Yes", "No", "Maybe"}},
|
||||
* {"status", "How is the weather?"}
|
||||
* };
|
||||
* @endcode
|
||||
*
|
||||
* @since 0.1.0
|
||||
*/
|
||||
using parametermap =
|
||||
map<string_view, variant<string_view, vector<string_view>>>;
|
||||
|
||||
/*!
|
||||
* @brief A single parameter of a parametermap.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*/
|
||||
using parameterpair =
|
||||
pair<string_view, variant<string_view, vector<string_view>>>;
|
||||
|
||||
/*!
|
||||
* @brief Handles the details of network connections.
|
||||
*
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
#define MASTODONPP_INSTANCE_HPP
|
||||
|
||||
#include "curl_wrapper.hpp"
|
||||
#include "answer.hpp"
|
||||
#include "types.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -14,22 +14,62 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#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 <cstdint>
|
||||
#include <map>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
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 “<tt>\@file:</tt>” followed by the file
|
||||
* name as value.
|
||||
*
|
||||
* Example:
|
||||
* @code
|
||||
* parametermap parameters
|
||||
* {
|
||||
* {"poll[expires_in]", "86400"},
|
||||
* {"poll[options]", vector<string_view>{"Yes", "No", "Maybe"}},
|
||||
* {"status", "How is the weather?"}
|
||||
* };
|
||||
* @endcode
|
||||
*
|
||||
* @since 0.1.0
|
||||
*/
|
||||
using parametermap =
|
||||
map<string_view, variant<string_view, vector<string_view>>>;
|
||||
|
||||
/*!
|
||||
* @brief A single parameter of a parametermap.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*/
|
||||
using parameterpair =
|
||||
pair<string_view, variant<string_view, vector<string_view>>>;
|
||||
|
||||
/*!
|
||||
* @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
|
28
src/api.cpp
28
src/api.cpp
|
@ -38,7 +38,7 @@ const map<API::endpoint_type,string_view> API::_endpoint_map
|
|||
{v1::accounts_id_followers, "/api/v1/accounts/<ID>/followers"},
|
||||
{v1::accounts_id_following, "/api/v1/accounts/<ID>/following"},
|
||||
{v1::accounts_id_lists, "/api/v1/accounts/<ID>/lists"},
|
||||
{v1::accounts_id_identity_proofs, "/api/v1/accounts/<ID>/identity/proofs"},
|
||||
{v1::accounts_id_identity_proofs, "/api/v1/accounts/<ID>/identity_proofs"},
|
||||
{v1::accounts_id_follow, "/api/v1/accounts/<ID>/follow"},
|
||||
{v1::accounts_id_unfollow, "/api/v1/accounts/<ID>/unfollow"},
|
||||
{v1::accounts_id_block, "/api/v1/accounts/<ID>/block"},
|
||||
|
@ -58,23 +58,23 @@ const map<API::endpoint_type,string_view> 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/<ID>"},
|
||||
|
||||
{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/<ID>/authorize"},
|
||||
{v1::follow_requests_id_reject, "/api/v1/follow/requests/<ID>/reject"},
|
||||
"/api/v1/follow_requests/<ID>/authorize"},
|
||||
{v1::follow_requests_id_reject, "/api/v1/follow_requests/<ID>/reject"},
|
||||
|
||||
{v1::endorsements, "/api/v1/endorsements"},
|
||||
|
||||
{v1::featured_tags, "/api/v1/featured/tags"},
|
||||
{v1::featured_tags_id, "/api/v1/featured/tags/<ID>"},
|
||||
{v1::featured_tags_suggestions, "/api/v1/featured/tags/suggestions"},
|
||||
{v1::featured_tags_id, "/api/v1/featured_tags/<ID>"},
|
||||
{v1::featured_tags_suggestions, "/api/v1/featured_tags/suggestions"},
|
||||
|
||||
{v1::preferences, "/api/v1/preferences"},
|
||||
|
||||
|
@ -84,8 +84,8 @@ const map<API::endpoint_type,string_view> API::_endpoint_map
|
|||
{v1::statuses, "/api/v1/statuses"},
|
||||
{v1::statuses_id, "/api/v1/statuses/<ID>"},
|
||||
{v1::statuses_id_context, "/api/v1/statuses/<ID>/context"},
|
||||
{v1::statuses_id_reblogged_by, "/api/v1/statuses/<ID>/reblogged/by"},
|
||||
{v1::statuses_id_favourited_by, "/api/v1/statuses/<ID>/favourited/by"},
|
||||
{v1::statuses_id_reblogged_by, "/api/v1/statuses/<ID>/reblogged_by"},
|
||||
{v1::statuses_id_favourited_by, "/api/v1/statuses/<ID>/favourited_by"},
|
||||
{v1::statuses_id_favourite, "/api/v1/statuses/<ID>/favourite"},
|
||||
{v1::statuses_id_unfavourite, "/api/v1/statuses/<ID>/unfavourite"},
|
||||
{v1::statuses_id_reblog, "/api/v1/statuses/<ID>/reblog"},
|
||||
|
@ -103,13 +103,13 @@ const map<API::endpoint_type,string_view> API::_endpoint_map
|
|||
{v1::polls_id, "/api/v1/polls/<ID>"},
|
||||
{v1::polls_id_votes, "/api/v1/polls/<ID>/votes"},
|
||||
|
||||
{v1::scheduled_statuses, "/api/v1/scheduled/statuses"},
|
||||
{v1::scheduled_statuses_id, "/api/v1/scheduled/statuses/<ID>"},
|
||||
{v1::scheduled_statuses, "/api/v1/scheduled_statuses"},
|
||||
{v1::scheduled_statuses_id, "/api/v1/scheduled_statuses/<ID>"},
|
||||
|
||||
{v1::timelines_public, "/api/v1/timelines/public"},
|
||||
{v1::timelines_tag_hashtag, "/api/v1/timelines/tag/<HASHTAG>"},
|
||||
{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_ID>"},
|
||||
|
||||
{v1::conversations, "/api/v1/conversations"},
|
||||
{v1::conversations_id, "/api/v1/conversations/<ID>"},
|
||||
|
@ -150,7 +150,7 @@ const map<API::endpoint_type,string_view> API::_endpoint_map
|
|||
{v1::admin_accounts, "/api/v1/admin/accounts"},
|
||||
{v1::admin_accounts_id, "/api/v1/admin/accounts/<ID>"},
|
||||
{v1::admin_accounts_account_id_action,
|
||||
"/api/v1/admin/accounts/account/<ID>/action"},
|
||||
"/api/v1/admin/accounts/<ACCOUNT_ID>/action"},
|
||||
{v1::admin_accounts_id_approve, "/api/v1/admin/accounts/<ID>/approve"},
|
||||
{v1::admin_accounts_id_reject, "/api/v1/admin/accounts/<ID>/reject"},
|
||||
{v1::admin_accounts_id_enable, "/api/v1/admin/accounts/<ID>/enable"},
|
||||
|
@ -159,7 +159,7 @@ const map<API::endpoint_type,string_view> API::_endpoint_map
|
|||
{v1::admin_reports, "/api/v1/admin/reports"},
|
||||
{v1::admin_reports_id, "/api/v1/admin/reports/<ID>"},
|
||||
{v1::admin_reports_id_assign_to_self,
|
||||
"/api/v1/admin/reports/<ID>/assign/to/self"},
|
||||
"/api/v1/admin/reports/<ID>/assign_to_self"},
|
||||
{v1::admin_reports_id_unassign, "/api/v1/admin/reports/<ID>/unassign"},
|
||||
{v1::admin_reports_id_resolve, "/api/v1/admin/reports/<ID>/resolve"},
|
||||
{v1::admin_reports_id_reopen, "/api/v1/admin/reports/<ID>/reopen"},
|
||||
|
|
|
@ -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; }))
|
||||
|
|
|
@ -14,7 +14,6 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "answer.hpp"
|
||||
#include "instance.hpp"
|
||||
#include "log.hpp"
|
||||
|
||||
|
@ -54,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<uint64_t>(stoull(max_toot_chars));
|
||||
if (regex_search(answer.body, match, re_chars))
|
||||
{
|
||||
return static_cast<uint64_t>(stoull(match[1].str()));
|
||||
}
|
||||
|
||||
debuglog << "max_toot_chars not found.\n";
|
||||
return default_max_chars;
|
||||
}();
|
||||
debuglog << "Set _max_chars to: " << _max_chars << '\n';
|
||||
}
|
||||
|
@ -81,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)
|
||||
|
@ -90,15 +85,15 @@ answer_type Instance::get_nodeinfo()
|
|||
return answer;
|
||||
}
|
||||
|
||||
size_t pos{0};
|
||||
vector<string> 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';
|
||||
|
@ -126,23 +121,23 @@ vector<string> 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';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,7 +14,8 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "answer.hpp"
|
||||
#include "log.hpp"
|
||||
#include "types.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
|
@ -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
|
Loading…
Reference in New Issue
Block a user