mirror of https://github.com/mastodon/flodgatt
Improve module privacy (#136)
* Adjust module privacy * Use trait object * Finish module privacy refactor
This commit is contained in:
parent
4a456c6e90
commit
016f49a2d8
|
@ -22,20 +22,20 @@ fn parse_to_timeline(msg: RedisMsg) -> Timeline {
|
||||||
assert_eq!(tl, Timeline(User(Id(1)), Federated, All));
|
assert_eq!(tl, Timeline(User(Id(1)), Federated, All));
|
||||||
tl
|
tl
|
||||||
}
|
}
|
||||||
fn parse_to_checked_event(msg: RedisMsg) -> Event {
|
fn parse_to_checked_event(msg: RedisMsg) -> EventKind {
|
||||||
Event::TypeSafe(serde_json::from_str(msg.event_txt).unwrap())
|
EventKind::TypeSafe(serde_json::from_str(msg.event_txt).unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_to_dyn_event(msg: RedisMsg) -> Event {
|
fn parse_to_dyn_event(msg: RedisMsg) -> EventKind {
|
||||||
Event::Dynamic(serde_json::from_str(msg.event_txt).unwrap())
|
EventKind::Dynamic(serde_json::from_str(msg.event_txt).unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn redis_msg_to_event_string(msg: RedisMsg) -> String {
|
fn redis_msg_to_event_string(msg: RedisMsg) -> String {
|
||||||
msg.event_txt.to_string()
|
msg.event_txt.to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn string_to_checked_event(event_txt: &String) -> Event {
|
fn string_to_checked_event(event_txt: &String) -> EventKind {
|
||||||
Event::TypeSafe(serde_json::from_str(event_txt).unwrap())
|
EventKind::TypeSafe(serde_json::from_str(event_txt).unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn criterion_benchmark(c: &mut Criterion) {
|
fn criterion_benchmark(c: &mut Criterion) {
|
||||||
|
|
|
@ -1,13 +1,12 @@
|
||||||
pub(crate) use postgres_cfg::Postgres;
|
pub use self::deployment_cfg::Deployment;
|
||||||
pub(crate) use redis_cfg::Redis;
|
pub use self::postgres_cfg::Postgres;
|
||||||
|
pub use self::redis_cfg::Redis;
|
||||||
use deployment_cfg::Deployment;
|
|
||||||
|
|
||||||
use self::environmental_variables::EnvVar;
|
use self::environmental_variables::EnvVar;
|
||||||
use super::err::FatalErr;
|
|
||||||
use hashbrown::HashMap;
|
use hashbrown::HashMap;
|
||||||
use std::env;
|
use std::env;
|
||||||
|
use std::fmt;
|
||||||
mod deployment_cfg;
|
mod deployment_cfg;
|
||||||
mod deployment_cfg_types;
|
mod deployment_cfg_types;
|
||||||
mod environmental_variables;
|
mod environmental_variables;
|
||||||
|
@ -16,13 +15,13 @@ mod postgres_cfg_types;
|
||||||
mod redis_cfg;
|
mod redis_cfg;
|
||||||
mod redis_cfg_types;
|
mod redis_cfg_types;
|
||||||
|
|
||||||
type Result<T> = std::result::Result<T, FatalErr>;
|
type Result<T> = std::result::Result<T, Error>;
|
||||||
|
|
||||||
pub fn merge_dotenv() -> Result<()> {
|
pub fn merge_dotenv() -> Result<()> {
|
||||||
let env_file = match env::var("ENV").ok().as_deref() {
|
let env_file = match env::var("ENV").ok().as_deref() {
|
||||||
Some("production") => ".env.production",
|
Some("production") => ".env.production",
|
||||||
Some("development") | None => ".env",
|
Some("development") | None => ".env",
|
||||||
Some(v) => Err(FatalErr::config("ENV", v, "`production` or `development`"))?,
|
Some(v) => Err(Error::config("ENV", v, "`production` or `development`"))?,
|
||||||
};
|
};
|
||||||
let res = dotenv::from_filename(env_file);
|
let res = dotenv::from_filename(env_file);
|
||||||
|
|
||||||
|
@ -58,3 +57,47 @@ pub fn from_env<'a>(
|
||||||
|
|
||||||
Ok((pg_cfg, redis_cfg, deployment_cfg))
|
Ok((pg_cfg, redis_cfg, deployment_cfg))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Error {
|
||||||
|
Config(String),
|
||||||
|
UrlEncoding(urlencoding::FromUrlEncodingError),
|
||||||
|
UrlParse(url::ParseError),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for Error {}
|
||||||
|
|
||||||
|
impl fmt::Display for Error {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> std::result::Result<(), fmt::Error> {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{}",
|
||||||
|
match self {
|
||||||
|
Self::Config(e) => e.to_string(),
|
||||||
|
Self::UrlEncoding(e) => format!("could not parse POSTGRES_URL.\n{:7}{:?}", "", e),
|
||||||
|
Self::UrlParse(e) => format!("could parse Postgres URL.\n{:7}{}", "", e),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Error {
|
||||||
|
pub fn config<T: fmt::Display>(var: T, value: T, allowed_vals: T) -> Self {
|
||||||
|
Self::Config(format!(
|
||||||
|
"{0} is set to `{1}`, which is invalid.\n{3:7}{0} must be {2}.",
|
||||||
|
var, value, allowed_vals, ""
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<urlencoding::FromUrlEncodingError> for Error {
|
||||||
|
fn from(e: urlencoding::FromUrlEncodingError) -> Self {
|
||||||
|
Self::UrlEncoding(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<url::ParseError> for Error {
|
||||||
|
fn from(e: url::ParseError) -> Self {
|
||||||
|
Self::UrlParse(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use super::{deployment_cfg_types::*, EnvVar};
|
use super::deployment_cfg_types::*;
|
||||||
use crate::err::FatalErr;
|
use super::{EnvVar, Error};
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct Deployment<'a> {
|
pub struct Deployment<'a> {
|
||||||
|
@ -13,7 +13,7 @@ pub struct Deployment<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Deployment<'_> {
|
impl Deployment<'_> {
|
||||||
pub(crate) fn from_env(env: &EnvVar) -> Result<Self, FatalErr> {
|
pub(crate) fn from_env(env: &EnvVar) -> Result<Self, Error> {
|
||||||
let mut cfg = Self {
|
let mut cfg = Self {
|
||||||
env: Env::default().maybe_update(env.get("NODE_ENV"))?,
|
env: Env::default().maybe_update(env.get("NODE_ENV"))?,
|
||||||
log_level: LogLevel::default().maybe_update(env.get("RUST_LOG"))?,
|
log_level: LogLevel::default().maybe_update(env.get("RUST_LOG"))?,
|
||||||
|
|
|
@ -61,6 +61,7 @@ impl fmt::Display for EnvVar {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
|
#[doc(hidden)]
|
||||||
macro_rules! maybe_update {
|
macro_rules! maybe_update {
|
||||||
($name:ident; $item: tt:$type:ty) => (
|
($name:ident; $item: tt:$type:ty) => (
|
||||||
pub(crate) fn $name(self, item: Option<$type>) -> Self {
|
pub(crate) fn $name(self, item: Option<$type>) -> Self {
|
||||||
|
@ -76,7 +77,9 @@ macro_rules! maybe_update {
|
||||||
None => Self { ..self }
|
None => Self { ..self }
|
||||||
}
|
}
|
||||||
})}
|
})}
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
|
#[doc(hidden)]
|
||||||
macro_rules! from_env_var {
|
macro_rules! from_env_var {
|
||||||
($(#[$outer:meta])*
|
($(#[$outer:meta])*
|
||||||
let name = $name:ident;
|
let name = $name:ident;
|
||||||
|
@ -106,15 +109,14 @@ macro_rules! from_env_var {
|
||||||
fn inner_from_str($arg: &str) -> Option<$type> {
|
fn inner_from_str($arg: &str) -> Option<$type> {
|
||||||
$body
|
$body
|
||||||
}
|
}
|
||||||
pub(crate) fn maybe_update(
|
pub(crate) fn maybe_update(self, var: Option<&String>) -> Result<Self, super::Error> {
|
||||||
self,
|
|
||||||
var: Option<&String>,
|
|
||||||
) -> Result<Self, crate::err::FatalErr> {
|
|
||||||
Ok(match var {
|
Ok(match var {
|
||||||
Some(empty_string) if empty_string.is_empty() => Self::default(),
|
Some(empty_string) if empty_string.is_empty() => Self::default(),
|
||||||
Some(value) => Self(Self::inner_from_str(value).ok_or_else(|| {
|
Some(value) => {
|
||||||
crate::err::FatalErr::config($env_var, value, $allowed_values)
|
Self(Self::inner_from_str(value).ok_or_else(|| {
|
||||||
})?),
|
super::Error::config($env_var, value, $allowed_values)
|
||||||
|
})?)
|
||||||
|
}
|
||||||
None => self,
|
None => self,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,19 @@
|
||||||
use super::{postgres_cfg_types::*, EnvVar};
|
use super::postgres_cfg_types::*;
|
||||||
use crate::err::FatalErr;
|
use super::{EnvVar, Error};
|
||||||
|
|
||||||
use url::Url;
|
use url::Url;
|
||||||
use urlencoding;
|
use urlencoding;
|
||||||
|
|
||||||
type Result<T> = std::result::Result<T, FatalErr>;
|
type Result<T> = std::result::Result<T, Error>;
|
||||||
|
|
||||||
|
/// Configuration values for Postgres
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Postgres {
|
pub struct Postgres {
|
||||||
pub(crate) user: PgUser,
|
pub(crate) user: PgUser,
|
||||||
pub(crate) host: PgHost,
|
pub(crate) host: PgHost,
|
||||||
pub(crate) password: PgPass,
|
pub(crate) password: PgPass,
|
||||||
pub(crate) database: PgDatabase,
|
/// The name of the postgres database to connect to
|
||||||
|
pub database: PgDatabase,
|
||||||
pub(crate) port: PgPort,
|
pub(crate) port: PgPort,
|
||||||
pub(crate) ssl_mode: PgSslMode,
|
pub(crate) ssl_mode: PgSslMode,
|
||||||
}
|
}
|
||||||
|
@ -27,7 +29,7 @@ impl EnvVar {
|
||||||
"password" => self.maybe_add_env_var("DB_PASS", Some(v.to_string())),
|
"password" => self.maybe_add_env_var("DB_PASS", Some(v.to_string())),
|
||||||
"host" => self.maybe_add_env_var("DB_HOST", Some(v.to_string())),
|
"host" => self.maybe_add_env_var("DB_HOST", Some(v.to_string())),
|
||||||
"sslmode" => self.maybe_add_env_var("DB_SSLMODE", Some(v.to_string())),
|
"sslmode" => self.maybe_add_env_var("DB_SSLMODE", Some(v.to_string())),
|
||||||
_ => Err(FatalErr::config(
|
_ => Err(Error::config(
|
||||||
"POSTGRES_URL",
|
"POSTGRES_URL",
|
||||||
&k,
|
&k,
|
||||||
"a URL with parameters `password`, `user`, `host`, and `sslmode` only",
|
"a URL with parameters `password`, `user`, `host`, and `sslmode` only",
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
use super::redis_cfg_types::*;
|
use super::redis_cfg_types::*;
|
||||||
use super::EnvVar;
|
use super::{EnvVar, Error};
|
||||||
use crate::err::FatalErr;
|
|
||||||
|
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
type Result<T> = std::result::Result<T, FatalErr>;
|
type Result<T> = std::result::Result<T, Error>;
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct Redis {
|
pub struct Redis {
|
||||||
|
@ -33,7 +32,7 @@ impl EnvVar {
|
||||||
match k.to_string().as_str() {
|
match k.to_string().as_str() {
|
||||||
"password" => self.maybe_add_env_var("REDIS_PASSWORD", Some(v.to_string())),
|
"password" => self.maybe_add_env_var("REDIS_PASSWORD", Some(v.to_string())),
|
||||||
"db" => self.maybe_add_env_var("REDIS_DB", Some(v.to_string())),
|
"db" => self.maybe_add_env_var("REDIS_DB", Some(v.to_string())),
|
||||||
_ => Err(FatalErr::config(
|
_ => Err(Error::config(
|
||||||
"REDIS_URL",
|
"REDIS_URL",
|
||||||
&k,
|
&k,
|
||||||
"a URL with parameters `password`, `db`, only",
|
"a URL with parameters `password`, `db`, only",
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::from_env_var;
|
use crate::from_env_var; //macro
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
//use std::{fmt, net::IpAddr, os::unix::net::UnixListener, str::FromStr, time::Duration};
|
//use std::{fmt, net::IpAddr, os::unix::net::UnixListener, str::FromStr, time::Duration};
|
||||||
//use strum_macros::{EnumString, EnumVariantNames};
|
//use strum_macros::{EnumString, EnumVariantNames};
|
||||||
|
|
74
src/err.rs
74
src/err.rs
|
@ -1,86 +1,80 @@
|
||||||
use crate::request::RequestErr;
|
use crate::config;
|
||||||
use crate::response::ManagerErr;
|
use crate::request;
|
||||||
|
use crate::response;
|
||||||
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
pub enum FatalErr {
|
pub enum Error {
|
||||||
ReceiverErr(ManagerErr),
|
Response(response::Error),
|
||||||
Logger(log::SetLoggerError),
|
Logger(log::SetLoggerError),
|
||||||
Postgres(RequestErr),
|
Postgres(request::Error),
|
||||||
Unrecoverable,
|
Unrecoverable,
|
||||||
StdIo(std::io::Error),
|
StdIo(std::io::Error),
|
||||||
// config errs
|
Config(config::Error),
|
||||||
UrlParse(url::ParseError),
|
|
||||||
UrlEncoding(urlencoding::FromUrlEncodingError),
|
|
||||||
ConfigErr(String),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FatalErr {
|
impl Error {
|
||||||
pub fn log(msg: impl fmt::Display) {
|
pub fn log(msg: impl fmt::Display) {
|
||||||
eprintln!("{}", msg);
|
eprintln!("{}", msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn config<T: fmt::Display>(var: T, value: T, allowed_vals: T) -> Self {
|
|
||||||
Self::ConfigErr(format!(
|
|
||||||
"{0} is set to `{1}`, which is invalid.\n{3:7}{0} must be {2}.",
|
|
||||||
var, value, allowed_vals, ""
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::error::Error for FatalErr {}
|
impl std::error::Error for Error {}
|
||||||
impl fmt::Debug for FatalErr {
|
|
||||||
|
impl fmt::Debug for Error {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||||
write!(f, "{}", self)
|
write!(f, "{}", self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for FatalErr {
|
impl fmt::Display for Error {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||||
use FatalErr::*;
|
use Error::*;
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
"{}",
|
"{}",
|
||||||
match self {
|
match self {
|
||||||
ReceiverErr(e) => format!("{}", e),
|
Response(e) => format!("{}", e),
|
||||||
Logger(e) => format!("{}", e),
|
Logger(e) => format!("{}", e),
|
||||||
StdIo(e) => format!("{}", e),
|
StdIo(e) => format!("{}", e),
|
||||||
Postgres(e) => format!("could not connect to Postgres.\n{:7}{}", "", e),
|
Postgres(e) => format!("could not connect to Postgres.\n{:7}{}", "", e),
|
||||||
ConfigErr(e) => e.to_string(),
|
Config(e) => format!("{}", e),
|
||||||
UrlParse(e) => format!("could parse Postgres URL.\n{:7}{}", "", e),
|
|
||||||
UrlEncoding(e) => format!("could not parse POSTGRES_URL.\n{:7}{:?}", "", e),
|
|
||||||
Unrecoverable => "Flodgatt will now shut down.".into(),
|
Unrecoverable => "Flodgatt will now shut down.".into(),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<RequestErr> for FatalErr {
|
#[doc(hidden)]
|
||||||
fn from(e: RequestErr) -> Self {
|
impl From<request::Error> for Error {
|
||||||
|
fn from(e: request::Error) -> Self {
|
||||||
Self::Postgres(e)
|
Self::Postgres(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<ManagerErr> for FatalErr {
|
#[doc(hidden)]
|
||||||
fn from(e: ManagerErr) -> Self {
|
impl From<response::Error> for Error {
|
||||||
Self::ReceiverErr(e)
|
fn from(e: response::Error) -> Self {
|
||||||
|
Self::Response(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl From<urlencoding::FromUrlEncodingError> for FatalErr {
|
|
||||||
fn from(e: urlencoding::FromUrlEncodingError) -> Self {
|
#[doc(hidden)]
|
||||||
Self::UrlEncoding(e)
|
impl From<config::Error> for Error {
|
||||||
|
fn from(e: config::Error) -> Self {
|
||||||
|
Self::Config(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl From<url::ParseError> for FatalErr {
|
|
||||||
fn from(e: url::ParseError) -> Self {
|
#[doc(hidden)]
|
||||||
Self::UrlParse(e)
|
impl From<std::io::Error> for Error {
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<std::io::Error> for FatalErr {
|
|
||||||
fn from(e: std::io::Error) -> Self {
|
fn from(e: std::io::Error) -> Self {
|
||||||
Self::StdIo(e)
|
Self::StdIo(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl From<log::SetLoggerError> for FatalErr {
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
impl From<log::SetLoggerError> for Error {
|
||||||
fn from(e: log::SetLoggerError) -> Self {
|
fn from(e: log::SetLoggerError) -> Self {
|
||||||
Self::Logger(e)
|
Self::Logger(e)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,134 +0,0 @@
|
||||||
mod application;
|
|
||||||
mod attachment;
|
|
||||||
mod card;
|
|
||||||
mod poll;
|
|
||||||
|
|
||||||
use super::account::Account;
|
|
||||||
use super::emoji::Emoji;
|
|
||||||
use super::id::Id;
|
|
||||||
use super::mention::Mention;
|
|
||||||
use super::tag::Tag;
|
|
||||||
use super::visibility::Visibility;
|
|
||||||
use application::Application;
|
|
||||||
use attachment::Attachment;
|
|
||||||
use card::Card;
|
|
||||||
use poll::Poll;
|
|
||||||
|
|
||||||
use crate::request::Blocks;
|
|
||||||
|
|
||||||
use hashbrown::HashSet;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use std::boxed::Box;
|
|
||||||
use std::string::String;
|
|
||||||
|
|
||||||
#[serde(deny_unknown_fields)]
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
|
||||||
pub struct Status {
|
|
||||||
id: Id,
|
|
||||||
uri: String,
|
|
||||||
created_at: String,
|
|
||||||
account: Account,
|
|
||||||
content: String,
|
|
||||||
visibility: Visibility,
|
|
||||||
sensitive: bool,
|
|
||||||
spoiler_text: String,
|
|
||||||
media_attachments: Vec<Attachment>,
|
|
||||||
application: Option<Application>, // Should be non-optional?
|
|
||||||
mentions: Vec<Mention>,
|
|
||||||
tags: Vec<Tag>,
|
|
||||||
emojis: Vec<Emoji>,
|
|
||||||
reblogs_count: i64,
|
|
||||||
favourites_count: i64,
|
|
||||||
replies_count: i64,
|
|
||||||
url: Option<String>,
|
|
||||||
in_reply_to_id: Option<Id>,
|
|
||||||
in_reply_to_account_id: Option<Id>,
|
|
||||||
reblog: Option<Box<Status>>,
|
|
||||||
poll: Option<Poll>,
|
|
||||||
card: Option<Card>,
|
|
||||||
language: Option<String>,
|
|
||||||
|
|
||||||
text: Option<String>,
|
|
||||||
// ↓↓↓ Only for authorized users
|
|
||||||
favourited: Option<bool>,
|
|
||||||
reblogged: Option<bool>,
|
|
||||||
muted: Option<bool>,
|
|
||||||
bookmarked: Option<bool>,
|
|
||||||
pinned: Option<bool>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Status {
|
|
||||||
/// Returns `true` if the status is filtered out based on its language
|
|
||||||
pub(crate) fn language_not(&self, allowed_langs: &HashSet<String>) -> bool {
|
|
||||||
const ALLOW: bool = false;
|
|
||||||
const REJECT: bool = true;
|
|
||||||
|
|
||||||
let reject_and_maybe_log = |toot_language| {
|
|
||||||
log::info!("Filtering out toot from `{}`", &self.account.acct);
|
|
||||||
log::info!("Toot language: `{}`", toot_language);
|
|
||||||
log::info!("Recipient's allowed languages: `{:?}`", allowed_langs);
|
|
||||||
REJECT
|
|
||||||
};
|
|
||||||
if allowed_langs.is_empty() {
|
|
||||||
return ALLOW; // listing no allowed_langs results in allowing all languages
|
|
||||||
}
|
|
||||||
|
|
||||||
match self.language.as_ref() {
|
|
||||||
Some(toot_language) if allowed_langs.contains(toot_language) => ALLOW,
|
|
||||||
None => ALLOW, // If toot language is unknown, toot is always allowed
|
|
||||||
Some(empty) if empty == &String::new() => ALLOW,
|
|
||||||
Some(toot_language) => reject_and_maybe_log(toot_language),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns `true` if the Status originated from a blocked domain, is from an account
|
|
||||||
/// that has blocked the current user, or if the User's list of blocked/muted users
|
|
||||||
/// includes a user involved in the Status.
|
|
||||||
///
|
|
||||||
/// A user is involved in the Status/toot if they:
|
|
||||||
/// * Are mentioned in this toot
|
|
||||||
/// * Wrote this toot
|
|
||||||
/// * Wrote a toot that this toot is replying to (if any)
|
|
||||||
/// * Wrote the toot that this toot is boosting (if any)
|
|
||||||
pub(crate) fn involves_any(&self, blocks: &Blocks) -> bool {
|
|
||||||
const ALLOW: bool = false;
|
|
||||||
const REJECT: bool = true;
|
|
||||||
let Blocks {
|
|
||||||
blocked_users,
|
|
||||||
blocking_users,
|
|
||||||
blocked_domains,
|
|
||||||
} = blocks;
|
|
||||||
let user_id = &Id(self.account.id.0);
|
|
||||||
|
|
||||||
if blocking_users.contains(user_id) || self.involves(blocked_users) {
|
|
||||||
REJECT
|
|
||||||
} else {
|
|
||||||
let full_username = &self.account.acct;
|
|
||||||
match full_username.split('@').nth(1) {
|
|
||||||
Some(originating_domain) if blocked_domains.contains(originating_domain) => REJECT,
|
|
||||||
Some(_) | None => ALLOW, // None means the local instance, which can't be blocked
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn involves(&self, blocked_users: &HashSet<Id>) -> bool {
|
|
||||||
// involved_users = mentioned_users + author + replied-to user + boosted user
|
|
||||||
let mut involved_users: HashSet<Id> = self
|
|
||||||
.mentions
|
|
||||||
.iter()
|
|
||||||
.map(|mention| Id(mention.id.0))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
// author
|
|
||||||
involved_users.insert(Id(self.account.id.0));
|
|
||||||
// replied-to user
|
|
||||||
if let Some(user_id) = self.in_reply_to_account_id {
|
|
||||||
involved_users.insert(Id(user_id.0));
|
|
||||||
}
|
|
||||||
// boosted user
|
|
||||||
if let Some(boosted_status) = self.reblog.clone() {
|
|
||||||
involved_users.insert(Id(boosted_status.account.id.0));
|
|
||||||
}
|
|
||||||
!involved_users.is_disjoint(blocked_users)
|
|
||||||
}
|
|
||||||
}
|
|
18
src/lib.rs
18
src/lib.rs
|
@ -35,12 +35,22 @@
|
||||||
//! polls the `Receiver` and the frequency with which the `Receiver` polls Redis.
|
//! polls the `Receiver` and the frequency with which the `Receiver` polls Redis.
|
||||||
//!
|
//!
|
||||||
|
|
||||||
//#![warn(clippy::pedantic)]
|
#![warn(clippy::pedantic)]
|
||||||
#![allow(clippy::try_err, clippy::match_bool)]
|
#![allow(clippy::try_err, clippy::match_bool)]
|
||||||
//#![allow(clippy::large_enum_variant)]
|
#![allow(clippy::large_enum_variant)]
|
||||||
|
|
||||||
|
pub use err::Error;
|
||||||
|
|
||||||
pub mod config;
|
pub mod config;
|
||||||
pub mod err;
|
mod err;
|
||||||
pub mod event;
|
|
||||||
pub mod request;
|
pub mod request;
|
||||||
pub mod response;
|
pub mod response;
|
||||||
|
|
||||||
|
/// A user ID.
|
||||||
|
///
|
||||||
|
/// Internally, Mastodon IDs are i64s, but are sent to clients as string because
|
||||||
|
/// JavaScript numbers don't support i64s. This newtype serializes to/from a string, but
|
||||||
|
/// keeps the i64 as the "true" value for internal use.
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub struct Id(pub i64);
|
||||||
|
|
40
src/main.rs
40
src/main.rs
|
@ -1,9 +1,7 @@
|
||||||
use flodgatt::config;
|
use flodgatt::config;
|
||||||
use flodgatt::err::FatalErr;
|
|
||||||
use flodgatt::event::Event;
|
|
||||||
use flodgatt::request::{Handler, Subscription, Timeline};
|
use flodgatt::request::{Handler, Subscription, Timeline};
|
||||||
use flodgatt::response::redis;
|
use flodgatt::response::{Event, RedisManager, SseStream, WsStream};
|
||||||
use flodgatt::response::stream;
|
use flodgatt::Error;
|
||||||
|
|
||||||
use futures::{future::lazy, stream::Stream as _};
|
use futures::{future::lazy, stream::Stream as _};
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
@ -16,7 +14,7 @@ use tokio::timer::Interval;
|
||||||
use warp::ws::Ws2;
|
use warp::ws::Ws2;
|
||||||
use warp::Filter;
|
use warp::Filter;
|
||||||
|
|
||||||
fn main() -> Result<(), FatalErr> {
|
fn main() -> Result<(), Error> {
|
||||||
config::merge_dotenv()?;
|
config::merge_dotenv()?;
|
||||||
pretty_env_logger::try_init()?;
|
pretty_env_logger::try_init()?;
|
||||||
let (postgres_cfg, redis_cfg, cfg) = config::from_env(dotenv::vars().collect())?;
|
let (postgres_cfg, redis_cfg, cfg) = config::from_env(dotenv::vars().collect())?;
|
||||||
|
@ -27,7 +25,7 @@ fn main() -> Result<(), FatalErr> {
|
||||||
let (cmd_tx, cmd_rx) = mpsc::unbounded_channel();
|
let (cmd_tx, cmd_rx) = mpsc::unbounded_channel();
|
||||||
|
|
||||||
let request = Handler::new(&postgres_cfg, *cfg.whitelist_mode)?;
|
let request = Handler::new(&postgres_cfg, *cfg.whitelist_mode)?;
|
||||||
let shared_manager = redis::Manager::try_from(&redis_cfg, event_tx, cmd_rx)?.into_arc();
|
let shared_manager = RedisManager::try_from(&redis_cfg, event_tx, cmd_rx)?.into_arc();
|
||||||
|
|
||||||
// Server Sent Events
|
// Server Sent Events
|
||||||
let sse_manager = shared_manager.clone();
|
let sse_manager = shared_manager.clone();
|
||||||
|
@ -37,10 +35,10 @@ fn main() -> Result<(), FatalErr> {
|
||||||
.and(warp::sse())
|
.and(warp::sse())
|
||||||
.map(move |subscription: Subscription, sse: warp::sse::Sse| {
|
.map(move |subscription: Subscription, sse: warp::sse::Sse| {
|
||||||
log::info!("Incoming SSE request for {:?}", subscription.timeline);
|
log::info!("Incoming SSE request for {:?}", subscription.timeline);
|
||||||
let mut manager = sse_manager.lock().unwrap_or_else(redis::Manager::recover);
|
let mut manager = sse_manager.lock().unwrap_or_else(RedisManager::recover);
|
||||||
manager.subscribe(&subscription);
|
manager.subscribe(&subscription);
|
||||||
|
|
||||||
stream::Sse::send_events(sse, sse_cmd_tx.clone(), subscription, sse_rx.clone())
|
SseStream::send_events(sse, sse_cmd_tx.clone(), subscription, sse_rx.clone())
|
||||||
})
|
})
|
||||||
.with(warp::reply::with::header("Connection", "keep-alive"));
|
.with(warp::reply::with::header("Connection", "keep-alive"));
|
||||||
|
|
||||||
|
@ -51,10 +49,10 @@ fn main() -> Result<(), FatalErr> {
|
||||||
.and(warp::ws::ws2())
|
.and(warp::ws::ws2())
|
||||||
.map(move |subscription: Subscription, ws: Ws2| {
|
.map(move |subscription: Subscription, ws: Ws2| {
|
||||||
log::info!("Incoming websocket request for {:?}", subscription.timeline);
|
log::info!("Incoming websocket request for {:?}", subscription.timeline);
|
||||||
let mut manager = ws_manager.lock().unwrap_or_else(redis::Manager::recover);
|
let mut manager = ws_manager.lock().unwrap_or_else(RedisManager::recover);
|
||||||
manager.subscribe(&subscription);
|
manager.subscribe(&subscription);
|
||||||
let token = subscription.access_token.clone().unwrap_or_default(); // token sent for security
|
let token = subscription.access_token.clone().unwrap_or_default(); // token sent for security
|
||||||
let ws_stream = stream::Ws::new(cmd_tx.clone(), event_rx.clone(), subscription);
|
let ws_stream = WsStream::new(cmd_tx.clone(), event_rx.clone(), subscription);
|
||||||
|
|
||||||
(ws.on_upgrade(move |ws| ws_stream.send_to(ws)), token)
|
(ws.on_upgrade(move |ws| ws_stream.send_to(ws)), token)
|
||||||
})
|
})
|
||||||
|
@ -66,9 +64,9 @@ fn main() -> Result<(), FatalErr> {
|
||||||
let (r1, r3) = (shared_manager.clone(), shared_manager.clone());
|
let (r1, r3) = (shared_manager.clone(), shared_manager.clone());
|
||||||
request.health().map(|| "OK")
|
request.health().map(|| "OK")
|
||||||
.or(request.status()
|
.or(request.status()
|
||||||
.map(move || r1.lock().unwrap_or_else(redis::Manager::recover).count()))
|
.map(move || r1.lock().unwrap_or_else(RedisManager::recover).count()))
|
||||||
.or(request.status_per_timeline()
|
.or(request.status_per_timeline()
|
||||||
.map(move || r3.lock().unwrap_or_else(redis::Manager::recover).list()))
|
.map(move || r3.lock().unwrap_or_else(RedisManager::recover).list()))
|
||||||
};
|
};
|
||||||
#[cfg(not(feature = "stub_status"))]
|
#[cfg(not(feature = "stub_status"))]
|
||||||
let status = request.health().map(|| "OK");
|
let status = request.health().map(|| "OK");
|
||||||
|
@ -78,22 +76,14 @@ fn main() -> Result<(), FatalErr> {
|
||||||
.allow_methods(cfg.cors.allowed_methods)
|
.allow_methods(cfg.cors.allowed_methods)
|
||||||
.allow_headers(cfg.cors.allowed_headers);
|
.allow_headers(cfg.cors.allowed_headers);
|
||||||
|
|
||||||
// use futures::future::Future;
|
|
||||||
let streaming_server = move || {
|
let streaming_server = move || {
|
||||||
let manager = shared_manager.clone();
|
let manager = shared_manager.clone();
|
||||||
let stream = Interval::new(Instant::now(), poll_freq)
|
let stream = Interval::new(Instant::now(), poll_freq)
|
||||||
// .take(1200)
|
|
||||||
.map_err(|e| log::error!("{}", e))
|
.map_err(|e| log::error!("{}", e))
|
||||||
.for_each(
|
.for_each(move |_| {
|
||||||
move |_| {
|
let mut manager = manager.lock().unwrap_or_else(RedisManager::recover);
|
||||||
let mut manager = manager.lock().unwrap_or_else(redis::Manager::recover);
|
manager.poll_broadcast().map_err(Error::log)
|
||||||
manager.poll_broadcast().map_err(FatalErr::log)
|
});
|
||||||
}, // ).and_then(|_| {
|
|
||||||
// log::info!("shutting down!");
|
|
||||||
// std::process::exit(0);
|
|
||||||
// futures::future::ok(())
|
|
||||||
// }
|
|
||||||
);
|
|
||||||
|
|
||||||
warp::spawn(lazy(move || stream));
|
warp::spawn(lazy(move || stream));
|
||||||
warp::serve(ws.or(sse).with(cors).or(status).recover(Handler::err))
|
warp::serve(ws.or(sse).with(cors).or(status).recover(Handler::err))
|
||||||
|
@ -109,5 +99,5 @@ fn main() -> Result<(), FatalErr> {
|
||||||
let server_addr = SocketAddr::new(*cfg.address, *cfg.port);
|
let server_addr = SocketAddr::new(*cfg.address, *cfg.port);
|
||||||
tokio::run(lazy(move || streaming_server().bind(server_addr)));
|
tokio::run(lazy(move || streaming_server().bind(server_addr)));
|
||||||
}
|
}
|
||||||
Err(FatalErr::Unrecoverable) // on get here if there's an unrecoverable error in poll_broadcast.
|
Err(Error::Unrecoverable) // only get here if there's an unrecoverable error in poll_broadcast.
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,21 +1,19 @@
|
||||||
//! Parse the client request and return a Subscription
|
//! Parse the client request and return a Subscription
|
||||||
mod postgres;
|
mod postgres;
|
||||||
mod query;
|
mod query;
|
||||||
pub mod timeline;
|
mod timeline;
|
||||||
|
|
||||||
mod err;
|
mod err;
|
||||||
mod subscription;
|
mod subscription;
|
||||||
|
|
||||||
pub(crate) use self::err::RequestErr;
|
pub use err::{Error, Timeline as TimelineErr};
|
||||||
pub(crate) use self::postgres::PgPool;
|
pub use subscription::{Blocks, Subscription};
|
||||||
|
|
||||||
pub(crate) use subscription::Blocks;
|
|
||||||
pub use subscription::Subscription;
|
|
||||||
pub use timeline::Timeline;
|
pub use timeline::Timeline;
|
||||||
pub(crate) use timeline::{Content, Reach, Stream, TimelineErr};
|
use timeline::{Content, Reach, Stream};
|
||||||
|
|
||||||
|
pub use self::postgres::PgPool;
|
||||||
use self::query::Query;
|
use self::query::Query;
|
||||||
use crate::config;
|
use crate::config::Postgres;
|
||||||
use warp::filters::BoxedFilter;
|
use warp::filters::BoxedFilter;
|
||||||
use warp::http::StatusCode;
|
use warp::http::StatusCode;
|
||||||
use warp::path;
|
use warp::path;
|
||||||
|
@ -26,7 +24,7 @@ mod sse_test;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod ws_test;
|
mod ws_test;
|
||||||
|
|
||||||
type Result<T> = std::result::Result<T, err::RequestErr>;
|
type Result<T> = std::result::Result<T, err::Error>;
|
||||||
|
|
||||||
/// Helper macro to match on the first of any of the provided filters
|
/// Helper macro to match on the first of any of the provided filters
|
||||||
macro_rules! any_of {
|
macro_rules! any_of {
|
||||||
|
@ -62,7 +60,7 @@ pub struct Handler {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Handler {
|
impl Handler {
|
||||||
pub fn new(postgres_cfg: &config::Postgres, whitelist_mode: bool) -> Result<Self> {
|
pub fn new(postgres_cfg: &Postgres, whitelist_mode: bool) -> Result<Self> {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
pg_conn: PgPool::new(postgres_cfg, whitelist_mode)?,
|
pg_conn: PgPool::new(postgres_cfg, whitelist_mode)?,
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum RequestErr {
|
pub enum Error {
|
||||||
PgPool(r2d2::Error),
|
PgPool(r2d2::Error),
|
||||||
Pg(postgres::Error),
|
Pg(postgres::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::error::Error for RequestErr {}
|
impl std::error::Error for Error {}
|
||||||
|
|
||||||
impl fmt::Display for RequestErr {
|
impl fmt::Display for Error {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||||
use RequestErr::*;
|
use Error::*;
|
||||||
let msg = match self {
|
let msg = match self {
|
||||||
PgPool(e) => format!("{}", e),
|
PgPool(e) => format!("{}", e),
|
||||||
Pg(e) => format!("{}", e),
|
Pg(e) => format!("{}", e),
|
||||||
|
@ -18,13 +18,40 @@ impl fmt::Display for RequestErr {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<r2d2::Error> for RequestErr {
|
impl From<r2d2::Error> for Error {
|
||||||
fn from(e: r2d2::Error) -> Self {
|
fn from(e: r2d2::Error) -> Self {
|
||||||
Self::PgPool(e)
|
Self::PgPool(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl From<postgres::Error> for RequestErr {
|
impl From<postgres::Error> for Error {
|
||||||
fn from(e: postgres::Error) -> Self {
|
fn from(e: postgres::Error) -> Self {
|
||||||
Self::Pg(e)
|
Self::Pg(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// TODO make Timeline & TimelineErr their own top-level module
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Timeline {
|
||||||
|
MissingHashtag,
|
||||||
|
InvalidInput,
|
||||||
|
BadTag,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for Timeline {}
|
||||||
|
|
||||||
|
impl From<std::num::ParseIntError> for Timeline {
|
||||||
|
fn from(_error: std::num::ParseIntError) -> Self {
|
||||||
|
Self::InvalidInput
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Timeline {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||||
|
use Timeline::*;
|
||||||
|
let msg = match self {
|
||||||
|
InvalidInput => "The timeline text from Redis could not be parsed into a supported timeline. TODO: add incoming timeline text",
|
||||||
|
MissingHashtag => "Attempted to send a hashtag timeline without supplying a tag name",
|
||||||
|
BadTag => "No hashtag exists with the specified hashtag ID"
|
||||||
|
};
|
||||||
|
write!(f, "{}", msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
use super::err;
|
use super::err;
|
||||||
use super::timeline::{Scope, UserData};
|
use super::timeline::{Scope, UserData};
|
||||||
use crate::config;
|
use crate::config;
|
||||||
use crate::event::Id;
|
use crate::Id;
|
||||||
|
|
||||||
use ::postgres;
|
use ::postgres;
|
||||||
use hashbrown::HashSet;
|
use hashbrown::HashSet;
|
||||||
|
@ -15,7 +15,7 @@ pub struct PgPool {
|
||||||
whitelist_mode: bool,
|
whitelist_mode: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
type Result<T> = std::result::Result<T, err::RequestErr>;
|
type Result<T> = std::result::Result<T, err::Error>;
|
||||||
type Rejectable<T> = std::result::Result<T, warp::Rejection>;
|
type Rejectable<T> = std::result::Result<T, warp::Rejection>;
|
||||||
|
|
||||||
impl PgPool {
|
impl PgPool {
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
// #[cfg(test)]
|
// #[cfg(test)]
|
||||||
// mod test {
|
// mod test {
|
||||||
// use super::*;
|
// use super::*;
|
||||||
// use crate::parse_client_request::user::{Blocks, Filter, OauthScope, PgPool};
|
|
||||||
|
|
||||||
// macro_rules! test_public_endpoint {
|
// macro_rules! test_public_endpoint {
|
||||||
// ($name:ident {
|
// ($name:ident {
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
use super::postgres::PgPool;
|
use super::postgres::PgPool;
|
||||||
use super::query::Query;
|
use super::query::Query;
|
||||||
use super::{Content, Reach, Stream, Timeline};
|
use super::{Content, Reach, Stream, Timeline};
|
||||||
use crate::event::Id;
|
use crate::Id;
|
||||||
|
|
||||||
use hashbrown::HashSet;
|
use hashbrown::HashSet;
|
||||||
|
|
||||||
|
@ -17,17 +17,19 @@ use warp::reject::Rejection;
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub struct Subscription {
|
pub struct Subscription {
|
||||||
pub timeline: Timeline,
|
pub timeline: Timeline,
|
||||||
pub(crate) allowed_langs: HashSet<String>,
|
pub allowed_langs: HashSet<String>,
|
||||||
pub(crate) blocks: Blocks,
|
/// [Blocks](./request/struct.Blocks.html)
|
||||||
pub(crate) hashtag_name: Option<String>,
|
pub blocks: Blocks,
|
||||||
|
pub hashtag_name: Option<String>,
|
||||||
pub access_token: Option<String>,
|
pub access_token: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Blocked and muted users and domains
|
||||||
#[derive(Clone, Default, Debug, PartialEq)]
|
#[derive(Clone, Default, Debug, PartialEq)]
|
||||||
pub(crate) struct Blocks {
|
pub struct Blocks {
|
||||||
pub(crate) blocked_domains: HashSet<String>,
|
pub blocked_domains: HashSet<String>,
|
||||||
pub(crate) blocked_users: HashSet<Id>,
|
pub blocked_users: HashSet<Id>,
|
||||||
pub(crate) blocking_users: HashSet<Id>,
|
pub blocking_users: HashSet<Id>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Subscription {
|
impl Default for Subscription {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
pub(crate) use self::err::TimelineErr;
|
|
||||||
pub(crate) use self::inner::{Content, Reach, Scope, Stream, UserData};
|
pub(crate) use self::inner::{Content, Reach, Scope, Stream, UserData};
|
||||||
|
use super::err::Timeline as Error;
|
||||||
use super::query::Query;
|
use super::query::Query;
|
||||||
|
|
||||||
use lru::LruCache;
|
use lru::LruCache;
|
||||||
|
@ -8,7 +8,7 @@ use warp::reject::Rejection;
|
||||||
mod err;
|
mod err;
|
||||||
mod inner;
|
mod inner;
|
||||||
|
|
||||||
type Result<T> = std::result::Result<T, TimelineErr>;
|
type Result<T> = std::result::Result<T, Error>;
|
||||||
|
|
||||||
#[derive(Clone, Debug, Copy, Eq, Hash, PartialEq)]
|
#[derive(Clone, Debug, Copy, Eq, Hash, PartialEq)]
|
||||||
pub struct Timeline(pub(crate) Stream, pub(crate) Reach, pub(crate) Content);
|
pub struct Timeline(pub(crate) Stream, pub(crate) Reach, pub(crate) Content);
|
||||||
|
@ -18,9 +18,25 @@ impl Timeline {
|
||||||
Self(Stream::Unset, Reach::Local, Content::Notification)
|
Self(Stream::Unset, Reach::Local, Content::Notification)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn is_public(&self) -> bool {
|
||||||
|
if let Self(Stream::Public, _, _) = self {
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn tag(&self) -> Option<i64> {
|
||||||
|
if let Self(Stream::Hashtag(id), _, _) = self {
|
||||||
|
Some(*id)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn to_redis_raw_timeline(&self, hashtag: Option<&String>) -> Result<String> {
|
pub(crate) fn to_redis_raw_timeline(&self, hashtag: Option<&String>) -> Result<String> {
|
||||||
// TODO -- does this need to account for namespaces?
|
// TODO -- does this need to account for namespaces?
|
||||||
use {Content::*, Reach::*, Stream::*, TimelineErr::*};
|
use {Content::*, Error::*, Reach::*, Stream::*};
|
||||||
|
|
||||||
Ok(match self {
|
Ok(match self {
|
||||||
Timeline(Public, Federated, All) => "timeline:public".to_string(),
|
Timeline(Public, Federated, All) => "timeline:public".to_string(),
|
||||||
|
@ -42,7 +58,7 @@ impl Timeline {
|
||||||
}
|
}
|
||||||
Timeline(List(id), Federated, All) => ["timeline:list:", &id.to_string()].concat(),
|
Timeline(List(id), Federated, All) => ["timeline:list:", &id.to_string()].concat(),
|
||||||
Timeline(Direct(id), Federated, All) => ["timeline:direct:", &id.to_string()].concat(),
|
Timeline(Direct(id), Federated, All) => ["timeline:direct:", &id.to_string()].concat(),
|
||||||
Timeline(_one, _two, _three) => Err(TimelineErr::InvalidInput)?,
|
Timeline(_one, _two, _three) => Err(Error::InvalidInput)?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,7 +66,7 @@ impl Timeline {
|
||||||
timeline: &str,
|
timeline: &str,
|
||||||
cache: &mut LruCache<String, i64>,
|
cache: &mut LruCache<String, i64>,
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
use {Content::*, Reach::*, Stream::*, TimelineErr::*};
|
use {Content::*, Error::*, Reach::*, Stream::*};
|
||||||
let mut tag_id = |t: &str| cache.get(&t.to_string()).map_or(Err(BadTag), |id| Ok(*id));
|
let mut tag_id = |t: &str| cache.get(&t.to_string()).map_or(Err(BadTag), |id| Ok(*id));
|
||||||
|
|
||||||
Ok(match &timeline.split(':').collect::<Vec<&str>>()[..] {
|
Ok(match &timeline.split(':').collect::<Vec<&str>>()[..] {
|
||||||
|
|
|
@ -1,28 +1 @@
|
||||||
use std::fmt;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum TimelineErr {
|
|
||||||
MissingHashtag,
|
|
||||||
InvalidInput,
|
|
||||||
BadTag,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::error::Error for TimelineErr {}
|
|
||||||
|
|
||||||
impl From<std::num::ParseIntError> for TimelineErr {
|
|
||||||
fn from(_error: std::num::ParseIntError) -> Self {
|
|
||||||
Self::InvalidInput
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for TimelineErr {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
|
||||||
use TimelineErr::*;
|
|
||||||
let msg = match self {
|
|
||||||
InvalidInput => "The timeline text from Redis could not be parsed into a supported timeline. TODO: add incoming timeline text",
|
|
||||||
MissingHashtag => "Attempted to send a hashtag timeline without supplying a tag name",
|
|
||||||
BadTag => "No hashtag exists with the specified hashtag ID"
|
|
||||||
};
|
|
||||||
write!(f, "{}", msg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use super::TimelineErr;
|
use super::Error;
|
||||||
use crate::event::Id;
|
use crate::Id;
|
||||||
|
|
||||||
use hashbrown::HashSet;
|
use hashbrown::HashSet;
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
|
@ -36,18 +36,18 @@ pub(crate) enum Scope {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<&str> for Scope {
|
impl TryFrom<&str> for Scope {
|
||||||
type Error = TimelineErr;
|
type Error = Error;
|
||||||
|
|
||||||
fn try_from(s: &str) -> Result<Self, TimelineErr> {
|
fn try_from(s: &str) -> Result<Self, Error> {
|
||||||
match s {
|
match s {
|
||||||
"read" => Ok(Scope::Read),
|
"read" => Ok(Scope::Read),
|
||||||
"read:statuses" => Ok(Scope::Statuses),
|
"read:statuses" => Ok(Scope::Statuses),
|
||||||
"read:notifications" => Ok(Scope::Notifications),
|
"read:notifications" => Ok(Scope::Notifications),
|
||||||
"read:lists" => Ok(Scope::Lists),
|
"read:lists" => Ok(Scope::Lists),
|
||||||
"write" | "follow" => Err(TimelineErr::InvalidInput), // ignore write scopes
|
"write" | "follow" => Err(Error::InvalidInput), // ignore write scopes
|
||||||
unexpected => {
|
unexpected => {
|
||||||
log::warn!("Ignoring unknown scope `{}`", unexpected);
|
log::warn!("Ignoring unknown scope `{}`", unexpected);
|
||||||
Err(TimelineErr::InvalidInput)
|
Err(Error::InvalidInput)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,8 @@
|
||||||
//! Filters for the WebSocket endpoint
|
//! Filters for the WebSocket endpoint
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// #[cfg(test)]
|
// #[cfg(test)]
|
||||||
// mod test {
|
// mod test {
|
||||||
// use super::*;
|
// use super::*;
|
||||||
// use crate::parse_client_request::user::{Blocks, Filter, OauthScope};
|
|
||||||
|
|
||||||
// macro_rules! test_public_endpoint {
|
// macro_rules! test_public_endpoint {
|
||||||
// ($name:ident {
|
// ($name:ident {
|
||||||
|
|
|
@ -1,9 +1,17 @@
|
||||||
//! Stream the updates appropriate for a given `User`/`timeline` pair from Redis.
|
//! Stream the updates appropriate for a given `User`/`timeline` pair from Redis.
|
||||||
|
|
||||||
pub mod redis;
|
pub use event::Event;
|
||||||
pub mod stream;
|
pub use redis::Manager as RedisManager;
|
||||||
|
pub use stream::{Sse as SseStream, Ws as WsStream};
|
||||||
|
|
||||||
pub(crate) use redis::ManagerErr;
|
pub(self) use event::err::Event as EventErr;
|
||||||
|
pub(self) use event::Payload;
|
||||||
|
|
||||||
|
pub(crate) mod event;
|
||||||
|
mod redis;
|
||||||
|
mod stream;
|
||||||
|
|
||||||
|
pub use redis::Error;
|
||||||
|
|
||||||
#[cfg(feature = "bench")]
|
#[cfg(feature = "bench")]
|
||||||
pub use redis::msg::{RedisMsg, RedisParseOutput};
|
pub use redis::msg::{RedisMsg, RedisParseOutput};
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
mod checked_event;
|
mod checked_event;
|
||||||
mod dynamic_event;
|
mod dynamic_event;
|
||||||
mod err;
|
pub mod err;
|
||||||
|
|
||||||
pub(crate) use checked_event::{CheckedEvent, Id};
|
use self::checked_event::CheckedEvent;
|
||||||
pub(crate) use dynamic_event::{DynEvent, EventKind};
|
use self::dynamic_event::{DynEvent, EventKind};
|
||||||
pub(crate) use err::EventErr;
|
use crate::Id;
|
||||||
|
|
||||||
|
use hashbrown::HashSet;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
use std::string::String;
|
use std::string::String;
|
||||||
|
@ -18,6 +19,18 @@ pub enum Event {
|
||||||
Ping,
|
Ping,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) trait Payload {
|
||||||
|
fn language_unset(&self) -> bool;
|
||||||
|
|
||||||
|
fn language(&self) -> String;
|
||||||
|
|
||||||
|
fn involved_users(&self) -> HashSet<Id>;
|
||||||
|
|
||||||
|
fn author(&self) -> &Id;
|
||||||
|
|
||||||
|
fn sent_from(&self) -> &str;
|
||||||
|
}
|
||||||
|
|
||||||
impl Event {
|
impl Event {
|
||||||
pub(crate) fn to_json_string(&self) -> String {
|
pub(crate) fn to_json_string(&self) -> String {
|
||||||
if let Event::Ping = self {
|
if let Event::Ping = self {
|
||||||
|
@ -43,6 +56,26 @@ impl Event {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn update_payload(&self) -> Option<&checked_event::Status> {
|
||||||
|
if let Self::TypeSafe(CheckedEvent::Update { payload, .. }) = self {
|
||||||
|
Some(&payload)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn dyn_update_payload(&self) -> Option<&dynamic_event::DynStatus> {
|
||||||
|
if let Self::Dynamic(DynEvent {
|
||||||
|
kind: EventKind::Update(s),
|
||||||
|
..
|
||||||
|
}) = self
|
||||||
|
{
|
||||||
|
Some(&s)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn event_name(&self) -> String {
|
fn event_name(&self) -> String {
|
||||||
String::from(match self {
|
String::from(match self {
|
||||||
Self::TypeSafe(checked) => match checked {
|
Self::TypeSafe(checked) => match checked {
|
||||||
|
@ -84,14 +117,14 @@ impl Event {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<String> for Event {
|
impl TryFrom<String> for Event {
|
||||||
type Error = EventErr;
|
type Error = err::Event;
|
||||||
|
|
||||||
fn try_from(event_txt: String) -> Result<Event, Self::Error> {
|
fn try_from(event_txt: String) -> Result<Event, Self::Error> {
|
||||||
Event::try_from(event_txt.as_str())
|
Event::try_from(event_txt.as_str())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl TryFrom<&str> for Event {
|
impl TryFrom<&str> for Event {
|
||||||
type Error = EventErr;
|
type Error = err::Event;
|
||||||
|
|
||||||
fn try_from(event_txt: &str) -> Result<Event, Self::Error> {
|
fn try_from(event_txt: &str) -> Result<Event, Self::Error> {
|
||||||
match serde_json::from_str(event_txt) {
|
match serde_json::from_str(event_txt) {
|
|
@ -11,13 +11,13 @@ mod status;
|
||||||
mod tag;
|
mod tag;
|
||||||
mod visibility;
|
mod visibility;
|
||||||
|
|
||||||
use announcement::Announcement;
|
pub(self) use super::Payload;
|
||||||
pub(in crate::event) use announcement_reaction::AnnouncementReaction;
|
pub(super) use announcement_reaction::AnnouncementReaction;
|
||||||
use conversation::Conversation;
|
pub(crate) use status::Status;
|
||||||
pub(crate) use id::Id;
|
|
||||||
use notification::Notification;
|
|
||||||
use status::Status;
|
|
||||||
|
|
||||||
|
use announcement::Announcement;
|
||||||
|
use conversation::Conversation;
|
||||||
|
use notification::Notification;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
#[serde(rename_all = "snake_case", tag = "event", deny_unknown_fields)]
|
#[serde(rename_all = "snake_case", tag = "event", deny_unknown_fields)]
|
|
@ -1,4 +1,5 @@
|
||||||
use super::{emoji::Emoji, id::Id, visibility::Visibility};
|
use super::{emoji::Emoji, visibility::Visibility};
|
||||||
|
use crate::Id;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[serde(deny_unknown_fields)]
|
#[serde(deny_unknown_fields)]
|
|
@ -1,4 +1,5 @@
|
||||||
use super::super::EventErr;
|
use super::super::err;
|
||||||
|
use crate::Id;
|
||||||
|
|
||||||
use serde::{
|
use serde::{
|
||||||
de::{self, Visitor},
|
de::{self, Visitor},
|
||||||
|
@ -7,19 +8,11 @@ use serde::{
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use std::{convert::TryFrom, fmt, num::ParseIntError, str::FromStr};
|
use std::{convert::TryFrom, fmt, num::ParseIntError, str::FromStr};
|
||||||
|
|
||||||
/// A user ID.
|
|
||||||
///
|
|
||||||
/// Internally, Mastodon IDs are i64s, but are sent to clients as string because
|
|
||||||
/// JavaScript numbers don't support i64s. This newtype serializes to/from a string, but
|
|
||||||
/// keeps the i64 as the "true" value for internal use.
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
|
||||||
pub struct Id(pub i64);
|
|
||||||
|
|
||||||
impl TryFrom<&Value> for Id {
|
impl TryFrom<&Value> for Id {
|
||||||
type Error = EventErr;
|
type Error = err::Event;
|
||||||
|
|
||||||
fn try_from(v: &Value) -> Result<Self, Self::Error> {
|
fn try_from(v: &Value) -> Result<Self, Self::Error> {
|
||||||
Ok(v.as_str().ok_or(EventErr::DynParse)?.parse()?)
|
Ok(v.as_str().ok_or(err::Event::DynParse)?.parse()?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use super::id::Id;
|
use crate::Id;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[serde(deny_unknown_fields)]
|
#[serde(deny_unknown_fields)]
|
|
@ -0,0 +1,103 @@
|
||||||
|
mod application;
|
||||||
|
mod attachment;
|
||||||
|
mod card;
|
||||||
|
mod poll;
|
||||||
|
|
||||||
|
use super::account::Account;
|
||||||
|
use super::emoji::Emoji;
|
||||||
|
use super::mention::Mention;
|
||||||
|
use super::tag::Tag;
|
||||||
|
use super::visibility::Visibility;
|
||||||
|
use super::Payload;
|
||||||
|
use crate::Id;
|
||||||
|
use application::Application;
|
||||||
|
use attachment::Attachment;
|
||||||
|
use card::Card;
|
||||||
|
use hashbrown::HashSet;
|
||||||
|
use poll::Poll;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::boxed::Box;
|
||||||
|
use std::string::String;
|
||||||
|
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||||
|
pub struct Status {
|
||||||
|
id: Id,
|
||||||
|
uri: String,
|
||||||
|
created_at: String,
|
||||||
|
account: Account,
|
||||||
|
content: String,
|
||||||
|
visibility: Visibility,
|
||||||
|
sensitive: bool,
|
||||||
|
spoiler_text: String,
|
||||||
|
media_attachments: Vec<Attachment>,
|
||||||
|
application: Option<Application>, // Should be non-optional?
|
||||||
|
mentions: Vec<Mention>,
|
||||||
|
tags: Vec<Tag>,
|
||||||
|
emojis: Vec<Emoji>,
|
||||||
|
reblogs_count: i64,
|
||||||
|
favourites_count: i64,
|
||||||
|
replies_count: i64,
|
||||||
|
url: Option<String>,
|
||||||
|
in_reply_to_id: Option<Id>,
|
||||||
|
in_reply_to_account_id: Option<Id>,
|
||||||
|
reblog: Option<Box<Status>>,
|
||||||
|
poll: Option<Poll>,
|
||||||
|
card: Option<Card>,
|
||||||
|
pub(crate) language: Option<String>,
|
||||||
|
|
||||||
|
text: Option<String>,
|
||||||
|
// ↓↓↓ Only for authorized users
|
||||||
|
favourited: Option<bool>,
|
||||||
|
reblogged: Option<bool>,
|
||||||
|
muted: Option<bool>,
|
||||||
|
bookmarked: Option<bool>,
|
||||||
|
pinned: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Payload for Status {
|
||||||
|
fn language_unset(&self) -> bool {
|
||||||
|
match &self.language {
|
||||||
|
None => true,
|
||||||
|
Some(empty) if empty == &String::new() => true,
|
||||||
|
Some(_language) => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn language(&self) -> String {
|
||||||
|
self.language.clone().unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns all users involved in the `Status`.
|
||||||
|
///
|
||||||
|
/// A user is involved in the Status/toot if they:
|
||||||
|
/// * Are mentioned in this toot
|
||||||
|
/// * Wrote this toot
|
||||||
|
/// * Wrote a toot that this toot is replying to (if any)
|
||||||
|
/// * Wrote the toot that this toot is boosting (if any)
|
||||||
|
fn involved_users(&self) -> HashSet<Id> {
|
||||||
|
// involved_users = mentioned_users + author + replied-to user + boosted user
|
||||||
|
let mut involved_users: HashSet<Id> = self.mentions.iter().map(|m| Id(m.id.0)).collect();
|
||||||
|
|
||||||
|
// author
|
||||||
|
involved_users.insert(Id(self.account.id.0));
|
||||||
|
// replied-to user
|
||||||
|
if let Some(user_id) = self.in_reply_to_account_id {
|
||||||
|
involved_users.insert(Id(user_id.0));
|
||||||
|
}
|
||||||
|
// boosted user
|
||||||
|
if let Some(boosted_status) = self.reblog.clone() {
|
||||||
|
involved_users.insert(Id(boosted_status.account.id.0));
|
||||||
|
}
|
||||||
|
involved_users
|
||||||
|
}
|
||||||
|
|
||||||
|
fn author(&self) -> &Id {
|
||||||
|
&self.account.id
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sent_from(&self) -> &str {
|
||||||
|
let sender_username = &self.account.acct;
|
||||||
|
sender_username.split('@').nth(1).unwrap_or_default() // default occurs when sent from local instance
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
use super::{EventErr, Id};
|
use super::err;
|
||||||
use crate::request::Blocks;
|
use super::Payload;
|
||||||
|
use crate::Id;
|
||||||
|
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
|
|
||||||
|
@ -38,7 +39,7 @@ pub(crate) struct DynStatus {
|
||||||
pub(crate) boosted_user: Option<Id>,
|
pub(crate) boosted_user: Option<Id>,
|
||||||
}
|
}
|
||||||
|
|
||||||
type Result<T> = std::result::Result<T, EventErr>;
|
type Result<T> = std::result::Result<T, err::Event>;
|
||||||
|
|
||||||
impl DynEvent {
|
impl DynEvent {
|
||||||
pub(crate) fn set_update(self) -> Result<Self> {
|
pub(crate) fn set_update(self) -> Result<Self> {
|
||||||
|
@ -50,16 +51,13 @@ impl DynEvent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DynStatus {
|
impl DynStatus {
|
||||||
pub(crate) fn new(payload: &Value) -> Result<Self> {
|
pub(crate) fn new(payload: &Value) -> Result<Self> {
|
||||||
use EventErr::*;
|
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
id: Id::try_from(&payload["account"]["id"])?,
|
id: Id::try_from(&payload["account"]["id"])?,
|
||||||
username: payload["account"]["acct"]
|
username: payload["account"]["acct"]
|
||||||
.as_str()
|
.as_str()
|
||||||
.ok_or(DynParse)?
|
.ok_or(err::Event::DynParse)?
|
||||||
.to_string(),
|
.to_string(),
|
||||||
language: payload["language"].as_str().map(String::from),
|
language: payload["language"].as_str().map(String::from),
|
||||||
mentioned_users: HashSet::new(),
|
mentioned_users: HashSet::new(),
|
||||||
|
@ -67,68 +65,50 @@ impl DynStatus {
|
||||||
boosted_user: Id::try_from(&payload["reblog"]["account"]["id"]).ok(),
|
boosted_user: Id::try_from(&payload["reblog"]["account"]["id"]).ok(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
/// Returns `true` if the status is filtered out based on its language
|
}
|
||||||
pub(crate) fn language_not(&self, allowed_langs: &HashSet<String>) -> bool {
|
|
||||||
const ALLOW: bool = false;
|
|
||||||
const REJECT: bool = true;
|
|
||||||
|
|
||||||
if allowed_langs.is_empty() {
|
impl Payload for DynStatus {
|
||||||
return ALLOW; // listing no allowed_langs results in allowing all languages
|
fn language_unset(&self) -> bool {
|
||||||
}
|
match &self.language {
|
||||||
|
None => true,
|
||||||
match self.language.clone() {
|
Some(empty) if empty == &String::new() => true,
|
||||||
Some(toot_language) if allowed_langs.contains(&toot_language) => ALLOW, //
|
Some(_language) => false,
|
||||||
None => ALLOW, // If toot language is unknown, toot is always allowed
|
|
||||||
Some(empty) if empty == String::new() => ALLOW,
|
|
||||||
Some(_toot_language) => REJECT,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns `true` if the toot contained in this Event originated from a blocked domain,
|
fn language(&self) -> String {
|
||||||
/// is from an account that has blocked the current user, or if the User's list of
|
self.language.clone().unwrap_or_default()
|
||||||
/// blocked/muted users includes a user involved in the toot.
|
}
|
||||||
|
/// Returns all users involved in the `Status`.
|
||||||
///
|
///
|
||||||
/// A user is involved in the toot if they:
|
/// A user is involved in the Status/toot if they:
|
||||||
/// * Are mentioned in this toot
|
/// * Are mentioned in this toot
|
||||||
/// * Wrote this toot
|
/// * Wrote this toot
|
||||||
/// * Wrote a toot that this toot is replying to (if any)
|
/// * Wrote a toot that this toot is replying to (if any)
|
||||||
/// * Wrote the toot that this toot is boosting (if any)
|
/// * Wrote the toot that this toot is boosting (if any)
|
||||||
pub(crate) fn involves_any(&self, blocks: &Blocks) -> bool {
|
fn involved_users(&self) -> HashSet<Id> {
|
||||||
const ALLOW: bool = false;
|
// involved_users = mentioned_users + author + replied-to user + boosted user
|
||||||
const REJECT: bool = true;
|
|
||||||
let Blocks {
|
|
||||||
blocked_users,
|
|
||||||
blocking_users,
|
|
||||||
blocked_domains,
|
|
||||||
} = blocks;
|
|
||||||
|
|
||||||
if self.involves(blocked_users) || blocking_users.contains(&self.id) {
|
|
||||||
REJECT
|
|
||||||
} else {
|
|
||||||
match self.username.split('@').nth(1) {
|
|
||||||
Some(originating_domain) if blocked_domains.contains(originating_domain) => REJECT,
|
|
||||||
Some(_) | None => ALLOW, // None means the local instance, which can't be blocked
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn involves(&self, blocked_users: &HashSet<Id>) -> bool {
|
|
||||||
// mentions
|
|
||||||
let mut involved_users: HashSet<Id> = self.mentioned_users.clone();
|
let mut involved_users: HashSet<Id> = self.mentioned_users.clone();
|
||||||
|
|
||||||
// author
|
// author
|
||||||
involved_users.insert(self.id);
|
involved_users.insert(self.id);
|
||||||
|
|
||||||
// replied-to user
|
// replied-to user
|
||||||
if let Some(user_id) = self.replied_to_user {
|
if let Some(user_id) = self.replied_to_user {
|
||||||
involved_users.insert(user_id);
|
involved_users.insert(user_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
// boosted user
|
// boosted user
|
||||||
if let Some(user_id) = self.boosted_user {
|
if let Some(boosted_status) = self.boosted_user {
|
||||||
involved_users.insert(user_id);
|
involved_users.insert(boosted_status);
|
||||||
}
|
}
|
||||||
|
involved_users
|
||||||
|
}
|
||||||
|
|
||||||
!involved_users.is_disjoint(blocked_users)
|
fn author(&self) -> &Id {
|
||||||
|
&self.id
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sent_from(&self) -> &str {
|
||||||
|
let sender_username = &self.username;
|
||||||
|
sender_username.split('@').nth(1).unwrap_or_default() // default occurs when sent from local instance
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,17 +1,17 @@
|
||||||
use std::{fmt, num::ParseIntError};
|
use std::{fmt, num::ParseIntError};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum EventErr {
|
pub enum Event {
|
||||||
SerdeParse(serde_json::Error),
|
SerdeParse(serde_json::Error),
|
||||||
NonNumId(ParseIntError),
|
NonNumId(ParseIntError),
|
||||||
DynParse,
|
DynParse,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::error::Error for EventErr {}
|
impl std::error::Error for Event {}
|
||||||
|
|
||||||
impl fmt::Display for EventErr {
|
impl fmt::Display for Event {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||||
use EventErr::*;
|
use Event::*;
|
||||||
match self {
|
match self {
|
||||||
SerdeParse(inner) => write!(f, "{}", inner),
|
SerdeParse(inner) => write!(f, "{}", inner),
|
||||||
NonNumId(inner) => write!(f, "ID could not be parsed: {}", inner),
|
NonNumId(inner) => write!(f, "ID could not be parsed: {}", inner),
|
||||||
|
@ -21,12 +21,12 @@ impl fmt::Display for EventErr {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<ParseIntError> for EventErr {
|
impl From<ParseIntError> for Event {
|
||||||
fn from(error: ParseIntError) -> Self {
|
fn from(error: ParseIntError) -> Self {
|
||||||
Self::NonNumId(error)
|
Self::NonNumId(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl From<serde_json::Error> for EventErr {
|
impl From<serde_json::Error> for Event {
|
||||||
fn from(error: serde_json::Error) -> Self {
|
fn from(error: serde_json::Error) -> Self {
|
||||||
Self::SerdeParse(error)
|
Self::SerdeParse(error)
|
||||||
}
|
}
|
|
@ -2,18 +2,21 @@ mod connection;
|
||||||
mod manager;
|
mod manager;
|
||||||
mod msg;
|
mod msg;
|
||||||
|
|
||||||
pub(crate) use connection::{RedisConn, RedisConnErr};
|
pub(self) use super::{Event, EventErr};
|
||||||
|
pub(self) use connection::RedisConn;
|
||||||
|
pub use manager::Error;
|
||||||
pub use manager::Manager;
|
pub use manager::Manager;
|
||||||
pub(crate) use manager::ManagerErr;
|
|
||||||
pub(crate) use msg::RedisParseErr;
|
|
||||||
|
|
||||||
pub(crate) enum RedisCmd {
|
use connection::RedisConnErr;
|
||||||
|
use msg::RedisParseErr;
|
||||||
|
|
||||||
|
enum RedisCmd {
|
||||||
Subscribe,
|
Subscribe,
|
||||||
Unsubscribe,
|
Unsubscribe,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RedisCmd {
|
impl RedisCmd {
|
||||||
pub(crate) fn into_sendable(self, tl: &str) -> (Vec<u8>, Vec<u8>) {
|
fn into_sendable(self, tl: &str) -> (Vec<u8>, Vec<u8>) {
|
||||||
match self {
|
match self {
|
||||||
RedisCmd::Subscribe => (
|
RedisCmd::Subscribe => (
|
||||||
[
|
[
|
||||||
|
|
|
@ -2,10 +2,11 @@ mod err;
|
||||||
pub(crate) use err::RedisConnErr;
|
pub(crate) use err::RedisConnErr;
|
||||||
|
|
||||||
use super::msg::{RedisParseErr, RedisParseOutput};
|
use super::msg::{RedisParseErr, RedisParseOutput};
|
||||||
use super::{ManagerErr, RedisCmd};
|
use super::Error as ManagerErr;
|
||||||
|
use super::Event;
|
||||||
|
use super::RedisCmd;
|
||||||
use crate::config::Redis;
|
use crate::config::Redis;
|
||||||
use crate::event::Event;
|
use crate::request::Timeline;
|
||||||
use crate::request::{Stream, Timeline};
|
|
||||||
|
|
||||||
use futures::{Async, Poll};
|
use futures::{Async, Poll};
|
||||||
use lru::LruCache;
|
use lru::LruCache;
|
||||||
|
@ -18,7 +19,7 @@ use std::time::Duration;
|
||||||
type Result<T> = std::result::Result<T, RedisConnErr>;
|
type Result<T> = std::result::Result<T, RedisConnErr>;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) struct RedisConn {
|
pub(super) struct RedisConn {
|
||||||
primary: TcpStream,
|
primary: TcpStream,
|
||||||
secondary: TcpStream,
|
secondary: TcpStream,
|
||||||
redis_namespace: Option<String>,
|
redis_namespace: Option<String>,
|
||||||
|
@ -29,7 +30,7 @@ pub(crate) struct RedisConn {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RedisConn {
|
impl RedisConn {
|
||||||
pub(crate) fn new(redis_cfg: &Redis) -> Result<Self> {
|
pub(super) fn new(redis_cfg: &Redis) -> Result<Self> {
|
||||||
let addr = [&*redis_cfg.host, ":", &*redis_cfg.port.to_string()].concat();
|
let addr = [&*redis_cfg.host, ":", &*redis_cfg.port.to_string()].concat();
|
||||||
|
|
||||||
let conn = Self::new_connection(&addr, redis_cfg.password.as_ref())?;
|
let conn = Self::new_connection(&addr, redis_cfg.password.as_ref())?;
|
||||||
|
@ -50,7 +51,7 @@ impl RedisConn {
|
||||||
Ok(redis_conn)
|
Ok(redis_conn)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn poll_redis(&mut self) -> Poll<Option<(Timeline, Event)>, ManagerErr> {
|
pub(super) fn poll_redis(&mut self) -> Poll<Option<(Timeline, Event)>, ManagerErr> {
|
||||||
loop {
|
loop {
|
||||||
match self.primary.read(&mut self.redis_input[self.cursor..]) {
|
match self.primary.read(&mut self.redis_input[self.cursor..]) {
|
||||||
Ok(n) => {
|
Ok(n) => {
|
||||||
|
@ -111,18 +112,15 @@ impl RedisConn {
|
||||||
res
|
res
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn update_cache(&mut self, hashtag: String, id: i64) {
|
pub(super) fn update_cache(&mut self, hashtag: String, id: i64) {
|
||||||
self.tag_id_cache.put(hashtag.clone(), id);
|
self.tag_id_cache.put(hashtag.clone(), id);
|
||||||
self.tag_name_cache.put(id, hashtag);
|
self.tag_name_cache.put(id, hashtag);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn send_cmd(&mut self, cmd: RedisCmd, timeline: &Timeline) -> Result<()> {
|
pub(crate) fn send_cmd(&mut self, cmd: RedisCmd, timeline: &Timeline) -> Result<()> {
|
||||||
let hashtag = match timeline {
|
let hashtag = timeline.tag().and_then(|id| self.tag_name_cache.get(&id));
|
||||||
Timeline(Stream::Hashtag(id), _, _) => self.tag_name_cache.get(id),
|
|
||||||
_non_hashtag_timeline => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
let tl = timeline.to_redis_raw_timeline(hashtag)?;
|
let tl = timeline.to_redis_raw_timeline(hashtag)?;
|
||||||
|
|
||||||
let (primary_cmd, secondary_cmd) = cmd.into_sendable(&tl);
|
let (primary_cmd, secondary_cmd) = cmd.into_sendable(&tl);
|
||||||
self.primary.write_all(&primary_cmd)?;
|
self.primary.write_all(&primary_cmd)?;
|
||||||
self.secondary.write_all(&secondary_cmd)?;
|
self.secondary.write_all(&secondary_cmd)?;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::request::TimelineErr;
|
use crate::request;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -9,11 +9,11 @@ pub enum RedisConnErr {
|
||||||
IncorrectPassword(String),
|
IncorrectPassword(String),
|
||||||
MissingPassword,
|
MissingPassword,
|
||||||
NotRedis(String),
|
NotRedis(String),
|
||||||
TimelineErr(TimelineErr),
|
TimelineErr(request::TimelineErr),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RedisConnErr {
|
impl RedisConnErr {
|
||||||
pub(crate) fn with_addr<T: AsRef<str>>(address: T, inner: std::io::Error) -> Self {
|
pub(super) fn with_addr<T: AsRef<str>>(address: T, inner: std::io::Error) -> Self {
|
||||||
Self::ConnectionErr {
|
Self::ConnectionErr {
|
||||||
addr: address.as_ref().to_string(),
|
addr: address.as_ref().to_string(),
|
||||||
inner,
|
inner,
|
||||||
|
@ -57,8 +57,8 @@ impl fmt::Display for RedisConnErr {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<TimelineErr> for RedisConnErr {
|
impl From<request::TimelineErr> for RedisConnErr {
|
||||||
fn from(e: TimelineErr) -> RedisConnErr {
|
fn from(e: request::TimelineErr) -> RedisConnErr {
|
||||||
RedisConnErr::TimelineErr(e)
|
RedisConnErr::TimelineErr(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,12 +2,14 @@
|
||||||
//! polled by the correct `ClientAgent`. Also manages sububscriptions and
|
//! polled by the correct `ClientAgent`. Also manages sububscriptions and
|
||||||
//! unsubscriptions to/from Redis.
|
//! unsubscriptions to/from Redis.
|
||||||
mod err;
|
mod err;
|
||||||
pub(crate) use err::ManagerErr;
|
pub use err::Error;
|
||||||
|
|
||||||
|
use super::Event;
|
||||||
use super::{RedisCmd, RedisConn};
|
use super::{RedisCmd, RedisConn};
|
||||||
use crate::config;
|
use crate::config;
|
||||||
use crate::event::Event;
|
use crate::request::{Subscription, Timeline};
|
||||||
use crate::request::{Stream, Subscription, Timeline};
|
|
||||||
|
pub(self) use super::EventErr;
|
||||||
|
|
||||||
use futures::{Async, Stream as _Stream};
|
use futures::{Async, Stream as _Stream};
|
||||||
use hashbrown::HashMap;
|
use hashbrown::HashMap;
|
||||||
|
@ -15,7 +17,7 @@ use std::sync::{Arc, Mutex, MutexGuard, PoisonError};
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
use tokio::sync::{mpsc, watch};
|
use tokio::sync::{mpsc, watch};
|
||||||
|
|
||||||
type Result<T> = std::result::Result<T, ManagerErr>;
|
type Result<T> = std::result::Result<T, Error>;
|
||||||
|
|
||||||
/// The item that streams from Redis and is polled by the `ClientAgent`
|
/// The item that streams from Redis and is polled by the `ClientAgent`
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -50,7 +52,7 @@ impl Manager {
|
||||||
|
|
||||||
pub fn subscribe(&mut self, subscription: &Subscription) {
|
pub fn subscribe(&mut self, subscription: &Subscription) {
|
||||||
let (tag, tl) = (subscription.hashtag_name.clone(), subscription.timeline);
|
let (tag, tl) = (subscription.hashtag_name.clone(), subscription.timeline);
|
||||||
if let (Some(hashtag), Timeline(Stream::Hashtag(id), _, _)) = (tag, tl) {
|
if let (Some(hashtag), Some(id)) = (tag, tl.tag()) {
|
||||||
self.redis_connection.update_cache(hashtag, id);
|
self.redis_connection.update_cache(hashtag, id);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
use super::super::{RedisConnErr, RedisParseErr};
|
use super::super::{RedisConnErr, RedisParseErr};
|
||||||
use crate::event::{Event, EventErr};
|
use super::{Event, EventErr};
|
||||||
use crate::request::{Timeline, TimelineErr};
|
use crate::request::{Timeline, TimelineErr};
|
||||||
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum ManagerErr {
|
pub enum Error {
|
||||||
InvalidId,
|
InvalidId,
|
||||||
TimelineErr(TimelineErr),
|
TimelineErr(TimelineErr),
|
||||||
EventErr(EventErr),
|
EventErr(EventErr),
|
||||||
|
@ -13,11 +13,11 @@ pub enum ManagerErr {
|
||||||
ChannelSendErr(tokio::sync::watch::error::SendError<(Timeline, Event)>),
|
ChannelSendErr(tokio::sync::watch::error::SendError<(Timeline, Event)>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::error::Error for ManagerErr {}
|
impl std::error::Error for Error {}
|
||||||
|
|
||||||
impl fmt::Display for ManagerErr {
|
impl fmt::Display for Error {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||||
use ManagerErr::*;
|
use Error::*;
|
||||||
match self {
|
match self {
|
||||||
InvalidId => write!(
|
InvalidId => write!(
|
||||||
f,
|
f,
|
||||||
|
@ -33,31 +33,31 @@ impl fmt::Display for ManagerErr {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<tokio::sync::watch::error::SendError<(Timeline, Event)>> for ManagerErr {
|
impl From<tokio::sync::watch::error::SendError<(Timeline, Event)>> for Error {
|
||||||
fn from(error: tokio::sync::watch::error::SendError<(Timeline, Event)>) -> Self {
|
fn from(error: tokio::sync::watch::error::SendError<(Timeline, Event)>) -> Self {
|
||||||
Self::ChannelSendErr(error)
|
Self::ChannelSendErr(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<EventErr> for ManagerErr {
|
impl From<EventErr> for Error {
|
||||||
fn from(error: EventErr) -> Self {
|
fn from(error: EventErr) -> Self {
|
||||||
Self::EventErr(error)
|
Self::EventErr(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<RedisConnErr> for ManagerErr {
|
impl From<RedisConnErr> for Error {
|
||||||
fn from(e: RedisConnErr) -> Self {
|
fn from(e: RedisConnErr) -> Self {
|
||||||
Self::RedisConnErr(e)
|
Self::RedisConnErr(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<TimelineErr> for ManagerErr {
|
impl From<TimelineErr> for Error {
|
||||||
fn from(e: TimelineErr) -> Self {
|
fn from(e: TimelineErr) -> Self {
|
||||||
Self::TimelineErr(e)
|
Self::TimelineErr(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<RedisParseErr> for ManagerErr {
|
impl From<RedisParseErr> for Error {
|
||||||
fn from(e: RedisParseErr) -> Self {
|
fn from(e: RedisParseErr) -> Self {
|
||||||
Self::RedisParseErr(e)
|
Self::RedisParseErr(e)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
pub use sse::Sse;
|
pub use sse::Sse;
|
||||||
pub use ws::Ws;
|
pub use ws::Ws;
|
||||||
|
|
||||||
|
pub(self) use super::{Event, Payload};
|
||||||
|
|
||||||
mod sse;
|
mod sse;
|
||||||
mod ws;
|
mod ws;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::event::Event;
|
use super::{Event, Payload};
|
||||||
use crate::request::{Subscription, Timeline};
|
use crate::request::{Subscription, Timeline};
|
||||||
|
|
||||||
use futures::stream::Stream;
|
use futures::stream::Stream;
|
||||||
|
@ -18,41 +18,19 @@ impl Sse {
|
||||||
sse_rx: watch::Receiver<(Timeline, Event)>,
|
sse_rx: watch::Receiver<(Timeline, Event)>,
|
||||||
) -> impl Reply {
|
) -> impl Reply {
|
||||||
let target_timeline = subscription.timeline;
|
let target_timeline = subscription.timeline;
|
||||||
let allowed_langs = subscription.allowed_langs;
|
|
||||||
let blocks = subscription.blocks;
|
|
||||||
|
|
||||||
let event_stream = sse_rx
|
let event_stream = sse_rx
|
||||||
.filter(move |(timeline, _)| target_timeline == *timeline)
|
.filter(move |(timeline, _)| target_timeline == *timeline)
|
||||||
.filter_map(move |(timeline, event)| {
|
.filter_map(move |(_timeline, event)| {
|
||||||
use crate::event::{
|
match (event.update_payload(), event.dyn_update_payload()) {
|
||||||
CheckedEvent, CheckedEvent::Update, DynEvent, Event::*, EventKind,
|
(Some(update), _) if Sse::update_not_filtered(subscription.clone(), update) => {
|
||||||
};
|
event.to_warp_reply()
|
||||||
|
|
||||||
use crate::request::Stream::Public;
|
|
||||||
match event {
|
|
||||||
TypeSafe(Update { payload, queued_at }) => match timeline {
|
|
||||||
Timeline(Public, _, _) if payload.language_not(&allowed_langs) => None,
|
|
||||||
_ if payload.involves_any(&blocks) => None,
|
|
||||||
_ => Event::TypeSafe(CheckedEvent::Update { payload, queued_at })
|
|
||||||
.to_warp_reply(),
|
|
||||||
},
|
|
||||||
TypeSafe(non_update) => Event::TypeSafe(non_update).to_warp_reply(),
|
|
||||||
Dynamic(dyn_event) => {
|
|
||||||
if let EventKind::Update(s) = dyn_event.kind {
|
|
||||||
match timeline {
|
|
||||||
Timeline(Public, _, _) if s.language_not(&allowed_langs) => None,
|
|
||||||
_ if s.involves_any(&blocks) => None,
|
|
||||||
_ => Dynamic(DynEvent {
|
|
||||||
kind: EventKind::Update(s),
|
|
||||||
..dyn_event
|
|
||||||
})
|
|
||||||
.to_warp_reply(),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Ping => None, // pings handled automatically
|
(None, None) => event.to_warp_reply(), // send all non-updates
|
||||||
|
(_, Some(update)) if Sse::update_not_filtered(subscription.clone(), update) => {
|
||||||
|
event.to_warp_reply()
|
||||||
|
}
|
||||||
|
(_, _) => None,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then(move |res| {
|
.then(move |res| {
|
||||||
|
@ -69,4 +47,23 @@ impl Sse {
|
||||||
.stream(event_stream),
|
.stream(event_stream),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn update_not_filtered(subscription: Subscription, update: &impl Payload) -> bool {
|
||||||
|
let blocks = &subscription.blocks;
|
||||||
|
let allowed_langs = &subscription.allowed_langs;
|
||||||
|
|
||||||
|
match subscription.timeline {
|
||||||
|
tl if tl.is_public()
|
||||||
|
&& !update.language_unset()
|
||||||
|
&& !allowed_langs.is_empty()
|
||||||
|
&& !allowed_langs.contains(&update.language()) =>
|
||||||
|
{
|
||||||
|
false
|
||||||
|
}
|
||||||
|
_ if !blocks.blocked_users.is_disjoint(&update.involved_users()) => false,
|
||||||
|
_ if blocks.blocking_users.contains(update.author()) => false,
|
||||||
|
_ if blocks.blocked_domains.contains(update.sent_from()) => false,
|
||||||
|
_ => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
use crate::event::Event;
|
use super::{Event, Payload};
|
||||||
use crate::request::{Subscription, Timeline};
|
use crate::request::{Subscription, Timeline};
|
||||||
|
|
||||||
use futures::{future::Future, stream::Stream};
|
use futures::{future::Future, stream::Stream};
|
||||||
use tokio::sync::{mpsc, watch};
|
use tokio::sync::{mpsc, watch};
|
||||||
use warp::ws::{Message, WebSocket};
|
use warp::ws::{Message, WebSocket};
|
||||||
|
|
||||||
|
type Result<T> = std::result::Result<T, ()>;
|
||||||
|
|
||||||
pub struct Ws {
|
pub struct Ws {
|
||||||
unsubscribe_tx: mpsc::UnboundedSender<Timeline>,
|
unsubscribe_tx: mpsc::UnboundedSender<Timeline>,
|
||||||
subscription: Subscription,
|
subscription: Subscription,
|
||||||
|
@ -52,40 +54,38 @@ impl Ws {
|
||||||
|
|
||||||
incoming_events.for_each(move |(tl, event)| {
|
incoming_events.for_each(move |(tl, event)| {
|
||||||
if matches!(event, Event::Ping) {
|
if matches!(event, Event::Ping) {
|
||||||
self.send_msg(&event)
|
self.send_msg(&event)?
|
||||||
} else if target_timeline == tl {
|
} else if target_timeline == tl {
|
||||||
use crate::event::{CheckedEvent::Update, Event::*, EventKind};
|
match (event.update_payload(), event.dyn_update_payload()) {
|
||||||
use crate::request::Stream::Public;
|
(Some(update), _) => self.send_or_filter(tl, &event, update)?,
|
||||||
let blocks = &self.subscription.blocks;
|
(None, None) => self.send_msg(&event)?, // send all non-updates
|
||||||
let allowed_langs = &self.subscription.allowed_langs;
|
(_, Some(dyn_update)) => self.send_or_filter(tl, &event, dyn_update)?,
|
||||||
|
|
||||||
match event {
|
|
||||||
TypeSafe(Update { payload, queued_at }) => match tl {
|
|
||||||
Timeline(Public, _, _) if payload.language_not(allowed_langs) => Ok(()),
|
|
||||||
_ if payload.involves_any(&blocks) => Ok(()),
|
|
||||||
_ => self.send_msg(&TypeSafe(Update { payload, queued_at })),
|
|
||||||
},
|
|
||||||
TypeSafe(non_update) => self.send_msg(&TypeSafe(non_update)),
|
|
||||||
Dynamic(dyn_event) => {
|
|
||||||
if let EventKind::Update(s) = dyn_event.kind.clone() {
|
|
||||||
match tl {
|
|
||||||
Timeline(Public, _, _) if s.language_not(allowed_langs) => Ok(()),
|
|
||||||
_ if s.involves_any(&blocks) => Ok(()),
|
|
||||||
_ => self.send_msg(&Dynamic(dyn_event)),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
self.send_msg(&Dynamic(dyn_event))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ping => unreachable!(), // handled pings above
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn send_msg(&mut self, event: &Event) -> Result<(), ()> {
|
fn send_or_filter(&mut self, tl: Timeline, event: &Event, update: &impl Payload) -> Result<()> {
|
||||||
|
let blocks = &self.subscription.blocks;
|
||||||
|
let allowed_langs = &self.subscription.allowed_langs;
|
||||||
|
const SKIP: Result<()> = Ok(());
|
||||||
|
match tl {
|
||||||
|
tl if tl.is_public()
|
||||||
|
&& !update.language_unset()
|
||||||
|
&& !allowed_langs.is_empty()
|
||||||
|
&& !allowed_langs.contains(&update.language()) =>
|
||||||
|
{
|
||||||
|
SKIP
|
||||||
|
}
|
||||||
|
_ if !blocks.blocked_users.is_disjoint(&update.involved_users()) => SKIP,
|
||||||
|
_ if blocks.blocking_users.contains(update.author()) => SKIP,
|
||||||
|
_ if blocks.blocked_domains.contains(update.sent_from()) => SKIP,
|
||||||
|
_ => Ok(self.send_msg(&event)?),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_msg(&mut self, event: &Event) -> Result<()> {
|
||||||
let txt = &event.to_json_string();
|
let txt = &event.to_json_string();
|
||||||
let tl = self.subscription.timeline;
|
let tl = self.subscription.timeline;
|
||||||
let mut channel = self.ws_tx.clone().ok_or(())?;
|
let mut channel = self.ws_tx.clone().ok_or(())?;
|
||||||
|
|
Loading…
Reference in New Issue