Merge branch 'rewrite' into main
continuous-integration/drone/push Build is passing
Details
continuous-integration/drone/push Build is passing
Details
This commit is contained in:
commit
bca496c87a
|
@ -2,7 +2,7 @@
|
|||
:doctype: manpage
|
||||
:Author: tastytea
|
||||
:Email: tastytea@tastytea.de
|
||||
:Date: 2019-12-25
|
||||
:Date: 2019-12-28
|
||||
:Revision: 0.0.0
|
||||
:man source: mastorss
|
||||
:man manual: General Commands Manual
|
||||
|
@ -157,15 +157,16 @@ proxy support yet, sorry.
|
|||
== ERROR CODES
|
||||
|
||||
[cols=">,<"]
|
||||
|===========================================================
|
||||
|===============================================================================
|
||||
| Code | Explanation
|
||||
|
||||
| 1 | No profile specified.
|
||||
| 2 | Network error.
|
||||
| 3 | File error.
|
||||
| 4 | Mastodon API error.
|
||||
| 5 | JSON error, most likely the file is wrongly formatted.
|
||||
| 9 | Unknown error.
|
||||
|===========================================================
|
||||
|===============================================================================
|
||||
|
||||
== DEBUGGING
|
||||
|
||||
|
|
|
@ -74,8 +74,8 @@ std::ostream &operator <<(std::ostream &out, const ProfileData &data)
|
|||
return out;
|
||||
}
|
||||
|
||||
Config::Config(string profile)
|
||||
:_profile{move(profile)}
|
||||
Config::Config(string profile_name)
|
||||
: profile{move(profile_name)}
|
||||
{
|
||||
const fs::path filename = get_filename();
|
||||
BOOST_LOG_TRIVIAL(debug) << "Config filename is: " << filename;
|
||||
|
@ -94,7 +94,7 @@ Config::Config(string profile)
|
|||
}
|
||||
}
|
||||
|
||||
fs::path Config::get_filename() const
|
||||
fs::path Config::get_config_dir() const
|
||||
{
|
||||
char *envdir = getenv("XDG_CONFIG_HOME");
|
||||
fs::path dir;
|
||||
|
@ -122,7 +122,12 @@ fs::path Config::get_filename() const
|
|||
BOOST_LOG_TRIVIAL(debug) << "Created config dir: " << dir;
|
||||
}
|
||||
|
||||
return dir /= "config-" + _profile + ".json";
|
||||
return dir;
|
||||
}
|
||||
|
||||
fs::path Config::get_filename() const
|
||||
{
|
||||
return get_config_dir() /= "config-" + profile + ".json";
|
||||
}
|
||||
|
||||
void Config::generate()
|
||||
|
@ -131,39 +136,39 @@ void Config::generate()
|
|||
|
||||
cout << "Instance (domain): ";
|
||||
getline(cin, line);
|
||||
data.instance = line;
|
||||
profiledata.instance = line;
|
||||
|
||||
data.access_token = get_access_token(line);
|
||||
profiledata.access_token = get_access_token(line);
|
||||
|
||||
cout << "URL of the feed: ";
|
||||
std::getline(cin, line);
|
||||
data.feedurl = line;
|
||||
profiledata.feedurl = line;
|
||||
|
||||
cout << "Post only titles? [y/n]: ";
|
||||
std::getline(cin, line);
|
||||
if (line[0] == 'y')
|
||||
{
|
||||
data.titles_only = true;
|
||||
profiledata.titles_only = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
data.titles_only = false;
|
||||
profiledata.titles_only = false;
|
||||
}
|
||||
|
||||
cout << "Post titles as cw? [y/n]: ";
|
||||
std::getline(cin, line);
|
||||
if (line[0] == 'y')
|
||||
{
|
||||
data.titles_as_cw = true;
|
||||
profiledata.titles_as_cw = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
data.titles_as_cw = false;
|
||||
profiledata.titles_as_cw = false;
|
||||
}
|
||||
|
||||
cout << "Append this string to each post: ";
|
||||
std::getline(cin, line);
|
||||
data.append = line;
|
||||
profiledata.append = line;
|
||||
|
||||
cout << "Interval between posts in seconds [30]: ";
|
||||
std::getline(cin, line);
|
||||
|
@ -171,7 +176,7 @@ void Config::generate()
|
|||
{
|
||||
line = "30";
|
||||
}
|
||||
data.interval = static_cast<uint32_t>(stoul(line));
|
||||
profiledata.interval = static_cast<uint32_t>(stoul(line));
|
||||
|
||||
cout << "Maximum size of posts [500]: ";
|
||||
std::getline(cin, line);
|
||||
|
@ -179,7 +184,7 @@ void Config::generate()
|
|||
{
|
||||
line = "500";
|
||||
}
|
||||
data.max_size = stoul(line);
|
||||
profiledata.max_size = stoul(line);
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug) << "Generated configuration.";
|
||||
write();
|
||||
|
@ -220,48 +225,48 @@ string Config::get_access_token(const string &instance) const
|
|||
|
||||
void Config::parse()
|
||||
{
|
||||
data.access_token = _json[_profile]["access_token"].asString();
|
||||
data.append = _json[_profile]["append"].asString();
|
||||
data.feedurl = _json[_profile]["feedurl"].asString();
|
||||
for (const auto &fix : _json[_profile]["fixes"])
|
||||
profiledata.access_token = _json[profile]["access_token"].asString();
|
||||
profiledata.append = _json[profile]["append"].asString();
|
||||
profiledata.feedurl = _json[profile]["feedurl"].asString();
|
||||
for (const auto &fix : _json[profile]["fixes"])
|
||||
{
|
||||
data.fixes.push_back(fix.asString());
|
||||
profiledata.fixes.push_back(fix.asString());
|
||||
}
|
||||
data.instance = _json[_profile]["instance"].asString();
|
||||
if (!_json[_profile]["interval"].isNull())
|
||||
profiledata.instance = _json[profile]["instance"].asString();
|
||||
if (!_json[profile]["interval"].isNull())
|
||||
{
|
||||
data.interval =
|
||||
static_cast<uint32_t>(_json[_profile]["interval"].asUInt64());
|
||||
profiledata.interval =
|
||||
static_cast<uint32_t>(_json[profile]["interval"].asUInt64());
|
||||
}
|
||||
data.last_guid = _json[_profile]["last_guid"].asString();
|
||||
if (!_json[_profile]["max_size"].isNull())
|
||||
profiledata.last_guid = _json[profile]["last_guid"].asString();
|
||||
if (!_json[profile]["max_size"].isNull())
|
||||
{
|
||||
data.max_size = _json[_profile]["max_size"].asUInt64();
|
||||
profiledata.max_size = _json[profile]["max_size"].asUInt64();
|
||||
}
|
||||
for (const auto &skip : _json[_profile]["skip"])
|
||||
for (const auto &skip : _json[profile]["skip"])
|
||||
{
|
||||
data.skip.push_back(skip.asString());
|
||||
profiledata.skip.push_back(skip.asString());
|
||||
}
|
||||
data.titles_as_cw = _json[_profile]["titles_as_cw"].asBool();
|
||||
data.titles_only = _json[_profile]["titles_only"].asBool();
|
||||
profiledata.titles_as_cw = _json[profile]["titles_as_cw"].asBool();
|
||||
profiledata.titles_only = _json[profile]["titles_only"].asBool();
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug) << "Read config: " << data;
|
||||
BOOST_LOG_TRIVIAL(debug) << "Read config: " << profiledata;
|
||||
}
|
||||
|
||||
void Config::write()
|
||||
{
|
||||
_json[_profile]["access_token"] = data.access_token;
|
||||
_json[_profile]["append"] = data.append;
|
||||
_json[_profile]["feedurl"] = data.feedurl;
|
||||
_json[profile]["access_token"] = profiledata.access_token;
|
||||
_json[profile]["append"] = profiledata.append;
|
||||
_json[profile]["feedurl"] = profiledata.feedurl;
|
||||
// Leave fixes.
|
||||
_json[_profile]["instance"] = data.instance;
|
||||
_json[_profile]["interval"] = data.interval;
|
||||
_json[_profile]["last_guid"] = data.last_guid;
|
||||
_json[_profile]["max_size"]
|
||||
= static_cast<Json::Value::UInt64>(data.max_size);
|
||||
_json[profile]["instance"] = profiledata.instance;
|
||||
_json[profile]["interval"] = profiledata.interval;
|
||||
_json[profile]["last_guid"] = profiledata.last_guid;
|
||||
_json[profile]["max_size"]
|
||||
= static_cast<Json::Value::UInt64>(profiledata.max_size);
|
||||
// Leave skip.
|
||||
_json[_profile]["titles_as_cw"] = data.titles_as_cw;
|
||||
_json[_profile]["titles_only"] = data.titles_only;
|
||||
_json[profile]["titles_as_cw"] = profiledata.titles_as_cw;
|
||||
_json[profile]["titles_only"] = profiledata.titles_only;
|
||||
|
||||
ofstream file{get_filename().c_str()};
|
||||
if (file.good())
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
#define MASTORSS_CONFIG_HPP
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <jsoncpp/json/json.h>
|
||||
#include <json/json.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
@ -64,14 +64,16 @@ struct ProfileData
|
|||
class Config
|
||||
{
|
||||
public:
|
||||
explicit Config(string profile);
|
||||
explicit Config(string profile_name);
|
||||
|
||||
ProfileData data;
|
||||
const string profile;
|
||||
ProfileData profiledata;
|
||||
|
||||
void write();
|
||||
[[nodiscard]]
|
||||
fs::path get_config_dir() const;
|
||||
|
||||
private:
|
||||
const string _profile;
|
||||
Json::Value _json;
|
||||
|
||||
[[nodiscard]]
|
||||
|
|
|
@ -21,9 +21,12 @@
|
|||
#include <boost/log/trivial.hpp>
|
||||
#include <boost/property_tree/xml_parser.hpp>
|
||||
#include <boost/regex.hpp>
|
||||
#include <json/json.h>
|
||||
#include <mastodon-cpp/mastodon-cpp.hpp>
|
||||
#include <restclient-cpp/connection.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <fstream>
|
||||
#include <list>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
@ -33,8 +36,11 @@ namespace mastorss
|
|||
{
|
||||
using boost::regex;
|
||||
using boost::regex_replace;
|
||||
using std::transform;
|
||||
using std::ifstream;
|
||||
using std::list;
|
||||
using std::istringstream;
|
||||
using std::stringstream;
|
||||
using std::string;
|
||||
using std::move;
|
||||
|
||||
|
@ -45,7 +51,7 @@ bool operator !=(const Item &a, const Item &b)
|
|||
|
||||
Document::Document(Config &cfg)
|
||||
: _cfg{cfg}
|
||||
, _profile{cfg.data}
|
||||
, _profiledata{_cfg.profiledata}
|
||||
{
|
||||
RestClient::init();
|
||||
|
||||
|
@ -70,20 +76,20 @@ void Document::download(const string &uri)
|
|||
case 200:
|
||||
{
|
||||
_raw_doc = response.body;
|
||||
BOOST_LOG_TRIVIAL(debug) << "Downloaded feed: " << _profile.feedurl;
|
||||
BOOST_LOG_TRIVIAL(debug) << "Downloaded feed: " << _profiledata.feedurl;
|
||||
break;
|
||||
}
|
||||
case 301:
|
||||
case 308:
|
||||
{
|
||||
_profile.feedurl = extract_location(response.headers);
|
||||
if (_profile.feedurl.empty())
|
||||
_profiledata.feedurl = extract_location(response.headers);
|
||||
if (_profiledata.feedurl.empty())
|
||||
{
|
||||
throw HTTPException{response.code};
|
||||
}
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug) << "Feed has new location (permanent): "
|
||||
<< _profile.feedurl;
|
||||
<< _profiledata.feedurl;
|
||||
_cfg.write();
|
||||
download();
|
||||
break;
|
||||
|
@ -99,7 +105,7 @@ void Document::download(const string &uri)
|
|||
}
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug) << "Feed has new location (temporary): "
|
||||
<< _profile.feedurl;
|
||||
<< _profiledata.feedurl;
|
||||
download(newuri);
|
||||
break;
|
||||
}
|
||||
|
@ -116,7 +122,7 @@ void Document::download(const string &uri)
|
|||
|
||||
void Document::download()
|
||||
{
|
||||
download(_profile.feedurl);
|
||||
download(_profiledata.feedurl);
|
||||
}
|
||||
|
||||
void Document::parse()
|
||||
|
@ -145,14 +151,14 @@ void Document::parse_rss(const pt::ptree &tree)
|
|||
{
|
||||
guid = rssitem.get<string>("link");
|
||||
}
|
||||
if (guid == _profile.last_guid)
|
||||
if (guid == _profiledata.last_guid)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
bool skipthis{false};
|
||||
string title{rssitem.get<string>("title")};
|
||||
for (const auto &skip : _profile.skip)
|
||||
for (const auto &skip : _profiledata.skip)
|
||||
{
|
||||
if (title.substr(0, skip.length()) == skip)
|
||||
{
|
||||
|
@ -171,11 +177,11 @@ void Document::parse_rss(const pt::ptree &tree)
|
|||
{
|
||||
string desc
|
||||
{remove_html(rssitem.get<string>("description"))};
|
||||
for (const auto &fix : _profile.fixes)
|
||||
for (const auto &fix : _profiledata.fixes)
|
||||
{
|
||||
desc = regex_replace(desc, regex{fix}, "");
|
||||
}
|
||||
return desc;
|
||||
return add_hashtags(desc);
|
||||
}();
|
||||
item.guid = move(guid);
|
||||
item.link = rssitem.get<string>("link");
|
||||
|
@ -184,7 +190,7 @@ void Document::parse_rss(const pt::ptree &tree)
|
|||
|
||||
BOOST_LOG_TRIVIAL(debug) << "Found GUID: " << item.guid;
|
||||
|
||||
if (_profile.last_guid.empty())
|
||||
if (_profiledata.last_guid.empty())
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(debug) << "This is the first run.";
|
||||
break;
|
||||
|
@ -232,4 +238,45 @@ string Document::extract_location(const RestClient::HeaderFields &headers) const
|
|||
}
|
||||
return location;
|
||||
}
|
||||
|
||||
string Document::add_hashtags(const string &text)
|
||||
{
|
||||
Json::Value json;
|
||||
const auto filepath = _cfg.get_config_dir() /= "watchwords.json";
|
||||
ifstream file{filepath};
|
||||
if (file.good())
|
||||
{
|
||||
stringstream rawjson;
|
||||
rawjson << file.rdbuf();
|
||||
rawjson >> json;
|
||||
BOOST_LOG_TRIVIAL(debug) << "Read " << filepath;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw FileException{"File Not found:"
|
||||
+ (_cfg.get_config_dir() /= "watchwords.json").string()};
|
||||
}
|
||||
|
||||
list<string> watchwords;
|
||||
const auto &tags_profile = json[_cfg.profile]["tags"];
|
||||
const auto &tags_global = json["global"]["tags"];
|
||||
transform(tags_profile.begin(), tags_profile.end(),
|
||||
std::back_inserter(watchwords),
|
||||
[](const Json::Value &value) { return value.asString(); });
|
||||
transform(tags_global.begin(), tags_global.end(),
|
||||
std::back_inserter(watchwords),
|
||||
[](const Json::Value &value) { return value.asString(); });
|
||||
|
||||
string out{text};
|
||||
for (const auto &tag : watchwords)
|
||||
{
|
||||
regex re_tag("([[:space:][:punct:]]|^)("
|
||||
+ tag + ")([[:space:][:punct:]]|$)", regex::icase);
|
||||
out = regex_replace(out, re_tag, "$1#$2$3", boost::format_first_only);
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
} // namespace mastorss
|
||||
|
|
|
@ -69,7 +69,7 @@ public:
|
|||
|
||||
private:
|
||||
Config &_cfg;
|
||||
ProfileData &_profile;
|
||||
ProfileData &_profiledata;
|
||||
string _raw_doc;
|
||||
|
||||
void parse_rss(const pt::ptree &tree);
|
||||
|
@ -77,6 +77,7 @@ private:
|
|||
string remove_html(string html) const;
|
||||
[[nodiscard]]
|
||||
string extract_location(const RestClient::HeaderFields &headers) const;
|
||||
string add_hashtags(const string &text);
|
||||
};
|
||||
} // namespace mastorss
|
||||
|
||||
|
|
18
src/main.cpp
18
src/main.cpp
|
@ -34,6 +34,7 @@ constexpr int noprofile = 1;
|
|||
constexpr int network = 2;
|
||||
constexpr int file = 3;
|
||||
constexpr int mastodon = 4;
|
||||
constexpr int json = 5;
|
||||
constexpr int unknown = 9;
|
||||
} // namespace error
|
||||
|
||||
|
@ -90,25 +91,25 @@ int main(int argc, char *argv[])
|
|||
}
|
||||
else
|
||||
{
|
||||
const string_view profile{args[1]};
|
||||
BOOST_LOG_TRIVIAL(debug) << "Using profile: " << profile;
|
||||
const string_view profilename{args[1]};
|
||||
BOOST_LOG_TRIVIAL(debug) << "Using profile: " << profilename;
|
||||
|
||||
try
|
||||
{
|
||||
Config cfg{profile.data()};
|
||||
Config cfg{profilename.data()};
|
||||
Document doc{cfg};
|
||||
doc.parse();
|
||||
|
||||
MastoAPI masto{cfg.data};
|
||||
MastoAPI masto{cfg.profiledata};
|
||||
if (!doc.new_items.empty())
|
||||
{
|
||||
for (const auto &item : doc.new_items)
|
||||
{
|
||||
masto.post_item(item);
|
||||
cfg.data.last_guid = item.guid;
|
||||
cfg.profiledata.last_guid = item.guid;
|
||||
if (item != *doc.new_items.rbegin())
|
||||
{ // Don't sleep if this is the last item.
|
||||
sleep_for(seconds(cfg.data.interval));
|
||||
sleep_for(seconds(cfg.profiledata.interval));
|
||||
}
|
||||
}
|
||||
cfg.write();
|
||||
|
@ -134,6 +135,11 @@ int main(int argc, char *argv[])
|
|||
cerr << e.what() << '\n';
|
||||
return error::network;
|
||||
}
|
||||
catch (const Json::RuntimeError &e)
|
||||
{
|
||||
cerr << "JSON error:\n" << e.what() << '\n';
|
||||
return error::json;
|
||||
}
|
||||
catch (const runtime_error &e)
|
||||
{
|
||||
cerr << e.what() << '\n';
|
||||
|
|
Loading…
Reference in New Issue