refactoring
This commit is contained in:
parent
3d72be291a
commit
0daf373d3c
|
@ -2,7 +2,7 @@ cmake_minimum_required (VERSION 3.7)
|
||||||
include(GNUInstallDirs)
|
include(GNUInstallDirs)
|
||||||
|
|
||||||
project (mastorss
|
project (mastorss
|
||||||
VERSION 0.2.2
|
VERSION 0.2.3
|
||||||
LANGUAGES CXX
|
LANGUAGES CXX
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -21,6 +21,6 @@ configure_file (
|
||||||
include(FindCURL)
|
include(FindCURL)
|
||||||
find_package(CURL REQUIRED)
|
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)
|
target_link_libraries(mastorss mastodon-cpp boost_system boost_filesystem ssl crypto ${CURL_LIBRARIES} curlpp)
|
||||||
install(TARGETS mastorss DESTINATION ${CMAKE_INSTALL_BINDIR})
|
install(TARGETS mastorss DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||||
|
|
117
src/config.cpp
Normal file
117
src/config.cpp
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
/* This file is part of mastorss.
|
||||||
|
* Copyright © 2018 tastytea <tastytea@tastytea.de>
|
||||||
|
*
|
||||||
|
* 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <boost/property_tree/ptree.hpp>
|
||||||
|
#include <boost/property_tree/json_parser.hpp>
|
||||||
|
#include <boost/property_tree/xml_parser.hpp>
|
||||||
|
#include <boost/filesystem.hpp>
|
||||||
|
#include <mastodon-cpp.hpp>
|
||||||
|
#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;
|
||||||
|
}
|
193
src/mastorss.cpp
193
src/mastorss.cpp
|
@ -17,11 +17,8 @@
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <cstdlib> // getenv()
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <cstdlib>
|
|
||||||
#include <random>
|
|
||||||
#include <regex>
|
|
||||||
#include <sstream>
|
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <boost/property_tree/ptree.hpp>
|
#include <boost/property_tree/ptree.hpp>
|
||||||
|
@ -39,188 +36,10 @@ using std::cerr;
|
||||||
using std::cin;
|
using std::cin;
|
||||||
using std::string;
|
using std::string;
|
||||||
|
|
||||||
|
// Initialize global variables
|
||||||
std::uint16_t max_size = 500;
|
std::uint16_t max_size = 500;
|
||||||
const string filepath = string(getenv("HOME")) + "/.config/mastorss/";
|
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<string> parse_website(const string &profile, const string &xml)
|
|
||||||
{
|
|
||||||
pt::ptree json;
|
|
||||||
std::vector<string> 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<string> 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("</p><p>");
|
|
||||||
std::regex recdata1("<!\\[CDATA\\[");
|
|
||||||
std::regex recdata2("\\]\\]>");
|
|
||||||
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[])
|
int main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
if (argc < 2)
|
if (argc < 2)
|
||||||
|
@ -284,11 +103,7 @@ int main(int argc, char *argv[])
|
||||||
};
|
};
|
||||||
ret = masto.post(API::v1::statuses, parameters, answer);
|
ret = masto.post(API::v1::statuses, parameters, answer);
|
||||||
|
|
||||||
if (ret == 0)
|
if (ret != 0)
|
||||||
{
|
|
||||||
pt::write_json(filepath + "config-" + profile + ".json", config);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
std::cerr << "Error code: " << ret << '\n';
|
std::cerr << "Error code: " << ret << '\n';
|
||||||
std::cerr << answer << '\n';
|
std::cerr << answer << '\n';
|
||||||
|
@ -298,5 +113,7 @@ int main(int argc, char *argv[])
|
||||||
std::this_thread::sleep_for(std::chrono::seconds(2));
|
std::this_thread::sleep_for(std::chrono::seconds(2));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pt::write_json(filepath + "config-" + profile + ".json", config);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,9 @@
|
||||||
namespace pt = boost::property_tree;
|
namespace pt = boost::property_tree;
|
||||||
using std::string;
|
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::uint16_t read_config(pt::ptree &config, const string &profile, string &instance, string &access_token, string &feedurl);
|
||||||
std::vector<string> parse_website(const string &profile, const string &xml);
|
std::vector<string> parse_website(const string &profile, const string &xml);
|
||||||
|
|
||||||
|
|
128
src/parse.cpp
Normal file
128
src/parse.cpp
Normal file
|
@ -0,0 +1,128 @@
|
||||||
|
/* This file is part of mastorss.
|
||||||
|
* Copyright © 2018 tastytea <tastytea@tastytea.de>
|
||||||
|
*
|
||||||
|
* 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <regex>
|
||||||
|
#include <sstream>
|
||||||
|
#include <boost/property_tree/ptree.hpp>
|
||||||
|
#include <boost/property_tree/json_parser.hpp>
|
||||||
|
#include <boost/property_tree/xml_parser.hpp>
|
||||||
|
#include <boost/filesystem.hpp>
|
||||||
|
#include <mastodon-cpp.hpp>
|
||||||
|
#include "mastorss.hpp"
|
||||||
|
|
||||||
|
namespace pt = boost::property_tree;
|
||||||
|
|
||||||
|
using std::cerr;
|
||||||
|
using std::string;
|
||||||
|
|
||||||
|
std::vector<string> parse_website(const string &profile, const string &xml)
|
||||||
|
{
|
||||||
|
pt::ptree json;
|
||||||
|
std::vector<string> 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<string> 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("</p><p>");
|
||||||
|
std::regex recdata1("<!\\[CDATA\\[");
|
||||||
|
std::regex recdata2("\\]\\]>");
|
||||||
|
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;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user