diff --git a/man/mastorss.1.adoc b/man/mastorss.1.adoc index 83ca3ea..15c6d92 100644 --- a/man/mastorss.1.adoc +++ b/man/mastorss.1.adoc @@ -66,6 +66,9 @@ Example: `http_proxy="http://localhost:3128/" mastorss` | 1 | No profile specified. | 2 | Network error. +| 3 | File error. +| 4 | Mastodon error. +| 9 | Unknown error. |=========================================================== == SEE ALSO diff --git a/src/config.cpp b/src/config.cpp new file mode 100644 index 0000000..7248ae8 --- /dev/null +++ b/src/config.cpp @@ -0,0 +1,243 @@ +/* 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 "config.hpp" +#include "exceptions.hpp" + +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace mastorss +{ +using std::getenv; +using std::ifstream; +using std::ofstream; +using std::cin; +using std::cout; +using std::stringstream; +using std::runtime_error; +using std::getline; +using std::move; + +std::ostream &operator <<(std::ostream &out, const ProfileData &data) +{ + out << "access_token: \"" << data.access_token << "\", " + << "append: \"" << data.append << "\", " + << "feedurl: \"" << data.feedurl << "\", " + << "fixes: ["; + for (const auto &fix : data.fixes) + { + out << '"' << fix << '"'; + if (fix != *data.fixes.rbegin()) + { + out << ", "; + } + } + out << "], " + << "instance: \"" << data.instance << "\", " + << "interval: " << data.interval << ", " + << "max_size: " << data.max_size << ", " + << "skip: ["; + for (const auto &skip : data.skip) + { + out << '"' << skip << '"'; + if (skip != *data.skip.rbegin()) + { + out << ", "; + } + } + out << "], " + << "titles_as_cw: " << data.titles_as_cw << ", " + << "titles_only: " << data.titles_only; + + return out; +} + +Config::Config(string profile) + :_profile{move(profile)} +{ + const fs::path filename = get_filename(); + BOOST_LOG_TRIVIAL(debug) << "Config filename is: " << filename; + + ifstream file{filename}; + if (file.good()) + { + stringstream rawjson; + rawjson << file.rdbuf(); + rawjson >> _json; + } + else + { + generate(); + } + + parse(); +} + +fs::path Config::get_filename() const +{ + char *envdir = getenv("XDG_CONFIG_HOME"); + fs::path dir; + + if (envdir != nullptr) + { + dir = envdir; + } + else + { + envdir = getenv("HOME"); + if (envdir != nullptr) + { + dir = fs::path{envdir} /= ".config"; + } + else + { + throw FileException{"Couldn't find configuration directory."}; + } + } + + return (dir /= "mastorss") /= "config-" + _profile + ".json"; +} + +void Config::generate() +{ + Json::Value newjson; + string line; + + cout << "Instance (domain): "; + getline(cin, line); + newjson[_profile]["instance"] = line; + + newjson[_profile]["access_token"] = get_access_token(line); + + cout << "URL of the feed: "; + std::getline(cin, line); + newjson[_profile]["feedurl"] = line; + + cout << "Post only titles? [y/n]: "; + std::getline(cin, line); + if (line[0] == 'y') + { + newjson[_profile]["titles_as_cw"] = true; + } + else + { + newjson[_profile]["titles_as_cw"] = false; + } + + cout << "Append this string to each post: "; + std::getline(cin, line); + newjson[_profile]["append"] = line; + + cout << "Interval between posts in seconds [30]: "; + std::getline(cin, line); + if (line.empty()) + { + line = "30"; + } + newjson[_profile]["interval"] = std::stoul(line); + + cout << "Maximum size of posts [500]: "; + std::getline(cin, line); + if (line.empty()) + { + line = "500"; + } + newjson[_profile]["max_size"] = std::stoul(line); + + _json = newjson; + ofstream file{get_filename()}; + if (file.good()) + { + file << newjson.toStyledString(); + } + + BOOST_LOG_TRIVIAL(debug) << "Wrote config file."; +} + +string Config::get_access_token(const string &instance) const +{ + string client_id; + string client_secret; + string url; + Mastodon::API masto(instance, ""); + + Mastodon::return_call ret + { + masto.register_app1("mastorss", "urn:ietf:wg:oauth:2.0:oob", + "write", + "https://schlomp.space/tastytea/mastorss", + client_id, client_secret, url) + }; + + if (ret) + { + string code; + string access_token; + + cout << "Visit " << url << " to authorize this application.\n"; + cout << "Insert code: "; + std::getline(cin, code); + ret = masto.register_app2(client_id, client_secret, + "urn:ietf:wg:oauth:2.0:oob", + code, access_token); + if (ret) + { + BOOST_LOG_TRIVIAL(debug) << "Got access token: " << access_token; + return access_token; + } + } + + throw MastodonException{ret.error_code}; +} + +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"]) + { + data.fixes.push_back(fix.asString()); + } + data.instance = _json[_profile]["instance"].asString(); + if (!_json[_profile]["interval"].isNull()) + { + data.interval = + static_cast(_json[_profile]["interval"].asUInt64()); + } + if (!_json[_profile]["max_size"].isNull()) + { + data.max_size = + static_cast(_json[_profile]["max_size"].asUInt64()); + } + for (const auto &skip : _json[_profile]["skip"]) + { + data.skip.push_back(skip.asString()); + } + data.titles_as_cw = _json[_profile]["titles_as_cw"].asBool(); + data.titles_only = _json[_profile]["titles_only"].asBool(); + + BOOST_LOG_TRIVIAL(debug) << "Read config: " << data; +} +} // namespace mastorss diff --git a/src/config.hpp b/src/config.hpp new file mode 100644 index 0000000..3e0bbd1 --- /dev/null +++ b/src/config.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_CONFIG_HPP +#define MASTORSS_CONFIG_HPP + +#include +#include + +#include +#include +#include +#include + +namespace mastorss +{ +namespace fs = boost::filesystem; +using std::uint32_t; +using std::string; +using std::string_view; +using std::vector; + +/*! + * @brief The configuration for a profile as data structure. + * + * @since 0.10.0 + */ +struct ProfileData +{ + string access_token; + string append; + string feedurl; + vector fixes; + string instance; + uint32_t interval{30}; + uint32_t max_size{500}; + vector skip; + bool titles_as_cw{false}; + bool titles_only{false}; + + friend std::ostream &operator <<(std::ostream &out, + const ProfileData &data); +}; + +/*! + * @brief A configuration file. + * + * @since 0.10.0 + */ +class Config +{ +public: + explicit Config(string profile); + + ProfileData data; + +private: + const string _profile; + Json::Value _json; + + [[nodiscard]] + fs::path get_filename() const; + void generate(); + [[nodiscard]] + string get_access_token(const string &instance) const; + void parse(); +}; +} // namespace mastorss + +#endif // MASTORSS_CONFIG_HPP diff --git a/src/exceptions.cpp b/src/exceptions.cpp index 58458b5..1a2587e 100644 --- a/src/exceptions.cpp +++ b/src/exceptions.cpp @@ -16,11 +16,11 @@ #include "exceptions.hpp" -#include +#include using namespace mastorss; -using std::string; using std::to_string; +using std::move; HTTPException::HTTPException(const int error) : error_code{static_cast(error)} @@ -41,3 +41,23 @@ 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 index aac1c9d..555e7a4 100644 --- a/src/exceptions.hpp +++ b/src/exceptions.hpp @@ -19,20 +19,23 @@ #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(const int error); + explicit HTTPException(int error); - virtual const char *what() const noexcept; + [[nodiscard]] + const char *what() const noexcept override; }; class CURLException : public exception @@ -40,9 +43,33 @@ class CURLException : public exception public: const uint16_t error_code; - explicit CURLException(const int error); + explicit CURLException(int error); - virtual const char *what() const noexcept; + [[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 diff --git a/src/main.cpp b/src/main.cpp index 422259e..4e5c6d3 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,6 +1,7 @@ -#include "version.hpp" +#include "config.hpp" #include "document.hpp" #include "exceptions.hpp" +#include "version.hpp" #include #include @@ -18,6 +19,9 @@ 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(); @@ -62,9 +66,23 @@ int main(int argc, char *argv[]) } else { + const string_view profile = args[1]; + BOOST_LOG_TRIVIAL(debug) << "Using profile: " << profile; + try { - Document doc("https://ip.tastytea.de/"); + Config cfg(profile.data()); + Document doc(cfg.data.feedurl); + } + 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) { @@ -76,7 +94,11 @@ int main(int argc, char *argv[]) cerr << e.what() << '\n'; return error::network; } - + catch (const runtime_error &e) + { + cerr << e.what() << '\n'; + return error::unknown; + } } }