2019-04-19 01:02:29 +02:00
|
|
|
mod error;
|
2019-04-15 20:22:44 +02:00
|
|
|
mod pubsub;
|
|
|
|
mod query;
|
2019-04-19 23:06:29 +02:00
|
|
|
mod user;
|
2019-04-19 18:16:03 +02:00
|
|
|
mod utils;
|
2019-04-15 20:22:44 +02:00
|
|
|
use futures::stream::Stream;
|
2019-04-27 02:00:11 +02:00
|
|
|
use futures::{Async, Poll};
|
2019-04-23 20:07:49 +02:00
|
|
|
use pubsub::PubSub;
|
2019-04-27 02:00:11 +02:00
|
|
|
use serde_json::Value;
|
|
|
|
use std::io::Error;
|
2019-04-21 15:21:44 +02:00
|
|
|
use user::{Filter, Scope, User};
|
2019-04-19 23:06:29 +02:00
|
|
|
use warp::{path, 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-19 23:06:29 +02:00
|
|
|
// GET /api/v1/streaming/user [private; language filter]
|
2019-04-19 18:16:03 +02:00
|
|
|
let user_timeline = path!("api" / "v1" / "streaming" / "user")
|
2019-04-15 20:22:44 +02:00
|
|
|
.and(path::end())
|
2019-04-19 23:06:29 +02:00
|
|
|
.and(user::get_access_token(Scope::Private))
|
|
|
|
.and_then(|token| user::get_account(token, Scope::Private))
|
2019-04-27 02:00:11 +02:00
|
|
|
.map(|user: User| (user.id.to_string(), user));
|
2019-02-11 09:45:14 +01:00
|
|
|
|
2019-04-19 23:06:29 +02:00
|
|
|
// GET /api/v1/streaming/user/notification [private; notification filter]
|
2019-04-19 18:16:03 +02:00
|
|
|
let user_timeline_notifications = path!("api" / "v1" / "streaming" / "user" / "notification")
|
2019-04-15 20:22:44 +02:00
|
|
|
.and(path::end())
|
2019-04-19 23:06:29 +02:00
|
|
|
.and(user::get_access_token(Scope::Private))
|
|
|
|
.and_then(|token| user::get_account(token, Scope::Private))
|
2019-04-27 02:00:11 +02:00
|
|
|
.map(|user: User| (user.id.to_string(), user.with_notification_filter()));
|
2019-02-11 09:45:14 +01:00
|
|
|
|
2019-04-19 23:06:29 +02:00
|
|
|
// GET /api/v1/streaming/public [public; language filter]
|
2019-04-19 18:16:03 +02:00
|
|
|
let public_timeline = path!("api" / "v1" / "streaming" / "public")
|
|
|
|
.and(path::end())
|
2019-04-19 23:06:29 +02:00
|
|
|
.and(user::get_access_token(user::Scope::Public))
|
|
|
|
.and_then(|token| user::get_account(token, Scope::Public))
|
2019-04-27 02:00:11 +02:00
|
|
|
.map(|user: User| ("public".into(), user.with_language_filter()));
|
2019-02-15 10:22:35 +01:00
|
|
|
|
2019-04-19 23:06:29 +02:00
|
|
|
// GET /api/v1/streaming/public?only_media=true [public; language filter]
|
2019-04-19 18:16:03 +02:00
|
|
|
let public_timeline_media = path!("api" / "v1" / "streaming" / "public")
|
2019-04-15 20:22:44 +02:00
|
|
|
.and(path::end())
|
2019-04-19 23:06:29 +02:00
|
|
|
.and(user::get_access_token(user::Scope::Public))
|
|
|
|
.and_then(|token| user::get_account(token, Scope::Public))
|
2019-04-19 18:16:03 +02:00
|
|
|
.and(warp::query())
|
2019-04-19 23:06:29 +02:00
|
|
|
.map(|user: User, q: query::Media| match q.only_media.as_ref() {
|
2019-04-27 02:00:11 +02:00
|
|
|
"1" | "true" => ("public:media".into(), user.with_language_filter()),
|
|
|
|
_ => ("public".into(), user.with_language_filter()),
|
2019-04-15 20:22:44 +02:00
|
|
|
});
|
2019-02-11 09:45:14 +01:00
|
|
|
|
2019-04-19 23:06:29 +02:00
|
|
|
// GET /api/v1/streaming/public/local [public; language filter]
|
2019-04-19 18:16:03 +02:00
|
|
|
let local_timeline = path!("api" / "v1" / "streaming" / "public" / "local")
|
2019-04-15 20:22:44 +02:00
|
|
|
.and(path::end())
|
2019-04-19 23:06:29 +02:00
|
|
|
.and(user::get_access_token(user::Scope::Public))
|
|
|
|
.and_then(|token| user::get_account(token, Scope::Public))
|
2019-04-27 02:00:11 +02:00
|
|
|
.map(|user: User| ("public:local".into(), user.with_language_filter()));
|
2019-02-11 09:45:14 +01:00
|
|
|
|
2019-04-19 23:06:29 +02:00
|
|
|
// GET /api/v1/streaming/public/local?only_media=true [public; language filter]
|
2019-04-19 18:16:03 +02:00
|
|
|
let local_timeline_media = path!("api" / "v1" / "streaming" / "public" / "local")
|
2019-04-19 23:06:29 +02:00
|
|
|
.and(user::get_access_token(user::Scope::Public))
|
|
|
|
.and_then(|token| user::get_account(token, Scope::Public))
|
2019-04-15 20:22:44 +02:00
|
|
|
.and(warp::query())
|
|
|
|
.and(path::end())
|
2019-04-19 23:06:29 +02:00
|
|
|
.map(|user: User, q: query::Media| match q.only_media.as_ref() {
|
2019-04-27 02:00:11 +02:00
|
|
|
"1" | "true" => ("public:local:media".into(), user.with_language_filter()),
|
|
|
|
_ => ("public:local".into(), user.with_language_filter()),
|
2019-04-15 20:22:44 +02:00
|
|
|
});
|
2019-02-11 18:58:51 +01:00
|
|
|
|
2019-04-19 23:06:29 +02:00
|
|
|
// GET /api/v1/streaming/direct [private; *no* filter]
|
2019-04-19 18:16:03 +02:00
|
|
|
let direct_timeline = path!("api" / "v1" / "streaming" / "direct")
|
2019-04-15 20:22:44 +02:00
|
|
|
.and(path::end())
|
2019-04-19 23:06:29 +02:00
|
|
|
.and(user::get_access_token(Scope::Private))
|
|
|
|
.and_then(|token| user::get_account(token, Scope::Private))
|
2019-04-27 02:00:11 +02:00
|
|
|
.map(|user: User| (format!("direct:{}", user.id), user.with_no_filter()));
|
2019-02-15 10:22:35 +01:00
|
|
|
|
2019-04-19 23:06:29 +02:00
|
|
|
// GET /api/v1/streaming/hashtag?tag=:hashtag [public; no filter]
|
2019-04-19 18:16:03 +02:00
|
|
|
let hashtag_timeline = path!("api" / "v1" / "streaming" / "hashtag")
|
2019-04-15 20:22:44 +02:00
|
|
|
.and(warp::query())
|
|
|
|
.and(path::end())
|
2019-04-27 02:00:11 +02:00
|
|
|
.map(|q: query::Hashtag| {
|
|
|
|
dbg!(&q);
|
|
|
|
(format!("hashtag:{}", q.tag), User::public())
|
|
|
|
});
|
2019-02-15 10:22:35 +01:00
|
|
|
|
2019-04-19 23:06:29 +02:00
|
|
|
// GET /api/v1/streaming/hashtag/local?tag=:hashtag [public; no filter]
|
2019-04-19 18:16:03 +02:00
|
|
|
let hashtag_timeline_local = path!("api" / "v1" / "streaming" / "hashtag" / "local")
|
2019-04-15 20:22:44 +02:00
|
|
|
.and(warp::query())
|
|
|
|
.and(path::end())
|
2019-04-27 02:00:11 +02:00
|
|
|
.map(|q: query::Hashtag| (format!("hashtag:{}:local", q.tag), User::public()));
|
2019-02-15 10:22:35 +01:00
|
|
|
|
2019-04-19 23:06:29 +02:00
|
|
|
// GET /api/v1/streaming/list?list=:list_id [private; no filter]
|
2019-04-19 18:16:03 +02:00
|
|
|
let list_timeline = path!("api" / "v1" / "streaming" / "list")
|
2019-04-19 23:06:29 +02:00
|
|
|
.and(user::get_access_token(Scope::Private))
|
|
|
|
.and_then(|token| user::get_account(token, Scope::Private))
|
2019-04-15 20:22:44 +02:00
|
|
|
.and(warp::query())
|
2019-04-23 20:07:49 +02:00
|
|
|
.and_then(|user: User, q: query::List| (user.is_authorized_for_list(q.list), Ok(user)))
|
2019-04-21 15:21:44 +02:00
|
|
|
.untuple_one()
|
2019-04-15 20:22:44 +02:00
|
|
|
.and(path::end())
|
2019-04-27 02:00:11 +02:00
|
|
|
.map(|list: i64, user: User| (format!("list:{}", list), user.with_no_filter()));
|
|
|
|
let event_stream = RedisStream::new();
|
|
|
|
let event_stream = warp::any().map(move || event_stream.clone());
|
2019-04-19 18:16:03 +02:00
|
|
|
let routes = or!(
|
|
|
|
user_timeline,
|
|
|
|
user_timeline_notifications,
|
|
|
|
public_timeline_media,
|
|
|
|
public_timeline,
|
|
|
|
local_timeline_media,
|
|
|
|
local_timeline,
|
|
|
|
direct_timeline,
|
|
|
|
hashtag_timeline,
|
|
|
|
hashtag_timeline_local,
|
|
|
|
list_timeline
|
|
|
|
)
|
2019-04-27 02:00:11 +02:00
|
|
|
.untuple_one()
|
2019-04-19 18:16:03 +02:00
|
|
|
.and(warp::sse())
|
2019-04-27 02:00:11 +02:00
|
|
|
.and(event_stream)
|
|
|
|
.map(
|
|
|
|
|timeline: String, user: User, sse: warp::sse::Sse, mut event_stream: RedisStream| {
|
|
|
|
event_stream.add(timeline.clone(), user);
|
|
|
|
sse.reply(warp::sse::keep(
|
|
|
|
event_stream.filter_map(move |item| {
|
|
|
|
println!("ding");
|
|
|
|
Some((warp::sse::event("event"), warp::sse::data(item.to_string())))
|
|
|
|
}),
|
|
|
|
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
|
|
|
}
|
2019-04-27 02:00:11 +02:00
|
|
|
|
|
|
|
use std::collections::HashMap;
|
|
|
|
use std::sync::{Arc, Mutex};
|
|
|
|
#[derive(Clone)]
|
|
|
|
struct RedisStream {
|
|
|
|
recv: Arc<Mutex<HashMap<String, pubsub::Receiver>>>,
|
|
|
|
current_stream: String,
|
|
|
|
}
|
|
|
|
impl RedisStream {
|
|
|
|
fn new() -> Self {
|
|
|
|
let recv = Arc::new(Mutex::new(HashMap::new()));
|
|
|
|
Self {
|
|
|
|
recv,
|
|
|
|
current_stream: "".to_string(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn add(&mut self, timeline: String, user: User) -> &Self {
|
|
|
|
let mut hash_map_of_streams = self.recv.lock().unwrap();
|
|
|
|
if !hash_map_of_streams.contains_key(&timeline) {
|
|
|
|
println!(
|
|
|
|
"First time encountering `{}`, saving it to the HashMap",
|
|
|
|
&timeline
|
|
|
|
);
|
|
|
|
hash_map_of_streams.insert(timeline.clone(), PubSub::from(timeline.clone(), user));
|
|
|
|
} else {
|
|
|
|
println!(
|
|
|
|
"HashMap already contains `{}`, returning unmodified HashMap",
|
|
|
|
&timeline
|
|
|
|
);
|
|
|
|
}
|
|
|
|
self.current_stream = timeline;
|
|
|
|
self
|
|
|
|
}
|
|
|
|
}
|
|
|
|
impl Stream for RedisStream {
|
|
|
|
type Item = Value;
|
|
|
|
type Error = Error;
|
|
|
|
|
|
|
|
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
|
|
|
|
println!("polling Interval");
|
|
|
|
let mut hash_map_of_streams = self.recv.lock().unwrap();
|
|
|
|
let target_stream = self.current_stream.clone();
|
|
|
|
let stream = hash_map_of_streams.get_mut(&target_stream).unwrap();
|
|
|
|
match stream.poll() {
|
|
|
|
Ok(Async::Ready(Some(value))) => Ok(Async::Ready(Some(value))),
|
|
|
|
Ok(Async::Ready(None)) => Ok(Async::Ready(None)),
|
|
|
|
Ok(Async::NotReady) => Ok(Async::NotReady),
|
|
|
|
Err(e) => Err(e),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|