Merge remote-tracking branch 'yerba/main' into main

This commit is contained in:
Dessalines 2020-09-24 09:14:09 -05:00
commit 4de80dc29d
67 changed files with 1532 additions and 1294 deletions

142
Cargo.lock generated
View file

@ -1113,12 +1113,6 @@ version = "1.0.4"
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"
@ -1809,6 +1803,92 @@ version = "1.4.0"
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"
@ -1844,58 +1924,41 @@ dependencies = [
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",
@ -1903,8 +1966,7 @@ dependencies = [
"lemmy_utils",
"log",
"serde 1.0.116",
"strum",
"strum_macros",
"serde_json",
]
[[package]]
@ -1924,12 +1986,36 @@ dependencies = [
"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"

View file

@ -8,56 +8,43 @@ lto = true
[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]

View file

@ -12,6 +12,9 @@ RUN mkdir -p lemmy_db/src/ \
lemmy_utils/src/ \
lemmy_structs/src/ \
lemmy_rate_limit/src/ \
lemmy_api/src/ \
lemmy_apub/src/ \
lemmy_websocket/src/ \
lemmy
# Copy the cargo tomls
@ -20,6 +23,9 @@ COPY lemmy_db/Cargo.toml ./lemmy_db/
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
@ -30,6 +36,9 @@ COPY lemmy_db/src ./lemmy_db/src/
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

View file

@ -15,11 +15,11 @@ services:
depends_on:
- pictrs
- iframely
- lemmy-alpha
- lemmy-beta
- lemmy-gamma
- lemmy-delta
- lemmy-epsilon
- lemmy-alpha-ui
- lemmy-beta-ui
- lemmy-gamma-ui
- lemmy-delta-ui
- lemmy-epsilon-ui
pictrs:
restart: always

View file

@ -3,6 +3,12 @@ events {
}
http {
upstream lemmy-alpha {
server "lemmy-alpha:8541";
}
upstream lemmy-alpha-ui {
server "lemmy-alpha-ui:1234";
}
server {
listen 8540;
server_name 127.0.0.1;
@ -11,14 +17,19 @@ http {
# Upload limit for pictshare
client_max_body_size 50M;
location /api/v1 {
proxy_pass http://lemmy-alpha:8541/api/v1;
location ~ ^/(api|docs|pictrs|feeds|nodeinfo|.well-known) {
proxy_pass http://lemmy-alpha;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
location / {
proxy_pass http://lemmy-alpha-ui:1234;
set $proxpass http://lemmy-alpha-ui;
if ($http_accept = "application/activity+json") {
set $proxpass http://lemmy-alpha;
}
proxy_pass $proxpass;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
@ -34,6 +45,12 @@ http {
}
}
upstream lemmy-beta {
server "lemmy-beta:8551";
}
upstream lemmy-beta-ui {
server "lemmy-beta-ui:1234";
}
server {
listen 8550;
server_name 127.0.0.1;
@ -42,14 +59,19 @@ http {
# Upload limit for pictshare
client_max_body_size 50M;
location /api/v1 {
proxy_pass http://lemmy-beta:8551/api/v1;
location ~ ^/(api|docs|pictrs|feeds|nodeinfo|.well-known) {
proxy_pass http://lemmy-beta;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
location / {
proxy_pass http://lemmy-beta-ui:1234;
set $proxpass http://lemmy-beta-ui;
if ($http_accept = "application/activity+json") {
set $proxpass http://lemmy-beta;
}
proxy_pass $proxpass;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
@ -65,6 +87,12 @@ http {
}
}
upstream lemmy-gamma {
server "lemmy-gamma:8561";
}
upstream lemmy-gamma-ui {
server "lemmy-gamma-ui:1234";
}
server {
listen 8560;
server_name 127.0.0.1;
@ -73,14 +101,19 @@ http {
# Upload limit for pictshare
client_max_body_size 50M;
location /api/v1 {
proxy_pass http://lemmy-gamma:8561/api/v1;
location ~ ^/(api|docs|pictrs|feeds|nodeinfo|.well-known) {
proxy_pass http://lemmy-gamma;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
location / {
proxy_pass http://lemmy-gamma-ui:1234;
set $proxpass http://lemmy-gamma-ui;
if ($http_accept = "application/activity+json") {
set $proxpass http://lemmy-gamma;
}
proxy_pass $proxpass;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
@ -96,6 +129,12 @@ http {
}
}
upstream lemmy-delta {
server "lemmy-delta:8571";
}
upstream lemmy-delta-ui {
server "lemmy-delta-ui:1234";
}
server {
listen 8570;
server_name 127.0.0.1;
@ -104,14 +143,19 @@ http {
# Upload limit for pictshare
client_max_body_size 50M;
location /api/v1 {
proxy_pass http://lemmy-delta:8571/api/v1;
location ~ ^/(api|docs|pictrs|feeds|nodeinfo|.well-known) {
proxy_pass http://lemmy-delta;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
location / {
proxy_pass http://lemmy-delta-ui:1234;
set $proxpass http://lemmy-delta-ui;
if ($http_accept = "application/activity+json") {
set $proxpass http://lemmy-delta;
}
proxy_pass $proxpass;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
@ -127,6 +171,12 @@ http {
}
}
upstream lemmy-epsilon {
server "lemmy-epsilon:8581";
}
upstream lemmy-epsilon-ui {
server "lemmy-epsilon-ui:1234";
}
server {
listen 8580;
server_name 127.0.0.1;
@ -135,14 +185,19 @@ http {
# Upload limit for pictshare
client_max_body_size 50M;
location /api/v1 {
proxy_pass http://lemmy-epsilon:8581/api/v1;
location ~ ^/(api|docs|pictrs|feeds|nodeinfo|.well-known) {
proxy_pass http://lemmy-epsilon;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
location / {
proxy_pass http://lemmy-epsilon-ui:1234;
set $proxpass http://lemmy-epsilon-ui;
if ($http_accept = "application/activity+json") {
set $proxpass http://lemmy-epsilon;
}
proxy_pass $proxpass;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

View file

@ -15,6 +15,9 @@ COPY lemmy_db ./lemmy_db
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

View file

@ -9,8 +9,8 @@ third_semver=$(echo $new_tag | cut -d "." -f 3)
# 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"

View file

@ -21,10 +21,12 @@ lemmy-epsilon | lemmy_epsilon | [127.0.0.1:8580](http://127.0.0.1:8580) | uses b
You can log into each using the instance name, and `lemmy` as the password, IE (`lemmy_alpha`, `lemmy`).
To start federation between instances, visit one of them and search for a user, community or post, like this:
- `!main@lemmy-alpha:8540`
- `http://lemmy-beta:8550/post/3`
- `@lemmy-gamma@lemmy-gamma:8560`
To start federation between instances, visit one of them and search for a user, community or post, like this. Note that
the Lemmy backend runs on a different port than the frontend, so you have to increment the port number from
the URL bar by one.
- `!main@lemmy-alpha:8541`
- `http://lemmy-beta:8551/post/3`
- `@lemmy-gamma@lemmy-gamma:8561`
Firefox containers are a good way to test them interacting.

49
lemmy_api/Cargo.toml Normal file
View file

@ -0,0 +1,49 @@
[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"] }

View file

@ -1,16 +1,13 @@
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,
};
use actix_web::web::Data;
use lemmy_apub::{ApubLikeableType, ApubObjectType};
use lemmy_db::{
comment::*,
comment_view::*,
@ -24,12 +21,7 @@ use lemmy_db::{
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},
@ -37,6 +29,7 @@ use lemmy_utils::{
ConnectionId,
LemmyError,
};
use lemmy_websocket::{messages::SendComment, LemmyContext, UserOperation};
use std::str::FromStr;
#[async_trait::async_trait(?Send)]

View file

@ -1,10 +1,7 @@
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,
@ -22,16 +19,7 @@ use lemmy_db::{
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,
@ -40,6 +28,11 @@ use lemmy_utils::{
ConnectionId,
LemmyError,
};
use lemmy_websocket::{
messages::{GetCommunityUsersOnline, JoinCommunityRoom, SendCommunityRoomMessage},
LemmyContext,
UserOperation,
};
use std::str::FromStr;
#[async_trait::async_trait(?Send)]

539
lemmy_api/src/lib.rs Normal file
View file

@ -0,0 +1,539 @@
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;
use url::Url;
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(in crate) async fn linked_instances(pool: &DbPool) -> Result<Vec<String>, LemmyError> {
let mut instances: Vec<String> = Vec::new();
if Settings::get().federation.enabled {
let distinct_communities = blocking(pool, move |conn| {
Community::distinct_federated_communities(conn)
})
.await??;
instances = distinct_communities
.iter()
.map(|actor_id| Ok(Url::parse(actor_id)?.host_str().unwrap_or("").to_string()))
.collect::<Result<Vec<String>, LemmyError>>()?;
instances.append(&mut Settings::get().get_allowed_instances());
instances.retain(|a| {
!Settings::get().get_blocked_instances().contains(a)
&& !a.eq("")
&& !a.eq(&Settings::get().hostname)
});
// Sort and remove dupes
instances.sort_unstable();
instances.dedup();
}
Ok(instances)
}
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());
// }
}

View file

@ -1,10 +1,13 @@
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::*,
@ -19,11 +22,7 @@ use lemmy_db::{
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},
@ -31,6 +30,11 @@ use lemmy_utils::{
ConnectionId,
LemmyError,
};
use lemmy_websocket::{
messages::{GetPostUsersOnline, JoinPostRoom, SendPost},
LemmyContext,
UserOperation,
};
use std::str::FromStr;
use url::Url;

View file

@ -1,11 +1,14 @@
use crate::{
api::{get_user_from_jwt, get_user_from_jwt_opt, is_admin, linked_instances, Perform},
apub::fetcher::search_by_apub_id,
get_user_from_jwt,
get_user_from_jwt_opt,
is_admin,
linked_instances,
version,
LemmyContext,
Perform,
};
use actix_web::web::Data;
use anyhow::Context;
use lemmy_apub::fetcher::search_by_apub_id;
use lemmy_db::{
category::*,
comment_view::*,
@ -22,12 +25,7 @@ use lemmy_db::{
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,
@ -36,6 +34,11 @@ use lemmy_utils::{
ConnectionId,
LemmyError,
};
use lemmy_websocket::{
messages::{GetUsersOnline, SendAllMessage},
LemmyContext,
UserOperation,
};
use log::{debug, info};
use std::str::FromStr;

View file

@ -1,14 +1,17 @@
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::*,
@ -34,18 +37,7 @@ use lemmy_db::{
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,
@ -63,6 +55,11 @@ use lemmy_utils::{
ConnectionId,
LemmyError,
};
use lemmy_websocket::{
messages::{CaptchaItem, CheckCaptcha, JoinUserRoom, SendAllMessage, SendUserRoomMessage},
LemmyContext,
UserOperation,
};
use log::error;
use std::str::FromStr;

47
lemmy_apub/Cargo.toml Normal file
View file

@ -0,0 +1,47 @@
[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"] }

View file

@ -1,13 +1,11 @@
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;
@ -37,7 +35,7 @@ where
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,
{

View file

@ -1,4 +1,4 @@
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,

View file

@ -1,5 +1,4 @@
use crate::{
apub::{
activities::{generate_activity_id, send_activity_to_community},
check_actor_domain,
create_apub_response,
@ -16,9 +15,6 @@ use crate::{
ApubObjectType,
FromApub,
ToApub,
},
DbPool,
LemmyContext,
};
use activitystreams::{
activity::{
@ -46,6 +42,7 @@ use lemmy_db::{
post::Post,
user::User_,
Crud,
DbPool,
};
use lemmy_structs::blocking;
use lemmy_utils::{
@ -53,6 +50,7 @@ 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;

View file

@ -1,5 +1,4 @@
use crate::{
apub::{
activities::generate_activity_id,
activity_queue::send_activity,
check_actor_domain,
@ -13,9 +12,6 @@ use crate::{
FromApub,
GroupExt,
ToApub,
},
DbPool,
LemmyContext,
};
use activitystreams::{
activity::{
@ -44,6 +40,7 @@ use lemmy_db::{
naive_now,
post::Post,
user::User_,
DbPool,
};
use lemmy_structs::blocking;
use lemmy_utils::{
@ -52,6 +49,7 @@ use lemmy_utils::{
utils::{check_slurs, check_slurs_opt, convert_datetime},
LemmyError,
};
use lemmy_websocket::LemmyContext;
use serde::Deserialize;
use url::Url;

View file

@ -1,4 +1,4 @@
use crate::apub::ActorType;
use crate::ActorType;
use activitystreams::unparsed::UnparsedMutExt;
use activitystreams_ext::UnparsedExtension;
use actix_web::{client::ClientRequest, HttpRequest};

View file

@ -1,5 +1,4 @@
use crate::{
apub::{
check_is_apub_id_valid,
ActorType,
FromApub,
@ -7,9 +6,6 @@ use crate::{
PageExt,
PersonExt,
APUB_JSON_CONTENT_TYPE,
},
request::{retry, RecvError},
LemmyContext,
};
use activitystreams::{base::BaseExt, collection::OrderedCollection, object::Note, prelude::*};
use anyhow::{anyhow, Context};
@ -30,7 +26,13 @@ use lemmy_db::{
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;

View file

@ -1,5 +1,4 @@
use crate::{
apub::inbox::{
use crate::inbox::{
activities::{
create::receive_create,
delete::receive_delete,
@ -10,8 +9,6 @@ use crate::{
update::receive_update,
},
shared_inbox::{get_community_id_from_activity, receive_unhandled_activity},
},
LemmyContext,
};
use activitystreams::{
activity::*,
@ -21,6 +18,7 @@ use activitystreams::{
use actix_web::HttpResponse;
use anyhow::Context;
use lemmy_utils::{location_info, LemmyError};
use lemmy_websocket::LemmyContext;
pub async fn receive_announce(
activity: AnyBase,

View file

@ -1,5 +1,4 @@
use crate::{
apub::{
inbox::shared_inbox::{
announce_if_community_is_local,
get_user_from_activity,
@ -8,8 +7,6 @@ use crate::{
ActorType,
FromApub,
PageExt,
},
LemmyContext,
};
use activitystreams::{activity::Create, base::AnyBase, object::Note, prelude::*};
use actix_web::HttpResponse;
@ -20,14 +17,13 @@ use lemmy_db::{
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,

View file

@ -1,5 +1,4 @@
use crate::{
apub::{
fetcher::{get_or_fetch_and_insert_comment, get_or_fetch_and_insert_post},
inbox::shared_inbox::{
announce_if_community_is_local,
@ -10,8 +9,6 @@ use crate::{
FromApub,
GroupExt,
PageExt,
},
LemmyContext,
};
use activitystreams::{activity::Delete, base::AnyBase, object::Note, prelude::*};
use actix_web::HttpResponse;
@ -31,9 +28,13 @@ use lemmy_structs::{
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,

View file

@ -1,5 +1,4 @@
use crate::{
apub::{
fetcher::{get_or_fetch_and_insert_comment, get_or_fetch_and_insert_post},
inbox::shared_inbox::{
announce_if_community_is_local,
@ -8,8 +7,6 @@ use crate::{
},
FromApub,
PageExt,
},
LemmyContext,
};
use activitystreams::{activity::Dislike, base::AnyBase, object::Note, prelude::*};
use actix_web::HttpResponse;
@ -21,13 +18,13 @@ use lemmy_db::{
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,

View file

@ -1,5 +1,4 @@
use crate::{
apub::{
fetcher::{get_or_fetch_and_insert_comment, get_or_fetch_and_insert_post},
inbox::shared_inbox::{
announce_if_community_is_local,
@ -8,8 +7,6 @@ use crate::{
},
FromApub,
PageExt,
},
LemmyContext,
};
use activitystreams::{activity::Like, base::AnyBase, object::Note, prelude::*};
use actix_web::HttpResponse;
@ -21,13 +18,13 @@ use lemmy_db::{
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,

View file

@ -1,5 +1,4 @@
use crate::{
apub::{
fetcher::{get_or_fetch_and_insert_comment, get_or_fetch_and_insert_post},
inbox::shared_inbox::{
announce_if_community_is_local,
@ -11,8 +10,6 @@ use crate::{
FromApub,
GroupExt,
PageExt,
},
LemmyContext,
};
use activitystreams::{activity::Remove, base::AnyBase, object::Note, prelude::*};
use actix_web::HttpResponse;
@ -32,9 +29,13 @@ use lemmy_structs::{
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,

View file

@ -1,5 +1,4 @@
use crate::{
apub::{
fetcher::{get_or_fetch_and_insert_comment, get_or_fetch_and_insert_post},
inbox::shared_inbox::{
announce_if_community_is_local,
@ -10,8 +9,6 @@ use crate::{
FromApub,
GroupExt,
PageExt,
},
LemmyContext,
};
use activitystreams::{
activity::*,
@ -37,9 +34,13 @@ use lemmy_structs::{
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,

View file

@ -1,5 +1,4 @@
use crate::{
apub::{
fetcher::{get_or_fetch_and_insert_comment, get_or_fetch_and_insert_post},
inbox::shared_inbox::{
announce_if_community_is_local,
@ -9,8 +8,6 @@ use crate::{
ActorType,
FromApub,
PageExt,
},
LemmyContext,
};
use activitystreams::{activity::Update, base::AnyBase, object::Note, prelude::*};
use actix_web::HttpResponse;
@ -22,14 +19,13 @@ use lemmy_db::{
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,

View file

@ -1,12 +1,9 @@
use crate::{
apub::{
check_is_apub_id_valid,
extensions::signatures::verify,
fetcher::get_or_fetch_and_upsert_user,
insert_activity,
ActorType,
},
LemmyContext,
};
use activitystreams::{
activity::{ActorAndObject, Follow, Undo},
@ -22,6 +19,7 @@ use lemmy_db::{
};
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;

View file

@ -1,5 +1,4 @@
use crate::{
apub::{
check_is_apub_id_valid,
community::do_announce,
extensions::signatures::verify,
@ -19,8 +18,6 @@ use crate::{
update::receive_update,
},
insert_activity,
},
LemmyContext,
};
use activitystreams::{
activity::{ActorAndObject, ActorAndObjectRef},
@ -32,6 +29,7 @@ use actix_web::{web, HttpRequest, HttpResponse};
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;
@ -97,7 +95,7 @@ pub async fn shared_inbox(
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
@ -107,7 +105,7 @@ 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>
@ -119,7 +117,7 @@ where
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
@ -136,7 +134,7 @@ 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,

View file

@ -1,12 +1,9 @@
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,
};
use activitystreams::{
activity::{Accept, ActorAndObject, Create, Delete, Undo, Update},
@ -25,12 +22,9 @@ use lemmy_db::{
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;

View file

@ -1,3 +1,6 @@
#[macro_use]
extern crate lazy_static;
pub mod activities;
pub mod activity_queue;
pub mod comment;
@ -9,16 +12,10 @@ pub mod post;
pub mod private_message;
pub mod user;
use crate::{
apub::extensions::{
use crate::extensions::{
group_extensions::GroupExtension,
page_extension::PageExtension,
signatures::{PublicKey, PublicKeyExtension},
},
request::{retry, RecvError},
routes::webfinger::WebFingerResponse,
DbPool,
LemmyContext,
};
use activitystreams::{
activity::Follow,
@ -32,15 +29,17 @@ use activitystreams_ext::{Ext1, Ext2};
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;
@ -191,7 +190,7 @@ pub trait ApubObjectType {
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>

View file

@ -1,5 +1,4 @@
use crate::{
apub::{
activities::{generate_activity_id, send_activity_to_community},
check_actor_domain,
create_apub_response,
@ -13,9 +12,6 @@ use crate::{
FromApub,
PageExt,
ToApub,
},
DbPool,
LemmyContext,
};
use activitystreams::{
activity::{
@ -40,6 +36,7 @@ use lemmy_db::{
post::{Post, PostForm},
user::User_,
Crud,
DbPool,
};
use lemmy_structs::blocking;
use lemmy_utils::{
@ -47,6 +44,7 @@ use lemmy_utils::{
utils::{check_slurs, convert_datetime, remove_slurs},
LemmyError,
};
use lemmy_websocket::LemmyContext;
use serde::Deserialize;
use url::Url;

View file

@ -1,5 +1,4 @@
use crate::{
apub::{
activities::generate_activity_id,
activity_queue::send_activity,
check_actor_domain,
@ -11,9 +10,6 @@ use crate::{
ApubObjectType,
FromApub,
ToApub,
},
DbPool,
LemmyContext,
};
use activitystreams::{
activity::{
@ -31,9 +27,11 @@ use lemmy_db::{
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)]

View file

@ -1,5 +1,4 @@
use crate::{
apub::{
activities::generate_activity_id,
activity_queue::send_activity,
check_actor_domain,
@ -10,9 +9,6 @@ use crate::{
FromApub,
PersonExt,
ToApub,
},
DbPool,
LemmyContext,
};
use activitystreams::{
activity::{
@ -30,6 +26,7 @@ use anyhow::Context;
use lemmy_db::{
naive_now,
user::{UserForm, User_},
DbPool,
};
use lemmy_structs::blocking;
use lemmy_utils::{
@ -37,6 +34,7 @@ use lemmy_utils::{
utils::{check_slurs, check_slurs_opt, convert_datetime},
LemmyError,
};
use lemmy_websocket::LemmyContext;
use serde::Deserialize;
use url::Url;

View file

@ -4,14 +4,6 @@ extern crate diesel;
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, *};

View file

@ -1,9 +1,5 @@
#[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};

View file

@ -14,8 +14,6 @@ lemmy_utils = { path = "../lemmy_utils" }
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"]}

View file

@ -1,12 +1,3 @@
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;
@ -25,6 +16,24 @@ use lemmy_db::{
};
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

View file

@ -1,195 +1 @@
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,
}

View file

@ -7,8 +7,6 @@ edition = "2018"
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"] }
@ -27,3 +25,4 @@ openssl = "0.10"
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"] }

View file

@ -1,19 +1,9 @@
#[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;

View file

@ -1,5 +1,5 @@
use crate::LemmyError;
use anyhow::anyhow;
use lemmy_utils::LemmyError;
use std::future::Future;
use thiserror::Error;

View file

@ -0,0 +1,28 @@
[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"] }

View file

@ -1,7 +1,4 @@
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;
@ -10,7 +7,7 @@ use diesel::{
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,
@ -29,6 +26,14 @@ use std::{
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.
@ -57,6 +62,8 @@ pub struct ChatServer {
/// A list of the current captchas
pub(super) captchas: Vec<CaptchaItem>,
message_handler: MessageHandlerType,
/// An HTTP Client
client: Client,
@ -75,6 +82,7 @@ impl ChatServer {
pub fn startup(
pool: Pool<ConnectionManager<PgConnection>>,
rate_limiter: RateLimit,
message_handler: MessageHandlerType,
client: Client,
activity_queue: QueueHandle,
) -> ChatServer {
@ -87,6 +95,7 @@ impl ChatServer {
pool,
rate_limiter,
captchas: Vec::new(),
message_handler,
client,
activity_queue,
}
@ -180,7 +189,7 @@ impl ChatServer {
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 {
@ -204,7 +213,7 @@ impl ChatServer {
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 {
@ -227,7 +236,7 @@ impl ChatServer {
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 {
@ -249,7 +258,7 @@ impl ChatServer {
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 {
@ -340,8 +349,6 @@ impl ChatServer {
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) {
@ -349,110 +356,27 @@ impl ChatServer {
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,
}
}
}

View file

@ -1,59 +1,12 @@
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 {
@ -241,26 +194,6 @@ impl Handler<GetCommunityUsersOnline> 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 = ();

144
lemmy_websocket/src/lib.rs Normal file
View file

@ -0,0 +1,144 @@
#[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,
}

View file

@ -0,0 +1,132 @@
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,
}

View file

@ -1,128 +0,0 @@
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::{settings::Settings, APIError, ConnectionId, LemmyError};
use url::Url;
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(())
}
}
pub(in crate::api) async fn linked_instances(pool: &DbPool) -> Result<Vec<String>, LemmyError> {
let mut instances: Vec<String> = Vec::new();
if Settings::get().federation.enabled {
let distinct_communities = blocking(pool, move |conn| {
Community::distinct_federated_communities(conn)
})
.await??;
instances = distinct_communities
.iter()
.map(|actor_id| Ok(Url::parse(actor_id)?.host_str().unwrap_or("").to_string()))
.collect::<Result<Vec<String>, LemmyError>>()?;
instances.append(&mut Settings::get().get_allowed_instances());
instances.retain(|a| {
!Settings::get().get_blocked_instances().contains(a)
&& !a.eq("")
&& !a.eq(&Settings::get().hostname)
});
// Sort and remove dupes
instances.sort_unstable();
instances.dedup();
}
Ok(instances)
}

View file

@ -1,307 +1,4 @@
#![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());
// }
}

View file

@ -16,17 +16,14 @@ use diesel::{
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;
@ -77,6 +74,7 @@ async fn main() -> Result<(), LemmyError> {
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(),
)
@ -84,7 +82,7 @@ async fn main() -> Result<(), LemmyError> {
// 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(),

View file

@ -1,7 +1,8 @@
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) {

View file

@ -1,4 +1,6 @@
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},
@ -6,8 +8,6 @@ use crate::apub::{
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};

View file

@ -1,8 +1,8 @@
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,
@ -15,6 +15,7 @@ use lemmy_db::{
};
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;

View file

@ -1,9 +1,10 @@
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;

View file

@ -1,38 +1,21 @@
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(

View file

@ -1,9 +1,12 @@
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};

View file

@ -1,2 +0,0 @@
pub mod chat_server;
pub mod handlers;