Added streaming support

This commit is contained in:
tastytea 2018-02-26 07:57:30 +01:00
parent f9b6009b57
commit e73a5a765b
Signed by: tastytea
GPG Key ID: 59346E0EA35C67E5
7 changed files with 317 additions and 24 deletions

View File

@ -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()

View File

@ -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

View File

@ -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
View 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);
}

View 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;
}

View File

@ -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;
}

View File

@ -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;
};
}