aboutsummaryrefslogtreecommitdiff
path: root/src/lib/Data/AniList
diff options
context:
space:
mode:
authorFuwn <[email protected]>2024-10-09 00:41:20 -0700
committerFuwn <[email protected]>2024-10-09 00:41:43 -0700
commit998b63a35256ac985a5a2714dd1ca451af4dfd8a (patch)
tree50796121a9d5ab0330fdc5d7e098bda2860d9726 /src/lib/Data/AniList
parentfeat(graphql): add badgeCount field (diff)
downloaddue.moe-998b63a35256ac985a5a2714dd1ca451af4dfd8a.tar.xz
due.moe-998b63a35256ac985a5a2714dd1ca451af4dfd8a.zip
chore(prettier): use spaces instead of tabs
Diffstat (limited to 'src/lib/Data/AniList')
-rw-r--r--src/lib/Data/AniList/activity.ts450
-rw-r--r--src/lib/Data/AniList/character.ts140
-rw-r--r--src/lib/Data/AniList/follow.ts80
-rw-r--r--src/lib/Data/AniList/following.ts72
-rw-r--r--src/lib/Data/AniList/forum.ts104
-rw-r--r--src/lib/Data/AniList/identity.ts48
-rw-r--r--src/lib/Data/AniList/media.ts778
-rw-r--r--src/lib/Data/AniList/notifications.ts104
-rw-r--r--src/lib/Data/AniList/prequels.ts260
-rw-r--r--src/lib/Data/AniList/schedule.ts138
-rw-r--r--src/lib/Data/AniList/user.ts138
-rw-r--r--src/lib/Data/AniList/wrapped.ts486
12 files changed, 1399 insertions, 1399 deletions
diff --git a/src/lib/Data/AniList/activity.ts b/src/lib/Data/AniList/activity.ts
index c50ffa62..d10a5da7 100644
--- a/src/lib/Data/AniList/activity.ts
+++ b/src/lib/Data/AniList/activity.ts
@@ -3,269 +3,269 @@ 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'];
+ })
+ })
+ ).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: '' };
+ 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)
- );
+ 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);
+ date.setDate(date.getDate() + 1);
- return { date, timezone: history.options.timezone };
+ 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()
+ anilistAuthorisation: AniListAuthorisation,
+ userIdentity: UserIdentity,
+ year = new Date().getFullYear()
): Promise<ActivityHistoryEntry[]> => {
- 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, year);
- 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, 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;
+
+ 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']) {
+ 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 } } }
@@ -273,34 +273,34 @@ export const activityLikes = async (id: number): Promise<Partial<User>[]> => {
... 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: `{
+ 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) }' : ''} }
}
}`
- })
- })
- ).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;
};
diff --git a/src/lib/Data/AniList/character.ts b/src/lib/Data/AniList/character.ts
index 94b2a717..a7ade17e 100644
--- a/src/lib/Data/AniList/character.ts
+++ b/src/lib/Data/AniList/character.ts
@@ -1,88 +1,88 @@
export interface Character {
- name: {
- full: string;
- };
- id: number;
- image: {
- large: string;
- medium: string;
- };
+ name: {
+ full: string;
+ };
+ id: number;
+ image: {
+ large: string;
+ medium: string;
+ };
}
export interface CharactersPage {
- data: {
- Page: {
- characters: Character[];
- pageInfo: {
- hasNextPage: boolean;
- currentPage: number;
- };
- };
- };
+ data: {
+ Page: {
+ characters: Character[];
+ pageInfo: {
+ hasNextPage: boolean;
+ currentPage: number;
+ };
+ };
+ };
}
const charactersPage = async (page: number): Promise<CharactersPage> =>
- await (
- await fetch('https://graphql.anilist.co', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- Accept: 'application/json'
- },
- body: JSON.stringify({
- query: `{ Page(page: ${page}, perPage: 50) {
+ await (
+ await fetch('https://graphql.anilist.co', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ Accept: 'application/json'
+ },
+ body: JSON.stringify({
+ query: `{ Page(page: ${page}, perPage: 50) {
characters(isBirthday: true) { name { full } id image { large medium } }
pageInfo { hasNextPage currentPage }
} }`
- })
- })
- ).json();
+ })
+ })
+ ).json();
export const todaysCharacterBirthdays = async (): Promise<Character[]> => {
- const characters = [];
- let page = 1;
- let currentPage = await charactersPage(page);
+ const characters = [];
+ let page = 1;
+ let currentPage = await charactersPage(page);
- for (const character of currentPage['data']['Page']['characters'])
- characters.push({
- id: character['id'],
- name: {
- full: character['name']['full']
- },
- image: {
- large: character['image']['large'],
- medium: character['image']['medium']
- }
- });
+ for (const character of currentPage['data']['Page']['characters'])
+ characters.push({
+ id: character['id'],
+ name: {
+ full: character['name']['full']
+ },
+ image: {
+ large: character['image']['large'],
+ medium: character['image']['medium']
+ }
+ });
- while (currentPage['data']['Page']['pageInfo']['hasNextPage']) {
- for (const character of currentPage['data']['Page']['characters'])
- characters.push({
- id: character['id'],
- name: {
- full: character['name']['full']
- },
- image: {
- large: character['image']['large'],
- medium: character['image']['medium']
- }
- });
+ while (currentPage['data']['Page']['pageInfo']['hasNextPage']) {
+ for (const character of currentPage['data']['Page']['characters'])
+ characters.push({
+ id: character['id'],
+ name: {
+ full: character['name']['full']
+ },
+ image: {
+ large: character['image']['large'],
+ medium: character['image']['medium']
+ }
+ });
- page += 1;
- currentPage = await charactersPage(page);
- }
+ page += 1;
+ currentPage = await charactersPage(page);
+ }
- for (const character of currentPage['data']['Page']['characters'])
- characters.push({
- id: character['id'],
- name: {
- full: character['name']['full']
- },
- image: {
- large: character['image']['large'],
- medium: character['image']['medium']
- }
- });
+ for (const character of currentPage['data']['Page']['characters'])
+ characters.push({
+ id: character['id'],
+ name: {
+ full: character['name']['full']
+ },
+ image: {
+ large: character['image']['large'],
+ medium: character['image']['medium']
+ }
+ });
- return characters;
+ return characters;
};
diff --git a/src/lib/Data/AniList/follow.ts b/src/lib/Data/AniList/follow.ts
index f03193cb..d450c57f 100644
--- a/src/lib/Data/AniList/follow.ts
+++ b/src/lib/Data/AniList/follow.ts
@@ -1,49 +1,49 @@
import type { AniListAuthorisation } from './identity';
export interface User {
- id: number;
- name: string;
- isFollowing: boolean;
- isFollower: boolean;
- avatar: {
- large: string;
- medium: string;
- };
+ id: number;
+ name: string;
+ isFollowing: boolean;
+ isFollower: boolean;
+ avatar: {
+ large: string;
+ medium: string;
+ };
}
export const toggleFollow = async (
- anilistAuthorisation: AniListAuthorisation,
- username: string
+ anilistAuthorisation: AniListAuthorisation,
+ username: string
): Promise<User> => {
- const {
- data: { User: user }
- } = 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: `{ User(name: "${username}") { id } }`
- })
- })
- ).json();
+ const {
+ data: { User: user }
+ } = 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: `{ User(name: "${username}") { id } }`
+ })
+ })
+ ).json();
- return (
- 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({
- mutation: `{ ToggleFollow(userId: ${user.id}) { id name isFollowing isFollower } }`
- })
- })
- ).json()
- )['data']['ToggleFollow'];
+ return (
+ 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({
+ mutation: `{ ToggleFollow(userId: ${user.id}) { id name isFollowing isFollower } }`
+ })
+ })
+ ).json()
+ )['data']['ToggleFollow'];
};
diff --git a/src/lib/Data/AniList/following.ts b/src/lib/Data/AniList/following.ts
index 1bba66b9..b4772afa 100644
--- a/src/lib/Data/AniList/following.ts
+++ b/src/lib/Data/AniList/following.ts
@@ -1,56 +1,56 @@
import { user, type User } from './user';
export interface FollowingPage {
- data: {
- Page: {
- pageInfo: {
- hasNextPage: boolean;
- };
- following: Partial<User>[];
- };
- };
+ data: {
+ Page: {
+ pageInfo: {
+ hasNextPage: boolean;
+ };
+ following: Partial<User>[];
+ };
+ };
}
const followingPage = async (page: number, id: number): Promise<FollowingPage> =>
- await (
- await fetch('https://graphql.anilist.co', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- Accept: 'application/json'
- },
- body: JSON.stringify({
- query: `{
+ await (
+ await fetch('https://graphql.anilist.co', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ Accept: 'application/json'
+ },
+ body: JSON.stringify({
+ query: `{
Page(page: ${page}) {
pageInfo { hasNextPage }
following(userId: ${id}) { name id }
}
}`
- })
- })
- ).json();
+ })
+ })
+ ).json();
export const followers = async (name: string): Promise<Partial<User>[]> => {
- const activities = [];
- let page = 1;
- const id = (await user(name)).id;
- let currentPage = await followingPage(page, id);
+ const activities = [];
+ let page = 1;
+ const id = (await user(name)).id;
+ let currentPage = await followingPage(page, id);
- for (const activity of currentPage.data.Page.following) activities.push(activity);
+ for (const activity of currentPage.data.Page.following) activities.push(activity);
- while (currentPage['data']['Page']['pageInfo']['hasNextPage']) {
- for (const activity of currentPage.data.Page.following) activities.push(activity);
+ while (currentPage['data']['Page']['pageInfo']['hasNextPage']) {
+ for (const activity of currentPage.data.Page.following) activities.push(activity);
- page += 1;
- currentPage = await followingPage(page, id);
- }
+ page += 1;
+ currentPage = await followingPage(page, id);
+ }
- for (const activity of currentPage.data.Page.following) activities.push(activity);
+ for (const activity of currentPage.data.Page.following) activities.push(activity);
- for (let i = activities.length - 1; i > 0; i--) {
- const j = Math.floor(Math.random() * (i + 1));
- [activities[i], activities[j]] = [activities[j], activities[i]];
- }
+ for (let i = activities.length - 1; i > 0; i--) {
+ const j = Math.floor(Math.random() * (i + 1));
+ [activities[i], activities[j]] = [activities[j], activities[i]];
+ }
- return activities;
+ return activities;
};
diff --git a/src/lib/Data/AniList/forum.ts b/src/lib/Data/AniList/forum.ts
index b7b838cd..a96b74ca 100644
--- a/src/lib/Data/AniList/forum.ts
+++ b/src/lib/Data/AniList/forum.ts
@@ -2,75 +2,75 @@ import type { User } from './follow';
import { user } from './user';
export interface Thread {
- id: number;
- title: string;
- createdAt: number;
- mediaCategories: {
- coverImage: {
- extraLarge: string;
- medium: string;
- };
- }[];
+ id: number;
+ title: string;
+ createdAt: number;
+ mediaCategories: {
+ coverImage: {
+ extraLarge: string;
+ medium: string;
+ };
+ }[];
}
export interface ThreadPage {
- data: {
- Page: {
- threads: Thread[];
- pageInfo: {
- hasNextPage: boolean;
- currentPage: number;
- };
- };
- };
+ data: {
+ Page: {
+ threads: Thread[];
+ pageInfo: {
+ hasNextPage: boolean;
+ currentPage: number;
+ };
+ };
+ };
}
const threadPage = async (page: number, userId: number): Promise<ThreadPage> =>
- await (
- await fetch('https://graphql.anilist.co', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- Accept: 'application/json'
- },
- body: JSON.stringify({
- query: `{ Page(perPage: 50, page: ${page}) {
+ await (
+ await fetch('https://graphql.anilist.co', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ Accept: 'application/json'
+ },
+ body: JSON.stringify({
+ query: `{ Page(perPage: 50, page: ${page}) {
threads(userId: ${userId}) { id title createdAt mediaCategories { coverImage { extraLarge medium } } }
pageInfo { hasNextPage }
} }`
- })
- })
- ).json();
+ })
+ })
+ ).json();
export const threads = async (username: string): Promise<Thread[]> => {
- const allThreads = [];
- const userId = (await user(username)).id;
- let page = 1;
- let currentPage = await threadPage(page, userId);
+ const allThreads = [];
+ const userId = (await user(username)).id;
+ let page = 1;
+ let currentPage = await threadPage(page, userId);
- for (const thread of currentPage.data.Page.threads) allThreads.push(thread);
+ for (const thread of currentPage.data.Page.threads) allThreads.push(thread);
- while (currentPage.data.Page.pageInfo.hasNextPage) {
- page += 1;
- currentPage = await threadPage(page, userId);
+ while (currentPage.data.Page.pageInfo.hasNextPage) {
+ page += 1;
+ currentPage = await threadPage(page, userId);
- for (const thread of currentPage.data.Page.threads) allThreads.push(thread);
- }
+ for (const thread of currentPage.data.Page.threads) allThreads.push(thread);
+ }
- return allThreads;
+ return allThreads;
};
export const threadLikes = 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: `{ Thread(id: ${id}) { likes { name avatar { large } } } }` })
- })
- ).json();
+ const activityResponse = await (
+ await fetch('https://graphql.anilist.co', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ Accept: 'application/json'
+ },
+ body: JSON.stringify({ query: `{ Thread(id: ${id}) { likes { name avatar { large } } } }` })
+ })
+ ).json();
- return activityResponse['data']['Thread']['likes'];
+ return activityResponse['data']['Thread']['likes'];
};
diff --git a/src/lib/Data/AniList/identity.ts b/src/lib/Data/AniList/identity.ts
index 7214ffa4..23f47a2e 100644
--- a/src/lib/Data/AniList/identity.ts
+++ b/src/lib/Data/AniList/identity.ts
@@ -1,34 +1,34 @@
export interface UserIdentity {
- id: number;
- name: string;
- avatar: string;
+ id: number;
+ name: string;
+ avatar: string;
}
export interface AniListAuthorisation {
- tokenType: string;
- accessToken: string;
- expiresIn: number;
- refreshToken: string;
+ tokenType: string;
+ accessToken: string;
+ expiresIn: number;
+ refreshToken: string;
}
export const userIdentity = async (
- anilistAuthorisation: AniListAuthorisation
+ anilistAuthorisation: AniListAuthorisation
): Promise<UserIdentity> => {
- const userIdResponse = 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: `{ Viewer { id name avatar { large } } }` })
- })
- ).json();
+ const userIdResponse = 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: `{ Viewer { id name avatar { large } } }` })
+ })
+ ).json();
- return {
- id: userIdResponse['data']['Viewer']['id'],
- name: userIdResponse['data']['Viewer']['name'],
- avatar: userIdResponse['data']['Viewer']['avatar']['large']
- };
+ return {
+ id: userIdResponse['data']['Viewer']['id'],
+ name: userIdResponse['data']['Viewer']['name'],
+ avatar: userIdResponse['data']['Viewer']['avatar']['large']
+ };
};
diff --git a/src/lib/Data/AniList/media.ts b/src/lib/Data/AniList/media.ts
index d3b38005..f8aeb401 100644
--- a/src/lib/Data/AniList/media.ts
+++ b/src/lib/Data/AniList/media.ts
@@ -8,133 +8,133 @@ import { options as getOptions, type Options } from '$lib/Notification/options';
import type { PrequelRelation, PrequelRelations } from './prequels';
export enum Type {
- Anime,
- Manga
+ Anime,
+ Manga
}
export interface MediaTitle {
- romaji: string;
- english: string;
- native: string;
+ romaji: string;
+ english: string;
+ native: string;
}
export type MediaStatus = 'FINISHED' | 'RELEASING' | 'NOT_YET_RELEASED' | 'CANCELLED' | 'HIATUS';
export type MediaListEntryStatus =
- | 'CURRENT'
- | 'PLANNING'
- | 'COMPLETED'
- | 'DROPPED'
- | 'PAUSED'
- | 'REPEATING';
+ | 'CURRENT'
+ | 'PLANNING'
+ | 'COMPLETED'
+ | 'DROPPED'
+ | 'PAUSED'
+ | 'REPEATING';
export type MediaType = 'ANIME' | 'MANGA';
export type MediaFormat =
- | 'TV'
- | 'TV_SHORT'
- | 'MOVIE'
- | 'SPECIAL'
- | 'OVA'
- | 'ONA'
- | 'MUSIC'
- | 'MANGA'
- | 'NOVEL'
- | 'ONE_SHOT';
+ | 'TV'
+ | 'TV_SHORT'
+ | 'MOVIE'
+ | 'SPECIAL'
+ | 'OVA'
+ | 'ONA'
+ | 'MUSIC'
+ | 'MANGA'
+ | 'NOVEL'
+ | 'ONE_SHOT';
export type MediaSeason = 'WINTER' | 'SPRING' | 'SUMMER' | 'FALL';
export interface Media {
- id: number;
- idMal: number;
- status: MediaStatus;
- type: MediaType;
- episodes: number;
- chapters: number;
- volumes: number;
- duration: number;
- format: MediaFormat;
- title: MediaTitle;
- nextAiringEpisode?: {
- episode: number;
- airingAt?: number;
- nativeAiringAt?: number;
- };
- synonyms: string[];
- mediaListEntry?: {
- progress: number;
- progressVolumes: number;
- status: MediaListEntryStatus;
- score: number;
- repeat: number;
- startedAt: {
- year: number;
- };
- completedAt: {
- year: number;
- };
- createdAt: number;
- updatedAt: number;
- };
- startDate: {
- year: number;
- };
- endDate: {
- year: number;
- };
- coverImage: {
- extraLarge: string;
- medium: string;
- };
- tags: {
- name: string;
- rank: number;
- }[];
- genres: string[];
- season: MediaSeason;
- isAdult: boolean;
- relations: PrequelRelations;
+ id: number;
+ idMal: number;
+ status: MediaStatus;
+ type: MediaType;
+ episodes: number;
+ chapters: number;
+ volumes: number;
+ duration: number;
+ format: MediaFormat;
+ title: MediaTitle;
+ nextAiringEpisode?: {
+ episode: number;
+ airingAt?: number;
+ nativeAiringAt?: number;
+ };
+ synonyms: string[];
+ mediaListEntry?: {
+ progress: number;
+ progressVolumes: number;
+ status: MediaListEntryStatus;
+ score: number;
+ repeat: number;
+ startedAt: {
+ year: number;
+ };
+ completedAt: {
+ year: number;
+ };
+ createdAt: number;
+ updatedAt: number;
+ };
+ startDate: {
+ year: number;
+ };
+ endDate: {
+ year: number;
+ };
+ coverImage: {
+ extraLarge: string;
+ medium: string;
+ };
+ tags: {
+ name: string;
+ rank: number;
+ }[];
+ genres: string[];
+ season: MediaSeason;
+ isAdult: boolean;
+ relations: PrequelRelations;
}
export const flattenLists = (
- lists: { name: string; entries: { media: Media }[] }[],
- all = false
+ lists: { name: string; entries: { media: Media }[] }[],
+ all = false
) => {
- if (lists === undefined) return [];
-
- const flattenedList: Media[] = [];
- const markedMediaIds: number[] = [];
- let dueInclude = false;
- const processedList = (list: Media[], include: boolean) =>
- list
- .filter((media) =>
- include ? markedMediaIds.includes(media.id) : !markedMediaIds.includes(media.id)
- )
- .filter(
- (item, index, self) =>
- self.findIndex((itemToCompare) => itemToCompare.id === item.id) === index
- );
-
- if (all) {
- for (const list of lists) flattenedList.push(...list.entries.map((entry) => entry.media));
- } else {
- for (const list of lists) {
- if (list.name.toLowerCase().includes('#dueinclude')) {
- dueInclude = true;
-
- markedMediaIds.splice(0, markedMediaIds.length);
- markedMediaIds.push(...list.entries.map((entry) => entry.media.id));
- }
-
- if (dueInclude) continue;
-
- if (list.name.toLowerCase().includes('#dueignore'))
- markedMediaIds.push(...list.entries.map((entry) => entry.media.id));
- else flattenedList.push(...list.entries.map((entry) => entry.media));
- }
- }
-
- return processedList(flattenedList, dueInclude);
+ if (lists === undefined) return [];
+
+ const flattenedList: Media[] = [];
+ const markedMediaIds: number[] = [];
+ let dueInclude = false;
+ const processedList = (list: Media[], include: boolean) =>
+ list
+ .filter((media) =>
+ include ? markedMediaIds.includes(media.id) : !markedMediaIds.includes(media.id)
+ )
+ .filter(
+ (item, index, self) =>
+ self.findIndex((itemToCompare) => itemToCompare.id === item.id) === index
+ );
+
+ if (all) {
+ for (const list of lists) flattenedList.push(...list.entries.map((entry) => entry.media));
+ } else {
+ for (const list of lists) {
+ if (list.name.toLowerCase().includes('#dueinclude')) {
+ dueInclude = true;
+
+ markedMediaIds.splice(0, markedMediaIds.length);
+ markedMediaIds.push(...list.entries.map((entry) => entry.media.id));
+ }
+
+ if (dueInclude) continue;
+
+ if (list.name.toLowerCase().includes('#dueignore'))
+ markedMediaIds.push(...list.entries.map((entry) => entry.media.id));
+ else flattenedList.push(...list.entries.map((entry) => entry.media));
+ }
+ }
+
+ return processedList(flattenedList, dueInclude);
};
const collectionQueryTemplate = (type: Type, userId: number, options: CollectionOptions = {}) =>
- `{
+ `{
MediaListCollection(
userId: ${userId},
type: ${type === Type.Anime ? 'ANIME' : 'MANGA'}
@@ -154,8 +154,8 @@ const collectionQueryTemplate = (type: Type, userId: number, options: Collection
endDate { year }
coverImage { extraLarge medium }
${
- options.includeRelations
- ? `
+ options.includeRelations
+ ? `
relations {
edges {
relationType
@@ -169,8 +169,8 @@ const collectionQueryTemplate = (type: Type, userId: number, options: Collection
}
}
`
- : ''
- }
+ : ''
+ }
}
}
}
@@ -178,190 +178,190 @@ const collectionQueryTemplate = (type: Type, userId: number, options: Collection
}`;
interface CollectionOptions {
- includeCompleted?: boolean;
- forcePrune?: boolean;
- all?: boolean;
- addNotification?: (preferences: Options) => void;
- notificationType?: string;
- includeRelations?: boolean;
+ includeCompleted?: boolean;
+ forcePrune?: boolean;
+ all?: boolean;
+ addNotification?: (preferences: Options) => void;
+ notificationType?: string;
+ includeRelations?: boolean;
}
const assignDefaultOptions = (options: CollectionOptions) => {
- const nonNullOptions: CollectionOptions = {
- includeCompleted: false,
- forcePrune: false,
- all: false,
- addNotification: undefined,
- notificationType: undefined,
- includeRelations: false
- };
-
- if (options.includeCompleted !== undefined)
- nonNullOptions.includeCompleted = options.includeCompleted;
- if (options.forcePrune !== undefined) nonNullOptions.forcePrune = options.forcePrune;
- if (options.all !== undefined) nonNullOptions.all = options.all;
- if (options.addNotification !== undefined)
- nonNullOptions.addNotification = options.addNotification;
- if (options.notificationType !== undefined)
- nonNullOptions.notificationType = options.notificationType;
- if (options.includeRelations !== undefined)
- nonNullOptions.includeRelations = options.includeRelations;
-
- return nonNullOptions;
+ const nonNullOptions: CollectionOptions = {
+ includeCompleted: false,
+ forcePrune: false,
+ all: false,
+ addNotification: undefined,
+ notificationType: undefined,
+ includeRelations: false
+ };
+
+ if (options.includeCompleted !== undefined)
+ nonNullOptions.includeCompleted = options.includeCompleted;
+ if (options.forcePrune !== undefined) nonNullOptions.forcePrune = options.forcePrune;
+ if (options.all !== undefined) nonNullOptions.all = options.all;
+ if (options.addNotification !== undefined)
+ nonNullOptions.addNotification = options.addNotification;
+ if (options.notificationType !== undefined)
+ nonNullOptions.notificationType = options.notificationType;
+ if (options.includeRelations !== undefined)
+ nonNullOptions.includeRelations = options.includeRelations;
+
+ return nonNullOptions;
};
export const mediaListCollection = async (
- anilistAuthorisation: AniListAuthorisation,
- userIdentity: UserIdentity,
- type: Type,
- mediaCache: string | undefined,
- currentLastPruneAt: string | number,
- inputOptions: CollectionOptions = {}
+ anilistAuthorisation: AniListAuthorisation,
+ userIdentity: UserIdentity,
+ type: Type,
+ mediaCache: string | undefined,
+ currentLastPruneAt: string | number,
+ inputOptions: CollectionOptions = {}
): Promise<Media[]> => {
- if (userIdentity.id === -1 || userIdentity.id === -2) return [];
-
- const options = assignDefaultOptions(inputOptions);
-
- let currentCacheMinutes;
-
- settings.subscribe((value) => (currentCacheMinutes = value.cacheMinutes));
-
- if (String(currentLastPruneAt) === '') {
- if (type === Type.Anime) lastPruneTimes.setKey('anime', new Date().getTime());
- else lastPruneTimes.setKey('manga', new Date().getTime());
- } else {
- if (
- (new Date().getTime() - Number(currentLastPruneAt)) / 1000 / 60 >
- Number(currentCacheMinutes) ||
- options.forcePrune
- ) {
- if (type === Type.Anime) {
- lastPruneTimes.setKey('anime', new Date().getTime());
- anime.set('');
- } else {
- lastPruneTimes.setKey('manga', new Date().getTime());
- manga.set('');
- }
-
- mediaCache = '';
- }
- }
-
- if (mediaCache !== undefined && mediaCache !== '') return JSON.parse(mediaCache);
-
- const userIdResponse = 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: collectionQueryTemplate(type, userIdentity.id, options)
- })
- })
- ).json();
-
- if (
- !userIdResponse['data'] ||
- !userIdResponse['data']['MediaListCollection'] ||
- !userIdResponse['data']['MediaListCollection']['lists']
- )
- return [];
-
- if (mediaCache === '')
- if (type === Type.Anime)
- anime.set(
- JSON.stringify(
- flattenLists(userIdResponse['data']['MediaListCollection']['lists'], options.all)
- )
- );
- else
- manga.set(
- JSON.stringify(
- flattenLists(userIdResponse['data']['MediaListCollection']['lists'], options.all)
- )
- );
-
- if (options.addNotification)
- options.addNotification(
- getOptions({
- heading: options.notificationType ? options.notificationType : Type[type],
- description: 'Re-cached media lists from AniList'
- })
- );
-
- return flattenLists(userIdResponse['data']['MediaListCollection']['lists'], options.all);
+ if (userIdentity.id === -1 || userIdentity.id === -2) return [];
+
+ const options = assignDefaultOptions(inputOptions);
+
+ let currentCacheMinutes;
+
+ settings.subscribe((value) => (currentCacheMinutes = value.cacheMinutes));
+
+ if (String(currentLastPruneAt) === '') {
+ if (type === Type.Anime) lastPruneTimes.setKey('anime', new Date().getTime());
+ else lastPruneTimes.setKey('manga', new Date().getTime());
+ } else {
+ if (
+ (new Date().getTime() - Number(currentLastPruneAt)) / 1000 / 60 >
+ Number(currentCacheMinutes) ||
+ options.forcePrune
+ ) {
+ if (type === Type.Anime) {
+ lastPruneTimes.setKey('anime', new Date().getTime());
+ anime.set('');
+ } else {
+ lastPruneTimes.setKey('manga', new Date().getTime());
+ manga.set('');
+ }
+
+ mediaCache = '';
+ }
+ }
+
+ if (mediaCache !== undefined && mediaCache !== '') return JSON.parse(mediaCache);
+
+ const userIdResponse = 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: collectionQueryTemplate(type, userIdentity.id, options)
+ })
+ })
+ ).json();
+
+ if (
+ !userIdResponse['data'] ||
+ !userIdResponse['data']['MediaListCollection'] ||
+ !userIdResponse['data']['MediaListCollection']['lists']
+ )
+ return [];
+
+ if (mediaCache === '')
+ if (type === Type.Anime)
+ anime.set(
+ JSON.stringify(
+ flattenLists(userIdResponse['data']['MediaListCollection']['lists'], options.all)
+ )
+ );
+ else
+ manga.set(
+ JSON.stringify(
+ flattenLists(userIdResponse['data']['MediaListCollection']['lists'], options.all)
+ )
+ );
+
+ if (options.addNotification)
+ options.addNotification(
+ getOptions({
+ heading: options.notificationType ? options.notificationType : Type[type],
+ description: 'Re-cached media lists from AniList'
+ })
+ );
+
+ return flattenLists(userIdResponse['data']['MediaListCollection']['lists'], options.all);
};
export const publicMediaListCollection = async (userId: number, type: Type): Promise<Media[]> =>
- flattenLists(
- (
- await (
- await fetch('https://graphql.anilist.co', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- Accept: 'application/json'
- },
- body: JSON.stringify({
- query: collectionQueryTemplate(type, userId, {})
- })
- })
- ).json()
- )['data']['MediaListCollection']['lists']
- );
+ flattenLists(
+ (
+ await (
+ await fetch('https://graphql.anilist.co', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ Accept: 'application/json'
+ },
+ body: JSON.stringify({
+ query: collectionQueryTemplate(type, userId, {})
+ })
+ })
+ ).json()
+ )['data']['MediaListCollection']['lists']
+ );
const countMedian = (guesses: number[]) => {
- guesses.sort((a: number, b: number) => a - b);
+ guesses.sort((a: number, b: number) => a - b);
- const mid = Math.floor(guesses.length / 2);
+ const mid = Math.floor(guesses.length / 2);
- return guesses.length % 2 !== 0 ? guesses[mid] : (guesses[mid - 1] + guesses[mid]) / 2;
+ return guesses.length % 2 !== 0 ? guesses[mid] : (guesses[mid - 1] + guesses[mid]) / 2;
};
const countIQR = (guesses: number[]) => {
- guesses.sort((a: number, b: number) => a - b);
+ guesses.sort((a: number, b: number) => a - b);
- const q1 = guesses[Math.floor(guesses.length / 4)];
- const q3 = guesses[Math.ceil(guesses.length * (3 / 4))];
- const iqr = q3 - q1;
+ const q1 = guesses[Math.floor(guesses.length / 4)];
+ const q3 = guesses[Math.ceil(guesses.length * (3 / 4))];
+ const iqr = q3 - q1;
- return guesses.filter((guess: number) => guess >= q1 - 1.5 * iqr && guess <= q3 + 1.5 * iqr);
+ return guesses.filter((guess: number) => guess >= q1 - 1.5 * iqr && guess <= q3 + 1.5 * iqr);
};
const countMode = (guesses: number[]) => {
- const frequency: { [key: number]: number } = {};
- let maximumFrequency = 0;
- let mode = 0;
+ const frequency: { [key: number]: number } = {};
+ let maximumFrequency = 0;
+ let mode = 0;
- for (const guess of guesses) {
- frequency[guess] = (frequency[guess] || 0) + 1;
+ for (const guess of guesses) {
+ frequency[guess] = (frequency[guess] || 0) + 1;
- if (frequency[guess] > maximumFrequency) {
- maximumFrequency = frequency[guess];
- mode = guess;
- }
- }
+ if (frequency[guess] > maximumFrequency) {
+ maximumFrequency = frequency[guess];
+ mode = guess;
+ }
+ }
- return mode;
+ return mode;
};
export const recentMediaActivities = async (
- userIdentity: UserIdentity,
- media: Media,
- method: 'median' | 'iqr_median' | 'iqr_mode' | 'mode'
+ userIdentity: UserIdentity,
+ media: Media,
+ method: 'median' | 'iqr_median' | 'iqr_mode' | 'mode'
): Promise<number | null> => {
- const activities = await (
- await fetch('https://graphql.anilist.co', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- Accept: 'application/json'
- },
- body: JSON.stringify({
- query: `{
+ const activities = await (
+ await fetch('https://graphql.anilist.co', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ Accept: 'application/json'
+ },
+ body: JSON.stringify({
+ query: `{
Page(page: 1) {
activities(mediaId: ${media.id}, sort: ID_DESC) {
... on ListActivity { progress }
@@ -370,136 +370,136 @@ export const recentMediaActivities = async (
MediaList(userId: ${userIdentity.id}, mediaId: ${media.id}) { progress }
}`
- })
- })
- ).json();
- const guesses: number[] = [];
-
- activities['data']['Page']['activities'].forEach((activity: { progress: string }) => {
- if (activity.progress !== null) {
- const progress = Number((activity.progress.match(/\d+$/) || [0])[0]);
-
- if (progress !== 65535) guesses.push(progress);
- }
- });
- guesses.sort((a, b) => b - a);
-
- if (guesses.length) {
- let bestGuess;
-
- switch (method) {
- case 'median':
- {
- bestGuess = countMedian(guesses);
- }
- break;
- case 'iqr_median':
- {
- bestGuess = countMedian(countIQR(guesses));
- }
- break;
- case 'iqr_mode':
- {
- bestGuess = countMode(countIQR(guesses));
- }
- break;
- case 'mode':
- {
- bestGuess = countMode(guesses);
- }
- break;
- default:
- {
- bestGuess = countMedian(guesses);
- }
- break;
- }
-
- // if (guesses.length > 2) {
- // if (guesses.filter((val) => val < 7000).length) {
- // guesses = guesses.filter((val) => val < 7000);
- // }
-
- // const difference = guesses[0] - guesses[1];
- // const inverseDifference = 1 + Math.ceil(25 / (difference + 1));
-
- // if (guesses.length >= inverseDifference) {
- // if (
- // guesses[1] === guesses[inverseDifference] ||
- // guesses[0] - guesses[1] > 500 ||
- // (guesses[0] - guesses[1] > 100 && guesses[1] >= guesses[inverseDifference] - 1)
- // ) {
- // bestGuess = guesses[1];
-
- // if (
- // guesses.length > 15 &&
- // guesses[1] - guesses[2] > 50 &&
- // guesses[2] === guesses[guesses.length - 1]
- // ) {
- // bestGuess = guesses[2];
- // }
- // }
- // }
- // }
-
- // if (activities['data']['MediaList']['progress'] !== null) {
- if (activities['data']['MediaList']['progress'] > bestGuess)
- bestGuess = activities['data']['MediaList']['progress'];
- // }
-
- return Math.round(bestGuess);
- }
-
- return null;
+ })
+ })
+ ).json();
+ const guesses: number[] = [];
+
+ activities['data']['Page']['activities'].forEach((activity: { progress: string }) => {
+ if (activity.progress !== null) {
+ const progress = Number((activity.progress.match(/\d+$/) || [0])[0]);
+
+ if (progress !== 65535) guesses.push(progress);
+ }
+ });
+ guesses.sort((a, b) => b - a);
+
+ if (guesses.length) {
+ let bestGuess;
+
+ switch (method) {
+ case 'median':
+ {
+ bestGuess = countMedian(guesses);
+ }
+ break;
+ case 'iqr_median':
+ {
+ bestGuess = countMedian(countIQR(guesses));
+ }
+ break;
+ case 'iqr_mode':
+ {
+ bestGuess = countMode(countIQR(guesses));
+ }
+ break;
+ case 'mode':
+ {
+ bestGuess = countMode(guesses);
+ }
+ break;
+ default:
+ {
+ bestGuess = countMedian(guesses);
+ }
+ break;
+ }
+
+ // if (guesses.length > 2) {
+ // if (guesses.filter((val) => val < 7000).length) {
+ // guesses = guesses.filter((val) => val < 7000);
+ // }
+
+ // const difference = guesses[0] - guesses[1];
+ // const inverseDifference = 1 + Math.ceil(25 / (difference + 1));
+
+ // if (guesses.length >= inverseDifference) {
+ // if (
+ // guesses[1] === guesses[inverseDifference] ||
+ // guesses[0] - guesses[1] > 500 ||
+ // (guesses[0] - guesses[1] > 100 && guesses[1] >= guesses[inverseDifference] - 1)
+ // ) {
+ // bestGuess = guesses[1];
+
+ // if (
+ // guesses.length > 15 &&
+ // guesses[1] - guesses[2] > 50 &&
+ // guesses[2] === guesses[guesses.length - 1]
+ // ) {
+ // bestGuess = guesses[2];
+ // }
+ // }
+ // }
+ // }
+
+ // if (activities['data']['MediaList']['progress'] !== null) {
+ if (activities['data']['MediaList']['progress'] > bestGuess)
+ bestGuess = activities['data']['MediaList']['progress'];
+ // }
+
+ return Math.round(bestGuess);
+ }
+
+ return null;
};
export const mediaCover = async (id: number) =>
- (
- await (
- await fetch('https://graphql.anilist.co', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- Accept: 'application/json'
- },
- body: JSON.stringify({
- query: `{
+ (
+ await (
+ await fetch('https://graphql.anilist.co', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ Accept: 'application/json'
+ },
+ body: JSON.stringify({
+ query: `{
Media(id: ${id}) { coverImage { extraLarge } }
}`
- })
- })
- ).json()
- )['data']['Media']['coverImage']['extraLarge'];
+ })
+ })
+ ).json()
+ )['data']['Media']['coverImage']['extraLarge'];
export interface UnwatchedRelationMap {
- media: Media;
- unwatchedRelations: PrequelRelation[];
+ media: Media;
+ unwatchedRelations: PrequelRelation[];
}
export const filterRelations = (media: Media[], includeSideStories = false) => {
- const unwatchedRelationsMap: UnwatchedRelationMap[] = [];
-
- for (const mediaItem of media) {
- const sequels = mediaItem.relations.edges.filter(
- (relation: PrequelRelation) =>
- (relation.relationType === 'SEQUEL' ||
- (relation.relationType === 'SIDE_STORY' && includeSideStories)) &&
- !media.some((mediaItem) => mediaItem.id === relation.node.id) &&
- (relation.node.mediaListEntry
- ? relation.node.mediaListEntry.status !== 'COMPLETED'
- : true) &&
- relation.node.episodes &&
- relation.node.status !== 'NOT_YET_RELEASED' &&
- relation.node.status !== 'CANCELLED'
- );
-
- if (sequels.length > 0) {
- unwatchedRelationsMap.push({
- media: mediaItem,
- unwatchedRelations: sequels
- });
- }
- }
-
- return unwatchedRelationsMap;
+ const unwatchedRelationsMap: UnwatchedRelationMap[] = [];
+
+ for (const mediaItem of media) {
+ const sequels = mediaItem.relations.edges.filter(
+ (relation: PrequelRelation) =>
+ (relation.relationType === 'SEQUEL' ||
+ (relation.relationType === 'SIDE_STORY' && includeSideStories)) &&
+ !media.some((mediaItem) => mediaItem.id === relation.node.id) &&
+ (relation.node.mediaListEntry
+ ? relation.node.mediaListEntry.status !== 'COMPLETED'
+ : true) &&
+ relation.node.episodes &&
+ relation.node.status !== 'NOT_YET_RELEASED' &&
+ relation.node.status !== 'CANCELLED'
+ );
+
+ if (sequels.length > 0) {
+ unwatchedRelationsMap.push({
+ media: mediaItem,
+ unwatchedRelations: sequels
+ });
+ }
+ }
+
+ return unwatchedRelationsMap;
};
diff --git a/src/lib/Data/AniList/notifications.ts b/src/lib/Data/AniList/notifications.ts
index bebf1b42..b21a71e4 100644
--- a/src/lib/Data/AniList/notifications.ts
+++ b/src/lib/Data/AniList/notifications.ts
@@ -1,60 +1,60 @@
export interface Notification {
- user: {
- name: string;
- avatar: {
- large: string;
- };
- };
- thread: {
- title: string;
- id: number;
- };
- activity: {
- id: number;
- };
- context: string;
- id: number;
- createdAt: number;
- type:
- | 'FOLLOWING'
- | 'ACTIVITY_MESSAGE'
- | 'ACTIVITY_MENTION'
- | 'ACTIVITY_REPLY'
- | 'ACTIVITY_REPLY_SUBSCRIBED'
- | 'ACTIVITY_LIKE'
- | 'ACTIVITY_REPLY_LIKE'
- | 'THREAD_COMMENT_MENTION'
- | 'THREAD_COMMENT_REPLY'
- | 'THREAD_SUBSCRIBED'
- | 'THREAD_COMMENT_LIKE'
- | 'THREAD_LIKE';
+ user: {
+ name: string;
+ avatar: {
+ large: string;
+ };
+ };
+ thread: {
+ title: string;
+ id: number;
+ };
+ activity: {
+ id: number;
+ };
+ context: string;
+ id: number;
+ createdAt: number;
+ type:
+ | 'FOLLOWING'
+ | 'ACTIVITY_MESSAGE'
+ | 'ACTIVITY_MENTION'
+ | 'ACTIVITY_REPLY'
+ | 'ACTIVITY_REPLY_SUBSCRIBED'
+ | 'ACTIVITY_LIKE'
+ | 'ACTIVITY_REPLY_LIKE'
+ | 'THREAD_COMMENT_MENTION'
+ | 'THREAD_COMMENT_REPLY'
+ | 'THREAD_SUBSCRIBED'
+ | 'THREAD_COMMENT_LIKE'
+ | 'THREAD_LIKE';
}
export const notifications = async (accessToken: string): Promise<Notification[] | null> => {
- const activityNotification = (type: string, extend = '') => `... on ${type} {
+ const activityNotification = (type: string, extend = '') => `... on ${type} {
id user { name avatar { large } } context createdAt type ${extend}
}`;
- const richActivityNotification = (type: string) =>
- `${activityNotification(
- type,
- `activity {
+ const richActivityNotification = (type: string) =>
+ `${activityNotification(
+ type,
+ `activity {
... on TextActivity { id }
... on ListActivity { id }
... on MessageActivity { id }
}`
- )}`;
- const threadNotification = (type: string) =>
- `${activityNotification(type, `thread { title id }`)}`;
- const data = await (
- await fetch('https://graphql.anilist.co', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- Authorization: `Bearer ${accessToken}`,
- Accept: 'application/json'
- },
- body: JSON.stringify({
- query: `{ Page(page: 1, perPage: 50) { notifications {
+ )}`;
+ const threadNotification = (type: string) =>
+ `${activityNotification(type, `thread { title id }`)}`;
+ const data = await (
+ await fetch('https://graphql.anilist.co', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: `Bearer ${accessToken}`,
+ Accept: 'application/json'
+ },
+ body: JSON.stringify({
+ query: `{ Page(page: 1, perPage: 50) { notifications {
${activityNotification('FollowingNotification')}
${activityNotification('ActivityMessageNotification')}
${richActivityNotification('ActivityMentionNotification')}
@@ -68,11 +68,11 @@ export const notifications = async (accessToken: string): Promise<Notification[]
${threadNotification('ThreadCommentLikeNotification')}
${threadNotification('ThreadLikeNotification')}
} } }`
- })
- })
- ).json();
+ })
+ })
+ ).json();
- if (data['errors']) return null;
+ if (data['errors']) return null;
- return data['data']['Page']['notifications'];
+ return data['data']['Page']['notifications'];
};
diff --git a/src/lib/Data/AniList/prequels.ts b/src/lib/Data/AniList/prequels.ts
index f995d727..47e83f0e 100644
--- a/src/lib/Data/AniList/prequels.ts
+++ b/src/lib/Data/AniList/prequels.ts
@@ -2,96 +2,96 @@ import type { AniListAuthorisation } from './identity';
import type { MediaListEntryStatus, MediaSeason, MediaStatus, MediaTitle } from './media';
export interface MediaPrequel {
- id: number;
- title: MediaTitle;
- episodes: number;
- seen: number;
- nextAiringEpisode?: {
- episode: number;
- airingAt?: number;
- };
- startDate: {
- year: number;
- month: number;
- day: number;
- };
- coverImage: {
- extraLarge: string;
- medium: string;
- };
+ id: number;
+ title: MediaTitle;
+ episodes: number;
+ seen: number;
+ nextAiringEpisode?: {
+ episode: number;
+ airingAt?: number;
+ };
+ startDate: {
+ year: number;
+ month: number;
+ day: number;
+ };
+ coverImage: {
+ extraLarge: string;
+ medium: string;
+ };
}
export interface PrequelRelationNode {
- id: number;
- title: MediaTitle;
- episodes: number;
- status: MediaStatus;
- mediaListEntry: {
- status: MediaListEntryStatus;
- progress: number;
- };
- coverImage: {
- extraLarge: string;
- medium: string;
- };
- startDate: {
- year: number;
- };
+ id: number;
+ title: MediaTitle;
+ episodes: number;
+ status: MediaStatus;
+ mediaListEntry: {
+ status: MediaListEntryStatus;
+ progress: number;
+ };
+ coverImage: {
+ extraLarge: string;
+ medium: string;
+ };
+ startDate: {
+ year: number;
+ };
}
export interface PrequelRelation {
- relationType: string;
- node: PrequelRelationNode;
+ relationType: string;
+ node: PrequelRelationNode;
}
export interface PrequelRelations {
- edges: PrequelRelation[];
+ edges: PrequelRelation[];
}
interface PrequelsPage {
- data: {
- Page: {
- media: {
- title: MediaTitle;
- id: number;
- relations: PrequelRelations;
- nextAiringEpisode?: {
- episode: number;
- airingAt?: number;
- };
- startDate: {
- year: number;
- month: number;
- day: number;
- };
- coverImage: {
- extraLarge: string;
- medium: string;
- };
- }[];
- pageInfo: {
- hasNextPage: boolean;
- };
- };
- };
+ data: {
+ Page: {
+ media: {
+ title: MediaTitle;
+ id: number;
+ relations: PrequelRelations;
+ nextAiringEpisode?: {
+ episode: number;
+ airingAt?: number;
+ };
+ startDate: {
+ year: number;
+ month: number;
+ day: number;
+ };
+ coverImage: {
+ extraLarge: string;
+ medium: string;
+ };
+ }[];
+ pageInfo: {
+ hasNextPage: boolean;
+ };
+ };
+ };
}
const prequelsPage = async (
- page: number,
- anilistAuthorisation: AniListAuthorisation,
- year: number,
- season: MediaSeason
+ page: number,
+ anilistAuthorisation: AniListAuthorisation,
+ year: number,
+ season: MediaSeason
): Promise<PrequelsPage> =>
- 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
@@ -125,63 +125,63 @@ const prequelsPage = async (
}
}
}`
- })
- })
- ).json();
+ })
+ })
+ ).json();
export const prequels = async (
- anilistAuthorisation: AniListAuthorisation,
- year: number,
- season: 'WINTER' | 'SPRING' | 'SUMMER' | 'FALL'
+ anilistAuthorisation: AniListAuthorisation,
+ year: number,
+ season: 'WINTER' | 'SPRING' | 'SUMMER' | 'FALL'
): Promise<MediaPrequel[]> => {
- const candidates = [];
- let page = 1;
- let currentPage = await prequelsPage(page, anilistAuthorisation, year, season);
-
- for (const candidate of currentPage.data.Page.media) candidates.push(candidate);
-
- while (currentPage['data']['Page']['pageInfo']['hasNextPage']) {
- for (const candidate of currentPage.data.Page.media) candidates.push(candidate);
-
- page += 1;
- currentPage = await prequelsPage(page, anilistAuthorisation, year, season);
- }
-
- for (const candidate of currentPage.data.Page.media) candidates.push(candidate);
-
- const media: MediaPrequel[] = [];
-
- for (const candidate of candidates) {
- let episodes = 0;
- let seen = 0;
-
- for (const relation of candidate.relations.edges) {
- if (relation.relationType === 'PREQUEL' || relation.relationType === 'PARENT') {
- if (
- relation.node.mediaListEntry === null ||
- relation.node.mediaListEntry.status !== 'COMPLETED'
- ) {
- episodes += relation.node.episodes;
-
- if (relation.node.mediaListEntry !== null)
- seen += relation.node.mediaListEntry.progress || 0;
- }
- }
- }
-
- if (media.some((m) => m.id === candidate.id)) continue;
-
- if (episodes !== 0)
- media.push({
- id: candidate.id,
- title: candidate.title,
- episodes,
- seen,
- nextAiringEpisode: candidate.nextAiringEpisode,
- startDate: candidate.startDate,
- coverImage: candidate.coverImage
- });
- }
-
- return media;
+ const candidates = [];
+ let page = 1;
+ let currentPage = await prequelsPage(page, anilistAuthorisation, year, season);
+
+ for (const candidate of currentPage.data.Page.media) candidates.push(candidate);
+
+ while (currentPage['data']['Page']['pageInfo']['hasNextPage']) {
+ for (const candidate of currentPage.data.Page.media) candidates.push(candidate);
+
+ page += 1;
+ currentPage = await prequelsPage(page, anilistAuthorisation, year, season);
+ }
+
+ for (const candidate of currentPage.data.Page.media) candidates.push(candidate);
+
+ const media: MediaPrequel[] = [];
+
+ for (const candidate of candidates) {
+ let episodes = 0;
+ let seen = 0;
+
+ for (const relation of candidate.relations.edges) {
+ if (relation.relationType === 'PREQUEL' || relation.relationType === 'PARENT') {
+ if (
+ relation.node.mediaListEntry === null ||
+ relation.node.mediaListEntry.status !== 'COMPLETED'
+ ) {
+ episodes += relation.node.episodes;
+
+ if (relation.node.mediaListEntry !== null)
+ seen += relation.node.mediaListEntry.progress || 0;
+ }
+ }
+ }
+
+ if (media.some((m) => m.id === candidate.id)) continue;
+
+ if (episodes !== 0)
+ media.push({
+ id: candidate.id,
+ title: candidate.title,
+ episodes,
+ seen,
+ nextAiringEpisode: candidate.nextAiringEpisode,
+ startDate: candidate.startDate,
+ coverImage: candidate.coverImage
+ });
+ }
+
+ return media;
};
diff --git a/src/lib/Data/AniList/schedule.ts b/src/lib/Data/AniList/schedule.ts
index 0477e4d8..18f02579 100644
--- a/src/lib/Data/AniList/schedule.ts
+++ b/src/lib/Data/AniList/schedule.ts
@@ -1,44 +1,44 @@
import type { Media, MediaTitle } from './media';
interface SchedulePage {
- data: {
- Page: {
- media: {
- title: MediaTitle;
- synonyms: string[];
- id: number;
- idMal: number;
- episodes: number;
- nextAiringEpisode?: {
- episode: number;
- airingAt?: number;
- };
- coverImage: {
- extraLarge: string;
- medium: string;
- };
- }[];
- pageInfo: {
- hasNextPage: boolean;
- };
- };
- };
+ data: {
+ Page: {
+ media: {
+ title: MediaTitle;
+ synonyms: string[];
+ id: number;
+ idMal: number;
+ episodes: number;
+ nextAiringEpisode?: {
+ episode: number;
+ airingAt?: number;
+ };
+ coverImage: {
+ extraLarge: string;
+ medium: string;
+ };
+ }[];
+ pageInfo: {
+ hasNextPage: boolean;
+ };
+ };
+ };
}
const schedulePage = async (
- page: number,
- year: number,
- season: 'WINTER' | 'SPRING' | 'SUMMER' | 'FALL'
+ page: number,
+ year: number,
+ season: 'WINTER' | 'SPRING' | 'SUMMER' | 'FALL'
): Promise<SchedulePage> =>
- await (
- await fetch('https://graphql.anilist.co', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- Accept: 'application/json'
- },
- body: JSON.stringify({
- query: `{
+ await (
+ await fetch('https://graphql.anilist.co', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ Accept: 'application/json'
+ },
+ body: JSON.stringify({
+ query: `{
Page(page: ${page}) {
pageInfo {
hasNextPage
@@ -51,56 +51,56 @@ const schedulePage = async (
}
}
}`
- })
- })
- ).json();
+ })
+ })
+ ).json();
type Season = 'WINTER' | 'SPRING' | 'SUMMER' | 'FALL';
export const scheduleMediaListCollection = async (
- year: number,
- season: Season,
- includeLastSeason = false
+ year: number,
+ season: Season,
+ includeLastSeason = false
) => {
- const scheduledMedia = [];
- let page = 1;
- let currentPage = await schedulePage(page, year, season);
+ const scheduledMedia = [];
+ let page = 1;
+ let currentPage = await schedulePage(page, year, season);
- for (const candidate of currentPage.data.Page.media) scheduledMedia.push(candidate);
+ for (const candidate of currentPage.data.Page.media) scheduledMedia.push(candidate);
- while (currentPage['data']['Page']['pageInfo']['hasNextPage']) {
- for (const candidate of currentPage.data.Page.media) scheduledMedia.push(candidate);
+ while (currentPage['data']['Page']['pageInfo']['hasNextPage']) {
+ for (const candidate of currentPage.data.Page.media) scheduledMedia.push(candidate);
- page += 1;
- currentPage = await schedulePage(page, year, season);
- }
+ page += 1;
+ currentPage = await schedulePage(page, year, season);
+ }
- for (const candidate of currentPage.data.Page.media) scheduledMedia.push(candidate);
+ for (const candidate of currentPage.data.Page.media) scheduledMedia.push(candidate);
- if (includeLastSeason) {
- const lastSeason = {
- WINTER: 'FALL',
- SPRING: 'WINTER',
- SUMMER: 'SPRING',
- FALL: 'SUMMER'
- }[season];
+ if (includeLastSeason) {
+ const lastSeason = {
+ WINTER: 'FALL',
+ SPRING: 'WINTER',
+ SUMMER: 'SPRING',
+ FALL: 'SUMMER'
+ }[season];
- const lastSeasonYear = season === 'WINTER' ? year - 1 : year;
+ const lastSeasonYear = season === 'WINTER' ? year - 1 : year;
- let page = 1;
- let currentPage = await schedulePage(page, lastSeasonYear, lastSeason as Season);
+ let page = 1;
+ let currentPage = await schedulePage(page, lastSeasonYear, lastSeason as Season);
- for (const candidate of currentPage.data.Page.media) scheduledMedia.push(candidate);
+ for (const candidate of currentPage.data.Page.media) scheduledMedia.push(candidate);
- while (currentPage['data']['Page']['pageInfo']['hasNextPage']) {
- for (const candidate of currentPage.data.Page.media) scheduledMedia.push(candidate);
+ while (currentPage['data']['Page']['pageInfo']['hasNextPage']) {
+ for (const candidate of currentPage.data.Page.media) scheduledMedia.push(candidate);
- page += 1;
- currentPage = await schedulePage(page, lastSeasonYear, lastSeason as Season);
- }
+ page += 1;
+ currentPage = await schedulePage(page, lastSeasonYear, lastSeason as Season);
+ }
- for (const candidate of currentPage.data.Page.media) scheduledMedia.push(candidate);
- }
+ for (const candidate of currentPage.data.Page.media) scheduledMedia.push(candidate);
+ }
- return scheduledMedia as Partial<Media[]>;
+ return scheduledMedia as Partial<Media[]>;
};
diff --git a/src/lib/Data/AniList/user.ts b/src/lib/Data/AniList/user.ts
index 40097c13..5b9390db 100644
--- a/src/lib/Data/AniList/user.ts
+++ b/src/lib/Data/AniList/user.ts
@@ -1,61 +1,61 @@
export interface User {
- name: string;
- id: number;
- statistics: {
- anime: {
- count: number;
- meanScore: number;
- minutesWatched: number;
- episodesWatched: number;
- };
- manga: {
- count: number;
- meanScore: number;
- chaptersRead: number;
- volumesRead: number;
- };
- };
- avatar: {
- large: string;
- medium: string;
- };
- bannerImage: string;
+ name: string;
+ id: number;
+ statistics: {
+ anime: {
+ count: number;
+ meanScore: number;
+ minutesWatched: number;
+ episodesWatched: number;
+ };
+ manga: {
+ count: number;
+ meanScore: number;
+ chaptersRead: number;
+ volumesRead: number;
+ };
+ };
+ avatar: {
+ large: string;
+ medium: string;
+ };
+ bannerImage: string;
}
export interface FullUser {
- id: number;
- name: string;
- avatar: {
- large: string;
- medium: string;
- };
- bans: JSON;
- bannerImage: string;
- siteUrl: string;
- donatorTier: number;
- donatorBadge: string;
- moderatorRoles: string[];
- createAt: number;
- updatedAt: number;
- previousNames: {
- name: string;
- createdAt: number;
- updatedAt: string;
- }[];
- about: string;
+ id: number;
+ name: string;
+ avatar: {
+ large: string;
+ medium: string;
+ };
+ bans: JSON;
+ bannerImage: string;
+ siteUrl: string;
+ donatorTier: number;
+ donatorBadge: string;
+ moderatorRoles: string[];
+ createAt: number;
+ updatedAt: number;
+ previousNames: {
+ name: string;
+ createdAt: number;
+ updatedAt: string;
+ }[];
+ about: string;
}
export const user = async (username: string, id = false): Promise<User> =>
- (
- await (
- await fetch('https://graphql.anilist.co', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- Accept: 'application/json'
- },
- body: JSON.stringify({
- query: `{ User(${id ? `id: ${username}` : `name: "${username}"`}) {
+ (
+ await (
+ await fetch('https://graphql.anilist.co', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ Accept: 'application/json'
+ },
+ body: JSON.stringify({
+ query: `{ User(${id ? `id: ${username}` : `name: "${username}"`}) {
name id bannerImage avatar { large medium } statistics {
anime {
count meanScore minutesWatched episodesWatched
@@ -65,29 +65,29 @@ export const user = async (username: string, id = false): Promise<User> =>
}
}
} }`
- })
- })
- ).json()
- )['data']['User'];
+ })
+ })
+ ).json()
+ )['data']['User'];
export const dumpUser = async (username: string): Promise<FullUser> =>
- (
- await (
- await fetch('https://graphql.anilist.co', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- Accept: 'application/json'
- },
- body: JSON.stringify({
- query: `{ User(name: "${username}") {
+ (
+ await (
+ await fetch('https://graphql.anilist.co', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ Accept: 'application/json'
+ },
+ body: JSON.stringify({
+ query: `{ User(name: "${username}") {
id name about
avatar { large medium }
bannerImage bans siteUrl donatorTier donatorBadge moderatorRoles
createdAt updatedAt
previousNames { name createdAt updatedAt }
} }`
- })
- })
- ).json()
- )['data']['User'];
+ })
+ })
+ ).json()
+ )['data']['User'];
diff --git a/src/lib/Data/AniList/wrapped.ts b/src/lib/Data/AniList/wrapped.ts
index 72c81cd8..f9302b90 100644
--- a/src/lib/Data/AniList/wrapped.ts
+++ b/src/lib/Data/AniList/wrapped.ts
@@ -2,72 +2,72 @@ import type { AniListAuthorisation, UserIdentity } from './identity';
import type { Media } from './media';
export enum SortOptions {
- SCORE,
- MINUTES_WATCHED,
- COUNT
+ SCORE,
+ MINUTES_WATCHED,
+ COUNT
}
export interface WrappedMediaFormat {
- startYears: {
- startYear: number;
- minutesWatched: number;
- count: number;
- }[];
- genres: {
- meanScore: number;
- minutesWatched: number;
- chaptersRead: number;
- genre: string;
- mediaIds: number[];
- }[];
- tags: {
- meanScore: number;
- minutesWatched: number;
- chaptersRead: number;
- tag: {
- name: string;
- };
- mediaIds: number[];
- }[];
+ startYears: {
+ startYear: number;
+ minutesWatched: number;
+ count: number;
+ }[];
+ genres: {
+ meanScore: number;
+ minutesWatched: number;
+ chaptersRead: number;
+ genre: string;
+ mediaIds: number[];
+ }[];
+ tags: {
+ meanScore: number;
+ minutesWatched: number;
+ chaptersRead: number;
+ tag: {
+ name: string;
+ };
+ mediaIds: number[];
+ }[];
}
export interface Wrapped {
- statistics: {
- anime: WrappedMediaFormat;
- manga: WrappedMediaFormat;
- };
- activities: {
- statusCount: number;
- messageCount: number;
- };
- avatar: {
- large: string;
- };
+ statistics: {
+ anime: WrappedMediaFormat;
+ manga: WrappedMediaFormat;
+ };
+ activities: {
+ statusCount: number;
+ messageCount: number;
+ };
+ avatar: {
+ large: string;
+ };
}
const profileActivities = async (
- user: AniListAuthorisation,
- identity: UserIdentity,
- date = new Date()
+ user: AniListAuthorisation,
+ identity: UserIdentity,
+ date = new Date()
) => {
- const now = date.getTime();
- const get = async (page: number) =>
- await (
- await fetch('https://graphql.anilist.co', {
- method: 'POST',
- headers: {
- Authorization: `${user.tokenType} ${user.accessToken}`,
- 'Content-Type': 'application/json',
- Accept: 'application/json'
- },
- body: JSON.stringify({
- query: `{
+ const now = date.getTime();
+ const get = async (page: number) =>
+ await (
+ await fetch('https://graphql.anilist.co', {
+ method: 'POST',
+ headers: {
+ Authorization: `${user.tokenType} ${user.accessToken}`,
+ 'Content-Type': 'application/json',
+ Accept: 'application/json'
+ },
+ body: JSON.stringify({
+ query: `{
Page(page: ${page}) {
activities(userId: ${identity.id}, type_in: [ TEXT, MESSAGE ], createdAt_greater: ${Math.floor(
- new Date(date.getFullYear(), 0, 1).getTime() / 1000
- )}, createdAt_lesser: ${Math.floor(
- new Date(date.getFullYear(), 7, 1).getTime() / 1000
- )}) {
+ new Date(date.getFullYear(), 0, 1).getTime() / 1000
+ )}, createdAt_lesser: ${Math.floor(
+ new Date(date.getFullYear(), 7, 1).getTime() / 1000
+ )}) {
... on TextActivity {
type
createdAt
@@ -82,66 +82,66 @@ const profileActivities = async (
}
}
}`
- })
- })
- ).json();
- const pages = [];
- let page = 1;
- let response = await get(page);
- const beginningOfYear = new Date(now).setMonth(0, 1) / 1000;
-
- pages.push(response['data']['Page']['activities']);
-
- while (response['data']['Page']['pageInfo']['hasNextPage']) {
- page += 1;
- response = await get(page);
-
- pages.push(response['data']['Page']['activities']);
- }
-
- return {
- statusCount: pages
- .flat()
- .filter(
- (activity) =>
- activity.type == 'TEXT' &&
- activity.createdAt > beginningOfYear &&
- activity.createdAt < now / 1000
- ).length,
- messageCount: pages
- .flat()
- .filter(
- (activity) =>
- activity.type == 'MESSAGE' &&
- activity.createdAt > beginningOfYear &&
- activity.createdAt < now / 1000
- ).length
- };
+ })
+ })
+ ).json();
+ const pages = [];
+ let page = 1;
+ let response = await get(page);
+ const beginningOfYear = new Date(now).setMonth(0, 1) / 1000;
+
+ pages.push(response['data']['Page']['activities']);
+
+ while (response['data']['Page']['pageInfo']['hasNextPage']) {
+ page += 1;
+ response = await get(page);
+
+ pages.push(response['data']['Page']['activities']);
+ }
+
+ return {
+ statusCount: pages
+ .flat()
+ .filter(
+ (activity) =>
+ activity.type == 'TEXT' &&
+ activity.createdAt > beginningOfYear &&
+ activity.createdAt < now / 1000
+ ).length,
+ messageCount: pages
+ .flat()
+ .filter(
+ (activity) =>
+ activity.type == 'MESSAGE' &&
+ activity.createdAt > beginningOfYear &&
+ activity.createdAt < now / 1000
+ ).length
+ };
};
export const wrapped = async (
- anilistAuthorisation: AniListAuthorisation | undefined,
- identity: UserIdentity,
- year = new Date().getFullYear(),
- skipActivities = false
+ anilistAuthorisation: AniListAuthorisation | undefined,
+ identity: UserIdentity,
+ year = new Date().getFullYear(),
+ skipActivities = false
): Promise<Wrapped> => {
- const headers: { [key: string]: string } = {
- 'Content-Type': 'application/json',
- Accept: 'application/json'
- };
-
- if (anilistAuthorisation) {
- headers[
- 'Authorization'
- ] = `${anilistAuthorisation.tokenType} ${anilistAuthorisation.accessToken}`;
- }
-
- const wrappedResponse = await (
- await fetch('https://graphql.anilist.co', {
- method: 'POST',
- headers,
- body: JSON.stringify({
- query: `{
+ const headers: { [key: string]: string } = {
+ 'Content-Type': 'application/json',
+ Accept: 'application/json'
+ };
+
+ if (anilistAuthorisation) {
+ headers[
+ 'Authorization'
+ ] = `${anilistAuthorisation.tokenType} ${anilistAuthorisation.accessToken}`;
+ }
+
+ const wrappedResponse = await (
+ await fetch('https://graphql.anilist.co', {
+ method: 'POST',
+ headers,
+ body: JSON.stringify({
+ query: `{
User(name: "${identity.name}") {
avatar { large }
statistics {
@@ -158,144 +158,144 @@ export const wrapped = async (
}
}
}`
- })
- })
- ).json();
- let statusCountActivities = 0;
- let messageCountActivities = 0;
-
- if (!skipActivities && anilistAuthorisation) {
- const { statusCount, messageCount } = await profileActivities(
- anilistAuthorisation,
- identity,
- new Date(year, 11, 31)
- );
-
- statusCountActivities = statusCount;
- messageCountActivities = messageCount;
- }
-
- return {
- statistics: wrappedResponse['data']['User']['statistics'],
- activities: {
- statusCount: statusCountActivities,
- messageCount: messageCountActivities
- },
- avatar: wrappedResponse['data']['User']['avatar']
- };
+ })
+ })
+ ).json();
+ let statusCountActivities = 0;
+ let messageCountActivities = 0;
+
+ if (!skipActivities && anilistAuthorisation) {
+ const { statusCount, messageCount } = await profileActivities(
+ anilistAuthorisation,
+ identity,
+ new Date(year, 11, 31)
+ );
+
+ statusCountActivities = statusCount;
+ messageCountActivities = messageCount;
+ }
+
+ return {
+ statistics: wrappedResponse['data']['User']['statistics'],
+ activities: {
+ statusCount: statusCountActivities,
+ messageCount: messageCountActivities
+ },
+ avatar: wrappedResponse['data']['User']['avatar']
+ };
};
export interface TopMedia {
- genres: {
- genre: string;
- averageScore: number;
- }[];
- tags: {
- tag: string;
- averageScore: number;
- }[];
- topGenreMedia: Media;
- topTagMedia: Media;
+ genres: {
+ genre: string;
+ averageScore: number;
+ }[];
+ tags: {
+ tag: string;
+ averageScore: number;
+ }[];
+ topGenreMedia: Media;
+ topTagMedia: Media;
}
export const tops = (
- media: Media[],
- amount: number,
- sortMode: SortOptions,
- excludedKeywords: string[] = []
+ media: Media[],
+ amount: number,
+ sortMode: SortOptions,
+ excludedKeywords: string[] = []
): TopMedia => {
- const genresMap: {
- [genre: string]: { totalScore: number; count: number; minutesWatched: number };
- } = {};
- const tagsMap: { [tag: string]: { totalScore: number; count: number; minutesWatched: number } } =
- {};
-
- media.forEach((m) => {
- if (m.mediaListEntry && m.mediaListEntry.score) {
- m.genres.forEach((genre) => {
- if (!genresMap[genre]) genresMap[genre] = { totalScore: 0, count: 0, minutesWatched: 0 };
-
- const score = m.mediaListEntry?.score;
-
- if (score) {
- genresMap[genre].totalScore += score;
- genresMap[genre].minutesWatched += m.duration;
- genresMap[genre].count++;
- }
- });
-
- m.tags.forEach((tag) => {
- if (tag.rank < 50) return;
-
- if (!tagsMap[tag.name]) tagsMap[tag.name] = { totalScore: 0, count: 0, minutesWatched: 0 };
-
- const score = m.mediaListEntry?.score;
-
- if (score) {
- tagsMap[tag.name].totalScore += score;
- tagsMap[tag.name].minutesWatched += m.duration;
- tagsMap[tag.name].count++;
- }
- });
- }
- });
-
- let genres = Object.keys(genresMap)
- .filter((genre) => !excludedKeywords.some((keyword) => genre.includes(keyword)))
- .map((genre) => ({
- genre,
- averageScore: Math.round(genresMap[genre].totalScore / genresMap[genre].count)
- }));
- let tags = Object.keys(tagsMap)
- .filter((genre) => !excludedKeywords.some((keyword) => genre.includes(keyword)))
- .map((tag) => ({
- tag,
- averageScore: Math.round(tagsMap[tag].totalScore / tagsMap[tag].count)
- }));
-
- switch (sortMode) {
- case SortOptions.SCORE:
- genres = genres.sort((a, b) => b.averageScore - a.averageScore);
- tags = tags.sort((a, b) => b.averageScore - a.averageScore);
-
- break;
- case SortOptions.MINUTES_WATCHED:
- genres = genres.sort(
- (a, b) => genresMap[b.genre].minutesWatched - genresMap[a.genre].minutesWatched
- );
- tags = tags.sort((a, b) => tagsMap[b.tag].minutesWatched - tagsMap[a.tag].minutesWatched);
-
- break;
- case SortOptions.COUNT:
- genres = genres.sort((a, b) => genresMap[b.genre].count - genresMap[a.genre].count);
- tags = tags.sort((a, b) => tagsMap[b.tag].count - tagsMap[a.tag].count);
-
- break;
- }
-
- genres = genres.slice(0, amount);
- tags = tags.slice(0, amount);
-
- let topGenreMedia;
-
- try {
- topGenreMedia = media.find((m) => m.genres.includes(genres[0].genre)) || media[0];
- } catch {
- topGenreMedia = media[0];
- }
-
- let topTagMedia;
-
- try {
- topTagMedia = media.find((m) => m.tags.some((tag) => tag.name === tags[0].tag)) || media[0];
- } catch {
- topTagMedia = media[0];
- }
-
- return {
- genres,
- tags,
- topGenreMedia,
- topTagMedia
- };
+ const genresMap: {
+ [genre: string]: { totalScore: number; count: number; minutesWatched: number };
+ } = {};
+ const tagsMap: { [tag: string]: { totalScore: number; count: number; minutesWatched: number } } =
+ {};
+
+ media.forEach((m) => {
+ if (m.mediaListEntry && m.mediaListEntry.score) {
+ m.genres.forEach((genre) => {
+ if (!genresMap[genre]) genresMap[genre] = { totalScore: 0, count: 0, minutesWatched: 0 };
+
+ const score = m.mediaListEntry?.score;
+
+ if (score) {
+ genresMap[genre].totalScore += score;
+ genresMap[genre].minutesWatched += m.duration;
+ genresMap[genre].count++;
+ }
+ });
+
+ m.tags.forEach((tag) => {
+ if (tag.rank < 50) return;
+
+ if (!tagsMap[tag.name]) tagsMap[tag.name] = { totalScore: 0, count: 0, minutesWatched: 0 };
+
+ const score = m.mediaListEntry?.score;
+
+ if (score) {
+ tagsMap[tag.name].totalScore += score;
+ tagsMap[tag.name].minutesWatched += m.duration;
+ tagsMap[tag.name].count++;
+ }
+ });
+ }
+ });
+
+ let genres = Object.keys(genresMap)
+ .filter((genre) => !excludedKeywords.some((keyword) => genre.includes(keyword)))
+ .map((genre) => ({
+ genre,
+ averageScore: Math.round(genresMap[genre].totalScore / genresMap[genre].count)
+ }));
+ let tags = Object.keys(tagsMap)
+ .filter((genre) => !excludedKeywords.some((keyword) => genre.includes(keyword)))
+ .map((tag) => ({
+ tag,
+ averageScore: Math.round(tagsMap[tag].totalScore / tagsMap[tag].count)
+ }));
+
+ switch (sortMode) {
+ case SortOptions.SCORE:
+ genres = genres.sort((a, b) => b.averageScore - a.averageScore);
+ tags = tags.sort((a, b) => b.averageScore - a.averageScore);
+
+ break;
+ case SortOptions.MINUTES_WATCHED:
+ genres = genres.sort(
+ (a, b) => genresMap[b.genre].minutesWatched - genresMap[a.genre].minutesWatched
+ );
+ tags = tags.sort((a, b) => tagsMap[b.tag].minutesWatched - tagsMap[a.tag].minutesWatched);
+
+ break;
+ case SortOptions.COUNT:
+ genres = genres.sort((a, b) => genresMap[b.genre].count - genresMap[a.genre].count);
+ tags = tags.sort((a, b) => tagsMap[b.tag].count - tagsMap[a.tag].count);
+
+ break;
+ }
+
+ genres = genres.slice(0, amount);
+ tags = tags.slice(0, amount);
+
+ let topGenreMedia;
+
+ try {
+ topGenreMedia = media.find((m) => m.genres.includes(genres[0].genre)) || media[0];
+ } catch {
+ topGenreMedia = media[0];
+ }
+
+ let topTagMedia;
+
+ try {
+ topTagMedia = media.find((m) => m.tags.some((tag) => tag.name === tags[0].tag)) || media[0];
+ } catch {
+ topTagMedia = media[0];
+ }
+
+ return {
+ genres,
+ tags,
+ topGenreMedia,
+ topTagMedia
+ };
};