import { database } from '$lib/Database/activities'; import type { AniListAuthorisation, UserIdentity } from './identity'; export interface ActivityHistoryEntry { date: number; amount: number; } export const fillMissingDays = ( inputActivities: ActivityHistoryEntry[], startOfYear = false ): ActivityHistoryEntry[] => { if (inputActivities.length === 0) return startOfYear ? fillDateRange( new Date(new Date().getUTCFullYear(), 0, 1), new Date(new Date().getUTCFullYear() + 1, 0, 1) ) : []; const sortedActivities = [...inputActivities].sort((a, b) => a.date - b.date); const endDate = new Date(sortedActivities[sortedActivities.length - 1].date * 1000); endDate.setUTCDate(endDate.getUTCDate() + 1); return fillDateRange( startOfYear ? new Date(new Date().getUTCFullYear(), 0, 1) : new Date(sortedActivities[0].date * 1000), endDate, sortedActivities ); }; const fillDateRange = ( startDate: Date, endDate: Date, existingActivities: ActivityHistoryEntry[] = [] ): ActivityHistoryEntry[] => { const outputActivities: ActivityHistoryEntry[] = []; for (let dt = new Date(startDate); dt < endDate; dt.setUTCDate(dt.getUTCDate() + 1)) { const dateString = dt.toDateString(); if ( !new Set( existingActivities.map((activity) => new Date(activity.date * 1000).toDateString()) ).has(dateString) ) { outputActivities.push({ date: Math.floor(dt.getTime() / 1000), amount: 0 }); } else { const activity = existingActivities.find( (activity) => new Date(activity.date * 1000).toDateString() === dateString ); if (activity) outputActivities.push(activity); } } return outputActivities; }; export const activityHistory = async ( userIdentity: UserIdentity ): Promise => { return ( await ( await fetch('https://graphql.anilist.co', { method: 'POST', headers: { 'Content-Type': 'application/json', Accept: 'application/json' }, body: JSON.stringify({ query: `{ User(id: ${userIdentity.id}) { stats { activityHistory { date amount } } } }` }) }) ).json() )['data']['User']['stats']['activityHistory']; }; export const lastActivityDate = async (userIdentity: UserIdentity): Promise => { const history = await activityHistory(userIdentity); const date = new Date( Number(history[history.length - 1]['date']) * 1000 + new Date().getTimezoneOffset() ); date.setDate(date.getDate() + 1); return date; }; export interface ActivitiesPage { data: { Page: { pageInfo: { hasNextPage: boolean; }; activities: { createdAt: number; }[]; }; }; } const activitiesPage = async ( page: number, anilistAuthorisation: AniListAuthorisation, userIdentity: UserIdentity ): Promise => await ( await fetch('https://graphql.anilist.co', { method: 'POST', headers: { Authorization: `${anilistAuthorisation.tokenType} ${anilistAuthorisation.accessToken}`, 'Content-Type': 'application/json', Accept: 'application/json' }, body: JSON.stringify({ query: `{ Page(page: ${page}) { pageInfo { hasNextPage } activities(userId: ${userIdentity.id}, createdAt_greater: ${Math.floor( new Date(new Date().getFullYear(), 0, 1).getTime() / 1000 )}, createdAt_lesser: ${Math.floor( new Date(new Date().getFullYear(), 6, 1).getTime() / 1000 )}) { ... on TextActivity { createdAt } ... on ListActivity { createdAt } ... on MessageActivity { createdAt } } } }` }) }) ).json(); export const fullActivityHistory = async ( anilistAuthorisation: AniListAuthorisation, userIdentity: UserIdentity ): Promise => { const activities = []; let page = 1; let currentDatabasePage = await database.activities.get(page); let currentPage; if (currentDatabasePage) currentPage = currentDatabasePage.data; else { currentPage = await activitiesPage(page, anilistAuthorisation, userIdentity); database.activities.add({ page, data: currentPage }); } for (const activity of currentPage.data.Page.activities) activities.push(activity); while (currentPage['data']['Page']['pageInfo']['hasNextPage']) { for (const activity of currentPage.data.Page.activities) activities.push(activity); page += 1; currentDatabasePage = await database.activities.get(page); if (currentDatabasePage) currentPage = currentDatabasePage.data; else { currentPage = await activitiesPage(page, anilistAuthorisation, userIdentity); database.activities.add({ page, data: currentPage }); } } let fullLocalActivityHistory: ActivityHistoryEntry[] = []; for (const activity of activities) { const date = new Date(activity.createdAt * 1000); const dateString = date.toDateString(); const activityHistoryEntry = fullLocalActivityHistory.find( (activityHistoryEntry) => new Date(activityHistoryEntry.date * 1000).toDateString() === dateString ); if (activityHistoryEntry) activityHistoryEntry.amount += 1; else fullLocalActivityHistory.push({ date: Math.floor(date.getTime() / 1000), amount: 1 }); } fullLocalActivityHistory = fullLocalActivityHistory.filter((a) => !isNaN(a.date)); fullLocalActivityHistory.push(...(await activityHistory(userIdentity))); fullLocalActivityHistory = fullLocalActivityHistory.filter( (activityHistoryEntry, index, self) => self.findIndex( (a) => new Date(a.date * 1000).toDateString() === new Date(activityHistoryEntry.date * 1000).toDateString() ) === index ); fullLocalActivityHistory = fullLocalActivityHistory.filter( (activityHistoryEntry) => new Date(activityHistoryEntry.date * 1000).getFullYear() === new Date().getFullYear() ); return fullLocalActivityHistory; };