diff --git a/CMakeLists.txt b/CMakeLists.txt index 8a80741..ee718c5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,3 +33,7 @@ target_link_libraries(statusip add_executable(statustemp "statustemp.cpp" "helpers.cpp") target_link_libraries(statustemp PRIVATE fmt::fmt Boost::program_options) + +add_executable(pushmsg "pushmsg.cpp" "helpers.cpp") +target_link_libraries(pushmsg + PRIVATE Boost::program_options restclient-cpp nlohmann_json::nlohmann_json) diff --git a/pushmsg.cpp b/pushmsg.cpp new file mode 100644 index 0000000..8dcdbb2 --- /dev/null +++ b/pushmsg.cpp @@ -0,0 +1,137 @@ +// Sends a push message to a Gotify server + +#include "helpers.hpp" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +struct message +{ + std::string base_url; + std::string token; + std::string title; + std::string body; + std::int16_t priority{5}; +} __attribute__((aligned(128))) message; + +void read_options(int argc, char *argv[]) +{ + namespace po = boost::program_options; + + po::options_description options("Options"); + // clang-format off + options.add_options() + ("help,h", "Display this help and exit.") + ("baseurl", po::value()->required(), + "Example: https://push.example.org") + ("token", po::value()->required(), "Application token") + ("title", po::value()->required(), "Title of the message") + ("message", po::value(), "Body of the message") + ("priority", po::value(), + "Priority of the message. Default is 5. " + "0 = no notification, 1-3 = silent, 4-7 = sound, 8-10 = popup."); + // clang-format on + + po::variables_map vm; + po::store(po::command_line_parser(argc, argv).options(options).run(), vm); + + std::ifstream configfile(helpers::get_config_file_path("pushmsg.cfg")); + po::store(po::parse_config_file(configfile, options, true), vm); + configfile.close(); + + if (vm.count("help") != 0) + { + std::cout << options; + std::exit(0); // NOLINT(concurrency-mt-unsafe) + } + po::notify(vm); + + message.base_url = vm["baseurl"].as(); + message.token = vm["token"].as(); + message.title = vm["title"].as(); + if (vm.count("message") != 0) + { + message.body = vm["message"].as(); + } + if (vm.count("priority") != 0) + { + message.priority = vm["priority"].as(); + } +} + +int main(int argc, char *argv[]) +{ + using std::cerr; + + try + { + read_options(argc, argv); + + RestClient::init(); + RestClient::Connection conn(message.base_url); + conn.FollowRedirects(true, 5); + conn.SetTimeout(30); + conn.SetHeaders({{"Content-Type", "application/json; charset=utf-8"}}); + + const nlohmann::json json{{"title", message.title}, + {"message", message.body}, + {"priority", message.priority}}; + std::cout << json.dump() << '\n'; + auto response{ + conn.post("/message?token=" + message.token, json.dump())}; + switch (response.code) + { + case 200: + { + break; + } + case 400: + { + cerr << "HTTP status 400: Bad Request.\n"; + break; + } + case 401: + { + cerr << "HTTP status 401: Unauthorized (invalid token).\n"; + break; + } + case 403: + { + cerr << "HTTP status 403: Forbidden.\n"; + break; + } + case 404: + { + cerr << "HTTP status 404: Not Found.\n"; + break; + } + case 502: + { + cerr << "HTTP status 502: Bad Gateway (server down).\n"; + break; + } + default: + { + cerr << "HTTP status " << response.code << ".\n"; + break; + } + } + + RestClient::disable(); + } + catch (const std::exception &e) + { + cerr << e.what() << '\n'; + return 1; + } +}