Merge branch 'rewrite' into main
continuous-integration/drone/push Build is passing Details

This commit is contained in:
tastytea 2019-12-28 10:35:31 +01:00
commit bca496c87a
Signed by: tastytea
GPG Key ID: CFC39497F1B26E07
6 changed files with 129 additions and 67 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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