anilist integrated to my list

This commit is contained in:
2025-12-06 17:18:03 +01:00
parent e5ec8aa7e5
commit 822a9f83cf
13 changed files with 774 additions and 257 deletions

View File

@@ -0,0 +1,214 @@
import { queryOne } from '../../shared/database';
const USER_DB = 'userdata';
export async function getUserAniList(appUserId: number) {
const sql = `
SELECT access_token, anilist_user_id
FROM UserIntegration
WHERE user_id = ? AND platform = 'AniList';
`;
const integration = await queryOne(sql, [appUserId], USER_DB) as any;
if (!integration) return [];
const { access_token, anilist_user_id } = integration;
const query = `
query ($userId: Int) {
anime: MediaListCollection(userId: $userId, type: ANIME) {
lists {
entries {
mediaId
status
progress
score
}
}
}
manga: MediaListCollection(userId: $userId, type: MANGA) {
lists {
entries {
mediaId
status
progress
score
}
}
}
}
`;
const res = await fetch('https://graphql.anilist.co', {
method: 'POST',
headers: {
'Authorization': `Bearer ${access_token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
query,
variables: { userId: anilist_user_id }
}),
});
const json = await res.json();
const normalize = (lists: any[], type: 'ANIME' | 'MANGA') => {
const result: any[] = [];
for (const list of lists || []) {
for (const entry of list.entries || []) {
result.push({
user_id: appUserId,
entry_id: entry.mediaId,
source: 'anilist',
entry_type: type,
status: entry.status,
progress: entry.progress || 0,
score: entry.score || null,
});
}
}
return result;
};
return [
...normalize(json?.data?.anime?.lists, 'ANIME'),
...normalize(json?.data?.manga?.lists, 'MANGA')
];
}
export async function updateAniListEntry(token: string, params: {
mediaId: number | string;
status?: string | null;
progress?: number | null;
score?: number | null;
}) {
const mutation = `
mutation ($mediaId: Int, $status: MediaListStatus, $progress: Int, $score: Float) {
SaveMediaListEntry (
mediaId: $mediaId,
status: $status,
progress: $progress,
score: $score
) {
id
status
progress
score
}
}
`;
const variables: any = {
mediaId: Number(params.mediaId),
};
if (params.status != null) variables.status = params.status;
if (params.progress != null) variables.progress = params.progress;
if (params.score != null) variables.score = params.score;
const res = await fetch('https://graphql.anilist.co', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ query: mutation, variables }),
});
const json = await res.json();
if (!res.ok || json?.errors?.length) {
throw new Error("AniList update failed");
}
return json.data?.SaveMediaListEntry || null;
}
export async function deleteAniListEntry(token: string, mediaId: number) {
const query = `
query ($mediaId: Int) {
MediaList(mediaId: $mediaId) {
id
}
}
`;
const qRes = await fetch('https://graphql.anilist.co', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ query, variables: { mediaId } }),
});
const qJson = await qRes.json();
const listEntryId = qJson?.data?.MediaList?.id;
if (!listEntryId) {
throw new Error("Entry not found or unauthorized to delete.");
}
const mutation = `
mutation ($id: Int) {
DeleteMediaListEntry(id: $id) {
deleted
}
}
`;
const mRes = await fetch('https://graphql.anilist.co', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: mutation,
variables: { id: listEntryId }
}),
});
const mJson = await mRes.json();
if (mJson?.errors?.length) {
throw new Error("Error eliminando entrada en AniList");
}
return true;
}
export async function getSingleAniListEntry(
token: string,
mediaId: number,
type: 'ANIME' | 'MANGA'
) {
const query = `
query ($mediaId: Int, $type: MediaType) {
MediaList(mediaId: $mediaId, type: $type) {
status
progress
score
}
}
`;
const res = await fetch('https://graphql.anilist.co', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
query,
variables: { mediaId, type }
})
});
const json = await res.json();
return json?.data?.MediaList || null;
}