diff options
| author | Fuwn <[email protected]> | 2026-03-01 16:20:51 -0800 |
|---|---|---|
| committer | Fuwn <[email protected]> | 2026-03-01 16:21:02 -0800 |
| commit | eae5d24d9e79e59a19d4721caaeaa0ca650ecb33 (patch) | |
| tree | 1b685bb248e051dfa26d2bfdebe6689402dd93c5 /src/lib/Data/AniList/activity.ts | |
| parent | chore(tooling): remove legacy eslint and prettier (diff) | |
| download | due.moe-eae5d24d9e79e59a19d4721caaeaa0ca650ecb33.tar.xz due.moe-eae5d24d9e79e59a19d4721caaeaa0ca650ecb33.zip | |
chore(biome): drop formatter style overrides
Diffstat (limited to 'src/lib/Data/AniList/activity.ts')
| -rw-r--r-- | src/lib/Data/AniList/activity.ts | 526 |
1 files changed, 285 insertions, 241 deletions
diff --git a/src/lib/Data/AniList/activity.ts b/src/lib/Data/AniList/activity.ts index 9a8d13ba..11466cc5 100644 --- a/src/lib/Data/AniList/activity.ts +++ b/src/lib/Data/AniList/activity.ts @@ -1,309 +1,353 @@ -import { database } from '$lib/Database/IDB/activities'; -import type { User } from './follow'; -import type { AniListAuthorisation, UserIdentity } from './identity'; +import { database } from "$lib/Database/IDB/activities"; +import type { User } from "./follow"; +import type { AniListAuthorisation, UserIdentity } from "./identity"; export interface ActivityHistoryEntry { - date: number; - amount: number; + date: number; + amount: number; } interface ActivityHistoryOptions { - stats: { - activityHistory: ActivityHistoryEntry[]; - }; - options: { - timezone: string; - }; + stats: { + activityHistory: ActivityHistoryEntry[]; + }; + options: { + timezone: string; + }; } interface LastActivity { - date: Date; - timezone: string; + date: Date; + timezone: string; } export const fillMissingDays = ( - inputActivities: ActivityHistoryEntry[], - startOfYear = false, - year = new Date().getFullYear() + inputActivities: ActivityHistoryEntry[], + startOfYear = false, + year = new Date().getFullYear(), ): ActivityHistoryEntry[] => { - const yearDate = new Date(year, 0, 0); - - if (inputActivities.length === 0) - return startOfYear - ? fillDateRange( - new Date(yearDate.getUTCFullYear(), 0, 1), - new Date(yearDate.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(yearDate.getUTCFullYear(), 0, 1) - : new Date(sortedActivities[0].date * 1000), - endDate, - sortedActivities - ); + const yearDate = new Date(year, 0, 0); + + if (inputActivities.length === 0) + return startOfYear + ? fillDateRange( + new Date(yearDate.getUTCFullYear(), 0, 1), + new Date(yearDate.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(yearDate.getUTCFullYear(), 0, 1) + : new Date(sortedActivities[0].date * 1000), + endDate, + sortedActivities, + ); }; const fillDateRange = ( - startDate: Date, - endDate: Date, - existingActivities: ActivityHistoryEntry[] = [] + 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; + 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 activityHistoryOptions = async ( - userIdentity: UserIdentity, - authorisation?: AniListAuthorisation + userIdentity: UserIdentity, + authorisation?: AniListAuthorisation, ): Promise<ActivityHistoryOptions> => { - return ( - await ( - await fetch('https://graphql.anilist.co', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - Accept: 'application/json', - ...(authorisation - ? { Authorization: `${authorisation.tokenType} ${authorisation.accessToken}` } - : {}) - }, - body: JSON.stringify({ - query: `{ User(id: ${userIdentity.id}) { + return ( + await ( + await fetch("https://graphql.anilist.co", { + method: "POST", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + ...(authorisation + ? { + Authorization: `${authorisation.tokenType} ${authorisation.accessToken}`, + } + : {}), + }, + body: JSON.stringify({ + query: `{ User(id: ${userIdentity.id}) { stats { activityHistory { date amount } } - ${authorisation ? 'options { timezone }' : ''} - } }` - }) - }) - ).json() - )['data']['User']; + ${authorisation ? "options { timezone }" : ""} + } }`, + }), + }) + ).json() + )["data"]["User"]; }; const convertToTimezoneOffset = (timeStr: string) => { - const [hours, minutes] = timeStr.split(':'); - let totalMinutes = parseInt(hours, 10) * 60 + parseInt(minutes, 10); + const [hours, minutes] = timeStr.split(":"); + let totalMinutes = parseInt(hours, 10) * 60 + parseInt(minutes, 10); - totalMinutes = -totalMinutes; + totalMinutes = -totalMinutes; - return totalMinutes; + return totalMinutes; }; export const activityHistory = async ( - userIdentity: UserIdentity, - authorisation?: AniListAuthorisation + userIdentity: UserIdentity, + authorisation?: AniListAuthorisation, ): Promise<ActivityHistoryEntry[]> => - (await activityHistoryOptions(userIdentity, authorisation)).stats.activityHistory; + (await activityHistoryOptions(userIdentity, authorisation)).stats + .activityHistory; export const lastActivityDate = async ( - userIdentity: UserIdentity, - authorisation: AniListAuthorisation + userIdentity: UserIdentity, + authorisation: AniListAuthorisation, ): Promise<LastActivity> => { - if (userIdentity.id === -1 || userIdentity.id === -2) - return { date: new Date(8640000000000000), timezone: '' }; - - const history = await activityHistoryOptions(userIdentity, authorisation); - const date = new Date( - Number(history.stats.activityHistory[history.stats.activityHistory.length - 1].date) * 1000 + - convertToTimezoneOffset(history.options.timezone) - ); - - date.setDate(date.getDate() + 1); - - return { date, timezone: history.options.timezone }; + if (userIdentity.id === -1 || userIdentity.id === -2) + return { date: new Date(8640000000000000), timezone: "" }; + + const history = await activityHistoryOptions(userIdentity, authorisation); + const date = new Date( + Number( + history.stats.activityHistory[history.stats.activityHistory.length - 1] + .date, + ) * + 1000 + + convertToTimezoneOffset(history.options.timezone), + ); + + date.setDate(date.getDate() + 1); + + return { date, timezone: history.options.timezone }; }; export interface ActivitiesPage { - data: { - Page: { - pageInfo: { - hasNextPage: boolean; - }; - activities: { - createdAt: number; - }[]; - }; - }; + data: { + Page: { + pageInfo: { + hasNextPage: boolean; + }; + activities: { + createdAt: number; + }[]; + }; + }; } const activitiesPage = async ( - page: number, - anilistAuthorisation: AniListAuthorisation, - userIdentity: UserIdentity, - year = new Date().getFullYear() + page: number, + anilistAuthorisation: AniListAuthorisation, + userIdentity: UserIdentity, + year = new Date().getFullYear(), ): Promise<ActivitiesPage> => - 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: `{ + 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(year, 0, 1).getTime() / 1000 - )}, createdAt_lesser: ${Math.floor(new Date(year, 7, 1).getTime() / 1000)}) { + new Date(year, 0, 1).getTime() / 1000, + )}, createdAt_lesser: ${Math.floor(new Date(year, 7, 1).getTime() / 1000)}) { ... on TextActivity { createdAt } ... on ListActivity { createdAt } ... on MessageActivity { createdAt } } } - }` - }) - }) - ).json(); + }`, + }), + }) + ).json(); export const fullActivityHistory = async ( - anilistAuthorisation: AniListAuthorisation, - userIdentity: UserIdentity, - year = new Date().getFullYear(), - disableLoopingActivityCounter = false + anilistAuthorisation: AniListAuthorisation, + userIdentity: UserIdentity, + year = new Date().getFullYear(), + disableLoopingActivityCounter = false, ): Promise<ActivityHistoryEntry[]> => { - const activities = []; - let page = 1; - let currentDatabasePage = await database.activities.get(page); - let currentPage: ActivitiesPage; - - if (currentDatabasePage) currentPage = currentDatabasePage.data; - else { - currentPage = await activitiesPage(page, anilistAuthorisation, userIdentity, year); - database.activities.add({ - page, - data: currentPage - }); - } - - for (const activity of currentPage.data.Page.activities) activities.push(activity); - - while (currentPage['data']['Page']['pageInfo']['hasNextPage']) { - if (disableLoopingActivityCounter) break; - - 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, year); - database.activities.add({ - page, - data: currentPage - }); - } - } - - for (const activity of currentPage.data.Page.activities) activities.push(activity); - - 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)); - - if (new Date().getMonth() > 6) - 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() === year - ); - - return fullLocalActivityHistory; + const activities = []; + let page = 1; + let currentDatabasePage = await database.activities.get(page); + let currentPage: ActivitiesPage; + + if (currentDatabasePage) currentPage = currentDatabasePage.data; + else { + currentPage = await activitiesPage( + page, + anilistAuthorisation, + userIdentity, + year, + ); + database.activities.add({ + page, + data: currentPage, + }); + } + + for (const activity of currentPage.data.Page.activities) + activities.push(activity); + + while (currentPage["data"]["Page"]["pageInfo"]["hasNextPage"]) { + if (disableLoopingActivityCounter) break; + + 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, + year, + ); + database.activities.add({ + page, + data: currentPage, + }); + } + } + + for (const activity of currentPage.data.Page.activities) + activities.push(activity); + + 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), + ); + + if (new Date().getMonth() > 6) + 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() === year, + ); + + return fullLocalActivityHistory; }; export const activityLikes = async (id: number): Promise<Partial<User>[]> => { - const activityResponse = await ( - await fetch('https://graphql.anilist.co', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - Accept: 'application/json' - }, - body: JSON.stringify({ - query: `{ + const activityResponse = await ( + await fetch("https://graphql.anilist.co", { + method: "POST", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + body: JSON.stringify({ + query: `{ Activity(id: ${id}) { __typename ... on TextActivity { likes { name avatar { large } } } ... on ListActivity { likes { name avatar { large } } } ... on MessageActivity { likes { name avatar { large } } } } - }` - }) - }) - ).json(); + }`, + }), + }) + ).json(); - return activityResponse['data']['Activity']['likes']; + return activityResponse["data"]["Activity"]["likes"]; }; -export const activityText = async (id: number, replies = false): Promise<string> => { - const activityResponse = await ( - await fetch('https://graphql.anilist.co', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - Accept: 'application/json' - }, - body: JSON.stringify({ - query: `{ +export const activityText = async ( + id: number, + replies = false, +): Promise<string> => { + const activityResponse = await ( + await fetch("https://graphql.anilist.co", { + method: "POST", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + body: JSON.stringify({ + query: `{ Activity(id: ${id}) { - ... on TextActivity { text(asHtml: true) ${replies ? 'replies { text(asHtml: true) }' : ''} } + ... on TextActivity { text(asHtml: true) ${replies ? "replies { text(asHtml: true) }" : ""} } } - }` - }) - }) - ).json(); - let text = activityResponse['data']['Activity']['text']; + }`, + }), + }) + ).json(); + let text = activityResponse["data"]["Activity"]["text"]; - if (replies) - for (const reply of activityResponse['data']['Activity']['replies']) text += reply.text; + if (replies) + for (const reply of activityResponse["data"]["Activity"]["replies"]) + text += reply.text; - return text; + return text; }; |