anilist integrated to my list
This commit is contained in:
214
src/api/anilist/anilist.service.ts
Normal file
214
src/api/anilist/anilist.service.ts
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user