fix: tautulli backfill use individual sessions, client-side date filter, actual watch time
This commit is contained in:
@@ -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 },
|
||||
|
||||
Reference in New Issue
Block a user