Added streaming support
This commit is contained in:
parent
f9b6009b57
commit
e73a5a765b
@ -1,6 +1,6 @@
|
||||
cmake_minimum_required (VERSION 3.7)
|
||||
project (mastodon-cpp
|
||||
VERSION 0.3.3
|
||||
VERSION 0.4.0
|
||||
LANGUAGES CXX
|
||||
)
|
||||
|
||||
@ -63,7 +63,7 @@ if(WITH_EXAMPLES)
|
||||
foreach(src ${sources_examples})
|
||||
get_filename_component(bin ${src} NAME_WE)
|
||||
add_executable(${bin} ${src})
|
||||
target_link_libraries(${bin} ${Boost_LIBRARIES} mastodon-cpp)
|
||||
target_link_libraries(${bin} -lpthread ${Boost_LIBRARIES} mastodon-cpp)
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
|
14
README.md
14
README.md
@ -75,6 +75,8 @@ After you did a `make install`, a project consisting of one file can be compiled
|
||||
| 1 | Invalid call |
|
||||
| 2 | Not implemented |
|
||||
| 3 | URL changed (HTTP 301 or 308) |
|
||||
| 4 | Aborted by user |
|
||||
| 10 | Failed to connect |
|
||||
| 100 - 999 | HTTP status codes |
|
||||
| 65535 | Unknown error |
|
||||
|
||||
@ -109,7 +111,7 @@ If you use a debug build, you get more verbose error messages.
|
||||
* [x] Support registering as an application
|
||||
* Version 0.4.0
|
||||
* [x] Handle X-RateLimit header
|
||||
* [ ] Streaming API
|
||||
* [x] Streaming API
|
||||
* Later
|
||||
* [ ] Asynchronous I/O
|
||||
* [ ] Improve error reporting
|
||||
@ -179,11 +181,11 @@ If you use a debug build, you get more verbose error messages.
|
||||
* [x] GET /api/v1/timelines/public
|
||||
* [x] GET /api/v1/timelines/tag/:hashtag
|
||||
* [x] GET /api/v1/timelines/list/:list_id
|
||||
* [ ] GET /api/v1/streaming/user
|
||||
* [ ] GET /api/v1/streaming/public
|
||||
* [ ] GET /api/v1/streaming/public/local
|
||||
* [ ] GET /api/v1/streaming/hashtag
|
||||
* [ ] GET /api/v1/streaming/list
|
||||
* [x] GET /api/v1/streaming/user
|
||||
* [x] GET /api/v1/streaming/public
|
||||
* [x] GET /api/v1/streaming/public/local
|
||||
* [x] GET /api/v1/streaming/hashtag
|
||||
* [x] GET /api/v1/streaming/list
|
||||
|
||||
# Copyright
|
||||
|
||||
|
@ -17,6 +17,7 @@
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <curlpp/cURLpp.hpp>
|
||||
#include "macros.hpp"
|
||||
#include "mastodon-cpp.hpp"
|
||||
@ -24,6 +25,7 @@
|
||||
using namespace Mastodon;
|
||||
using std::string;
|
||||
using std::cerr;
|
||||
|
||||
const std::uint16_t API::get(const Mastodon::API::v1 &call, string &answer)
|
||||
{
|
||||
const parametermap p;
|
||||
|
85
src/api_get_stream.cpp
Normal file
85
src/api_get_stream.cpp
Normal file
@ -0,0 +1,85 @@
|
||||
/* This file is part of mastodon-cpp.
|
||||
* Copyright © 2018 tastytea <tastytea@tastytea.de>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include "macros.hpp"
|
||||
#include "mastodon-cpp.hpp"
|
||||
|
||||
using namespace Mastodon;
|
||||
using std::string;
|
||||
using std::cerr;
|
||||
|
||||
const std::uint16_t API::get_stream(const Mastodon::API::v1 &call,
|
||||
const string &argument,
|
||||
string &answer,
|
||||
std::unique_ptr<Mastodon::API::http> &ptr)
|
||||
{
|
||||
string strcall = "";
|
||||
const string argument_encoded = curlpp::escape(argument);
|
||||
|
||||
switch (call)
|
||||
{
|
||||
case v1::streaming_hashtag:
|
||||
strcall = "/api/v1/streaming/hashtag?tag=" + argument_encoded;
|
||||
break;
|
||||
case v1::streaming_list:
|
||||
strcall = "/api/v1/streaming/list?list=" + argument_encoded;
|
||||
break;
|
||||
default:
|
||||
ttdebug << "ERROR: Invalid call.\n";
|
||||
return 1;
|
||||
break;
|
||||
}
|
||||
|
||||
ptr = std::make_unique<http>(*this, _instance, _access_token);
|
||||
return ptr->request_sync(http::method::GET_STREAM, strcall, answer);
|
||||
}
|
||||
|
||||
const std::uint16_t API::get_stream(const Mastodon::API::v1 &call,
|
||||
string &answer,
|
||||
std::unique_ptr<Mastodon::API::http> &ptr)
|
||||
{
|
||||
string strcall = "";
|
||||
|
||||
switch (call)
|
||||
{
|
||||
case v1::streaming_user:
|
||||
strcall = "/api/v1/streaming/user";
|
||||
break;
|
||||
case v1::streaming_public:
|
||||
strcall = "/api/v1/streaming/public";
|
||||
break;
|
||||
case v1::streaming_public_local:
|
||||
strcall = "/api/v1/streaming/public/local";
|
||||
break;
|
||||
default:
|
||||
ttdebug << "ERROR: Invalid call.\n";
|
||||
return 1;
|
||||
break;
|
||||
}
|
||||
|
||||
ptr = std::make_unique<http>(*this, _instance, _access_token);
|
||||
return ptr->request_sync(http::method::GET_STREAM, strcall, answer);
|
||||
}
|
||||
|
||||
const std::uint16_t API::get_stream(const std::string &call, string &answer,
|
||||
std::unique_ptr<http> &ptr)
|
||||
{
|
||||
ptr = std::make_unique<http>(*this, _instance, _access_token);
|
||||
return ptr->request_sync(http::method::GET_STREAM, call, answer);
|
||||
}
|
69
src/examples/example9_streaming_api.cpp
Normal file
69
src/examples/example9_streaming_api.cpp
Normal file
@ -0,0 +1,69 @@
|
||||
/* This file is part of mastodon-cpp.
|
||||
* How to use the streaming API
|
||||
*/
|
||||
|
||||
/* This file is part of mastodon-cpp.
|
||||
*/
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <cstdint>
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
#include "../mastodon-cpp.hpp"
|
||||
|
||||
using Mastodon::API;
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
if (argc < 3)
|
||||
{
|
||||
std::cerr << "usage: " << argv[0] << " <instance> <access_token>\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
static std::string answer;
|
||||
static std::unique_ptr<Mastodon::API::http> ptr;
|
||||
|
||||
std::cout << "Dumping public timeline...\n";
|
||||
std::thread pub([=]
|
||||
{
|
||||
Mastodon::API masto(argv[1], argv[2]);
|
||||
masto.set_useragent("mastodon-cpp-example/1.3.3.7");
|
||||
masto.get_stream(API::v1::streaming_public, answer, ptr);
|
||||
});
|
||||
|
||||
std::uint8_t counter = 0;
|
||||
while (true)
|
||||
{
|
||||
++counter;
|
||||
std::cout << answer;
|
||||
answer.clear();
|
||||
if (counter == 10)
|
||||
{
|
||||
std::cerr << "Aborting...\n";
|
||||
ptr->abort_stream();
|
||||
break;
|
||||
}
|
||||
std::this_thread::sleep_for(std::chrono::seconds(2));
|
||||
}
|
||||
pub.join();
|
||||
std::cout << '\n';
|
||||
|
||||
std::cout << "Dumping the #np tag...\n";
|
||||
answer = "";
|
||||
std::thread tag([=]
|
||||
{
|
||||
Mastodon::API masto(argv[1], argv[2]);
|
||||
masto.set_useragent("mastodon-cpp-example/1.3.3.7");
|
||||
masto.get_stream(API::v1::streaming_hashtag, "np", answer, ptr);
|
||||
});
|
||||
std::this_thread::sleep_for(std::chrono::seconds(20));
|
||||
ptr->abort_stream();
|
||||
std::cout << answer;
|
||||
std::cout << '\n';
|
||||
tag.join();
|
||||
|
||||
return 0;
|
||||
}
|
@ -17,7 +17,9 @@
|
||||
#include <string>
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <functional> // std::bind
|
||||
#include <list>
|
||||
#include <cstring> // std::strncmp
|
||||
#include <curlpp/cURLpp.hpp>
|
||||
#include <curlpp/Easy.hpp>
|
||||
#include <curlpp/Options.hpp>
|
||||
@ -36,10 +38,16 @@ API::http::http(const API &api, const string &instance,
|
||||
: parent(api)
|
||||
, _instance(instance)
|
||||
, _access_token(access_token)
|
||||
, _abort_stream(false)
|
||||
{
|
||||
curlpp::initialize();
|
||||
}
|
||||
|
||||
API::http::~http()
|
||||
{
|
||||
curlpp::terminate();
|
||||
}
|
||||
|
||||
const std::uint16_t API::http::request_sync(const method &meth,
|
||||
const string &path,
|
||||
string &answer)
|
||||
@ -52,23 +60,34 @@ const std::uint16_t API::http::request_sync(const method &meth,
|
||||
const curlpp::Forms &formdata,
|
||||
string &answer)
|
||||
{
|
||||
using namespace std::placeholders; // _1, _2, _3
|
||||
|
||||
std::uint16_t ret;
|
||||
ttdebug << "Path is: " << path << '\n';
|
||||
|
||||
try
|
||||
{
|
||||
std::ostringstream oss;
|
||||
curlpp::Easy request;
|
||||
std::list<string> headers;
|
||||
|
||||
request.setOpt<curlopts::Url>("https://" + _instance + path);
|
||||
request.setOpt<curlopts::UserAgent>(parent.get_useragent());
|
||||
request.setOpt<curlopts::HttpHeader>(
|
||||
|
||||
headers.push_back("Connection: close");
|
||||
if (!_access_token.empty())
|
||||
{
|
||||
"Connection: close",
|
||||
"Authorization: Bearer " + _access_token
|
||||
});
|
||||
headers.push_back("Authorization: Bearer " + _access_token);
|
||||
}
|
||||
request.setOpt<curlopts::HttpHeader>(headers);
|
||||
|
||||
// Get headers from server
|
||||
request.setOpt<curlpp::options::Header>(true);
|
||||
if (meth != http::method::GET_STREAM)
|
||||
{
|
||||
request.setOpt<curlpp::options::Header>(true);
|
||||
}
|
||||
request.setOpt<curlopts::FollowLocation>(true);
|
||||
request.setOpt<curlopts::WriteStream>(&oss);
|
||||
request.setOpt<curlpp::options::WriteFunction>
|
||||
(std::bind(&http::callback, this, _1, _2, _3, &answer));
|
||||
if (!formdata.empty())
|
||||
{
|
||||
request.setOpt<curlopts::HttpPost>(formdata);
|
||||
@ -93,15 +112,15 @@ const std::uint16_t API::http::request_sync(const method &meth,
|
||||
}
|
||||
|
||||
request.perform();
|
||||
std::uint16_t ret = curlpp::infos::ResponseCode::get(request);
|
||||
ret = curlpp::infos::ResponseCode::get(request);
|
||||
ttdebug << "Response code: " << ret << '\n';
|
||||
size_t pos = oss.str().find("\r\n\r\n");
|
||||
_headers = oss.str().substr(0, pos);
|
||||
size_t pos = answer.find("\r\n\r\n");
|
||||
_headers = answer.substr(0, pos);
|
||||
|
||||
if (ret == 200 || ret == 302 || ret == 307)
|
||||
{ // OK or Found or Temporary Redirect
|
||||
// Only return body
|
||||
answer = oss.str().substr(pos + 4);
|
||||
answer = answer.substr(pos + 4);
|
||||
}
|
||||
else if (ret == 301 || ret == 308)
|
||||
{ // Moved Permanently or Permanent Redirect
|
||||
@ -116,9 +135,25 @@ const std::uint16_t API::http::request_sync(const method &meth,
|
||||
}
|
||||
catch (curlpp::RuntimeError &e)
|
||||
{
|
||||
cerr << "RUNTIME ERROR: " << e.what() << std::endl;
|
||||
if (std::strncmp(e.what(),
|
||||
"Failed writing body", 19) == 0)
|
||||
{
|
||||
ttdebug << "Request was aborted by user\n";
|
||||
return 4;
|
||||
}
|
||||
else if (std::strncmp(e.what(),
|
||||
"Failed to connect to", 20) == 0)
|
||||
{
|
||||
ret = 10;
|
||||
}
|
||||
else
|
||||
{
|
||||
cerr << "RUNTIME ERROR: " << e.what() << std::endl;
|
||||
ret = 0xffff;
|
||||
}
|
||||
ttdebug << e.what() << std::endl;
|
||||
|
||||
return 0xffff;
|
||||
return ret;
|
||||
}
|
||||
catch (curlpp::LogicError &e)
|
||||
{
|
||||
@ -133,3 +168,21 @@ const void API::http::get_headers(string &headers) const
|
||||
{
|
||||
headers = _headers;
|
||||
}
|
||||
|
||||
const size_t API::http::callback(char* data, size_t size, size_t nmemb,
|
||||
string *str)
|
||||
{
|
||||
if (_abort_stream)
|
||||
{
|
||||
// This throws the runtime error: Failed writing body
|
||||
return 0;
|
||||
}
|
||||
str->append(data);
|
||||
// ttdebug << "Received " << size * nmemb << " Bytes\n";
|
||||
return size * nmemb;
|
||||
};
|
||||
|
||||
const void API::http::abort_stream()
|
||||
{
|
||||
_abort_stream = true;
|
||||
}
|
||||
|
@ -21,6 +21,7 @@
|
||||
#include <vector>
|
||||
#include <cstdint>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <curlpp/cURLpp.hpp>
|
||||
#include <curlpp/Easy.hpp>
|
||||
|
||||
@ -33,6 +34,7 @@
|
||||
* @example example6_toot_delete-toot.cpp
|
||||
* @example example7_register_app.cpp
|
||||
* @example example8_rate_limiting.cpp
|
||||
* @example example9_streaming_api.cpp
|
||||
*/
|
||||
namespace Mastodon
|
||||
{
|
||||
@ -52,6 +54,8 @@ namespace Mastodon
|
||||
class API
|
||||
{
|
||||
public:
|
||||
class http;
|
||||
|
||||
/*!
|
||||
* @brief Used for passing (most of the time) optional parameters.
|
||||
*
|
||||
@ -127,7 +131,13 @@ public:
|
||||
statuses_id_pin,
|
||||
statuses_id_unpin,
|
||||
statuses_id_mute,
|
||||
statuses_id_unmute
|
||||
statuses_id_unmute,
|
||||
// Streaming
|
||||
streaming_user,
|
||||
streaming_public,
|
||||
streaming_public_local,
|
||||
streaming_hashtag,
|
||||
streaming_list
|
||||
};
|
||||
|
||||
/*!
|
||||
@ -288,6 +298,56 @@ public:
|
||||
const std::uint16_t get(const std::string &call,
|
||||
std::string &answer);
|
||||
|
||||
/*!
|
||||
* @brief Make a streaming GET request.
|
||||
*
|
||||
* @param call A call defined in Mastodon::API::v1
|
||||
* @param argument The non-optional argument
|
||||
* @param answer The answer from the server. Usually JSON. On error an
|
||||
* empty string.
|
||||
* @param ptr Pointer to the http object. Can be used to call
|
||||
* ptr->abort_stream()
|
||||
*
|
||||
* @return @ref error "Error code". If the URL has permanently changed, 3
|
||||
* is returned and answer is set to the new URL.
|
||||
*/
|
||||
const std::uint16_t get_stream(const Mastodon::API::v1 &call,
|
||||
const std::string &argument,
|
||||
std::string &answer,
|
||||
std::unique_ptr<Mastodon::API::http> &ptr);
|
||||
|
||||
/*!
|
||||
* @brief Make a streaming GET request.
|
||||
*
|
||||
* @param call A call defined in Mastodon::API::v1
|
||||
* @param answer The answer from the server. Usually JSON. On error an
|
||||
* empty string.
|
||||
* @param ptr Pointer to the http object. Can be used to call
|
||||
* ptr->abort_stream()
|
||||
*
|
||||
* @return @ref error "Error code". If the URL has permanently changed, 3
|
||||
* is returned and answer is set to the new URL.
|
||||
*/
|
||||
const std::uint16_t get_stream(const Mastodon::API::v1 &call,
|
||||
std::string &answer,
|
||||
std::unique_ptr<Mastodon::API::http> &ptr);
|
||||
|
||||
/*!
|
||||
* @brief Make a streaming GET request.
|
||||
*
|
||||
* @param call String in the form `/api/v1/example`
|
||||
* @param answer The answer from the server. Usually JSON. On error an
|
||||
* empty string.
|
||||
* @param ptr Pointer to the http object. Can be used to call
|
||||
* ptr->abort_stream()
|
||||
*
|
||||
* @return @ref error "Error code". If the URL has permanently changed, 3
|
||||
* is returned and answer is set to the new URL.
|
||||
*/
|
||||
const std::uint16_t get_stream(const std::string &call,
|
||||
std::string &answer,
|
||||
std::unique_ptr<Mastodon::API::http> &ptr);
|
||||
|
||||
/*!
|
||||
* @brief Make a PATCH request.
|
||||
*
|
||||
@ -507,6 +567,10 @@ private:
|
||||
*/
|
||||
const curlpp::Forms maptoformdata(const parametermap &map);
|
||||
|
||||
public:
|
||||
/*!
|
||||
* @brief http class. Do not use this directly.
|
||||
*/
|
||||
class http
|
||||
{
|
||||
public:
|
||||
@ -516,17 +580,21 @@ private:
|
||||
PATCH,
|
||||
POST,
|
||||
PUT,
|
||||
DELETE
|
||||
DELETE,
|
||||
GET_STREAM
|
||||
};
|
||||
|
||||
explicit http(const API &api, const std::string &instance,
|
||||
const std::string &access_token);
|
||||
~http();
|
||||
const std::uint16_t request_sync(const method &meth,
|
||||
const std::string &path,
|
||||
std::string &answer);
|
||||
|
||||
/*!
|
||||
* @brief Blocking request.
|
||||
*
|
||||
*
|
||||
*
|
||||
* @param meth The method defined in http::method
|
||||
* @param path The api call as string
|
||||
@ -543,11 +611,25 @@ private:
|
||||
|
||||
const void get_headers(std::string &headers) const;
|
||||
|
||||
const size_t callback(char* data, size_t size, size_t nmemb,
|
||||
std::string *oss);
|
||||
|
||||
/*!
|
||||
* @brief Aborts the stream. Use only with streams.
|
||||
*
|
||||
* Aborts the stream next time data comes in. Can take a few
|
||||
* seconds.
|
||||
* This works only with streams, because only streams have an
|
||||
* own http object.
|
||||
*/
|
||||
const void abort_stream();
|
||||
|
||||
private:
|
||||
const API &parent;
|
||||
const std::string _instance;
|
||||
const std::string _access_token;
|
||||
std::string _headers;
|
||||
bool _abort_stream;
|
||||
} _http;
|
||||
};
|
||||
}
|
||||
|
Reference in New Issue
Block a user