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.email?.toLowerCase() === user.plexUsername.toLowerCase() ||
|
||||||
u.friendly_name?.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`, {
|
const historyResponse = await axios.get(`${TAUTULLI_URL}/api/v2`, {
|
||||||
params: {
|
params: {
|
||||||
apikey: TAUTULLI_API_KEY,
|
apikey: TAUTULLI_API_KEY,
|
||||||
cmd: "get_history",
|
cmd: "get_history",
|
||||||
user_id: tautulliUser.user_id,
|
user_id: tautulliUser.user_id,
|
||||||
after: afterTimestamp,
|
grouping: 0,
|
||||||
length: 1000,
|
length: 1000,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const historyData = historyResponse.data?.response?.data?.data || [];
|
const allHistoryData = historyResponse.data?.response?.data?.data || [];
|
||||||
if (!Array.isArray(historyData) || historyData.length === 0) return;
|
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 settings = await prisma.systemSettings.findFirst();
|
||||||
const creditsPerMinute = settings?.creditsPerMinute || 2;
|
const creditsPerMinute = settings?.creditsPerMinute || 2;
|
||||||
@@ -49,22 +62,54 @@ export async function backfillUserHistory(user: {
|
|||||||
let totalCredits = 0;
|
let totalCredits = 0;
|
||||||
let totalMinutes = 0;
|
let totalMinutes = 0;
|
||||||
for (const item of historyData) {
|
for (const item of historyData) {
|
||||||
const sessionId = item.reference_id?.toString() || item.id?.toString();
|
const sessionId =
|
||||||
if (!sessionId) continue;
|
item.id?.toString() ||
|
||||||
if (await prisma.watchEvent.findUnique({ where: { sessionId } }))
|
item.reference_id?.toString() ||
|
||||||
|
`${user.id}_${item.started}`;
|
||||||
|
if (!sessionId) {
|
||||||
|
console.log("Skip: no sessionId", item.title);
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
if (await prisma.watchEvent.findUnique({ where: { sessionId } })) {
|
||||||
|
console.log("Skip existing:", sessionId);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
const percentComplete = item.percent_complete || 0;
|
const percentComplete = item.percent_complete || 0;
|
||||||
const watchDurationSeconds =
|
const pausedCounter = item.paused_counter || 0;
|
||||||
item.duration ||
|
|
||||||
(item.stopped && item.started ? item.stopped - item.started : 0);
|
if (!item.started || !item.stopped) {
|
||||||
const watchDurationMinutes = Math.floor(watchDurationSeconds / 60);
|
console.log("Skip missing timestamps:", item.title);
|
||||||
if (
|
|
||||||
percentComplete < minWatchPercent ||
|
|
||||||
watchDurationMinutes < minWatchMinutes ||
|
|
||||||
watchDurationMinutes > 600
|
|
||||||
)
|
|
||||||
continue;
|
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;
|
const creditsEarned = watchDurationMinutes * creditsPerMinute;
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
"Credit:",
|
||||||
|
item.title,
|
||||||
|
watchDurationMinutes + "min",
|
||||||
|
creditsEarned + "$COOP",
|
||||||
|
);
|
||||||
|
|
||||||
const watchEvent = await prisma.watchEvent.create({
|
const watchEvent = await prisma.watchEvent.create({
|
||||||
data: {
|
data: {
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
@@ -73,7 +118,7 @@ export async function backfillUserHistory(user: {
|
|||||||
contentType: item.media_type || "movie",
|
contentType: item.media_type || "movie",
|
||||||
title: item.title || item.full_title || "Unknown",
|
title: item.title || item.full_title || "Unknown",
|
||||||
grandparentTitle: item.grandparent_title || null,
|
grandparentTitle: item.grandparent_title || null,
|
||||||
duration: item.stopped - item.started,
|
duration: watchDurationSeconds,
|
||||||
percentComplete: Math.floor(percentComplete),
|
percentComplete: Math.floor(percentComplete),
|
||||||
creditsEarned,
|
creditsEarned,
|
||||||
isProcessed: true,
|
isProcessed: true,
|
||||||
@@ -93,6 +138,11 @@ export async function backfillUserHistory(user: {
|
|||||||
totalCredits += creditsEarned;
|
totalCredits += creditsEarned;
|
||||||
totalMinutes += watchDurationMinutes;
|
totalMinutes += watchDurationMinutes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`Backfill done ${user.plexUsername}: ${totalCredits} credits, ${totalMinutes} min`,
|
||||||
|
);
|
||||||
|
|
||||||
if (totalCredits > 0)
|
if (totalCredits > 0)
|
||||||
await prisma.user.update({
|
await prisma.user.update({
|
||||||
where: { id: user.id },
|
where: { id: user.id },
|
||||||
|
|||||||
Reference in New Issue
Block a user