mirror of
https://github.com/immich-app/immich.git
synced 2026-05-18 03:10:24 +03:00
wip: call host function from plugin
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "immich-core",
|
"name": "immich-core",
|
||||||
"version": "2.4.1 ",
|
"version": "2.4.1",
|
||||||
"title": "Immich Core",
|
"title": "Immich Core",
|
||||||
"description": "Core workflow capabilities for Immich",
|
"description": "Core workflow capabilities for Immich",
|
||||||
"author": "Immich Team",
|
"author": "Immich Team",
|
||||||
|
|||||||
Vendored
+1
@@ -9,5 +9,6 @@ declare module 'extism:host' {
|
|||||||
interface user {
|
interface user {
|
||||||
updateAsset(ptr: PTR): I32;
|
updateAsset(ptr: PTR): I32;
|
||||||
addAssetToAlbum(ptr: PTR): I32;
|
addAssetToAlbum(ptr: PTR): I32;
|
||||||
|
getFacesForAsset(ptr: PTR): PTR;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+37
-15
@@ -1,4 +1,4 @@
|
|||||||
const { updateAsset, addAssetToAlbum } = Host.getFunctions();
|
const { updateAsset, addAssetToAlbum, getFacesForAsset } = Host.getFunctions();
|
||||||
|
|
||||||
function parseInput() {
|
function parseInput() {
|
||||||
return JSON.parse(Host.inputString());
|
return JSON.parse(Host.inputString());
|
||||||
@@ -9,29 +9,51 @@ function returnOutput(output: any) {
|
|||||||
return 0;
|
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() {
|
export function filterPerson() {
|
||||||
const input = parseInput();
|
const input = parseInput();
|
||||||
|
const { authToken, data, config } = input;
|
||||||
|
const { personIds, matchMode = 'any' } = config;
|
||||||
|
|
||||||
const { data, config } = input;
|
if (!personIds || personIds.length === 0) {
|
||||||
const { personIds, matchMode } = config;
|
return returnOutput({ passed: true });
|
||||||
|
|
||||||
const faces = data.faces || [];
|
|
||||||
|
|
||||||
if (faces.length === 0) {
|
|
||||||
return returnOutput({ passed: false });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
const assetPersonIds: string[] = faces
|
||||||
.filter((face: { personId: string | null }) => face.personId !== null)
|
.filter((face: { personId: string | null }) => face.personId !== null)
|
||||||
.map((face: { personId: string }) => face.personId);
|
.map((face: { personId: string }) => face.personId);
|
||||||
|
|
||||||
let passed = false;
|
let passed = false;
|
||||||
|
|
||||||
if (!personIds || personIds.length === 0) {
|
if (matchMode === 'all') {
|
||||||
passed = true;
|
|
||||||
} else if (matchMode === 'any') {
|
|
||||||
passed = personIds.some((id: string) => assetPersonIds.includes(id));
|
|
||||||
} else if (matchMode === 'all') {
|
|
||||||
passed = personIds.every((id: string) => assetPersonIds.includes(id));
|
passed = personIds.every((id: string) => assetPersonIds.includes(id));
|
||||||
} else if (matchMode === 'exact') {
|
} else if (matchMode === 'exact') {
|
||||||
const uniquePersonIds = new Set(personIds);
|
const uniquePersonIds = new Set(personIds);
|
||||||
@@ -80,7 +102,7 @@ export function actionAddToAlbum() {
|
|||||||
authToken,
|
authToken,
|
||||||
assetId: data.asset.id,
|
assetId: data.asset.id,
|
||||||
albumId: albumId,
|
albumId: albumId,
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
addAssetToAlbum(ptr.offset);
|
addAssetToAlbum(ptr.offset);
|
||||||
@@ -97,7 +119,7 @@ export function actionArchive() {
|
|||||||
authToken,
|
authToken,
|
||||||
id: data.asset.id,
|
id: data.asset.id,
|
||||||
visibility: 'archive',
|
visibility: 'archive',
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
updateAsset(ptr.offset);
|
updateAsset(ptr.offset);
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { AlbumRepository } from 'src/repositories/album.repository';
|
|||||||
import { AssetRepository } from 'src/repositories/asset.repository';
|
import { AssetRepository } from 'src/repositories/asset.repository';
|
||||||
import { CryptoRepository } from 'src/repositories/crypto.repository';
|
import { CryptoRepository } from 'src/repositories/crypto.repository';
|
||||||
import { LoggingRepository } from 'src/repositories/logging.repository';
|
import { LoggingRepository } from 'src/repositories/logging.repository';
|
||||||
|
import { PersonRepository } from 'src/repositories/person.repository';
|
||||||
import { AssetTable } from 'src/schema/tables/asset.table';
|
import { AssetTable } from 'src/schema/tables/asset.table';
|
||||||
import { requireAccess } from 'src/utils/access';
|
import { requireAccess } from 'src/utils/access';
|
||||||
|
|
||||||
@@ -20,6 +21,7 @@ export class PluginHostFunctions {
|
|||||||
private albumRepository: AlbumRepository,
|
private albumRepository: AlbumRepository,
|
||||||
private accessRepository: AccessRepository,
|
private accessRepository: AccessRepository,
|
||||||
private cryptoRepository: CryptoRepository,
|
private cryptoRepository: CryptoRepository,
|
||||||
|
private personRepository: PersonRepository,
|
||||||
private logger: LoggingRepository,
|
private logger: LoggingRepository,
|
||||||
private pluginJwtSecret: string,
|
private pluginJwtSecret: string,
|
||||||
) {}
|
) {}
|
||||||
@@ -33,6 +35,7 @@ export class PluginHostFunctions {
|
|||||||
'extism:host/user': {
|
'extism:host/user': {
|
||||||
updateAsset: (cp: CurrentPlugin, offs: bigint) => this.handleUpdateAsset(cp, offs),
|
updateAsset: (cp: CurrentPlugin, offs: bigint) => this.handleUpdateAsset(cp, offs),
|
||||||
addAssetToAlbum: (cp: CurrentPlugin, offs: bigint) => this.handleAddAssetToAlbum(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]);
|
await this.albumRepository.addAssetIds(albumId, [assetId]);
|
||||||
return 0;
|
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 {
|
interface WorkflowContext {
|
||||||
authToken: string;
|
authToken: string;
|
||||||
asset: Asset;
|
asset: Asset;
|
||||||
faces?: { faceId: string; personId: string | null }[];
|
personId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PluginInput<T = unknown> {
|
interface PluginInput<T = unknown> {
|
||||||
@@ -25,7 +25,7 @@ interface PluginInput<T = unknown> {
|
|||||||
config: T;
|
config: T;
|
||||||
data: {
|
data: {
|
||||||
asset: Asset;
|
asset: Asset;
|
||||||
faces?: { faceId: string; personId: string | null }[];
|
personId?: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,6 +46,7 @@ export class PluginService extends BaseService {
|
|||||||
this.albumRepository,
|
this.albumRepository,
|
||||||
this.accessRepository,
|
this.accessRepository,
|
||||||
this.cryptoRepository,
|
this.cryptoRepository,
|
||||||
|
this.personRepository,
|
||||||
this.logger,
|
this.logger,
|
||||||
this.pluginJwtSecret,
|
this.pluginJwtSecret,
|
||||||
);
|
);
|
||||||
@@ -231,7 +232,7 @@ export class PluginService extends BaseService {
|
|||||||
|
|
||||||
const authToken = this.cryptoRepository.signJwt({ userId: data.userId }, this.pluginJwtSecret);
|
const authToken = this.cryptoRepository.signJwt({ userId: data.userId }, this.pluginJwtSecret);
|
||||||
|
|
||||||
const context = {
|
const context: WorkflowContext = {
|
||||||
authToken,
|
authToken,
|
||||||
asset,
|
asset,
|
||||||
};
|
};
|
||||||
@@ -257,16 +258,10 @@ export class PluginService extends BaseService {
|
|||||||
|
|
||||||
const authToken = this.cryptoRepository.signJwt({ userId: data.userId }, this.pluginJwtSecret);
|
const authToken = this.cryptoRepository.signJwt({ userId: data.userId }, this.pluginJwtSecret);
|
||||||
|
|
||||||
const faces = await this.personRepository.getFaces(data.assetId);
|
const context: WorkflowContext = {
|
||||||
const facePayload = faces.map((face) => ({
|
|
||||||
faceId: face.id,
|
|
||||||
personId: face.personId,
|
|
||||||
}));
|
|
||||||
|
|
||||||
const context = {
|
|
||||||
authToken,
|
authToken,
|
||||||
asset,
|
asset,
|
||||||
faces: facePayload,
|
personId: data.personId,
|
||||||
};
|
};
|
||||||
|
|
||||||
const filtersPassed = await this.executeFilters(workflowFilters, context);
|
const filtersPassed = await this.executeFilters(workflowFilters, context);
|
||||||
@@ -309,7 +304,7 @@ export class PluginService extends BaseService {
|
|||||||
config: workflowFilter.filterConfig,
|
config: workflowFilter.filterConfig,
|
||||||
data: {
|
data: {
|
||||||
asset: context.asset,
|
asset: context.asset,
|
||||||
faces: context.faces,
|
personId: context.personId,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -352,6 +347,7 @@ export class PluginService extends BaseService {
|
|||||||
config: workflowAction.actionConfig,
|
config: workflowAction.actionConfig,
|
||||||
data: {
|
data: {
|
||||||
asset: context.asset,
|
asset: context.asset,
|
||||||
|
personId: context.personId,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user