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";
}
|