Files
streamyfin/utils/recently-added-notifications.ts
2025-02-20 20:34:20 +01:00

184 lines
5.5 KiB
TypeScript

import { storage } from "@/utils/mmkv";
import { Jellyfin } from "@jellyfin/sdk";
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
import { getItemsApi, getUserLibraryApi } from "@jellyfin/sdk/lib/utils/api";
import * as Notifications from "expo-notifications";
import { Platform } from "react-native";
import { getDeviceName } from "react-native-device-info";
import { getOrSetDeviceId } from "./device";
export const RECENTLY_ADDED_SENT_NOTIFICATIONS_ITEM_IDS_KEY =
"notification_send_for_item_ids";
const acceptedIemTypes = ["Movie", "Episode", "Series"];
async function sendNewItemNotification(item: BaseItemDto) {
if (Platform.isTV) return;
if (!item.Type) return;
if (!acceptedIemTypes.includes(item.Type)) return;
if (item.Type === "Movie")
await Notifications.scheduleNotificationAsync({
content: {
title: `New Movie Added`,
body: `${item.Name} (${item.ProductionYear})`,
},
trigger: null,
});
else if (item.Type === "Episode")
await Notifications.scheduleNotificationAsync({
content: {
title: `New Episode Added`,
body: `${item.SeriesName} - ${item.Name}`,
},
trigger: null,
});
else if (item.Type === "Series")
await Notifications.scheduleNotificationAsync({
content: {
title: `New Series Added`,
body: `${item.Name} (${item.ProductionYear})`,
},
trigger: null,
});
}
/**
* Fetches recently added items from Jellyfin and sends notifications for new content.
*
* This function performs the following operations:
* 1. Retrieves previously notified item IDs from storage
* 2. Connects to Jellyfin server using provided credentials
* 3. Fetches 5 most recent episodes and 5 most recent movies
* 4. Checks for new items that haven't been notified before
* 5. Sends notifications for new items
* 6. Updates storage with new item IDs
*
* Note: On first run (when no previous notifications exist), it will store all
* current items without sending notifications to avoid mass-notifications.
*
* @param userId - The Jellyfin user ID to fetch items for
* @param basePath - The base URL of the Jellyfin server
* @param token - The authentication token for the Jellyfin server
*/
export async function fetchAndStoreRecentlyAdded(
userId: string,
basePath: string,
token: string
) {
try {
const deviceName = await getDeviceName();
const id = getOrSetDeviceId();
// Get stored items
const _alreadySentItemIds = storage.getString(
RECENTLY_ADDED_SENT_NOTIFICATIONS_ITEM_IDS_KEY
);
const alreadySentItemIds: string[] = _alreadySentItemIds
? JSON.parse(_alreadySentItemIds)
: [];
console.log(
"fetchAndStoreRecentlyAdded ~ notifications stored:",
alreadySentItemIds.length
);
const jellyfin = new Jellyfin({
clientInfo: { name: "Streamyfin", version: "0.27.0" },
deviceInfo: {
name: deviceName,
id,
},
});
const api = jellyfin?.createApi(basePath, token);
const response1 = await getItemsApi(api).getItems({
userId: userId,
limit: 10,
recursive: true,
includeItemTypes: ["Episode"],
sortOrder: ["Descending"],
sortBy: ["DateCreated"],
});
const response2 = await getItemsApi(api).getItems({
userId: userId,
limit: 10,
recursive: true,
includeItemTypes: ["Movie"],
sortOrder: ["Descending"],
sortBy: ["DateCreated"],
});
const response3 = await getItemsApi(api).getItems({
userId: userId,
limit: 10,
recursive: true,
includeItemTypes: ["Series"],
sortOrder: ["Descending"],
sortBy: ["DateCreated"],
});
const newEpisodes =
response1.data.Items?.map((item) => ({
Id: item.Id,
Name: item.Name,
DateCreated: item.DateCreated,
Type: item.Type,
})) ?? [];
const newMovies =
response2.data.Items?.map((item) => ({
Id: item.Id,
Name: item.Name,
DateCreated: item.DateCreated,
Type: item.Type,
})) ?? [];
const newSeries =
response3.data.Items?.map((item) => ({
Id: item.Id,
Name: item.Name,
DateCreated: item.DateCreated,
Type: item.Type,
})) ?? [];
const newIds: string[] = [];
const items = [...newEpisodes, ...newMovies, ...newSeries];
// Don't send initial mass-notifications if there are no previously sent notifications
if (alreadySentItemIds.length === 0) {
// Store all items as sent (since these items are already in the users library)
storage.set(
RECENTLY_ADDED_SENT_NOTIFICATIONS_ITEM_IDS_KEY,
JSON.stringify(items.map((item) => item.Id))
);
return;
} else {
// Only send notifications for items that haven't been sent yet
for (const newItem of items) {
const alreadySentNotificationFor = alreadySentItemIds.some(
(id) => id === newItem.Id
);
if (!alreadySentNotificationFor) {
const fullItem = await getUserLibraryApi(api).getItem({
itemId: newItem.Id!,
userId: userId,
});
await sendNewItemNotification(fullItem.data);
newIds.push(newItem.Id!);
}
}
// Store all new items as sent, so that we don't send notifications for them again
storage.set(
RECENTLY_ADDED_SENT_NOTIFICATIONS_ITEM_IDS_KEY,
JSON.stringify([...new Set([...alreadySentItemIds, ...newIds])])
);
}
} catch (error) {
console.error("Error fetching recently added items:", error);
}
}