summaryrefslogtreecommitdiff
path: root/src/RSS.cpp
blob: 93c391d024cb05ed38cfe7277b223c80ce6b8b14 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
#include "RSS.hpp"
#include <iomanip>
#include <iostream>
#include <ctime>
#include <memory>
#include <stdexcept>
#include <curl/curl.h>

#define DT_TEMPLATES_LEN 2

const char *DT_TEMPLATES[] = {
    "%Y-%m-%dT%H:%M:%SZ",
    "%a, %d %b %Y %H:%M:%S GMT",
};

std::tm ParseTime(std::string original) {
    std::tm parsed{};
    for (auto i = 0; i < DT_TEMPLATES_LEN; i++) {
        strptime(original.c_str(), DT_TEMPLATES[i], &parsed);
        if (parsed.tm_year != 0) break;
    }
    return parsed;
}

RSS::RSS(std::string url) {
    URL = url;
    channelInfo = ChannelInfo(url);
    parse(request());
}

size_t WriteCallback(void* contents, size_t size, size_t nmemb, std::string* userp) {
    size_t totalSize = size * nmemb;
    userp->append((char*)contents, totalSize);
    return totalSize;
}

std::string RSS::request() {
    std::unique_ptr<CURL, decltype(&curl_easy_cleanup)> curl (
            curl_easy_init(), &curl_easy_cleanup);

    if (!curl) {
        throw std::runtime_error("Failed to initialize CURL");
    }

    std::string data;

    curl_easy_setopt(curl.get(), CURLOPT_URL, URL.c_str());
    curl_easy_setopt(curl.get(), CURLOPT_WRITEFUNCTION, WriteCallback);
    curl_easy_setopt(curl.get(), CURLOPT_WRITEDATA, &data);
    curl_easy_setopt(curl.get(), CURLOPT_FOLLOWLOCATION, 1L);

    auto res = curl_easy_perform(curl.get());
    if (res != CURLE_OK) {
        throw std::runtime_error(std::string("CURL request failed: ") + curl_easy_strerror(res));
    }

    curl_easy_cleanup(curl.get());
    return data;
}

void RSS::parse(std::string contents) {
    // std::cout << "starting tokenization..." << std::endl;
    try {
        auto leaf = XML_leaf(contents).GetChild("channel");
        if (leaf.Raw == "<>") 
            throw new std::runtime_error("The feed does not contain <channel> element");

        this->channelInfo.Title = leaf.GetChild("title").Value;

        auto items = leaf.GetChildren("item");
        for (auto item : items)
            Entries.push_back(RSS_Entry(item));
    } catch (const char *err) {
        std::cout << "Failed to parse feed '" << URL << "' :" << err << std::endl;
        return;
    }
}

void RSS::print() {
    channelInfo.print();
    int i = 0;
    for (auto entry : Entries) {
        if (i > 3) break;
        entry.print();
        ++i;
    }
}

void RSS::print_latest(int minutesSpan) {
    std::time_t now = std::time(nullptr);
    std::tm* gmt_time = std::gmtime(&now);
    gmt_time->tm_min -= minutesSpan;
    auto ticks = std::mktime(gmt_time);

    for (auto entry : Entries) {
        if (entry.pubDate.tm_year == 0 || std::mktime(&entry.pubDate) < ticks) break;
        entry.print();
    }
}

RSS_Entry::RSS_Entry(XML_leaf node) {
    Title = node.GetChild("title").GetValue();
    URL = node.GetChild("link").GetValue();
    Contents = node.GetChild("description").GetValue();

    auto pubDateField = node.GetChild("pubDate");
    if (pubDateField.Value.length() == 0) return;
    pubDate = ParseTime(pubDateField.Value);
}

void RSS_Entry::print() {
    std::cout 
        << Title << '\n'
        << std::put_time(&pubDate, "%Y-%m-%d %H:%M:%S") << '\n'
        << Contents << '\n'
        << URL<< '\n'
        << "==============================\n";
}