feat: add immich integration and widget (#5165)

* feat: add immich integration and widget

* feat: use sdk
This commit is contained in:
Manuel authored and GitHub committed 2026-03-07 09:39:31 +01:00
1 parent 6aaaf31d8b
commit cdd90b8aa0
18 files changed
+609

No files matched your search

@@ -19,6 +19,7 @@ import { GitlabIntegration } from "../gitlab/gitlab-integration";
import { GlancesIntegration } from "../glances/glances-integration";
import { HomeAssistantIntegration } from "../homeassistant/homeassistant-integration";
import { ICalIntegration } from "../ical/ical-integration";
import { ImmichIntegration } from "../immich/immich-integration";
import { JellyfinIntegration } from "../jellyfin/jellyfin-integration";
import { JellyseerrIntegration } from "../jellyseerr/jellyseerr-integration";
import { LinuxServerIOIntegration } from "../linuxserverio/linuxserverio-integration";
@@ -111,6 +112,7 @@ export const integrationCreators = {
unraid: UnraidIntegration,
coolify: CoolifyIntegration,
glances: GlancesIntegration,
immich: ImmichIntegration,
} satisfies Record<IntegrationKind, IntegrationInstance | [(input: IntegrationInput) => Promise<Integration>]>;
type IntegrationInstanceOfKind<TKind extends keyof typeof integrationCreators> = {
@@ -0,0 +1,111 @@
import { getAlbumInfo, getAllAlbums, getMyUser, getServerStatistics, init, searchUsers } from "@immich/sdk";
import { createLogger } from "@homarr/core/infrastructure/logs";
import { ImageProxy } from "@homarr/image-proxy";
import type { IntegrationInput, IntegrationTestingInput } from "../base/integration";
import { Integration } from "../base/integration";
import type { TestingResult } from "../base/test-connection/test-connection-service";
export interface ImmichServerStats {
userCount: number;
photoCount: number;
videoCount: number;
totalLibraryUsageInBytes: number;
}
export interface ImmichAlbum {
id: string;
albumName: string;
assets: ImmichAsset[];
}
export interface ImmichAsset {
id: string;
deviceAssetId: string;
originalPath: string;
thumbhash: string | null;
fileModifiedAt: string;
fileCreatedAt: string;
updatedAt: string;
type: "IMAGE" | "VIDEO" | "AUDIO" | "OTHER";
publicLink: string;
}
const logger = createLogger({ module: "immich-integration" });
export class ImmichIntegration extends Integration {
constructor(integration: IntegrationInput) {
super(integration);
}
public async getServerStatsAsync(): Promise<ImmichServerStats> {
this.initClient();
const [statistics, users] = await Promise.all([getServerStatistics(), searchUsers()]);
return {
photoCount: statistics.photos,
userCount: users.length,
totalLibraryUsageInBytes: statistics.usage,
videoCount: statistics.videos,
};
}
public async getAlbumAsync(albumId: string): Promise<ImmichAlbum> {
this.initClient();
const album = await getAlbumInfo({ id: albumId });
const imageProxy = new ImageProxy();
return {
albumName: album.albumName,
assets: await Promise.all(
album.assets.map(async (asset) => {
const publicLink = await imageProxy.createImageAsync(
this.url(`/api/assets/${asset.id}/original`).toString(),
{
"x-api-key": this.getSecretValue("apiKey"),
},
);
return {
id: asset.id,
type: asset.type,
deviceAssetId: asset.deviceAssetId,
thumbhash: asset.thumbhash,
fileCreatedAt: asset.fileCreatedAt,
fileModifiedAt: asset.fileModifiedAt,
originalPath: asset.originalPath,
updatedAt: asset.updatedAt,
publicLink,
};
}),
),
id: album.id,
};
}
public async getAlbumsAsync(): Promise<
{
id: string;
albumName: string;
assetCount: number;
}[]
> {
this.initClient();
const albums = await getAllAlbums({});
return albums.map((album) => ({ id: album.id, albumName: album.albumName, assetCount: album.assetCount }));
}
protected async testingAsync(_: IntegrationTestingInput): Promise<TestingResult> {
this.initClient();
const user = await getMyUser();
logger.debug(`Logged in as ${user.name} (${user.id})`);
return { success: true };
}
/**
* Sets the credentials and prepares the client for calls.
* Must be called before any other functions.
* @private
*/
private initClient() {
init({ baseUrl: this.url("/api").toString(), apiKey: this.getSecretValue("apiKey") });
}
}
+2
View File
@@ -29,6 +29,7 @@ export { UnraidIntegration } from "./unraid/unraid-integration";
export { OPNsenseIntegration } from "./opnsense/opnsense-integration";
export { ICalIntegration } from "./ical/ical-integration";
export { CoolifyIntegration } from "./coolify/coolify-integration";
export { ImmichIntegration } from "./immich/immich-integration";
// Types
export type { IntegrationInput } from "./base/integration";
@@ -54,6 +55,7 @@ export type {
} from "./interfaces/media-transcoding/media-transcoding-types";
export type { ReleasesRepository, ReleaseResponse } from "./interfaces/releases-providers/releases-providers-types";
export type { Notification } from "./interfaces/notifications/notification-types";
export type { ImmichServerStats, ImmichAlbum, ImmichAsset } from "./immich/immich-integration";
// Schemas
export { downloadClientItemSchema } from "./interfaces/downloads/download-client-items";