mirror of
https://github.com/immich-app/immich.git
synced 2026-01-21 17:13:17 -08:00
wip: call host function from plugin
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "immich-core",
|
||||
"version": "2.4.1 ",
|
||||
"version": "2.4.1",
|
||||
"title": "Immich Core",
|
||||
"description": "Core workflow capabilities for Immich",
|
||||
"author": "Immich Team",
|
||||
|
||||
1
plugins/src/index.d.ts
vendored
1
plugins/src/index.d.ts
vendored
@@ -9,5 +9,6 @@ declare module 'extism:host' {
|
||||
interface user {
|
||||
updateAsset(ptr: PTR): I32;
|
||||
addAssetToAlbum(ptr: PTR): I32;
|
||||
getFacesForAsset(ptr: PTR): PTR;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const { updateAsset, addAssetToAlbum } = Host.getFunctions();
|
||||
const { updateAsset, addAssetToAlbum, getFacesForAsset } = Host.getFunctions();
|
||||
|
||||
function parseInput() {
|
||||
return JSON.parse(Host.inputString());
|
||||
@@ -9,29 +9,51 @@ function returnOutput(output: any) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter by person - checks if the recognized person matches the configured person IDs.
|
||||
*
|
||||
* For PersonRecognized trigger:
|
||||
* - data.personId contains the ID of the person that was just recognized
|
||||
* - Checks if personId is in the configured list
|
||||
*
|
||||
* matchMode options:
|
||||
* - 'any': passes if the triggering person is in the list
|
||||
* - 'all': passes if all configured persons are present in the asset
|
||||
* - 'exact': passes if the asset contains exactly the configured persons
|
||||
*/
|
||||
export function filterPerson() {
|
||||
const input = parseInput();
|
||||
const { authToken, data, config } = input;
|
||||
const { personIds, matchMode = 'any' } = config;
|
||||
|
||||
const { data, config } = input;
|
||||
const { personIds, matchMode } = config;
|
||||
|
||||
const faces = data.faces || [];
|
||||
|
||||
if (faces.length === 0) {
|
||||
return returnOutput({ passed: false });
|
||||
if (!personIds || personIds.length === 0) {
|
||||
return returnOutput({ passed: true });
|
||||
}
|
||||
|
||||
const triggerPersonId = data.personId;
|
||||
|
||||
if (matchMode === 'any') {
|
||||
const passed = triggerPersonId && personIds.includes(triggerPersonId);
|
||||
return returnOutput({ passed });
|
||||
}
|
||||
|
||||
const payload = Memory.fromJsonObject({
|
||||
authToken,
|
||||
assetId: data.asset.id,
|
||||
});
|
||||
|
||||
const resultPtr = getFacesForAsset(payload.offset);
|
||||
payload.free();
|
||||
|
||||
const faces = JSON.parse(Memory.find(resultPtr).readJsonObject());
|
||||
|
||||
const assetPersonIds: string[] = faces
|
||||
.filter((face: { personId: string | null }) => face.personId !== null)
|
||||
.map((face: { personId: string }) => face.personId);
|
||||
|
||||
let passed = false;
|
||||
|
||||
if (!personIds || personIds.length === 0) {
|
||||
passed = true;
|
||||
} else if (matchMode === 'any') {
|
||||
passed = personIds.some((id: string) => assetPersonIds.includes(id));
|
||||
} else if (matchMode === 'all') {
|
||||
if (matchMode === 'all') {
|
||||
passed = personIds.every((id: string) => assetPersonIds.includes(id));
|
||||
} else if (matchMode === 'exact') {
|
||||
const uniquePersonIds = new Set(personIds);
|
||||
@@ -80,7 +102,7 @@ export function actionAddToAlbum() {
|
||||
authToken,
|
||||
assetId: data.asset.id,
|
||||
albumId: albumId,
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
addAssetToAlbum(ptr.offset);
|
||||
@@ -97,7 +119,7 @@ export function actionArchive() {
|
||||
authToken,
|
||||
id: data.asset.id,
|
||||
visibility: 'archive',
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
updateAsset(ptr.offset);
|
||||
|
||||
@@ -7,6 +7,7 @@ import { AlbumRepository } from 'src/repositories/album.repository';
|
||||
import { AssetRepository } from 'src/repositories/asset.repository';
|
||||
import { CryptoRepository } from 'src/repositories/crypto.repository';
|
||||
import { LoggingRepository } from 'src/repositories/logging.repository';
|
||||
import { PersonRepository } from 'src/repositories/person.repository';
|
||||
import { AssetTable } from 'src/schema/tables/asset.table';
|
||||
import { requireAccess } from 'src/utils/access';
|
||||
|
||||
@@ -20,6 +21,7 @@ export class PluginHostFunctions {
|
||||
private albumRepository: AlbumRepository,
|
||||
private accessRepository: AccessRepository,
|
||||
private cryptoRepository: CryptoRepository,
|
||||
private personRepository: PersonRepository,
|
||||
private logger: LoggingRepository,
|
||||
private pluginJwtSecret: string,
|
||||
) {}
|
||||
@@ -33,6 +35,7 @@ export class PluginHostFunctions {
|
||||
'extism:host/user': {
|
||||
updateAsset: (cp: CurrentPlugin, offs: bigint) => this.handleUpdateAsset(cp, offs),
|
||||
addAssetToAlbum: (cp: CurrentPlugin, offs: bigint) => this.handleAddAssetToAlbum(cp, offs),
|
||||
getFacesForAsset: (cp: CurrentPlugin, offs: bigint) => this.handleGetFacesForAsset(cp, offs),
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -117,4 +120,28 @@ export class PluginHostFunctions {
|
||||
await this.albumRepository.addAssetIds(albumId, [assetId]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Host function wrapper for getFacesForAsset.
|
||||
* Reads the input from the plugin, parses it, and returns faces data.
|
||||
*/
|
||||
private async handleGetFacesForAsset(cp: CurrentPlugin, offs: bigint) {
|
||||
const input = JSON.parse(cp.read(offs)!.text());
|
||||
const result = await this.getFacesForAsset(input);
|
||||
return cp.store(JSON.stringify(result));
|
||||
}
|
||||
|
||||
async getFacesForAsset(input: { authToken: string; assetId: string }) {
|
||||
const { authToken, assetId } = input;
|
||||
|
||||
const auth = this.validateToken(authToken);
|
||||
|
||||
await requireAccess(this.accessRepository, {
|
||||
auth: { user: { id: auth.userId } } as any,
|
||||
permission: Permission.AssetRead,
|
||||
ids: [assetId],
|
||||
});
|
||||
|
||||
return this.personRepository.getFaces(assetId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ import { IWorkflowJob, JobItem, JobOf, WorkflowData } from 'src/types';
|
||||
interface WorkflowContext {
|
||||
authToken: string;
|
||||
asset: Asset;
|
||||
faces?: { faceId: string; personId: string | null }[];
|
||||
personId?: string;
|
||||
}
|
||||
|
||||
interface PluginInput<T = unknown> {
|
||||
@@ -25,7 +25,7 @@ interface PluginInput<T = unknown> {
|
||||
config: T;
|
||||
data: {
|
||||
asset: Asset;
|
||||
faces?: { faceId: string; personId: string | null }[];
|
||||
personId?: string;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -46,6 +46,7 @@ export class PluginService extends BaseService {
|
||||
this.albumRepository,
|
||||
this.accessRepository,
|
||||
this.cryptoRepository,
|
||||
this.personRepository,
|
||||
this.logger,
|
||||
this.pluginJwtSecret,
|
||||
);
|
||||
@@ -231,7 +232,7 @@ export class PluginService extends BaseService {
|
||||
|
||||
const authToken = this.cryptoRepository.signJwt({ userId: data.userId }, this.pluginJwtSecret);
|
||||
|
||||
const context = {
|
||||
const context: WorkflowContext = {
|
||||
authToken,
|
||||
asset,
|
||||
};
|
||||
@@ -257,16 +258,10 @@ export class PluginService extends BaseService {
|
||||
|
||||
const authToken = this.cryptoRepository.signJwt({ userId: data.userId }, this.pluginJwtSecret);
|
||||
|
||||
const faces = await this.personRepository.getFaces(data.assetId);
|
||||
const facePayload = faces.map((face) => ({
|
||||
faceId: face.id,
|
||||
personId: face.personId,
|
||||
}));
|
||||
|
||||
const context = {
|
||||
const context: WorkflowContext = {
|
||||
authToken,
|
||||
asset,
|
||||
faces: facePayload,
|
||||
personId: data.personId,
|
||||
};
|
||||
|
||||
const filtersPassed = await this.executeFilters(workflowFilters, context);
|
||||
@@ -309,7 +304,7 @@ export class PluginService extends BaseService {
|
||||
config: workflowFilter.filterConfig,
|
||||
data: {
|
||||
asset: context.asset,
|
||||
faces: context.faces,
|
||||
personId: context.personId,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -352,6 +347,7 @@ export class PluginService extends BaseService {
|
||||
config: workflowAction.actionConfig,
|
||||
data: {
|
||||
asset: context.asset,
|
||||
personId: context.personId,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user