("link");
+ item.title = move(title);
+ new_items.push_front(item);
+
+ BOOST_LOG_TRIVIAL(debug) << "Found GUID: " << item.guid;
+
+ if (_profile.last_guid.empty())
+ {
+ BOOST_LOG_TRIVIAL(debug) << "This is the first run.";
+ break;
+ }
+ }
+ }
+}
+
+string Document::remove_html(string html) const
+{
+ html = Mastodon::unescape_html(html); // Decode HTML entities.
+
+ html = regex_replace(html, regex{""}, "\n\n");
+
+ const list re_list
+ {
+ regex{R"()"}, // CDATA end.
+ regex{"<[^>]+>"}, // HTML tags.
+ regex{R"(\r)"}, // Carriage return.
+ regex{"\\n[ \\t\u00a0]+\\n"}, // Whitespace between newlines.
+ regex{R"(^\n+)"} // Newlines at the beginning.
+ };
+ for (const regex &re : re_list)
+ {
+ html = regex_replace(html, re, "");
+ }
+
+ // Remove excess newlines.
+ html = regex_replace(html, regex{R"(\n{3,})"}, "\n\n");
+ // Replace single newlines with spaces (?<= is lookbehind, ?= is lookahead).
+ html = regex_replace(html, regex{R"((?<=[^\n])\n(?=[^\n]))"}, " ");
+
+ BOOST_LOG_TRIVIAL(debug) << "Converted HTML to text.";
+
+ return html;
+}
+
+string Document::extract_location(const RestClient::HeaderFields &headers) const
+{
+ string location{headers.at("Location")};
+ if (location.empty())
+ {
+ location = headers.at("location");
+ }
+ return location;
+}
+} // namespace mastorss
diff --git a/src/document.hpp b/src/document.hpp
new file mode 100644
index 0000000..6dd842f
--- /dev/null
+++ b/src/document.hpp
@@ -0,0 +1,83 @@
+/* This file is part of mastorss.
+ * Copyright © 2019 tastytea
+ *
+ * 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 .
+ */
+
+#ifndef MASTORSS_DOCUMENT_HPP
+#define MASTORSS_DOCUMENT_HPP
+
+#include "config.hpp"
+
+#include
+#include
+
+#include
+#include
+
+namespace mastorss
+{
+namespace pt = boost::property_tree;
+using std::string;
+using std::list;
+
+/*!
+ * @brief An Item of a feed.
+ *
+ * @since 0.10.0
+ */
+struct Item
+{
+ string description;
+ string guid;
+ string link;
+ string title;
+
+ friend bool operator !=(const Item &a, const Item &b);
+};
+
+/*!
+ * @brief A feed.
+ *
+ * @since 0.10.0
+ */
+class Document
+{
+public:
+ explicit Document(Config &cfg);
+ ~Document();
+ Document(const Document &other) = default;
+ Document &operator=(const Document &other) = delete;
+ Document(Document &&other) = default;
+ Document &operator=(Document &&other) = delete;
+
+ list- new_items;
+
+ void download();
+ void download(const string &uri);
+ void parse();
+
+private:
+ Config &_cfg;
+ ProfileData &_profile;
+ string _raw_doc;
+
+ void parse_rss(const pt::ptree &tree);
+ [[nodiscard]]
+ string remove_html(string html) const;
+ [[nodiscard]]
+ string extract_location(const RestClient::HeaderFields &headers) const;
+};
+} // namespace mastorss
+
+#endif // MASTORSS_DOCUMENT_HPP
diff --git a/src/exceptions.cpp b/src/exceptions.cpp
new file mode 100644
index 0000000..1a2587e
--- /dev/null
+++ b/src/exceptions.cpp
@@ -0,0 +1,63 @@
+/* This file is part of mastorss.
+ * Copyright © 2019 tastytea
+ *
+ * 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 .
+ */
+
+#include "exceptions.hpp"
+
+#include
+
+using namespace mastorss;
+using std::to_string;
+using std::move;
+
+HTTPException::HTTPException(const int error)
+ : error_code{static_cast(error)}
+{}
+
+const char *HTTPException::what() const noexcept
+{
+ static const string error_string{"HTTP error: " + to_string(error_code)};
+ return error_string.c_str();
+}
+
+CURLException::CURLException(const int error)
+ : error_code{static_cast(error)}
+{}
+
+const char *CURLException::what() const noexcept
+{
+ static const string error_string{"libCURL error: " + to_string(error_code)};
+ return error_string.c_str();
+}
+
+MastodonException::MastodonException(const int error)
+ : error_code{static_cast(error)}
+{}
+
+const char *MastodonException::what() const noexcept
+{
+ static const string error_string{"Mastodon error: "
+ + to_string(error_code)};
+ return error_string.c_str();
+}
+
+FileException::FileException(string message)
+ : _message{move(message)}
+{}
+
+const char *FileException::what() const noexcept
+{
+ return _message.c_str();
+}
diff --git a/src/exceptions.hpp b/src/exceptions.hpp
new file mode 100644
index 0000000..555e7a4
--- /dev/null
+++ b/src/exceptions.hpp
@@ -0,0 +1,76 @@
+/* This file is part of mastorss.
+ * Copyright © 2019 tastytea
+ *
+ * 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 .
+ */
+
+#ifndef MASTORSS_EXCEPTIONS_HPP
+#define MASTORSS_EXCEPTIONS_HPP
+
+#include
+#include
+#include
+
+namespace mastorss
+{
+using std::uint16_t;
+using std::exception;
+using std::string;
+
+class HTTPException : public exception
+{
+public:
+ const uint16_t error_code;
+
+ explicit HTTPException(int error);
+
+ [[nodiscard]]
+ const char *what() const noexcept override;
+};
+
+class CURLException : public exception
+{
+public:
+ const uint16_t error_code;
+
+ explicit CURLException(int error);
+
+ [[nodiscard]]
+ const char *what() const noexcept override;
+};
+
+class MastodonException : public exception
+{
+public:
+ const uint16_t error_code;
+
+ explicit MastodonException(int error);
+
+ [[nodiscard]]
+ const char *what() const noexcept override;
+};
+
+class FileException : public exception
+{
+public:
+ explicit FileException(string message);
+
+ [[nodiscard]]
+ const char *what() const noexcept override;
+
+private:
+ const string _message;
+};
+} // namespace mastorss
+
+#endif // MASTORSS_EXCEPTIONS_HPP
diff --git a/src/http.cpp b/src/http.cpp
deleted file mode 100644
index 71c0bd8..0000000
--- a/src/http.cpp
+++ /dev/null
@@ -1,87 +0,0 @@
-/* This file is part of mastorss.
- * Copyright © 2018, 2019 tastytea
- *
- * 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 .
- */
-
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include "mastorss.hpp"
-
-using std::string;
-using std::cerr;
-
-namespace curlopts = curlpp::options;
-
-void curlpp_init()
-{
- curlpp::initialize();
-}
-
-std::uint16_t http_get(const string &feedurl, string &answer,
- const string &useragent)
-{
- try
- {
- std::ostringstream oss;
- curlpp::Easy request;
- request.setOpt(feedurl);
- request.setOpt(useragent);
- request.setOpt(
- {
- "Connection: close",
- });
- request.setOpt(true);
- request.setOpt(&oss);
-
- request.perform();
- std::uint16_t ret = curlpp::infos::ResponseCode::get(request);
- if (ret == 200 || ret == 302 || ret == 307)
- { // OK or Found or Temporary Redirect
- answer = oss.str();
- }
- else if (ret == 301 || ret == 308)
- { // Moved Permanently or Permanent Redirect
- // FIXME: The new URL should be passed back somehow
- answer = oss.str();
- }
- else
- {
- return ret;
- }
-
- return 0;
- }
- // TODO: More error codes
- catch (curlpp::RuntimeError &e)
- {
- cerr << "RUNTIME ERROR: " << e.what() << std::endl;
- return 0xffff;
- }
- catch (curlpp::LogicError &e)
- {
- cerr << "LOGIC ERROR: " << e.what() << std::endl;
- return 0xffff;
- }
-
- return 0;
-}
diff --git a/src/main.cpp b/src/main.cpp
new file mode 100644
index 0000000..f6c4d60
--- /dev/null
+++ b/src/main.cpp
@@ -0,0 +1,146 @@
+#include "config.hpp"
+#include "document.hpp"
+#include "exceptions.hpp"
+#include "mastoapi.hpp"
+#include "version.hpp"
+
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+using namespace mastorss;
+using std::chrono::seconds;
+using std::getenv;
+using std::cout;
+using std::cerr;
+using std::runtime_error;
+using std::string_view;
+using std::vector;
+using std::this_thread::sleep_for;
+
+namespace mastorss
+{
+namespace error
+{
+constexpr int noprofile = 1;
+constexpr int network = 2;
+constexpr int file = 3;
+constexpr int mastodon = 4;
+constexpr int unknown = 9;
+} // namespace error
+
+void print_version();
+void print_help(const string_view &command);
+
+void print_version()
+{
+ cout << "mastorss " << version << "\n"
+ "Copyright (C) 2019 tastytea \n"
+ "License GPLv3: GNU GPL version 3 "
+ ".\n"
+ "This program comes with ABSOLUTELY NO WARRANTY. "
+ "This is free software,\n"
+ "and you are welcome to redistribute it under certain conditions.\n";
+}
+
+void print_help(const string_view &command)
+{
+ cerr << "Usage: " << command << " [--version|--help] \n";
+}
+} // namespace mastorss
+
+int main(int argc, char *argv[])
+{
+ const vector args(argv, argv + argc);
+
+ if (getenv("MASTORSS_DEBUG") == nullptr)
+ {
+ boost::log::core::get()->set_filter
+ (boost::log::trivial::severity >= boost::log::trivial::info);
+ }
+ else
+ {
+ boost::log::core::get()->set_filter
+ (boost::log::trivial::severity >= boost::log::trivial::debug);
+ }
+
+ if (args.size() == 1)
+ {
+ print_help(args[0]);
+ return error::noprofile;
+ }
+
+ if (args.size() > 1)
+ {
+ if (args[1] == "--version")
+ {
+ print_version();
+ }
+ else if (args[1] == "--help")
+ {
+ print_help(args[0]);
+ }
+ else
+ {
+ const string_view profile{args[1]};
+ BOOST_LOG_TRIVIAL(debug) << "Using profile: " << profile;
+
+ try
+ {
+ Config cfg{profile.data()};
+ Document doc{cfg};
+ doc.parse();
+
+ MastoAPI masto{cfg.data};
+ if (!doc.new_items.empty())
+ {
+ for (const auto &item : doc.new_items)
+ {
+ masto.post_item(item);
+ cfg.data.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));
+ }
+ }
+ cfg.write();
+ }
+ }
+ catch (const FileException &e)
+ {
+ cerr << e.what() << '\n';
+ return error::file;
+ }
+ catch (const MastodonException &e)
+ {
+ cerr << e.what() << '\n';
+ return error::mastodon;
+ }
+ catch (const HTTPException &e)
+ {
+ cerr << e.what() << '\n';
+ return error::network;
+ }
+ catch (const CURLException &e)
+ {
+ cerr << e.what() << '\n';
+ return error::network;
+ }
+ catch (const runtime_error &e)
+ {
+ cerr << e.what() << '\n';
+ return error::unknown;
+ }
+ }
+ }
+
+ return 0;
+}
diff --git a/src/mastoapi.cpp b/src/mastoapi.cpp
new file mode 100644
index 0000000..1e00aff
--- /dev/null
+++ b/src/mastoapi.cpp
@@ -0,0 +1,89 @@
+/* This file is part of mastorss.
+ * Copyright © 2019 tastytea
+ *
+ * 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 .
+ */
+
+#include "exceptions.hpp"
+#include "mastoapi.hpp"
+
+#include
+
+#include
+
+namespace mastorss
+{
+using std::string;
+
+MastoAPI::MastoAPI(const ProfileData &data)
+ : _profile{data}
+ , _masto{_profile.instance, _profile.access_token}
+{
+}
+
+void MastoAPI::post_item(const Item &item)
+{
+ string status{[&]
+ {
+ if (_profile.titles_as_cw)
+ {
+ if (_profile.titles_only)
+ {
+ return string{};
+ }
+ return item.description;
+ }
+
+ string s{item.title};
+ if (!_profile.titles_only)
+ {
+ s.append("\n\n" + item.description);
+ }
+ return s;
+ }()};
+ status.append("\n\n" + item.link);
+
+ if (!_profile.append.empty())
+ {
+ const size_t len{status.size()};
+ const size_t len_append{_profile.append.size() + 2};
+ const size_t len_max{_profile.max_size};
+
+ if ((len + len_append) > len_max)
+ {
+ status.resize(len_max - len_append);
+ }
+ status.append("\n\n" + _profile.append);
+ }
+
+ Mastodon::parameters params{{"status", {status}}};
+ if (_profile.titles_as_cw)
+ {
+ params.push_back({"spoiler_text", {item.title}});
+ }
+
+ const auto ret = _masto.post(Mastodon::API::v1::statuses, params);
+ if (!ret)
+ {
+ if (ret.http_error_code != 200)
+ {
+ throw HTTPException{ret.http_error_code};
+ }
+ else
+ {
+ throw MastodonException{ret.error_code};
+ }
+ }
+ BOOST_LOG_TRIVIAL(debug) << "Posted status with GUID: " << item.guid;
+}
+} // namespace mastorss
diff --git a/src/mastoapi.hpp b/src/mastoapi.hpp
new file mode 100644
index 0000000..6b9a041
--- /dev/null
+++ b/src/mastoapi.hpp
@@ -0,0 +1,40 @@
+/* This file is part of mastorss.
+ * Copyright © 2019 tastytea
+ *
+ * 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 .
+ */
+
+#ifndef MASTORSS_MASTOAPI_HPP
+#define MASTORSS_MASTOAPI_HPP
+
+#include "config.hpp"
+#include "document.hpp"
+
+#include
+
+namespace mastorss
+{
+class MastoAPI
+{
+public:
+ explicit MastoAPI(const ProfileData &data);
+
+ void post_item(const Item &item);
+
+private:
+ const ProfileData &_profile;
+ Mastodon::API _masto;
+};
+} // namespace mastorss
+
+#endif // MASTORSS_MASTOAPI_HPP
diff --git a/src/mastorss.cpp b/src/mastorss.cpp
deleted file mode 100644
index 28e33a8..0000000
--- a/src/mastorss.cpp
+++ /dev/null
@@ -1,164 +0,0 @@
-/* This file is part of mastorss.
- * Copyright © 2018, 2019 tastytea
- *
- * 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 .
- */
-
-#include
-#include
-#include
-#include // getenv()
-#include
-#include
-#include
-#include
-#include
-#include
-#include "version.hpp"
-#include "mastorss.hpp"
-
-using namespace Mastodon;
-
-using std::cout;
-using std::cerr;
-using std::cin;
-using std::endl;
-using std::string;
-using std::this_thread::sleep_for;
-using std::chrono::seconds;
-
-// Initialize global variables
-std::uint16_t max_size = 500;
-const string filepath = string(getenv("HOME")) + "/.config/mastorss/";
-Json::Value config;
-std::string profile;
-
-int main(int argc, char *argv[])
-{
- if (argc < 2)
- {
- cerr << "usage: " << argv[0] << " [max size]\n";
- return 10;
- }
-
- if (argc == 3)
- {
- max_size = std::stoi(argv[2]);
- }
-
- string instance = "";
- string access_token = "";
- string feedurl = "";
- profile = argv[1];
- std::uint_fast16_t ret;
- string answer;
- std::vector entries;
-
- read_config(instance, access_token, feedurl);
- curlpp_init();
-
- ret = http_get(feedurl, answer, "mastorss/" + (string)global::version);
- if (ret != 0)
- {
- std::cerr << "Error code: " << ret << '\n';
- std::cerr << answer << '\n';
- return ret;
- }
- entries = parse_feed(answer);
-
- string last_entry = config[profile]["last_entry"].asString();
- if (last_entry.empty())
- {
- // If no last_entry is stored in the config file,
- // make last_entry the second-newest entry.
- last_entry = entries.at(1).content();
- }
- config[profile]["last_entry"] = entries.front().content();
-
- bool new_content = false;
- for (auto rit = entries.rbegin(); rit != entries.rend(); ++rit)
- {
- if (!new_content && (*rit).content().compare(last_entry) == 0)
- {
- // If the last entry is found in entries,
- // start tooting in the next loop.
- new_content = true;
- continue;
- }
- else if (!new_content)
- {
- continue;
- }
-
- Easy::return_entity ret_status;
- Mastodon::Easy::API masto(instance, access_token);
-
- ret_status = masto.send_post(*rit);
-
- if (!ret_status)
- {
- const uint8_t err = ret_status.error_code;
- switch (err)
- {
- case 110:
- {
- cerr << "Error " << err << ": Timeout\n";
- break;
- }
- case 111:
- {
- cerr << "Error " << err << ": Connection refused\n";
- cerr << "HTTP Error " << ret_status.http_error_code << endl;
- break;
- }
- case 113:
- {
- cerr << "Error " << err << ": Could not reach host.\n";
- break;
- }
- case 192:
- case 193:
- {
- cerr << "Error " << err << ": curlpp error\n";
- break;
- }
- default:
- {
- cerr << "Error " << err << '\n';
- cerr << "HTTP status " << ret_status.http_error_code << endl;
- }
- }
-
- cerr << ret_status.entity.to_string() << '\n';
- return ret;
- }
-
- if (!ret_status.entity.valid())
- {
- cerr << "Could not send post for unknown reasons.\n";
- cerr << "Please file a bug at "
- ".\n";
- return 1;
- }
-
- if (rit != entries.rend())
- { // Only sleep if this is not the last entry
- sleep_for(seconds(config[profile]["interval"].asUInt64()));
- }
- }
-
- // Write the new last_entry only if no error happened.
- write_config();
-
- return 0;
-}
diff --git a/src/mastorss.hpp b/src/mastorss.hpp
deleted file mode 100644
index 7d9a6e8..0000000
--- a/src/mastorss.hpp
+++ /dev/null
@@ -1,43 +0,0 @@
-/* This file is part of mastorss.
- * Copyright © 2018, 2019 tastytea
- *
- * 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 .
- */
-
-#ifndef mastorss_HPP
-#define mastorss_HPP
-
-#include
-#include
-#include
-#include
-#include
-
-using std::string;
-
-extern std::uint16_t max_size;
-extern const string filepath;
-extern Json::Value config;
-extern std::string profile;
-
-std::uint16_t read_config(string &instance, string &access_token, string &feedurl);
-bool write_config();
-
-std::vector parse_feed(const string &xml);
-void individual_fixes(string &str);
-
-std::uint16_t http_get(const string &feedurl,
- string &answer, const string &useragent = "");
-void curlpp_init();
-
-#endif // mastorss_HPP
diff --git a/src/parse.cpp b/src/parse.cpp
deleted file mode 100644
index a6fcd58..0000000
--- a/src/parse.cpp
+++ /dev/null
@@ -1,210 +0,0 @@
-/* This file is part of mastorss.
- * Copyright © 2018, 2019 tastytea
- *
- * 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 .
- */
-
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include "mastorss.hpp"
-
-using std::cerr;
-using std::string;
-namespace pt = boost::property_tree;
-
-std::vector parse_feed(const string &xml)
-{
- Json::Value list;
- std::vector watchwords;
-
- std::ifstream file(filepath + "watchwords.json");
- if (file.is_open())
- {
- std::stringstream json;
- json << file.rdbuf();
- file.close();
- json >> list;
- }
- else
- {
- cerr << "WARNING: " << filepath << "watchwords.json not found or not readable.\n";
- }
-
- // Read profile-specific hashtags or fail silently
- const Json::Value &tags_profile = list[profile]["tags"];
- std::transform(tags_profile.begin(), tags_profile.end(),
- std::back_inserter(watchwords),
- [](const Json::Value &value)
- { return value.asString(); });
-
- // Read global hashtags or fail silently
- const Json::Value &tags_global = list["global"]["tags"];
- std::transform(tags_global.begin(), tags_global.end(),
- std::back_inserter(watchwords),
- [](const Json::Value &value)
- { return value.asString(); });
-
- pt::ptree rss;
- std::istringstream iss(xml);
- pt::read_xml(iss, rss);
- std::vector ret;
-
- for (const pt::ptree::value_type &chanchild : rss.get_child("rss.channel"))
- {
- if (chanchild.second.size() > 0)
- {
- if (string(chanchild.first.data()).compare("item") == 0)
- {
- string title = chanchild.second.get_child("title").data();
- string link = chanchild.second.get_child("link").data();
- string desc = chanchild.second.get_child("description").data();
-
- Mastodon::Easy::Status status;
- string content = "";
- if (config[profile]["titles_as_cw"].asBool())
- {
- status.spoiler_text(Mastodon::unescape_html(title));
- }
- else
- {
- content = title;
- }
- if (!config[profile]["titles_only"].asBool())
- {
- if (!content.empty())
- {
- content += "\n\n";
- }
- content += desc;
-
- // Shrink overly long texts, to speed up replace operations
- if (content.length() > 2000)
- {
- content.resize(2000);
- }
- }
-
- bool skipthis = false;
- try
- {
- // Skip entries beginning with this text
- for (const Json::Value &v : config[profile]["skip"])
- {
- const string skip = v.asString();
- if (!skip.empty())
- {
- if (title.compare(0, skip.length(), skip) == 0)
- {
- skipthis = true;
- break;
- }
- }
- }
- }
- catch (const std::exception &e)
- {
- // Node not found, no problem
- }
- if (skipthis)
- {
- continue;
- }
-
- content = Mastodon::unescape_html(content);
-
- // Try to turn the HTML into human-readable text
- std::regex reparagraph("
");
- std::regex recdata1("");
- std::regex restrip("<[^>]*>");
-
- individual_fixes(content);
-
- content = std::regex_replace(content, reparagraph, "\n\n");
- content = std::regex_replace(content, recdata1, "");
- content = std::regex_replace(content, recdata2, "");
- content = std::regex_replace(content, restrip, "");
- // remove \r
- content = std::regex_replace(content, std::regex("\\r"), "");
- // replace NO-BREAK SPACE with space (UTF-8: 0xc2a0)
- content = std::regex_replace(content, std::regex("\u00a0"), " ");
- // remove whitespace between newlines
- content = std::regex_replace(content, std::regex("\\n[ \t]+\\n"), "");
- // remove excess newlines
- content = std::regex_replace(content, std::regex("\\n{3,}"), "\n\n");
-
- for (const string &hashtag : watchwords)
- {
- std::regex rehashtag("([[:space:][:punct:]]|^)(" + hashtag
- + ")([[:space:][:punct:]]|$)",
- std::regex_constants::icase);
- content = std::regex_replace(content, rehashtag, "$1#$2$3",
- std::regex_constants::format_first_only);
- }
- // Why is this necessary? Why does ##hashtag happen?
- content = std::regex_replace(content, std::regex("##"), "#");
-
- uint16_t appendix_size = config[profile]["append"].asString().length();
- if ((status.spoiler_text().size() + content.size() + link.size() + appendix_size)
- > static_cast(max_size - 4))
- {
- content.resize((max_size - status.spoiler_text().size()
- - link.size() - appendix_size - 4));
- content.resize(content.rfind(' ')); // Cut at word boundary
- content += " […]";
- }
- // Remove trailing newlines
- while (content.back() == '\n' ||
- content.back() == '\r')
- {
- content.resize(content.length() - 1);
- }
-
- content += "\n\n" + link;
-
- if (!config[profile]["append"].empty())
- {
- content += "\n\n" + config[profile]["append"].asString();
- }
- status.content(content);
- ret.push_back(status);
- }
- }
- }
-
- return ret;
-}
-
-// Read regular expressions from the config file and delete all matches.
-void individual_fixes(string &str)
-{
- for (const Json::Value &v : config[profile]["fixes"])
- {
- std::regex refix(v.asString());
- str = std::regex_replace(str, refix, "");
- }
-}
diff --git a/src/version.hpp.in b/src/version.hpp.in
index cec7915..ec2ea02 100644
--- a/src/version.hpp.in
+++ b/src/version.hpp.in
@@ -1,9 +1,13 @@
-#ifndef VERSION_HPP
-#define VERSION_HPP
+#ifndef MASTORSS_VERSION_HPP
+#define MASTORSS_VERSION_HPP
-namespace global
+#include
+
+namespace mastorss
{
- static constexpr char version[] = "@PROJECT_VERSION@";
-}
+using std::string_view;
-#endif // VERSION_HPP
+static constexpr string_view version = "@PROJECT_VERSION@";
+} // namespace mastorss
+
+#endif // MASTORSS_VERSION_HPP