2019-05-01 00:41:13 +02:00
|
|
|
//! Streaming server for Mastodon
|
|
|
|
//!
|
|
|
|
//!
|
|
|
|
//! This server provides live, streaming updates for Mastodon clients. Specifically, when a server
|
|
|
|
//! is running this sever, Mastodon clients can use either Server Sent Events or WebSockets to
|
|
|
|
//! connect to the server with the API described [in the public API
|
|
|
|
//! documentation](https://docs.joinmastodon.org/api/streaming/)
|
|
|
|
//!
|
|
|
|
//! # Notes on data flow
|
|
|
|
//! * **Client Request → Warp**:
|
|
|
|
//! Warp filters for valid requests and parses request data. Based on that data, it repeatedly polls
|
|
|
|
//! the StreamManager
|
|
|
|
//!
|
|
|
|
//! * **Warp → StreamManager**:
|
|
|
|
//! The StreamManager consults a hash table to see if there is a currently open PubSub channel. If
|
|
|
|
//! there is, it uses that channel; if not, it (synchronously) sends a subscribe command to Redis.
|
|
|
|
//! The StreamManager polls the Receiver, providing info about which StreamManager it is that is
|
|
|
|
//! doing the polling. The stream manager is also responsible for monitoring the hash table to see
|
|
|
|
//! if it should unsubscribe from any channels and, if necessary, sending the unsubscribe command.
|
|
|
|
//!
|
|
|
|
//! * **StreamManger → Receiver**:
|
|
|
|
//! The Receiver receives data from Redis and stores it in a series of queues (one for each
|
|
|
|
//! StreamManager). When (asynchronously) polled by the StreamManager, it sends back the messages
|
|
|
|
//! relevant to that StreamManager and removes them from the queue.
|
|
|
|
|
|
|
|
pub mod error;
|
|
|
|
pub mod query;
|
|
|
|
pub mod receiver;
|
|
|
|
pub mod stream;
|
|
|
|
pub mod timeline;
|
|
|
|
pub mod user;
|
2019-04-15 20:22:44 +02:00
|
|
|
use futures::stream::Stream;
|
2019-04-30 15:44:51 +02:00
|
|
|
use receiver::Receiver;
|
2019-04-28 23:28:57 +02:00
|
|
|
use stream::StreamManager;
|
2019-05-01 00:41:13 +02:00
|
|
|
use user::{Filter, User};
|
|
|
|
use warp::Filter as WarpFilter;
|
2019-02-19 20:29:32 +01:00
|
|
|
|
2019-04-15 20:22:44 +02:00
|
|
|
fn main() {
|
2019-04-18 16:10:01 +02:00
|
|
|
pretty_env_logger::init();
|
2019-02-11 18:58:51 +01:00
|
|
|
|
2019-04-30 15:44:51 +02:00
|
|
|
let redis_updates = StreamManager::new(Receiver::new());
|
2019-05-01 00:41:13 +02:00
|
|
|
|
|
|
|
let routes = any_of!(
|
|
|
|
// GET /api/v1/streaming/user/notification [private; notification filter]
|
|
|
|
timeline::user_notifications(),
|
|
|
|
// GET /api/v1/streaming/user [private; language filter]
|
|
|
|
timeline::user(),
|
|
|
|
// GET /api/v1/streaming/public/local?only_media=true [public; language filter]
|
|
|
|
timeline::public_local_media(),
|
|
|
|
// GET /api/v1/streaming/public?only_media=true [public; language filter]
|
|
|
|
timeline::public_media(),
|
|
|
|
// GET /api/v1/streaming/public/local [public; language filter]
|
|
|
|
timeline::public_local(),
|
|
|
|
// GET /api/v1/streaming/public [public; language filter]
|
|
|
|
timeline::public(),
|
|
|
|
// GET /api/v1/streaming/direct [private; *no* filter]
|
|
|
|
timeline::direct(),
|
|
|
|
// GET /api/v1/streaming/hashtag?tag=:hashtag [public; no filter]
|
|
|
|
timeline::hashtag(),
|
|
|
|
// GET /api/v1/streaming/hashtag/local?tag=:hashtag [public; no filter]
|
|
|
|
timeline::hashtag_local(),
|
|
|
|
// GET /api/v1/streaming/list?list=:list_id [private; no filter]
|
|
|
|
timeline::list()
|
2019-04-19 18:16:03 +02:00
|
|
|
)
|
2019-04-27 02:00:11 +02:00
|
|
|
.untuple_one()
|
2019-04-19 18:16:03 +02:00
|
|
|
.and(warp::sse())
|
2019-04-28 23:28:57 +02:00
|
|
|
.and(warp::any().map(move || redis_updates.new_copy()))
|
2019-04-27 02:00:11 +02:00
|
|
|
.map(
|
2019-04-28 23:28:57 +02:00
|
|
|
|timeline: String, user: User, sse: warp::sse::Sse, mut event_stream: StreamManager| {
|
|
|
|
event_stream.add(&timeline, &user);
|
2019-04-27 02:00:11 +02:00
|
|
|
sse.reply(warp::sse::keep(
|
|
|
|
event_stream.filter_map(move |item| {
|
2019-04-28 23:28:57 +02:00
|
|
|
let payload = item["payload"].clone();
|
|
|
|
let event = item["event"].clone().to_string();
|
|
|
|
let toot_lang = payload["language"].as_str().expect("redis str").to_string();
|
|
|
|
let user_langs = user.langs.clone();
|
|
|
|
|
|
|
|
match (&user.filter, user_langs) {
|
|
|
|
(Filter::Notification, _) if event != "notification" => None,
|
|
|
|
(Filter::Language, Some(ref langs)) if !langs.contains(&toot_lang) => None,
|
|
|
|
_ => Some((warp::sse::event(event), warp::sse::data(payload))),
|
|
|
|
}
|
2019-04-27 02:00:11 +02:00
|
|
|
}),
|
|
|
|
None,
|
|
|
|
))
|
|
|
|
},
|
|
|
|
)
|
2019-04-21 15:31:16 +02:00
|
|
|
.with(warp::reply::with::header("Connection", "keep-alive"))
|
2019-04-19 18:16:03 +02:00
|
|
|
.recover(error::handle_errors);
|
2019-02-15 10:22:35 +01:00
|
|
|
|
2019-04-15 20:22:44 +02:00
|
|
|
warp::serve(routes).run(([127, 0, 0, 1], 3030));
|
2019-02-11 09:45:14 +01:00
|
|
|
}
|