Public,
Hashtag(String),
User(String, Boosts, Replies),
+ Mentions,
}
#[derive(Debug, PartialEq, Eq, Clone)]
.param("exclude_reblogs", *boosts == Boosts::Hide)
.param("exclude_replies", *replies == Replies::Hide)
},
+ FeedId::Mentions => {
+ Req::get("notifications").param("types[]", "mention")
+ },
};
let req = match ext {
let (url, req) = self.api_request(req)?;
let body = req.send()?.text()?;
- let sts: Vec<Status> = match serde_json::from_str(&body) {
- Ok(sts) => Ok(sts),
- Err(e) => {
- dbg!(&body);
- Err(ClientError::UrlError(url.clone(), e.to_string()))
+
+ // Decode the JSON response as a different kind of type
+ // depending on the feed. But in all cases we expect to end up
+ // with a list of ids.
+ let ids: VecDeque<String> = match id {
+ FeedId::Home | FeedId::Local | FeedId::Public |
+ FeedId::Hashtag(..) | FeedId::User(..) => {
+ let sts: Vec<Status> = match serde_json::from_str(&body) {
+ Ok(sts) => Ok(sts),
+ Err(e) => {
+ dbg!(&body);
+ Err(ClientError::UrlError(url.clone(), e.to_string()))
+ },
+ }?;
+ for st in &sts {
+ self.cache_status(st);
+ }
+ sts.iter().rev().map(|st| st.id.clone()).collect()
},
- }?;
- let any_new = !sts.is_empty();
- for st in &sts {
- self.cache_status(st);
- }
- let ids = sts.iter().rev().map(|st| st.id.clone()).collect();
+ FeedId::Mentions => {
+ let mut nots: Vec<Notification> = match serde_json::from_str(
+ &body) {
+ Ok(nots) => Ok(nots),
+ Err(e) => {
+ dbg!(&body);
+ Err(ClientError::UrlError(url.clone(), e.to_string()))
+ },
+ }?;
+
+ // According to the protocol spec, all notifications
+ // of type Mention should have a status in them. We
+ // double-check that here, so that the rest of our
+ // code can safely .unwrap() or .expect() the status
+ // from notifications they get via this feed.
+ nots.retain(|not| {
+ not.ntype == NotificationType::Mention &&
+ not.status.is_some()
+ });
+ for not in ¬s {
+ self.cache_notification(not);
+ }
+ nots.iter().rev().map(|not| not.id.clone()).collect()
+ },
+ };
+ let any_new = !ids.is_empty();
+
match ext {
FeedExtend::Initial => {
self.feeds.insert(id.clone(), Feed {
match (up.id, up.response) {
(StreamId::User, StreamResponse::Line(_)) => {
- self.fetch_feed(&FeedId::Home, FeedExtend::Future)?;
- updates.insert(FeedId::Home);
+ // This stream interleaves updates to the home
+ // timeline with notifications, so it can cause an
+ // update in more than one thing we think of as a feed.
+ for id in &[FeedId::Home, FeedId::Mentions] {
+ if self.fetch_feed(id, FeedExtend::Future)? {
+ updates.insert(id.clone());
+ }
+ }
},
// FIXME: we probably _should_ handle EOF from the
}
}
+struct NotificationStatusFeedType {}
+impl FeedType for NotificationStatusFeedType {
+ type Item = StatusDisplay;
+
+ fn get_from_client(id: &str, client: &mut Client) ->
+ Result<Self::Item, ClientError>
+ {
+ let not = client.notification_by_id(&id)?;
+ let st = ¬.status.expect(
+ "expected all notifications in this feed would have statuses");
+ Ok(StatusDisplay::new(st.clone(), client))
+ }
+}
+
struct FeedFileContents<Type: FeedType> {
id: FeedId,
header: FileHeader,
"HHHHHHHHHHHHHHHHHHHKH"))?;
Ok(Box::new(file))
}
+
+pub fn mentions(client: &mut Client) ->
+ Result<Box<dyn ActivityState>, ClientError>
+{
+ let file = FeedFile::<NotificationStatusFeedType>::new(
+ client, FeedId::Mentions, ColouredString::general(
+ "Mentions [ESC][R]",
+ "HHHHHHHHHHHHKKKHHKH"))?;
+ Ok(Box::new(file))
+}