source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0"
-[[package]]
-name = "dotenv"
-version = "0.15.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f"
-
[[package]]
name = "dtoa"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+[[package]]
+name = "lemmy_api"
+version = "0.1.0"
+dependencies = [
+ "actix",
+ "actix-rt",
+ "actix-web",
+ "anyhow",
+ "async-trait",
+ "awc",
+ "background-jobs",
+ "base64 0.12.3",
+ "bcrypt",
+ "captcha",
+ "chrono",
+ "diesel",
+ "futures",
+ "http",
+ "http-signature-normalization-actix",
+ "itertools",
+ "jsonwebtoken",
+ "lazy_static",
+ "lemmy_apub",
+ "lemmy_db",
+ "lemmy_rate_limit",
+ "lemmy_structs",
+ "lemmy_utils",
+ "lemmy_websocket",
+ "log",
+ "openssl",
+ "percent-encoding",
+ "rand 0.7.3",
+ "reqwest",
+ "serde 1.0.116",
+ "serde_json",
+ "sha2",
+ "strum",
+ "strum_macros",
+ "thiserror",
+ "tokio",
+ "url",
+ "uuid 0.8.1",
+]
+
+[[package]]
+name = "lemmy_apub"
+version = "0.1.0"
+dependencies = [
+ "activitystreams",
+ "activitystreams-ext",
+ "actix",
+ "actix-rt",
+ "actix-web",
+ "anyhow",
+ "async-trait",
+ "awc",
+ "background-jobs",
+ "base64 0.12.3",
+ "bcrypt",
+ "chrono",
+ "diesel",
+ "futures",
+ "http",
+ "http-signature-normalization-actix",
+ "itertools",
+ "lazy_static",
+ "lemmy_db",
+ "lemmy_structs",
+ "lemmy_utils",
+ "lemmy_websocket",
+ "log",
+ "openssl",
+ "percent-encoding",
+ "rand 0.7.3",
+ "reqwest",
+ "serde 1.0.116",
+ "serde_json",
+ "sha2",
+ "strum",
+ "strum_macros",
+ "thiserror",
+ "tokio",
+ "url",
+ "uuid 0.8.1",
+]
+
[[package]]
name = "lemmy_db"
version = "0.1.0"
name = "lemmy_server"
version = "0.0.1"
dependencies = [
- "activitystreams",
- "activitystreams-ext",
"actix",
"actix-files",
- "actix-rt",
"actix-web",
"actix-web-actors",
"anyhow",
- "async-trait",
"awc",
- "background-jobs",
- "base64 0.12.3",
- "bcrypt",
- "captcha",
"cargo-husky",
"chrono",
"diesel",
"diesel_migrations",
- "dotenv",
"env_logger",
- "futures",
- "http",
"http-signature-normalization-actix",
- "itertools",
- "jsonwebtoken",
"lazy_static",
+ "lemmy_api",
+ "lemmy_apub",
"lemmy_db",
"lemmy_rate_limit",
"lemmy_structs",
"lemmy_utils",
+ "lemmy_websocket",
"log",
"openssl",
- "percent-encoding",
- "rand 0.7.3",
"reqwest",
"rss",
"serde 1.0.116",
- "serde_json",
"sha2",
"strum",
- "strum_macros",
- "thiserror",
"tokio",
"url",
- "uuid 0.8.1",
]
[[package]]
name = "lemmy_structs"
version = "0.1.0"
dependencies = [
- "actix",
"actix-web",
"chrono",
"diesel",
"lemmy_utils",
"log",
"serde 1.0.116",
- "strum",
- "strum_macros",
+ "serde_json",
]
[[package]]
"openssl",
"rand 0.7.3",
"regex",
+ "reqwest",
"serde 1.0.116",
"serde_json",
"thiserror",
"url",
]
+[[package]]
+name = "lemmy_websocket"
+version = "0.1.0"
+dependencies = [
+ "actix",
+ "anyhow",
+ "background-jobs",
+ "chrono",
+ "diesel",
+ "lemmy_db",
+ "lemmy_rate_limit",
+ "lemmy_structs",
+ "lemmy_utils",
+ "log",
+ "rand 0.7.3",
+ "reqwest",
+ "serde 1.0.116",
+ "serde_json",
+ "strum",
+ "strum_macros",
+ "tokio",
+]
+
[[package]]
name = "lettre"
version = "0.9.3"
[workspace]
members = [
+ "lemmy_api",
+ "lemmy_apub",
"lemmy_utils",
"lemmy_db",
"lemmy_structs",
"lemmy_rate_limit",
+ "lemmy_websocket",
]
[dependencies]
+lemmy_api = { path = "./lemmy_api" }
+lemmy_apub = { path = "./lemmy_apub" }
lemmy_utils = { path = "./lemmy_utils" }
lemmy_db = { path = "./lemmy_db" }
lemmy_structs = { path = "./lemmy_structs" }
lemmy_rate_limit = { path = "./lemmy_rate_limit" }
+lemmy_websocket = { path = "./lemmy_websocket" }
diesel = "1.4"
diesel_migrations = "1.4"
-dotenv = "0.15"
-activitystreams = "0.7.0-alpha.4"
-activitystreams-ext = "0.1.0-alpha.2"
-bcrypt = "0.8"
chrono = { version = "0.4", features = ["serde"] }
-serde_json = { version = "1.0", features = ["preserve_order"]}
serde = { version = "1.0", features = ["derive"] }
actix = "0.10"
actix-web = { version = "3.0", default-features = false, features = ["rustls"] }
actix-files = { version = "0.3", default-features = false }
actix-web-actors = { version = "3.0", default-features = false }
-actix-rt = { version = "1.1", default-features = false }
awc = { version = "2.0", default-features = false }
log = "0.4"
env_logger = "0.7"
-rand = "0.7"
strum = "0.19"
-strum_macros = "0.19"
-jsonwebtoken = "7.0"
lazy_static = "1.3"
rss = "1.9"
url = { version = "2.1", features = ["serde"] }
-percent-encoding = "2.1"
openssl = "0.10"
-http = "0.2"
http-signature-normalization-actix = { version = "0.4", default-features = false, features = ["sha-2"] }
-base64 = "0.12"
tokio = "0.2"
-futures = "0.3"
-itertools = "0.9"
-uuid = { version = "0.8", features = ["serde", "v4"] }
sha2 = "0.9"
-async-trait = "0.1"
-captcha = "0.0"
anyhow = "1.0"
-thiserror = "1.0"
-background-jobs = " 0.8"
reqwest = { version = "0.10", features = ["json"] }
[dev-dependencies.cargo-husky]
lemmy_utils/src/ \
lemmy_structs/src/ \
lemmy_rate_limit/src/ \
+ lemmy_api/src/ \
+ lemmy_apub/src/ \
+ lemmy_websocket/src/ \
lemmy
# Copy the cargo tomls
COPY lemmy_utils/Cargo.toml ./lemmy_utils/
COPY lemmy_structs/Cargo.toml ./lemmy_structs/
COPY lemmy_rate_limit/Cargo.toml ./lemmy_rate_limit/
+COPY lemmy_api/Cargo.toml ./lemmy_api/
+COPY lemmy_apub/Cargo.toml ./lemmy_apub/
+COPY lemmy_websocket/Cargo.toml ./lemmy_websocket/
# Cache the deps
RUN cargo build-deps
COPY lemmy_utils/src/ ./lemmy_utils/src/
COPY lemmy_structs/src/ ./lemmy_structs/src/
COPY lemmy_rate_limit/src/ ./lemmy_rate_limit/src/
+COPY lemmy_api/src/ ./lemmy_api/src/
+COPY lemmy_apub/src/ ./lemmy_apub/src/
+COPY lemmy_websocket/src/ ./lemmy_websocket/src/
COPY migrations ./migrations/
# Build for debug
COPY lemmy_utils ./lemmy_utils
COPY lemmy_structs ./lemmy_structs
COPY lemmy_rate_limit ./lemmy_rate_limit
+COPY lemmy_api ./lemmy_api
+COPY lemmy_apub ./lemmy_apub
+COPY lemmy_websocket ./lemmy_websocket
RUN mkdir -p ./src/bin \
&& echo 'fn main() { println!("Dummy") }' > ./src/bin/main.rs
RUN cargo build --release
# Setting the version on the front end
cd ../../
# Setting the version on the backend
-echo "pub const VERSION: &str = \"$new_tag\";" > "src/version.rs"
-git add "src/version.rs"
+echo "pub const VERSION: &str = \"$new_tag\";" > "lemmy_api/src/version.rs"
+git add "lemmy_api/src/version.rs"
# Setting the version for Ansible
echo $new_tag > "ansible/VERSION"
git add "ansible/VERSION"
--- /dev/null
+[package]
+name = "lemmy_api"
+version = "0.1.0"
+authors = ["Felix Ableitner <me@nutomic.com>"]
+edition = "2018"
+
+[lib]
+name = "lemmy_api"
+path = "src/lib.rs"
+
+[dependencies]
+lemmy_apub = { path = "../lemmy_apub" }
+lemmy_utils = { path = "../lemmy_utils" }
+lemmy_db = { path = "../lemmy_db" }
+lemmy_structs = { path = "../lemmy_structs" }
+lemmy_rate_limit = { path = "../lemmy_rate_limit" }
+lemmy_websocket = { path = "../lemmy_websocket" }
+diesel = "1.4"
+bcrypt = "0.8"
+chrono = { version = "0.4", features = ["serde"] }
+serde_json = { version = "1.0", features = ["preserve_order"]}
+serde = { version = "1.0", features = ["derive"] }
+actix = "0.10"
+actix-web = { version = "3.0", default-features = false }
+actix-rt = { version = "1.1", default-features = false }
+awc = { version = "2.0", default-features = false }
+log = "0.4"
+rand = "0.7"
+strum = "0.19"
+strum_macros = "0.19"
+jsonwebtoken = "7.0"
+lazy_static = "1.3"
+url = { version = "2.1", features = ["serde"] }
+percent-encoding = "2.1"
+openssl = "0.10"
+http = "0.2"
+http-signature-normalization-actix = { version = "0.4", default-features = false, features = ["sha-2"] }
+base64 = "0.12"
+tokio = "0.2"
+futures = "0.3"
+itertools = "0.9"
+uuid = { version = "0.8", features = ["serde", "v4"] }
+sha2 = "0.9"
+async-trait = "0.1"
+captcha = "0.0"
+anyhow = "1.0"
+thiserror = "1.0"
+background-jobs = " 0.8"
+reqwest = { version = "0.10", features = ["json"] }
use crate::{
- api::{
- check_community_ban,
- get_post,
- get_user_from_jwt,
- get_user_from_jwt_opt,
- is_mod_or_admin,
- Perform,
- },
- apub::{ApubLikeableType, ApubObjectType},
- LemmyContext,
+ check_community_ban,
+ get_post,
+ get_user_from_jwt,
+ get_user_from_jwt_opt,
+ is_mod_or_admin,
+ Perform,
};
use actix_web::web::Data;
+use lemmy_apub::{ApubLikeableType, ApubObjectType};
use lemmy_db::{
comment::*,
comment_view::*,
Saveable,
SortType,
};
-use lemmy_structs::{
- blocking,
- comment::*,
- send_local_notifs,
- websocket::{SendComment, UserOperation},
-};
+use lemmy_structs::{blocking, comment::*, send_local_notifs};
use lemmy_utils::{
apub::{make_apub_endpoint, EndpointType},
utils::{remove_slurs, scrape_text_for_mentions},
ConnectionId,
LemmyError,
};
+use lemmy_websocket::{messages::SendComment, LemmyContext, UserOperation};
use std::str::FromStr;
#[async_trait::async_trait(?Send)]
-use crate::{
- api::{get_user_from_jwt, get_user_from_jwt_opt, is_admin, is_mod_or_admin, Perform},
- apub::ActorType,
- LemmyContext,
-};
+use crate::{get_user_from_jwt, get_user_from_jwt_opt, is_admin, is_mod_or_admin, Perform};
use actix_web::web::Data;
use anyhow::Context;
+use lemmy_apub::ActorType;
use lemmy_db::{
comment::Comment,
comment_view::CommentQueryBuilder,
Joinable,
SortType,
};
-use lemmy_structs::{
- blocking,
- community::*,
- websocket::{
- GetCommunityUsersOnline,
- JoinCommunityRoom,
- SendCommunityRoomMessage,
- UserOperation,
- },
-};
+use lemmy_structs::{blocking, community::*};
use lemmy_utils::{
apub::{generate_actor_keypair, make_apub_endpoint, EndpointType},
location_info,
ConnectionId,
LemmyError,
};
+use lemmy_websocket::{
+ messages::{GetCommunityUsersOnline, JoinCommunityRoom, SendCommunityRoomMessage},
+ LemmyContext,
+ UserOperation,
+};
use std::str::FromStr;
#[async_trait::async_trait(?Send)]
--- /dev/null
+use crate::claims::Claims;
+use actix_web::{web, web::Data};
+use anyhow::anyhow;
+use lemmy_db::{
+ community::Community,
+ community_view::CommunityUserBanView,
+ post::Post,
+ user::User_,
+ Crud,
+ DbPool,
+};
+use lemmy_structs::{blocking, comment::*, community::*, post::*, site::*, user::*};
+use lemmy_utils::{
+ apub::get_apub_protocol_string,
+ request::{retry, RecvError},
+ settings::Settings,
+ APIError,
+ ConnectionId,
+ LemmyError,
+};
+use lemmy_websocket::{serialize_websocket_message, LemmyContext, UserOperation};
+use log::error;
+use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC};
+use reqwest::Client;
+use serde::Deserialize;
+use std::process::Command;
+
+pub mod claims;
+pub mod comment;
+pub mod community;
+pub mod post;
+pub mod site;
+pub mod user;
+pub mod version;
+
+#[async_trait::async_trait(?Send)]
+pub trait Perform {
+ type Response: serde::ser::Serialize + Send;
+
+ async fn perform(
+ &self,
+ context: &Data<LemmyContext>,
+ websocket_id: Option<ConnectionId>,
+ ) -> Result<Self::Response, LemmyError>;
+}
+
+pub(in crate) async fn is_mod_or_admin(
+ pool: &DbPool,
+ user_id: i32,
+ community_id: i32,
+) -> Result<(), LemmyError> {
+ let is_mod_or_admin = blocking(pool, move |conn| {
+ Community::is_mod_or_admin(conn, user_id, community_id)
+ })
+ .await?;
+ if !is_mod_or_admin {
+ return Err(APIError::err("not_a_mod_or_admin").into());
+ }
+ Ok(())
+}
+pub async fn is_admin(pool: &DbPool, user_id: i32) -> Result<(), LemmyError> {
+ let user = blocking(pool, move |conn| User_::read(conn, user_id)).await??;
+ if !user.admin {
+ return Err(APIError::err("not_an_admin").into());
+ }
+ Ok(())
+}
+
+pub(in crate) async fn get_post(post_id: i32, pool: &DbPool) -> Result<Post, LemmyError> {
+ match blocking(pool, move |conn| Post::read(conn, post_id)).await? {
+ Ok(post) => Ok(post),
+ Err(_e) => Err(APIError::err("couldnt_find_post").into()),
+ }
+}
+
+pub(in crate) async fn get_user_from_jwt(jwt: &str, pool: &DbPool) -> Result<User_, LemmyError> {
+ let claims = match Claims::decode(&jwt) {
+ Ok(claims) => claims.claims,
+ Err(_e) => return Err(APIError::err("not_logged_in").into()),
+ };
+ let user_id = claims.id;
+ let user = blocking(pool, move |conn| User_::read(conn, user_id)).await??;
+ // Check for a site ban
+ if user.banned {
+ return Err(APIError::err("site_ban").into());
+ }
+ Ok(user)
+}
+
+pub(in crate) async fn get_user_from_jwt_opt(
+ jwt: &Option<String>,
+ pool: &DbPool,
+) -> Result<Option<User_>, LemmyError> {
+ match jwt {
+ Some(jwt) => Ok(Some(get_user_from_jwt(jwt, pool).await?)),
+ None => Ok(None),
+ }
+}
+
+pub(in crate) async fn check_community_ban(
+ user_id: i32,
+ community_id: i32,
+ pool: &DbPool,
+) -> Result<(), LemmyError> {
+ let is_banned = move |conn: &'_ _| CommunityUserBanView::get(conn, user_id, community_id).is_ok();
+ if blocking(pool, is_banned).await? {
+ Err(APIError::err("community_ban").into())
+ } else {
+ Ok(())
+ }
+}
+
+pub async fn match_websocket_operation(
+ context: LemmyContext,
+ id: ConnectionId,
+ op: UserOperation,
+ data: &str,
+) -> Result<String, LemmyError> {
+ match op {
+ // User ops
+ UserOperation::Login => do_websocket_operation::<Login>(context, id, op, data).await,
+ UserOperation::Register => do_websocket_operation::<Register>(context, id, op, data).await,
+ UserOperation::GetCaptcha => do_websocket_operation::<GetCaptcha>(context, id, op, data).await,
+ UserOperation::GetUserDetails => {
+ do_websocket_operation::<GetUserDetails>(context, id, op, data).await
+ }
+ UserOperation::GetReplies => do_websocket_operation::<GetReplies>(context, id, op, data).await,
+ UserOperation::AddAdmin => do_websocket_operation::<AddAdmin>(context, id, op, data).await,
+ UserOperation::BanUser => do_websocket_operation::<BanUser>(context, id, op, data).await,
+ UserOperation::GetUserMentions => {
+ do_websocket_operation::<GetUserMentions>(context, id, op, data).await
+ }
+ UserOperation::MarkUserMentionAsRead => {
+ do_websocket_operation::<MarkUserMentionAsRead>(context, id, op, data).await
+ }
+ UserOperation::MarkAllAsRead => {
+ do_websocket_operation::<MarkAllAsRead>(context, id, op, data).await
+ }
+ UserOperation::DeleteAccount => {
+ do_websocket_operation::<DeleteAccount>(context, id, op, data).await
+ }
+ UserOperation::PasswordReset => {
+ do_websocket_operation::<PasswordReset>(context, id, op, data).await
+ }
+ UserOperation::PasswordChange => {
+ do_websocket_operation::<PasswordChange>(context, id, op, data).await
+ }
+ UserOperation::UserJoin => do_websocket_operation::<UserJoin>(context, id, op, data).await,
+ UserOperation::PostJoin => do_websocket_operation::<PostJoin>(context, id, op, data).await,
+ UserOperation::CommunityJoin => {
+ do_websocket_operation::<CommunityJoin>(context, id, op, data).await
+ }
+ UserOperation::SaveUserSettings => {
+ do_websocket_operation::<SaveUserSettings>(context, id, op, data).await
+ }
+
+ // Private Message ops
+ UserOperation::CreatePrivateMessage => {
+ do_websocket_operation::<CreatePrivateMessage>(context, id, op, data).await
+ }
+ UserOperation::EditPrivateMessage => {
+ do_websocket_operation::<EditPrivateMessage>(context, id, op, data).await
+ }
+ UserOperation::DeletePrivateMessage => {
+ do_websocket_operation::<DeletePrivateMessage>(context, id, op, data).await
+ }
+ UserOperation::MarkPrivateMessageAsRead => {
+ do_websocket_operation::<MarkPrivateMessageAsRead>(context, id, op, data).await
+ }
+ UserOperation::GetPrivateMessages => {
+ do_websocket_operation::<GetPrivateMessages>(context, id, op, data).await
+ }
+
+ // Site ops
+ UserOperation::GetModlog => do_websocket_operation::<GetModlog>(context, id, op, data).await,
+ UserOperation::CreateSite => do_websocket_operation::<CreateSite>(context, id, op, data).await,
+ UserOperation::EditSite => do_websocket_operation::<EditSite>(context, id, op, data).await,
+ UserOperation::GetSite => do_websocket_operation::<GetSite>(context, id, op, data).await,
+ UserOperation::GetSiteConfig => {
+ do_websocket_operation::<GetSiteConfig>(context, id, op, data).await
+ }
+ UserOperation::SaveSiteConfig => {
+ do_websocket_operation::<SaveSiteConfig>(context, id, op, data).await
+ }
+ UserOperation::Search => do_websocket_operation::<Search>(context, id, op, data).await,
+ UserOperation::TransferCommunity => {
+ do_websocket_operation::<TransferCommunity>(context, id, op, data).await
+ }
+ UserOperation::TransferSite => {
+ do_websocket_operation::<TransferSite>(context, id, op, data).await
+ }
+ UserOperation::ListCategories => {
+ do_websocket_operation::<ListCategories>(context, id, op, data).await
+ }
+
+ // Community ops
+ UserOperation::GetCommunity => {
+ do_websocket_operation::<GetCommunity>(context, id, op, data).await
+ }
+ UserOperation::ListCommunities => {
+ do_websocket_operation::<ListCommunities>(context, id, op, data).await
+ }
+ UserOperation::CreateCommunity => {
+ do_websocket_operation::<CreateCommunity>(context, id, op, data).await
+ }
+ UserOperation::EditCommunity => {
+ do_websocket_operation::<EditCommunity>(context, id, op, data).await
+ }
+ UserOperation::DeleteCommunity => {
+ do_websocket_operation::<DeleteCommunity>(context, id, op, data).await
+ }
+ UserOperation::RemoveCommunity => {
+ do_websocket_operation::<RemoveCommunity>(context, id, op, data).await
+ }
+ UserOperation::FollowCommunity => {
+ do_websocket_operation::<FollowCommunity>(context, id, op, data).await
+ }
+ UserOperation::GetFollowedCommunities => {
+ do_websocket_operation::<GetFollowedCommunities>(context, id, op, data).await
+ }
+ UserOperation::BanFromCommunity => {
+ do_websocket_operation::<BanFromCommunity>(context, id, op, data).await
+ }
+ UserOperation::AddModToCommunity => {
+ do_websocket_operation::<AddModToCommunity>(context, id, op, data).await
+ }
+
+ // Post ops
+ UserOperation::CreatePost => do_websocket_operation::<CreatePost>(context, id, op, data).await,
+ UserOperation::GetPost => do_websocket_operation::<GetPost>(context, id, op, data).await,
+ UserOperation::GetPosts => do_websocket_operation::<GetPosts>(context, id, op, data).await,
+ UserOperation::EditPost => do_websocket_operation::<EditPost>(context, id, op, data).await,
+ UserOperation::DeletePost => do_websocket_operation::<DeletePost>(context, id, op, data).await,
+ UserOperation::RemovePost => do_websocket_operation::<RemovePost>(context, id, op, data).await,
+ UserOperation::LockPost => do_websocket_operation::<LockPost>(context, id, op, data).await,
+ UserOperation::StickyPost => do_websocket_operation::<StickyPost>(context, id, op, data).await,
+ UserOperation::CreatePostLike => {
+ do_websocket_operation::<CreatePostLike>(context, id, op, data).await
+ }
+ UserOperation::SavePost => do_websocket_operation::<SavePost>(context, id, op, data).await,
+
+ // Comment ops
+ UserOperation::CreateComment => {
+ do_websocket_operation::<CreateComment>(context, id, op, data).await
+ }
+ UserOperation::EditComment => {
+ do_websocket_operation::<EditComment>(context, id, op, data).await
+ }
+ UserOperation::DeleteComment => {
+ do_websocket_operation::<DeleteComment>(context, id, op, data).await
+ }
+ UserOperation::RemoveComment => {
+ do_websocket_operation::<RemoveComment>(context, id, op, data).await
+ }
+ UserOperation::MarkCommentAsRead => {
+ do_websocket_operation::<MarkCommentAsRead>(context, id, op, data).await
+ }
+ UserOperation::SaveComment => {
+ do_websocket_operation::<SaveComment>(context, id, op, data).await
+ }
+ UserOperation::GetComments => {
+ do_websocket_operation::<GetComments>(context, id, op, data).await
+ }
+ UserOperation::CreateCommentLike => {
+ do_websocket_operation::<CreateCommentLike>(context, id, op, data).await
+ }
+ }
+}
+
+async fn do_websocket_operation<'a, 'b, Data>(
+ context: LemmyContext,
+ id: ConnectionId,
+ op: UserOperation,
+ data: &str,
+) -> Result<String, LemmyError>
+where
+ for<'de> Data: Deserialize<'de> + 'a,
+ Data: Perform,
+{
+ let parsed_data: Data = serde_json::from_str(&data)?;
+ let res = parsed_data
+ .perform(&web::Data::new(context), Some(id))
+ .await?;
+ serialize_websocket_message(&op, &res)
+}
+
+pub(crate) fn captcha_espeak_wav_base64(captcha: &str) -> Result<String, LemmyError> {
+ let mut built_text = String::new();
+
+ // Building proper speech text for espeak
+ for mut c in captcha.chars() {
+ let new_str = if c.is_alphabetic() {
+ if c.is_lowercase() {
+ c.make_ascii_uppercase();
+ format!("lower case {} ... ", c)
+ } else {
+ c.make_ascii_uppercase();
+ format!("capital {} ... ", c)
+ }
+ } else {
+ format!("{} ...", c)
+ };
+
+ built_text.push_str(&new_str);
+ }
+
+ espeak_wav_base64(&built_text)
+}
+
+pub(crate) fn espeak_wav_base64(text: &str) -> Result<String, LemmyError> {
+ // Make a temp file path
+ let uuid = uuid::Uuid::new_v4().to_string();
+ let file_path = format!("/tmp/lemmy_espeak_{}.wav", &uuid);
+
+ // Write the wav file
+ Command::new("espeak")
+ .arg("-w")
+ .arg(&file_path)
+ .arg(text)
+ .status()?;
+
+ // Read the wav file bytes
+ let bytes = std::fs::read(&file_path)?;
+
+ // Delete the file
+ std::fs::remove_file(file_path)?;
+
+ // Convert to base64
+ let base64 = base64::encode(bytes);
+
+ Ok(base64)
+}
+
+#[derive(Deserialize, Debug)]
+pub(crate) struct IframelyResponse {
+ title: Option<String>,
+ description: Option<String>,
+ thumbnail_url: Option<String>,
+ html: Option<String>,
+}
+
+pub(crate) async fn fetch_iframely(
+ client: &Client,
+ url: &str,
+) -> Result<IframelyResponse, LemmyError> {
+ let fetch_url = format!("http://iframely/oembed?url={}", url);
+
+ let response = retry(|| client.get(&fetch_url).send()).await?;
+
+ let res: IframelyResponse = response
+ .json()
+ .await
+ .map_err(|e| RecvError(e.to_string()))?;
+ Ok(res)
+}
+
+#[derive(Deserialize, Debug, Clone)]
+pub(crate) struct PictrsResponse {
+ files: Vec<PictrsFile>,
+ msg: String,
+}
+
+#[derive(Deserialize, Debug, Clone)]
+pub(crate) struct PictrsFile {
+ file: String,
+ delete_token: String,
+}
+
+pub(crate) async fn fetch_pictrs(
+ client: &Client,
+ image_url: &str,
+) -> Result<PictrsResponse, LemmyError> {
+ is_image_content_type(client, image_url).await?;
+
+ let fetch_url = format!(
+ "http://pictrs:8080/image/download?url={}",
+ utf8_percent_encode(image_url, NON_ALPHANUMERIC) // TODO this might not be needed
+ );
+
+ let response = retry(|| client.get(&fetch_url).send()).await?;
+
+ let response: PictrsResponse = response
+ .json()
+ .await
+ .map_err(|e| RecvError(e.to_string()))?;
+
+ if response.msg == "ok" {
+ Ok(response)
+ } else {
+ Err(anyhow!("{}", &response.msg).into())
+ }
+}
+
+async fn fetch_iframely_and_pictrs_data(
+ client: &Client,
+ url: Option<String>,
+) -> (
+ Option<String>,
+ Option<String>,
+ Option<String>,
+ Option<String>,
+) {
+ match &url {
+ Some(url) => {
+ // Fetch iframely data
+ let (iframely_title, iframely_description, iframely_thumbnail_url, iframely_html) =
+ match fetch_iframely(client, url).await {
+ Ok(res) => (res.title, res.description, res.thumbnail_url, res.html),
+ Err(e) => {
+ error!("iframely err: {}", e);
+ (None, None, None, None)
+ }
+ };
+
+ // Fetch pictrs thumbnail
+ let pictrs_hash = match iframely_thumbnail_url {
+ Some(iframely_thumbnail_url) => match fetch_pictrs(client, &iframely_thumbnail_url).await {
+ Ok(res) => Some(res.files[0].file.to_owned()),
+ Err(e) => {
+ error!("pictrs err: {}", e);
+ None
+ }
+ },
+ // Try to generate a small thumbnail if iframely is not supported
+ None => match fetch_pictrs(client, &url).await {
+ Ok(res) => Some(res.files[0].file.to_owned()),
+ Err(e) => {
+ error!("pictrs err: {}", e);
+ None
+ }
+ },
+ };
+
+ // The full urls are necessary for federation
+ let pictrs_thumbnail = if let Some(pictrs_hash) = pictrs_hash {
+ Some(format!(
+ "{}://{}/pictrs/image/{}",
+ get_apub_protocol_string(),
+ Settings::get().hostname,
+ pictrs_hash
+ ))
+ } else {
+ None
+ };
+
+ (
+ iframely_title,
+ iframely_description,
+ iframely_html,
+ pictrs_thumbnail,
+ )
+ }
+ None => (None, None, None, None),
+ }
+}
+
+pub(crate) async fn is_image_content_type(client: &Client, test: &str) -> Result<(), LemmyError> {
+ let response = retry(|| client.get(test).send()).await?;
+
+ if response
+ .headers()
+ .get("Content-Type")
+ .ok_or_else(|| anyhow!("No Content-Type header"))?
+ .to_str()?
+ .starts_with("image/")
+ {
+ Ok(())
+ } else {
+ Err(anyhow!("Not an image type.").into())
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::{captcha_espeak_wav_base64, is_image_content_type};
+
+ #[test]
+ fn test_image() {
+ actix_rt::System::new("tset_image").block_on(async move {
+ let client = reqwest::Client::default();
+ assert!(is_image_content_type(&client, "https://1734811051.rsc.cdn77.org/data/images/full/365645/as-virus-kills-navajos-in-their-homes-tribal-women-provide-lifeline.jpg?w=600?w=650").await.is_ok());
+ assert!(is_image_content_type(&client,
+ "https://twitter.com/BenjaminNorton/status/1259922424272957440?s=20"
+ )
+ .await.is_err()
+ );
+ });
+ }
+
+ #[test]
+ fn test_espeak() {
+ assert!(captcha_espeak_wav_base64("WxRt2l").is_ok())
+ }
+
+ // These helped with testing
+ // #[test]
+ // fn test_iframely() {
+ // let res = fetch_iframely(client, "https://www.redspark.nu/?p=15341").await;
+ // assert!(res.is_ok());
+ // }
+
+ // #[test]
+ // fn test_pictshare() {
+ // let res = fetch_pictshare("https://upload.wikimedia.org/wikipedia/en/2/27/The_Mandalorian_logo.jpg");
+ // assert!(res.is_ok());
+ // let res_other = fetch_pictshare("https://upload.wikimedia.org/wikipedia/en/2/27/The_Mandalorian_logo.jpgaoeu");
+ // assert!(res_other.is_err());
+ // }
+}
use crate::{
- api::{check_community_ban, get_user_from_jwt, get_user_from_jwt_opt, is_mod_or_admin, Perform},
- apub::{ApubLikeableType, ApubObjectType},
+ check_community_ban,
fetch_iframely_and_pictrs_data,
- LemmyContext,
+ get_user_from_jwt,
+ get_user_from_jwt_opt,
+ is_mod_or_admin,
+ Perform,
};
use actix_web::web::Data;
+use lemmy_apub::{ApubLikeableType, ApubObjectType};
use lemmy_db::{
comment_view::*,
community_view::*,
Saveable,
SortType,
};
-use lemmy_structs::{
- blocking,
- post::*,
- websocket::{GetPostUsersOnline, JoinPostRoom, SendPost, UserOperation},
-};
+use lemmy_structs::{blocking, post::*};
use lemmy_utils::{
apub::{make_apub_endpoint, EndpointType},
utils::{check_slurs, check_slurs_opt, is_valid_post_title},
ConnectionId,
LemmyError,
};
+use lemmy_websocket::{
+ messages::{GetPostUsersOnline, JoinPostRoom, SendPost},
+ LemmyContext,
+ UserOperation,
+};
use std::str::FromStr;
use url::Url;
-use crate::{
- api::{get_user_from_jwt, get_user_from_jwt_opt, is_admin, Perform},
- apub::fetcher::search_by_apub_id,
- version,
- LemmyContext,
-};
+use crate::{get_user_from_jwt, get_user_from_jwt_opt, is_admin, version, Perform};
use actix_web::web::Data;
use anyhow::Context;
+use lemmy_apub::fetcher::search_by_apub_id;
use lemmy_db::{
category::*,
comment_view::*,
SearchType,
SortType,
};
-use lemmy_structs::{
- blocking,
- site::*,
- user::Register,
- websocket::{GetUsersOnline, SendAllMessage, UserOperation},
-};
+use lemmy_structs::{blocking, site::*, user::Register};
use lemmy_utils::{
location_info,
settings::Settings,
ConnectionId,
LemmyError,
};
+use lemmy_websocket::{
+ messages::{GetUsersOnline, SendAllMessage},
+ LemmyContext,
+ UserOperation,
+};
use log::{debug, info};
use std::str::FromStr;
use crate::{
- api::{claims::Claims, get_user_from_jwt, get_user_from_jwt_opt, is_admin, Perform},
- apub::ApubObjectType,
captcha_espeak_wav_base64,
- LemmyContext,
+ claims::Claims,
+ get_user_from_jwt,
+ get_user_from_jwt_opt,
+ is_admin,
+ Perform,
};
use actix_web::web::Data;
use anyhow::Context;
use bcrypt::verify;
use captcha::{gen, Difficulty};
use chrono::Duration;
+use lemmy_apub::ApubObjectType;
use lemmy_db::{
comment::*,
comment_view::*,
ListingType,
SortType,
};
-use lemmy_structs::{
- blocking,
- user::*,
- websocket::{
- CaptchaItem,
- CheckCaptcha,
- JoinUserRoom,
- SendAllMessage,
- SendUserRoomMessage,
- UserOperation,
- },
-};
+use lemmy_structs::{blocking, user::*};
use lemmy_utils::{
apub::{generate_actor_keypair, make_apub_endpoint, EndpointType},
email::send_email,
ConnectionId,
LemmyError,
};
+use lemmy_websocket::{
+ messages::{CaptchaItem, CheckCaptcha, JoinUserRoom, SendAllMessage, SendUserRoomMessage},
+ LemmyContext,
+ UserOperation,
+};
use log::error;
use std::str::FromStr;
--- /dev/null
+[package]
+name = "lemmy_apub"
+version = "0.1.0"
+authors = ["Felix Ableitner <me@nutomic.com>"]
+edition = "2018"
+
+[lib]
+name = "lemmy_apub"
+path = "src/lib.rs"
+
+[dependencies]
+lemmy_utils = { path = "../lemmy_utils" }
+lemmy_db = { path = "../lemmy_db" }
+lemmy_structs = { path = "../lemmy_structs" }
+lemmy_websocket = { path = "../lemmy_websocket" }
+diesel = "1.4"
+activitystreams = "0.7.0-alpha.4"
+activitystreams-ext = "0.1.0-alpha.2"
+bcrypt = "0.8"
+chrono = { version = "0.4", features = ["serde"] }
+serde_json = { version = "1.0", features = ["preserve_order"]}
+serde = { version = "1.0", features = ["derive"] }
+actix = "0.10"
+actix-web = { version = "3.0", default-features = false }
+actix-rt = { version = "1.1", default-features = false }
+awc = { version = "2.0", default-features = false }
+log = "0.4"
+rand = "0.7"
+strum = "0.19"
+strum_macros = "0.19"
+lazy_static = "1.3"
+url = { version = "2.1", features = ["serde"] }
+percent-encoding = "2.1"
+openssl = "0.10"
+http = "0.2"
+http-signature-normalization-actix = { version = "0.4", default-features = false, features = ["sha-2"] }
+base64 = "0.12"
+tokio = "0.2"
+futures = "0.3"
+itertools = "0.9"
+uuid = { version = "0.8", features = ["serde", "v4"] }
+sha2 = "0.9"
+async-trait = "0.1"
+anyhow = "1.0"
+thiserror = "1.0"
+background-jobs = " 0.8"
+reqwest = { version = "0.10", features = ["json"] }
\ No newline at end of file
-use crate::{
- apub::{activity_queue::send_activity, community::do_announce, insert_activity},
- LemmyContext,
-};
+use crate::{activity_queue::send_activity, community::do_announce, insert_activity};
use activitystreams::{
base::{Extends, ExtendsExt},
object::AsObject,
};
use lemmy_db::{community::Community, user::User_};
use lemmy_utils::{apub::get_apub_protocol_string, settings::Settings, LemmyError};
+use lemmy_websocket::LemmyContext;
use serde::{export::fmt::Debug, Serialize};
use url::{ParseError, Url};
use uuid::Uuid;
Ok(())
}
-pub(in crate::apub) fn generate_activity_id<T>(kind: T) -> Result<Url, ParseError>
+pub(in crate) fn generate_activity_id<T>(kind: T) -> Result<Url, ParseError>
where
T: ToString,
{
-use crate::apub::{check_is_apub_id_valid, extensions::signatures::sign, ActorType};
+use crate::{check_is_apub_id_valid, extensions::signatures::sign, ActorType};
use activitystreams::{
base::{Extends, ExtendsExt},
object::AsObject,
use crate::{
- apub::{
- activities::{generate_activity_id, send_activity_to_community},
- check_actor_domain,
- create_apub_response,
- create_apub_tombstone_response,
- create_tombstone,
- fetch_webfinger_url,
- fetcher::{
- get_or_fetch_and_insert_comment,
- get_or_fetch_and_insert_post,
- get_or_fetch_and_upsert_user,
- },
- ActorType,
- ApubLikeableType,
- ApubObjectType,
- FromApub,
- ToApub,
+ activities::{generate_activity_id, send_activity_to_community},
+ check_actor_domain,
+ create_apub_response,
+ create_apub_tombstone_response,
+ create_tombstone,
+ fetch_webfinger_url,
+ fetcher::{
+ get_or_fetch_and_insert_comment,
+ get_or_fetch_and_insert_post,
+ get_or_fetch_and_upsert_user,
},
- DbPool,
- LemmyContext,
+ ActorType,
+ ApubLikeableType,
+ ApubObjectType,
+ FromApub,
+ ToApub,
};
use activitystreams::{
activity::{
post::Post,
user::User_,
Crud,
+ DbPool,
};
use lemmy_structs::blocking;
use lemmy_utils::{
utils::{convert_datetime, remove_slurs, scrape_text_for_mentions, MentionData},
LemmyError,
};
+use lemmy_websocket::LemmyContext;
use log::debug;
use serde::Deserialize;
use serde_json::Error;
use crate::{
- apub::{
- activities::generate_activity_id,
- activity_queue::send_activity,
- check_actor_domain,
- create_apub_response,
- create_apub_tombstone_response,
- create_tombstone,
- extensions::group_extensions::GroupExtension,
- fetcher::{get_or_fetch_and_upsert_actor, get_or_fetch_and_upsert_user},
- insert_activity,
- ActorType,
- FromApub,
- GroupExt,
- ToApub,
- },
- DbPool,
- LemmyContext,
+ activities::generate_activity_id,
+ activity_queue::send_activity,
+ check_actor_domain,
+ create_apub_response,
+ create_apub_tombstone_response,
+ create_tombstone,
+ extensions::group_extensions::GroupExtension,
+ fetcher::{get_or_fetch_and_upsert_actor, get_or_fetch_and_upsert_user},
+ insert_activity,
+ ActorType,
+ FromApub,
+ GroupExt,
+ ToApub,
};
use activitystreams::{
activity::{
naive_now,
post::Post,
user::User_,
+ DbPool,
};
use lemmy_structs::blocking;
use lemmy_utils::{
utils::{check_slurs, check_slurs_opt, convert_datetime},
LemmyError,
};
+use lemmy_websocket::LemmyContext;
use serde::Deserialize;
use url::Url;
-use crate::apub::ActorType;
+use crate::ActorType;
use activitystreams::unparsed::UnparsedMutExt;
use activitystreams_ext::UnparsedExtension;
use actix_web::{client::ClientRequest, HttpRequest};
use crate::{
- apub::{
- check_is_apub_id_valid,
- ActorType,
- FromApub,
- GroupExt,
- PageExt,
- PersonExt,
- APUB_JSON_CONTENT_TYPE,
- },
- request::{retry, RecvError},
- LemmyContext,
+ check_is_apub_id_valid,
+ ActorType,
+ FromApub,
+ GroupExt,
+ PageExt,
+ PersonExt,
+ APUB_JSON_CONTENT_TYPE,
};
use activitystreams::{base::BaseExt, collection::OrderedCollection, object::Note, prelude::*};
use anyhow::{anyhow, Context};
SearchType,
};
use lemmy_structs::{blocking, site::SearchResponse};
-use lemmy_utils::{apub::get_apub_protocol_string, location_info, LemmyError};
+use lemmy_utils::{
+ apub::get_apub_protocol_string,
+ location_info,
+ request::{retry, RecvError},
+ LemmyError,
+};
+use lemmy_websocket::LemmyContext;
use log::debug;
use reqwest::Client;
use serde::Deserialize;
-use crate::{
- apub::inbox::{
- activities::{
- create::receive_create,
- delete::receive_delete,
- dislike::receive_dislike,
- like::receive_like,
- remove::receive_remove,
- undo::receive_undo,
- update::receive_update,
- },
- shared_inbox::{get_community_id_from_activity, receive_unhandled_activity},
+use crate::inbox::{
+ activities::{
+ create::receive_create,
+ delete::receive_delete,
+ dislike::receive_dislike,
+ like::receive_like,
+ remove::receive_remove,
+ undo::receive_undo,
+ update::receive_update,
},
- LemmyContext,
+ shared_inbox::{get_community_id_from_activity, receive_unhandled_activity},
};
use activitystreams::{
activity::*,
use actix_web::HttpResponse;
use anyhow::Context;
use lemmy_utils::{location_info, LemmyError};
+use lemmy_websocket::LemmyContext;
pub async fn receive_announce(
activity: AnyBase,
use crate::{
- apub::{
- inbox::shared_inbox::{
- announce_if_community_is_local,
- get_user_from_activity,
- receive_unhandled_activity,
- },
- ActorType,
- FromApub,
- PageExt,
+ inbox::shared_inbox::{
+ announce_if_community_is_local,
+ get_user_from_activity,
+ receive_unhandled_activity,
},
- LemmyContext,
+ ActorType,
+ FromApub,
+ PageExt,
};
use activitystreams::{activity::Create, base::AnyBase, object::Note, prelude::*};
use actix_web::HttpResponse;
post::{Post, PostForm},
post_view::PostView,
};
-use lemmy_structs::{
- blocking,
- comment::CommentResponse,
- post::PostResponse,
- send_local_notifs,
- websocket::{SendComment, SendPost, UserOperation},
-};
+use lemmy_structs::{blocking, comment::CommentResponse, post::PostResponse, send_local_notifs};
use lemmy_utils::{location_info, utils::scrape_text_for_mentions, LemmyError};
+use lemmy_websocket::{
+ messages::{SendComment, SendPost},
+ LemmyContext,
+ UserOperation,
+};
pub async fn receive_create(
activity: AnyBase,
use crate::{
- apub::{
- fetcher::{get_or_fetch_and_insert_comment, get_or_fetch_and_insert_post},
- inbox::shared_inbox::{
- announce_if_community_is_local,
- get_user_from_activity,
- receive_unhandled_activity,
- },
- ActorType,
- FromApub,
- GroupExt,
- PageExt,
+ fetcher::{get_or_fetch_and_insert_comment, get_or_fetch_and_insert_post},
+ inbox::shared_inbox::{
+ announce_if_community_is_local,
+ get_user_from_activity,
+ receive_unhandled_activity,
},
- LemmyContext,
+ ActorType,
+ FromApub,
+ GroupExt,
+ PageExt,
};
use activitystreams::{activity::Delete, base::AnyBase, object::Note, prelude::*};
use actix_web::HttpResponse;
comment::CommentResponse,
community::CommunityResponse,
post::PostResponse,
- websocket::{SendComment, SendCommunityRoomMessage, SendPost, UserOperation},
};
use lemmy_utils::{location_info, LemmyError};
+use lemmy_websocket::{
+ messages::{SendComment, SendCommunityRoomMessage, SendPost},
+ LemmyContext,
+ UserOperation,
+};
pub async fn receive_delete(
activity: AnyBase,
use crate::{
- apub::{
- fetcher::{get_or_fetch_and_insert_comment, get_or_fetch_and_insert_post},
- inbox::shared_inbox::{
- announce_if_community_is_local,
- get_user_from_activity,
- receive_unhandled_activity,
- },
- FromApub,
- PageExt,
+ fetcher::{get_or_fetch_and_insert_comment, get_or_fetch_and_insert_post},
+ inbox::shared_inbox::{
+ announce_if_community_is_local,
+ get_user_from_activity,
+ receive_unhandled_activity,
},
- LemmyContext,
+ FromApub,
+ PageExt,
};
use activitystreams::{activity::Dislike, base::AnyBase, object::Note, prelude::*};
use actix_web::HttpResponse;
post_view::PostView,
Likeable,
};
-use lemmy_structs::{
- blocking,
- comment::CommentResponse,
- post::PostResponse,
- websocket::{SendComment, SendPost, UserOperation},
-};
+use lemmy_structs::{blocking, comment::CommentResponse, post::PostResponse};
use lemmy_utils::{location_info, LemmyError};
+use lemmy_websocket::{
+ messages::{SendComment, SendPost},
+ LemmyContext,
+ UserOperation,
+};
pub async fn receive_dislike(
activity: AnyBase,
use crate::{
- apub::{
- fetcher::{get_or_fetch_and_insert_comment, get_or_fetch_and_insert_post},
- inbox::shared_inbox::{
- announce_if_community_is_local,
- get_user_from_activity,
- receive_unhandled_activity,
- },
- FromApub,
- PageExt,
+ fetcher::{get_or_fetch_and_insert_comment, get_or_fetch_and_insert_post},
+ inbox::shared_inbox::{
+ announce_if_community_is_local,
+ get_user_from_activity,
+ receive_unhandled_activity,
},
- LemmyContext,
+ FromApub,
+ PageExt,
};
use activitystreams::{activity::Like, base::AnyBase, object::Note, prelude::*};
use actix_web::HttpResponse;
post_view::PostView,
Likeable,
};
-use lemmy_structs::{
- blocking,
- comment::CommentResponse,
- post::PostResponse,
- websocket::{SendComment, SendPost, UserOperation},
-};
+use lemmy_structs::{blocking, comment::CommentResponse, post::PostResponse};
use lemmy_utils::{location_info, LemmyError};
+use lemmy_websocket::{
+ messages::{SendComment, SendPost},
+ LemmyContext,
+ UserOperation,
+};
pub async fn receive_like(
activity: AnyBase,
use crate::{
- apub::{
- fetcher::{get_or_fetch_and_insert_comment, get_or_fetch_and_insert_post},
- inbox::shared_inbox::{
- announce_if_community_is_local,
- get_community_id_from_activity,
- get_user_from_activity,
- receive_unhandled_activity,
- },
- ActorType,
- FromApub,
- GroupExt,
- PageExt,
+ fetcher::{get_or_fetch_and_insert_comment, get_or_fetch_and_insert_post},
+ inbox::shared_inbox::{
+ announce_if_community_is_local,
+ get_community_id_from_activity,
+ get_user_from_activity,
+ receive_unhandled_activity,
},
- LemmyContext,
+ ActorType,
+ FromApub,
+ GroupExt,
+ PageExt,
};
use activitystreams::{activity::Remove, base::AnyBase, object::Note, prelude::*};
use actix_web::HttpResponse;
comment::CommentResponse,
community::CommunityResponse,
post::PostResponse,
- websocket::{SendComment, SendCommunityRoomMessage, SendPost, UserOperation},
};
use lemmy_utils::{location_info, LemmyError};
+use lemmy_websocket::{
+ messages::{SendComment, SendCommunityRoomMessage, SendPost},
+ LemmyContext,
+ UserOperation,
+};
pub async fn receive_remove(
activity: AnyBase,
use crate::{
- apub::{
- fetcher::{get_or_fetch_and_insert_comment, get_or_fetch_and_insert_post},
- inbox::shared_inbox::{
- announce_if_community_is_local,
- get_user_from_activity,
- receive_unhandled_activity,
- },
- ActorType,
- FromApub,
- GroupExt,
- PageExt,
+ fetcher::{get_or_fetch_and_insert_comment, get_or_fetch_and_insert_post},
+ inbox::shared_inbox::{
+ announce_if_community_is_local,
+ get_user_from_activity,
+ receive_unhandled_activity,
},
- LemmyContext,
+ ActorType,
+ FromApub,
+ GroupExt,
+ PageExt,
};
use activitystreams::{
activity::*,
comment::CommentResponse,
community::CommunityResponse,
post::PostResponse,
- websocket::{SendComment, SendCommunityRoomMessage, SendPost, UserOperation},
};
use lemmy_utils::{location_info, LemmyError};
+use lemmy_websocket::{
+ messages::{SendComment, SendCommunityRoomMessage, SendPost},
+ LemmyContext,
+ UserOperation,
+};
pub async fn receive_undo(
activity: AnyBase,
use crate::{
- apub::{
- fetcher::{get_or_fetch_and_insert_comment, get_or_fetch_and_insert_post},
- inbox::shared_inbox::{
- announce_if_community_is_local,
- get_user_from_activity,
- receive_unhandled_activity,
- },
- ActorType,
- FromApub,
- PageExt,
+ fetcher::{get_or_fetch_and_insert_comment, get_or_fetch_and_insert_post},
+ inbox::shared_inbox::{
+ announce_if_community_is_local,
+ get_user_from_activity,
+ receive_unhandled_activity,
},
- LemmyContext,
+ ActorType,
+ FromApub,
+ PageExt,
};
use activitystreams::{activity::Update, base::AnyBase, object::Note, prelude::*};
use actix_web::HttpResponse;
post_view::PostView,
Crud,
};
-use lemmy_structs::{
- blocking,
- comment::CommentResponse,
- post::PostResponse,
- send_local_notifs,
- websocket::{SendComment, SendPost, UserOperation},
-};
+use lemmy_structs::{blocking, comment::CommentResponse, post::PostResponse, send_local_notifs};
use lemmy_utils::{location_info, utils::scrape_text_for_mentions, LemmyError};
+use lemmy_websocket::{
+ messages::{SendComment, SendPost},
+ LemmyContext,
+ UserOperation,
+};
pub async fn receive_update(
activity: AnyBase,
use crate::{
- apub::{
- check_is_apub_id_valid,
- extensions::signatures::verify,
- fetcher::get_or_fetch_and_upsert_user,
- insert_activity,
- ActorType,
- },
- LemmyContext,
+ check_is_apub_id_valid,
+ extensions::signatures::verify,
+ fetcher::get_or_fetch_and_upsert_user,
+ insert_activity,
+ ActorType,
};
use activitystreams::{
activity::{ActorAndObject, Follow, Undo},
};
use lemmy_structs::blocking;
use lemmy_utils::{location_info, LemmyError};
+use lemmy_websocket::LemmyContext;
use log::debug;
use serde::{Deserialize, Serialize};
use std::fmt::Debug;
use crate::{
- apub::{
- check_is_apub_id_valid,
- community::do_announce,
- extensions::signatures::verify,
- fetcher::{
- get_or_fetch_and_upsert_actor,
- get_or_fetch_and_upsert_community,
- get_or_fetch_and_upsert_user,
- },
- inbox::activities::{
- announce::receive_announce,
- create::receive_create,
- delete::receive_delete,
- dislike::receive_dislike,
- like::receive_like,
- remove::receive_remove,
- undo::receive_undo,
- update::receive_update,
- },
- insert_activity,
+ check_is_apub_id_valid,
+ community::do_announce,
+ extensions::signatures::verify,
+ fetcher::{
+ get_or_fetch_and_upsert_actor,
+ get_or_fetch_and_upsert_community,
+ get_or_fetch_and_upsert_user,
},
- LemmyContext,
+ inbox::activities::{
+ announce::receive_announce,
+ create::receive_create,
+ delete::receive_delete,
+ dislike::receive_dislike,
+ like::receive_like,
+ remove::receive_remove,
+ undo::receive_undo,
+ update::receive_update,
+ },
+ insert_activity,
};
use activitystreams::{
activity::{ActorAndObject, ActorAndObjectRef},
use anyhow::Context;
use lemmy_db::user::User_;
use lemmy_utils::{location_info, LemmyError};
+use lemmy_websocket::LemmyContext;
use log::debug;
use serde::{Deserialize, Serialize};
use std::fmt::Debug;
res
}
-pub(in crate::apub::inbox) fn receive_unhandled_activity<A>(
+pub(in crate::inbox) fn receive_unhandled_activity<A>(
activity: A,
) -> Result<HttpResponse, LemmyError>
where
Ok(HttpResponse::NotImplemented().finish())
}
-pub(in crate::apub::inbox) async fn get_user_from_activity<T, A>(
+pub(in crate::inbox) async fn get_user_from_activity<T, A>(
activity: &T,
context: &LemmyContext,
) -> Result<User_, LemmyError>
get_or_fetch_and_upsert_user(&user_uri, context).await
}
-pub(in crate::apub::inbox) fn get_community_id_from_activity<T, A>(
+pub(in crate::inbox) fn get_community_id_from_activity<T, A>(
activity: &T,
) -> Result<Url, LemmyError>
where
)
}
-pub(in crate::apub::inbox) async fn announce_if_community_is_local<T, Kind>(
+pub(in crate::inbox) async fn announce_if_community_is_local<T, Kind>(
activity: T,
user: &User_,
context: &LemmyContext,
use crate::{
- apub::{
- check_is_apub_id_valid,
- extensions::signatures::verify,
- fetcher::{get_or_fetch_and_upsert_actor, get_or_fetch_and_upsert_community},
- insert_activity,
- FromApub,
- },
- LemmyContext,
+ check_is_apub_id_valid,
+ extensions::signatures::verify,
+ fetcher::{get_or_fetch_and_upsert_actor, get_or_fetch_and_upsert_community},
+ insert_activity,
+ FromApub,
};
use activitystreams::{
activity::{Accept, ActorAndObject, Create, Delete, Undo, Update},
Crud,
Followable,
};
-use lemmy_structs::{
- blocking,
- user::PrivateMessageResponse,
- websocket::{SendUserRoomMessage, UserOperation},
-};
+use lemmy_structs::{blocking, user::PrivateMessageResponse};
use lemmy_utils::{location_info, LemmyError};
+use lemmy_websocket::{messages::SendUserRoomMessage, LemmyContext, UserOperation};
use log::debug;
use serde::{Deserialize, Serialize};
use std::fmt::Debug;
+#[macro_use]
+extern crate lazy_static;
+
pub mod activities;
pub mod activity_queue;
pub mod comment;
pub mod private_message;
pub mod user;
-use crate::{
- apub::extensions::{
- group_extensions::GroupExtension,
- page_extension::PageExtension,
- signatures::{PublicKey, PublicKeyExtension},
- },
- request::{retry, RecvError},
- routes::webfinger::WebFingerResponse,
- DbPool,
- LemmyContext,
+use crate::extensions::{
+ group_extensions::GroupExtension,
+ page_extension::PageExtension,
+ signatures::{PublicKey, PublicKeyExtension},
};
use activitystreams::{
activity::Follow,
use actix_web::{body::Body, HttpResponse};
use anyhow::{anyhow, Context};
use chrono::NaiveDateTime;
-use lemmy_db::{activity::do_insert_activity, user::User_};
-use lemmy_structs::blocking;
+use lemmy_db::{activity::do_insert_activity, user::User_, DbPool};
+use lemmy_structs::{blocking, WebFingerResponse};
use lemmy_utils::{
apub::get_apub_protocol_string,
location_info,
+ request::{retry, RecvError},
settings::Settings,
utils::{convert_datetime, MentionData},
LemmyError,
};
+use lemmy_websocket::LemmyContext;
use log::debug;
use reqwest::Client;
use serde::Serialize;
async fn send_undo_remove(&self, mod_: &User_, context: &LemmyContext) -> Result<(), LemmyError>;
}
-pub(in crate::apub) fn check_actor_domain<T, Kind>(
+pub(in crate) fn check_actor_domain<T, Kind>(
apub: &T,
expected_domain: Option<Url>,
) -> Result<String, LemmyError>
use crate::{
- apub::{
- activities::{generate_activity_id, send_activity_to_community},
- check_actor_domain,
- create_apub_response,
- create_apub_tombstone_response,
- create_tombstone,
- extensions::page_extension::PageExtension,
- fetcher::{get_or_fetch_and_upsert_community, get_or_fetch_and_upsert_user},
- ActorType,
- ApubLikeableType,
- ApubObjectType,
- FromApub,
- PageExt,
- ToApub,
- },
- DbPool,
- LemmyContext,
+ activities::{generate_activity_id, send_activity_to_community},
+ check_actor_domain,
+ create_apub_response,
+ create_apub_tombstone_response,
+ create_tombstone,
+ extensions::page_extension::PageExtension,
+ fetcher::{get_or_fetch_and_upsert_community, get_or_fetch_and_upsert_user},
+ ActorType,
+ ApubLikeableType,
+ ApubObjectType,
+ FromApub,
+ PageExt,
+ ToApub,
};
use activitystreams::{
activity::{
post::{Post, PostForm},
user::User_,
Crud,
+ DbPool,
};
use lemmy_structs::blocking;
use lemmy_utils::{
utils::{check_slurs, convert_datetime, remove_slurs},
LemmyError,
};
+use lemmy_websocket::LemmyContext;
use serde::Deserialize;
use url::Url;
use crate::{
- apub::{
- activities::generate_activity_id,
- activity_queue::send_activity,
- check_actor_domain,
- check_is_apub_id_valid,
- create_tombstone,
- fetcher::get_or_fetch_and_upsert_user,
- insert_activity,
- ActorType,
- ApubObjectType,
- FromApub,
- ToApub,
- },
- DbPool,
- LemmyContext,
+ activities::generate_activity_id,
+ activity_queue::send_activity,
+ check_actor_domain,
+ check_is_apub_id_valid,
+ create_tombstone,
+ fetcher::get_or_fetch_and_upsert_user,
+ insert_activity,
+ ActorType,
+ ApubObjectType,
+ FromApub,
+ ToApub,
};
use activitystreams::{
activity::{
private_message::{PrivateMessage, PrivateMessageForm},
user::User_,
Crud,
+ DbPool,
};
use lemmy_structs::blocking;
use lemmy_utils::{location_info, utils::convert_datetime, LemmyError};
+use lemmy_websocket::LemmyContext;
use url::Url;
#[async_trait::async_trait(?Send)]
use crate::{
- apub::{
- activities::generate_activity_id,
- activity_queue::send_activity,
- check_actor_domain,
- create_apub_response,
- fetcher::get_or_fetch_and_upsert_actor,
- insert_activity,
- ActorType,
- FromApub,
- PersonExt,
- ToApub,
- },
- DbPool,
- LemmyContext,
+ activities::generate_activity_id,
+ activity_queue::send_activity,
+ check_actor_domain,
+ create_apub_response,
+ fetcher::get_or_fetch_and_upsert_actor,
+ insert_activity,
+ ActorType,
+ FromApub,
+ PersonExt,
+ ToApub,
};
use activitystreams::{
activity::{
use lemmy_db::{
naive_now,
user::{UserForm, User_},
+ DbPool,
};
use lemmy_structs::blocking;
use lemmy_utils::{
utils::{check_slurs, check_slurs_opt, convert_datetime},
LemmyError,
};
+use lemmy_websocket::LemmyContext;
use serde::Deserialize;
use url::Url;
extern crate strum_macros;
#[macro_use]
extern crate lazy_static;
-extern crate bcrypt;
-extern crate chrono;
-extern crate log;
-extern crate regex;
-extern crate serde;
-extern crate serde_json;
-extern crate sha2;
-extern crate strum;
use chrono::NaiveDateTime;
use diesel::{result::Error, *};
#[macro_use]
extern crate strum_macros;
-extern crate actix_web;
-extern crate futures;
-extern crate log;
-extern crate tokio;
use actix_web::dev::{Service, ServiceRequest, ServiceResponse, Transform};
use futures::future::{ok, Ready};
serde = { version = "1.0", features = ["derive"] }
log = "0.4"
diesel = "1.4"
-actix = "0.10"
actix-web = { version = "3.0" }
-strum = "0.19"
-strum_macros = "0.19"
chrono = { version = "0.4", features = ["serde"] }
+serde_json = { version = "1.0", features = ["preserve_order"]}
-extern crate actix;
-extern crate actix_web;
-extern crate diesel;
-extern crate log;
-extern crate serde;
-#[macro_use]
-extern crate strum_macros;
-extern crate chrono;
-
pub mod comment;
pub mod community;
pub mod post;
};
use lemmy_utils::{email::send_email, settings::Settings, utils::MentionData, LemmyError};
use log::error;
+use serde::{Deserialize, Serialize};
+
+#[derive(Serialize, Deserialize, Debug)]
+pub struct WebFingerLink {
+ pub rel: Option<String>,
+ #[serde(rename(serialize = "type", deserialize = "type"))]
+ pub type_: Option<String>,
+ pub href: Option<String>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub template: Option<String>,
+}
+
+#[derive(Serialize, Deserialize, Debug)]
+pub struct WebFingerResponse {
+ pub subject: String,
+ pub aliases: Vec<String>,
+ pub links: Vec<WebFingerLink>,
+}
pub async fn blocking<F, T>(pool: &DbPool, f: F) -> Result<T, LemmyError>
where
-use crate::{comment::CommentResponse, post::PostResponse};
-use actix::{prelude::*, Recipient};
-use lemmy_utils::{CommunityId, ConnectionId, IPAddr, PostId, UserId};
-use serde::{Deserialize, Serialize};
-#[derive(EnumString, ToString, Debug, Clone)]
-pub enum UserOperation {
- Login,
- Register,
- GetCaptcha,
- CreateCommunity,
- CreatePost,
- ListCommunities,
- ListCategories,
- GetPost,
- GetCommunity,
- CreateComment,
- EditComment,
- DeleteComment,
- RemoveComment,
- MarkCommentAsRead,
- SaveComment,
- CreateCommentLike,
- GetPosts,
- CreatePostLike,
- EditPost,
- DeletePost,
- RemovePost,
- LockPost,
- StickyPost,
- SavePost,
- EditCommunity,
- DeleteCommunity,
- RemoveCommunity,
- FollowCommunity,
- GetFollowedCommunities,
- GetUserDetails,
- GetReplies,
- GetUserMentions,
- MarkUserMentionAsRead,
- GetModlog,
- BanFromCommunity,
- AddModToCommunity,
- CreateSite,
- EditSite,
- GetSite,
- AddAdmin,
- BanUser,
- Search,
- MarkAllAsRead,
- SaveUserSettings,
- TransferCommunity,
- TransferSite,
- DeleteAccount,
- PasswordReset,
- PasswordChange,
- CreatePrivateMessage,
- EditPrivateMessage,
- DeletePrivateMessage,
- MarkPrivateMessageAsRead,
- GetPrivateMessages,
- UserJoin,
- GetComments,
- GetSiteConfig,
- SaveSiteConfig,
- PostJoin,
- CommunityJoin,
-}
-
-/// Chat server sends this messages to session
-#[derive(Message)]
-#[rtype(result = "()")]
-pub struct WSMessage(pub String);
-
-/// Message for chat server communications
-
-/// New chat session is created
-#[derive(Message)]
-#[rtype(usize)]
-pub struct Connect {
- pub addr: Recipient<WSMessage>,
- pub ip: IPAddr,
-}
-
-/// Session is disconnected
-#[derive(Message)]
-#[rtype(result = "()")]
-pub struct Disconnect {
- pub id: ConnectionId,
- pub ip: IPAddr,
-}
-
-/// The messages sent to websocket clients
-#[derive(Serialize, Deserialize, Message)]
-#[rtype(result = "Result<String, std::convert::Infallible>")]
-pub struct StandardMessage {
- /// Id of the client session
- pub id: ConnectionId,
- /// Peer message
- pub msg: String,
-}
-
-#[derive(Message)]
-#[rtype(result = "()")]
-pub struct SendAllMessage<Response> {
- pub op: UserOperation,
- pub response: Response,
- pub websocket_id: Option<ConnectionId>,
-}
-
-#[derive(Message)]
-#[rtype(result = "()")]
-pub struct SendUserRoomMessage<Response> {
- pub op: UserOperation,
- pub response: Response,
- pub recipient_id: UserId,
- pub websocket_id: Option<ConnectionId>,
-}
-
-#[derive(Message)]
-#[rtype(result = "()")]
-pub struct SendCommunityRoomMessage<Response> {
- pub op: UserOperation,
- pub response: Response,
- pub community_id: CommunityId,
- pub websocket_id: Option<ConnectionId>,
-}
-
-#[derive(Message)]
-#[rtype(result = "()")]
-pub struct SendPost {
- pub op: UserOperation,
- pub post: PostResponse,
- pub websocket_id: Option<ConnectionId>,
-}
-
-#[derive(Message)]
-#[rtype(result = "()")]
-pub struct SendComment {
- pub op: UserOperation,
- pub comment: CommentResponse,
- pub websocket_id: Option<ConnectionId>,
-}
-
-#[derive(Message)]
-#[rtype(result = "()")]
-pub struct JoinUserRoom {
- pub user_id: UserId,
- pub id: ConnectionId,
-}
-
-#[derive(Message)]
-#[rtype(result = "()")]
-pub struct JoinCommunityRoom {
- pub community_id: CommunityId,
- pub id: ConnectionId,
-}
-
-#[derive(Message)]
-#[rtype(result = "()")]
-pub struct JoinPostRoom {
- pub post_id: PostId,
- pub id: ConnectionId,
-}
-
-#[derive(Message)]
-#[rtype(usize)]
-pub struct GetUsersOnline;
-
-#[derive(Message)]
-#[rtype(usize)]
-pub struct GetPostUsersOnline {
- pub post_id: PostId,
-}
-
-#[derive(Message)]
-#[rtype(usize)]
-pub struct GetCommunityUsersOnline {
- pub community_id: CommunityId,
-}
-
-#[derive(Message, Debug)]
-#[rtype(result = "()")]
-pub struct CaptchaItem {
- pub uuid: String,
- pub answer: String,
- pub expires: chrono::NaiveDateTime,
-}
-
-#[derive(Message)]
-#[rtype(bool)]
-pub struct CheckCaptcha {
- pub uuid: String,
- pub answer: String,
-}
name = "lemmy_utils"
path = "src/lib.rs"
-# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
-
[dependencies]
regex = "1.3"
config = { version = "0.10", default-features = false, features = ["hjson"] }
url = { version = "2.1", features = ["serde"] }
actix-web = { version = "3.0", default-features = false, features = ["rustls"] }
anyhow = "1.0"
+reqwest = { version = "0.10", features = ["json"] }
#[macro_use]
extern crate lazy_static;
-extern crate actix_web;
-extern crate anyhow;
-extern crate comrak;
-extern crate lettre;
-extern crate lettre_email;
-extern crate openssl;
-extern crate rand;
-extern crate regex;
-extern crate serde_json;
-extern crate thiserror;
-extern crate url;
pub mod apub;
pub mod email;
+pub mod request;
pub mod settings;
#[cfg(test)]
mod test;
+use crate::LemmyError;
use anyhow::anyhow;
-use lemmy_utils::LemmyError;
use std::future::Future;
use thiserror::Error;
--- /dev/null
+[package]
+name = "lemmy_websocket"
+version = "0.1.0"
+authors = ["Felix Ableitner <me@nutomic.com>"]
+edition = "2018"
+
+[lib]
+name = "lemmy_websocket"
+path = "src/lib.rs"
+
+[dependencies]
+lemmy_utils = { path = "../lemmy_utils" }
+lemmy_structs = { path = "../lemmy_structs" }
+lemmy_db = { path = "../lemmy_db" }
+lemmy_rate_limit = { path = "../lemmy_rate_limit" }
+reqwest = { version = "0.10", features = ["json"] }
+log = "0.4"
+rand = "0.7"
+serde = { version = "1.0", features = ["derive"] }
+serde_json = { version = "1.0", features = ["preserve_order"]}
+actix = "0.10"
+anyhow = "1.0"
+diesel = "1.4"
+background-jobs = " 0.8"
+tokio = "0.2"
+strum = "0.19"
+strum_macros = "0.19"
+chrono = { version = "0.4", features = ["serde"] }
-use crate::{
- websocket::handlers::{do_user_operation, to_json_string, Args},
- LemmyContext,
-};
+use crate::{messages::*, serialize_websocket_message, LemmyContext, UserOperation};
use actix::prelude::*;
use anyhow::Context as acontext;
use background_jobs::QueueHandle;
PgConnection,
};
use lemmy_rate_limit::RateLimit;
-use lemmy_structs::{comment::*, community::*, post::*, site::*, user::*, websocket::*};
+use lemmy_structs::{comment::*, post::*};
use lemmy_utils::{
location_info,
APIError,
collections::{HashMap, HashSet},
str::FromStr,
};
+use tokio::macros::support::Pin;
+
+type MessageHandlerType = fn(
+ context: LemmyContext,
+ id: ConnectionId,
+ op: UserOperation,
+ data: &str,
+) -> Pin<Box<dyn Future<Output = Result<String, LemmyError>> + '_>>;
/// `ChatServer` manages chat rooms and responsible for coordinating chat
/// session.
/// A list of the current captchas
pub(super) captchas: Vec<CaptchaItem>,
+ message_handler: MessageHandlerType,
+
/// An HTTP Client
client: Client,
pub fn startup(
pool: Pool<ConnectionManager<PgConnection>>,
rate_limiter: RateLimit,
+ message_handler: MessageHandlerType,
client: Client,
activity_queue: QueueHandle,
) -> ChatServer {
pool,
rate_limiter,
captchas: Vec::new(),
+ message_handler,
client,
activity_queue,
}
where
Response: Serialize,
{
- let res_str = &to_json_string(op, response)?;
+ let res_str = &serialize_websocket_message(op, response)?;
if let Some(sessions) = self.post_rooms.get(&post_id) {
for id in sessions {
if let Some(my_id) = websocket_id {
where
Response: Serialize,
{
- let res_str = &to_json_string(op, response)?;
+ let res_str = &serialize_websocket_message(op, response)?;
if let Some(sessions) = self.community_rooms.get(&community_id) {
for id in sessions {
if let Some(my_id) = websocket_id {
where
Response: Serialize,
{
- let res_str = &to_json_string(op, response)?;
+ let res_str = &serialize_websocket_message(op, response)?;
for id in self.sessions.keys() {
if let Some(my_id) = websocket_id {
if *id == my_id {
where
Response: Serialize,
{
- let res_str = &to_json_string(op, response)?;
+ let res_str = &serialize_websocket_message(op, response)?;
if let Some(sessions) = self.user_rooms.get(&recipient_id) {
for id in sessions {
if let Some(my_id) = websocket_id {
msg: StandardMessage,
ctx: &mut Context<Self>,
) -> impl Future<Output = Result<String, LemmyError>> {
- let addr = ctx.address();
- let pool = self.pool.clone();
let rate_limiter = self.rate_limiter.clone();
let ip: IPAddr = match self.sessions.get(&msg.id) {
None => "blank_ip".to_string(),
};
- let client = self.client.clone();
- let activity_queue = self.activity_queue.clone();
+ let context = LemmyContext {
+ pool: self.pool.clone(),
+ chat_server: ctx.address(),
+ client: self.client.to_owned(),
+ activity_queue: self.activity_queue.to_owned(),
+ };
+ let message_handler = self.message_handler;
async move {
- let msg = msg;
let json: Value = serde_json::from_str(&msg.msg)?;
let data = &json["data"].to_string();
let op = &json["op"].as_str().ok_or(APIError {
message: "Unknown op type".to_string(),
})?;
- let user_operation: UserOperation = UserOperation::from_str(&op)?;
-
- let context = LemmyContext::new(pool, addr, client, activity_queue);
- let args = Args {
- context,
- rate_limiter,
- id: msg.id,
- ip,
- op: user_operation.clone(),
- data,
- };
-
+ let user_operation = UserOperation::from_str(&op)?;
+ let fut = (message_handler)(context, msg.id, user_operation.clone(), data);
match user_operation {
- // User ops
- UserOperation::Login => do_user_operation::<Login>(args).await,
- UserOperation::Register => do_user_operation::<Register>(args).await,
- UserOperation::GetCaptcha => do_user_operation::<GetCaptcha>(args).await,
- UserOperation::GetUserDetails => do_user_operation::<GetUserDetails>(args).await,
- UserOperation::GetReplies => do_user_operation::<GetReplies>(args).await,
- UserOperation::AddAdmin => do_user_operation::<AddAdmin>(args).await,
- UserOperation::BanUser => do_user_operation::<BanUser>(args).await,
- UserOperation::GetUserMentions => do_user_operation::<GetUserMentions>(args).await,
- UserOperation::MarkUserMentionAsRead => {
- do_user_operation::<MarkUserMentionAsRead>(args).await
- }
- UserOperation::MarkAllAsRead => do_user_operation::<MarkAllAsRead>(args).await,
- UserOperation::DeleteAccount => do_user_operation::<DeleteAccount>(args).await,
- UserOperation::PasswordReset => do_user_operation::<PasswordReset>(args).await,
- UserOperation::PasswordChange => do_user_operation::<PasswordChange>(args).await,
- UserOperation::UserJoin => do_user_operation::<UserJoin>(args).await,
- UserOperation::PostJoin => do_user_operation::<PostJoin>(args).await,
- UserOperation::CommunityJoin => do_user_operation::<CommunityJoin>(args).await,
- UserOperation::SaveUserSettings => do_user_operation::<SaveUserSettings>(args).await,
-
- // Private Message ops
- UserOperation::CreatePrivateMessage => {
- do_user_operation::<CreatePrivateMessage>(args).await
- }
- UserOperation::EditPrivateMessage => do_user_operation::<EditPrivateMessage>(args).await,
- UserOperation::DeletePrivateMessage => {
- do_user_operation::<DeletePrivateMessage>(args).await
- }
- UserOperation::MarkPrivateMessageAsRead => {
- do_user_operation::<MarkPrivateMessageAsRead>(args).await
- }
- UserOperation::GetPrivateMessages => do_user_operation::<GetPrivateMessages>(args).await,
-
- // Site ops
- UserOperation::GetModlog => do_user_operation::<GetModlog>(args).await,
- UserOperation::CreateSite => do_user_operation::<CreateSite>(args).await,
- UserOperation::EditSite => do_user_operation::<EditSite>(args).await,
- UserOperation::GetSite => do_user_operation::<GetSite>(args).await,
- UserOperation::GetSiteConfig => do_user_operation::<GetSiteConfig>(args).await,
- UserOperation::SaveSiteConfig => do_user_operation::<SaveSiteConfig>(args).await,
- UserOperation::Search => do_user_operation::<Search>(args).await,
- UserOperation::TransferCommunity => do_user_operation::<TransferCommunity>(args).await,
- UserOperation::TransferSite => do_user_operation::<TransferSite>(args).await,
- UserOperation::ListCategories => do_user_operation::<ListCategories>(args).await,
-
- // Community ops
- UserOperation::GetCommunity => do_user_operation::<GetCommunity>(args).await,
- UserOperation::ListCommunities => do_user_operation::<ListCommunities>(args).await,
- UserOperation::CreateCommunity => do_user_operation::<CreateCommunity>(args).await,
- UserOperation::EditCommunity => do_user_operation::<EditCommunity>(args).await,
- UserOperation::DeleteCommunity => do_user_operation::<DeleteCommunity>(args).await,
- UserOperation::RemoveCommunity => do_user_operation::<RemoveCommunity>(args).await,
- UserOperation::FollowCommunity => do_user_operation::<FollowCommunity>(args).await,
- UserOperation::GetFollowedCommunities => {
- do_user_operation::<GetFollowedCommunities>(args).await
- }
- UserOperation::BanFromCommunity => do_user_operation::<BanFromCommunity>(args).await,
- UserOperation::AddModToCommunity => do_user_operation::<AddModToCommunity>(args).await,
-
- // Post ops
- UserOperation::CreatePost => do_user_operation::<CreatePost>(args).await,
- UserOperation::GetPost => do_user_operation::<GetPost>(args).await,
- UserOperation::GetPosts => do_user_operation::<GetPosts>(args).await,
- UserOperation::EditPost => do_user_operation::<EditPost>(args).await,
- UserOperation::DeletePost => do_user_operation::<DeletePost>(args).await,
- UserOperation::RemovePost => do_user_operation::<RemovePost>(args).await,
- UserOperation::LockPost => do_user_operation::<LockPost>(args).await,
- UserOperation::StickyPost => do_user_operation::<StickyPost>(args).await,
- UserOperation::CreatePostLike => do_user_operation::<CreatePostLike>(args).await,
- UserOperation::SavePost => do_user_operation::<SavePost>(args).await,
-
- // Comment ops
- UserOperation::CreateComment => do_user_operation::<CreateComment>(args).await,
- UserOperation::EditComment => do_user_operation::<EditComment>(args).await,
- UserOperation::DeleteComment => do_user_operation::<DeleteComment>(args).await,
- UserOperation::RemoveComment => do_user_operation::<RemoveComment>(args).await,
- UserOperation::MarkCommentAsRead => do_user_operation::<MarkCommentAsRead>(args).await,
- UserOperation::SaveComment => do_user_operation::<SaveComment>(args).await,
- UserOperation::GetComments => do_user_operation::<GetComments>(args).await,
- UserOperation::CreateCommentLike => do_user_operation::<CreateCommentLike>(args).await,
+ UserOperation::Register => rate_limiter.register().wrap(ip, fut).await,
+ UserOperation::CreatePost => rate_limiter.post().wrap(ip, fut).await,
+ UserOperation::CreateCommunity => rate_limiter.register().wrap(ip, fut).await,
+ _ => rate_limiter.message().wrap(ip, fut).await,
}
}
}
use crate::{
- api::Perform,
- websocket::chat_server::{ChatServer, SessionInfo},
- LemmyContext,
+ chat_server::{ChatServer, SessionInfo},
+ messages::*,
};
use actix::{Actor, Context, Handler, ResponseFuture};
-use actix_web::web;
use lemmy_db::naive_now;
-use lemmy_rate_limit::RateLimit;
-use lemmy_structs::websocket::*;
-use lemmy_utils::{ConnectionId, IPAddr, LemmyError};
use log::{error, info};
use rand::Rng;
-use serde::{Deserialize, Serialize};
-
-pub(super) struct Args<'a> {
- pub(super) context: LemmyContext,
- pub(super) rate_limiter: RateLimit,
- pub(super) id: ConnectionId,
- pub(super) ip: IPAddr,
- pub(super) op: UserOperation,
- pub(super) data: &'a str,
-}
-
-pub(super) async fn do_user_operation<'a, 'b, Data>(args: Args<'b>) -> Result<String, LemmyError>
-where
- for<'de> Data: Deserialize<'de> + 'a,
- Data: Perform,
-{
- let Args {
- context,
- rate_limiter,
- id,
- ip,
- op,
- data,
- } = args;
-
- let data = data.to_string();
- let op2 = op.clone();
-
- let fut = async move {
- let parsed_data: Data = serde_json::from_str(&data)?;
- let res = parsed_data
- .perform(&web::Data::new(context), Some(id))
- .await?;
- to_json_string(&op, &res)
- };
-
- match op2 {
- UserOperation::Register => rate_limiter.register().wrap(ip, fut).await,
- UserOperation::CreatePost => rate_limiter.post().wrap(ip, fut).await,
- UserOperation::CreateCommunity => rate_limiter.register().wrap(ip, fut).await,
- _ => rate_limiter.message().wrap(ip, fut).await,
- }
-}
+use serde::Serialize;
/// Make actor from `ChatServer`
impl Actor for ChatServer {
}
}
-#[derive(Serialize)]
-struct WebsocketResponse<T> {
- op: String,
- data: T,
-}
-
-pub(super) fn to_json_string<Response>(
- op: &UserOperation,
- data: &Response,
-) -> Result<String, LemmyError>
-where
- Response: Serialize,
-{
- let response = WebsocketResponse {
- op: op.to_string(),
- data,
- };
- Ok(serde_json::to_string(&response)?)
-}
-
impl Handler<CaptchaItem> for ChatServer {
type Result = ();
--- /dev/null
+#[macro_use]
+extern crate strum_macros;
+
+use crate::chat_server::ChatServer;
+use actix::Addr;
+use background_jobs::QueueHandle;
+use lemmy_db::DbPool;
+use lemmy_utils::LemmyError;
+use reqwest::Client;
+use serde::Serialize;
+
+pub mod chat_server;
+pub mod handlers;
+pub mod messages;
+
+pub struct LemmyContext {
+ pub pool: DbPool,
+ pub chat_server: Addr<ChatServer>,
+ pub client: Client,
+ pub activity_queue: QueueHandle,
+}
+
+impl LemmyContext {
+ pub fn create(
+ pool: DbPool,
+ chat_server: Addr<ChatServer>,
+ client: Client,
+ activity_queue: QueueHandle,
+ ) -> LemmyContext {
+ LemmyContext {
+ pool,
+ chat_server,
+ client,
+ activity_queue,
+ }
+ }
+ pub fn pool(&self) -> &DbPool {
+ &self.pool
+ }
+ pub fn chat_server(&self) -> &Addr<ChatServer> {
+ &self.chat_server
+ }
+ pub fn client(&self) -> &Client {
+ &self.client
+ }
+ pub fn activity_queue(&self) -> &QueueHandle {
+ &self.activity_queue
+ }
+}
+
+impl Clone for LemmyContext {
+ fn clone(&self) -> Self {
+ LemmyContext {
+ pool: self.pool.clone(),
+ chat_server: self.chat_server.clone(),
+ client: self.client.clone(),
+ activity_queue: self.activity_queue.clone(),
+ }
+ }
+}
+
+#[derive(Serialize)]
+struct WebsocketResponse<T> {
+ op: String,
+ data: T,
+}
+
+pub fn serialize_websocket_message<Response>(
+ op: &UserOperation,
+ data: &Response,
+) -> Result<String, LemmyError>
+where
+ Response: Serialize,
+{
+ let response = WebsocketResponse {
+ op: op.to_string(),
+ data,
+ };
+ Ok(serde_json::to_string(&response)?)
+}
+
+#[derive(EnumString, ToString, Debug, Clone)]
+pub enum UserOperation {
+ Login,
+ Register,
+ GetCaptcha,
+ CreateCommunity,
+ CreatePost,
+ ListCommunities,
+ ListCategories,
+ GetPost,
+ GetCommunity,
+ CreateComment,
+ EditComment,
+ DeleteComment,
+ RemoveComment,
+ MarkCommentAsRead,
+ SaveComment,
+ CreateCommentLike,
+ GetPosts,
+ CreatePostLike,
+ EditPost,
+ DeletePost,
+ RemovePost,
+ LockPost,
+ StickyPost,
+ SavePost,
+ EditCommunity,
+ DeleteCommunity,
+ RemoveCommunity,
+ FollowCommunity,
+ GetFollowedCommunities,
+ GetUserDetails,
+ GetReplies,
+ GetUserMentions,
+ MarkUserMentionAsRead,
+ GetModlog,
+ BanFromCommunity,
+ AddModToCommunity,
+ CreateSite,
+ EditSite,
+ GetSite,
+ AddAdmin,
+ BanUser,
+ Search,
+ MarkAllAsRead,
+ SaveUserSettings,
+ TransferCommunity,
+ TransferSite,
+ DeleteAccount,
+ PasswordReset,
+ PasswordChange,
+ CreatePrivateMessage,
+ EditPrivateMessage,
+ DeletePrivateMessage,
+ MarkPrivateMessageAsRead,
+ GetPrivateMessages,
+ UserJoin,
+ GetComments,
+ GetSiteConfig,
+ SaveSiteConfig,
+ PostJoin,
+ CommunityJoin,
+}
--- /dev/null
+use crate::UserOperation;
+use actix::{prelude::*, Recipient};
+use lemmy_structs::{comment::CommentResponse, post::PostResponse};
+use lemmy_utils::{CommunityId, ConnectionId, IPAddr, PostId, UserId};
+use serde::{Deserialize, Serialize};
+
+/// Chat server sends this messages to session
+#[derive(Message)]
+#[rtype(result = "()")]
+pub struct WSMessage(pub String);
+
+/// Message for chat server communications
+
+/// New chat session is created
+#[derive(Message)]
+#[rtype(usize)]
+pub struct Connect {
+ pub addr: Recipient<WSMessage>,
+ pub ip: IPAddr,
+}
+
+/// Session is disconnected
+#[derive(Message)]
+#[rtype(result = "()")]
+pub struct Disconnect {
+ pub id: ConnectionId,
+ pub ip: IPAddr,
+}
+
+/// The messages sent to websocket clients
+#[derive(Serialize, Deserialize, Message)]
+#[rtype(result = "Result<String, std::convert::Infallible>")]
+pub struct StandardMessage {
+ /// Id of the client session
+ pub id: ConnectionId,
+ /// Peer message
+ pub msg: String,
+}
+
+#[derive(Message)]
+#[rtype(result = "()")]
+pub struct SendAllMessage<Response> {
+ pub op: UserOperation,
+ pub response: Response,
+ pub websocket_id: Option<ConnectionId>,
+}
+
+#[derive(Message)]
+#[rtype(result = "()")]
+pub struct SendUserRoomMessage<Response> {
+ pub op: UserOperation,
+ pub response: Response,
+ pub recipient_id: UserId,
+ pub websocket_id: Option<ConnectionId>,
+}
+
+#[derive(Message)]
+#[rtype(result = "()")]
+pub struct SendCommunityRoomMessage<Response> {
+ pub op: UserOperation,
+ pub response: Response,
+ pub community_id: CommunityId,
+ pub websocket_id: Option<ConnectionId>,
+}
+
+#[derive(Message)]
+#[rtype(result = "()")]
+pub struct SendPost {
+ pub op: UserOperation,
+ pub post: PostResponse,
+ pub websocket_id: Option<ConnectionId>,
+}
+
+#[derive(Message)]
+#[rtype(result = "()")]
+pub struct SendComment {
+ pub op: UserOperation,
+ pub comment: CommentResponse,
+ pub websocket_id: Option<ConnectionId>,
+}
+
+#[derive(Message)]
+#[rtype(result = "()")]
+pub struct JoinUserRoom {
+ pub user_id: UserId,
+ pub id: ConnectionId,
+}
+
+#[derive(Message)]
+#[rtype(result = "()")]
+pub struct JoinCommunityRoom {
+ pub community_id: CommunityId,
+ pub id: ConnectionId,
+}
+
+#[derive(Message)]
+#[rtype(result = "()")]
+pub struct JoinPostRoom {
+ pub post_id: PostId,
+ pub id: ConnectionId,
+}
+
+#[derive(Message)]
+#[rtype(usize)]
+pub struct GetUsersOnline;
+
+#[derive(Message)]
+#[rtype(usize)]
+pub struct GetPostUsersOnline {
+ pub post_id: PostId,
+}
+
+#[derive(Message)]
+#[rtype(usize)]
+pub struct GetCommunityUsersOnline {
+ pub community_id: CommunityId,
+}
+
+#[derive(Message, Debug)]
+#[rtype(result = "()")]
+pub struct CaptchaItem {
+ pub uuid: String,
+ pub answer: String,
+ pub expires: chrono::NaiveDateTime,
+}
+
+#[derive(Message)]
+#[rtype(bool)]
+pub struct CheckCaptcha {
+ pub uuid: String,
+ pub answer: String,
+}
+++ /dev/null
-use crate::{api::claims::Claims, DbPool, LemmyContext};
-use actix_web::web::Data;
-use lemmy_db::{
- community::Community,
- community_view::CommunityUserBanView,
- post::Post,
- user::User_,
- Crud,
-};
-use lemmy_structs::blocking;
-use lemmy_utils::{APIError, ConnectionId, LemmyError};
-
-pub mod claims;
-pub mod comment;
-pub mod community;
-pub mod post;
-pub mod site;
-pub mod user;
-
-#[async_trait::async_trait(?Send)]
-pub trait Perform {
- type Response: serde::ser::Serialize + Send;
-
- async fn perform(
- &self,
- context: &Data<LemmyContext>,
- websocket_id: Option<ConnectionId>,
- ) -> Result<Self::Response, LemmyError>;
-}
-
-pub(in crate::api) async fn is_mod_or_admin(
- pool: &DbPool,
- user_id: i32,
- community_id: i32,
-) -> Result<(), LemmyError> {
- let is_mod_or_admin = blocking(pool, move |conn| {
- Community::is_mod_or_admin(conn, user_id, community_id)
- })
- .await?;
- if !is_mod_or_admin {
- return Err(APIError::err("not_a_mod_or_admin").into());
- }
- Ok(())
-}
-pub async fn is_admin(pool: &DbPool, user_id: i32) -> Result<(), LemmyError> {
- let user = blocking(pool, move |conn| User_::read(conn, user_id)).await??;
- if !user.admin {
- return Err(APIError::err("not_an_admin").into());
- }
- Ok(())
-}
-
-pub(in crate::api) async fn get_post(post_id: i32, pool: &DbPool) -> Result<Post, LemmyError> {
- match blocking(pool, move |conn| Post::read(conn, post_id)).await? {
- Ok(post) => Ok(post),
- Err(_e) => Err(APIError::err("couldnt_find_post").into()),
- }
-}
-
-pub(in crate::api) async fn get_user_from_jwt(
- jwt: &str,
- pool: &DbPool,
-) -> Result<User_, LemmyError> {
- let claims = match Claims::decode(&jwt) {
- Ok(claims) => claims.claims,
- Err(_e) => return Err(APIError::err("not_logged_in").into()),
- };
- let user_id = claims.id;
- let user = blocking(pool, move |conn| User_::read(conn, user_id)).await??;
- // Check for a site ban
- if user.banned {
- return Err(APIError::err("site_ban").into());
- }
- Ok(user)
-}
-
-pub(in crate::api) async fn get_user_from_jwt_opt(
- jwt: &Option<String>,
- pool: &DbPool,
-) -> Result<Option<User_>, LemmyError> {
- match jwt {
- Some(jwt) => Ok(Some(get_user_from_jwt(jwt, pool).await?)),
- None => Ok(None),
- }
-}
-
-pub(in crate::api) async fn check_community_ban(
- user_id: i32,
- community_id: i32,
- pool: &DbPool,
-) -> Result<(), LemmyError> {
- let is_banned = move |conn: &'_ _| CommunityUserBanView::get(conn, user_id, community_id).is_ok();
- if blocking(pool, is_banned).await? {
- Err(APIError::err("community_ban").into())
- } else {
- Ok(())
- }
-}
#![recursion_limit = "512"]
-#[macro_use]
-extern crate lazy_static;
-extern crate actix;
-extern crate actix_web;
-extern crate base64;
-extern crate bcrypt;
-extern crate captcha;
-extern crate chrono;
-extern crate diesel;
-extern crate dotenv;
-extern crate jsonwebtoken;
-extern crate log;
-extern crate openssl;
-extern crate reqwest;
-extern crate rss;
-extern crate serde;
-extern crate serde_json;
-extern crate sha2;
-extern crate strum;
-pub mod api;
-pub mod apub;
pub mod code_migrations;
-pub mod request;
pub mod routes;
-pub mod version;
-pub mod websocket;
-
-use crate::{
- request::{retry, RecvError},
- websocket::chat_server::ChatServer,
-};
-use actix::Addr;
-use anyhow::anyhow;
-use background_jobs::QueueHandle;
-use lemmy_db::DbPool;
-use lemmy_utils::{apub::get_apub_protocol_string, settings::Settings, LemmyError};
-use log::error;
-use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC};
-use reqwest::Client;
-use serde::Deserialize;
-use std::process::Command;
-
-pub struct LemmyContext {
- pub pool: DbPool,
- pub chat_server: Addr<ChatServer>,
- pub client: Client,
- pub activity_queue: QueueHandle,
-}
-
-impl LemmyContext {
- pub fn new(
- pool: DbPool,
- chat_server: Addr<ChatServer>,
- client: Client,
- activity_queue: QueueHandle,
- ) -> LemmyContext {
- LemmyContext {
- pool,
- chat_server,
- client,
- activity_queue,
- }
- }
- pub fn pool(&self) -> &DbPool {
- &self.pool
- }
- pub fn chat_server(&self) -> &Addr<ChatServer> {
- &self.chat_server
- }
- pub fn client(&self) -> &Client {
- &self.client
- }
- pub fn activity_queue(&self) -> &QueueHandle {
- &self.activity_queue
- }
-}
-
-impl Clone for LemmyContext {
- fn clone(&self) -> Self {
- LemmyContext::new(
- self.pool.clone(),
- self.chat_server.clone(),
- self.client.clone(),
- self.activity_queue.clone(),
- )
- }
-}
-
-#[derive(Deserialize, Debug)]
-pub struct IframelyResponse {
- title: Option<String>,
- description: Option<String>,
- thumbnail_url: Option<String>,
- html: Option<String>,
-}
-
-pub async fn fetch_iframely(client: &Client, url: &str) -> Result<IframelyResponse, LemmyError> {
- let fetch_url = format!("http://iframely/oembed?url={}", url);
-
- let response = retry(|| client.get(&fetch_url).send()).await?;
-
- let res: IframelyResponse = response
- .json()
- .await
- .map_err(|e| RecvError(e.to_string()))?;
- Ok(res)
-}
-
-#[derive(Deserialize, Debug, Clone)]
-pub struct PictrsResponse {
- files: Vec<PictrsFile>,
- msg: String,
-}
-
-#[derive(Deserialize, Debug, Clone)]
-pub struct PictrsFile {
- file: String,
- delete_token: String,
-}
-
-pub async fn fetch_pictrs(client: &Client, image_url: &str) -> Result<PictrsResponse, LemmyError> {
- is_image_content_type(client, image_url).await?;
-
- let fetch_url = format!(
- "http://pictrs:8080/image/download?url={}",
- utf8_percent_encode(image_url, NON_ALPHANUMERIC) // TODO this might not be needed
- );
-
- let response = retry(|| client.get(&fetch_url).send()).await?;
-
- let response: PictrsResponse = response
- .json()
- .await
- .map_err(|e| RecvError(e.to_string()))?;
-
- if response.msg == "ok" {
- Ok(response)
- } else {
- Err(anyhow!("{}", &response.msg).into())
- }
-}
-
-async fn fetch_iframely_and_pictrs_data(
- client: &Client,
- url: Option<String>,
-) -> (
- Option<String>,
- Option<String>,
- Option<String>,
- Option<String>,
-) {
- match &url {
- Some(url) => {
- // Fetch iframely data
- let (iframely_title, iframely_description, iframely_thumbnail_url, iframely_html) =
- match fetch_iframely(client, url).await {
- Ok(res) => (res.title, res.description, res.thumbnail_url, res.html),
- Err(e) => {
- error!("iframely err: {}", e);
- (None, None, None, None)
- }
- };
-
- // Fetch pictrs thumbnail
- let pictrs_hash = match iframely_thumbnail_url {
- Some(iframely_thumbnail_url) => match fetch_pictrs(client, &iframely_thumbnail_url).await {
- Ok(res) => Some(res.files[0].file.to_owned()),
- Err(e) => {
- error!("pictrs err: {}", e);
- None
- }
- },
- // Try to generate a small thumbnail if iframely is not supported
- None => match fetch_pictrs(client, &url).await {
- Ok(res) => Some(res.files[0].file.to_owned()),
- Err(e) => {
- error!("pictrs err: {}", e);
- None
- }
- },
- };
-
- // The full urls are necessary for federation
- let pictrs_thumbnail = if let Some(pictrs_hash) = pictrs_hash {
- Some(format!(
- "{}://{}/pictrs/image/{}",
- get_apub_protocol_string(),
- Settings::get().hostname,
- pictrs_hash
- ))
- } else {
- None
- };
-
- (
- iframely_title,
- iframely_description,
- iframely_html,
- pictrs_thumbnail,
- )
- }
- None => (None, None, None, None),
- }
-}
-
-pub async fn is_image_content_type(client: &Client, test: &str) -> Result<(), LemmyError> {
- let response = retry(|| client.get(test).send()).await?;
-
- if response
- .headers()
- .get("Content-Type")
- .ok_or_else(|| anyhow!("No Content-Type header"))?
- .to_str()?
- .starts_with("image/")
- {
- Ok(())
- } else {
- Err(anyhow!("Not an image type.").into())
- }
-}
-
-pub fn captcha_espeak_wav_base64(captcha: &str) -> Result<String, LemmyError> {
- let mut built_text = String::new();
-
- // Building proper speech text for espeak
- for mut c in captcha.chars() {
- let new_str = if c.is_alphabetic() {
- if c.is_lowercase() {
- c.make_ascii_uppercase();
- format!("lower case {} ... ", c)
- } else {
- c.make_ascii_uppercase();
- format!("capital {} ... ", c)
- }
- } else {
- format!("{} ...", c)
- };
-
- built_text.push_str(&new_str);
- }
-
- espeak_wav_base64(&built_text)
-}
-
-pub fn espeak_wav_base64(text: &str) -> Result<String, LemmyError> {
- // Make a temp file path
- let uuid = uuid::Uuid::new_v4().to_string();
- let file_path = format!("/tmp/lemmy_espeak_{}.wav", &uuid);
-
- // Write the wav file
- Command::new("espeak")
- .arg("-w")
- .arg(&file_path)
- .arg(text)
- .status()?;
-
- // Read the wav file bytes
- let bytes = std::fs::read(&file_path)?;
-
- // Delete the file
- std::fs::remove_file(file_path)?;
-
- // Convert to base64
- let base64 = base64::encode(bytes);
-
- Ok(base64)
-}
-
-#[cfg(test)]
-mod tests {
- use crate::{captcha_espeak_wav_base64, is_image_content_type};
-
- #[test]
- fn test_image() {
- actix_rt::System::new("tset_image").block_on(async move {
- let client = reqwest::Client::default();
- assert!(is_image_content_type(&client, "https://1734811051.rsc.cdn77.org/data/images/full/365645/as-virus-kills-navajos-in-their-homes-tribal-women-provide-lifeline.jpg?w=600?w=650").await.is_ok());
- assert!(is_image_content_type(&client,
- "https://twitter.com/BenjaminNorton/status/1259922424272957440?s=20"
- )
- .await.is_err()
- );
- });
- }
-
- #[test]
- fn test_espeak() {
- assert!(captcha_espeak_wav_base64("WxRt2l").is_ok())
- }
-
- // These helped with testing
- // #[test]
- // fn test_iframely() {
- // let res = fetch_iframely(client, "https://www.redspark.nu/?p=15341").await;
- // assert!(res.is_ok());
- // }
-
- // #[test]
- // fn test_pictshare() {
- // let res = fetch_pictshare("https://upload.wikimedia.org/wikipedia/en/2/27/The_Mandalorian_logo.jpg");
- // assert!(res.is_ok());
- // let res_other = fetch_pictshare("https://upload.wikimedia.org/wikipedia/en/2/27/The_Mandalorian_logo.jpgaoeu");
- // assert!(res_other.is_err());
- // }
-}
PgConnection,
};
use lazy_static::lazy_static;
+use lemmy_api::match_websocket_operation;
+use lemmy_apub::activity_queue::create_activity_queue;
use lemmy_db::get_database_url_from_env;
use lemmy_rate_limit::{rate_limiter::RateLimiter, RateLimit};
-use lemmy_server::{
- apub::activity_queue::create_activity_queue,
- code_migrations::run_advanced_migrations,
- routes::*,
- websocket::chat_server::ChatServer,
- LemmyContext,
-};
+use lemmy_server::{code_migrations::run_advanced_migrations, routes::*};
use lemmy_structs::blocking;
use lemmy_utils::{settings::Settings, LemmyError, CACHE_CONTROL_REGEX};
+use lemmy_websocket::{chat_server::ChatServer, LemmyContext};
use reqwest::Client;
use std::sync::Arc;
use tokio::sync::Mutex;
let chat_server = ChatServer::startup(
pool.clone(),
rate_limiter.clone(),
+ |c, i, o, d| Box::pin(match_websocket_operation(c, i, o, d)),
Client::default(),
activity_queue.clone(),
)
// Create Http server with websocket support
HttpServer::new(move || {
- let context = LemmyContext::new(
+ let context = LemmyContext::create(
pool.clone(),
chat_server.to_owned(),
Client::default(),
-use crate::{api::Perform, LemmyContext};
use actix_web::{error::ErrorBadRequest, *};
+use lemmy_api::Perform;
use lemmy_rate_limit::RateLimit;
use lemmy_structs::{comment::*, community::*, post::*, site::*, user::*};
+use lemmy_websocket::LemmyContext;
use serde::Deserialize;
pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimit) {
-use crate::apub::{
+use actix_web::*;
+use http_signature_normalization_actix::digest::middleware::VerifyDigest;
+use lemmy_apub::{
comment::get_apub_comment,
community::*,
inbox::{community_inbox::community_inbox, shared_inbox::shared_inbox, user_inbox::user_inbox},
user::*,
APUB_JSON_CONTENT_TYPE,
};
-use actix_web::*;
-use http_signature_normalization_actix::digest::middleware::VerifyDigest;
use lemmy_utils::settings::Settings;
use sha2::{Digest, Sha256};
-use crate::{api::claims::Claims, LemmyContext};
use actix_web::{error::ErrorBadRequest, *};
use anyhow::anyhow;
use chrono::{DateTime, NaiveDateTime, Utc};
use diesel::PgConnection;
+use lemmy_api::claims::Claims;
use lemmy_db::{
comment_view::{ReplyQueryBuilder, ReplyView},
community::Community,
};
use lemmy_structs::blocking;
use lemmy_utils::{settings::Settings, utils::markdown_to_html, LemmyError};
+use lemmy_websocket::LemmyContext;
use rss::{CategoryBuilder, ChannelBuilder, GuidBuilder, Item, ItemBuilder};
use serde::Deserialize;
use std::str::FromStr;
-use crate::{version, LemmyContext};
use actix_web::{body::Body, error::ErrorBadRequest, *};
use anyhow::anyhow;
+use lemmy_api::version;
use lemmy_db::site_view::SiteView;
use lemmy_structs::blocking;
use lemmy_utils::{apub::get_apub_protocol_string, settings::Settings, LemmyError};
+use lemmy_websocket::LemmyContext;
use serde::{Deserialize, Serialize};
use url::Url;
-use crate::LemmyContext;
use actix_web::{error::ErrorBadRequest, web::Query, *};
use anyhow::anyhow;
use lemmy_db::{community::Community, user::User_};
-use lemmy_structs::blocking;
+use lemmy_structs::{blocking, WebFingerLink, WebFingerResponse};
use lemmy_utils::{
settings::Settings,
LemmyError,
WEBFINGER_COMMUNITY_REGEX,
WEBFINGER_USER_REGEX,
};
-use serde::{Deserialize, Serialize};
+use lemmy_websocket::LemmyContext;
+use serde::Deserialize;
#[derive(Deserialize)]
pub struct Params {
resource: String,
}
-#[derive(Serialize, Deserialize, Debug)]
-pub struct WebFingerResponse {
- pub subject: String,
- pub aliases: Vec<String>,
- pub links: Vec<WebFingerLink>,
-}
-
-#[derive(Serialize, Deserialize, Debug)]
-pub struct WebFingerLink {
- pub rel: Option<String>,
- #[serde(rename(serialize = "type", deserialize = "type"))]
- pub type_: Option<String>,
- pub href: Option<String>,
- #[serde(skip_serializing_if = "Option::is_none")]
- pub template: Option<String>,
-}
-
pub fn config(cfg: &mut web::ServiceConfig) {
if Settings::get().federation.enabled {
cfg.route(
-use crate::{websocket::chat_server::ChatServer, LemmyContext};
use actix::prelude::*;
use actix_web::*;
use actix_web_actors::ws;
-use lemmy_structs::websocket::{Connect, Disconnect, StandardMessage, WSMessage};
use lemmy_utils::utils::get_ip;
+use lemmy_websocket::{
+ chat_server::ChatServer,
+ messages::{Connect, Disconnect, StandardMessage, WSMessage},
+ LemmyContext,
+};
use log::{debug, error, info};
use std::time::{Duration, Instant};
+++ /dev/null
-pub mod chat_server;
-pub mod handlers;