add federation plugin hook

pull/4695/merge^2
Felix Ableitner 2024-05-07 11:05:13 +02:00
parent ef76b48505
commit 723045a32a
8 changed files with 70 additions and 48 deletions

4
Cargo.lock generated
View File

@ -3206,6 +3206,8 @@ dependencies = [
"chrono",
"diesel",
"enum_delegate",
"extism",
"extism-convert",
"futures",
"html2md",
"html2text",
@ -3404,8 +3406,6 @@ dependencies = [
"console-subscriber 0.1.10",
"diesel",
"diesel-async",
"extism",
"extism-convert",
"futures-util",
"lemmy_api",
"lemmy_api_common",

View File

@ -207,10 +207,6 @@ clap = { workspace = true }
serde.workspace = true
actix-web-prom = "0.7.0"
actix-http = "3.6.0"
extism = { version = "1.2.0", features = [
"register-filesystem",
], default-features = false }
extism-convert = { version = "1.2.0", default-features = false }
[dev-dependencies]
pretty_assertions = { workspace = true }

View File

@ -47,6 +47,10 @@ html2md = "0.2.14"
html2text = "0.6.0"
stringreader = "0.1.1"
enum_delegate = "0.2.0"
extism = { version = "1.2.0", features = [
"register-filesystem",
], default-features = false }
extism-convert = { version = "1.2.0", default-features = false }
[dev-dependencies]
serial_test = { workspace = true }

View File

@ -27,6 +27,7 @@ pub mod fetcher;
pub mod http;
pub(crate) mod mentions;
pub mod objects;
pub mod plugins;
pub mod protocol;
pub const FEDERATION_HTTP_FETCH_LIMIT: u32 = 50;

View File

@ -3,6 +3,7 @@ use crate::{
check_apub_id_valid_with_strictness,
local_site_data_cached,
objects::{read_from_string_or_source_opt, verify_is_remote_object},
plugins::{call_plugin, load_plugins},
protocol::{
objects::{
page::{Attachment, AttributedTo, Hashtag, HashtagType, Page, PageType},
@ -50,6 +51,7 @@ use lemmy_utils::{
};
use std::ops::Deref;
use stringreader::StringReader;
use tracing::info;
use url::Url;
const MAX_TITLE_LENGTH: usize = 200;
@ -224,7 +226,7 @@ impl Object for ApubPost {
let first_attachment = page.attachment.first();
let local_site = LocalSite::read(&mut context.pool()).await.ok();
let form = if !page.is_mod_action(context).await? {
let mut form = if !page.is_mod_action(context).await? {
let url = if let Some(attachment) = first_attachment.cloned() {
Some(attachment.url())
} else if page.kind == PageType::Video {
@ -275,8 +277,25 @@ impl Object for ApubPost {
.build()
};
// TODO: move this all into helper function
let before_plugin_hook = "federation_before_receive_post";
info!("Calling plugin hook {}", &before_plugin_hook);
if let Some(mut plugins) = load_plugins()? {
if plugins.function_exists(&before_plugin_hook) {
call_plugin(plugins, &before_plugin_hook, &mut form)?;
}
}
let timestamp = page.updated.or(page.published).unwrap_or_else(naive_now);
let post = Post::insert_apub(&mut context.pool(), timestamp, &form).await?;
let mut post = Post::insert_apub(&mut context.pool(), timestamp, &form).await?;
let after_plugin_hook = "federation_after_receive_post";
info!("Calling plugin hook {}", &after_plugin_hook);
if let Some(mut plugins) = load_plugins()? {
if plugins.function_exists(&after_plugin_hook) {
call_plugin(plugins, &after_plugin_hook, &mut post)?;
}
}
generate_post_link_metadata(
post.clone(),

View File

@ -0,0 +1,39 @@
use extism::{Manifest, Plugin};
use lemmy_utils::{error::LemmyResult, LemmyErrorType};
use serde::{Deserialize, Serialize};
use std::{ffi::OsStr, fs::read_dir};
pub fn load_plugins() -> LemmyResult<Option<Plugin>> {
// TODO: make dir configurable via env var
// TODO: should only read fs once at startup for performance
let plugin_paths = read_dir("plugins")?;
let mut wasm_files = vec![];
for path in plugin_paths {
let path = path?.path();
if path.extension() == Some(OsStr::new("wasm")) {
wasm_files.push(path);
}
}
if !wasm_files.is_empty() {
// TODO: what if theres more than one plugin for the same hook?
let manifest = Manifest::new(wasm_files);
let plugin = Plugin::new(manifest, [], true)?;
Ok(Some(plugin))
} else {
Ok(None)
}
}
pub fn call_plugin<T: Serialize + for<'de> Deserialize<'de> + Clone>(
mut plugins: Plugin,
name: &str,
data: &mut T,
) -> LemmyResult<()> {
*data = plugins
.call::<extism_convert::Json<T>, extism_convert::Json<T>>(name, (*data).clone().into())
.map_err(|e| LemmyErrorType::PluginError(e.to_string()))?
.0
.into();
Ok(())
}

View File

@ -60,7 +60,7 @@ pub struct Post {
pub alt_text: Option<String>,
}
#[derive(Debug, Clone, TypedBuilder)]
#[derive(Debug, Clone, TypedBuilder, Serialize, Deserialize)]
#[builder(field_defaults(default))]
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
#[cfg_attr(feature = "full", diesel(table_name = post))]

View File

@ -6,12 +6,10 @@ use actix_web::{
Error,
};
use core::future::Ready;
use extism::{Manifest, Plugin};
use futures_util::future::LocalBoxFuture;
use lemmy_utils::{error::LemmyResult, LemmyErrorType};
use serde::{Deserialize, Serialize};
use lemmy_apub::plugins::{call_plugin, load_plugins};
use serde_json::Value;
use std::{ffi::OsStr, fs::read_dir, future::ready, rc::Rc};
use std::{future::ready, rc::Rc};
use tracing::info;
#[derive(Clone)]
@ -97,38 +95,3 @@ where
})
}
}
fn load_plugins() -> LemmyResult<Option<Plugin>> {
// TODO: make dir configurable via env var
// TODO: should only read fs once at startup for performance
let plugin_paths = read_dir("plugins")?;
let mut wasm_files = vec![];
for path in plugin_paths {
let path = path?.path();
if path.extension() == Some(OsStr::new("wasm")) {
wasm_files.push(path);
}
}
if !wasm_files.is_empty() {
// TODO: what if theres more than one plugin for the same hook?
let manifest = Manifest::new(wasm_files);
let plugin = Plugin::new(manifest, [], true)?;
Ok(Some(plugin))
} else {
Ok(None)
}
}
pub fn call_plugin<T: Serialize + for<'de> Deserialize<'de> + Clone>(
mut plugins: Plugin,
name: &str,
data: &mut T,
) -> LemmyResult<()> {
*data = plugins
.call::<extism_convert::Json<T>, extism_convert::Json<T>>(name, (*data).clone().into())
.map_err(|e| LemmyErrorType::PluginError(e.to_string()))?
.0
.into();
Ok(())
}