Merge branch 'develop' into main
This commit is contained in:
commit
c049af3212
@ -51,7 +51,7 @@ int main(int argc, char *argv[])
|
|||||||
const auto answer{connection.patch(
|
const auto answer{connection.patch(
|
||||||
masto::API::v1::accounts_update_credentials,
|
masto::API::v1::accounts_update_credentials,
|
||||||
{
|
{
|
||||||
{"display_name", name},
|
{"display_name", name}
|
||||||
})};
|
})};
|
||||||
if (answer)
|
if (answer)
|
||||||
{
|
{
|
||||||
|
111
examples/example08_obtain_token.cpp
Normal file
111
examples/example08_obtain_token.cpp
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
/* This file is part of mastodonpp.
|
||||||
|
* Copyright © 2020 tastytea <tastytea@tastytea.de>
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Obtain an access token and verify that it works.
|
||||||
|
|
||||||
|
#include "mastodonpp.hpp"
|
||||||
|
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace masto = mastodonpp;
|
||||||
|
using std::exit;
|
||||||
|
using std::cout;
|
||||||
|
using std::cerr;
|
||||||
|
using std::endl;
|
||||||
|
using std::cin;
|
||||||
|
using std::string;
|
||||||
|
using std::to_string;
|
||||||
|
using std::string_view;
|
||||||
|
using std::vector;
|
||||||
|
|
||||||
|
void handle_error(const masto::answer_type &answer);
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
const vector<string_view> args(argv, argv + argc);
|
||||||
|
if (args.size() <= 1)
|
||||||
|
{
|
||||||
|
cerr << "Usage: " << args[0] << " <instance hostname>\n";
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Initialize Instance and Instance::ObtainToken.
|
||||||
|
masto::Instance instance{args[1], {}};
|
||||||
|
masto::Instance::ObtainToken token{instance};
|
||||||
|
|
||||||
|
// Create an “Application” (/api/v1/apps),
|
||||||
|
// and get URI for the authorization code (/oauth/authorize).
|
||||||
|
auto answer{token.step_1("Testclient", "read:blocks read:mutes",
|
||||||
|
"https://tastytea.de/")};
|
||||||
|
if (!answer)
|
||||||
|
{
|
||||||
|
handle_error(answer);
|
||||||
|
}
|
||||||
|
|
||||||
|
cout << "Please visit " << answer << "\nand paste the code here: ";
|
||||||
|
string code;
|
||||||
|
cin >> code;
|
||||||
|
|
||||||
|
// Obtain the token (/oauth/token).
|
||||||
|
answer = token.step_2(code);
|
||||||
|
if (!answer)
|
||||||
|
{
|
||||||
|
handle_error(answer);
|
||||||
|
}
|
||||||
|
|
||||||
|
cout << "Your access token is: " << answer << endl;
|
||||||
|
|
||||||
|
// Test if the token works.
|
||||||
|
masto::Connection connection{instance};
|
||||||
|
answer = connection.get(masto::API::v1::apps_verify_credentials);
|
||||||
|
if (!answer)
|
||||||
|
{
|
||||||
|
handle_error(answer);
|
||||||
|
}
|
||||||
|
|
||||||
|
cout << answer << 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
void handle_error(const masto::answer_type &answer)
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
exit(1);
|
||||||
|
}
|
@ -18,6 +18,7 @@
|
|||||||
#define MASTODONPP_ANSWER_HPP
|
#define MASTODONPP_ANSWER_HPP
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <ostream>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
|
|
||||||
@ -26,6 +27,7 @@ namespace mastodonpp
|
|||||||
|
|
||||||
using std::uint8_t;
|
using std::uint8_t;
|
||||||
using std::uint16_t;
|
using std::uint16_t;
|
||||||
|
using std::ostream;
|
||||||
using std::string;
|
using std::string;
|
||||||
using std::string_view;
|
using std::string_view;
|
||||||
|
|
||||||
@ -97,8 +99,7 @@ struct answer_type
|
|||||||
*
|
*
|
||||||
* @since 0.1.0
|
* @since 0.1.0
|
||||||
*/
|
*/
|
||||||
friend std::ostream &operator <<(std::ostream &out,
|
friend ostream &operator <<(ostream &out, const answer_type &answer);
|
||||||
const answer_type &answer);
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* @brief Returns the value of a header field.
|
* @brief Returns the value of a header field.
|
||||||
|
@ -82,7 +82,12 @@ public:
|
|||||||
*
|
*
|
||||||
* @since 0.1.0
|
* @since 0.1.0
|
||||||
*/
|
*/
|
||||||
explicit Connection(Instance &instance);
|
explicit Connection(Instance &instance)
|
||||||
|
: _instance{instance}
|
||||||
|
, _baseuri{instance.get_baseuri()}
|
||||||
|
{
|
||||||
|
_instance.copy_connection_properties(*this);
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* @brief Make a HTTP GET call with parameters.
|
* @brief Make a HTTP GET call with parameters.
|
||||||
|
@ -145,18 +145,6 @@ public:
|
|||||||
return _connection;
|
return _connection;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
|
||||||
* @brief Set the proxy to use.
|
|
||||||
*
|
|
||||||
* See [CURLOPT_PROXY(3)]
|
|
||||||
* (https://curl.haxx.se/libcurl/c/CURLOPT_PROXY.html).
|
|
||||||
*
|
|
||||||
* @param proxy Examples: "socks4a://127.0.0.1:9050", "http://[::1]:3128".
|
|
||||||
*
|
|
||||||
* @since 0.1.0
|
|
||||||
*/
|
|
||||||
void set_proxy(string_view proxy);
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* @brief URL encodes the given string.
|
* @brief URL encodes the given string.
|
||||||
*
|
*
|
||||||
@ -169,6 +157,7 @@ public:
|
|||||||
*
|
*
|
||||||
* @since 0.3.0
|
* @since 0.3.0
|
||||||
*/
|
*/
|
||||||
|
[[nodiscard]]
|
||||||
inline string escape_url(const string_view url) const
|
inline string escape_url(const string_view url) const
|
||||||
{
|
{
|
||||||
char *cbuf{curl_easy_escape(_connection, url.data(),
|
char *cbuf{curl_easy_escape(_connection, url.data(),
|
||||||
@ -190,6 +179,7 @@ public:
|
|||||||
*
|
*
|
||||||
* @since 0.3.0
|
* @since 0.3.0
|
||||||
*/
|
*/
|
||||||
|
[[nodiscard]]
|
||||||
inline string unescape_url(const string_view url) const
|
inline string unescape_url(const string_view url) const
|
||||||
{
|
{
|
||||||
char *cbuf{curl_easy_unescape(_connection, url.data(),
|
char *cbuf{curl_easy_unescape(_connection, url.data(),
|
||||||
@ -199,6 +189,18 @@ public:
|
|||||||
return sbuf;
|
return sbuf;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* @brief Set some properties of the connection.
|
||||||
|
*
|
||||||
|
* Meant for internal use. See Instance::copy_connection_properties().
|
||||||
|
*
|
||||||
|
* @since 0.3.0
|
||||||
|
*/
|
||||||
|
void setup_connection_properties(string_view proxy,
|
||||||
|
string_view access_token,
|
||||||
|
string_view cainfo,
|
||||||
|
string_view useragent);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
/*!
|
/*!
|
||||||
* @brief Mutex for #get_buffer a.k.a. _curl_buffer_body.
|
* @brief Mutex for #get_buffer a.k.a. _curl_buffer_body.
|
||||||
@ -248,6 +250,18 @@ protected:
|
|||||||
_stream_cancelled = true;
|
_stream_cancelled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* @brief Set the proxy to use.
|
||||||
|
*
|
||||||
|
* See [CURLOPT_PROXY(3)]
|
||||||
|
* (https://curl.haxx.se/libcurl/c/CURLOPT_PROXY.html).
|
||||||
|
*
|
||||||
|
* @param proxy Examples: "socks4a://127.0.0.1:9050", "http://[::1]:3128".
|
||||||
|
*
|
||||||
|
* @since 0.1.0
|
||||||
|
*/
|
||||||
|
void set_proxy(string_view proxy);
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* @brief Set OAuth 2.0 Bearer Access Token.
|
* @brief Set OAuth 2.0 Bearer Access Token.
|
||||||
*
|
*
|
||||||
@ -259,10 +273,12 @@ protected:
|
|||||||
/*!
|
/*!
|
||||||
* @brief Set path to Certificate Authority (CA) bundle.
|
* @brief Set path to Certificate Authority (CA) bundle.
|
||||||
*
|
*
|
||||||
* @since 0.2.1
|
* @since 0.3.0
|
||||||
*/
|
*/
|
||||||
void set_cainfo(string_view path);
|
void set_cainfo(string_view path);
|
||||||
|
|
||||||
|
void set_useragent(string_view useragent);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
CURL *_connection;
|
CURL *_connection;
|
||||||
char _curl_buffer_error[CURL_ERROR_SIZE];
|
char _curl_buffer_error[CURL_ERROR_SIZE];
|
||||||
@ -356,7 +372,7 @@ private:
|
|||||||
* @param data Data of the field. If it begins with <tt>`\@file:<tt>, the
|
* @param data Data of the field. If it begins with <tt>`\@file:<tt>, the
|
||||||
* rest of the ergument is treated as a filename.
|
* rest of the ergument is treated as a filename.
|
||||||
*
|
*
|
||||||
* @since 0.1.1
|
* @since 0.2.0
|
||||||
*/
|
*/
|
||||||
void add_mime_part(curl_mime *mime,
|
void add_mime_part(curl_mime *mime,
|
||||||
string_view name, string_view data) const;
|
string_view name, string_view data) const;
|
||||||
|
@ -53,7 +53,29 @@ public:
|
|||||||
*
|
*
|
||||||
* @since 0.1.0
|
* @since 0.1.0
|
||||||
*/
|
*/
|
||||||
explicit Instance(string_view hostname, string_view access_token);
|
explicit Instance(const string_view hostname,
|
||||||
|
const string_view access_token)
|
||||||
|
: _hostname{hostname}
|
||||||
|
, _baseuri{"https://" + _hostname}
|
||||||
|
, _access_token{access_token}
|
||||||
|
, _max_chars{0}
|
||||||
|
{}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* @brief Set the properties of the connection of the calling class up.
|
||||||
|
*
|
||||||
|
* Meant for internal use. This aligns the properties of the connection of
|
||||||
|
* the calling class with the properties of connection of this class.
|
||||||
|
*
|
||||||
|
* @param curlwrapper The CURLWrapper parent of the calling class.
|
||||||
|
*
|
||||||
|
* @since 0.3.0
|
||||||
|
*/
|
||||||
|
inline void copy_connection_properties(CURLWrapper &curlwrapper)
|
||||||
|
{
|
||||||
|
curlwrapper.setup_connection_properties(_proxy, _access_token, _cainfo,
|
||||||
|
_useragent);
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* @brief Returns the hostname.
|
* @brief Returns the hostname.
|
||||||
@ -61,7 +83,7 @@ public:
|
|||||||
* @since 0.1.0
|
* @since 0.1.0
|
||||||
*/
|
*/
|
||||||
[[nodiscard]]
|
[[nodiscard]]
|
||||||
inline string_view get_hostname() const
|
inline string_view get_hostname() const noexcept
|
||||||
{
|
{
|
||||||
return _hostname;
|
return _hostname;
|
||||||
}
|
}
|
||||||
@ -74,7 +96,7 @@ public:
|
|||||||
* @since 0.1.0
|
* @since 0.1.0
|
||||||
*/
|
*/
|
||||||
[[nodiscard]]
|
[[nodiscard]]
|
||||||
inline string_view get_baseuri() const
|
inline string_view get_baseuri() const noexcept
|
||||||
{
|
{
|
||||||
return _baseuri;
|
return _baseuri;
|
||||||
}
|
}
|
||||||
@ -85,7 +107,7 @@ public:
|
|||||||
* @since 0.1.0
|
* @since 0.1.0
|
||||||
*/
|
*/
|
||||||
[[nodiscard]]
|
[[nodiscard]]
|
||||||
inline string_view get_access_token() const
|
inline string_view get_access_token() const noexcept
|
||||||
{
|
{
|
||||||
return _access_token;
|
return _access_token;
|
||||||
}
|
}
|
||||||
@ -115,7 +137,7 @@ public:
|
|||||||
* @since 0.1.0
|
* @since 0.1.0
|
||||||
*/
|
*/
|
||||||
[[nodiscard]]
|
[[nodiscard]]
|
||||||
uint64_t get_max_chars();
|
uint64_t get_max_chars() noexcept;
|
||||||
|
|
||||||
/*! @copydoc CURLWrapper::set_proxy(string_view)
|
/*! @copydoc CURLWrapper::set_proxy(string_view)
|
||||||
*
|
*
|
||||||
@ -128,19 +150,6 @@ public:
|
|||||||
CURLWrapper::set_proxy(proxy);
|
CURLWrapper::set_proxy(proxy);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
|
||||||
* @brief Returns the proxy string that was previously set.
|
|
||||||
*
|
|
||||||
* Does not return the proxy if it was set from an environment variable.
|
|
||||||
*
|
|
||||||
* @since 0.1.0
|
|
||||||
*/
|
|
||||||
[[nodiscard]]
|
|
||||||
string_view get_proxy() const
|
|
||||||
{
|
|
||||||
return _proxy;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* @brief Returns the NodeInfo of the instance.
|
* @brief Returns the NodeInfo of the instance.
|
||||||
*
|
*
|
||||||
@ -164,7 +173,7 @@ public:
|
|||||||
*
|
*
|
||||||
* @since 0.3.0
|
* @since 0.3.0
|
||||||
*/
|
*/
|
||||||
vector<string> get_post_formats();
|
vector<string> get_post_formats() noexcept;
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* @brief Set path to Certificate Authority (CA) bundle.
|
* @brief Set path to Certificate Authority (CA) bundle.
|
||||||
@ -172,7 +181,7 @@ public:
|
|||||||
* Sets also the CA info for all Connection%s that are initialized with
|
* Sets also the CA info for all Connection%s that are initialized with
|
||||||
* this Instance afterwards.
|
* this Instance afterwards.
|
||||||
*
|
*
|
||||||
* @since 0.2.1
|
* @since 0.3.0
|
||||||
*/
|
*/
|
||||||
void set_cainfo(string_view path)
|
void set_cainfo(string_view path)
|
||||||
{
|
{
|
||||||
@ -180,18 +189,95 @@ public:
|
|||||||
CURLWrapper::set_cainfo(path);
|
CURLWrapper::set_cainfo(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
void set_useragent(const string_view useragent)
|
||||||
* @brief Returns the cainfo path that was previously set.
|
|
||||||
*
|
|
||||||
* This is used when initializing a Connection.
|
|
||||||
*
|
|
||||||
* @since 0.2.1
|
|
||||||
*/
|
|
||||||
string_view get_cainfo()
|
|
||||||
{
|
{
|
||||||
return _cainfo;
|
_useragent = useragent;
|
||||||
|
CURLWrapper::set_useragent(useragent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* @brief Simplifies obtaining an OAuth 2.0 Bearer Access Token.
|
||||||
|
*
|
||||||
|
* * Create an Instance() and initialize this class with it.
|
||||||
|
* * Call step_1() to get the URI your user has to visit.
|
||||||
|
* * Get the authorization code from your user.
|
||||||
|
* * Call step_2() with the code.
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* @code
|
||||||
|
* mastodonpp::Instance instance("example.com", {});
|
||||||
|
* mastodonpp::Instance::ObtainToken token(instance);
|
||||||
|
* auto answer{token.step1("Good program", "read:blocks read:mutes", "")};
|
||||||
|
* if (answer)
|
||||||
|
* {
|
||||||
|
* std::cout << "Please visit " << answer << "\nand paste the code: ";
|
||||||
|
* std::string code;
|
||||||
|
* std::cin >> code;
|
||||||
|
* answer = access_token{token.step2(code)};
|
||||||
|
* if (answer)
|
||||||
|
* {
|
||||||
|
* std::cout << "Success!\n";
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* @endcode
|
||||||
|
*
|
||||||
|
* @since 0.3.0
|
||||||
|
*/
|
||||||
|
class ObtainToken : public CURLWrapper
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ObtainToken(Instance &instance)
|
||||||
|
: _instance{instance}
|
||||||
|
, _baseuri{instance.get_baseuri()}
|
||||||
|
{
|
||||||
|
_instance.copy_connection_properties(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* @brief Creates an application via `/api/v1/apps`.
|
||||||
|
*
|
||||||
|
* The `body` of the returned @link answer_type answer @endlink
|
||||||
|
* contains only the URI, not the whole JSON response.
|
||||||
|
*
|
||||||
|
* @param client_name The name of your application.
|
||||||
|
* @param scopes Space separated list of scopes. Defaults to
|
||||||
|
* “read” if empty.
|
||||||
|
* @param website The URI to the homepage of your application. Can
|
||||||
|
* be an empty string.
|
||||||
|
*
|
||||||
|
* @return The URI your user has to visit.
|
||||||
|
*
|
||||||
|
* @since 0.3.0
|
||||||
|
*/
|
||||||
|
[[nodiscard]]
|
||||||
|
answer_type step_1(string_view client_name, string_view scopes,
|
||||||
|
string_view website);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* @brief Creates a token via `/oauth/token`.
|
||||||
|
*
|
||||||
|
* The `body` of the returned @link answer_type answer @endlink
|
||||||
|
* contains only the access token, not the whole JSON response.
|
||||||
|
*
|
||||||
|
* The access token will be set in the parent Instance.
|
||||||
|
*
|
||||||
|
* @param code The authorization code you got from the user.
|
||||||
|
*
|
||||||
|
* @return The access token.
|
||||||
|
*
|
||||||
|
* @since 0.3.0
|
||||||
|
*/
|
||||||
|
[[nodiscard]]
|
||||||
|
answer_type step_2(string_view code);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Instance &_instance;
|
||||||
|
const string _baseuri;
|
||||||
|
string _scopes;
|
||||||
|
string _client_id;
|
||||||
|
string _client_secret;
|
||||||
|
};
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const string _hostname;
|
const string _hostname;
|
||||||
const string _baseuri;
|
const string _baseuri;
|
||||||
@ -200,6 +286,7 @@ private:
|
|||||||
string _proxy;
|
string _proxy;
|
||||||
vector<string> _post_formats;
|
vector<string> _post_formats;
|
||||||
string _cainfo;
|
string _cainfo;
|
||||||
|
string _useragent;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace mastodonpp
|
} // namespace mastodonpp
|
||||||
|
@ -105,6 +105,7 @@
|
|||||||
* @example example05_update_notification_settings.cpp
|
* @example example05_update_notification_settings.cpp
|
||||||
* @example example06_update_name.cpp
|
* @example example06_update_name.cpp
|
||||||
* @example example07_delete_status.cpp
|
* @example example07_delete_status.cpp
|
||||||
|
* @example example08_obtain_token.cpp
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
|
@ -28,7 +28,7 @@ API::API(const endpoint_type &endpoint)
|
|||||||
const map<API::endpoint_type,string_view> API::_endpoint_map
|
const map<API::endpoint_type,string_view> API::_endpoint_map
|
||||||
{
|
{
|
||||||
{v1::apps, "/api/v1/apps"},
|
{v1::apps, "/api/v1/apps"},
|
||||||
{v1::apps_verify_credentials, "/api/v1/apps/verify/credentials"},
|
{v1::apps_verify_credentials, "/api/v1/apps/verify_credentials"},
|
||||||
|
|
||||||
{v1::accounts, "/api/v1/accounts"},
|
{v1::accounts, "/api/v1/accounts"},
|
||||||
{v1::accounts_verify_credentials, "/api/v1/accounts/verify_credentials"},
|
{v1::accounts_verify_credentials, "/api/v1/accounts/verify_credentials"},
|
||||||
|
@ -21,26 +21,6 @@ namespace mastodonpp
|
|||||||
|
|
||||||
using std::holds_alternative;
|
using std::holds_alternative;
|
||||||
|
|
||||||
Connection::Connection(Instance &instance)
|
|
||||||
: _instance{instance}
|
|
||||||
, _baseuri{instance.get_baseuri()}
|
|
||||||
{
|
|
||||||
auto proxy{_instance.get_proxy()};
|
|
||||||
if (!proxy.empty())
|
|
||||||
{
|
|
||||||
CURLWrapper::set_proxy(proxy);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!_instance.get_access_token().empty())
|
|
||||||
{
|
|
||||||
CURLWrapper::set_access_token(_instance.get_access_token());
|
|
||||||
}
|
|
||||||
if (!_instance.get_cainfo().empty())
|
|
||||||
{
|
|
||||||
CURLWrapper::set_cainfo(_instance.get_cainfo());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
string Connection::endpoint_to_uri(const endpoint_variant &endpoint) const
|
string Connection::endpoint_to_uri(const endpoint_variant &endpoint) const
|
||||||
{
|
{
|
||||||
if (holds_alternative<API::endpoint_type>(endpoint))
|
if (holds_alternative<API::endpoint_type>(endpoint))
|
||||||
|
@ -64,16 +64,6 @@ CURLWrapper::~CURLWrapper() noexcept
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void CURLWrapper::set_proxy(const string_view proxy)
|
|
||||||
{
|
|
||||||
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
|
|
||||||
CURLcode code{curl_easy_setopt(_connection, CURLOPT_PROXY, proxy.data())};
|
|
||||||
if (code != CURLE_OK)
|
|
||||||
{
|
|
||||||
throw CURLException{code, "Failed to set proxy", _curl_buffer_error};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
answer_type CURLWrapper::make_request(const http_method &method, string uri,
|
answer_type CURLWrapper::make_request(const http_method &method, string uri,
|
||||||
const parametermap ¶meters)
|
const parametermap ¶meters)
|
||||||
{
|
{
|
||||||
@ -197,6 +187,42 @@ answer_type CURLWrapper::make_request(const http_method &method, string uri,
|
|||||||
return answer;
|
return answer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CURLWrapper::setup_connection_properties(const string_view proxy,
|
||||||
|
const string_view access_token,
|
||||||
|
const string_view cainfo,
|
||||||
|
const string_view useragent)
|
||||||
|
{
|
||||||
|
if (!proxy.empty())
|
||||||
|
{
|
||||||
|
set_proxy(proxy);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!access_token.empty())
|
||||||
|
{
|
||||||
|
set_access_token(access_token);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!cainfo.empty())
|
||||||
|
{
|
||||||
|
set_cainfo(cainfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!useragent.empty())
|
||||||
|
{
|
||||||
|
set_useragent(useragent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CURLWrapper::set_proxy(const string_view proxy)
|
||||||
|
{
|
||||||
|
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
|
||||||
|
CURLcode code{curl_easy_setopt(_connection, CURLOPT_PROXY, proxy.data())};
|
||||||
|
if (code != CURLE_OK)
|
||||||
|
{
|
||||||
|
throw CURLException{code, "Failed to set proxy", _curl_buffer_error};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void CURLWrapper::set_access_token(const string_view access_token)
|
void CURLWrapper::set_access_token(const string_view access_token)
|
||||||
{
|
{
|
||||||
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg, hicpp-signed-bitwise)
|
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg, hicpp-signed-bitwise)
|
||||||
@ -223,7 +249,7 @@ void CURLWrapper::set_access_token(const string_view access_token)
|
|||||||
debuglog << "Set authorization token.\n";
|
debuglog << "Set authorization token.\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
void CURLWrapper::set_cainfo(string_view path)
|
void CURLWrapper::set_cainfo(const string_view path)
|
||||||
{
|
{
|
||||||
CURLcode code{curl_easy_setopt(_connection, CURLOPT_CAINFO, path.data())};
|
CURLcode code{curl_easy_setopt(_connection, CURLOPT_CAINFO, path.data())};
|
||||||
if (code != CURLE_OK)
|
if (code != CURLE_OK)
|
||||||
@ -232,6 +258,19 @@ void CURLWrapper::set_cainfo(string_view path)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CURLWrapper::set_useragent(const string_view useragent)
|
||||||
|
{
|
||||||
|
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
|
||||||
|
CURLcode code{curl_easy_setopt(_connection, CURLOPT_USERAGENT,
|
||||||
|
useragent.data())};
|
||||||
|
if (code != CURLE_OK)
|
||||||
|
{
|
||||||
|
throw CURLException{code, "Failed to set User-Agent",
|
||||||
|
_curl_buffer_error};
|
||||||
|
}
|
||||||
|
debuglog << "Set User-Agent to: " << useragent << '\n';
|
||||||
|
}
|
||||||
|
|
||||||
size_t CURLWrapper::writer_body(char *data, size_t size, size_t nmemb)
|
size_t CURLWrapper::writer_body(char *data, size_t size, size_t nmemb)
|
||||||
{
|
{
|
||||||
if(data == nullptr)
|
if(data == nullptr)
|
||||||
@ -296,18 +335,11 @@ void CURLWrapper::setup_curl()
|
|||||||
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
|
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
|
||||||
curl_easy_setopt(_connection, CURLOPT_NOPROGRESS, 0L);
|
curl_easy_setopt(_connection, CURLOPT_NOPROGRESS, 0L);
|
||||||
|
|
||||||
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
|
set_useragent((string("mastodonpp/") += version));
|
||||||
CURLcode code{curl_easy_setopt(_connection, CURLOPT_USERAGENT,
|
|
||||||
(string("mastodonpp/") += version).c_str())};
|
|
||||||
if (code != CURLE_OK)
|
|
||||||
{
|
|
||||||
throw CURLException{code, "Failed to set User-Agent",
|
|
||||||
_curl_buffer_error};
|
|
||||||
}
|
|
||||||
|
|
||||||
// The next 2 only fail if HTTP is not supported.
|
// The next 2 only fail if HTTP is not supported.
|
||||||
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
|
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
|
||||||
code = curl_easy_setopt(_connection, CURLOPT_FOLLOWLOCATION, 1L);
|
CURLcode code{curl_easy_setopt(_connection, CURLOPT_FOLLOWLOCATION, 1L)};
|
||||||
if (code != CURLE_OK)
|
if (code != CURLE_OK)
|
||||||
{
|
{
|
||||||
throw CURLException{code, "HTTP is not supported.", _curl_buffer_error};
|
throw CURLException{code, "HTTP is not supported.", _curl_buffer_error};
|
||||||
|
217
src/instance.cpp
217
src/instance.cpp
@ -20,6 +20,7 @@
|
|||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <exception>
|
#include <exception>
|
||||||
|
#include <regex>
|
||||||
|
|
||||||
namespace mastodonpp
|
namespace mastodonpp
|
||||||
{
|
{
|
||||||
@ -27,54 +28,52 @@ namespace mastodonpp
|
|||||||
using std::sort;
|
using std::sort;
|
||||||
using std::stoull;
|
using std::stoull;
|
||||||
using std::exception;
|
using std::exception;
|
||||||
|
using std::regex;
|
||||||
|
using std::regex_search;
|
||||||
|
using std::smatch;
|
||||||
|
|
||||||
Instance::Instance(const string_view hostname, const string_view access_token)
|
uint64_t Instance::get_max_chars() noexcept
|
||||||
: _hostname{hostname}
|
|
||||||
, _baseuri{"https://" + _hostname}
|
|
||||||
, _access_token{access_token}
|
|
||||||
, _max_chars{0}
|
|
||||||
{}
|
|
||||||
|
|
||||||
uint64_t Instance::get_max_chars()
|
|
||||||
{
|
{
|
||||||
constexpr uint64_t default_max_chars{500};
|
constexpr uint64_t default_max_chars{500};
|
||||||
|
|
||||||
if (_max_chars == 0)
|
if (_max_chars != 0)
|
||||||
{
|
{
|
||||||
try
|
return _max_chars;
|
||||||
{
|
}
|
||||||
debuglog << "Querying " << _hostname << " for max_toot_chars…\n";
|
|
||||||
const auto answer{make_request(http_method::GET,
|
|
||||||
_baseuri + "/api/v1/instance", {})};
|
|
||||||
if (!answer)
|
|
||||||
{
|
|
||||||
debuglog << "Could not get instance info.\n";
|
|
||||||
return default_max_chars;
|
|
||||||
}
|
|
||||||
|
|
||||||
_max_chars = [&answer]
|
try
|
||||||
{
|
{
|
||||||
auto &body{answer.body};
|
debuglog << "Querying " << _hostname << " for max_toot_chars…\n";
|
||||||
size_t pos_start{body.find("max_toot_chars")};
|
const auto answer{make_request(http_method::GET,
|
||||||
if (pos_start == string::npos)
|
_baseuri + "/api/v1/instance", {})};
|
||||||
{
|
if (!answer)
|
||||||
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 auto max_toot_chars{body.substr(pos_start,
|
|
||||||
pos_end - pos_start)};
|
|
||||||
return static_cast<uint64_t>(stoull(max_toot_chars));
|
|
||||||
}();
|
|
||||||
debuglog << "Set _max_chars to: " << _max_chars << '\n';
|
|
||||||
}
|
|
||||||
catch (const exception &e)
|
|
||||||
{
|
{
|
||||||
debuglog << "Unexpected exception: " << e.what() << '\n';
|
debuglog << "Could not get instance info.\n";
|
||||||
return default_max_chars;
|
return default_max_chars;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_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 auto max_toot_chars{body.substr(pos_start,
|
||||||
|
pos_end - pos_start)};
|
||||||
|
return static_cast<uint64_t>(stoull(max_toot_chars));
|
||||||
|
}();
|
||||||
|
debuglog << "Set _max_chars to: " << _max_chars << '\n';
|
||||||
|
}
|
||||||
|
catch (const exception &e)
|
||||||
|
{
|
||||||
|
debuglog << "Unexpected exception: " << e.what() << '\n';
|
||||||
|
return default_max_chars;
|
||||||
}
|
}
|
||||||
|
|
||||||
return _max_chars;
|
return _max_chars;
|
||||||
@ -107,7 +106,7 @@ answer_type Instance::get_nodeinfo()
|
|||||||
return make_request(http_method::GET, hrefs.back(), {});
|
return make_request(http_method::GET, hrefs.back(), {});
|
||||||
}
|
}
|
||||||
|
|
||||||
vector<string> Instance::get_post_formats()
|
vector<string> Instance::get_post_formats() noexcept
|
||||||
{
|
{
|
||||||
constexpr auto default_value{"text/plain"};
|
constexpr auto default_value{"text/plain"};
|
||||||
|
|
||||||
@ -116,36 +115,128 @@ vector<string> Instance::get_post_formats()
|
|||||||
return _post_formats;
|
return _post_formats;
|
||||||
}
|
}
|
||||||
|
|
||||||
debuglog << "Querying " << _hostname << " for postFormats…\n";
|
try
|
||||||
const auto answer{get_nodeinfo()};
|
|
||||||
if (!answer)
|
|
||||||
{
|
{
|
||||||
debuglog << "Couldn't get NodeInfo.\n";
|
debuglog << "Querying " << _hostname << " for postFormats…\n";
|
||||||
_post_formats = {default_value};
|
const auto answer{get_nodeinfo()};
|
||||||
return _post_formats;
|
if (!answer)
|
||||||
}
|
{
|
||||||
|
debuglog << "Couldn't get NodeInfo.\n";
|
||||||
|
_post_formats = {default_value};
|
||||||
|
return _post_formats;
|
||||||
|
}
|
||||||
|
|
||||||
constexpr string_view searchstring{R"("postFormats":[)"};
|
constexpr string_view searchstring{R"("postFormats":[)"};
|
||||||
auto pos{answer.body.find(searchstring)};
|
auto pos{answer.body.find(searchstring)};
|
||||||
if (pos == string::npos)
|
if (pos == string::npos)
|
||||||
{
|
{
|
||||||
debuglog << "Couldn't find metadata.postFormats.\n";
|
debuglog << "Couldn't find metadata.postFormats.\n";
|
||||||
_post_formats = {default_value};
|
_post_formats = {default_value};
|
||||||
return _post_formats;
|
return _post_formats;
|
||||||
}
|
}
|
||||||
pos += searchstring.size();
|
pos += searchstring.size();
|
||||||
auto endpos{answer.body.find("],", pos)};
|
auto endpos{answer.body.find("],", pos)};
|
||||||
string formats{answer.body.substr(pos, endpos - pos)};
|
string formats{answer.body.substr(pos, endpos - pos)};
|
||||||
debuglog << "Extracted postFormats: " << formats << '\n';
|
debuglog << "Extracted postFormats: " << formats << '\n';
|
||||||
|
|
||||||
while ((pos = formats.find('"', 1)) != string::npos)
|
while ((pos = formats.find('"', 1)) != string::npos)
|
||||||
|
{
|
||||||
|
_post_formats.push_back(formats.substr(1, pos - 1));
|
||||||
|
formats.erase(0, pos + 2); // 2 is the length of: ",
|
||||||
|
debuglog << "Found postFormat: " << _post_formats.back() << '\n';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (const std::exception &e)
|
||||||
{
|
{
|
||||||
_post_formats.push_back(formats.substr(1, pos - 1));
|
debuglog << "Unexpected exception: " << e.what() << '\n';
|
||||||
formats.erase(0, pos + 2); // 2 is the length of: ",
|
return {default_value};
|
||||||
debuglog << "Found postFormat: " << _post_formats.back() << '\n';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return _post_formats;
|
return _post_formats;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
answer_type Instance::ObtainToken::step_1(const string_view client_name,
|
||||||
|
const string_view scopes,
|
||||||
|
const string_view website)
|
||||||
|
{
|
||||||
|
parametermap parameters
|
||||||
|
{
|
||||||
|
{"client_name", client_name},
|
||||||
|
{"redirect_uris", "urn:ietf:wg:oauth:2.0:oob"}
|
||||||
|
};
|
||||||
|
if (!scopes.empty())
|
||||||
|
{
|
||||||
|
_scopes = scopes;
|
||||||
|
parameters.insert({"scopes", scopes});
|
||||||
|
}
|
||||||
|
if (!website.empty())
|
||||||
|
{
|
||||||
|
parameters.insert({"website", website});
|
||||||
|
}
|
||||||
|
|
||||||
|
auto answer{make_request(http_method::POST, _baseuri + "/api/v1/apps",
|
||||||
|
parameters)};
|
||||||
|
if (answer)
|
||||||
|
{
|
||||||
|
const regex re_id{R"("client_id"\s*:\s*"([^"]+)\")"};
|
||||||
|
const regex re_secret{R"("client_secret"\s*:\s*"([^"]+)\")"};
|
||||||
|
smatch match;
|
||||||
|
|
||||||
|
if (regex_search(answer.body, match, re_id))
|
||||||
|
{
|
||||||
|
_client_id = match[1].str();
|
||||||
|
}
|
||||||
|
if (regex_search(answer.body, match, re_secret))
|
||||||
|
{
|
||||||
|
_client_secret = match[1].str();
|
||||||
|
}
|
||||||
|
|
||||||
|
string uri{_baseuri + "/oauth/authorize?scope=" + escape_url(scopes)
|
||||||
|
+ "&response_type=code"
|
||||||
|
"&redirect_uri=" + escape_url("urn:ietf:wg:oauth:2.0:oob")
|
||||||
|
+ "&client_id=" + _client_id};
|
||||||
|
if (!website.empty())
|
||||||
|
{
|
||||||
|
uri += "&website=" + escape_url(website);
|
||||||
|
}
|
||||||
|
answer.body = uri;
|
||||||
|
debuglog << "Built URI.";
|
||||||
|
}
|
||||||
|
|
||||||
|
return answer;
|
||||||
|
}
|
||||||
|
|
||||||
|
answer_type Instance::ObtainToken::step_2(const string_view code)
|
||||||
|
{
|
||||||
|
parametermap parameters
|
||||||
|
{
|
||||||
|
{"client_id", _client_id},
|
||||||
|
{"client_secret", _client_secret},
|
||||||
|
{"redirect_uri", "urn:ietf:wg:oauth:2.0:oob"},
|
||||||
|
{"code", code},
|
||||||
|
{"grant_type", "client_credentials"}
|
||||||
|
};
|
||||||
|
if (!_scopes.empty())
|
||||||
|
{
|
||||||
|
parameters.insert({"scope", _scopes});
|
||||||
|
}
|
||||||
|
|
||||||
|
auto answer{make_request(http_method::POST, _baseuri + "/oauth/token",
|
||||||
|
parameters)};
|
||||||
|
if (answer)
|
||||||
|
{
|
||||||
|
const regex re_token{R"("access_token"\s*:\s*"([^"]+)\")"};
|
||||||
|
smatch match;
|
||||||
|
|
||||||
|
if (regex_search(answer.body, match, re_token))
|
||||||
|
{
|
||||||
|
answer.body = match[1].str();
|
||||||
|
debuglog << "Got access token.\n";
|
||||||
|
_instance.set_access_token(answer.body);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return answer;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace mastodonpp
|
} // namespace mastodonpp
|
||||||
|
Loading…
x
Reference in New Issue
Block a user