diff --git a/CMakeLists.txt b/CMakeLists.txt index e1388d1..b2e4e58 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required (VERSION 3.7) include(GNUInstallDirs) project (mastorss - VERSION 0.2.2 + VERSION 0.2.3 LANGUAGES CXX ) @@ -21,6 +21,6 @@ configure_file ( include(FindCURL) find_package(CURL REQUIRED) -add_executable(mastorss src/mastorss.cpp src/http.cpp) +add_executable(mastorss src/mastorss.cpp src/http.cpp src/config.cpp src/parse.cpp) target_link_libraries(mastorss mastodon-cpp boost_system boost_filesystem ssl crypto ${CURL_LIBRARIES} curlpp) install(TARGETS mastorss DESTINATION ${CMAKE_INSTALL_BINDIR}) diff --git a/src/config.cpp b/src/config.cpp new file mode 100644 index 0000000..9e1269a --- /dev/null +++ b/src/config.cpp @@ -0,0 +1,117 @@ +/* This file is part of mastorss. + * Copyright © 2018 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 "mastorss.hpp" + +namespace pt = boost::property_tree; + +using std::cout; +using std::cerr; +using std::cin; +using std::string; + +std::uint16_t read_config(pt::ptree &config, const string &profile, string &instance, string &access_token, string &feedurl) +{ + bool config_changed = false; + + // Read config file, get access token + try { + pt::read_json(filepath + "config-" + profile + ".json", config); + instance = config.get(profile + ".instance", ""); + access_token = config.get(profile + ".access_token", ""); + feedurl = config.get(profile + ".feedurl", ""); + } + catch (std::exception &e) + { + // most likely no config file found + cout << "Config file not readable. Building new one.\n"; + const boost::filesystem::path path(filepath); + boost::filesystem::create_directory(filepath); + } + + if (instance.empty()) + { + cout << "Instance: "; + cin >> instance; + config.put(profile + ".instance", instance); + config_changed = true; + } + if (access_token.empty()) + { + cout << "No access token found.\n"; + string client_id, client_secret, url; + Mastodon::API masto(instance, ""); + std::uint16_t ret = masto.register_app1(instance, + "mastorss", + "urn:ietf:wg:oauth:2.0:oob", + "write", + "https://github.com/tastytea/mastorss", + client_id, + client_secret, + url); + if (ret == 0) + { + string code; + cout << "Visit " << url << " to authorize this application.\n"; + cout << "Insert code: "; + cin >> code; + + masto.register_app2(instance, + client_id, + client_secret, + "urn:ietf:wg:oauth:2.0:oob", + code, + access_token); + if (ret == 0) + { + config.put(profile + ".access_token", access_token); + config_changed = true; + } + else + { + cerr << "Error code: " << ret << '\n'; + return ret; + } + } + else + { + cerr << "Error code: " << ret << '\n'; + return ret; + } + + } + if (feedurl.empty()) + { + cout << "feedurl: "; + cin >> feedurl; + config.put(profile + ".feedurl", feedurl); + config_changed = true; + } + if (config_changed) + { + pt::write_json(filepath + "config-" + profile + ".json", config); + } + + return 0; +} diff --git a/src/mastorss.cpp b/src/mastorss.cpp index 0eb6780..fdcd93a 100644 --- a/src/mastorss.cpp +++ b/src/mastorss.cpp @@ -17,11 +17,8 @@ #include #include #include +#include // getenv() #include -#include -#include -#include -#include #include #include #include @@ -39,188 +36,10 @@ using std::cerr; using std::cin; using std::string; +// Initialize global variables std::uint16_t max_size = 500; const string filepath = string(getenv("HOME")) + "/.config/mastorss/"; -std::uint16_t read_config(pt::ptree &config, const string &profile, string &instance, string &access_token, string &feedurl) -{ - bool config_changed = false; - - // Read config file, get access token - try { - pt::read_json(filepath + "config-" + profile + ".json", config); - instance = config.get(profile + ".instance", ""); - access_token = config.get(profile + ".access_token", ""); - feedurl = config.get(profile + ".feedurl", ""); - } - catch (std::exception &e) - { - // most likely no config file found - cout << "Config file not readable. Building new one.\n"; - const boost::filesystem::path path(filepath); - boost::filesystem::create_directory(filepath); - } - - if (instance.empty()) - { - cout << "Instance: "; - cin >> instance; - config.put(profile + ".instance", instance); - config_changed = true; - } - if (access_token.empty()) - { - cout << "No access token found.\n"; - string client_id, client_secret, url; - Mastodon::API masto(instance, ""); - std::uint16_t ret = masto.register_app1(instance, - "mastorss", - "urn:ietf:wg:oauth:2.0:oob", - "write", - "https://github.com/tastytea/mastorss", - client_id, - client_secret, - url); - if (ret == 0) - { - string code; - cout << "Visit " << url << " to authorize this application.\n"; - cout << "Insert code: "; - cin >> code; - - masto.register_app2(instance, - client_id, - client_secret, - "urn:ietf:wg:oauth:2.0:oob", - code, - access_token); - if (ret == 0) - { - config.put(profile + ".access_token", access_token); - config_changed = true; - } - else - { - cerr << "Error code: " << ret << '\n'; - return ret; - } - } - else - { - cerr << "Error code: " << ret << '\n'; - return ret; - } - - } - if (feedurl.empty()) - { - cout << "feedurl: "; - cin >> feedurl; - config.put(profile + ".feedurl", feedurl); - config_changed = true; - } - if (config_changed) - { - pt::write_json(filepath + "config-" + profile + ".json", config); - } - - return 0; -} - -std::vector parse_website(const string &profile, const string &xml) -{ - pt::ptree json; - std::vector watchwords; - - try - { - pt::read_json(filepath + "watchwords.json", json); - } - catch (std::exception &e) - { - // most likely file not found - std::cerr << "ERROR: " << filepath << "watchwords.json not found or not readable.\n"; - std::cerr << e.what() << '\n'; - return {}; - } - - try - { - for (const pt::ptree::value_type &value : json.get_child(profile + ".tags")) - { - watchwords.push_back(value.second.data()); - } - } - catch (const std::exception &e) - { - // Node not found, no problem - } - try - { - for (const pt::ptree::value_type &value : json.get_child("global.tags")) - { - watchwords.push_back(value.second.data()); - } - } - catch (const std::exception &e) - { - // Node not found, no problem - } - - pt::ptree rss; - std::istringstream iss(xml); - pt::read_xml(iss, rss); - std::vector ret; - - for (const pt::ptree::value_type &v : rss.get_child("rss.channel")) - { - if (v.second.size() > 0) - { - if (string(v.first.data()).compare("item") == 0) - { - string title = v.second.get_child("title").data(); - string link = v.second.get_child("link").data(); - string desc = v.second.get_child("description").data(); - string str = title + "\n\n" + desc; - - // Some feeds contain encoded xhtml-tags >:| - std::regex relt("<"); - std::regex regt(">"); - std::regex reparagraph("

"); - std::regex recdata1(""); - std::regex restrip("<[^>]*>"); - std::regex reindyfuckup("\\/\\* Style Definitions \\*\\/[.[:space:]]*$"); - - str = std::regex_replace(str, relt, "<"); - str = std::regex_replace(str, regt, ">"); - str = std::regex_replace(str, reparagraph, "\n\n"); - str = std::regex_replace(str, recdata1, ""); - str = std::regex_replace(str, recdata2, ""); - str = std::regex_replace(str, restrip, ""); - str = std::regex_replace(str, reindyfuckup, ""); - - for (const string &hashtag : watchwords) - { - std::regex rehashtag("([[:space:][:punct:]]|^)(" + hashtag + ")([[:space:][:punct:]]|$)", - std::regex_constants::icase); - str = std::regex_replace(str, rehashtag, "$1#$2$3", - std::regex_constants::format_first_only); - } - if ((str.size() + link.size()) > (std::uint16_t)(max_size - 15)) - { - str.resize((max_size - link.size() - 15)); - str += " […]"; - } - str += "\n\n" + link + "\n\n#bot"; - ret.push_back(str); - } - } - } - - return ret; -} - int main(int argc, char *argv[]) { if (argc < 2) @@ -284,11 +103,7 @@ int main(int argc, char *argv[]) }; ret = masto.post(API::v1::statuses, parameters, answer); - if (ret == 0) - { - pt::write_json(filepath + "config-" + profile + ".json", config); - } - else + if (ret != 0) { std::cerr << "Error code: " << ret << '\n'; std::cerr << answer << '\n'; @@ -298,5 +113,7 @@ int main(int argc, char *argv[]) std::this_thread::sleep_for(std::chrono::seconds(2)); } + pt::write_json(filepath + "config-" + profile + ".json", config); + return 0; } diff --git a/src/mastorss.hpp b/src/mastorss.hpp index d80603f..a5d3f4b 100644 --- a/src/mastorss.hpp +++ b/src/mastorss.hpp @@ -9,6 +9,9 @@ namespace pt = boost::property_tree; using std::string; +extern std::uint16_t max_size; +extern const string filepath; + std::uint16_t read_config(pt::ptree &config, const string &profile, string &instance, string &access_token, string &feedurl); std::vector parse_website(const string &profile, const string &xml); diff --git a/src/parse.cpp b/src/parse.cpp new file mode 100644 index 0000000..e37dc5d --- /dev/null +++ b/src/parse.cpp @@ -0,0 +1,128 @@ +/* This file is part of mastorss. + * Copyright © 2018 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" + +namespace pt = boost::property_tree; + +using std::cerr; +using std::string; + +std::vector parse_website(const string &profile, const string &xml) +{ + pt::ptree json; + std::vector watchwords; + + try + { + pt::read_json(filepath + "watchwords.json", json); + } + catch (std::exception &e) + { + // most likely file not found + cerr << "ERROR: " << filepath << "watchwords.json not found or not readable.\n"; + cerr << e.what() << '\n'; + return {}; + } + + try + { + for (const pt::ptree::value_type &value : json.get_child(profile + ".tags")) + { + watchwords.push_back(value.second.data()); + } + } + catch (const std::exception &e) + { + // Node not found, no problem + } + try + { + for (const pt::ptree::value_type &value : json.get_child("global.tags")) + { + watchwords.push_back(value.second.data()); + } + } + catch (const std::exception &e) + { + // Node not found, no problem + } + + pt::ptree rss; + std::istringstream iss(xml); + pt::read_xml(iss, rss); + std::vector ret; + + for (const pt::ptree::value_type &v : rss.get_child("rss.channel")) + { + if (v.second.size() > 0) + { + if (string(v.first.data()).compare("item") == 0) + { + string title = v.second.get_child("title").data(); + string link = v.second.get_child("link").data(); + string desc = v.second.get_child("description").data(); + string str = title + "\n\n" + desc; + + // Some feeds contain encoded xhtml-tags >:| + std::regex relt("<"); + std::regex regt(">"); + std::regex reparagraph("

"); + std::regex recdata1(""); + std::regex restrip("<[^>]*>"); + std::regex reindyfuckup("\\/\\* Style Definitions \\*\\/[.[:space:]]*$"); + + str = std::regex_replace(str, relt, "<"); + str = std::regex_replace(str, regt, ">"); + str = std::regex_replace(str, reparagraph, "\n\n"); + str = std::regex_replace(str, recdata1, ""); + str = std::regex_replace(str, recdata2, ""); + str = std::regex_replace(str, restrip, ""); + str = std::regex_replace(str, reindyfuckup, ""); + str = std::regex_replace(str, std::regex("[\\n\\r]+"), "\n"); // remove excess newlines + + for (const string &hashtag : watchwords) + { + std::regex rehashtag("([[:space:][:punct:]]|^)(" + hashtag + ")([[:space:][:punct:]]|$)", + std::regex_constants::icase); + str = std::regex_replace(str, rehashtag, "$1#$2$3", + std::regex_constants::format_first_only); + } + if ((str.size() + link.size()) > (std::uint16_t)(max_size - 15)) + { + str.resize((max_size - link.size() - 15)); + str += " […]"; + } + str += "\n\n" + link + "\n\n#bot"; + ret.push_back(str); + } + } + } + + return ret; +}