293 lines
11 KiB
Rust
293 lines
11 KiB
Rust
|
mod structs;
|
||
|
|
||
|
use pulldown_cmark::{Options, Parser};
|
||
|
use quick_xml::events::{BytesEnd, BytesStart, BytesText, Event};
|
||
|
use quick_xml::Writer;
|
||
|
use reqwest::{
|
||
|
header::{HeaderMap, HeaderValue},
|
||
|
ClientBuilder,
|
||
|
};
|
||
|
use std::env;
|
||
|
use std::io::Cursor;
|
||
|
use std::iter::Skip;
|
||
|
extern crate serde_json;
|
||
|
|
||
|
pub async fn run(mut args: Skip<env::Args>) {
|
||
|
let repo = args.next().expect("Missing repo");
|
||
|
let issue_number = args.next().expect("Missing issue number");
|
||
|
let mut header_map = HeaderMap::new();
|
||
|
header_map.insert(
|
||
|
"accept",
|
||
|
HeaderValue::from_static("application/vnd.github.v3+json"),
|
||
|
);
|
||
|
let client = ClientBuilder::new()
|
||
|
.default_headers(header_map)
|
||
|
.user_agent(&format!(
|
||
|
"{}/{}",
|
||
|
env!("CARGO_PKG_NAME"),
|
||
|
env!("CARGO_PKG_VERSION")
|
||
|
))
|
||
|
.build()
|
||
|
.unwrap();
|
||
|
let text = client
|
||
|
.get(&format!(
|
||
|
"https://api.github.com/repos/{}/issues/{}",
|
||
|
repo, issue_number
|
||
|
))
|
||
|
.send()
|
||
|
.await
|
||
|
.unwrap()
|
||
|
.text()
|
||
|
.await
|
||
|
.unwrap();
|
||
|
let issue: structs::Issue = serde_json::from_str(&text).unwrap();
|
||
|
let text = client
|
||
|
.get(&issue.events_url)
|
||
|
.send()
|
||
|
.await
|
||
|
.unwrap()
|
||
|
.text()
|
||
|
.await
|
||
|
.unwrap();
|
||
|
let mut events: Vec<structs::EventType> = serde_json::from_str(&text).unwrap();
|
||
|
let text = client
|
||
|
.get(&issue.comments_url)
|
||
|
.send()
|
||
|
.await
|
||
|
.unwrap()
|
||
|
.text()
|
||
|
.await
|
||
|
.unwrap();
|
||
|
let comments: Vec<structs::EventType> = serde_json::from_str(&text).unwrap();
|
||
|
events.extend(comments);
|
||
|
events.sort_by(|i, j| {
|
||
|
let i = match i {
|
||
|
structs::EventType::Comment(comment) => comment.created_at,
|
||
|
structs::EventType::Event(event) => event.created_at,
|
||
|
};
|
||
|
let j = match j {
|
||
|
structs::EventType::Comment(comment) => comment.created_at,
|
||
|
structs::EventType::Event(event) => event.created_at,
|
||
|
};
|
||
|
i.partial_cmp(&j).unwrap()
|
||
|
});
|
||
|
|
||
|
let mut writer = Writer::new(Cursor::new(Vec::new()));
|
||
|
{
|
||
|
let mut elem = BytesStart::owned(b"rss".to_vec(), 3);
|
||
|
elem.push_attribute(("version", "2.0"));
|
||
|
writer.write_event(Event::Start(elem)).unwrap();
|
||
|
|
||
|
let elem = BytesStart::owned(b"channel".to_vec(), 7);
|
||
|
writer.write_event(Event::Start(elem)).unwrap();
|
||
|
|
||
|
let elem = BytesStart::owned(b"title".to_vec(), 5);
|
||
|
writer.write_event(Event::Start(elem)).unwrap();
|
||
|
|
||
|
let elem = BytesText::from_plain_str(&issue.title).into_owned();
|
||
|
writer.write_event(Event::Text(elem)).unwrap();
|
||
|
|
||
|
let elem = BytesEnd::owned(b"title".to_vec());
|
||
|
writer.write_event(Event::End(elem)).unwrap();
|
||
|
|
||
|
let elem = BytesStart::owned(b"link".to_vec(), 4);
|
||
|
writer.write_event(Event::Start(elem)).unwrap();
|
||
|
|
||
|
let elem = BytesText::from_plain_str(&issue.html_url).into_owned();
|
||
|
writer.write_event(Event::Text(elem)).unwrap();
|
||
|
|
||
|
let elem = BytesEnd::owned(b"link".to_vec());
|
||
|
writer.write_event(Event::End(elem)).unwrap();
|
||
|
|
||
|
let elem = BytesStart::owned(b"description".to_vec(), 11);
|
||
|
writer.write_event(Event::Start(elem)).unwrap();
|
||
|
|
||
|
let elem = BytesText::from_plain_str(&format!("Comments and events from {}", &issue.title))
|
||
|
.into_owned();
|
||
|
writer.write_event(Event::Text(elem)).unwrap();
|
||
|
|
||
|
let elem = BytesEnd::owned(b"description".to_vec());
|
||
|
writer.write_event(Event::End(elem)).unwrap();
|
||
|
|
||
|
let elem = BytesStart::owned(b"item".to_vec(), 4);
|
||
|
writer.write_event(Event::Start(elem)).unwrap();
|
||
|
|
||
|
let elem = BytesStart::owned(b"title".to_vec(), 5);
|
||
|
writer.write_event(Event::Start(elem)).unwrap();
|
||
|
|
||
|
let elem = BytesText::from_plain_str(&issue.title).into_owned();
|
||
|
writer.write_event(Event::Text(elem)).unwrap();
|
||
|
|
||
|
let elem = BytesEnd::owned(b"title".to_vec());
|
||
|
writer.write_event(Event::End(elem)).unwrap();
|
||
|
|
||
|
let elem = BytesStart::owned(b"link".to_vec(), 4);
|
||
|
writer.write_event(Event::Start(elem)).unwrap();
|
||
|
|
||
|
let elem = BytesText::from_plain_str(&issue.html_url).into_owned();
|
||
|
writer.write_event(Event::Text(elem)).unwrap();
|
||
|
|
||
|
let elem = BytesEnd::owned(b"link".to_vec());
|
||
|
writer.write_event(Event::End(elem)).unwrap();
|
||
|
|
||
|
let mut elem = BytesStart::owned(b"guid".to_vec(), 4);
|
||
|
elem.push_attribute(("isPermaLink", "false"));
|
||
|
writer.write_event(Event::Start(elem)).unwrap();
|
||
|
|
||
|
let elem = BytesText::from_plain_str(&issue.node_id).into_owned();
|
||
|
writer.write_event(Event::Text(elem)).unwrap();
|
||
|
|
||
|
let elem = BytesEnd::owned(b"guid".to_vec());
|
||
|
writer.write_event(Event::End(elem)).unwrap();
|
||
|
|
||
|
let elem = BytesStart::owned(b"author".to_vec(), 6);
|
||
|
writer.write_event(Event::Start(elem)).unwrap();
|
||
|
|
||
|
let elem = BytesText::from_plain_str(&issue.user.login).into_owned();
|
||
|
writer.write_event(Event::Text(elem)).unwrap();
|
||
|
|
||
|
let elem = BytesEnd::owned(b"author".to_vec());
|
||
|
writer.write_event(Event::End(elem)).unwrap();
|
||
|
|
||
|
let elem = BytesStart::owned(b"pubDate".to_vec(), 7);
|
||
|
writer.write_event(Event::Start(elem)).unwrap();
|
||
|
|
||
|
let elem = BytesText::from_plain_str(&issue.created_at.to_rfc2822()).into_owned();
|
||
|
writer.write_event(Event::Text(elem)).unwrap();
|
||
|
|
||
|
let elem = BytesEnd::owned(b"pubDate".to_vec());
|
||
|
writer.write_event(Event::End(elem)).unwrap();
|
||
|
|
||
|
if !issue.body.is_empty() {
|
||
|
let elem = BytesStart::owned(b"description".to_vec(), 11);
|
||
|
writer.write_event(Event::Start(elem)).unwrap();
|
||
|
|
||
|
let parser = Parser::new_ext(&issue.body, Options::all());
|
||
|
let mut html_buf = String::with_capacity(issue.body.len());
|
||
|
pulldown_cmark::html::push_html(&mut html_buf, parser);
|
||
|
let elem = BytesText::from_plain_str(html_buf.trim()).into_owned();
|
||
|
writer.write_event(Event::Text(elem)).unwrap();
|
||
|
|
||
|
let elem = BytesEnd::owned(b"description".to_vec());
|
||
|
writer.write_event(Event::End(elem)).unwrap();
|
||
|
}
|
||
|
|
||
|
let elem = BytesEnd::owned(b"item".to_vec());
|
||
|
writer.write_event(Event::End(elem)).unwrap();
|
||
|
}
|
||
|
|
||
|
for event in events {
|
||
|
let (title, link, description, author, guid, pub_date) = match event {
|
||
|
structs::EventType::Comment(comment) => {
|
||
|
let parser = Parser::new_ext(&comment.body, Options::all());
|
||
|
let mut html_buf = String::with_capacity(comment.body.len());
|
||
|
pulldown_cmark::html::push_html(&mut html_buf, parser);
|
||
|
let mut title = comment.body.splitn(2, '\n').next().unwrap().to_string();
|
||
|
if title.len() > 80 {
|
||
|
title = format!("{}...", title.split_at(80).0);
|
||
|
}
|
||
|
(
|
||
|
title,
|
||
|
Some(comment.html_url),
|
||
|
html_buf.trim().to_string(),
|
||
|
comment.user.login,
|
||
|
comment.node_id,
|
||
|
comment.created_at.to_rfc2822(),
|
||
|
)
|
||
|
}
|
||
|
structs::EventType::Event(event) => {
|
||
|
let mut text = "Extra fields:\n<pre><code>".to_string();
|
||
|
pulldown_cmark::escape::escape_html(
|
||
|
&mut text,
|
||
|
&serde_json::to_string_pretty(&event.extra)
|
||
|
.unwrap_or_else(|_| format!("{:?}", &event.extra)),
|
||
|
)
|
||
|
.unwrap();
|
||
|
text.push_str("</code></pre>");
|
||
|
(
|
||
|
format!("New event: {}", &event.event),
|
||
|
None,
|
||
|
text,
|
||
|
event.actor.login,
|
||
|
event.node_id,
|
||
|
event.created_at.to_rfc2822(),
|
||
|
)
|
||
|
}
|
||
|
};
|
||
|
|
||
|
let elem = BytesStart::owned(b"item".to_vec(), 4);
|
||
|
writer.write_event(Event::Start(elem)).unwrap();
|
||
|
|
||
|
let elem = BytesStart::owned(b"title".to_vec(), 5);
|
||
|
writer.write_event(Event::Start(elem)).unwrap();
|
||
|
|
||
|
let elem = BytesText::from_plain_str(&title).into_owned();
|
||
|
writer.write_event(Event::Text(elem)).unwrap();
|
||
|
|
||
|
let elem = BytesEnd::owned(b"title".to_vec());
|
||
|
writer.write_event(Event::End(elem)).unwrap();
|
||
|
|
||
|
if let Some(link) = link {
|
||
|
let elem = BytesStart::owned(b"link".to_vec(), 4);
|
||
|
writer.write_event(Event::Start(elem)).unwrap();
|
||
|
|
||
|
let elem = BytesText::from_plain_str(&link).into_owned();
|
||
|
writer.write_event(Event::Text(elem)).unwrap();
|
||
|
|
||
|
let elem = BytesEnd::owned(b"link".to_vec());
|
||
|
writer.write_event(Event::End(elem)).unwrap();
|
||
|
}
|
||
|
|
||
|
let mut elem = BytesStart::owned(b"guid".to_vec(), 4);
|
||
|
elem.push_attribute(("isPermaLink", "false"));
|
||
|
writer.write_event(Event::Start(elem)).unwrap();
|
||
|
|
||
|
let elem = BytesText::from_plain_str(&guid).into_owned();
|
||
|
writer.write_event(Event::Text(elem)).unwrap();
|
||
|
|
||
|
let elem = BytesEnd::owned(b"guid".to_vec());
|
||
|
writer.write_event(Event::End(elem)).unwrap();
|
||
|
|
||
|
let elem = BytesStart::owned(b"author".to_vec(), 6);
|
||
|
writer.write_event(Event::Start(elem)).unwrap();
|
||
|
|
||
|
let elem = BytesText::from_plain_str(&author).into_owned();
|
||
|
writer.write_event(Event::Text(elem)).unwrap();
|
||
|
|
||
|
let elem = BytesEnd::owned(b"author".to_vec());
|
||
|
writer.write_event(Event::End(elem)).unwrap();
|
||
|
|
||
|
let elem = BytesStart::owned(b"pubDate".to_vec(), 7);
|
||
|
writer.write_event(Event::Start(elem)).unwrap();
|
||
|
|
||
|
let elem = BytesText::from_plain_str(&pub_date).into_owned();
|
||
|
writer.write_event(Event::Text(elem)).unwrap();
|
||
|
|
||
|
let elem = BytesEnd::owned(b"pubDate".to_vec());
|
||
|
writer.write_event(Event::End(elem)).unwrap();
|
||
|
|
||
|
let elem = BytesStart::owned(b"description".to_vec(), 11);
|
||
|
writer.write_event(Event::Start(elem)).unwrap();
|
||
|
|
||
|
let elem = BytesText::from_plain_str(&description).into_owned();
|
||
|
writer.write_event(Event::Text(elem)).unwrap();
|
||
|
|
||
|
let elem = BytesEnd::owned(b"description".to_vec());
|
||
|
writer.write_event(Event::End(elem)).unwrap();
|
||
|
|
||
|
let elem = BytesEnd::owned(b"item".to_vec());
|
||
|
writer.write_event(Event::End(elem)).unwrap();
|
||
|
}
|
||
|
|
||
|
let elem = BytesEnd::owned(b"channel".to_vec());
|
||
|
writer.write_event(Event::End(elem)).unwrap();
|
||
|
|
||
|
let elem = BytesEnd::owned(b"rss".to_vec());
|
||
|
writer.write_event(Event::End(elem)).unwrap();
|
||
|
|
||
|
println!(
|
||
|
"{}",
|
||
|
String::from_utf8(writer.into_inner().into_inner()).unwrap()
|
||
|
);
|
||
|
}
|