feat: more plugin triggers and methods (#28690)

This commit is contained in:
Jason Rasmussen 2026-05-29 14:02:07 -04:00 committed by GitHub
parent 58586483dc
commit da8505f61d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
28 changed files with 322 additions and 133 deletions

View file

@ -7,8 +7,8 @@
"wasmPath": "dist/plugin.wasm",
"templates": [
{
"name": "auto-archive-screenshots",
"title": "Auto-archive screenshots",
"name": "screenshots-smart-album",
"title": "Archive screenshots",
"description": "Archive uploads with \"screenshot\" in the filename and optionally add them to an album",
"trigger": "AssetCreate",
"steps": [
@ -29,6 +29,27 @@
{
"method": "immich-plugin-core#assetAddToAlbums",
"config": {
"albumName": "Screenshots",
"albumIds": []
}
}
],
"uiHints": ["SmartAlbum"]
},
{
"name": "missing-timezone-smart-album",
"title": "Missing timezone",
"description": "Automatically create an album for assets without a time zone",
"trigger": "AssetMetadataExtraction",
"steps": [
{
"method": "immich-plugin-core#assetMissingTimeZoneFilter",
"config": {}
},
{
"method": "immich-plugin-core#assetAddToAlbums",
"config": {
"albumName": "Missing time zone",
"albumIds": []
}
}
@ -68,6 +89,24 @@
},
"uiHints": ["Filter"]
},
{
"name": "assetMissingTimeZoneFilter",
"title": "Filter by missing time zone",
"description": "Filter assets that have no time zone information",
"types": ["AssetV1"],
"schema": {
"type": "object",
"properties": {
"inverse": {
"type": "boolean",
"title": "Inverse",
"description": "Missing by default, set to true to filter assets with a time zone",
"default": false
}
}
},
"uiHints": ["Filter"]
},
{
"name": "filterFileType",
"title": "Filter by file type",
@ -189,6 +228,12 @@
"array": true,
"description": "Target album IDs",
"uiHint": "AlbumId"
},
"albumName": {
"type": "string",
"title": "Album name",
"array": true,
"description": "Use an album with this name if one exists, otherwise create a new one"
}
},
"required": ["albumIds"]

View file

@ -13,6 +13,7 @@
"license": "AGPL-3.0",
"devDependencies": {
"@extism/js-pdk": "^1.0.1",
"@immich/sdk": "workspace:*",
"@immich/plugin-sdk": "workspace:*",
"esbuild": "^0.28.0",
"typescript": "^6.0.0"

View file

@ -1,14 +1,20 @@
// copy from
// import '@immich/plugin-sdk/host-functions';
// keep in sync with plugin-sdk/host-functions.ts';
declare module 'extism:host' {
interface user {
albumAddAssets(ptr: PTR): I64;
searchAlbums(ptr: PTR): I64;
createAlbum(ptr: PTR): I64;
addAssetsToAlbum(ptr: PTR): I64;
addAssetsToAlbums(ptr: PTR): I64;
}
}
// keep in sync with manifest.json
declare module 'main' {
// filters
export function assetFileFilter(): I32;
export function assetMissingTimeZoneFilter(): I32;
// updates
export function assetFavorite(): I32;
export function assetVisibility(): I32;
export function assetArchive(): I32;

View file

@ -1,4 +1,5 @@
import { AssetStatus, AssetVisibility, WorkflowType, wrapper } from '@immich/plugin-sdk';
import { wrapper } from '@immich/plugin-sdk';
import { AssetVisibility, WorkflowType } from '@immich/sdk';
type AssetFileFilterConfig = {
pattern: string;
@ -41,6 +42,14 @@ export const assetFileFilter = () => {
});
};
export const assetMissingTimeZoneFilter = () => {
return wrapper<WorkflowType.AssetV1, { inverse?: boolean }>(({ config, data }) => {
const hasTimeZone = !!data.asset?.exifInfo?.timeZone;
const needsTimeZone = config.inverse ? true : false;
return { workflow: { continue: hasTimeZone === needsTimeZone } };
});
};
export const assetFavorite = () => {
return wrapper<WorkflowType.AssetV1, { inverse?: boolean }>(({ config, data }) => {
const target = config.inverse ? false : true;
@ -89,28 +98,35 @@ export const assetLock = () => {
};
export const assetTrash = () => {
return wrapper<WorkflowType.AssetV1, { inverse?: boolean }>(({ config, data }) => ({
changes: {
asset: config.inverse
? { deletedAt: null, status: AssetStatus.Active }
: { deletedAt: new Date().toISOString(), status: AssetStatus.Trashed },
},
}));
// TODO use trash/untrash host functions
return wrapper<WorkflowType.AssetV1, { inverse?: boolean }>(() => ({}));
};
export const assetAddToAlbums = () => {
return wrapper<WorkflowType.AssetV1, { albumIds: string[] }>(({ config, data, functions }) => {
return wrapper<WorkflowType.AssetV1, { albumIds: string[]; albumName?: string }>(({ config, data, functions }) => {
const assetId = data.asset.id;
if (config.albumIds.length === 0) {
// noop
return {};
if (!config.albumName) {
return {};
}
const [existing] = functions.searchAlbums({ name: config.albumName });
if (!existing) {
const created = functions.createAlbum({ albumName: config.albumName, assetIds: [assetId] });
config.albumIds.push(created.id);
return {};
}
config.albumIds.push(existing.id);
}
if (config.albumIds.length === 1) {
functions.albumAddAssets(config.albumIds[0], [data.asset.id]);
functions.addAssetsToAlbum(config.albumIds[0], [assetId]);
return {};
}
functions.addAssetsToAlbums({ albumIds: config.albumIds, assetIds: [data.asset.id] });
functions.addAssetsToAlbums({ albumIds: config.albumIds, assetIds: [assetId] });
return {};
});
};