cppscripts/statusip.cpp

149 lines
3.9 KiB
C++

// Print your IP(s) for i3 status bar.
/* i3blocks config:
* [ip]
* command=statusip --url=<URL that returns IP as plain text>
* interval=persist
* markup=pango
*/
#include "helpers.hpp"
#include <arpa/inet.h>
#include <boost/program_options/options_description.hpp>
#include <boost/program_options/parsers.hpp>
#include <boost/program_options/variables_map.hpp>
#include <fmt/core.h>
#include <netdb.h>
#include <restclient-cpp/connection.h>
#include <restclient-cpp/restclient.h>
#include <exception>
#include <fstream>
#include <iostream>
#include <string>
#include <string_view>
#include <thread>
std::string get_url(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.")
("url", po::value<std::string>()->required()->value_name("URL"),
"URL that returns your IP address as plain text.");
// 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("statusip.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);
return vm["url"].as<std::string>();
}
std::string format_responses(const std::string_view ipv6,
const std::string_view ipv4)
{
using fmt::format;
std::string out;
if (size_t pos{0}; (pos = ipv6.find(':')) != std::string::npos)
{
for (int n = 0; n < 4; ++n)
{
pos = ipv6.find(':', pos);
++pos;
}
out += format(R"({:s})"
R"(<span color="#aaddaa">{:s}</span>)",
ipv6.substr(0, pos), ipv6.substr(pos));
}
if (ipv4.find('.') != std::string::npos)
{
(out += " ") += ipv4;
}
return out;
}
std::string get_host(const std::string_view url)
{
const size_t pos{url.find("//") + 2};
return std::string(url.substr(pos, url.find('/', pos) - pos));
}
// Ughf. 🙈 <https://github.com/mrtazz/restclient-cpp/issues/164>
std::string get_ipv4(const std::string_view url)
{
// NOLINTNEXTLINE(concurrency-mt-unsafe)
auto *hostent{gethostbyname(get_host(url).data())};
// NOLINTNEXTLINE
auto *addr = reinterpret_cast<struct in_addr *>(hostent->h_addr_list[0]);
return inet_ntoa(addr[0]); // NOLINT
}
int main(int argc, char *argv[])
{
using namespace std::chrono_literals;
try
{
const auto url{get_url(argc, argv)};
std::string ipv6;
std::string ipv4;
while (true)
{
RestClient::init();
RestClient::Connection conn("");
// We can't force IPv4 or IPv6?
// <https://github.com/mrtazz/restclient-cpp/issues/164>
conn.FollowRedirects(true, 5);
conn.SetTimeout(120);
auto response{conn.get(url)};
if (response.code == 200)
{
const auto &body{response.body};
ipv6 = body.substr(0, body.find_first_of("\r\n"));
}
conn.SetHeaders({{"Host", get_host(url)}});
response = conn.get(get_ipv4(url));
if (response.code == 200)
{
const auto &body{response.body};
ipv4 = body.substr(0, body.find_first_of("\r\n"));
}
RestClient::disable();
std::cout << format_responses(ipv6, ipv4) << std::endl;
std::this_thread::sleep_for(10min);
}
}
catch (const std::exception &e)
{
std::cout << R"(<span color="red">)" << e.what() << "</span>"
<< std::endl;
return 1;
}
}
// Local Variables:
// eval: (rainbow-mode 1)
// End: