fix: tautulli backfill use individual sessions, client-side date filter, actual watch time

This commit is contained in:
2026-04-23 11:42:01 -04:00
parent 3b9d709589
commit 4a68cc85f3
+73 -23
View File
@@ -21,25 +21,38 @@ export async function backfillUserHistory(user: {
u.email?.toLowerCase() === user.plexUsername.toLowerCase() ||
u.friendly_name?.toLowerCase() === user.plexUsername.toLowerCase(),
);
if (!tautulliUser) return;
if (!tautulliUser) {
console.log(`Tautulli user not found for ${user.plexUsername}`);
return;
}
const afterTimestamp = Math.floor(
(Date.now() - 30 * 24 * 60 * 60 * 1000) / 1000,
);
const startDate = (() => {
const d = new Date();
d.setDate(d.getDate() - 30);
return d.toISOString().split("T")[0];
})();
const afterTimestamp = Math.floor(new Date(startDate).getTime() / 1000);
const historyResponse = await axios.get(`${TAUTULLI_URL}/api/v2`, {
params: {
apikey: TAUTULLI_API_KEY,
cmd: "get_history",
user_id: tautulliUser.user_id,
after: afterTimestamp,
grouping: 0,
length: 1000,
},
});
const historyData = historyResponse.data?.response?.data?.data || [];
if (!Array.isArray(historyData) || historyData.length === 0) return;
const allHistoryData = historyResponse.data?.response?.data?.data || [];
const historyData = allHistoryData.filter((item: any) => {
if (!item.started) return false;
return item.started >= afterTimestamp;
});
console.log(
`Backfill ${user.plexUsername}: ${allHistoryData.length} total, ${historyData.length} in last 30d`,
);
if (!Array.isArray(historyData) || historyData.length === 0) {
console.log(`No recent history for ${user.plexUsername}`);
return;
}
const settings = await prisma.systemSettings.findFirst();
const creditsPerMinute = settings?.creditsPerMinute || 2;
@@ -49,22 +62,54 @@ export async function backfillUserHistory(user: {
let totalCredits = 0;
let totalMinutes = 0;
for (const item of historyData) {
const sessionId = item.reference_id?.toString() || item.id?.toString();
if (!sessionId) continue;
if (await prisma.watchEvent.findUnique({ where: { sessionId } }))
const sessionId =
item.id?.toString() ||
item.reference_id?.toString() ||
`${user.id}_${item.started}`;
if (!sessionId) {
console.log("Skip: no sessionId", item.title);
continue;
}
if (await prisma.watchEvent.findUnique({ where: { sessionId } })) {
console.log("Skip existing:", sessionId);
continue;
}
const percentComplete = item.percent_complete || 0;
const watchDurationSeconds =
item.duration ||
(item.stopped && item.started ? item.stopped - item.started : 0);
const watchDurationMinutes = Math.floor(watchDurationSeconds / 60);
if (
percentComplete < minWatchPercent ||
watchDurationMinutes < minWatchMinutes ||
watchDurationMinutes > 600
)
const pausedCounter = item.paused_counter || 0;
if (!item.started || !item.stopped) {
console.log("Skip missing timestamps:", item.title);
continue;
}
const watchDurationSeconds = item.stopped - item.started - pausedCounter;
const watchDurationMinutes = Math.floor(watchDurationSeconds / 60);
if (
watchDurationSeconds < 0 ||
watchDurationMinutes > 600 ||
percentComplete < minWatchPercent ||
watchDurationMinutes < minWatchMinutes
) {
console.log(
"Skip:",
item.title,
percentComplete + "%",
watchDurationMinutes + "min",
);
continue;
}
const creditsEarned = watchDurationMinutes * creditsPerMinute;
console.log(
"Credit:",
item.title,
watchDurationMinutes + "min",
creditsEarned + "$COOP",
);
const watchEvent = await prisma.watchEvent.create({
data: {
userId: user.id,
@@ -73,7 +118,7 @@ export async function backfillUserHistory(user: {
contentType: item.media_type || "movie",
title: item.title || item.full_title || "Unknown",
grandparentTitle: item.grandparent_title || null,
duration: item.stopped - item.started,
duration: watchDurationSeconds,
percentComplete: Math.floor(percentComplete),
creditsEarned,
isProcessed: true,
@@ -93,6 +138,11 @@ export async function backfillUserHistory(user: {
totalCredits += creditsEarned;
totalMinutes += watchDurationMinutes;
}
console.log(
`Backfill done ${user.plexUsername}: ${totalCredits} credits, ${totalMinutes} min`,
);
if (totalCredits > 0)
await prisma.user.update({
where: { id: user.id },