allow loading multiple plugins

This commit is contained in:
Felix Ableitner 2024-05-15 12:33:15 +02:00
parent 2ef6e2e43b
commit 28da169259
3 changed files with 72 additions and 67 deletions

View file

@ -3,7 +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}, plugins::Plugins,
protocol::{ protocol::{
objects::{ objects::{
page::{Attachment, AttributedTo, Hashtag, HashtagType, Page, PageType}, page::{Attachment, AttributedTo, Hashtag, HashtagType, Page, PageType},
@ -51,7 +51,6 @@ 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;
@ -277,25 +276,13 @@ impl Object for ApubPost {
.build() .build()
}; };
// TODO: move this all into helper function let mut plugins = Plugins::load()?;
let before_plugin_hook = "federation_before_receive_post"; plugins.call("federation_before_receive_post", &mut form)?;
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 mut 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"; plugins.call("federation_after_receive_post", &mut 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(),

View file

@ -2,8 +2,14 @@ use extism::{Manifest, Plugin};
use lemmy_utils::{error::LemmyResult, LemmyErrorType}; use lemmy_utils::{error::LemmyResult, LemmyErrorType};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::{ffi::OsStr, fs::read_dir}; use std::{ffi::OsStr, fs::read_dir};
use tracing::info;
pub fn load_plugins() -> LemmyResult<Option<Plugin>> { pub struct Plugins {
plugins: Vec<Plugin>,
}
impl Plugins {
pub fn load() -> LemmyResult<Self> {
// TODO: make dir configurable via env var // TODO: make dir configurable via env var
// TODO: should only read fs once at startup for performance // TODO: should only read fs once at startup for performance
let plugin_paths = read_dir("plugins")?; let plugin_paths = read_dir("plugins")?;
@ -15,25 +21,40 @@ pub fn load_plugins() -> LemmyResult<Option<Plugin>> {
wasm_files.push(path); wasm_files.push(path);
} }
} }
if !wasm_files.is_empty() { let plugins = wasm_files
// TODO: what if theres more than one plugin for the same hook? .into_iter()
let manifest = Manifest::new(wasm_files); .map(|w| {
let plugin = Plugin::new(manifest, [], true)?; let manifest = Manifest::new(vec![w]);
Ok(Some(plugin)) Plugin::new(manifest, [], true).unwrap()
} else { })
Ok(None) .collect();
} Ok(Self { plugins })
} }
pub fn call_plugin<T: Serialize + for<'de> Deserialize<'de> + Clone>( pub fn exists(&mut self, name: &str) -> bool {
mut plugins: Plugin, for p in &mut self.plugins {
if p.function_exists(name) {
return true;
}
}
false
}
pub fn call<T: Serialize + for<'de> Deserialize<'de> + Clone>(
&mut self,
name: &str, name: &str,
data: &mut T, data: &mut T,
) -> LemmyResult<()> { ) -> LemmyResult<()> {
*data = plugins info!("Calling plugin hook {name}");
for p in &mut self.plugins {
if p.function_exists(name) {
*data = p
.call::<extism_convert::Json<T>, extism_convert::Json<T>>(name, (*data).clone().into()) .call::<extism_convert::Json<T>, extism_convert::Json<T>>(name, (*data).clone().into())
.map_err(|e| LemmyErrorType::PluginError(e.to_string()))? .map_err(|e| LemmyErrorType::PluginError(e.to_string()))?
.0 .0
.into(); .into();
}
}
Ok(()) Ok(())
} }
}

View file

@ -7,7 +7,7 @@ use actix_web::{
}; };
use core::future::Ready; use core::future::Ready;
use futures_util::future::LocalBoxFuture; use futures_util::future::LocalBoxFuture;
use lemmy_apub::plugins::{call_plugin, load_plugins}; use lemmy_apub::plugins::Plugins;
use serde_json::Value; use serde_json::Value;
use std::{future::ready, rc::Rc}; use std::{future::ready, rc::Rc};
use tracing::info; use tracing::info;
@ -61,34 +61,31 @@ where
let path = service_req.path().replace("/api/v3/", "").replace("/", "_"); let path = service_req.path().replace("/api/v3/", "").replace("/", "_");
// TODO: naming can be a bit silly, `POST /api/v3/post` becomes `api_before_post_post` // TODO: naming can be a bit silly, `POST /api/v3/post` becomes `api_before_post_post`
let before_plugin_hook = format!("api_before_{method}_{path}").to_lowercase(); let before_plugin_hook = format!("api_before_{method}_{path}").to_lowercase();
let mut plugins = Plugins::load()?;
info!("Calling plugin hook {}", &before_plugin_hook); info!("Calling plugin hook {}", &before_plugin_hook);
if let Some(mut plugins) = load_plugins()? { if plugins.exists(&before_plugin_hook) {
if plugins.function_exists(&before_plugin_hook) {
let payload = service_req.extract::<Bytes>().await?; let payload = service_req.extract::<Bytes>().await?;
let mut json: Value = serde_json::from_slice(&payload.to_vec()).unwrap_or(Value::Null);
let mut json: Value = serde_json::from_slice(&payload.to_vec())?; plugins.call(&before_plugin_hook, &mut json)?;
call_plugin(plugins, &before_plugin_hook, &mut json)?;
let (_, mut new_payload) = Payload::create(true); let (_, mut new_payload) = Payload::create(true);
new_payload.unread_data(Bytes::from(serde_json::to_vec(&json)?)); new_payload.unread_data(Bytes::from(serde_json::to_vec(&json)?));
service_req.set_payload(new_payload.into()); service_req.set_payload(new_payload.into());
} }
}
let mut res = svc.call(service_req).await?; let mut res = svc.call(service_req).await?;
let after_plugin_hook = format!("api_after_{method}_{path}").to_lowercase(); let after_plugin_hook = format!("api_after_{method}_{path}").to_lowercase();
info!("Calling plugin hook {}", &after_plugin_hook); info!("Calling plugin hook {}", &after_plugin_hook);
if let Some(mut plugins) = load_plugins()? { if plugins.exists(&after_plugin_hook) {
if plugins.function_exists(&before_plugin_hook) {
res = res.map_body(|_, body| { res = res.map_body(|_, body| {
let mut json: Value = let mut json: Value =
serde_json::from_slice(&body.try_into_bytes().unwrap().to_vec()).unwrap(); serde_json::from_slice(&body.try_into_bytes().unwrap().to_vec()).unwrap();
call_plugin(plugins, &after_plugin_hook, &mut json).unwrap(); plugins.call(&after_plugin_hook, &mut json).unwrap();
BoxBody::new(Bytes::from(serde_json::to_vec(&json).unwrap())) BoxBody::new(Bytes::from(serde_json::to_vec(&json).unwrap()))
}); });
} }
}
Ok(res) Ok(res)
}) })