mirror of
https://github.com/LemmyNet/lemmy.git
synced 2025-02-23 06:06:22 +00:00
add federation plugin hook
This commit is contained in:
parent
ef76b48505
commit
723045a32a
8 changed files with 70 additions and 48 deletions
4
Cargo.lock
generated
4
Cargo.lock
generated
|
@ -3206,6 +3206,8 @@ dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"diesel",
|
"diesel",
|
||||||
"enum_delegate",
|
"enum_delegate",
|
||||||
|
"extism",
|
||||||
|
"extism-convert",
|
||||||
"futures",
|
"futures",
|
||||||
"html2md",
|
"html2md",
|
||||||
"html2text",
|
"html2text",
|
||||||
|
@ -3404,8 +3406,6 @@ dependencies = [
|
||||||
"console-subscriber 0.1.10",
|
"console-subscriber 0.1.10",
|
||||||
"diesel",
|
"diesel",
|
||||||
"diesel-async",
|
"diesel-async",
|
||||||
"extism",
|
|
||||||
"extism-convert",
|
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"lemmy_api",
|
"lemmy_api",
|
||||||
"lemmy_api_common",
|
"lemmy_api_common",
|
||||||
|
|
|
@ -207,10 +207,6 @@ clap = { workspace = true }
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
actix-web-prom = "0.7.0"
|
actix-web-prom = "0.7.0"
|
||||||
actix-http = "3.6.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]
|
[dev-dependencies]
|
||||||
pretty_assertions = { workspace = true }
|
pretty_assertions = { workspace = true }
|
||||||
|
|
|
@ -47,6 +47,10 @@ html2md = "0.2.14"
|
||||||
html2text = "0.6.0"
|
html2text = "0.6.0"
|
||||||
stringreader = "0.1.1"
|
stringreader = "0.1.1"
|
||||||
enum_delegate = "0.2.0"
|
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]
|
[dev-dependencies]
|
||||||
serial_test = { workspace = true }
|
serial_test = { workspace = true }
|
||||||
|
|
|
@ -27,6 +27,7 @@ pub mod fetcher;
|
||||||
pub mod http;
|
pub mod http;
|
||||||
pub(crate) mod mentions;
|
pub(crate) mod mentions;
|
||||||
pub mod objects;
|
pub mod objects;
|
||||||
|
pub mod plugins;
|
||||||
pub mod protocol;
|
pub mod protocol;
|
||||||
|
|
||||||
pub const FEDERATION_HTTP_FETCH_LIMIT: u32 = 50;
|
pub const FEDERATION_HTTP_FETCH_LIMIT: u32 = 50;
|
||||||
|
|
|
@ -3,6 +3,7 @@ use crate::{
|
||||||
check_apub_id_valid_with_strictness,
|
check_apub_id_valid_with_strictness,
|
||||||
local_site_data_cached,
|
local_site_data_cached,
|
||||||
objects::{read_from_string_or_source_opt, verify_is_remote_object},
|
objects::{read_from_string_or_source_opt, verify_is_remote_object},
|
||||||
|
plugins::{call_plugin, load_plugins},
|
||||||
protocol::{
|
protocol::{
|
||||||
objects::{
|
objects::{
|
||||||
page::{Attachment, AttributedTo, Hashtag, HashtagType, Page, PageType},
|
page::{Attachment, AttributedTo, Hashtag, HashtagType, Page, PageType},
|
||||||
|
@ -50,6 +51,7 @@ use lemmy_utils::{
|
||||||
};
|
};
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use stringreader::StringReader;
|
use stringreader::StringReader;
|
||||||
|
use tracing::info;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
const MAX_TITLE_LENGTH: usize = 200;
|
const MAX_TITLE_LENGTH: usize = 200;
|
||||||
|
@ -224,7 +226,7 @@ impl Object for ApubPost {
|
||||||
let first_attachment = page.attachment.first();
|
let first_attachment = page.attachment.first();
|
||||||
let local_site = LocalSite::read(&mut context.pool()).await.ok();
|
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() {
|
let url = if let Some(attachment) = first_attachment.cloned() {
|
||||||
Some(attachment.url())
|
Some(attachment.url())
|
||||||
} else if page.kind == PageType::Video {
|
} else if page.kind == PageType::Video {
|
||||||
|
@ -275,8 +277,25 @@ impl Object for ApubPost {
|
||||||
.build()
|
.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 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(
|
generate_post_link_metadata(
|
||||||
post.clone(),
|
post.clone(),
|
||||||
|
|
39
crates/apub/src/plugins.rs
Normal file
39
crates/apub/src/plugins.rs
Normal 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(())
|
||||||
|
}
|
|
@ -60,7 +60,7 @@ pub struct Post {
|
||||||
pub alt_text: Option<String>,
|
pub alt_text: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, TypedBuilder)]
|
#[derive(Debug, Clone, TypedBuilder, Serialize, Deserialize)]
|
||||||
#[builder(field_defaults(default))]
|
#[builder(field_defaults(default))]
|
||||||
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
|
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
|
||||||
#[cfg_attr(feature = "full", diesel(table_name = post))]
|
#[cfg_attr(feature = "full", diesel(table_name = post))]
|
||||||
|
|
|
@ -6,12 +6,10 @@ use actix_web::{
|
||||||
Error,
|
Error,
|
||||||
};
|
};
|
||||||
use core::future::Ready;
|
use core::future::Ready;
|
||||||
use extism::{Manifest, Plugin};
|
|
||||||
use futures_util::future::LocalBoxFuture;
|
use futures_util::future::LocalBoxFuture;
|
||||||
use lemmy_utils::{error::LemmyResult, LemmyErrorType};
|
use lemmy_apub::plugins::{call_plugin, load_plugins};
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use std::{ffi::OsStr, fs::read_dir, future::ready, rc::Rc};
|
use std::{future::ready, rc::Rc};
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[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(())
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in a new issue